From 69e6477053b83affece68a66591016b6c7cf4f5d Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Mar 2024 16:23:31 -0700 Subject: [PATCH 01/35] manager: Implement new API --- src/hardware.rs | 54 +++++++ src/main.rs | 4 +- src/manager.rs | 393 ++++++++++++++++++++++++++---------------------- src/power.rs | 82 ++++++++-- src/wifi.rs | 82 +++++++++- 5 files changed, 419 insertions(+), 196 deletions(-) diff --git a/src/hardware.rs b/src/hardware.rs index 5c19b06..7c07912 100644 --- a/src/hardware.rs +++ b/src/hardware.rs @@ -6,10 +6,12 @@ */ use anyhow::{Error, Result}; +use std::fmt; use std::str::FromStr; use tokio::fs; use crate::path; +use crate::process::run_script; const BOARD_VENDOR_PATH: &str = "/sys/class/dmi/id/board_vendor"; const BOARD_NAME_PATH: &str = "/sys/class/dmi/id/board_name"; @@ -21,6 +23,14 @@ pub enum HardwareVariant { Galileo, } +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +pub enum HardwareCurrentlySupported { + UnsupportedFeature = 0, + Unsupported = 1, + Supported = 2, +} + impl FromStr for HardwareVariant { type Err = Error; fn from_str(input: &str) -> Result { @@ -32,6 +42,34 @@ impl FromStr for HardwareVariant { } } +impl TryFrom for HardwareCurrentlySupported { + type Error = &'static str; + fn try_from(v: u32) -> Result { + match v { + x if x == HardwareCurrentlySupported::UnsupportedFeature as u32 => { + Ok(HardwareCurrentlySupported::UnsupportedFeature) + } + x if x == HardwareCurrentlySupported::Unsupported as u32 => { + Ok(HardwareCurrentlySupported::Unsupported) + } + x if x == HardwareCurrentlySupported::Supported as u32 => { + Ok(HardwareCurrentlySupported::Supported) + } + _ => Err("No enum match for value {v}"), + } + } +} + +impl fmt::Display for HardwareCurrentlySupported { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + HardwareCurrentlySupported::UnsupportedFeature => write!(f, "Unsupported feature"), + HardwareCurrentlySupported::Unsupported => write!(f, "Unsupported"), + HardwareCurrentlySupported::Supported => write!(f, "Supported"), + } + } +} + pub async fn variant() -> Result { let board_vendor = fs::read_to_string(path(BOARD_VENDOR_PATH)).await?; if board_vendor.trim_end() != "Valve" { @@ -81,3 +119,19 @@ mod test { assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown); } } + +pub async fn check_support() -> Result { + // Run jupiter-check-support note this script does exit 1 for "Support: No" case + // so no need to parse output, etc. + let res = run_script( + "check hardware support", + "/usr/bin/jupiter-check-support", + &[""], + ) + .await?; + + Ok(match res { + true => HardwareCurrentlySupported::Supported, + false => HardwareCurrentlySupported::Unsupported, + }) +} diff --git a/src/main.rs b/src/main.rs index 12242eb..01b6d20 100644 --- a/src/main.rs +++ b/src/main.rs @@ -124,10 +124,10 @@ async fn reload() -> Result<()> { } async fn create_connection() -> Result { - let manager = manager::SMManager::new().await?; + let manager = manager::SteamOSManager::new().await?; ConnectionBuilder::system()? - .name("com.steampowered.SteamOSManager1")? + .name("com.steampowered.SteamOSManager1.Manager")? .serve_at("/com/steampowered/SteamOSManager1", manager)? .build() .await diff --git a/src/manager.rs b/src/manager.rs index a4664f5..4078f54 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -6,99 +6,180 @@ */ use anyhow::Result; +use std::fmt; use tokio::fs::File; -use tracing::{error, warn}; +use tracing::error; use zbus::{interface, zvariant::Fd}; -use crate::hardware::{variant, HardwareVariant}; -use crate::power::{set_gpu_clocks, set_gpu_performance_level, set_tdp_limit}; +use crate::hardware::{check_support, variant, HardwareCurrentlySupported, HardwareVariant}; +use crate::power::{ + get_gpu_performance_level, set_gpu_clocks, set_gpu_performance_level, set_tdp_limit, + GPUPerformanceLevel, +}; use crate::process::{run_script, script_output, SYSTEMCTL_PATH}; -use crate::wifi::{restart_iwd, setup_iwd_config, start_tracing, stop_tracing, WifiDebugMode}; +use crate::wifi::{set_wifi_debug_mode, WifiDebugMode, WifiPowerManagement}; -pub struct SMManager { +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +enum PrepareFactoryReset { + Unknown = 0, + RebootRequired = 1, +} + +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +enum FanControl { + UnsupportedFeature = 0, + BIOS = 1, + OS = 2, +} + +impl TryFrom for FanControl { + type Error = &'static str; + fn try_from(v: u32) -> Result { + match v { + x if x == FanControl::UnsupportedFeature as u32 => Ok(FanControl::UnsupportedFeature), + x if x == FanControl::BIOS as u32 => Ok(FanControl::BIOS), + x if x == FanControl::OS as u32 => Ok(FanControl::BIOS), + _ => Err("No enum match for value {v}"), + } + } +} + +impl fmt::Display for FanControl { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + FanControl::UnsupportedFeature => write!(f, "Unsupported feature"), + FanControl::BIOS => write!(f, "BIOS"), + FanControl::OS => write!(f, "OS"), + } + } +} + +pub struct SteamOSManager { wifi_debug_mode: WifiDebugMode, // Whether we should use trace-cmd or not. // True on galileo devices, false otherwise should_trace: bool, } -impl SMManager { +impl SteamOSManager { pub async fn new() -> Result { - Ok(SMManager { + Ok(SteamOSManager { wifi_debug_mode: WifiDebugMode::Off, should_trace: variant().await? == HardwareVariant::Galileo, }) } } -const MIN_BUFFER_SIZE: u32 = 100; - const ALS_INTEGRATION_PATH: &str = "/sys/devices/platform/AMDI0010:00/i2c-0/i2c-PRP0001:01/iio:device0/in_illuminance_integration_time"; -#[interface(name = "com.steampowered.SteamOSManager1")] -impl SMManager { - const API_VERSION: u32 = 1; +#[interface(name = "com.steampowered.SteamOSManager1.Manager")] +impl SteamOSManager { + const API_VERSION: u32 = 7; - async fn say_hello(&self, name: &str) -> String { - format!("Hello {}!", name) - } - - async fn factory_reset(&self) -> bool { + async fn prepare_factory_reset(&self) -> u32 { // Run steamos factory reset script and return true on success - run_script( + let res = run_script( "factory reset", "/usr/bin/steamos-factory-reset-config", &[""], ) .await - .unwrap_or(false) - } - - async fn disable_wifi_power_management(&self) -> bool { - // Run polkit helper script and return true on success - run_script( - "disable wifi power management", - "/usr/bin/steamos-polkit-helpers/steamos-disable-wireless-power-management", - &[""], - ) - .await - .unwrap_or(false) - } - - async fn enable_fan_control(&self, enable: bool) -> bool { - // Run what steamos-polkit-helpers/jupiter-fan-control does - if enable { - run_script( - "enable fan control", - SYSTEMCTL_PATH, - &["start", "jupiter-fan-control-service"], - ) - .await - .unwrap_or(false) - } else { - run_script( - "disable fan control", - SYSTEMCTL_PATH, - &["stop", "jupiter-fan-control.service"], - ) - .await - .unwrap_or(false) + .unwrap_or(false); + match res { + true => PrepareFactoryReset::RebootRequired as u32, + false => PrepareFactoryReset::Unknown as u32, } } - async fn hardware_check_support(&self) -> bool { - // Run jupiter-check-support note this script does exit 1 for "Support: No" case - // so no need to parse output, etc. - run_script( - "check hardware support", - "/usr/bin/jupiter-check-support", - &[""], - ) - .await - .unwrap_or(false) + #[zbus(property)] + fn wifi_power_management_state(&self) -> u32 { + WifiPowerManagement::UnsupportedFeature as u32 // TODO } - async fn read_als_calibration(&self) -> f32 { + #[zbus(property)] + async fn set_wifi_power_management_state(&self, state: u32) -> zbus::Result<()> { + let state = match WifiPowerManagement::try_from(state) { + Ok(state) => state, + Err(err) => return Err(zbus::fdo::Error::InvalidArgs(err.to_string()).into()), + }; + let state = match state { + WifiPowerManagement::Disabled => "off", + WifiPowerManagement::Enabled => "on", + WifiPowerManagement::UnsupportedFeature => { + return Err(zbus::fdo::Error::InvalidArgs(String::from( + "Can't set state to unsupported", + )) + .into()) + } + }; + + let res = run_script( + "set wifi power management", + "/usr/bin/iwconfig", + &["wlan0", "power", state], + ) + .await; + + match res { + Ok(true) => Ok(()), + Ok(false) => Err(zbus::Error::Failure(String::from( + "iwconfig returned non-zero", + ))), + Err(e) => Err(zbus::Error::Failure(e.to_string())), + } + } + + #[zbus(property)] + fn fan_control_state(&self) -> u32 { + FanControl::UnsupportedFeature as u32 // TODO + } + + #[zbus(property)] + async fn set_fan_control_state(&self, state: u32) -> zbus::Result<()> { + let state = match FanControl::try_from(state) { + Ok(state) => state, + Err(err) => return Err(zbus::fdo::Error::InvalidArgs(err.to_string()).into()), + }; + let state = match state { + FanControl::OS => "stop", + FanControl::BIOS => "start", + FanControl::UnsupportedFeature => { + return Err(zbus::fdo::Error::InvalidArgs(String::from( + "Can't set state to unsupported", + )) + .into()) + } + }; + + // Run what steamos-polkit-helpers/jupiter-fan-control does + let res = run_script( + "enable fan control", + SYSTEMCTL_PATH, + &[state, "jupiter-fan-control-service"], + ) + .await; + + match res { + Ok(true) => Ok(()), + Ok(false) => Err(zbus::Error::Failure(String::from( + "systemctl returned non-zero", + ))), + Err(e) => Err(zbus::Error::Failure(format!("{e}"))), + } + } + + #[zbus(property)] + async fn hardware_currently_supported(&self) -> u32 { + match check_support().await { + Ok(res) => res as u32, + Err(_) => HardwareCurrentlySupported::UnsupportedFeature as u32, + } + } + + #[zbus(property)] + async fn als_calibration_gain(&self) -> f64 { // Run script to get calibration value let result = script_output( "/usr/bin/steamos-polkit-helpers/jupiter-get-als-gain", @@ -114,40 +195,61 @@ impl SMManager { } } - async fn update_bios(&self) -> bool { + async fn get_als_integration_time_file_descriptor(&self) -> Result { + // Get the file descriptor for the als integration time sysfs path + let result = File::create(ALS_INTEGRATION_PATH).await; + match result { + Ok(f) => Ok(Fd::Owned(std::os::fd::OwnedFd::from(f.into_std().await))), + Err(message) => { + error!("Error opening sysfs file for giving file descriptor: {message}"); + Err(zbus::fdo::Error::IOError(message.to_string())) + } + } + } + + async fn update_bios(&self) -> Result<(), zbus::fdo::Error> { // Update the bios as needed - // Return true if the script was successful (though that might mean no update was needed), false otherwise - run_script( + let res = run_script( "update bios", "/usr/bin/steamos-potlkit-helpers/jupiter-biosupdate", &["--auto"], ) - .await - .unwrap_or(false) + .await; + + match res { + Ok(_) => Ok(()), + Err(e) => Err(zbus::fdo::Error::Failed(e.to_string())), + } } - async fn update_dock(&self) -> bool { + async fn update_dock(&self) -> Result<(), zbus::fdo::Error> { // Update the dock firmware as needed - // Retur true if successful, false otherwise - run_script( + let res = run_script( "update dock firmware", "/usr/bin/steamos-polkit-helpers/jupiter-dock-updater", &[""], ) - .await - .unwrap_or(false) + .await; + + match res { + Ok(_) => Ok(()), + Err(e) => Err(zbus::fdo::Error::Failed(e.to_string())), + } } - async fn trim_devices(&self) -> bool { + async fn trim_devices(&self) -> Result<(), zbus::fdo::Error> { // Run steamos-trim-devices script - // return true on success, false otherwise - run_script( + let res = run_script( "trim devices", "/usr/bin/steamos-polkit-helpers/steamos-trim-devices", &[""], ) - .await - .unwrap_or(false) + .await; + + match res { + Ok(_) => Ok(()), + Err(e) => Err(zbus::fdo::Error::Failed(e.to_string())), + } } async fn format_sdcard(&self) -> bool { @@ -162,8 +264,23 @@ impl SMManager { .unwrap_or(false) } - async fn set_gpu_performance_level(&self, level: i32) -> bool { - set_gpu_performance_level(level).await.is_ok() + #[zbus(property)] + async fn gpu_performance_level(&self) -> u32 { + match get_gpu_performance_level().await { + Ok(level) => level as u32, + Err(_) => GPUPerformanceLevel::UnsupportedFeature as u32, + } + } + + #[zbus(property)] + async fn set_gpu_performance_level(&self, level: u32) -> zbus::Result<()> { + let level = match GPUPerformanceLevel::try_from(level) { + Ok(level) => level, + Err(e) => return Err(zbus::Error::Failure(e.to_string())), + }; + set_gpu_performance_level(level) + .await + .map_err(|e| zbus::Error::Failure(e.to_string())) } async fn set_gpu_clocks(&self, clocks: i32) -> bool { @@ -174,121 +291,43 @@ impl SMManager { set_tdp_limit(limit).await.is_ok() } - async fn get_als_integration_time_file_descriptor(&self) -> Result { - // Get the file descriptor for the als integration time sysfs path - let result = File::create(ALS_INTEGRATION_PATH).await; - match result { - Ok(f) => Ok(Fd::Owned(std::os::fd::OwnedFd::from(f.into_std().await))), - Err(message) => { - error!("Error opening sysfs file for giving file descriptor: {message}"); - Err(zbus::fdo::Error::IOError(message.to_string())) - } - } - } - - async fn get_wifi_debug_mode(&mut self) -> u32 { + #[zbus(property)] + async fn wifi_debug_mode_state(&self) -> u32 { // Get the wifi debug mode self.wifi_debug_mode as u32 } - async fn set_wifi_debug_mode(&mut self, mode: u32, buffer_size: u32) -> bool { + async fn set_wifi_debug_mode( + &mut self, + mode: u32, + buffer_size: u32, + ) -> Result<(), zbus::fdo::Error> { // Set the wifi debug mode to mode, using an int for flexibility going forward but only // doing things on 0 or 1 for now // Return false on error - let wanted_mode = WifiDebugMode::try_from(mode); - match wanted_mode { - Ok(WifiDebugMode::Off) => { - // If mode is 0 disable wifi debug mode - // Stop any existing trace and flush to disk. - if self.should_trace { - let result = match stop_tracing().await { - Ok(result) => result, - Err(message) => { - error!("stop_tracing command got an error: {message}"); - return false; - } - }; - if !result { - error!("stop_tracing command returned non-zero"); - return false; - } - } - // Stop_tracing was successful - if let Err(message) = setup_iwd_config(false).await { - error!("setup_iwd_config false got an error: {message}"); - return false; - } - // setup_iwd_config false worked - let value = match restart_iwd().await { - Ok(value) => value, - Err(message) => { - error!("restart_iwd got an error: {message}"); - return false; - } - }; - if value { - // restart iwd worked - self.wifi_debug_mode = WifiDebugMode::Off; - } else { - // restart_iwd failed - error!("restart_iwd failed, check log above"); - return false; - } + let wanted_mode = match WifiDebugMode::try_from(mode) { + Ok(WifiDebugMode::UnsupportedFeature) => { + return Err(zbus::fdo::Error::InvalidArgs(String::from("Invalid mode"))) } - Ok(WifiDebugMode::On) => { - // If mode is 1 enable wifi debug mode - if buffer_size < MIN_BUFFER_SIZE { - return false; - } - - if let Err(message) = setup_iwd_config(true).await { - error!("setup_iwd_config true got an error: {message}"); - return false; - } - // setup_iwd_config worked - let value = match restart_iwd().await { - Ok(value) => value, - Err(message) => { - error!("restart_iwd got an error: {message}"); - return false; - } - }; - if !value { - error!("restart_iwd failed"); - return false; - } - // restart_iwd worked - if self.should_trace { - let value = match start_tracing(buffer_size).await { - Ok(value) => value, - Err(message) => { - error!("start_tracing got an error: {message}"); - return false; - } - }; - if !value { - // start_tracing failed - error!("start_tracing failed"); - return false; - } - } - // start_tracing worked - self.wifi_debug_mode = WifiDebugMode::On; + Ok(mode) => mode, + Err(e) => return Err(zbus::fdo::Error::InvalidArgs(e.to_string())), + }; + match set_wifi_debug_mode(wanted_mode, buffer_size, self.should_trace).await { + Ok(()) => { + self.wifi_debug_mode = wanted_mode; + Ok(()) } - Err(_) => { - // Invalid mode requested, more coming later, but add this catch-all for now - warn!("Invalid wifi debug mode {mode} requested"); - return false; + Err(e) => { + error!("Setting wifi debug mode failed: {e}"); + Err(zbus::fdo::Error::Failed(e.to_string())) } } - - true } /// A version property. #[zbus(property)] async fn version(&self) -> u32 { - SMManager::API_VERSION + SteamOSManager::API_VERSION } } diff --git a/src/power.rs b/src/power.rs index 363a00c..cff8a49 100644 --- a/src/power.rs +++ b/src/power.rs @@ -5,7 +5,8 @@ * SPDX-License-Identifier: MIT */ -use anyhow::{bail, ensure, Result}; +use anyhow::{anyhow, bail, ensure, Error, Result}; +use std::str::FromStr; use tokio::fs::{self, File}; use tokio::io::AsyncWriteExt; use tracing::error; @@ -18,22 +19,81 @@ const GPU_PERFORMANCE_LEVEL_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level"; const GPU_CLOCKS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; -pub async fn set_gpu_performance_level(level: i32) -> Result<()> { - // Set given GPU performance level - // Levels are defined below - // return true if able to write, false otherwise or if level is out of range, etc. - let levels = ["auto", "low", "high", "manual", "peak_performance"]; - ensure!( - level >= 0 && level < levels.len() as i32, - "Invalid performance level" - ); +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +pub enum GPUPerformanceLevel { + UnsupportedFeature = 0, + Auto = 1, + Low = 2, + High = 3, + Manual = 4, + ProfilePeak = 5, +} +impl TryFrom for GPUPerformanceLevel { + type Error = &'static str; + fn try_from(v: u32) -> Result { + match v { + x if x == GPUPerformanceLevel::UnsupportedFeature as u32 => { + Ok(GPUPerformanceLevel::UnsupportedFeature) + } + x if x == GPUPerformanceLevel::Auto as u32 => Ok(GPUPerformanceLevel::Auto), + x if x == GPUPerformanceLevel::Low as u32 => Ok(GPUPerformanceLevel::Low), + x if x == GPUPerformanceLevel::High as u32 => Ok(GPUPerformanceLevel::High), + x if x == GPUPerformanceLevel::Manual as u32 => Ok(GPUPerformanceLevel::Manual), + x if x == GPUPerformanceLevel::ProfilePeak as u32 => { + Ok(GPUPerformanceLevel::ProfilePeak) + } + _ => Err("No enum match for value {v}"), + } + } +} + +impl FromStr for GPUPerformanceLevel { + type Err = Error; + fn from_str(input: &str) -> Result { + match input { + "auto" => Ok(GPUPerformanceLevel::Auto), + "low" => Ok(GPUPerformanceLevel::Low), + "high" => Ok(GPUPerformanceLevel::High), + "manual" => Ok(GPUPerformanceLevel::Manual), + "peak_performance" => Ok(GPUPerformanceLevel::ProfilePeak), + v => Err(anyhow!("No enum match for value {v}")), + } + } +} + +impl TryInto for GPUPerformanceLevel { + type Error = Error; + fn try_into(self) -> Result { + Ok(String::from(match self { + GPUPerformanceLevel::Auto => "auto", + GPUPerformanceLevel::Low => "low", + GPUPerformanceLevel::High => "high", + GPUPerformanceLevel::Manual => "manual", + GPUPerformanceLevel::ProfilePeak => "peak_performance", + GPUPerformanceLevel::UnsupportedFeature => bail!("No valid string representation"), + })) + } +} + +pub async fn get_gpu_performance_level() -> Result { + let level = fs::read_to_string(GPU_PERFORMANCE_LEVEL_PATH) + .await + .inspect_err(|message| error!("Error opening sysfs file for reading: {message}"))?; + + GPUPerformanceLevel::from_str(level.as_ref()) +} + +pub async fn set_gpu_performance_level(level: GPUPerformanceLevel) -> Result<()> { let mut myfile = File::create(GPU_PERFORMANCE_LEVEL_PATH) .await .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; + let level: String = level.try_into()?; + myfile - .write_all(levels[level as usize].as_bytes()) + .write_all(level.as_bytes()) .await .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; Ok(()) diff --git a/src/wifi.rs b/src/wifi.rs index 70c5600..210b13f 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -5,7 +5,7 @@ * SPDX-License-Identifier: MIT */ -use anyhow::Result; +use anyhow::{bail, ensure, Result}; use std::fmt; use tokio::fs; use tracing::error; @@ -24,11 +24,14 @@ const OVERRIDE_PATH: &str = "/etc/systemd/system/iwd.service.d/override.conf"; const OUTPUT_FILE: &str = "/var/log/wifitrace.dat"; const TRACE_CMD_PATH: &str = "/usr/bin/trace-cmd"; +const MIN_BUFFER_SIZE: u32 = 100; + #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum WifiDebugMode { - Off, - On, + UnsupportedFeature = 0, + Off = 1, + On = 2, } #[derive(PartialEq, Debug, Copy, Clone)] @@ -43,6 +46,9 @@ impl TryFrom for WifiDebugMode { type Error = &'static str; fn try_from(v: u32) -> Result { match v { + x if x == WifiDebugMode::UnsupportedFeature as u32 => { + Ok(WifiDebugMode::UnsupportedFeature) + } x if x == WifiDebugMode::Off as u32 => Ok(WifiDebugMode::Off), x if x == WifiDebugMode::On as u32 => Ok(WifiDebugMode::On), _ => Err("No enum match for value {v}"), @@ -53,6 +59,7 @@ impl TryFrom for WifiDebugMode { impl fmt::Display for WifiDebugMode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { + WifiDebugMode::UnsupportedFeature => write!(f, "Unsupported feature"), WifiDebugMode::Off => write!(f, "Off"), WifiDebugMode::On => write!(f, "On"), } @@ -99,7 +106,7 @@ pub async fn setup_iwd_config(want_override: bool) -> std::io::Result<()> { } } -pub async fn restart_iwd() -> Result { +async fn restart_iwd() -> Result { // First reload systemd since we modified the config most likely // otherwise we wouldn't be restarting iwd. match run_script("reload systemd", SYSTEMCTL_PATH, &["daemon-reload"]).await { @@ -120,7 +127,7 @@ pub async fn restart_iwd() -> Result { } } -pub async fn stop_tracing() -> Result { +async fn stop_tracing() -> Result { // Stop tracing and extract ring buffer to disk for capture run_script("stop tracing", TRACE_CMD_PATH, &["stop"]).await?; // stop tracing worked @@ -132,7 +139,7 @@ pub async fn stop_tracing() -> Result { .await } -pub async fn start_tracing(buffer_size: u32) -> Result { +async fn start_tracing(buffer_size: u32) -> Result { // Start tracing let size_str = format!("{}", buffer_size); run_script( @@ -142,3 +149,66 @@ pub async fn start_tracing(buffer_size: u32) -> Result { ) .await } + +pub async fn set_wifi_debug_mode( + mode: WifiDebugMode, + buffer_size: u32, + should_trace: bool, +) -> Result<()> { + // Set the wifi debug mode to mode, using an int for flexibility going forward but only + // doing things on 0 or 1 for now + // Return false on error + + match mode { + WifiDebugMode::Off => { + // If mode is 0 disable wifi debug mode + // Stop any existing trace and flush to disk. + if should_trace { + let result = match stop_tracing().await { + Ok(result) => result, + Err(message) => bail!("stop_tracing command got an error: {message}"), + }; + ensure!(result, "stop_tracing command returned non-zero"); + } + // Stop_tracing was successful + if let Err(message) = setup_iwd_config(false).await { + bail!("setup_iwd_config false got an error: {message}"); + } + // setup_iwd_config false worked + let value = match restart_iwd().await { + Ok(value) => value, + Err(message) => { + bail!("restart_iwd got an error: {message}"); + } + }; + // restart_iwd failed + ensure!(value, "restart_iwd failed, check log above"); + } + WifiDebugMode::On => { + ensure!(buffer_size > MIN_BUFFER_SIZE, "Buffer size too small"); + + if let Err(message) = setup_iwd_config(true).await { + bail!("setup_iwd_config true got an error: {message}"); + } + // setup_iwd_config worked + let value = match restart_iwd().await { + Ok(value) => value, + Err(message) => bail!("restart_iwd got an error: {message}"), + }; + ensure!(value, "restart_iwd failed"); + // restart_iwd worked + if should_trace { + let value = match start_tracing(buffer_size).await { + Ok(value) => value, + Err(message) => bail!("start_tracing got an error: {message}"), + }; + ensure!(value, "start_tracing failed"); + } + } + mode => { + // Invalid mode requested, more coming later, but add this catch-all for now + bail!("Invalid wifi debug mode {mode} requested"); + } + } + Ok(()) +} From fd14514d6c12baca87555dd20d7ede89bf97053e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Mar 2024 16:57:47 -0700 Subject: [PATCH 02/35] manager: Implement format_device --- src/manager.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 4078f54..bb86c17 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -252,16 +252,22 @@ impl SteamOSManager { } } - async fn format_sdcard(&self) -> bool { - // Run steamos-format-sdcard script - // return true on success, false otherwise - run_script( - "format sdcard", - "/usr/bin/steamos-polkit-helpers/steamos-format-sdcard", - &[""], + async fn format_device(&self, device: &str, label: &str, validate: bool) -> Result<(), zbus::fdo::Error> { + let mut args = vec!["--label", label, "--device", device]; + if !validate { + args.push("--skip-validation"); + } + let res = run_script( + "format device", + "/usr/lib/hwsupport/format-device.sh", + args.as_ref() ) - .await - .unwrap_or(false) + .await; + + match res { + Ok(_) => Ok(()), + Err(e) => Err(zbus::fdo::Error::Failed(e.to_string())), + } } #[zbus(property)] From 8ae6c985546fcc0a4c81c051b38e2914a7228e1e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Mar 2024 18:12:21 -0700 Subject: [PATCH 03/35] process: Make API less fiddly --- src/hardware.rs | 13 +++----- src/main.rs | 8 +++++ src/manager.rs | 87 ++++++++++++++++++------------------------------- src/process.rs | 26 ++++++++++----- src/wifi.rs | 49 +++++++++------------------- 5 files changed, 76 insertions(+), 107 deletions(-) diff --git a/src/hardware.rs b/src/hardware.rs index 7c07912..fc1ef1f 100644 --- a/src/hardware.rs +++ b/src/hardware.rs @@ -11,7 +11,7 @@ use std::str::FromStr; use tokio::fs; use crate::path; -use crate::process::run_script; +use crate::process::script_exit_code; const BOARD_VENDOR_PATH: &str = "/sys/class/dmi/id/board_vendor"; const BOARD_NAME_PATH: &str = "/sys/class/dmi/id/board_name"; @@ -123,15 +123,10 @@ mod test { pub async fn check_support() -> Result { // Run jupiter-check-support note this script does exit 1 for "Support: No" case // so no need to parse output, etc. - let res = run_script( - "check hardware support", - "/usr/bin/jupiter-check-support", - &[""], - ) - .await?; + let res = script_exit_code("/usr/bin/jupiter-check-support", &[] as &[String; 0]).await?; Ok(match res { - true => HardwareCurrentlySupported::Supported, - false => HardwareCurrentlySupported::Unsupported, + 0 => HardwareCurrentlySupported::Supported, + _ => HardwareCurrentlySupported::Unsupported, }) } diff --git a/src/main.rs b/src/main.rs index 01b6d20..714f7d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,6 +123,14 @@ async fn reload() -> Result<()> { } } +pub fn anyhow_to_zbus(error: Error) -> zbus::Error { + zbus::Error::Failure(error.to_string()) +} + +pub fn anyhow_to_zbus_fdo(error: Error) -> zbus::fdo::Error { + zbus::fdo::Error::Failed(error.to_string()) +} + async fn create_connection() -> Result { let manager = manager::SteamOSManager::new().await?; diff --git a/src/manager.rs b/src/manager.rs index bb86c17..817d12b 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -18,6 +18,7 @@ use crate::power::{ }; use crate::process::{run_script, script_output, SYSTEMCTL_PATH}; use crate::wifi::{set_wifi_debug_mode, WifiDebugMode, WifiPowerManagement}; +use crate::{anyhow_to_zbus, anyhow_to_zbus_fdo}; #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] @@ -85,11 +86,10 @@ impl SteamOSManager { "/usr/bin/steamos-factory-reset-config", &[""], ) - .await - .unwrap_or(false); + .await; match res { - true => PrepareFactoryReset::RebootRequired as u32, - false => PrepareFactoryReset::Unknown as u32, + Ok(_) => PrepareFactoryReset::RebootRequired as u32, + Err(_) => PrepareFactoryReset::Unknown as u32, } } @@ -115,20 +115,13 @@ impl SteamOSManager { } }; - let res = run_script( + run_script( "set wifi power management", "/usr/bin/iwconfig", &["wlan0", "power", state], ) - .await; - - match res { - Ok(true) => Ok(()), - Ok(false) => Err(zbus::Error::Failure(String::from( - "iwconfig returned non-zero", - ))), - Err(e) => Err(zbus::Error::Failure(e.to_string())), - } + .await + .map_err(anyhow_to_zbus) } #[zbus(property)] @@ -154,20 +147,13 @@ impl SteamOSManager { }; // Run what steamos-polkit-helpers/jupiter-fan-control does - let res = run_script( + run_script( "enable fan control", SYSTEMCTL_PATH, &[state, "jupiter-fan-control-service"], ) - .await; - - match res { - Ok(true) => Ok(()), - Ok(false) => Err(zbus::Error::Failure(String::from( - "systemctl returned non-zero", - ))), - Err(e) => Err(zbus::Error::Failure(format!("{e}"))), - } + .await + .map_err(anyhow_to_zbus) } #[zbus(property)] @@ -209,65 +195,54 @@ impl SteamOSManager { async fn update_bios(&self) -> Result<(), zbus::fdo::Error> { // Update the bios as needed - let res = run_script( + run_script( "update bios", "/usr/bin/steamos-potlkit-helpers/jupiter-biosupdate", &["--auto"], ) - .await; - - match res { - Ok(_) => Ok(()), - Err(e) => Err(zbus::fdo::Error::Failed(e.to_string())), - } + .await + .map_err(anyhow_to_zbus_fdo) } async fn update_dock(&self) -> Result<(), zbus::fdo::Error> { // Update the dock firmware as needed - let res = run_script( + run_script( "update dock firmware", "/usr/bin/steamos-polkit-helpers/jupiter-dock-updater", &[""], ) - .await; - - match res { - Ok(_) => Ok(()), - Err(e) => Err(zbus::fdo::Error::Failed(e.to_string())), - } + .await + .map_err(anyhow_to_zbus_fdo) } async fn trim_devices(&self) -> Result<(), zbus::fdo::Error> { // Run steamos-trim-devices script - let res = run_script( + run_script( "trim devices", "/usr/bin/steamos-polkit-helpers/steamos-trim-devices", &[""], ) - .await; - - match res { - Ok(_) => Ok(()), - Err(e) => Err(zbus::fdo::Error::Failed(e.to_string())), - } + .await + .map_err(anyhow_to_zbus_fdo) } - async fn format_device(&self, device: &str, label: &str, validate: bool) -> Result<(), zbus::fdo::Error> { + async fn format_device( + &self, + device: &str, + label: &str, + validate: bool, + ) -> Result<(), zbus::fdo::Error> { let mut args = vec!["--label", label, "--device", device]; if !validate { args.push("--skip-validation"); } - let res = run_script( + run_script( "format device", "/usr/lib/hwsupport/format-device.sh", - args.as_ref() + args.as_ref(), ) - .await; - - match res { - Ok(_) => Ok(()), - Err(e) => Err(zbus::fdo::Error::Failed(e.to_string())), - } + .await + .map_err(anyhow_to_zbus_fdo) } #[zbus(property)] @@ -286,7 +261,7 @@ impl SteamOSManager { }; set_gpu_performance_level(level) .await - .map_err(|e| zbus::Error::Failure(e.to_string())) + .map_err(anyhow_to_zbus) } async fn set_gpu_clocks(&self, clocks: i32) -> bool { @@ -326,7 +301,7 @@ impl SteamOSManager { } Err(e) => { error!("Setting wifi debug mode failed: {e}"); - Err(zbus::fdo::Error::Failed(e.to_string())) + Err(anyhow_to_zbus_fdo(e)) } } } diff --git a/src/process.rs b/src/process.rs index e38f15c..0398ad1 100644 --- a/src/process.rs +++ b/src/process.rs @@ -5,26 +5,34 @@ * SPDX-License-Identifier: MIT */ -use anyhow::Result; +use anyhow::{anyhow, Result}; use std::ffi::OsStr; use tokio::process::Command; use tracing::warn; pub const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl"; -pub async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> Result { - // Run given script and return true on success +pub async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> Result { + // Run given script and return the exit code let mut child = Command::new(executable).args(args).spawn()?; let status = child.wait().await?; - Ok(status.success()) + status.code().ok_or(anyhow!("Killed by signal")) } -pub async fn run_script(name: &str, executable: &str, args: &[impl AsRef]) -> Result { +pub async fn run_script(name: &str, executable: &str, args: &[impl AsRef]) -> Result<()> { // Run given script to get exit code and return true on success. - // Return false on failure, but also print an error if needed - script_exit_code(executable, args) - .await - .inspect_err(|message| warn!("Error running {name} {message}")) + // Return Err on failure, but also print an error if needed + match script_exit_code(executable, args).await { + Ok(0) => Ok(()), + Ok(code) => { + warn!("Error running {name}: exited {code}"); + Err(anyhow!("Exited {code}")) + } + Err(message) => { + warn!("Error running {name}: {message}"); + Err(message) + } + } } pub async fn script_output(executable: &str, args: &[impl AsRef]) -> Result { diff --git a/src/wifi.rs b/src/wifi.rs index 210b13f..c87df5b 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -106,19 +106,13 @@ pub async fn setup_iwd_config(want_override: bool) -> std::io::Result<()> { } } -async fn restart_iwd() -> Result { +async fn restart_iwd() -> Result<()> { // First reload systemd since we modified the config most likely // otherwise we wouldn't be restarting iwd. match run_script("reload systemd", SYSTEMCTL_PATH, &["daemon-reload"]).await { - Ok(value) => { - if value { - // worked, now restart iwd - run_script("restart iwd", SYSTEMCTL_PATH, &["restart", "iwd"]).await - } else { - // reload failed - error!("restart_iwd: reload systemd failed with non-zero exit code"); - Ok(false) - } + Ok(_) => { + // worked, now restart iwd + run_script("restart iwd", SYSTEMCTL_PATH, &["restart", "iwd"]).await } Err(message) => { error!("restart_iwd: reload systemd got an error: {message}"); @@ -127,7 +121,7 @@ async fn restart_iwd() -> Result { } } -async fn stop_tracing() -> Result { +async fn stop_tracing() -> Result<()> { // Stop tracing and extract ring buffer to disk for capture run_script("stop tracing", TRACE_CMD_PATH, &["stop"]).await?; // stop tracing worked @@ -139,7 +133,7 @@ async fn stop_tracing() -> Result { .await } -async fn start_tracing(buffer_size: u32) -> Result { +async fn start_tracing(buffer_size: u32) -> Result<()> { // Start tracing let size_str = format!("{}", buffer_size); run_script( @@ -164,25 +158,18 @@ pub async fn set_wifi_debug_mode( // If mode is 0 disable wifi debug mode // Stop any existing trace and flush to disk. if should_trace { - let result = match stop_tracing().await { - Ok(result) => result, - Err(message) => bail!("stop_tracing command got an error: {message}"), + if let Err(message) = stop_tracing().await { + bail!("stop_tracing command got an error: {message}"); }; - ensure!(result, "stop_tracing command returned non-zero"); } // Stop_tracing was successful if let Err(message) = setup_iwd_config(false).await { bail!("setup_iwd_config false got an error: {message}"); - } - // setup_iwd_config false worked - let value = match restart_iwd().await { - Ok(value) => value, - Err(message) => { - bail!("restart_iwd got an error: {message}"); - } }; - // restart_iwd failed - ensure!(value, "restart_iwd failed, check log above"); + // setup_iwd_config false worked + if let Err(message) = restart_iwd().await { + bail!("restart_iwd got an error: {message}"); + }; } WifiDebugMode::On => { ensure!(buffer_size > MIN_BUFFER_SIZE, "Buffer size too small"); @@ -191,18 +178,14 @@ pub async fn set_wifi_debug_mode( bail!("setup_iwd_config true got an error: {message}"); } // setup_iwd_config worked - let value = match restart_iwd().await { - Ok(value) => value, - Err(message) => bail!("restart_iwd got an error: {message}"), + if let Err(message) = restart_iwd().await { + bail!("restart_iwd got an error: {message}"); }; - ensure!(value, "restart_iwd failed"); // restart_iwd worked if should_trace { - let value = match start_tracing(buffer_size).await { - Ok(value) => value, - Err(message) => bail!("start_tracing got an error: {message}"), + if let Err(message) = start_tracing(buffer_size).await { + bail!("start_tracing got an error: {message}"); }; - ensure!(value, "start_tracing failed"); } } mode => { From aaaf8b8bc484de094e1379adca5acca88aa0b341 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Mar 2024 18:52:24 -0700 Subject: [PATCH 04/35] hardware: Fix style --- src/hardware.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/hardware.rs b/src/hardware.rs index fc1ef1f..9c45871 100644 --- a/src/hardware.rs +++ b/src/hardware.rs @@ -80,6 +80,17 @@ pub async fn variant() -> Result { HardwareVariant::from_str(board_name.trim_end()) } +pub async fn check_support() -> Result { + // Run jupiter-check-support note this script does exit 1 for "Support: No" case + // so no need to parse output, etc. + let res = script_exit_code("/usr/bin/jupiter-check-support", &[] as &[String; 0]).await?; + + Ok(match res { + 0 => HardwareCurrentlySupported::Supported, + _ => HardwareCurrentlySupported::Unsupported, + }) +} + #[cfg(test)] mod test { use super::*; @@ -119,14 +130,3 @@ mod test { assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown); } } - -pub async fn check_support() -> Result { - // Run jupiter-check-support note this script does exit 1 for "Support: No" case - // so no need to parse output, etc. - let res = script_exit_code("/usr/bin/jupiter-check-support", &[] as &[String; 0]).await?; - - Ok(match res { - 0 => HardwareCurrentlySupported::Supported, - _ => HardwareCurrentlySupported::Unsupported, - }) -} From ffebee093014635d3815fd25bedef5424c261dfc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Thu, 28 Mar 2024 18:51:09 -0700 Subject: [PATCH 05/35] manager: Start writing tests --- src/manager.rs | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/manager.rs b/src/manager.rs index 817d12b..ca02b22 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -312,3 +312,60 @@ impl SteamOSManager { SteamOSManager::API_VERSION } } + +#[cfg(test)] +mod test { + use super::*; + use crate::testing; + use tokio::fs::{create_dir_all, write}; + use zbus::connection::Connection; + use zbus::ConnectionBuilder; + + struct TestHandle { + handle: testing::TestHandle, + connection: Connection, + } + + async fn start() -> TestHandle { + let handle = testing::start(); + create_dir_all(crate::path("/sys/class/dmi/id")) + .await + .expect("create_dir_all"); + write(crate::path("/sys/class/dmi/id/board_vendor"), "Valve\n") + .await + .expect("write"); + write(crate::path("/sys/class/dmi/id/board_name"), "Jupiter\n") + .await + .expect("write"); + + let manager = SteamOSManager::new().await.unwrap(); + let connection = ConnectionBuilder::session() + .unwrap() + .name("com.steampowered.SteamOSManager1.Test") + .unwrap() + .serve_at("/com/steampowered/SteamOSManager1", manager) + .unwrap() + .build() + .await + .unwrap(); + + TestHandle { handle, connection } + } + + #[zbus::proxy( + interface = "com.steampowered.SteamOSManager1.Manager", + default_service = "com.steampowered.SteamOSManager1.Test", + default_path = "/com/steampowered/SteamOSManager1" + )] + trait Version { + #[zbus(property)] + fn version(&self) -> zbus::Result; + } + + #[tokio::test] + async fn version() { + let test = start().await; + let proxy = VersionProxy::new(&test.connection).await.unwrap(); + assert_eq!(proxy.version().await, Ok(SteamOSManager::API_VERSION)); + } +} From 452690adeef97a6e858adc80d576094036c00acc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Mar 2024 15:30:55 -0700 Subject: [PATCH 06/35] Kill UnsupportedFeature --- src/hardware.rs | 5 ----- src/manager.rs | 40 +++++++++++----------------------------- src/power.rs | 16 +++++----------- src/wifi.rs | 14 -------------- 4 files changed, 16 insertions(+), 59 deletions(-) diff --git a/src/hardware.rs b/src/hardware.rs index 9c45871..e046a47 100644 --- a/src/hardware.rs +++ b/src/hardware.rs @@ -26,7 +26,6 @@ pub enum HardwareVariant { #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum HardwareCurrentlySupported { - UnsupportedFeature = 0, Unsupported = 1, Supported = 2, } @@ -46,9 +45,6 @@ impl TryFrom for HardwareCurrentlySupported { type Error = &'static str; fn try_from(v: u32) -> Result { match v { - x if x == HardwareCurrentlySupported::UnsupportedFeature as u32 => { - Ok(HardwareCurrentlySupported::UnsupportedFeature) - } x if x == HardwareCurrentlySupported::Unsupported as u32 => { Ok(HardwareCurrentlySupported::Unsupported) } @@ -63,7 +59,6 @@ impl TryFrom for HardwareCurrentlySupported { impl fmt::Display for HardwareCurrentlySupported { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - HardwareCurrentlySupported::UnsupportedFeature => write!(f, "Unsupported feature"), HardwareCurrentlySupported::Unsupported => write!(f, "Unsupported"), HardwareCurrentlySupported::Supported => write!(f, "Supported"), } diff --git a/src/manager.rs b/src/manager.rs index ca02b22..441df10 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -11,7 +11,7 @@ use tokio::fs::File; use tracing::error; use zbus::{interface, zvariant::Fd}; -use crate::hardware::{check_support, variant, HardwareCurrentlySupported, HardwareVariant}; +use crate::hardware::{check_support, variant, HardwareVariant}; use crate::power::{ get_gpu_performance_level, set_gpu_clocks, set_gpu_performance_level, set_tdp_limit, GPUPerformanceLevel, @@ -30,7 +30,6 @@ enum PrepareFactoryReset { #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] enum FanControl { - UnsupportedFeature = 0, BIOS = 1, OS = 2, } @@ -39,7 +38,6 @@ impl TryFrom for FanControl { type Error = &'static str; fn try_from(v: u32) -> Result { match v { - x if x == FanControl::UnsupportedFeature as u32 => Ok(FanControl::UnsupportedFeature), x if x == FanControl::BIOS as u32 => Ok(FanControl::BIOS), x if x == FanControl::OS as u32 => Ok(FanControl::BIOS), _ => Err("No enum match for value {v}"), @@ -50,7 +48,6 @@ impl TryFrom for FanControl { impl fmt::Display for FanControl { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - FanControl::UnsupportedFeature => write!(f, "Unsupported feature"), FanControl::BIOS => write!(f, "BIOS"), FanControl::OS => write!(f, "OS"), } @@ -94,8 +91,8 @@ impl SteamOSManager { } #[zbus(property)] - fn wifi_power_management_state(&self) -> u32 { - WifiPowerManagement::UnsupportedFeature as u32 // TODO + fn wifi_power_management_state(&self) -> zbus::fdo::Result { + Err(zbus::fdo::Error::UnknownProperty(String::from("This property can't currently be read"))) } #[zbus(property)] @@ -107,12 +104,6 @@ impl SteamOSManager { let state = match state { WifiPowerManagement::Disabled => "off", WifiPowerManagement::Enabled => "on", - WifiPowerManagement::UnsupportedFeature => { - return Err(zbus::fdo::Error::InvalidArgs(String::from( - "Can't set state to unsupported", - )) - .into()) - } }; run_script( @@ -125,8 +116,8 @@ impl SteamOSManager { } #[zbus(property)] - fn fan_control_state(&self) -> u32 { - FanControl::UnsupportedFeature as u32 // TODO + fn fan_control_state(&self) -> zbus::fdo::Result { + Err(zbus::fdo::Error::UnknownProperty(String::from("This property can't currently be read"))) } #[zbus(property)] @@ -138,12 +129,6 @@ impl SteamOSManager { let state = match state { FanControl::OS => "stop", FanControl::BIOS => "start", - FanControl::UnsupportedFeature => { - return Err(zbus::fdo::Error::InvalidArgs(String::from( - "Can't set state to unsupported", - )) - .into()) - } }; // Run what steamos-polkit-helpers/jupiter-fan-control does @@ -157,10 +142,10 @@ impl SteamOSManager { } #[zbus(property)] - async fn hardware_currently_supported(&self) -> u32 { + async fn hardware_currently_supported(&self) -> zbus::fdo::Result { match check_support().await { - Ok(res) => res as u32, - Err(_) => HardwareCurrentlySupported::UnsupportedFeature as u32, + Ok(res) => Ok(res as u32), + Err(e) => Err(anyhow_to_zbus_fdo(e)), } } @@ -246,10 +231,10 @@ impl SteamOSManager { } #[zbus(property)] - async fn gpu_performance_level(&self) -> u32 { + async fn gpu_performance_level(&self) -> zbus::fdo::Result { match get_gpu_performance_level().await { - Ok(level) => level as u32, - Err(_) => GPUPerformanceLevel::UnsupportedFeature as u32, + Ok(level) => Ok(level as u32), + Err(e) => Err(anyhow_to_zbus_fdo(e)), } } @@ -288,9 +273,6 @@ impl SteamOSManager { // Return false on error let wanted_mode = match WifiDebugMode::try_from(mode) { - Ok(WifiDebugMode::UnsupportedFeature) => { - return Err(zbus::fdo::Error::InvalidArgs(String::from("Invalid mode"))) - } Ok(mode) => mode, Err(e) => return Err(zbus::fdo::Error::InvalidArgs(e.to_string())), }; diff --git a/src/power.rs b/src/power.rs index cff8a49..ef97e53 100644 --- a/src/power.rs +++ b/src/power.rs @@ -22,7 +22,6 @@ const GPU_CLOCKS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum GPUPerformanceLevel { - UnsupportedFeature = 0, Auto = 1, Low = 2, High = 3, @@ -34,9 +33,6 @@ impl TryFrom for GPUPerformanceLevel { type Error = &'static str; fn try_from(v: u32) -> Result { match v { - x if x == GPUPerformanceLevel::UnsupportedFeature as u32 => { - Ok(GPUPerformanceLevel::UnsupportedFeature) - } x if x == GPUPerformanceLevel::Auto as u32 => Ok(GPUPerformanceLevel::Auto), x if x == GPUPerformanceLevel::Low as u32 => Ok(GPUPerformanceLevel::Low), x if x == GPUPerformanceLevel::High as u32 => Ok(GPUPerformanceLevel::High), @@ -63,17 +59,15 @@ impl FromStr for GPUPerformanceLevel { } } -impl TryInto for GPUPerformanceLevel { - type Error = Error; - fn try_into(self) -> Result { - Ok(String::from(match self { +impl ToString for GPUPerformanceLevel { + fn to_string(&self) -> String { + String::from(match self { GPUPerformanceLevel::Auto => "auto", GPUPerformanceLevel::Low => "low", GPUPerformanceLevel::High => "high", GPUPerformanceLevel::Manual => "manual", GPUPerformanceLevel::ProfilePeak => "peak_performance", - GPUPerformanceLevel::UnsupportedFeature => bail!("No valid string representation"), - })) + }) } } @@ -90,7 +84,7 @@ pub async fn set_gpu_performance_level(level: GPUPerformanceLevel) -> Result<()> .await .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; - let level: String = level.try_into()?; + let level: String = level.to_string(); myfile .write_all(level.as_bytes()) diff --git a/src/wifi.rs b/src/wifi.rs index c87df5b..7882fbd 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -29,7 +29,6 @@ const MIN_BUFFER_SIZE: u32 = 100; #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum WifiDebugMode { - UnsupportedFeature = 0, Off = 1, On = 2, } @@ -37,7 +36,6 @@ pub enum WifiDebugMode { #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum WifiPowerManagement { - UnsupportedFeature = 0, Disabled = 1, Enabled = 2, } @@ -46,9 +44,6 @@ impl TryFrom for WifiDebugMode { type Error = &'static str; fn try_from(v: u32) -> Result { match v { - x if x == WifiDebugMode::UnsupportedFeature as u32 => { - Ok(WifiDebugMode::UnsupportedFeature) - } x if x == WifiDebugMode::Off as u32 => Ok(WifiDebugMode::Off), x if x == WifiDebugMode::On as u32 => Ok(WifiDebugMode::On), _ => Err("No enum match for value {v}"), @@ -59,7 +54,6 @@ impl TryFrom for WifiDebugMode { impl fmt::Display for WifiDebugMode { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - WifiDebugMode::UnsupportedFeature => write!(f, "Unsupported feature"), WifiDebugMode::Off => write!(f, "Off"), WifiDebugMode::On => write!(f, "On"), } @@ -70,9 +64,6 @@ impl TryFrom for WifiPowerManagement { type Error = &'static str; fn try_from(v: u32) -> Result { match v { - x if x == WifiPowerManagement::UnsupportedFeature as u32 => { - Ok(WifiPowerManagement::UnsupportedFeature) - } x if x == WifiPowerManagement::Disabled as u32 => Ok(WifiPowerManagement::Disabled), x if x == WifiPowerManagement::Enabled as u32 => Ok(WifiPowerManagement::Enabled), _ => Err("No enum match for value {v}"), @@ -83,7 +74,6 @@ impl TryFrom for WifiPowerManagement { impl fmt::Display for WifiPowerManagement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - WifiPowerManagement::UnsupportedFeature => write!(f, "Unsupported feature"), WifiPowerManagement::Disabled => write!(f, "Disabled"), WifiPowerManagement::Enabled => write!(f, "Enabled"), } @@ -188,10 +178,6 @@ pub async fn set_wifi_debug_mode( }; } } - mode => { - // Invalid mode requested, more coming later, but add this catch-all for now - bail!("Invalid wifi debug mode {mode} requested"); - } } Ok(()) } From b0628fc7b9ddb7f24f891967e417cea3233d5fe0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Mar 2024 16:18:01 -0700 Subject: [PATCH 07/35] Add support to get/set WiFi back-end This change implements the ability to get and set the WiFi back-end, by leveraging a new script created for that purpose. The idea is to be able to use 'wpa_supplicant' (which is the default back-end in NetworkManager upstream) when 'iwd' (the default in SteamOS) does not work for some reason. This change interacts with another feature of steamos-manager, which is to set debug mode for WiFi. Handling debug mode complicates things substantially because those methods write config for 'iwd' and restart the daemons/systemd units, with many interdependencies. Instead of trying to implement all at once and attempt feature-parity between back-ends on this aspect from the start, as a first step the operations are just declared incompatible. As a result, if WifiDebugMode=on the back-end cannot be changed, and conversely the WifiDebugMode cannot be turned on when the back-end is 'wpa_supplicant'. Co-authored-by: Manuel A. Fernandez Montecelo --- LICENSE | 1 + src/manager.rs | 52 +++++++++++++++++++++++++++++++++- src/wifi.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 125 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 658021b..0cf8237 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ Copyright © 2023 Collabora Ltd. Copyright © 2024 Valve Software +Copyright © 2024 Igalia S.L. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in diff --git a/src/manager.rs b/src/manager.rs index 441df10..592ceaa 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -1,6 +1,7 @@ /* * Copyright © 2023 Collabora Ltd. * Copyright © 2024 Valve Software + * Copyright © 2024 Igalia S.L. * * SPDX-License-Identifier: MIT */ @@ -17,7 +18,10 @@ use crate::power::{ GPUPerformanceLevel, }; use crate::process::{run_script, script_output, SYSTEMCTL_PATH}; -use crate::wifi::{set_wifi_debug_mode, WifiDebugMode, WifiPowerManagement}; +use crate::wifi::{ + get_wifi_backend_from_conf, get_wifi_backend_from_script, set_wifi_backend, + set_wifi_debug_mode, WifiBackend, WifiDebugMode, WifiPowerManagement, +}; use crate::{anyhow_to_zbus, anyhow_to_zbus_fdo}; #[derive(PartialEq, Debug, Copy, Clone)] @@ -55,6 +59,7 @@ impl fmt::Display for FanControl { } pub struct SteamOSManager { + wifi_backend: WifiBackend, wifi_debug_mode: WifiDebugMode, // Whether we should use trace-cmd or not. // True on galileo devices, false otherwise @@ -64,6 +69,7 @@ pub struct SteamOSManager { impl SteamOSManager { pub async fn new() -> Result { Ok(SteamOSManager { + wifi_backend: get_wifi_backend_from_conf().await?, wifi_debug_mode: WifiDebugMode::Off, should_trace: variant().await? == HardwareVariant::Galileo, }) @@ -263,6 +269,35 @@ impl SteamOSManager { self.wifi_debug_mode as u32 } + /// WifiBackend property. + #[zbus(property)] + async fn wifi_backend(&self) -> u32 { + self.wifi_backend as u32 + } + + #[zbus(property)] + async fn set_wifi_backend(&mut self, backend: u32) -> zbus::fdo::Result<()> { + if self.wifi_debug_mode == WifiDebugMode::On { + return Err(zbus::fdo::Error::Failed(String::from( + "operation not supported when wifi_debug_mode=on", + ))); + } + let backend = match WifiBackend::try_from(backend) { + Ok(backend) => backend, + Err(e) => return Err(zbus::fdo::Error::InvalidArgs(e.to_string())), + }; + match set_wifi_backend(backend).await { + Ok(()) => { + self.wifi_backend = backend; + Ok(()) + } + Err(e) => { + error!("Setting wifi backend failed: {e}"); + Err(anyhow_to_zbus_fdo(e)) + } + } + } + async fn set_wifi_debug_mode( &mut self, mode: u32, @@ -271,6 +306,15 @@ impl SteamOSManager { // Set the wifi debug mode to mode, using an int for flexibility going forward but only // doing things on 0 or 1 for now // Return false on error + match get_wifi_backend_from_script().await { + Ok(WifiBackend::IWD) => (), + Ok(backend) => { + return Err(zbus::fdo::Error::Failed(format!( + "Setting wifi debug mode not supported when backend is {backend}", + ))); + } + Err(e) => return Err(anyhow_to_zbus_fdo(e)), + } let wanted_mode = match WifiDebugMode::try_from(mode) { Ok(mode) => mode, @@ -319,6 +363,12 @@ mod test { write(crate::path("/sys/class/dmi/id/board_name"), "Jupiter\n") .await .expect("write"); + create_dir_all(crate::path("/etc/NetworkManager/conf.d")) + .await + .expect("create_dir_all"); + write(crate::path("/etc/NetworkManager/conf.d/wifi_backend.conf"), "wifi.backend=iwd\n") + .await + .expect("write"); let manager = SteamOSManager::new().await.unwrap(); let connection = ConnectionBuilder::session() diff --git a/src/wifi.rs b/src/wifi.rs index 7882fbd..5ac9e4f 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -5,12 +5,14 @@ * SPDX-License-Identifier: MIT */ -use anyhow::{bail, ensure, Result}; +use anyhow::{bail, ensure, Error, Result}; use std::fmt; +use std::str::FromStr; use tokio::fs; use tracing::error; -use crate::process::{run_script, SYSTEMCTL_PATH}; +use crate::path; +use crate::process::{run_script, script_output, SYSTEMCTL_PATH}; const OVERRIDE_CONTENTS: &str = "[Service] ExecStart= @@ -26,6 +28,8 @@ const TRACE_CMD_PATH: &str = "/usr/bin/trace-cmd"; const MIN_BUFFER_SIZE: u32 = 100; +const WIFI_BACKEND_PATH: &str = "/etc/NetworkManager/conf.d/wifi_backend.conf"; + #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum WifiDebugMode { @@ -40,6 +44,13 @@ pub enum WifiPowerManagement { Enabled = 2, } +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +pub enum WifiBackend { + IWD = 1, + WPASupplicant = 2, +} + impl TryFrom for WifiDebugMode { type Error = &'static str; fn try_from(v: u32) -> Result { @@ -80,6 +91,37 @@ impl fmt::Display for WifiPowerManagement { } } +impl TryFrom for WifiBackend { + type Error = &'static str; + fn try_from(v: u32) -> Result { + match v { + x if x == WifiBackend::IWD as u32 => Ok(WifiBackend::IWD), + x if x == WifiBackend::WPASupplicant as u32 => Ok(WifiBackend::WPASupplicant), + _ => Err("No enum match for WifiBackend value {v}"), + } + } +} + +impl FromStr for WifiBackend { + type Err = Error; + fn from_str(input: &str) -> Result { + Ok(match input { + "iwd" => WifiBackend::IWD, + "wpa_supplicant" => WifiBackend::WPASupplicant, + _ => bail!("Unknown backend"), + }) + } +} + +impl fmt::Display for WifiBackend { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WifiBackend::IWD => write!(f, "iwd"), + WifiBackend::WPASupplicant => write!(f, "wpa_supplicant"), + } + } +} + pub async fn setup_iwd_config(want_override: bool) -> std::io::Result<()> { // Copy override.conf file into place or out of place depending // on install value @@ -181,3 +223,32 @@ pub async fn set_wifi_debug_mode( } Ok(()) } + +pub async fn get_wifi_backend_from_conf() -> Result { + let wifi_backend_contents = fs::read_to_string(path(WIFI_BACKEND_PATH)) + .await? + .trim() + .to_string(); + for line in wifi_backend_contents.lines() { + if line.starts_with("wifi.backend=") { + let backend = line.trim_start_matches("wifi.backend=").trim(); + return WifiBackend::from_str(backend); + } + } + + bail!("WiFi backend not found in config"); +} + +pub async fn get_wifi_backend_from_script() -> Result { + let result = script_output("/usr/bin/steamos-wifi-set-backend", &["--check"]).await?; + WifiBackend::from_str(result.trim()) +} + +pub async fn set_wifi_backend(backend: WifiBackend) -> Result<()> { + run_script( + "set wifi backend", + "/usr/bin/steamos-wifi-set-backend", + &[backend.to_string()], + ) + .await +} From 86515aceaf29041d16be17d855895c3e72fb5db5 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Mar 2024 16:18:53 -0700 Subject: [PATCH 08/35] Run cargo fmt --- src/manager.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 592ceaa..caffb44 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -98,7 +98,9 @@ impl SteamOSManager { #[zbus(property)] fn wifi_power_management_state(&self) -> zbus::fdo::Result { - Err(zbus::fdo::Error::UnknownProperty(String::from("This property can't currently be read"))) + Err(zbus::fdo::Error::UnknownProperty(String::from( + "This property can't currently be read", + ))) } #[zbus(property)] @@ -123,7 +125,9 @@ impl SteamOSManager { #[zbus(property)] fn fan_control_state(&self) -> zbus::fdo::Result { - Err(zbus::fdo::Error::UnknownProperty(String::from("This property can't currently be read"))) + Err(zbus::fdo::Error::UnknownProperty(String::from( + "This property can't currently be read", + ))) } #[zbus(property)] @@ -366,9 +370,12 @@ mod test { create_dir_all(crate::path("/etc/NetworkManager/conf.d")) .await .expect("create_dir_all"); - write(crate::path("/etc/NetworkManager/conf.d/wifi_backend.conf"), "wifi.backend=iwd\n") - .await - .expect("write"); + write( + crate::path("/etc/NetworkManager/conf.d/wifi_backend.conf"), + "wifi.backend=iwd\n", + ) + .await + .expect("write"); let manager = SteamOSManager::new().await.unwrap(); let connection = ConnectionBuilder::session() From 9ec6fc8852c6b72514fed80f54db1237b5bf2b7b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Mar 2024 16:38:08 -0700 Subject: [PATCH 09/35] manager: Clean up Result<_, zbus::fdo::Error> into zbus::fdo::Result<_> --- src/manager.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index caffb44..5d2bc10 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -176,7 +176,7 @@ impl SteamOSManager { } } - async fn get_als_integration_time_file_descriptor(&self) -> Result { + async fn get_als_integration_time_file_descriptor(&self) -> zbus::fdo::Result { // Get the file descriptor for the als integration time sysfs path let result = File::create(ALS_INTEGRATION_PATH).await; match result { @@ -188,7 +188,7 @@ impl SteamOSManager { } } - async fn update_bios(&self) -> Result<(), zbus::fdo::Error> { + async fn update_bios(&self) -> zbus::fdo::Result<()> { // Update the bios as needed run_script( "update bios", @@ -199,7 +199,7 @@ impl SteamOSManager { .map_err(anyhow_to_zbus_fdo) } - async fn update_dock(&self) -> Result<(), zbus::fdo::Error> { + async fn update_dock(&self) -> zbus::fdo::Result<()> { // Update the dock firmware as needed run_script( "update dock firmware", @@ -210,7 +210,7 @@ impl SteamOSManager { .map_err(anyhow_to_zbus_fdo) } - async fn trim_devices(&self) -> Result<(), zbus::fdo::Error> { + async fn trim_devices(&self) -> zbus::fdo::Result<()> { // Run steamos-trim-devices script run_script( "trim devices", @@ -226,7 +226,7 @@ impl SteamOSManager { device: &str, label: &str, validate: bool, - ) -> Result<(), zbus::fdo::Error> { + ) -> zbus::fdo::Result<()> { let mut args = vec!["--label", label, "--device", device]; if !validate { args.push("--skip-validation"); @@ -302,11 +302,7 @@ impl SteamOSManager { } } - async fn set_wifi_debug_mode( - &mut self, - mode: u32, - buffer_size: u32, - ) -> Result<(), zbus::fdo::Error> { + async fn set_wifi_debug_mode(&mut self, mode: u32, buffer_size: u32) -> zbus::fdo::Result<()> { // Set the wifi debug mode to mode, using an int for flexibility going forward but only // doing things on 0 or 1 for now // Return false on error From 853ce3dd84831cd630b726442b0c5a9d6111b9fd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Mar 2024 17:58:11 -0700 Subject: [PATCH 10/35] systemd: Add new module for interacting with systemd --- src/main.rs | 15 ++++++----- src/manager.rs | 46 +++++++++++++++++++------------- src/process.rs | 2 -- src/systemd.rs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/wifi.rs | 28 ++++++++++---------- 5 files changed, 123 insertions(+), 40 deletions(-) create mode 100644 src/systemd.rs diff --git a/src/main.rs b/src/main.rs index 714f7d3..47219ff 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,6 +26,7 @@ mod manager; mod power; mod process; mod sls; +mod systemd; mod wifi; #[cfg(test)] @@ -132,14 +133,16 @@ pub fn anyhow_to_zbus_fdo(error: Error) -> zbus::fdo::Error { } async fn create_connection() -> Result { - let manager = manager::SteamOSManager::new().await?; - - ConnectionBuilder::system()? + let connection = ConnectionBuilder::system()? .name("com.steampowered.SteamOSManager1.Manager")? - .serve_at("/com/steampowered/SteamOSManager1", manager)? .build() - .await - .map_err(|e| e.into()) + .await?; + let manager = manager::SteamOSManager::new(connection.clone()).await?; + connection + .object_server() + .at("/com/steampowered/SteamOSManager1", manager) + .await?; + Ok(connection) } #[tokio::main] diff --git a/src/manager.rs b/src/manager.rs index 5d2bc10..4202588 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -10,14 +10,16 @@ use anyhow::Result; use std::fmt; use tokio::fs::File; use tracing::error; -use zbus::{interface, zvariant::Fd}; +use zbus::zvariant::Fd; +use zbus::{interface, Connection}; use crate::hardware::{check_support, variant, HardwareVariant}; use crate::power::{ get_gpu_performance_level, set_gpu_clocks, set_gpu_performance_level, set_tdp_limit, GPUPerformanceLevel, }; -use crate::process::{run_script, script_output, SYSTEMCTL_PATH}; +use crate::process::{run_script, script_output}; +use crate::systemd::SystemdUnit; use crate::wifi::{ get_wifi_backend_from_conf, get_wifi_backend_from_script, set_wifi_backend, set_wifi_debug_mode, WifiBackend, WifiDebugMode, WifiPowerManagement, @@ -59,6 +61,7 @@ impl fmt::Display for FanControl { } pub struct SteamOSManager { + connection: Connection, wifi_backend: WifiBackend, wifi_debug_mode: WifiDebugMode, // Whether we should use trace-cmd or not. @@ -67,8 +70,9 @@ pub struct SteamOSManager { } impl SteamOSManager { - pub async fn new() -> Result { + pub async fn new(connection: Connection) -> Result { Ok(SteamOSManager { + connection, wifi_backend: get_wifi_backend_from_conf().await?, wifi_debug_mode: WifiDebugMode::Off, should_trace: variant().await? == HardwareVariant::Galileo, @@ -136,18 +140,15 @@ impl SteamOSManager { Ok(state) => state, Err(err) => return Err(zbus::fdo::Error::InvalidArgs(err.to_string()).into()), }; - let state = match state { - FanControl::OS => "stop", - FanControl::BIOS => "start", - }; - // Run what steamos-polkit-helpers/jupiter-fan-control does - run_script( - "enable fan control", - SYSTEMCTL_PATH, - &[state, "jupiter-fan-control-service"], - ) - .await + let jupiter_fan_control = + SystemdUnit::new(self.connection.clone(), "jupiter_2dfan_2dcontrol_2eservice") + .await + .map_err(anyhow_to_zbus)?; + match state { + FanControl::OS => jupiter_fan_control.start().await, + FanControl::BIOS => jupiter_fan_control.stop().await, + } .map_err(anyhow_to_zbus) } @@ -320,7 +321,14 @@ impl SteamOSManager { Ok(mode) => mode, Err(e) => return Err(zbus::fdo::Error::InvalidArgs(e.to_string())), }; - match set_wifi_debug_mode(wanted_mode, buffer_size, self.should_trace).await { + match set_wifi_debug_mode( + wanted_mode, + buffer_size, + self.should_trace, + self.connection.clone(), + ) + .await + { Ok(()) => { self.wifi_debug_mode = wanted_mode; Ok(()) @@ -373,16 +381,18 @@ mod test { .await .expect("write"); - let manager = SteamOSManager::new().await.unwrap(); let connection = ConnectionBuilder::session() .unwrap() .name("com.steampowered.SteamOSManager1.Test") .unwrap() - .serve_at("/com/steampowered/SteamOSManager1", manager) - .unwrap() .build() .await .unwrap(); + let manager = SteamOSManager::new(connection.clone()).await.unwrap(); + connection.object_server() + .at("/com/steampowered/SteamOSManager1", manager) + .await + .expect("object_server at"); TestHandle { handle, connection } } diff --git a/src/process.rs b/src/process.rs index 0398ad1..443ffd8 100644 --- a/src/process.rs +++ b/src/process.rs @@ -10,8 +10,6 @@ use std::ffi::OsStr; use tokio::process::Command; use tracing::warn; -pub const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl"; - pub async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> Result { // Run given script and return the exit code let mut child = Command::new(executable).args(args).spawn()?; diff --git a/src/systemd.rs b/src/systemd.rs new file mode 100644 index 0000000..e8d4395 --- /dev/null +++ b/src/systemd.rs @@ -0,0 +1,72 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::{anyhow, Result}; +use std::path::PathBuf; +use zbus::zvariant::OwnedObjectPath; +use zbus::Connection; + +#[zbus::proxy( + interface = "org.freedesktop.systemd1.Unit", + default_service = "org.freedesktop.systemd1" +)] +trait SystemdUnit { + #[zbus(property)] + fn active_state(&self) -> Result; + + async fn restart(&self, mode: &str) -> Result; + async fn start(&self, mode: &str) -> Result; + async fn stop(&self, mode: &str) -> Result; +} + +#[zbus::proxy( + interface = "org.freedesktop.systemd1.Manager", + default_service = "org.freedesktop.systemd1", + default_path = "/org/freedesktop/systemd1" +)] +trait SystemdManager { + async fn reload(&self) -> Result<()>; +} + +pub struct SystemdUnit<'dbus> { + proxy: SystemdUnitProxy<'dbus>, +} + +pub async fn daemon_reload(connection: &Connection) -> Result<()> { + let proxy = SystemdManagerProxy::new(&connection).await?; + proxy.reload().await?; + Ok(()) +} + +impl<'dbus> SystemdUnit<'dbus> { + pub async fn new(connection: Connection, name: &str) -> Result> { + let path = PathBuf::from("/org/freedesktop/systemd1/unit").join(name); + let path = String::from(path.to_str().ok_or(anyhow!("Unit name {name} invalid"))?); + Ok(SystemdUnit { + proxy: SystemdUnitProxy::builder(&connection) + .cache_properties(zbus::CacheProperties::No) + .path(path)? + .build() + .await?, + }) + } + + pub async fn restart(&self) -> Result<()> { + self.proxy.restart("fail").await?; + Ok(()) + } + + pub async fn start(&self) -> Result<()> { + self.proxy.start("fail").await?; + Ok(()) + } + + pub async fn stop(&self) -> Result<()> { + self.proxy.stop("fail").await?; + Ok(()) + } +} diff --git a/src/wifi.rs b/src/wifi.rs index 5ac9e4f..65035c6 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -10,9 +10,11 @@ use std::fmt; use std::str::FromStr; use tokio::fs; use tracing::error; +use zbus::Connection; use crate::path; -use crate::process::{run_script, script_output, SYSTEMCTL_PATH}; +use crate::process::{run_script, script_output}; +use crate::systemd::{daemon_reload, SystemdUnit}; const OVERRIDE_CONTENTS: &str = "[Service] ExecStart= @@ -138,19 +140,16 @@ pub async fn setup_iwd_config(want_override: bool) -> std::io::Result<()> { } } -async fn restart_iwd() -> Result<()> { +async fn restart_iwd(connection: Connection) -> Result<()> { // First reload systemd since we modified the config most likely // otherwise we wouldn't be restarting iwd. - match run_script("reload systemd", SYSTEMCTL_PATH, &["daemon-reload"]).await { - Ok(_) => { - // worked, now restart iwd - run_script("restart iwd", SYSTEMCTL_PATH, &["restart", "iwd"]).await - } - Err(message) => { - error!("restart_iwd: reload systemd got an error: {message}"); - Err(message) - } - } + daemon_reload(&connection).await.inspect_err(|message| + error!("restart_iwd: reload systemd got an error: {message}"))?; + + // worked, now restart iwd + let unit = SystemdUnit::new(connection, "iwd_2eservice").await?; + unit.restart().await.inspect_err(|message| + error!("restart_iwd: restart unit got an error: {message}")) } async fn stop_tracing() -> Result<()> { @@ -180,6 +179,7 @@ pub async fn set_wifi_debug_mode( mode: WifiDebugMode, buffer_size: u32, should_trace: bool, + connection: Connection, ) -> Result<()> { // Set the wifi debug mode to mode, using an int for flexibility going forward but only // doing things on 0 or 1 for now @@ -199,7 +199,7 @@ pub async fn set_wifi_debug_mode( bail!("setup_iwd_config false got an error: {message}"); }; // setup_iwd_config false worked - if let Err(message) = restart_iwd().await { + if let Err(message) = restart_iwd(connection).await { bail!("restart_iwd got an error: {message}"); }; } @@ -210,7 +210,7 @@ pub async fn set_wifi_debug_mode( bail!("setup_iwd_config true got an error: {message}"); } // setup_iwd_config worked - if let Err(message) = restart_iwd().await { + if let Err(message) = restart_iwd(connection).await { bail!("restart_iwd got an error: {message}"); }; // restart_iwd worked From 406988fbdf968f19ac40ce781f40d57052cef11b Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Mar 2024 18:04:06 -0700 Subject: [PATCH 11/35] manager: Implement fan_control_state --- src/manager.rs | 15 +++++++++++---- src/systemd.rs | 4 ++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 4202588..a51240a 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -128,10 +128,17 @@ impl SteamOSManager { } #[zbus(property)] - fn fan_control_state(&self) -> zbus::fdo::Result { - Err(zbus::fdo::Error::UnknownProperty(String::from( - "This property can't currently be read", - ))) + async fn fan_control_state(&self) -> zbus::fdo::Result { + let jupiter_fan_control = + SystemdUnit::new(self.connection.clone(), "jupiter_2dfan_2dcontrol_2eservice") + .await + .map_err(anyhow_to_zbus_fdo)?; + let active = jupiter_fan_control.active().await + .map_err(anyhow_to_zbus_fdo)?; + Ok(match active { + true => FanControl::OS as u32, + false => FanControl::BIOS as u32, + }) } #[zbus(property)] diff --git a/src/systemd.rs b/src/systemd.rs index e8d4395..6c111f5 100644 --- a/src/systemd.rs +++ b/src/systemd.rs @@ -69,4 +69,8 @@ impl<'dbus> SystemdUnit<'dbus> { self.proxy.stop("fail").await?; Ok(()) } + + pub async fn active(&self) -> Result { + Ok(self.proxy.active_state().await? == "active") + } } From 8d92fae7dbf84e5e34ebba782eba4179c76b431a Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 19:12:34 -0700 Subject: [PATCH 12/35] manager: Add stub implementations of min/max properties --- src/manager.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/manager.rs b/src/manager.rs index a51240a..2e2a74c 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -271,10 +271,34 @@ impl SteamOSManager { set_gpu_clocks(clocks).await.is_ok() } + #[zbus(property)] + async fn manual_gpu_clock_min(&self) -> u32 { + // TODO: Can this be queried from somewhere? + 200 + } + + #[zbus(property)] + async fn manual_gpu_clock_max(&self) -> u32 { + // TODO: Can this be queried from somewhere? + 1600 + } + async fn set_tdp_limit(&self, limit: i32) -> bool { set_tdp_limit(limit).await.is_ok() } + #[zbus(property)] + async fn tdp_limit_min(&self) -> u32 { + // TODO: Can this be queried from somewhere? + 3 + } + + #[zbus(property)] + async fn tdp_limit_max(&self) -> u32 { + // TODO: Can this be queried from somewhere? + 15 + } + #[zbus(property)] async fn wifi_debug_mode_state(&self) -> u32 { // Get the wifi debug mode From b0250bda0101adfc1c7c1ec1e1cfa09d2d0f9c58 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 19:13:55 -0700 Subject: [PATCH 13/35] manager: Fix method order --- src/manager.rs | 58 +++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 2e2a74c..88f60da 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -305,35 +305,6 @@ impl SteamOSManager { self.wifi_debug_mode as u32 } - /// WifiBackend property. - #[zbus(property)] - async fn wifi_backend(&self) -> u32 { - self.wifi_backend as u32 - } - - #[zbus(property)] - async fn set_wifi_backend(&mut self, backend: u32) -> zbus::fdo::Result<()> { - if self.wifi_debug_mode == WifiDebugMode::On { - return Err(zbus::fdo::Error::Failed(String::from( - "operation not supported when wifi_debug_mode=on", - ))); - } - let backend = match WifiBackend::try_from(backend) { - Ok(backend) => backend, - Err(e) => return Err(zbus::fdo::Error::InvalidArgs(e.to_string())), - }; - match set_wifi_backend(backend).await { - Ok(()) => { - self.wifi_backend = backend; - Ok(()) - } - Err(e) => { - error!("Setting wifi backend failed: {e}"); - Err(anyhow_to_zbus_fdo(e)) - } - } - } - async fn set_wifi_debug_mode(&mut self, mode: u32, buffer_size: u32) -> zbus::fdo::Result<()> { // Set the wifi debug mode to mode, using an int for flexibility going forward but only // doing things on 0 or 1 for now @@ -371,6 +342,35 @@ impl SteamOSManager { } } + /// WifiBackend property. + #[zbus(property)] + async fn wifi_backend(&self) -> u32 { + self.wifi_backend as u32 + } + + #[zbus(property)] + async fn set_wifi_backend(&mut self, backend: u32) -> zbus::fdo::Result<()> { + if self.wifi_debug_mode == WifiDebugMode::On { + return Err(zbus::fdo::Error::Failed(String::from( + "operation not supported when wifi_debug_mode=on", + ))); + } + let backend = match WifiBackend::try_from(backend) { + Ok(backend) => backend, + Err(e) => return Err(zbus::fdo::Error::InvalidArgs(e.to_string())), + }; + match set_wifi_backend(backend).await { + Ok(()) => { + self.wifi_backend = backend; + Ok(()) + } + Err(e) => { + error!("Setting wifi backend failed: {e}"); + Err(anyhow_to_zbus_fdo(e)) + } + } + } + /// A version property. #[zbus(property)] async fn version(&self) -> u32 { From 9431ae94741195a109c764805795afc051e90853 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 19:26:37 -0700 Subject: [PATCH 14/35] wifi: Remove get_wifi_backend_from_script The script does exactly the same thing as what the _from_conf function did --- src/manager.rs | 26 +++++++++----------------- src/wifi.rs | 9 ++------- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 88f60da..d989e5e 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -21,8 +21,8 @@ use crate::power::{ use crate::process::{run_script, script_output}; use crate::systemd::SystemdUnit; use crate::wifi::{ - get_wifi_backend_from_conf, get_wifi_backend_from_script, set_wifi_backend, - set_wifi_debug_mode, WifiBackend, WifiDebugMode, WifiPowerManagement, + get_wifi_backend, set_wifi_backend, set_wifi_debug_mode, WifiBackend, WifiDebugMode, + WifiPowerManagement, }; use crate::{anyhow_to_zbus, anyhow_to_zbus_fdo}; @@ -62,7 +62,6 @@ impl fmt::Display for FanControl { pub struct SteamOSManager { connection: Connection, - wifi_backend: WifiBackend, wifi_debug_mode: WifiDebugMode, // Whether we should use trace-cmd or not. // True on galileo devices, false otherwise @@ -73,7 +72,6 @@ impl SteamOSManager { pub async fn new(connection: Connection) -> Result { Ok(SteamOSManager { connection, - wifi_backend: get_wifi_backend_from_conf().await?, wifi_debug_mode: WifiDebugMode::Off, should_trace: variant().await? == HardwareVariant::Galileo, }) @@ -309,7 +307,7 @@ impl SteamOSManager { // Set the wifi debug mode to mode, using an int for flexibility going forward but only // doing things on 0 or 1 for now // Return false on error - match get_wifi_backend_from_script().await { + match get_wifi_backend().await { Ok(WifiBackend::IWD) => (), Ok(backend) => { return Err(zbus::fdo::Error::Failed(format!( @@ -344,8 +342,11 @@ impl SteamOSManager { /// WifiBackend property. #[zbus(property)] - async fn wifi_backend(&self) -> u32 { - self.wifi_backend as u32 + async fn wifi_backend(&self) -> zbus::fdo::Result { + match get_wifi_backend().await { + Ok(backend) => Ok(backend as u32), + Err(e) => Err(anyhow_to_zbus_fdo(e)), + } } #[zbus(property)] @@ -359,16 +360,7 @@ impl SteamOSManager { Ok(backend) => backend, Err(e) => return Err(zbus::fdo::Error::InvalidArgs(e.to_string())), }; - match set_wifi_backend(backend).await { - Ok(()) => { - self.wifi_backend = backend; - Ok(()) - } - Err(e) => { - error!("Setting wifi backend failed: {e}"); - Err(anyhow_to_zbus_fdo(e)) - } - } + set_wifi_backend(backend).await.map_err(anyhow_to_zbus_fdo) } /// A version property. diff --git a/src/wifi.rs b/src/wifi.rs index 65035c6..801e0ec 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -13,7 +13,7 @@ use tracing::error; use zbus::Connection; use crate::path; -use crate::process::{run_script, script_output}; +use crate::process::run_script; use crate::systemd::{daemon_reload, SystemdUnit}; const OVERRIDE_CONTENTS: &str = "[Service] @@ -224,7 +224,7 @@ pub async fn set_wifi_debug_mode( Ok(()) } -pub async fn get_wifi_backend_from_conf() -> Result { +pub async fn get_wifi_backend() -> Result { let wifi_backend_contents = fs::read_to_string(path(WIFI_BACKEND_PATH)) .await? .trim() @@ -239,11 +239,6 @@ pub async fn get_wifi_backend_from_conf() -> Result { bail!("WiFi backend not found in config"); } -pub async fn get_wifi_backend_from_script() -> Result { - let result = script_output("/usr/bin/steamos-wifi-set-backend", &["--check"]).await?; - WifiBackend::from_str(result.trim()) -} - pub async fn set_wifi_backend(backend: WifiBackend) -> Result<()> { run_script( "set wifi backend", From 36c34fcbdaf656378e383061cfcdd553389da265 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 19:26:49 -0700 Subject: [PATCH 15/35] Run cargo fmt --- src/manager.rs | 9 ++++++--- src/wifi.rs | 10 ++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index d989e5e..529cb58 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -131,8 +131,10 @@ impl SteamOSManager { SystemdUnit::new(self.connection.clone(), "jupiter_2dfan_2dcontrol_2eservice") .await .map_err(anyhow_to_zbus_fdo)?; - let active = jupiter_fan_control.active().await - .map_err(anyhow_to_zbus_fdo)?; + let active = jupiter_fan_control + .active() + .await + .map_err(anyhow_to_zbus_fdo)?; Ok(match active { true => FanControl::OS as u32, false => FanControl::BIOS as u32, @@ -412,7 +414,8 @@ mod test { .await .unwrap(); let manager = SteamOSManager::new(connection.clone()).await.unwrap(); - connection.object_server() + connection + .object_server() .at("/com/steampowered/SteamOSManager1", manager) .await .expect("object_server at"); diff --git a/src/wifi.rs b/src/wifi.rs index 801e0ec..7cd524b 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -143,13 +143,15 @@ pub async fn setup_iwd_config(want_override: bool) -> std::io::Result<()> { async fn restart_iwd(connection: Connection) -> Result<()> { // First reload systemd since we modified the config most likely // otherwise we wouldn't be restarting iwd. - daemon_reload(&connection).await.inspect_err(|message| - error!("restart_iwd: reload systemd got an error: {message}"))?; + daemon_reload(&connection) + .await + .inspect_err(|message| error!("restart_iwd: reload systemd got an error: {message}"))?; // worked, now restart iwd let unit = SystemdUnit::new(connection, "iwd_2eservice").await?; - unit.restart().await.inspect_err(|message| - error!("restart_iwd: restart unit got an error: {message}")) + unit.restart() + .await + .inspect_err(|message| error!("restart_iwd: restart unit got an error: {message}")) } async fn stop_tracing() -> Result<()> { From 1c825797be7d20679dcea6133633964c5b30c2fc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 20:21:38 -0700 Subject: [PATCH 16/35] manager: Implement wifi_power_management_state --- src/manager.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 529cb58..2988690 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -99,9 +99,19 @@ impl SteamOSManager { } #[zbus(property)] - fn wifi_power_management_state(&self) -> zbus::fdo::Result { - Err(zbus::fdo::Error::UnknownProperty(String::from( - "This property can't currently be read", + async fn wifi_power_management_state(&self) -> zbus::fdo::Result { + let output = script_output("/usr/bin/iwconfig", &["wlan0"]) + .await + .map_err(anyhow_to_zbus_fdo)?; + for line in output.lines() { + return Ok(match line.trim() { + "Power Management:on" => WifiPowerManagement::Enabled as u32, + "Power Management:off" => WifiPowerManagement::Disabled as u32, + _ => continue, + }); + } + Err(zbus::fdo::Error::Failed(String::from( + "Failed to query power management state", ))) } From c37bd22db019b07eecf15865cbbf59a183df1854 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 20:58:29 -0700 Subject: [PATCH 17/35] process: Use a callback instead of real subprocess in tests --- src/process.rs | 18 ++++++++++++++++++ src/testing.rs | 4 ++++ 2 files changed, 22 insertions(+) diff --git a/src/process.rs b/src/process.rs index 443ffd8..e27f0a5 100644 --- a/src/process.rs +++ b/src/process.rs @@ -10,6 +10,7 @@ use std::ffi::OsStr; use tokio::process::Command; use tracing::warn; +#[cfg(not(test))] pub async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> Result { // Run given script and return the exit code let mut child = Command::new(executable).args(args).spawn()?; @@ -17,6 +18,14 @@ pub async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> R status.code().ok_or(anyhow!("Killed by signal")) } +#[cfg(test)] +pub async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> Result { + let test = crate::testing::current(); + let args: Vec<&OsStr> = args.iter().map(|arg| arg.as_ref()).collect(); + let cb = &test.process_cb; + cb(executable, args.as_ref()).map(|(res, _)| res) +} + pub async fn run_script(name: &str, executable: &str, args: &[impl AsRef]) -> Result<()> { // Run given script to get exit code and return true on success. // Return Err on failure, but also print an error if needed @@ -33,6 +42,7 @@ pub async fn run_script(name: &str, executable: &str, args: &[impl AsRef] } } +#[cfg(not(test))] pub async fn script_output(executable: &str, args: &[impl AsRef]) -> Result { // Run given command and return the output given let output = Command::new(executable).args(args).output(); @@ -42,3 +52,11 @@ pub async fn script_output(executable: &str, args: &[impl AsRef]) -> Resu let s = std::str::from_utf8(&output.stdout)?; Ok(s.to_string()) } + +#[cfg(test)] +pub async fn script_output(executable: &str, args: &[impl AsRef]) -> Result { + let test = crate::testing::current(); + let args: Vec<&OsStr> = args.iter().map(|arg| arg.as_ref()).collect(); + let cb = &test.process_cb; + cb(executable, args.as_ref()).map(|(_, res)| res) +} diff --git a/src/testing.rs b/src/testing.rs index 0276d09..042434d 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -1,4 +1,6 @@ +use anyhow::{anyhow, Result}; use std::cell::RefCell; +use std::ffi::OsStr; use std::path::Path; use std::rc::Rc; use tempfile::{tempdir, TempDir}; @@ -12,6 +14,7 @@ pub fn start() -> TestHandle { assert!(lock.borrow().as_ref().is_none()); let test: Rc = Rc::new(Test { base: tempdir().expect("Couldn't create test directory"), + process_cb: |_, _| Err(anyhow!("No current process_cb")), }); *lock.borrow_mut() = Some(test.clone()); TestHandle { test } @@ -28,6 +31,7 @@ pub fn current() -> Rc { pub struct Test { base: TempDir, + pub process_cb: fn(&str, &[&OsStr]) -> Result<(i32, String)>, } pub struct TestHandle { From 35e520712d07e79820b835125644f84d9d13f473 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 21:10:38 -0700 Subject: [PATCH 18/35] Realign enums that had UnsupportedFeature to start at 0 --- com.steampowered.SteamOSManager1.xml | 18 +++++++++--------- src/hardware.rs | 4 ++-- src/manager.rs | 4 ++-- src/power.rs | 10 +++++----- src/wifi.rs | 12 ++++++------ 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/com.steampowered.SteamOSManager1.xml b/com.steampowered.SteamOSManager1.xml index 58bfac0..223cc49 100644 --- a/com.steampowered.SteamOSManager1.xml +++ b/com.steampowered.SteamOSManager1.xml @@ -47,7 +47,7 @@ Controls the wifi chip's power management features. - Valid states: 0 = Unsupported Feature, 1 = Disabled, 2 = Enabled + Valid states: 0 = Disabled, 1 = Enabled Version available: 7 --> @@ -58,7 +58,7 @@ Controls whether the OS or the BIOS should manage fan speed - Valid states: 0 = Unsupported Feature, 1 = BIOS, 2 = OS + Valid states: 0 = BIOS, 1 = OS Version available: 7 --> @@ -69,7 +69,7 @@ Reports whether the current hardware is supported or not. - Valid states: 0 = Unsupported Feature, 1 = Unsupported, 2 = Supported + Valid states: 0 = Unsupported, 1 = Supported Version available: 7 --> @@ -105,7 +105,7 @@ Reports whether a BIOS update is available. - Valid states: 0 = Unsupported Feature, 1 = Up to date, 2 = Update available + Valid states: 0 = Up to date, 1 = Update available Version available: 7 --> @@ -138,7 +138,7 @@ Reports whether a Dock Firmware update is available. - Valid states: 0 = Unsupported Feature, 1 = Up to date, 2 = Update available + Valid states: 0 = Up to date, 1 = Update available Version available: 7 --> @@ -199,7 +199,7 @@ The current GPU performance level. - Valid states: 0 = Feature Unsupported, 1 = Auto, 2 = Low, 3 = High, 4 = Manual, 5 = Profile Peak + Valid states: 0 = Auto, 1 = Low, 2 = High, 3 = Manual, 4 = Profile Peak Version available: 7 --> @@ -238,7 +238,7 @@ Controls the system TDP limit. - Valid states: 0 = Feature Unsupported, or in range of [ TDPLimitMin, TDPLimitMax ] + Valid states: In range of [ TDPLimitMin, TDPLimitMax ] Version available: 7 --> @@ -268,7 +268,7 @@ Whether wifi debug mode is currently enabled. - Valid states: 0 = Feature Unsupported, 1 = Disabled, 2 = Enabled + Valid states: 0 = Disabled, 1 = Enabled Version available: 7 --> @@ -309,7 +309,7 @@ Controls which Wifi backend is used by NetworkManager - Valid states: 0 = Feature Unsupported, 1 = iwd, 2 = wpa_supplicant + Valid states: 0 = iwd, 1 = wpa_supplicant Version available: 7 --> diff --git a/src/hardware.rs b/src/hardware.rs index e046a47..1d3b449 100644 --- a/src/hardware.rs +++ b/src/hardware.rs @@ -26,8 +26,8 @@ pub enum HardwareVariant { #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum HardwareCurrentlySupported { - Unsupported = 1, - Supported = 2, + Unsupported = 0, + Supported = 1, } impl FromStr for HardwareVariant { diff --git a/src/manager.rs b/src/manager.rs index 2988690..afdb03b 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -36,8 +36,8 @@ enum PrepareFactoryReset { #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] enum FanControl { - BIOS = 1, - OS = 2, + BIOS = 0, + OS = 1, } impl TryFrom for FanControl { diff --git a/src/power.rs b/src/power.rs index ef97e53..60aaf50 100644 --- a/src/power.rs +++ b/src/power.rs @@ -22,11 +22,11 @@ const GPU_CLOCKS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum GPUPerformanceLevel { - Auto = 1, - Low = 2, - High = 3, - Manual = 4, - ProfilePeak = 5, + Auto = 0, + Low = 1, + High = 2, + Manual = 3, + ProfilePeak = 4, } impl TryFrom for GPUPerformanceLevel { diff --git a/src/wifi.rs b/src/wifi.rs index 7cd524b..ef1912f 100644 --- a/src/wifi.rs +++ b/src/wifi.rs @@ -35,22 +35,22 @@ const WIFI_BACKEND_PATH: &str = "/etc/NetworkManager/conf.d/wifi_backend.conf"; #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum WifiDebugMode { - Off = 1, - On = 2, + Off = 0, + On = 1, } #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum WifiPowerManagement { - Disabled = 1, - Enabled = 2, + Disabled = 0, + Enabled = 1, } #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum WifiBackend { - IWD = 1, - WPASupplicant = 2, + IWD = 0, + WPASupplicant = 1, } impl TryFrom for WifiDebugMode { From d751110086d91a00c00c2703dae6ccd17a149ebd Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 21:25:43 -0700 Subject: [PATCH 19/35] manager: Add emits_changed_signal where appropriate --- src/manager.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index afdb03b..54237b0 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -11,7 +11,7 @@ use std::fmt; use tokio::fs::File; use tracing::error; use zbus::zvariant::Fd; -use zbus::{interface, Connection}; +use zbus::{interface, Connection, SignalContext}; use crate::hardware::{check_support, variant, HardwareVariant}; use crate::power::{ @@ -98,7 +98,7 @@ impl SteamOSManager { } } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "false"))] async fn wifi_power_management_state(&self) -> zbus::fdo::Result { let output = script_output("/usr/bin/iwconfig", &["wlan0"]) .await @@ -135,7 +135,7 @@ impl SteamOSManager { .map_err(anyhow_to_zbus) } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "false"))] async fn fan_control_state(&self) -> zbus::fdo::Result { let jupiter_fan_control = SystemdUnit::new(self.connection.clone(), "jupiter_2dfan_2dcontrol_2eservice") @@ -169,7 +169,7 @@ impl SteamOSManager { .map_err(anyhow_to_zbus) } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "const"))] async fn hardware_currently_supported(&self) -> zbus::fdo::Result { match check_support().await { Ok(res) => Ok(res as u32), @@ -177,7 +177,7 @@ impl SteamOSManager { } } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "false"))] async fn als_calibration_gain(&self) -> f64 { // Run script to get calibration value let result = script_output( @@ -258,7 +258,7 @@ impl SteamOSManager { .map_err(anyhow_to_zbus_fdo) } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "false"))] async fn gpu_performance_level(&self) -> zbus::fdo::Result { match get_gpu_performance_level().await { Ok(level) => Ok(level as u32), @@ -281,13 +281,13 @@ impl SteamOSManager { set_gpu_clocks(clocks).await.is_ok() } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "const"))] async fn manual_gpu_clock_min(&self) -> u32 { // TODO: Can this be queried from somewhere? 200 } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "const"))] async fn manual_gpu_clock_max(&self) -> u32 { // TODO: Can this be queried from somewhere? 1600 @@ -297,13 +297,13 @@ impl SteamOSManager { set_tdp_limit(limit).await.is_ok() } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "const"))] async fn tdp_limit_min(&self) -> u32 { // TODO: Can this be queried from somewhere? 3 } - #[zbus(property)] + #[zbus(property(emits_changed_signal = "const"))] async fn tdp_limit_max(&self) -> u32 { // TODO: Can this be queried from somewhere? 15 @@ -315,7 +315,12 @@ impl SteamOSManager { self.wifi_debug_mode as u32 } - async fn set_wifi_debug_mode(&mut self, mode: u32, buffer_size: u32) -> zbus::fdo::Result<()> { + async fn set_wifi_debug_mode( + &mut self, + mode: u32, + buffer_size: u32, + #[zbus(signal_context)] ctx: SignalContext<'_>, + ) -> zbus::fdo::Result<()> { // Set the wifi debug mode to mode, using an int for flexibility going forward but only // doing things on 0 or 1 for now // Return false on error @@ -343,6 +348,7 @@ impl SteamOSManager { { Ok(()) => { self.wifi_debug_mode = wanted_mode; + self.wifi_debug_mode_state_changed(&ctx).await?; Ok(()) } Err(e) => { @@ -353,7 +359,7 @@ impl SteamOSManager { } /// WifiBackend property. - #[zbus(property)] + #[zbus(property(emits_changed_signal = "false"))] async fn wifi_backend(&self) -> zbus::fdo::Result { match get_wifi_backend().await { Ok(backend) => Ok(backend as u32), @@ -376,7 +382,7 @@ impl SteamOSManager { } /// A version property. - #[zbus(property)] + #[zbus(property(emits_changed_signal = "const"))] async fn version(&self) -> u32 { SteamOSManager::API_VERSION } From d13010dcb0a83b38cb7d15435b60cd46821b1b17 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 1 Apr 2024 22:06:07 -0700 Subject: [PATCH 20/35] power: Implement get_gpu_clocks and ManualGPUClock --- src/manager.rs | 14 ++++++++++---- src/power.rs | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 54237b0..1b9f15f 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -15,8 +15,8 @@ use zbus::{interface, Connection, SignalContext}; use crate::hardware::{check_support, variant, HardwareVariant}; use crate::power::{ - get_gpu_performance_level, set_gpu_clocks, set_gpu_performance_level, set_tdp_limit, - GPUPerformanceLevel, + get_gpu_clocks, get_gpu_performance_level, set_gpu_clocks, set_gpu_performance_level, + set_tdp_limit, GPUPerformanceLevel, }; use crate::process::{run_script, script_output}; use crate::systemd::SystemdUnit; @@ -277,8 +277,14 @@ impl SteamOSManager { .map_err(anyhow_to_zbus) } - async fn set_gpu_clocks(&self, clocks: i32) -> bool { - set_gpu_clocks(clocks).await.is_ok() + #[zbus(property)] + async fn manual_gpu_clock(&self) -> zbus::fdo::Result { + get_gpu_clocks().await.map_err(anyhow_to_zbus_fdo) + } + + #[zbus(property)] + async fn set_manual_gpu_clock(&self, clocks: u32) -> zbus::Result<()> { + set_gpu_clocks(clocks).await.map_err(anyhow_to_zbus) } #[zbus(property(emits_changed_signal = "const"))] diff --git a/src/power.rs b/src/power.rs index 60aaf50..f87e949 100644 --- a/src/power.rs +++ b/src/power.rs @@ -8,7 +8,7 @@ use anyhow::{anyhow, bail, ensure, Error, Result}; use std::str::FromStr; use tokio::fs::{self, File}; -use tokio::io::AsyncWriteExt; +use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tracing::error; use crate::path; @@ -93,7 +93,7 @@ pub async fn set_gpu_performance_level(level: GPUPerformanceLevel) -> Result<()> Ok(()) } -pub async fn set_gpu_clocks(clocks: i32) -> Result<()> { +pub async fn set_gpu_clocks(clocks: u32) -> Result<()> { // Set GPU clocks to given value valid between 200 - 1600 // Only used when GPU Performance Level is manual, but write whenever called. ensure!((200..=1600).contains(&clocks), "Invalid clocks"); @@ -121,6 +121,35 @@ pub async fn set_gpu_clocks(clocks: i32) -> Result<()> { Ok(()) } +pub async fn get_gpu_clocks() -> Result { + let clocks_file = File::open(GPU_CLOCKS_PATH).await?; + let mut reader = BufReader::new(clocks_file); + loop { + let mut line = String::new(); + if reader.read_line(&mut line).await? == 0 { + break; + } + if line != "OD_SCLK:\n" { + continue; + } + + let mut line = String::new(); + if reader.read_line(&mut line).await? == 0 { + break; + } + let mhz = match line.split_whitespace().nth(1) { + Some(mhz) if mhz.ends_with("Mhz") => mhz.trim_end_matches("Mhz"), + _ => break, + }; + + match mhz.parse() { + Ok(mhz) => return Ok(mhz), + Err(e) => return Err(e.into()), + } + } + Err(anyhow!("Couldn't find GPU clocks")) +} + pub async fn set_tdp_limit(limit: i32) -> Result<()> { // Set TDP limit given if within range (3-15) // Returns false on error or out of range From eabf6d5a9871cda412f73a8001380f0f2110e720 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 2 Apr 2024 18:07:47 -0700 Subject: [PATCH 21/35] power: Implement get_tdp_limit and TDPLimit --- src/manager.rs | 14 ++++++++++---- src/power.rs | 26 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 1b9f15f..ea4ad08 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -15,8 +15,8 @@ use zbus::{interface, Connection, SignalContext}; use crate::hardware::{check_support, variant, HardwareVariant}; use crate::power::{ - get_gpu_clocks, get_gpu_performance_level, set_gpu_clocks, set_gpu_performance_level, - set_tdp_limit, GPUPerformanceLevel, + get_gpu_clocks, get_gpu_performance_level, get_tdp_limit, set_gpu_clocks, + set_gpu_performance_level, set_tdp_limit, GPUPerformanceLevel, }; use crate::process::{run_script, script_output}; use crate::systemd::SystemdUnit; @@ -299,8 +299,14 @@ impl SteamOSManager { 1600 } - async fn set_tdp_limit(&self, limit: i32) -> bool { - set_tdp_limit(limit).await.is_ok() + #[zbus(property(emits_changed_signal = "false"))] + async fn tdp_limit(&self) -> zbus::fdo::Result { + get_tdp_limit().await.map_err(anyhow_to_zbus_fdo) + } + + #[zbus(property)] + async fn set_tdp_limit(&self, limit: u32) -> zbus::Result<()> { + set_tdp_limit(limit).await.map_err(anyhow_to_zbus) } #[zbus(property(emits_changed_signal = "const"))] diff --git a/src/power.rs b/src/power.rs index f87e949..143cc28 100644 --- a/src/power.rs +++ b/src/power.rs @@ -6,6 +6,7 @@ */ use anyhow::{anyhow, bail, ensure, Error, Result}; +use std::path::PathBuf; use std::str::FromStr; use tokio::fs::{self, File}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; @@ -150,24 +151,33 @@ pub async fn get_gpu_clocks() -> Result { Err(anyhow!("Couldn't find GPU clocks")) } -pub async fn set_tdp_limit(limit: i32) -> Result<()> { - // Set TDP limit given if within range (3-15) - // Returns false on error or out of range - ensure!((3..=15).contains(&limit), "Invalid limit"); - +async fn find_hwmon() -> Result { let mut dir = fs::read_dir(path(GPU_HWMON_PREFIX)).await?; - let base = loop { + loop { let base = match dir.next_entry().await? { Some(entry) => entry.path(), None => bail!("hwmon not found"), }; if fs::try_exists(base.join("power1_cap")).await? { - break base; + return Ok(base); } - }; + } +} +pub async fn get_tdp_limit() -> Result { + let base = find_hwmon().await?; + let power1cap = fs::read_to_string(base.join("power1_cap")).await?; + let power1cap: u32 = power1cap.parse()?; + Ok(power1cap / 1000000) +} + +pub async fn set_tdp_limit(limit: u32) -> Result<()> { + // Set TDP limit given if within range (3-15) + // Returns false on error or out of range + ensure!((3..=15).contains(&limit), "Invalid limit"); let data = format!("{limit}000000"); + let base = find_hwmon().await?; let mut power1file = File::create(base.join("power1_cap")) .await .inspect_err(|message| { From 9ddbc9997d569eb8e0157e81799dbc71947f42f2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 2 Apr 2024 18:37:12 -0700 Subject: [PATCH 22/35] power: Add get/set_gpu_performance_level tests --- src/power.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 5 deletions(-) diff --git a/src/power.rs b/src/power.rs index 143cc28..f39a860 100644 --- a/src/power.rs +++ b/src/power.rs @@ -73,15 +73,15 @@ impl ToString for GPUPerformanceLevel { } pub async fn get_gpu_performance_level() -> Result { - let level = fs::read_to_string(GPU_PERFORMANCE_LEVEL_PATH) + let level = fs::read_to_string(path(GPU_PERFORMANCE_LEVEL_PATH)) .await .inspect_err(|message| error!("Error opening sysfs file for reading: {message}"))?; - GPUPerformanceLevel::from_str(level.as_ref()) + GPUPerformanceLevel::from_str(level.trim().as_ref()) } pub async fn set_gpu_performance_level(level: GPUPerformanceLevel) -> Result<()> { - let mut myfile = File::create(GPU_PERFORMANCE_LEVEL_PATH) + let mut myfile = File::create(path(GPU_PERFORMANCE_LEVEL_PATH)) .await .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; @@ -99,7 +99,7 @@ pub async fn set_gpu_clocks(clocks: u32) -> Result<()> { // Only used when GPU Performance Level is manual, but write whenever called. ensure!((200..=1600).contains(&clocks), "Invalid clocks"); - let mut myfile = File::create(GPU_CLOCKS_PATH) + let mut myfile = File::create(path(GPU_CLOCKS_PATH)) .await .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; @@ -123,7 +123,7 @@ pub async fn set_gpu_clocks(clocks: u32) -> Result<()> { } pub async fn get_gpu_clocks() -> Result { - let clocks_file = File::open(GPU_CLOCKS_PATH).await?; + let clocks_file = File::open(path(GPU_CLOCKS_PATH)).await?; let mut reader = BufReader::new(clocks_file); loop { let mut line = String::new(); @@ -204,6 +204,98 @@ mod test { use anyhow::anyhow; use tokio::fs::{create_dir_all, read_to_string, remove_dir, write}; + #[tokio::test] + async fn test_get_gpu_performance_level() { + let h = testing::start(); + + let filename = path(GPU_PERFORMANCE_LEVEL_PATH); + create_dir_all(filename.parent().unwrap()) + .await + .expect("create_dir_all"); + assert!(get_gpu_performance_level().await.is_err()); + + write(filename.as_path(), "auto\n").await.expect("write"); + assert_eq!( + get_gpu_performance_level().await.unwrap(), + GPUPerformanceLevel::Auto + ); + + write(filename.as_path(), "low\n").await.expect("write"); + assert_eq!( + get_gpu_performance_level().await.unwrap(), + GPUPerformanceLevel::Low + ); + + write(filename.as_path(), "high\n").await.expect("write"); + assert_eq!( + get_gpu_performance_level().await.unwrap(), + GPUPerformanceLevel::High + ); + + write(filename.as_path(), "manual\n").await.expect("write"); + assert_eq!( + get_gpu_performance_level().await.unwrap(), + GPUPerformanceLevel::Manual + ); + + write(filename.as_path(), "peak_performance\n") + .await + .expect("write"); + assert_eq!( + get_gpu_performance_level().await.unwrap(), + GPUPerformanceLevel::ProfilePeak + ); + + write(filename.as_path(), "nothing\n").await.expect("write"); + assert!(get_gpu_performance_level().await.is_err()); + } + + #[tokio::test] + async fn test_set_gpu_performance_level() { + let h = testing::start(); + + let filename = path(GPU_PERFORMANCE_LEVEL_PATH); + create_dir_all(filename.parent().unwrap()) + .await + .expect("create_dir_all"); + + set_gpu_performance_level(GPUPerformanceLevel::Auto) + .await + .expect("set"); + assert_eq!( + read_to_string(filename.as_path()).await.unwrap().trim(), + "auto" + ); + set_gpu_performance_level(GPUPerformanceLevel::Low) + .await + .expect("set"); + assert_eq!( + read_to_string(filename.as_path()).await.unwrap().trim(), + "low" + ); + set_gpu_performance_level(GPUPerformanceLevel::High) + .await + .expect("set"); + assert_eq!( + read_to_string(filename.as_path()).await.unwrap().trim(), + "high" + ); + set_gpu_performance_level(GPUPerformanceLevel::Manual) + .await + .expect("set"); + assert_eq!( + read_to_string(filename.as_path()).await.unwrap().trim(), + "manual" + ); + set_gpu_performance_level(GPUPerformanceLevel::ProfilePeak) + .await + .expect("set"); + assert_eq!( + read_to_string(filename.as_path()).await.unwrap().trim(), + "peak_performance" + ); + } + #[tokio::test] async fn test_set_tdp_limit() { let h = testing::start(); From 3c62c57d52810b8cfd63396af5aba74fd2ce3613 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 2 Apr 2024 19:02:19 -0700 Subject: [PATCH 23/35] manager: Add get/set_gpu_performance_level tests --- src/manager.rs | 49 ++++++++++++++++++++++++++++++++++++++++++++----- src/power.rs | 16 ++++++++++------ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index ea4ad08..6d57e31 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -403,7 +403,7 @@ impl SteamOSManager { #[cfg(test)] mod test { use super::*; - use crate::testing; + use crate::{power, testing}; use tokio::fs::{create_dir_all, write}; use zbus::connection::Connection; use zbus::ConnectionBuilder; @@ -413,7 +413,7 @@ mod test { connection: Connection, } - async fn start() -> TestHandle { + async fn start(name: &str) -> TestHandle { let handle = testing::start(); create_dir_all(crate::path("/sys/class/dmi/id")) .await @@ -436,7 +436,7 @@ mod test { let connection = ConnectionBuilder::session() .unwrap() - .name("com.steampowered.SteamOSManager1.Test") + .name(format!("com.steampowered.SteamOSManager1.Test.{name}")) .unwrap() .build() .await @@ -453,7 +453,46 @@ mod test { #[zbus::proxy( interface = "com.steampowered.SteamOSManager1.Manager", - default_service = "com.steampowered.SteamOSManager1.Test", + default_service = "com.steampowered.SteamOSManager1.Test.GPUPerformanceLevel", + default_path = "/com/steampowered/SteamOSManager1" + )] + trait GPUPerformanceLevel { + #[zbus(property)] + fn gpu_performance_level(&self) -> zbus::Result; + + #[zbus(property)] + fn set_gpu_performance_level(&self, level: u32) -> zbus::Result<()>; + } + + #[tokio::test] + async fn gpu_performance_level() { + let test = start("GPUPerformanceLevel").await; + power::setup_test().await; + + let proxy = GPUPerformanceLevelProxy::new(&test.connection) + .await + .unwrap(); + set_gpu_performance_level(GPUPerformanceLevel::Auto) + .await + .expect("set"); + assert_eq!( + proxy.gpu_performance_level().await.unwrap(), + GPUPerformanceLevel::Auto as u32 + ); + + proxy + .set_gpu_performance_level(GPUPerformanceLevel::Low as u32) + .await + .expect("proxy_set"); + assert_eq!( + get_gpu_performance_level().await.unwrap(), + GPUPerformanceLevel::Low + ); + } + + #[zbus::proxy( + interface = "com.steampowered.SteamOSManager1.Manager", + default_service = "com.steampowered.SteamOSManager1.Test.Version", default_path = "/com/steampowered/SteamOSManager1" )] trait Version { @@ -463,7 +502,7 @@ mod test { #[tokio::test] async fn version() { - let test = start().await; + let test = start("Version").await; let proxy = VersionProxy::new(&test.connection).await.unwrap(); assert_eq!(proxy.version().await, Ok(SteamOSManager::API_VERSION)); } diff --git a/src/power.rs b/src/power.rs index f39a860..f57b7c1 100644 --- a/src/power.rs +++ b/src/power.rs @@ -197,6 +197,14 @@ pub async fn set_tdp_limit(limit: u32) -> Result<()> { Ok(()) } +#[cfg(test)] +pub async fn setup_test() { + let filename = path(GPU_PERFORMANCE_LEVEL_PATH); + fs::create_dir_all(filename.parent().unwrap()) + .await + .expect("create_dir_all"); +} + #[cfg(test)] mod test { use super::*; @@ -209,9 +217,7 @@ mod test { let h = testing::start(); let filename = path(GPU_PERFORMANCE_LEVEL_PATH); - create_dir_all(filename.parent().unwrap()) - .await - .expect("create_dir_all"); + setup_test().await; assert!(get_gpu_performance_level().await.is_err()); write(filename.as_path(), "auto\n").await.expect("write"); @@ -255,9 +261,7 @@ mod test { let h = testing::start(); let filename = path(GPU_PERFORMANCE_LEVEL_PATH); - create_dir_all(filename.parent().unwrap()) - .await - .expect("create_dir_all"); + setup_test().await; set_gpu_performance_level(GPUPerformanceLevel::Auto) .await From bc6af282ebcf1370ef95115300b162269a27c5f0 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 3 Apr 2024 19:17:59 -0700 Subject: [PATCH 24/35] power: Add get/set_gpu_clocks and get_tdp_limit tests --- src/power.rs | 90 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 13 deletions(-) diff --git a/src/power.rs b/src/power.rs index f57b7c1..c145749 100644 --- a/src/power.rs +++ b/src/power.rs @@ -20,6 +20,9 @@ const GPU_PERFORMANCE_LEVEL_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level"; const GPU_CLOCKS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; +const TDP_LIMIT1: &str = "power1_cap"; +const TDP_LIMIT2: &str = "power2_cap"; + #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] pub enum GPUPerformanceLevel { @@ -158,7 +161,7 @@ async fn find_hwmon() -> Result { Some(entry) => entry.path(), None => bail!("hwmon not found"), }; - if fs::try_exists(base.join("power1_cap")).await? { + if fs::try_exists(base.join(TDP_LIMIT1)).await? { return Ok(base); } } @@ -166,7 +169,7 @@ async fn find_hwmon() -> Result { pub async fn get_tdp_limit() -> Result { let base = find_hwmon().await?; - let power1cap = fs::read_to_string(base.join("power1_cap")).await?; + let power1cap = fs::read_to_string(base.join(TDP_LIMIT1)).await?; let power1cap: u32 = power1cap.parse()?; Ok(power1cap / 1000000) } @@ -178,7 +181,7 @@ pub async fn set_tdp_limit(limit: u32) -> Result<()> { let data = format!("{limit}000000"); let base = find_hwmon().await?; - let mut power1file = File::create(base.join("power1_cap")) + let mut power1file = File::create(base.join(TDP_LIMIT1)) .await .inspect_err(|message| { error!("Error opening sysfs power1_cap file for writing TDP limits {message}") @@ -188,7 +191,7 @@ pub async fn set_tdp_limit(limit: u32) -> Result<()> { .await .inspect_err(|message| error!("Error writing to power1_cap file: {message}"))?; - if let Ok(mut power2file) = File::create(base.join("power2_cap")).await { + if let Ok(mut power2file) = File::create(base.join(TDP_LIMIT2)).await { power2file .write(data.as_bytes()) .await @@ -300,6 +303,21 @@ mod test { ); } + #[tokio::test] + async fn test_get_tdp_limit() { + let h = testing::start(); + + let hwmon = path(GPU_HWMON_PREFIX); + create_dir_all(hwmon.join("hwmon5").as_path()) + .await + .expect("create_dir_all"); + + assert!(get_tdp_limit().await.is_err()); + + write(hwmon.join("hwmon5").join(TDP_LIMIT1), "15000000").await.expect("write"); + assert_eq!(get_tdp_limit().await.unwrap(), 15); + } + #[tokio::test] async fn test_set_tdp_limit() { let h = testing::start(); @@ -324,10 +342,10 @@ mod test { ); let hwmon = hwmon.join("hwmon5"); - create_dir_all(hwmon.join("power1_cap")) + create_dir_all(hwmon.join(TDP_LIMIT1)) .await .expect("create_dir_all"); - create_dir_all(hwmon.join("power2_cap")) + create_dir_all(hwmon.join(TDP_LIMIT2)) .await .expect("create_dir_all"); assert_eq!( @@ -335,28 +353,74 @@ mod test { anyhow!("Is a directory (os error 21)").to_string() ); - remove_dir(hwmon.join("power1_cap")) + remove_dir(hwmon.join(TDP_LIMIT1)) .await .expect("remove_dir"); - write(hwmon.join("power1_cap"), "0").await.expect("write"); + write(hwmon.join(TDP_LIMIT1), "0").await.expect("write"); assert!(set_tdp_limit(10).await.is_ok()); - let power1_cap = read_to_string(hwmon.join("power1_cap")) + let power1_cap = read_to_string(hwmon.join(TDP_LIMIT1)) .await .expect("power1_cap"); assert_eq!(power1_cap, "10000000"); - remove_dir(hwmon.join("power2_cap")) + remove_dir(hwmon.join(TDP_LIMIT2)) .await .expect("remove_dir"); - write(hwmon.join("power2_cap"), "0").await.expect("write"); + write(hwmon.join(TDP_LIMIT2), "0").await.expect("write"); assert!(set_tdp_limit(15).await.is_ok()); - let power1_cap = read_to_string(hwmon.join("power1_cap")) + let power1_cap = read_to_string(hwmon.join(TDP_LIMIT1)) .await .expect("power1_cap"); assert_eq!(power1_cap, "15000000"); - let power2_cap = read_to_string(hwmon.join("power2_cap")) + let power2_cap = read_to_string(hwmon.join(TDP_LIMIT2)) .await .expect("power2_cap"); assert_eq!(power2_cap, "15000000"); } + + #[tokio::test] + async fn test_get_gpu_clocks() { + let h = testing::start(); + + const contents: &str = "OD_SCLK: +0: 1600Mhz +1: 1600Mhz +OD_RANGE: +SCLK: 200Mhz 1600Mhz +CCLK: 1400Mhz 3500Mhz +CCLK_RANGE in Core0: +0: 1400Mhz +1: 3500Mhz\n"; + let filename = path(GPU_PERFORMANCE_LEVEL_PATH); + setup_test().await; + + assert!(get_gpu_clocks().await.is_err()); + write(path(GPU_CLOCKS_PATH), contents).await.expect("write"); + + assert_eq!(get_gpu_clocks().await.unwrap(), 1600); + } + + #[tokio::test] + async fn test_set_gpu_clocks() { + let h = testing::start(); + + let filename = path(GPU_CLOCKS_PATH); + assert!(set_gpu_clocks(1600).await.is_err()); + setup_test().await; + + assert!(set_gpu_clocks(100).await.is_err()); + assert!(set_gpu_clocks(2000).await.is_err()); + + assert!(set_gpu_clocks(200).await.is_ok()); + assert_eq!( + read_to_string(filename.as_path()).await.expect("string"), + "s 0 200\ns 1 200\nc\n" + ); + + assert!(set_gpu_clocks(1600).await.is_ok()); + assert_eq!( + read_to_string(filename.as_path()).await.expect("string"), + "s 0 1600\ns 1 1600\nc\n" + ); + } } From 8b59bd3a3909175174ada75d3bf24e80856f7783 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 3 Apr 2024 19:42:09 -0700 Subject: [PATCH 25/35] manager: Fix property capitalization --- src/manager.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 6d57e31..0a6f1e5 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -258,7 +258,7 @@ impl SteamOSManager { .map_err(anyhow_to_zbus_fdo) } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property(emits_changed_signal = "false"), name = "GPUPerformanceLevel")] async fn gpu_performance_level(&self) -> zbus::fdo::Result { match get_gpu_performance_level().await { Ok(level) => Ok(level as u32), @@ -266,7 +266,7 @@ impl SteamOSManager { } } - #[zbus(property)] + #[zbus(property, name = "GPUPerformanceLevel")] async fn set_gpu_performance_level(&self, level: u32) -> zbus::Result<()> { let level = match GPUPerformanceLevel::try_from(level) { Ok(level) => level, @@ -277,45 +277,45 @@ impl SteamOSManager { .map_err(anyhow_to_zbus) } - #[zbus(property)] + #[zbus(property, name = "ManualGPUClock")] async fn manual_gpu_clock(&self) -> zbus::fdo::Result { get_gpu_clocks().await.map_err(anyhow_to_zbus_fdo) } - #[zbus(property)] + #[zbus(property, name = "ManualGPUClock")] async fn set_manual_gpu_clock(&self, clocks: u32) -> zbus::Result<()> { set_gpu_clocks(clocks).await.map_err(anyhow_to_zbus) } - #[zbus(property(emits_changed_signal = "const"))] + #[zbus(property(emits_changed_signal = "const"), name = "ManualGPUClockMin")] async fn manual_gpu_clock_min(&self) -> u32 { // TODO: Can this be queried from somewhere? 200 } - #[zbus(property(emits_changed_signal = "const"))] + #[zbus(property(emits_changed_signal = "const"), name = "ManualGPUClockMax")] async fn manual_gpu_clock_max(&self) -> u32 { // TODO: Can this be queried from somewhere? 1600 } - #[zbus(property(emits_changed_signal = "false"))] + #[zbus(property(emits_changed_signal = "false"), name = "TDPLimit")] async fn tdp_limit(&self) -> zbus::fdo::Result { get_tdp_limit().await.map_err(anyhow_to_zbus_fdo) } - #[zbus(property)] + #[zbus(property, name = "TDPLimit")] async fn set_tdp_limit(&self, limit: u32) -> zbus::Result<()> { set_tdp_limit(limit).await.map_err(anyhow_to_zbus) } - #[zbus(property(emits_changed_signal = "const"))] + #[zbus(property(emits_changed_signal = "const"), name = "TDPLimitMin")] async fn tdp_limit_min(&self) -> u32 { // TODO: Can this be queried from somewhere? 3 } - #[zbus(property(emits_changed_signal = "const"))] + #[zbus(property(emits_changed_signal = "const"), name = "TDPLimitMax")] async fn tdp_limit_max(&self) -> u32 { // TODO: Can this be queried from somewhere? 15 @@ -457,10 +457,10 @@ mod test { default_path = "/com/steampowered/SteamOSManager1" )] trait GPUPerformanceLevel { - #[zbus(property)] + #[zbus(property, name = "GPUPerformanceLevel")] fn gpu_performance_level(&self) -> zbus::Result; - #[zbus(property)] + #[zbus(property, name = "GPUPerformanceLevel")] fn set_gpu_performance_level(&self, level: u32) -> zbus::Result<()>; } From cf4d7b9ba9cd4591ae2490316fe647d38056c7f3 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 3 Apr 2024 20:07:19 -0700 Subject: [PATCH 26/35] manager: Add manual_gpu_clock tests --- src/manager.rs | 35 ++++++++++++++++++++-- src/power.rs | 78 ++++++++++++++++++++++++++++---------------------- 2 files changed, 77 insertions(+), 36 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 0a6f1e5..435be1c 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -277,7 +277,7 @@ impl SteamOSManager { .map_err(anyhow_to_zbus) } - #[zbus(property, name = "ManualGPUClock")] + #[zbus(property(emits_changed_signal = "false"), name = "ManualGPUClock")] async fn manual_gpu_clock(&self) -> zbus::fdo::Result { get_gpu_clocks().await.map_err(anyhow_to_zbus_fdo) } @@ -467,7 +467,7 @@ mod test { #[tokio::test] async fn gpu_performance_level() { let test = start("GPUPerformanceLevel").await; - power::setup_test().await; + power::test::setup().await; let proxy = GPUPerformanceLevelProxy::new(&test.connection) .await @@ -490,6 +490,37 @@ mod test { ); } + #[zbus::proxy( + interface = "com.steampowered.SteamOSManager1.Manager", + default_service = "com.steampowered.SteamOSManager1.Test.ManualGPUClock", + default_path = "/com/steampowered/SteamOSManager1" + )] + trait ManualGPUClock { + #[zbus(property, name = "ManualGPUClock")] + fn manual_gpu_clock(&self) -> zbus::Result; + + #[zbus(property, name = "ManualGPUClock")] + fn set_manual_gpu_clock(&self, clocks: u32) -> zbus::Result<()>; + } + + #[tokio::test] + async fn manual_gpu_clock() { + let test = start("ManualGPUClock").await; + + let proxy = ManualGPUClockProxy::new(&test.connection).await.unwrap(); + + assert!(proxy.manual_gpu_clock().await.is_err()); + + power::test::write_clocks(1600).await; + assert_eq!(proxy.manual_gpu_clock().await.unwrap(), 1600); + + proxy.set_manual_gpu_clock(200).await.expect("proxy_set"); + power::test::expect_clocks(200); + + assert!(proxy.set_manual_gpu_clock(100).await.is_err()); + power::test::expect_clocks(200); + } + #[zbus::proxy( interface = "com.steampowered.SteamOSManager1.Manager", default_service = "com.steampowered.SteamOSManager1.Test.Version", diff --git a/src/power.rs b/src/power.rs index c145749..f31ec65 100644 --- a/src/power.rs +++ b/src/power.rs @@ -201,26 +201,51 @@ pub async fn set_tdp_limit(limit: u32) -> Result<()> { } #[cfg(test)] -pub async fn setup_test() { - let filename = path(GPU_PERFORMANCE_LEVEL_PATH); - fs::create_dir_all(filename.parent().unwrap()) - .await - .expect("create_dir_all"); -} - -#[cfg(test)] -mod test { +pub mod test { use super::*; use crate::testing; use anyhow::anyhow; use tokio::fs::{create_dir_all, read_to_string, remove_dir, write}; + pub async fn setup() { + let filename = path(GPU_PERFORMANCE_LEVEL_PATH); + create_dir_all(filename.parent().unwrap()) + .await + .expect("create_dir_all"); + } + + pub async fn write_clocks(mhz: u32) { + let filename = path(GPU_CLOCKS_PATH); + create_dir_all(filename.parent().unwrap()) + .await + .expect("create_dir_all"); + + let contents = format!( + "OD_SCLK: +0: {mhz}Mhz +1: {mhz}Mhz +OD_RANGE: +SCLK: 200Mhz 1600Mhz +CCLK: 1400Mhz 3500Mhz +CCLK_RANGE in Core0: +0: 1400Mhz +1: 3500Mhz\n" + ); + + write(filename.as_path(), contents).await.expect("write"); + } + + pub async fn expect_clocks(mhz: u32) { + let clocks = read_to_string(path(GPU_CLOCKS_PATH)).await.expect("read"); + assert_eq!(clocks, format!("s 0 {mhz}\ns 1 {mhz}\nc\n")); + } + #[tokio::test] async fn test_get_gpu_performance_level() { let h = testing::start(); let filename = path(GPU_PERFORMANCE_LEVEL_PATH); - setup_test().await; + setup().await; assert!(get_gpu_performance_level().await.is_err()); write(filename.as_path(), "auto\n").await.expect("write"); @@ -264,7 +289,7 @@ mod test { let h = testing::start(); let filename = path(GPU_PERFORMANCE_LEVEL_PATH); - setup_test().await; + setup().await; set_gpu_performance_level(GPUPerformanceLevel::Auto) .await @@ -314,7 +339,9 @@ mod test { assert!(get_tdp_limit().await.is_err()); - write(hwmon.join("hwmon5").join(TDP_LIMIT1), "15000000").await.expect("write"); + write(hwmon.join("hwmon5").join(TDP_LIMIT1), "15000000") + .await + .expect("write"); assert_eq!(get_tdp_limit().await.unwrap(), 15); } @@ -381,21 +408,11 @@ mod test { #[tokio::test] async fn test_get_gpu_clocks() { let h = testing::start(); - - const contents: &str = "OD_SCLK: -0: 1600Mhz -1: 1600Mhz -OD_RANGE: -SCLK: 200Mhz 1600Mhz -CCLK: 1400Mhz 3500Mhz -CCLK_RANGE in Core0: -0: 1400Mhz -1: 3500Mhz\n"; let filename = path(GPU_PERFORMANCE_LEVEL_PATH); - setup_test().await; + setup().await; assert!(get_gpu_clocks().await.is_err()); - write(path(GPU_CLOCKS_PATH), contents).await.expect("write"); + write_clocks(1600).await; assert_eq!(get_gpu_clocks().await.unwrap(), 1600); } @@ -404,23 +421,16 @@ CCLK_RANGE in Core0: async fn test_set_gpu_clocks() { let h = testing::start(); - let filename = path(GPU_CLOCKS_PATH); assert!(set_gpu_clocks(1600).await.is_err()); - setup_test().await; + setup().await; assert!(set_gpu_clocks(100).await.is_err()); assert!(set_gpu_clocks(2000).await.is_err()); assert!(set_gpu_clocks(200).await.is_ok()); - assert_eq!( - read_to_string(filename.as_path()).await.expect("string"), - "s 0 200\ns 1 200\nc\n" - ); + expect_clocks(200).await; assert!(set_gpu_clocks(1600).await.is_ok()); - assert_eq!( - read_to_string(filename.as_path()).await.expect("string"), - "s 0 1600\ns 1 1600\nc\n" - ); + expect_clocks(1600).await; } } From 64129783dd09d71364a588521aab7abcb1e0cfbc Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 3 Apr 2024 20:11:14 -0700 Subject: [PATCH 27/35] xml: Remove things that won't be implemented in API version 7 --- com.steampowered.SteamOSManager1.xml | 63 ---------------------------- 1 file changed, 63 deletions(-) diff --git a/com.steampowered.SteamOSManager1.xml b/com.steampowered.SteamOSManager1.xml index 223cc49..395ff6c 100644 --- a/com.steampowered.SteamOSManager1.xml +++ b/com.steampowered.SteamOSManager1.xml @@ -100,17 +100,6 @@ - - - - - - - - - - - - - - - - - - - - + - +