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}