diff --git a/src/fan.rs b/src/fan.rs index 03fdadd..4b3db3b 100644 --- a/src/fan.rs +++ b/src/fan.rs @@ -23,9 +23,11 @@ pub enum FanDaemonError { pub struct FanDaemon { curve: FanCurve, + curve_nvme: FanCurve, amdgpus: Vec, platforms: Vec, cpus: Vec, + nvmes: Vec, nvidia_exists: bool, displayed_warning: Cell, } @@ -41,9 +43,11 @@ impl FanDaemon { "thelio-massive-b1" => FanCurve::xeon(), _ => FanCurve::standard(), }, + curve_nvme: FanCurve::nvme(), amdgpus: Vec::new(), platforms: Vec::new(), cpus: Vec::new(), + nvmes: Vec::new(), nvidia_exists, displayed_warning: Cell::new(false), }; @@ -60,6 +64,7 @@ impl FanDaemon { self.amdgpus.clear(); self.platforms.clear(); self.cpus.clear(); + self.nvmes.clear(); for hwmon in HwMon::all().map_err(FanDaemonError::HwmonDevices)? { if let Ok(name) = hwmon.name() { @@ -70,6 +75,7 @@ impl FanDaemon { "system76" => (), // TODO: Support laptops "system76_io" | "system76_thelio_io" => self.platforms.push(hwmon), "coretemp" | "k10temp" => self.cpus.push(hwmon), + "nvme" => self.nvmes.push(hwmon), _ => (), } } @@ -83,6 +89,10 @@ impl FanDaemon { return Err(FanDaemonError::CpuHwmonNotFound); } + if self.nvmes.is_empty() { + // No error. + } + Ok(()) } @@ -128,6 +138,25 @@ impl FanDaemon { temp_opt } + /// Get the maximum measured temperature from any NVME on the system, in thousandths of a + /// Celsius. Thousandths celsius is the standard Linux hwmon temperature unit. + pub fn get_nvme_temp(&self) -> Option { + self + .nvmes + .iter() + .filter_map(|sensor| sensor.temp(1).ok()) + .filter_map(|temp| temp.input().ok()) + .fold(None, |mut temp_opt, input| { + // Assume temperatures are always above freezing + if temp_opt.map_or(true, |x| input as u32 > x) { + log::debug!("highest hwmon nvme temp: {}", input); + temp_opt = Some(input as u32); + } + + temp_opt + }) + } + /// Get the correct duty cycle for a temperature in thousandths Celsius, from 0 to 255 /// Thousandths celsius is the standard Linux hwmon temperature unit /// 0 to 255 is the standard Linux hwmon pwm unit @@ -137,6 +166,15 @@ impl FanDaemon { .map(|duty| (((u32::from(duty)) * 255) / 10_000) as u8) } + /// Get the correct duty cycle for an NVME temperature in thousandths Celsius, from 0 to 255 + /// Thousandths celsius is the standard Linux hwmon temperature unit + /// 0 to 255 is the standard Linux hwmon pwm unit + pub fn get_nvme_duty(&self, temp: u32) -> Option { + self.curve_nvme + .get_duty((temp / 10) as i16) + .map(|duty| (((u32::from(duty)) * 255) / 10_000) as u8) + } + /// Set the current duty cycle, from 0 to 255 /// 0 to 255 is the standard Linux hwmon pwm unit pub fn set_duty(&self, duty_opt: Option) { @@ -159,7 +197,20 @@ impl FanDaemon { /// Calculate the correct duty cycle and apply it to all fans pub fn step(&mut self) { if let Ok(()) = self.discover() { - self.set_duty(self.get_temp().and_then(|temp| self.get_duty(temp))); + self.set_duty({ + let cpu_duty = self.get_temp().and_then(|temp| self.get_duty(temp)); + log::debug!("cpu duty: {:?}", cpu_duty); + if self.nvmes.is_empty() { + cpu_duty + } else { + let nvme_duty = self.get_nvme_temp().and_then(|temp| self.get_nvme_duty(temp)); + log::debug!("nvme duty: {:?}", nvme_duty); + match(cpu_duty, nvme_duty) { + (Some(cpu_duty), Some(nvme_duty)) => Some(cmp::max(cpu_duty, nvme_duty)), + _ => None, + } + } + }); } } } @@ -282,6 +333,15 @@ impl FanCurve { .append(78_00, 100_00) } + /// Fan curve for NVME drives + pub fn nvme() -> Self { + Self::default() + .append(00_00, 00_00) + .append(60_00, 00_00) + .append(65_00, 70_00) + .append(68_00, 100_00) + } + pub fn get_duty(&self, temp: i16) -> Option { // If the temp is less than the first point, return the first point duty if let Some(first) = self.points.first() {