diff --git a/kernel/src/arch/loongarch/mod.rs b/kernel/src/arch/loongarch/mod.rs index e2a78f3c5..4ff453e2c 100644 --- a/kernel/src/arch/loongarch/mod.rs +++ b/kernel/src/arch/loongarch/mod.rs @@ -1,4 +1,9 @@ // SPDX-License-Identifier: MPL-2.0 pub mod cpu; +mod power; pub mod signal; + +pub fn init() { + power::init(); +} diff --git a/kernel/src/arch/loongarch/power.rs b/kernel/src/arch/loongarch/power.rs new file mode 100644 index 000000000..a94952dbf --- /dev/null +++ b/kernel/src/arch/loongarch/power.rs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MPL-2.0 + +use ostd::{ + arch::boot::DEVICE_TREE, + io::IoMem, + mm::VmIoOnce, + power::{inject_poweroff_handler, ExitCode}, +}; +use spin::Once; + +static POWEROFF_REG_AND_VAL: Once<(IoMem, u8)> = Once::new(); + +fn try_poweroff(_code: ExitCode) { + // If possible, keep this method panic-free because it may be called by the panic handler. + if let Some((reg, val)) = POWEROFF_REG_AND_VAL.get() { + let _ = reg.write_once(0, val); + } +} + +pub(super) fn init() { + let Some((poweroff_addr, poweroff_value)) = lookup_poweroff_paddr_value() else { + return; + }; + let Ok(poweroff_reg) = IoMem::acquire(poweroff_addr..poweroff_addr + size_of::()) else { + log::warn!("The poweroff register from syscon-poweroff is not available"); + return; + }; + + POWEROFF_REG_AND_VAL.call_once(move || (poweroff_reg, poweroff_value)); + inject_poweroff_handler(try_poweroff); +} + +fn lookup_poweroff_paddr_value() -> Option<(usize, u8)> { + let device_tree = DEVICE_TREE.get().unwrap(); + + let ged = device_tree.find_node("/ged")?; + if !ged.compatible()?.all().any(|c| c == "syscon") { + return None; + } + let ged_reg_base_address = ged.reg()?.next()?.starting_address as usize; + + let poweroff = device_tree.find_node("/poweroff")?; + if !poweroff.compatible()?.all().any(|c| c == "syscon-poweroff") { + return None; + } + let poweroff_offset = poweroff.property("offset")?.as_usize()?; + let poweroff_value = poweroff.property("value")?.as_usize()? as u8; + + Some((ged_reg_base_address + poweroff_offset, poweroff_value)) +} diff --git a/kernel/src/arch/riscv/mod.rs b/kernel/src/arch/riscv/mod.rs index e2a78f3c5..bf69fc0d5 100644 --- a/kernel/src/arch/riscv/mod.rs +++ b/kernel/src/arch/riscv/mod.rs @@ -2,3 +2,5 @@ pub mod cpu; pub mod signal; + +pub fn init() {} diff --git a/kernel/src/arch/x86/mod.rs b/kernel/src/arch/x86/mod.rs index e2a78f3c5..bf69fc0d5 100644 --- a/kernel/src/arch/x86/mod.rs +++ b/kernel/src/arch/x86/mod.rs @@ -2,3 +2,5 @@ pub mod cpu; pub mod signal; + +pub fn init() {} diff --git a/kernel/src/init.rs b/kernel/src/init.rs index 574553356..bc558314b 100644 --- a/kernel/src/init.rs +++ b/kernel/src/init.rs @@ -4,11 +4,7 @@ use aster_cmdline::KCMDLINE; use component::InitStage; -use ostd::{ - arch::qemu::{exit_qemu, QemuExitCode}, - cpu::CpuId, - util::id_set::Id, -}; +use ostd::{cpu::CpuId, util::id_set::Id}; use spin::once::Once; use crate::{ @@ -39,6 +35,7 @@ pub(super) fn main() { } fn init() { + crate::arch::init(); crate::thread::init(); crate::util::random::init(); crate::driver::init(); @@ -108,13 +105,15 @@ fn bsp_idle_loop() { ostd::task::halt_cpu(); } - // TODO: exit via qemu isa debug device should not be the only way. - let exit_code = if init_process.status().exit_code() == 0 { - QemuExitCode::Success + // According to the Linux implementation, we should panic once the init process exits. + // Currently, we choose to power off the machine for more flexibility in testing with QEMU. + let raw_exit_code = init_process.status().exit_code(); + let exit_code = if raw_exit_code == 0 { + ostd::power::ExitCode::Success } else { - QemuExitCode::Failed + ostd::power::ExitCode::Failure }; - exit_qemu(exit_code); + ostd::power::poweroff(exit_code); } fn ap_idle_loop() { diff --git a/kernel/src/syscall/reboot.rs b/kernel/src/syscall/reboot.rs index 2ebaef1e9..00573c0c0 100644 --- a/kernel/src/syscall/reboot.rs +++ b/kernel/src/syscall/reboot.rs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +use ostd::power::{poweroff, restart, ExitCode}; + use super::SyscallReturn; use crate::{prelude::*, process::credentials::capabilities::CapSet}; @@ -55,14 +57,9 @@ pub fn sys_reboot( let cmd = RebootCmd::try_from(op)?; + // TODO: Perform any necessary cleanup before powering off or restarting. match cmd { - RebootCmd::Restart => { - // TODO: Implement restart functionality. - return_errno_with_message!(Errno::ENOSYS, "restart is not implemented yet"); - } - RebootCmd::Halt | RebootCmd::PowerOff => { - // TODO: Perform any necessary cleanup before powering off. - ostd::arch::cpu::poweroff::poweroff(); - } + RebootCmd::Restart => restart(ExitCode::Success), + RebootCmd::Halt | RebootCmd::PowerOff => poweroff(ExitCode::Success), } } diff --git a/osdk/deps/test-kernel/src/lib.rs b/osdk/deps/test-kernel/src/lib.rs index f724932f0..414f12ebf 100644 --- a/osdk/deps/test-kernel/src/lib.rs +++ b/osdk/deps/test-kernel/src/lib.rs @@ -38,14 +38,14 @@ fn main() { let test_task = move || { use alloc::string::ToString; - use ostd::arch::qemu::{exit_qemu, QemuExitCode}; + use ostd::power::{poweroff, ExitCode}; match run_ktests( get_ktest_test_whitelist().map(|s| s.iter().map(|s| s.to_string())), get_ktest_crate_whitelist(), ) { - KtestResult::Ok => exit_qemu(QemuExitCode::Success), - KtestResult::Failed => exit_qemu(QemuExitCode::Failed), + KtestResult::Ok => poweroff(ExitCode::Success), + KtestResult::Failed => poweroff(ExitCode::Failure), }; }; diff --git a/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs index 3e3aa1176..81c6162cf 100644 --- a/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs +++ b/osdk/tests/examples_in_book/write_a_kernel_in_100_lines_templates/lib.rs @@ -12,11 +12,11 @@ use alloc::sync::Arc; use alloc::vec; use ostd::arch::cpu::context::UserContext; -use ostd::arch::qemu::{exit_qemu, QemuExitCode}; use ostd::mm::{ CachePolicy, FallibleVmRead, FrameAllocOptions, PageFlags, PageProperty, Vaddr, VmIo, VmSpace, VmWriter, PAGE_SIZE, }; +use ostd::power::{poweroff, ExitCode}; use ostd::prelude::*; use ostd::task::{disable_preempt, Task, TaskOptions}; use ostd::user::{ReturnReason, UserMode}; @@ -130,7 +130,7 @@ fn handle_syscall(user_context: &mut UserContext, vm_space: &VmSpace) { // Manipulate the user-space CPU registers safely. user_context.set_rax(buf_len); } - SYS_EXIT => exit_qemu(QemuExitCode::Success), + SYS_EXIT => poweroff(ExitCode::Success), _ => unimplemented!(), } } diff --git a/ostd/src/arch/loongarch/cpu/mod.rs b/ostd/src/arch/loongarch/cpu/mod.rs index 9ca848d32..bf160e756 100644 --- a/ostd/src/arch/loongarch/cpu/mod.rs +++ b/ostd/src/arch/loongarch/cpu/mod.rs @@ -4,4 +4,3 @@ pub mod context; pub mod local; -pub mod poweroff; diff --git a/ostd/src/arch/loongarch/cpu/poweroff.rs b/ostd/src/arch/loongarch/cpu/poweroff.rs deleted file mode 100644 index a2b14a549..000000000 --- a/ostd/src/arch/loongarch/cpu/poweroff.rs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! Provides the implementation of the poweroff functionality. - -use crate::arch::qemu::{exit_qemu, QemuExitCode}; - -/// Powers off the system. -pub fn poweroff() -> ! { - // TODO: Implement the poweroff behavior on a real machine. - exit_qemu(QemuExitCode::Success); -} diff --git a/ostd/src/arch/loongarch/mod.rs b/ostd/src/arch/loongarch/mod.rs index 46a37762b..196f15d77 100644 --- a/ostd/src/arch/loongarch/mod.rs +++ b/ostd/src/arch/loongarch/mod.rs @@ -11,7 +11,6 @@ mod io; pub(crate) mod iommu; pub(crate) mod irq; pub(crate) mod mm; -pub mod qemu; pub(crate) mod serial; pub(crate) mod task; mod timer; diff --git a/ostd/src/arch/loongarch/qemu.rs b/ostd/src/arch/loongarch/qemu.rs deleted file mode 100644 index 2e1bc8837..000000000 --- a/ostd/src/arch/loongarch/qemu.rs +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! Providing the ability to exit QEMU and return a value as debug result. - -use crate::arch::{boot::DEVICE_TREE, mm::paddr_to_daddr}; - -/// The exit code of QEMU. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum QemuExitCode { - /// The code that indicates a successful exit. - Success, - /// The code that indicates a failed exit. - Failed, -} - -/// Exits QEMU with the given exit code. -// FIXME: Support the transfer of the exit code to QEMU and multiple platforms. -pub fn exit_qemu(_exit_code: QemuExitCode) { - let (poweroff_addr, poweroff_value) = lookup_poweroff_paddr_value().unwrap(); - let poweroff_daddr = paddr_to_daddr(poweroff_addr) as *mut u8; - - // SAFETY: It is safe because the poweroff address is acquired from the device tree, - // and be mapped in DMW2. - unsafe { - core::ptr::write_volatile(poweroff_daddr, poweroff_value); - } -} - -// FIXME: We should reserve the address region in `io_mem_allocator`. -fn lookup_poweroff_paddr_value() -> Option<(usize, u8)> { - let device_tree = DEVICE_TREE.get().unwrap(); - - let ged = device_tree.find_node("/ged")?; - if !ged.compatible()?.all().any(|c| c == "syscon") { - return None; - } - let ged_reg_base_address = ged.reg()?.next()?.starting_address as usize; - - let poweroff = device_tree.find_node("/poweroff").unwrap(); - if !poweroff.compatible()?.all().any(|c| c == "syscon-poweroff") { - return None; - } - let poweroff_offset = poweroff.property("offset")?.as_usize()?; - let poweroff_value = poweroff.property("value")?.as_usize()? as u8; - - Some((ged_reg_base_address + poweroff_offset, poweroff_value)) -} diff --git a/ostd/src/arch/riscv/cpu/mod.rs b/ostd/src/arch/riscv/cpu/mod.rs index 316ed239f..fc7b22f67 100644 --- a/ostd/src/arch/riscv/cpu/mod.rs +++ b/ostd/src/arch/riscv/cpu/mod.rs @@ -5,4 +5,3 @@ pub mod context; pub mod extension; pub mod local; -pub mod poweroff; diff --git a/ostd/src/arch/riscv/cpu/poweroff.rs b/ostd/src/arch/riscv/cpu/poweroff.rs deleted file mode 100644 index a2b14a549..000000000 --- a/ostd/src/arch/riscv/cpu/poweroff.rs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! Provides the implementation of the poweroff functionality. - -use crate::arch::qemu::{exit_qemu, QemuExitCode}; - -/// Powers off the system. -pub fn poweroff() -> ! { - // TODO: Implement the poweroff behavior on a real machine. - exit_qemu(QemuExitCode::Success); -} diff --git a/ostd/src/arch/riscv/mod.rs b/ostd/src/arch/riscv/mod.rs index 31a9c474e..32907dabd 100644 --- a/ostd/src/arch/riscv/mod.rs +++ b/ostd/src/arch/riscv/mod.rs @@ -11,7 +11,7 @@ mod io; pub(crate) mod iommu; pub mod irq; pub(crate) mod mm; -pub mod qemu; +mod power; pub(crate) mod serial; pub(crate) mod task; mod timer; @@ -61,6 +61,8 @@ pub(crate) unsafe fn late_init_on_bsp() { // 1. All the system device memory have been removed from the builder. // 2. RISC-V platforms do not have port I/O. unsafe { crate::io::init(io_mem_builder) }; + + power::init(); } /// Initializes application-processor-specific state. diff --git a/ostd/src/arch/riscv/power.rs b/ostd/src/arch/riscv/power.rs new file mode 100644 index 000000000..c34ee17d2 --- /dev/null +++ b/ostd/src/arch/riscv/power.rs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Power management. + +use crate::power::{inject_poweroff_handler, inject_restart_handler, ExitCode}; + +fn try_poweroff(code: ExitCode) { + let _ = match code { + ExitCode::Success => sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::NoReason), + ExitCode::Failure => sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::SystemFailure), + }; +} + +fn try_restart(code: ExitCode) { + let _ = match code { + ExitCode::Success => sbi_rt::system_reset(sbi_rt::ColdReboot, sbi_rt::NoReason), + ExitCode::Failure => sbi_rt::system_reset(sbi_rt::ColdReboot, sbi_rt::SystemFailure), + }; +} + +pub(super) fn init() { + inject_poweroff_handler(try_poweroff); + inject_restart_handler(try_restart); +} diff --git a/ostd/src/arch/riscv/qemu.rs b/ostd/src/arch/riscv/qemu.rs deleted file mode 100644 index 851586310..000000000 --- a/ostd/src/arch/riscv/qemu.rs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! Providing the ability to exit QEMU and return a value as debug result. - -/// The exit code of QEMU. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum QemuExitCode { - /// The code that indicates a successful exit. - Success, - /// The code that indicates a failed exit. - Failed, -} - -/// Exit QEMU with the given exit code. -pub fn exit_qemu(exit_code: QemuExitCode) { - log::debug!("exit qemu with exit code {exit_code:?}"); - let error_code = match exit_code { - QemuExitCode::Success => sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::NoReason), - QemuExitCode::Failed => sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::SystemFailure), - }; - - log::error!( - "SBI system_reset cannot shut down the underlying machine, error code: {:#?}", - error_code - ); -} diff --git a/ostd/src/arch/x86/cpu/mod.rs b/ostd/src/arch/x86/cpu/mod.rs index 294ce2f1a..eacd55ca5 100644 --- a/ostd/src/arch/x86/cpu/mod.rs +++ b/ostd/src/arch/x86/cpu/mod.rs @@ -6,4 +6,3 @@ pub mod context; pub mod cpuid; pub mod extension; pub mod local; -pub mod poweroff; diff --git a/ostd/src/arch/x86/cpu/poweroff.rs b/ostd/src/arch/x86/cpu/poweroff.rs deleted file mode 100644 index d93a7a505..000000000 --- a/ostd/src/arch/x86/cpu/poweroff.rs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! Provides the implementation of the poweroff functionality. - -use crate::arch::{ - cpu::cpuid::query_is_running_in_qemu, - qemu::{exit_qemu, QemuExitCode}, -}; - -/// Powers off the system. -pub fn poweroff() -> ! { - if query_is_running_in_qemu() { - exit_qemu(QemuExitCode::Success); - } - - todo!("Implement ACPI shutdown"); -} diff --git a/ostd/src/arch/x86/mod.rs b/ostd/src/arch/x86/mod.rs index 93d5ab698..a6d7aec5c 100644 --- a/ostd/src/arch/x86/mod.rs +++ b/ostd/src/arch/x86/mod.rs @@ -10,7 +10,7 @@ pub(crate) mod iommu; pub mod irq; pub mod kernel; pub(crate) mod mm; -pub mod qemu; +mod power; pub(crate) mod serial; pub(crate) mod task; mod timer; @@ -74,8 +74,6 @@ pub(crate) unsafe fn late_init_on_bsp() { kernel::tsc::init_tsc_freq(); timer::init_on_bsp(); - kernel::acpi::init(); - // SAFETY: We're on the BSP and we're ready to boot all APs. unsafe { crate::boot::smp::boot_all_aps() }; @@ -92,6 +90,9 @@ pub(crate) unsafe fn late_init_on_bsp() { // 2. All the port I/O regions belonging to the system device are defined using the macros. // 3. `MAX_IO_PORT` defined in `crate::arch::io` is the maximum value specified by x86-64. unsafe { crate::io::init(io_mem_builder) }; + + kernel::acpi::init(); + power::init(); } /// Initializes application-processor-specific state. diff --git a/ostd/src/arch/x86/power.rs b/ostd/src/arch/x86/power.rs new file mode 100644 index 000000000..73d4db4db --- /dev/null +++ b/ostd/src/arch/x86/power.rs @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Power management. + +mod qemu_isa_debug { + //! The isa-debug-exit device in QEMU. + //! + //! Reference: + + use spin::Once; + + use crate::{ + arch::device::io_port::WriteOnlyAccess, + io::IoPort, + power::{inject_poweroff_handler, ExitCode}, + }; + + // For `qemu-system-x86_64`, the exit code will be `(code << 1) | 1`. So it is not possible to + // let QEMU invoke `exit(0)`. We also need to check if the exit code is returned by the kernel, + // so we cannot use `0` as `EXIT_SUCCESS` because it may conflict with QEMU's return value `1`, + // which indicates that QEMU itself fails. + const EXIT_SUCCESS: u32 = 0x10; + const EXIT_FAILURE: u32 = 0x20; + + static DEBUG_EXIT_PORT: Once> = Once::new(); + + fn try_exit_qemu(code: ExitCode) { + let value = match code { + ExitCode::Success => EXIT_SUCCESS, + ExitCode::Failure => EXIT_FAILURE, + }; + + // If possible, keep this method panic-free because it may be called by the panic handler. + if let Some(port) = DEBUG_EXIT_PORT.get() { + port.write(value); + } + } + + pub(super) fn init() { + const DEBUG_EXIT_PORT_NUM: u16 = 0xF4; + + let debug_exit_port = IoPort::acquire(DEBUG_EXIT_PORT_NUM).unwrap(); + + DEBUG_EXIT_PORT.call_once(|| debug_exit_port); + inject_poweroff_handler(try_exit_qemu); + } +} + +pub(super) fn init() { + use super::cpu::cpuid; + + if !cpuid::query_is_running_in_qemu() { + return; + } + + // FIXME: We assume that the kernel is running in QEMU with the following QEMU command line + // arguments that specify the isa-debug-exit device: + // `-device isa-debug-exit,iobase=0xf4,iosize=0x04`. + log::info!("QEMU hypervisor detected, assuming that the isa-debug-exit device exists"); + + qemu_isa_debug::init(); +} diff --git a/ostd/src/arch/x86/qemu.rs b/ostd/src/arch/x86/qemu.rs deleted file mode 100644 index 00c9e780f..000000000 --- a/ostd/src/arch/x86/qemu.rs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -//! Provides the ability to exit QEMU and return a value as debug result. - -/// The exit code of x86 QEMU isa debug device. -/// -/// In `qemu-system-x86_64` the exit code will be `(code << 1) | 1`. So you -/// could never let QEMU invoke `exit(0)`. We also need to check if the exit -/// code is returned by the kernel, so we couldn't use 0 as exit_success -/// because this may conflict with QEMU return value 1, which indicates that -/// QEMU itself fails. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum QemuExitCode { - /// The code that indicates a successful exit. - Success = 0x10, - /// The code that indicates a failed exit. - Failed = 0x20, -} - -/// Exits QEMU with the given exit code. -/// -/// This function assumes that the kernel is run in QEMU with the following -/// QEMU command line arguments that specifies the ISA debug exit device: -/// `-device isa-debug-exit,iobase=0xf4,iosize=0x04`. -pub fn exit_qemu(exit_code: QemuExitCode) { - #[cfg(feature = "coverage")] - crate::coverage::on_qemu_exit(); - - use x86_64::instructions::port::Port; - let mut port = Port::new(0xf4); - - // SAFETY: The write to the ISA debug exit port is safe and `0xf4` should - // be the port number. - unsafe { - port.write(exit_code as u32); - } -} diff --git a/ostd/src/coverage.rs b/ostd/src/coverage.rs index f05a1f4b4..75e3e3590 100644 --- a/ostd/src/coverage.rs +++ b/ostd/src/coverage.rs @@ -8,18 +8,21 @@ use alloc::vec::Vec; use core::mem::ManuallyDrop; +use spin::Once; + use crate::sync::SpinLock; -/// A hook to be invoked on QEMU exit for dumping the code coverage data. -pub(crate) fn on_qemu_exit() { - let mut coverage = ManuallyDrop::new(Vec::new()); - static COV_LOCK: SpinLock<()> = SpinLock::new(()); - let _guard = COV_LOCK.disable_irq().lock(); - // SAFETY: The above lock ensures that this function is not called - // concurrently by multiple threads. - unsafe { - minicov::capture_coverage(&mut *coverage).unwrap(); - } +/// A hook that is invoked when the system exits to dump the code coverage data. +pub(crate) fn on_system_exit() { + static COVERAGE_DATA: Once> = Once::new(); + + let coverage = COVERAGE_DATA.call_once(|| { + let mut coverage = Vec::new(); + // SAFETY: `call_once` guarantees that this function will not be called concurrently by + // multiple threads. + unsafe { minicov::capture_coverage(&mut coverage).unwrap() }; + coverage + }); crate::early_println!("#### Coverage: {:p} {}", coverage.as_ptr(), coverage.len()); } diff --git a/ostd/src/lib.rs b/ostd/src/lib.rs index 524fa76aa..d61e5cd4a 100644 --- a/ostd/src/lib.rs +++ b/ostd/src/lib.rs @@ -38,6 +38,7 @@ pub mod irq; pub mod logger; pub mod mm; pub mod panic; +pub mod power; pub mod prelude; pub mod smp; pub mod sync; diff --git a/ostd/src/panic.rs b/ostd/src/panic.rs index 4b8f77ee7..36b7c2325 100644 --- a/ostd/src/panic.rs +++ b/ostd/src/panic.rs @@ -2,12 +2,7 @@ //! Panic support. -use core::sync::atomic::Ordering; - -use crate::{ - arch::qemu::{exit_qemu, QemuExitCode}, - early_println, -}; +use crate::early_println; extern crate cfg_if; extern crate gimli; @@ -38,18 +33,14 @@ pub fn __ostd_panic_handler(info: &core::panic::PanicInfo) -> ! { abort(); } -/// Aborts the QEMU +/// Aborts the system. +/// +/// This function will first attempt to power off the system. If that fails, it will halt all CPUs. pub fn abort() -> ! { - use crate::{arch::irq::disable_local_and_halt, cpu::CpuSet, smp::inter_processor_call}; - - exit_qemu(QemuExitCode::Failed); - - // TODO: `inter_processor_call` may panic again (e.g., if IPIs have not been initialized or if - // there is an out-of-memory error). We should find a way to make it panic-free. - if !crate::IN_BOOTSTRAP_CONTEXT.load(Ordering::Relaxed) { - inter_processor_call(&CpuSet::new_full(), || disable_local_and_halt()); - } - disable_local_and_halt(); + // TODO: The main purpose of powering off here is to allow QEMU to exit. Otherwise, the CI may + // freeze after panicking. However, this is unnecessary and may prevent debugging on a real + // machine (i.e., the message will disappear afterward). + crate::power::poweroff(crate::power::ExitCode::Failure); } #[cfg(not(target_arch = "loongarch64"))] diff --git a/ostd/src/power.rs b/ostd/src/power.rs new file mode 100644 index 000000000..55d39bb49 --- /dev/null +++ b/ostd/src/power.rs @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MPL-2.0 + +//! Power management. + +use core::sync::atomic::Ordering; + +use spin::Once; + +use crate::{arch::irq::disable_local_and_halt, cpu::CpuSet, smp::inter_processor_call}; + +/// An exit code that denotes the reason for restarting or powering off. +/// +/// Whether or not the code is used depends on the hardware. In a virtualization environment, it +/// can be passed to the hypervisor (e.g., as QEMU's exit code). In a bare-metal environment, it +/// can be passed to the firmware. In either case, the code may be silently ignored if reporting +/// the code is not supported. +pub enum ExitCode { + /// The code that indicates a successful exit. + Success, + /// The code that indicates a failed exit. + Failure, +} + +static RESTART_HANDLER: Once = Once::new(); + +/// Injects a handler that can restart the system. +/// +/// The function may be called only once; subsequent calls take no effect. +/// +/// Note that, depending on the specific architecture, OSTD may already have a built-in handler. If +/// so, calling this function outside of OSTD will never take effect. Currently, it happens in +/// - x86_64: Never; +/// - riscv64: Always; +/// - loongarch64: Never. +pub fn inject_restart_handler(handler: fn(ExitCode)) { + RESTART_HANDLER.call_once(|| handler); +} + +/// Restarts the system. +/// +/// This function will not return. If a restart handler is missing or not working, it will halt all +/// CPUs on the machine. +pub fn restart(code: ExitCode) -> ! { + if let Some(handler) = RESTART_HANDLER.get() { + (handler)(code); + log::error!("Failed to restart the system because the restart handler fails"); + } else { + log::error!("Failed to restart the system because a restart handler is missing"); + } + + machine_halt(); +} + +static POWEROFF_HANDLER: Once = Once::new(); + +/// Injects a handler that can power off the system. +/// +/// The function may be called only once; subsequent calls take no effect. +/// +/// Note that, depending on the specific architecture, OSTD may already have a built-in handler. If +/// so, calling this function outside of OSTD will never take effect. Currently, it happens in +/// - x86_64: If a QEMU hypervisor is detected; +/// - riscv64: Always; +/// - loongarch64: Never. +pub fn inject_poweroff_handler(handler: fn(ExitCode)) { + POWEROFF_HANDLER.call_once(|| handler); +} + +/// Powers off the system. +/// +/// This function will not return. If a poweroff handler is missing or not working, it will halt +/// all CPUs on the machine. +pub fn poweroff(code: ExitCode) -> ! { + #[cfg(feature = "coverage")] + crate::coverage::on_system_exit(); + + if let Some(handler) = POWEROFF_HANDLER.get() { + (handler)(code); + log::error!("Failed to power off the system because the poweroff handler fails"); + } else { + log::error!("Failed to power off the system because a poweroff handler is missing"); + } + + machine_halt(); +} + +fn machine_halt() -> ! { + log::error!("Halting the machine..."); + + // TODO: `inter_processor_call` may panic again (e.g., if IPIs have not been initialized or if + // there is an out-of-memory error). We should find a way to make it panic-free. + if !crate::IN_BOOTSTRAP_CONTEXT.load(Ordering::Relaxed) { + inter_processor_call(&CpuSet::new_full(), || disable_local_and_halt()); + } + disable_local_and_halt(); +}