//! 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 { 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; 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, pub gpe1: Option, pub pm1a_event: Option, pub pm1b_event: Option, /// 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::::new(port).read(); value |= 1 << bit; Pio::::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::::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::::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; if let Some(port) = self.pm1a_event { value |= Pio::::new(port).read(); } if let Some(port) = self.pm1b_event { value |= Pio::::new(port).read(); } value } pub fn pm1_clear(&self, bits: u16) { if let Some(port) = self.pm1a_event { Pio::::new(port).write(bits); } if let Some(port) = self.pm1b_event { Pio::::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::::new(enable_port).read(); value |= bits; Pio::::new(enable_port).write(value); } if let Some(port) = self.pm1b_event { let enable_port = port + en_offset; let mut value = Pio::::new(enable_port).read(); value |= bits; Pio::::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()); } }