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/// If no handler is injected, [`restart`] calls [`crate::arch::power::try_restart`].
28/// Injection replaces that behavior: [`restart`] invokes only the injected handler, which may call
29/// the architecture-specific operation as part of its own policy.
30///
31/// ```no_run
32/// use ostd::power::{self, ExitCode};
33///
34/// fn init() {
35///     power::inject_restart_handler(restart_policy);
36/// }
37///
38/// fn restart_policy(code: ExitCode) {
39///     try_platform_restart();
40///     ostd::arch::power::try_restart(code);
41/// }
42///
43/// fn try_platform_restart() {
44///     // Try a platform-specific restart mechanism, if available.
45/// }
46/// ```
47///
48/// The function may be called only once; subsequent calls take no effect.
49pub fn inject_restart_handler(handler: fn(ExitCode)) {
50    RESTART_HANDLER.call_once(|| handler);
51}
52
53/// Restarts the system.
54///
55/// This function will not return. If the restart mechanism fails, this function will halt all CPUs
56/// on the machine.
57pub fn restart(code: ExitCode) -> ! {
58    if let Some(handler) = RESTART_HANDLER.get() {
59        (handler)(code);
60    } else {
61        crate::arch::power::try_restart(code);
62    }
63    crate::error!("Failed to restart the system");
64
65    machine_halt();
66}
67
68static POWEROFF_HANDLER: Once<fn(ExitCode)> = Once::new();
69
70/// Injects a handler that can power off the system.
71///
72/// If no handler is injected, [`poweroff`] calls [`crate::arch::power::try_poweroff`].
73/// Injection replaces that behavior: [`poweroff`] invokes only the injected handler, which may call
74/// the architecture-specific operation as part of its own policy.
75///
76/// ```no_run
77/// use ostd::power::{self, ExitCode};
78///
79/// fn init() {
80///     power::inject_poweroff_handler(poweroff_policy);
81/// }
82///
83/// fn poweroff_policy(code: ExitCode) {
84///     try_platform_poweroff();
85///     ostd::arch::power::try_poweroff(code);
86/// }
87///
88/// fn try_platform_poweroff() {
89///     // Try a platform-specific poweroff mechanism, if available.
90/// }
91/// ```
92///
93/// The function may be called only once; subsequent calls take no effect.
94pub fn inject_poweroff_handler(handler: fn(ExitCode)) {
95    POWEROFF_HANDLER.call_once(|| handler);
96}
97
98/// Powers off the system.
99///
100/// This function will not return. If the poweroff mechanism fails, this function will halt all CPUs
101/// on the machine.
102pub fn poweroff(code: ExitCode) -> ! {
103    #[cfg(feature = "coverage")]
104    crate::coverage::on_system_exit();
105
106    if let Some(handler) = POWEROFF_HANDLER.get() {
107        (handler)(code);
108    } else {
109        crate::arch::power::try_poweroff(code);
110    }
111    crate::error!("Failed to power off the system");
112
113    machine_halt();
114}
115
116fn machine_halt() -> ! {
117    crate::error!("Halting the machine...");
118
119    // TODO: `inter_processor_call` may panic again (e.g., if there is an out-of-memory error). We
120    // should find a way to make it panic-free.
121    if let Some(ipi_sender) = crate::smp::IPI_SENDER.get() {
122        ipi_sender.inter_processor_call(&CpuSet::new_full(), || disable_local_and_halt());
123    }
124    disable_local_and_halt();
125}