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 --- rsdos/src/main.rs | 3 +++ rsnix/src/lib.rs | 1 + rsnix/src/power.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ rsnix/src/uefi.rs | 34 ++++++++++++++++++++++++++++++++-- shell/src/lib.rs | 22 +++++++++++++--------- 5 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 rsnix/src/power.rs diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs index 1169785..d9578e2 100644 --- a/rsdos/src/main.rs +++ b/rsdos/src/main.rs @@ -37,6 +37,9 @@ pub extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut SystemT let bs = unsafe { &mut *st.boot_services }; let con_out = unsafe { &mut *st.con_out }; + // Keep the runtime-services table for later (ResetSystem → poweroff). + rsnix::power::init(st.runtime_services); + // ---- Phase 1: UEFI boot services alive ---- // Print banner via UEFI console 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 } // --------------------------------------------------------------------------- diff --git a/shell/src/lib.rs b/shell/src/lib.rs index 4d5ba9c..5cab122 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs @@ -40,7 +40,8 @@ pub fn run() -> ! { "help" => cmd_help(), "echo" => cmd_echo(args), "clear" => cmd_clear(), - "halt" | "poweroff" | "exit" => cmd_halt(), + "halt" => cmd_halt(), + "poweroff" | "exit" => cmd_poweroff(), "hello" => { gop_println!("World!"); serial_println!("World!"); @@ -123,10 +124,11 @@ fn cmd_help() { gop_println!(" mem — show memory info"); gop_println!(" mmap — dump the UEFI memory map"); gop_println!(" stress — stress-test heap/page allocators"); - gop_println!(" halt — halt the system"); - gop_println!(" exit — alias for halt"); + gop_println!(" halt — halt the system (hlt loop)"); + gop_println!(" poweroff — shut down via ACPI (ResetSystem)"); + gop_println!(" exit — alias for poweroff"); serial_println!("available commands:"); - serial_println!(" help, echo, clear, hello, info, mem, mmap, stress, halt, exit"); + serial_println!(" help, echo, clear, hello, info, mem, mmap, stress, halt, poweroff, exit"); } fn cmd_echo(args: &str) { @@ -359,11 +361,13 @@ fn cmd_stress(args: &str) { fn cmd_halt() -> ! { gop_println!("halting..."); serial_println!("halting..."); - loop { - unsafe { - core::arch::asm!("cli; hlt"); - } - } + rsnix::power::halt() +} + +fn cmd_poweroff() -> ! { + gop_println!("shutting down..."); + serial_println!("shutting down..."); + rsnix::power::poweroff() } // --------------------------------------------------------------------------- -- cgit v1.2.3