redbear-power: synthetic P-state table from cpuinfo min-max, freq-based index matching

- acpi.rs: fallback creates 6 evenly-spaced P-states from
  cpuinfo_min_freq / cpuinfo_max_freq when scaling_available_frequencies
  is absent (intel_pstate, amd-pstate drivers)
- platform.rs: probe accepts cpuinfo_max_freq as valid PSS source
- app.rs: match current frequency against synthetic P-state table
  to compute current_idx without MSR access
- pss_source label: 'sysfs (cpuinfo min-max)' for intel_pstate
This commit is contained in:
2026-06-28 18:32:24 +03:00
parent d2b969eb05
commit 03fd3a0690
3 changed files with 62 additions and 7 deletions
@@ -241,9 +241,38 @@ pub fn read_acpi_pss(cpu: u32) -> Vec<PState> {
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::<u32>(), max_s.trim().parse::<u32>())
{
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()
}
@@ -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 {
@@ -164,16 +164,23 @@ fn probe_acpi_pss(platform: &Platform) -> Option<AcpiPssBackend> {
}
}
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