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

veridian_kernel/power/
mod.rs

1//! Power management subsystem for VeridianOS
2//!
3//! Provides CPU power state management:
4//! - C-states (idle states): C0 (Active), C1 (Halt), C2 (StopClock), C3 (Sleep)
5//! - P-states (performance states): frequency/voltage scaling
6//! - OnDemand governor: automatic frequency scaling based on CPU utilization
7//!
8//! Architecture support:
9//! - x86_64: HLT (C1), MWAIT (C2/C3), IA32_PERF_CTL MSR for P-states
10//! - AArch64: WFI for idle states
11//! - RISC-V: WFI for idle states
12
13#![allow(dead_code)]
14
15use core::sync::atomic::{AtomicU8, AtomicUsize, Ordering};
16
17use spin::RwLock;
18
19use crate::error::KernelError;
20
21// ---------------------------------------------------------------------------
22// C-state definitions
23// ---------------------------------------------------------------------------
24
25/// CPU idle power states (ACPI C-states)
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
27#[repr(u8)]
28pub(crate) enum CState {
29    /// C0: CPU is actively executing instructions
30    C0 = 0,
31    /// C1: Halt — core clock stopped, fastest wake-up
32    C1 = 1,
33    /// C2: Stop-Clock — deeper sleep, longer exit latency
34    C2 = 2,
35    /// C3: Sleep — caches may be flushed, longest exit latency
36    C3 = 3,
37}
38
39impl CState {
40    /// Number of defined C-states
41    pub(crate) const COUNT: usize = 4;
42
43    /// Convert from a raw u8 value
44    pub(crate) fn from_u8(val: u8) -> Option<Self> {
45        match val {
46            0 => Some(CState::C0),
47            1 => Some(CState::C1),
48            2 => Some(CState::C2),
49            3 => Some(CState::C3),
50            _ => None,
51        }
52    }
53}
54
55/// Information about a specific C-state
56#[derive(Debug, Clone, Copy)]
57pub(crate) struct CStateInfo {
58    /// The C-state this info describes
59    pub(crate) state: CState,
60    /// Exit latency in nanoseconds (time to return to C0)
61    pub(crate) exit_latency_ns: u64,
62    /// Minimum residency in nanoseconds (must stay this long for net benefit)
63    pub(crate) target_residency_ns: u64,
64    /// Whether this state is supported on the current hardware
65    pub(crate) supported: bool,
66}
67
68/// Default C-state table with typical latencies
69const DEFAULT_CSTATE_TABLE: [CStateInfo; CState::COUNT] = [
70    CStateInfo {
71        state: CState::C0,
72        exit_latency_ns: 0,
73        target_residency_ns: 0,
74        supported: true,
75    },
76    CStateInfo {
77        state: CState::C1,
78        exit_latency_ns: 1_000,      // 1 us
79        target_residency_ns: 10_000, // 10 us
80        supported: true,
81    },
82    CStateInfo {
83        state: CState::C2,
84        exit_latency_ns: 100_000,     // 100 us
85        target_residency_ns: 500_000, // 500 us
86        supported: true,
87    },
88    CStateInfo {
89        state: CState::C3,
90        exit_latency_ns: 1_000_000,     // 1 ms
91        target_residency_ns: 5_000_000, // 5 ms
92        supported: true,
93    },
94];
95
96// ---------------------------------------------------------------------------
97// P-state definitions
98// ---------------------------------------------------------------------------
99
100/// CPU performance state (frequency/voltage operating point)
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub(crate) struct PState {
103    /// CPU frequency in MHz
104    pub(crate) frequency_mhz: u32,
105    /// Core voltage in millivolts
106    pub(crate) voltage_mv: u32,
107    /// Estimated power draw in milliwatts
108    pub(crate) power_mw: u32,
109}
110
111/// Maximum number of P-state entries
112pub(crate) const MAX_PSTATES: usize = 16;
113
114/// Default P-state table (generic x86_64-like frequency steps)
115const DEFAULT_PSTATE_TABLE: [PState; 4] = [
116    PState {
117        frequency_mhz: 800,
118        voltage_mv: 700,
119        power_mw: 5_000,
120    },
121    PState {
122        frequency_mhz: 1600,
123        voltage_mv: 900,
124        power_mw: 15_000,
125    },
126    PState {
127        frequency_mhz: 2400,
128        voltage_mv: 1050,
129        power_mw: 35_000,
130    },
131    PState {
132        frequency_mhz: 3200,
133        voltage_mv: 1200,
134        power_mw: 65_000,
135    },
136];
137
138// ---------------------------------------------------------------------------
139// Governor
140// ---------------------------------------------------------------------------
141
142/// Frequency scaling governor policy
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub(crate) enum Governor {
145    /// Scale frequency based on CPU utilization
146    OnDemand,
147    /// Always run at maximum frequency
148    Performance,
149    /// Always run at minimum frequency
150    PowerSave,
151}
152
153/// Utilization threshold above which governor selects max P-state (percent)
154const GOVERNOR_HIGH_THRESHOLD: u32 = 80;
155/// Utilization threshold below which governor selects min P-state (percent)
156const GOVERNOR_LOW_THRESHOLD: u32 = 20;
157
158/// Compute the target P-state index for OnDemand governor.
159///
160/// - utilization >= 80%: returns max index (highest frequency = index 0 is
161///   lowest, so max = num_pstates - 1... but conventionally P0 is fastest). We
162///   use index `num_pstates - 1` as max frequency (highest index = fastest).
163/// - utilization <= 20%: returns 0 (lowest frequency)
164/// - Between: linear interpolation (integer math, no FPU)
165///
166/// Returns the target P-state index in `0..num_pstates`.
167fn ondemand_target(utilization_percent: u32, num_pstates: usize) -> usize {
168    if num_pstates == 0 {
169        return 0;
170    }
171    let max_idx = num_pstates - 1;
172    if max_idx == 0 {
173        return 0;
174    }
175
176    if utilization_percent >= GOVERNOR_HIGH_THRESHOLD {
177        return max_idx;
178    }
179    if utilization_percent <= GOVERNOR_LOW_THRESHOLD {
180        return 0;
181    }
182
183    // Linear interpolation between low and high thresholds
184    // scaled = (util - low) * max_idx / (high - low)
185    let range = GOVERNOR_HIGH_THRESHOLD - GOVERNOR_LOW_THRESHOLD; // 60
186    let offset = utilization_percent - GOVERNOR_LOW_THRESHOLD;
187    let scaled = (offset as usize * max_idx) / range as usize;
188
189    // Clamp just in case
190    if scaled > max_idx {
191        max_idx
192    } else {
193        scaled
194    }
195}
196
197// ---------------------------------------------------------------------------
198// Global state
199// ---------------------------------------------------------------------------
200
201/// Global power management state
202struct PowerState {
203    /// Supported C-states
204    cstates: [CStateInfo; CState::COUNT],
205    /// Supported P-states (valid entries: 0..num_pstates)
206    pstates: [PState; MAX_PSTATES],
207    /// Number of valid P-state entries
208    num_pstates: usize,
209    /// Active governor policy
210    governor: Governor,
211    /// Whether the subsystem has been initialized
212    initialized: bool,
213    /// Whether MWAIT is supported (x86_64)
214    mwait_supported: bool,
215}
216
217impl PowerState {
218    const fn new() -> Self {
219        Self {
220            cstates: DEFAULT_CSTATE_TABLE,
221            pstates: [PState {
222                frequency_mhz: 0,
223                voltage_mv: 0,
224                power_mw: 0,
225            }; MAX_PSTATES],
226            num_pstates: 0,
227            governor: Governor::OnDemand,
228            initialized: false,
229            mwait_supported: false,
230        }
231    }
232}
233
234static POWER_STATE: RwLock<PowerState> = RwLock::new(PowerState::new());
235
236/// Current C-state (atomic for lock-free read from interrupt context)
237static CURRENT_CSTATE: AtomicU8 = AtomicU8::new(0); // C0
238
239/// Current P-state index (atomic for lock-free read)
240static CURRENT_PSTATE: AtomicUsize = AtomicUsize::new(0);
241
242// ---------------------------------------------------------------------------
243// Initialization
244// ---------------------------------------------------------------------------
245
246/// Initialize the power management subsystem.
247///
248/// Detects hardware capabilities (MWAIT, P-state support) and populates
249/// the C-state and P-state tables. Falls back to hardcoded defaults when
250/// ACPI tables are not available.
251pub(crate) fn init() -> Result<(), KernelError> {
252    let mut state = POWER_STATE.write();
253    if state.initialized {
254        return Err(KernelError::AlreadyExists {
255            resource: "power",
256            id: 0,
257        });
258    }
259
260    // Detect MWAIT support on x86_64 bare metal
261    state.mwait_supported = detect_mwait();
262
263    // Update C-state support based on hardware
264    // C0 and C1 (HLT/WFI) are always supported
265    // C2/C3 require MWAIT on x86_64
266    state.cstates = DEFAULT_CSTATE_TABLE;
267    #[cfg(target_arch = "x86_64")]
268    {
269        state.cstates[2].supported = state.mwait_supported; // C2
270        state.cstates[3].supported = state.mwait_supported; // C3
271    }
272
273    // Populate P-state table from defaults
274    // (In a full implementation, this would parse ACPI _PSS objects)
275    let defaults = &DEFAULT_PSTATE_TABLE;
276    for (i, pstate) in defaults.iter().enumerate() {
277        state.pstates[i] = *pstate;
278    }
279    state.num_pstates = defaults.len();
280
281    state.initialized = true;
282    CURRENT_CSTATE.store(CState::C0 as u8, Ordering::Release);
283    CURRENT_PSTATE.store(0, Ordering::Release);
284
285    Ok(())
286}
287
288// ---------------------------------------------------------------------------
289// MWAIT detection
290// ---------------------------------------------------------------------------
291
292/// Check if MWAIT/MONITOR instructions are supported.
293#[cfg(all(target_arch = "x86_64", target_os = "none"))]
294fn detect_mwait() -> bool {
295    // CPUID leaf 1, ECX bit 3 = MONITOR/MWAIT support
296    let ecx: u32;
297    // SAFETY: CPUID is a non-privileged instruction that returns CPU feature
298    // flags. Leaf 1 is universally supported on x86_64. We save/restore rbx
299    // because LLVM reserves it.
300    unsafe {
301        core::arch::asm!(
302            "push rbx",
303            "cpuid",
304            "pop rbx",
305            inout("eax") 1u32 => _,
306            inout("ecx") 0u32 => ecx,
307            out("edx") _,
308        );
309    }
310    ecx & (1 << 3) != 0
311}
312
313/// Stub for non-x86_64 or host-target builds.
314#[cfg(not(all(target_arch = "x86_64", target_os = "none")))]
315fn detect_mwait() -> bool {
316    false
317}
318
319// ---------------------------------------------------------------------------
320// C-state transitions
321// ---------------------------------------------------------------------------
322
323/// Enter an idle state. The CPU will halt until an interrupt occurs.
324///
325/// The actual C-state entered may be lower than `suggested` if the hardware
326/// does not support the requested state.
327pub(crate) fn enter_idle(suggested: CState) {
328    if suggested == CState::C0 {
329        // C0 = active, nothing to do
330        return;
331    }
332
333    // Clamp to the deepest supported state
334    let effective = clamp_cstate(suggested);
335    CURRENT_CSTATE.store(effective as u8, Ordering::Release);
336
337    // Architecture-specific idle entry
338    arch_enter_idle(effective);
339
340    // We have returned from idle (interrupt woke us)
341    CURRENT_CSTATE.store(CState::C0 as u8, Ordering::Release);
342}
343
344/// Clamp a requested C-state down to the deepest supported one.
345fn clamp_cstate(requested: CState) -> CState {
346    let state = POWER_STATE.read();
347    let mut best = CState::C1; // C1 is always supported
348    let req_val = requested as u8;
349
350    for info in &state.cstates {
351        let sv = info.state as u8;
352        if sv <= req_val && sv >= (best as u8) && info.supported && sv > 0 {
353            best = info.state;
354        }
355    }
356    best
357}
358
359// --- x86_64 bare-metal idle ---
360
361#[cfg(all(target_arch = "x86_64", target_os = "none"))]
362fn arch_enter_idle(cstate: CState) {
363    match cstate {
364        CState::C0 => {} // Should not reach here
365        CState::C1 => {
366            // HLT: halt until next interrupt
367            // SAFETY: HLT is a privileged instruction that halts the CPU
368            // until an interrupt fires. Interrupts must be enabled.
369            unsafe {
370                core::arch::asm!("sti; hlt", options(nomem, nostack));
371            }
372        }
373        CState::C2 => {
374            // MWAIT with C2 hint (sub-state 0x10)
375            x86_mwait(0x10);
376        }
377        CState::C3 => {
378            // MWAIT with C3 hint (sub-state 0x20)
379            x86_mwait(0x20);
380        }
381    }
382}
383
384/// Execute MONITOR + MWAIT with the given hint.
385///
386/// MONITOR sets up an address range to watch; MWAIT halts until a write
387/// to that range (or an interrupt) occurs. We use a dummy stack variable
388/// as the monitored address.
389#[cfg(all(target_arch = "x86_64", target_os = "none"))]
390fn x86_mwait(hint: u32) {
391    // SAFETY: MONITOR/MWAIT are privileged instructions. We pass a valid
392    // stack address for MONITOR. MWAIT halts until an interrupt or store
393    // to the monitored region. Interrupts must be enabled.
394    unsafe {
395        let dummy: u64 = 0;
396        let addr = &dummy as *const u64 as usize;
397        core::arch::asm!(
398            "monitor",
399            in("eax") addr as u32,
400            in("ecx") 0u32,
401            in("edx") 0u32,
402            options(nomem, nostack, preserves_flags),
403        );
404        core::arch::asm!(
405            "sti",
406            "mwait",
407            in("eax") hint,
408            in("ecx") 0u32, // no extensions
409            options(nomem, nostack),
410        );
411    }
412}
413
414// --- AArch64 bare-metal idle ---
415
416#[cfg(all(target_arch = "aarch64", target_os = "none"))]
417fn arch_enter_idle(_cstate: CState) {
418    // AArch64: WFI (Wait For Interrupt) for all idle states.
419    // Deeper C-states would require platform-specific PSCI calls.
420    // SAFETY: WFI halts the core until an interrupt occurs.
421    unsafe {
422        core::arch::asm!("wfi", options(nomem, nostack, preserves_flags));
423    }
424}
425
426// --- RISC-V bare-metal idle ---
427
428#[cfg(all(target_arch = "riscv64", target_os = "none"))]
429fn arch_enter_idle(_cstate: CState) {
430    // RISC-V: WFI (Wait For Interrupt) for all idle states.
431    // Deeper states would require SBI HSM extension calls.
432    // SAFETY: WFI halts the hart until an interrupt occurs.
433    unsafe {
434        core::arch::asm!("wfi", options(nomem, nostack, preserves_flags));
435    }
436}
437
438// --- Host target stub ---
439
440#[cfg(not(target_os = "none"))]
441fn arch_enter_idle(_cstate: CState) {
442    // No-op on host target (used for unit testing / CI)
443}
444
445// ---------------------------------------------------------------------------
446// P-state transitions
447// ---------------------------------------------------------------------------
448
449/// MSR address for IA32_PERF_CTL (P-state selection)
450#[cfg(all(target_arch = "x86_64", target_os = "none"))]
451const IA32_PERF_CTL: u32 = 0x199;
452
453/// Set the CPU frequency to the given P-state index.
454///
455/// Index 0 is the lowest frequency; index `num_pstates - 1` is the highest.
456/// Returns `Err` if the index is out of range or the subsystem is not
457/// initialized.
458pub(crate) fn set_frequency(pstate_index: usize) -> Result<(), KernelError> {
459    let state = POWER_STATE.read();
460    if !state.initialized {
461        return Err(KernelError::NotInitialized { subsystem: "power" });
462    }
463    if pstate_index >= state.num_pstates {
464        return Err(KernelError::InvalidArgument {
465            name: "pstate_index",
466            value: "out of range",
467        });
468    }
469
470    let pstate = state.pstates[pstate_index];
471    drop(state); // Release lock before hardware access
472
473    arch_set_pstate(pstate_index, &pstate);
474    CURRENT_PSTATE.store(pstate_index, Ordering::Release);
475
476    Ok(())
477}
478
479// --- x86_64 bare-metal P-state write ---
480
481#[cfg(all(target_arch = "x86_64", target_os = "none"))]
482fn arch_set_pstate(index: usize, _pstate: &PState) {
483    // IA32_PERF_CTL bits [15:0] select the target P-state.
484    // The actual encoding is platform-specific; we use the index directly
485    // as a simplified mapping. A production implementation would use the
486    // ratio field from ACPI _PSS.
487    let value = index as u64 & 0xFFFF;
488    crate::arch::x86_64::msr::wrmsr(IA32_PERF_CTL, value);
489}
490
491// --- Non-x86_64 / host-target stub ---
492
493#[cfg(not(all(target_arch = "x86_64", target_os = "none")))]
494fn arch_set_pstate(_index: usize, _pstate: &PState) {
495    // P-state control is architecture-specific.
496    // AArch64: SCMI or platform firmware calls would go here.
497    // RISC-V: No standard P-state interface; vendor-specific.
498    // Host: No-op for testing.
499}
500
501// ---------------------------------------------------------------------------
502// Governor
503// ---------------------------------------------------------------------------
504
505/// Called periodically (e.g., on scheduler tick) to adjust P-state
506/// based on CPU utilization.
507///
508/// `utilization_percent` should be 0..=100 representing the fraction
509/// of the last tick period the CPU was not idle.
510pub(crate) fn governor_tick(utilization_percent: u32) -> Result<(), KernelError> {
511    let state = POWER_STATE.read();
512    if !state.initialized {
513        return Err(KernelError::NotInitialized { subsystem: "power" });
514    }
515
516    let governor = state.governor;
517    let num = state.num_pstates;
518    drop(state);
519
520    if num == 0 {
521        return Ok(());
522    }
523
524    let target = match governor {
525        Governor::OnDemand => ondemand_target(utilization_percent, num),
526        Governor::Performance => num - 1,
527        Governor::PowerSave => 0,
528    };
529
530    let current = CURRENT_PSTATE.load(Ordering::Acquire);
531    if target != current {
532        set_frequency(target)?;
533    }
534
535    Ok(())
536}
537
538/// Set the active governor policy.
539pub(crate) fn set_governor(gov: Governor) -> Result<(), KernelError> {
540    let mut state = POWER_STATE.write();
541    if !state.initialized {
542        return Err(KernelError::NotInitialized { subsystem: "power" });
543    }
544    state.governor = gov;
545    Ok(())
546}
547
548/// Get the active governor policy.
549pub(crate) fn get_governor() -> Governor {
550    POWER_STATE.read().governor
551}
552
553// ---------------------------------------------------------------------------
554// Query functions
555// ---------------------------------------------------------------------------
556
557/// Get the current C-state (lock-free atomic read).
558pub(crate) fn get_current_cstate() -> CState {
559    let val = CURRENT_CSTATE.load(Ordering::Acquire);
560    CState::from_u8(val).unwrap_or(CState::C0)
561}
562
563/// Get the current P-state index (lock-free atomic read).
564pub(crate) fn get_current_pstate() -> usize {
565    CURRENT_PSTATE.load(Ordering::Acquire)
566}
567
568/// Get the table of supported C-states.
569///
570/// Returns a fixed-size array; check `supported` field per entry.
571pub(crate) fn get_supported_cstates() -> [CStateInfo; CState::COUNT] {
572    POWER_STATE.read().cstates
573}
574
575/// Get the supported P-states.
576///
577/// Returns a slice-like view: the first `count` entries in the returned
578/// tuple are valid.
579pub(crate) fn get_supported_pstates() -> ([PState; MAX_PSTATES], usize) {
580    let state = POWER_STATE.read();
581    (state.pstates, state.num_pstates)
582}
583
584/// Get the number of supported P-states.
585pub(crate) fn get_pstate_count() -> usize {
586    POWER_STATE.read().num_pstates
587}
588
589/// Check if the subsystem is initialized.
590pub(crate) fn is_initialized() -> bool {
591    POWER_STATE.read().initialized
592}
593
594// ---------------------------------------------------------------------------
595// Unit tests
596// ---------------------------------------------------------------------------
597
598#[cfg(test)]
599mod tests {
600    use super::*;
601
602    // -- Governor logic tests --
603
604    #[test]
605    fn test_ondemand_high_utilization() {
606        // >= 80% should return max index
607        assert_eq!(ondemand_target(80, 4), 3);
608        assert_eq!(ondemand_target(100, 4), 3);
609        assert_eq!(ondemand_target(95, 8), 7);
610    }
611
612    #[test]
613    fn test_ondemand_low_utilization() {
614        // <= 20% should return 0
615        assert_eq!(ondemand_target(0, 4), 0);
616        assert_eq!(ondemand_target(10, 4), 0);
617        assert_eq!(ondemand_target(20, 4), 0);
618    }
619
620    #[test]
621    fn test_ondemand_mid_utilization() {
622        // 50% is in the middle of the 20-80 range (offset 30 out of 60)
623        // With 4 P-states (max_idx=3): 30*3/60 = 1
624        assert_eq!(ondemand_target(50, 4), 1);
625
626        // 60% -> offset 40 out of 60, 4 states: 40*3/60 = 2
627        assert_eq!(ondemand_target(60, 4), 2);
628
629        // 70% -> offset 50 out of 60, 4 states: 50*3/60 = 2
630        assert_eq!(ondemand_target(70, 4), 2);
631    }
632
633    #[test]
634    fn test_ondemand_edge_cases() {
635        // 0 P-states
636        assert_eq!(ondemand_target(50, 0), 0);
637        // 1 P-state
638        assert_eq!(ondemand_target(50, 1), 0);
639        // 2 P-states, 50% -> offset 30/60 * 1 = 0
640        assert_eq!(ondemand_target(50, 2), 0);
641        // 2 P-states, 79% -> offset 59/60 * 1 = 0 (integer division)
642        assert_eq!(ondemand_target(79, 2), 0);
643        // 2 P-states, 80% -> max
644        assert_eq!(ondemand_target(80, 2), 1);
645    }
646
647    #[test]
648    fn test_ondemand_linear_scaling() {
649        // With 16 P-states (max_idx=15):
650        // 50% -> offset 30 out of 60: 30*15/60 = 7
651        assert_eq!(ondemand_target(50, 16), 7);
652        // 21% -> offset 1 out of 60: 1*15/60 = 0
653        assert_eq!(ondemand_target(21, 16), 0);
654        // 79% -> offset 59 out of 60: 59*15/60 = 14
655        assert_eq!(ondemand_target(79, 16), 14);
656    }
657
658    // -- C-state tests --
659
660    #[test]
661    fn test_cstate_ordering() {
662        assert!(CState::C0 < CState::C1);
663        assert!(CState::C1 < CState::C2);
664        assert!(CState::C2 < CState::C3);
665    }
666
667    #[test]
668    fn test_cstate_from_u8() {
669        assert_eq!(CState::from_u8(0), Some(CState::C0));
670        assert_eq!(CState::from_u8(1), Some(CState::C1));
671        assert_eq!(CState::from_u8(2), Some(CState::C2));
672        assert_eq!(CState::from_u8(3), Some(CState::C3));
673        assert_eq!(CState::from_u8(4), None);
674        assert_eq!(CState::from_u8(255), None);
675    }
676
677    #[test]
678    fn test_cstate_info_defaults() {
679        let table = DEFAULT_CSTATE_TABLE;
680        // C0 has zero latency
681        assert_eq!(table[0].exit_latency_ns, 0);
682        assert_eq!(table[0].target_residency_ns, 0);
683        assert!(table[0].supported);
684
685        // C1 has 1us exit latency
686        assert_eq!(table[1].exit_latency_ns, 1_000);
687        assert!(table[1].supported);
688
689        // Deeper states have increasing latency
690        assert!(table[2].exit_latency_ns > table[1].exit_latency_ns);
691        assert!(table[3].exit_latency_ns > table[2].exit_latency_ns);
692    }
693
694    // -- P-state tests --
695
696    #[test]
697    fn test_pstate_defaults() {
698        let table = DEFAULT_PSTATE_TABLE;
699        // Frequency increases with index
700        for i in 1..table.len() {
701            assert!(table[i].frequency_mhz > table[i - 1].frequency_mhz);
702        }
703        // Power increases with frequency
704        for i in 1..table.len() {
705            assert!(table[i].power_mw > table[i - 1].power_mw);
706        }
707    }
708
709    #[test]
710    fn test_pstate_bounds() {
711        // Ensure default P-states are within reasonable bounds
712        for p in &DEFAULT_PSTATE_TABLE {
713            assert!(p.frequency_mhz >= 100);
714            assert!(p.frequency_mhz <= 10_000);
715            assert!(p.voltage_mv >= 500);
716            assert!(p.voltage_mv <= 2_000);
717            assert!(p.power_mw > 0);
718            assert!(p.power_mw <= 500_000);
719        }
720    }
721
722    // -- Integration-style tests (use global state) --
723
724    #[test]
725    fn test_enter_idle_c0_noop() {
726        // C0 should be a no-op (no halt)
727        enter_idle(CState::C0);
728        // If we get here, it did not halt
729    }
730
731    #[test]
732    fn test_governor_variants() {
733        // Verify governor enum values
734        assert_ne!(Governor::OnDemand, Governor::Performance);
735        assert_ne!(Governor::Performance, Governor::PowerSave);
736        assert_ne!(Governor::OnDemand, Governor::PowerSave);
737    }
738}