From ecd5f0bec4a15cc29867179eaa852a9c59d0f9c1 Mon Sep 17 00:00:00 2001 From: CMGS Date: Wed, 19 Aug 2026 12:34:35 +0800 Subject: [PATCH] efi: Implement reset_system for Cloud Hypervisor The ResetSystem runtime service was intentionally left as a no-op to force Linux to use ACPI for shutdown. However, Windows calls ResetSystem(EfiResetShutdown) as its final shutdown step and does not fall back to ACPI when the call returns. This causes the Cloud Hypervisor process to hang indefinitely after Windows shuts down. Write the appropriate value to Cloud Hypervisor's shutdown I/O port (0x600) based on the reset type: - EfiResetShutdown: SLP_TYP=5 + SLP_EN (triggers VM exit) - EfiResetCold/Warm: reboot value (triggers VM reset) The service is split into two phases because EFI applications like shim call ResetSystem during early boot when MOK state import fails (set_variable returns UNSUPPORTED). Writing to the shutdown port there made VMs either shut down on Cloud Hypervisor or hang on QEMU during boot. So reset_system_boot keeps the old no-op behavior during boot services, and the port I/O only becomes active after ExitBootServices. fixup_at_virtual remaps reset_system to its virtual address rather than not_available, so ResetSystem still works after SetVirtualAddressMap, which Windows needs to shut down on Cloud Hypervisor. Linux guests are unaffected as they use ACPI directly. Fixes: cloud-hypervisor/rust-hypervisor-firmware#422 Signed-off-by: CMGS --- src/efi/boot_services.rs | 3 +++ src/efi/runtime_services.rs | 52 ++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/efi/boot_services.rs b/src/efi/boot_services.rs index 26a93ec5..6c1826a0 100644 --- a/src/efi/boot_services.rs +++ b/src/efi/boot_services.rs @@ -453,6 +453,9 @@ pub extern "efiapi" fn unload_image(_: Handle) -> Status { } pub extern "efiapi" fn exit_boot_services(_: Handle, _: usize) -> Status { + unsafe { + super::runtime_services::swap_reset_system(); + } Status::SUCCESS } diff --git a/src/efi/runtime_services.rs b/src/efi/runtime_services.rs index fee334f1..3346f65f 100644 --- a/src/efi/runtime_services.rs +++ b/src/efi/runtime_services.rs @@ -38,7 +38,7 @@ pub static mut RS: SyncUnsafeCell = get_next_variable_name, set_variable, get_next_high_mono_count, - reset_system, + reset_system: reset_system_boot, update_capsule, query_capsule_capabilities, query_variable_info, @@ -62,7 +62,11 @@ unsafe fn fixup_at_virtual(descriptors: &[MemoryDescriptor]) { rs.get_variable = transmute(ptr); rs.set_variable = transmute(ptr); rs.get_next_variable_name = transmute(ptr); - rs.reset_system = transmute(ptr); + let reset_ptr = ALLOCATOR + .borrow() + .convert_internal_pointer(descriptors, (reset_system as *const ()) as u64) + .unwrap(); + rs.reset_system = transmute(reset_ptr); rs.update_capsule = transmute(ptr); rs.query_capsule_capabilities = transmute(ptr); rs.query_variable_info = transmute(ptr); @@ -196,8 +200,48 @@ pub extern "efiapi" fn get_next_high_mono_count(_: *mut u32) -> Status { Status::DEVICE_ERROR } -pub extern "efiapi" fn reset_system(_: ResetType, _: Status, _: usize, _: *mut c_void) { - // Don't do anything to force the kernel to use ACPI for shutdown and triple-fault for reset +// No-op used during boot services phase. EFI applications like shim may call +// ResetSystem during early boot (e.g. on MOK import failure). Returning here +// lets them continue, matching the original firmware behavior. +pub extern "efiapi" fn reset_system_boot(_: ResetType, _: Status, _: usize, _: *mut c_void) {} + +// Activated after ExitBootServices via swap_reset_system(). Writes to Cloud +// Hypervisor's AcpiShutdownDevice at IO port 0x600 to perform the actual +// VM shutdown or reset. On non-CH platforms the port write is a no-op and +// the function falls through to a halt loop. +pub extern "efiapi" fn reset_system(_reset_type: ResetType, _: Status, _: usize, _: *mut c_void) { + #[cfg(target_arch = "x86_64")] + { + const SHUTDOWN_PORT: u16 = 0x600; + const S5_SLEEP_VALUE: u8 = (5 << 2) | (1 << 5); // SLP_TYP=5, SLP_EN=1 + const REBOOT_VALUE: u8 = 1; + + let value = if _reset_type == efi::RESET_SHUTDOWN { + S5_SLEEP_VALUE + } else { + REBOOT_VALUE + }; + unsafe { + core::arch::asm!("out dx, al", in("dx") SHUTDOWN_PORT, in("al") value); + } + } + + loop { + #[cfg(target_arch = "x86_64")] + unsafe { + core::arch::asm!("hlt"); + } + #[cfg(not(target_arch = "x86_64"))] + core::hint::spin_loop(); + } +} + +/// Switch the runtime services table from the boot-phase no-op to the real +/// reset_system implementation. Called from exit_boot_services. +pub unsafe fn swap_reset_system() { + #[allow(static_mut_refs)] + let rs = RS.get_mut(); + rs.reset_system = reset_system; } pub extern "efiapi" fn update_capsule(