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:
@@ -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 = """
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user