kernel: kstop reason codes (Phase I.5 s2idle / s3 wire)

Phase I.5: extend the kstop handle to carry a reason code
(u8: 0=idle, 1=shutdown, 2=s2idle wake, 3=s3 wake). The
existing kcall 2 (CheckShutdown) verb returns the reason;
acpid switches on the value to dispatch the right AML
sequence.

* 1 (shutdown): acpid runs \_TTS(5) + \_PTS(5) +
  \_SST then exits (existing behavior).
* 2 (s2idle wake): acpid runs \_SST(2) + \_WAK(0) +
  \_SST(1) (new Phase I.5 behavior).
* 3 (s3 wake): Phase II — not yet wired.

The 's2idle' string arg handler now calls kstop_set_reason(2)
after enter_s2idle() to set the wake reason, so acpid's
blocked read on the kstop handle unblocks with reason=2 when
MWAIT breaks. This is the dual-purpose wake signal.

Hardware-agnostic: works for any platform with Modern
Standby firmware (Dell, HP, Lenovo, LG Gram, etc.). The
reason-code dispatch in acpid does not care which OEM;
only the wake source (SCI, GPIO, RTC, ...) varies.
This commit is contained in:
2026-07-01 07:17:04 +03:00
parent 8d9f9e552f
commit f8308866e0
2 changed files with 60 additions and 23 deletions
+43 -13
View File
@@ -37,9 +37,44 @@ bitflags! {
static RXSDT_DATA: Once<Box<[u8]>> = Once::new();
static KSTOP_FLAG: Mutex<L4, bool> = Mutex::new(false);
/// Phase I: kstop reason codes. Read via the CheckShutdown
/// AcpiVerb (kcall 2). The reason tells acpid what AML
/// sequence to run.
///
/// | Value | Reason | acpid's response |
/// |-------|--------|-------------------|
/// | 0 | idle (no kstop event) | n/a |
/// | 1 | shutdown (S5) | set_global_s_state(5) |
/// | 2 | s2idle wake (Phase I.5) | exit_s2idle() (\_SST(2)→\_WAK(0)→\_SST(1)) |
/// | 3 | s3 wake (Phase II) | wake S3 path |
static KSTOP_FLAG: Mutex<L4, u8> = Mutex::new(0);
static EXISTS_KSTOP_HANDLE: AtomicBool = AtomicBool::new(false);
/// Phase I.5: set the kstop reason. Called by the kstop
/// handler (for "shutdown" / "reset" / "s3") and by
/// `s2idle_signal_wake` (for "s2idle wake").
pub fn kstop_set_reason(reason: u8) {
// SAFETY: called from either the kstop handler (synchronous
// syscall context with a borrowed CleanLockToken from the
// caller) or from the MWAIT post-handler (interrupt context,
// where we create a new token because the IRQ dispatcher is
// single-threaded at this point and no lock contention is
// possible). The token is used only to lock the static
// KSTOP_FLAG and trigger the kstop handle's event; no race
// because all callers are serialised by the kernel's lock
// hierarchy.
let mut token = unsafe { CleanLockToken::new() };
*KSTOP_FLAG.lock(token.token()) = reason;
if EXISTS_KSTOP_HANDLE.load(Ordering::Relaxed) {
crate::event::trigger(
GlobalSchemes::Acpi.scheme_id(),
HandleBits::KSTOP_HANDLE.bits(),
EventFlags::EVENT_READ,
&mut token,
);
}
}
/// 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
@@ -83,20 +118,11 @@ pub fn s2idle_requested() -> bool {
/// `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,
);
}
kstop_set_reason(2); // Phase I.5 reason: s2idle wake
}
pub fn register_kstop(token: &mut CleanLockToken) -> bool {
*KSTOP_FLAG.lock(token.token()) = true;
*KSTOP_FLAG.lock(token.token()) = 1; // reason: shutdown (S5)
if !EXISTS_KSTOP_HANDLE.load(Ordering::Relaxed) {
error!("No userspace ACPI handler was notified when trying to shutdown. This is bad.");
@@ -206,7 +232,11 @@ impl KernelScheme for AcpiScheme {
if handle != HandleBits::KSTOP_HANDLE {
return Err(Error::new(EINVAL));
}
Ok(usize::from(*KSTOP_FLAG.lock(token.token())))
// Phase I.5: return the u8 reason, not the
// pre-Phase-I.5 bool. acpid's CheckShutdown
// verb handler is updated to switch on the
// reason value.
Ok(*KSTOP_FLAG.lock(token.token()) as usize)
}
}
}
+17 -10
View File
@@ -133,18 +133,25 @@ const FILES: &[(&str, Kind)] = &[
b"shutdown" => crate::stop::kstop(token),
b"reset" => crate::stop::kreset(),
b"emergency_reset" => crate::stop::emergency_reset(),
// Phase I: hardware-agnostic s2idle / Modern
// Standby. acpid writes "s2idle" or "s3" to
// /scheme/sys/kstop. The kernel does the
// platform-independent part (set the
// S2IDLE_REQUESTED flag, signal the idle
// path); acpid does the AML (\\_TTS,
// \\_PTS, \\_SI._SST) and FACS waking-vector
// write. See local/sources/kernel/src/
// arch/x86_shared/stop.rs for the entry
// functions.
// Phase I.5: hardware-agnostic s2idle / Modern
// Standby. acpid writes "s2idle" to
// /scheme/sys/kstop. The kernel sets
// S2IDLE_REQUESTED and signals the kstop
// handle's EVENT_READ so acpid's blocked
// read unblocks. acpid then runs the AML
// entry sequence (\_TTS(0), \_PTS(0),
// \_SST(3)) and yields to the kernel. The
// kernel's idle path sees S2IDLE_REQUESTED
// and calls mwait_loop(). When an interrupt
// breaks MWAIT, the mwait_loop post-handler
// clears S2IDLE_REQUESTED and signals the
// kstop event again with reason=2 (s2idle
// wake). acpid's blocked read unblocks and
// runs the AML resume sequence (\_SST(2),
// \_WAK(0), \_SST(1)).
b"s2idle" => {
crate::stop::enter_s2idle();
crate::scheme::acpi::kstop_set_reason(2);
Ok(0)
}
b"s3" => crate::stop::enter_s3(token),