Add missing functionality needed for PowerTools drivers

This commit is contained in:
NGnius (Graham) 2025-01-21 18:11:59 -05:00
parent 95e52020f9
commit 867b937cd8
8 changed files with 427 additions and 21 deletions

View file

@ -4,3 +4,8 @@ pub fn write_confirm_pp_od_clk_voltage(sysfs: &BasicEntityPath) -> Result<(), po
sysfs.set(super::DEVICE_PP_OD_CLK_VOLTAGE, super::pp_od_clk_voltage::PpOdClkVoltageWriteValues::C.sysfs_str())
.map_err(powerbox::PowerError::Io)
}
pub fn write_reset_pp_od_clk_voltage(sysfs: &BasicEntityPath) -> Result<(), powerbox::PowerError> {
sysfs.set(super::DEVICE_PP_OD_CLK_VOLTAGE, super::pp_od_clk_voltage::PpOdClkVoltageWriteValues::R.sysfs_str())
.map_err(powerbox::PowerError::Io)
}

View file

@ -8,14 +8,18 @@ use super::AmdGpu;
pub enum AmdGpuOp {
PpDpm(super::AmdGpuPpDpm),
PpOdClk(super::AmdGpuPpOdClkVoltage),
Commit(AmdGpuCommit),
Reset(AmdGpuReset),
}
impl PowerOp for AmdGpuOp {
fn is_eq_op(&self, other: &Self) -> bool {
match self {
Self::PpDpm(x) => if let Self::PpDpm(other) = other { x.is_eq_op(other) } else { false },
Self::PpOdClk(x) => if let Self::PpOdClk(other) = other { x.is_eq_op(other) } else { false },
Self::Commit(_) => matches!(other, Self::Commit(_)),
Self::Reset(_) => matches!(other, Self::Reset(_)),
}
}
}
@ -32,13 +36,20 @@ impl Power<AmdGpuOp> for AmdGpu {
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=AmdGpuOp>> {
// TODO
Box::new(core::iter::empty())
Box::new(
Power::<super::AmdGpuPpDpm, _>::supported_operations(self).map(core::convert::Into::<AmdGpuOp>::into)
.chain(Power::<super::AmdGpuPpOdClkVoltage, _>::supported_operations(self).map(core::convert::Into::<AmdGpuOp>::into))
.chain(Power::<AmdGpuCommit, _>::supported_operations(self).map(core::convert::Into::<AmdGpuOp>::into))
.chain(Power::<AmdGpuReset, _>::supported_operations(self).map(core::convert::Into::<AmdGpuOp>::into))
)
}
fn act(&self, op: AmdGpuOp) -> Result<powerbox::primitives::Value, powerbox::PowerError> {
match op {
_ => Err(powerbox::PowerError::Unknown),
AmdGpuOp::PpDpm(freq) => self.act(freq).map(powerbox::primitives::Value::into_any),
AmdGpuOp::PpOdClk(clk) => self.act(clk).map(powerbox::primitives::Value::into_any),
AmdGpuOp::Commit(c) => self.act(c).map(|_| powerbox::primitives::Value::Unknown),
AmdGpuOp::Reset(r) => self.act(r).map(|_| powerbox::primitives::Value::Unknown),
}
}
}
@ -85,3 +96,43 @@ impl Into<AmdGpuOp> for AmdGpuCommit {
}
}
pub struct AmdGpuReset;
impl PowerOp for AmdGpuReset {
fn is_eq_op(&self, _other: &Self) -> bool {
true
}
}
impl GpuPowerOp for AmdGpuReset {}
impl Power<AmdGpuReset, ()> for AmdGpu {
fn is_on(&self) -> bool {
self.is_enabled()
}
fn is_available(&self) -> bool {
self.is_compatible()
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=AmdGpuReset>> {
if self.sysfs.exists(&super::DEVICE_PP_OD_CLK_VOLTAGE) {
Box::new(core::iter::once(AmdGpuReset))
} else {
Box::new(core::iter::empty())
}
}
fn act(&self, _: AmdGpuReset) -> Result<(), powerbox::PowerError> {
super::common::write_reset_pp_od_clk_voltage(&self.sysfs)
}
}
impl GpuPower<AmdGpuReset, ()> for AmdGpu {}
impl Into<AmdGpuOp> for AmdGpuReset {
fn into(self) -> AmdGpuOp {
AmdGpuOp::Reset(self)
}
}

View file

@ -10,8 +10,8 @@ mod pp_dpm_star;
mod pp_od_clk_voltage;
pub use cpus::{AmdGpuCpuOp, AmdGpuCpuFreq, GetCoreMinFrequency, GetCoreMaxFrequency, SetCoreMinFrequency, SetCoreMaxFrequency, SetCoreLimits};
pub use gpu::{AmdGpuOp, AmdGpuCommit};
//pub use pp_od_clk_voltage::MinMax;
pub use gpu::{AmdGpuOp, AmdGpuCommit, AmdGpuReset};
pub use pp_od_clk_voltage::{AmdGpuPpOdInfo, AmdGpuPpOdClkVoltage, AmdGpuPpOdSclk, SetAmdGpuPpOdSclk, GetAmdGpuPpOdSclk};
pub use pp_dpm_star::{AmdGpuPpDpm, AmdGpuFclk, GetAmdGpuFclk, SetAmdGpuFclk, AmdGpuMclk, GetAmdGpuMclk, SetAmdGpuMclk, AmdGpuSclk, GetAmdGpuSclk, SetAmdGpuSclk, AmdGpuSocclk, GetAmdGpuSocclk, SetAmdGpuSocclk};
use sysfuss::{BasicEntityPath, SysEntityAttributesExt};
@ -39,6 +39,10 @@ impl AmdGpu {
}
}
pub fn sysfs_path(&self) -> &'_ std::path::Path {
self.sysfs.as_ref()
}
pub(super) fn is_enabled(&self) -> bool {
self.sysfs.attribute::<String>(DEVICE_ENABLE).is_ok_and(|val: String| val == "1")
}

