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/lib.rs | 1 + rsnix/src/power.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ rsnix/src/uefi.rs | 34 ++++++++++++++++++++++++++++++++-- 3 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 rsnix/src/power.rs (limited to 'rsnix/src') diff --git a/rsnix/src/lib.rs b/rsnix/src/lib.rs index d2dacf0..8c69b35 100644 --- a/rsnix/src/lib.rs +++ b/rsnix/src/lib.rs @@ -7,6 +7,7 @@ pub mod gop; pub mod interrupts; pub mod memory; pub mod paging; +pub mod power; pub mod ps2; pub mod serial; pub mod uefi; 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() + } +} diff --git a/rsnix/src/uefi.rs b/rsnix/src/uefi.rs index 619739e..b1b5e48 100644 --- a/rsnix/src/uefi.rs +++ b/rsnix/src/uefi.rs @@ -86,12 +86,42 @@ pub struct SimpleTextInput { } // --------------------------------------------------------------------------- -// Runtime services (stub — we don't use them) +// Runtime services // --------------------------------------------------------------------------- + +/// `EFI_RESET_SYSTEM` — ResetType, ResetStatus, DataSize, ResetData. +/// ResetType: 0 cold, 1 warm, 2 shutdown, 3 platform-specific. +pub type EfiResetSystem = + unsafe extern "efiapi" fn(u32, u64, usize, *const u16) -> Status; + +pub const RESET_COLD: u32 = 0; +#[allow(dead_code)] +pub const RESET_WARM: u32 = 1; +pub const RESET_SHUTDOWN: u32 = 2; +#[allow(dead_code)] +pub const RESET_PLATFORM_SPECIFIC: u32 = 3; + +/// EFI_RUNTIME_SERVICES — function pointers in spec order after the header. +/// We only use ResetSystem; the rest stay opaque. The table remains +/// callable after ExitBootServices because we never call SetVirtualAddressMap, +/// so the pointers keep working with physical (identity) addresses. #[repr(C)] pub struct RuntimeServices { _hdr: TableHeader, - _reserved: [*mut c_void; 30], + _get_time: *mut c_void, // 0 + _set_time: *mut c_void, // 1 + _get_wakeup_time: *mut c_void, // 2 + _set_wakeup_time: *mut c_void, // 3 + _set_virtual_address_map: *mut c_void, // 4 + _convert_pointer: *mut c_void, // 5 + _get_variable: *mut c_void, // 6 + _get_next_variable_name: *mut c_void, // 7 + _set_variable: *mut c_void, // 8 + _get_next_high_monotonic_count: *mut c_void, // 9 + pub reset_system: EfiResetSystem, // 10 + _update_capsule: *mut c_void, // 11 + _query_capsule_capabilities: *mut c_void, // 12 + _query_variable_info: *mut c_void, // 13 } // --------------------------------------------------------------------------- -- cgit v1.2.3