Skip to main content

cpu_temp/temperature/
hwmon.rs

1// Hwmon temperature reader for Linux
2// Reads temperatures from /sys/class/hwmon
3
4use std::fs;
5use std::path::PathBuf;
6
7/// Represents a hwmon sensor path and its prefix
8#[derive(Clone)]
9pub struct HwmonSensor {
10    /// Path to the hwmon directory (e.g., /sys/class/hwmon/hwmon0)
11    pub path: PathBuf,
12    /// Sensor name (from name file)
13    pub name: String,
14}
15
16/// Find all hwmon devices that represent CPU temperature sensors
17pub fn find_cpu_hwmon_sensors() -> anyhow::Result<Vec<HwmonSensor>> {
18    let mut sensors = Vec::new();
19
20    for entry in fs::read_dir("/sys/class/hwmon")? {
21        let entry = entry?;
22        let path = entry.path();
23
24        if !path.is_dir() {
25            continue;
26        }
27
28        let name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
29
30        // Read the hwmon name
31        let hwmon_name_path = path.join("name");
32        let hwmon_name = fs::read_to_string(&hwmon_name_path).ok();
33
34        // Check if this is an Intel CPU sensor (intel_ platform name or has temp files)
35        if let Some(ref name_str) = hwmon_name {
36            if name_str.contains("coretemp") || name_str.contains("cpu") {
37                let sensor = HwmonSensor {
38                    path: path.clone(),
39                    name: name_str.clone(),
40                };
41                sensors.push(sensor);
42                continue;
43            }
44        }
45
46        // Also check if it has temp files as a fallback
47        let temp_input_path = path.join("temp1_input");
48        if temp_input_path.exists() {
49            let sensor = HwmonSensor {
50                path: path.clone(),
51                name: hwmon_name.unwrap_or_else(|| name.to_string()),
52            };
53            sensors.push(sensor);
54        }
55    }
56
57    // Sort by name to get consistent ordering, prioritize coretemp
58    sensors.sort_by(|a, b| {
59        if a.name.contains("coretemp") {
60            std::cmp::Ordering::Less
61        } else if b.name.contains("coretemp") {
62            std::cmp::Ordering::Greater
63        } else {
64            a.name.cmp(&b.name)
65        }
66    });
67
68    Ok(sensors)
69}
70
71/// Temperature data from hwmon sensor
72#[derive(Debug, Clone)]
73pub struct HwmonTempData {
74    pub id: u32,
75    pub name: String,
76    pub temperature: f32,
77}
78
79/// Read all temperatures from a hwmon sensor
80pub fn read_hwmon_temperatures(sensor: &HwmonSensor) -> anyhow::Result<Vec<HwmonTempData>> {
81    let mut temps = Vec::new();
82    let mut id = 1;
83
84    loop {
85        let temp_label = sensor.path.join(format!("temp{}_label", id));
86        let temp_input = sensor.path.join(format!("temp{}_input", id));
87
88        if !temp_input.exists() {
89            break;
90        }
91
92        let temp_name = if temp_label.exists() {
93            fs::read_to_string(&temp_label)
94                .ok()
95                .and_then(|s| {
96                    let s = s.trim().to_string();
97                    if s.is_empty() { None } else { Some(s) }
98                })
99                .unwrap_or(format!("temp{}", id))
100        } else {
101            format!("temp{}", id)
102        };
103
104        let value = fs::read_to_string(&temp_input)?;
105        let temperature = value.trim().parse::<f32>()? / 1000.0;
106
107        temps.push(HwmonTempData {
108            id,
109            name: temp_name,
110            temperature,
111        });
112
113        id += 1;
114    }
115
116    Ok(temps)
117}
118
119/// Get TjMax for the CPU (typically 100°C for most Intel CPUs)
120/// Try to read from cpuinfo or fallback to default
121pub fn get_tj_max() -> f32 {
122    // Try reading from cpuinfo
123    let cpuinfo_path = "/proc/cpuinfo";
124    if let Ok(content) = fs::read_to_string(cpuinfo_path) {
125        for line in content.lines() {
126            if line.starts_with("cpu M") && line.contains("TjMax") {
127                if let Some(value_str) = line.split(':').nth(1) {
128                    if let Ok(value) = value_str.trim().parse::<f32>() {
129                        return value;
130                    }
131                }
132            }
133        }
134    }
135
136    // Fallback to common default values based on Intel architecture
137    // Most modern Intel CPUs have TjMax of 100°C
138    100.0
139}