Skip to main content

ostd/
power.rs

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