use acpi::sdt::fadt::Fadt;
use log::{error, info, warn};
#[derive(Debug)]
pub enum ShutdownError {
AcpiNotAvailable,
NoFadt,
InvalidPm1Address,
}
impl core::fmt::Display for ShutdownError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ShutdownError::AcpiNotAvailable => write!(f, "ACPI not available"),
ShutdownError::NoFadt => write!(f, "FADT table not found"),
ShutdownError::InvalidPm1Address => write!(f, "Invalid PM1 register address"),
}
}
}
#[allow(dead_code)]
pub fn shutdown() -> ! {
info!("Attempting ACPI shutdown...");
match shutdown_internal() {
Ok(_) => {
info!("ACPI shutdown initiated, waiting for power off...");
loop {
unsafe { core::arch::asm!("idle 0") };
}
}
Err(e) => {
error!("ACPI shutdown failed: {}", e);
warn!("Falling back to idle loop");
loop {
unsafe { core::arch::asm!("idle 0") };
}
}
}
}
fn shutdown_internal() -> Result<(), ShutdownError> {
let tables = crate::acpi::tables().map_err(|_| ShutdownError::AcpiNotAvailable)?;
let fadt_mapping = tables
.find_tables::<Fadt>()
.next()
.ok_or(ShutdownError::NoFadt)?;
let fadt = &*fadt_mapping;
let pm1a_legacy = fadt.pm1a_control_block;
let pm1b_legacy = fadt.pm1b_control_block;
info!(
"FADT: pm1a_cnt_blk={:#x}, pm1b_cnt_blk={:#x}",
pm1a_legacy, pm1b_legacy
);
let pm1a_generic = fadt
.pm1a_control_block()
.map_err(|_| ShutdownError::InvalidPm1Address)?;
let pm1a_cnt = pm1a_generic.address as usize;
let pm1a_addr = pm1a_generic.address;
let pm1a_space = format!("{:?}", pm1a_generic.address_space);
let pm1a_width = pm1a_generic.bit_width;
info!(
"PM1a control block: address={:#x}, space={}, bit_width={}",
pm1a_addr, pm1a_space, pm1a_width
);
let pm1b_cnt = match fadt.pm1b_control_block() {
Ok(Some(generic)) => {
let addr = generic.address;
let space = format!("{:?}", generic.address_space);
info!("PM1b control block: address={:#x}, space={}", addr, space);
Some(addr as usize)
}
_ => None,
};
info!("Final: PM1a_CNT={:#x}, PM1b_CNT={:#x?}", pm1a_cnt, pm1b_cnt);
if pm1a_cnt == 0 {
warn!("PM1 control block address is 0, trying QEMU-specific shutdown");
return qemu_shutdown();
}
let (s5_typa, s5_typb) = get_s5_sleep_type(fadt)?;
info!("S5 sleep type: a={:#x}, b={:#x}", s5_typa, s5_typb);
const SLP_EN: u16 = 0x2000;
let pm1a_value = s5_typa | SLP_EN;
info!("Writing PM1a_CNT: {:#x}", pm1a_value);
unsafe {
let virt_addr = crate::mem::phys_to_virt(pm1a_cnt) as *mut u16;
core::ptr::write_volatile(virt_addr, pm1a_value);
}
if let Some(pm1b_addr) = pm1b_cnt {
let pm1b_value = s5_typb | SLP_EN;
info!("Writing PM1b_CNT: {:#x}", pm1b_value);
unsafe {
let virt_addr = crate::mem::phys_to_virt(pm1b_addr) as *mut u16;
core::ptr::write_volatile(virt_addr, pm1b_value);
}
}
Ok(())
}
fn qemu_shutdown() -> Result<(), ShutdownError> {
error!("QEMU shutdown not implemented for LoongArch64 yet");
Err(ShutdownError::InvalidPm1Address)
}
fn get_s5_sleep_type(_fadt: &Fadt) -> Result<(u16, u16), ShutdownError> {
const S5_TYP: u16 = 0x7 << 10;
Ok((S5_TYP, S5_TYP))
}