From ab195e1d84021fb250fe4198fcab63244df1bc69 Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Mon, 13 Sep 2021 11:46:22 +1200 Subject: [PATCH] Fan curve enablement - Add CtrlProfileTask - Add method to reset active profile curve to platform default - Wrap the zbus methods for profiles + fan curves - Enable CLI args for fan curves - CLI mod and save curves --- CHANGELOG.md | 10 + Cargo.lock | 9 +- README.md | 2 +- asusctl/Cargo.toml | 3 +- asusctl/src/cli_opts.rs | 6 +- asusctl/src/main.rs | 194 +++++++++++++----- asusctl/src/profiles_cli.rs | 39 ++++ daemon/Cargo.toml | 2 +- daemon/src/config.rs | 14 +- daemon/src/ctrl_anime/config.rs | 20 +- daemon/src/ctrl_anime/mod.rs | 24 ++- daemon/src/ctrl_aura/config.rs | 13 +- daemon/src/ctrl_profiles/config.rs | 37 ++-- daemon/src/ctrl_profiles/controller.rs | 79 +++++--- daemon/src/ctrl_profiles/zbus.rs | 77 +++++--- daemon/src/daemon.rs | 34 ++-- rog-dbus/Cargo.toml | 2 +- rog-dbus/src/zbus_profile.rs | 76 +++++-- rog-profiles/Cargo.toml | 2 +- rog-profiles/src/error.rs | 17 +- rog-profiles/src/fan_curve_set.rs | 262 +++++++++++++++++-------- rog-profiles/src/lib.rs | 189 +++++++++++------- 22 files changed, 783 insertions(+), 328 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42c56627..c86fb429 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +# [4.0.2] - 2021-09-14 +### Changed +- Backup old configs to *-old if parse fails +- Prevent some types of crashes related to unpatched kernels +- Add better help for graphics errors +- Add better help for asusctl general errors +- Implement fan-curve dbus API +- Implement partial fan-curve control via CLI tool + + Set fan curve for profile + fan gpu/cpu + # [4.0.1] - 2021-09-11 ### Changed - Fix asusd-ledmodes.toml diff --git a/Cargo.lock b/Cargo.lock index 428e4e26..ef85f43a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,7 +45,7 @@ dependencies = [ [[package]] name = "asusctl" -version = "4.0.0" +version = "4.0.2" dependencies = [ "daemon", "gif", @@ -59,6 +59,7 @@ dependencies = [ "supergfxctl", "sysfs-class", "tinybmp", + "toml", "zbus", ] @@ -209,7 +210,7 @@ dependencies = [ [[package]] name = "daemon" -version = "4.0.1" +version = "4.0.2" dependencies = [ "env_logger", "log", @@ -920,7 +921,7 @@ dependencies = [ [[package]] name = "rog_dbus" -version = "4.0.0" +version = "4.0.2" dependencies = [ "rog_anime", "rog_aura", @@ -934,7 +935,7 @@ dependencies = [ [[package]] name = "rog_profiles" -version = "1.0.1" +version = "1.1.2" dependencies = [ "serde", "serde_derive", diff --git a/README.md b/README.md index 9ca885b8..eda01b1c 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ will probably suffer another rename once it becomes generic enough to do so. Requirements are rust >= 1.47 installed from rustup.io if the distro provided version is too old, and `make`. -**Ubuntu*:** `apt install libclang-dev libudev-dev` +**Ubuntu:** `apt install libclang-dev libudev-dev` **fedora:** `dnf install clang-devel systemd-devel` diff --git a/asusctl/Cargo.toml b/asusctl/Cargo.toml index 1635021d..3554d917 100644 --- a/asusctl/Cargo.toml +++ b/asusctl/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "asusctl" -version = "4.0.0" +version = "4.0.2" authors = ["Luke D Jones "] edition = "2018" @@ -16,6 +16,7 @@ rog_supported = { path = "../rog-supported" } daemon = { path = "../daemon" } gumdrop = "^0.8" supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", tag = "2.0.0" } +toml = "^0.5.8" sysfs-class = "^0.1.2" diff --git a/asusctl/src/cli_opts.rs b/asusctl/src/cli_opts.rs index 32fcbed9..272d268c 100644 --- a/asusctl/src/cli_opts.rs +++ b/asusctl/src/cli_opts.rs @@ -1,7 +1,7 @@ use crate::{ anime_cli::AnimeCommand, aura_cli::{LedBrightness, SetAuraBuiltin}, - profiles_cli::ProfileCommand, + profiles_cli::{FanCurveCommand, ProfileCommand}, }; use gumdrop::Options; use supergfxctl::gfx_vendors::GfxVendors; @@ -30,8 +30,10 @@ pub struct CliStart { pub enum CliCommand { #[options(help = "Set the keyboard lighting from built-in modes")] LedMode(LedModeCommand), - #[options(help = "Create and configure profiles")] + #[options(help = "Set or select platform_profile")] Profile(ProfileCommand), + #[options(help = "Set, select, or modify fan curves if suported")] + FanCurve(FanCurveCommand), #[options(help = "Set the graphics mode")] Graphics(GraphicsCommand), #[options(name = "anime", help = "Manage AniMe Matrix")] diff --git a/asusctl/src/main.rs b/asusctl/src/main.rs index 820fe039..68610896 100644 --- a/asusctl/src/main.rs +++ b/asusctl/src/main.rs @@ -7,7 +7,7 @@ use crate::aura_cli::{LedBrightness, SetAuraBuiltin}; use crate::cli_opts::*; use anime_cli::{AnimeActions, AnimeCommand}; use gumdrop::{Opt, Options}; -use profiles_cli::ProfileCommand; +use profiles_cli::{FanCurveCommand, ProfileCommand}; use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2, ANIME_DATA_LEN}; use rog_aura::{self, AuraEffect}; use rog_dbus::RogDbusClient; @@ -16,6 +16,7 @@ use rog_supported::{ AnimeSupportedFunctions, LedSupportedFunctions, PlatformProfileFunctions, RogBiosSupportedFunctions, }; +use std::process::Command; use std::{env::args, path::Path, sync::mpsc::channel}; use supergfxctl::{ gfx_vendors::GfxRequiredUserAction, @@ -24,8 +25,6 @@ use supergfxctl::{ }; use zbus::Connection; -const PLEASE: &str = - "Please use `systemctl status asusd` and `journalctl -b -u asusd` for more information"; const CONFIG_ADVICE: &str = "A config file need to be removed so a new one can be generated"; fn main() -> Result<(), Box> { @@ -49,50 +48,48 @@ fn main() -> Result<(), Box> { } } - let (dbus, _) = RogDbusClient::new().map_err(|e| { - println!("\nIs asusd running?\n"); - println!("{}", PLEASE); - println!("{}\n", CONFIG_ADVICE); - e - })?; + let (dbus, _) = RogDbusClient::new() + .map_err(|e| { + print_error_help(Box::new(e), None); + std::process::exit(3); + }) + .unwrap(); let supported = dbus .proxies() .supported() .get_supported_functions() .map_err(|e| { - println!("\nIs asusd running?\n"); - println!("{}", PLEASE); - println!("{}\n", CONFIG_ADVICE); - e - })?; + print_error_help(Box::new(e), None); + std::process::exit(4); + }) + .unwrap(); if parsed.version { print_versions(); println!(); print_laptop_info(); - println!("{}\n", PLEASE); return Ok(()); } if let Err(err) = do_parsed(&parsed, &supported, &dbus) { - print_error_help(err, &supported); + print_error_help(err, Some(&supported)); } Ok(()) } -fn print_error_help(err: Box, supported: &SupportedFunctions) { - println!("Error: {}\n", err); - print_versions(); - println!(); - print_laptop_info(); - println!(); - println!("Supported laptop functions:\n\n{}", supported); - println!(); - println!("{}", PLEASE); - println!("The above may give some indication that an option is not supported"); - println!("or that a config file must be removed or fixed"); +fn print_error_help(err: Box, supported: Option<&SupportedFunctions>) { + if do_diagnose("asusd") { + println!("\nError: {}\n", err); + print_versions(); + println!(); + print_laptop_info(); + if let Some(supported) = supported { + println!(); + println!("Supported laptop functions:\n\n{}", supported); + } + } } fn print_versions() { @@ -117,6 +114,30 @@ fn print_laptop_info() { println!("Board name: {}", board_name.trim()); } +fn do_diagnose(name: &str) -> bool { + if name != "asusd" && !check_systemd_unit_enabled(name) { + println!( + "\n\x1b[0;31m{} is not enabled, enable it with `systemctl enable {}\x1b[0m", + name, name + ); + return true; + } else if !check_systemd_unit_active(name) { + println!( + "\n\x1b[0;31m{} is not running, start it with `systemctl start {}\x1b[0m", + name, name + ); + return true; + } else { + println!("\nSome error happened (sorry)"); + println!( + "Please use `systemctl status {}` and `journalctl -b -u {}` for more information", + name, name + ); + println!("{}", CONFIG_ADVICE); + } + false +} + fn do_parsed( parsed: &CliStart, supported: &SupportedFunctions, @@ -125,7 +146,13 @@ fn do_parsed( match &parsed.command { Some(CliCommand::LedMode(mode)) => handle_led_mode(dbus, &supported.keyboard_led, mode)?, Some(CliCommand::Profile(cmd)) => handle_profile(dbus, &supported.platform_profile, cmd)?, - Some(CliCommand::Graphics(cmd)) => do_gfx(cmd)?, + Some(CliCommand::FanCurve(cmd)) => { + handle_fan_curve(dbus, &supported.platform_profile, cmd)? + } + Some(CliCommand::Graphics(cmd)) => do_gfx(cmd).map_err(|err| { + do_gfx_diagnose(); + err + })?, Some(CliCommand::Anime(cmd)) => handle_anime(dbus, &supported.anime_ctrl, cmd)?, Some(CliCommand::Bios(cmd)) => handle_bios_option(dbus, &supported.rog_bios_ctrl, cmd)?, None => { @@ -177,6 +204,12 @@ fn do_parsed( Ok(()) } +fn do_gfx_diagnose() { + println!("\nGraphics mode change error."); + do_diagnose("supergfxd"); + println!(); +} + fn do_gfx(command: &GraphicsCommand) -> Result<(), Box> { if command.mode.is_none() && !command.get && !command.pow && !command.force || command.help { println!("{}", command.self_usage()); @@ -194,13 +227,11 @@ fn do_gfx(command: &GraphicsCommand) -> Result<(), Box> { std::process::exit(-1); } - println!("If anything fails check `journalctl -b -u asusd`\n"); + println!( + "If anything fails check `journalctl -b -u asusd` and `journalctl -b -u supergfxd`\n" + ); - proxy.gfx_write_mode(&mode).map_err(|err|{ - println!("Graphics mode change error. You may be in an invalid state."); - println!("Check mode with `asusctl graphics -g` and switch to opposite\nmode to correct it, e.g: if integrated, switch to hybrid, or if nvidia, switch to integrated.\n"); - err - })?; + proxy.gfx_write_mode(&mode)?; loop { proxy.next_signal()?; @@ -372,25 +403,16 @@ fn handle_led_mode( fn handle_profile( dbus: &RogDbusClient, - supported: &PlatformProfileFunctions, + _supported: &PlatformProfileFunctions, cmd: &ProfileCommand, ) -> Result<(), Box> { - println!("Warning: Profiles should work fine but now depend on power-profiles-daemon v0.9+"); + println!("Warning: Profiles now depend on power-profiles-daemon v0.9+"); println!("Warning: Fan-curve support is coming in a 4.1.x release"); if !cmd.next && !cmd.list { if !cmd.help { println!("Missing arg or command\n"); } - let usage: Vec = ProfileCommand::usage() - .lines() - .map(|s| s.to_string()) - .collect(); - for line in usage - .iter() - .filter(|line| !line.contains("--curve") || supported.fan_curves) - { - println!("{}", line); - } + println!("{}", ProfileCommand::usage()); if let Some(lst) = cmd.self_command_list() { println!("\n{}", lst); @@ -404,6 +426,7 @@ fn handle_profile( if cmd.next { dbus.proxies().profile().next_profile()?; } + if cmd.list { let res = dbus.proxies().profile().profiles()?; res.iter().for_each(|p| println!("{:?}", p)); @@ -411,6 +434,61 @@ fn handle_profile( Ok(()) } +fn handle_fan_curve( + dbus: &RogDbusClient, + _supported: &PlatformProfileFunctions, + cmd: &FanCurveCommand, +) -> Result<(), Box> { + if !cmd.get_enabled && !cmd.default && cmd.mod_profile.is_none() { + if !cmd.help { + println!("Missing arg or command\n"); + } + println!("{}", FanCurveCommand::usage()); + + if let Some(lst) = cmd.self_command_list() { + println!("\n{}", lst); + } + std::process::exit(1); + } + + if cmd.enabled.is_some() || cmd.fan.is_some() || cmd.data.is_some() && cmd.mod_profile.is_none() + { + println!("--enabled, --fan, and --data options require --mod-profile"); + std::process::exit(666); + } + + if cmd.get_enabled { + let res = dbus.proxies().profile().enabled_fan_profiles()?; + println!("{:?}", res); + } + + if cmd.default { + dbus.proxies().profile().set_active_curve_to_defaults()?; + } + + if let Some(profile) = cmd.mod_profile { + if cmd.enabled.is_none() && cmd.data.is_none() { + let data = dbus.proxies().profile().fan_curve_data(profile)?; + let data = toml::to_string(&data)?; + println!("\nFan curves for {:?}\n\n{}", profile, data); + } + + if let Some(enabled) = cmd.enabled { + dbus.proxies() + .profile() + .set_fan_curve_enabled(profile, enabled)?; + } + + if let Some(mut curve) = cmd.data.clone() { + let fan = cmd.fan.unwrap_or_default(); + curve.set_fan(fan); + dbus.proxies().profile().set_fan_curve(curve, profile)?; + } + } + + Ok(()) +} + fn handle_bios_option( dbus: &RogDbusClient, supported: &RogBiosSupportedFunctions, @@ -464,3 +542,27 @@ fn handle_bios_option( } Ok(()) } + +fn check_systemd_unit_active(name: &str) -> bool { + if let Ok(out) = Command::new("systemctl") + .arg("is-active") + .arg(name) + .output() + { + let buf = String::from_utf8_lossy(&out.stdout); + return !buf.contains("inactive") && !buf.contains("failed"); + } + false +} + +fn check_systemd_unit_enabled(name: &str) -> bool { + if let Ok(out) = Command::new("systemctl") + .arg("is-enabled") + .arg(name) + .output() + { + let buf = String::from_utf8_lossy(&out.stdout); + return buf.contains("enabled"); + } + false +} diff --git a/asusctl/src/profiles_cli.rs b/asusctl/src/profiles_cli.rs index 09550a42..e52fdc47 100644 --- a/asusctl/src/profiles_cli.rs +++ b/asusctl/src/profiles_cli.rs @@ -1,4 +1,5 @@ use gumdrop::Options; +use rog_profiles::{fan_curve_set::CurveData, FanCurvePU, Profile}; #[derive(Debug, Clone, Options)] pub struct ProfileCommand { @@ -8,4 +9,42 @@ pub struct ProfileCommand { pub next: bool, #[options(help = "list available profiles")] pub list: bool, + + #[options(help = "get profile")] + pub profile_get: bool, + #[options(help = "set the active profile")] + pub profile_set: Option, +} + +#[derive(Debug, Clone, Options)] +pub struct FanCurveCommand { + #[options(help = "print help message")] + pub help: bool, + + #[options(help = "get enabled fan profiles")] + pub get_enabled: bool, + #[options(help = "set the active profile's fan curve to default")] + pub default: bool, + + #[options( + meta = "", + help = "profile to modify fan-curve for. Shows data if no options provided" + )] + pub mod_profile: Option, + #[options( + meta = "", + help = "enable or disable fan curve. `mod-profile` required" + )] + pub enabled: Option, + #[options( + meta = "", + help = "select fan to modify. `mod-profile` required" + )] + pub fan: Option, + + #[options( + meta = "", + help = "data format = 30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%. `mod-profile` required" + )] + pub data: Option, } diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml index c9239f69..dafed479 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daemon" -version = "4.0.1" +version = "4.0.2" license = "MPL-2.0" readme = "README.md" authors = ["Luke "] diff --git a/daemon/src/config.rs b/daemon/src/config.rs index 273fbcac..8c891191 100644 --- a/daemon/src/config.rs +++ b/daemon/src/config.rs @@ -34,8 +34,18 @@ impl Config { } else if let Ok(data) = serde_json::from_str(&buf) { config = data; } else { - warn!("Could not deserialise {}", CONFIG_PATH); - panic!("Please remove {} then restart asusd", CONFIG_PATH); + warn!( + "Could not deserialise {}.\nWill rename to {}-old and recreate config", + CONFIG_PATH, CONFIG_PATH + ); + let cfg_old = CONFIG_PATH.to_string() + "-old"; + std::fs::rename(CONFIG_PATH, cfg_old).unwrap_or_else(|err| { + panic!( + "Could not rename. Please remove {} then restart service: Error {}", + CONFIG_PATH, err + ) + }); + config = Self::new(); } } else { config = Self::new() diff --git a/daemon/src/ctrl_anime/config.rs b/daemon/src/ctrl_anime/config.rs index e189ad8e..c18c4220 100644 --- a/daemon/src/ctrl_anime/config.rs +++ b/daemon/src/ctrl_anime/config.rs @@ -166,11 +166,17 @@ impl AnimeConfig { info!("Updated config version to: {}", VERSION); return config; } - AnimeConfig::write_backup(buf); warn!( - "Could not deserialise {}. Backed up as *-old", - ANIME_CONFIG_PATH + "Could not deserialise {}.\nWill rename to {}-old and recreate config", + ANIME_CONFIG_PATH, ANIME_CONFIG_PATH ); + let cfg_old = ANIME_CONFIG_PATH.to_string() + "-old"; + std::fs::rename(ANIME_CONFIG_PATH, cfg_old).unwrap_or_else(|err| { + panic!( + "Could not rename. Please remove {} then restart service: Error {}", + ANIME_CONFIG_PATH, err + ) + }); } } AnimeConfig::create_default(&mut file) @@ -246,12 +252,4 @@ impl AnimeConfig { file.write_all(json.as_bytes()) .unwrap_or_else(|err| error!("Could not write config: {}", err)); } - - fn write_backup(buf: String) { - let mut path = ANIME_CONFIG_PATH.to_string(); - path.push_str("-old"); - let mut file = File::create(&path).expect("Couldn't overwrite config"); - file.write_all(buf.as_bytes()) - .unwrap_or_else(|err| error!("Could not write config: {}", err)); - } } diff --git a/daemon/src/ctrl_anime/mod.rs b/daemon/src/ctrl_anime/mod.rs index 694036d4..4025ca6f 100644 --- a/daemon/src/ctrl_anime/mod.rs +++ b/daemon/src/ctrl_anime/mod.rs @@ -5,6 +5,7 @@ use ::zbus::Connection; use log::{error, info, warn}; use logind_zbus::ManagerProxy; use rog_anime::{ + error::AnimeError, usb::{ pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID, VENDOR_ID, @@ -181,15 +182,20 @@ impl CtrlAnime { for action in actions.iter() { match action { ActionData::Animation(frames) => { - if rog_anime::run_animation(frames, thread_exit.clone(), &|frame| { - if let Ok(lock) = inner.try_lock() { - lock.write_data_buffer(frame); - } - Ok(()) - }) - .map_err(|err| warn!("rog_anime::run_animation: {}", err)) - .is_err() - { + if let Err(err) = rog_anime::run_animation( + frames, + thread_exit.clone(), + &|frame| { + inner + .try_lock() + .map(|lock| lock.write_data_buffer(frame)) + .map_err(|err| { + warn!("rog_anime::run_animation: {}", err); + AnimeError::NoFrames + }) + }, + ) { + warn!("rog_anime::run_animation: {}", err); break 'main; }; diff --git a/daemon/src/ctrl_aura/config.rs b/daemon/src/ctrl_aura/config.rs index 6e5d3778..7087d601 100644 --- a/daemon/src/ctrl_aura/config.rs +++ b/daemon/src/ctrl_aura/config.rs @@ -105,8 +105,17 @@ impl AuraConfig { info!("Updated AuraConfig version"); return config; } - warn!("Could not deserialise {}", AURA_CONFIG_PATH); - panic!("Please remove {} then restart asusd", AURA_CONFIG_PATH); + warn!( + "Could not deserialise {}.\nWill rename to {}-old and recreate config", + AURA_CONFIG_PATH, AURA_CONFIG_PATH + ); + let cfg_old = AURA_CONFIG_PATH.to_string() + "-old"; + std::fs::rename(AURA_CONFIG_PATH, cfg_old).unwrap_or_else(|err| { + panic!( + "Could not rename. Please remove {} then restart service: Error {}", + AURA_CONFIG_PATH, err + ) + }); } } AuraConfig::create_default(&mut file, supported_led_modes) diff --git a/daemon/src/ctrl_profiles/config.rs b/daemon/src/ctrl_profiles/config.rs index 176d9158..0848fdee 100644 --- a/daemon/src/ctrl_profiles/config.rs +++ b/daemon/src/ctrl_profiles/config.rs @@ -1,5 +1,4 @@ use log::{error, warn}; -use rog_profiles::fan_curve_set::FanCurveSet; use rog_profiles::{FanCurveProfiles, Profile}; use serde_derive::{Deserialize, Serialize}; use std::fs::{File, OpenOptions}; @@ -17,20 +16,22 @@ pub struct ProfileConfig { impl ProfileConfig { fn new(config_path: String) -> Self { - let mut platform = ProfileConfig { + Self { config_path, - active_profile: Profile::get_active_profile().unwrap_or(Profile::Balanced), + active_profile: Profile::Balanced, fan_curves: None, - }; + } + } - if let Ok(res) = FanCurveSet::is_supported() { + pub fn set_defaults_and_save(&mut self) { + self.active_profile = Profile::get_active_profile().unwrap_or(Profile::Balanced); + if let Ok(res) = FanCurveProfiles::is_supported() { if res { let curves = FanCurveProfiles::default(); - platform.fan_curves = Some(curves); + self.fan_curves = Some(curves); } } - - platform + self.write(); } pub fn load(config_path: String) -> Self { @@ -45,17 +46,29 @@ impl ProfileConfig { if let Ok(read_len) = file.read_to_string(&mut buf) { if read_len == 0 { config = Self::new(config_path); + config.set_defaults_and_save(); } else if let Ok(data) = toml::from_str(&buf) { config = data; config.config_path = config_path; } else { - warn!("Could not deserialise {}", config_path); - panic!("Please remove {} then restart service", config_path); + warn!( + "Could not deserialise {}.\nWill rename to {}-old and recreate config", + config_path, config_path + ); + let cfg_old = config_path.clone() + "-old"; + std::fs::rename(config_path.clone(), cfg_old).unwrap_or_else(|err| { + panic!( + "Could not rename. Please remove {} then restart service: Error {}", + config_path, err + ) + }); + config = Self::new(config_path); + config.set_defaults_and_save(); } } else { - config = Self::new(config_path) + config = Self::new(config_path); + config.set_defaults_and_save(); } - config.write(); config } diff --git a/daemon/src/ctrl_profiles/controller.rs b/daemon/src/ctrl_profiles/controller.rs index 4f55d52a..be8b968f 100644 --- a/daemon/src/ctrl_profiles/controller.rs +++ b/daemon/src/ctrl_profiles/controller.rs @@ -1,17 +1,16 @@ +use std::sync::{Arc, Mutex}; + use crate::error::RogError; -use crate::GetSupported; +use crate::{CtrlTask, GetSupported}; use log::{info, warn}; use rog_profiles::error::ProfileError; -use rog_profiles::fan_curve_set::FanCurveSet; -use rog_profiles::Profile; +use rog_profiles::{FanCurveProfiles, Profile}; use rog_supported::PlatformProfileFunctions; -use udev::Device; use super::config::ProfileConfig; pub struct CtrlPlatformProfile { pub config: ProfileConfig, - pub fan_device: Option, } impl GetSupported for CtrlPlatformProfile { @@ -28,7 +27,7 @@ https://lkml.org/lkml/2021/8/18/1022 ); } - let res = FanCurveSet::is_supported(); + let res = FanCurveProfiles::is_supported(); let mut fan_curve_supported = res.is_err(); if let Ok(r) = res { fan_curve_supported = r; @@ -55,9 +54,9 @@ Please note that as of 24/08/2021 this is not final. impl crate::Reloadable for CtrlPlatformProfile { /// Fetch the active profile and use that to set all related components up fn reload(&mut self) -> Result<(), RogError> { - if let Some(curves) = &self.config.fan_curves { - if let Ok(mut device) = FanCurveSet::get_device() { - curves.write_to_platform(self.config.active_profile, &mut device); + if let Some(curves) = &mut self.config.fan_curves { + if let Ok(mut device) = FanCurveProfiles::get_device() { + curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?; } } Ok(()) @@ -65,30 +64,24 @@ impl crate::Reloadable for CtrlPlatformProfile { } impl CtrlPlatformProfile { - pub fn new(mut config: ProfileConfig, fan_device: Option) -> Result { + pub fn new(mut config: ProfileConfig) -> Result { if Profile::is_platform_profile_supported() { info!("Device has profile control available"); - if let Some(ref device) = fan_device { + if let Ok(ref device) = FanCurveProfiles::get_device() { let profile = config.active_profile; - config - .fan_curves - .as_mut() - .unwrap() - .read_from_dev_profile(profile, device); + if let Some(curve) = config.fan_curves.as_mut() { + curve.read_from_dev_profile(profile, device); + } } config.write(); - return Ok(CtrlPlatformProfile { config, fan_device }); + return Ok(CtrlPlatformProfile { config }); } Err(ProfileError::NotSupported.into()) } - pub fn get_device(&self) -> Option { - self.fan_device.clone() - } - pub fn save_config(&self) { self.config.write(); } @@ -96,8 +89,6 @@ impl CtrlPlatformProfile { /// Toggle to next profile in list. This will first read the config, switch, then write out pub(super) fn set_next_profile(&mut self) -> Result<(), RogError> { // Read first just incase the user has modified the config before calling this - self.config.read(); - match self.config.active_profile { Profile::Balanced => { Profile::set_profile(Profile::Performance)?; @@ -112,9 +103,49 @@ impl CtrlPlatformProfile { self.config.active_profile = Profile::Balanced; } } + self.write_profile_curve_to_platform()?; + Ok(()) + } - self.config.write(); + /// Set the curve for the active profile active + pub(super) fn write_profile_curve_to_platform(&mut self) -> Result<(), RogError> { + if let Some(curves) = &mut self.config.fan_curves { + if let Ok(mut device) = FanCurveProfiles::get_device() { + curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?; + } + } + Ok(()) + } + pub(super) fn set_active_curve_to_defaults(&mut self) -> Result<(), RogError> { + if let Some(curves) = self.config.fan_curves.as_mut() { + if let Ok(mut device) = FanCurveProfiles::get_device() { + curves.set_active_curve_to_defaults(self.config.active_profile, &mut device)?; + } + } + Ok(()) + } +} + +pub struct CtrlProfileTask { + ctrl: Arc>, +} + +impl CtrlProfileTask { + pub fn new(ctrl: Arc>) -> Self { + Self { ctrl } + } +} + +impl CtrlTask for CtrlProfileTask { + fn do_task(&self) -> Result<(), RogError> { + if let Ok(ref mut lock) = self.ctrl.try_lock() { + let new_profile = Profile::get_active_profile().unwrap(); + if new_profile != lock.config.active_profile { + lock.config.active_profile = new_profile; + lock.save_config(); + } + } Ok(()) } } diff --git a/daemon/src/ctrl_profiles/zbus.rs b/daemon/src/ctrl_profiles/zbus.rs index c0f1e61c..a1711dfb 100644 --- a/daemon/src/ctrl_profiles/zbus.rs +++ b/daemon/src/ctrl_profiles/zbus.rs @@ -35,11 +35,13 @@ impl ProfileZbus { )) } - /// Toggle to next platform_profile. Names provided by `Profiles` + /// Toggle to next platform_profile. Names provided by `Profiles`. + /// If fan-curves are supported will also activate a fan curve for profile. fn next_profile(&mut self) { if let Ok(mut ctrl) = self.inner.try_lock() { ctrl.set_next_profile() .unwrap_or_else(|err| warn!("{}", err)); + ctrl.save_config(); } self.do_notification(); } @@ -61,9 +63,12 @@ impl ProfileZbus { // Read first just incase the user has modified the config before calling this ctrl.config.read(); Profile::set_profile(profile) - .map_err(|e| warn!("Profile::set_profile, {}", e)) + .map_err(|e| warn!("set_profile, {}", e)) .ok(); ctrl.config.active_profile = profile; + ctrl.write_profile_curve_to_platform() + .map_err(|e| warn!("write_profile_curve_to_platform, {}", e)) + .ok(); ctrl.save_config(); } @@ -84,14 +89,23 @@ impl ProfileZbus { )) } - /// Get a list of profiles that have fan-curves enabled. - fn set_enabled_fan_profiles(&mut self, profiles: Vec) -> zbus::fdo::Result<()> { + /// Set a profile fan curve enabled status. Will also activate a fan curve if in the + /// same profile mode + fn set_fan_curve_enabled(&mut self, profile: Profile, enabled: bool) -> zbus::fdo::Result<()> { if let Ok(mut ctrl) = self.inner.try_lock() { ctrl.config.read(); if let Some(curves) = &mut ctrl.config.fan_curves { - curves.set_enabled_curve_profiles(profiles); + curves.set_profile_curve_enabled(profile, enabled); + + ctrl.write_profile_curve_to_platform() + .map_err(|e| warn!("write_profile_curve_to_platform, {}", e)) + .ok(); + + ctrl.save_config(); + return Ok(()); + } else { + return Err(Error::Failed(UNSUPPORTED_MSG.to_string())); } - return Err(Error::Failed(UNSUPPORTED_MSG.to_string())); } Err(Error::Failed( "Failed to get enabled fan curve names".to_string(), @@ -99,46 +113,51 @@ impl ProfileZbus { } /// Get the fan-curve data for the currently active Profile - fn active_fan_curve_data(&mut self) -> zbus::fdo::Result { + fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result { if let Ok(mut ctrl) = self.inner.try_lock() { ctrl.config.read(); if let Some(curves) = &ctrl.config.fan_curves { - return Ok((*curves.get_active_fan_curves()).clone()); + let curve = curves.get_fan_curves_for(profile); + return Ok(curve.clone()); } return Err(Error::Failed(UNSUPPORTED_MSG.to_string())); } Err(Error::Failed("Failed to get fan curve data".to_string())) } - /// Get fan-curve data for each Profile as an array of objects - fn fan_curves(&self) -> zbus::fdo::Result> { + /// Set the fan curve for the specified profile. + /// Will also activate the fan curve if the user is in the same mode. + fn set_fan_curve(&self, profile: Profile, curve: CurveData) -> zbus::fdo::Result<()> { if let Ok(mut ctrl) = self.inner.try_lock() { ctrl.config.read(); - if let Some(curves) = &ctrl.config.fan_curves { - return Ok(curves.get_all_fan_curves()); - } - return Err(Error::Failed(UNSUPPORTED_MSG.to_string())); - } - Err(Error::Failed("Failed to get all fan curves".to_string())) - } - - /// Set this fan-curve data - fn set_fan_curve(&self, curve: CurveData) -> zbus::fdo::Result<()> { - if let Ok(mut ctrl) = self.inner.try_lock() { - ctrl.config.read(); - let profile = ctrl.config.active_profile; - if let Some(mut device) = ctrl.get_device() { - if let Some(curves) = &mut ctrl.config.fan_curves { - curves.write_and_set_fan_curve(curve, profile, &mut device); - } + if let Some(curves) = &mut ctrl.config.fan_curves { + curves + .save_fan_curve(curve, profile) + .map_err(|err| zbus::fdo::Error::Failed(err.to_string()))?; } else { return Err(Error::Failed(UNSUPPORTED_MSG.to_string())); } - + ctrl.write_profile_curve_to_platform() + .map_err(|e| warn!("Profile::set_profile, {}", e)) + .ok(); ctrl.save_config(); } + Ok(()) + } - Err(Error::Failed("Failed to set fan curves".to_string())) + /// Reset the stored (self) and device curve to the defaults of the platform. + /// + /// Each platform_profile has a different default and the defualt can be read + /// only for the currently active profile. + fn set_active_curve_to_defaults(&self) -> zbus::fdo::Result<()> { + if let Ok(mut ctrl) = self.inner.try_lock() { + ctrl.config.read(); + ctrl.set_active_curve_to_defaults() + .map_err(|e| warn!("Profile::set_active_curve_to_defaults, {}", e)) + .ok(); + ctrl.save_config(); + } + Ok(()) } #[dbus_interface(signal)] diff --git a/daemon/src/daemon.rs b/daemon/src/daemon.rs index 46df535b..846fc7a4 100644 --- a/daemon/src/daemon.rs +++ b/daemon/src/daemon.rs @@ -7,6 +7,7 @@ use daemon::ctrl_aura::controller::{ }; use daemon::ctrl_charge::CtrlCharge; use daemon::ctrl_profiles::config::ProfileConfig; +use daemon::ctrl_profiles::controller::CtrlProfileTask; use daemon::{ config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported, }; @@ -20,7 +21,7 @@ use daemon::{CtrlTask, Reloadable, ZbusAdd}; use log::LevelFilter; use log::{error, info, warn}; use rog_dbus::DBUS_NAME; -use rog_profiles::fan_curve_set::FanCurveSet; +use rog_profiles::Profile; use std::env; use std::error::Error; use std::io::Write; @@ -108,23 +109,24 @@ fn start_daemon() -> Result<(), Box> { } } - let fan_device = if let Ok(res) = FanCurveSet::get_device() { - Some(res) - } else { - None - }; - let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into()); - match CtrlPlatformProfile::new(profile_config, fan_device) { - Ok(mut ctrl) => { - ctrl.reload() - .unwrap_or_else(|err| warn!("Profile control: {}", err)); + if Profile::is_platform_profile_supported() { + let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into()); + match CtrlPlatformProfile::new(profile_config) { + Ok(mut ctrl) => { + ctrl.reload() + .unwrap_or_else(|err| warn!("Profile control: {}", err)); - let tmp = Arc::new(Mutex::new(ctrl)); - ProfileZbus::new(tmp).add_to_server(&mut object_server); - } - Err(err) => { - error!("Profile control: {}", err); + let tmp = Arc::new(Mutex::new(ctrl)); + ProfileZbus::new(tmp.clone()).add_to_server(&mut object_server); + + tasks.push(Box::new(CtrlProfileTask::new(tmp))); + } + Err(err) => { + error!("Profile control: {}", err); + } } + } else { + warn!("platform_profile support not found. This requires kernel 5.15.x or the patch applied: https://lkml.org/lkml/2021/8/18/1022"); } match CtrlAnime::new(AnimeConfig::load()) { diff --git a/rog-dbus/Cargo.toml b/rog-dbus/Cargo.toml index 81b7a443..9a8fbe12 100644 --- a/rog-dbus/Cargo.toml +++ b/rog-dbus/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rog_dbus" -version = "4.0.0" +version = "4.0.2" license = "MPL-2.0" readme = "README.md" authors = ["Luke "] diff --git a/rog-dbus/src/zbus_profile.rs b/rog-dbus/src/zbus_profile.rs index 954d63ea..3842c47c 100644 --- a/rog-dbus/src/zbus_profile.rs +++ b/rog-dbus/src/zbus_profile.rs @@ -21,7 +21,10 @@ use std::sync::mpsc::Sender; -use rog_profiles::{fan_curve_set::FanCurveSet, Profile}; +use rog_profiles::{ + fan_curve_set::{CurveData, FanCurveSet}, + Profile, +}; use zbus::{dbus_proxy, Connection, Result}; #[dbus_proxy( @@ -29,29 +32,37 @@ use zbus::{dbus_proxy, Connection, Result}; default_path = "/org/asuslinux/Profile" )] trait Daemon { - /// Get the active `Profile` data - fn active_fan_curve_data(&self) -> zbus::Result; + /// Get the fan-curve data for the currently active Profile + fn fan_curve_data(&self, profile: Profile) -> zbus::Result; - /// Profile, get the active profile + /// Fetch the active profile name fn active_profile(&self) -> zbus::Result; - /// Get enabled fan curves + /// Get a list of profiles that have fan-curves enabled. fn enabled_fan_profiles(&self) -> zbus::Result>; - /// Get all fan curve data - fn fan_curves(&self) -> zbus::Result>; - - /// NextProfile method + /// Toggle to next platform_profile. Names provided by `Profiles`. + /// If fan-curves are supported will also activate a fan curve for profile. fn next_profile(&self) -> zbus::Result<()>; - /// Profiles method + /// Fetch profile names fn profiles(&self) -> zbus::Result>; - /// Set the specific profile as active + /// Set this platform_profile name as active fn set_active_profile(&self, profile: Profile) -> zbus::Result<()>; - /// Set a fan curve. If a field is empty then the exisiting saved curve is used - fn set_fan_curve(&self, curve: FanCurveSet) -> zbus::Result<()>; + /// Set a profile fan curve enabled status. Will also activate a fan curve. + fn set_fan_curve_enabled(&self, profile: Profile, enabled: bool) -> zbus::Result<()>; + + /// Set the fan curve for the specified profile, or the profile the user is + /// currently in if profile == None. Will also activate the fan curve. + fn set_fan_curve(&self, profile: Profile, curve: CurveData) -> zbus::Result<()>; + + /// Reset the stored (self) and device curve to the defaults of the platform. + /// + /// Each platform_profile has a different default and the defualt can be read + /// only for the currently active profile. + fn set_active_curve_to_defaults(&self) -> zbus::Result<()>; /// NotifyProfile signal #[dbus_proxy(signal)] @@ -72,8 +83,18 @@ impl<'a> ProfileProxy<'a> { } #[inline] - pub fn profiles(&self) -> Result> { - self.0.profiles() + pub fn active_profile(&self) -> zbus::Result { + self.0.active_profile() + } + + #[inline] + pub fn enabled_fan_profiles(&self) -> zbus::Result> { + self.0.enabled_fan_profiles() + } + + #[inline] + pub fn fan_curve_data(&self, profile: Profile) -> zbus::Result { + self.0.fan_curve_data(profile) } #[inline] @@ -81,6 +102,31 @@ impl<'a> ProfileProxy<'a> { self.0.next_profile() } + #[inline] + pub fn profiles(&self) -> Result> { + self.0.profiles() + } + + #[inline] + pub fn set_active_profile(&self, profile: Profile) -> zbus::Result<()> { + self.0.set_active_profile(profile) + } + + #[inline] + pub fn set_fan_curve_enabled(&self, profile: Profile, enabled: bool) -> zbus::Result<()> { + self.0.set_fan_curve_enabled(profile, enabled) + } + + #[inline] + pub fn set_fan_curve(&self, curve: CurveData, profile: Profile) -> zbus::Result<()> { + self.0.set_fan_curve(profile, curve) + } + + #[inline] + pub fn set_active_curve_to_defaults(&self) -> zbus::Result<()> { + self.0.set_active_curve_to_defaults() + } + #[inline] pub fn connect_notify_profile(&self, send: Sender) -> zbus::fdo::Result<()> { self.0.connect_notify_profile(move |data| { diff --git a/rog-profiles/Cargo.toml b/rog-profiles/Cargo.toml index 4d99e5b8..baad34eb 100644 --- a/rog-profiles/Cargo.toml +++ b/rog-profiles/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rog_profiles" -version = "1.0.1" +version = "1.1.2" authors = ["Luke D. Jones "] edition = "2018" diff --git a/rog-profiles/src/error.rs b/rog-profiles/src/error.rs index d80f8551..6980e904 100644 --- a/rog-profiles/src/error.rs +++ b/rog-profiles/src/error.rs @@ -8,7 +8,11 @@ pub enum ProfileError { NotSupported, NotFound(String), Io(std::io::Error), - //Zbus(zbus::Error), + ParseProfileName, + ParseFanCurveDigit(std::num::ParseIntError), + /// (pwm/temp, prev, next) + ParseFanCurvePrevHigher(&'static str, u8, u8), + // Zbus(zbus::Error), } impl fmt::Display for ProfileError { @@ -21,7 +25,16 @@ impl fmt::Display for ProfileError { ProfileError::NotSupported => write!(f, "Not supported"), ProfileError::NotFound(deets) => write!(f, "Not found: {}", deets), ProfileError::Io(detail) => write!(f, "std::io error: {}", detail), - //Error::Zbus(detail) => write!(f, "Zbus error: {}", detail), + ProfileError::ParseProfileName => write!(f, "Invalid profile name"), + ProfileError::ParseFanCurveDigit(e) => { + write!(f, "Could not parse number to 0-255: {}", e) + } + ProfileError::ParseFanCurvePrevHigher(part, prev, next) => write!( + f, + "Invalid {}, previous value {} is higher than next value {}", + part, prev, next + ), + // Error::Zbus(detail) => write!(f, "Zbus error: {}", detail), } } } diff --git a/rog-profiles/src/fan_curve_set.rs b/rog-profiles/src/fan_curve_set.rs index 1ebd93e8..1096cab2 100644 --- a/rog-profiles/src/fan_curve_set.rs +++ b/rog-profiles/src/fan_curve_set.rs @@ -4,24 +4,24 @@ use udev::Device; #[cfg(feature = "dbus")] use zvariant_derive::Type; -use crate::{error::ProfileError, write_to_fan, FanCurvePU}; +use crate::{error::ProfileError, FanCurvePU}; -pub fn pwm_str(fan: char, index: char) -> String { +pub(crate) fn pwm_str(fan: char, index: usize) -> String { let mut buf = "pwm1_auto_point1_pwm".to_string(); unsafe { let tmp = buf.as_bytes_mut(); tmp[3] = fan as u8; - tmp[15] = index as u8; + tmp[15] = char::from_digit(index as u32 + 1, 10).unwrap() as u8; } buf } -pub fn temp_str(fan: char, index: char) -> String { +pub(crate) fn temp_str(fan: char, index: usize) -> String { let mut buf = "pwm1_auto_point1_temp".to_string(); unsafe { let tmp = buf.as_bytes_mut(); tmp[3] = fan as u8; - tmp[15] = index as u8; + tmp[15] = char::from_digit(index as u32 + 1, 10).unwrap() as u8; } buf } @@ -29,22 +29,135 @@ pub fn temp_str(fan: char, index: char) -> String { #[cfg_attr(feature = "dbus", derive(Type))] #[derive(Deserialize, Serialize, Default, Debug, Clone)] pub struct CurveData { - pub fan: FanCurvePU, - pub pwm: [u8; 8], - pub temp: [u8; 8], + pub(crate) fan: FanCurvePU, + pub(crate) pwm: [u8; 8], + pub(crate) temp: [u8; 8], +} + +impl std::str::FromStr for CurveData { + type Err = ProfileError; + + fn from_str(input: &str) -> Result { + let mut temp = [0u8; 8]; + let mut pwm = [0u8; 8]; + let mut temp_prev = 0; + let mut pwm_prev = 0; + + for (index, value) in input.split(',').enumerate() { + for (select, num) in value.splitn(2, |c| c == 'c' || c == ':').enumerate() { + let r = num.trim_matches(|c| c == 'c' || c == ':' || c == '%'); + let r = r.parse::().map_err(ProfileError::ParseFanCurveDigit)?; + + if select == 0 { + if temp_prev > r { + return Err(ProfileError::ParseFanCurvePrevHigher( + "temperature", + temp_prev, + r, + )); + } + temp_prev = r; + temp[index] = r; + } else { + if pwm_prev > r { + return Err(ProfileError::ParseFanCurvePrevHigher( + "percentage", + pwm_prev, + r, + )); + } + pwm_prev = r; + pwm[index] = r; + } + } + } + Ok(Self { + fan: FanCurvePU::CPU, + pwm, + temp, + }) + } +} + +impl CurveData { + pub fn set_fan(&mut self, fan: FanCurvePU) { + self.fan = fan; + } + + fn set_val_from_attr(tmp: &str, device: &Device, buf: &mut [u8; 8]) { + if let Some(n) = tmp.chars().nth(15) { + let i = n.to_digit(10).unwrap() as usize; + let d = device.attribute_value(tmp).unwrap(); + let d: u8 = d.to_string_lossy().parse().unwrap(); + buf[i - 1] = d; + } + } + + fn read_from_device(&mut self, device: &Device) { + for attr in device.attributes() { + let tmp = attr.name().to_string_lossy(); + if tmp.starts_with("pwm1") && tmp.ends_with("_temp") { + Self::set_val_from_attr(tmp.as_ref(), device, &mut self.temp) + } + if tmp.starts_with("pwm1") && tmp.ends_with("_pwm") { + Self::set_val_from_attr(tmp.as_ref(), device, &mut self.pwm) + } + } + } + + fn init_if_zeroed(&mut self, device: &mut Device) -> std::io::Result<()> { + if self.pwm == [0u8; 8] && self.temp == [0u8; 8] { + // Need to reset the device to defaults to get the proper profile defaults + match self.fan { + FanCurvePU::CPU => device.set_attribute_value("pwm1_enable", "3")?, + FanCurvePU::GPU => device.set_attribute_value("pwm2_enable", "3")?, + }; + self.read_from_device(device); + } + Ok(()) + } + + /// Write this curve to the device fan specified by `self.fan` + fn write_to_device(&self, device: &mut Device, enable: bool) -> std::io::Result<()> { + let pwm_num = match self.fan { + FanCurvePU::CPU => '1', + FanCurvePU::GPU => '2', + }; + let enable = if enable { "1" } else { "2" }; + + for (index, out) in self.pwm.iter().enumerate() { + let pwm = pwm_str(pwm_num, index); + device.set_attribute_value(&pwm, &out.to_string())?; + } + + for (index, out) in self.temp.iter().enumerate() { + let temp = temp_str(pwm_num, index); + device.set_attribute_value(&temp, &out.to_string())?; + } + + // Enable must be done *after* all points are written + match self.fan { + FanCurvePU::CPU => device.set_attribute_value("pwm1_enable", enable)?, + FanCurvePU::GPU => device.set_attribute_value("pwm2_enable", enable)?, + }; + + Ok(()) + } } /// A `FanCurveSet` contains both CPU and GPU fan curve data #[cfg_attr(feature = "dbus", derive(Type))] #[derive(Deserialize, Serialize, Debug, Clone)] pub struct FanCurveSet { - pub cpu: CurveData, - pub gpu: CurveData, + pub(crate) enabled: bool, + pub(crate) cpu: CurveData, + pub(crate) gpu: CurveData, } impl Default for FanCurveSet { fn default() -> Self { Self { + enabled: false, cpu: CurveData { fan: FanCurvePU::CPU, pwm: [0u8; 8], @@ -60,80 +173,71 @@ impl Default for FanCurveSet { } impl FanCurveSet { - pub fn get_device() -> Result { - let mut enumerator = udev::Enumerator::new()?; - enumerator.match_subsystem("hwmon")?; - - for device in enumerator.scan_devices().unwrap() { - if device.parent_with_subsystem("platform").unwrap().is_some() { - if let Some(name) = device.attribute_value("name") { - if name == "asus_custom_fan_curve" { - return Ok(device); - } - } - } - } - Err(ProfileError::NotSupported) + pub(crate) fn read_cpu_from_device(&mut self, device: &Device) { + self.cpu.read_from_device(device); } - pub fn is_supported() -> Result { - if Self::get_device().is_ok() { - return Ok(true); - } - - Ok(false) + pub(crate) fn read_gpu_from_device(&mut self, device: &Device) { + self.gpu.read_from_device(device); } - pub fn new() -> Result<(Self, Device), ProfileError> { - if let Ok(device) = Self::get_device() { - let mut fans = Self { - cpu: CurveData::default(), - gpu: CurveData::default(), - }; - - fans.cpu.fan = FanCurvePU::CPU; - fans.cpu.fan = FanCurvePU::GPU; - - fans.read_from_device(&device); - - return Ok((fans, device)); - } - - Err(ProfileError::NotSupported) + pub(crate) fn write_cpu_fan(&mut self, device: &mut Device) -> std::io::Result<()> { + self.cpu.init_if_zeroed(device)?; + self.cpu.write_to_device(device, self.enabled) } - fn set_val_from_attr(tmp: &str, device: &Device, buf: &mut [u8; 8]) { - if let Some(n) = tmp.chars().nth(15) { - let i = n.to_digit(10).unwrap() as usize; - let d = device.attribute_value(tmp).unwrap(); - let d: u8 = d.to_string_lossy().parse().unwrap(); - buf[i - 1] = d; - } - } - - pub fn read_from_device(&mut self, device: &Device) { - for attr in device.attributes() { - let tmp = attr.name().to_string_lossy(); - if tmp.starts_with("pwm1") && tmp.ends_with("_temp") { - Self::set_val_from_attr(tmp.as_ref(), device, &mut self.cpu.temp) - } - if tmp.starts_with("pwm1") && tmp.ends_with("_pwm") { - Self::set_val_from_attr(tmp.as_ref(), device, &mut self.cpu.pwm) - } - if tmp.starts_with("pwm2") && tmp.ends_with("_temp") { - Self::set_val_from_attr(tmp.as_ref(), device, &mut self.gpu.temp) - } - if tmp.starts_with("pwm2") && tmp.ends_with("_pwm") { - Self::set_val_from_attr(tmp.as_ref(), device, &mut self.gpu.pwm) - } - } - } - - pub fn write_cpu_fan(&self, device: &mut Device) { - write_to_fan(&self.cpu, '1', device); - } - - pub fn write_gpu_fan(&self, device: &mut Device) { - write_to_fan(&self.gpu, '2', device); + pub(crate) fn write_gpu_fan(&mut self, device: &mut Device) -> std::io::Result<()> { + self.gpu.init_if_zeroed(device)?; + self.gpu.write_to_device(device, self.enabled) + } +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use super::*; + + #[test] + fn curve_data_from_str() { + let curve = + CurveData::from_str("30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%") + .unwrap(); + assert_eq!(curve.fan, FanCurvePU::CPU); + assert_eq!(curve.temp, [30, 49, 59, 69, 79, 89, 99, 109]); + assert_eq!(curve.pwm, [1, 2, 3, 4, 31, 49, 56, 58]); + } + + #[test] + fn curve_data_from_str_simple() { + let curve = CurveData::from_str("30:1,49:2,59:3,69:4,79:31,89:49,99:56,109:58").unwrap(); + assert_eq!(curve.fan, FanCurvePU::CPU); + assert_eq!(curve.temp, [30, 49, 59, 69, 79, 89, 99, 109]); + assert_eq!(curve.pwm, [1, 2, 3, 4, 31, 49, 56, 58]); + } + + #[test] + fn curve_data_from_str_invalid_pwm() { + let curve = + CurveData::from_str("30c:4%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%"); + assert!(&curve.is_err()); + assert!(matches!( + curve, + Err(ProfileError::ParseFanCurvePrevHigher(_, _, _)) + )); + } + + #[test] + fn check_pwm_str() { + assert_eq!(pwm_str('1', 0), "pwm1_auto_point1_pwm"); + assert_eq!(pwm_str('1', 4), "pwm1_auto_point5_pwm"); + assert_eq!(pwm_str('1', 7), "pwm1_auto_point8_pwm"); + } + + #[test] + fn check_temp_str() { + assert_eq!(temp_str('1', 0), "pwm1_auto_point1_temp"); + assert_eq!(temp_str('1', 4), "pwm1_auto_point5_temp"); + assert_eq!(temp_str('1', 7), "pwm1_auto_point8_temp"); } } diff --git a/rog-profiles/src/lib.rs b/rog-profiles/src/lib.rs index d276af80..657f5260 100644 --- a/rog-profiles/src/lib.rs +++ b/rog-profiles/src/lib.rs @@ -38,7 +38,7 @@ pub fn find_fan_curve_node() -> Result, ProfileError> { } #[cfg_attr(feature = "dbus", derive(Type))] -#[derive(Deserialize, Serialize, Debug, Clone, Copy)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy)] pub enum Profile { Balanced, Performance, @@ -51,10 +51,7 @@ impl Profile { } pub fn get_active_profile() -> Result { - let mut file = OpenOptions::new() - .read(true) - .open(&PLATFORM_PROFILE) - .unwrap_or_else(|_| panic!("{} not found", &PLATFORM_PROFILE)); + let mut file = OpenOptions::new().read(true).open(&PLATFORM_PROFILE)?; let mut buf = String::new(); file.read_to_string(&mut buf)?; @@ -62,10 +59,7 @@ impl Profile { } pub fn get_profile_names() -> Result, ProfileError> { - let mut file = OpenOptions::new() - .read(true) - .open(&PLATFORM_PROFILES) - .unwrap_or_else(|_| panic!("{} not found", &PLATFORM_PROFILES)); + let mut file = OpenOptions::new().read(true).open(&PLATFORM_PROFILES)?; let mut buf = String::new(); file.read_to_string(&mut buf)?; @@ -73,10 +67,7 @@ impl Profile { } pub fn set_profile(profile: Profile) -> Result<(), ProfileError> { - let mut file = OpenOptions::new() - .write(true) - .open(PLATFORM_PROFILE) - .unwrap_or_else(|_| panic!("{} not found", PLATFORM_PROFILE)); + let mut file = OpenOptions::new().write(true).open(PLATFORM_PROFILE)?; file.write_all(<&str>::from(profile).as_bytes())?; Ok(()) @@ -110,8 +101,21 @@ impl From<&str> for Profile { } } +impl std::str::FromStr for Profile { + type Err = ProfileError; + + fn from_str(profile: &str) -> Result { + match profile.to_ascii_lowercase().trim() { + "balanced" => Ok(Profile::Balanced), + "performance" => Ok(Profile::Performance), + "quiet" => Ok(Profile::Quiet), + _ => Err(ProfileError::ParseProfileName), + } + } +} + #[cfg_attr(feature = "dbus", derive(Type))] -#[derive(Deserialize, Serialize, Debug, Clone, Copy)] +#[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy)] pub enum FanCurvePU { CPU, GPU, @@ -126,27 +130,63 @@ impl From for &str { } } +impl std::str::FromStr for FanCurvePU { + type Err = ProfileError; + + fn from_str(fan: &str) -> Result { + match fan.to_ascii_lowercase().trim() { + "cpu" => Ok(FanCurvePU::CPU), + "gpu" => Ok(FanCurvePU::GPU), + _ => Err(ProfileError::ParseProfileName), + } + } +} + impl Default for FanCurvePU { fn default() -> Self { Self::CPU } } -/// Main purpose of `FanCurves` is to enable retoring state on system boot +/// Main purpose of `FanCurves` is to enable restoring state on system boot #[cfg_attr(feature = "dbus", derive(Type))] #[derive(Deserialize, Serialize, Debug, Default)] pub struct FanCurveProfiles { - enabled: Vec, balanced: FanCurveSet, performance: FanCurveSet, quiet: FanCurveSet, } impl FanCurveProfiles { + pub fn get_device() -> Result { + let mut enumerator = udev::Enumerator::new()?; + enumerator.match_subsystem("hwmon")?; + + for device in enumerator.scan_devices()? { + if device.parent_with_subsystem("platform")?.is_some() { + if let Some(name) = device.attribute_value("name") { + if name == "asus_custom_fan_curve" { + return Ok(device); + } + } + } + } + Err(ProfileError::NotSupported) + } + + pub fn is_supported() -> Result { + if Self::get_device().is_ok() { + return Ok(true); + } + + Ok(false) + } + /// pub fn read_from_dev_profile(&mut self, profile: Profile, device: &Device) { let mut tmp = FanCurveSet::default(); - tmp.read_from_device(device); + tmp.read_cpu_from_device(device); + tmp.read_gpu_from_device(device); match profile { Profile::Balanced => self.balanced = tmp, Profile::Performance => self.performance = tmp, @@ -154,22 +194,67 @@ impl FanCurveProfiles { } } - pub fn write_to_platform(&self, profile: Profile, device: &mut Device) { + /// Reset the stored (self) and device curve to the defaults of the platform. + /// + /// Each platform_profile has a different default and the defualt can be read + /// only for the currently active profile. + pub fn set_active_curve_to_defaults( + &mut self, + profile: Profile, + device: &mut Device, + ) -> std::io::Result<()> { + // Do reset + device.set_attribute_value("pwm1_enable", "3")?; + device.set_attribute_value("pwm2_enable", "3")?; + // Then read + let mut tmp = FanCurveSet::default(); + tmp.read_cpu_from_device(device); + tmp.read_gpu_from_device(device); + match profile { + Profile::Balanced => self.balanced = tmp, + Profile::Performance => self.performance = tmp, + Profile::Quiet => self.quiet = tmp, + } + Ok(()) + } + + /// Write the curves for the selected profile to the device. If the curve is + /// in the enabled list it will become active. + pub fn write_profile_curve_to_platform( + &mut self, + profile: Profile, + device: &mut Device, + ) -> std::io::Result<()> { let fans = match profile { - Profile::Balanced => &self.balanced, - Profile::Performance => &self.performance, - Profile::Quiet => &self.quiet, + Profile::Balanced => &mut self.balanced, + Profile::Performance => &mut self.performance, + Profile::Quiet => &mut self.quiet, }; - fans.write_cpu_fan(device); - fans.write_gpu_fan(device); + fans.write_cpu_fan(device)?; + fans.write_gpu_fan(device)?; + Ok(()) } - pub fn get_enabled_curve_profiles(&self) -> &[Profile] { - &self.enabled + pub fn get_enabled_curve_profiles(&self) -> Vec { + let mut tmp = Vec::new(); + if self.balanced.enabled { + tmp.push(Profile::Balanced); + } + if self.performance.enabled { + tmp.push(Profile::Performance); + } + if self.quiet.enabled { + tmp.push(Profile::Quiet); + } + tmp } - pub fn set_enabled_curve_profiles(&mut self, profiles: Vec) { - self.enabled = profiles + pub fn set_profile_curve_enabled(&mut self, profile: Profile, enabled: bool) { + match profile { + Profile::Balanced => self.balanced.enabled = enabled, + Profile::Performance => self.performance.enabled = enabled, + Profile::Quiet => self.quiet.enabled = enabled, + } } pub fn get_all_fan_curves(&self) -> Vec { @@ -180,11 +265,11 @@ impl FanCurveProfiles { ] } - pub fn get_active_fan_curves(&self) -> &FanCurveSet { - match Profile::get_active_profile().unwrap() { - Profile::Balanced => &self.balanced, - Profile::Performance => &self.performance, - Profile::Quiet => &self.quiet, + pub fn get_active_fan_curves(&self) -> Result<&FanCurveSet, ProfileError> { + match Profile::get_active_profile()? { + Profile::Balanced => Ok(&self.balanced), + Profile::Performance => Ok(&self.performance), + Profile::Quiet => Ok(&self.quiet), } } @@ -213,16 +298,7 @@ impl FanCurveProfiles { } } - pub fn write_and_set_fan_curve( - &mut self, - curve: CurveData, - profile: Profile, - device: &mut Device, - ) { - match curve.fan { - FanCurvePU::CPU => write_to_fan(&curve, '1', device), - FanCurvePU::GPU => write_to_fan(&curve, '2', device), - } + pub fn save_fan_curve(&mut self, curve: CurveData, profile: Profile) -> std::io::Result<()> { match profile { Profile::Balanced => match curve.fan { FanCurvePU::CPU => self.balanced.cpu = curve, @@ -237,33 +313,6 @@ impl FanCurveProfiles { FanCurvePU::GPU => self.quiet.gpu = curve, }, } - } -} - -pub fn write_to_fan(curve: &CurveData, pwm_num: char, device: &mut Device) { - let mut pwm = "pwmN_auto_pointN_pwm".to_string(); - - for (index, out) in curve.pwm.iter().enumerate() { - unsafe { - let buf = pwm.as_bytes_mut(); - buf[3] = pwm_num as u8; - // Should be quite safe to unwrap as we're not going over 8 - buf[15] = char::from_digit(index as u32 + 1, 10).unwrap() as u8; - } - let out = out.to_string(); - device.set_attribute_value(&pwm, &out).unwrap(); - } - - let mut pwm = "pwmN_auto_pointN_temp".to_string(); - - for (index, out) in curve.temp.iter().enumerate() { - unsafe { - let buf = pwm.as_bytes_mut(); - buf[3] = pwm_num as u8; - // Should be quite safe to unwrap as we're not going over 8 - buf[15] = char::from_digit(index as u32 + 1, 10).unwrap() as u8; - } - let out = out.to_string(); - device.set_attribute_value(&pwm, &out).unwrap(); + Ok(()) } }