diff --git a/drivers/acpid/src/gpe.rs b/drivers/acpid/src/gpe.rs index c418ed133c..97014e72d4 100644 --- a/drivers/acpid/src/gpe.rs +++ b/drivers/acpid/src/gpe.rs @@ -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 { + let port = fadt.pm_timer_block as u16; + if fadt.pm_timer_block == 0 || fadt.pm_timer_length == 0 { + return None; + } + Some(Pio::::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::::new(port).write(1 << bit); } + /// GPEs with both status and enable bits set (evgpe detect semantics). + pub fn enabled_active_gpes(&self) -> Vec { + 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::::new(status_port).read() & Pio::::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; diff --git a/drivers/acpid/src/power_events.rs b/drivers/acpid/src/power_events.rs index 3ece213e06..7e5bd680e7 100644 --- a/drivers/acpid/src/power_events.rs +++ b/drivers/acpid/src/power_events.rs @@ -237,6 +237,28 @@ pub fn handle_sci(context: &AcpiContext) -> Vec { } } + // 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() { diff --git a/drivers/acpid/src/scheme.rs b/drivers/acpid/src/scheme.rs index 5bdcda3253..7ef1e2296e 100644 --- a/drivers/acpid/src/scheme.rs +++ b/drivers/acpid/src/scheme.rs @@ -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)); }