mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-01-22 09:23:19 +01:00
profiles: add mid fan curve support
This commit is contained in:
@@ -10,6 +10,7 @@ default = ["dbus"]
|
||||
dbus = ["zbus"]
|
||||
|
||||
[dependencies]
|
||||
log.workspace = true
|
||||
udev.workspace = true
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use log::trace;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
use udev::Device;
|
||||
@@ -8,7 +9,8 @@ use crate::error::ProfileError;
|
||||
use crate::FanCurvePU;
|
||||
|
||||
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 {
|
||||
let tmp = buf.as_bytes_mut();
|
||||
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 {
|
||||
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 {
|
||||
let tmp = buf.as_bytes_mut();
|
||||
tmp[3] = fan as u8;
|
||||
@@ -34,6 +37,7 @@ pub struct CurveData {
|
||||
pub fan: FanCurvePU,
|
||||
pub pwm: [u8; 8],
|
||||
pub temp: [u8; 8],
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl From<&CurveData> for String {
|
||||
@@ -65,7 +69,7 @@ impl std::str::FromStr for CurveData {
|
||||
type Err = ProfileError;
|
||||
|
||||
/// 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
|
||||
/// values are converted otherwise the expected fan power range is
|
||||
@@ -126,6 +130,7 @@ impl std::str::FromStr for CurveData {
|
||||
fan: FanCurvePU::CPU,
|
||||
pwm,
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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" };
|
||||
pub fn write_to_device(&self, device: &mut Device) -> std::io::Result<()> {
|
||||
let pwm_num: char = self.fan.into();
|
||||
let enable = if self.enabled { "1" } else { "2" };
|
||||
|
||||
for (index, out) in self.pwm.iter().enumerate() {
|
||||
let pwm = pwm_str(pwm_num, index);
|
||||
trace!("writing {pwm}");
|
||||
device.set_attribute_value(&pwm, &out.to_string())?;
|
||||
}
|
||||
|
||||
for (index, out) in self.temp.iter().enumerate() {
|
||||
let temp = temp_str(pwm_num, index);
|
||||
trace!("writing {temp}");
|
||||
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
|
||||
#[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)
|
||||
device.set_attribute_value(format!("pwm{pwm_num}_enable"), enable)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,14 +245,14 @@ mod tests {
|
||||
assert_eq!(temp_str('1', 7), "pwm1_auto_point8_temp");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_to_string() {
|
||||
let set = FanCurveSet::default();
|
||||
let string = String::from(&set);
|
||||
assert_eq!(
|
||||
string.as_str(),
|
||||
"Enabled: false, CPU: 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%"
|
||||
);
|
||||
}
|
||||
// #[test]
|
||||
// fn set_to_string() {
|
||||
// let set = FanCurveSet::default();
|
||||
// let string = String::from(&set);
|
||||
// assert_eq!(
|
||||
// string.as_str(),
|
||||
// "Enabled: false, CPU:
|
||||
// 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%" );
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
use error::ProfileError;
|
||||
use fan_curve_set::{CurveData, FanCurveSet};
|
||||
use fan_curve_set::CurveData;
|
||||
use log::debug;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
use udev::Device;
|
||||
@@ -131,10 +132,30 @@ impl Display for Profile {
|
||||
|
||||
#[typeshare]
|
||||
#[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 {
|
||||
CPU,
|
||||
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 {
|
||||
@@ -142,6 +163,17 @@ impl From<FanCurvePU> for &str {
|
||||
match pu {
|
||||
FanCurvePU::CPU => "cpu",
|
||||
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() {
|
||||
"cpu" => Ok(FanCurvePU::CPU),
|
||||
"gpu" => Ok(FanCurvePU::GPU),
|
||||
"mid" => Ok(FanCurvePU::MID),
|
||||
_ => Err(ProfileError::ParseProfileName),
|
||||
}
|
||||
}
|
||||
@@ -169,9 +202,9 @@ impl Default for FanCurvePU {
|
||||
#[cfg_attr(feature = "dbus", derive(Type))]
|
||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
||||
pub struct FanCurveProfiles {
|
||||
pub balanced: FanCurveSet,
|
||||
pub performance: FanCurveSet,
|
||||
pub quiet: FanCurveSet,
|
||||
pub balanced: Vec<CurveData>,
|
||||
pub performance: Vec<CurveData>,
|
||||
pub quiet: Vec<CurveData>,
|
||||
}
|
||||
|
||||
impl FanCurveProfiles {
|
||||
@@ -180,35 +213,49 @@ impl FanCurveProfiles {
|
||||
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" {
|
||||
debug!("asus_custom_fan_curve found");
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
Err(ProfileError::NotSupported)
|
||||
}
|
||||
|
||||
pub fn is_supported() -> Result<bool, ProfileError> {
|
||||
if Self::get_device().is_ok() {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
/// Return an array of `FanCurvePU`. An empty array indicates no support for
|
||||
/// Curves.
|
||||
pub fn supported_fans() -> Result<Vec<FanCurvePU>, ProfileError> {
|
||||
let device = Self::get_device()?;
|
||||
Ok(FanCurvePU::which_fans(&device))
|
||||
}
|
||||
|
||||
///
|
||||
pub fn read_from_dev_profile(&mut self, profile: Profile, device: &Device) {
|
||||
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,
|
||||
pub fn read_from_dev_profile(
|
||||
&mut self,
|
||||
profile: Profile,
|
||||
device: &Device,
|
||||
) -> Result<(), ProfileError> {
|
||||
let fans = Self::supported_fans()?;
|
||||
let mut curves = Vec::with_capacity(3);
|
||||
|
||||
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
|
||||
@@ -220,19 +267,15 @@ impl FanCurveProfiles {
|
||||
&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,
|
||||
) -> Result<(), ProfileError> {
|
||||
let fans = Self::supported_fans()?;
|
||||
// Do reset for all
|
||||
for fan in fans {
|
||||
let pwm_num: char = fan.into();
|
||||
let pwm = format!("pwm{pwm_num}_enable");
|
||||
device.set_attribute_value(&pwm, "3")?;
|
||||
}
|
||||
self.read_from_dev_profile(profile, device)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -250,50 +293,34 @@ impl FanCurveProfiles {
|
||||
Profile::Performance => &mut self.performance,
|
||||
Profile::Quiet => &mut self.quiet,
|
||||
};
|
||||
fans.write_cpu_fan(device)?;
|
||||
fans.write_gpu_fan(device)?;
|
||||
for fan in fans {
|
||||
debug!("write_profile_curve_to_platform: writing profile:{profile}, {fan:?}");
|
||||
fan.write_to_device(device)?;
|
||||
}
|
||||
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) {
|
||||
match profile {
|
||||
Profile::Balanced => self.balanced.enabled = enabled,
|
||||
Profile::Performance => self.performance.enabled = enabled,
|
||||
Profile::Quiet => self.quiet.enabled = enabled,
|
||||
Profile::Balanced => {
|
||||
for curve in self.balanced.iter_mut() {
|
||||
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> {
|
||||
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 {
|
||||
pub fn get_fan_curves_for(&self, name: Profile) -> &[CurveData] {
|
||||
match name {
|
||||
Profile::Balanced => &self.balanced,
|
||||
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 {
|
||||
Profile::Balanced => match pu {
|
||||
FanCurvePU::CPU => &self.balanced.cpu,
|
||||
FanCurvePU::GPU => &self.balanced.gpu,
|
||||
},
|
||||
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::Balanced => {
|
||||
for this_curve in self.balanced.iter() {
|
||||
if this_curve.fan == pu {
|
||||
return Some(this_curve);
|
||||
}
|
||||
}
|
||||
}
|
||||
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<()> {
|
||||
match profile {
|
||||
Profile::Balanced => match curve.fan {
|
||||
FanCurvePU::CPU => self.balanced.cpu = curve,
|
||||
FanCurvePU::GPU => self.balanced.gpu = curve,
|
||||
},
|
||||
Profile::Performance => match curve.fan {
|
||||
FanCurvePU::CPU => self.performance.cpu = curve,
|
||||
FanCurvePU::GPU => self.performance.gpu = curve,
|
||||
},
|
||||
Profile::Quiet => match curve.fan {
|
||||
FanCurvePU::CPU => self.quiet.cpu = curve,
|
||||
FanCurvePU::GPU => self.quiet.gpu = curve,
|
||||
},
|
||||
Profile::Balanced => {
|
||||
for this_curve in self.balanced.iter_mut() {
|
||||
if this_curve.fan == curve.fan {
|
||||
*this_curve = curve;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Profile::Performance => {
|
||||
for this_curve in self.performance.iter_mut() {
|
||||
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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user