feat: intelligent platform detection for cpufreqd and coretempd

cpufreqd:
- Read CPU vendor and frequency from /scheme/sys/cpu (CPUID-based)
- Generate P-states dynamically from detected max/base frequency
- Remove hardcoded 2400-1200 kHz fallback table
- Intel SpeedStep and AMD encoding support

coretempd:
- Detect vendor from /scheme/sys/cpu before MSR probing
- Read CPU count from /scheme/sys/cpu for accuracy
- Fall back to MSR detection only when platform info unavailable
This commit is contained in:
2026-06-01 11:14:52 +03:00
parent 1b266ddda7
commit 052be6d264
2 changed files with 82 additions and 21 deletions
@@ -53,6 +53,17 @@ fn probe_msr_available() -> bool {
}
fn detect_vendor(cpu: u32) -> Vendor {
// Prefer platform info from /scheme/sys/cpu — avoids MSR access
if let Ok(d) = fs::read_to_string("/scheme/sys/cpu") {
let lower = d.to_lowercase();
if lower.contains("intel") || lower.contains("genuineintel") {
return Vendor::Intel;
}
if lower.contains("amd") || lower.contains("authenticamd") {
return Vendor::Amd;
}
}
// Fall back to MSR probing if platform info unavailable
if read_msr(cpu, IA32_THERM_STATUS).is_some() {
Vendor::Intel
} else if read_msr(cpu, AMD_TCTL).is_some() {
@@ -63,21 +74,27 @@ fn detect_vendor(cpu: u32) -> Vendor {
}
fn detect_cpus() -> Vec<u32> {
let mut v = Vec::new();
// Try /scheme/sys/cpu first for CPU count
if let Ok(d) = fs::read_to_string("/scheme/sys/cpu") {
for l in d.lines() {
if let Some(id_str) = l.strip_prefix("CPU ") {
if let Some((num, _)) = id_str.split_once(':') {
if let Ok(id) = num.trim().parse::<u32>() {
v.push(id);
}
let mut v = Vec::new();
for line in d.lines() {
if line.starts_with("CPUs: ") {
if let Ok(count) = line[6..].trim().parse::<u32>() {
return (0..count).collect();
}
}
}
}
if v.is_empty() {
v.push(0);
// Fall back to /dev/cpu
let mut v = Vec::new();
if let Ok(e) = fs::read_dir("/dev/cpu") {
for x in e.flatten() {
if let Ok(n) = x.file_name().into_string() {
if let Ok(id) = n.parse() { v.push(id); }
}
}
}
if v.is_empty() { v.push(0); }
v
}
@@ -39,6 +39,53 @@ fn detect_cpus() -> Vec<u32> {
v
}
fn read_sys_cpu_info() -> (String, u32, u32) {
let mut vendor = String::new();
let mut base_mhz: u32 = 0;
let mut max_mhz: u32 = 0;
if let Ok(d) = fs::read_to_string("/scheme/sys/cpu") {
for line in d.lines() {
if let Some(v) = line.strip_prefix("Vendor: ") {
vendor = v.to_string();
} else if let Some(b) = line.strip_prefix("CPU Base MHz: ") {
base_mhz = b.parse().unwrap_or(0);
} else if let Some(m) = line.strip_prefix("CPU Max MHz: ") {
max_mhz = m.parse().unwrap_or(0);
}
}
}
(vendor, base_mhz, max_mhz)
}
fn generate_pstates_from_freq(base_mhz: u32, max_mhz: u32, is_intel: bool) -> Vec<PState> {
if max_mhz == 0 || base_mhz == 0 {
return vec![];
}
let bus_mhz = if base_mhz > 0 { base_mhz } else { 100 };
let max_ratio = max_mhz * 1000 / bus_mhz; // convert to kHz ratio
let min_ratio = (max_ratio / 2).max(8); // reasonable minimum
let mut states = Vec::new();
let steps = 4; // generate 4 P-states
for i in 0..steps {
let ratio = max_ratio - (i * (max_ratio - min_ratio) / (steps - 1));
let freq_khz = ratio * bus_mhz;
// Intel SpeedStep encoding: ratio in bits 15:8
let ctl = if is_intel {
((ratio as u64) << 8) & 0xFF00
} else {
ratio as u64 // AMD uses raw ratio in lower bits
};
states.push(PState {
freq_khz,
power_mw: (35000 * ratio / max_ratio) as u32,
latency_us: 10,
ctl,
});
}
states
}
fn read_acpi_pss(cpu: u32) -> Vec<PState> {
let path = format!("/scheme/acpi/processor/CPU{}/pss", cpu);
if let Ok(d) = fs::read_to_string(&path) {
@@ -53,12 +100,7 @@ fn read_acpi_pss(cpu: u32) -> Vec<PState> {
}
if !s.is_empty() { return s; }
}
vec![
PState { freq_khz: 2400, power_mw: 35000, latency_us: 10, ctl: 0x1a00 },
PState { freq_khz: 2000, power_mw: 25000, latency_us: 10, ctl: 0x1600 },
PState { freq_khz: 1600, power_mw: 18000, latency_us: 10, ctl: 0x1200 },
PState { freq_khz: 1200, power_mw: 12000, latency_us: 10, ctl: 0x0e00 },
]
vec![]
}
fn write_msr_raw(cpu: u32, msr: u32, val: u64) -> bool {
@@ -156,6 +198,10 @@ fn main() {
std::process::exit(if ok { 0 } else { 1 });
}
let (vendor, base_mhz, max_mhz) = read_sys_cpu_info();
let is_intel = vendor.to_lowercase().contains("intel");
eprintln!("[INFO] cpufreqd: platform {} base={}MHz max={}MHz", vendor, base_mhz, max_mhz);
let governor = match env::var("CPUFREQ_GOVERNOR").unwrap_or_default().as_str() {
"performance" => Governor::Performance, "powersave" => Governor::Powersave,
"conservative" => Governor::Conservative, "schedutil" => Governor::Schedutil,
@@ -164,11 +210,6 @@ fn main() {
let cpus = detect_cpus();
eprintln!("[INFO] cpufreqd: detected {} CPU(s), governor={:?}", cpus.len(), governor);
// Probe MSR availability before attempting any writes.
// In environments without MSR 0x199 (QEMU default, some hypervisors),
// wrmsr causes a kernel #GP that kills the process. By spawning a child
// to test the write first, we detect this safely and degrade to
// monitoring-only mode.
let msr_ok = cpus.iter().all(|&id| probe_msr_available(id));
if !msr_ok {
MSR_AVAILABLE.store(false, Ordering::Relaxed);
@@ -176,7 +217,10 @@ fn main() {
}
let mut ci: Vec<CpuInfo> = cpus.iter().map(|&id| {
let ps = read_acpi_pss(id);
let mut ps = read_acpi_pss(id);
if ps.is_empty() {
ps = generate_pstates_from_freq(base_mhz, max_mhz, is_intel);
}
eprintln!("[INFO] cpufreqd: CPU{}: {} P-states ({} - {} kHz)", id, ps.len(), ps.first().map_or(0, |p| p.freq_khz), ps.last().map_or(0, |p| p.freq_khz));
CpuInfo { id, pstates: ps, current_idx: 0, load_history: [0.0; SAMPLE_WINDOW], load_idx: 0, throttle: false, msr_errors: 0, msr_suppressed: false }
}).collect();