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 {
|
||||
|
||||
@@ -18,6 +18,7 @@ mod ec;
|
||||
mod gpe;
|
||||
mod notifications;
|
||||
mod power_events;
|
||||
mod wake;
|
||||
|
||||
mod scheme;
|
||||
|
||||
@@ -87,6 +88,35 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
];
|
||||
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?
|
||||
#[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");
|
||||
|
||||
@@ -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