amdgpu_sysfs/gpu_handle/overdrive/
mod.rs

1//! GPU overdrive (overclocking)
2//!
3//! <https://kernel.org/doc/html/latest/gpu/amdgpu/thermal.html#pp-od-clk-voltage>
4pub 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/// Shared functionality across all table formats.
21#[enum_dispatch]
22pub trait ClocksTable: FromStr {
23    /// Writes commands needed to apply the state that is in the table struct on the GPU.
24    fn write_commands<W: Write>(
25        &self,
26        writer: &mut W,
27        previous_table: &ClocksTableGen,
28    ) -> Result<()>;
29
30    /// Gets the list of commands that will apply the current state of the clocks table.
31    /// `write_commands` should generally be preferred instead.
32    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    /// Gets the core clock range usable at the highest power level.
42    fn get_max_sclk_range(&self) -> Option<Range>;
43
44    /// Gets the core clock range usable at the lowest power level.
45    fn get_min_sclk_range(&self) -> Option<Range>;
46
47    /// Gets the memory clock range usable at the highest power level.
48    fn get_max_mclk_range(&self) -> Option<Range>;
49
50    /// Gets the memory clock range usable at the lowest power level.
51    fn get_min_mclk_range(&self) -> Option<Range>;
52
53    /// Gets the voltage range usable at the highest power level.
54    fn get_max_voltage_range(&self) -> Option<Range>;
55
56    /// Gets the voltage range usable at the lowest power level.
57    fn get_min_voltage_range(&self) -> Option<Range>;
58
59    /// Gets the current voltage range.
60    fn get_current_voltage_range(&self) -> Option<Range>;
61
62    /// Gets the current maximum core clock.
63    fn get_max_sclk(&self) -> Option<i32> {
64        self.get_current_sclk_range().max
65    }
66
67    /// Gets the current range of values for core clocks.
68    fn get_current_sclk_range(&self) -> Range;
69
70    /// Gets the current range of values for memory clocks.
71    fn get_current_mclk_range(&self) -> Range;
72
73    /// Sets the maximum core clock.
74    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    /// Sets the maximum core clock (without checking if it's in the allowed range).
81    fn set_max_sclk_unchecked(&mut self, clockspeed: i32) -> Result<()>;
82
83    /// Sets the minimum core clock.
84    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    /// Sets the minimum core clock (without checking if it's in the allowed range).
91    fn set_min_sclk_unchecked(&mut self, clockspeed: i32) -> Result<()>;
92
93    /// Gets the current maximum memory clock.
94    fn get_max_mclk(&self) -> Option<i32> {
95        self.get_current_mclk_range().max
96    }
97
98    /// Sets the maximum memory clock.
99    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    /// Sets the maximum memory clock (without checking if it's in the allowed range).
106    fn set_max_mclk_unchecked(&mut self, clockspeed: i32) -> Result<()>;
107
108    /// Sets the minimum memory clock.
109    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    /// Sets the minimum memory clock (without checking if it's in the allowed range).
116    fn set_min_mclk_unchecked(&mut self, clockspeed: i32) -> Result<()>;
117
118    /// Sets the voltage to be used at the maximum clockspeed.
119    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    /// Sets the voltage to be used at the maximum clockspeed (without checking if it's in the allowed range).
126    fn set_max_voltage_unchecked(&mut self, voltage: i32) -> Result<()>;
127
128    /// Sets the voltage to be used at the minimum clockspeed.
129    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    /// Sets the voltage to be used at the minimum clockspeed (without checking if it's in the allowed range).
136    fn set_min_voltage_unchecked(&mut self, voltage: i32) -> Result<()>;
137
138    /// Gets the current maximum voltage (used on maximum clockspeed).
139    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/// Representation of clocks and voltage table (`pp_od_clk_voltage`).
159///
160/// NOTE: the variant names are not 100% accurate, they roughly represent what generations of GPUs use each format.
161/// For example, Radeon VII (Vega20) uses the new "RDNA" format.
162#[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    /// Vega10 (and older) format
171    Gcn(gcn::Table),
172    /// Vega20 (and newer) format
173    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
207/// Takes the next item from a split, strips the given suffixes, an parses it to a type
208fn 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/// A range.
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
242#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
243pub struct Range {
244    /// The lower value of a range.
245    pub min: Option<i32>,
246    /// The higher value of a range.
247    pub max: Option<i32>,
248}
249
250impl Range {
251    /// Creates a range with both a minimum and a maximum value.
252    pub fn full(min: i32, max: i32) -> Self {
253        Self {
254            min: Some(min),
255            max: Some(max),
256        }
257    }
258
259    /// Creates a rage with a minimum value only.
260    pub fn min(min: i32) -> Self {
261        Self {
262            min: Some(min),
263            max: None,
264        }
265    }
266
267    /// Creates a rage with a maximum value only.
268    pub fn max(max: i32) -> Self {
269        Self {
270            min: None,
271            max: Some(max),
272        }
273    }
274
275    /// Creates an empty range.
276    pub const fn empty() -> Self {
277        Self {
278            min: None,
279            max: None,
280        }
281    }
282
283    /// Tries to convert the current range into a (min, max) pair.
284    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/// Represents a combination of a clockspeed and voltage. May be used in different context based on the table format.
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
303#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
304pub struct ClocksLevel {
305    /// Clockspeed (in MHz)
306    pub clockspeed: i32,
307    /// Voltage (in mV)
308    pub voltage: i32,
309}
310
311impl ClocksLevel {
312    /// Create a new clocks level.
313    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}