use super::ffi::AdlPmLogDataOutput;
pub const PMLOG_CLK_GFXCLK: usize = 1;
pub const PMLOG_CLK_MEMCLK: usize = 2;
pub const PMLOG_TEMPERATURE_EDGE: usize = 8;
pub const PMLOG_TEMPERATURE_MEM: usize = 9;
pub const PMLOG_FAN_RPM: usize = 14;
pub const PMLOG_INFO_ACTIVITY_GFX: usize = 19;
pub const PMLOG_INFO_ACTIVITY_MEM: usize = 20;
pub const PMLOG_ASIC_POWER: usize = 23;
pub const PMLOG_TEMPERATURE_HOTSPOT: usize = 27;
pub const PMLOG_TEMPERATURE_GFX: usize = 28;
pub const PMLOG_GFX_POWER: usize = 30;
pub const PMLOG_BOARD_POWER: usize = 73;
const TEMPERATURE_C: std::ops::RangeInclusive<i32> = -40..=150;
const POWER_W: std::ops::RangeInclusive<i32> = 0..=1000;
const FAN_RPM: std::ops::RangeInclusive<i32> = 0..=20_000;
const CLOCK_MHZ: std::ops::RangeInclusive<i32> = 0..=10_000;
const ACTIVITY_PCT: std::ops::RangeInclusive<i32> = 0..=100;
#[derive(Debug, Default, Clone, PartialEq)]
pub struct AdlReadout {
pub temperature_edge_c: Option<i32>,
pub temperature_gfx_c: Option<i32>,
pub temperature_hotspot_c: Option<i32>,
pub temperature_mem_c: Option<i32>,
pub power_w: Option<f64>,
pub fan_rpm: Option<u32>,
pub clock_gfx_mhz: Option<u32>,
pub clock_mem_mhz: Option<u32>,
pub activity_gfx_pct: Option<f64>,
pub activity_mem_pct: Option<f64>,
}
impl AdlReadout {
pub fn is_empty(&self) -> bool {
self.temperature_edge_c.is_none()
&& self.temperature_gfx_c.is_none()
&& self.temperature_hotspot_c.is_none()
&& self.temperature_mem_c.is_none()
&& self.power_w.is_none()
&& self.fan_rpm.is_none()
&& self.clock_gfx_mhz.is_none()
&& self.clock_mem_mhz.is_none()
&& self.activity_gfx_pct.is_none()
&& self.activity_mem_pct.is_none()
}
pub fn primary_temperature_c(&self) -> Option<(i32, &'static str)> {
if let Some(edge) = self.temperature_edge_c {
return Some((edge, "ADL (edge)"));
}
if let Some(gfx) = self.temperature_gfx_c {
return Some((gfx, "ADL (gfx)"));
}
self.temperature_hotspot_c
.map(|hotspot| (hotspot, "ADL (hotspot)"))
}
}
fn sensor(
output: &AdlPmLogDataOutput,
index: usize,
range: std::ops::RangeInclusive<i32>,
) -> Option<i32> {
let entry = output.sensors.get(index)?;
if entry.supported == 0 {
return None;
}
range.contains(&entry.value).then_some(entry.value)
}
pub fn extract(output: &AdlPmLogDataOutput) -> AdlReadout {
AdlReadout {
temperature_edge_c: sensor(output, PMLOG_TEMPERATURE_EDGE, TEMPERATURE_C),
temperature_gfx_c: sensor(output, PMLOG_TEMPERATURE_GFX, TEMPERATURE_C),
temperature_hotspot_c: sensor(output, PMLOG_TEMPERATURE_HOTSPOT, TEMPERATURE_C),
temperature_mem_c: sensor(output, PMLOG_TEMPERATURE_MEM, TEMPERATURE_C),
power_w: sensor(output, PMLOG_BOARD_POWER, POWER_W)
.or_else(|| sensor(output, PMLOG_ASIC_POWER, POWER_W))
.or_else(|| sensor(output, PMLOG_GFX_POWER, POWER_W))
.map(|v| v as f64),
fan_rpm: sensor(output, PMLOG_FAN_RPM, FAN_RPM).map(|v| v as u32),
clock_gfx_mhz: sensor(output, PMLOG_CLK_GFXCLK, CLOCK_MHZ).map(|v| v as u32),
clock_mem_mhz: sensor(output, PMLOG_CLK_MEMCLK, CLOCK_MHZ).map(|v| v as u32),
activity_gfx_pct: sensor(output, PMLOG_INFO_ACTIVITY_GFX, ACTIVITY_PCT).map(|v| v as f64),
activity_mem_pct: sensor(output, PMLOG_INFO_ACTIVITY_MEM, ACTIVITY_PCT).map(|v| v as f64),
}
}
pub fn supported_raw(output: &AdlPmLogDataOutput) -> Vec<(usize, i32)> {
output
.sensors
.iter()
.enumerate()
.filter(|(_, entry)| entry.supported != 0)
.map(|(index, entry)| (index, entry.value))
.collect()
}
#[cfg(test)]
mod tests {
use super::super::ffi::AdlPmLogDataOutput;
use super::*;
fn table(entries: &[(usize, i32)]) -> AdlPmLogDataOutput {
let mut output = AdlPmLogDataOutput::default();
for (index, value) in entries {
output.sensors[*index].supported = 1;
output.sensors[*index].value = *value;
}
output
}
#[test]
fn extracts_a_realistic_rdna_readout() {
let output = table(&[
(PMLOG_CLK_GFXCLK, 2400),
(PMLOG_CLK_MEMCLK, 1250),
(PMLOG_TEMPERATURE_EDGE, 62),
(PMLOG_TEMPERATURE_MEM, 70),
(PMLOG_TEMPERATURE_HOTSPOT, 81),
(PMLOG_FAN_RPM, 1450),
(PMLOG_ASIC_POWER, 310),
(PMLOG_INFO_ACTIVITY_GFX, 97),
(PMLOG_INFO_ACTIVITY_MEM, 44),
]);
let readout = extract(&output);
assert_eq!(readout.temperature_edge_c, Some(62));
assert_eq!(readout.temperature_hotspot_c, Some(81));
assert_eq!(readout.temperature_mem_c, Some(70));
assert_eq!(readout.power_w, Some(310.0));
assert_eq!(readout.fan_rpm, Some(1450));
assert_eq!(readout.clock_gfx_mhz, Some(2400));
assert_eq!(readout.clock_mem_mhz, Some(1250));
assert_eq!(readout.activity_gfx_pct, Some(97.0));
assert_eq!(readout.activity_mem_pct, Some(44.0));
assert!(!readout.is_empty());
assert_eq!(readout.primary_temperature_c(), Some((62, "ADL (edge)")));
}
#[test]
fn unsupported_sensors_are_absent_not_zero() {
let readout = extract(&AdlPmLogDataOutput::default());
assert!(readout.is_empty());
assert_eq!(readout.primary_temperature_c(), None);
assert_eq!(readout.power_w, None);
}
#[test]
fn a_supported_flag_with_an_absurd_value_is_rejected() {
let output = table(&[
(PMLOG_TEMPERATURE_EDGE, 2400),
(PMLOG_ASIC_POWER, 99_999),
(PMLOG_FAN_RPM, 250_000),
(PMLOG_INFO_ACTIVITY_GFX, 5_000),
(PMLOG_CLK_GFXCLK, 1_000_000),
]);
let readout = extract(&output);
assert!(readout.is_empty(), "got {readout:?}");
}
#[test]
fn negative_power_and_activity_are_rejected_but_cold_dies_are_not() {
let nonsense = table(&[(PMLOG_ASIC_POWER, -1), (PMLOG_INFO_ACTIVITY_GFX, -3)]);
assert!(extract(&nonsense).is_empty());
let cold = table(&[(PMLOG_TEMPERATURE_EDGE, -12)]);
assert_eq!(extract(&cold).temperature_edge_c, Some(-12));
let absurd = table(&[(PMLOG_TEMPERATURE_EDGE, -273)]);
assert!(extract(&absurd).is_empty());
}
#[test]
fn board_power_outranks_asic_power() {
let both = table(&[
(PMLOG_BOARD_POWER, 355),
(PMLOG_ASIC_POWER, 290),
(PMLOG_GFX_POWER, 180),
]);
assert_eq!(extract(&both).power_w, Some(355.0));
let older = table(&[(PMLOG_ASIC_POWER, 290), (PMLOG_GFX_POWER, 180)]);
assert_eq!(extract(&older).power_w, Some(290.0));
}
#[test]
fn asic_power_wins_over_gfx_power_but_gfx_is_the_fallback() {
let both = table(&[(PMLOG_ASIC_POWER, 300), (PMLOG_GFX_POWER, 180)]);
assert_eq!(extract(&both).power_w, Some(300.0));
let gfx_only = table(&[(PMLOG_GFX_POWER, 180)]);
assert_eq!(extract(&gfx_only).power_w, Some(180.0));
let asic_bogus = table(&[(PMLOG_ASIC_POWER, 50_000), (PMLOG_GFX_POWER, 180)]);
assert_eq!(extract(&asic_bogus).power_w, Some(180.0));
}
#[test]
fn gfx_then_hotspot_stand_in_when_edge_is_unavailable() {
let gfx = table(&[(PMLOG_TEMPERATURE_GFX, 64), (PMLOG_TEMPERATURE_HOTSPOT, 88)]);
assert_eq!(
extract(&gfx).primary_temperature_c(),
Some((64, "ADL (gfx)"))
);
let hotspot = table(&[(PMLOG_TEMPERATURE_HOTSPOT, 88)]);
assert_eq!(
extract(&hotspot).primary_temperature_c(),
Some((88, "ADL (hotspot)"))
);
}
#[test]
fn the_raw_dump_reports_index_and_value_unfiltered() {
let output = table(&[(3, 1234), (27, 81), (200, -5)]);
let dumped = supported_raw(&output);
assert_eq!(dumped, vec![(3, 1234), (27, 81), (200, -5)]);
}
#[test]
fn boundary_values_are_inclusive() {
let output = table(&[
(PMLOG_TEMPERATURE_EDGE, 150),
(PMLOG_INFO_ACTIVITY_GFX, 100),
(PMLOG_FAN_RPM, 0),
]);
let readout = extract(&output);
assert_eq!(readout.temperature_edge_c, Some(150));
assert_eq!(readout.activity_gfx_pct, Some(100.0));
assert_eq!(readout.fan_rpm, Some(0));
}
}