veridian_kernel/arch/x86_64/
cpufreq.rs1#![allow(dead_code)]
14
15use core::sync::atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering};
16
17use spin::Mutex;
18
19use crate::error::{KernelError, KernelResult};
20
21const MSR_IA32_PERF_STATUS: u32 = 0x198;
28
29const MSR_IA32_PERF_CTL: u32 = 0x199;
32
33const MSR_PLATFORM_INFO: u32 = 0xCE;
37
38const MSR_IA32_MISC_ENABLE: u32 = 0x1A0;
41
42const EIST_ENABLE_BIT: u64 = 1 << 16;
44
45const CPUID_LEAF_POWER: u32 = 0x06;
47
48const CPUID_EIST_BIT: u32 = 1 << 1;
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[repr(u8)]
58pub enum CpuGovernor {
59 Performance = 0,
61 Powersave = 1,
63 Ondemand = 2,
65}
66
67impl CpuGovernor {
68 fn from_u8(val: u8) -> Self {
70 match val {
71 0 => Self::Performance,
72 1 => Self::Powersave,
73 2 => Self::Ondemand,
74 _ => Self::Performance,
75 }
76 }
77
78 pub fn name(&self) -> &'static str {
80 match self {
81 Self::Performance => "performance",
82 Self::Powersave => "powersave",
83 Self::Ondemand => "ondemand",
84 }
85 }
86
87 pub fn from_name(name: &str) -> Option<Self> {
89 match name {
90 "performance" => Some(Self::Performance),
91 "powersave" => Some(Self::Powersave),
92 "ondemand" => Some(Self::Ondemand),
93 _ => None,
94 }
95 }
96}
97
98impl core::fmt::Display for CpuGovernor {
99 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
100 write!(f, "{}", self.name())
101 }
102}
103
104#[derive(Debug, Clone, Copy)]
110struct CpuFreqInfo {
111 min_ratio: u8,
113 max_ratio: u8,
115 bus_clock_khz: u64,
117 eist_supported: bool,
119 pstate_available: bool,
121}
122
123impl CpuFreqInfo {
124 const fn new() -> Self {
125 Self {
126 min_ratio: 0,
127 max_ratio: 0,
128 bus_clock_khz: 100_000, eist_supported: false,
130 pstate_available: false,
131 }
132 }
133
134 fn ratio_to_khz(&self, ratio: u8) -> u64 {
136 (ratio as u64).saturating_mul(self.bus_clock_khz)
137 }
138
139 fn min_freq_khz(&self) -> u64 {
141 self.ratio_to_khz(self.min_ratio)
142 }
143
144 fn max_freq_khz(&self) -> u64 {
146 self.ratio_to_khz(self.max_ratio)
147 }
148}
149
150#[derive(Debug)]
156struct OndemandState {
157 up_threshold: u8,
159 down_threshold: u8,
161 sampling_interval_ms: u32,
163 last_total_ticks: u64,
165 last_busy_ticks: u64,
167}
168
169impl OndemandState {
170 const fn new() -> Self {
171 Self {
172 up_threshold: 80,
173 down_threshold: 20,
174 sampling_interval_ms: 100,
175 last_total_ticks: 0,
176 last_busy_ticks: 0,
177 }
178 }
179}
180
181static CPUFREQ_INITIALIZED: AtomicBool = AtomicBool::new(false);
186static CURRENT_GOVERNOR: AtomicU8 = AtomicU8::new(0); static CURRENT_FREQ_KHZ: AtomicU64 = AtomicU64::new(0);
188static CPUFREQ_INFO: Mutex<CpuFreqInfo> = Mutex::new(CpuFreqInfo::new());
189static ONDEMAND_STATE: Mutex<OndemandState> = Mutex::new(OndemandState::new());
190
191pub fn cpufreq_init() -> KernelResult<()> {
200 if CPUFREQ_INITIALIZED.load(Ordering::Acquire) {
201 return Err(KernelError::AlreadyExists {
202 resource: "cpufreq",
203 id: 0,
204 });
205 }
206
207 println!("[CPUFREQ] Initializing CPU frequency scaling...");
208
209 let mut info = CpuFreqInfo::new();
210
211 info.eist_supported = check_eist_support();
213 if !info.eist_supported {
214 println!("[CPUFREQ] Enhanced SpeedStep not supported by CPU");
215 info.pstate_available = false;
217 } else {
218 println!("[CPUFREQ] Enhanced SpeedStep supported");
219 info.pstate_available = true;
220
221 enable_eist();
223 }
224
225 if info.pstate_available {
227 let platform_info = super::msr::rdmsr(MSR_PLATFORM_INFO);
228
229 info.max_ratio = ((platform_info >> 8) & 0xFF) as u8;
231 info.min_ratio = ((platform_info >> 40) & 0xFF) as u8;
233
234 if info.min_ratio == 0 || info.max_ratio == 0 || info.min_ratio >= info.max_ratio {
236 info.min_ratio = 8; info.max_ratio = 30; println!("[CPUFREQ] Using fallback P-state ratios");
240 }
241
242 println!(
243 "[CPUFREQ] P-state range: ratio {}..{} ({} MHz .. {} MHz)",
244 info.min_ratio,
245 info.max_ratio,
246 info.min_freq_khz() / 1000,
247 info.max_freq_khz() / 1000,
248 );
249 }
250
251 let current = read_current_frequency(&info);
253 CURRENT_FREQ_KHZ.store(current, Ordering::Release);
254
255 *CPUFREQ_INFO.lock() = info;
256 CPUFREQ_INITIALIZED.store(true, Ordering::Release);
257
258 println!(
259 "[CPUFREQ] Initialized: current={} MHz, governor=performance",
260 current / 1000
261 );
262
263 Ok(())
264}
265
266fn check_eist_support() -> bool {
268 unsafe {
272 core::arch::asm!(
273 "push rbx", "cpuid",
275 "pop rbx",
276 inout("eax") CPUID_LEAF_POWER => _,
277 out("ecx") _,
278 out("edx") _,
279 options(nomem, preserves_flags),
280 );
281 }
282
283 let ecx_leaf1: u32;
285 unsafe {
286 core::arch::asm!(
287 "push rbx",
288 "cpuid",
289 "pop rbx",
290 inout("eax") 1u32 => _,
291 out("ecx") ecx_leaf1,
292 out("edx") _,
293 options(nomem, preserves_flags),
294 );
295 }
296
297 (ecx_leaf1 & (1 << 7)) != 0
299}
300
301fn enable_eist() {
303 let misc_enable = super::msr::rdmsr(MSR_IA32_MISC_ENABLE);
304 if misc_enable & EIST_ENABLE_BIT == 0 {
305 super::msr::wrmsr(MSR_IA32_MISC_ENABLE, misc_enable | EIST_ENABLE_BIT);
306 println!("[CPUFREQ] EIST enabled via IA32_MISC_ENABLE");
307 }
308}
309
310fn read_current_frequency(info: &CpuFreqInfo) -> u64 {
312 if !info.pstate_available {
313 return info.max_freq_khz();
314 }
315
316 let perf_status = super::msr::rdmsr(MSR_IA32_PERF_STATUS);
317 let current_ratio = ((perf_status >> 8) & 0xFF) as u8;
318
319 if current_ratio == 0 {
320 return info.max_freq_khz();
321 }
322
323 info.ratio_to_khz(current_ratio)
324}
325
326pub fn cpufreq_set_governor(governor: CpuGovernor) -> KernelResult<()> {
332 if !CPUFREQ_INITIALIZED.load(Ordering::Acquire) {
333 return Err(KernelError::NotInitialized {
334 subsystem: "cpufreq",
335 });
336 }
337
338 let old = CpuGovernor::from_u8(CURRENT_GOVERNOR.load(Ordering::Acquire));
339 CURRENT_GOVERNOR.store(governor as u8, Ordering::Release);
340
341 let info = CPUFREQ_INFO.lock();
343 match governor {
344 CpuGovernor::Performance => {
345 set_pstate_ratio(&info, info.max_ratio);
346 }
347 CpuGovernor::Powersave => {
348 set_pstate_ratio(&info, info.min_ratio);
349 }
350 CpuGovernor::Ondemand => {
351 let mut od = ONDEMAND_STATE.lock();
353 od.last_total_ticks = 0;
354 od.last_busy_ticks = 0;
355 set_pstate_ratio(&info, info.max_ratio);
357 }
358 }
359
360 println!("[CPUFREQ] Governor changed: {} -> {}", old, governor);
361 Ok(())
362}
363
364pub fn cpufreq_get_governor() -> CpuGovernor {
366 CpuGovernor::from_u8(CURRENT_GOVERNOR.load(Ordering::Acquire))
367}
368
369pub fn cpufreq_get_frequency() -> u64 {
371 if !CPUFREQ_INITIALIZED.load(Ordering::Acquire) {
372 return 0;
373 }
374
375 let info = CPUFREQ_INFO.lock();
376 let freq = read_current_frequency(&info);
377 CURRENT_FREQ_KHZ.store(freq, Ordering::Release);
378 freq
379}
380
381pub fn cpufreq_set_frequency(khz: u64) -> KernelResult<()> {
386 if !CPUFREQ_INITIALIZED.load(Ordering::Acquire) {
387 return Err(KernelError::NotInitialized {
388 subsystem: "cpufreq",
389 });
390 }
391
392 let info = CPUFREQ_INFO.lock();
393 if !info.pstate_available {
394 return Err(KernelError::OperationNotSupported {
395 operation: "cpufreq_set_frequency (no P-state support)",
396 });
397 }
398
399 if info.bus_clock_khz == 0 {
401 return Err(KernelError::InvalidArgument {
402 name: "bus_clock_khz",
403 value: "zero",
404 });
405 }
406 let ratio = (khz / info.bus_clock_khz) as u8;
407
408 let clamped = ratio.clamp(info.min_ratio, info.max_ratio);
410 set_pstate_ratio(&info, clamped);
411
412 let actual_khz = info.ratio_to_khz(clamped);
413 CURRENT_FREQ_KHZ.store(actual_khz, Ordering::Release);
414
415 Ok(())
416}
417
418#[cfg(feature = "alloc")]
422pub fn cpufreq_get_available_frequencies() -> alloc::vec::Vec<u64> {
423 let info = CPUFREQ_INFO.lock();
424 let mut freqs = alloc::vec::Vec::new();
425
426 if info.min_ratio == 0 || info.max_ratio == 0 {
427 return freqs;
428 }
429
430 let mut ratio = info.min_ratio;
431 while ratio <= info.max_ratio {
432 freqs.push(info.ratio_to_khz(ratio));
433 ratio = ratio.saturating_add(1);
434 }
435
436 freqs
437}
438
439pub fn cpufreq_get_min_frequency() -> u64 {
441 let info = CPUFREQ_INFO.lock();
442 info.min_freq_khz()
443}
444
445pub fn cpufreq_get_max_frequency() -> u64 {
447 let info = CPUFREQ_INFO.lock();
448 info.max_freq_khz()
449}
450
451pub fn cpufreq_available_governors() -> &'static str {
453 "performance powersave ondemand"
454}
455
456fn set_pstate_ratio(info: &CpuFreqInfo, ratio: u8) {
462 if !info.pstate_available || ratio == 0 {
463 return;
464 }
465
466 let value = (ratio as u64) << 8;
468 super::msr::wrmsr(MSR_IA32_PERF_CTL, value);
469}
470
471pub fn cpufreq_ondemand_sample(busy_ticks: u64, total_ticks: u64) {
483 if !CPUFREQ_INITIALIZED.load(Ordering::Acquire) {
484 return;
485 }
486
487 if CpuGovernor::from_u8(CURRENT_GOVERNOR.load(Ordering::Acquire)) != CpuGovernor::Ondemand {
488 return;
489 }
490
491 let info = CPUFREQ_INFO.lock();
492 if !info.pstate_available {
493 return;
494 }
495
496 let mut od = ONDEMAND_STATE.lock();
497
498 let delta_total = total_ticks.saturating_sub(od.last_total_ticks);
500 let delta_busy = busy_ticks.saturating_sub(od.last_busy_ticks);
501
502 od.last_total_ticks = total_ticks;
503 od.last_busy_ticks = busy_ticks;
504
505 if delta_total == 0 {
506 return;
507 }
508
509 let load_pct = (delta_busy.saturating_mul(100)) / delta_total;
511
512 let perf_status = super::msr::rdmsr(MSR_IA32_PERF_STATUS);
514 let current_ratio = ((perf_status >> 8) & 0xFF) as u8;
515
516 let new_ratio = if load_pct >= od.up_threshold as u64 {
517 info.max_ratio
519 } else if load_pct < od.down_threshold as u64 {
520 current_ratio.saturating_sub(1).max(info.min_ratio)
522 } else {
523 current_ratio
525 };
526
527 if new_ratio != current_ratio && new_ratio >= info.min_ratio && new_ratio <= info.max_ratio {
528 set_pstate_ratio(&info, new_ratio);
529 let new_freq = info.ratio_to_khz(new_ratio);
530 CURRENT_FREQ_KHZ.store(new_freq, Ordering::Release);
531 }
532}
533
534pub fn is_initialized() -> bool {
540 CPUFREQ_INITIALIZED.load(Ordering::Acquire)
541}
542
543#[cfg(test)]
548mod tests {
549 use super::*;
550
551 #[test]
552 fn test_governor_from_u8() {
553 assert_eq!(CpuGovernor::from_u8(0), CpuGovernor::Performance);
554 assert_eq!(CpuGovernor::from_u8(1), CpuGovernor::Powersave);
555 assert_eq!(CpuGovernor::from_u8(2), CpuGovernor::Ondemand);
556 assert_eq!(CpuGovernor::from_u8(99), CpuGovernor::Performance);
557 }
558
559 #[test]
560 fn test_governor_name() {
561 assert_eq!(CpuGovernor::Performance.name(), "performance");
562 assert_eq!(CpuGovernor::Powersave.name(), "powersave");
563 assert_eq!(CpuGovernor::Ondemand.name(), "ondemand");
564 }
565
566 #[test]
567 fn test_governor_from_name() {
568 assert_eq!(
569 CpuGovernor::from_name("performance"),
570 Some(CpuGovernor::Performance)
571 );
572 assert_eq!(
573 CpuGovernor::from_name("powersave"),
574 Some(CpuGovernor::Powersave)
575 );
576 assert_eq!(
577 CpuGovernor::from_name("ondemand"),
578 Some(CpuGovernor::Ondemand)
579 );
580 assert_eq!(CpuGovernor::from_name("turbo"), None);
581 }
582
583 #[test]
584 fn test_governor_display() {
585 assert_eq!(
586 alloc::format!("{}", CpuGovernor::Performance),
587 "performance"
588 );
589 assert_eq!(alloc::format!("{}", CpuGovernor::Powersave), "powersave");
590 assert_eq!(alloc::format!("{}", CpuGovernor::Ondemand), "ondemand");
591 }
592
593 #[test]
594 fn test_cpufreq_info_defaults() {
595 let info = CpuFreqInfo::new();
596 assert_eq!(info.bus_clock_khz, 100_000);
597 assert_eq!(info.min_ratio, 0);
598 assert_eq!(info.max_ratio, 0);
599 assert!(!info.eist_supported);
600 }
601
602 #[test]
603 fn test_ratio_to_khz() {
604 let mut info = CpuFreqInfo::new();
605 info.min_ratio = 8;
606 info.max_ratio = 30;
607 assert_eq!(info.ratio_to_khz(8), 800_000);
609 assert_eq!(info.ratio_to_khz(30), 3_000_000);
611 }
612
613 #[test]
614 fn test_min_max_freq() {
615 let mut info = CpuFreqInfo::new();
616 info.min_ratio = 10;
617 info.max_ratio = 40;
618 assert_eq!(info.min_freq_khz(), 1_000_000);
619 assert_eq!(info.max_freq_khz(), 4_000_000);
620 }
621
622 #[test]
623 fn test_ondemand_state_defaults() {
624 let od = OndemandState::new();
625 assert_eq!(od.up_threshold, 80);
626 assert_eq!(od.down_threshold, 20);
627 assert_eq!(od.sampling_interval_ms, 100);
628 }
629
630 #[test]
631 fn test_ondemand_load_calculation() {
632 let busy = 80u64;
634 let total = 100u64;
635 let load_pct = (busy * 100) / total;
636 assert_eq!(load_pct, 80);
637
638 let load_low = (10u64 * 100) / 100;
640 assert_eq!(load_low, 10);
641 }
642
643 #[test]
644 fn test_available_governors_string() {
645 let govs = cpufreq_available_governors();
646 assert!(govs.contains("performance"));
647 assert!(govs.contains("powersave"));
648 assert!(govs.contains("ondemand"));
649 }
650
651 #[test]
652 fn test_eist_enable_bit() {
653 assert_eq!(EIST_ENABLE_BIT, 1 << 16);
654 }
655}