⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/arch/x86_64/
cpufreq.rs

1//! CPU Frequency Scaling via MSR for x86_64.
2//!
3//! Controls CPU P-states (performance states) through the IA32_PERF_CTL
4//! and IA32_PERF_STATUS MSRs. Supports three governors:
5//! - Performance: fixed at maximum frequency
6//! - Powersave: fixed at minimum frequency
7//! - Ondemand: dynamic scaling based on CPU utilization (integer math)
8//!
9//! P-state ratios are read from MSR_PLATFORM_INFO (0xCE) which provides
10//! the minimum and maximum non-turbo frequency ratios. The bus clock
11//! frequency (typically 100 MHz) is used to convert ratios to kHz.
12
13#![allow(dead_code)]
14
15use core::sync::atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering};
16
17use spin::Mutex;
18
19use crate::error::{KernelError, KernelResult};
20
21// ---------------------------------------------------------------------------
22// MSR addresses
23// ---------------------------------------------------------------------------
24
25/// IA32_PERF_STATUS: current P-state (read-only).
26/// Bits 15:0 contain the current performance state value.
27const MSR_IA32_PERF_STATUS: u32 = 0x198;
28
29/// IA32_PERF_CTL: target P-state (read-write).
30/// Bits 15:0 set the target performance state.
31const MSR_IA32_PERF_CTL: u32 = 0x199;
32
33/// MSR_PLATFORM_INFO: platform information including P-state ratios.
34/// Bits 15:8  = maximum non-turbo ratio
35/// Bits 47:40 = minimum ratio (maximum efficiency)
36const MSR_PLATFORM_INFO: u32 = 0xCE;
37
38/// IA32_MISC_ENABLE: miscellaneous feature control.
39/// Bit 16 = Enhanced SpeedStep Technology Enable.
40const MSR_IA32_MISC_ENABLE: u32 = 0x1A0;
41
42/// Enhanced SpeedStep enable bit in IA32_MISC_ENABLE.
43const EIST_ENABLE_BIT: u64 = 1 << 16;
44
45/// CPUID leaf for checking SpeedStep/P-state support.
46const CPUID_LEAF_POWER: u32 = 0x06;
47
48/// CPUID EAX bit 1: Enhanced SpeedStep available.
49const CPUID_EIST_BIT: u32 = 1 << 1;
50
51// ---------------------------------------------------------------------------
52// Governor definitions
53// ---------------------------------------------------------------------------
54
55/// CPU frequency scaling governor.
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57#[repr(u8)]
58pub enum CpuGovernor {
59    /// Fixed at maximum frequency.
60    Performance = 0,
61    /// Fixed at minimum frequency.
62    Powersave = 1,
63    /// Dynamic scaling based on CPU utilization.
64    Ondemand = 2,
65}
66
67impl CpuGovernor {
68    /// Convert from raw u8.
69    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    /// Governor name as a string.
79    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    /// Parse governor name from string.
88    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// ---------------------------------------------------------------------------
105// P-state information
106// ---------------------------------------------------------------------------
107
108/// CPU frequency scaling information.
109#[derive(Debug, Clone, Copy)]
110struct CpuFreqInfo {
111    /// Minimum P-state ratio (maximum efficiency).
112    min_ratio: u8,
113    /// Maximum P-state ratio (non-turbo).
114    max_ratio: u8,
115    /// Bus clock frequency in kHz (typically 100000 = 100 MHz).
116    bus_clock_khz: u64,
117    /// Whether Enhanced SpeedStep is supported.
118    eist_supported: bool,
119    /// Whether P-state control is available.
120    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, // 100 MHz default bus clock
129            eist_supported: false,
130            pstate_available: false,
131        }
132    }
133
134    /// Convert a P-state ratio to frequency in kHz.
135    fn ratio_to_khz(&self, ratio: u8) -> u64 {
136        (ratio as u64).saturating_mul(self.bus_clock_khz)
137    }
138
139    /// Minimum frequency in kHz.
140    fn min_freq_khz(&self) -> u64 {
141        self.ratio_to_khz(self.min_ratio)
142    }
143
144    /// Maximum frequency in kHz.
145    fn max_freq_khz(&self) -> u64 {
146        self.ratio_to_khz(self.max_ratio)
147    }
148}
149
150// ---------------------------------------------------------------------------
151// Ondemand governor state
152// ---------------------------------------------------------------------------
153
154/// Ondemand governor configuration and state.
155#[derive(Debug)]
156struct OndemandState {
157    /// CPU load percentage threshold to scale up (default 80%).
158    up_threshold: u8,
159    /// CPU load percentage threshold to scale down (default 20%).
160    down_threshold: u8,
161    /// Sampling interval in milliseconds (default 100ms).
162    sampling_interval_ms: u32,
163    /// Total ticks in the last sampling period.
164    last_total_ticks: u64,
165    /// Busy ticks in the last sampling period.
166    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
181// ---------------------------------------------------------------------------
182// Global state
183// ---------------------------------------------------------------------------
184
185static CPUFREQ_INITIALIZED: AtomicBool = AtomicBool::new(false);
186static CURRENT_GOVERNOR: AtomicU8 = AtomicU8::new(0); // Performance
187static 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
191// ---------------------------------------------------------------------------
192// Initialization
193// ---------------------------------------------------------------------------
194
195/// Initialize the CPU frequency scaling subsystem.
196///
197/// Detects P-state support via CPUID, reads min/max ratios from
198/// MSR_PLATFORM_INFO, and enables Enhanced SpeedStep if available.
199pub 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    // Check CPUID for Enhanced SpeedStep support.
212    info.eist_supported = check_eist_support();
213    if !info.eist_supported {
214        println!("[CPUFREQ] Enhanced SpeedStep not supported by CPU");
215        // Still initialize with defaults for frequency reporting.
216        info.pstate_available = false;
217    } else {
218        println!("[CPUFREQ] Enhanced SpeedStep supported");
219        info.pstate_available = true;
220
221        // Enable EIST if not already enabled.
222        enable_eist();
223    }
224
225    // Read P-state ratios from MSR_PLATFORM_INFO.
226    if info.pstate_available {
227        let platform_info = super::msr::rdmsr(MSR_PLATFORM_INFO);
228
229        // Bits 15:8 = max non-turbo ratio.
230        info.max_ratio = ((platform_info >> 8) & 0xFF) as u8;
231        // Bits 47:40 = min ratio.
232        info.min_ratio = ((platform_info >> 40) & 0xFF) as u8;
233
234        // Sanity check: if min >= max, use defaults.
235        if info.min_ratio == 0 || info.max_ratio == 0 || info.min_ratio >= info.max_ratio {
236            // Fallback: assume a reasonable range.
237            info.min_ratio = 8; // 800 MHz
238            info.max_ratio = 30; // 3000 MHz
239            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    // Read current frequency.
252    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
266/// Check if Enhanced SpeedStep Technology is supported via CPUID.
267fn check_eist_support() -> bool {
268    // Use CPUID leaf 0x06 to check thermal and power management features.
269    // SAFETY: CPUID is a non-privileged instruction that reads CPU
270    // identification data. Leaf 0x06 returns power management features.
271    unsafe {
272        core::arch::asm!(
273            "push rbx",   // CPUID clobbers EBX
274            "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    // Also check CPUID leaf 1, ECX bit 7 for EIST.
284    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    // EIST is indicated by CPUID.01H:ECX bit 7.
298    (ecx_leaf1 & (1 << 7)) != 0
299}
300
301/// Enable Enhanced SpeedStep via IA32_MISC_ENABLE MSR.
302fn 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
310/// Read current CPU frequency from IA32_PERF_STATUS.
311fn 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
326// ---------------------------------------------------------------------------
327// Governor control
328// ---------------------------------------------------------------------------
329
330/// Set the active CPU frequency governor.
331pub 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    // Apply the governor's frequency policy.
342    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            // Reset ondemand state counters.
352            let mut od = ONDEMAND_STATE.lock();
353            od.last_total_ticks = 0;
354            od.last_busy_ticks = 0;
355            // Start at maximum frequency; ondemand will scale down if idle.
356            set_pstate_ratio(&info, info.max_ratio);
357        }
358    }
359
360    println!("[CPUFREQ] Governor changed: {} -> {}", old, governor);
361    Ok(())
362}
363
364/// Get the current governor.
365pub fn cpufreq_get_governor() -> CpuGovernor {
366    CpuGovernor::from_u8(CURRENT_GOVERNOR.load(Ordering::Acquire))
367}
368
369/// Get the current CPU frequency in kHz.
370pub 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
381/// Set the target CPU frequency in kHz.
382///
383/// Only effective in Performance or Powersave governors. Ondemand
384/// governor manages frequency automatically.
385pub 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    // Convert kHz to ratio (integer division).
400    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    // Clamp to valid range.
409    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/// Get available CPU frequencies in kHz.
419///
420/// Returns frequencies for each P-state ratio from min to max.
421#[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
439/// Get the minimum frequency in kHz.
440pub fn cpufreq_get_min_frequency() -> u64 {
441    let info = CPUFREQ_INFO.lock();
442    info.min_freq_khz()
443}
444
445/// Get the maximum frequency in kHz.
446pub fn cpufreq_get_max_frequency() -> u64 {
447    let info = CPUFREQ_INFO.lock();
448    info.max_freq_khz()
449}
450
451/// Get the list of available governor names.
452pub fn cpufreq_available_governors() -> &'static str {
453    "performance powersave ondemand"
454}
455
456// ---------------------------------------------------------------------------
457// P-state MSR access
458// ---------------------------------------------------------------------------
459
460/// Set the target P-state ratio via IA32_PERF_CTL MSR.
461fn set_pstate_ratio(info: &CpuFreqInfo, ratio: u8) {
462    if !info.pstate_available || ratio == 0 {
463        return;
464    }
465
466    // IA32_PERF_CTL bits 15:8 = target ratio.
467    let value = (ratio as u64) << 8;
468    super::msr::wrmsr(MSR_IA32_PERF_CTL, value);
469}
470
471// ---------------------------------------------------------------------------
472// Ondemand governor tick
473// ---------------------------------------------------------------------------
474
475/// Sample CPU load and adjust frequency (called periodically).
476///
477/// Uses integer math: `load_pct = (busy_ticks * 100) / total_ticks`.
478/// Scales up when load > 80%, scales down when load < 20%.
479///
480/// `busy_ticks` and `total_ticks` are the cumulative scheduler tick
481/// counts since the last sample.
482pub 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    // Calculate delta since last sample.
499    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    // Integer percentage: load_pct = (busy * 100) / total.
510    let load_pct = (delta_busy.saturating_mul(100)) / delta_total;
511
512    // Read current ratio from IA32_PERF_STATUS.
513    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        // High load: jump to max frequency.
518        info.max_ratio
519    } else if load_pct < od.down_threshold as u64 {
520        // Low load: step down one ratio.
521        current_ratio.saturating_sub(1).max(info.min_ratio)
522    } else {
523        // Medium load: keep current.
524        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
534// ---------------------------------------------------------------------------
535// Query API
536// ---------------------------------------------------------------------------
537
538/// Check if cpufreq is initialized.
539pub fn is_initialized() -> bool {
540    CPUFREQ_INITIALIZED.load(Ordering::Acquire)
541}
542
543// ---------------------------------------------------------------------------
544// Tests
545// ---------------------------------------------------------------------------
546
547#[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        // 8 * 100_000 = 800_000 kHz = 800 MHz
608        assert_eq!(info.ratio_to_khz(8), 800_000);
609        // 30 * 100_000 = 3_000_000 kHz = 3000 MHz
610        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        // Simulate: 80 busy ticks out of 100 total = 80% load.
633        let busy = 80u64;
634        let total = 100u64;
635        let load_pct = (busy * 100) / total;
636        assert_eq!(load_pct, 80);
637
638        // 10 busy out of 100 = 10% load.
639        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}