View file

@ -62,10 +62,16 @@ impl Power<AmdGpuPpDpm, Value<ValueMap<usize, Selectable<ClockFrequency>>>> for
impl GpuPower<AmdGpuPpDpm, Value<ValueMap<usize, Selectable<ClockFrequency>>>> for AmdGpu {}
impl Into<super::AmdGpuOp> for AmdGpuPpDpm {
fn into(self) -> super::AmdGpuOp {
super::AmdGpuOp::PpDpm(self)
}
}
macro_rules! get_set_gen_impl {
($alias:ident, $getter:ident, $setter:ident, $device_const:ident) => {
pub struct $getter;
pub struct $setter(Vec<usize>);
pub struct $setter(pub Vec<usize>);
pub type $alias = GetSet<$getter, $setter>;

View file

@ -1,6 +1,11 @@
use std::{borrow::Cow, collections::HashMap};
use powerbox::primitives::{ValueMap, ClockFrequency, Selectable, SpacedList, ValueMapParseErr};
use powerbox::primitives::{ValueMap, Value, ClockFrequency, Selectable, SpacedList, ValueMapParseErr, GetSet};
use powerbox::{Power, PowerOp, RatifiedPower};
use sysfuss::SysEntityAttributesExt;
use crate::gpu::{GpuPower, GpuPowerOp};
use super::AmdGpu;
#[allow(non_camel_case_types)]
#[derive(PartialEq, Eq, Hash, Clone)]
@ -55,6 +60,7 @@ impl OdTableName {
}
}
#[allow(dead_code)]
fn table_name(&self) -> Cow<'_, str> {
match self {
Self::SCLK => Cow::from("OD_SCLK"),
@ -115,10 +121,10 @@ impl std::str::FromStr for PpOdClkVoltageReadValues {
let mut table_end = table_start;
let table_name = table_name_line.trim_end().trim_end_matches(|c: char| c == ':');
let table_name = OdTableName::from_table_name(table_name);
println!("table name: {}", table_name.table_name());
//println!("table name: {}", table_name.table_name());
let next_table_name_line;
if let OdTableName::RANGE = table_name {
println!("range table special case!");
//println!("range table special case!");
'range_table_loop: loop {
let line = lines_iter.next();
if let Some(line) = line.and_then(|line| is_valid_range_table_entry_line(line).then_some(line)) {
@ -130,7 +136,7 @@ impl std::str::FromStr for PpOdClkVoltageReadValues {
}
let table_str = &s[table_start..table_end-1];
println!("table str: '{}'", table_str);
//println!("table str: '{}'", table_str);
match table_str.parse() {
Ok(r_t) => range_table = Some(r_t),
Err(ValueMapParseErr::InnerVal(v)) => return Err(ValueMapParseErr::InnerVal(v)),
@ -150,7 +156,7 @@ impl std::str::FromStr for PpOdClkVoltageReadValues {
}
let table_str = &s[table_start..table_end-1];
println!("table str: '{}'", table_str);
//println!("table str: '{}'", table_str);
let new_table = table_str.parse()?;
tables.insert(table_name, new_table);
table_start = table_end;
@ -191,13 +197,15 @@ pub(super) enum MinMax {
pub(super) enum PpOdClkVoltageWriteValues {
/// Something clock
SCLK {
limit: MinMax,
point: usize,
clockspeed: ClockFrequency,
millivolts: Option<usize>,
},
/// Memory clock
MCLK {
limit: MinMax,
point: usize,
clockspeed: ClockFrequency,
millivolts: usize,
},
/// Core clock
CCLK {
@ -217,17 +225,24 @@ pub(super) enum PpOdClkVoltageWriteValues {
},
/// Confirm (required to apply another other written value)
C,
/// Reset all related settings
R,
}
impl PpOdClkVoltageWriteValues {
pub fn sysfs_str(&self) -> String {
match self {
Self::SCLK { limit, clockspeed } => format!("s {} {}", Self::limit_num(limit), Self::mhz_clock(clockspeed)),
Self::MCLK { limit, clockspeed } => format!("m {} {}", Self::limit_num(limit), Self::mhz_clock(clockspeed)),
Self::SCLK { point, clockspeed, millivolts } => if let Some(millivolts) = millivolts {
format!("s {} {} {}", point, Self::mhz_clock(clockspeed), millivolts)
} else {
format!("s {} {}", point, Self::mhz_clock(clockspeed))
},
Self::MCLK { point, clockspeed, millivolts } => format!("m {} {} {}", point, Self::mhz_clock(clockspeed), millivolts),
Self::CCLK { core, limit, clockspeed } => format!("p {} {} {}", core, Self::limit_num(limit), Self::mhz_clock(clockspeed)),
Self::VC { point, clockspeed, millivolts } => format!("vc {} {} {}", point, Self::mhz_clock(clockspeed), millivolts),
Self::VO { millivolts } => format!("vo {}", millivolts),
Self::C => "c".to_owned(),
Self::R => "r".to_owned(),
}
}
@ -247,6 +262,318 @@ impl PpOdClkVoltageWriteValues {
}
}
// Power impls
pub struct AmdGpuPpOdInfo {
pub range: Option<SpacedList<ClockFrequency>>,
pub levels: ValueMap<usize, Selectable<ClockFrequency>>,
}
macro_rules! get_set_gen_impl {
($alias:ident, $getter:ident, $setter:ident, $table:ident, $variant:ident) => {
pub struct $getter;
pub type $alias = GetSet<$getter, $setter>;
// GetSet impl
impl GpuPowerOp for $alias {}
impl Power<$alias, Value<AmdGpuPpOdInfo>> for AmdGpu {
fn is_on(&self) -> bool {
self.is_enabled()
}
fn is_available(&self) -> bool {
self.is_compatible()
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=$alias>> {
Box::new(
Power::<$getter, _>::supported_operations(self).map(core::convert::Into::<$alias>::into)
.chain(Power::<$setter, _>::supported_operations(self).map(core::convert::Into::<$alias>::into))
)
}
fn act(&self, op: $alias) -> Result<Value<AmdGpuPpOdInfo>, powerbox::PowerError> {
match op {
$alias::Get(clk) => self.act(clk).map(Value::Custom),
$alias::Set(clk) => self.act(clk).map(|_| Value::Unknown),
}
}
}
impl GpuPower<$alias, Value<AmdGpuPpOdInfo>> for AmdGpu {}
impl core::convert::Into<AmdGpuPpOdClkVoltage> for $alias {
fn into(self) -> AmdGpuPpOdClkVoltage {
AmdGpuPpOdClkVoltage::$variant(self)
}
}
// Get impl
impl PowerOp for $getter {
fn is_eq_op(&self, _: &Self) -> bool {
true
}
}
impl GpuPowerOp for $getter {}
impl Power<$getter, AmdGpuPpOdInfo> for AmdGpu {
fn is_on(&self) -> bool {
self.is_enabled()
}
fn is_available(&self) -> bool {
self.is_compatible()
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=$getter>> {
if let Ok(rv) = self.read_pp_od_clk_voltage() {
if rv.tables.contains_key(&OdTableName::$table) {
Box::new(core::iter::once($getter))
} else {
Box::new(core::iter::empty())
}
} else { Box::new(core::iter::empty()) }
}
fn act(&self, _: $getter) -> Result<AmdGpuPpOdInfo, powerbox::PowerError> {
match self.read_pp_od_clk_voltage() {
Ok(mut rv) => {
Ok(AmdGpuPpOdInfo {
range: rv.ranges.map(|mut ranges| ranges.0.remove(&OdTableName::$table)).flatten(),
levels: rv.tables.remove(&OdTableName::$table).unwrap_or_else(|| ValueMap(Default::default()))
})
},
Err(sysfuss::EitherErr2::First(e)) => Err(powerbox::PowerError::Io(e)),
Err(sysfuss::EitherErr2::Second(_)) => Err(powerbox::PowerError::Unknown),
}
}
}
impl GpuPower<$getter, AmdGpuPpOdInfo> for AmdGpu {}
impl core::convert::Into<$alias> for $getter {
fn into(self) -> $alias {
$alias::Get(self)
}
}
// Set impl
impl PowerOp for $setter {
fn is_eq_op(&self, _: &Self) -> bool {
true
}
}
impl GpuPowerOp for $setter {}
impl Power<$setter, ()> for AmdGpu {
fn is_on(&self) -> bool {
self.is_enabled()
}
fn is_available(&self) -> bool {
self.is_compatible()
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=$setter>> {
if let Ok(rv) = self.read_pp_od_clk_voltage() {
if rv.ranges.is_some_and(|ranges| ranges.0.contains_key(&OdTableName::$table)) {
Box::new(core::iter::once($setter::filler_default()))
} else {
Box::new(core::iter::empty())
}
} else { Box::new(core::iter::empty()) }
}
fn act(&self, op: $setter) -> Result<(), powerbox::PowerError> {
let payload = op.write_payload();
if !self.is_pdfpl_manual() {
self.sysfs.set(
super::DEVICE_POWER_DPM_FORCE_LIMITS_ATTRIBUTE,
super::power_dpm_force_performance_level::PowerDpmForcePerformanceLevel::Manual.sysfs_str())
.map_err(powerbox::PowerError::Io)?;
}
self.sysfs.set(super::DEVICE_PP_OD_CLK_VOLTAGE, payload.sysfs_str())
.map_err(powerbox::PowerError::Io)?;
if op.should_commit() {
super::common::write_confirm_pp_od_clk_voltage(&self.sysfs)
} else {
Ok(())
}
}
}
impl RatifiedPower<$setter> for AmdGpu {
fn is_possible(&self, op: &$setter) -> bool {
if let Ok(gotten) = self.act($getter) {
op.is_within_range(&gotten)
} else {
false
}
}
fn clamp(&self, op: &mut $setter) -> bool {
if let Ok(gotten) = self.act($getter) {
op.clamp_to(&gotten);
true
} else {
false
}
}
}
impl GpuPower<$setter, ()> for AmdGpu {}
impl core::convert::Into<$alias> for $setter {
fn into(self) -> $alias {
$alias::Set(self)
}
}
};
}
pub enum AmdGpuPpOdClkVoltage {
Sclk(AmdGpuPpOdSclk),
//MCLK(AmdGpuPpOdMclk),
//VC(AmdGpuPpOdVc),
//VO(AmdGpuPpOdVo),
}
impl PowerOp for AmdGpuPpOdClkVoltage {
fn is_eq_op(&self, other: &Self) -> bool {
#[allow(irrefutable_let_patterns)]
match self {
//Self::Fclk(f) => if let Self::Fclk(other) = other { f.is_eq_op(other) } else { false },
//Self::Mclk(m) => if let Self::Mclk(other) = other { m.is_eq_op(other) } else { false },
Self::Sclk(s) => if let Self::Sclk(other) = other { s.is_eq_op(other) } else { false },
//Self::Socclk(soc) => if let Self::Socclk(other) = other { soc.is_eq_op(other) } else { false },
}
}
}
impl GpuPowerOp for AmdGpuPpOdClkVoltage {}
impl Power<AmdGpuPpOdClkVoltage, Value<AmdGpuPpOdInfo>> for AmdGpu {
fn is_on(&self) -> bool {
self.is_enabled()
}
fn is_available(&self) -> bool {
self.is_compatible()
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=AmdGpuPpOdClkVoltage>> {
Box::new(
Power::<AmdGpuPpOdSclk, _>::supported_operations(self).map(core::convert::Into::<AmdGpuPpOdClkVoltage>::into)
//.chain(Power::<AmdGpuMclk, _>::supported_operations(self).map(core::convert::Into::<AmdGpuPpOdClkVoltage>::into))
//.chain(Power::<AmdGpuFclk, _>::supported_operations(self).map(core::convert::Into::<AmdGpuPpOdClkVoltage>::into))
//.chain(Power::<AmdGpuSocclk, _>::supported_operations(self).map(core::convert::Into::<AmdGpuPpOdClkVoltage>::into))
)
}
fn act(&self, op: AmdGpuPpOdClkVoltage) -> Result<Value<AmdGpuPpOdInfo>, powerbox::PowerError> {
match op {
//AmdGpuPpOdClkVoltage::Fclk(clk) => self.act(clk),
//AmdGpuPpOdClkVoltage::Mclk(clk) => self.act(clk),
AmdGpuPpOdClkVoltage::Sclk(clk) => self.act(clk),
//AmdGpuPpOdClkVoltage::Socclk(clk) => self.act(clk),
}
}
}
impl GpuPower<AmdGpuPpOdClkVoltage, Value<AmdGpuPpOdInfo>> for AmdGpu {}
impl Into<super::AmdGpuOp> for AmdGpuPpOdClkVoltage {
fn into(self) -> super::AmdGpuOp {
super::AmdGpuOp::PpOdClk(self)
}
}
pub struct SetAmdGpuPpOdSclk {
pub level: usize,
pub clockspeed: ClockFrequency,
pub millivolts: Option<usize>,
pub no_commit: bool,
}
impl SetAmdGpuPpOdSclk {
fn filler_default() -> Self {
Self {
level: 0,
clockspeed: ClockFrequency {
value: 1_000,
si_prefix: 1_000_000,
},
millivolts: None,
no_commit: false,
}
}
fn write_payload(&self) -> PpOdClkVoltageWriteValues {
PpOdClkVoltageWriteValues::SCLK {
point: self.level,
clockspeed: self.clockspeed,
millivolts: self.millivolts,
}
}
#[inline]
fn should_commit(&self) -> bool {
!self.no_commit
}
fn is_within_range(&self, get: &AmdGpuPpOdInfo) -> bool {
get.levels.0.contains_key(&self.level)
&& get.range.as_ref().is_some_and(|range|
!range.0.is_empty()
&& self.clockspeed.in_hz() > range.0[0].in_hz()
&& self.clockspeed.in_hz() > range.0[range.0.len()-1].in_hz()
)
}
fn clamp_to(&mut self, get: &AmdGpuPpOdInfo) {
if !get.levels.0.contains_key(&self.level) {
let min = get.levels.0.keys().min().map(|x| *x).unwrap_or_default();
let max = get.levels.0.keys().max().map(|x| *x).unwrap_or_default();
if self.level > max {
self.level = max;
} else {
self.level = min;
}
}
}
}
get_set_gen_impl!(AmdGpuPpOdSclk, GetAmdGpuPpOdSclk, SetAmdGpuPpOdSclk, SCLK, Sclk);
/*
// System/engine clock (SCLK)
pub struct EngineClock {
limit: MinMax,
clockspeed: ClockFrequency,
}
/// Memory clock (MCLK)
pub struct MemoryClock {
limit: MinMax,
clockspeed: ClockFrequency,
}
/// Voltage curve (VC)
pub struct VoltageCurve {
point: usize,
clockspeed: ClockFrequency,
millivolts: usize,
}
/// Voltage offset (VO)
pub struct VoltageOffset {
millivolts: isize,
}*/
#[cfg(test)]
mod test {
use super::*;

View file

@ -31,6 +31,15 @@ impl AmdCpu {
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
{false}
}
pub fn as_basic(&self) -> &'_ BasicCpu {
&self.basic
}
/// Create an AmdCpu without checking if it's compatible (use try_into() instead)
pub fn new(basic: BasicCpu) -> Self {
Self { basic, }
}
}
impl core::ops::Deref for AmdCpu {

View file

@ -140,7 +140,7 @@ impl Power<SetSMT, ()> for BasicCpus {
}
fn is_available(&self) -> bool {
self.is_exists()
self.sysfs.exists(&CPUS_SMT_CONTROL)
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=SetSMT>> {
@ -195,7 +195,7 @@ impl Power<GetSMT, bool> for BasicCpus {
}
fn is_available(&self) -> bool {
self.is_exists()
self.sysfs.exists(&CPUS_SMT_ACTIVE)
}
fn supported_operations(&self) -> Box<dyn core::iter::Iterator<Item=GetSMT>> {

View file

@ -49,6 +49,10 @@ impl BasicCpu {
index,
}
}
pub fn sysfs_path(&self) -> &std::path::Path {
self.sysfs.as_ref()
}
}
fn parse_cpu_index(path: &str) -> Option<usize> {
@ -611,7 +615,7 @@ impl core::convert::Into<EnergyPerfPreference> for GetEnergyPerfPreference {
}
}
pub struct SetEnergyPerfPreference(String);
pub struct SetEnergyPerfPreference(pub String);
impl PowerOp for SetEnergyPerfPreference {
fn is_eq_op(&self, _: &Self) -> bool {
@ -906,7 +910,7 @@ impl core::convert::Into<ScalingGovernor> for GetScalingGovernor {
}
}
pub struct SetScalingGovernor(String);
pub struct SetScalingGovernor(pub String);
impl PowerOp for SetScalingGovernor {
fn is_eq_op(&self, _: &Self) -> bool {
@ -1024,7 +1028,7 @@ impl core::convert::Into<ScalingMaxFreq> for GetScalingMaxFreq {
}
}
pub struct SetScalingMaxFreq(usize);
pub struct SetScalingMaxFreq(pub usize);
impl PowerOp for SetScalingMaxFreq {
fn is_eq_op(&self, _: &Self) -> bool {
@ -1142,7 +1146,7 @@ impl core::convert::Into<ScalingMinFreq> for GetScalingMinFreq {
}
}
pub struct SetScalingMinFreq(usize);
pub struct SetScalingMinFreq(pub usize);
impl PowerOp for SetScalingMinFreq {
fn is_eq_op(&self, _: &Self) -> bool {