From 7071c4529e75849afb9fe5ffe0cf46e11852e90a Mon Sep 17 00:00:00 2001 From: Red Bear OS Date: Wed, 22 Jul 2026 15:41:51 +0900 Subject: [PATCH] acpid: _DSW/_PSW wake arming + sleep state discovery + GPE wake re-arm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- drivers/acpid/src/acpi.rs | 36 +++++++--------- drivers/acpid/src/main.rs | 1 + drivers/acpid/src/wake.rs | 90 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 107 insertions(+), 20 deletions(-) diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs index 38124fd9a5..6073977dd1 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -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>, + + pub wake_registry: RwLock>, } #[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); diff --git a/drivers/acpid/src/main.rs b/drivers/acpid/src/main.rs index 032b50b159..f374e14172 100644 --- a/drivers/acpid/src/main.rs +++ b/drivers/acpid/src/main.rs @@ -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"))] diff --git a/drivers/acpid/src/wake.rs b/drivers/acpid/src/wake.rs index b302f5be71..1064eb7d62 100644 --- a/drivers/acpid/src/wake.rs +++ b/drivers/acpid/src/wake.rs @@ -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 { @@ -229,6 +276,49 @@ pub fn init_lpit(acpi: &AcpiContext) -> Option { 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::*;