1use 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)]
59pub 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
69pub struct PState {
76 path: &'static str,
77}
78
79impl PState {
80 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 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 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 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 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 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 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 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 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 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 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
208fn write_value<V: Display>(path: &str, value: V) -> io::Result<()> {
210 write!(File::create(path)?, "{}", value)
211}