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.
15pub enum ExitCode {
16 /// The code that indicates a successful exit.
17 Success,
18 /// The code that indicates a failed exit.
19 Failure,
20}
21
22static RESTART_HANDLER: Once<fn(ExitCode)> = Once::new();
23
24/// Injects a handler that can restart the system.
25///
26/// The function may be called only once; subsequent calls take no effect.
27///
28/// Note that, depending on the specific architecture, OSTD may already have a built-in handler. If
29/// so, calling this function outside of OSTD will never take effect. Currently, it happens in
30/// - x86_64: Never;
31/// - riscv64: Always;
32/// - loongarch64: Never.
33pub fn inject_restart_handler(handler: fn(ExitCode)) {
34 RESTART_HANDLER.call_once(|| handler);
35}
36
37/// Restarts the system.
38///
39/// This function will not return. If a restart handler is missing or not working, it will halt all
40/// CPUs on the machine.
41pub fn restart(code: ExitCode) -> ! {
42 if let Some(handler) = RESTART_HANDLER.get() {
43 (handler)(code);
44 log::error!("Failed to restart the system because the restart handler fails");
45 } else {
46 log::error!("Failed to restart the system because a restart handler is missing");
47 }
48
49 machine_halt();
50}
51
52static POWEROFF_HANDLER: Once<fn(ExitCode)> = Once::new();
53
54/// Injects a handler that can power off the system.
55///
56/// The function may be called only once; subsequent calls take no effect.
57///
58/// Note that, depending on the specific architecture, OSTD may already have a built-in handler. If
59/// so, calling this function outside of OSTD will never take effect. Currently, it happens in
60/// - x86_64: If a QEMU hypervisor is detected;
61/// - riscv64: Always;
62/// - loongarch64: Never.
63pub fn inject_poweroff_handler(handler: fn(ExitCode)) {
64 POWEROFF_HANDLER.call_once(|| handler);
65}
66
67/// Powers off the system.
68///
69/// This function will not return. If a poweroff handler is missing or not working, it will halt
70/// all CPUs on the machine.
71pub fn poweroff(code: ExitCode) -> ! {
72 #[cfg(feature = "coverage")]
73 crate::coverage::on_system_exit();
74
75 if let Some(handler) = POWEROFF_HANDLER.get() {
76 (handler)(code);
77 log::error!("Failed to power off the system because the poweroff handler fails");
78 } else {
79 log::error!("Failed to power off the system because a poweroff handler is missing");
80 }
81
82 machine_halt();
83}
84
85fn machine_halt() -> ! {
86 log::error!("Halting the machine...");
87
88 // TODO: `inter_processor_call` may panic again (e.g., if there is an out-of-memory error). We
89 // should find a way to make it panic-free.
90 if let Some(ipi_sender) = crate::smp::IPI_SENDER.get() {
91 ipi_sender.inter_processor_call(&CpuSet::new_full(), || disable_local_and_halt());
92 }
93 disable_local_and_halt();
94}