Files
RedBear-OS/drivers/acpid/src/gpe.rs
T
Red Bear OS 0485ae662a 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.
2026-07-22 19:09:42 +09:00

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());
}
}