mirror of
https://gitlab.com/asus-linux/asusctl.git
synced 2026-02-06 00:15:04 +01:00
311 lines
9.8 KiB
Rust
311 lines
9.8 KiB
Rust
use serde_derive::{Deserialize, Serialize};
|
|
|
|
use udev::Device;
|
|
#[cfg(feature = "dbus")]
|
|
use zvariant_derive::Type;
|
|
|
|
use crate::{error::ProfileError, FanCurvePU};
|
|
|
|
pub(crate) fn pwm_str(fan: char, index: usize) -> String {
|
|
let mut buf = "pwm1_auto_point1_pwm".to_string();
|
|
unsafe {
|
|
let tmp = buf.as_bytes_mut();
|
|
tmp[3] = fan as u8;
|
|
tmp[15] = char::from_digit(index as u32 + 1, 10).unwrap() as u8;
|
|
}
|
|
buf
|
|
}
|
|
|
|
pub(crate) fn temp_str(fan: char, index: usize) -> String {
|
|
let mut buf = "pwm1_auto_point1_temp".to_string();
|
|
unsafe {
|
|
let tmp = buf.as_bytes_mut();
|
|
tmp[3] = fan as u8;
|
|
tmp[15] = char::from_digit(index as u32 + 1, 10).unwrap() 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],
|
|
}
|
|
|
|
impl From<&CurveData> for String {
|
|
fn from(c: &CurveData) -> Self {
|
|
format!(
|
|
"{:?}: {}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%,{}c:{}%",
|
|
c.fan,
|
|
c.temp[0],
|
|
(c.pwm[0] as u32) * 100 / 255,
|
|
c.temp[1],
|
|
(c.pwm[1] as u32) * 100 / 255,
|
|
c.temp[2],
|
|
(c.pwm[2] as u32) * 100 / 255,
|
|
c.temp[3],
|
|
(c.pwm[3] as u32) * 100 / 255,
|
|
c.temp[4],
|
|
(c.pwm[4] as u32) * 100 / 255,
|
|
c.temp[5],
|
|
(c.pwm[5] as u32) * 100 / 255,
|
|
c.temp[6],
|
|
(c.pwm[6] as u32) * 100 / 255,
|
|
c.temp[7],
|
|
(c.pwm[7] as u32) * 100 / 255,
|
|
)
|
|
}
|
|
}
|
|
|
|
impl std::str::FromStr for CurveData {
|
|
type Err = ProfileError;
|
|
|
|
/// Parse a string to the correct values that the fan curve kernel driver expects
|
|
///
|
|
/// If the fan curve is given with percentage char '%' then the fan power values are converted
|
|
/// otherwise the expected fan power range is 0-255.
|
|
///
|
|
/// Temperature range is 0-255 in degrees C. You don't want to be setting over 100.
|
|
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;
|
|
let mut percentages = false;
|
|
|
|
for (index, value) in input.split(',').enumerate() {
|
|
for (select, num) in value.splitn(2, |c| c == 'c' || c == ':').enumerate() {
|
|
if num.contains('%') {
|
|
percentages = true;
|
|
}
|
|
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 {
|
|
let mut p = r;
|
|
if percentages {
|
|
if r > 100 {
|
|
return Err(ProfileError::ParseFanCurvePercentOver100(r));
|
|
}
|
|
p = (p as f32 * 2.55).round() as u8;
|
|
}
|
|
if pwm_prev > p {
|
|
return Err(ProfileError::ParseFanCurvePrevHigher(
|
|
"percentage",
|
|
pwm_prev,
|
|
p,
|
|
));
|
|
}
|
|
pwm_prev = p;
|
|
pwm[index] = p;
|
|
}
|
|
}
|
|
}
|
|
Ok(Self {
|
|
fan: FanCurvePU::CPU,
|
|
pwm,
|
|
temp,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl CurveData {
|
|
pub fn set_fan(&mut self, fan: FanCurvePU) {
|
|
self.fan = fan;
|
|
}
|
|
|
|
fn set_val_from_attr(tmp: &str, device: &Device, buf: &mut [u8; 8]) {
|
|
if let Some(n) = tmp.chars().nth(15) {
|
|
let i = n.to_digit(10).unwrap() as usize;
|
|
let d = device.attribute_value(tmp).unwrap();
|
|
let d: u8 = d.to_string_lossy().parse().unwrap();
|
|
buf[i - 1] = d;
|
|
}
|
|
}
|
|
|
|
fn read_from_device(&mut self, device: &Device) {
|
|
for attr in device.attributes() {
|
|
let tmp = attr.name().to_string_lossy();
|
|
if tmp.starts_with("pwm1") && tmp.ends_with("_temp") {
|
|
Self::set_val_from_attr(tmp.as_ref(), device, &mut self.temp)
|
|
}
|
|
if tmp.starts_with("pwm1") && tmp.ends_with("_pwm") {
|
|
Self::set_val_from_attr(tmp.as_ref(), device, &mut self.pwm)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn init_if_zeroed(&mut self, device: &mut Device) -> std::io::Result<()> {
|
|
if self.pwm == [0u8; 8] && self.temp == [0u8; 8] {
|
|
// Need to reset the device to defaults to get the proper profile defaults
|
|
match self.fan {
|
|
FanCurvePU::CPU => device.set_attribute_value("pwm1_enable", "3")?,
|
|
FanCurvePU::GPU => device.set_attribute_value("pwm2_enable", "3")?,
|
|
};
|
|
self.read_from_device(device);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Write this curve to the device fan specified by `self.fan`
|
|
fn write_to_device(&self, device: &mut Device, enable: bool) -> std::io::Result<()> {
|
|
let pwm_num = match self.fan {
|
|
FanCurvePU::CPU => '1',
|
|
FanCurvePU::GPU => '2',
|
|
};
|
|
let enable = if enable { "1" } else { "2" };
|
|
|
|
for (index, out) in self.pwm.iter().enumerate() {
|
|
let pwm = pwm_str(pwm_num, index);
|
|
device.set_attribute_value(&pwm, &out.to_string())?;
|
|
}
|
|
|
|
for (index, out) in self.temp.iter().enumerate() {
|
|
let temp = temp_str(pwm_num, index);
|
|
device.set_attribute_value(&temp, &out.to_string())?;
|
|
}
|
|
|
|
// Enable must be done *after* all points are written
|
|
match self.fan {
|
|
FanCurvePU::CPU => device.set_attribute_value("pwm1_enable", enable)?,
|
|
FanCurvePU::GPU => device.set_attribute_value("pwm2_enable", enable)?,
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// A `FanCurveSet` contains both CPU and GPU fan curve data
|
|
#[cfg_attr(feature = "dbus", derive(Type))]
|
|
#[derive(Deserialize, Serialize, Debug, Clone)]
|
|
pub struct FanCurveSet {
|
|
pub 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)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::str::FromStr;
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn curve_data_from_str_to_str() {
|
|
let curve =
|
|
CurveData::from_str("30c:1%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%")
|
|
.unwrap();
|
|
assert_eq!(curve.fan, FanCurvePU::CPU);
|
|
assert_eq!(curve.temp, [30, 49, 59, 69, 79, 89, 99, 109]);
|
|
assert_eq!(curve.pwm, [3, 5, 8, 10, 79, 125, 143, 148]);
|
|
|
|
let string: String = (&curve).into();
|
|
// End result is slightly different due to type conversions and rounding errors
|
|
assert_eq!(
|
|
string.as_str(),
|
|
"CPU: 30c:1%,49c:1%,59c:3%,69c:3%,79c:30%,89c:49%,99c:56%,109c:58%"
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn curve_data_from_str_simple() {
|
|
let curve = CurveData::from_str("30:1,49:2,59:3,69:4,79:31,89:49,99:56,109:58").unwrap();
|
|
assert_eq!(curve.fan, FanCurvePU::CPU);
|
|
assert_eq!(curve.temp, [30, 49, 59, 69, 79, 89, 99, 109]);
|
|
assert_eq!(curve.pwm, [1, 2, 3, 4, 31, 49, 56, 58]);
|
|
}
|
|
|
|
#[test]
|
|
fn curve_data_from_str_invalid_pwm() {
|
|
let curve =
|
|
CurveData::from_str("30c:4%,49c:2%,59c:3%,69c:4%,79c:31%,89c:49%,99c:56%,109c:58%");
|
|
assert!(&curve.is_err());
|
|
assert!(matches!(
|
|
curve,
|
|
Err(ProfileError::ParseFanCurvePrevHigher(_, _, _))
|
|
));
|
|
}
|
|
|
|
#[test]
|
|
fn check_pwm_str() {
|
|
assert_eq!(pwm_str('1', 0), "pwm1_auto_point1_pwm");
|
|
assert_eq!(pwm_str('1', 4), "pwm1_auto_point5_pwm");
|
|
assert_eq!(pwm_str('1', 7), "pwm1_auto_point8_pwm");
|
|
}
|
|
|
|
#[test]
|
|
fn check_temp_str() {
|
|
assert_eq!(temp_str('1', 0), "pwm1_auto_point1_temp");
|
|
assert_eq!(temp_str('1', 4), "pwm1_auto_point5_temp");
|
|
assert_eq!(temp_str('1', 7), "pwm1_auto_point8_temp");
|
|
}
|
|
|
|
#[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%");
|
|
}
|
|
}
|