diff --git a/config/redbear-device-services.toml b/config/redbear-device-services.toml index e622f45c4d..3b91fa8873 100644 --- a/config/redbear-device-services.toml +++ b/config/redbear-device-services.toml @@ -278,7 +278,7 @@ default_dependencies = false [service] cmd = "acpid" inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"] -type = { scheme = "acpi" } +type = "notify" """ # ACPI GPIO/I2C controller drivers @@ -435,6 +435,18 @@ cmd = "/usr/bin/thermald" type = "oneshot_async" """ +[[files]] +path = "/etc/init.d/15_coretempd.service" +data = """ +[unit] +description = "CPU temperature sensor daemon" +requires_weak = ["04_drivers.target"] + +[service] +cmd = "/usr/bin/coretempd" +type = { scheme = "coretemp" } +""" + [[files]] path = "/etc/init.d/15_hwrngd.service" data = """ diff --git a/config/redbear-mini.toml b/config/redbear-mini.toml index 3048e137da..d3ccebc54e 100644 --- a/config/redbear-mini.toml +++ b/config/redbear-mini.toml @@ -52,6 +52,7 @@ redbear-info = {} cub = {} cpufreqd = {} thermald = {} +coretempd = {} hwrngd = {} redbear-acmd = {} redbear-ecmd = {} diff --git a/local/recipes/system/coretempd/recipe.toml b/local/recipes/system/coretempd/recipe.toml new file mode 100644 index 0000000000..4e47e6bb51 --- /dev/null +++ b/local/recipes/system/coretempd/recipe.toml @@ -0,0 +1,5 @@ +[source] +path = "source" + +[build] +template = "cargo" diff --git a/local/recipes/system/coretempd/source/Cargo.toml b/local/recipes/system/coretempd/source/Cargo.toml new file mode 100644 index 0000000000..fc5ba10858 --- /dev/null +++ b/local/recipes/system/coretempd/source/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "coretempd" +version = "0.1.0" +edition = "2024" + +[dependencies] +redox-scheme = "0.11" +redox_syscall = "0.7" +libc = "0.2" + +[profile.release] +opt-level = 3 +lto = true diff --git a/local/recipes/system/coretempd/source/src/main.rs b/local/recipes/system/coretempd/source/src/main.rs new file mode 100644 index 0000000000..1bfdb8c380 --- /dev/null +++ b/local/recipes/system/coretempd/source/src/main.rs @@ -0,0 +1,105 @@ +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 IA32_THERM_STATUS: u32 = 0x19c; +const IA32_TEMPERATURE_TARGET: u32 = 0x1a2; +const POLL_MS: u64 = 2000; + +fn read_msr(cpu: u32, msr: u32) -> Option { + let path = format!("/scheme/sys/msr/{}/{:x}", cpu, msr); + fs::read_to_string(&path).ok() + .and_then(|s| u64::from_str_radix(s.trim(), 16).ok()) +} + +fn detect_cpus() -> Vec { + let mut v = Vec::new(); + if let Ok(d) = fs::read_to_string("/scheme/sys/cpu") { + for l in d.lines() { + if let Some(id_str) = l.strip_prefix("CPU ") { + if let Some((num, _)) = id_str.split_once(':') { + if let Ok(id) = num.trim().parse() { v.push(id); } + } + } + } + } + if v.is_empty() { v.push(0); } + v +} + +fn read_temperature(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; } + let temp = tjmax.saturating_sub(digital_readout); + Some(temp as i16) +} + +fn read_tjmax(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; } + } + 100 +} + +fn main() { + let scheme_path = ":coretemp"; + let _ = fs::remove_file(scheme_path); + let listener = UnixListener::bind(scheme_path).expect("bind scheme"); + eprintln!("[INFO] coretempd: starting"); + + 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 cpus_clone = cpus.clone(); + let tjmax_clone = tjmax_values.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 stream in listener.incoming() { + if let Ok(mut stream) = stream { + let mut buf = [0u8; 64]; + if let Ok(n) = stream.read(&mut buf) { + 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)); + } + 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) + } else { + "N/A\n".to_string() + } + } else { + "N/A\n".to_string() + } + } else { + "N/A\n".to_string() + } + } else { + "N/A\n".to_string() + }; + let _ = stream.write_all(resp.as_bytes()); + } + } + } +}