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:
+16
-20
@@ -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);
|
||||
|
||||
@@ -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"))]
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user