intel_pstate/
lib.rs

1// Copyright 2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MIT
3
4//! Crate for fetching and modifying the intel_pstate kernel parameters.
5//!
6//! # Example
7//!
8//! ```rust,no_run
9//! use intel_pstate::{PState, PStateError};
10//!
11//! fn main() -> Result<(), PStateError> {
12//!     let pstate = PState::new()?;
13//!
14//!     let _ = pstate.set_hwp_dynamic_boost(true);
15//!     pstate.set_min_perf_pct(50)?;
16//!     pstate.set_max_perf_pct(100)?;
17//!     pstate.set_no_turbo(false)?;
18//!
19//!     Ok(())
20//! }
21//! ```
22
23use derive_setters::Setters;
24use smart_default::SmartDefault;
25use thiserror::Error;
26
27use std::{
28    fmt::Display,
29    fs::{self, File},
30    io::{self, Write},
31    path::Path,
32    str::FromStr,
33};
34
35const HWP_DYNAMIC_BOOST: &str = "hwp_dynamic_boost";
36const MAX_PERF_PCT: &str = "max_perf_pct";
37const MIN_PERF_PCT: &str = "min_perf_pct";
38const NO_TURBO: &str = "no_turbo";
39
40#[derive(Debug, Error)]
41pub enum PStateError {
42    #[error("failed to get {} pstate value", src)]
43    GetValue {
44        src: &'static str,
45        source: io::Error,
46    },
47
48    #[error("intel_pstate directory not found")]
49    NotFound,
50
51    #[error("failed to set {} pstate value", src)]
52    SetValue {
53        src: &'static str,
54        source: io::Error,
55    },
56}
57
58#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Setters, SmartDefault)]
59/// A set of pstate values that was retrieved, or is to be set.
60pub struct PStateValues {
61    #[setters(strip_option)]
62    pub hwp_dynamic_boost: Option<bool>,
63    pub min_perf_pct: u8,
64    #[default(100)]
65    pub max_perf_pct: u8,
66    pub no_turbo: bool,
67}
68
69/// Handle for fetching and modifying Intel PState kernel parameters.
70///
71/// # Note
72///
73/// - Currently, ony Linux is supported.
74/// - Setting parameters will require root permissions.
75pub struct PState {
76    path: &'static str,
77}
78
79impl PState {
80    /// Attempt to fetch a handle to the Intel PState sysfs kernel instance.
81    pub fn new() -> Result<PState, PStateError> {
82        let path = "/sys/devices/system/cpu/intel_pstate/";
83        if Path::new(path).is_dir() {
84            Ok(PState { path })
85        } else {
86            Err(PStateError::NotFound)
87        }
88    }
89
90    fn file(&self, file: &str) -> String {
91        [self.path, file].concat()
92    }
93
94    /// Get the status of HWP dynamic boost, if it is available.
95    pub fn hwp_dynamic_boost(&self) -> Result<Option<bool>, PStateError> {
96        let file = self.file(HWP_DYNAMIC_BOOST);
97
98        if Path::new(&*file).exists() {
99            return parse_file::<u8>(&file)
100                .map(|v| Some(v == 1))
101                .map_err(|source| PStateError::GetValue {
102                    src: HWP_DYNAMIC_BOOST,
103                    source,
104                });
105        }
106
107        Ok(None)
108    }
109
110    /// Set the HWP dynamic boost status.
111    pub fn set_hwp_dynamic_boost(&self, boost: bool) -> Result<(), PStateError> {
112        write_value(&self.file(HWP_DYNAMIC_BOOST), if boost { "1" } else { "0" }).map_err(
113            |source| PStateError::SetValue {
114                src: HWP_DYNAMIC_BOOST,
115                source,
116            },
117        )
118    }
119
120    /// Get the minimum performance percent.
121    pub fn min_perf_pct(&self) -> Result<u8, PStateError> {
122        parse_file(&self.file(MIN_PERF_PCT)).map_err(|source| PStateError::GetValue {
123            src: MIN_PERF_PCT,
124            source,
125        })
126    }
127
128    /// Set the minimum performance percent.
129    pub fn set_min_perf_pct(&self, min: u8) -> Result<(), PStateError> {
130        write_value(&self.file(MIN_PERF_PCT), min).map_err(|source| PStateError::SetValue {
131            src: MIN_PERF_PCT,
132            source,
133        })
134    }
135
136    /// Get the maximum performance percent.
137    pub fn max_perf_pct(&self) -> Result<u8, PStateError> {
138        parse_file(&self.file(MAX_PERF_PCT)).map_err(|source| PStateError::GetValue {
139            src: MAX_PERF_PCT,
140            source,
141        })
142    }
143
144    /// Set the maximum performance percent.
145    pub fn set_max_perf_pct(&self, max: u8) -> Result<(), PStateError> {
146        write_value(&self.file(MAX_PERF_PCT), max).map_err(|source| PStateError::SetValue {
147            src: MAX_PERF_PCT,
148            source,
149        })
150    }
151
152    /// If true, this signifies that turbo is disabled.
153    pub fn no_turbo(&self) -> Result<bool, PStateError> {
154        let value =
155            parse_file::<u8>(&self.file(NO_TURBO)).map_err(|source| PStateError::GetValue {
156                src: NO_TURBO,
157                source,
158            })?;
159        Ok(value > 0)
160    }
161
162    /// Set the no_turbo value; `true` will disable turbo.
163    pub fn set_no_turbo(&self, no_turbo: bool) -> Result<(), PStateError> {
164        write_value(&self.file(NO_TURBO), if no_turbo { "1" } else { "0" }).map_err(|source| {
165            PStateError::SetValue {
166                src: NO_TURBO,
167                source,
168            }
169        })
170    }
171
172    /// Get current PState values.
173    pub fn values(&self) -> Result<PStateValues, PStateError> {
174        let values = PStateValues {
175            min_perf_pct: self.min_perf_pct()?,
176            max_perf_pct: self.max_perf_pct()?,
177            no_turbo: self.no_turbo()?,
178            hwp_dynamic_boost: self.hwp_dynamic_boost()?,
179        };
180
181        Ok(values)
182    }
183
184    /// Set all values in the given config.
185    pub fn set_values(&self, values: PStateValues) -> Result<(), PStateError> {
186        if let Some(boost) = values.hwp_dynamic_boost {
187            let _ = self.set_hwp_dynamic_boost(boost);
188        }
189
190        let result1 = self.set_min_perf_pct(values.min_perf_pct);
191        let result2 = self.set_max_perf_pct(values.max_perf_pct);
192        let result3 = self.set_no_turbo(values.no_turbo);
193
194        result1.and(result2).and(result3)
195    }
196}
197
198fn parse_file<F: FromStr>(path: &str) -> io::Result<F>
199where
200    F::Err: Display,
201{
202    fs::read_to_string(path)?
203        .trim()
204        .parse()
205        .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, format!("{}", err)))
206}
207
208/// Write a value that implements `Display` to a file
209fn write_value<V: Display>(path: &str, value: V) -> io::Result<()> {
210    write!(File::create(path)?, "{}", value)
211}