diff --git a/src/hardware.rs b/src/hardware.rs new file mode 100644 index 0000000..5c19b06 --- /dev/null +++ b/src/hardware.rs @@ -0,0 +1,83 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +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"; + +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum HardwareVariant { + Unknown, + Jupiter, + 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" { + return Ok(HardwareVariant::Unknown); + } + + let board_name = fs::read_to_string(path(BOARD_NAME_PATH)).await?; + HardwareVariant::from_str(board_name.trim_end()) +} + +#[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(BOARD_VENDOR_PATH), "LENOVO\n") + .await + .expect("write"); + assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown); + + write(crate::path(BOARD_VENDOR_PATH), "Valve\n") + .await + .expect("write"); + write(crate::path(BOARD_NAME_PATH), "Jupiter\n") + .await + .expect("write"); + assert_eq!(variant().await.unwrap(), HardwareVariant::Jupiter); + + write(crate::path(BOARD_NAME_PATH), "Galileo\n") + .await + .expect("write"); + assert_eq!(variant().await.unwrap(), HardwareVariant::Galileo); + + write(crate::path(BOARD_NAME_PATH), "Neptune\n") + .await + .expect("write"); + assert_eq!(variant().await.unwrap(), HardwareVariant::Unknown); + } +} diff --git a/src/main.rs b/src/main.rs index 1f56064..949dd42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,8 +21,12 @@ use crate::sls::ftrace::Ftrace; use crate::sls::{LogLayer, LogReceiver}; mod ds_inhibit; +mod hardware; mod manager; +mod power; +mod process; mod sls; +mod wifi; #[cfg(test)] mod testing; @@ -117,7 +121,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 6a8fe5f..9f2e6ef 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -5,38 +5,15 @@ * SPDX-License-Identifier: MIT */ -use anyhow::{ensure, Result}; -use std::{ffi::OsStr, fmt, fs}; -use tokio::{fs::File, io::AsyncWriteExt, process::Command}; +use anyhow::Result; +use tokio::fs::File; use tracing::{error, warn}; use zbus::{interface, zvariant::Fd}; -#[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::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}; pub struct SMManager { wifi_debug_mode: WifiDebugMode, @@ -46,211 +23,17 @@ 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, }) } } -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 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"; - -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) -} - -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 - - if want_override { - // Copy it in - // Make sure the folder exists - tokio::fs::create_dir_all(OVERRIDE_FOLDER).await?; - // Then write the contents into the file - tokio::fs::write(OVERRIDE_PATH, OVERRIDE_CONTENTS).await - } else { - // Delete it - tokio::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 { - Ok(value) => { - if value { - // worked, now restart iwd - run_script("restart iwd", "systemctl", &["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(should_trace: bool) -> Result { - if !should_trace { - return Ok(true); - } - - // Stop tracing and extract ring buffer to disk for capture - run_script("stop tracing", "trace-cmd", &["stop"]).await?; - // stop tracing worked - run_script( - "extract traces", - "trace-cmd", - &["extract", "-o", OUTPUT_FILE], - ) - .await -} - -async fn start_tracing(buffer_size: u32, should_trace: bool) -> Result { - if !should_trace { - return Ok(true); - } - - // Start tracing - let size_str = format!("{}", buffer_size); - run_script( - "start tracing", - "trace-cmd", - &["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 - // 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 { @@ -262,9 +45,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 { @@ -283,7 +70,7 @@ impl SMManager { if enable { run_script( "enable fan control", - "systemcltl", + SYSTEMCTL_PATH, &["start", "jupiter-fan-control-service"], ) .await @@ -291,7 +78,7 @@ impl SMManager { } else { run_script( "disable fan control", - "systemctl", + SYSTEMCTL_PATH, &["stop", "jupiter-fan-control.service"], ) .await @@ -302,9 +89,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 { @@ -410,16 +201,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 +259,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 diff --git a/src/power.rs b/src/power.rs new file mode 100644 index 0000000..363a00c --- /dev/null +++ b/src/power.rs @@ -0,0 +1,173 @@ +/* + * Copyright © 2023 Collabora Ltd. + * Copyright © 2024 Valve Software + * + * SPDX-License-Identifier: MIT + */ + +use anyhow::{bail, ensure, Result}; +use tokio::fs::{self, File}; +use tokio::io::AsyncWriteExt; +use tracing::error; + +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"; +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 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 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}"))?; + + 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"); + } +} diff --git a/src/process.rs b/src/process.rs new file mode 100644 index 0000000..e38f15c --- /dev/null +++ b/src/process.rs @@ -0,0 +1,38 @@ +/* + * 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 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()?; + 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()) +} 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 +}