diff --git a/data/platforms/legion-go-s.toml b/data/platforms/legion-go-s.toml index ccdce9b..f8f69b9 100644 --- a/data/platforms/legion-go-s.toml +++ b/data/platforms/legion-go-s.toml @@ -1,3 +1,6 @@ [performance_profile] platform_profile_name = "lenovo-wmi-gamezone" suggested_default = "custom" + +[tdp_limit] +method = "lenovo_wmi" diff --git a/src/power.rs b/src/power.rs index b5821e9..bfef6af 100644 --- a/src/power.rs +++ b/src/power.rs @@ -93,11 +93,15 @@ pub enum CPUScalingGovernor { #[strum(serialize_all = "snake_case")] pub enum TdpLimitingMethod { GpuHwmon, + LenovoWmi, } #[derive(Debug)] pub(crate) struct GpuHwmonTdpLimitManager {} +#[derive(Debug)] +pub(crate) struct LenovoWmiTdpLimitManager {} + #[async_trait] pub(crate) trait TdpLimitManager: Send + Sync { async fn get_tdp_limit(&self) -> Result; @@ -113,6 +117,7 @@ pub(crate) async fn tdp_limit_manager() -> Result> { .ok_or(anyhow!("No TDP limit configured"))?; Ok(match config.method { + TdpLimitingMethod::LenovoWmi => Box::new(LenovoWmiTdpLimitManager {}), TdpLimitingMethod::GpuHwmon => Box::new(GpuHwmonTdpLimitManager {}), }) } @@ -451,6 +456,83 @@ impl TdpLimitManager for GpuHwmonTdpLimitManager { } } +impl LenovoWmiTdpLimitManager { + const PREFIX: &str = "/sys/class/firmware-attributes/lenovo-wmi-other-0/attributes"; + const SPL_SUFFIX: &str = "ppt_pl1_spl"; + const SPPT_SUFFIX: &str = "ppt_pl2_sppt"; + const FPPT_SUFFIX: &str = "ppt_pl3_fppt"; +} + +#[async_trait] +impl TdpLimitManager for LenovoWmiTdpLimitManager { + async fn get_tdp_limit(&self) -> Result { + let config = platform_config().await?; + if let Some(config) = config + .as_ref() + .and_then(|config| config.performance_profile.as_ref()) + { + ensure!( + get_platform_profile(&config.platform_profile_name).await? == "custom", + "TDP limiting not active" + ); + } + let base = path(Self::PREFIX); + + fs::read_to_string(base.join(Self::SPL_SUFFIX).join("current_value")) + .await + .map_err(|message| anyhow!("Error reading sysfs: {message}"))? + .trim() + .parse() + .map_err(|e| anyhow!("Error parsing value: {e}")) + } + + async fn set_tdp_limit(&self, limit: u32) -> Result<()> { + ensure!( + self.get_tdp_limit_range().await?.contains(&limit), + "Invalid limit" + ); + + let limit = limit.to_string(); + let base = path(Self::PREFIX); + write_synced( + base.join(Self::SPL_SUFFIX).join("current_value"), + limit.as_bytes(), + ) + .await + .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; + write_synced( + base.join(Self::SPPT_SUFFIX).join("current_value"), + limit.as_bytes(), + ) + .await + .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; + write_synced( + base.join(Self::FPPT_SUFFIX).join("current_value"), + limit.as_bytes(), + ) + .await + .inspect_err(|message| error!("Error writing to sysfs file: {message}")) + } + + async fn get_tdp_limit_range(&self) -> Result> { + let base = path(Self::PREFIX).join(Self::SPL_SUFFIX); + + let min: u32 = fs::read_to_string(base.join("min_value")) + .await + .map_err(|message| anyhow!("Error reading sysfs: {message}"))? + .trim() + .parse() + .map_err(|e| anyhow!("Error parsing value: {e}"))?; + let max: u32 = fs::read_to_string(base.join("max_value")) + .await + .map_err(|message| anyhow!("Error reading sysfs: {message}"))? + .trim() + .parse() + .map_err(|e| anyhow!("Error parsing value: {e}"))?; + Ok(min..=max) + } +} + pub(crate) async fn get_max_charge_level() -> Result { let config = platform_config().await?; let config = config