use alloc::boxed::Box; use core::sync::atomic::{AtomicBool, Ordering}; use crate::sync::ordered::{Mutex, L4}; use spin::Once; use syscall::data::GlobalSchemes; use crate::{ acpi::{RxsdtEnum, RXSDT_ENUM}, context::file::InternalFlags, scheme::{SchemeExt, StrOrBytes}, sync::CleanLockToken, }; use crate::syscall::{ error::{Error, Result, EACCES, EBADFD, EINVAL, ENOENT}, flag::{AcpiVerb, CallFlags, EventFlags}, usercopy::UserSliceRw, }; use super::{CallerCtx, KernelScheme, OpenResult}; /// A scheme used to access the RSDT or XSDT, and listen for shutdown, which is needed for e.g. `acpid` to function. pub struct AcpiScheme; bitflags! { #[derive(PartialEq)] struct HandleBits: usize { const CAN_READ_RXSDT = 1; const CAN_REGISTER_KSTOP = 2; // mutually exclusive with the other flags const KSTOP_HANDLE = 4; } } static RXSDT_DATA: Once> = Once::new(); static KSTOP_FLAG: Mutex = Mutex::new(false); static EXISTS_KSTOP_HANDLE: AtomicBool = AtomicBool::new(false); /// Phase I: s2idle (Modern Standby / S0ix) coordination flag. /// Set by `s2idle_request_set` (called from the kstop handler /// when acpid writes "s2idle" to /scheme/sys/kstop). Read by /// the kernel's idle path which calls `mwait_loop()` while /// the flag is set. Cleared by `s2idle_request_clear` when an /// SCI breaks the MWAIT, signaling the idle path to stop /// calling `mwait_loop()`. /// /// Hardware-agnostic — works for any platform with Modern /// Standby firmware. Mirrors Linux 7.1 /// `s2idle_state == S2IDLE_STATE_ENTER` in /// `kernel/power/suspend.c:91`. static S2IDLE_REQUESTED: AtomicBool = AtomicBool::new(false); /// Set by the kstop handler when acpid requests s2idle entry. /// Idempotent. pub fn s2idle_request_set() { S2IDLE_REQUESTED.store(true, Ordering::Release); } /// Clear by the interrupt handler when an SCI breaks the MWAIT, /// or by the s2idle wake path. Idempotent. pub fn s2idle_request_clear() { S2IDLE_REQUESTED.store(false, Ordering::Release); } /// Read by the kernel's idle path. Returns true if acpid has /// requested s2idle entry and the kernel has not yet broken /// out of MWAIT. pub fn s2idle_requested() -> bool { S2IDLE_REQUESTED.load(Ordering::Acquire) } /// Phase I: signal acpid that s2idle MWAIT was broken by an /// interrupt. Called from `mwait_loop` after MWAIT returns. /// Triggers the kstop handle's EVENT_READ so acpid's main loop /// wakes and runs the \_SST(2) → \_WAK(0) → \_SST(1) AML /// sequence on resume. /// /// Mirrors Linux 7.1 `acpi_s2idle_wake` in /// `drivers/acpi/sleep.c:758` — the kernel clears /// s2idle_state and signals the userspace ACPI driver. pub fn s2idle_signal_wake() { let mut token = CleanLockToken::new(); *KSTOP_FLAG.lock(token.token()) = true; if EXISTS_KSTOP_HANDLE.load(Ordering::Relaxed) { crate::event::trigger( GlobalSchemes::Acpi.scheme_id(), HandleBits::KSTOP_HANDLE.bits(), EventFlags::EVENT_READ, &mut token, ); } } pub fn register_kstop(token: &mut CleanLockToken) -> bool { *KSTOP_FLAG.lock(token.token()) = true; if !EXISTS_KSTOP_HANDLE.load(Ordering::Relaxed) { error!("No userspace ACPI handler was notified when trying to shutdown. This is bad."); // Let the kernel shutdown without ACPI. return false; } crate::event::trigger( GlobalSchemes::Acpi.scheme_id(), HandleBits::KSTOP_HANDLE.bits(), EventFlags::EVENT_READ, token, ); // TODO: Context switch directly to the waiting context, to avoid annoying timeouts. true } impl AcpiScheme { pub fn init() { // NOTE: This __must__ be called from the main kernel context, while initializing all // schemes. If it is called by any other context, then all ACPI data will probably not even // be mapped. let mut data_init = false; RXSDT_DATA.call_once(|| { data_init = true; let table = match RXSDT_ENUM.get() { Some(RxsdtEnum::Rsdt(rsdt)) => rsdt.as_slice(), Some(RxsdtEnum::Xsdt(xsdt)) => xsdt.as_slice(), None => { warn!("expected RXSDT_ENUM to be initialized before AcpiScheme, is ACPI available?"); &[] } }; Box::from(table) }); if !data_init { error!("AcpiScheme::init called multiple times"); } } } impl KernelScheme for AcpiScheme { fn scheme_root(&self, _token: &mut CleanLockToken) -> Result { Ok((HandleBits::CAN_READ_RXSDT | HandleBits::CAN_REGISTER_KSTOP).bits()) } fn kopenat( &self, id: usize, path: StrOrBytes, _flags: usize, _fcntl_flags: u32, caller: CallerCtx, _token: &mut CleanLockToken, ) -> Result { let bits = HandleBits::from_bits_retain(id); let new_bits = match path.as_bytes() { b"" | b"/" => bits, b"kstop" | b"/kstop" => { // TODO: can the uid check be removed? if caller.uid != 0 || !bits.contains(HandleBits::CAN_REGISTER_KSTOP) { return Err(Error::new(EACCES)); } EXISTS_KSTOP_HANDLE.store(true, Ordering::Relaxed); HandleBits::KSTOP_HANDLE } _ => return Err(Error::new(ENOENT)), }; Ok(OpenResult::SchemeLocal( new_bits.bits(), InternalFlags::empty(), )) } fn kcall( &self, fds: &[usize], payload: UserSliceRw, flags: CallFlags, metadata: &[u64], token: &mut CleanLockToken, ) -> Result { let [handle] = <&[usize; 1]>::try_from(fds) .map_err(|_| Error::new(EINVAL))? .map(HandleBits::from_bits_retain); let verb = metadata .get(0) .copied() .and_then(AcpiVerb::try_from_raw) .ok_or(Error::new(EINVAL))?; match verb { AcpiVerb::ReadRxsdt => { if !handle.contains(HandleBits::CAN_READ_RXSDT) || !flags.contains(CallFlags::READ) { return Err(Error::new(EINVAL)); } let src = RXSDT_DATA.get().ok_or(Error::new(EBADFD))?; payload.copy_common_bytes_from_slice(src)?; Ok(src.len()) } AcpiVerb::CheckShutdown => { if handle != HandleBits::KSTOP_HANDLE { return Err(Error::new(EINVAL)); } Ok(usize::from(*KSTOP_FLAG.lock(token.token()))) } } } }