1#![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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[repr(u8)]
26pub enum AcpiSleepState {
27 S0Working = 0,
29 S1Standby = 1,
31 S3Suspend = 3,
33 S4Hibernate = 4,
35 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
51const SLP_EN: u16 = 1 << 13;
57
58const SCI_EN: u16 = 1;
60
61const WAK_STS: u16 = 1 << 15;
63
64const PWRBTN_STS: u16 = 1 << 8;
66
67const PWRBTN_EN: u16 = 1 << 8;
69
70const GBL_STS: u16 = 1 << 5;
72
73const TMR_STS: u16 = 1;
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum AcpiWakeEvent {
83 PowerButton,
85 LidOpen,
87 RtcAlarm,
89 UsbWake,
91 NetworkWake,
93 Unknown,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum LidState {
104 Open,
105 Closed,
106 Unknown,
107}
108
109#[derive(Debug, Clone, Copy)]
115struct FadtPmInfo {
116 pm1a_cnt_blk: u16,
118 pm1b_cnt_blk: u16,
120 pm1a_evt_blk: u16,
122 pm1b_evt_blk: u16,
124 pm1_evt_len: u8,
126 pm1_cnt_len: u8,
128 slp_typ_s1: u16,
130 slp_typ_s3: u16,
132 slp_typ_s4: u16,
134 slp_typ_s5: u16,
136 sci_int: u16,
138 smi_cmd: u16,
140 acpi_enable: u8,
142 acpi_disable: u8,
144 gpe0_blk: u16,
146 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, slp_typ_s4: 6, slp_typ_s5: 7, 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 fn pm1a_sts_port(&self) -> u16 {
174 self.pm1a_evt_blk
175 }
176
177 fn pm1a_en_port(&self) -> u16 {
179 self.pm1a_evt_blk + (self.pm1_evt_len as u16 / 2)
180 }
181}
182
183#[repr(C)]
193#[derive(Debug, Clone, Copy)]
194struct CpuSuspendContext {
195 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 rip: u64,
214 rflags: u64,
216 cr3: u64,
218 cr0: u64,
220 cr4: u64,
222 gdt_limit: u16,
224 gdt_base: u64,
225 idt_limit: u16,
227 idt_base: u64,
228 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
275static 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); static LID_STATE: AtomicU8 = AtomicU8::new(2); static WAKE_EVENT_COUNT: AtomicU32 = AtomicU32::new(0);
285static LAST_WAKE_EVENT: AtomicU8 = AtomicU8::new(5); static SUPPORTED_STATES: AtomicU16 = AtomicU16::new(0);
289
290const FADT_SIGNATURE: &[u8; 4] = b"FACP";
295
296#[repr(C, packed)]
299struct FadtHeader {
300 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 firmware_ctrl: u32,
312 dsdt: u32,
314 _reserved1: u8,
316 preferred_pm_profile: u8,
318 sci_int: u16,
320 smi_cmd: u32,
322 acpi_enable: u8,
324 acpi_disable: u8,
326 _s4bios_req: u8,
328 _pstate_cnt: u8,
329 pm1a_evt_blk: u32,
331 pm1b_evt_blk: u32,
333 pm1a_cnt_blk: u32,
335 pm1b_cnt_blk: u32,
337 _pm2_cnt_blk: u32,
339 _pm_tmr_blk: u32,
341 gpe0_blk: u32,
343 _gpe1_blk: u32,
345 pm1_evt_len: u8,
347 pm1_cnt_len: u8,
349 _pm2_cnt_len: u8,
351 _pm_tmr_len: u8,
353 gpe0_blk_len: u8,
355}
356
357pub 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 let mut info = FadtPmInfo::new();
380
381 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 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 let mut supported: u16 = 1; supported |= 1 << 1; if info.pm1a_cnt_blk != 0 {
407 supported |= 1 << 3; supported |= 1 << 4; supported |= 1 << 5; }
411
412 SUPPORTED_STATES.store(supported, Ordering::Release);
413
414 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 unsafe {
423 super::outb(info.smi_cmd, info.acpi_enable);
424 }
425
426 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 unsafe {
442 super::inb(0x80);
443 }
444 }
445 }
446 }
447
448 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
467fn parse_fadt_from_tables() -> Option<FadtPmInfo> {
469 #[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 let rsdp = unsafe { &*(rsdp_vaddr as *const FadtRsdp) };
481 if &rsdp.signature != b"RSD PTR " {
482 return None;
483 }
484
485 if rsdp.revision >= 2 {
487 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#[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 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 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 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 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 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 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
606fn read_pm1a_cnt(info: &FadtPmInfo) -> u16 {
612 if info.pm1a_cnt_blk == 0 {
613 return 0;
614 }
615 unsafe { super::inw(info.pm1a_cnt_blk) }
619}
620
621fn write_pm1a_cnt(info: &FadtPmInfo, value: u16) {
623 if info.pm1a_cnt_blk == 0 {
624 return;
625 }
626 unsafe { super::outw(info.pm1a_cnt_blk, value) }
630}
631
632fn write_pm1b_cnt(info: &FadtPmInfo, value: u16) {
634 if info.pm1b_cnt_blk == 0 {
635 return;
636 }
637 unsafe { super::outw(info.pm1b_cnt_blk, value) }
640}
641
642fn read_pm1a_sts(info: &FadtPmInfo) -> u16 {
644 if info.pm1a_evt_blk == 0 {
645 return 0;
646 }
647 unsafe { super::inw(info.pm1a_sts_port()) }
650}
651
652fn write_pm1a_sts(info: &FadtPmInfo, value: u16) {
654 if info.pm1a_evt_blk == 0 {
655 return;
656 }
657 unsafe { super::outw(info.pm1a_sts_port(), value) }
660}
661
662fn read_pm1a_en(info: &FadtPmInfo) -> u16 {
664 if info.pm1a_evt_blk == 0 || info.pm1_evt_len < 4 {
665 return 0;
666 }
667 unsafe { super::inw(info.pm1a_en_port()) }
670}
671
672fn write_pm1a_en(info: &FadtPmInfo, value: u16) {
674 if info.pm1a_evt_blk == 0 || info.pm1_evt_len < 4 {
675 return;
676 }
677 unsafe { super::outw(info.pm1a_en_port(), value) }
680}
681
682fn enable_power_button_event(info: &FadtPmInfo) {
684 write_pm1a_sts(info, PWRBTN_STS);
686
687 let en = read_pm1a_en(info);
689 write_pm1a_en(info, en | PWRBTN_EN);
690}
691
692fn save_cpu_context() {
701 let mut ctx = SUSPEND_CONTEXT.lock();
702
703 unsafe {
707 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 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 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 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 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 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 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
806fn restore_cpu_context() {
808 let ctx = SUSPEND_CONTEXT.lock();
809
810 unsafe {
814 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 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 core::arch::asm!("mov cr3, {}", in(reg) ctx.cr3, options(nomem, nostack));
842
843 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
862pub 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 save_cpu_context();
888
889 unsafe {
893 core::arch::asm!("cli", options(nomem, nostack));
894 }
895
896 let info = FADT_PM_INFO.lock();
897
898 unsafe {
903 core::arch::asm!("wbinvd", options(nomem, nostack));
904 }
905
906 write_pm1a_sts(&info, WAK_STS);
908
909 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 unsafe {
919 core::arch::asm!("hlt", options(nomem, nostack));
920 }
921
922 drop(info);
923
924 restore_cpu_context();
926 CURRENT_STATE.store(0, Ordering::Release);
927
928 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
940pub 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 save_cpu_context();
963
964 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 println!("[ACPI-PM] Writing hibernate image to swap...");
976 unsafe {
981 core::arch::asm!("cli", options(nomem, nostack));
982 }
983
984 let info = FADT_PM_INFO.lock();
985
986 unsafe {
990 core::arch::asm!("wbinvd", options(nomem, nostack));
991 }
992
993 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 unsafe {
1003 core::arch::asm!("hlt", options(nomem, nostack));
1004 }
1005
1006 drop(info);
1007
1008 restore_cpu_context();
1010 CURRENT_STATE.store(0, Ordering::Release);
1011
1012 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
1023pub 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 unsafe {
1040 core::arch::asm!("cli", options(nomem, nostack));
1041 }
1042
1043 let info = FADT_PM_INFO.lock();
1044
1045 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 println!("[ACPI-PM] WARNING: S5 power off did not take effect, halting");
1054
1055 loop {
1057 unsafe {
1058 core::arch::asm!("hlt", options(nomem, nostack));
1059 }
1060 }
1061}
1062
1063pub 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 if status & PWRBTN_STS != 0 {
1082 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 if status & TMR_STS != 0 {
1090 write_pm1a_sts(&info, TMR_STS);
1091 }
1092
1093 if status & GBL_STS != 0 {
1095 write_pm1a_sts(&info, GBL_STS);
1096 }
1097
1098 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_events(&info);
1107}
1108
1109fn 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 for i in 0..half_len {
1122 let port = info.gpe0_blk + i as u16;
1123 let status = unsafe { super::inb(port) };
1126
1127 if status != 0 {
1128 unsafe { super::outb(port, status) };
1131
1132 if status & 0x02 != 0 {
1134 handle_lid_event();
1135 }
1136 }
1137 }
1138}
1139
1140fn handle_lid_event() {
1142 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
1160fn snapshot_active_pages() -> usize {
1170 256 * 1024 }
1174
1175pub fn is_initialized() -> bool {
1181 PM_INITIALIZED.load(Ordering::Acquire)
1182}
1183
1184pub 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
1196pub 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
1205pub 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
1217pub 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
1233pub 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
1245pub fn wake_event_count() -> u32 {
1247 WAKE_EVENT_COUNT.load(Ordering::Acquire)
1248}
1249
1250#[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 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 let supported: u16 = 1 | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5);
1327 assert!(supported & 1 != 0); assert!(supported & (1 << 1) != 0); assert!(supported & (1 << 2) == 0); assert!(supported & (1 << 3) != 0); assert!(supported & (1 << 4) != 0); assert!(supported & (1 << 5) != 0); }
1334
1335 #[test]
1336 fn test_snapshot_active_pages() {
1337 let pages = snapshot_active_pages();
1338 assert!(pages > 0);
1339 }
1340}