power: Add Lenovo WMI-based TDP limiting

This commit is contained in:
Vicki Pfau 2025-03-24 19:30:03 -07:00
parent a3125be955
commit 6086e23cc8
2 changed files with 85 additions and 0 deletions

View file

@ -1,3 +1,6 @@
[performance_profile] [performance_profile]
platform_profile_name = "lenovo-wmi-gamezone" platform_profile_name = "lenovo-wmi-gamezone"
suggested_default = "custom" suggested_default = "custom"
[tdp_limit]
method = "lenovo_wmi"

View file

@ -93,11 +93,15 @@ pub enum CPUScalingGovernor {
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum TdpLimitingMethod { pub enum TdpLimitingMethod {
GpuHwmon, GpuHwmon,
LenovoWmi,
} }
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct GpuHwmonTdpLimitManager {} pub(crate) struct GpuHwmonTdpLimitManager {}
#[derive(Debug)]
pub(crate) struct LenovoWmiTdpLimitManager {}
#[async_trait] #[async_trait]
pub(crate) trait TdpLimitManager: Send + Sync { pub(crate) trait TdpLimitManager: Send + Sync {
async fn get_tdp_limit(&self) -> Result<u32>; async fn get_tdp_limit(&self) -> Result<u32>;
@ -113,6 +117,7 @@ pub(crate) async fn tdp_limit_manager() -> Result<Box<dyn TdpLimitManager>> {
.ok_or(anyhow!("No TDP limit configured"))?; .ok_or(anyhow!("No TDP limit configured"))?;
Ok(match config.method { Ok(match config.method {
TdpLimitingMethod::LenovoWmi => Box::new(LenovoWmiTdpLimitManager {}),
TdpLimitingMethod::GpuHwmon => Box::new(GpuHwmonTdpLimitManager {}), 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<u32> {
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<RangeInclusive<u32>> {
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<i32> { pub(crate) async fn get_max_charge_level() -> Result<i32> {
let config = platform_config().await?; let config = platform_config().await?;
let config = config let config = config