coretempd: Add AMD Zen temperature sensor support

Detect CPU vendor by probing MSRs (Intel IA32_THERM_STATUS vs AMD
TCTL MSR C0010293). Support both Intel Tjmax-based and AMD direct
temperature reading. Log detected vendor per CPU at startup.
This commit is contained in:
2026-05-20 18:52:01 +03:00
parent 2c7de8dea6
commit e5b82a644a
2 changed files with 67 additions and 15 deletions
@@ -1,13 +1,21 @@
use std::fs;
use std::io::{Read, Write};
use std::os::unix::net::UnixListener;
use std::path::Path;
use std::thread;
use std::time::Duration;
const POLL_MS: u64 = 2000;
const IA32_THERM_STATUS: u32 = 0x19c;
const IA32_TEMPERATURE_TARGET: u32 = 0x1a2;
const POLL_MS: u64 = 2000;
const AMD_TCTL: u32 = 0xc0010293;
#[derive(Clone, Copy, Debug, PartialEq)]
enum Vendor {
Intel,
Amd,
Unknown,
}
fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
let path = format!("/scheme/sys/msr/{}/{:x}", cpu, msr);
@@ -15,6 +23,16 @@ fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
.and_then(|s| u64::from_str_radix(s.trim(), 16).ok())
}
fn detect_vendor(cpu: u32) -> Vendor {
if read_msr(cpu, IA32_THERM_STATUS).is_some() {
Vendor::Intel
} else if read_msr(cpu, AMD_TCTL).is_some() {
Vendor::Amd
} else {
Vendor::Unknown
}
}
fn detect_cpus() -> Vec<u32> {
let mut v = Vec::new();
if let Ok(d) = fs::read_to_string("/scheme/sys/cpu") {
@@ -30,7 +48,7 @@ fn detect_cpus() -> Vec<u32> {
v
}
fn read_temperature(cpu: u32, tjmax: u8) -> Option<i16> {
fn read_temperature_intel(cpu: u32, tjmax: u8) -> Option<i16> {
let raw = read_msr(cpu, IA32_THERM_STATUS)?;
let digital_readout = ((raw >> 16) & 0x7F) as u8;
if digital_readout == 0 { return None; }
@@ -38,7 +56,7 @@ fn read_temperature(cpu: u32, tjmax: u8) -> Option<i16> {
Some(temp as i16)
}
fn read_tjmax(cpu: u32) -> u8 {
fn read_tjmax_intel(cpu: u32) -> u8 {
if let Some(raw) = read_msr(cpu, IA32_TEMPERATURE_TARGET) {
let tj = ((raw >> 16) & 0xFF) as u8;
if tj > 0 && tj < 150 { return tj; }
@@ -46,6 +64,21 @@ fn read_tjmax(cpu: u32) -> u8 {
100
}
fn read_temperature_amd(cpu: u32) -> Option<i16> {
let raw = read_msr(cpu, AMD_TCTL)?;
let tctl = ((raw >> 21) & 0x3FF) as u16;
if tctl == 0 { return None; }
let temp = (tctl as f32) / 8.0;
Some(temp as i16)
}
#[derive(Clone)]
struct CpuInfo {
id: u32,
vendor: Vendor,
tjmax: u8,
}
fn main() {
let scheme_path = ":coretemp";
let _ = fs::remove_file(scheme_path);
@@ -55,16 +88,29 @@ fn main() {
let cpus = detect_cpus();
eprintln!("[INFO] coretempd: detected {} CPU(s)", cpus.len());
let tjmax_values: Vec<u8> = cpus.iter().map(|&c| read_tjmax(c)).collect();
let cpu_infos: Vec<CpuInfo> = cpus.iter().map(|&id| {
let vendor = detect_vendor(id);
let tjmax = if vendor == Vendor::Intel {
read_tjmax_intel(id)
} else {
0
};
eprintln!("[INFO] coretempd: CPU {} = {:?}", id, vendor);
CpuInfo { id, vendor, tjmax }
}).collect();
let cpus_clone = cpus.clone();
let tjmax_clone = tjmax_values.clone();
let infos_clone = cpu_infos.clone();
thread::spawn(move || {
loop {
thread::sleep(Duration::from_millis(POLL_MS));
for (&cpu, &tjmax) in cpus_clone.iter().zip(&tjmax_clone) {
if let Some(temp) = read_temperature(cpu, tjmax) {
let _ = fs::write(format!("/tmp/coretemp_cpu{}", cpu), format!("{}\n", temp));
for info in &infos_clone {
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
if let Some(t) = temp {
let _ = fs::write(format!("/tmp/coretemp_cpu{}", info.id), format!("{}\n", t));
}
}
}
@@ -77,15 +123,20 @@ fn main() {
let req = String::from_utf8_lossy(&buf[..n]).trim().to_string();
let resp = if req == "/" {
let mut names = String::new();
for &cpu in &cpus {
names.push_str(&format!("cpu{}\n", cpu));
for info in &cpu_infos {
names.push_str(&format!("cpu{}\n", info.id));
}
names
} else if let Some(cpu_str) = req.strip_prefix("/cpu") {
if let Ok(cpu) = cpu_str.parse::<u32>() {
if let Some(idx) = cpus.iter().position(|&c| c == cpu) {
if let Some(temp) = read_temperature(cpu, tjmax_values[idx]) {
format!("{}\n", temp)
if let Some(info) = cpu_infos.iter().find(|i| i.id == cpu) {
let temp = match info.vendor {
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
Vendor::Amd => read_temperature_amd(info.id),
Vendor::Unknown => None,
};
if let Some(t) = temp {
format!("{}\n", t)
} else {
"N/A\n".to_string()
}
+1
View File
@@ -0,0 +1 @@
../../local/recipes/system/coretempd