fix: rewrite coretempd to use redox_scheme Socket + SchemeSync
Replaced broken UnixListener::bind(':coretemp') with proper redox_scheme::Socket::create() + SchemeSync trait impl. Event loop uses next_request/handle_sync/write_response pattern. Verified: registers scheme:coretemp, detects CPU info, zero panics.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -6,6 +6,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
redox-scheme = "0.11"
|
||||
redox_syscall = "0.7"
|
||||
libredox = "0.1"
|
||||
libc = "0.2"
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::net::UnixListener;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use redox_scheme::scheme::SchemeSync;
|
||||
use redox_scheme::scheme::SchemeState;
|
||||
use redox_scheme::{CallerCtx, OpenResult, SignalBehavior, Socket};
|
||||
use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT};
|
||||
use syscall::flag::O_ACCMODE;
|
||||
use syscall::schemev2::NewFdFlags;
|
||||
|
||||
const POLL_MS: u64 = 2000;
|
||||
const SCHEME_ROOT_ID: usize = 0;
|
||||
|
||||
const IA32_THERM_STATUS: u32 = 0x19c;
|
||||
const IA32_TEMPERATURE_TARGET: u32 = 0x1a2;
|
||||
@@ -19,7 +26,8 @@ enum Vendor {
|
||||
|
||||
fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
|
||||
let path = format!("/scheme/sys/msr/{}/{:x}", cpu, msr);
|
||||
fs::read_to_string(&path).ok()
|
||||
fs::read_to_string(&path)
|
||||
.ok()
|
||||
.and_then(|s| u64::from_str_radix(s.trim(), 16).ok())
|
||||
}
|
||||
|
||||
@@ -39,27 +47,34 @@ fn detect_cpus() -> Vec<u32> {
|
||||
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 let Ok(id) = num.trim().parse::<u32>() {
|
||||
v.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.is_empty() { v.push(0); }
|
||||
if v.is_empty() {
|
||||
v.push(0);
|
||||
}
|
||||
v
|
||||
}
|
||||
|
||||
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; }
|
||||
let temp = tjmax.saturating_sub(digital_readout);
|
||||
Some(temp as i16)
|
||||
if digital_readout == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(tjmax.saturating_sub(digital_readout) as i16)
|
||||
}
|
||||
|
||||
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; }
|
||||
if tj > 0 && tj < 150 {
|
||||
return tj;
|
||||
}
|
||||
}
|
||||
100
|
||||
}
|
||||
@@ -67,9 +82,10 @@ fn read_tjmax_intel(cpu: u32) -> u8 {
|
||||
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)
|
||||
if tctl == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(((tctl as f32) / 8.0) as i16)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -79,78 +95,256 @@ struct CpuInfo {
|
||||
tjmax: u8,
|
||||
}
|
||||
|
||||
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");
|
||||
#[derive(Clone)]
|
||||
enum Handle {
|
||||
Listing,
|
||||
CpuTemp { cpu_id: u32 },
|
||||
}
|
||||
|
||||
let cpus = detect_cpus();
|
||||
eprintln!("[INFO] coretempd: detected {} CPU(s)", cpus.len());
|
||||
struct CoretempScheme {
|
||||
cpus: Vec<CpuInfo>,
|
||||
next_handle: usize,
|
||||
handles: Vec<Option<Handle>>,
|
||||
}
|
||||
|
||||
let cpu_infos: Vec<CpuInfo> = cpus.iter().map(|&id| {
|
||||
let vendor = detect_vendor(id);
|
||||
let tjmax = if vendor == Vendor::Intel {
|
||||
read_tjmax_intel(id)
|
||||
impl CoretempScheme {
|
||||
fn new(cpus: Vec<CpuInfo>) -> Self {
|
||||
Self {
|
||||
cpus,
|
||||
next_handle: 1,
|
||||
handles: vec![None],
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_handle(&mut self, h: Handle) -> usize {
|
||||
let id = self.next_handle;
|
||||
self.handles.push(Some(h));
|
||||
self.next_handle += 1;
|
||||
id
|
||||
}
|
||||
|
||||
fn get_handle(&self, id: usize) -> Result<&Handle> {
|
||||
self.handles
|
||||
.get(id)
|
||||
.and_then(|opt| opt.as_ref())
|
||||
.ok_or_else(|| Error::new(EBADF))
|
||||
}
|
||||
|
||||
fn format_listing(&self) -> String {
|
||||
let mut out = String::new();
|
||||
for info in &self.cpus {
|
||||
let temp = match info.vendor {
|
||||
Vendor::Intel => read_temperature_intel(info.id, info.tjmax),
|
||||
Vendor::Amd => read_temperature_amd(info.id),
|
||||
Vendor::Unknown => None,
|
||||
};
|
||||
match temp {
|
||||
Some(t) => out.push_str(&format!("cpu{}: {}C\n", info.id, t)),
|
||||
None => out.push_str(&format!("cpu{}: N/A\n", info.id)),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
impl SchemeSync for CoretempScheme {
|
||||
fn scheme_root(&mut self) -> Result<usize> {
|
||||
Ok(SCHEME_ROOT_ID)
|
||||
}
|
||||
|
||||
fn openat(
|
||||
&mut self,
|
||||
dirfd: usize,
|
||||
path: &str,
|
||||
flags: usize,
|
||||
_fcntl_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<OpenResult> {
|
||||
if flags & O_ACCMODE != 0 {
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
let handle = if dirfd == SCHEME_ROOT_ID {
|
||||
let trimmed = path.trim_start_matches('/');
|
||||
if trimmed.is_empty() {
|
||||
Handle::Listing
|
||||
} else if let Some(cpu_str) = trimmed.strip_prefix("cpu") {
|
||||
let cpu_id = cpu_str
|
||||
.trim_end_matches('/')
|
||||
.parse::<u32>()
|
||||
.map_err(|_| Error::new(ENOENT))?;
|
||||
if self.cpus.iter().any(|c| c.id == cpu_id) {
|
||||
Handle::CpuTemp { cpu_id }
|
||||
} else {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::new(ENOENT));
|
||||
}
|
||||
} else {
|
||||
0
|
||||
return Err(Error::new(ENOENT));
|
||||
};
|
||||
eprintln!("[INFO] coretempd: CPU {} = {:?}", id, vendor);
|
||||
CpuInfo { id, vendor, tjmax }
|
||||
}).collect();
|
||||
|
||||
let infos_clone = cpu_infos.clone();
|
||||
thread::spawn(move || {
|
||||
loop {
|
||||
thread::sleep(Duration::from_millis(POLL_MS));
|
||||
for info in &infos_clone {
|
||||
Ok(OpenResult::ThisScheme {
|
||||
number: self.alloc_handle(handle),
|
||||
flags: NewFdFlags::empty(),
|
||||
})
|
||||
}
|
||||
|
||||
fn read(
|
||||
&mut self,
|
||||
id: usize,
|
||||
buf: &mut [u8],
|
||||
offset: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
let data = match self.get_handle(id)? {
|
||||
Handle::Listing => self.format_listing(),
|
||||
Handle::CpuTemp { cpu_id } => {
|
||||
let info = self
|
||||
.cpus
|
||||
.iter()
|
||||
.find(|c| c.id == *cpu_id)
|
||||
.ok_or_else(|| Error::new(ENOENT))?;
|
||||
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));
|
||||
match temp {
|
||||
Some(t) => format!("{}\n", t),
|
||||
None => "N/A\n".to_string(),
|
||||
}
|
||||
}
|
||||
};
|
||||
let bytes = data.as_bytes();
|
||||
let off = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?;
|
||||
if off >= bytes.len() {
|
||||
return Ok(0);
|
||||
}
|
||||
let count = (bytes.len() - off).min(buf.len());
|
||||
buf[..count].copy_from_slice(&bytes[off..off + count]);
|
||||
Ok(count)
|
||||
}
|
||||
|
||||
fn write(
|
||||
&mut self,
|
||||
_id: usize,
|
||||
_buf: &[u8],
|
||||
_offset: u64,
|
||||
_flags: u32,
|
||||
_ctx: &CallerCtx,
|
||||
) -> Result<usize> {
|
||||
Err(Error::new(EINVAL))
|
||||
}
|
||||
|
||||
fn on_close(&mut self, id: usize) {
|
||||
if id < self.handles.len() {
|
||||
self.handles[id] = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn init_notify_fd() -> Option<i32> {
|
||||
env::var("INIT_NOTIFY").ok()?.parse::<i32>().ok()
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn notify_scheme_ready(
|
||||
notify_fd: i32,
|
||||
socket: &Socket,
|
||||
scheme: &mut CoretempScheme,
|
||||
) -> Result<(), String> {
|
||||
let cap_id = scheme
|
||||
.scheme_root()
|
||||
.map_err(|err| format!("coretempd: scheme_root failed: {err}"))?;
|
||||
let cap_fd = socket
|
||||
.create_this_scheme_fd(0, cap_id, 0, 0)
|
||||
.map_err(|err| format!("coretempd: create_this_scheme_fd failed: {err}"))?;
|
||||
|
||||
syscall::call::write(
|
||||
notify_fd as usize,
|
||||
&libredox::Fd::new(cap_fd).into_raw().to_ne_bytes(),
|
||||
)
|
||||
.map_err(|err| format!("coretempd: failed to notify init: {err}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(target_os = "redox")]
|
||||
fn run_daemon() -> Result<(), String> {
|
||||
let notify_fd = init_notify_fd();
|
||||
|
||||
let cpus = detect_cpus();
|
||||
eprintln!("[INFO] coretempd: detected {} CPU(s)", cpus.len());
|
||||
|
||||
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 socket = Socket::create()
|
||||
.map_err(|err| format!("coretempd: failed to create scheme socket: {err}"))?;
|
||||
let mut state = redox_scheme::scheme::SchemeState::new();
|
||||
let mut scheme = CoretempScheme::new(cpu_infos);
|
||||
|
||||
if let Some(fd) = notify_fd {
|
||||
notify_scheme_ready(fd, &socket, &mut scheme)?;
|
||||
}
|
||||
|
||||
eprintln!("[INFO] coretempd: registered scheme:coretemp");
|
||||
|
||||
let infos_clone = scheme.cpus.clone();
|
||||
thread::spawn(move || loop {
|
||||
thread::sleep(Duration::from_millis(POLL_MS));
|
||||
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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
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 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(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()
|
||||
}
|
||||
} 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());
|
||||
}
|
||||
loop {
|
||||
let request = socket
|
||||
.next_request(SignalBehavior::Restart)
|
||||
.map_err(|err| format!("coretempd: failed to read scheme request: {err}"))?;
|
||||
|
||||
let Some(request) = request else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if let redox_scheme::RequestKind::Call(request) = request.kind() {
|
||||
let response = request.handle_sync(&mut scheme, &mut state);
|
||||
socket
|
||||
.write_response(response, SignalBehavior::Restart)
|
||||
.map_err(|err| format!("coretempd: failed to write response: {err}"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "redox"))]
|
||||
fn run_daemon() -> Result<(), String> {
|
||||
eprintln!("[INFO] coretempd: not running on Redox, exiting");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if let Err(e) = run_daemon() {
|
||||
eprintln!("[ERROR] coretempd: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user