Merge branch 'fluke/fan_curves_v9' into 'main'

Fluke/fan curves v9

See merge request asus-linux/asusctl!77
This commit is contained in:
Luke Jones
2021-09-10 12:26:14 +00:00
14 changed files with 414 additions and 265 deletions

View File

@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
+ `BREAKING:` plain `Image` with time period is changed and old anime configs break as a result (sorry) + `BREAKING:` plain `Image` with time period is changed and old anime configs break as a result (sorry)
- LED: - LED:
+ By popular request LED prev/next cycle is added + By popular request LED prev/next cycle is added
+ Add led modes for GX551Q
### BREAKING CHANGES ### BREAKING CHANGES
- Graphics control: - Graphics control:
+ graphics control is pulled out of asusd and moved to new package; https://gitlab.com/asus-linux/supergfxctl + graphics control is pulled out of asusd and moved to new package; https://gitlab.com/asus-linux/supergfxctl
@@ -19,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
+ profiles now depend on power-profile-daemon plus kernel patches for support of platform_profile + profiles now depend on power-profile-daemon plus kernel patches for support of platform_profile
- if your system supports fan-curves you will also require upcoming kernel patches for this - if your system supports fan-curves you will also require upcoming kernel patches for this
+ profiles are now moved to a new file + profiles are now moved to a new file
+ fan-curves are only partially completed due to this release needing to be done sooner
# [3.7.2] - 2021-08-02 # [3.7.2] - 2021-08-02
### Added ### Added

1
Cargo.lock generated
View File

@@ -938,6 +938,7 @@ version = "1.0.0"
dependencies = [ dependencies = [
"serde", "serde",
"serde_derive", "serde_derive",
"udev",
"zvariant", "zvariant",
"zvariant_derive", "zvariant_derive",
] ]

View File

@@ -375,7 +375,9 @@ fn handle_profile(
supported: &PlatformProfileFunctions, supported: &PlatformProfileFunctions,
cmd: &ProfileCommand, cmd: &ProfileCommand,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
if !cmd.next && !cmd.list && !cmd.active_name && !cmd.active_data && !cmd.profiles_data { println!("Warning: Profiles should work fine but 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 { if !cmd.help {
println!("Missing arg or command\n"); println!("Missing arg or command\n");
} }
@@ -394,14 +396,18 @@ fn handle_profile(
println!("\n{}", lst); println!("\n{}", lst);
} }
println!("Note: turbo, frequency, fan preset and fan curve options will apply to"); // println!("Note: turbo, frequency, fan preset and fan curve options will apply to");
println!(" to the currently active profile unless a profile name is specified"); // println!(" to the currently active profile unless a profile name is specified");
std::process::exit(1); std::process::exit(1);
} }
if cmd.next { if cmd.next {
dbus.proxies().profile().next_profile()?; dbus.proxies().profile().next_profile()?;
} }
if cmd.list {
let res = dbus.proxies().profile().profiles()?;
res.iter().for_each(|p| println!("{:?}", p));
}
Ok(()) Ok(())
} }

View File

@@ -8,10 +8,4 @@ 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 active profile name")]
pub active_name: bool,
#[options(help = "get active profile data")]
pub active_data: bool,
#[options(help = "get all profile data")]
pub profiles_data: bool,
} }

View File

@@ -39,7 +39,7 @@ logind-zbus = "^0.7.1"
serde = "^1.0" serde = "^1.0"
serde_derive = "^1.0" serde_derive = "^1.0"
serde_json = "^1.0" serde_json = "^1.0"
toml = "^0.5" toml = "^0.5.8"
# Device control # Device control
sysfs-class = "^0.1.2" # used for backlight control and baord ID sysfs-class = "^0.1.2" # used for backlight control and baord ID

View File

