From 7a21c2ff4aa8ddf530605c49eab2c56d28729c8b Mon Sep 17 00:00:00 2001 From: "Derek J. Clark" Date: Sat, 7 Sep 2024 01:21:55 -0700 Subject: [PATCH] feat(asus_wmi): Add Asus WMI TDP methods. - Make Ryzenadj a separate interface from AMDGPU. Use it as fallback when AsusWmi is not available. - Add AsusWmi TDP interface. This interface is currently only supported for AMD ROG devices. This interface can be adapted for use on Intel devices as well. - Update Cargo.toml - Misc clean ups. --- Cargo.toml | 2 +- src/main.rs | 13 +- .../cpu/{cpu.rs => cpu_features.rs} | 24 +- src/performance/cpu/mod.rs | 2 +- src/performance/gpu/acpi/firmware.rs | 125 ++++ src/performance/gpu/acpi/mod.rs | 1 + src/performance/gpu/amd/amdgpu.rs | 18 +- src/performance/gpu/amd/asus.rs | 95 --- src/performance/gpu/amd/mod.rs | 1 + src/performance/gpu/amd/ryzenadj.rs | 346 +++++++++++ src/performance/gpu/amd/tdp.rs | 556 +++++++++--------- src/performance/gpu/asus/asus_wmi.rs | 161 +++++ src/performance/gpu/asus/mod.rs | 1 + src/performance/gpu/dbus/devices.rs | 22 +- src/performance/gpu/dbus/tdp.rs | 9 +- src/performance/gpu/intel/intelgpu.rs | 16 +- src/performance/gpu/intel/tdp.rs | 5 + src/performance/gpu/interface.rs | 4 +- src/performance/gpu/mod.rs | 10 +- src/performance/gpu/tdp.rs | 5 +- 20 files changed, 972 insertions(+), 444 deletions(-) rename src/performance/cpu/{cpu.rs => cpu_features.rs} (96%) create mode 100644 src/performance/gpu/acpi/firmware.rs create mode 100644 src/performance/gpu/acpi/mod.rs delete mode 100644 src/performance/gpu/amd/asus.rs create mode 100644 src/performance/gpu/amd/ryzenadj.rs create mode 100644 src/performance/gpu/asus/asus_wmi.rs create mode 100644 src/performance/gpu/asus/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 44f1c69..b40abd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,12 @@ pciutils = "*" [dependencies] libryzenadj = { git = "https://gitlab.com/shadowapex/libryzenadj-rs.git" } -#libryzenadj = "0.13.0" log = "0.4.20" simple_logger = "4.2.0" tokio = { version = "*", features = ["full"] } zbus = { version = "3.14.1", default-features = false, features = ["tokio"] } zbus_macros = "3.14.1" +rog_platform = { git = "https://gitlab.com/asus-linux/asusctl.git", default-features = true } [profile.release] debug = false diff --git a/src/main.rs b/src/main.rs index c96d3d5..26d4973 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,11 +3,8 @@ use std::{error::Error, future::pending}; use zbus::Connection; use crate::constants::{BUS_NAME, CPU_PATH, GPU_PATH}; -use crate::dbus::gpu::get_connectors; -use crate::dbus::gpu::get_gpus; -use crate::dbus::gpu::GPUBus; -use crate::performance::cpu::cpu; -use crate::performance::gpu::dbus; +use crate::dbus::gpu::{get_connectors, get_gpus, GPUBus}; +use crate::performance::{cpu::cpu_features, gpu::dbus}; mod constants; mod performance; @@ -19,8 +16,8 @@ async fn main() -> Result<(), Box> { log::info!("Starting PowerStation v{}", VERSION); // Discover all CPUs - let cpu = cpu::CPU::new(); - let cores = cpu::get_cores(); + let cpu = cpu_features::Cpu::new(); + let cores = cpu_features::get_cores(); // Configure the connection let connection = Connection::system().await?; @@ -46,7 +43,7 @@ async fn main() -> Result<(), Box> { Some(tdp) => { log::debug!("Discovered TDP interface on card: {}", card_name); connection.object_server().at(gpu_path.clone(), tdp).await?; - }, + } None => { log::warn!("Card {} does not have a TDP interface", card_name); } diff --git a/src/performance/cpu/cpu.rs b/src/performance/cpu/cpu_features.rs similarity index 96% rename from src/performance/cpu/cpu.rs rename to src/performance/cpu/cpu_features.rs index fe94037..cb4f984 100644 --- a/src/performance/cpu/cpu.rs +++ b/src/performance/cpu/cpu_features.rs @@ -14,14 +14,14 @@ const SMT_PATH: &str = "/sys/devices/system/cpu/smt/control"; const BOOST_PATH: &str = "/sys/devices/system/cpu/cpufreq/boost"; // Instance of the CPU on the host machine -pub struct CPU { +pub struct Cpu { core_map: HashMap>, core_count: u32, } -impl CPU { +impl Cpu { // Returns a new CPU instance - pub fn new() -> CPU { + pub fn new() -> Cpu { // Create a hashmap to organize the cores by their core ID let mut core_map: HashMap> = HashMap::new(); let mut cores = get_cores(); @@ -40,17 +40,17 @@ impl CPU { for core in cores { core_count += 1; let core_id = core.core_id().unwrap(); - if core_map.get(&core_id).is_none() { + core_map.entry(core_id).or_insert_with(|| { let list: Vec = Vec::new(); - core_map.insert(core_id, list); - } + list + }); let list = core_map.get_mut(&core_id).unwrap(); list.push(core); } log::info!("Core Map: {:?}", core_map); - CPU { + Cpu { core_map, core_count, } @@ -58,7 +58,7 @@ impl CPU { } #[dbus_interface(name = "org.shadowblip.CPU")] -impl CPU { +impl Cpu { // Returns whether or not boost is enabled #[dbus_interface(property)] pub async fn boost_enabled(&self) -> fdo::Result { @@ -142,7 +142,7 @@ impl CPU { /// Returns the total number of CPU cores detected #[dbus_interface(property)] pub async fn cores_count(&self) -> fdo::Result { - Ok(self.core_count.clone()) + Ok(self.core_count) } #[dbus_interface(property)] @@ -286,13 +286,11 @@ async fn get_features() -> fdo::Result> { pub fn get_cores() -> Vec { let mut cores: Vec = Vec::new(); let paths = std::fs::read_dir(CPUID_PATH).unwrap(); - let mut i = 0; - for path in paths { + for (i, path) in paths.enumerate() { log::info!("Discovered core: {}", path.unwrap().path().display()); let core_path = format!("/sys/bus/cpu/devices/cpu{0}", i); - let core = CPUCore::new(i, core_path); + let core = CPUCore::new(i as u32, core_path); cores.push(core); - i += 1; } cores diff --git a/src/performance/cpu/mod.rs b/src/performance/cpu/mod.rs index 730e502..7589f61 100644 --- a/src/performance/cpu/mod.rs +++ b/src/performance/cpu/mod.rs @@ -1,2 +1,2 @@ pub mod core; -pub mod cpu; +pub mod cpu_features; diff --git a/src/performance/gpu/acpi/firmware.rs b/src/performance/gpu/acpi/firmware.rs new file mode 100644 index 0000000..07ea0b0 --- /dev/null +++ b/src/performance/gpu/acpi/firmware.rs @@ -0,0 +1,125 @@ +use crate::performance::gpu::tdp::{TDPError, TDPResult}; + +use std::fs; + +const PLATFORM_PROFILE_PATH: &str = "/sys/firmware/acpi/platform_profile"; +const PLATFORM_PROFILES_AVAIAL_PATH: &str = "/sys/firmware/acpi/platform_profile_choices"; + +/// Implementation of acpi sysfs +pub struct Acpi {} + +impl Acpi { + /// Check if ACPI supports platform profiles on this device + pub async fn new() -> Option { + if fs::metadata(PLATFORM_PROFILE_PATH).is_err() + || fs::metadata(PLATFORM_PROFILES_AVAIAL_PATH).is_err() + { + return None; + } + Some(Self {}) + } + + /// Reads the currently set power profile + pub async fn power_profile(&self) -> TDPResult { + match fs::read(PLATFORM_PROFILE_PATH) { + Ok(data) => { + let profile = match String::from_utf8(data) { + Ok(profile) => profile.split_whitespace().collect(), + Err(e) => { + return Err(TDPError::IOError(format!( + "Failed to convert utf8 data to string: {e:?}" + ))) + } + }; + + log::info!("Platform profile is currently set to {profile}"); + Ok(profile) + } + Err(e) => Err(TDPError::IOError(format!( + "Failed to read platform profile: {e:?}" + ))), + } + } + + /// Returns a list of valid power profiles for this interface + pub async fn power_profiles_available(&self) -> TDPResult> { + match fs::read(PLATFORM_PROFILES_AVAIAL_PATH) { + Ok(data) => { + let profiles_raw = match String::from_utf8(data) { + Ok(profile) => profile, + Err(e) => { + return Err(TDPError::IOError(format!( + "Failed to convert utf8 data to string: {e:?}" + ))) + } + }; + let mut profiles = Vec::new(); + for profile in profiles_raw.split_whitespace() { + profiles.push(profile.to_string()); + } + + log::info!("Available platform profiles: {profiles:?}"); + Ok(profiles) + } + Err(e) => Err(TDPError::IOError(format!( + "Failed to read platform profile: {e:?}" + ))), + } + } + + /// Sets the power profile to the given value + pub async fn set_power_profile(&mut self, profile: String) -> TDPResult<()> { + // Get the current profile so we can check if it needs to be set. + let current = self.power_profile().await?; + if current == profile { + return Ok(()); + } + + let valid_profiles = self.power_profiles_available().await?; + //TODO: This supports a legacy interface from when only RyzenAdj was supported. Once + //OpenGamepadUI is updated to use the new power_profiles_available methods, switch to + //only returning and error here. + if !valid_profiles.contains(&profile) { + log::warn!("Incompatible profile requested: {profile}. Attempting to translate to valid profile."); + match profile.as_str() { + "max-performance" => match fs::write(PLATFORM_PROFILE_PATH, "performance") { + Ok(_) => { + log::info!("Set platform perfomance profile to performance"); + return Ok(()); + } + Err(e) => { + return Err(TDPError::IOError(format!( + "Failed to set power profile: {e:?}" + ))) + } + }, + "power-saving" => match fs::write(PLATFORM_PROFILE_PATH, "balanced") { + Ok(_) => { + log::info!("Set platform perfomance profile to balanced"); + return Ok(()); + } + Err(e) => { + return Err(TDPError::IOError(format!( + "Failed to set power profile: {e:?}" + ))) + } + }, + _ => { + return Err(TDPError::InvalidArgument(format!( + "{profile} is not a valid profile for Asus WMI." + ))) + } + }; + }; + + match fs::write(PLATFORM_PROFILE_PATH, profile.clone()) { + Ok(_) => { + log::info!("Set platform perfomance profile to {profile}"); + Ok(()) + } + Err(e) => Err(TDPError::IOError(format!( + "Failed to set power profile: {e:?}" + ))), + } + } +} diff --git a/src/performance/gpu/acpi/mod.rs b/src/performance/gpu/acpi/mod.rs new file mode 100644 index 0000000..1b67bc8 --- /dev/null +++ b/src/performance/gpu/acpi/mod.rs @@ -0,0 +1 @@ +pub mod firmware; diff --git a/src/performance/gpu/amd/amdgpu.rs b/src/performance/gpu/amd/amdgpu.rs index 171c3e5..9a4db2c 100644 --- a/src/performance/gpu/amd/amdgpu.rs +++ b/src/performance/gpu/amd/amdgpu.rs @@ -7,10 +7,12 @@ use std::{ use tokio::sync::Mutex; use crate::constants::GPU_PATH; -use crate::performance::gpu::amd; -use crate::performance::gpu::dbus::devices::TDPDevices; -use crate::performance::gpu::interface::GPUDevice; -use crate::performance::gpu::interface::{GPUError, GPUResult}; +use crate::performance::gpu::{ + dbus::devices::TDPDevices, + interface::{GPUDevice, GPUError, GPUResult}, +}; + +use super::tdp::Tdp; #[derive(Debug, Clone)] pub struct AmdGpu { @@ -36,12 +38,10 @@ impl GPUDevice for AmdGpu { /// Returns the TDP DBus interface for this GPU async fn get_tdp_interface(&self) -> Option>> { - // if asusd is present, or asus-wmi is present this is where it is bound to the GPU match self.class.as_str() { - "integrated" => Some(Arc::new(Mutex::new(TDPDevices::Amd(amd::tdp::Tdp::new( - self.path.clone(), - self.device_id.clone(), - ))))), + "integrated" => Some(Arc::new(Mutex::new(TDPDevices::Amd( + Tdp::new(self.path.as_str(), self.device_id.as_str()).await, + )))), _ => None, } } diff --git a/src/performance/gpu/amd/asus.rs b/src/performance/gpu/amd/asus.rs deleted file mode 100644 index ac440cc..0000000 --- a/src/performance/gpu/amd/asus.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::sync::Arc; -use udev::{Enumerator, Device}; - -use crate::performance::gpu::tdp::{TDPDevice, TDPResult, TDPError}; -use crate::performance::gpu::dbus::devices::TDPDevices; - -use zbus::{Connection, Result}; - -use rog_dbus::RogDbusClientBlocking; -use rog_dbus::DbusProxies; -use rog_platform::{platform::RogPlatform, error::PlatformError}; -use rog_platform::platform::{GpuMode, Properties, ThrottlePolicy}; -use rog_profiles::error::ProfileError; - -use std::sync::Mutex; - -/// Implementation of asusd with a fallback to asus-wmi sysfs -/// See https://www.kernel.org/doc/html/v6.8-rc4/admin-guide/abi-testing.html#abi-sys-devices-platform-platform-ppt-apu-sppt -pub struct ASUS { - platform: Arc>, -} - -impl ASUS { - - /// test if we are in an asus system with asus-wmi loaded - pub async fn new() -> Option { - match RogPlatform::new() { - Ok(platform) => { - log::info!("Module asus-wmi WAS found"); - Some(Self { - platform: Arc::new(Mutex::new(platform)) - }) - }, - Err(err) => { - log::info!("Module asus-wmi not found: {}", err); - None - } - } - } - -} - -impl TDPDevice for ASUS { - async fn tdp(&self) -> TDPResult { - match RogDbusClientBlocking::new() { - Ok((dbus, _)) => { - let supported_properties = dbus.proxies().platform().supported_properties().unwrap(); - let supported_interfaces = dbus.proxies().platform().supported_interfaces().unwrap(); - - match dbus.proxies().platform().ppt_apu_sppt() { - Ok(result) => { - log::info!("Initial ppt_apu_sppt: {}", result); - Ok(result as f64) - }, - Err(err) => { - log::warn!("Error fetching ppt_apu_sppt: {}", err); - Err(TDPError::FailedOperation(format!(""))) - } - } - }, - Err(err) => { - log::warn!("Unable to use asusd to read tdp, asus-wmi interface will be used"); - Err(TDPError::FailedOperation(format!(""))) - } - } - } - - async fn set_tdp(&mut self, value: f64) -> TDPResult<()> { - todo!() - } - - async fn boost(&self) -> TDPResult { - todo!() - } - - async fn set_boost(&mut self, value: f64) -> TDPResult<()> { - todo!() - } - - async fn thermal_throttle_limit_c(&self) -> TDPResult { - todo!() - } - - async fn set_thermal_throttle_limit_c(&mut self, limit: f64) -> TDPResult<()> { - todo!() - } - - async fn power_profile(&self) -> TDPResult { - todo!() - } - - async fn set_power_profile(&mut self, profile: String) -> TDPResult<()> { - todo!() - } -} diff --git a/src/performance/gpu/amd/mod.rs b/src/performance/gpu/amd/mod.rs index 6e0a02f..f939d63 100644 --- a/src/performance/gpu/amd/mod.rs +++ b/src/performance/gpu/amd/mod.rs @@ -1,2 +1,3 @@ pub mod amdgpu; +pub mod ryzenadj; pub mod tdp; diff --git a/src/performance/gpu/amd/ryzenadj.rs b/src/performance/gpu/amd/ryzenadj.rs new file mode 100644 index 0000000..bf31546 --- /dev/null +++ b/src/performance/gpu/amd/ryzenadj.rs @@ -0,0 +1,346 @@ +use std::error::Error; + +use libryzenadj::RyzenAdj; + +use crate::performance::gpu::tdp::{TDPDevice, TDPError, TDPResult}; + +/// Steam Deck GPU ID +const DEV_ID_VANGOGH: &str = "163f"; +const DEV_ID_SEPHIROTH: &str = "1435"; + +/// Implementation of TDP control for AMD GPUs +pub struct RyzenAdjTdp { + //pub path: String, + pub device_id: String, + pub profile: String, + ryzenadj: RyzenAdj, + pub unsupported_stapm_limit: f32, + pub unsupported_ppt_limit_fast: f32, + pub unsupported_thm_limit: f32, +} + +unsafe impl Sync for RyzenAdjTdp {} // implementor (RyzenAdj) may be unsafe +unsafe impl Send for RyzenAdjTdp {} // implementor (RyzenAdj) may be unsafe + +impl RyzenAdjTdp { + /// Create a new TDP instance + pub fn new(_path: String, device_id: String) -> Result> { + // Currently there is no known way to read this value + let profile = String::from("power-saving"); + + // Set fake TDP limits for GPUs that don't support ryzenadj monitoring (e.g. Steam Deck) + let unsupported_stapm_limit: f32 = match device_id.as_str() { + DEV_ID_VANGOGH => 12.0, + DEV_ID_SEPHIROTH => 12.0, + _ => 10.0, + }; + let unsupported_ppt_limit_fast: f32 = match device_id.as_str() { + DEV_ID_VANGOGH => 15.0, + DEV_ID_SEPHIROTH => 15.0, + _ => 10.0, + }; + let unsupported_thm_limit: f32 = match device_id.as_str() { + DEV_ID_VANGOGH => 95.0, + DEV_ID_SEPHIROTH => 95.0, + _ => 95.0, + }; + let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; + + Ok(RyzenAdjTdp { + //path, + device_id, + profile, + ryzenadj, + unsupported_stapm_limit, + unsupported_ppt_limit_fast, + unsupported_thm_limit, + }) + } + + /// Returns true if ryzenadj cannot read values from the given GPU + fn is_unsupported_gpu(&self) -> bool { + matches!(self.device_id.as_str(), DEV_ID_VANGOGH | DEV_ID_SEPHIROTH) + } + + /// Set the current Slow PPT limit using ryzenadj + fn set_ppt_limit_slow(&mut self, value: u32) -> Result<(), String> { + log::debug!("Setting slow ppt limit to {}", value); + match self.ryzenadj.set_slow_limit(value) { + Ok(x) => Ok(x), + Err(e) => { + let err = format!("Failed to set slow ppt limit: {}", e); + log::error!("{}", err); + Err(err) + } + } + } + + // Get the PPT slow limit + fn get_ppt_limit_slow(&self) -> Result { + log::debug!("Getting ppt slow limit"); + match self.ryzenadj.get_slow_limit() { + Ok(x) => Ok(x), + Err(e) => { + let err = format!("Failed to get ppt slow limit: {}", e); + log::error!("{}", err); + Err(err) + } + } + } + + /// Set the current Fast PPT limit using ryzenadj + fn set_ppt_limit_fast(&mut self, value: u32) -> Result<(), String> { + log::debug!("Setting fast ppt limit to {}", value); + match self.ryzenadj.set_fast_limit(value) { + Ok(x) => { + // Save the new value for APU's that can't read this attribute. + if self.is_unsupported_gpu() { + self.unsupported_ppt_limit_fast = value as f32; + } + Ok(x) + } + Err(e) => { + let err = format!("Failed to set fast ppt limit: {}", e); + log::error!("{}", err); + Err(err) + } + } + } + + /// Get the PPT fast limit + fn _get_ppt_limit_fast(&self) -> Result { + log::debug!("Getting ppt fast limit"); + + // Return what we last set the value to for APU's that can't read this + // attribute. + if self.is_unsupported_gpu() { + return Ok(self.unsupported_ppt_limit_fast); + } + + // Get the fast limit from ryzenadj + match self.ryzenadj.get_fast_limit() { + Ok(x) => { + log::debug!("Got fast limit: {}", x); + Ok(x) + } + Err(e) => { + let err = format!("Failed to get ppt fast limit: {}", e); + log::error!("{}", err); + Err(err) + } + } + } + + /// Set the current TDP value using ryzenadj + fn set_stapm_limit(&mut self, value: u32) -> Result<(), String> { + log::debug!("Setting stapm limit to {}", value); + match self.ryzenadj.set_stapm_limit(value) { + Ok(x) => { + log::debug!("Set stapm limit to {}", value); + // Save the new value for APU's that can't read this attribute. + if self.is_unsupported_gpu() { + self.unsupported_stapm_limit = value as f32; + } + Ok(x) + } + Err(e) => { + let err = format!("Failed to set stapm limit: {}", e); + log::error!("{}", err); + Err(err) + } + } + } + + /// Returns the current TDP value using ryzenadj + fn get_stapm_limit(&self) -> Result { + log::debug!("Getting stapm limit"); + + // Return what we last set the value to for APU's that can't read this + // attribute. + if self.is_unsupported_gpu() { + return Ok(self.unsupported_stapm_limit); + } + + // Get the value from ryzenadj + match self.ryzenadj.get_stapm_limit() { + Ok(x) => { + log::debug!("Got stapm limit: {}", x); + Ok(x) + } + Err(e) => { + let err = format!("Failed to get stapm limit: {}", e); + log::error!("{}", err); + Err(err) + } + } + } + + // Sets the thermal limit value using ryzenadj + fn set_thm_limit(&mut self, value: u32) -> Result<(), String> { + log::debug!("Setting thm limit to: {}", value); + match self.ryzenadj.set_tctl_temp(value) { + Ok(x) => { + // Save the new value for APU's that can't read this attribute. + if self.is_unsupported_gpu() { + self.unsupported_thm_limit = value as f32; + } + Ok(x) + } + + Err(e) => { + let err = format!("Failed to set tctl limit: {}", e); + log::error!("{}", err); + Err(err) + } + } + } + + /// Returns the current thermal limit value using ryzenadj + fn get_thm_limit(&self) -> Result { + log::debug!("Getting thm limit"); + + // Return what we last set the value to for APU's that can't read this + // attribute. + if self.is_unsupported_gpu() { + return Ok(self.unsupported_thm_limit); + } + + // Get the value from ryzenadj + match self.ryzenadj.get_tctl_temp() { + Ok(x) => Ok(x), + Err(e) => { + let err = format!("Failed to get tctl temp: {}", e); + log::error!("{}", err); + Err(err) + } + } + } + + /// Set the power profile to the given profile + fn set_power_profile(&self, profile: String) -> Result<(), String> { + log::debug!("Setting power profile"); + match profile.as_str() { + "power-saving" => self + .ryzenadj + .set_power_saving() + .map_err(|err| err.to_string()), + "max-performance" => self + .ryzenadj + .set_max_performance() + .map_err(|err| err.to_string()), + _ => Err(String::from( + "Invalid power profile. Must be in [max-performance, power-saving]", + )), + } + } +} + +impl TDPDevice for RyzenAdjTdp { + async fn tdp(&self) -> TDPResult { + // Get the current stapm limit from ryzenadj + match RyzenAdjTdp::get_stapm_limit(self) { + Ok(result) => Ok(result.into()), + Err(err) => Err(TDPError::FailedOperation(err.to_string())), + } + } + + async fn set_tdp(&mut self, value: f64) -> TDPResult<()> { + log::debug!("Setting TDP to: {}", value); + if value < 1.0 { + log::warn!("Cowardly refusing to set TDP less than 1W"); + return Err(TDPError::InvalidArgument(format!( + "Cowardly refusing to set TDP less than 1W: provided {}W", + value + ))); + } + + // Get the current boost value before updating the STAPM limit. We will + // use this value to also adjust the Fast PPT Limit. + let boost = match self.boost().await { + Ok(boost) => boost, + Err(e) => return Err(e), + }; + + // Update the STAPM limit with the TDP value + let limit: u32 = (value * 1000.0) as u32; + RyzenAdjTdp::set_stapm_limit(self, limit).map_err(TDPError::FailedOperation)?; + + // Update the s/fppt values with the new TDP + self.set_boost(boost).await?; + + Ok(()) + } + + async fn boost(&self) -> TDPResult { + let slow_ppt_limit = + RyzenAdjTdp::get_ppt_limit_slow(self).map_err(TDPError::FailedOperation)? as f64; + let stapm_limit = + RyzenAdjTdp::get_stapm_limit(self).map_err(TDPError::FailedOperation)? as f64; + + // TODO: Is this a bug in ryzenadj? Sometimes it is ~0 + if slow_ppt_limit < 1.0 { + log::warn!("Got a slow limit less than 1. Setting boost to 0"); + return Ok(0.0); + } + + let boost = slow_ppt_limit - stapm_limit; + Ok(boost) + } + + async fn set_boost(&mut self, value: f64) -> TDPResult<()> { + log::debug!("Setting boost to: {}", value); + if value < 0.0 { + log::warn!("Cowardly refusing to set TDP Boost less than 0W"); + return Err(TDPError::InvalidArgument(format!( + "Cowardly refusing to set TDP Boost less than 0W: {}W provided", + value + ))); + } + + // Get the STAPM Limit so we can calculate what S/FPPT limits to set. + let stapm_limit = + RyzenAdjTdp::get_stapm_limit(self).map_err(TDPError::FailedOperation)? as f64; + + // Set the new slow PPT limit + let slow_ppt_limit = ((stapm_limit + value) * 1000.0) as u32; + RyzenAdjTdp::set_ppt_limit_slow(self, slow_ppt_limit).map_err(TDPError::FailedOperation)?; + + // Set the new fast PPT limit + let fast_ppt_limit = ((stapm_limit + value) * 1250.0) as u32; + RyzenAdjTdp::set_ppt_limit_fast(self, fast_ppt_limit).map_err(TDPError::FailedOperation)?; + + Ok(()) + } + + async fn thermal_throttle_limit_c(&self) -> TDPResult { + let limit = RyzenAdjTdp::get_thm_limit(self) + .map_err(|err| TDPError::FailedOperation(err.to_string()))?; + Ok(limit.into()) + } + + async fn set_thermal_throttle_limit_c(&mut self, limit: f64) -> TDPResult<()> { + log::debug!("Setting thermal throttle limit to: {}", limit); + let limit = limit as u32; + RyzenAdjTdp::set_thm_limit(self, limit) + .map_err(|err| TDPError::FailedOperation(err.to_string())) + } + + async fn power_profile(&self) -> TDPResult { + Ok(self.profile.clone()) + } + + async fn set_power_profile(&mut self, profile: String) -> TDPResult<()> { + log::debug!("Setting power profile to: {}", profile); + RyzenAdjTdp::set_power_profile(self, profile.clone()) + .map_err(|err| TDPError::FailedOperation(err.to_string()))?; + self.profile = profile; + Ok(()) + } + + async fn power_profiles_available(&self) -> TDPResult> { + Ok(vec![ + "max-performance".to_string(), + "power-saving".to_string(), + ]) + } +} diff --git a/src/performance/gpu/amd/tdp.rs b/src/performance/gpu/amd/tdp.rs index 7d0db28..fe634b8 100644 --- a/src/performance/gpu/amd/tdp.rs +++ b/src/performance/gpu/amd/tdp.rs @@ -1,332 +1,312 @@ -use libryzenadj::RyzenAdj; +use crate::performance::gpu::{ + acpi::firmware::Acpi, + asus::asus_wmi::AsusWmi, + tdp::{TDPDevice, TDPError, TDPResult}, +}; -use crate::performance::gpu::tdp::{TDPDevice, TDPError, TDPResult}; - -/// Steam Deck GPU ID -const DEV_ID_VANGOGH: &str = "163f"; -const DEV_ID_SEPHIROTH: &str = "1435"; +use super::ryzenadj::RyzenAdjTdp; /// Implementation of TDP control for AMD GPUs pub struct Tdp { - //pub path: String, - pub device_id: String, - pub profile: String, - pub unsupported_stapm_limit: f32, - pub unsupported_ppt_limit_fast: f32, - pub unsupported_thm_limit: f32, + asus_wmi: Option, + acpi: Option, + ryzenadj: Option, } -unsafe impl Sync for Tdp {} // implementor (RyzenAdj) may be unsafe -unsafe impl Send for Tdp {} // implementor (RyzenAdj) may be unsafe - impl Tdp { - /// Create a new TDP instance - pub fn new(_path: String, device_id: String) -> Tdp { - // Currently there is no known way to read this value - let profile = String::from("power-saving"); - - // Set fake TDP limits for GPUs that don't support ryzenadj monitoring (e.g. Steam Deck) - let unsupported_stapm_limit: f32 = match device_id.as_str() { - DEV_ID_VANGOGH => 12.0, - DEV_ID_SEPHIROTH => 12.0, - _ => 10.0, - }; - let unsupported_ppt_limit_fast: f32 = match device_id.as_str() { - DEV_ID_VANGOGH => 15.0, - DEV_ID_SEPHIROTH => 15.0, - _ => 10.0, - }; - let unsupported_thm_limit: f32 = match device_id.as_str() { - DEV_ID_VANGOGH => 95.0, - DEV_ID_SEPHIROTH => 95.0, - _ => 95.0, - }; - - Tdp { - //path, - device_id, - profile, - unsupported_stapm_limit, - unsupported_ppt_limit_fast, - unsupported_thm_limit, - } - } - - /// Returns true if ryzenadj cannot read values from the given GPU - fn is_unsupported_gpu(&self) -> bool { - matches!(self.device_id.as_str(), DEV_ID_VANGOGH | DEV_ID_SEPHIROTH) - } - - /// Set the current Slow PPT limit using ryzenadj - fn set_ppt_limit_slow(&mut self, value: u32) -> Result<(), String> { - log::debug!("Setting slow ppt limit to {}", value); - let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - match ryzenadj.set_slow_limit(value) { - Ok(x) => Ok(x), - Err(e) => { - let err = format!("Failed to set slow ppt limit: {}", e); - log::error!("{}", err); - Err(err) - } - } - } - - //// Get the PPT slow limit - //fn get_ppt_limit_slow(&self) -> Result { - // log::debug!("Getting ppt slow limit"); - // let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - // match ryzenadj.get_slow_limit() { - // Ok(x) => Ok(x), - // Err(e) => { - // let err = format!("Failed to get ppt slow limit: {}", e); - // log::error!("{}", err); - // Err(String::from(err)) - // } - // } - //} - - /// Set the current Fast PPT limit using ryzenadj - fn set_ppt_limit_fast(&mut self, value: u32) -> Result<(), String> { - log::debug!("Setting fast ppt limit to {}", value); - if self.is_unsupported_gpu() { - self.unsupported_ppt_limit_fast = value as f32; - } - let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - match ryzenadj.set_fast_limit(value) { - Ok(x) => Ok(x), - Err(e) => { - let err = format!("Failed to set fast ppt limit: {}", e); - log::error!("{}", err); - Err(err) - } - } - } - - /// Get the PPT fast limit - fn get_ppt_limit_fast(&self) -> Result { - log::debug!("Getting ppt fast limit"); - - // Return what we _think_ the value is for unsupported GPUs - if self.is_unsupported_gpu() { - return Ok(self.unsupported_ppt_limit_fast); - } - - // Get the fast limit from ryzenadj - let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - match ryzenadj.get_fast_limit() { - Ok(x) => { - log::debug!("Got fast limit: {}", x); - Ok(x) - } - Err(e) => { - let err = format!("Failed to get ppt fast limit: {}", e); - log::error!("{}", err); - Err(err) - } - } - } - - /// Set the current TDP value using ryzenadj - fn set_stapm_limit(&mut self, value: u32) -> Result<(), String> { - log::debug!("Setting stapm limit to {}", value); - if self.is_unsupported_gpu() { - self.unsupported_stapm_limit = value as f32; - } - let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - match ryzenadj.set_stapm_limit(value) { - Ok(x) => { - log::debug!("Set stapm limit to {}", value); - Ok(x) - } - Err(e) => { - let err = format!("Failed to set stapm limit: {}", e); - log::error!("{}", err); - Err(err) + pub async fn new(path: &str, device_id: &str) -> Tdp { + let asus_wmi = match AsusWmi::new().await { + Some(asus_wmi) => { + log::info!("Found Asus WMI interface for TDP control"); + Some(asus_wmi) } - } - } - - /// Returns the current TDP value using ryzenadj - fn get_stapm_limit(&self) -> Result { - log::debug!("Getting stapm limit"); - - // Return what we _think_ the value is for unsupported GPUs - if self.is_unsupported_gpu() { - return Ok(self.unsupported_stapm_limit); - } + None => None, + }; - // Get the value from ryzenadj - let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - match ryzenadj.get_stapm_limit() { - Ok(x) => { - log::debug!("Got stapm limit: {}", x); - Ok(x) - } - Err(e) => { - let err = format!("Failed to get stapm limit: {}", e); - log::error!("{}", err); - Err(err) + let acpi = match Acpi::new().await { + Some(acpi) => { + log::info!("Found ACPI interface for platform profile control"); + Some(acpi) } - } - } + None => None, + }; - // Sets the thermal limit value using ryzenadj - fn set_thm_limit(&mut self, value: u32) -> Result<(), String> { - log::debug!("Setting thm limit to: {}", value); - if self.is_unsupported_gpu() { - self.unsupported_thm_limit = value as f32; - } - let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - match ryzenadj.set_tctl_temp(value) { - Ok(x) => Ok(x), - Err(e) => { - let err = format!("Failed to set tctl limit: {}", e); - log::error!("{}", err); - Err(err) + let ryzenadj = match RyzenAdjTdp::new(path.to_string(), device_id.to_string()) { + Ok(ryzenadj) => { + log::info!("Found RyzenAdj interface for TDP control"); + Some(ryzenadj) } - } - } - - /// Returns the current thermal limit value using ryzenadj - fn get_thm_limit(&self) -> Result { - log::debug!("Getting thm limit"); - - // Return what we _think_ the value is for unsupported GPUs - if self.is_unsupported_gpu() { - return Ok(self.unsupported_thm_limit); - } - - // Get the value from ryzenadj - let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - match ryzenadj.get_tctl_temp() { - Ok(x) => Ok(x), Err(e) => { - let err = format!("Failed to get tctl temp: {}", e); - log::error!("{}", err); - Err(err) + log::warn!("Failed to create Ryzenadj Instance: {e:?}"); + None } - } - } + }; - /// Set the power profile to the given profile - fn set_power_profile(&self, profile: String) -> Result<(), String> { - log::debug!("Setting power profile"); - let ryzenadj = RyzenAdj::new().map_err(|err| err.to_string())?; - match profile.as_str() { - "power-saving" => ryzenadj.set_power_saving().map_err(|err| err.to_string()), - "max-performance" => ryzenadj - .set_max_performance() - .map_err(|err| err.to_string()), - _ => Err(String::from( - "Invalid power profile. Must be in [max-performance, power-saving]", - )), + Tdp { + asus_wmi, + acpi, + ryzenadj, } } } impl TDPDevice for Tdp { async fn tdp(&self) -> TDPResult { - // Get the current stapm limit from ryzenadj - match Tdp::get_stapm_limit(self) { - Ok(result) => Ok(result.into()), - Err(err) => Err(TDPError::FailedOperation(err.to_string())), - } + log::info!("Get TDP"); + + // TODO: set platform profile based on % of max TDP. + if self.asus_wmi.is_some() { + let asus_wmi = self.asus_wmi.as_ref().unwrap(); + match asus_wmi.tdp().await { + Ok(tdp) => { + log::info!("TDP is currently {tdp}"); + return Ok(tdp); + } + Err(e) => { + log::warn!("Failed to read current TDP using Asus WMI: {e:?}"); + } + }; + }; + // TODO: set platform profile based on % of max TDP. + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_ref().unwrap(); + match ryzenadj.tdp().await { + Ok(tdp) => { + log::info!("TDP is currently {tdp}"); + return Ok(tdp); + } + Err(e) => { + log::warn!("Failed to read current TDP using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to read TDP.".into(), + )) } async fn set_tdp(&mut self, value: f64) -> TDPResult<()> { - log::debug!("Setting TDP to: {}", value); - if value < 1.0 { - log::warn!("Cowardly refusing to set TDP less than 1W"); - return Err(TDPError::InvalidArgument(format!( - "Cowardly refusing to set TDP less than 1W: provided {}W", - value - ))); - } - - // Get the current boost value before updating the STAPM limit. We will - // use this value to also adjust the Fast PPT Limit. - let fast_ppt_limit = Tdp::get_ppt_limit_fast(self).map_err(TDPError::FailedOperation)?; - let mut fast_ppt_limit = fast_ppt_limit as f64; - let stapm_limit = Tdp::get_stapm_limit(self).map_err(TDPError::FailedOperation)?; - let stapm_limit = stapm_limit as f64; - - // TODO: Is this a bug in ryzenadj? Sometimes fast_ppt_limit is ~0 - if fast_ppt_limit < 1.0 { - log::warn!("Got a fast limit less than 1. Possible ryzenadj bug?"); - fast_ppt_limit = stapm_limit; - } - - let boost = fast_ppt_limit - stapm_limit; - log::debug!("Current boost value is: {}", boost); - - // Update the STAPM limit with the TDP value - let limit: u32 = (value * 1000.0) as u32; - Tdp::set_stapm_limit(self, limit).map_err(TDPError::FailedOperation)?; - - // Also update the slow PPT limit - Tdp::set_ppt_limit_slow(self, limit).map_err(TDPError::FailedOperation)?; - - // After successfully setting the STAPM limit, we also need to adjust the - // Fast PPT Limit accordingly so it is *boost* distance away. - let fast_ppt_limit = ((value + boost) * 1000.0) as u32; - Tdp::set_ppt_limit_fast(self, fast_ppt_limit).map_err(TDPError::FailedOperation)?; - - Ok(()) + log::info!("Set TDP"); + if self.asus_wmi.is_some() { + let asus_wmi = self.asus_wmi.as_mut().unwrap(); + match asus_wmi.set_tdp(value).await { + Ok(_) => { + log::info!("TDP set to {value}"); + return Ok(()); + } + Err(e) => { + log::warn!("Failed to set TDP using Asus WMI: {e:?}"); + } + }; + }; + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_mut().unwrap(); + match ryzenadj.set_tdp(value).await { + Ok(_) => { + log::info!("TDP set to {value}"); + return Ok(()); + } + Err(e) => { + log::warn!("Failed to set TDP using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to set TDP.".into(), + )) } async fn boost(&self) -> TDPResult { - let fast_ppt_limit = Tdp::get_ppt_limit_fast(self).map_err(TDPError::FailedOperation)?; - let fast_ppt_limit = fast_ppt_limit as f64; - let stapm_limit = Tdp::get_stapm_limit(self).map_err(TDPError::FailedOperation)?; - let stapm_limit = stapm_limit as f64; - - let boost = fast_ppt_limit - stapm_limit; - - Ok(boost) + log::info!("Get TDP Boost"); + if self.asus_wmi.is_some() { + let asus_wmi = self.asus_wmi.as_ref().unwrap(); + match asus_wmi.boost().await { + Ok(boost) => { + log::info!("Boost is currently {boost}"); + return Ok(boost); + } + Err(e) => { + log::warn!("Failed to read current boost using Asus WMI: {e:?}"); + } + }; + }; + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_ref().unwrap(); + match ryzenadj.boost().await { + Ok(boost) => { + log::info!("Boost is currently {boost}"); + return Ok(boost); + } + Err(e) => { + log::warn!("Failed to read current boost using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to read boost.".into(), + )) } async fn set_boost(&mut self, value: f64) -> TDPResult<()> { - log::debug!("Setting boost to: {}", value); - if value < 0.0 { - log::warn!("Cowardly refusing to set TDP Boost less than 0W"); - return Err(TDPError::InvalidArgument(format!( - "Cowardly refusing to set TDP Boost less than 0W: {}W provided", - value - ))); - } - - // Get the STAPM Limit so we can calculate what Fast PPT Limit to set. - let stapm_limit = Tdp::get_stapm_limit(self).map_err(TDPError::FailedOperation)?; - let stapm_limit = stapm_limit as f64; - - // Set the new fast ppt limit - let fast_ppt_limit = ((stapm_limit + value) * 1000.0) as u32; - Tdp::set_ppt_limit_fast(self, fast_ppt_limit).map_err(TDPError::FailedOperation)?; - - Ok(()) + log::info!("Set TDP Boost"); + if self.asus_wmi.is_some() { + let asus_wmi = self.asus_wmi.as_mut().unwrap(); + match asus_wmi.set_boost(value).await { + Ok(_) => { + log::info!("Boost set to {value}"); + return Ok(()); + } + Err(e) => { + log::warn!("Failed to set boost using Asus WMI: {e:?}"); + } + }; + }; + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_mut().unwrap(); + match ryzenadj.set_boost(value).await { + Ok(_) => { + log::info!("Boost set to {value}"); + return Ok(()); + } + Err(e) => { + log::warn!("Failed to set boost using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to set boost.".into(), + )) } async fn thermal_throttle_limit_c(&self) -> TDPResult { - let limit = - Tdp::get_thm_limit(self).map_err(|err| TDPError::FailedOperation(err.to_string()))?; - Ok(limit.into()) + log::info!("Get tctl limit"); + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_ref().unwrap(); + match ryzenadj.thermal_throttle_limit_c().await { + Ok(limit) => { + log::info!("Thermal throttle limit is currently {limit}"); + return Ok(limit); + } + Err(e) => { + log::warn!("Failed to read thermal trottle limit using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to read thermal throttle limit.".into(), + )) } async fn set_thermal_throttle_limit_c(&mut self, limit: f64) -> TDPResult<()> { - log::debug!("Setting thermal throttle limit to: {}", limit); - let limit = limit as u32; - Tdp::set_thm_limit(self, limit).map_err(|err| TDPError::FailedOperation(err.to_string())) + log::info!("Set tctl limit"); + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_mut().unwrap(); + match ryzenadj.set_thermal_throttle_limit_c(limit).await { + Ok(_) => { + log::info!("Thermal throttle limit was set to {:e}", limit as i32); + return Ok(()); + } + Err(e) => { + log::warn!("Failed to set thermal trottle limit using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to set thermal throttle limit.".into(), + )) } async fn power_profile(&self) -> TDPResult { - Ok(self.profile.clone()) + log::info!("Get power_profile"); + if self.acpi.is_some() { + let acpi = self.acpi.as_ref().unwrap(); + match acpi.power_profile().await { + Ok(profile) => { + log::info!("Power profile is currently {profile}"); + return Ok(profile); + } + Err(e) => { + log::warn!("Failed to read power profile using ACPI: {e:?}"); + } + }; + }; + + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_ref().unwrap(); + match ryzenadj.power_profile().await { + Ok(profile) => { + log::info!("Power profile is currently {profile}"); + return Ok(profile); + } + Err(e) => { + log::warn!("Failed to read power profile using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to read power profile.".into(), + )) } async fn set_power_profile(&mut self, profile: String) -> TDPResult<()> { - log::debug!("Setting power profile to: {}", profile); - Tdp::set_power_profile(self, profile.clone()) - .map_err(|err| TDPError::FailedOperation(err.to_string()))?; - self.profile = profile; - Ok(()) + log::info!("Set power_profile"); + if self.acpi.is_some() { + let acpi = self.acpi.as_mut().unwrap(); + match acpi.set_power_profile(profile.clone()).await { + Ok(_) => { + log::info!("Power profile was set to {profile}"); + return Ok(()); + } + Err(e) => { + log::warn!("Failed to set power profile using ACPI: {e:?}"); + } + }; + }; + + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_mut().unwrap(); + match ryzenadj.set_power_profile(profile.clone()).await { + Ok(_) => { + log::info!("Power profile was set to {profile}"); + return Ok(()); + } + Err(e) => { + log::warn!("Failed to set power profile using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to set power profile.".into(), + )) + } + + async fn power_profiles_available(&self) -> TDPResult> { + if self.acpi.is_some() { + let acpi = self.acpi.as_ref().unwrap(); + match acpi.power_profiles_available().await { + Ok(profiles) => { + log::info!("Available power profiles are {profiles:?}"); + return Ok(profiles); + } + Err(e) => { + log::warn!("Failed to read available power profiles using ACPI: {e:?}"); + } + }; + }; + if self.ryzenadj.is_some() { + let ryzenadj = self.ryzenadj.as_ref().unwrap(); + match ryzenadj.power_profiles_available().await { + Ok(profiles) => { + log::info!("Available power profiles are {profiles:?}"); + return Ok(profiles); + } + Err(e) => { + log::warn!("Failed to read available power profiles using RyzenAdj: {e:?}"); + } + }; + }; + Err(TDPError::FailedOperation( + "No TDP Interface available to list available power profiles.".into(), + )) } } diff --git a/src/performance/gpu/asus/asus_wmi.rs b/src/performance/gpu/asus/asus_wmi.rs new file mode 100644 index 0000000..53e1fb0 --- /dev/null +++ b/src/performance/gpu/asus/asus_wmi.rs @@ -0,0 +1,161 @@ +use crate::performance::gpu::tdp::{TDPError, TDPResult}; + +use rog_platform::{ + firmware_attributes::{AttrValue, FirmwareAttributes}, + platform::RogPlatform, +}; + +/// Implementation of asus-wmi sysfs +/// See https://www.kernel.org/doc/html/v6.8-rc4/admin-guide/abi-testing.html#abi-sys-devices-platform-platform-ppt-apu-sppt +pub struct AsusWmi { + attributes: FirmwareAttributes, +} + +impl AsusWmi { + /// test if we are in an asus system with asus-wmi loaded + pub async fn new() -> Option { + match RogPlatform::new() { + Ok(_) => { + log::info!("Module asus-wmi found"); + Some(Self { + attributes: FirmwareAttributes::new(), + }) + } + Err(err) => { + log::info!("Module asus-wmi not found: {}", err); + None + } + } + } + + /// Returns the currently set STAPM value + pub async fn tdp(&self) -> TDPResult { + match self + .attributes + .attributes() + .iter() + .find(|a| a.name() == "ppt_pl1_spl") + .unwrap() + .current_value() + { + Ok(attr_value) => match attr_value { + AttrValue::Integer(value) => { + log::info!("Found STAPM value: {value}"); + Ok(value as f64) + } + _ => Err(TDPError::FailedOperation( + "Failed to read STAPM value".to_string(), + )), + }, + Err(e) => Err(TDPError::FailedOperation(format!( + "Failed to read STAPM Value: {e:?}" + ))), + } + } + + /// Sets STAPM to the given value and adjusts the SPPT/FPPT to maintaing the current boost + /// ratio + pub async fn set_tdp(&mut self, value: f64) -> TDPResult<()> { + // Get the current Boost value + let boost = self.boost().await?; + + // Set the STAPM value to the given TDP value + let val = AttrValue::Integer(value as i32); + match self + .attributes + .attributes_mut() + .iter() + .find(|a| a.name() == "ppt_pl1_spl") + .unwrap() + .set_current_value(val) + { + Ok(_) => { + log::info!("Set STAPM value to {value}"); + } + Err(e) => { + return Err(TDPError::FailedOperation(format!( + "Failed to set STAPM value: {e:}" + ))); + } + } + + // Set the boost back to the expeted value with the new TDP + self.set_boost(boost).await?; + + Ok(()) + } + + /// Returns the current difference between STAPM and SPPT + pub async fn boost(&self) -> TDPResult { + let stapm = match self.tdp().await { + Ok(val) => val, + Err(e) => { + return Err(e); + } + }; + + let slow_ppt = match self + .attributes + .attributes() + .iter() + .find(|a| a.name() == "ppt_platform_sppt") + .unwrap() + .current_value() + { + Ok(attr_value) => match attr_value { + AttrValue::Integer(value) => { + log::info!("Found Slow PPT value: {value}"); + value as f64 + } + _ => { + return Err(TDPError::FailedOperation( + "Failed to read Slow PPT value".to_string(), + )) + } + }, + Err(e) => { + return Err(TDPError::FailedOperation(format!( + "Failed to read Slow PPT value: {e:?}" + ))) + } + }; + + let boost = slow_ppt - stapm; + log::info!("Found current boost: {boost}"); + Ok(boost) + } + + /// Sets SPPT and FPPT to the current STAPM plus the given value + pub async fn set_boost(&mut self, value: f64) -> TDPResult<()> { + let stapm = match self.tdp().await { + Ok(val) => val, + Err(e) => { + return Err(e); + } + }; + + let sppt_val = (stapm + value) as i32; + let boost = AttrValue::Integer(sppt_val); + + // ppt_platform_sppt will set sppt to value and fppt to value + 25% + match self + .attributes + .attributes_mut() + .iter() + .find(|a| a.name() == "ppt_platform_sppt") + .unwrap() + .set_current_value(boost) + { + Ok(_) => { + log::info!("Set Slow PPT to {sppt_val}"); + } + Err(e) => { + return Err(TDPError::FailedOperation(format!( + "Failed to set Slow PPT: {e:?}" + ))) + } + }; + + Ok(()) + } +} diff --git a/src/performance/gpu/asus/mod.rs b/src/performance/gpu/asus/mod.rs new file mode 100644 index 0000000..85311d4 --- /dev/null +++ b/src/performance/gpu/asus/mod.rs @@ -0,0 +1 @@ +pub mod asus_wmi; diff --git a/src/performance/gpu/dbus/devices.rs b/src/performance/gpu/dbus/devices.rs index db87712..e8cbae6 100644 --- a/src/performance/gpu/dbus/devices.rs +++ b/src/performance/gpu/dbus/devices.rs @@ -2,15 +2,13 @@ use std::sync::Arc; use tokio::sync::Mutex; -use crate::performance::gpu::interface::GPUResult; -use crate::performance::gpu::{amd, intel}; use crate::performance::gpu::{ - interface::GPUDevice, + amd, intel, + interface::{GPUDevice, GPUResult}, tdp::{TDPDevice, TDPResult}, }; pub enum TDPDevices { - //ASUS(amd::asus::ASUS), Amd(amd::tdp::Tdp), Intel(intel::tdp::Tdp), } @@ -18,7 +16,6 @@ pub enum TDPDevices { impl TDPDevices { pub async fn tdp(&self) -> TDPResult { match self { - //Self::ASUS(dev) => dev.tdp().await, Self::Amd(dev) => dev.tdp().await, Self::Intel(dev) => dev.tdp().await, } @@ -26,7 +23,6 @@ impl TDPDevices { pub async fn set_tdp(&mut self, value: f64) -> TDPResult<()> { match self { - //Self::ASUS(dev) => dev.set_tdp(value).await, Self::Amd(dev) => dev.set_tdp(value).await, Self::Intel(dev) => dev.set_tdp(value).await, } @@ -34,7 +30,6 @@ impl TDPDevices { pub async fn boost(&self) -> TDPResult { match self { - //Self::ASUS(dev) => dev.boost().await, Self::Amd(dev) => dev.boost().await, Self::Intel(dev) => dev.boost().await, } @@ -42,7 +37,6 @@ impl TDPDevices { pub async fn set_boost(&mut self, value: f64) -> TDPResult<()> { match self { - //Self::ASUS(dev) => dev.set_boost(value).await, Self::Amd(dev) => dev.set_boost(value).await, Self::Intel(dev) => dev.set_boost(value).await, } @@ -50,7 +44,6 @@ impl TDPDevices { pub async fn thermal_throttle_limit_c(&self) -> TDPResult { match self { - //Self::ASUS(dev) => dev.thermal_throttle_limit_c().await, Self::Amd(dev) => dev.thermal_throttle_limit_c().await, Self::Intel(dev) => dev.thermal_throttle_limit_c().await, } @@ -58,15 +51,14 @@ impl TDPDevices { pub async fn set_thermal_throttle_limit_c(&mut self, limit: f64) -> TDPResult<()> { match self { - //Self::ASUS(dev) => dev.set_thermal_throttle_limit_c(limit).await, Self::Amd(dev) => dev.set_thermal_throttle_limit_c(limit).await, Self::Intel(dev) => dev.set_thermal_throttle_limit_c(limit).await, } } + //TODO: Deprecate the power_profile functions and set them automatically with TDP. pub async fn power_profile(&self) -> TDPResult { match self { - //Self::ASUS(dev) => dev.power_profile().await, Self::Amd(dev) => dev.power_profile().await, Self::Intel(dev) => dev.power_profile().await, } @@ -74,11 +66,17 @@ impl TDPDevices { pub async fn set_power_profile(&mut self, profile: String) -> TDPResult<()> { match self { - //Self::ASUS(dev) => dev.set_power_profile(profile).await, Self::Amd(dev) => dev.set_power_profile(profile).await, Self::Intel(dev) => dev.set_power_profile(profile).await, } } + + pub async fn power_profiles_available(&self) -> TDPResult> { + match self { + Self::Amd(dev) => dev.power_profiles_available().await, + Self::Intel(dev) => dev.power_profiles_available().await, + } + } } pub enum GPUDevices { diff --git a/src/performance/gpu/dbus/tdp.rs b/src/performance/gpu/dbus/tdp.rs index 01771ab..d31a45d 100644 --- a/src/performance/gpu/dbus/tdp.rs +++ b/src/performance/gpu/dbus/tdp.rs @@ -104,5 +104,12 @@ impl GPUTDPDBusIface { TDPResult::Err(err) => Err(err.into()), } } -} + #[dbus_interface(property)] + async fn power_profiles_avaialable(&self) -> fdo::Result> { + match self.dev.lock().await.power_profiles_available().await { + TDPResult::Ok(result) => Ok(result), + TDPResult::Err(err) => Err(err.into()), + } + } +} diff --git a/src/performance/gpu/intel/intelgpu.rs b/src/performance/gpu/intel/intelgpu.rs index 1323e99..c803aae 100644 --- a/src/performance/gpu/intel/intelgpu.rs +++ b/src/performance/gpu/intel/intelgpu.rs @@ -7,10 +7,12 @@ use std::{ use tokio::sync::Mutex; use crate::constants::PREFIX; -use crate::performance::gpu::dbus::devices::TDPDevices; -use crate::performance::gpu::intel; -use crate::performance::gpu::interface::GPUDevice; -use crate::performance::gpu::interface::{GPUError, GPUResult}; +use crate::performance::gpu::{ + dbus::devices::TDPDevices, + interface::{GPUDevice, GPUError, GPUResult}, +}; + +use super::tdp::Tdp; #[derive(Debug, Clone)] pub struct IntelGPU { @@ -38,9 +40,9 @@ impl GPUDevice for IntelGPU { /// Returns the TDP DBus interface for this GPU async fn get_tdp_interface(&self) -> Option>> { match self.class.as_str() { - "integrated" => Some(Arc::new(Mutex::new(TDPDevices::Intel( - intel::tdp::Tdp::new(self.path.clone()), - )))), + "integrated" => Some(Arc::new(Mutex::new(TDPDevices::Intel(Tdp::new( + self.path.clone(), + ))))), _ => None, } } diff --git a/src/performance/gpu/intel/tdp.rs b/src/performance/gpu/intel/tdp.rs index d1dcc6d..2b47e09 100644 --- a/src/performance/gpu/intel/tdp.rs +++ b/src/performance/gpu/intel/tdp.rs @@ -128,4 +128,9 @@ impl TDPDevice for Tdp { log::error!("Power profiles not supported on intel gpu"); Err(TDPError::FeatureUnsupported) } + + async fn power_profiles_available(&self) -> TDPResult> { + log::error!("Power profiles not supported on intel gpu"); + Err(TDPError::FeatureUnsupported) + } } diff --git a/src/performance/gpu/interface.rs b/src/performance/gpu/interface.rs index b1949ce..0093446 100644 --- a/src/performance/gpu/interface.rs +++ b/src/performance/gpu/interface.rs @@ -12,7 +12,7 @@ pub enum GPUError { } impl From for String { - fn from(val: GPUError) -> Self { + fn from(_val: GPUError) -> Self { todo!() } } @@ -22,9 +22,7 @@ pub type GPUResult = Result; /// Represents the data contained in /sys/class/drm/cardX pub trait GPUDevice: Send + Sync { async fn get_tdp_interface(&self) -> Option>>; - async fn get_gpu_path(&self) -> String; - async fn name(&self) -> String; async fn path(&self) -> String; async fn class(&self) -> String; diff --git a/src/performance/gpu/mod.rs b/src/performance/gpu/mod.rs index 3966c2f..cdb2efb 100644 --- a/src/performance/gpu/mod.rs +++ b/src/performance/gpu/mod.rs @@ -1,6 +1,8 @@ -pub mod tdp; -pub mod dbus; +pub mod acpi; +pub mod amd; +pub mod asus; pub mod connector; +pub mod dbus; pub mod intel; -pub mod amd; -pub mod interface; \ No newline at end of file +pub mod interface; +pub mod tdp; diff --git a/src/performance/gpu/tdp.rs b/src/performance/gpu/tdp.rs index a20cb9e..1f9f78a 100644 --- a/src/performance/gpu/tdp.rs +++ b/src/performance/gpu/tdp.rs @@ -1,3 +1,4 @@ +#[derive(Debug)] pub enum TDPError { FeatureUnsupported, FailedOperation(String), @@ -6,7 +7,7 @@ pub enum TDPError { } impl From for String { - fn from(val: TDPError) -> Self { + fn from(_val: TDPError) -> Self { todo!() } } @@ -21,6 +22,6 @@ pub trait TDPDevice: Sync + Send { async fn thermal_throttle_limit_c(&self) -> TDPResult; async fn set_thermal_throttle_limit_c(&mut self, limit: f64) -> TDPResult<()>; async fn power_profile(&self) -> TDPResult; + async fn power_profiles_available(&self) -> TDPResult>; async fn set_power_profile(&mut self, profile: String) -> TDPResult<()>; } -