//! Machine power control: enter ACPI mode, reboot, and power off (ACPI S5). //! //! Built entirely on the register map `acpi` extracted from the FADT plus the //! sleep-state (`_Sx`) types the AML submodule pulled from the DSDT, driven through the //! injected `Hal` (port I/O and MMIO). Nothing here is x86-specific beyond the //! well-known legacy reset fallbacks, which are guarded behind the ACPI methods. //! //! S3 (suspend-to-RAM) is stubbed: it needs a wake trampoline and device //! re-initialisation, a milestone of its own. const acpi = @import("acpi.zig"); const device = @import("device.zig"); const Hal = device.Hal; const slp_en: u32 = 1 << 13; // SLP_EN: writing 1 triggers the sleep transition const sci_en: u32 = 1 << 0; // SCI_EN in PM1 control: set once ACPI mode is active /// Switch the platform into ACPI mode if it isn't already, so the PM1 control /// register is live. A no-op when the firmware exposes no SMI command port (ACPI /// already enabled, as under QEMU/OVMF) — we still verify SCI_EN first. pub fn enable(hal: Hal) void { const pi = acpi.power_info; if (!pi.pm1a_cnt.present()) return; if (readReg(hal, pi.pm1a_cnt) & sci_en != 0) return; // already in ACPI mode if (pi.smi_cmd == 0 or pi.acpi_enable == 0) return; // no way to switch; assume fine hal.pioWrite(1, pi.smi_cmd, pi.acpi_enable); var spins: usize = 0; while (readReg(hal, pi.pm1a_cnt) & sci_en == 0 and spins < 1_000_000) : (spins += 1) {} } /// Restart the machine. Tries the ACPI reset register first, then the two legacy /// fallbacks. Returns only if every method failed (very unlikely). pub fn reboot(hal: Hal) void { const pi = acpi.power_info; // 1. The FADT reset register, when the firmware advertises support. if (pi.reset_supported and pi.reset.present()) { writeReg(hal, pi.reset, pi.reset_value); delay(); } // 2. The PCI reset-control register at port 0xCF9 (RST_CPU | SYS_RST). hal.pioWrite(1, 0xCF9, 0x0E); hal.pioWrite(1, 0xCF9, 0x06); delay(); // 3. Pulse the 8042 keyboard controller's reset line. hal.pioWrite(1, 0x64, 0xFE); delay(); } /// Power the machine off via ACPI S5. Requires the soft-off (`_S5`) sleep type; if /// it wasn't found in the AML, there is nothing safe to do and this returns. pub fn shutdown(hal: Hal) void { enable(hal); const pi = acpi.power_info; const s5 = pi.s5 orelse return; if (pi.pm1a_cnt.present()) { writeReg(hal, pi.pm1a_cnt, sleepValue(s5.slp_typ_a)); } if (pi.pm1b_cnt.present()) { writeReg(hal, pi.pm1b_cnt, sleepValue(s5.slp_typ_b)); } delay(); } /// S3 suspend-to-RAM — not implemented (needs a wake path + device re-init). pub fn sleepS3(hal: Hal) error{Unsupported}!void { _ = hal; return error.Unsupported; } /// The PM1 control write that requests sleep type `slp_typ`: SLP_TYP in bits /// [12:10], SLP_EN in bit 13. fn sleepValue(slp_typ: u8) u32 { return (@as(u32, slp_typ & 0x7) << 10) | slp_en; } fn readReg(hal: Hal, reg: acpi.RegAccess) u32 { if (reg.mmio) { hal.mapMmio(reg.address, reg.address, true); const p: *align(1) volatile u32 = @ptrFromInt(reg.address); return p.*; } return hal.pioRead(reg.width, @intCast(reg.address)); } fn writeReg(hal: Hal, reg: acpi.RegAccess, value: u32) void { if (reg.mmio) { hal.mapMmio(reg.address, reg.address, true); const p: *align(1) volatile u32 = @ptrFromInt(reg.address); p.* = value; } else { hal.pioWrite(reg.width, @intCast(reg.address), value); } } /// A short busy-wait so a reset/power-off takes effect before we fall through to /// the next method. The empty asm is an arch-neutral barrier that keeps the loop /// from being optimised away. fn delay() void { var i: usize = 0; while (i < 50_000_000) : (i += 1) { asm volatile ("" ::: .{ .memory = true }); } }