diff --git a/local/recipes/system/cpufreqd/source/src/main.rs b/local/recipes/system/cpufreqd/source/src/main.rs index f76839fc9a..800b3c42e1 100644 --- a/local/recipes/system/cpufreqd/source/src/main.rs +++ b/local/recipes/system/cpufreqd/source/src/main.rs @@ -31,6 +31,17 @@ struct CpuInfo { } fn detect_cpus() -> Vec { + // 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::() { + 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() { diff --git a/local/recipes/system/redbear-power/source/src/acpi.rs b/local/recipes/system/redbear-power/source/src/acpi.rs index c2227b57d9..4d73c5f216 100644 --- a/local/recipes/system/redbear-power/source/src/acpi.rs +++ b/local/recipes/system/redbear-power/source/src/acpi.rs @@ -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 { + 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::() { + 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::() { + 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::() { + 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::() { + 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 { - // 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 = 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::() { + if n > 0 { return (0..n).collect(); } + } } } } + if let Ok(entries) = fs::read_dir("/dev/cpu") { + let mut v: Vec = 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] }