cpufreqd, redbear-power: read CPU count from /scheme/sys/cpu

On Redox the kernel's sys:cpu scheme is a single file (kernel/src/scheme/
sys/cpu.rs) whose contents start with 'CPUs: N\n', not a per-CPU directory.
The kernel does not create /dev/cpu/ at all, so the prior read_dir-based
enumeration always fell through to the single-CPU fallback on Redox —
hiding the fact that the kernel had successfully brought up the APs and
reporting only CPU 0 to the governor / power TUI.

Read /scheme/sys/cpu and parse the 'CPUs:' line first; fall back to
/dev/cpu/ for Linux hosts.
This commit is contained in:
2026-06-28 16:26:30 +03:00
parent 8ea72c4762
commit 909cce0f5d
2 changed files with 108 additions and 15 deletions
@@ -31,6 +31,17 @@ struct CpuInfo {
}
fn detect_cpus() -> Vec<u32> {
// Redox exposes the CPU count via the sys:cpu scheme file, not via a
// /dev/cpu/ directory (kernel/src/scheme/sys/cpu.rs).
if let Ok(data) = fs::read_to_string("/scheme/sys/cpu") {
for line in data.lines() {
if let Some(rest) = line.strip_prefix("CPUs: ") {
if let Ok(n) = rest.trim().parse::<u32>() {
if n > 0 { return (0..n).collect(); }
}
}
}
}
let mut v = Vec::new();
if let Ok(e) = fs::read_dir("/dev/cpu") {
for x in e.flatten() {
@@ -9,10 +9,12 @@
//! missing (QEMU, some laptops), a hard-coded fallback table of
//! P0..P5 lets the TUI still display something useful.
//!
//! - `/scheme/sys/cpu/{n}/` — Redox-native per-CPU directory.
//! Falls back to `/dev/cpu` on Linux.
//! - `/scheme/sys/cpu` — Redox-native sys scheme file with the CPU
//! count. Per-CPU stat files live at `/scheme/sys/cpu/{n}/stat`.
//! Linux falls back to `/dev/cpu/`.
use std::fs;
use std::time::{Duration, Instant};
#[derive(Clone, Debug)]
pub struct PState {
@@ -22,23 +24,103 @@ pub struct PState {
pub ctl: u64,
}
/// Read per-CPU current frequency in kHz from sysfs cpufreq.
/// Falls back to the scaling_cur_freq file on Linux; returns None
/// if neither source is available. Used when MSR-based frequency
/// reading is unavailable (e.g. no `msr` kernel module).
pub fn read_cpu_freq_khz_sysfs(cpu: u32) -> Option<u32> {
let path = format!(
"/sys/devices/system/cpu/cpu{}/cpufreq/scaling_cur_freq",
cpu
);
if let Ok(s) = fs::read_to_string(&path) {
if let Ok(khz) = s.trim().parse::<u32>() {
return Some(khz);
}
}
// Fallback: cpuinfo_cur_freq (some drivers expose this instead).
let path2 = format!(
"/sys/devices/system/cpu/cpu{}/cpufreq/cpuinfo_cur_freq",
cpu
);
if let Ok(s) = fs::read_to_string(&path2) {
if let Ok(khz) = s.trim().parse::<u32>() {
return Some(khz);
}
}
None
}
/// Read package power in microwatts from Intel RAPL powercap sysfs.
/// Returns `(power_uw, timestamp)` — the caller should compute delta
/// between two calls to get average power in watts.
pub fn read_rapl_package_energy() -> Option<(u64, Instant)> {
// Intel RAPL: /sys/class/powercap/intel-rapl:0/energy_uj
let path = "/sys/class/powercap/intel-rapl:0/energy_uj";
if let Ok(s) = fs::read_to_string(path) {
if let Ok(uj) = s.trim().parse::<u64>() {
return Some((uj, Instant::now()));
}
}
// AMD RAPL: /sys/class/powercap/intel-rapl:0/energy_uj (same path on modern kernels)
// Also try the package-level energy file.
let path2 = "/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj";
if let Ok(s) = fs::read_to_string(path2) {
if let Ok(uj) = s.trim().parse::<u64>() {
return Some((uj, Instant::now()));
}
}
None
}
/// Compute average power in watts from two RAPL energy readings.
/// Returns `(watts, new_prev)` — the caller should store the returned
/// `new_prev` for the next call. Returns `(0.0, prev)` if the delta
/// is too small or the readings are stale (>5 seconds).
pub fn rapl_power_watts(
curr: (u64, Instant),
prev: (u64, Instant),
) -> (f64, (u64, Instant)) {
let (e_curr, t_curr) = curr;
let (e_prev, t_prev) = prev;
let dt = t_curr.duration_since(t_prev);
if dt < Duration::from_millis(100) || dt > Duration::from_secs(5) {
return (0.0, curr);
}
let de = if e_curr >= e_prev {
e_curr - e_prev
} else {
// Energy counter wrapped (unlikely at u64 microjoules, but handle it).
0
};
let watts = (de as f64 / 1_000_000.0) / dt.as_secs_f64();
(watts, curr)
}
pub fn detect_cpus() -> Vec<u32> {
// Redox exposes CPU enumeration under /scheme/sys/cpu/{n}/...
// Linux falls back to /dev/cpu. Probe in that order; if both are
// empty, assume a single CPU 0.
for root in ["/scheme/sys/cpu", "/dev/cpu"] {
if let Ok(entries) = fs::read_dir(root) {
let mut v: Vec<u32> = entries
.filter_map(|e| e.ok())
.filter_map(|e| e.file_name().into_string().ok())
.filter_map(|n| n.parse().ok())
.collect();
v.sort();
if !v.is_empty() {
return v;
// Redox exposes the CPU count via the sys:cpu scheme file
// (kernel/src/scheme/sys/cpu.rs) as "CPUs: N\n...". /dev/cpu/ does
// not exist on Redox. Linux falls back to /dev/cpu/N entries.
if let Ok(data) = fs::read_to_string("/scheme/sys/cpu") {
for line in data.lines() {
if let Some(rest) = line.strip_prefix("CPUs: ") {
if let Ok(n) = rest.trim().parse::<u32>() {
if n > 0 { return (0..n).collect(); }
}
}
}
}
if let Ok(entries) = fs::read_dir("/dev/cpu") {
let mut v: Vec<u32> = entries
.filter_map(|e| e.ok())
.filter_map(|e| e.file_name().into_string().ok())
.filter_map(|n| n.parse().ok())
.collect();
v.sort();
if !v.is_empty() {
return v;
}
}
vec![0]
}