acpid: _DSW/_PSW wake arming + sleep state discovery + GPE wake re-arm

- WakeRegistry::arm_wake_devices(): evaluates _DSW(1,0,Sx) per wake
  device (falls back to _PSW(1)), enables wake GPEs in the GPE block.
  Wired into enter_s2idle() — wake devices are now armed before
  the kernel MWAIT loop. Ported from Linux acpi_enable_wakeup_devices.

- WakeRegistry::disarm_wake_devices(): evaluates _DSW(0,0,0) per
  wake device (falls back to _PSW(0)), clears wake GPE status bits.
  Wired into exit_s2idle() — wake devices are disarmed on resume.
  Ported from Linux acpi_disable_wakeup_devices.

- SleepStates::discover(): enumerates _S0 through _S5 to find
  available sleep states. s3_supported() and s2idle_only() helpers
  identify the platform's sleep capability profile.

- AcpiContext.wake_registry: new field storing the populated
  WakeRegistry for use by enter_s2idle/exit_s2idle.
This commit is contained in:
Red Bear OS
2026-07-22 15:41:51 +09:00
parent 9c571e2942
commit 7071c4529e
3 changed files with 107 additions and 20 deletions
+16 -20
View File
@@ -437,6 +437,8 @@ pub struct AcpiContext {
/// ACPI fan device namespace paths (PNP0C0B, ACPI 4.0 fan extensions:
/// `_FST` for status, `_FSL` for speed control).
pub fan_devices: RwLock<Vec<String>>,
pub wake_registry: RwLock<Option<crate::wake::WakeRegistry>>,
}
#[derive(Clone, Debug, Default)]
@@ -570,6 +572,7 @@ impl AcpiContext {
power_button_events: RwLock::new(0),
sleep_button_events: RwLock::new(0),
fan_devices: RwLock::new(Vec::new()),
wake_registry: RwLock::new(None),
sdt_order: RwLock::new(Vec::new()),
dmi: None,
@@ -675,20 +678,15 @@ impl AcpiContext {
pub fn enter_s2idle(&self) {
log::info!("entering s2idle (Modern Standby) preparation");
// Step 1: _TTS(0) — Transition To S0 "working" state. Linux
// calls this at the start of every transition, including s2idle.
// We use the `transition_to_s_state` helper but note: this
// is technically transitioning to "S0" which is the *active*
// state. The semantic here is "prepare to leave S0 for sleep".
// Linux's acpi_s2idle_prepare does not call _TTS directly;
// it's called by the s2idle wake path on resume. We follow
// the resume path here: when acpid later calls wake_from_s_state
// it will execute _TTS(0) again. We log but skip the _TTS call
// here to avoid double-invocation.
log::debug!("s2idle prepare: skipping _TTS(0) — handled by wake path");
// Step 5: set internal flag. Future Phase I/II work will add
// an `is_s2idle()` accessor and a `wake_pending()` poll.
// Arm wake devices per Linux acpi_enable_wakeup_devices.
if let Some(registry) = self.wake_registry.read().as_ref() {
if let Some(gpe_blocks) = self.gpe.read().as_ref() {
registry.arm_wake_devices(self, gpe_blocks);
}
}
log::info!("s2idle preparation complete; ready for kernel MWAIT");
}
@@ -705,15 +703,13 @@ impl AcpiContext {
pub fn exit_s2idle(&self) {
log::info!("exiting s2idle (Modern Standby) resume");
// Steps 1-5: kernel-side work. The acpid main loop has
// already received the SCI IRQ by the time this is called.
// Kernel GPE re-enable happens in the kernel's IRQ handler
// chain. acpid's job is the AML sequence.
// Disarm wake devices per Linux acpi_disable_wakeup_devices.
if let Some(registry) = self.wake_registry.read().as_ref() {
if let Some(gpe_blocks) = self.gpe.read().as_ref() {
registry.disarm_wake_devices(self, gpe_blocks);
}
}
// Step 6: full Linux wake sequence for S0.
// S0 state code is 0 (the value passed to _WAK is the state
// the system is *waking from*, which for s2idle is 0 — the
// system never left S0).
let result = self.wake_from_s_state(0);
if let Err(e) = result {
log::warn!("s2idle exit: _WAK(0) failed: {:?}, continuing", e);
+1
View File
@@ -116,6 +116,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
wake_registry.wake_gpes()
);
}
*acpi_context.wake_registry.write() = Some(wake_registry);
// TODO: I/O permission bitmap?
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
+90
View File
@@ -3,6 +3,7 @@ use std::sync::RwLock;
use log::{debug, info, warn};
use crate::acpi::AcpiContext;
use crate::gpe::GpeBlocks;
const LPIT_TYPE_NATIVE_CSTATE: u32 = 0x00;
@@ -215,6 +216,52 @@ impl WakeRegistry {
pub fn device_count(&self) -> usize {
self.devices.read().unwrap().len()
}
pub fn arm_wake_devices(&self, acpi: &AcpiContext, gpe_blocks: &GpeBlocks) {
let devices = self.devices.read().unwrap();
for device in devices.iter() {
let dsw_ok = acpi
.evaluate_acpi_method(&device.acpi_path, "_DSW", &[1, 0, device.sleep_state as u64])
.is_ok();
if !dsw_ok {
let _ = acpi.evaluate_acpi_method(&device.acpi_path, "_PSW", &[1]);
}
if let Some(gpe) = device.gpe_number {
if gpe <= 255 {
if gpe_blocks.enable_gpe(gpe as u8) {
debug!("acpid: wake GPE {} enabled for {}", gpe, device.acpi_path);
} else {
debug!("acpid: wake GPE {} not in any GPE block", gpe);
}
}
}
}
info!(
"acpid: {} wake device(s) armed for s2idle",
devices.len()
);
}
pub fn disarm_wake_devices(&self, acpi: &AcpiContext, gpe_blocks: &GpeBlocks) {
let devices = self.devices.read().unwrap();
for device in devices.iter() {
let dsw_ok = acpi
.evaluate_acpi_method(&device.acpi_path, "_DSW", &[0, 0, 0])
.is_ok();
if !dsw_ok {
let _ = acpi.evaluate_acpi_method(&device.acpi_path, "_PSW", &[0]);
}
if let Some(gpe) = device.gpe_number {
if gpe <= 255 {
gpe_blocks.clear_gpe(gpe as u8);
}
}
}
debug!(
"acpid: {} wake device(s) disarmed after s2idle wake",
devices.len()
);
}
}
pub fn init_lpit(acpi: &AcpiContext) -> Option<LpitInfo> {
@@ -229,6 +276,49 @@ pub fn init_lpit(acpi: &AcpiContext) -> Option<LpitInfo> {
Some(info)
}
#[derive(Clone, Debug, Default)]
pub struct SleepStates {
pub s0: bool,
pub s1: bool,
pub s2: bool,
pub s3: bool,
pub s4: bool,
pub s5: bool,
}
impl SleepStates {
pub fn discover(acpi: &AcpiContext) -> Self {
let mut states = Self::default();
for (idx, name) in ["_S0", "_S1", "_S2", "_S3", "_S4", "_S5"].iter().enumerate() {
let path = format!("\\{}", name);
if acpi.evaluate_acpi_method(&path, "", &[]).is_ok() {
match idx {
0 => states.s0 = true,
1 => states.s1 = true,
2 => states.s2 = true,
3 => states.s3 = true,
4 => states.s4 = true,
5 => states.s5 = true,
_ => {}
}
}
}
info!(
"acpid: sleep states discovered: S0={} S1={} S2={} S3={} S4={} S5={}",
states.s0, states.s1, states.s2, states.s3, states.s4, states.s5
);
states
}
pub fn s3_supported(&self) -> bool {
self.s3
}
pub fn s2idle_only(&self) -> bool {
self.s0 && !self.s3
}
}
#[cfg(test)]
mod tests {
use super::*;