acpid: general GPE _Lxx/_Exx dispatch + ACPI PM timer (ACPICA port)

- GpeBlocks::enabled_active_gpes(): scans all GPE block status+enable
  register pairs, returns every GPE with both bits set (evgpe detect
  semantics — a GPE only fires the SCI when enabled AND active).

- handle_sci general GPE dispatch (acpi_ev_gpe_detect): every
  enabled+active GPE that is not the EC GPE gets its \_GPE._L{gpe:02X}
  (level, tried first) or \_GPE._E{gpe:02X} (edge) control method
  evaluated; status cleared after. This is the ACPICA core GPE model
  that drives lid, device wake, and all non-EC GPE events — previously
  only the EC GPE and PM1 fixed events were dispatched.

- pm_timer_read(): reads the FADT pm_timer_block free-running counter
  (3.579545 MHz, PM_TIMER_FREQUENCY_HZ). Exposed at
  /scheme/acpi/pmtimer for precise sleep-path timing.
This commit is contained in:
Red Bear OS
2026-07-22 19:09:42 +09:00
parent 94d1b92d91
commit 0485ae662a
3 changed files with 65 additions and 1 deletions
+32
View File
@@ -12,6 +12,16 @@ use common::io::{Io, Pio};
use crate::acpi::FadtStruct;
pub const PM_TIMER_FREQUENCY_HZ: u64 = 3_579_545;
pub fn pm_timer_read(fadt: &FadtStruct) -> Option<u32> {
let port = fadt.pm_timer_block as u16;
if fadt.pm_timer_block == 0 || fadt.pm_timer_length == 0 {
return None;
}
Some(Pio::<u32>::new(port).read())
}
// PM1 fixed-event bits in PM1_STS/PM1_EN (ACPI 6.4 §4.8.3.1).
pub const PM1_PWRBTN: u16 = 1 << 8;
pub const PM1_SLPBTN: u16 = 1 << 9;
@@ -129,6 +139,28 @@ impl GpeBlocks {
Pio::<u8>::new(port).write(1 << bit);
}
/// GPEs with both status and enable bits set (evgpe detect semantics).
pub fn enabled_active_gpes(&self) -> Vec<u8> {
let mut out = Vec::new();
for block in [self.gpe0, self.gpe1].into_iter().flatten() {
let half = block.len / 2;
for byte in 0..half {
let status_port = block.port + byte as u16;
let enable_port = block.port + (half as u16) + byte as u16;
let active = Pio::<u8>::new(status_port).read() & Pio::<u8>::new(enable_port).read();
if active == 0 {
continue;
}
for bit in 0..8 {
if active & (1 << bit) != 0 {
out.push(block.base + byte * 8 + bit);
}
}
}
}
out
}
/// PM1 fixed-event status register (16-bit across the a/b blocks).
pub fn pm1_status(&self) -> u16 {
let mut value = 0u16;
+22
View File
@@ -237,6 +237,28 @@ pub fn handle_sci(context: &AcpiContext) -> Vec<PowerEvent> {
}
}
// General GPE dispatch (evgpe.c acpi_ev_gpe_detect): non-EC enabled+active
// GPEs get \_GPE._Lxx (level, tried first) or \_GPE._Exx (edge) evaluated.
let ec_gpe = context.ec_device.read().clone().map(|(_, g)| g);
for gpe in blocks.enabled_active_gpes() {
if Some(gpe) == ec_gpe {
continue;
}
let level = format!("\\_GPE._L{gpe:02X}");
let edge = format!("\\_GPE._E{gpe:02X}");
let handled = context.evaluate_acpi_method(&level, "", &[]).is_ok()
|| context.evaluate_acpi_method(&edge, "", &[]).is_ok();
if handled {
log::info!("acpid: GPE {:#04x} dispatched to AML control method", gpe);
} else {
log::debug!(
"acpid: GPE {:#04x} active but no _L{gpe:02X}/_E{gpe:02X} method",
gpe
);
}
blocks.clear_gpe(gpe);
}
// Drain AML notifications (from _Qxx methods and any other Notify).
let lid_path = context.lid_device.read().clone();
for (device, value) in context.notifications().drain() {
+11 -1
View File
@@ -18,7 +18,7 @@ use syscall::FobtainFdFlags;
use syscall::data::Stat;
use syscall::error::{Error, Result};
use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENOENT, ENOTDIR};
use syscall::error::{EACCES, EBADF, EBADFD, EINVAL, EIO, EISDIR, ENODEV, ENOENT, ENOTDIR};
use syscall::flag::{MODE_DIR, MODE_FILE};
use syscall::flag::{O_ACCMODE, O_DIRECTORY, O_RDONLY, O_STAT, O_SYMLINK};
use syscall::{EOVERFLOW, EPERM};
@@ -67,6 +67,7 @@ enum HandleKind<'a> {
/// listing is empty.
Thermal,
ThermalZone { zone: String, kind: ThermalFileKind },
PmTimer,
/// `/scheme/acpi/power` -- entries are PowerResource objects in
/// the AML namespace. On laptops these are AC adapters and
/// battery controllers. On desktops and QEMU the listing is
@@ -153,6 +154,7 @@ impl HandleKind<'_> {
Self::DmiField(_) => false,
Self::ProcFile { .. } => false,
Self::LidState | Self::Button { .. } | Self::Notifications => false,
Self::PmTimer => false,
Self::Lid | Self::ButtonDir => true,
Self::Fan => true,
Self::FanState(_) | Self::FanSpeed(_) => false,
@@ -182,6 +184,7 @@ impl HandleKind<'_> {
Self::Notifications | Self::Lid | Self::ButtonDir | Self::Fan => 0,
Self::FanState(_) | Self::FanSpeed(_) => 4,
Self::ThermalZone { .. } => 16,
Self::PmTimer => 16,
// Directories
Self::TopLevel | Self::Symbols(_) | Self::Tables => 0,
Self::Thermal | Self::Power | Self::Processor | Self::DmiDir => 0,
@@ -479,6 +482,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
}
["lid"] => HandleKind::Lid,
["pmtimer"] => HandleKind::PmTimer,
["lid", "state"] => HandleKind::LidState,
["button"] => HandleKind::ButtonDir,
["button", "power"] => HandleKind::Button { power: true },
@@ -692,6 +696,12 @@ impl SchemeSync for AcpiScheme<'_, '_> {
dmi_buf = format!("{}\n", raw);
dmi_buf.as_bytes()
}
HandleKind::PmTimer => {
let fadt = self.ctx.fadt().ok_or(Error::new(ENODEV))?;
let value = crate::gpe::pm_timer_read(fadt).ok_or(Error::new(ENODEV))?;
dmi_buf = format!("{value}\n");
dmi_buf.as_bytes()
}
HandleKind::Processor | HandleKind::DmiDir | HandleKind::Thermal | HandleKind::Power | HandleKind::PowerBatteries | HandleKind::Symbols(_) | HandleKind::RegisterPci | HandleKind::TopLevel | HandleKind::SchemeRoot | HandleKind::Lid | HandleKind::ButtonDir | HandleKind::Fan => {
return Err(Error::new(EISDIR));
}