kernel: s2idle MWAIT wake signal (Phase I.5)
Phase I.5: complete the acpid <-> kernel s2idle wire. After
MWAIT returns from an interrupt (typically an SCI from
acpid), the kernel now:
1. Clears S2IDLE_REQUESTED (via s2idle_request_clear)
2. Sets KSTOP_FLAG and triggers EVENT_READ on the kstop
handle (via s2idle_signal_wake)
This is the kernel-side analog of Linux 7.1
`acpi_s2idle_wake` in `drivers/acpi/sleep.c:758`. The
existing irq_trigger in generic_irq has already routed the
SCI to acpid's listener (which opened /scheme/irq/{sci}
earlier in the boot sequence), so the AML interpretation
is done by acpid asynchronously.
The s2idle flow now:
1. acpid: enter_s2idle() (\_TTS(0), \_PTS(0), \_SST(3))
2. acpid: write 's2idle\n' to /scheme/sys/kstop
-> kernel sets S2IDLE_REQUESTED, returns
3. Kernel idle path: mwait_loop() at deepest C-state
4. SCI breaks MWAIT (any interrupt, not just SCI)
5. Kernel mwait_loop post-handler (this commit):
- s2idle_request_clear()
- s2idle_signal_wake() -> KSTOP_FLAG set, EVENT_READ
6. acpid main loop: wakes from kstop handle read
7. acpid: exit_s2idle() (\_SST(2), \_WAK(0), \_SST(1))
The KSTOP_FLAG set in step 5 also serves as a 'reason'
indicator — acpid's CheckShutdown verb (kcall 2) returns
the flag, so acpid can distinguish a kstop-shutdown event
from a kstop-s2idle-wake event by polling CheckShutdown
after waking.
Hardware-agnostic: the same flow works for any platform
with Modern Standby firmware (Dell, HP, Lenovo, LG Gram,
etc.). The s2idle is the universal mechanism for low-power
idle; only the wake source (SCI, GPIO, RTC, ...) varies
per OEM.
This commit is contained in:
@@ -107,6 +107,19 @@ pub unsafe fn mwait_loop(eax_hint: u32, ecx_hint: u32) {
|
||||
);
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
let _ = (eax_hint, ecx_hint);
|
||||
|
||||
// MWAIT returns when an interrupt fires (ecx=0 means
|
||||
// "break on any interrupt"). The interrupt dispatcher
|
||||
// has already run and returned by the time we get here.
|
||||
// If the CPU was in s2idle (S2IDLE_REQUESTED was set by
|
||||
// the kstop handler), we now know an SCI or other wake
|
||||
// event has occurred. Clear the s2idle flag and trigger
|
||||
// the kstop handle's EVENT_READ so acpid's main loop
|
||||
// wakes up and runs the _WAK AML sequence.
|
||||
if crate::scheme::acpi::s2idle_requested() {
|
||||
crate::scheme::acpi::s2idle_request_clear();
|
||||
crate::scheme::acpi::s2idle_signal_wake();
|
||||
}
|
||||
}
|
||||
|
||||
/// Probe MWAIT support and enter the deepest available C-state
|
||||
|
||||
@@ -73,6 +73,28 @@ 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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user