amdgpud/amdvold/src/clock_state.rs
2021-12-03 07:35:17 +01:00

433 lines
12 KiB
Rust

use std::iter::Peekable;
use std::str::Chars;
const ENGINE_CLOCK_LABEL: &str = "OD_SCLK:";
const MEMORY_CLOCK_LABEL: &str = "OD_MCLK:";
const CURVE_POINTS_LABEL: &str = "OD_VDDC_CURVE:";
#[derive(Debug, PartialEq)]
pub struct Frequency {
pub value: u32,
pub unit: String,
}
impl ToString for Frequency {
fn to_string(&self) -> String {
format!("{}{}", self.value, self.unit)
}
}
impl std::str::FromStr for Frequency {
type Err = ClockStateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut buffer = String::with_capacity(8);
let mut value = None;
for c in s.trim().chars() {
if c.is_numeric() && value.is_none() {
buffer.push(c);
} else if c.is_numeric() {
return Err(ClockStateError::NotFrequency(s.to_string()));
} else if value.is_none() {
if buffer.is_empty() {
return Err(ClockStateError::NotFrequency(s.to_string()));
}
value = Some(buffer.parse()?);
buffer.clear();
buffer.push(c);
} else {
buffer.push(c);
}
}
let value = value.ok_or_else(|| ClockStateError::NotFrequency(s.to_string()))?;
if !buffer.ends_with("hz") && !buffer.ends_with("Hz") {
return Err(ClockStateError::NotFrequency(s.to_string()));
}
Ok(Self {
value,
unit: buffer,
})
}
}
#[derive(Debug, PartialEq)]
pub struct Voltage {
pub value: u32,
pub unit: String,
}
impl ToString for Voltage {
fn to_string(&self) -> String {
format!("{}{}", self.value, self.unit)
}
}
impl std::str::FromStr for Voltage {
type Err = ClockStateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut buffer = String::with_capacity(8);
let mut value = None;
for c in s.trim().chars() {
if c.is_numeric() && value.is_none() {
buffer.push(c);
} else if c.is_numeric() {
return Err(ClockStateError::NotVoltage(s.to_string()));
} else if value.is_none() {
if buffer.is_empty() {
return Err(ClockStateError::NotVoltage(s.to_string()));
}
value = Some(buffer.parse()?);
buffer.clear();
buffer.push(c);
} else {
buffer.push(c);
}
}
let value = value.ok_or_else(|| ClockStateError::NotVoltage(s.to_string()))?;
if !buffer.ends_with('V') {
return Err(ClockStateError::NotVoltage(s.to_string()));
}
Ok(Self {
value,
unit: buffer,
})
}
}
#[derive(Debug, PartialEq)]
pub struct CurvePoint {
pub freq: Frequency,
pub voltage: Voltage,
}
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum ClockStateError {
#[error("Can't parse value. {0:?}")]
ParseValue(#[from] std::num::ParseIntError),
#[error("Value {0:?} is not a voltage")]
NotVoltage(String),
#[error("Value {0:?} is not a frequency")]
NotFrequency(String),
#[error("Voltage section for engine clock is not valid. Line {0:?} is malformed")]
InvalidEngineClockSection(String),
}
#[derive(Debug, PartialEq)]
pub struct ClockState {
pub curve_labels: Vec<CurvePoint>,
pub engine_label_lowest: Option<Frequency>,
pub engine_label_highest: Option<Frequency>,
pub memory_label_lowest: Option<Frequency>,
pub memory_label_highest: Option<Frequency>,
}
impl Default for ClockState {
fn default() -> Self {
Self {
curve_labels: Vec::with_capacity(3),
engine_label_lowest: None,
engine_label_highest: None,
memory_label_lowest: None,
memory_label_highest: None,
}
}
}
impl std::str::FromStr for ClockState {
type Err = ClockStateError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut clock_state = Self::default();
enum State {
Unknown,
ParseEngineClock,
ParseMemoryClock,
ParseCurve,
}
let mut state = State::Unknown;
for line in s.lines() {
let start = match line.chars().position(|c| c != ' ' && c != '\0') {
Some(idx) => idx,
_ => continue,
};
let line = line[start..].trim();
match state {
_ if line == "OD_RANGE:" => break,
_ if line == ENGINE_CLOCK_LABEL => {
state = State::ParseEngineClock;
}
_ if line == MEMORY_CLOCK_LABEL => {
state = State::ParseMemoryClock;
}
_ if line == CURVE_POINTS_LABEL => {
state = State::ParseCurve;
}
State::ParseEngineClock => {
if clock_state.engine_label_lowest.is_none() {
clock_state.engine_label_lowest = Some(parse_freq_line(line)?);
} else {
clock_state.engine_label_highest = Some(parse_freq_line(line)?);
}
}
State::ParseMemoryClock => {
if clock_state.memory_label_lowest.is_none() {
clock_state.memory_label_lowest = Some(parse_freq_line(line)?);
} else {
clock_state.memory_label_highest = Some(parse_freq_line(line)?);
}
}
State::ParseCurve => {
let (freq, volt) = parse_freq_voltage_line(line)?;
clock_state.curve_labels.push(CurvePoint {
freq,
voltage: volt,
});
}
_ => {}
}
}
Ok(clock_state)
}
}
fn consume_mode_number<'line>(
line: &'line str,
chars: &mut Peekable<Chars<'line>>,
) -> std::result::Result<(), ClockStateError> {
let mut buffer = String::with_capacity(4);
while chars.peek().filter(|c| c.is_numeric()).is_some() {
buffer.push(chars.next().unwrap());
}
if buffer.is_empty() {
return Err(ClockStateError::InvalidEngineClockSection(line.to_string()));
}
chars
.next()
.filter(|c| *c == ':')
.ok_or_else(|| ClockStateError::InvalidEngineClockSection(line.to_string()))?;
Ok(())
}
fn consume_freq(chars: &mut Peekable<Chars>) -> std::result::Result<Frequency, ClockStateError> {
consume_white(chars);
chars
.take_while(|c| *c != ' ')
.collect::<String>()
.parse::<Frequency>()
}
fn consume_voltage(chars: &mut Peekable<Chars>) -> std::result::Result<Voltage, ClockStateError> {
consume_white(chars);
chars
.take_while(|c| *c != ' ')
.collect::<String>()
.parse::<Voltage>()
}
fn consume_white(chars: &mut Peekable<Chars>) {
while chars.peek().filter(|c| **c == ' ').is_some() {
let _ = chars.next();
}
}
fn parse_freq_line(line: &str) -> std::result::Result<Frequency, ClockStateError> {
let mut chars = line.chars().peekable();
consume_mode_number(line, &mut chars)?;
consume_freq(&mut chars)
}
fn parse_freq_voltage_line(
line: &str,
) -> std::result::Result<(Frequency, Voltage), ClockStateError> {
let mut chars = line.chars().peekable();
consume_mode_number(line, &mut chars)?;
let freq = consume_freq(&mut chars)?;
consume_white(&mut chars);
Ok((freq, consume_voltage(&mut chars)?))
}
#[cfg(test)]
mod parse_frequency {
use crate::clock_state::{ClockStateError, Frequency};
#[test]
fn parse_empty_string() {
assert_eq!(
"".parse::<Frequency>(),
Err(ClockStateError::NotFrequency("".to_string()))
);
}
#[test]
fn parse_only_v_letter() {
assert_eq!(
"v".parse::<Frequency>(),
Err(ClockStateError::NotFrequency("v".to_string()))
);
}
#[test]
fn parse_only_hz() {
assert_eq!(
"hz".parse::<Frequency>(),
Err(ClockStateError::NotFrequency("hz".to_string()))
);
}
#[test]
fn parse_only_mhz() {
assert_eq!(
"Mhz".parse::<Frequency>(),
Err(ClockStateError::NotFrequency("Mhz".to_string()))
);
}
#[test]
fn parse_0mhz() {
assert_eq!(
"0Mhz".parse::<Frequency>(),
Ok(Frequency {
value: 0,
unit: "Mhz".to_string(),
})
);
}
#[test]
fn parse_0khz() {
assert_eq!(
"0khz".parse::<Frequency>(),
Ok(Frequency {
value: 0,
unit: "khz".to_string(),
})
);
}
#[test]
fn parse_0kz() {
assert_eq!(
"0hz".parse::<Frequency>(),
Ok(Frequency {
value: 0,
unit: "hz".to_string(),
})
);
}
#[test]
fn parse_123mhz() {
assert_eq!(
"123Mhz".parse::<Frequency>(),
Ok(Frequency {
value: 123,
unit: "Mhz".to_string(),
})
);
}
#[test]
fn parse_123khz() {
assert_eq!(
"123khz".parse::<Frequency>(),
Ok(Frequency {
value: 123,
unit: "khz".to_string(),
})
);
}
#[test]
fn parse_123kz() {
assert_eq!(
"123hz".parse::<Frequency>(),
Ok(Frequency {
value: 123,
unit: "hz".to_string(),
})
);
}
}
#[cfg(test)]
mod state_tests {
use crate::clock_state::{ClockState, CurvePoint, Frequency, Voltage};
#[test]
fn valid_string() {
let s = r#"
OD_SCLK:
0: 800Mhz
1: 2100Mhz
OD_MCLK:
1: 875MHz
OD_VDDC_CURVE:
0: 800MHz 706mV
1: 1450MHz 772mV
2: 2100MHz 1143mV
OD_RANGE:
SCLK: 800Mhz 2150Mhz
MCLK: 625Mhz 950Mhz
VDDC_CURVE_SCLK[0]: 800Mhz 2150Mhz
VDDC_CURVE_VOLT[0]: 750mV 1200mV
VDDC_CURVE_SCLK[1]: 800Mhz 2150Mhz
VDDC_CURVE_VOLT[1]: 750mV 1200mV
VDDC_CURVE_SCLK[2]: 800Mhz 2150Mhz
VDDC_CURVE_VOLT[2]: 750mV 1200mV
"#;
let res = s.trim().parse::<ClockState>();
assert_eq!(
res,
Ok(ClockState {
curve_labels: vec![
CurvePoint {
freq: Frequency {
value: 800,
unit: "MHz".to_string(),
},
voltage: Voltage {
value: 706,
unit: "mV".to_string(),
},
},
CurvePoint {
freq: Frequency {
value: 1450,
unit: "MHz".to_string(),
},
voltage: Voltage {
value: 772,
unit: "mV".to_string(),
},
},
CurvePoint {
freq: Frequency {
value: 2100,
unit: "MHz".to_string(),
},
voltage: Voltage {
value: 1143,
unit: "mV".to_string(),
},
},
],
engine_label_lowest: Some(Frequency {
value: 800,
unit: "Mhz".to_string(),
}),
engine_label_highest: Some(Frequency {
value: 2100,
unit: "Mhz".to_string(),
}),
memory_label_lowest: Some(Frequency {
value: 875,
unit: "MHz".to_string(),
}),
memory_label_highest: None,
})
);
}
}