profiles: add mid fan curve support

This commit is contained in:
Luke D. Jones
2023-07-21 22:07:04 +12:00
parent eb54250e4d
commit 51bcb0082b
16 changed files with 267 additions and 294 deletions

2
Cargo.lock generated
View File

@@ -2739,6 +2739,7 @@ dependencies = [
"inotify", "inotify",
"log", "log",
"rog_aura", "rog_aura",
"rog_profiles",
"rusb", "rusb",
"serde", "serde",
"serde_derive", "serde_derive",
@@ -2753,6 +2754,7 @@ name = "rog_profiles"
version = "4.7.0-RC3" version = "4.7.0-RC3"
dependencies = [ dependencies = [
"cargo-husky", "cargo-husky",
"log",
"serde", "serde",
"serde_derive", "serde_derive",
"typeshare", "typeshare",

View File

@@ -660,7 +660,7 @@ fn handle_fan_curve(
supported: &PlatformProfileFunctions, supported: &PlatformProfileFunctions,
cmd: &FanCurveCommand, cmd: &FanCurveCommand,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
if !supported.fan_curves { if supported.fans.is_empty() {
println!("Fan-curves not supported by either this kernel or by the laptop."); println!("Fan-curves not supported by either this kernel or by the laptop.");
println!("This requires kernel 5.17 or the fan curve patch listed in the readme."); println!("This requires kernel 5.17 or the fan curve patch listed in the readme.");
return Err(ProfileError::NotSupported.into()); return Err(ProfileError::NotSupported.into());
@@ -686,8 +686,9 @@ fn handle_fan_curve(
} }
if cmd.get_enabled { if cmd.get_enabled {
let res = dbus.proxies().profile().enabled_fan_profiles()?; // TODO:
println!("{:?}", res); // let res = dbus.proxies().profile().enabled_fan_profiles()?;
// println!("{:?}", res);
} }
if cmd.default { if cmd.default {

View File

@@ -34,12 +34,12 @@ pub struct FanCurveCommand {
pub mod_profile: Option<Profile>, pub mod_profile: Option<Profile>,
#[options( #[options(
meta = "", meta = "",
help = "enable or disable <true/false> fan curve. `mod-profile` required" help = "enable or disable <true/false> fan curve. `mod_profile` required"
)] )]
pub enabled: Option<bool>, pub enabled: Option<bool>,
#[options( #[options(
meta = "", meta = "",
help = "select fan <cpu/gpu> to modify. `mod-profile` required" help = "select fan <cpu/gpu/mid> to modify. `mod_profile` required"
)] )]
pub fan: Option<FanCurvePU>, pub fan: Option<FanCurvePU>,

View File

