Manage poweroff and restart handlers
This commit is contained in:
parent
15446386af
commit
35d70fca71
|
|
@ -1,4 +1,9 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
pub mod cpu;
|
||||
mod power;
|
||||
pub mod signal;
|
||||
|
||||
pub fn init() {
|
||||
power::init();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use ostd::{
|
||||
arch::boot::DEVICE_TREE,
|
||||
io::IoMem,
|
||||
mm::VmIoOnce,
|
||||
power::{inject_poweroff_handler, ExitCode},
|
||||
};
|
||||
use spin::Once;
|
||||
|
||||
static POWEROFF_REG_AND_VAL: Once<(IoMem, u8)> = Once::new();
|
||||
|
||||
fn try_poweroff(_code: ExitCode) {
|
||||
// If possible, keep this method panic-free because it may be called by the panic handler.
|
||||
if let Some((reg, val)) = POWEROFF_REG_AND_VAL.get() {
|
||||
let _ = reg.write_once(0, val);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn init() {
|
||||
let Some((poweroff_addr, poweroff_value)) = lookup_poweroff_paddr_value() else {
|
||||
return;
|
||||
};
|
||||
let Ok(poweroff_reg) = IoMem::acquire(poweroff_addr..poweroff_addr + size_of::<u8>()) else {
|
||||
log::warn!("The poweroff register from syscon-poweroff is not available");
|
||||
return;
|
||||
};
|
||||
|
||||
POWEROFF_REG_AND_VAL.call_once(move || (poweroff_reg, poweroff_value));
|
||||
inject_poweroff_handler(try_poweroff);
|
||||
}
|
||||
|
||||
fn lookup_poweroff_paddr_value() -> Option<(usize, u8)> {
|
||||
let device_tree = DEVICE_TREE.get().unwrap();
|
||||
|
||||
let ged = device_tree.find_node("/ged")?;
|
||||
if !ged.compatible()?.all().any(|c| c == "syscon") {
|
||||
return None;
|
||||
}
|
||||
let ged_reg_base_address = ged.reg()?.next()?.starting_address as usize;
|
||||
|
||||
let poweroff = device_tree.find_node("/poweroff")?;
|
||||
if !poweroff.compatible()?.all().any(|c| c == "syscon-poweroff") {
|
||||
return None;
|
||||
}
|
||||
let poweroff_offset = poweroff.property("offset")?.as_usize()?;
|
||||
let poweroff_value = poweroff.property("value")?.as_usize()? as u8;
|
||||
|
||||
Some((ged_reg_base_address + poweroff_offset, poweroff_value))
|
||||
}
|
||||
|
|
@ -2,3 +2,5 @@
|
|||
|
||||
pub mod cpu;
|
||||
pub mod signal;
|
||||
|
||||
pub fn init() {}
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@
|
|||
|
||||
pub mod cpu;
|
||||
pub mod signal;
|
||||
|
||||
pub fn init() {}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,7 @@
|
|||
|
||||
use aster_cmdline::KCMDLINE;
|
||||
use component::InitStage;
|
||||
use ostd::{
|
||||
arch::qemu::{exit_qemu, QemuExitCode},
|
||||
cpu::CpuId,
|
||||
util::id_set::Id,
|
||||
};
|
||||
use ostd::{cpu::CpuId, util::id_set::Id};
|
||||
use spin::once::Once;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -39,6 +35,7 @@ pub(super) fn main() {
|
|||
}
|
||||
|
||||
fn init() {
|
||||
crate::arch::init();
|
||||
crate::thread::init();
|
||||
crate::util::random::init();
|
||||
crate::driver::init();
|
||||
|
|
@ -108,13 +105,15 @@ fn bsp_idle_loop() {
|
|||
ostd::task::halt_cpu();
|
||||
}
|
||||
|
||||
// TODO: exit via qemu isa debug device should not be the only way.
|
||||
let exit_code = if init_process.status().exit_code() == 0 {
|
||||
QemuExitCode::Success
|
||||
// According to the Linux implementation, we should panic once the init process exits.
|
||||
// Currently, we choose to power off the machine for more flexibility in testing with QEMU.
|
||||
let raw_exit_code = init_process.status().exit_code();
|
||||
let exit_code = if raw_exit_code == 0 {
|
||||
ostd::power::ExitCode::Success
|
||||
} else {
|
||||
QemuExitCode::Failed
|
||||
ostd::power::ExitCode::Failure
|
||||
};
|
||||
exit_qemu(exit_code);
|
||||
ostd::power::poweroff(exit_code);
|
||||
}
|
||||
|
||||
fn ap_idle_loop() {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use ostd::power::{poweroff, restart, ExitCode};
|
||||
|
||||
use super::SyscallReturn;
|
||||
use crate::{prelude::*, process::credentials::capabilities::CapSet};
|
||||
|
||||
|
|
@ -55,14 +57,9 @@ pub fn sys_reboot(
|
|||
|
||||
let cmd = RebootCmd::try_from(op)?;
|
||||
|
||||
// TODO: Perform any necessary cleanup before powering off or restarting.
|
||||
match cmd {
|
||||
RebootCmd::Restart => {
|
||||
// TODO: Implement restart functionality.
|
||||
return_errno_with_message!(Errno::ENOSYS, "restart is not implemented yet");
|
||||
}
|
||||
RebootCmd::Halt | RebootCmd::PowerOff => {
|
||||
// TODO: Perform any necessary cleanup before powering off.
|
||||
ostd::arch::cpu::poweroff::poweroff();
|
||||
}
|
||||
RebootCmd::Restart => restart(ExitCode::Success),
|
||||
RebootCmd::Halt | RebootCmd::PowerOff => poweroff(ExitCode::Success),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,14 +38,14 @@ fn main() {
|
|||
let test_task = move || {
|
||||
use alloc::string::ToString;
|
||||
|
||||
use ostd::arch::qemu::{exit_qemu, QemuExitCode};
|
||||
use ostd::power::{poweroff, ExitCode};
|
||||
|
||||
match run_ktests(
|
||||
get_ktest_test_whitelist().map(|s| s.iter().map(|s| s.to_string())),
|
||||
get_ktest_crate_whitelist(),
|
||||
) {
|
||||
KtestResult::Ok => exit_qemu(QemuExitCode::Success),
|
||||
KtestResult::Failed => exit_qemu(QemuExitCode::Failed),
|
||||
KtestResult::Ok => poweroff(ExitCode::Success),
|
||||
KtestResult::Failed => poweroff(ExitCode::Failure),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ use alloc::sync::Arc;
|
|||
use alloc::vec;
|
||||
|
||||
use ostd::arch::cpu::context::UserContext;
|
||||
use ostd::arch::qemu::{exit_qemu, QemuExitCode};
|
||||
use ostd::mm::{
|
||||
CachePolicy, FallibleVmRead, FrameAllocOptions, PageFlags, PageProperty, Vaddr, VmIo, VmSpace,
|
||||
VmWriter, PAGE_SIZE,
|
||||
};
|
||||
use ostd::power::{poweroff, ExitCode};
|
||||
use ostd::prelude::*;
|
||||
use ostd::task::{disable_preempt, Task, TaskOptions};
|
||||
use ostd::user::{ReturnReason, UserMode};
|
||||
|
|
@ -130,7 +130,7 @@ fn handle_syscall(user_context: &mut UserContext, vm_space: &VmSpace) {
|
|||
// Manipulate the user-space CPU registers safely.
|
||||
user_context.set_rax(buf_len);
|
||||
}
|
||||
SYS_EXIT => exit_qemu(QemuExitCode::Success),
|
||||
SYS_EXIT => poweroff(ExitCode::Success),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,3 @@
|
|||
|
||||
pub mod context;
|
||||
pub mod local;
|
||||
pub mod poweroff;
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Provides the implementation of the poweroff functionality.
|
||||
|
||||
use crate::arch::qemu::{exit_qemu, QemuExitCode};
|
||||
|
||||
/// Powers off the system.
|
||||
pub fn poweroff() -> ! {
|
||||
// TODO: Implement the poweroff behavior on a real machine.
|
||||
exit_qemu(QemuExitCode::Success);
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@ mod io;
|
|||
pub(crate) mod iommu;
|
||||
pub(crate) mod irq;
|
||||
pub(crate) mod mm;
|
||||
pub mod qemu;
|
||||
pub(crate) mod serial;
|
||||
pub(crate) mod task;
|
||||
mod timer;
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Providing the ability to exit QEMU and return a value as debug result.
|
||||
|
||||
use crate::arch::{boot::DEVICE_TREE, mm::paddr_to_daddr};
|
||||
|
||||
/// The exit code of QEMU.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum QemuExitCode {
|
||||
/// The code that indicates a successful exit.
|
||||
Success,
|
||||
/// The code that indicates a failed exit.
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// Exits QEMU with the given exit code.
|
||||
// FIXME: Support the transfer of the exit code to QEMU and multiple platforms.
|
||||
pub fn exit_qemu(_exit_code: QemuExitCode) {
|
||||
let (poweroff_addr, poweroff_value) = lookup_poweroff_paddr_value().unwrap();
|
||||
let poweroff_daddr = paddr_to_daddr(poweroff_addr) as *mut u8;
|
||||
|
||||
// SAFETY: It is safe because the poweroff address is acquired from the device tree,
|
||||
// and be mapped in DMW2.
|
||||
unsafe {
|
||||
core::ptr::write_volatile(poweroff_daddr, poweroff_value);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: We should reserve the address region in `io_mem_allocator`.
|
||||
fn lookup_poweroff_paddr_value() -> Option<(usize, u8)> {
|
||||
let device_tree = DEVICE_TREE.get().unwrap();
|
||||
|
||||
let ged = device_tree.find_node("/ged")?;
|
||||
if !ged.compatible()?.all().any(|c| c == "syscon") {
|
||||
return None;
|
||||
}
|
||||
let ged_reg_base_address = ged.reg()?.next()?.starting_address as usize;
|
||||
|
||||
let poweroff = device_tree.find_node("/poweroff").unwrap();
|
||||
if !poweroff.compatible()?.all().any(|c| c == "syscon-poweroff") {
|
||||
return None;
|
||||
}
|
||||
let poweroff_offset = poweroff.property("offset")?.as_usize()?;
|
||||
let poweroff_value = poweroff.property("value")?.as_usize()? as u8;
|
||||
|
||||
Some((ged_reg_base_address + poweroff_offset, poweroff_value))
|
||||
}
|
||||
|
|
@ -5,4 +5,3 @@
|
|||
pub mod context;
|
||||
pub mod extension;
|
||||
pub mod local;
|
||||
pub mod poweroff;
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Provides the implementation of the poweroff functionality.
|
||||
|
||||
use crate::arch::qemu::{exit_qemu, QemuExitCode};
|
||||
|
||||
/// Powers off the system.
|
||||
pub fn poweroff() -> ! {
|
||||
// TODO: Implement the poweroff behavior on a real machine.
|
||||
exit_qemu(QemuExitCode::Success);
|
||||
}
|
||||
|
|
@ -11,7 +11,7 @@ mod io;
|
|||
pub(crate) mod iommu;
|
||||
pub mod irq;
|
||||
pub(crate) mod mm;
|
||||
pub mod qemu;
|
||||
mod power;
|
||||
pub(crate) mod serial;
|
||||
pub(crate) mod task;
|
||||
mod timer;
|
||||
|
|
@ -61,6 +61,8 @@ pub(crate) unsafe fn late_init_on_bsp() {
|
|||
// 1. All the system device memory have been removed from the builder.
|
||||
// 2. RISC-V platforms do not have port I/O.
|
||||
unsafe { crate::io::init(io_mem_builder) };
|
||||
|
||||
power::init();
|
||||
}
|
||||
|
||||
/// Initializes application-processor-specific state.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Power management.
|
||||
|
||||
use crate::power::{inject_poweroff_handler, inject_restart_handler, ExitCode};
|
||||
|
||||
fn try_poweroff(code: ExitCode) {
|
||||
let _ = match code {
|
||||
ExitCode::Success => sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::NoReason),
|
||||
ExitCode::Failure => sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::SystemFailure),
|
||||
};
|
||||
}
|
||||
|
||||
fn try_restart(code: ExitCode) {
|
||||
let _ = match code {
|
||||
ExitCode::Success => sbi_rt::system_reset(sbi_rt::ColdReboot, sbi_rt::NoReason),
|
||||
ExitCode::Failure => sbi_rt::system_reset(sbi_rt::ColdReboot, sbi_rt::SystemFailure),
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) fn init() {
|
||||
inject_poweroff_handler(try_poweroff);
|
||||
inject_restart_handler(try_restart);
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Providing the ability to exit QEMU and return a value as debug result.
|
||||
|
||||
/// The exit code of QEMU.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum QemuExitCode {
|
||||
/// The code that indicates a successful exit.
|
||||
Success,
|
||||
/// The code that indicates a failed exit.
|
||||
Failed,
|
||||
}
|
||||
|
||||
/// Exit QEMU with the given exit code.
|
||||
pub fn exit_qemu(exit_code: QemuExitCode) {
|
||||
log::debug!("exit qemu with exit code {exit_code:?}");
|
||||
let error_code = match exit_code {
|
||||
QemuExitCode::Success => sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::NoReason),
|
||||
QemuExitCode::Failed => sbi_rt::system_reset(sbi_rt::Shutdown, sbi_rt::SystemFailure),
|
||||
};
|
||||
|
||||
log::error!(
|
||||
"SBI system_reset cannot shut down the underlying machine, error code: {:#?}",
|
||||
error_code
|
||||
);
|
||||
}
|
||||
|
|
@ -6,4 +6,3 @@ pub mod context;
|
|||
pub mod cpuid;
|
||||
pub mod extension;
|
||||
pub mod local;
|
||||
pub mod poweroff;
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Provides the implementation of the poweroff functionality.
|
||||
|
||||
use crate::arch::{
|
||||
cpu::cpuid::query_is_running_in_qemu,
|
||||
qemu::{exit_qemu, QemuExitCode},
|
||||
};
|
||||
|
||||
/// Powers off the system.
|
||||
pub fn poweroff() -> ! {
|
||||
if query_is_running_in_qemu() {
|
||||
exit_qemu(QemuExitCode::Success);
|
||||
}
|
||||
|
||||
todo!("Implement ACPI shutdown");
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ pub(crate) mod iommu;
|
|||
pub mod irq;
|
||||
pub mod kernel;
|
||||
pub(crate) mod mm;
|
||||
pub mod qemu;
|
||||
mod power;
|
||||
pub(crate) mod serial;
|
||||
pub(crate) mod task;
|
||||
mod timer;
|
||||
|
|
@ -74,8 +74,6 @@ pub(crate) unsafe fn late_init_on_bsp() {
|
|||
kernel::tsc::init_tsc_freq();
|
||||
timer::init_on_bsp();
|
||||
|
||||
kernel::acpi::init();
|
||||
|
||||
// SAFETY: We're on the BSP and we're ready to boot all APs.
|
||||
unsafe { crate::boot::smp::boot_all_aps() };
|
||||
|
||||
|
|
@ -92,6 +90,9 @@ pub(crate) unsafe fn late_init_on_bsp() {
|
|||
// 2. All the port I/O regions belonging to the system device are defined using the macros.
|
||||
// 3. `MAX_IO_PORT` defined in `crate::arch::io` is the maximum value specified by x86-64.
|
||||
unsafe { crate::io::init(io_mem_builder) };
|
||||
|
||||
kernel::acpi::init();
|
||||
power::init();
|
||||
}
|
||||
|
||||
/// Initializes application-processor-specific state.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Power management.
|
||||
|
||||
mod qemu_isa_debug {
|
||||
//! The isa-debug-exit device in QEMU.
|
||||
//!
|
||||
//! Reference: <https://elixir.bootlin.com/qemu/v10.1.2/source/hw/misc/debugexit.c>
|
||||
|
||||
use spin::Once;
|
||||
|
||||
use crate::{
|
||||
arch::device::io_port::WriteOnlyAccess,
|
||||
io::IoPort,
|
||||
power::{inject_poweroff_handler, ExitCode},
|
||||
};
|
||||
|
||||
// For `qemu-system-x86_64`, the exit code will be `(code << 1) | 1`. So it is not possible to
|
||||
// let QEMU invoke `exit(0)`. We also need to check if the exit code is returned by the kernel,
|
||||
// so we cannot use `0` as `EXIT_SUCCESS` because it may conflict with QEMU's return value `1`,
|
||||
// which indicates that QEMU itself fails.
|
||||
const EXIT_SUCCESS: u32 = 0x10;
|
||||
const EXIT_FAILURE: u32 = 0x20;
|
||||
|
||||
static DEBUG_EXIT_PORT: Once<IoPort<u32, WriteOnlyAccess>> = Once::new();
|
||||
|
||||
fn try_exit_qemu(code: ExitCode) {
|
||||
let value = match code {
|
||||
ExitCode::Success => EXIT_SUCCESS,
|
||||
ExitCode::Failure => EXIT_FAILURE,
|
||||
};
|
||||
|
||||
// If possible, keep this method panic-free because it may be called by the panic handler.
|
||||
if let Some(port) = DEBUG_EXIT_PORT.get() {
|
||||
port.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn init() {
|
||||
const DEBUG_EXIT_PORT_NUM: u16 = 0xF4;
|
||||
|
||||
let debug_exit_port = IoPort::acquire(DEBUG_EXIT_PORT_NUM).unwrap();
|
||||
|
||||
DEBUG_EXIT_PORT.call_once(|| debug_exit_port);
|
||||
inject_poweroff_handler(try_exit_qemu);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn init() {
|
||||
use super::cpu::cpuid;
|
||||
|
||||
if !cpuid::query_is_running_in_qemu() {
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: We assume that the kernel is running in QEMU with the following QEMU command line
|
||||
// arguments that specify the isa-debug-exit device:
|
||||
// `-device isa-debug-exit,iobase=0xf4,iosize=0x04`.
|
||||
log::info!("QEMU hypervisor detected, assuming that the isa-debug-exit device exists");
|
||||
|
||||
qemu_isa_debug::init();
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Provides the ability to exit QEMU and return a value as debug result.
|
||||
|
||||
/// The exit code of x86 QEMU isa debug device.
|
||||
///
|
||||
/// In `qemu-system-x86_64` the exit code will be `(code << 1) | 1`. So you
|
||||
/// could never let QEMU invoke `exit(0)`. We also need to check if the exit
|
||||
/// code is returned by the kernel, so we couldn't use 0 as exit_success
|
||||
/// because this may conflict with QEMU return value 1, which indicates that
|
||||
/// QEMU itself fails.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[repr(u32)]
|
||||
pub enum QemuExitCode {
|
||||
/// The code that indicates a successful exit.
|
||||
Success = 0x10,
|
||||
/// The code that indicates a failed exit.
|
||||
Failed = 0x20,
|
||||
}
|
||||
|
||||
/// Exits QEMU with the given exit code.
|
||||
///
|
||||
/// This function assumes that the kernel is run in QEMU with the following
|
||||
/// QEMU command line arguments that specifies the ISA debug exit device:
|
||||
/// `-device isa-debug-exit,iobase=0xf4,iosize=0x04`.
|
||||
pub fn exit_qemu(exit_code: QemuExitCode) {
|
||||
#[cfg(feature = "coverage")]
|
||||
crate::coverage::on_qemu_exit();
|
||||
|
||||
use x86_64::instructions::port::Port;
|
||||
let mut port = Port::new(0xf4);
|
||||
|
||||
// SAFETY: The write to the ISA debug exit port is safe and `0xf4` should
|
||||
// be the port number.
|
||||
unsafe {
|
||||
port.write(exit_code as u32);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,18 +8,21 @@
|
|||
use alloc::vec::Vec;
|
||||
use core::mem::ManuallyDrop;
|
||||
|
||||
use spin::Once;
|
||||
|
||||
use crate::sync::SpinLock;
|
||||
|
||||
/// A hook to be invoked on QEMU exit for dumping the code coverage data.
|
||||
pub(crate) fn on_qemu_exit() {
|
||||
let mut coverage = ManuallyDrop::new(Vec::new());
|
||||
static COV_LOCK: SpinLock<()> = SpinLock::new(());
|
||||
let _guard = COV_LOCK.disable_irq().lock();
|
||||
// SAFETY: The above lock ensures that this function is not called
|
||||
// concurrently by multiple threads.
|
||||
unsafe {
|
||||
minicov::capture_coverage(&mut *coverage).unwrap();
|
||||
}
|
||||
/// A hook that is invoked when the system exits to dump the code coverage data.
|
||||
pub(crate) fn on_system_exit() {
|
||||
static COVERAGE_DATA: Once<Vec<u8>> = Once::new();
|
||||
|
||||
let coverage = COVERAGE_DATA.call_once(|| {
|
||||
let mut coverage = Vec::new();
|
||||
// SAFETY: `call_once` guarantees that this function will not be called concurrently by
|
||||
// multiple threads.
|
||||
unsafe { minicov::capture_coverage(&mut coverage).unwrap() };
|
||||
coverage
|
||||
});
|
||||
|
||||
crate::early_println!("#### Coverage: {:p} {}", coverage.as_ptr(), coverage.len());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ pub mod irq;
|
|||
pub mod logger;
|
||||
pub mod mm;
|
||||
pub mod panic;
|
||||
pub mod power;
|
||||
pub mod prelude;
|
||||
pub mod smp;
|
||||
pub mod sync;
|
||||
|
|
|
|||
|
|
@ -2,12 +2,7 @@
|
|||
|
||||
//! Panic support.
|
||||
|
||||
use core::sync::atomic::Ordering;
|
||||
|
||||
use crate::{
|
||||
arch::qemu::{exit_qemu, QemuExitCode},
|
||||
early_println,
|
||||
};
|
||||
use crate::early_println;
|
||||
|
||||
extern crate cfg_if;
|
||||
extern crate gimli;
|
||||
|
|
@ -38,18 +33,14 @@ pub fn __ostd_panic_handler(info: &core::panic::PanicInfo) -> ! {
|
|||
abort();
|
||||
}
|
||||
|
||||
/// Aborts the QEMU
|
||||
/// Aborts the system.
|
||||
///
|
||||
/// This function will first attempt to power off the system. If that fails, it will halt all CPUs.
|
||||
pub fn abort() -> ! {
|
||||
use crate::{arch::irq::disable_local_and_halt, cpu::CpuSet, smp::inter_processor_call};
|
||||
|
||||
exit_qemu(QemuExitCode::Failed);
|
||||
|
||||
// TODO: `inter_processor_call` may panic again (e.g., if IPIs have not been initialized or if
|
||||
// there is an out-of-memory error). We should find a way to make it panic-free.
|
||||
if !crate::IN_BOOTSTRAP_CONTEXT.load(Ordering::Relaxed) {
|
||||
inter_processor_call(&CpuSet::new_full(), || disable_local_and_halt());
|
||||
}
|
||||
disable_local_and_halt();
|
||||
// TODO: The main purpose of powering off here is to allow QEMU to exit. Otherwise, the CI may
|
||||
// freeze after panicking. However, this is unnecessary and may prevent debugging on a real
|
||||
// machine (i.e., the message will disappear afterward).
|
||||
crate::power::poweroff(crate::power::ExitCode::Failure);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "loongarch64"))]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Power management.
|
||||
|
||||
use core::sync::atomic::Ordering;
|
||||
|
||||
use spin::Once;
|
||||
|
||||
use crate::{arch::irq::disable_local_and_halt, cpu::CpuSet, smp::inter_processor_call};
|
||||
|
||||
/// An exit code that denotes the reason for restarting or powering off.
|
||||
///
|
||||
/// Whether or not the code is used depends on the hardware. In a virtualization environment, it
|
||||
/// can be passed to the hypervisor (e.g., as QEMU's exit code). In a bare-metal environment, it
|
||||
/// can be passed to the firmware. In either case, the code may be silently ignored if reporting
|
||||
/// the code is not supported.
|
||||
pub enum ExitCode {
|
||||
/// The code that indicates a successful exit.
|
||||
Success,
|
||||
/// The code that indicates a failed exit.
|
||||
Failure,
|
||||
}
|
||||
|
||||
static RESTART_HANDLER: Once<fn(ExitCode)> = Once::new();
|
||||
|
||||
/// Injects a handler that can restart the system.
|
||||
///
|
||||
/// The function may be called only once; subsequent calls take no effect.
|
||||
///
|
||||
/// Note that, depending on the specific architecture, OSTD may already have a built-in handler. If
|
||||
/// so, calling this function outside of OSTD will never take effect. Currently, it happens in
|
||||
/// - x86_64: Never;
|
||||
/// - riscv64: Always;
|
||||
/// - loongarch64: Never.
|
||||
pub fn inject_restart_handler(handler: fn(ExitCode)) {
|
||||
RESTART_HANDLER.call_once(|| handler);
|
||||
}
|
||||
|
||||
/// Restarts the system.
|
||||
///
|
||||
/// This function will not return. If a restart handler is missing or not working, it will halt all
|
||||
/// CPUs on the machine.
|
||||
pub fn restart(code: ExitCode) -> ! {
|
||||
if let Some(handler) = RESTART_HANDLER.get() {
|
||||
(handler)(code);
|
||||
log::error!("Failed to restart the system because the restart handler fails");
|
||||
} else {
|
||||
log::error!("Failed to restart the system because a restart handler is missing");
|
||||
}
|
||||
|
||||
machine_halt();
|
||||
}
|
||||
|
||||
static POWEROFF_HANDLER: Once<fn(ExitCode)> = Once::new();
|
||||
|
||||
/// Injects a handler that can power off the system.
|
||||
///
|
||||
/// The function may be called only once; subsequent calls take no effect.
|
||||
///
|
||||
/// Note that, depending on the specific architecture, OSTD may already have a built-in handler. If
|
||||
/// so, calling this function outside of OSTD will never take effect. Currently, it happens in
|
||||
/// - x86_64: If a QEMU hypervisor is detected;
|
||||
/// - riscv64: Always;
|
||||
/// - loongarch64: Never.
|
||||
pub fn inject_poweroff_handler(handler: fn(ExitCode)) {
|
||||
POWEROFF_HANDLER.call_once(|| handler);
|
||||
}
|
||||
|
||||
/// Powers off the system.
|
||||
///
|
||||
/// This function will not return. If a poweroff handler is missing or not working, it will halt
|
||||
/// all CPUs on the machine.
|
||||
pub fn poweroff(code: ExitCode) -> ! {
|
||||
#[cfg(feature = "coverage")]
|
||||
crate::coverage::on_system_exit();
|
||||
|
||||
if let Some(handler) = POWEROFF_HANDLER.get() {
|
||||
(handler)(code);
|
||||
log::error!("Failed to power off the system because the poweroff handler fails");
|
||||
} else {
|
||||
log::error!("Failed to power off the system because a poweroff handler is missing");
|
||||
}
|
||||
|
||||
machine_halt();
|
||||
}
|
||||
|
||||
fn machine_halt() -> ! {
|
||||
log::error!("Halting the machine...");
|
||||
|
||||
// TODO: `inter_processor_call` may panic again (e.g., if IPIs have not been initialized or if
|
||||
// there is an out-of-memory error). We should find a way to make it panic-free.
|
||||
if !crate::IN_BOOTSTRAP_CONTEXT.load(Ordering::Relaxed) {
|
||||
inter_processor_call(&CpuSet::new_full(), || disable_local_and_halt());
|
||||
}
|
||||
disable_local_and_halt();
|
||||
}
|
||||
Loading…
Reference in New Issue