Add coretempd CPU temperature sensor daemon

New local recipe coretempd reads IA32_THERM_STATUS MSR via the
new sys:msr scheme and exposes per-CPU temperatures via scheme:coretemp.

- Reads IA32_THERM_STATUS (0x19C) and IA32_TEMPERATURE_TARGET (0x1A2)
- Calculates Celsius from digital readout relative to TjMax
- Exposes /scheme/coretemp/ directory with per-CPU temperature files
- Added to redbear-mini.toml (inherited by redbear-full)
- Added 15_coretempd.service init file
This commit is contained in:
2026-05-20 13:46:43 +03:00
parent 7999a896d0
commit 6ca3e47383
5 changed files with 137 additions and 1 deletions
+13 -1
View File
@@ -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 = """
+1
View File
@@ -52,6 +52,7 @@ redbear-info = {}
cub = {}
cpufreqd = {}
thermald = {}
coretempd = {}
hwrngd = {}
redbear-acmd = {}
redbear-ecmd = {}
@@ -0,0 +1,5 @@
[source]
path = "source"
[build]
template = "cargo"
@@ -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
@@ -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<u64> {
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<u32> {
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<i16> {
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<u8> = 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::<u32>() {
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());
}
}
}
}