Add amdgpu pp_dpm_* interfaces

This commit is contained in:
NGnius (Graham) 2024-07-20 12:58:46 -04:00
parent 60d038c79f
commit c9f2f2849e
10 changed files with 345 additions and 3 deletions

View file

@ -5,7 +5,7 @@ mod power_error;
pub use power_error::PowerError; pub use power_error::PowerError;
mod power_trait; mod power_trait;
pub use power_trait::{Power, PowerOp, RatifiedPower}; pub use power_trait::{Power, PowerOp, RatifiedPower, ManualRatifiedPower};
pub mod primitives; pub mod primitives;

View file

@ -28,3 +28,12 @@ pub trait RatifiedPower<OP: PowerOp> {
/// Returns false if that is not possible. /// Returns false if that is not possible.
fn clamp(&self, op: &mut OP) -> bool; fn clamp(&self, op: &mut OP) -> bool;
} }
/// Power control operation validation with provided limits
pub trait ManualRatifiedPower<OP: PowerOp, L: ?Sized> {
/// Is this operation within the provided limits?
fn is_possible(&self, op: &OP, limits: &L) -> bool;
/// Set operation parameters to nearest allowed values of the provided limits.
/// Returns false if that is not possible
fn clamp(&self, op: &mut OP, limits: &L) -> bool;
}

View file

@ -13,6 +13,21 @@ impl ClockFrequency {
pub fn in_hz(&self) -> usize { pub fn in_hz(&self) -> usize {
self.value * self.si_prefix self.value * self.si_prefix
} }
/// Create a clock frequency from a raw clock speed number, in Hz
pub fn from_hz(hz: usize) -> Self {
if hz > 1_000_000 {
Self {
value: hz / 1_000_000,
si_prefix: 1_000_000,
}
} else {
Self {
value: hz,
si_prefix: 1
}
}
}
} }
/// Clock speed parse errors /// Clock speed parse errors
@ -91,6 +106,26 @@ fn si_prefix_char_to_number(si_prefix: char) -> Option<usize> {
} }
} }
impl core::cmp::PartialOrd for ClockFrequency {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
if other.si_prefix == self.si_prefix {
self.value.partial_cmp(&other.value)
} else {
self.in_hz().partial_cmp(&other.in_hz())
}
}
}
impl core::cmp::Ord for ClockFrequency {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
if other.si_prefix == self.si_prefix {
self.value.cmp(&other.value)
} else {
self.in_hz().cmp(&other.in_hz())
}
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::str::FromStr; use std::str::FromStr;

View file

@ -0,0 +1,16 @@
/// Getter and setter power operation
pub enum GetSet<G, S> {
/// Get power operation
Get(G),
/// Set power operation
Set(S),
}
impl <G: crate::PowerOp, S: crate::PowerOp> crate::PowerOp for GetSet<G, S> {
fn is_eq_op(&self, other: &Self) -> bool {
match self {
Self::Get(getter) => if let Self::Get(other) = other { getter.is_eq_op(other) } else { false },
Self::Set(setter) => if let Self::Set(other) = other { setter.is_eq_op(other) } else { false },
}
}
}

View file

@ -17,3 +17,6 @@ pub use space_separated_list::SpacedList;
mod value; mod value;
pub use value::Value; pub use value::Value;
mod get_set;
pub use get_set::GetSet;

View file

@ -17,6 +17,21 @@ impl <T: FromStr> FromStr for SpacedList<T> {
} }
} }
impl <T: core::fmt::Display> core::fmt::Display for SpacedList<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.0.is_empty() {
return Ok(());
}
let mut vec_iter = self.0.iter();
let mut now = vec_iter.next().unwrap();
while let Some(next) = vec_iter.next() {
write!(f, "{} ", now)?;
now = next;
}
write!(f, "{}", now)
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@ -38,4 +53,10 @@ mod test {
let strings: SpacedList<usize> = "1 2 3 4\t5\t\t \t6 7 8 9 10 11".parse().expect("fail"); let strings: SpacedList<usize> = "1 2 3 4\t5\t\t \t6 7 8 9 10 11".parse().expect("fail");
assert_eq!(strings.0, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); assert_eq!(strings.0, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
} }
#[test]
fn print_list() {
let numbers = SpacedList(vec![0, 1, 4, 5]);
assert_eq!(numbers.to_string(), "0 1 4 5");
}
} }

