diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 18b08bd..ae26942 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -5,7 +5,7 @@ mod power_error; pub use power_error::PowerError; mod power_trait; -pub use power_trait::{Power, PowerOp, RatifiedPower}; +pub use power_trait::{Power, PowerOp, RatifiedPower, ManualRatifiedPower}; pub mod primitives; diff --git a/crates/core/src/power_trait.rs b/crates/core/src/power_trait.rs index 9ae0cc2..7d59a48 100644 --- a/crates/core/src/power_trait.rs +++ b/crates/core/src/power_trait.rs @@ -28,3 +28,12 @@ pub trait RatifiedPower { /// Returns false if that is not possible. fn clamp(&self, op: &mut OP) -> bool; } + +/// Power control operation validation with provided limits +pub trait ManualRatifiedPower { + /// 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; +} diff --git a/crates/core/src/primitives/clockspeed.rs b/crates/core/src/primitives/clockspeed.rs index df7b80b..27865c0 100644 --- a/crates/core/src/primitives/clockspeed.rs +++ b/crates/core/src/primitives/clockspeed.rs @@ -13,6 +13,21 @@ impl ClockFrequency { pub fn in_hz(&self) -> usize { 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 @@ -91,6 +106,26 @@ fn si_prefix_char_to_number(si_prefix: char) -> Option { } } +impl core::cmp::PartialOrd for ClockFrequency { + fn partial_cmp(&self, other: &Self) -> Option { + 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)] mod test { use std::str::FromStr; diff --git a/crates/core/src/primitives/get_set.rs b/crates/core/src/primitives/get_set.rs new file mode 100644 index 0000000..ea30f13 --- /dev/null +++ b/crates/core/src/primitives/get_set.rs @@ -0,0 +1,16 @@ +/// Getter and setter power operation +pub enum GetSet { + /// Get power operation + Get(G), + /// Set power operation + Set(S), +} + +impl crate::PowerOp for GetSet { + 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 }, + } + } +} diff --git a/crates/core/src/primitives/mod.rs b/crates/core/src/primitives/mod.rs index 8a6c0ed..836a1a4 100644 --- a/crates/core/src/primitives/mod.rs +++ b/crates/core/src/primitives/mod.rs @@ -17,3 +17,6 @@ pub use space_separated_list::SpacedList; mod value; pub use value::Value; + +mod get_set; +pub use get_set::GetSet; diff --git a/crates/core/src/primitives/space_separated_list.rs b/crates/core/src/primitives/space_separated_list.rs index a6cc228..daee001 100644 --- a/crates/core/src/primitives/space_separated_list.rs +++ b/crates/core/src/primitives/space_separated_list.rs @@ -17,6 +17,21 @@ impl FromStr for SpacedList { } } +impl core::fmt::Display for SpacedList { + 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)] mod test { use super::*; @@ -38,4 +53,10 @@ mod test { let strings: SpacedList = "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]); } + + #[test] + fn print_list() { + let numbers = SpacedList(vec![0, 1, 4, 5]); + assert_eq!(numbers.to_string(), "0 1 4 5"); + } } diff --git a/crates/procbox/src/combo/amdgpu/cpus.rs b/crates/procbox/src/combo/amdgpu/cpus.rs index d75cf63..1847633 100644 --- a/crates/procbox/src/combo/amdgpu/cpus.rs +++ b/crates/procbox/src/combo/amdgpu/cpus.rs @@ -1,6 +1,6 @@ //! CPU power management implementation -use powerbox::{Power, PowerOp, RatifiedPower}; +use powerbox::{ManualRatifiedPower, Power, PowerOp, RatifiedPower}; use powerbox::primitives::ClockFrequency; use sysfuss::SysEntityAttributesExt; @@ -174,6 +174,13 @@ impl Into 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 core: usize, pub clock: ClockFrequency, @@ -278,6 +285,22 @@ impl RatifiedPower for AmdGpu { } } +impl ManualRatifiedPower 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 for SetCoreMinFrequency { fn into(self) -> AmdGpuCpuFreq { AmdGpuCpuFreq::SetMin(self) @@ -376,6 +399,22 @@ impl RatifiedPower for AmdGpu { } } +impl ManualRatifiedPower 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 for SetCoreMaxFrequency { fn into(self) -> AmdGpuCpuFreq { AmdGpuCpuFreq::SetMax(self) diff --git a/crates/procbox/src/combo/amdgpu/gpu.rs b/crates/procbox/src/combo/amdgpu/gpu.rs index 126756c..610b55b 100644 --- a/crates/procbox/src/combo/amdgpu/gpu.rs +++ b/crates/procbox/src/combo/amdgpu/gpu.rs @@ -7,12 +7,14 @@ use crate::gpu::{GpuPower, GpuPowerOp}; use super::AmdGpu; pub enum AmdGpuOp { + PpDpm(super::AmdGpuPpDpm), Commit(AmdGpuCommit), } 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::Commit(_) => matches!(other, Self::Commit(_)), } } diff --git a/crates/procbox/src/combo/amdgpu/mod.rs b/crates/procbox/src/combo/amdgpu/mod.rs index c929d3b..82a7704 100644 --- a/crates/procbox/src/combo/amdgpu/mod.rs +++ b/crates/procbox/src/combo/amdgpu/mod.rs @@ -6,11 +6,13 @@ mod common; mod cpus; mod gpu; mod power_dpm_force_performance_level; +mod pp_dpm_star; 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 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}; diff --git a/crates/procbox/src/combo/amdgpu/pp_dpm_star.rs b/crates/procbox/src/combo/amdgpu/pp_dpm_star.rs new file mode 100644 index 0000000..48827dd --- /dev/null +++ b/crates/procbox/src/combo/amdgpu/pp_dpm_star.rs @@ -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>>> for AmdGpu { + fn is_on(&self) -> bool { + self.is_enabled() + } + + fn is_available(&self) -> bool { + self.is_compatible() + } + + fn supported_operations(&self) -> Box> { + Box::new( + Power::::supported_operations(self).map(core::convert::Into::::into) + .chain(Power::::supported_operations(self).map(core::convert::Into::::into)) + .chain(Power::::supported_operations(self).map(core::convert::Into::::into)) + .chain(Power::::supported_operations(self).map(core::convert::Into::::into)) + ) + } + + fn act(&self, op: AmdGpuPpDpm) -> Result>>, 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>>> for AmdGpu {} + +macro_rules! get_set_gen_impl { + ($alias:ident, $getter:ident, $setter:ident, $device_const:ident) => { + pub struct $getter; + pub struct $setter(Vec); + + pub type $alias = GetSet<$getter, $setter>; + + // GetSet impl + impl GpuPowerOp for $alias {} + + impl Power<$alias, Value>>> for AmdGpu { + fn is_on(&self) -> bool { + self.is_enabled() + } + + fn is_available(&self) -> bool { + self.is_compatible() + } + + fn supported_operations(&self) -> Box> { + 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>>, 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>>> for AmdGpu {} + + // Get impl + impl PowerOp for $getter { + fn is_eq_op(&self, _: &Self) -> bool { + true + } + } + + impl GpuPowerOp for $getter {} + + impl Power<$getter, ValueMap>> for AmdGpu { + fn is_on(&self) -> bool { + self.is_enabled() + } + + fn is_available(&self) -> bool { + self.is_compatible() + } + + fn supported_operations(&self) -> Box> { + if self.sysfs.exists(&$device_const) { Box::new(core::iter::once($getter)) } else { Box::new(core::iter::empty()) } + } + + fn act(&self, _: $getter) -> Result>, powerbox::PowerError> { + if self.sysfs.exists(&$device_const) { + match self.sysfs.attribute::>>($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>> 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> { + 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 for AmdGpuFclk { + fn into(self) -> AmdGpuPpDpm { + AmdGpuPpDpm::Fclk(self) + } +} + +get_set_gen_impl!(AmdGpuMclk, GetAmdGpuMclk, SetAmdGpuMclk, DEVICE_PP_DPM_MCLK); + +impl core::convert::Into for AmdGpuMclk { + fn into(self) -> AmdGpuPpDpm { + AmdGpuPpDpm::Mclk(self) + } +} + +get_set_gen_impl!(AmdGpuSclk, GetAmdGpuSclk, SetAmdGpuSclk, DEVICE_PP_DPM_SCLK); + +impl core::convert::Into for AmdGpuSclk { + fn into(self) -> AmdGpuPpDpm { + AmdGpuPpDpm::Sclk(self) + } +} + +get_set_gen_impl!(AmdGpuSocclk, GetAmdGpuSocclk, SetAmdGpuSocclk, DEVICE_PP_DPM_SOCCLK); + +impl core::convert::Into for AmdGpuSocclk { + fn into(self) -> AmdGpuPpDpm { + AmdGpuPpDpm::Socclk(self) + } +}