ostd/
power.rs

1// SPDX-License-Identifier: MPL-2.0
2
3//! Power management.
4
5use core::sync::atomic::Ordering;
6
7use spin::Once;
8
9use crate::{arch::irq::disable_local_and_halt, cpu::CpuSet, smp::inter_processor_call};
10
11/// An exit code that denotes the reason for restarting or powering off.
12///
13/// Whether or not the code is used depends on the hardware. In a virtualization environment, it
14/// can be passed to the hypervisor (e.g., as QEMU's exit code). In a bare-metal environment, it
15/// can be passed to the firmware. In either case, the code may be silently ignored if reporting
16/// the code is not supported.
17pub enum ExitCode {
18    /// The code that indicates a successful exit.
19    Success,
20    /// The code that indicates a failed exit.
21    Failure,
22}
23
24static RESTART_HANDLER: Once<fn(ExitCode)> = Once::new();
25
26/// Injects a handler that can restart the system.
27///
28/// The function may be called only once; subsequent calls take no effect.
29///
30/// Note that, depending on the specific architecture, OSTD may already have a built-in handler. If
31/// so, calling this function outside of OSTD will never take effect. Currently, it happens in
32///  - x86_64: Never;
33///  - riscv64: Always;
34///  - loongarch64: Never.
35pub fn inject_restart_handler(handler: fn(ExitCode)) {
36    RESTART_HANDLER.call_once(|| handler);
37}
38
39/// Restarts the system.
40///
41/// This function will not return. If a restart handler is missing or not working, it will halt all
42/// CPUs on the machine.
43pub fn restart(code: ExitCode) -> ! {
44    if let Some(handler) = RESTART_HANDLER.get() {
45        (handler)(code);
46        log::error!("Failed to restart the system because the restart handler fails");
47    } else {
48        log::error!("Failed to restart the system because a restart handler is missing");
49    }
50
51    machine_halt();
52}
53
54static POWEROFF_HANDLER: Once<fn(ExitCode)> = Once::new();
55
56/// Injects a handler that can power off the system.
57///
58/// The function may be called only once; subsequent calls take no effect.
59///
60/// Note that, depending on the specific architecture, OSTD may already have a built-in handler. If
61/// so, calling this function outside of OSTD will never take effect. Currently, it happens in
62///  - x86_64: If a QEMU hypervisor is detected;
63///  - riscv64: Always;
64///  - loongarch64: Never.
65pub fn inject_poweroff_handler(handler: fn(ExitCode)) {
66    POWEROFF_HANDLER.call_once(|| handler);
67}
68
69/// Powers off the system.
70///
71/// This function will not return. If a poweroff handler is missing or not working, it will halt
72/// all CPUs on the machine.
73pub fn poweroff(code: ExitCode) -> ! {
74    #[cfg(feature = "coverage")]
75    crate::coverage::on_system_exit();
76
77    if let Some(handler) = POWEROFF_HANDLER.get() {
78        (handler)(code);
79        log::error!("Failed to power off the system because the poweroff handler fails");
80    } else {
81        log::error!("Failed to power off the system because a poweroff handler is missing");
82    }
83
84    machine_halt();
85}
86
87fn machine_halt() -> ! {
88    log::error!("Halting the machine...");
89
90    // TODO: `inter_processor_call` may panic again (e.g., if IPIs have not been initialized or if
91    // there is an out-of-memory error). We should find a way to make it panic-free.
92    if !crate::IN_BOOTSTRAP_CONTEXT.load(Ordering::Relaxed) {
93        inter_processor_call(&CpuSet::new_full(), || disable_local_and_halt());
94    }
95    disable_local_and_halt();
96}