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
This commit is contained in:
Luke D. Jones
2021-09-13 11:46:22 +12:00
parent 7041d77256
commit ab195e1d84
22 changed files with 783 additions and 328 deletions

View File

@@ -6,6 +6,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [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 # [4.0.1] - 2021-09-11
### Changed ### Changed
- Fix asusd-ledmodes.toml - Fix asusd-ledmodes.toml

9
Cargo.lock generated
View File

@@ -45,7 +45,7 @@ dependencies = [
[[package]] [[package]]
name = "asusctl" name = "asusctl"
version = "4.0.0" version = "4.0.2"
dependencies = [ dependencies = [
"daemon", "daemon",
"gif", "gif",
@@ -59,6 +59,7 @@ dependencies = [
"supergfxctl", "supergfxctl",
"sysfs-class", "sysfs-class",
"tinybmp", "tinybmp",
"toml",
"zbus", "zbus",
] ]
@@ -209,7 +210,7 @@ dependencies = [
[[package]] [[package]]
name = "daemon" name = "daemon"
version = "4.0.1" version = "4.0.2"
dependencies = [ dependencies = [
"env_logger", "env_logger",
"log", "log",
@@ -920,7 +921,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_dbus" name = "rog_dbus"
version = "4.0.0" version = "4.0.2"
dependencies = [ dependencies = [
"rog_anime", "rog_anime",
"rog_aura", "rog_aura",
@@ -934,7 +935,7 @@ dependencies = [
[[package]] [[package]]
name = "rog_profiles" name = "rog_profiles"
version = "1.0.1" version = "1.1.2"
dependencies = [ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",

View File

@@ -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`. 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` **fedora:** `dnf install clang-devel systemd-devel`

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "asusctl" name = "asusctl"
version = "4.0.0" version = "4.0.2"
authors = ["Luke D Jones <luke@ljones.dev>"] authors = ["Luke D Jones <luke@ljones.dev>"]
edition = "2018" edition = "2018"
@@ -16,6 +16,7 @@ rog_supported = { path = "../rog-supported" }
daemon = { path = "../daemon" } daemon = { path = "../daemon" }
gumdrop = "^0.8" gumdrop = "^0.8"
supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", tag = "2.0.0" } supergfxctl = { git = "https://gitlab.com/asus-linux/supergfxctl.git", tag = "2.0.0" }
toml = "^0.5.8"
sysfs-class = "^0.1.2" sysfs-class = "^0.1.2"

View File

@@ -1,7 +1,7 @@
use crate::{ use crate::{
anime_cli::AnimeCommand, anime_cli::AnimeCommand,
aura_cli::{LedBrightness, SetAuraBuiltin}, aura_cli::{LedBrightness, SetAuraBuiltin},
profiles_cli::ProfileCommand, profiles_cli::{FanCurveCommand, ProfileCommand},
}; };
use gumdrop::Options; use gumdrop::Options;
use supergfxctl::gfx_vendors::GfxVendors; use supergfxctl::gfx_vendors::GfxVendors;
@@ -30,8 +30,10 @@ pub struct CliStart {
pub enum CliCommand { pub enum CliCommand {
#[options(help = "Set the keyboard lighting from built-in modes")] #[options(help = "Set the keyboard lighting from built-in modes")]
LedMode(LedModeCommand), LedMode(LedModeCommand),
#[options(help = "Create and configure profiles")] #[options(help = "Set or select platform_profile")]
Profile(ProfileCommand), Profile(ProfileCommand),
#[options(help = "Set, select, or modify fan curves if suported")]
FanCurve(FanCurveCommand),
#[options(help = "Set the graphics mode")] #[options(help = "Set the graphics mode")]
Graphics(GraphicsCommand), Graphics(GraphicsCommand),
#[options(name = "anime", help = "Manage AniMe Matrix")] #[options(name = "anime", help = "Manage AniMe Matrix")]

View File

@@ -7,7 +7,7 @@ use crate::aura_cli::{LedBrightness, SetAuraBuiltin};
use crate::cli_opts::*; use crate::cli_opts::*;
use anime_cli::{AnimeActions, AnimeCommand}; use anime_cli::{AnimeActions, AnimeCommand};
use gumdrop::{Opt, Options}; use gumdrop::{Opt, Options};
use profiles_cli::ProfileCommand; use profiles_cli::{FanCurveCommand, ProfileCommand};
use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2, ANIME_DATA_LEN}; use rog_anime::{AnimeDataBuffer, AnimeImage, Vec2, ANIME_DATA_LEN};
use rog_aura::{self, AuraEffect}; use rog_aura::{self, AuraEffect};
use rog_dbus::RogDbusClient; use rog_dbus::RogDbusClient;
@@ -16,6 +16,7 @@ use rog_supported::{
AnimeSupportedFunctions, LedSupportedFunctions, PlatformProfileFunctions, AnimeSupportedFunctions, LedSupportedFunctions, PlatformProfileFunctions,
RogBiosSupportedFunctions, RogBiosSupportedFunctions,
}; };
use std::process::Command;
use std::{env::args, path::Path, sync::mpsc::channel}; use std::{env::args, path::Path, sync::mpsc::channel};
use supergfxctl::{ use supergfxctl::{
gfx_vendors::GfxRequiredUserAction, gfx_vendors::GfxRequiredUserAction,
@@ -24,8 +25,6 @@ use supergfxctl::{
}; };
use zbus::Connection; 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"; const CONFIG_ADVICE: &str = "A config file need to be removed so a new one can be generated";
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -49,50 +48,48 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
let (dbus, _) = RogDbusClient::new().map_err(|e| { let (dbus, _) = RogDbusClient::new()
println!("\nIs asusd running?\n"); .map_err(|e| {
println!("{}", PLEASE); print_error_help(Box::new(e), None);
println!("{}\n", CONFIG_ADVICE); std::process::exit(3);
e })
})?; .unwrap();
let supported = dbus let supported = dbus
.proxies() .proxies()
.supported() .supported()
.get_supported_functions() .get_supported_functions()
.map_err(|e| { .map_err(|e| {
println!("\nIs asusd running?\n"); print_error_help(Box::new(e), None);
println!("{}", PLEASE); std::process::exit(4);
println!("{}\n", CONFIG_ADVICE); })
e .unwrap();
})?;
if parsed.version { if parsed.version {
print_versions(); print_versions();
println!(); println!();
print_laptop_info(); print_laptop_info();
println!("{}\n", PLEASE);
return Ok(()); return Ok(());
} }
if let Err(err) = do_parsed(&parsed, &supported, &dbus) { if let Err(err) = do_parsed(&parsed, &supported, &dbus) {
print_error_help(err, &supported); print_error_help(err, Some(&supported));
} }
Ok(()) Ok(())
} }
fn print_error_help(err: Box<dyn std::error::Error>, supported: &SupportedFunctions) { fn print_error_help(err: Box<dyn std::error::Error>, supported: Option<&SupportedFunctions>) {
println!("Error: {}\n", err); if do_diagnose("asusd") {
print_versions(); println!("\nError: {}\n", err);
println!(); print_versions();
print_laptop_info(); println!();
println!(); print_laptop_info();
println!("Supported laptop functions:\n\n{}", supported); if let Some(supported) = supported {
println!(); println!();
println!("{}", PLEASE); println!("Supported laptop functions:\n\n{}", supported);
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_versions() { fn print_versions() {
@@ -117,6 +114,30 @@ fn print_laptop_info() {
println!("Board name: {}", board_name.trim()); 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( fn do_parsed(
parsed: &CliStart, parsed: &CliStart,
supported: &SupportedFunctions, supported: &SupportedFunctions,
@@ -125,7 +146,13 @@ fn do_parsed(
match &parsed.command { match &parsed.command {
Some(CliCommand::LedMode(mode)) => handle_led_mode(dbus, &supported.keyboard_led, mode)?, 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::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::Anime(cmd)) => handle_anime(dbus, &supported.anime_ctrl, cmd)?,
Some(CliCommand::Bios(cmd)) => handle_bios_option(dbus, &supported.rog_bios_ctrl, cmd)?, Some(CliCommand::Bios(cmd)) => handle_bios_option(dbus, &supported.rog_bios_ctrl, cmd)?,
None => { None => {
@@ -177,6 +204,12 @@ fn do_parsed(
Ok(()) Ok(())
} }
fn do_gfx_diagnose() {
println!("\nGraphics mode change error.");
do_diagnose("supergfxd");
println!();
}
fn do_gfx(command: &GraphicsCommand) -> Result<(), Box<dyn std::error::Error>> { fn do_gfx(command: &GraphicsCommand) -> Result<(), Box<dyn std::error::Error>> {
if command.mode.is_none() && !command.get && !command.pow && !command.force || command.help { if command.mode.is_none() && !command.get && !command.pow && !command.force || command.help {
println!("{}", command.self_usage()); println!("{}", command.self_usage());
@@ -194,13 +227,11 @@ fn do_gfx(command: &GraphicsCommand) -> Result<(), Box<dyn std::error::Error>> {
std::process::exit(-1); 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|{ proxy.gfx_write_mode(&mode)?;
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
})?;
loop { loop {
proxy.next_signal()?; proxy.next_signal()?;
@@ -372,25 +403,16 @@ fn handle_led_mode(
fn handle_profile( fn handle_profile(
dbus: &RogDbusClient, dbus: &RogDbusClient,
supported: &PlatformProfileFunctions, _supported: &PlatformProfileFunctions,
cmd: &ProfileCommand, cmd: &ProfileCommand,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
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"); println!("Warning: Fan-curve support is coming in a 4.1.x release");
if !cmd.next && !cmd.list { if !cmd.next && !cmd.list {
if !cmd.help { if !cmd.help {
println!("Missing arg or command\n"); println!("Missing arg or command\n");
} }
let usage: Vec<String> = ProfileCommand::usage() println!("{}", ProfileCommand::usage());
.lines()
.map(|s| s.to_string())
.collect();
for line in usage
.iter()
.filter(|line| !line.contains("--curve") || supported.fan_curves)
{
println!("{}", line);
}
if let Some(lst) = cmd.self_command_list() { if let Some(lst) = cmd.self_command_list() {
println!("\n{}", lst); println!("\n{}", lst);
@@ -404,6 +426,7 @@ fn handle_profile(
if cmd.next { if cmd.next {
dbus.proxies().profile().next_profile()?; dbus.proxies().profile().next_profile()?;
} }
if cmd.list { if cmd.list {
let res = dbus.proxies().profile().profiles()?; let res = dbus.proxies().profile().profiles()?;
res.iter().for_each(|p| println!("{:?}", p)); res.iter().for_each(|p| println!("{:?}", p));
@@ -411,6 +434,61 @@ fn handle_profile(
Ok(()) Ok(())
} }
fn handle_fan_curve(
dbus: &RogDbusClient,
_supported: &PlatformProfileFunctions,
cmd: &FanCurveCommand,
) -> Result<(), Box<dyn std::error::Error>> {
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( fn handle_bios_option(
dbus: &RogDbusClient, dbus: &RogDbusClient,
supported: &RogBiosSupportedFunctions, supported: &RogBiosSupportedFunctions,
@@ -464,3 +542,27 @@ fn handle_bios_option(
} }
Ok(()) 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
}

View File

@@ -1,4 +1,5 @@
use gumdrop::Options; use gumdrop::Options;
use rog_profiles::{fan_curve_set::CurveData, FanCurvePU, Profile};
#[derive(Debug, Clone, Options)] #[derive(Debug, Clone, Options)]
pub struct ProfileCommand { pub struct ProfileCommand {
@@ -8,4 +9,42 @@ pub struct ProfileCommand {
pub next: bool, pub next: bool,
#[options(help = "list available profiles")] #[options(help = "list available profiles")]
pub list: bool, pub list: bool,
#[options(help = "get profile")]
pub profile_get: bool,
#[options(help = "set the active profile")]
pub profile_set: Option<Profile>,
}
#[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<Profile>,
#[options(
meta = "",
help = "enable or disable <true/false> fan curve. `mod-profile` required"
)]
pub enabled: Option<bool>,
#[options(
meta = "",
help = "select fan <cpu/gpu> to modify. `mod-profile` required"
)]
pub fan: Option<FanCurvePU>,
#[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<CurveData>,
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "daemon" name = "daemon"
version = "4.0.1" version = "4.0.2"
license = "MPL-2.0" license = "MPL-2.0"
readme = "README.md" readme = "README.md"
authors = ["Luke <luke@ljones.dev>"] authors = ["Luke <luke@ljones.dev>"]

View File

@@ -34,8 +34,18 @@ impl Config {
} else if let Ok(data) = serde_json::from_str(&buf) { } else if let Ok(data) = serde_json::from_str(&buf) {
config = data; config = data;
} else { } else {
warn!("Could not deserialise {}", CONFIG_PATH); warn!(
panic!("Please remove {} then restart asusd", CONFIG_PATH); "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 { } else {
config = Self::new() config = Self::new()

View File

@@ -166,11 +166,17 @@ impl AnimeConfig {
info!("Updated config version to: {}", VERSION); info!("Updated config version to: {}", VERSION);
return config; return config;
} }
AnimeConfig::write_backup(buf);
warn!( warn!(
"Could not deserialise {}. Backed up as *-old", "Could not deserialise {}.\nWill rename to {}-old and recreate config",
ANIME_CONFIG_PATH 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) AnimeConfig::create_default(&mut file)
@@ -246,12 +252,4 @@ impl AnimeConfig {
file.write_all(json.as_bytes()) file.write_all(json.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err)); .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));
}
} }

View File

@@ -5,6 +5,7 @@ use ::zbus::Connection;
use log::{error, info, warn}; use log::{error, info, warn};
use logind_zbus::ManagerProxy; use logind_zbus::ManagerProxy;
use rog_anime::{ use rog_anime::{
error::AnimeError,
usb::{ usb::{
pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID, pkt_for_apply, pkt_for_flush, pkt_for_set_boot, pkt_for_set_on, pkts_for_init, PROD_ID,
VENDOR_ID, VENDOR_ID,
@@ -181,15 +182,20 @@ impl CtrlAnime {
for action in actions.iter() { for action in actions.iter() {
match action { match action {
ActionData::Animation(frames) => { ActionData::Animation(frames) => {
if rog_anime::run_animation(frames, thread_exit.clone(), &|frame| { if let Err(err) = rog_anime::run_animation(
if let Ok(lock) = inner.try_lock() { frames,
lock.write_data_buffer(frame); thread_exit.clone(),
} &|frame| {
Ok(()) inner
}) .try_lock()
.map_err(|err| warn!("rog_anime::run_animation: {}", err)) .map(|lock| lock.write_data_buffer(frame))
.is_err() .map_err(|err| {
{ warn!("rog_anime::run_animation: {}", err);
AnimeError::NoFrames
})
},
) {
warn!("rog_anime::run_animation: {}", err);
break 'main; break 'main;
}; };

View File

@@ -105,8 +105,17 @@ impl AuraConfig {
info!("Updated AuraConfig version"); info!("Updated AuraConfig version");
return config; return config;
} }
warn!("Could not deserialise {}", AURA_CONFIG_PATH); warn!(
panic!("Please remove {} then restart asusd", AURA_CONFIG_PATH); "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) AuraConfig::create_default(&mut file, supported_led_modes)

View File

@@ -1,5 +1,4 @@
use log::{error, warn}; use log::{error, warn};
use rog_profiles::fan_curve_set::FanCurveSet;
use rog_profiles::{FanCurveProfiles, Profile}; use rog_profiles::{FanCurveProfiles, Profile};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use std::fs::{File, OpenOptions}; use std::fs::{File, OpenOptions};
@@ -17,20 +16,22 @@ pub struct ProfileConfig {
impl ProfileConfig { impl ProfileConfig {
fn new(config_path: String) -> Self { fn new(config_path: String) -> Self {
let mut platform = ProfileConfig { Self {
config_path, config_path,
active_profile: Profile::get_active_profile().unwrap_or(Profile::Balanced), active_profile: Profile::Balanced,
fan_curves: None, 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 { if res {
let curves = FanCurveProfiles::default(); let curves = FanCurveProfiles::default();
platform.fan_curves = Some(curves); self.fan_curves = Some(curves);
} }
} }
self.write();
platform
} }
pub fn load(config_path: String) -> Self { 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 let Ok(read_len) = file.read_to_string(&mut buf) {
if read_len == 0 { if read_len == 0 {
config = Self::new(config_path); config = Self::new(config_path);
config.set_defaults_and_save();
} else if let Ok(data) = toml::from_str(&buf) { } else if let Ok(data) = toml::from_str(&buf) {
config = data; config = data;
config.config_path = config_path; config.config_path = config_path;
} else { } else {
warn!("Could not deserialise {}", config_path); warn!(
panic!("Please remove {} then restart service", config_path); "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 { } else {
config = Self::new(config_path) config = Self::new(config_path);
config.set_defaults_and_save();
} }
config.write();
config config
} }

View File

@@ -1,17 +1,16 @@
use std::sync::{Arc, Mutex};
use crate::error::RogError; use crate::error::RogError;
use crate::GetSupported; use crate::{CtrlTask, GetSupported};
use log::{info, warn}; use log::{info, warn};
use rog_profiles::error::ProfileError; use rog_profiles::error::ProfileError;
use rog_profiles::fan_curve_set::FanCurveSet; use rog_profiles::{FanCurveProfiles, Profile};
use rog_profiles::Profile;
use rog_supported::PlatformProfileFunctions; use rog_supported::PlatformProfileFunctions;
use udev::Device;
use super::config::ProfileConfig; use super::config::ProfileConfig;
pub struct CtrlPlatformProfile { pub struct CtrlPlatformProfile {
pub config: ProfileConfig, pub config: ProfileConfig,
pub fan_device: Option<Device>,
} }
impl GetSupported for CtrlPlatformProfile { 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(); let mut fan_curve_supported = res.is_err();
if let Ok(r) = res { if let Ok(r) = res {
fan_curve_supported = r; 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 { impl crate::Reloadable for CtrlPlatformProfile {
/// Fetch the active profile and use that to set all related components up /// Fetch the active profile and use that to set all related components up
fn reload(&mut self) -> Result<(), RogError> { fn reload(&mut self) -> Result<(), RogError> {
if let Some(curves) = &self.config.fan_curves { if let Some(curves) = &mut self.config.fan_curves {
if let Ok(mut device) = FanCurveSet::get_device() { if let Ok(mut device) = FanCurveProfiles::get_device() {
curves.write_to_platform(self.config.active_profile, &mut device); curves.write_profile_curve_to_platform(self.config.active_profile, &mut device)?;
} }
} }
Ok(()) Ok(())
@@ -65,30 +64,24 @@ impl crate::Reloadable for CtrlPlatformProfile {
} }
impl CtrlPlatformProfile { impl CtrlPlatformProfile {
pub fn new(mut config: ProfileConfig, fan_device: Option<Device>) -> Result<Self, RogError> { pub fn new(mut config: ProfileConfig) -> Result<Self, RogError> {
if Profile::is_platform_profile_supported() { if Profile::is_platform_profile_supported() {
info!("Device has profile control available"); 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; let profile = config.active_profile;
config if let Some(curve) = config.fan_curves.as_mut() {
.fan_curves curve.read_from_dev_profile(profile, device);
.as_mut() }
.unwrap()
.read_from_dev_profile(profile, device);
} }
config.write(); config.write();
return Ok(CtrlPlatformProfile { config, fan_device }); return Ok(CtrlPlatformProfile { config });
} }
Err(ProfileError::NotSupported.into()) Err(ProfileError::NotSupported.into())
} }
pub fn get_device(&self) -> Option<Device> {
self.fan_device.clone()
}
pub fn save_config(&self) { pub fn save_config(&self) {
self.config.write(); 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 /// 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> { pub(super) fn set_next_profile(&mut self) -> Result<(), RogError> {
// Read first just incase the user has modified the config before calling this // Read first just incase the user has modified the config before calling this
self.config.read();
match self.config.active_profile { match self.config.active_profile {
Profile::Balanced => { Profile::Balanced => {
Profile::set_profile(Profile::Performance)?; Profile::set_profile(Profile::Performance)?;
@@ -112,9 +103,49 @@ impl CtrlPlatformProfile {
self.config.active_profile = Profile::Balanced; 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<Mutex<CtrlPlatformProfile>>,
}
impl CtrlProfileTask {
pub fn new(ctrl: Arc<Mutex<CtrlPlatformProfile>>) -> 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(()) Ok(())
} }
} }

View File

@@ -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) { fn next_profile(&mut self) {
if let Ok(mut ctrl) = self.inner.try_lock() { if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.set_next_profile() ctrl.set_next_profile()
.unwrap_or_else(|err| warn!("{}", err)); .unwrap_or_else(|err| warn!("{}", err));
ctrl.save_config();
} }
self.do_notification(); self.do_notification();
} }
@@ -61,9 +63,12 @@ impl ProfileZbus {
// Read first just incase the user has modified the config before calling this // Read first just incase the user has modified the config before calling this
ctrl.config.read(); ctrl.config.read();
Profile::set_profile(profile) Profile::set_profile(profile)
.map_err(|e| warn!("Profile::set_profile, {}", e)) .map_err(|e| warn!("set_profile, {}", e))
.ok(); .ok();
ctrl.config.active_profile = profile; 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(); ctrl.save_config();
} }
@@ -84,14 +89,23 @@ impl ProfileZbus {
)) ))
} }
/// Get a list of profiles that have fan-curves enabled. /// Set a profile fan curve enabled status. Will also activate a fan curve if in the
fn set_enabled_fan_profiles(&mut self, profiles: Vec<Profile>) -> zbus::fdo::Result<()> { /// 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() { if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read(); ctrl.config.read();
if let Some(curves) = &mut ctrl.config.fan_curves { 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( Err(Error::Failed(
"Failed to get enabled fan curve names".to_string(), "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 /// Get the fan-curve data for the currently active Profile
fn active_fan_curve_data(&mut self) -> zbus::fdo::Result<FanCurveSet> { fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result<FanCurveSet> {
if let Ok(mut ctrl) = self.inner.try_lock() { if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read(); ctrl.config.read();
if let Some(curves) = &ctrl.config.fan_curves { 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())); return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
} }
Err(Error::Failed("Failed to get fan curve data".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 /// Set the fan curve for the specified profile.
fn fan_curves(&self) -> zbus::fdo::Result<Vec<FanCurveSet>> { /// 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() { if let Ok(mut ctrl) = self.inner.try_lock() {
ctrl.config.read(); ctrl.config.read();
if let Some(curves) = &ctrl.config.fan_curves { if let Some(curves) = &mut ctrl.config.fan_curves {
return Ok(curves.get_all_fan_curves()); curves
} .save_fan_curve(curve, profile)
return Err(Error::Failed(UNSUPPORTED_MSG.to_string())); .map_err(|err| zbus::fdo::Error::Failed(err.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);
}
} else { } else {
return Err(Error::Failed(UNSUPPORTED_MSG.to_string())); 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(); 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)] #[dbus_interface(signal)]

View File

@@ -7,6 +7,7 @@ use daemon::ctrl_aura::controller::{
}; };
use daemon::ctrl_charge::CtrlCharge; use daemon::ctrl_charge::CtrlCharge;
use daemon::ctrl_profiles::config::ProfileConfig; use daemon::ctrl_profiles::config::ProfileConfig;
use daemon::ctrl_profiles::controller::CtrlProfileTask;
use daemon::{ use daemon::{
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported, config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
}; };
@@ -20,7 +21,7 @@ use daemon::{CtrlTask, Reloadable, ZbusAdd};
use log::LevelFilter; use log::LevelFilter;
use log::{error, info, warn}; use log::{error, info, warn};
use rog_dbus::DBUS_NAME; use rog_dbus::DBUS_NAME;
use rog_profiles::fan_curve_set::FanCurveSet; use rog_profiles::Profile;
use std::env; use std::env;
use std::error::Error; use std::error::Error;
use std::io::Write; use std::io::Write;
@@ -108,23 +109,24 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
} }
} }
let fan_device = if let Ok(res) = FanCurveSet::get_device() { if Profile::is_platform_profile_supported() {
Some(res) let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into());
} else { match CtrlPlatformProfile::new(profile_config) {
None Ok(mut ctrl) => {
}; ctrl.reload()
let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into()); .unwrap_or_else(|err| warn!("Profile control: {}", err));
match CtrlPlatformProfile::new(profile_config, fan_device) {
Ok(mut ctrl) => {
ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err));
let tmp = Arc::new(Mutex::new(ctrl)); let tmp = Arc::new(Mutex::new(ctrl));
ProfileZbus::new(tmp).add_to_server(&mut object_server); ProfileZbus::new(tmp.clone()).add_to_server(&mut object_server);
}
Err(err) => { tasks.push(Box::new(CtrlProfileTask::new(tmp)));
error!("Profile control: {}", err); }
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()) { match CtrlAnime::new(AnimeConfig::load()) {

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "rog_dbus" name = "rog_dbus"
version = "4.0.0" version = "4.0.2"
license = "MPL-2.0" license = "MPL-2.0"
readme = "README.md" readme = "README.md"
authors = ["Luke <luke@ljones.dev>"] authors = ["Luke <luke@ljones.dev>"]

View File

@@ -21,7 +21,10 @@
use std::sync::mpsc::Sender; 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}; use zbus::{dbus_proxy, Connection, Result};
#[dbus_proxy( #[dbus_proxy(
@@ -29,29 +32,37 @@ use zbus::{dbus_proxy, Connection, Result};
default_path = "/org/asuslinux/Profile" default_path = "/org/asuslinux/Profile"
)] )]
trait Daemon { trait Daemon {
/// Get the active `Profile` data /// Get the fan-curve data for the currently active Profile
fn active_fan_curve_data(&self) -> zbus::Result<FanCurveSet>; fn fan_curve_data(&self, profile: Profile) -> zbus::Result<FanCurveSet>;
/// Profile, get the active profile /// Fetch the active profile name
fn active_profile(&self) -> zbus::Result<Profile>; fn active_profile(&self) -> zbus::Result<Profile>;
/// Get enabled fan curves /// Get a list of profiles that have fan-curves enabled.
fn enabled_fan_profiles(&self) -> zbus::Result<Vec<Profile>>; fn enabled_fan_profiles(&self) -> zbus::Result<Vec<Profile>>;
/// Get all fan curve data /// Toggle to next platform_profile. Names provided by `Profiles`.
fn fan_curves(&self) -> zbus::Result<Vec<FanCurveSet>>; /// If fan-curves are supported will also activate a fan curve for profile.
/// NextProfile method
fn next_profile(&self) -> zbus::Result<()>; fn next_profile(&self) -> zbus::Result<()>;
/// Profiles method /// Fetch profile names
fn profiles(&self) -> zbus::Result<Vec<Profile>>; fn profiles(&self) -> zbus::Result<Vec<Profile>>;
/// Set the specific profile as active /// Set this platform_profile name as active
fn set_active_profile(&self, profile: Profile) -> zbus::Result<()>; 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 /// Set a profile fan curve enabled status. Will also activate a fan curve.
fn set_fan_curve(&self, curve: FanCurveSet) -> zbus::Result<()>; 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 /// NotifyProfile signal
#[dbus_proxy(signal)] #[dbus_proxy(signal)]
@@ -72,8 +83,18 @@ impl<'a> ProfileProxy<'a> {
} }
#[inline] #[inline]
pub fn profiles(&self) -> Result<Vec<Profile>> { pub fn active_profile(&self) -> zbus::Result<Profile> {
self.0.profiles() self.0.active_profile()
}
#[inline]
pub fn enabled_fan_profiles(&self) -> zbus::Result<Vec<Profile>> {
self.0.enabled_fan_profiles()
}
#[inline]
pub fn fan_curve_data(&self, profile: Profile) -> zbus::Result<FanCurveSet> {
self.0.fan_curve_data(profile)
} }
#[inline] #[inline]
@@ -81,6 +102,31 @@ impl<'a> ProfileProxy<'a> {
self.0.next_profile() self.0.next_profile()
} }
#[inline]
pub fn profiles(&self) -> Result<Vec<Profile>> {
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] #[inline]
pub fn connect_notify_profile(&self, send: Sender<Profile>) -> zbus::fdo::Result<()> { pub fn connect_notify_profile(&self, send: Sender<Profile>) -> zbus::fdo::Result<()> {
self.0.connect_notify_profile(move |data| { self.0.connect_notify_profile(move |data| {

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "rog_profiles" name = "rog_profiles"
version = "1.0.1" version = "1.1.2"
authors = ["Luke D. Jones <luke@ljones.dev>"] authors = ["Luke D. Jones <luke@ljones.dev>"]
edition = "2018" edition = "2018"

View File

@@ -8,7 +8,11 @@ pub enum ProfileError {
NotSupported, NotSupported,
NotFound(String), NotFound(String),
Io(std::io::Error), 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 { impl fmt::Display for ProfileError {
@@ -21,7 +25,16 @@ impl fmt::Display for ProfileError {
ProfileError::NotSupported => write!(f, "Not supported"), ProfileError::NotSupported => write!(f, "Not supported"),
ProfileError::NotFound(deets) => write!(f, "Not found: {}", deets), ProfileError::NotFound(deets) => write!(f, "Not found: {}", deets),
ProfileError::Io(detail) => write!(f, "std::io error: {}", detail), 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),
} }
} }
} }

View File

@@ -4,24 +4,24 @@ use udev::Device;
#[cfg(feature = "dbus")] #[cfg(feature = "dbus")]
use zvariant_derive::Type; 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(); let mut buf = "pwm1_auto_point1_pwm".to_string();
unsafe { unsafe {
let tmp = buf.as_bytes_mut(); let tmp = buf.as_bytes_mut();
tmp[3] = fan as u8; tmp[3] = fan as u8;
tmp[15] = index as u8; tmp[15] = char::from_digit(index as u32 + 1, 10).unwrap() as u8;
} }
buf 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(); let mut buf = "pwm1_auto_point1_temp".to_string();
unsafe { unsafe {
let tmp = buf.as_bytes_mut(); let tmp = buf.as_bytes_mut();
tmp[3] = fan as u8; tmp[3] = fan as u8;
tmp[15] = index as u8; tmp[15] = char::from_digit(index as u32 + 1, 10).unwrap() as u8;
} }
buf buf
} }
@@ -29,22 +29,135 @@ pub fn temp_str(fan: char, index: char) -> String {
#[cfg_attr(feature = "dbus", derive(Type))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Default, Debug, Clone)] #[derive(Deserialize, Serialize, Default, Debug, Clone)]
pub struct CurveData { pub struct CurveData {
pub fan: FanCurvePU, pub(crate) fan: FanCurvePU,
pub pwm: [u8; 8], pub(crate) pwm: [u8; 8],
pub temp: [u8; 8], pub(crate) temp: [u8; 8],
}
impl std::str::FromStr for CurveData {
type Err = ProfileError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
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::<u8>().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 /// A `FanCurveSet` contains both CPU and GPU fan curve data
#[cfg_attr(feature = "dbus", derive(Type))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FanCurveSet { pub struct FanCurveSet {
pub cpu: CurveData, pub(crate) enabled: bool,
pub gpu: CurveData, pub(crate) cpu: CurveData,
pub(crate) gpu: CurveData,
} }
impl Default for FanCurveSet { impl Default for FanCurveSet {
fn default() -> Self { fn default() -> Self {
Self { Self {
enabled: false,
cpu: CurveData { cpu: CurveData {
fan: FanCurvePU::CPU, fan: FanCurvePU::CPU,
pwm: [0u8; 8], pwm: [0u8; 8],
@@ -60,80 +173,71 @@ impl Default for FanCurveSet {
} }
impl FanCurveSet { impl FanCurveSet {
pub fn get_device() -> Result<Device, ProfileError> { pub(crate) fn read_cpu_from_device(&mut self, device: &Device) {
let mut enumerator = udev::Enumerator::new()?; self.cpu.read_from_device(device);
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 fn is_supported() -> Result<bool, ProfileError> { pub(crate) fn read_gpu_from_device(&mut self, device: &Device) {
if Self::get_device().is_ok() { self.gpu.read_from_device(device);
return Ok(true);
}
Ok(false)
} }
pub fn new() -> Result<(Self, Device), ProfileError> { pub(crate) fn write_cpu_fan(&mut self, device: &mut Device) -> std::io::Result<()> {
if let Ok(device) = Self::get_device() { self.cpu.init_if_zeroed(device)?;
let mut fans = Self { self.cpu.write_to_device(device, self.enabled)
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)
} }
fn set_val_from_attr(tmp: &str, device: &Device, buf: &mut [u8; 8]) { pub(crate) fn write_gpu_fan(&mut self, device: &mut Device) -> std::io::Result<()> {
if let Some(n) = tmp.chars().nth(15) { self.gpu.init_if_zeroed(device)?;
let i = n.to_digit(10).unwrap() as usize; self.gpu.write_to_device(device, self.enabled)
let d = device.attribute_value(tmp).unwrap(); }
let d: u8 = d.to_string_lossy().parse().unwrap(); }
buf[i - 1] = d;
} #[cfg(test)]
} mod tests {
use std::str::FromStr;
pub fn read_from_device(&mut self, device: &Device) {
for attr in device.attributes() { use super::*;
let tmp = attr.name().to_string_lossy();
if tmp.starts_with("pwm1") && tmp.ends_with("_temp") { #[test]
Self::set_val_from_attr(tmp.as_ref(), device, &mut self.cpu.temp) fn curve_data_from_str() {
} let curve =
if tmp.starts_with("pwm1") && tmp.ends_with("_pwm") { CurveData::from_str("30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%")
Self::set_val_from_attr(tmp.as_ref(), device, &mut self.cpu.pwm) .unwrap();
} assert_eq!(curve.fan, FanCurvePU::CPU);
if tmp.starts_with("pwm2") && tmp.ends_with("_temp") { assert_eq!(curve.temp, [30, 49, 59, 69, 79, 89, 99, 109]);
Self::set_val_from_attr(tmp.as_ref(), device, &mut self.gpu.temp) assert_eq!(curve.pwm, [1, 2, 3, 4, 31, 49, 56, 58]);
} }
if tmp.starts_with("pwm2") && tmp.ends_with("_pwm") {
Self::set_val_from_attr(tmp.as_ref(), device, &mut self.gpu.pwm) #[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]);
pub fn write_cpu_fan(&self, device: &mut Device) { assert_eq!(curve.pwm, [1, 2, 3, 4, 31, 49, 56, 58]);
write_to_fan(&self.cpu, '1', device); }
}
#[test]
pub fn write_gpu_fan(&self, device: &mut Device) { fn curve_data_from_str_invalid_pwm() {
write_to_fan(&self.gpu, '2', device); 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");
} }
} }

View File

@@ -38,7 +38,7 @@ pub fn find_fan_curve_node() -> Result<Option<Device>, ProfileError> {
} }
#[cfg_attr(feature = "dbus", derive(Type))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, Clone, Copy)] #[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy)]
pub enum Profile { pub enum Profile {
Balanced, Balanced,
Performance, Performance,
@@ -51,10 +51,7 @@ impl Profile {
} }
pub fn get_active_profile() -> Result<Profile, ProfileError> { pub fn get_active_profile() -> Result<Profile, ProfileError> {
let mut file = OpenOptions::new() let mut file = OpenOptions::new().read(true).open(&PLATFORM_PROFILE)?;
.read(true)
.open(&PLATFORM_PROFILE)
.unwrap_or_else(|_| panic!("{} not found", &PLATFORM_PROFILE));
let mut buf = String::new(); let mut buf = String::new();
file.read_to_string(&mut buf)?; file.read_to_string(&mut buf)?;
@@ -62,10 +59,7 @@ impl Profile {
} }
pub fn get_profile_names() -> Result<Vec<Profile>, ProfileError> { pub fn get_profile_names() -> Result<Vec<Profile>, ProfileError> {
let mut file = OpenOptions::new() let mut file = OpenOptions::new().read(true).open(&PLATFORM_PROFILES)?;
.read(true)
.open(&PLATFORM_PROFILES)
.unwrap_or_else(|_| panic!("{} not found", &PLATFORM_PROFILES));
let mut buf = String::new(); let mut buf = String::new();
file.read_to_string(&mut buf)?; file.read_to_string(&mut buf)?;
@@ -73,10 +67,7 @@ impl Profile {
} }
pub fn set_profile(profile: Profile) -> Result<(), ProfileError> { pub fn set_profile(profile: Profile) -> Result<(), ProfileError> {
let mut file = OpenOptions::new() let mut file = OpenOptions::new().write(true).open(PLATFORM_PROFILE)?;
.write(true)
.open(PLATFORM_PROFILE)
.unwrap_or_else(|_| panic!("{} not found", PLATFORM_PROFILE));
file.write_all(<&str>::from(profile).as_bytes())?; file.write_all(<&str>::from(profile).as_bytes())?;
Ok(()) 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<Self, Self::Err> {
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))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, Clone, Copy)] #[derive(Deserialize, Serialize, Debug, PartialEq, Clone, Copy)]
pub enum FanCurvePU { pub enum FanCurvePU {
CPU, CPU,
GPU, GPU,
@@ -126,27 +130,63 @@ impl From<FanCurvePU> for &str {
} }
} }
impl std::str::FromStr for FanCurvePU {
type Err = ProfileError;
fn from_str(fan: &str) -> Result<Self, Self::Err> {
match fan.to_ascii_lowercase().trim() {
"cpu" => Ok(FanCurvePU::CPU),
"gpu" => Ok(FanCurvePU::GPU),
_ => Err(ProfileError::ParseProfileName),
}
}
}
impl Default for FanCurvePU { impl Default for FanCurvePU {
fn default() -> Self { fn default() -> Self {
Self::CPU 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))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, Default)] #[derive(Deserialize, Serialize, Debug, Default)]
pub struct FanCurveProfiles { pub struct FanCurveProfiles {
enabled: Vec<Profile>,
balanced: FanCurveSet, balanced: FanCurveSet,
performance: FanCurveSet, performance: FanCurveSet,
quiet: FanCurveSet, quiet: FanCurveSet,
} }
impl FanCurveProfiles { impl FanCurveProfiles {
pub fn get_device() -> Result<Device, ProfileError> {
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<bool, ProfileError> {
if Self::get_device().is_ok() {
return Ok(true);
}
Ok(false)
}
/// ///
pub fn read_from_dev_profile(&mut self, profile: Profile, device: &Device) { pub fn read_from_dev_profile(&mut self, profile: Profile, device: &Device) {
let mut tmp = FanCurveSet::default(); let mut tmp = FanCurveSet::default();
tmp.read_from_device(device); tmp.read_cpu_from_device(device);
tmp.read_gpu_from_device(device);
match profile { match profile {
Profile::Balanced => self.balanced = tmp, Profile::Balanced => self.balanced = tmp,
Profile::Performance => self.performance = 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 { let fans = match profile {
Profile::Balanced => &self.balanced, Profile::Balanced => &mut self.balanced,
Profile::Performance => &self.performance, Profile::Performance => &mut self.performance,
Profile::Quiet => &self.quiet, Profile::Quiet => &mut self.quiet,
}; };
fans.write_cpu_fan(device); fans.write_cpu_fan(device)?;
fans.write_gpu_fan(device); fans.write_gpu_fan(device)?;
Ok(())
} }
pub fn get_enabled_curve_profiles(&self) -> &[Profile] { pub fn get_enabled_curve_profiles(&self) -> Vec<Profile> {
&self.enabled 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<Profile>) { pub fn set_profile_curve_enabled(&mut self, profile: Profile, enabled: bool) {
self.enabled = profiles 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<FanCurveSet> { pub fn get_all_fan_curves(&self) -> Vec<FanCurveSet> {
@@ -180,11 +265,11 @@ impl FanCurveProfiles {
] ]
} }
pub fn get_active_fan_curves(&self) -> &FanCurveSet { pub fn get_active_fan_curves(&self) -> Result<&FanCurveSet, ProfileError> {
match Profile::get_active_profile().unwrap() { match Profile::get_active_profile()? {
Profile::Balanced => &self.balanced, Profile::Balanced => Ok(&self.balanced),
Profile::Performance => &self.performance, Profile::Performance => Ok(&self.performance),
Profile::Quiet => &self.quiet, Profile::Quiet => Ok(&self.quiet),
} }
} }
@@ -213,16 +298,7 @@ impl FanCurveProfiles {
} }
} }
pub fn write_and_set_fan_curve( pub fn save_fan_curve(&mut self, curve: CurveData, profile: Profile) -> std::io::Result<()> {
&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),
}
match profile { match profile {
Profile::Balanced => match curve.fan { Profile::Balanced => match curve.fan {
FanCurvePU::CPU => self.balanced.cpu = curve, FanCurvePU::CPU => self.balanced.cpu = curve,
@@ -237,33 +313,6 @@ impl FanCurveProfiles {
FanCurvePU::GPU => self.quiet.gpu = curve, FanCurvePU::GPU => self.quiet.gpu = curve,
}, },
} }
} Ok(())
}
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();
} }
} }