View file

@ -1,6 +1,6 @@
//! CPU power management implementation //! CPU power management implementation
use powerbox::{Power, PowerOp, RatifiedPower}; use powerbox::{ManualRatifiedPower, Power, PowerOp, RatifiedPower};
use powerbox::primitives::ClockFrequency; use powerbox::primitives::ClockFrequency;
use sysfuss::SysEntityAttributesExt; use sysfuss::SysEntityAttributesExt;
@ -174,6 +174,13 @@ impl Into<AmdGpuCpuFreq> for GetCoreMaxFrequency {
} }
} }
pub struct SetCoreLimits {
pub core_min: usize,
pub core_max: usize,
pub clock_min: ClockFrequency,
pub clock_max: ClockFrequency,
}
pub struct SetCoreMinFrequency { pub struct SetCoreMinFrequency {
pub core: usize, pub core: usize,
pub clock: ClockFrequency, pub clock: ClockFrequency,
@ -278,6 +285,22 @@ impl RatifiedPower<SetCoreMinFrequency> for AmdGpu {
} }
} }
impl ManualRatifiedPower<SetCoreMinFrequency, SetCoreLimits> for AmdGpu {
fn is_possible(&self, op: &SetCoreMinFrequency, limits: &SetCoreLimits) -> bool {
let clock_hz = op.clock.in_hz();
op.core >= limits.core_min
&& op.core <= limits.core_max
&& clock_hz >= limits.clock_min.in_hz()
&& clock_hz <= limits.clock_max.in_hz()
}
fn clamp(&self, op: &mut SetCoreMinFrequency, limits: &SetCoreLimits) -> bool {
op.core = op.core.clamp(limits.core_min, limits.core_max);
op.clock = op.clock.clamp(limits.clock_min, limits.clock_max);
true
}
}
impl Into<AmdGpuCpuFreq> for SetCoreMinFrequency { impl Into<AmdGpuCpuFreq> for SetCoreMinFrequency {
fn into(self) -> AmdGpuCpuFreq { fn into(self) -> AmdGpuCpuFreq {
AmdGpuCpuFreq::SetMin(self) AmdGpuCpuFreq::SetMin(self)
@ -376,6 +399,22 @@ impl RatifiedPower<SetCoreMaxFrequency> for AmdGpu {
} }
} }
impl ManualRatifiedPower<SetCoreMaxFrequency, SetCoreLimits> for AmdGpu {
fn is_possible(&self, op: &SetCoreMaxFrequency, limits: &SetCoreLimits) -> bool {
let clock_hz = op.clock.in_hz();
op.core >= limits.core_min
&& op.core <= limits.core_max
&& clock_hz >= limits.clock_min.in_hz()
&& clock_hz <= limits.clock_max.in_hz()
}
fn clamp(&self, op: &mut SetCoreMaxFrequency, limits: &SetCoreLimits) -> bool {
op.core = op.core.clamp(limits.core_min, limits.core_max);
op.clock = op.clock.clamp(limits.clock_min, limits.clock_max);
true
}
}
impl Into<AmdGpuCpuFreq> for SetCoreMaxFrequency { impl Into<AmdGpuCpuFreq> for SetCoreMaxFrequency {
fn into(self) -> AmdGpuCpuFreq { fn into(self) -> AmdGpuCpuFreq {
AmdGpuCpuFreq::SetMax(self) AmdGpuCpuFreq::SetMax(self)

View file

@ -7,12 +7,14 @@ use crate::gpu::{GpuPower, GpuPowerOp};
use super::AmdGpu; use super::AmdGpu;
pub enum AmdGpuOp { pub enum AmdGpuOp {
PpDpm(super::AmdGpuPpDpm),
Commit(AmdGpuCommit), Commit(AmdGpuCommit),
} }
impl PowerOp for AmdGpuOp { impl PowerOp for AmdGpuOp {
fn is_eq_op(&self, other: &Self) -> bool { fn is_eq_op(&self, other: &Self) -> bool {
match self { match self {
Self::PpDpm(x) => if let Self::PpDpm(other) = other { x.is_eq_op(other) } else { false },
Self::Commit(_) => matches!(other, Self::Commit(_)), Self::Commit(_) => matches!(other, Self::Commit(_)),
} }
} }

View file

@ -6,11 +6,13 @@ mod common;
mod cpus; mod cpus;
mod gpu; mod gpu;
mod power_dpm_force_performance_level; mod power_dpm_force_performance_level;
mod pp_dpm_star;
mod pp_od_clk_voltage; mod pp_od_clk_voltage;
pub use cpus::{AmdGpuCpuOp, AmdGpuCpuFreq, GetCoreMinFrequency, GetCoreMaxFrequency, SetCoreMinFrequency, SetCoreMaxFrequency}; pub use cpus::{AmdGpuCpuOp, AmdGpuCpuFreq, GetCoreMinFrequency, GetCoreMaxFrequency, SetCoreMinFrequency, SetCoreMaxFrequency, SetCoreLimits};
pub use gpu::{AmdGpuOp, AmdGpuCommit}; pub use gpu::{AmdGpuOp, AmdGpuCommit};
//pub use pp_od_clk_voltage::MinMax; //pub use pp_od_clk_voltage::MinMax;
pub use pp_dpm_star::{AmdGpuPpDpm, AmdGpuFclk, GetAmdGpuFclk, SetAmdGpuFclk, AmdGpuMclk, GetAmdGpuMclk, SetAmdGpuMclk, AmdGpuSclk, GetAmdGpuSclk, SetAmdGpuSclk, AmdGpuSocclk, GetAmdGpuSocclk, SetAmdGpuSocclk};
use sysfuss::{BasicEntityPath, SysEntityAttributesExt}; use sysfuss::{BasicEntityPath, SysEntityAttributesExt};

View file

@ -0,0 +1,215 @@
use powerbox::primitives::{GetSet, ClockFrequency, Value, ValueMap, Selectable, SpacedList};
use powerbox::{Power, PowerOp};
use sysfuss::SysEntityAttributesExt;
use crate::gpu::{GpuPower, GpuPowerOp};
use super::AmdGpu;
//const DEVICE_PP_DPM_DCEFCLK: &str = "device/pp_dpm_dcefclk";
const DEVICE_PP_DPM_FCLK: &str = "device/pp_dpm_fclk";
const DEVICE_PP_DPM_MCLK: &str = "device/pp_dpm_mclk";
//const DEVICE_PP_DPM_PCIE: &str = "device/pp_dpm_pcie";
const DEVICE_PP_DPM_SCLK: &str = "device/pp_dpm_sclk";
const DEVICE_PP_DPM_SOCCLK: &str = "device/pp_dpm_socclk";
pub enum AmdGpuPpDpm {
Fclk(AmdGpuFclk),
Mclk(AmdGpuMclk),
Sclk(AmdGpuSclk),
Socclk(AmdGpuSocclk),
}
impl PowerOp for AmdGpuPpDpm {
fn is_eq_op(&self, other: &Self) -> bool {
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 AmdGpuPpDpm {}
impl Power<AmdGpuPpDpm, Value<ValueMap<usize, Selectable<ClockFrequency>>>> 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=AmdGpuPpDpm>> {
Box::new(
Power::<AmdGpuFclk, _>::supported_operations(self).map(core::convert::Into::<AmdGpuPpDpm>::into)
.chain(Power::<AmdGpuMclk, _>::supported_operations(self).map(core::convert::Into::<AmdGpuPpDpm>::into))
.chain(Power::<AmdGpuSclk, _>::supported_operations(self).map(core::convert::Into::<AmdGpuPpDpm>::into))
.chain(Power::<AmdGpuSocclk, _>::supported_operations(self).map(core::convert::Into::<AmdGpuPpDpm>::into))
)
}
fn act(&self, op: AmdGpuPpDpm) -> Result<Value<ValueMap<usize, Selectable<ClockFrequency>>>, powerbox::PowerError> {
match op {
AmdGpuPpDpm::Fclk(clk) => self.act(clk),
AmdGpuPpDpm::Mclk(clk) => self.act(clk),
AmdGpuPpDpm::Sclk(clk) => self.act(clk),
AmdGpuPpDpm::Socclk(clk) => self.act(clk),
}
}
}
impl GpuPower<AmdGpuPpDpm, Value<ValueMap<usize, Selectable<ClockFrequency>>>> for AmdGpu {}
macro_rules! get_set_gen_impl {
($alias:ident, $getter:ident, $setter:ident, $device_const:ident) => {
pub struct $getter;
pub struct $setter(Vec<usize>);
pub type $alias = GetSet<$getter, $setter>;
// GetSet impl
impl GpuPowerOp for $alias {}
impl Power<$alias, Value<ValueMap<usize, Selectable<ClockFrequency>>>> 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<ValueMap<usize, Selectable<ClockFrequency>>>, 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<ValueMap<usize, Selectable<ClockFrequency>>>> for AmdGpu {}
// Get impl
impl PowerOp for $getter {
fn is_eq_op(&self, _: &Self) -> bool {
true
}
}
impl GpuPowerOp for $getter {}
impl Power<$getter, ValueMap<usize, Selectable<ClockFrequency>>> 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 self.sysfs.exists(&$device_const) { Box::new(core::iter::once($getter)) } else { Box::new(core::iter::empty()) }
}
fn act(&self, _: $getter) -> Result<ValueMap<usize, Selectable<ClockFrequency>>, powerbox::PowerError> {
if self.sysfs.exists(&$device_const) {
match self.sysfs.attribute::<ValueMap<usize, Selectable<ClockFrequency>>>($device_const) {
Ok(vm) => Ok(vm),
Err(sysfuss::EitherErr2::First(e)) => Err(powerbox::PowerError::Io(e)),
Err(sysfuss::EitherErr2::Second(_)) => Err(powerbox::PowerError::Unknown),
}
} else {
Err(powerbox::PowerError::Unknown)
}
}
}
impl GpuPower<$getter, ValueMap<usize, Selectable<ClockFrequency>>> 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 self.sysfs.exists(&$device_const) { Box::new(core::iter::once($setter(Vec::new()))) } else { Box::new(core::iter::empty()) }
}
fn act(&self, op: $setter) -> Result<(), powerbox::PowerError> {
if self.sysfs.exists(&$device_const) {
self.sysfs.set($device_const, SpacedList(op.0).to_string()).map_err(powerbox::PowerError::Io)
} else {
Err(powerbox::PowerError::Unknown)
}
}
}
impl GpuPower<$setter, ()> for AmdGpu {}
impl core::convert::Into<$alias> for $setter {
fn into(self) -> $alias {
$alias::Set(self)
}
}
};
}
get_set_gen_impl!(AmdGpuFclk, GetAmdGpuFclk, SetAmdGpuFclk, DEVICE_PP_DPM_FCLK);
impl core::convert::Into<AmdGpuPpDpm> for AmdGpuFclk {
fn into(self) -> AmdGpuPpDpm {
AmdGpuPpDpm::Fclk(self)
}
}
get_set_gen_impl!(AmdGpuMclk, GetAmdGpuMclk, SetAmdGpuMclk, DEVICE_PP_DPM_MCLK);
impl core::convert::Into<AmdGpuPpDpm> for AmdGpuMclk {
fn into(self) -> AmdGpuPpDpm {
AmdGpuPpDpm::Mclk(self)
}
}
get_set_gen_impl!(AmdGpuSclk, GetAmdGpuSclk, SetAmdGpuSclk, DEVICE_PP_DPM_SCLK);
impl core::convert::Into<AmdGpuPpDpm> for AmdGpuSclk {
fn into(self) -> AmdGpuPpDpm {
AmdGpuPpDpm::Sclk(self)
}
}
get_set_gen_impl!(AmdGpuSocclk, GetAmdGpuSocclk, SetAmdGpuSocclk, DEVICE_PP_DPM_SOCCLK);
impl core::convert::Into<AmdGpuPpDpm> for AmdGpuSocclk {
fn into(self) -> AmdGpuPpDpm {
AmdGpuPpDpm::Socclk(self)
}
}