amdgpu_sysfs/gpu_handle/overdrive/
mod.rs1pub mod gcn;
5pub mod rdna;
6
7use crate::{
8 error::{Error, ErrorKind},
9 Result,
10};
11use enum_dispatch::enum_dispatch;
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14use std::{
15 convert::TryFrom,
16 io::Write,
17 str::{FromStr, SplitWhitespace},
18};
19
20#[enum_dispatch]
22pub trait ClocksTable: FromStr {
23 fn write_commands<W: Write>(
25 &self,
26 writer: &mut W,
27 previous_table: &ClocksTableGen,
28 ) -> Result<()>;
29
30 fn get_commands(&self, previous_table: &ClocksTableGen) -> Result<Vec<String>> {
33 let mut buf = Vec::new();
34 self.write_commands(&mut buf, previous_table)?;
35 let raw_commands = String::from_utf8(buf).map_err(|_| {
36 ErrorKind::Unsupported("Generated clocks table commands are not valid UTF-8".into())
37 })?;
38 Ok(raw_commands.lines().map(str::to_owned).collect())
39 }
40
41 fn get_max_sclk_range(&self) -> Option<Range>;
43
44 fn get_min_sclk_range(&self) -> Option<Range>;
46
47 fn get_max_mclk_range(&self) -> Option<Range>;
49
50 fn get_min_mclk_range(&self) -> Option<Range>;
52
53 fn get_max_voltage_range(&self) -> Option<Range>;
55
56 fn get_min_voltage_range(&self) -> Option<Range>;
58
59 fn get_current_voltage_range(&self) -> Option<Range>;
61
62 fn get_max_sclk(&self) -> Option<i32> {
64 self.get_current_sclk_range().max
65 }
66
67 fn get_current_sclk_range(&self) -> Range;
69
70 fn get_current_mclk_range(&self) -> Range;
72
73 fn set_max_sclk(&mut self, clockspeed: i32) -> Result<()> {
75 let range = self.get_max_sclk_range();
76 check_clockspeed_in_range(range, clockspeed)?;
77 self.set_max_sclk_unchecked(clockspeed)
78 }
79
80 fn set_max_sclk_unchecked(&mut self, clockspeed: i32) -> Result<()>;
82
83 fn set_min_sclk(&mut self, clockspeed: i32) -> Result<()> {
85 let range = self.get_min_sclk_range();
86 check_clockspeed_in_range(range, clockspeed)?;
87 self.set_min_sclk_unchecked(clockspeed)
88 }
89
90 fn set_min_sclk_unchecked(&mut self, clockspeed: i32) -> Result<()>;
92
93 fn get_max_mclk(&self) -> Option<i32> {
95 self.get_current_mclk_range().max
96 }
97
98 fn set_max_mclk(&mut self, clockspeed: i32) -> Result<()> {
100 let range = self.get_max_mclk_range();
101 check_clockspeed_in_range(range, clockspeed)?;
102 self.set_max_mclk_unchecked(clockspeed)
103 }
104
105 fn set_max_mclk_unchecked(&mut self, clockspeed: i32) -> Result<()>;
107
108 fn set_min_mclk(&mut self, clockspeed: i32) -> Result<()> {
110 let range = self.get_min_mclk_range();
111 check_clockspeed_in_range(range, clockspeed)?;
112 self.set_min_mclk_unchecked(clockspeed)
113 }
114
115 fn set_min_mclk_unchecked(&mut self, clockspeed: i32) -> Result<()>;
117
118 fn set_max_voltage(&mut self, voltage: i32) -> Result<()> {
120 let range = self.get_max_voltage_range();
121 check_clockspeed_in_range(range, voltage)?;
122 self.set_max_voltage_unchecked(voltage)
123 }
124
125 fn set_max_voltage_unchecked(&mut self, voltage: i32) -> Result<()>;
127
128 fn set_min_voltage(&mut self, voltage: i32) -> Result<()> {
130 let range = self.get_min_voltage_range();
131 check_clockspeed_in_range(range, voltage)?;
132 self.set_min_voltage_unchecked(voltage)
133 }
134
135 fn set_min_voltage_unchecked(&mut self, voltage: i32) -> Result<()>;
137
138 fn get_max_sclk_voltage(&self) -> Option<i32>;
140}
141
142fn check_clockspeed_in_range(range: Option<Range>, clockspeed: i32) -> Result<()> {
143 if let (Some(min), Some(max)) = range.map_or((None, None), |range| (range.min, range.max)) {
144 if (min..=max).contains(&clockspeed) {
145 Ok(())
146 } else {
147 Err(Error::not_allowed(format!(
148 "Given clockspeed {clockspeed} is out of the allowed OD range {min} to {max}"
149 )))
150 }
151 } else {
152 Err(Error::not_allowed(
153 "GPU does not report allowed OD ranges".to_owned(),
154 ))
155 }
156}
157
158#[derive(Debug, Clone)]
163#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
164#[cfg_attr(
165 feature = "serde",
166 serde(tag = "kind", content = "data", rename_all = "snake_case")
167)]
168#[enum_dispatch(ClocksTable)]
169pub enum ClocksTableGen {
170 Gcn(gcn::Table),
172 Rdna(rdna::Table),
174}
175
176impl FromStr for ClocksTableGen {
177 type Err = Error;
178
179 fn from_str(s: &str) -> Result<Self> {
180 if s.contains("VDDC_CURVE") || s.contains("OD_VDDGFX_OFFSET") || {
181 let mut lines = s.lines();
182 lines.next() == Some("OD_SCLK:")
183 && lines.next().is_some_and(|sclk_line| {
184 let sclk_line = sclk_line.to_ascii_lowercase();
185 sclk_line.contains("mhz") && !sclk_line.contains("mv")
186 })
187 } {
188 rdna::Table::from_str(s).map(Self::Rdna)
189 } else {
190 gcn::Table::from_str(s).map(Self::Gcn)
191 }
192 }
193}
194
195fn parse_range_line(line: &str, i: usize) -> Result<(Range, &str)> {
196 let mut split = line.split_whitespace();
197 let name = split
198 .next()
199 .ok_or_else(|| Error::unexpected_eol("range name", i))?
200 .trim_end_matches(':');
201 let min = parse_line_item(&mut split, i, "range minimum", &["mhz", "mv"])?;
202 let max = parse_line_item(&mut split, i, "range maximum", &["mhz", "mv"])?;
203
204 Ok((Range::full(min, max), name))
205}
206
207fn parse_line_item<T>(
209 split: &mut SplitWhitespace,
210 i: usize,
211 item: &str,
212 suffixes: &[&str],
213) -> Result<T>
214where
215 T: FromStr,
216 <T as FromStr>::Err: std::fmt::Display,
217{
218 let text = split
219 .next()
220 .ok_or_else(|| Error::unexpected_eol(item, i))?
221 .to_lowercase();
222 let mut trimmed_text = text.as_str();
223
224 for suffix in suffixes {
225 if cfg!(test) && suffix.chars().any(|ch| ch.is_uppercase()) {
226 panic!("Suffixes must be all lowercase");
227 }
228 trimmed_text = trimmed_text.trim_end_matches(suffix);
229 }
230
231 trimmed_text.parse().map_err(|err| {
232 ErrorKind::ParseError {
233 msg: format!("Could not parse {item} with value {trimmed_text}: {err}"),
234 line: i,
235 }
236 .into()
237 })
238}
239
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
242#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
243pub struct Range {
244 pub min: Option<i32>,
246 pub max: Option<i32>,
248}
249
250impl Range {
251 pub fn full(min: i32, max: i32) -> Self {
253 Self {
254 min: Some(min),
255 max: Some(max),
256 }
257 }
258
259 pub fn min(min: i32) -> Self {
261 Self {
262 min: Some(min),
263 max: None,
264 }
265 }
266
267 pub fn max(max: i32) -> Self {
269 Self {
270 min: None,
271 max: Some(max),
272 }
273 }
274
275 pub const fn empty() -> Self {
277 Self {
278 min: None,
279 max: None,
280 }
281 }
282
283 pub fn into_full(self) -> Option<(i32, i32)> {
285 self.min.zip(self.max)
286 }
287}
288
289impl TryFrom<Range> for (i32, i32) {
290 type Error = ();
291
292 fn try_from(value: Range) -> std::result::Result<Self, Self::Error> {
293 if let (Some(min), Some(max)) = (value.min, value.max) {
294 Ok((min, max))
295 } else {
296 Err(())
297 }
298 }
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
303#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
304pub struct ClocksLevel {
305 pub clockspeed: i32,
307 pub voltage: i32,
309}
310
311impl ClocksLevel {
312 pub fn new(clockspeed: i32, voltage: i32) -> Self {
314 Self {
315 clockspeed,
316 voltage,
317 }
318 }
319}
320
321fn parse_level_line(line: &str, i: usize) -> Result<(ClocksLevel, usize)> {
322 let mut split = line.split_whitespace();
323 let num = parse_line_item(&mut split, i, "level number", &[":"])?;
324 let clockspeed = parse_line_item(&mut split, i, "clockspeed", &["mhz"])?;
325 let voltage = parse_line_item(&mut split, i, "voltage", &["mv"])?;
326
327 Ok((ClocksLevel::new(clockspeed, voltage), num))
328}
329
330fn push_level_line(line: &str, levels: &mut Vec<ClocksLevel>, i: usize) -> Result<()> {
331 let (level, num) = parse_level_line(line, i)?;
332
333 let len = levels.len();
334 if num != len {
335 return Err(ErrorKind::ParseError {
336 msg: format!("Unexpected level num: expected {len}, got {num}"),
337 line: i,
338 }
339 .into());
340 }
341
342 levels.push(level);
343 Ok(())
344}
345
346#[cfg(test)]
347fn arr_commands<const N: usize>(commands: [&str; N]) -> String {
348 let mut output = commands.join("\n");
349 output.push('\n');
350 output
351}
352
353#[cfg(test)]
354mod tests {
355 use std::str::FromStr;
356
357 use insta::assert_yaml_snapshot;
358
359 use crate::gpu_handle::overdrive::ClocksTableGen;
360
361 use super::{check_clockspeed_in_range, parse_level_line, parse_range_line, Range};
362
363 #[macro_export]
364 macro_rules! include_table {
365 ($name:literal) => {
366 include_test_data!(concat!($name, "/pp_od_clk_voltage"))
367 };
368 }
369
370 pub const TABLE_PHOENIX: &str = include_table!("internal-7840u");
371 pub const TABLE_VEGA56: &str = include_table!("vega56");
372
373 #[test]
374 fn parse_range_line_sclk() {
375 let line = "SCLK: 300MHz 2000MHz";
376 let (level, name) = parse_range_line(line, 50).unwrap();
377 assert_eq!(name, "SCLK");
378 assert_eq!(level.min, Some(300));
379 assert_eq!(level.max, Some(2000));
380 }
381
382 #[test]
383 fn parse_level_line_basic() {
384 let line = "0: 300MHz 750mV";
385 let (level, i) = parse_level_line(line, 50).unwrap();
386 assert_eq!(i, 0);
387 assert_eq!(level.clockspeed, 300);
388 assert_eq!(level.voltage, 750);
389 }
390
391 #[test]
392 fn allowed_ranges() {
393 let range = Some(Range::full(300, 1000));
394 check_clockspeed_in_range(range, 300).unwrap();
395 check_clockspeed_in_range(range, 750).unwrap();
396 check_clockspeed_in_range(range, 1000).unwrap();
397 check_clockspeed_in_range(range, 1001).unwrap_err();
398 check_clockspeed_in_range(range, 250).unwrap_err();
399 }
400
401 #[test]
402 fn parse_range_line_voltage_point() {
403 let line = "VDDC_CURVE_SCLK[2]: 800Mhz 2150Mhz";
404 let (range, name) = parse_range_line(line, 0).unwrap();
405 assert_eq!(range, Range::full(800, 2150));
406 assert_eq!(name, "VDDC_CURVE_SCLK[2]");
407 }
408
409 #[test]
410 fn detect_type_phoenix() {
411 let table = ClocksTableGen::from_str(TABLE_PHOENIX).unwrap();
412 assert_yaml_snapshot!(table);
413 }
414
415 #[test]
416 fn detect_type_vega10() {
417 let table = ClocksTableGen::from_str(TABLE_VEGA56).unwrap();
418 assert_yaml_snapshot!(table);
419 }
420}