Skip to content

Commit

Permalink
feat(asus_wmi): Add Asus WMI TDP methods.
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
pastaq committed Sep 20, 2024
1 parent 4f9d36f commit 7a21c2f
Show file tree
Hide file tree
Showing 20 changed files with 972 additions and 444 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 5 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,8 +16,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
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?;
Expand All @@ -46,7 +43,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
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);
}
Expand Down
24 changes: 11 additions & 13 deletions src/performance/cpu/cpu.rs → src/performance/cpu/cpu_features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32, Vec<CPUCore>>,
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<u32, Vec<CPUCore>> = HashMap::new();
let mut cores = get_cores();
Expand All @@ -40,25 +40,25 @@ 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<CPUCore> = 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,
}
}
}

#[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<bool> {
Expand Down Expand Up @@ -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<u32> {
Ok(self.core_count.clone())
Ok(self.core_count)
}

#[dbus_interface(property)]
Expand Down Expand Up @@ -286,13 +286,11 @@ async fn get_features() -> fdo::Result<Vec<String>> {
pub fn get_cores() -> Vec<CPUCore> {
let mut cores: Vec<CPUCore> = 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
Expand Down
2 changes: 1 addition & 1 deletion src/performance/cpu/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod core;
pub mod cpu;
pub mod cpu_features;
125 changes: 125 additions & 0 deletions src/performance/gpu/acpi/firmware.rs
Original file line number Diff line number Diff line change
@@ -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<Self> {
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<String> {
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<Vec<String>> {
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:?}"
))),
}
}
}
1 change: 1 addition & 0 deletions src/performance/gpu/acpi/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod firmware;
18 changes: 9 additions & 9 deletions src/performance/gpu/amd/amdgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -36,12 +38,10 @@ impl GPUDevice for AmdGpu {

/// Returns the TDP DBus interface for this GPU
async fn get_tdp_interface(&self) -> Option<Arc<Mutex<TDPDevices>>> {
// 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,
}
}
Expand Down
95 changes: 0 additions & 95 deletions src/performance/gpu/amd/asus.rs

This file was deleted.

1 change: 1 addition & 0 deletions src/performance/gpu/amd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod amdgpu;
pub mod ryzenadj;
pub mod tdp;
Loading

0 comments on commit 7a21c2f

Please sign in to comment.