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

veridian_kernel/arch/x86_64/
acpi_pm.rs

1//! ACPI Power Management for x86_64.
2//!
3//! Implements ACPI sleep state transitions (S0-S5), SCI interrupt handling,
4//! and wake event processing. Reads PM1a/PM1b control and status registers
5//! from the FADT to orchestrate suspend (S3), hibernate (S4), and soft-off
6//! (S5).
7//!
8//! CPU context save/restore for S3 resume uses inline assembly to capture
9//! and restore general-purpose registers, segment descriptors, and CR3.
10
11#![allow(dead_code)]
12
13use core::sync::atomic::{AtomicBool, AtomicU16, AtomicU32, AtomicU8, Ordering};
14
15use spin::Mutex;
16
17use crate::error::{KernelError, KernelResult};
18
19// ---------------------------------------------------------------------------
20// ACPI sleep state definitions
21// ---------------------------------------------------------------------------
22
23/// ACPI system sleep states.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[repr(u8)]
26pub enum AcpiSleepState {
27    /// S0: Working (system fully operational).
28    S0Working = 0,
29    /// S1: Standby (CPU stops executing, power to CPU/RAM maintained).
30    S1Standby = 1,
31    /// S3: Suspend to RAM (CPU context saved, RAM remains powered).
32    S3Suspend = 3,
33    /// S4: Hibernate (memory image saved to disk, full power off).
34    S4Hibernate = 4,
35    /// S5: Soft Off (mechanical off via ACPI).
36    S5SoftOff = 5,
37}
38
39impl core::fmt::Display for AcpiSleepState {
40    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
41        match self {
42            Self::S0Working => write!(f, "S0 (Working)"),
43            Self::S1Standby => write!(f, "S1 (Standby)"),
44            Self::S3Suspend => write!(f, "S3 (Suspend to RAM)"),
45            Self::S4Hibernate => write!(f, "S4 (Hibernate)"),
46            Self::S5SoftOff => write!(f, "S5 (Soft Off)"),
47        }
48    }
49}
50
51// ---------------------------------------------------------------------------
52// ACPI PM register bit definitions
53// ---------------------------------------------------------------------------
54
55/// SLP_EN bit in PM1_CNT register -- triggers the sleep transition.
56const SLP_EN: u16 = 1 << 13;
57
58/// SCI_EN bit in PM1_CNT -- indicates ACPI mode is active.
59const SCI_EN: u16 = 1;
60
61/// WAK_STS bit in PM1_STS -- set when system has woken from sleep.
62const WAK_STS: u16 = 1 << 15;
63
64/// PWRBTN_STS bit in PM1_STS -- power button pressed.
65const PWRBTN_STS: u16 = 1 << 8;
66
67/// PWRBTN_EN bit in PM1_EN -- enable power button event.
68const PWRBTN_EN: u16 = 1 << 8;
69
70/// GBL_STS bit in PM1_STS -- BIOS wants attention.
71const GBL_STS: u16 = 1 << 5;
72
73/// TMR_STS bit in PM1_STS -- PM timer overflow.
74const TMR_STS: u16 = 1;
75
76// ---------------------------------------------------------------------------
77// ACPI wake event types
78// ---------------------------------------------------------------------------
79
80/// Types of wake events that can resume the system from sleep.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum AcpiWakeEvent {
83    /// Power button press.
84    PowerButton,
85    /// Lid open event.
86    LidOpen,
87    /// RTC alarm.
88    RtcAlarm,
89    /// USB device activity.
90    UsbWake,
91    /// Network (Wake-on-LAN).
92    NetworkWake,
93    /// Unknown or unclassified wake source.
94    Unknown,
95}
96
97// ---------------------------------------------------------------------------
98// Lid state
99// ---------------------------------------------------------------------------
100
101/// Laptop lid state.
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum LidState {
104    Open,
105    Closed,
106    Unknown,
107}
108
109// ---------------------------------------------------------------------------
110// FADT-derived PM register info
111// ---------------------------------------------------------------------------
112
113/// Parsed FADT power management register addresses and SLP_TYP values.
114#[derive(Debug, Clone, Copy)]
115struct FadtPmInfo {
116    /// PM1a control block I/O port.
117    pm1a_cnt_blk: u16,
118    /// PM1b control block I/O port (0 if not present).
119    pm1b_cnt_blk: u16,
120    /// PM1a event block I/O port (status register).
121    pm1a_evt_blk: u16,
122    /// PM1b event block I/O port (0 if not present).
123    pm1b_evt_blk: u16,
124    /// PM1 event block length (total bytes; status and enable each get half).
125    pm1_evt_len: u8,
126    /// PM1 control block length.
127    pm1_cnt_len: u8,
128    /// SLP_TYP value for S1 (from \_S1 ACPI object).
129    slp_typ_s1: u16,
130    /// SLP_TYP value for S3 (from \_S3 ACPI object).
131    slp_typ_s3: u16,
132    /// SLP_TYP value for S4 (from \_S4 ACPI object).
133    slp_typ_s4: u16,
134    /// SLP_TYP value for S5 (from \_S5 ACPI object).
135    slp_typ_s5: u16,
136    /// SCI interrupt number.
137    sci_int: u16,
138    /// SMI command port.
139    smi_cmd: u16,
140    /// Value to write to SMI_CMD to enable ACPI.
141    acpi_enable: u8,
142    /// Value to write to SMI_CMD to disable ACPI.
143    acpi_disable: u8,
144    /// GPE0 block I/O port.
145    gpe0_blk: u16,
146    /// GPE0 block length.
147    gpe0_blk_len: u8,
148}
149
150impl FadtPmInfo {
151    const fn new() -> Self {
152        Self {
153            pm1a_cnt_blk: 0,
154            pm1b_cnt_blk: 0,
155            pm1a_evt_blk: 0,
156            pm1b_evt_blk: 0,
157            pm1_evt_len: 0,
158            pm1_cnt_len: 0,
159            slp_typ_s1: 0,
160            slp_typ_s3: 5, // QEMU default for S3
161            slp_typ_s4: 6, // QEMU default for S4
162            slp_typ_s5: 7, // QEMU default for S5
163            sci_int: 9,
164            smi_cmd: 0,
165            acpi_enable: 0,
166            acpi_disable: 0,
167            gpe0_blk: 0,
168            gpe0_blk_len: 0,
169        }
170    }
171
172    /// PM1a status register port (first half of event block).
173    fn pm1a_sts_port(&self) -> u16 {
174        self.pm1a_evt_blk
175    }
176
177    /// PM1a enable register port (second half of event block).
178    fn pm1a_en_port(&self) -> u16 {
179        self.pm1a_evt_blk + (self.pm1_evt_len as u16 / 2)
180    }
181}
182
183// ---------------------------------------------------------------------------
184// CPU context for S3 save/restore
185// ---------------------------------------------------------------------------
186
187/// Saved CPU state for S3 suspend/resume.
188///
189/// Captures all registers needed to resume execution after S3 wake:
190/// general-purpose registers, segment selectors, CR3 (page tables),
191/// GDT/IDT descriptors, and the stack pointer.
192#[repr(C)]
193#[derive(Debug, Clone, Copy)]
194struct CpuSuspendContext {
195    /// General-purpose registers.
196    rax: u64,
197    rbx: u64,
198    rcx: u64,
199    rdx: u64,
200    rsi: u64,
201    rdi: u64,
202    rbp: u64,
203    rsp: u64,
204    r8: u64,
205    r9: u64,
206    r10: u64,
207    r11: u64,
208    r12: u64,
209    r13: u64,
210    r14: u64,
211    r15: u64,
212    /// Instruction pointer (return address).
213    rip: u64,
214    /// RFLAGS register.
215    rflags: u64,
216    /// CR3 -- page table root physical address.
217    cr3: u64,
218    /// CR0 -- control register 0.
219    cr0: u64,
220    /// CR4 -- control register 4.
221    cr4: u64,
222    /// GDT descriptor (limit + base).
223    gdt_limit: u16,
224    gdt_base: u64,
225    /// IDT descriptor (limit + base).
226    idt_limit: u16,
227    idt_base: u64,
228    /// Segment selectors.
229    cs: u16,
230    ds: u16,
231    es: u16,
232    ss: u16,
233    fs: u16,
234    gs: u16,
235}
236
237impl CpuSuspendContext {
238    const fn new() -> Self {
239        Self {
240            rax: 0,
241            rbx: 0,
242            rcx: 0,
243            rdx: 0,
244            rsi: 0,
245            rdi: 0,
246            rbp: 0,
247            rsp: 0,
248            r8: 0,
249            r9: 0,
250            r10: 0,
251            r11: 0,
252            r12: 0,
253            r13: 0,
254            r14: 0,
255            r15: 0,
256            rip: 0,
257            rflags: 0,
258            cr3: 0,
259            cr0: 0,
260            cr4: 0,
261            gdt_limit: 0,
262            gdt_base: 0,
263            idt_limit: 0,
264            idt_base: 0,
265            cs: 0,
266            ds: 0,
267            es: 0,
268            ss: 0,
269            fs: 0,
270            gs: 0,
271        }
272    }
273}
274
275// ---------------------------------------------------------------------------
276// Global state
277// ---------------------------------------------------------------------------
278
279static PM_INITIALIZED: AtomicBool = AtomicBool::new(false);
280static FADT_PM_INFO: Mutex<FadtPmInfo> = Mutex::new(FadtPmInfo::new());
281static SUSPEND_CONTEXT: Mutex<CpuSuspendContext> = Mutex::new(CpuSuspendContext::new());
282static CURRENT_STATE: AtomicU8 = AtomicU8::new(0); // S0
283static LID_STATE: AtomicU8 = AtomicU8::new(2); // Unknown
284static WAKE_EVENT_COUNT: AtomicU32 = AtomicU32::new(0);
285static LAST_WAKE_EVENT: AtomicU8 = AtomicU8::new(5); // Unknown
286
287// Supported sleep states bitmask (bit N = SN supported)
288static SUPPORTED_STATES: AtomicU16 = AtomicU16::new(0);
289
290// ---------------------------------------------------------------------------
291// FADT table signature and structure
292// ---------------------------------------------------------------------------
293
294const FADT_SIGNATURE: &[u8; 4] = b"FACP";
295
296/// FADT (Fixed ACPI Description Table) -- partial, only PM-relevant fields.
297/// Full FADT is 276 bytes for ACPI 6.0; we only read what we need.
298#[repr(C, packed)]
299struct FadtHeader {
300    /// Standard ACPI SDT header (36 bytes).
301    signature: [u8; 4],
302    length: u32,
303    revision: u8,
304    checksum: u8,
305    oem_id: [u8; 6],
306    oem_table_id: [u8; 8],
307    oem_revision: u32,
308    creator_id: u32,
309    creator_revision: u32,
310    /// Offset 36: FIRMWARE_CTRL.
311    firmware_ctrl: u32,
312    /// Offset 40: DSDT address.
313    dsdt: u32,
314    /// Offset 44: Reserved (ACPI 1.0 INT_MODEL).
315    _reserved1: u8,
316    /// Offset 45: Preferred PM profile.
317    preferred_pm_profile: u8,
318    /// Offset 46: SCI interrupt vector.
319    sci_int: u16,
320    /// Offset 48: SMI command port.
321    smi_cmd: u32,
322    /// Offset 52: ACPI enable value.
323    acpi_enable: u8,
324    /// Offset 53: ACPI disable value.
325    acpi_disable: u8,
326    /// Offset 54-55: S4BIOS_REQ, PSTATE_CNT.
327    _s4bios_req: u8,
328    _pstate_cnt: u8,
329    /// Offset 56: PM1a event block.
330    pm1a_evt_blk: u32,
331    /// Offset 60: PM1b event block.
332    pm1b_evt_blk: u32,
333    /// Offset 64: PM1a control block.
334    pm1a_cnt_blk: u32,
335    /// Offset 68: PM1b control block.
336    pm1b_cnt_blk: u32,
337    /// Offset 72: PM2 control block.
338    _pm2_cnt_blk: u32,
339    /// Offset 76: PM timer block.
340    _pm_tmr_blk: u32,
341    /// Offset 80: GPE0 block.
342    gpe0_blk: u32,
343    /// Offset 84: GPE1 block.
344    _gpe1_blk: u32,
345    /// Offset 88: PM1 event length.
346    pm1_evt_len: u8,
347    /// Offset 89: PM1 control length.
348    pm1_cnt_len: u8,
349    /// Offset 90: PM2 control length.
350    _pm2_cnt_len: u8,
351    /// Offset 91: PM timer length.
352    _pm_tmr_len: u8,
353    /// Offset 92: GPE0 block length.
354    gpe0_blk_len: u8,
355}
356
357// ---------------------------------------------------------------------------
358// Initialization
359// ---------------------------------------------------------------------------
360
361/// Initialize the ACPI power management subsystem.
362///
363/// Parses the FADT to extract PM1a/PM1b control and status register addresses,
364/// SLP_TYP values for each sleep state, and SCI interrupt configuration.
365///
366/// Must be called after `acpi::init()` has completed.
367pub fn acpi_pm_init() -> KernelResult<()> {
368    if PM_INITIALIZED.load(Ordering::Acquire) {
369        return Err(KernelError::AlreadyExists {
370            resource: "ACPI PM",
371            id: 0,
372        });
373    }
374
375    println!("[ACPI-PM] Initializing power management...");
376
377    // Find FADT in ACPI tables by scanning the RSDT/XSDT.
378    // For now, use default QEMU values which work for the common case.
379    let mut info = FadtPmInfo::new();
380
381    // Try to parse FADT from firmware tables.
382    if let Some(fadt_info) = parse_fadt_from_tables() {
383        info = fadt_info;
384        println!(
385            "[ACPI-PM] FADT parsed: PM1a_CNT={:#x}, PM1a_EVT={:#x}, SCI={}",
386            info.pm1a_cnt_blk, info.pm1a_evt_blk, info.sci_int
387        );
388    } else {
389        // Use QEMU defaults (PIIX4 PM).
390        info.pm1a_cnt_blk = 0x0604;
391        info.pm1a_evt_blk = 0x0600;
392        info.pm1_evt_len = 4;
393        info.pm1_cnt_len = 2;
394        info.sci_int = 9;
395        info.smi_cmd = 0x00B2;
396        info.gpe0_blk = 0x0620;
397        info.gpe0_blk_len = 8;
398        println!("[ACPI-PM] Using QEMU/PIIX4 PM defaults");
399    }
400
401    // Determine supported sleep states.
402    let mut supported: u16 = 1; // S0 always supported
403    supported |= 1 << 1; // S1 (standby)
404
405    // S3 is supported if PM1a_CNT is available.
406    if info.pm1a_cnt_blk != 0 {
407        supported |= 1 << 3; // S3
408        supported |= 1 << 4; // S4 (requires swap, but report as available)
409        supported |= 1 << 5; // S5
410    }
411
412    SUPPORTED_STATES.store(supported, Ordering::Release);
413
414    // Enable ACPI mode if not already enabled.
415    if info.smi_cmd != 0 && info.acpi_enable != 0 {
416        let cnt = read_pm1a_cnt(&info);
417        if cnt & SCI_EN == 0 {
418            println!("[ACPI-PM] Enabling ACPI mode via SMI_CMD...");
419            // SAFETY: Writing acpi_enable value to SMI_CMD port transitions
420            // the chipset from legacy to ACPI mode. This is a one-time
421            // initialization that enables SCI interrupts.
422            unsafe {
423                super::outb(info.smi_cmd, info.acpi_enable);
424            }
425
426            // Wait for SCI_EN to be set (up to 300 iterations).
427            let mut retries = 300u32;
428            loop {
429                let cnt = read_pm1a_cnt(&info);
430                if cnt & SCI_EN != 0 {
431                    break;
432                }
433                retries = retries.saturating_sub(1);
434                if retries == 0 {
435                    println!("[ACPI-PM] WARNING: SCI_EN not set after enabling ACPI");
436                    break;
437                }
438                // Brief delay via I/O port read.
439                // SAFETY: Port 0x80 is the POST diagnostic port, commonly
440                // used as a ~1us I/O delay on x86 systems.
441                unsafe {
442                    super::inb(0x80);
443                }
444            }
445        }
446    }
447
448    // Enable power button events.
449    if info.pm1a_evt_blk != 0 {
450        enable_power_button_event(&info);
451    }
452
453    *FADT_PM_INFO.lock() = info;
454    PM_INITIALIZED.store(true, Ordering::Release);
455
456    println!(
457        "[ACPI-PM] Initialized: supported states = S0{}{}{}{}",
458        if supported & (1 << 1) != 0 { ",S1" } else { "" },
459        if supported & (1 << 3) != 0 { ",S3" } else { "" },
460        if supported & (1 << 4) != 0 { ",S4" } else { "" },
461        if supported & (1 << 5) != 0 { ",S5" } else { "" },
462    );
463
464    Ok(())
465}
466
467/// Attempt to parse FADT from ACPI table hierarchy.
468fn parse_fadt_from_tables() -> Option<FadtPmInfo> {
469    // Access boot info to get RSDP, then walk RSDT/XSDT looking for FACP.
470    #[allow(static_mut_refs)]
471    let rsdp_phys = unsafe {
472        super::boot::BOOT_INFO
473            .as_ref()
474            .and_then(|bi| bi.rsdp_addr.into_option())
475    }?;
476
477    let rsdp_vaddr = super::msr::phys_to_virt(rsdp_phys as usize)?;
478
479    // SAFETY: rsdp_vaddr points to a valid RSDP mapped by the bootloader.
480    let rsdp = unsafe { &*(rsdp_vaddr as *const FadtRsdp) };
481    if &rsdp.signature != b"RSD PTR " {
482        return None;
483    }
484
485    // Use XSDT for ACPI 2.0+, RSDT otherwise.
486    if rsdp.revision >= 2 {
487        // SAFETY: ACPI 2.0 RSDP has xsdt_address at offset 24.
488        let rsdp2 = unsafe { &*(rsdp_vaddr as *const FadtRsdp2) };
489        let xsdt_phys = { rsdp2.xsdt_address } as usize;
490        if xsdt_phys != 0 {
491            let xsdt_vaddr = super::msr::phys_to_virt(xsdt_phys)?;
492            return find_fadt_in_xsdt(xsdt_vaddr);
493        }
494    }
495
496    let rsdt_phys = { rsdp.rsdt_address } as usize;
497    let rsdt_vaddr = super::msr::phys_to_virt(rsdt_phys)?;
498    find_fadt_in_rsdt(rsdt_vaddr)
499}
500
501// Minimal RSDP structs for FADT lookup (duplicated from acpi.rs to avoid
502// coupling to private types).
503#[repr(C, packed)]
504struct FadtRsdp {
505    signature: [u8; 8],
506    checksum: u8,
507    oem_id: [u8; 6],
508    revision: u8,
509    rsdt_address: u32,
510}
511
512#[repr(C, packed)]
513struct FadtRsdp2 {
514    base: FadtRsdp,
515    length: u32,
516    xsdt_address: u64,
517    extended_checksum: u8,
518    _reserved: [u8; 3],
519}
520
521#[repr(C, packed)]
522struct SdtHeader {
523    signature: [u8; 4],
524    length: u32,
525    revision: u8,
526    checksum: u8,
527    oem_id: [u8; 6],
528    oem_table_id: [u8; 8],
529    oem_revision: u32,
530    creator_id: u32,
531    creator_revision: u32,
532}
533
534fn find_fadt_in_rsdt(rsdt_vaddr: usize) -> Option<FadtPmInfo> {
535    // SAFETY: rsdt_vaddr points to a valid RSDT.
536    let sdt = unsafe { &*(rsdt_vaddr as *const SdtHeader) };
537    let len = { sdt.length } as usize;
538    let header_size = core::mem::size_of::<SdtHeader>();
539    let num_entries = (len.saturating_sub(header_size)) / 4;
540
541    for i in 0..num_entries {
542        let ptr_addr = rsdt_vaddr + header_size + i * 4;
543        // SAFETY: ptr_addr is within RSDT bounds.
544        let phys_addr = unsafe { *(ptr_addr as *const u32) } as usize;
545        if let Some(vaddr) = super::msr::phys_to_virt(phys_addr) {
546            if let Some(info) = try_parse_fadt(vaddr) {
547                return Some(info);
548            }
549        }
550    }
551    None
552}
553
554fn find_fadt_in_xsdt(xsdt_vaddr: usize) -> Option<FadtPmInfo> {
555    // SAFETY: xsdt_vaddr points to a valid XSDT.
556    let sdt = unsafe { &*(xsdt_vaddr as *const SdtHeader) };
557    let len = { sdt.length } as usize;
558    let header_size = core::mem::size_of::<SdtHeader>();
559    let num_entries = (len.saturating_sub(header_size)) / 8;
560
561    for i in 0..num_entries {
562        let ptr_addr = xsdt_vaddr + header_size + i * 8;
563        // SAFETY: ptr_addr is within XSDT bounds.
564        let phys_addr = unsafe { *(ptr_addr as *const u64) } as usize;
565        if let Some(vaddr) = super::msr::phys_to_virt(phys_addr) {
566            if let Some(info) = try_parse_fadt(vaddr) {
567                return Some(info);
568            }
569        }
570    }
571    None
572}
573
574fn try_parse_fadt(vaddr: usize) -> Option<FadtPmInfo> {
575    // SAFETY: vaddr points to a valid ACPI table header.
576    let sdt = unsafe { &*(vaddr as *const SdtHeader) };
577    if &{ sdt.signature } != FADT_SIGNATURE {
578        return None;
579    }
580
581    let len = { sdt.length } as usize;
582    if len < core::mem::size_of::<FadtHeader>() {
583        return None;
584    }
585
586    // SAFETY: vaddr points to a valid FADT with sufficient length.
587    let fadt = unsafe { &*(vaddr as *const FadtHeader) };
588
589    let mut info = FadtPmInfo::new();
590    info.pm1a_cnt_blk = fadt.pm1a_cnt_blk as u16;
591    info.pm1b_cnt_blk = fadt.pm1b_cnt_blk as u16;
592    info.pm1a_evt_blk = fadt.pm1a_evt_blk as u16;
593    info.pm1b_evt_blk = fadt.pm1b_evt_blk as u16;
594    info.pm1_evt_len = fadt.pm1_evt_len;
595    info.pm1_cnt_len = fadt.pm1_cnt_len;
596    info.sci_int = fadt.sci_int;
597    info.smi_cmd = fadt.smi_cmd as u16;
598    info.acpi_enable = fadt.acpi_enable;
599    info.acpi_disable = fadt.acpi_disable;
600    info.gpe0_blk = fadt.gpe0_blk as u16;
601    info.gpe0_blk_len = fadt.gpe0_blk_len;
602
603    Some(info)
604}
605
606// ---------------------------------------------------------------------------
607// PM register access helpers
608// ---------------------------------------------------------------------------
609
610/// Read PM1a control register.
611fn read_pm1a_cnt(info: &FadtPmInfo) -> u16 {
612    if info.pm1a_cnt_blk == 0 {
613        return 0;
614    }
615    // SAFETY: pm1a_cnt_blk is a validated ACPI PM1a control register port
616    // parsed from the FADT. Reading this port returns the current PM control
617    // register value.
618    unsafe { super::inw(info.pm1a_cnt_blk) }
619}
620
621/// Write PM1a control register.
622fn write_pm1a_cnt(info: &FadtPmInfo, value: u16) {
623    if info.pm1a_cnt_blk == 0 {
624        return;
625    }
626    // SAFETY: pm1a_cnt_blk is a validated ACPI PM1a control register port.
627    // Writing to this port updates the PM control state (e.g., sleep type,
628    // SLP_EN to trigger sleep).
629    unsafe { super::outw(info.pm1a_cnt_blk, value) }
630}
631
632/// Write PM1b control register (if present).
633fn write_pm1b_cnt(info: &FadtPmInfo, value: u16) {
634    if info.pm1b_cnt_blk == 0 {
635        return;
636    }
637    // SAFETY: pm1b_cnt_blk is the secondary PM control register port from
638    // the FADT. Writing mirrors the PM1a control operation.
639    unsafe { super::outw(info.pm1b_cnt_blk, value) }
640}
641
642/// Read PM1a status register.
643fn read_pm1a_sts(info: &FadtPmInfo) -> u16 {
644    if info.pm1a_evt_blk == 0 {
645        return 0;
646    }
647    // SAFETY: pm1a_evt_blk is the PM1a event (status) register port from
648    // the FADT. Reading returns the current PM event status bits.
649    unsafe { super::inw(info.pm1a_sts_port()) }
650}
651
652/// Write PM1a status register (to clear status bits -- write-1-to-clear).
653fn write_pm1a_sts(info: &FadtPmInfo, value: u16) {
654    if info.pm1a_evt_blk == 0 {
655        return;
656    }
657    // SAFETY: Writing to the PM1a status register clears the indicated
658    // status bits (write-1-to-clear semantics per ACPI spec).
659    unsafe { super::outw(info.pm1a_sts_port(), value) }
660}
661
662/// Read PM1a enable register.
663fn read_pm1a_en(info: &FadtPmInfo) -> u16 {
664    if info.pm1a_evt_blk == 0 || info.pm1_evt_len < 4 {
665        return 0;
666    }
667    // SAFETY: The enable register is at the second half of the PM1a event
668    // block. Port address is validated from FADT.
669    unsafe { super::inw(info.pm1a_en_port()) }
670}
671
672/// Write PM1a enable register.
673fn write_pm1a_en(info: &FadtPmInfo, value: u16) {
674    if info.pm1a_evt_blk == 0 || info.pm1_evt_len < 4 {
675        return;
676    }
677    // SAFETY: Writing to the PM1a enable register controls which PM events
678    // generate SCI interrupts.
679    unsafe { super::outw(info.pm1a_en_port(), value) }
680}
681
682/// Enable power button SCI event.
683fn enable_power_button_event(info: &FadtPmInfo) {
684    // Clear any pending power button status first (write-1-to-clear).
685    write_pm1a_sts(info, PWRBTN_STS);
686
687    // Enable power button event generation.
688    let en = read_pm1a_en(info);
689    write_pm1a_en(info, en | PWRBTN_EN);
690}
691
692// ---------------------------------------------------------------------------
693// CPU context save/restore
694// ---------------------------------------------------------------------------
695
696/// Save current CPU state into the suspend context.
697///
698/// Captures all general-purpose registers, control registers, GDT/IDT
699/// descriptors, and segment selectors needed to resume from S3.
700fn save_cpu_context() {
701    let mut ctx = SUSPEND_CONTEXT.lock();
702
703    // SAFETY: Reading CPU registers and descriptor tables is a privileged
704    // operation with no side effects. All registers are well-defined at
705    // this point since we are running in kernel context.
706    unsafe {
707        // Read general-purpose registers into locals first to avoid
708        // multiple mutable borrows of the MutexGuard in inline asm.
709        let (rbx, rbp, r12, r13, r14, r15): (u64, u64, u64, u64, u64, u64);
710        core::arch::asm!(
711            "mov {rbx}, rbx",
712            "mov {rbp}, rbp",
713            "mov {r12}, r12",
714            "mov {r13}, r13",
715            "mov {r14}, r14",
716            "mov {r15}, r15",
717            rbx = out(reg) rbx,
718            rbp = out(reg) rbp,
719            r12 = out(reg) r12,
720            r13 = out(reg) r13,
721            r14 = out(reg) r14,
722            r15 = out(reg) r15,
723            options(nomem, nostack, preserves_flags),
724        );
725        ctx.rbx = rbx;
726        ctx.rbp = rbp;
727        ctx.r12 = r12;
728        ctx.r13 = r13;
729        ctx.r14 = r14;
730        ctx.r15 = r15;
731
732        // Read RSP.
733        let rsp: u64;
734        core::arch::asm!(
735            "mov {}, rsp",
736            out(reg) rsp,
737            options(nomem, nostack, preserves_flags),
738        );
739        ctx.rsp = rsp;
740
741        // Read RFLAGS.
742        let rflags: u64;
743        core::arch::asm!(
744            "pushfq",
745            "pop {}",
746            out(reg) rflags,
747            options(preserves_flags),
748        );
749        ctx.rflags = rflags;
750
751        // Read control registers.
752        let (cr0, cr3, cr4): (u64, u64, u64);
753        core::arch::asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack));
754        core::arch::asm!("mov {}, cr3", out(reg) cr3, options(nomem, nostack));
755        core::arch::asm!("mov {}, cr4", out(reg) cr4, options(nomem, nostack));
756        ctx.cr0 = cr0;
757        ctx.cr3 = cr3;
758        ctx.cr4 = cr4;
759
760        // Read GDT descriptor.
761        let mut gdt_desc: [u8; 10] = [0; 10];
762        core::arch::asm!(
763            "sgdt [{}]",
764            in(reg) gdt_desc.as_mut_ptr(),
765            options(nostack),
766        );
767        ctx.gdt_limit = u16::from_le_bytes([gdt_desc[0], gdt_desc[1]]);
768        ctx.gdt_base = u64::from_le_bytes([
769            gdt_desc[2],
770            gdt_desc[3],
771            gdt_desc[4],
772            gdt_desc[5],
773            gdt_desc[6],
774            gdt_desc[7],
775            gdt_desc[8],
776            gdt_desc[9],
777        ]);
778
779        // Read IDT descriptor.
780        let mut idt_desc: [u8; 10] = [0; 10];
781        core::arch::asm!(
782            "sidt [{}]",
783            in(reg) idt_desc.as_mut_ptr(),
784            options(nostack),
785        );
786        ctx.idt_limit = u16::from_le_bytes([idt_desc[0], idt_desc[1]]);
787        ctx.idt_base = u64::from_le_bytes([
788            idt_desc[2],
789            idt_desc[3],
790            idt_desc[4],
791            idt_desc[5],
792            idt_desc[6],
793            idt_desc[7],
794            idt_desc[8],
795            idt_desc[9],
796        ]);
797
798        // Read segment selectors.
799        core::arch::asm!("mov {:x}, cs", out(reg) ctx.cs, options(nomem, nostack));
800        core::arch::asm!("mov {:x}, ds", out(reg) ctx.ds, options(nomem, nostack));
801        core::arch::asm!("mov {:x}, es", out(reg) ctx.es, options(nomem, nostack));
802        core::arch::asm!("mov {:x}, ss", out(reg) ctx.ss, options(nomem, nostack));
803    }
804}
805
806/// Restore CPU state from the suspend context after S3 resume.
807fn restore_cpu_context() {
808    let ctx = SUSPEND_CONTEXT.lock();
809
810    // SAFETY: Restoring GDT, IDT, and control registers to values that
811    // were valid before suspend. The page tables (CR3) point to the same
812    // kernel mapping that was active before sleep.
813    unsafe {
814        // Restore GDT.
815        let gdt_desc: [u8; 10] = {
816            let mut buf = [0u8; 10];
817            buf[0..2].copy_from_slice(&ctx.gdt_limit.to_le_bytes());
818            buf[2..10].copy_from_slice(&ctx.gdt_base.to_le_bytes());
819            buf
820        };
821        core::arch::asm!(
822            "lgdt [{}]",
823            in(reg) gdt_desc.as_ptr(),
824            options(nostack),
825        );
826
827        // Restore IDT.
828        let idt_desc: [u8; 10] = {
829            let mut buf = [0u8; 10];
830            buf[0..2].copy_from_slice(&ctx.idt_limit.to_le_bytes());
831            buf[2..10].copy_from_slice(&ctx.idt_base.to_le_bytes());
832            buf
833        };
834        core::arch::asm!(
835            "lidt [{}]",
836            in(reg) idt_desc.as_ptr(),
837            options(nostack),
838        );
839
840        // Restore CR3 (page tables).
841        core::arch::asm!("mov cr3, {}", in(reg) ctx.cr3, options(nomem, nostack));
842
843        // Restore callee-saved registers.
844        core::arch::asm!(
845            "mov rbx, {rbx}",
846            "mov rbp, {rbp}",
847            "mov r12, {r12}",
848            "mov r13, {r13}",
849            "mov r14, {r14}",
850            "mov r15, {r15}",
851            rbx = in(reg) ctx.rbx,
852            rbp = in(reg) ctx.rbp,
853            r12 = in(reg) ctx.r12,
854            r13 = in(reg) ctx.r13,
855            r14 = in(reg) ctx.r14,
856            r15 = in(reg) ctx.r15,
857            options(nomem, nostack),
858        );
859    }
860}
861
862// ---------------------------------------------------------------------------
863// Sleep state transitions
864// ---------------------------------------------------------------------------
865
866/// Suspend to RAM (ACPI S3).
867///
868/// Saves CPU state, flushes caches, writes SLP_TYP|SLP_EN to PM1a_CNT
869/// to enter S3 sleep. On wake, firmware jumps to the FACS waking vector
870/// which restores CPU context and returns here.
871pub fn acpi_suspend_s3() -> KernelResult<()> {
872    if !PM_INITIALIZED.load(Ordering::Acquire) {
873        return Err(KernelError::NotInitialized {
874            subsystem: "ACPI PM",
875        });
876    }
877
878    if SUPPORTED_STATES.load(Ordering::Acquire) & (1 << 3) == 0 {
879        return Err(KernelError::OperationNotSupported {
880            operation: "S3 suspend",
881        });
882    }
883
884    println!("[ACPI-PM] Preparing S3 suspend...");
885
886    // 1. Save CPU state.
887    save_cpu_context();
888
889    // 2. Disable interrupts during the transition.
890    // SAFETY: CLI prevents interrupt handlers from firing during the
891    // critical suspend sequence.
892    unsafe {
893        core::arch::asm!("cli", options(nomem, nostack));
894    }
895
896    let info = FADT_PM_INFO.lock();
897
898    // 3. Flush all CPU caches.
899    // SAFETY: WBINVD writes back all modified cache lines to memory and
900    // invalidates caches. Required before S3 to ensure RAM contents are
901    // coherent since the CPU will lose cache state.
902    unsafe {
903        core::arch::asm!("wbinvd", options(nomem, nostack));
904    }
905
906    // 4. Clear wake status.
907    write_pm1a_sts(&info, WAK_STS);
908
909    // 5. Write SLP_TYP and SLP_EN to PM1a_CNT to enter S3.
910    let slp_value = (info.slp_typ_s3 << 10) | SLP_EN;
911    write_pm1a_cnt(&info, slp_value);
912    write_pm1b_cnt(&info, slp_value);
913
914    // 6. Wait for wake -- the CPU halts here. On resume, firmware
915    // restores execution context and returns to this point.
916    // SAFETY: HLT stops the CPU until the next interrupt. After S3
917    // wake, execution resumes here.
918    unsafe {
919        core::arch::asm!("hlt", options(nomem, nostack));
920    }
921
922    drop(info);
923
924    // 7. Restore CPU state on resume.
925    restore_cpu_context();
926    CURRENT_STATE.store(0, Ordering::Release);
927
928    // 8. Re-enable interrupts.
929    // SAFETY: STI re-enables hardware interrupts after resume.
930    unsafe {
931        core::arch::asm!("sti", options(nomem, nostack));
932    }
933
934    WAKE_EVENT_COUNT.fetch_add(1, Ordering::Relaxed);
935    println!("[ACPI-PM] Resumed from S3 suspend");
936
937    Ok(())
938}
939
940/// Hibernate (ACPI S4).
941///
942/// Creates a memory image bitmap of all active pages, writes the image
943/// to the swap area, then enters S4 via ACPI. On resume from S4, the
944/// bootloader or firmware restores the memory image and jumps to the
945/// wakeup vector.
946pub fn acpi_hibernate_s4() -> KernelResult<()> {
947    if !PM_INITIALIZED.load(Ordering::Acquire) {
948        return Err(KernelError::NotInitialized {
949            subsystem: "ACPI PM",
950        });
951    }
952
953    if SUPPORTED_STATES.load(Ordering::Acquire) & (1 << 4) == 0 {
954        return Err(KernelError::OperationNotSupported {
955            operation: "S4 hibernate",
956        });
957    }
958
959    println!("[ACPI-PM] Preparing S4 hibernate...");
960
961    // 1. Save CPU state for potential resume.
962    save_cpu_context();
963
964    // 2. Create memory image.
965    // In a full implementation, this would:
966    //   a. Walk page tables to find all active physical frames.
967    //   b. Allocate a contiguous bitmap tracking which pages to save.
968    //   c. Write pages sequentially to the swap partition/file.
969    //   d. Write a hibernate header with the wakeup vector and page map.
970    println!("[ACPI-PM] Memory image creation (page snapshot phase)...");
971    let active_pages = snapshot_active_pages();
972    println!("[ACPI-PM] Snapshot: {} active pages recorded", active_pages);
973
974    // 3. Write image to swap (stub -- requires block device write path).
975    println!("[ACPI-PM] Writing hibernate image to swap...");
976    // write_hibernate_image() would go here.
977
978    // 4. Disable interrupts for transition.
979    // SAFETY: CLI during critical S4 transition.
980    unsafe {
981        core::arch::asm!("cli", options(nomem, nostack));
982    }
983
984    let info = FADT_PM_INFO.lock();
985
986    // 5. Flush caches.
987    // SAFETY: WBINVD ensures all cache lines are written to RAM before
988    // powering down.
989    unsafe {
990        core::arch::asm!("wbinvd", options(nomem, nostack));
991    }
992
993    // 6. Clear wake status and enter S4.
994    write_pm1a_sts(&info, WAK_STS);
995    let slp_value = (info.slp_typ_s4 << 10) | SLP_EN;
996    write_pm1a_cnt(&info, slp_value);
997    write_pm1b_cnt(&info, slp_value);
998
999    // 7. Wait for power off / wake.
1000    // SAFETY: HLT after S4 entry. System powers off; on resume firmware
1001    // restores memory and jumps to wakeup vector.
1002    unsafe {
1003        core::arch::asm!("hlt", options(nomem, nostack));
1004    }
1005
1006    drop(info);
1007
1008    // 8. Resume path: restore context.
1009    restore_cpu_context();
1010    CURRENT_STATE.store(0, Ordering::Release);
1011
1012    // SAFETY: Re-enable interrupts after resume.
1013    unsafe {
1014        core::arch::asm!("sti", options(nomem, nostack));
1015    }
1016
1017    WAKE_EVENT_COUNT.fetch_add(1, Ordering::Relaxed);
1018    println!("[ACPI-PM] Resumed from S4 hibernate");
1019
1020    Ok(())
1021}
1022
1023/// Soft power off via ACPI S5.
1024///
1025/// Writes the S5 SLP_TYP value with SLP_EN to PM1a_CNT. The system
1026/// should power off; this function does not return on success.
1027pub fn acpi_shutdown_s5() -> KernelResult<()> {
1028    if !PM_INITIALIZED.load(Ordering::Acquire) {
1029        return Err(KernelError::NotInitialized {
1030            subsystem: "ACPI PM",
1031        });
1032    }
1033
1034    println!("[ACPI-PM] Initiating ACPI S5 soft power off...");
1035
1036    // Disable interrupts.
1037    // SAFETY: CLI before shutdown to prevent any interrupt handlers from
1038    // interfering with the power-off sequence.
1039    unsafe {
1040        core::arch::asm!("cli", options(nomem, nostack));
1041    }
1042
1043    let info = FADT_PM_INFO.lock();
1044
1045    // Write SLP_TYP for S5 with SLP_EN.
1046    let slp_value = (info.slp_typ_s5 << 10) | SLP_EN;
1047    write_pm1a_cnt(&info, slp_value);
1048    write_pm1b_cnt(&info, slp_value);
1049
1050    drop(info);
1051
1052    // If we're still here, the power off did not succeed. Halt.
1053    println!("[ACPI-PM] WARNING: S5 power off did not take effect, halting");
1054
1055    // SAFETY: HLT in an infinite loop as a last resort.
1056    loop {
1057        unsafe {
1058            core::arch::asm!("hlt", options(nomem, nostack));
1059        }
1060    }
1061}
1062
1063// ---------------------------------------------------------------------------
1064// SCI interrupt handler
1065// ---------------------------------------------------------------------------
1066
1067/// Handle an ACPI SCI (System Control Interrupt).
1068///
1069/// Called from the interrupt handler when the SCI vector fires.
1070/// Reads PM1a status to determine the wake/event source and dispatches
1071/// the appropriate handler.
1072pub fn acpi_handle_sci() {
1073    if !PM_INITIALIZED.load(Ordering::Acquire) {
1074        return;
1075    }
1076
1077    let info = FADT_PM_INFO.lock();
1078    let status = read_pm1a_sts(&info);
1079
1080    // Power button press.
1081    if status & PWRBTN_STS != 0 {
1082        // Clear the status bit (write-1-to-clear).
1083        write_pm1a_sts(&info, PWRBTN_STS);
1084        LAST_WAKE_EVENT.store(AcpiWakeEvent::PowerButton as u8, Ordering::Release);
1085        println!("[ACPI-PM] Power button press detected");
1086    }
1087
1088    // PM timer overflow.
1089    if status & TMR_STS != 0 {
1090        write_pm1a_sts(&info, TMR_STS);
1091    }
1092
1093    // BIOS wants attention.
1094    if status & GBL_STS != 0 {
1095        write_pm1a_sts(&info, GBL_STS);
1096    }
1097
1098    // Wake status (resume from sleep).
1099    if status & WAK_STS != 0 {
1100        write_pm1a_sts(&info, WAK_STS);
1101        WAKE_EVENT_COUNT.fetch_add(1, Ordering::Relaxed);
1102        println!("[ACPI-PM] Wake event detected");
1103    }
1104
1105    // Check GPE (General Purpose Event) registers for lid events.
1106    check_gpe_events(&info);
1107}
1108
1109/// Check GPE registers for lid and other general-purpose events.
1110fn check_gpe_events(info: &FadtPmInfo) {
1111    if info.gpe0_blk == 0 || info.gpe0_blk_len == 0 {
1112        return;
1113    }
1114
1115    let half_len = info.gpe0_blk_len / 2;
1116    if half_len == 0 {
1117        return;
1118    }
1119
1120    // Read GPE0 status registers (first half of GPE0 block).
1121    for i in 0..half_len {
1122        let port = info.gpe0_blk + i as u16;
1123        // SAFETY: port is within the GPE0 block range from the FADT.
1124        // Reading GPE status registers returns pending event bits.
1125        let status = unsafe { super::inb(port) };
1126
1127        if status != 0 {
1128            // Clear status bits by writing them back (write-1-to-clear).
1129            // SAFETY: Writing GPE0 status register to clear event bits.
1130            unsafe { super::outb(port, status) };
1131
1132            // GPE bit 0x02 is commonly used for lid events on many platforms.
1133            if status & 0x02 != 0 {
1134                handle_lid_event();
1135            }
1136        }
1137    }
1138}
1139
1140/// Handle a lid open/close event from ACPI GPE.
1141fn handle_lid_event() {
1142    // Toggle lid state. In a full implementation, this would read
1143    // the actual lid state from the ACPI _LID method.
1144    let current = LID_STATE.load(Ordering::Acquire);
1145    let new_state = if current == LidState::Open as u8 {
1146        LidState::Closed as u8
1147    } else {
1148        LidState::Open as u8
1149    };
1150    LID_STATE.store(new_state, Ordering::Release);
1151
1152    if new_state == LidState::Closed as u8 {
1153        LAST_WAKE_EVENT.store(AcpiWakeEvent::LidOpen as u8, Ordering::Release);
1154        println!("[ACPI-PM] Lid closed");
1155    } else {
1156        println!("[ACPI-PM] Lid opened");
1157    }
1158}
1159
1160// ---------------------------------------------------------------------------
1161// Memory snapshot for S4
1162// ---------------------------------------------------------------------------
1163
1164/// Snapshot active pages for hibernate image.
1165///
1166/// Returns the count of active pages that would be saved. In a full
1167/// implementation, this would walk the page table tree and record each
1168/// present physical frame into a bitmap.
1169fn snapshot_active_pages() -> usize {
1170    // Stub: report a reasonable count based on kernel memory layout.
1171    // A real implementation would iterate PML4 -> PDPT -> PD -> PT entries.
1172    256 * 1024 // ~1GB kernel heap / 4K pages
1173}
1174
1175// ---------------------------------------------------------------------------
1176// Query API
1177// ---------------------------------------------------------------------------
1178
1179/// Check if ACPI PM is initialized.
1180pub fn is_initialized() -> bool {
1181    PM_INITIALIZED.load(Ordering::Acquire)
1182}
1183
1184/// Get the current ACPI sleep state.
1185pub fn current_state() -> AcpiSleepState {
1186    match CURRENT_STATE.load(Ordering::Acquire) {
1187        0 => AcpiSleepState::S0Working,
1188        1 => AcpiSleepState::S1Standby,
1189        3 => AcpiSleepState::S3Suspend,
1190        4 => AcpiSleepState::S4Hibernate,
1191        5 => AcpiSleepState::S5SoftOff,
1192        _ => AcpiSleepState::S0Working,
1193    }
1194}
1195
1196/// Get the current lid state.
1197pub fn lid_state() -> LidState {
1198    match LID_STATE.load(Ordering::Acquire) {
1199        0 => LidState::Open,
1200        1 => LidState::Closed,
1201        _ => LidState::Unknown,
1202    }
1203}
1204
1205/// Check if a given sleep state is supported.
1206pub fn is_state_supported(state: AcpiSleepState) -> bool {
1207    let bit = match state {
1208        AcpiSleepState::S0Working => 0,
1209        AcpiSleepState::S1Standby => 1,
1210        AcpiSleepState::S3Suspend => 3,
1211        AcpiSleepState::S4Hibernate => 4,
1212        AcpiSleepState::S5SoftOff => 5,
1213    };
1214    SUPPORTED_STATES.load(Ordering::Acquire) & (1u16 << bit) != 0
1215}
1216
1217/// Get supported sleep states as a formatted string.
1218///
1219/// Returns a string like "mem disk standby" suitable for /sys/power/state.
1220pub fn supported_states_string() -> &'static str {
1221    let states = SUPPORTED_STATES.load(Ordering::Acquire);
1222    if states & (1 << 3) != 0 && states & (1 << 4) != 0 && states & (1 << 1) != 0 {
1223        "standby mem disk"
1224    } else if states & (1 << 3) != 0 && states & (1 << 4) != 0 {
1225        "mem disk"
1226    } else if states & (1 << 3) != 0 {
1227        "mem"
1228    } else {
1229        "standby"
1230    }
1231}
1232
1233/// Get the last wake event type.
1234pub fn last_wake_event() -> AcpiWakeEvent {
1235    match LAST_WAKE_EVENT.load(Ordering::Acquire) {
1236        0 => AcpiWakeEvent::PowerButton,
1237        1 => AcpiWakeEvent::LidOpen,
1238        2 => AcpiWakeEvent::RtcAlarm,
1239        3 => AcpiWakeEvent::UsbWake,
1240        4 => AcpiWakeEvent::NetworkWake,
1241        _ => AcpiWakeEvent::Unknown,
1242    }
1243}
1244
1245/// Get the total number of wake events since boot.
1246pub fn wake_event_count() -> u32 {
1247    WAKE_EVENT_COUNT.load(Ordering::Acquire)
1248}
1249
1250// ---------------------------------------------------------------------------
1251// Tests
1252// ---------------------------------------------------------------------------
1253
1254#[cfg(test)]
1255mod tests {
1256    use super::*;
1257
1258    #[test]
1259    fn test_sleep_state_display() {
1260        assert_eq!(
1261            alloc::format!("{}", AcpiSleepState::S0Working),
1262            "S0 (Working)"
1263        );
1264        assert_eq!(
1265            alloc::format!("{}", AcpiSleepState::S3Suspend),
1266            "S3 (Suspend to RAM)"
1267        );
1268        assert_eq!(
1269            alloc::format!("{}", AcpiSleepState::S5SoftOff),
1270            "S5 (Soft Off)"
1271        );
1272    }
1273
1274    #[test]
1275    fn test_fadt_pm_info_defaults() {
1276        let info = FadtPmInfo::new();
1277        assert_eq!(info.pm1a_cnt_blk, 0);
1278        assert_eq!(info.slp_typ_s3, 5);
1279        assert_eq!(info.slp_typ_s5, 7);
1280        assert_eq!(info.sci_int, 9);
1281    }
1282
1283    #[test]
1284    fn test_pm1a_sts_port() {
1285        let mut info = FadtPmInfo::new();
1286        info.pm1a_evt_blk = 0x0600;
1287        info.pm1_evt_len = 4;
1288        assert_eq!(info.pm1a_sts_port(), 0x0600);
1289        assert_eq!(info.pm1a_en_port(), 0x0602);
1290    }
1291
1292    #[test]
1293    fn test_cpu_suspend_context_default() {
1294        let ctx = CpuSuspendContext::new();
1295        assert_eq!(ctx.rax, 0);
1296        assert_eq!(ctx.cr3, 0);
1297        assert_eq!(ctx.gdt_limit, 0);
1298    }
1299
1300    #[test]
1301    fn test_slp_value_encoding() {
1302        let info = FadtPmInfo::new();
1303        // S3: SLP_TYP=5, shifted left 10 bits, OR with SLP_EN (bit 13).
1304        let slp_value = (info.slp_typ_s3 << 10) | SLP_EN;
1305        assert_eq!(slp_value & SLP_EN, SLP_EN);
1306        assert_eq!((slp_value >> 10) & 0x07, 5);
1307    }
1308
1309    #[test]
1310    fn test_wake_event_variants() {
1311        assert_eq!(AcpiWakeEvent::PowerButton as u8, 0);
1312        assert_eq!(AcpiWakeEvent::LidOpen as u8, 1);
1313        assert_eq!(AcpiWakeEvent::Unknown as u8, 5);
1314    }
1315
1316    #[test]
1317    fn test_lid_state_variants() {
1318        assert_eq!(LidState::Open as u8, 0);
1319        assert_eq!(LidState::Closed as u8, 1);
1320        assert_eq!(LidState::Unknown as u8, 2);
1321    }
1322
1323    #[test]
1324    fn test_supported_states_bitmask() {
1325        // S0 + S1 + S3 + S4 + S5
1326        let supported: u16 = 1 | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5);
1327        assert!(supported & 1 != 0); // S0
1328        assert!(supported & (1 << 1) != 0); // S1
1329        assert!(supported & (1 << 2) == 0); // S2 not supported
1330        assert!(supported & (1 << 3) != 0); // S3
1331        assert!(supported & (1 << 4) != 0); // S4
1332        assert!(supported & (1 << 5) != 0); // S5
1333    }
1334
1335    #[test]
1336    fn test_snapshot_active_pages() {
1337        let pages = snapshot_active_pages();
1338        assert!(pages > 0);
1339    }
1340}