kernel/power/
mod.rs

1use tracing::{error, info};
2
3use crate::{
4    acpi,
5    cpu::{self},
6    devices::{keyboard_mouse, Device},
7    fs,
8    io::console,
9    process::scheduler,
10    sync::once::OnceLock,
11};
12
13static CURRENT_CMD: OnceLock<PowerCommand> = OnceLock::new();
14
15#[derive(Debug, Copy, Clone)]
16pub enum PowerCommand {
17    Shutdown,
18    Reboot,
19}
20
21/// Power device
22///
23/// This device is used to control the power of the system,
24/// its mostly used with `echo [cmd] > /devices/power`
25/// such as:
26/// - `echo shutdown > /devices/power` to shutdown the system.
27#[derive(Debug)]
28pub struct PowerDevice;
29
30impl Device for PowerDevice {
31    fn name(&self) -> &str {
32        "power"
33    }
34
35    // This is needed to support the `echo shutdown > /dev/power`, as it will
36    // open the file and truncate it to 0, then write to it.
37    fn set_size(&self, size: u64) -> Result<(), fs::FileSystemError> {
38        if size != 0 {
39            // TODO: replace the errors with better ones
40            return Err(fs::FileSystemError::EndOfFile);
41        }
42
43        Ok(())
44    }
45
46    // TODO: replace the errors with better ones
47    fn write(&self, offset: u64, buf: &[u8]) -> Result<u64, fs::FileSystemError> {
48        if offset != 0 {
49            return Err(fs::FileSystemError::EndOfFile);
50        }
51
52        if let Some(rest) = buf.strip_prefix(b"shutdown") {
53            if rest.trim_ascii().is_empty() {
54                start_power_sequence(PowerCommand::Shutdown);
55                Ok(buf.len() as u64)
56            } else {
57                Err(fs::FileSystemError::EndOfFile)
58            }
59        } else if let Some(rest) = buf.strip_prefix(b"reboot") {
60            if rest.trim_ascii().is_empty() {
61                start_power_sequence(PowerCommand::Reboot);
62                Ok(buf.len() as u64)
63            } else {
64                Err(fs::FileSystemError::EndOfFile)
65            }
66        } else {
67            Err(fs::FileSystemError::EndOfFile)
68        }
69    }
70}
71
72/// Start the shutdown process
73pub fn start_power_sequence(cmd: PowerCommand) {
74    if let Err(current_cmd) = CURRENT_CMD.set(cmd) {
75        error!("Power command already set: {current_cmd:?}, ignoring: {cmd:?}",);
76        return;
77    }
78    match cmd {
79        PowerCommand::Shutdown => {
80            info!("Shutting down the system");
81        }
82        PowerCommand::Reboot => {
83            info!("Rebooting the system");
84        }
85    }
86
87    // tell the scheduler to initiate shutdown/reboot, the rest will be handled by
88    // [`finish_power_sequence`]
89    scheduler::stop_scheduler();
90}
91
92/// reverse of [`crate::kernel_main`]
93/// Called by [`crate::kernel_main`] after all processes have exited and cleaned up
94pub fn finish_power_sequence() -> ! {
95    let cmd = CURRENT_CMD.try_get().expect("No power command set");
96
97    console::tracing::shutdown_log_file();
98    // unmount all filesystems
99    fs::unmount_all();
100
101    cpu::cpu().push_cli();
102    match cmd {
103        PowerCommand::Shutdown => {
104            // shutdown through ACPI, state S5
105            acpi::sleep(5).expect("Could not shutdown");
106        }
107        PowerCommand::Reboot => {
108            // TODO: implement using the `reset_register` in ACPI if available
109            //       not doing it now because for my qemu its not enabled,
110            //       and using the below method is easier for now.
111            info!("Rebooting the system using the keyboard controller");
112            keyboard_mouse::reset_system();
113        }
114    }
115
116    // if ACPI failed or woke up, halt the CPU
117    loop {
118        unsafe {
119            cpu::halt();
120        }
121    }
122}