@@ -1,7 +1,7 @@
use std::path::PathBuf; use std::path::PathBuf;
use config_traits::{StdConfig, StdConfigLoad}; use config_traits::{StdConfig, StdConfigLoad};
use rog_profiles::fan_curve_set::FanCurveSet; use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::Profile; use rog_profiles::Profile;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
@@ -36,9 +36,9 @@ impl StdConfigLoad for ProfileConfig {}
#[derive(Deserialize, Serialize, Debug, Default)] #[derive(Deserialize, Serialize, Debug, Default)]
pub struct FanCurveConfig { pub struct FanCurveConfig {
pub balanced: FanCurveSet, pub balanced: Vec<CurveData>,
pub performance: FanCurveSet, pub performance: Vec<CurveData>,
pub quiet: FanCurveSet, pub quiet: Vec<CurveData>,
} }
impl StdConfig for FanCurveConfig { impl StdConfig for FanCurveConfig {

View File

@@ -56,13 +56,9 @@ impl GetSupported for CtrlPlatformProfile {
); );
} }
let res = FanCurveProfiles::is_supported(); let res = FanCurveProfiles::supported_fans();
let mut fan_curve_supported = res.is_err();
if let Ok(r) = res {
fan_curve_supported = r;
};
if !fan_curve_supported { if res.is_err() {
info!( info!(
"fan curves kernel interface not found, your laptop does not support this, or the \ "fan curves kernel interface not found, your laptop does not support this, or the \
interface is missing." interface is missing."
@@ -71,7 +67,7 @@ impl GetSupported for CtrlPlatformProfile {
PlatformProfileFunctions { PlatformProfileFunctions {
platform_profile: Profile::is_platform_profile_supported(), platform_profile: Profile::is_platform_profile_supported(),
fan_curves: fan_curve_supported, fans: res.unwrap_or_default(),
} }
} }
} }
@@ -113,10 +109,10 @@ impl CtrlPlatformProfile {
let active = Profile::get_active_profile().unwrap_or(Profile::Balanced); let active = Profile::get_active_profile().unwrap_or(Profile::Balanced);
if let Some(curves) = controller.fan_curves.as_ref() { if let Some(curves) = controller.fan_curves.as_ref() {
info!( info!("{MOD_NAME}: {active:?}:");
"{MOD_NAME}: {active:?}: {}", for curve in curves.profiles().get_fan_curves_for(active) {
String::from(curves.profiles().get_fan_curves_for(active)) info!("{}", String::from(curve));
); }
} }
} }
if let Some(curves) = controller.fan_curves.as_ref() { if let Some(curves) = controller.fan_curves.as_ref() {

View File

@@ -4,7 +4,7 @@ use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use config_traits::StdConfig; use config_traits::StdConfig;
use log::{error, info, warn}; use log::{error, info, warn};
use rog_profiles::fan_curve_set::{CurveData, FanCurveSet}; use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::{FanCurveProfiles, Profile}; use rog_profiles::{FanCurveProfiles, Profile};
use zbus::export::futures_util::lock::Mutex; use zbus::export::futures_util::lock::Mutex;
use zbus::export::futures_util::StreamExt; use zbus::export::futures_util::StreamExt;
@@ -83,16 +83,6 @@ impl ProfileZbus {
.ok(); .ok();
} }
/// Get a list of profiles that have fan-curves enabled.
async fn enabled_fan_profiles(&mut self) -> zbus::fdo::Result<Vec<Profile>> {
let mut ctrl = self.0.lock().await;
ctrl.profile_config.read();
if let Some(curves) = &mut ctrl.fan_curves {
return Ok(curves.profiles().get_enabled_curve_profiles());
}
Err(Error::Failed(UNSUPPORTED_MSG.to_owned()))
}
/// Set a profile fan curve enabled status. Will also activate a fan curve /// Set a profile fan curve enabled status. Will also activate a fan curve
/// if in the same profile mode /// if in the same profile mode
async fn set_fan_curve_enabled( async fn set_fan_curve_enabled(
@@ -119,12 +109,12 @@ impl ProfileZbus {
} }
/// Get the fan-curve data for the currently active Profile /// Get the fan-curve data for the currently active Profile
async fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result<FanCurveSet> { async fn fan_curve_data(&mut self, profile: Profile) -> zbus::fdo::Result<Vec<CurveData>> {
let mut ctrl = self.0.lock().await; let mut ctrl = self.0.lock().await;
ctrl.profile_config.read(); ctrl.profile_config.read();
if let Some(curves) = &mut ctrl.fan_curves { if let Some(curves) = &mut ctrl.fan_curves {
let curve = curves.profiles().get_fan_curves_for(profile); let curve = curves.profiles().get_fan_curves_for(profile);
return Ok(curve.clone()); return Ok(curve.to_vec());
} }
Err(Error::Failed(UNSUPPORTED_MSG.to_owned())) Err(Error::Failed(UNSUPPORTED_MSG.to_owned()))
} }

View File

@@ -1,6 +1,6 @@
use egui::Ui; use egui::{RichText, Ui};
use rog_platform::supported::SupportedFunctions; use rog_platform::supported::SupportedFunctions;
use rog_profiles::Profile; use rog_profiles::{FanCurvePU, Profile};
use crate::system_state::{FanCurvesState, ProfilesState, SystemState}; use crate::system_state::{FanCurvesState, ProfilesState, SystemState};
use crate::widgets::fan_graphs; use crate::widgets::fan_graphs;
@@ -12,10 +12,6 @@ impl RogApp {
egui::CentralPanel::default().show(ctx, |ui| { egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Custom fan curves"); ui.heading("Custom fan curves");
ui.label(
"A fan curve is only active when the related profile is active and the curve is \
enabled",
);
Self::fan_curve( Self::fan_curve(
supported, supported,
&mut states.profiles, &mut states.profiles,
@@ -44,35 +40,35 @@ impl RogApp {
ui: &mut Ui, ui: &mut Ui,
) { ) {
ui.separator(); ui.separator();
ui.label("Enabled fan-curves");
let mut changed = false; let mut changed = false;
ui.horizontal(|ui| { ui.horizontal(|ui| {
let mut item = |p: Profile, curves: &mut FanCurvesState, mut checked: bool| { ui.label("Current profile: ");
ui.label(RichText::new(format!("{}", profiles.current)).strong());
});
ui.horizontal(|ui| {
ui.label("Enabled fan-curves: ");
let mut fan_curve_enable = |profile: Profile, fan: FanCurvePU, mut checked: bool| {
if ui if ui
.add(egui::Checkbox::new(&mut checked, format!("{:?}", p))) .add(egui::Checkbox::new(&mut checked, format!("{:?}", fan)))
.changed() .changed()
{ {
dbus.proxies() dbus.proxies()
.profile() .profile()
.set_fan_curve_enabled(p, checked) .set_fan_curve_enabled(profile, checked)
.map_err(|err| { .map_err(|err| {
*do_error = Some(err.to_string()); *do_error = Some(err.to_string());
}) })
.ok(); .ok();
if !checked {
curves.enabled.remove(&p);
} else {
curves.enabled.insert(p);
}
changed = true; changed = true;
} }
}; };
profiles.list.sort(); if let Some(curves) = curves.curves.get_mut(&profiles.current) {
for f in &profiles.list { for curve in curves.iter_mut() {
item(*f, curves, curves.enabled.contains(f)); fan_curve_enable(profiles.current, curve.fan, curve.enabled);
}
} }
}); });

View File

@@ -12,7 +12,7 @@ use rog_aura::usb::AuraPowerDev;
use rog_aura::{AuraEffect, AuraModeNum}; use rog_aura::{AuraEffect, AuraModeNum};
use rog_platform::platform::GpuMode; use rog_platform::platform::GpuMode;
use rog_platform::supported::SupportedFunctions; use rog_platform::supported::SupportedFunctions;
use rog_profiles::fan_curve_set::FanCurveSet; use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::{FanCurvePU, Profile}; use rog_profiles::{FanCurvePU, Profile};
use supergfxctl::pci_device::{GfxMode, GfxPower}; use supergfxctl::pci_device::{GfxMode, GfxPower};
#[cfg(not(feature = "mocking"))] #[cfg(not(feature = "mocking"))]
@@ -96,8 +96,8 @@ impl ProfilesState {
pub struct FanCurvesState { pub struct FanCurvesState {
pub show_curve: Profile, pub show_curve: Profile,
pub show_graph: FanCurvePU, pub show_graph: FanCurvePU,
pub enabled: HashSet<Profile>, pub curves: BTreeMap<Profile, Vec<CurveData>>,
pub curves: BTreeMap<Profile, FanCurveSet>, pub available_fans: HashSet<FanCurvePU>,
pub drag_delta: Vec2, pub drag_delta: Vec2,
} }
@@ -108,34 +108,24 @@ impl FanCurvesState {
} else { } else {
vec![Profile::Balanced, Profile::Quiet, Profile::Performance] vec![Profile::Balanced, Profile::Quiet, Profile::Performance]
}; };
let enabled = if supported.platform_profile.fan_curves {
dbus.proxies()
.profile()
.enabled_fan_profiles()?
.iter()
.cloned()
.collect::<HashSet<_>>()
} else {
HashSet::from([Profile::Balanced, Profile::Quiet, Profile::Performance])
};
let mut curves: BTreeMap<Profile, FanCurveSet> = BTreeMap::new(); let mut curves: BTreeMap<Profile, Vec<CurveData>> = BTreeMap::new();
for p in &profiles { for p in &profiles {
if supported.platform_profile.fan_curves { if !supported.platform_profile.fans.is_empty() {
if let Ok(curve) = dbus.proxies().profile().fan_curve_data(*p) { if let Ok(curve) = dbus.proxies().profile().fan_curve_data(*p) {
curves.insert(*p, curve); curves.insert(*p, curve);
} }
} else { } else {
let mut curve = FanCurveSet::default(); curves.insert(*p, Vec::default());
curve.cpu.pwm = [30, 40, 60, 100, 140, 180, 200, 250];
curve.cpu.temp = [20, 30, 40, 50, 70, 80, 90, 100];
curve.gpu.pwm = [40, 80, 100, 140, 170, 200, 230, 250];
curve.gpu.temp = [20, 30, 40, 50, 70, 80, 90, 100];
curves.insert(*p, curve);
} }
} }
let show_curve = if supported.platform_profile.fan_curves { let mut available_fans = HashSet::new();
for fan in supported.platform_profile.fans.iter() {
available_fans.insert(*fan);
}
let show_curve = if !supported.platform_profile.fans.is_empty() {
dbus.proxies().profile().active_profile()? dbus.proxies().profile().active_profile()?
} else { } else {
Profile::Balanced Profile::Balanced
@@ -144,8 +134,8 @@ impl FanCurvesState {
Ok(Self { Ok(Self {
show_curve, show_curve,
show_graph: FanCurvePU::CPU, show_graph: FanCurvePU::CPU,
enabled,
curves, curves,
available_fans,
drag_delta: Vec2::default(), drag_delta: Vec2::default(),
}) })
} }

View File

@@ -1,6 +1,7 @@
use egui::plot::Points; use egui::plot::Points;
use egui::Ui; use egui::Ui;
use rog_platform::supported::SupportedFunctions; use rog_platform::supported::SupportedFunctions;
use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::{FanCurvePU, Profile}; use rog_profiles::{FanCurvePU, Profile};
use crate::system_state::FanCurvesState; use crate::system_state::FanCurvesState;
@@ -15,20 +16,36 @@ pub fn fan_graphs(
) { ) {
ui.separator(); ui.separator();
let mut item = |p: Profile, ui: &mut Ui| { let mut item = |profile: Profile, ui: &mut Ui| {
ui.group(|ui| { ui.group(|ui| {
ui.selectable_value(&mut curves.show_curve, p, format!("{p:?}")); if ui
ui.add_enabled_ui(curves.show_curve == p, |ui| { .selectable_value(&mut curves.show_curve, profile, format!("{profile:?}"))
.clicked()
{
dbus.proxies().profile().set_active_profile(profile).ok();
}
ui.add_enabled_ui(curves.show_curve == profile, |ui| {
if curves.available_fans.contains(&FanCurvePU::CPU) {
ui.selectable_value( ui.selectable_value(
&mut curves.show_graph, &mut curves.show_graph,
FanCurvePU::CPU, FanCurvePU::CPU,
format!("{:?}", FanCurvePU::CPU), format!("{:?}", FanCurvePU::CPU),
); );
}
if curves.available_fans.contains(&FanCurvePU::GPU) {
ui.selectable_value( ui.selectable_value(
&mut curves.show_graph, &mut curves.show_graph,
FanCurvePU::GPU, FanCurvePU::GPU,
format!("{:?}", FanCurvePU::GPU), format!("{:?}", FanCurvePU::GPU),
); );
}
if curves.available_fans.contains(&FanCurvePU::MID) {
ui.selectable_value(
&mut curves.show_graph,
FanCurvePU::MID,
format!("{:?}", FanCurvePU::MID),
);
}
}); });
}); });
}; };
@@ -43,11 +60,13 @@ pub fn fan_graphs(
use egui::plot::{Line, Plot}; use egui::plot::{Line, Plot};
let data = if curves.show_graph == FanCurvePU::CPU { let mut data = &mut CurveData::default();
&mut curve.cpu for c in curve {
} else { if c.fan == curves.show_graph {
&mut curve.gpu data = c;
}; break;
}
}
let mut points: Vec<[f64; 2]> = data let mut points: Vec<[f64; 2]> = data
.temp .temp

View File

@@ -18,7 +18,7 @@ impl RogApp {
*page = Page::System; *page = Page::System;
} }
if self.supported.platform_profile.fan_curves { if !self.supported.platform_profile.fans.is_empty() {
ui.separator(); ui.separator();
if ui if ui
.selectable_value(page, Page::FanCurves, "Fan Curves") .selectable_value(page, Page::FanCurves, "Fan Curves")

View File

@@ -20,7 +20,7 @@
//! //!
//! …consequently `zbus-xmlgen` did not generate code for the above interfaces. //! …consequently `zbus-xmlgen` did not generate code for the above interfaces.
use rog_profiles::fan_curve_set::{CurveData, FanCurveSet}; use rog_profiles::fan_curve_set::CurveData;
use rog_profiles::Profile; use rog_profiles::Profile;
use zbus::dbus_proxy; use zbus::dbus_proxy;
@@ -30,14 +30,11 @@ use zbus::dbus_proxy;
)] )]
trait Profile { trait Profile {
/// Get the fan-curve data for the currently active Profile /// Get the fan-curve data for the currently active Profile
fn fan_curve_data(&self, profile: Profile) -> zbus::Result<FanCurveSet>; fn fan_curve_data(&self, profile: Profile) -> zbus::Result<Vec<CurveData>>;
/// Fetch the active profile name /// Fetch the active profile name
fn active_profile(&self) -> zbus::Result<Profile>; fn active_profile(&self) -> zbus::Result<Profile>;
/// Get a list of profiles that have fan-curves enabled.
fn enabled_fan_profiles(&self) -> zbus::Result<Vec<Profile>>;
/// 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. /// If fan-curves are supported will also activate a fan curve for profile.
fn next_profile(&self) -> zbus::Result<()>; fn next_profile(&self) -> zbus::Result<()>;

View File

@@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
log.workspace = true log.workspace = true
rog_aura = { path = "../rog-aura" } rog_aura = { path = "../rog-aura" }
rog_profiles = { path = "../rog-profiles" }
serde.workspace = true serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true
zbus.workspace = true zbus.workspace = true

View File

@@ -3,6 +3,7 @@ use std::fmt;
use rog_aura::aura_detection::PowerZones; use rog_aura::aura_detection::PowerZones;
use rog_aura::usb::AuraDevice; use rog_aura::usb::AuraDevice;
use rog_aura::{AdvancedAuraType, AuraModeNum, AuraZone}; use rog_aura::{AdvancedAuraType, AuraModeNum, AuraZone};
use rog_profiles::FanCurvePU;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use typeshare::typeshare; use typeshare::typeshare;
use zbus::zvariant::Type; use zbus::zvariant::Type;
@@ -31,7 +32,7 @@ pub struct ChargeSupportedFunctions {
#[derive(Serialize, Deserialize, Type, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Type, Debug, Default, Clone)]
pub struct PlatformProfileFunctions { pub struct PlatformProfileFunctions {
pub platform_profile: bool, pub platform_profile: bool,
pub fan_curves: bool, pub fans: Vec<FanCurvePU>,
} }
#[typeshare] #[typeshare]
@@ -106,7 +107,7 @@ impl fmt::Display for PlatformProfileFunctions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Platform profiles:")?; writeln!(f, "Platform profiles:")?;
writeln!(f, "\tplatform: {}", self.platform_profile)?; writeln!(f, "\tplatform: {}", self.platform_profile)?;
writeln!(f, "\tfan curves: {}", self.fan_curves) writeln!(f, "\tfan curves: {:?}", self.fans)
} }
} }
impl fmt::Display for LedSupportedFunctions { impl fmt::Display for LedSupportedFunctions {

View File

@@ -10,6 +10,7 @@ default = ["dbus"]
dbus = ["zbus"] dbus = ["zbus"]
[dependencies] [dependencies]
log.workspace = true
udev.workspace = true udev.workspace = true
serde.workspace = true serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true

View File

@@ -1,3 +1,4 @@
use log::trace;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use typeshare::typeshare; use typeshare::typeshare;
use udev::Device; use udev::Device;
@@ -8,7 +9,8 @@ use crate::error::ProfileError;
use crate::FanCurvePU; use crate::FanCurvePU;
pub(crate) fn pwm_str(fan: char, index: usize) -> String { pub(crate) fn pwm_str(fan: char, index: usize) -> String {
let mut buf = "pwm1_auto_point1_pwm".to_owned(); // The char 'X' is replaced via indexing
let mut buf = "pwmX_auto_pointX_pwm".to_owned();
unsafe { unsafe {
let tmp = buf.as_bytes_mut(); let tmp = buf.as_bytes_mut();
tmp[3] = fan as u8; tmp[3] = fan as u8;
@@ -18,7 +20,8 @@ pub(crate) fn pwm_str(fan: char, index: usize) -> String {
} }
pub(crate) fn temp_str(fan: char, index: usize) -> String { pub(crate) fn temp_str(fan: char, index: usize) -> String {
let mut buf = "pwm1_auto_point1_temp".to_owned(); // The char 'X' is replaced via indexing
let mut buf = "pwmX_auto_pointX_temp".to_owned();
unsafe { unsafe {
let tmp = buf.as_bytes_mut(); let tmp = buf.as_bytes_mut();
tmp[3] = fan as u8; tmp[3] = fan as u8;
@@ -34,6 +37,7 @@ pub struct CurveData {
pub fan: FanCurvePU, pub fan: FanCurvePU,
pub pwm: [u8; 8], pub pwm: [u8; 8],
pub temp: [u8; 8], pub temp: [u8; 8],
pub enabled: bool,
} }
impl From<&CurveData> for String { impl From<&CurveData> for String {
@@ -65,7 +69,7 @@ impl std::str::FromStr for CurveData {
type Err = ProfileError; type Err = ProfileError;
/// Parse a string to the correct values that the fan curve kernel driver /// Parse a string to the correct values that the fan curve kernel driver
/// expects /// expects. The returned `CurveData` is not enabled by default.
/// ///
/// If the fan curve is given with percentage char '%' then the fan power /// If the fan curve is given with percentage char '%' then the fan power
/// values are converted otherwise the expected fan power range is /// values are converted otherwise the expected fan power range is
@@ -126,6 +130,7 @@ impl std::str::FromStr for CurveData {
fan: FanCurvePU::CPU, fan: FanCurvePU::CPU,
pwm, pwm,
temp, temp,
enabled: false,
}) })
} }
} }
@@ -144,114 +149,39 @@ impl CurveData {
} }
} }
fn read_from_device(&mut self, device: &Device) { pub fn read_from_device(&mut self, device: &Device) {
for attr in device.attributes() { for attr in device.attributes() {
let tmp = attr.name().to_string_lossy(); let tmp = attr.name().to_string_lossy();
if tmp.starts_with("pwm1") && tmp.ends_with("_temp") { let pwm_num: char = self.fan.into();
let pwm = format!("pwm{pwm_num}");
if tmp.starts_with(&pwm) && tmp.ends_with("_temp") {
Self::set_val_from_attr(tmp.as_ref(), device, &mut self.temp); Self::set_val_from_attr(tmp.as_ref(), device, &mut self.temp);
} }
if tmp.starts_with("pwm1") && tmp.ends_with("_pwm") { if tmp.starts_with(&pwm) && tmp.ends_with("_pwm") {
Self::set_val_from_attr(tmp.as_ref(), device, &mut self.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` /// Write this curve to the device fan specified by `self.fan`
fn write_to_device(&self, device: &mut Device, enable: bool) -> std::io::Result<()> { pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> {
let pwm_num = match self.fan { let pwm_num: char = self.fan.into();
FanCurvePU::CPU => '1', let enable = if self.enabled { "1" } else { "2" };
FanCurvePU::GPU => '2',
};
let enable = if enable { "1" } else { "2" };
for (index, out) in self.pwm.iter().enumerate() { for (index, out) in self.pwm.iter().enumerate() {
let pwm = pwm_str(pwm_num, index); let pwm = pwm_str(pwm_num, index);
trace!("writing {pwm}");
device.set_attribute_value(&pwm, &out.to_string())?; device.set_attribute_value(&pwm, &out.to_string())?;
} }
for (index, out) in self.temp.iter().enumerate() { for (index, out) in self.temp.iter().enumerate() {
let temp = temp_str(pwm_num, index); let temp = temp_str(pwm_num, index);
trace!("writing {temp}");
device.set_attribute_value(&temp, &out.to_string())?; device.set_attribute_value(&temp, &out.to_string())?;
} }
// Enable must be done *after* all points are written // Enable must be done *after* all points are written
match self.fan { device.set_attribute_value(format!("pwm{pwm_num}_enable"), enable)
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
#[typeshare]
#[cfg_attr(feature = "dbus", derive(Type))]
#[derive(Deserialize, Serialize, Debug, Clone)]
pub struct FanCurveSet {
pub enabled: bool,
pub cpu: CurveData,
pub gpu: CurveData,
}
impl Default for FanCurveSet {
fn default() -> Self {
Self {
enabled: false,
cpu: CurveData {
fan: FanCurvePU::CPU,
pwm: [0u8; 8],
temp: [0u8; 8],
},
gpu: CurveData {
fan: FanCurvePU::GPU,
pwm: [0u8; 8],
temp: [0u8; 8],
},
}
}
}
impl From<&FanCurveSet> for String {
fn from(s: &FanCurveSet) -> Self {
format!(
"Enabled: {}, {}, {}",
s.enabled,
String::from(&s.cpu),
String::from(&s.gpu),
)
}
}
impl FanCurveSet {
pub(crate) fn read_cpu_from_device(&mut self, device: &Device) {
self.cpu.read_from_device(device);
}
pub(crate) fn read_gpu_from_device(&mut self, device: &Device) {
self.gpu.read_from_device(device);
}
pub(crate) fn write_cpu_fan(&mut self, device: &mut Device) -> std::io::Result<()> {
self.cpu.init_if_zeroed(device)?;
self.cpu.write_to_device(device, self.enabled)
}
pub(crate) fn write_gpu_fan(&mut self, device: &mut Device) -> std::io::Result<()> {
self.gpu.init_if_zeroed(device)?;
self.gpu.write_to_device(device, self.enabled)
} }
} }
@@ -315,14 +245,14 @@ mod tests {
assert_eq!(temp_str('1', 7), "pwm1_auto_point8_temp"); assert_eq!(temp_str('1', 7), "pwm1_auto_point8_temp");
} }
#[test] // #[test]
fn set_to_string() { // fn set_to_string() {
let set = FanCurveSet::default(); // let set = FanCurveSet::default();
let string = String::from(&set); // let string = String::from(&set);
assert_eq!( // assert_eq!(
string.as_str(), // string.as_str(),
"Enabled: false, CPU: 0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%, GPU: \ // "Enabled: false, CPU:
0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%" // 0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%, GPU: \ 0c:
); // 0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%,0c:0%" );
} // }
} }

View File

@@ -7,7 +7,8 @@ use std::io::Write;
use std::path::Path; use std::path::Path;
use error::ProfileError; use error::ProfileError;
use fan_curve_set::{CurveData, FanCurveSet}; use fan_curve_set::CurveData;
use log::debug;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use typeshare::typeshare; use typeshare::typeshare;
use udev::Device; use udev::Device;
@@ -131,10 +132,30 @@ impl Display for Profile {
#[typeshare] #[typeshare]
#[cfg_attr(feature = "dbus", derive(Type), zvariant(signature = "s"))] #[cfg_attr(feature = "dbus", derive(Type), zvariant(signature = "s"))]
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone, Copy)] #[derive(Deserialize, Serialize, Debug, Hash, PartialEq, Eq, Clone, Copy)]
pub enum FanCurvePU { pub enum FanCurvePU {
CPU, CPU,
GPU, GPU,
MID,
}
impl FanCurvePU {
fn which_fans(device: &Device) -> Vec<Self> {
let mut fans = Vec::with_capacity(3);
for fan in [Self::CPU, Self::GPU, Self::MID] {
let pwm_num: char = fan.into();
let pwm_enable = format!("pwm{pwm_num}_enable");
debug!("Looking for {pwm_enable}");
for attr in device.attributes() {
let tmp = attr.name().to_string_lossy();
if tmp.contains(&pwm_enable) {
debug!("Found {pwm_enable}");
fans.push(fan);
}
}
}
fans
}
} }
impl From<FanCurvePU> for &str { impl From<FanCurvePU> for &str {
@@ -142,6 +163,17 @@ impl From<FanCurvePU> for &str {
match pu { match pu {
FanCurvePU::CPU => "cpu", FanCurvePU::CPU => "cpu",
FanCurvePU::GPU => "gpu", FanCurvePU::GPU => "gpu",
FanCurvePU::MID => "mid",
}
}
}
impl From<FanCurvePU> for char {
fn from(pu: FanCurvePU) -> char {
match pu {
FanCurvePU::CPU => '1',
FanCurvePU::GPU => '2',
FanCurvePU::MID => '3',
} }
} }
} }
@@ -153,6 +185,7 @@ impl std::str::FromStr for FanCurvePU {
match fan.to_ascii_lowercase().trim() { match fan.to_ascii_lowercase().trim() {
"cpu" => Ok(FanCurvePU::CPU), "cpu" => Ok(FanCurvePU::CPU),
"gpu" => Ok(FanCurvePU::GPU), "gpu" => Ok(FanCurvePU::GPU),
"mid" => Ok(FanCurvePU::MID),
_ => Err(ProfileError::ParseProfileName), _ => Err(ProfileError::ParseProfileName),
} }
} }
@@ -169,9 +202,9 @@ impl Default for FanCurvePU {
#[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 {
pub balanced: FanCurveSet, pub balanced: Vec<CurveData>,
pub performance: FanCurveSet, pub performance: Vec<CurveData>,
pub quiet: FanCurveSet, pub quiet: Vec<CurveData>,
} }
impl FanCurveProfiles { impl FanCurveProfiles {
@@ -180,35 +213,49 @@ impl FanCurveProfiles {
enumerator.match_subsystem("hwmon")?; enumerator.match_subsystem("hwmon")?;
for device in enumerator.scan_devices()? { for device in enumerator.scan_devices()? {
// if device.parent_with_subsystem("platform")?.is_some() {
if let Some(name) = device.attribute_value("name") { if let Some(name) = device.attribute_value("name") {
if name == "asus_custom_fan_curve" { if name == "asus_custom_fan_curve" {
debug!("asus_custom_fan_curve found");
return Ok(device); return Ok(device);
} }
} }
// }
} }
Err(ProfileError::NotSupported) Err(ProfileError::NotSupported)
} }
pub fn is_supported() -> Result<bool, ProfileError> { /// Return an array of `FanCurvePU`. An empty array indicates no support for
if Self::get_device().is_ok() { /// Curves.
return Ok(true); pub fn supported_fans() -> Result<Vec<FanCurvePU>, ProfileError> {
} let device = Self::get_device()?;
Ok(FanCurvePU::which_fans(&device))
Ok(false)
} }
/// ///
pub fn read_from_dev_profile(&mut self, profile: Profile, device: &Device) { pub fn read_from_dev_profile(
let mut tmp = FanCurveSet::default(); &mut self,
tmp.read_cpu_from_device(device); profile: Profile,
tmp.read_gpu_from_device(device); device: &Device,
match profile { ) -> Result<(), ProfileError> {
Profile::Balanced => self.balanced = tmp, let fans = Self::supported_fans()?;
Profile::Performance => self.performance = tmp, let mut curves = Vec::with_capacity(3);
Profile::Quiet => self.quiet = tmp,
for fan in fans {
let mut curve = CurveData {
fan,
..Default::default()
};
debug!("Reading curve for {fan:?}");
curve.read_from_device(device);
debug!("Curve: {curve:?}");
curves.push(curve);
} }
match profile {
Profile::Balanced => self.balanced = curves,
Profile::Performance => self.performance = curves,
Profile::Quiet => self.quiet = curves,
}
Ok(())
} }
/// Reset the stored (self) and device curve to the defaults of the /// Reset the stored (self) and device curve to the defaults of the
@@ -220,19 +267,15 @@ impl FanCurveProfiles {
&mut self, &mut self,
profile: Profile, profile: Profile,
device: &mut Device, device: &mut Device,
) -> std::io::Result<()> { ) -> Result<(), ProfileError> {
// Do reset let fans = Self::supported_fans()?;
device.set_attribute_value("pwm1_enable", "3")?; // Do reset for all
device.set_attribute_value("pwm2_enable", "3")?; for fan in fans {
// Then read let pwm_num: char = fan.into();
let mut tmp = FanCurveSet::default(); let pwm = format!("pwm{pwm_num}_enable");
tmp.read_cpu_from_device(device); device.set_attribute_value(&pwm, "3")?;
tmp.read_gpu_from_device(device);
match profile {
Profile::Balanced => self.balanced = tmp,
Profile::Performance => self.performance = tmp,
Profile::Quiet => self.quiet = tmp,
} }
self.read_from_dev_profile(profile, device)?;
Ok(()) Ok(())
} }
@@ -250,50 +293,34 @@ impl FanCurveProfiles {
Profile::Performance => &mut self.performance, Profile::Performance => &mut self.performance,
Profile::Quiet => &mut self.quiet, Profile::Quiet => &mut self.quiet,
}; };
fans.write_cpu_fan(device)?; for fan in fans {
fans.write_gpu_fan(device)?; debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}");
fan.write_to_device(device)?;
}
Ok(()) Ok(())
} }
pub fn get_enabled_curve_profiles(&self) -> Vec<Profile> {
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_profile_curve_enabled(&mut self, profile: Profile, enabled: bool) { pub fn set_profile_curve_enabled(&mut self, profile: Profile, enabled: bool) {
match profile { match profile {
Profile::Balanced => self.balanced.enabled = enabled, Profile::Balanced => {
Profile::Performance => self.performance.enabled = enabled, for curve in self.balanced.iter_mut() {
Profile::Quiet => self.quiet.enabled = enabled, curve.enabled = enabled;
}
}
Profile::Performance => {
for curve in self.performance.iter_mut() {
curve.enabled = enabled;
}
}
Profile::Quiet => {
for curve in self.quiet.iter_mut() {
curve.enabled = enabled;
}
}
} }
} }
pub fn get_all_fan_curves(&self) -> Vec<FanCurveSet> { pub fn get_fan_curves_for(&self, name: Profile) -> &[CurveData] {
vec![
self.balanced.clone(),
self.performance.clone(),
self.quiet.clone(),
]
}
pub fn get_active_fan_curves(&self) -> Result<&FanCurveSet, ProfileError> {
match Profile::get_active_profile()? {
Profile::Balanced => Ok(&self.balanced),
Profile::Performance => Ok(&self.performance),
Profile::Quiet => Ok(&self.quiet),
}
}
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,
@@ -301,37 +328,59 @@ impl FanCurveProfiles {
} }
} }
pub fn get_fan_curve_for(&self, name: &Profile, pu: &FanCurvePU) -> &CurveData { pub fn get_fan_curve_for(&self, name: &Profile, pu: FanCurvePU) -> Option<&CurveData> {
match name { match name {
Profile::Balanced => match pu { Profile::Balanced => {
FanCurvePU::CPU => &self.balanced.cpu, for this_curve in self.balanced.iter() {
FanCurvePU::GPU => &self.balanced.gpu, if this_curve.fan == pu {
}, return Some(this_curve);
Profile::Performance => match pu {
FanCurvePU::CPU => &self.performance.cpu,
FanCurvePU::GPU => &self.performance.gpu,
},
Profile::Quiet => match pu {
FanCurvePU::CPU => &self.quiet.cpu,
FanCurvePU::GPU => &self.quiet.gpu,
},
} }
} }
}
Profile::Performance => {
for this_curve in self.performance.iter() {
if this_curve.fan == pu {
return Some(this_curve);
}
}
}
Profile::Quiet => {
for this_curve in self.quiet.iter() {
if this_curve.fan == pu {
return Some(this_curve);
}
}
}
}
None
}
pub fn save_fan_curve(&mut self, curve: CurveData, profile: Profile) -> std::io::Result<()> { pub fn save_fan_curve(&mut self, curve: CurveData, profile: Profile) -> std::io::Result<()> {
match profile { match profile {
Profile::Balanced => match curve.fan { Profile::Balanced => {
FanCurvePU::CPU => self.balanced.cpu = curve, for this_curve in self.balanced.iter_mut() {
FanCurvePU::GPU => self.balanced.gpu = curve, if this_curve.fan == curve.fan {
}, *this_curve = curve;
Profile::Performance => match curve.fan { break;
FanCurvePU::CPU => self.performance.cpu = curve, }
FanCurvePU::GPU => self.performance.gpu = curve, }
}, }
Profile::Quiet => match curve.fan { Profile::Performance => {
FanCurvePU::CPU => self.quiet.cpu = curve, for this_curve in self.performance.iter_mut() {
FanCurvePU::GPU => self.quiet.gpu = curve, if this_curve.fan == curve.fan {
}, *this_curve = curve;
break;
}
}
}
Profile::Quiet => {
for this_curve in self.quiet.iter_mut() {
if this_curve.fan == curve.fan {
*this_curve = curve;
break;
}
}
}
} }
Ok(()) Ok(())
} }