acpid: LPIT parser + _PRW wake enumeration + FADT S0-idle detection (ACPICA port)
Port useful ACPICA components into the existing Rust ACPI stack: - LPIT (Low Power Idle Table) parser in new acpid/wake.rs. Parses native C-state LPI entries (entry trigger, residency, latency, residency counter, counter frequency). Computes total residency and deepest LPI state. 4 unit tests cover parsing, empty input, short input, and deepest-entry selection. Reference: Linux include/acpi/actbl2.h struct acpi_lpit_native. - _PRW wake device enumeration in acpid/wake.rs. WakeRegistry::enumerate() evaluates _PRW on known wake-capable device paths (LID, PWRB, SLPB, XHCI, HDAS, CNVW, I2C0/1, THC0/1). Extracts GPE number and sleep state from the _PRW package. Reference: Linux drivers/acpi/scan.c acpi_bus_get_wakeup_device_flags. - FADT S0-idle detection: Fadt::supports_s0_idle() checks bit 21 (ACPI_FADT_LOW_POWER_S0) to detect platforms that support s2idle. Reference: Linux include/acpi/actbl.h. - acpid/main.rs: LPIT parse + wake enumeration + S0-idle detection wired into init after AcpiContext::init() and before power events. ACPICA assessment: do NOT port ACPICA as a C library. Port its useful data structures and algorithms into the existing Rust stack (acpi-rs vendored fork + acpid daemon). The AML interpreter already has comprehensive opcode coverage (only DefLoad/DefLoadTable remain unimplemented, both optional). The GPE/EC/fixed-event infrastructure is already in acpid. LPIT + _PRW + S0-idle detection fill the remaining gaps for Phase 9.1 s2idle completion.
This commit is contained in:
@@ -1841,6 +1841,10 @@ impl Fadt {
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn supports_s0_idle(&self) -> bool {
|
||||||
|
(self.flags & (1 << 21)) != 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Fadt {
|
impl Deref for Fadt {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ mod ec;
|
|||||||
mod gpe;
|
mod gpe;
|
||||||
mod notifications;
|
mod notifications;
|
||||||
mod power_events;
|
mod power_events;
|
||||||
|
mod wake;
|
||||||
|
|
||||||
mod scheme;
|
mod scheme;
|
||||||
|
|
||||||
@@ -87,6 +88,35 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
|||||||
];
|
];
|
||||||
let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers);
|
let acpi_context = self::acpi::AcpiContext::init(physaddrs_iter, region_handlers);
|
||||||
|
|
||||||
|
// LPIT parse for s2idle residency reporting (Phase 9.1).
|
||||||
|
if let Some(lpit) = wake::init_lpit(&acpi_context) {
|
||||||
|
log::info!(
|
||||||
|
"acpid: LPIT loaded — {} LPI state(s), deepest {} us residency",
|
||||||
|
lpit.entries.len(),
|
||||||
|
lpit.deepest_lpi().map(|e| e.residency_us).unwrap_or(0)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
log::debug!("acpid: no LPIT table found (s2idle residency reporting unavailable)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(fadt) = acpi_context.fadt() {
|
||||||
|
if fadt.supports_s0_idle() {
|
||||||
|
log::info!("acpid: platform supports S0 low-power idle (s2idle)");
|
||||||
|
} else {
|
||||||
|
log::info!("acpid: platform does not advertise S0 low-power idle — s2idle unavailable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// _PRW wake device enumeration for s2idle wake management.
|
||||||
|
let wake_registry = wake::WakeRegistry::enumerate(&acpi_context);
|
||||||
|
if wake_registry.device_count() > 0 {
|
||||||
|
log::info!(
|
||||||
|
"acpid: {} wake-capable device(s) registered (GPEs: {:?})",
|
||||||
|
wake_registry.device_count(),
|
||||||
|
wake_registry.wake_gpes()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: I/O permission bitmap?
|
// TODO: I/O permission bitmap?
|
||||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||||
common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
|
common::acquire_port_io_rights().expect("acpid: failed to set I/O privilege level to Ring 3");
|
||||||
|
|||||||
@@ -0,0 +1,289 @@
|
|||||||
|
use std::sync::RwLock;
|
||||||
|
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
|
||||||
|
use crate::acpi::AcpiContext;
|
||||||
|
|
||||||
|
const LPIT_TYPE_NATIVE_CSTATE: u32 = 0x00;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct LpiEntry {
|
||||||
|
pub unique_id: u16,
|
||||||
|
pub entry_trigger_addr: u64,
|
||||||
|
pub residency_us: u32,
|
||||||
|
pub latency_us: u32,
|
||||||
|
pub residency_counter_addr: u64,
|
||||||
|
pub counter_frequency: u64,
|
||||||
|
pub disabled: bool,
|
||||||
|
pub no_counter: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct LpitInfo {
|
||||||
|
pub entries: Vec<LpiEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LpitInfo {
|
||||||
|
pub fn parse(data: &[u8]) -> Option<Self> {
|
||||||
|
if data.len() < 8 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
let mut offset = 0;
|
||||||
|
while offset + 16 <= data.len() {
|
||||||
|
let type_ = u32::from_le_bytes([
|
||||||
|
data[offset],
|
||||||
|
data[offset + 1],
|
||||||
|
data[offset + 2],
|
||||||
|
data[offset + 3],
|
||||||
|
]);
|
||||||
|
let length = u32::from_le_bytes([
|
||||||
|
data[offset + 4],
|
||||||
|
data[offset + 5],
|
||||||
|
data[offset + 6],
|
||||||
|
data[offset + 7],
|
||||||
|
]) as usize;
|
||||||
|
if length < 16 || offset + length > data.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let unique_id = u16::from_le_bytes([data[offset + 8], data[offset + 9]]);
|
||||||
|
let flags = u32::from_le_bytes([
|
||||||
|
data[offset + 12],
|
||||||
|
data[offset + 13],
|
||||||
|
data[offset + 14],
|
||||||
|
data[offset + 15],
|
||||||
|
]);
|
||||||
|
let disabled = (flags & 1) != 0;
|
||||||
|
let no_counter = (flags & 2) != 0;
|
||||||
|
|
||||||
|
if type_ == LPIT_TYPE_NATIVE_CSTATE && length >= 48 {
|
||||||
|
let entry_trigger_addr = u64::from_le_bytes([
|
||||||
|
data[offset + 16],
|
||||||
|
data[offset + 17],
|
||||||
|
data[offset + 18],
|
||||||
|
data[offset + 19],
|
||||||
|
data[offset + 20],
|
||||||
|
data[offset + 21],
|
||||||
|
data[offset + 22],
|
||||||
|
data[offset + 23],
|
||||||
|
]);
|
||||||
|
let residency_us = u32::from_le_bytes([
|
||||||
|
data[offset + 24],
|
||||||
|
data[offset + 25],
|
||||||
|
data[offset + 26],
|
||||||
|
data[offset + 27],
|
||||||
|
]);
|
||||||
|
let latency_us = u32::from_le_bytes([
|
||||||
|
data[offset + 28],
|
||||||
|
data[offset + 29],
|
||||||
|
data[offset + 30],
|
||||||
|
data[offset + 31],
|
||||||
|
]);
|
||||||
|
let residency_counter_addr = u64::from_le_bytes([
|
||||||
|
data[offset + 32],
|
||||||
|
data[offset + 33],
|
||||||
|
data[offset + 34],
|
||||||
|
data[offset + 35],
|
||||||
|
data[offset + 36],
|
||||||
|
data[offset + 37],
|
||||||
|
data[offset + 38],
|
||||||
|
data[offset + 39],
|
||||||
|
]);
|
||||||
|
let counter_frequency = u64::from_le_bytes([
|
||||||
|
data[offset + 40],
|
||||||
|
data[offset + 41],
|
||||||
|
data[offset + 42],
|
||||||
|
data[offset + 43],
|
||||||
|
data[offset + 44],
|
||||||
|
data[offset + 45],
|
||||||
|
data[offset + 46],
|
||||||
|
data[offset + 47],
|
||||||
|
]);
|
||||||
|
entries.push(LpiEntry {
|
||||||
|
unique_id,
|
||||||
|
entry_trigger_addr,
|
||||||
|
residency_us,
|
||||||
|
latency_us,
|
||||||
|
residency_counter_addr,
|
||||||
|
counter_frequency,
|
||||||
|
disabled,
|
||||||
|
no_counter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
offset += length;
|
||||||
|
}
|
||||||
|
if entries.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self { entries })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn total_residency_us(&self) -> u64 {
|
||||||
|
self.entries
|
||||||
|
.iter()
|
||||||
|
.filter(|e| !e.disabled)
|
||||||
|
.map(|e| u64::from(e.residency_us))
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deepest_lpi(&self) -> Option<&LpiEntry> {
|
||||||
|
self.entries
|
||||||
|
.iter()
|
||||||
|
.filter(|e| !e.disabled)
|
||||||
|
.max_by_key(|e| e.residency_us)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct WakeDevice {
|
||||||
|
pub acpi_path: String,
|
||||||
|
pub gpe_number: Option<u32>,
|
||||||
|
pub gpe_device_path: Option<String>,
|
||||||
|
pub sleep_state: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WakeRegistry {
|
||||||
|
devices: RwLock<Vec<WakeDevice>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WakeRegistry {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
devices: RwLock::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enumerate(acpi: &AcpiContext) -> Self {
|
||||||
|
let registry = Self::new();
|
||||||
|
let candidates = [
|
||||||
|
"\\_SB.LID_",
|
||||||
|
"\\_SB.PWRB",
|
||||||
|
"\\_SB.SLPB",
|
||||||
|
"\\_SB.PC00.XHCI",
|
||||||
|
"\\_SB.PC00.HDAS",
|
||||||
|
"\\_SB.PC00.CNVW",
|
||||||
|
"\\_SB.PC00.I2C0",
|
||||||
|
"\\_SB.PC00.I2C1",
|
||||||
|
"\\_SB.PC00.THC0",
|
||||||
|
"\\_SB.PC00.THC1",
|
||||||
|
];
|
||||||
|
for path in &candidates {
|
||||||
|
if let Some(wake) = registry.evaluate_prw(acpi, path) {
|
||||||
|
info!(
|
||||||
|
"acpid: wake device {} (GPE {:?}, S{})",
|
||||||
|
wake.acpi_path, wake.gpe_number, wake.sleep_state
|
||||||
|
);
|
||||||
|
registry.devices.write().unwrap().push(wake);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let count = registry.devices.read().unwrap().len();
|
||||||
|
if count > 0 {
|
||||||
|
info!("acpid: {} wake-capable device(s) enumerated", count);
|
||||||
|
} else {
|
||||||
|
debug!("acpid: no wake-capable devices found via _PRW");
|
||||||
|
}
|
||||||
|
registry
|
||||||
|
}
|
||||||
|
|
||||||
|
fn evaluate_prw(&self, acpi: &AcpiContext, path: &str) -> Option<WakeDevice> {
|
||||||
|
let values = acpi.evaluate_acpi_method(path, "_PRW", &[]).ok()?;
|
||||||
|
if values.len() < 2 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let gpe_number = Some(values[0] as u32);
|
||||||
|
let sleep_state = values[1] as u8;
|
||||||
|
|
||||||
|
Some(WakeDevice {
|
||||||
|
acpi_path: path.to_string(),
|
||||||
|
gpe_number,
|
||||||
|
gpe_device_path: None,
|
||||||
|
sleep_state,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wake_gpes(&self) -> Vec<u32> {
|
||||||
|
self.devices
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|d| d.gpe_number)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn device_count(&self) -> usize {
|
||||||
|
self.devices.read().unwrap().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_lpit(acpi: &AcpiContext) -> Option<LpitInfo> {
|
||||||
|
let lpit_sdt = acpi.take_single_sdt(*b"LPIT")?;
|
||||||
|
let info = LpitInfo::parse(lpit_sdt.data())?;
|
||||||
|
info!(
|
||||||
|
"acpid: LPIT parsed — {} LPI entries, total residency {} us, deepest {} us",
|
||||||
|
info.entries.len(),
|
||||||
|
info.total_residency_us(),
|
||||||
|
info.deepest_lpi().map(|e| e.residency_us).unwrap_or(0)
|
||||||
|
);
|
||||||
|
Some(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lpit_parse_native_cstate() {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
data.extend_from_slice(&0u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&48u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&1u16.to_le_bytes());
|
||||||
|
data.extend_from_slice(&0u16.to_le_bytes());
|
||||||
|
data.extend_from_slice(&0u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&0x1000u64.to_le_bytes());
|
||||||
|
data.extend_from_slice(&300u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&10u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&0x2000u64.to_le_bytes());
|
||||||
|
data.extend_from_slice(&19200u64.to_le_bytes());
|
||||||
|
let info = LpitInfo::parse(&data).unwrap();
|
||||||
|
assert_eq!(info.entries.len(), 1);
|
||||||
|
assert_eq!(info.entries[0].unique_id, 1);
|
||||||
|
assert_eq!(info.entries[0].residency_us, 300);
|
||||||
|
assert_eq!(info.entries[0].latency_us, 10);
|
||||||
|
assert!(!info.entries[0].disabled);
|
||||||
|
assert!(!info.entries[0].no_counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lpit_parse_empty() {
|
||||||
|
assert!(LpitInfo::parse(&[]).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lpit_parse_too_short() {
|
||||||
|
let data = [0u8; 7];
|
||||||
|
assert!(LpitInfo::parse(&data).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lpit_deepest_entry() {
|
||||||
|
let mut data = Vec::new();
|
||||||
|
for (id, residency) in [(1u16, 100u32), (2, 500), (3, 50)] {
|
||||||
|
data.extend_from_slice(&0u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&48u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&id.to_le_bytes());
|
||||||
|
data.extend_from_slice(&0u16.to_le_bytes());
|
||||||
|
data.extend_from_slice(&0u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&0x1000u64.to_le_bytes());
|
||||||
|
data.extend_from_slice(&residency.to_le_bytes());
|
||||||
|
data.extend_from_slice(&10u32.to_le_bytes());
|
||||||
|
data.extend_from_slice(&0x2000u64.to_le_bytes());
|
||||||
|
data.extend_from_slice(&19200u64.to_le_bytes());
|
||||||
|
}
|
||||||
|
let info = LpitInfo::parse(&data).unwrap();
|
||||||
|
let deepest = info.deepest_lpi().unwrap();
|
||||||
|
assert_eq!(deepest.unique_id, 2);
|
||||||
|
assert_eq!(deepest.residency_us, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user