From ebd89f4f72805ad5d5b0c1649fc26465e01571a2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 25 Mar 2024 16:15:14 -0700 Subject: [PATCH 1/7] manager: Just don't call start-/stop_tracing if should_trace is false --- src/manager.rs | 55 +++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/manager.rs b/src/manager.rs index 6a8fe5f..62fa3b2 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -146,11 +146,7 @@ async fn restart_iwd() -> Result { } } -async fn stop_tracing(should_trace: bool) -> Result { - if !should_trace { - return Ok(true); - } - +async fn stop_tracing() -> Result { // Stop tracing and extract ring buffer to disk for capture run_script("stop tracing", "trace-cmd", &["stop"]).await?; // stop tracing worked @@ -162,11 +158,7 @@ async fn stop_tracing(should_trace: bool) -> Result { .await } -async fn start_tracing(buffer_size: u32, should_trace: bool) -> Result { - if !should_trace { - return Ok(true); - } - +async fn start_tracing(buffer_size: u32) -> Result { // Start tracing let size_str = format!("{}", buffer_size); run_script( @@ -410,16 +402,18 @@ impl SMManager { Ok(WifiDebugMode::Off) => { // If mode is 0 disable wifi debug mode // Stop any existing trace and flush to disk. - let result = match stop_tracing(self.should_trace).await { - Ok(result) => result, - Err(message) => { - error!("stop_tracing command got an error: {message}"); + 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; } - }; - if !result { - error!("stop_tracing command returned non-zero"); - return false; } // Stop_tracing was successful if let Err(message) = setup_iwd_config(false).await { @@ -466,21 +460,22 @@ impl SMManager { return false; } // restart_iwd worked - let value = match start_tracing(buffer_size, self.should_trace).await { - Ok(value) => value, - Err(message) => { - error!("start_tracing got an error: {message}"); + 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; } - }; - if value { - // start_tracing worked - self.wifi_debug_mode = WifiDebugMode::On; - } else { - // start_tracing failed - error!("start_tracing failed"); - return false; } + // start_tracing worked + self.wifi_debug_mode = WifiDebugMode::On; } Err(_) => { // Invalid mode requested, more coming later, but add this catch-all for now From 3565886d3f13da359ea819e83960779596c3909e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Mon, 25 Mar 2024 16:31:48 -0700 Subject: [PATCH 2/7] Turn galileo detection into generalized model detection --- src/hardware.rs | 78 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 +- src/manager.rs | 62 ++++++++++++++++++++------------------- 3 files changed, 112 insertions(+), 31 deletions(-) create mode 100644 src/hardware.rs diff --git a/src/hardware.rs b/src/hardware.rs new file mode 100644 index 0000000..3978ac1 --- /dev/null +++ b/src/hardware.rs @@ -0,0 +1,78 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::Result; +use tokio::fs; + +use crate::path; + +const BOARD_VENDOR_PATH: &str = "/sys/class/dmi/id/board_vendor"; +const BOARD_NAME_PATH: &str = "/sys/class/dmi/id/board_name"; +const VALVE_VENDOR: &str = "Valve"; +const JUPITER_NAME: &str = "Jupiter"; +const GALILEO_NAME: &str = "Galileo"; + +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum HardwareVariant { + Unknown, + Jupiter, + Galileo, +} + +pub async fn variant() -> Result { + let board_vendor = fs::read_to_string(path(BOARD_VENDOR_PATH)).await?; + if board_vendor.trim_end() != VALVE_VENDOR { + return Ok(HardwareVariant::Unknown); + } + + let board_name = fs::read_to_string(path(BOARD_NAME_PATH)).await?; + Ok(match board_name.trim_end() { + JUPITER_NAME => HardwareVariant::Jupiter, + GALILEO_NAME => HardwareVariant::Galileo, + _ => HardwareVariant::Unknown, + }) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::testing; + use tokio::fs::{create_dir_all, write}; + + #[tokio::test] + async fn board_lookup() { + let h = testing::start(); + + create_dir_all(crate::path("/sys/class/dmi/id")) + .await + .expect("create_dir_all"); + assert!(variant().await.is_err()); + + write(crate::path("/sys/class/dmi/id/board_vendor"), "LENOVO\n") + .await + .expect("write"); + assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown); + + 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"); + assert_eq!(variant().await.unwrap(), HardwareVariant::Jupiter); + + write(crate::path("/sys/class/dmi/id/board_name"), "Galileo\n") + .await + .expect("write"); + assert_eq!(variant().await.unwrap(), HardwareVariant::Galileo); + + write(crate::path("/sys/class/dmi/id/board_name"), "Neptune\n") + .await + .expect("write"); + assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown); + } +} diff --git a/src/main.rs b/src/main.rs index 1f56064..2a1b71e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ use crate::sls::ftrace::Ftrace; use crate::sls::{LogLayer, LogReceiver}; mod ds_inhibit; +mod hardware; mod manager; mod sls; @@ -117,7 +118,7 @@ async fn reload() -> Result<()> { } async fn create_connection() -> Result { - let manager = manager::SMManager::new()?; + let manager = manager::SMManager::new().await?; ConnectionBuilder::system()? .name("com.steampowered.SteamOSManager1")? diff --git a/src/manager.rs b/src/manager.rs index 62fa3b2..3408c35 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -6,11 +6,13 @@ */ use anyhow::{ensure, Result}; -use std::{ffi::OsStr, fmt, fs}; -use tokio::{fs::File, io::AsyncWriteExt, process::Command}; +use std::{ffi::OsStr, fmt}; +use tokio::{fs, fs::File, io::AsyncWriteExt, process::Command}; use tracing::{error, warn}; use zbus::{interface, zvariant::Fd}; +use crate::hardware::{variant, HardwareVariant}; + #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] enum WifiDebugMode { @@ -46,10 +48,10 @@ pub struct SMManager { } impl SMManager { - pub fn new() -> Result { + pub async fn new() -> Result { Ok(SMManager { wifi_debug_mode: WifiDebugMode::Off, - should_trace: is_galileo()?, + should_trace: variant().await? == HardwareVariant::Galileo, }) } } @@ -65,9 +67,6 @@ const OVERRIDE_PATH: &str = "/etc/systemd/system/iwd.service.d/override.conf"; const OUTPUT_FILE: &str = "/var/log/wifitrace.dat"; const MIN_BUFFER_SIZE: u32 = 100; -const BOARD_NAME_PATH: &str = "/sys/class/dmi/id/board_name"; -const GALILEO_NAME: &str = "Galileo"; - const ALS_INTEGRATION_PATH: &str = "/sys/devices/platform/AMDI0010:00/i2c-0/i2c-PRP0001:01/iio:device0/in_illuminance_integration_time"; const POWER1_CAP_PATH: &str = "/sys/class/hwmon/hwmon5/power1_cap"; const POWER2_CAP_PATH: &str = "/sys/class/hwmon/hwmon5/power2_cap"; @@ -76,13 +75,8 @@ 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"; -fn is_galileo() -> Result { - let mut board_name = fs::read_to_string(BOARD_NAME_PATH)?; - board_name = board_name.trim().to_string(); - - let matches = board_name == GALILEO_NAME; - Ok(matches) -} +const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl"; +const TRACE_CMD_PATH: &str = "/usr/bin/trace-cmd"; async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> Result { // Run given script and return true on success @@ -116,23 +110,23 @@ async fn setup_iwd_config(want_override: bool) -> std::io::Result<()> { if want_override { // Copy it in // Make sure the folder exists - tokio::fs::create_dir_all(OVERRIDE_FOLDER).await?; + fs::create_dir_all(OVERRIDE_FOLDER).await?; // Then write the contents into the file - tokio::fs::write(OVERRIDE_PATH, OVERRIDE_CONTENTS).await + fs::write(OVERRIDE_PATH, OVERRIDE_CONTENTS).await } else { // Delete it - tokio::fs::remove_file(OVERRIDE_PATH).await + fs::remove_file(OVERRIDE_PATH).await } } 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", &["daemon-reload"]).await { + match run_script("reload systemd", SYSTEMCTL_PATH, &["daemon-reload"]).await { Ok(value) => { if value { // worked, now restart iwd - run_script("restart iwd", "systemctl", &["restart", "iwd"]).await + run_script("restart iwd", SYSTEMCTL_PATH, &["restart", "iwd"]).await } else { // reload failed error!("restart_iwd: reload systemd failed with non-zero exit code"); @@ -148,11 +142,11 @@ async fn restart_iwd() -> Result { async fn stop_tracing() -> Result { // Stop tracing and extract ring buffer to disk for capture - run_script("stop tracing", "trace-cmd", &["stop"]).await?; + run_script("stop tracing", TRACE_CMD_PATH, &["stop"]).await?; // stop tracing worked run_script( "extract traces", - "trace-cmd", + TRACE_CMD_PATH, &["extract", "-o", OUTPUT_FILE], ) .await @@ -163,7 +157,7 @@ async fn start_tracing(buffer_size: u32) -> Result { let size_str = format!("{}", buffer_size); run_script( "start tracing", - "trace-cmd", + TRACE_CMD_PATH, &["start", "-e", "ath11k_wmi_diag", "-b", &size_str], ) .await @@ -254,9 +248,13 @@ impl SMManager { async fn factory_reset(&self) -> bool { // Run steamos factory reset script and return true on success - run_script("factory reset", "steamos-factory-reset-config", &[""]) - .await - .unwrap_or(false) + run_script( + "factory reset", + "/usr/bin/steamos-factory-reset-config", + &[""], + ) + .await + .unwrap_or(false) } async fn disable_wifi_power_management(&self) -> bool { @@ -275,7 +273,7 @@ impl SMManager { if enable { run_script( "enable fan control", - "systemcltl", + SYSTEMCTL_PATH, &["start", "jupiter-fan-control-service"], ) .await @@ -283,7 +281,7 @@ impl SMManager { } else { run_script( "disable fan control", - "systemctl", + SYSTEMCTL_PATH, &["stop", "jupiter-fan-control.service"], ) .await @@ -294,9 +292,13 @@ impl SMManager { 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", "jupiter-check-support", &[""]) - .await - .unwrap_or(false) + run_script( + "check hardware support", + "/usr/bin/jupiter-check-support", + &[""], + ) + .await + .unwrap_or(false) } async fn read_als_calibration(&self) -> f32 { From 128c5ee36aa5862313068090f0cf3a14579932e2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Tue, 26 Mar 2024 00:42:48 -0700 Subject: [PATCH 3/7] hardware: Split HardwareVariant name parsing out from file reading --- src/hardware.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/hardware.rs b/src/hardware.rs index 3978ac1..5c19b06 100644 --- a/src/hardware.rs +++ b/src/hardware.rs @@ -5,16 +5,14 @@ * SPDX-License-Identifier: MIT */ -use anyhow::Result; +use anyhow::{Error, Result}; +use std::str::FromStr; use tokio::fs; use crate::path; const BOARD_VENDOR_PATH: &str = "/sys/class/dmi/id/board_vendor"; const BOARD_NAME_PATH: &str = "/sys/class/dmi/id/board_name"; -const VALVE_VENDOR: &str = "Valve"; -const JUPITER_NAME: &str = "Jupiter"; -const GALILEO_NAME: &str = "Galileo"; #[derive(PartialEq, Debug, Copy, Clone)] pub enum HardwareVariant { @@ -23,18 +21,25 @@ pub enum HardwareVariant { Galileo, } +impl FromStr for HardwareVariant { + type Err = Error; + fn from_str(input: &str) -> Result { + Ok(match input { + "Jupiter" => HardwareVariant::Jupiter, + "Galileo" => HardwareVariant::Galileo, + _ => HardwareVariant::Unknown, + }) + } +} + pub async fn variant() -> Result { let board_vendor = fs::read_to_string(path(BOARD_VENDOR_PATH)).await?; - if board_vendor.trim_end() != VALVE_VENDOR { + if board_vendor.trim_end() != "Valve" { return Ok(HardwareVariant::Unknown); } let board_name = fs::read_to_string(path(BOARD_NAME_PATH)).await?; - Ok(match board_name.trim_end() { - JUPITER_NAME => HardwareVariant::Jupiter, - GALILEO_NAME => HardwareVariant::Galileo, - _ => HardwareVariant::Unknown, - }) + HardwareVariant::from_str(board_name.trim_end()) } #[cfg(test)] @@ -52,25 +57,25 @@ mod test { .expect("create_dir_all"); assert!(variant().await.is_err()); - write(crate::path("/sys/class/dmi/id/board_vendor"), "LENOVO\n") + write(crate::path(BOARD_VENDOR_PATH), "LENOVO\n") .await .expect("write"); assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown); - write(crate::path("/sys/class/dmi/id/board_vendor"), "Valve\n") + write(crate::path(BOARD_VENDOR_PATH), "Valve\n") .await .expect("write"); - write(crate::path("/sys/class/dmi/id/board_name"), "Jupiter\n") + write(crate::path(BOARD_NAME_PATH), "Jupiter\n") .await .expect("write"); assert_eq!(variant().await.unwrap(), HardwareVariant::Jupiter); - write(crate::path("/sys/class/dmi/id/board_name"), "Galileo\n") + write(crate::path(BOARD_NAME_PATH), "Galileo\n") .await .expect("write"); assert_eq!(variant().await.unwrap(), HardwareVariant::Galileo); - write(crate::path("/sys/class/dmi/id/board_name"), "Neptune\n") + write(crate::path(BOARD_NAME_PATH), "Neptune\n") .await .expect("write"); assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown); From 0d908598414fa838b42874caff8560aa567d378e Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 27 Mar 2024 15:53:36 -0700 Subject: [PATCH 4/7] process: Split out from manager --- src/main.rs | 1 + src/manager.rs | 30 +++--------------------------- src/process.rs | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 27 deletions(-) create mode 100644 src/process.rs diff --git a/src/main.rs b/src/main.rs index 2a1b71e..cad6be3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ use crate::sls::{LogLayer, LogReceiver}; mod ds_inhibit; mod hardware; mod manager; +mod process; mod sls; #[cfg(test)] diff --git a/src/manager.rs b/src/manager.rs index 3408c35..faf116c 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -6,12 +6,13 @@ */ use anyhow::{ensure, Result}; -use std::{ffi::OsStr, fmt}; -use tokio::{fs, fs::File, io::AsyncWriteExt, process::Command}; +use std::fmt; +use tokio::{fs, fs::File, io::AsyncWriteExt}; use tracing::{error, warn}; use zbus::{interface, zvariant::Fd}; use crate::hardware::{variant, HardwareVariant}; +use crate::process::{run_script, script_output}; #[derive(PartialEq, Debug, Copy, Clone)] #[repr(u32)] @@ -78,31 +79,6 @@ const GPU_CLOCKS_PATH: &str = "/sys/class/drm/card0/device/pp_od_clk_voltage"; const SYSTEMCTL_PATH: &str = "/usr/bin/systemctl"; const TRACE_CMD_PATH: &str = "/usr/bin/trace-cmd"; -async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> Result { - // Run given script and return true on success - let mut child = Command::new(executable).args(args).spawn()?; - let status = child.wait().await?; - Ok(status.success()) -} - -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}")) -} - -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(); - - let output = output.await?; - - let s = std::str::from_utf8(&output.stdout)?; - Ok(s.to_string()) -} - 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 diff --git a/src/process.rs b/src/process.rs new file mode 100644 index 0000000..a5e6847 --- /dev/null +++ b/src/process.rs @@ -0,0 +1,36 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::Result; +use std::ffi::OsStr; +use tokio::process::Command; +use tracing::warn; + +pub async fn script_exit_code(executable: &str, args: &[impl AsRef]) -> Result { + // Run given script and return true on success + let mut child = Command::new(executable).args(args).spawn()?; + let status = child.wait().await?; + Ok(status.success()) +} + +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}")) +} + +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(); + + let output = output.await?; + + let s = std::str::from_utf8(&output.stdout)?; + Ok(s.to_string()) +} From 309f2acc1b7c5003f5ec32bb0f80708f2d86baa2 Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 27 Mar 2024 15:57:43 -0700 Subject: [PATCH 5/7] wifi: Split out from manager --- src/main.rs | 1 + src/manager.rs | 105 ++---------------------------------- src/process.rs | 2 + src/wifi.rs | 144 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 150 insertions(+), 102 deletions(-) create mode 100644 src/wifi.rs diff --git a/src/main.rs b/src/main.rs index cad6be3..869504f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,7 @@ mod hardware; mod manager; mod process; mod sls; +mod wifi; #[cfg(test)] mod testing; diff --git a/src/manager.rs b/src/manager.rs index faf116c..cbf514b 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -6,40 +6,13 @@ */ use anyhow::{ensure, Result}; -use std::fmt; -use tokio::{fs, fs::File, io::AsyncWriteExt}; +use tokio::{fs::File, io::AsyncWriteExt}; use tracing::{error, warn}; use zbus::{interface, zvariant::Fd}; use crate::hardware::{variant, HardwareVariant}; -use crate::process::{run_script, script_output}; - -#[derive(PartialEq, Debug, Copy, Clone)] -#[repr(u32)] -enum WifiDebugMode { - Off, - On, -} - -impl TryFrom for WifiDebugMode { - type Error = &'static str; - fn try_from(v: u32) -> Result { - match v { - 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}"), - } - } -} - -impl fmt::Display for WifiDebugMode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - WifiDebugMode::Off => write!(f, "Off"), - WifiDebugMode::On => write!(f, "On"), - } - } -} +use crate::process::{run_script, script_output, SYSTEMCTL_PATH}; +use crate::wifi::{restart_iwd, setup_iwd_config, start_tracing, stop_tracing, WifiDebugMode}; pub struct SMManager { wifi_debug_mode: WifiDebugMode, @@ -57,15 +30,6 @@ impl SMManager { } } -const OVERRIDE_CONTENTS: &str = "[Service] -ExecStart= -ExecStart=/usr/lib/iwd/iwd -d -"; -const OVERRIDE_FOLDER: &str = "/etc/systemd/system/iwd.service.d"; -const OVERRIDE_PATH: &str = "/etc/systemd/system/iwd.service.d/override.conf"; -// Only use one path for output for now. If needed we can add a timestamp later -// to have multiple files, etc. -const OUTPUT_FILE: &str = "/var/log/wifitrace.dat"; 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"; @@ -76,69 +40,6 @@ 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 SYSTEMCTL_PATH: &str = "/usr/bin/systemctl"; -const TRACE_CMD_PATH: &str = "/usr/bin/trace-cmd"; - -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 - - if want_override { - // Copy it in - // Make sure the folder exists - fs::create_dir_all(OVERRIDE_FOLDER).await?; - // Then write the contents into the file - fs::write(OVERRIDE_PATH, OVERRIDE_CONTENTS).await - } else { - // Delete it - fs::remove_file(OVERRIDE_PATH).await - } -} - -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) - } - } - Err(message) => { - error!("restart_iwd: reload systemd got an error: {message}"); - Err(message) - } - } -} - -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 - run_script( - "extract traces", - TRACE_CMD_PATH, - &["extract", "-o", OUTPUT_FILE], - ) - .await -} - -async fn start_tracing(buffer_size: u32) -> Result { - // Start tracing - let size_str = format!("{}", buffer_size); - run_script( - "start tracing", - TRACE_CMD_PATH, - &["start", "-e", "ath11k_wmi_diag", "-b", &size_str], - ) - .await -} - async fn set_gpu_performance_level(level: i32) -> Result<()> { // Set given GPU performance level // Levels are defined below diff --git a/src/process.rs b/src/process.rs index a5e6847..e38f15c 100644 --- a/src/process.rs +++ b/src/process.rs @@ -10,6 +10,8 @@ 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 let mut child = Command::new(executable).args(args).spawn()?; diff --git a/src/wifi.rs b/src/wifi.rs new file mode 100644 index 0000000..70c5600 --- /dev/null +++ b/src/wifi.rs @@ -0,0 +1,144 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::Result; +use std::fmt; +use tokio::fs; +use tracing::error; + +use crate::process::{run_script, SYSTEMCTL_PATH}; + +const OVERRIDE_CONTENTS: &str = "[Service] +ExecStart= +ExecStart=/usr/lib/iwd/iwd -d +"; +const OVERRIDE_FOLDER: &str = "/etc/systemd/system/iwd.service.d"; +const OVERRIDE_PATH: &str = "/etc/systemd/system/iwd.service.d/override.conf"; + +// Only use one path for output for now. If needed we can add a timestamp later +// to have multiple files, etc. +const OUTPUT_FILE: &str = "/var/log/wifitrace.dat"; +const TRACE_CMD_PATH: &str = "/usr/bin/trace-cmd"; + +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +pub enum WifiDebugMode { + Off, + On, +} + +#[derive(PartialEq, Debug, Copy, Clone)] +#[repr(u32)] +pub enum WifiPowerManagement { + UnsupportedFeature = 0, + Disabled = 1, + Enabled = 2, +} + +impl TryFrom for WifiDebugMode { + type Error = &'static str; + fn try_from(v: u32) -> Result { + match v { + 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}"), + } + } +} + +impl fmt::Display for WifiDebugMode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WifiDebugMode::Off => write!(f, "Off"), + WifiDebugMode::On => write!(f, "On"), + } + } +} + +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}"), + } + } +} + +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"), + } + } +} + +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 + + if want_override { + // Copy it in + // Make sure the folder exists + fs::create_dir_all(OVERRIDE_FOLDER).await?; + // Then write the contents into the file + fs::write(OVERRIDE_PATH, OVERRIDE_CONTENTS).await + } else { + // Delete it + fs::remove_file(OVERRIDE_PATH).await + } +} + +pub 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) + } + } + Err(message) => { + error!("restart_iwd: reload systemd got an error: {message}"); + Err(message) + } + } +} + +pub 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 + run_script( + "extract traces", + TRACE_CMD_PATH, + &["extract", "-o", OUTPUT_FILE], + ) + .await +} + +pub async fn start_tracing(buffer_size: u32) -> Result { + // Start tracing + let size_str = format!("{}", buffer_size); + run_script( + "start tracing", + TRACE_CMD_PATH, + &["start", "-e", "ath11k_wmi_diag", "-b", &size_str], + ) + .await +} From 6e3ac42e8ec087c062001e7ef0bd892b627b86ec Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Wed, 27 Mar 2024 16:58:11 -0700 Subject: [PATCH 6/7] power: Split out from manager --- src/main.rs | 1 + src/manager.rs | 86 ++-------------------------------------------- src/power.rs | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 83 deletions(-) create mode 100644 src/power.rs diff --git a/src/main.rs b/src/main.rs index 869504f..949dd42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ use crate::sls::{LogLayer, LogReceiver}; mod ds_inhibit; mod hardware; mod manager; +mod power; mod process; mod sls; mod wifi; diff --git a/src/manager.rs b/src/manager.rs index cbf514b..9f2e6ef 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -5,12 +5,13 @@ * SPDX-License-Identifier: MIT */ -use anyhow::{ensure, Result}; -use tokio::{fs::File, io::AsyncWriteExt}; +use anyhow::Result; +use tokio::fs::File; use tracing::{error, warn}; 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::process::{run_script, script_output, SYSTEMCTL_PATH}; use crate::wifi::{restart_iwd, setup_iwd_config, start_tracing, stop_tracing, WifiDebugMode}; @@ -33,87 +34,6 @@ impl SMManager { 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"; -const POWER1_CAP_PATH: &str = "/sys/class/hwmon/hwmon5/power1_cap"; -const POWER2_CAP_PATH: &str = "/sys/class/hwmon/hwmon5/power2_cap"; - -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"; - -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" - ); - - let mut myfile = File::create(GPU_PERFORMANCE_LEVEL_PATH) - .await - .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; - - myfile - .write_all(levels[level as usize].as_bytes()) - .await - .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; - Ok(()) -} - -async fn set_gpu_clocks(clocks: i32) -> 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"); - - let mut myfile = File::create(GPU_CLOCKS_PATH) - .await - .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; - - let data = format!("s 0 {clocks}\n"); - myfile - .write(data.as_bytes()) - .await - .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; - - let data = format!("s 1 {clocks}\n"); - myfile - .write(data.as_bytes()) - .await - .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; - - myfile - .write("c\n".as_bytes()) - .await - .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; - Ok(()) -} - -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"); - - let mut power1file = File::create(POWER1_CAP_PATH).await.inspect_err(|message| { - error!("Error opening sysfs power1_cap file for writing TDP limits {message}") - })?; - - let mut power2file = File::create(POWER2_CAP_PATH).await.inspect_err(|message| { - error!("Error opening sysfs power2_cap file for wtriting TDP limits {message}") - })?; - - // Now write the value * 1,000,000 - let data = format!("{limit}000000"); - power1file - .write(data.as_bytes()) - .await - .inspect_err(|message| error!("Error writing to power1_cap file: {message}"))?; - power2file - .write(data.as_bytes()) - .await - .inspect_err(|message| error!("Error writing to power2_cap file: {message}"))?; - Ok(()) -} #[interface(name = "com.steampowered.SteamOSManager1")] impl SMManager { diff --git a/src/power.rs b/src/power.rs new file mode 100644 index 0000000..23c7afe --- /dev/null +++ b/src/power.rs @@ -0,0 +1,92 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::{ensure, Result}; +use tokio::{fs::File, io::AsyncWriteExt}; +use tracing::error; + +const POWER1_CAP_PATH: &str = "/sys/class/hwmon/hwmon5/power1_cap"; +const POWER2_CAP_PATH: &str = "/sys/class/hwmon/hwmon5/power2_cap"; + +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" + ); + + let mut myfile = File::create(GPU_PERFORMANCE_LEVEL_PATH) + .await + .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; + + myfile + .write_all(levels[level as usize].as_bytes()) + .await + .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; + Ok(()) +} + +pub async fn set_gpu_clocks(clocks: i32) -> 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"); + + let mut myfile = File::create(GPU_CLOCKS_PATH) + .await + .inspect_err(|message| error!("Error opening sysfs file for writing: {message}"))?; + + let data = format!("s 0 {clocks}\n"); + myfile + .write(data.as_bytes()) + .await + .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; + + let data = format!("s 1 {clocks}\n"); + myfile + .write(data.as_bytes()) + .await + .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; + + myfile + .write("c\n".as_bytes()) + .await + .inspect_err(|message| error!("Error writing to sysfs file: {message}"))?; + Ok(()) +} + +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"); + + let mut power1file = File::create(POWER1_CAP_PATH).await.inspect_err(|message| { + error!("Error opening sysfs power1_cap file for writing TDP limits {message}") + })?; + + let mut power2file = File::create(POWER2_CAP_PATH).await.inspect_err(|message| { + error!("Error opening sysfs power2_cap file for wtriting TDP limits {message}") + })?; + + // Now write the value * 1,000,000 + let data = format!("{limit}000000"); + power1file + .write(data.as_bytes()) + .await + .inspect_err(|message| error!("Error writing to power1_cap file: {message}"))?; + power2file + .write(data.as_bytes()) + .await + .inspect_err(|message| error!("Error writing to power2_cap file: {message}"))?; + Ok(()) +} From 27493c647d4a5c950b4c403201a03a319983637c Mon Sep 17 00:00:00 2001 From: Vicki Pfau Date: Fri, 29 Mar 2024 14:20:10 -0700 Subject: [PATCH 7/7] power: Redo hwmon handling --- src/power.rs | 113 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 97 insertions(+), 16 deletions(-) diff --git a/src/power.rs b/src/power.rs index 23c7afe..363a00c 100644 --- a/src/power.rs +++ b/src/power.rs @@ -5,12 +5,14 @@ * SPDX-License-Identifier: MIT */ -use anyhow::{ensure, Result}; -use tokio::{fs::File, io::AsyncWriteExt}; +use anyhow::{bail, ensure, Result}; +use tokio::fs::{self, File}; +use tokio::io::AsyncWriteExt; use tracing::error; -const POWER1_CAP_PATH: &str = "/sys/class/hwmon/hwmon5/power1_cap"; -const POWER2_CAP_PATH: &str = "/sys/class/hwmon/hwmon5/power2_cap"; +use crate::path; + +const GPU_HWMON_PREFIX: &str = "/sys/class/drm/card0/device/hwmon"; const GPU_PERFORMANCE_LEVEL_PATH: &str = "/sys/class/drm/card0/device/power_dpm_force_performance_level"; @@ -70,23 +72,102 @@ pub async fn set_tdp_limit(limit: i32) -> Result<()> { // Returns false on error or out of range ensure!((3..=15).contains(&limit), "Invalid limit"); - let mut power1file = File::create(POWER1_CAP_PATH).await.inspect_err(|message| { - error!("Error opening sysfs power1_cap file for writing TDP limits {message}") - })?; + let mut dir = fs::read_dir(path(GPU_HWMON_PREFIX)).await?; + let base = 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; + } + }; - let mut power2file = File::create(POWER2_CAP_PATH).await.inspect_err(|message| { - error!("Error opening sysfs power2_cap file for wtriting TDP limits {message}") - })?; - - // Now write the value * 1,000,000 let data = format!("{limit}000000"); + + let mut power1file = File::create(base.join("power1_cap")) + .await + .inspect_err(|message| { + error!("Error opening sysfs power1_cap file for writing TDP limits {message}") + })?; power1file .write(data.as_bytes()) .await .inspect_err(|message| error!("Error writing to power1_cap file: {message}"))?; - power2file - .write(data.as_bytes()) - .await - .inspect_err(|message| error!("Error writing to power2_cap file: {message}"))?; + + if let Ok(mut power2file) = File::create(base.join("power2_cap")).await { + power2file + .write(data.as_bytes()) + .await + .inspect_err(|message| error!("Error writing to power2_cap file: {message}"))?; + } Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + use crate::testing; + use anyhow::anyhow; + use tokio::fs::{create_dir_all, read_to_string, remove_dir, write}; + + #[tokio::test] + async fn test_set_tdp_limit() { + let h = testing::start(); + + assert_eq!( + set_tdp_limit(2).await.unwrap_err().to_string(), + anyhow!("Invalid limit").to_string() + ); + assert_eq!( + set_tdp_limit(20).await.unwrap_err().to_string(), + anyhow!("Invalid limit").to_string() + ); + assert!(set_tdp_limit(10).await.is_err()); + + let hwmon = path(GPU_HWMON_PREFIX); + create_dir_all(hwmon.as_path()) + .await + .expect("create_dir_all"); + assert_eq!( + set_tdp_limit(10).await.unwrap_err().to_string(), + anyhow!("hwmon not found").to_string() + ); + + let hwmon = hwmon.join("hwmon5"); + create_dir_all(hwmon.join("power1_cap")) + .await + .expect("create_dir_all"); + create_dir_all(hwmon.join("power2_cap")) + .await + .expect("create_dir_all"); + assert_eq!( + set_tdp_limit(10).await.unwrap_err().to_string(), + anyhow!("Is a directory (os error 21)").to_string() + ); + + remove_dir(hwmon.join("power1_cap")) + .await + .expect("remove_dir"); + write(hwmon.join("power1_cap"), "0").await.expect("write"); + assert!(set_tdp_limit(10).await.is_ok()); + let power1_cap = read_to_string(hwmon.join("power1_cap")) + .await + .expect("power1_cap"); + assert_eq!(power1_cap, "10000000"); + + remove_dir(hwmon.join("power2_cap")) + .await + .expect("remove_dir"); + write(hwmon.join("power2_cap"), "0").await.expect("write"); + assert!(set_tdp_limit(15).await.is_ok()); + let power1_cap = read_to_string(hwmon.join("power1_cap")) + .await + .expect("power1_cap"); + assert_eq!(power1_cap, "15000000"); + let power2_cap = read_to_string(hwmon.join("power2_cap")) + .await + .expect("power2_cap"); + assert_eq!(power2_cap, "15000000"); + } +}