@@ -1,5 +1,6 @@
use log::{error, warn}; use log::{error, warn};
use rog_profiles::{FanCurves, Profile}; use rog_profiles::fan_curve_set::FanCurveSet;
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};
use std::io::{Read, Write}; use std::io::{Read, Write};
@@ -9,23 +10,24 @@ pub struct ProfileConfig {
#[serde(skip)] #[serde(skip)]
config_path: String, config_path: String,
/// For restore on boot /// For restore on boot
pub active: Profile, pub active_profile: Profile,
/// States to restore /// States to restore
pub fan_curves: Option<FanCurves>, pub fan_curves: Option<FanCurveProfiles>,
} }
impl ProfileConfig { impl ProfileConfig {
fn new(config_path: String) -> Self { fn new(config_path: String) -> Self {
let mut platform = ProfileConfig { let mut platform = ProfileConfig {
config_path, config_path,
active: Profile::Balanced, active_profile: Profile::get_active_profile().unwrap_or(Profile::Balanced),
fan_curves: None, fan_curves: None,
}; };
if FanCurves::is_fan_curves_supported() { if let Ok(res) = FanCurveSet::is_supported() {
let mut curves = FanCurves::default(); if res {
curves.update_from_platform(); let curves = FanCurveProfiles::default();
platform.fan_curves = Some(curves); platform.fan_curves = Some(curves);
}
} }
platform platform
@@ -43,7 +45,7 @@ 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);
} else if let Ok(data) = serde_json::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 {
@@ -68,7 +70,7 @@ impl ProfileConfig {
if l == 0 { if l == 0 {
warn!("File is empty {}", self.config_path); warn!("File is empty {}", self.config_path);
} else { } else {
let mut data: ProfileConfig = serde_json::from_str(&buf) let mut data: ProfileConfig = toml::from_str(&buf)
.unwrap_or_else(|_| panic!("Could not deserialise {}", self.config_path)); .unwrap_or_else(|_| panic!("Could not deserialise {}", self.config_path));
// copy over serde skipped values // copy over serde skipped values
data.config_path = self.config_path.clone(); data.config_path = self.config_path.clone();
@@ -79,8 +81,8 @@ impl ProfileConfig {
pub fn write(&self) { pub fn write(&self) {
let mut file = File::create(&self.config_path).expect("Couldn't overwrite config"); let mut file = File::create(&self.config_path).expect("Couldn't overwrite config");
let json = serde_json::to_string_pretty(self).expect("Parse config to JSON failed"); let data = toml::to_string(self).expect("Parse config to toml failed");
file.write_all(json.as_bytes()) file.write_all(data.as_bytes())
.unwrap_or_else(|err| error!("Could not write config: {}", err)); .unwrap_or_else(|err| error!("Could not write config: {}", err));
} }
} }

View File

@@ -2,37 +2,16 @@ use crate::error::RogError;
use crate::GetSupported; use crate::GetSupported;
use log::{info, warn}; use log::{info, warn};
use rog_profiles::error::ProfileError; use rog_profiles::error::ProfileError;
use rog_profiles::{FanCurves, Profile}; use rog_profiles::fan_curve_set::FanCurveSet;
use rog_profiles::Profile;
use rog_supported::PlatformProfileFunctions; use rog_supported::PlatformProfileFunctions;
use std::sync::Arc; use udev::Device;
use std::sync::Mutex;
use super::config::ProfileConfig; use super::config::ProfileConfig;
pub struct CtrlPlatformTask {
config: Arc<Mutex<ProfileConfig>>,
}
impl CtrlPlatformTask {
pub fn new(config: Arc<Mutex<ProfileConfig>>) -> Self {
Self { config }
}
}
impl crate::CtrlTask for CtrlPlatformTask {
fn do_task(&self) -> Result<(), RogError> {
if let Ok(mut lock) = self.config.try_lock() {
// Refresh the config in-case the user has edited it
if let Some(curves) = &mut lock.fan_curves {
curves.update_from_platform();
}
}
Ok(())
}
}
pub struct CtrlPlatformProfile { pub struct CtrlPlatformProfile {
pub config: Arc<Mutex<ProfileConfig>>, pub config: ProfileConfig,
pub fan_device: Option<Device>,
} }
impl GetSupported for CtrlPlatformProfile { impl GetSupported for CtrlPlatformProfile {
@@ -48,7 +27,14 @@ https://lkml.org/lkml/2021/8/18/1022
"# "#
); );
} }
if !FanCurves::is_fan_curves_supported() {
let res = FanCurveSet::is_supported();
let mut fan_curve_supported = res.is_err();
if let Ok(r) = res {
fan_curve_supported = r;
};
if fan_curve_supported {
info!( info!(
r#" r#"
fan curves kernel interface not found, your laptop does not support this, or the interface is missing. fan curves kernel interface not found, your laptop does not support this, or the interface is missing.
@@ -58,9 +44,10 @@ Please note that as of 24/08/2021 this is not final.
"# "#
); );
} }
PlatformProfileFunctions { PlatformProfileFunctions {
platform_profile: Profile::is_platform_profile_supported(), platform_profile: Profile::is_platform_profile_supported(),
fan_curves: FanCurves::is_fan_curves_supported(), fan_curves: fan_curve_supported,
} }
} }
} }
@@ -68,9 +55,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 Ok(cfg) = self.config.clone().try_lock() { if let Some(curves) = &self.config.fan_curves {
if let Some(curves) = &cfg.fan_curves { if let Ok(mut device) = FanCurveSet::get_device() {
curves.update_platform(); curves.write_to_platform(self.config.active_profile, &mut device);
} }
} }
Ok(()) Ok(())
@@ -78,43 +65,56 @@ impl crate::Reloadable for CtrlPlatformProfile {
} }
impl CtrlPlatformProfile { impl CtrlPlatformProfile {
pub fn new(config: Arc<Mutex<ProfileConfig>>) -> Result<Self, RogError> { pub fn new(mut config: ProfileConfig, fan_device: Option<Device>) -> 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");
return Ok(CtrlPlatformProfile { config });
if let Some(ref device) = fan_device {
let profile = config.active_profile;
config
.fan_curves
.as_mut()
.unwrap()
.read_from_dev_profile(profile, device);
}
config.write();
return Ok(CtrlPlatformProfile { config, fan_device });
} }
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) {
if let Ok(lock) = self.config.lock() { self.config.write();
lock.write();
}
} }
/// 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> {
if let Ok(mut config) = self.config.clone().try_lock() { // 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();
config.read();
match config.active { match self.config.active_profile {
Profile::Balanced => { Profile::Balanced => {
Profile::set_profile(Profile::Performance)?; Profile::set_profile(Profile::Performance)?;
config.active = Profile::Performance; self.config.active_profile = Profile::Performance;
} }
Profile::Performance => { Profile::Performance => {
Profile::set_profile(Profile::Quiet)?; Profile::set_profile(Profile::Quiet)?;
config.active = Profile::Quiet; self.config.active_profile = Profile::Quiet;
} }
Profile::Quiet => { Profile::Quiet => {
Profile::set_profile(Profile::Balanced)?; Profile::set_profile(Profile::Balanced)?;
config.active = Profile::Balanced; self.config.active_profile = Profile::Balanced;
}
} }
config.write();
} }
self.config.write();
Ok(()) Ok(())
} }
} }

