0485ae662a
- 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.
254 lines
8.7 KiB
Rust
254 lines
8.7 KiB
Rust
//! GPE (General Purpose Event) and PM1 fixed-event infrastructure.
|
|
//!
|
|
//! Ported from Linux 7.1 `drivers/acpi/evgpeblk.c` (GPE block enable/status
|
|
//! register layout) and `drivers/acpi/events/evxface.c` (PM1 fixed events:
|
|
//! power button, sleep button). The GPE block is split in half: the first
|
|
//! `len/2` bytes are the status registers (write-1-to-clear), the second
|
|
//! `len/2` bytes are the enable registers. acpid owns only the GPEs it
|
|
//! explicitly enables (the EC GPE on laptop platforms); all other enable
|
|
//! bits are left exactly as firmware set them.
|
|
|
|
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;
|
|
pub const PM1_RTC: u16 = 1 << 10;
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct GpeBlock {
|
|
pub port: u16,
|
|
/// Total block length in bytes (status half + enable half).
|
|
pub len: u8,
|
|
/// First GPE number covered by this block (0 for GPE0, `gpe1_base` for GPE1).
|
|
pub base: u8,
|
|
}
|
|
|
|
impl GpeBlock {
|
|
fn status_port(&self, gpe: u8) -> Option<(u16, u8)> {
|
|
let index = gpe.checked_sub(self.base)?;
|
|
let byte = index / 8;
|
|
if byte >= self.len / 2 {
|
|
return None;
|
|
}
|
|
Some((self.port + byte as u16, index % 8))
|
|
}
|
|
|
|
fn enable_port(&self, gpe: u8) -> Option<(u16, u8)> {
|
|
let index = gpe.checked_sub(self.base)?;
|
|
let byte = index / 8;
|
|
if byte >= self.len / 2 {
|
|
return None;
|
|
}
|
|
Some((self.port + (self.len / 2) as u16 + byte as u16, index % 8))
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct GpeBlocks {
|
|
pub sci_irq: u16,
|
|
pub gpe0: Option<GpeBlock>,
|
|
pub gpe1: Option<GpeBlock>,
|
|
pub pm1a_event: Option<u16>,
|
|
pub pm1b_event: Option<u16>,
|
|
/// Total PM1 event block length in bytes (STS half + EN half).
|
|
pub pm1_event_len: u8,
|
|
}
|
|
|
|
impl GpeBlocks {
|
|
/// Build the register map from the parsed FADT. Blocks with a zero
|
|
/// address are absent (ACPI 6.4 §5.2.9). Event-block ports come from the
|
|
/// 32-bit FADT fields; the extended X_ GAS variants are used by few
|
|
/// laptops and are covered by the 32-bit fields on x86.
|
|
pub fn from_fadt(fadt: &FadtStruct) -> Self {
|
|
let gpe0 = (fadt.gpe0_block != 0 && fadt.gpe0_ength != 0).then_some(GpeBlock {
|
|
port: fadt.gpe0_block as u16,
|
|
len: fadt.gpe0_ength,
|
|
base: 0,
|
|
});
|
|
let gpe1 = (fadt.gpe1_block != 0 && fadt.gpe1_length != 0).then_some(GpeBlock {
|
|
port: fadt.gpe1_block as u16,
|
|
len: fadt.gpe1_length,
|
|
base: fadt.gpe1_base,
|
|
});
|
|
Self {
|
|
sci_irq: fadt.sci_interrupt,
|
|
gpe0,
|
|
gpe1,
|
|
pm1a_event: (fadt.pm1a_event_block != 0).then_some(fadt.pm1a_event_block as u16),
|
|
pm1b_event: (fadt.pm1b_event_block != 0).then_some(fadt.pm1b_event_block as u16),
|
|
pm1_event_len: fadt.pm1_event_length,
|
|
}
|
|
}
|
|
|
|
fn block_for(&self, gpe: u8) -> Option<&GpeBlock> {
|
|
self.gpe0
|
|
.as_ref()
|
|
.filter(|block| gpe >= block.base && gpe < block.base + block.len / 2 * 8)
|
|
.or(self
|
|
.gpe1
|
|
.as_ref()
|
|
.filter(|block| gpe >= block.base && gpe < block.base + block.len / 2 * 8))
|
|
}
|
|
|
|
/// Enable a single GPE (evgpeblk's enable-write-preserve: read-modify-write
|
|
/// only the one bit; every other GPE keeps its firmware state).
|
|
pub fn enable_gpe(&self, gpe: u8) -> bool {
|
|
let Some(block) = self.block_for(gpe) else {
|
|
return false;
|
|
};
|
|
let Some((port, bit)) = block.enable_port(gpe) else {
|
|
return false;
|
|
};
|
|
let mut value = Pio::<u8>::new(port).read();
|
|
value |= 1 << bit;
|
|
Pio::<u8>::new(port).write(value);
|
|
true
|
|
}
|
|
|
|
pub fn gpe_status(&self, gpe: u8) -> bool {
|
|
let Some(block) = self.block_for(gpe) else {
|
|
return false;
|
|
};
|
|
let Some((port, bit)) = block.status_port(gpe) else {
|
|
return false;
|
|
};
|
|
Pio::<u8>::new(port).read() & (1 << bit) != 0
|
|
}
|
|
|
|
/// Write-1-to-clear the status bit for one GPE.
|
|
pub fn clear_gpe(&self, gpe: u8) {
|
|
let Some(block) = self.block_for(gpe) else {
|
|
return;
|
|
};
|
|
let Some((port, bit)) = block.status_port(gpe) else {
|
|
return;
|
|
};
|
|
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;
|
|
if let Some(port) = self.pm1a_event {
|
|
value |= Pio::<u16>::new(port).read();
|
|
}
|
|
if let Some(port) = self.pm1b_event {
|
|
value |= Pio::<u16>::new(port).read();
|
|
}
|
|
value
|
|
}
|
|
|
|
pub fn pm1_clear(&self, bits: u16) {
|
|
if let Some(port) = self.pm1a_event {
|
|
Pio::<u16>::new(port).write(bits);
|
|
}
|
|
if let Some(port) = self.pm1b_event {
|
|
Pio::<u16>::new(port).write(bits);
|
|
}
|
|
}
|
|
|
|
/// Enable fixed events in PM1_EN (read-modify-write; other enable bits
|
|
/// keep their firmware state). PM1_EN lives `pm1_event_len / 2` bytes
|
|
/// after PM1_STS within each event block (ACPI 6.4 §4.8.3.1).
|
|
pub fn pm1_enable(&self, bits: u16) {
|
|
let en_offset = (self.pm1_event_len / 2) as u16;
|
|
if let Some(port) = self.pm1a_event {
|
|
let enable_port = port + en_offset;
|
|
let mut value = Pio::<u16>::new(enable_port).read();
|
|
value |= bits;
|
|
Pio::<u16>::new(enable_port).write(value);
|
|
}
|
|
if let Some(port) = self.pm1b_event {
|
|
let enable_port = port + en_offset;
|
|
let mut value = Pio::<u16>::new(enable_port).read();
|
|
value |= bits;
|
|
Pio::<u16>::new(enable_port).write(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
fn fadt() -> FadtStruct {
|
|
let mut fadt: FadtStruct = unsafe { core::mem::zeroed() };
|
|
fadt.sci_interrupt = 9;
|
|
fadt.gpe0_block = 0x1828;
|
|
fadt.gpe0_ength = 0x20;
|
|
fadt.gpe1_block = 0x1848;
|
|
fadt.gpe1_length = 0x10;
|
|
fadt.gpe1_base = 0x80;
|
|
fadt.pm1a_event_block = 0x1800;
|
|
fadt
|
|
}
|
|
|
|
#[test]
|
|
fn gpe_block_mapping() {
|
|
let blocks = GpeBlocks::from_fadt(&fadt());
|
|
let gpe0 = blocks.gpe0.expect("gpe0 present");
|
|
assert_eq!(gpe0.port, 0x1828);
|
|
assert_eq!(gpe0.len, 0x20);
|
|
assert_eq!(gpe0.base, 0);
|
|
// GPE 0x6E (LG Gram EC GPE): index 110 → byte 13, bit 6.
|
|
let (status_port, bit) = gpe0.status_port(0x6e).expect("in range");
|
|
assert_eq!(status_port, 0x1828 + 13);
|
|
assert_eq!(bit, 6);
|
|
// Enable port is offset by len/2 (16).
|
|
let (enable_port, _) = gpe0.enable_port(0x6e).expect("in range");
|
|
assert_eq!(enable_port, 0x1828 + 16 + 13);
|
|
// Out of range: len/2 = 16 bytes → 128 GPEs max.
|
|
assert!(gpe0.status_port(0x7f).is_some());
|
|
assert!(gpe0.status_port(0x80).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn gpe1_base_offset() {
|
|
let blocks = GpeBlocks::from_fadt(&fadt());
|
|
let gpe1 = blocks.gpe1.expect("gpe1 present");
|
|
assert_eq!(gpe1.base, 0x80);
|
|
assert_eq!(gpe1.status_port(0x7f), None);
|
|
let (port, bit) = gpe1.status_port(0x81).expect("in range");
|
|
assert_eq!(port, 0x1848);
|
|
assert_eq!(bit, 1);
|
|
assert!(blocks.block_for(0x6e).is_some());
|
|
assert!(blocks.block_for(0x81).is_some());
|
|
assert!(blocks.block_for(0xff).is_none());
|
|
}
|
|
}
|