From ba667148af04963f5d70c647511ea1fad1add21d Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 31 Jul 2026 16:01:46 +0200 Subject: power: proper shutdown via UEFI ResetSystem (ACPI S5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - uefi.rs: full EFI_RUNTIME_SERVICES layout with reset_system at index 10 (spec order); EfiResetSystem type + RESET_* constants - power.rs: stash the runtime-services table pre-EBS; poweroff() calls ResetSystem(EfiResetShutdown) — still callable after ExitBootServices because we never call SetVirtualAddressMap, so the pointers keep working with physical addresses; falls back to the hlt loop - shell: 'poweroff' and 'exit' shut the machine down (QEMU exits), 'halt' keeps the plain hlt loop --- rsnix/src/power.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 rsnix/src/power.rs (limited to 'rsnix/src/power.rs') diff --git a/rsnix/src/power.rs b/rsnix/src/power.rs new file mode 100644 index 0000000..cb5d296 --- /dev/null +++ b/rsnix/src/power.rs @@ -0,0 +1,48 @@ +// --------------------------------------------------------------------------- +// Power — halt loop and ACPI shutdown via the UEFI ResetSystem runtime +// service. +// +// The firmware's runtime-services table is stashed before ExitBootServices +// (`init`). Because we never call SetVirtualAddressMap, the service +// pointers keep working with physical (identity) addresses after EBS, so +// poweroff() can ask the firmware to shut the machine down (ACPI S5) — on +// QEMU this makes the VM exit, on real hardware the power drops. +// --------------------------------------------------------------------------- + +use crate::uefi::{RuntimeServices, EFI_SUCCESS, RESET_SHUTDOWN}; + +static mut RUNTIME_SERVICES: *mut RuntimeServices = core::ptr::null_mut(); + +/// Stash the runtime-services table pointer (call in Phase 1, pre-EBS). +pub fn init(rt: *mut RuntimeServices) { + unsafe { + RUNTIME_SERVICES = rt; + } +} + +/// Halt the CPU forever (interrupts off, hlt loop). +pub fn halt() -> ! { + loop { + unsafe { + core::arch::asm!("cli; hlt", options(nomem, nostack)); + } + } +} + +/// Shut the machine down through `ResetSystem(EfiResetShutdown)`. Falls +/// back to `halt()` if no table was saved or the call failed. +pub fn poweroff() -> ! { + unsafe { + let rt = RUNTIME_SERVICES; + if rt.is_null() { + halt(); + } + let reset = (*rt).reset_system; + if reset as usize == 0 { + halt(); + } + (reset)(RESET_SHUTDOWN, EFI_SUCCESS, 0, core::ptr::null()); + // ResetSystem only returns on failure. + halt() + } +} -- cgit v1.2.3