ce0ac10b6d
Kirigami: remove stub .cpp, add Qt platform integration headers for QML gate. Matches KDE src/pattern for direct header-only builds. Cookbook: add --no-backup-if-mismatch to patch invocation (fetch.rs). Kernel: consolidate patch chain, add debug-scheme-serial-fix. Docs: archive old audit reports, add CHANGELOG and hardware validation matrix. Update AGENTS.md with Linux reference source policy. Scripts: add test-network-qemu.sh, test-storage-qemu.sh. .gitignore: add local/reference/ exclusion.
178 lines
6.5 KiB
Diff
178 lines
6.5 KiB
Diff
0a1,176
|
|
> use std::env;
|
|
> use std::fs;
|
|
> use std::io::{Read, Write};
|
|
> use std::thread;
|
|
> use std::time::{Duration, Instant};
|
|
> use log::{info, warn, error, LevelFilter};
|
|
>
|
|
> const IA32_PERF_CTL: u32 = 0x199;
|
|
> const IA32_PERF_STATUS: u32 = 0x198;
|
|
> const POLL_INTERVAL_MS: u64 = 100;
|
|
>
|
|
> struct StderrLogger;
|
|
> impl log::Log for StderrLogger {
|
|
> fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info }
|
|
> fn log(&self, r: &log::Record) { eprintln!("[{}] cpufreqd: {}", r.level(), r.args()); }
|
|
> fn flush(&self) {}
|
|
> }
|
|
>
|
|
> #[derive(Clone, Copy, PartialEq)]
|
|
> enum Governor { Performance, Powersave, Ondemand }
|
|
>
|
|
> struct PState { freq_mhz: u32, power_mw: u32, latency_us: u32, ctl_value: u64 }
|
|
>
|
|
> struct CpuState { id: u32, current_pstate: usize, load: f64 }
|
|
>
|
|
> fn read_msr(cpu: u32, msr: u32) -> Option<u64> {
|
|
> let path = format!("/dev/cpu/{}/msr", cpu);
|
|
> let mut f = fs::OpenOptions::new().read(true).open(&path).ok()?;
|
|
> let mut buf = [0u8; 8];
|
|
> f.read_exact(&mut buf).ok()?;
|
|
> Some(u64::from_ne_bytes(buf))
|
|
> }
|
|
>
|
|
> fn write_msr(cpu: u32, msr: u32, value: u64) -> bool {
|
|
> let path = format!("/dev/cpu/{}/msr", cpu);
|
|
> fs::OpenOptions::new().write(true).open(&path)
|
|
> .and_then(|mut f| f.write_all(&value.to_ne_bytes()))
|
|
> .is_ok()
|
|
> }
|
|
>
|
|
> fn read_acpi_pss(cpu: u32) -> Vec<PState> {
|
|
> let path = format!("/scheme/acpi/processor/CPU{}/pss", cpu);
|
|
> let data = fs::read_to_string(&path).unwrap_or_default();
|
|
> let mut states = Vec::new();
|
|
> for line in data.lines() {
|
|
> let parts: Vec<&str> = line.split_whitespace().collect();
|
|
> if parts.len() >= 6 {
|
|
> if let (Ok(freq), Ok(power), Ok(latency), Ok(ctl)) = (
|
|
> parts[0].parse::<u32>(), parts[2].parse::<u32>(),
|
|
> parts[4].parse::<u32>(), u64::from_str_radix(parts[5], 16)
|
|
> ) {
|
|
> states.push(PState { freq_mhz: freq, power_mw: power, latency_us: latency, ctl_value: ctl });
|
|
> }
|
|
> }
|
|
> }
|
|
> if states.is_empty() {
|
|
> states.push(PState { freq_mhz: 2400, power_mw: 35000, latency_us: 10, ctl_value: 0x1a00 });
|
|
> states.push(PState { freq_mhz: 1200, power_mw: 15000, latency_us: 10, ctl_value: 0x0d00 });
|
|
> }
|
|
> states
|
|
> }
|
|
>
|
|
> fn detect_cpus() -> Vec<u32> {
|
|
> let mut cpus = Vec::new();
|
|
> if let Ok(entries) = fs::read_dir("/dev/cpu") {
|
|
> for entry in entries.flatten() {
|
|
> if let Ok(name) = entry.file_name().into_string() {
|
|
> if let Ok(id) = name.parse::<u32>() {
|
|
> cpus.push(id);
|
|
> }
|
|
> }
|
|
> }
|
|
> }
|
|
> if cpus.is_empty() { cpus.push(0); }
|
|
> cpus
|
|
> }
|
|
>
|
|
> fn measure_cpu_load(cpu: u32, prev: &mut (u64, u64)) -> f64 {
|
|
> let path = format!("/scheme/sys/cpu/{}/stat", cpu);
|
|
> if let Ok(data) = fs::read_to_string(&path) {
|
|
> let parts: Vec<u64> = data.split_whitespace().filter_map(|s| s.parse().ok()).collect();
|
|
> if parts.len() >= 4 {
|
|
> let total: u64 = parts.iter().sum();
|
|
> let idle = parts.get(3).copied().unwrap_or(0);
|
|
> let prev_total = prev.0;
|
|
> let prev_idle = prev.1;
|
|
> *prev = (total, idle);
|
|
> if total > prev_total {
|
|
> let total_delta = total - prev_total;
|
|
> let idle_delta = idle.saturating_sub(prev_idle);
|
|
> return 1.0 - (idle_delta as f64 / total_delta as f64);
|
|
> }
|
|
> }
|
|
> }
|
|
> 0.0
|
|
> }
|
|
>
|
|
> fn choose_pstate(governor: Governor, pstates: &[PState], current: usize, load: f64) -> usize {
|
|
> match governor {
|
|
> Governor::Performance => 0,
|
|
> Governor::Powersave => pstates.len() - 1,
|
|
> Governor::Ondemand => {
|
|
> if load > 0.8 && current > 0 { current - 1 }
|
|
> else if load < 0.3 && current + 1 < pstates.len() { current + 1 }
|
|
> else { current }
|
|
> }
|
|
> }
|
|
> }
|
|
>
|
|
> fn main() {
|
|
> log::set_logger(&StderrLogger).ok();
|
|
> log::set_max_level(LevelFilter::Info);
|
|
>
|
|
> let governor = match env::var("CPUFREQ_GOVERNOR").unwrap_or_else(|_| "ondemand".to_string()).as_str() {
|
|
> "performance" => Governor::Performance,
|
|
> "powersave" => Governor::Powersave,
|
|
> _ => Governor::Ondemand,
|
|
> };
|
|
>
|
|
> let cpus = detect_cpus();
|
|
> info!("detected {} CPU(s)", cpus.len());
|
|
>
|
|
> let all_pstates: Vec<Vec<PState>> = cpus.iter().map(|cpu| read_acpi_pss(*cpu)).collect();
|
|
> if all_pstates.iter().all(|p| p.is_empty()) {
|
|
> error!("no P-states found, cannot scale frequency");
|
|
> return;
|
|
> }
|
|
>
|
|
> let mut cpu_states: Vec<CpuState> = cpus.iter().enumerate().map(|(i, &id)| {
|
|
> CpuState { id, current_pstate: 0, load: 0.0 }
|
|
> }).collect();
|
|
> let mut prev_stats: Vec<(u64, u64)> = vec![(0, 0); cpus.len()];
|
|
>
|
|
> info!("governor={:?}, {} P-states available", governor, all_pstates[0].len());
|
|
> for (i, p) in all_pstates[0].iter().enumerate() {
|
|
> info!(" P{}: {} MHz, {} mW, latency {} us", i, p.freq_mhz, p.power_mw, p.latency_us);
|
|
> }
|
|
>
|
|
> for (i, cs) in cpu_states.iter_mut().enumerate() {
|
|
> let pstates = &all_pstates[i];
|
|
> if !pstates.is_empty() {
|
|
> let ctl = pstates[0].ctl_value;
|
|
> if write_msr(cs.id, IA32_PERF_CTL, ctl) {
|
|
> info!("CPU{}: set P0 ({} MHz)", cs.id, pstates[0].freq_mhz);
|
|
> } else {
|
|
> warn!("CPU{}: MSR write failed, trying ACPI path", cs.id);
|
|
> }
|
|
> }
|
|
> }
|
|
>
|
|
> let poll = Duration::from_millis(POLL_INTERVAL_MS);
|
|
>
|
|
> loop {
|
|
> thread::sleep(poll);
|
|
>
|
|
> for (i, cs) in cpu_states.iter_mut().enumerate() {
|
|
> let load = measure_cpu_load(cs.id, &mut prev_stats[i]);
|
|
> cs.load = load;
|
|
>
|
|
> let pstates = &all_pstates[i];
|
|
> if pstates.is_empty() { continue; }
|
|
>
|
|
> let new_idx = choose_pstate(governor, pstates, cs.current_pstate, load);
|
|
> if new_idx != cs.current_pstate {
|
|
> let ctl = pstates[new_idx].ctl_value;
|
|
> if write_msr(cs.id, IA32_PERF_CTL, ctl) {
|
|
> info!("CPU{}: P{}→P{} ({}→{} MHz, load={:.1}%)",
|
|
> cs.id, cs.current_pstate, new_idx,
|
|
> pstates[cs.current_pstate].freq_mhz, pstates[new_idx].freq_mhz,
|
|
> load * 100.0);
|
|
> cs.current_pstate = new_idx;
|
|
> }
|
|
> }
|
|
> }
|
|
> }
|
|
> }
|