From e5b82a644a2b330e14cd66a61752eece2823cd22 Mon Sep 17 00:00:00 2001 From: Admin Pupkin Date: Wed, 20 May 2026 18:52:01 +0300 Subject: [PATCH] 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. --- .../system/coretempd/source/src/main.rs | 81 +++++++++++++++---- recipes/system/coretempd | 1 + 2 files changed, 67 insertions(+), 15 deletions(-) create mode 120000 recipes/system/coretempd diff --git a/local/recipes/system/coretempd/source/src/main.rs b/local/recipes/system/coretempd/source/src/main.rs index 1bfdb8c380..064aec0c1e 100644 --- a/local/recipes/system/coretempd/source/src/main.rs +++ b/local/recipes/system/coretempd/source/src/main.rs @@ -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 { let path = format!("/scheme/sys/msr/{}/{:x}", cpu, msr); @@ -15,6 +23,16 @@ fn read_msr(cpu: u32, msr: u32) -> Option { .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 { let mut v = Vec::new(); if let Ok(d) = fs::read_to_string("/scheme/sys/cpu") { @@ -30,7 +48,7 @@ fn detect_cpus() -> Vec { v } -fn read_temperature(cpu: u32, tjmax: u8) -> Option { +fn read_temperature_intel(cpu: u32, tjmax: u8) -> Option { 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 { 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 { + 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 = cpus.iter().map(|&c| read_tjmax(c)).collect(); + let cpu_infos: Vec = 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::() { - 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() } diff --git a/recipes/system/coretempd b/recipes/system/coretempd new file mode 120000 index 0000000000..27d058791e --- /dev/null +++ b/recipes/system/coretempd @@ -0,0 +1 @@ +../../local/recipes/system/coretempd \ No newline at end of file