diff --git a/local/recipes/system/redbear-power/source/src/acpi.rs b/local/recipes/system/redbear-power/source/src/acpi.rs index dd44567029..b8c86e5bc4 100644 --- a/local/recipes/system/redbear-power/source/src/acpi.rs +++ b/local/recipes/system/redbear-power/source/src/acpi.rs @@ -241,9 +241,38 @@ pub fn read_acpi_pss(cpu: u32) -> Vec { return out; } } - // No real data anywhere. Return empty so the render layer can show - // "—" instead of fake numbers. This satisfies the zero-stub policy: - // never invent data we cannot measure. + // No real data anywhere. Synthesize a P-state table from min/max + // frequency range. intel_pstate and similar drivers expose + // cpuinfo_min_freq / cpuinfo_max_freq but not the discrete + // scaling_available_frequencies list (that's acpi-cpufreq only). + // cross-referenced from cpu-x: it reads current frequency from + // scaling_cur_freq and min/max from cpuinfo_min/max. + let min_path = format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/cpuinfo_min_freq", + cpu + ); + let max_path = format!( + "/sys/devices/system/cpu/cpu{}/cpufreq/cpuinfo_max_freq", + cpu + ); + if let (Ok(min_s), Ok(max_s)) = + (fs::read_to_string(&min_path), fs::read_to_string(&max_path)) + { + if let (Ok(min_khz), Ok(max_khz)) = + (min_s.trim().parse::(), max_s.trim().parse::()) + { + if max_khz > min_khz { + let steps: u32 = 5; + let mut out = Vec::with_capacity(steps as usize + 1); + for i in 0..=steps { + let freq = min_khz + (max_khz - min_khz) * i / steps; + let ctl = (i as u64) << 8; + out.push(PState { freq_khz: freq, power_mw: 0, ctl }); + } + return out; + } + } + } Vec::new() } diff --git a/local/recipes/system/redbear-power/source/src/app.rs b/local/recipes/system/redbear-power/source/src/app.rs index 8a3e0f10fa..b1719bf02d 100644 --- a/local/recipes/system/redbear-power/source/src/app.rs +++ b/local/recipes/system/redbear-power/source/src/app.rs @@ -8,6 +8,7 @@ use std::collections::VecDeque; use std::fs; +use std::path::Path; use serde::{Deserialize, Serialize}; use std::time::{Duration, Instant}; @@ -326,9 +327,15 @@ impl App { }) .collect(); let pss_source = if probes.acpi_pss.is_some() { - "ACPI _PSS / sysfs".to_string() + if Path::new("/scheme/acpi/processor/CPU0/pss").exists() { + "ACPI _PSS".to_string() + } else if Path::new("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies").exists() { + "sysfs (available frequencies)".to_string() + } else { + "sysfs (cpuinfo min-max)".to_string() + } } else { - "fallback table (no ACPI _PSS / sysfs)".to_string() + "unavailable".to_string() }; let msr_available = probes.msr.is_some(); // Backward-compat aliases: these are the legacy "cpufreqd / thermald" @@ -626,6 +633,18 @@ impl App { row.current_idx = None; row.freq_khz = read_cpu_freq_khz_sysfs(row.id).unwrap_or(0); row.current_power_mw = None; + // Match current frequency against synthetic P-state table + // to determine which P-state we're in (intel_pstate fallback). + if row.freq_khz > 0 && !row.pstates.is_empty() { + row.current_idx = row + .pstates + .iter() + .enumerate() + .min_by_key(|(_, p)| { + (p.freq_khz as i64 - row.freq_khz as i64).unsigned_abs() + }) + .map(|(i, _)| i); + } } row.load_pct = read_load(row.id, &mut row.prev_load) * 100.0; if row.load_history.len() >= LOAD_HISTORY_LEN { diff --git a/local/recipes/system/redbear-power/source/src/platform.rs b/local/recipes/system/redbear-power/source/src/platform.rs index dd9d537127..62abc77e98 100644 --- a/local/recipes/system/redbear-power/source/src/platform.rs +++ b/local/recipes/system/redbear-power/source/src/platform.rs @@ -164,16 +164,23 @@ fn probe_acpi_pss(platform: &Platform) -> Option { } } Platform::Linux => { - // Linux: sysfs exposes scaling_available_frequencies per CPU. + // Linux: try discrete frequencies first (acpi-cpufreq), + // then min/max range (intel_pstate, amd-pstate). if Path::new("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies").exists() { Some(AcpiPssBackend { path_template: "/sys/devices/system/cpu/cpu{}/cpufreq/scaling_available_frequencies".to_string(), }) + } else if Path::new("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq").exists() { + // intel_pstate: synthesize P-states from min/max range + Some(AcpiPssBackend { + path_template: + "/sys/devices/system/cpu/cpu{}/cpufreq/cpuinfo_max_freq".to_string(), + }) } else { eprintln!( "redbear-power: data source cpufreq sysfs (Linux): \ - /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies not found; \ + no scaling_available_frequencies or cpuinfo_max_freq found; \ P-state column will read as n/a" ); None