View File

@@ -1,5 +1,6 @@
use log::warn; use log::warn;
use rog_profiles::FanCurve; use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::fan_curve_set::FanCurveSet;
use rog_profiles::Profile; use rog_profiles::Profile;
use std::sync::Arc; use std::sync::Arc;
@@ -45,11 +46,9 @@ impl ProfileZbus {
/// Fetch the active profile name /// Fetch the active profile name
fn active_profile(&mut self) -> zbus::fdo::Result<Profile> { fn active_profile(&mut self) -> zbus::fdo::Result<Profile> {
if let Ok(ctrl) = self.inner.try_lock() { if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() { ctrl.config.read();
cfg.read(); return Ok(ctrl.config.active_profile);
return Ok(cfg.active);
}
} }
Err(Error::Failed( Err(Error::Failed(
"Failed to get active profile name".to_string(), "Failed to get active profile name".to_string(),
@@ -58,15 +57,14 @@ impl ProfileZbus {
/// Set this platform_profile name as active /// Set this platform_profile name as active
fn set_active_profile(&self, profile: Profile) { fn set_active_profile(&self, profile: Profile) {
if let Ok(ctrl) = self.inner.try_lock() { if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() { // 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();
cfg.read(); Profile::set_profile(profile)
Profile::set_profile(profile) .map_err(|e| warn!("Profile::set_profile, {}", e))
.map_err(|e| warn!("Profile::set_profile, {}", e)) .ok();
.ok(); ctrl.config.active_profile = profile;
cfg.active = profile;
}
ctrl.save_config(); ctrl.save_config();
} }
self.do_notification(); self.do_notification();
@@ -74,14 +72,26 @@ impl ProfileZbus {
/// Get a list of profiles that have fan-curves enabled. /// Get a list of profiles that have fan-curves enabled.
fn enabled_fan_profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> { fn enabled_fan_profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
if let Ok(ctrl) = self.inner.try_lock() { if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() { ctrl.config.read();
cfg.read(); if let Some(curves) = &ctrl.config.fan_curves {
if let Some(curves) = &cfg.fan_curves { return Ok(curves.get_enabled_curve_profiles().to_vec());
return Ok(curves.get_enabled_curve_names().to_vec());
}
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(),
))
}
/// Get a list of profiles that have fan-curves enabled.
fn set_enabled_fan_profiles(&mut self, profiles: Vec<Profile>) -> 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);
}
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(),
@@ -89,43 +99,42 @@ 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<FanCurve> { fn active_fan_curve_data(&mut self) -> zbus::fdo::Result<FanCurveSet> {
if let Ok(ctrl) = self.inner.try_lock() { if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() { ctrl.config.read();
cfg.read(); if let Some(curves) = &ctrl.config.fan_curves {
if let Some(curves) = &cfg.fan_curves { return Ok((*curves.get_active_fan_curves()).clone());
return Ok((*curves.get_active_fan_curves()).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 /// Get fan-curve data for each Profile as an array of objects
fn fan_curves(&self) -> zbus::fdo::Result<Vec<FanCurve>> { fn fan_curves(&self) -> zbus::fdo::Result<Vec<FanCurveSet>> {
if let Ok(ctrl) = self.inner.try_lock() { if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() { ctrl.config.read();
cfg.read(); if let Some(curves) = &ctrl.config.fan_curves {
if let Some(curves) = &cfg.fan_curves { return Ok(curves.get_all_fan_curves());
return Ok(curves.get_all_fan_curves());
}
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
} }
return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
} }
Err(Error::Failed("Failed to get all fan curves".to_string())) Err(Error::Failed("Failed to get all fan curves".to_string()))
} }
/// Set this fan-curve data /// Set this fan-curve data
fn set_fan_curve(&self, curve: FanCurve) -> zbus::fdo::Result<()> { fn set_fan_curve(&self, curve: CurveData) -> zbus::fdo::Result<()> {
if let Ok(ctrl) = self.inner.try_lock() { if let Ok(mut ctrl) = self.inner.try_lock() {
if let Ok(mut cfg) = ctrl.config.try_lock() { ctrl.config.read();
cfg.read(); let profile = ctrl.config.active_profile;
if let Some(curves) = &mut cfg.fan_curves { if let Some(mut device) = ctrl.get_device() {
curves.set_fan_curve(curve); if let Some(curves) = &mut ctrl.config.fan_curves {
curves.write_and_set_fan_curve(curve, profile, &mut device);
} }
} else {
return Err(Error::Failed(UNSUPPORTED_MSG.to_string())); return Err(Error::Failed(UNSUPPORTED_MSG.to_string()));
} }
ctrl.save_config(); ctrl.save_config();
} }
@@ -139,10 +148,8 @@ impl ProfileZbus {
impl ProfileZbus { impl ProfileZbus {
fn do_notification(&self) { fn do_notification(&self) {
if let Ok(ctrl) = self.inner.try_lock() { if let Ok(ctrl) = self.inner.try_lock() {
if let Ok(cfg) = ctrl.config.clone().try_lock() { self.notify_profile(&ctrl.config.active_profile)
self.notify_profile(&cfg.active) .unwrap_or_else(|err| warn!("{}", err));
.unwrap_or_else(|err| warn!("{}", err));
}
} }
} }
} }

View File

@@ -7,7 +7,6 @@ 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::CtrlPlatformTask;
use daemon::{ use daemon::{
config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported, config::Config, ctrl_supported::SupportedFunctions, laptops::print_board_info, GetSupported,
}; };
@@ -21,6 +20,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 std::env; use std::env;
use std::error::Error; use std::error::Error;
use std::io::Write; use std::io::Write;
@@ -108,16 +108,19 @@ fn start_daemon() -> Result<(), Box<dyn Error>> {
} }
} }
let profile_config = Arc::new(Mutex::new(ProfileConfig::load(PROFILE_CONFIG_PATH.into()))); let fan_device = if let Ok(res) = FanCurveSet::get_device() {
match CtrlPlatformProfile::new(profile_config.clone()) { Some(res)
} else {
None
};
let profile_config = ProfileConfig::load(PROFILE_CONFIG_PATH.into());
match CtrlPlatformProfile::new(profile_config, fan_device) {
Ok(mut ctrl) => { Ok(mut ctrl) => {
ctrl.reload() ctrl.reload()
.unwrap_or_else(|err| warn!("Profile control: {}", err)); .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).add_to_server(&mut object_server);
tasks.push(Box::new(CtrlPlatformTask::new(profile_config)));
} }
Err(err) => { Err(err) => {
error!("Profile control: {}", err); error!("Profile control: {}", err);

View File

@@ -109,3 +109,10 @@ board_names = ["GX550L"]
standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"] standard = ["Static", "Breathe", "Strobe", "Rainbow", "Star", "Rain", "Highlight", "Laser", "Ripple", "Pulse", "Comet", "Flash"]
multizone = false multizone = false
per_key = true per_key = true
[[led_data]]
prod_family = "ROG Zephyrus Duo 15 SE"
board_name = ["GX551Q"]
standard ["Static", "Breathe", "Pulse", "Rainbow", "Strobe"]
multizone = false
per_key = true

View File

@@ -21,7 +21,7 @@
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use rog_profiles::{FanCurve, Profile}; use rog_profiles::{fan_curve_set::FanCurveSet, Profile};
use zbus::{dbus_proxy, Connection, Result}; use zbus::{dbus_proxy, Connection, Result};
#[dbus_proxy( #[dbus_proxy(
@@ -29,29 +29,29 @@ use zbus::{dbus_proxy, Connection, Result};
default_path = "/org/asuslinux/Profile" default_path = "/org/asuslinux/Profile"
)] )]
trait Daemon { trait Daemon {
/// Profiles method /// Get the active `Profile` data
fn profiles(&self) -> zbus::Result<Vec<Profile>>; fn active_fan_curve_data(&self) -> zbus::Result<FanCurveSet>;
/// NextProfile method
fn next_profile(&self) -> zbus::Result<()>;
/// Profile, get the active profile /// Profile, get the active profile
fn active_profile(&self) -> zbus::Result<Profile>; fn active_profile(&self) -> zbus::Result<Profile>;
/// Set the specific profile as active
fn set_active_profile(&self, profile: Profile) -> zbus::Result<()>;
/// Get enabled fan curves /// Get enabled fan curves
fn enabled_fan_profiles(&self) -> zbus::Result<Vec<Profile>>; fn enabled_fan_profiles(&self) -> zbus::Result<Vec<Profile>>;
/// Get the active `Profile` data
fn active_fan_data(&self) -> zbus::Result<FanCurve>;
/// Get all fan curve data /// Get all fan curve data
fn fan_curves(&self) -> zbus::Result<Vec<FanCurve>>; fn fan_curves(&self) -> zbus::Result<Vec<FanCurveSet>>;
/// NextProfile method
fn next_profile(&self) -> zbus::Result<()>;
/// Profiles method
fn profiles(&self) -> zbus::Result<Vec<Profile>>;
/// Set the specific profile 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 /// Set a fan curve. If a field is empty then the exisiting saved curve is used
fn set_fan_curve(&self, curve: FanCurve) -> zbus::Result<()>; fn set_fan_curve(&self, curve: FanCurveSet) -> zbus::Result<()>;
/// NotifyProfile signal /// NotifyProfile signal
#[dbus_proxy(signal)] #[dbus_proxy(signal)]

View File

@@ -9,6 +9,7 @@ default = ["dbus"]
dbus = ["zvariant", "zvariant_derive"] dbus = ["zvariant", "zvariant_derive"]
[dependencies] [dependencies]
udev = "^0.6"
serde = "^1.0" serde = "^1.0"
serde_derive = "^1.0" serde_derive = "^1.0"

View File

@@ -0,0 +1,139 @@
use serde_derive::{Deserialize, Serialize};
use udev::Device;
#[cfg(feature = "dbus")]
use zvariant_derive::Type;
use crate::{error::ProfileError, write_to_fan, FanCurvePU};
pub fn pwm_str(fan: char, index: char) -> 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;
}
buf
}
pub fn temp_str(fan: char, index: char) -> 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;
}
buf
}
#[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],
}
/// 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,
}
impl Default for FanCurveSet {
fn default() -> Self {
Self {
cpu: CurveData {
fan: FanCurvePU::CPU,
pwm: [0u8; 8],
temp: [0u8; 8],
},
gpu: CurveData {
fan: FanCurvePU::GPU,
pwm: [0u8; 8],
temp: [0u8; 8],
},
}
}
}
impl FanCurveSet {
pub fn get_device() -> Result<Device, ProfileError> {
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 fn is_supported() -> Result<bool, ProfileError> {
if Self::get_device().is_ok() {
return Ok(true);
}
Ok(false)
}
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)
}
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);
}
}

View File

@@ -1,26 +1,42 @@
pub mod error; pub mod error;
pub mod fan_curve_set;
use std::{ use std::{
fs::OpenOptions, fs::OpenOptions,
io::{Read, Write}, io::{Read, Write},
path::{Path, PathBuf}, path::Path,
}; };
use error::ProfileError; use error::ProfileError;
use fan_curve_set::{CurveData, FanCurveSet};
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use udev::Device;
#[cfg(feature = "dbus")] #[cfg(feature = "dbus")]
use zvariant_derive::Type; use zvariant_derive::Type;
pub static PLATFORM_PROFILE: &str = "/sys/firmware/acpi/platform_profile"; pub static PLATFORM_PROFILE: &str = "/sys/firmware/acpi/platform_profile";
pub static PLATFORM_PROFILES: &str = "/sys/firmware/acpi/platform_profile_choices"; pub static PLATFORM_PROFILES: &str = "/sys/firmware/acpi/platform_profile_choices";
pub static FAN_CURVE_BASE_PATH: &str = "/sys/devices/platform/asus-nb-wmi/";
pub static FAN_CURVE_ACTIVE_FILE: &str = "enabled_fan_curve_profiles";
pub static FAN_CURVE_FILENAME_PART: &str = "_fan_curve_";
pub static VERSION: &str = env!("CARGO_PKG_VERSION"); pub static VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn find_fan_curve_node() -> Result<Option<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(Some(device));
}
}
}
}
Err(ProfileError::NotSupported)
}
#[cfg_attr(feature = "dbus", derive(Type))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, Clone, Copy)] #[derive(Deserialize, Serialize, Debug, Clone, Copy)]
pub enum Profile { pub enum Profile {
@@ -116,82 +132,47 @@ impl Default for FanCurvePU {
} }
} }
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Default, Debug, Clone)]
pub struct FanCurve {
pub profile: Profile,
pub cpu: String,
pub gpu: String,
}
/// Main purpose of `FanCurves` is to enable retoring state on system boot /// Main purpose of `FanCurves` is to enable retoring state on system boot
#[cfg_attr(feature = "dbus", derive(Type))] #[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug, Default)]
pub struct FanCurves { pub struct FanCurveProfiles {
enabled: Vec<Profile>, enabled: Vec<Profile>,
balanced: FanCurve, balanced: FanCurveSet,
performance: FanCurve, performance: FanCurveSet,
quiet: FanCurve, quiet: FanCurveSet,
} }
impl Default for FanCurves { impl FanCurveProfiles {
fn default() -> Self { ///
let mut curves = Self { pub fn read_from_dev_profile(&mut self, profile: Profile, device: &Device) {
enabled: Default::default(), let mut tmp = FanCurveSet::default();
balanced: Default::default(), tmp.read_from_device(device);
performance: Default::default(), match profile {
quiet: Default::default(), Profile::Balanced => self.balanced = tmp,
Profile::Performance => self.performance = tmp,
Profile::Quiet => self.quiet = tmp,
}
}
pub fn write_to_platform(&self, profile: Profile, device: &mut Device) {
let fans = match profile {
Profile::Balanced => &self.balanced,
Profile::Performance => &self.performance,
Profile::Quiet => &self.quiet,
}; };
curves.balanced.profile = Profile::Balanced; fans.write_cpu_fan(device);
curves.performance.profile = Profile::Performance; fans.write_gpu_fan(device);
curves.quiet.profile = Profile::Quiet;
curves
}
}
impl FanCurves {
pub fn is_fan_curves_supported() -> bool {
let mut path = PathBuf::new();
path.push(FAN_CURVE_BASE_PATH);
path.push(FAN_CURVE_ACTIVE_FILE);
path.exists()
} }
pub fn update_from_platform(&mut self) { pub fn get_enabled_curve_profiles(&self) -> &[Profile] {
self.balanced.cpu = Self::get_fan_curve_from_file(Profile::Balanced, FanCurvePU::CPU);
self.balanced.gpu = Self::get_fan_curve_from_file(Profile::Balanced, FanCurvePU::GPU);
self.performance.cpu = Self::get_fan_curve_from_file(Profile::Performance, FanCurvePU::CPU);
self.performance.gpu = Self::get_fan_curve_from_file(Profile::Performance, FanCurvePU::GPU);
self.quiet.cpu = Self::get_fan_curve_from_file(Profile::Quiet, FanCurvePU::CPU);
self.quiet.gpu = Self::get_fan_curve_from_file(Profile::Quiet, FanCurvePU::GPU);
}
pub fn update_platform(&self) {
Self::set_fan_curve_for_platform(Profile::Balanced, FanCurvePU::CPU, &self.balanced.cpu);
Self::set_fan_curve_for_platform(Profile::Balanced, FanCurvePU::GPU, &self.balanced.gpu);
Self::set_fan_curve_for_platform(
Profile::Performance,
FanCurvePU::CPU,
&self.performance.cpu,
);
Self::set_fan_curve_for_platform(
Profile::Performance,
FanCurvePU::GPU,
&self.performance.gpu,
);
Self::set_fan_curve_for_platform(Profile::Quiet, FanCurvePU::CPU, &self.quiet.cpu);
Self::set_fan_curve_for_platform(Profile::Quiet, FanCurvePU::GPU, &self.quiet.gpu);
}
pub fn get_enabled_curve_names(&self) -> &[Profile] {
&self.enabled &self.enabled
} }
pub fn get_all_fan_curves(&self) -> Vec<FanCurve> { pub fn set_enabled_curve_profiles(&mut self, profiles: Vec<Profile>) {
self.enabled = profiles
}
pub fn get_all_fan_curves(&self) -> Vec<FanCurveSet> {
vec![ vec![
self.balanced.clone(), self.balanced.clone(),
self.performance.clone(), self.performance.clone(),
@@ -199,7 +180,7 @@ impl FanCurves {
] ]
} }
pub fn get_active_fan_curves(&self) -> &FanCurve { pub fn get_active_fan_curves(&self) -> &FanCurveSet {
match Profile::get_active_profile().unwrap() { match Profile::get_active_profile().unwrap() {
Profile::Balanced => &self.balanced, Profile::Balanced => &self.balanced,
Profile::Performance => &self.performance, Profile::Performance => &self.performance,
@@ -207,7 +188,7 @@ impl FanCurves {
} }
} }
pub fn get_fan_curves_for(&self, name: Profile) -> &FanCurve { pub fn get_fan_curves_for(&self, name: Profile) -> &FanCurveSet {
match name { match name {
Profile::Balanced => &self.balanced, Profile::Balanced => &self.balanced,
Profile::Performance => &self.performance, Profile::Performance => &self.performance,
@@ -215,23 +196,7 @@ impl FanCurves {
} }
} }
fn get_fan_curve_from_file(name: Profile, pu: FanCurvePU) -> String { pub fn get_fan_curve_for(&self, name: &Profile, pu: &FanCurvePU) -> &CurveData {
let mut file: String = FAN_CURVE_BASE_PATH.into();
file.push_str(pu.into());
file.push_str(FAN_CURVE_FILENAME_PART);
file.push_str(name.into());
let mut file = OpenOptions::new()
.read(true)
.open(&file)
.unwrap_or_else(|_| panic!("{} not found", &file));
let mut buf = String::new();
file.read_to_string(&mut buf).unwrap();
buf.trim().to_string()
}
pub fn get_fan_curve_for(&self, name: &Profile, pu: &FanCurvePU) -> &str {
match name { match name {
Profile::Balanced => match pu { Profile::Balanced => match pu {
FanCurvePU::CPU => &self.balanced.cpu, FanCurvePU::CPU => &self.balanced.cpu,
@@ -248,38 +213,60 @@ impl FanCurves {
} }
} }
fn set_fan_curve_for_platform(name: Profile, pu: FanCurvePU, curve: &str) { pub fn write_and_set_fan_curve(
let mut file: String = FAN_CURVE_BASE_PATH.into(); &mut self,
file.push_str(pu.into()); curve: CurveData,
file.push_str(FAN_CURVE_FILENAME_PART); profile: Profile,
file.push_str(name.into()); device: &mut Device,
) {
let mut file = OpenOptions::new() match curve.fan {
.write(true) FanCurvePU::CPU => write_to_fan(&curve, '1', device),
.open(&file) FanCurvePU::GPU => write_to_fan(&curve, '2', device),
.unwrap_or_else(|_| panic!("{} not found", &file)); }
match profile {
file.write_all(curve.as_bytes()).unwrap(); Profile::Balanced => match curve.fan {
} FanCurvePU::CPU => self.balanced.cpu = curve,
FanCurvePU::GPU => self.balanced.gpu = curve,
pub fn set_fan_curve(&mut self, curve: FanCurve) { },
// First, set the profiles. Profile::Performance => match curve.fan {
Self::set_fan_curve_for_platform(curve.profile, FanCurvePU::CPU, &curve.cpu); FanCurvePU::CPU => self.performance.cpu = curve,
match curve.profile { FanCurvePU::GPU => self.performance.gpu = curve,
Profile::Balanced => self.balanced.cpu = curve.cpu, },
Profile::Performance => self.performance.cpu = curve.cpu, Profile::Quiet => match curve.fan {
Profile::Quiet => self.quiet.cpu = curve.cpu, FanCurvePU::CPU => self.quiet.cpu = curve,
}; FanCurvePU::GPU => self.quiet.gpu = curve,
},
Self::set_fan_curve_for_platform(curve.profile, FanCurvePU::GPU, &curve.gpu); }
match curve.profile { }
Profile::Balanced => self.balanced.gpu = curve.gpu, }
Profile::Performance => self.performance.gpu = curve.gpu,
Profile::Quiet => self.quiet.cpu = curve.gpu, pub fn write_to_fan(curve: &CurveData, pwm_num: char, device: &mut Device) {
}; let mut pwm = "pwmN_auto_pointN_pwm".to_string();
// Any curve that was blank will have been reset, so repopulate the settings dbg!(&device);
// Note: successfully set curves will just be re-read in. for (index, out) in curve.pwm.iter().enumerate() {
self.update_from_platform(); 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();
dbg!(&pwm);
dbg!(&out);
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();
} }
} }