acpid/acpi-rs: _REG opregion connect + LPIT parse fix + thermal active + PM timer + HW-reduced + GPE dispatch
acpi-rs (vendored fork): - _REG opregion connect (ACPICA evrgnini.c): run \_SB._REG(space, 1) per installed handler after \_INI — firmware gates EC access behind this. Device-level _REG for each device holding an OpRegion of an installed space. Fills the last big TODO in initialize_namespace. - RegionSpace: add From<RegionSpace> for u8 (region-space ID for _REG). acpid: - LPIT parser BUG FIX: GAS (Generic Address Structure) is 12 bytes (4-byte header + 8-byte address); the parser previously read entry_trigger/residency/latency/counter_frequency 4 bytes early and required length>=48 instead of 56. Fixed offsets, added entry_trigger_space_id, and mwait_hint()/best_mwait_hint() for the FFH MWAIT hint. Tests rewritten with correct 56-byte layout. - Thermal active cooling: /scheme/acpi/thermal/<zone>/active evaluates _AC0.._AC9 and returns defined trip points (tenths of K). - ACPI PM timer: pm_timer_read() from FADT pm_timer_block; /scheme/acpi/pmtimer endpoint (3.579545 MHz). - HW-reduced ACPI: Fadt::is_hardware_reduced() (FADT bit 20); skip PM1/GPE setup on such platforms. - General GPE dispatch: enabled_active_gpes() + \_GPE._Lxx/_Exx method evaluation (evgpe.c acpi_ev_gpe_detect).
This commit is contained in:
@@ -255,7 +255,61 @@ where
|
||||
warn!("Invoking \\_SB._INI failed: {:?}", err);
|
||||
}
|
||||
|
||||
// TODO: run all _REGs for globally-installed handlers (this might need more bookkeeping)
|
||||
// _REG opregion connect (ACPICA evrgnini.c): run \_SB._REG(space, 1)
|
||||
// per installed handler — firmware gates EC access behind this.
|
||||
let installed_spaces: Vec<RegionSpace> =
|
||||
self.region_handlers.lock().keys().copied().collect();
|
||||
for space in &installed_spaces {
|
||||
let space_id = u8::from(*space) as u64;
|
||||
if let Err(err) = self.evaluate_if_present(
|
||||
AmlName::from_str("\\_SB._REG").unwrap(),
|
||||
vec![Object::Integer(space_id).wrap(), Object::Integer(1).wrap()],
|
||||
) {
|
||||
warn!("\\_SB._REG({:?}, connect) failed: {:?}", space, err);
|
||||
}
|
||||
}
|
||||
|
||||
// Device-level _REG: for each device holding an OpRegion of an
|
||||
// installed space, run <device>._REG(space, 1).
|
||||
{
|
||||
let mut reg_namespace = self.namespace.lock().clone();
|
||||
let _ = reg_namespace.traverse(|path, level| {
|
||||
match level.kind {
|
||||
NamespaceLevelKind::Device
|
||||
| NamespaceLevelKind::Processor
|
||||
| NamespaceLevelKind::ThermalZone
|
||||
| NamespaceLevelKind::PowerResource => {
|
||||
let mut device_spaces: Vec<RegionSpace> = level
|
||||
.values
|
||||
.values()
|
||||
.filter_map(|(_, obj)| match &**obj {
|
||||
Object::OpRegion(region)
|
||||
if installed_spaces.contains(®ion.space) =>
|
||||
{
|
||||
Some(region.space)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
device_spaces.sort();
|
||||
device_spaces.dedup();
|
||||
for space in device_spaces {
|
||||
let space_id = u8::from(space) as u64;
|
||||
if let Ok(reg_path) =
|
||||
AmlName::from_str("_REG").unwrap().resolve(path)
|
||||
{
|
||||
let _ = self.evaluate_if_present(
|
||||
reg_path,
|
||||
vec![Object::Integer(space_id).wrap(), Object::Integer(1).wrap()],
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
_ => Ok(true),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* We can now initialize each device in the namespace. For each device, we evaluate `_STA`,
|
||||
|
||||
@@ -54,3 +54,22 @@ impl From<u8> for RegionSpace {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RegionSpace> for u8 {
|
||||
fn from(space: RegionSpace) -> u8 {
|
||||
match space {
|
||||
RegionSpace::SystemMemory => 0,
|
||||
RegionSpace::SystemIO => 1,
|
||||
RegionSpace::PciConfig => 2,
|
||||
RegionSpace::EmbeddedControl => 3,
|
||||
RegionSpace::SmBus => 4,
|
||||
RegionSpace::SystemCmos => 5,
|
||||
RegionSpace::PciBarTarget => 6,
|
||||
RegionSpace::Ipmi => 7,
|
||||
RegionSpace::GeneralPurposeIo => 8,
|
||||
RegionSpace::GenericSerialBus => 9,
|
||||
RegionSpace::Pcc => 10,
|
||||
RegionSpace::Oem(value) => value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1841,6 +1841,10 @@ impl Fadt {
|
||||
pub fn supports_s0_idle(&self) -> bool {
|
||||
(self.flags & (1 << 21)) != 0
|
||||
}
|
||||
|
||||
pub fn is_hardware_reduced(&self) -> bool {
|
||||
(self.flags & (1 << 20)) != 0
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Fadt {
|
||||
|
||||
@@ -60,6 +60,13 @@ pub fn init_power_events(context: &AcpiContext) -> Result<(), Box<dyn Error>> {
|
||||
log::warn!("acpid: no FADT; GPE/PM1 power events unavailable");
|
||||
return Ok(());
|
||||
};
|
||||
if fadt.is_hardware_reduced() {
|
||||
// HW-reduced ACPI (FADT bit 20): no PM1/GPE register blocks. Sleep and
|
||||
// wake use the HW-reduced sleep control/status registers instead.
|
||||
log::info!("acpid: hardware-reduced ACPI platform — PM1/GPE blocks absent");
|
||||
*context.gpe.write() = None;
|
||||
return Ok(());
|
||||
}
|
||||
let blocks = GpeBlocks::from_fadt(&fadt);
|
||||
log::info!(
|
||||
"acpid: SCI irq {} gpe0={:?} gpe1={:?} pm1a_evt={:#x?} pm1_evt_len={}",
|
||||
|
||||
+36
-12
@@ -127,6 +127,7 @@ enum ThermalFileKind {
|
||||
Temperature,
|
||||
Passive,
|
||||
Critical,
|
||||
Active,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -430,6 +431,7 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||
"temperature" => ThermalFileKind::Temperature,
|
||||
"passive" => ThermalFileKind::Passive,
|
||||
"critical" => ThermalFileKind::Critical,
|
||||
"active" => ThermalFileKind::Active,
|
||||
_ => return Err(Error::new(ENOENT)),
|
||||
};
|
||||
HandleKind::ThermalZone {
|
||||
@@ -683,18 +685,40 @@ impl SchemeSync for AcpiScheme<'_, '_> {
|
||||
dmi_buf.as_bytes()
|
||||
}
|
||||
HandleKind::ThermalZone { zone, kind } => {
|
||||
let method = match kind {
|
||||
ThermalFileKind::Temperature => "_TMP",
|
||||
ThermalFileKind::Passive => "_PSV",
|
||||
ThermalFileKind::Critical => "_CRT",
|
||||
};
|
||||
let values = self
|
||||
.ctx
|
||||
.evaluate_acpi_method(zone, method, &[])
|
||||
.map_err(|_| Error::new(EIO))?;
|
||||
let raw = values.first().copied().unwrap_or(0);
|
||||
dmi_buf = format!("{}\n", raw);
|
||||
dmi_buf.as_bytes()
|
||||
match kind {
|
||||
ThermalFileKind::Active => {
|
||||
// _AC0.._AC9 active cooling trip points (tenths of K);
|
||||
// only the defined ones are returned, one per line.
|
||||
let mut lines = String::new();
|
||||
for idx in 0..10u8 {
|
||||
let method = format!("_AC{idx}");
|
||||
if let Ok(values) =
|
||||
self.ctx.evaluate_acpi_method(zone, &method, &[])
|
||||
{
|
||||
if let Some(v) = values.first() {
|
||||
lines.push_str(&format!("{method}={v}\n"));
|
||||
}
|
||||
}
|
||||
}
|
||||
dmi_buf = lines;
|
||||
dmi_buf.as_bytes()
|
||||
}
|
||||
_ => {
|
||||
let method = match kind {
|
||||
ThermalFileKind::Temperature => "_TMP",
|
||||
ThermalFileKind::Passive => "_PSV",
|
||||
ThermalFileKind::Critical => "_CRT",
|
||||
ThermalFileKind::Active => unreachable!(),
|
||||
};
|
||||
let values = self
|
||||
.ctx
|
||||
.evaluate_acpi_method(zone, method, &[])
|
||||
.map_err(|_| Error::new(EIO))?;
|
||||
let raw = values.first().copied().unwrap_or(0);
|
||||
dmi_buf = format!("{}\n", raw);
|
||||
dmi_buf.as_bytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
HandleKind::PmTimer => {
|
||||
let fadt = self.ctx.fadt().ok_or(Error::new(ENODEV))?;
|
||||
|
||||
+60
-32
@@ -6,10 +6,12 @@ use crate::acpi::AcpiContext;
|
||||
use crate::gpe::GpeBlocks;
|
||||
|
||||
const LPIT_TYPE_NATIVE_CSTATE: u32 = 0x00;
|
||||
const GAS_SPACE_FIXED_HARDWARE: u8 = 0x7F;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LpiEntry {
|
||||
pub unique_id: u16,
|
||||
pub entry_trigger_space_id: u8,
|
||||
pub entry_trigger_addr: u64,
|
||||
pub residency_us: u32,
|
||||
pub latency_us: u32,
|
||||
@@ -19,6 +21,16 @@ pub struct LpiEntry {
|
||||
pub no_counter: bool,
|
||||
}
|
||||
|
||||
impl LpiEntry {
|
||||
pub fn mwait_hint(&self) -> Option<u32> {
|
||||
if self.entry_trigger_space_id == GAS_SPACE_FIXED_HARDWARE {
|
||||
Some(self.entry_trigger_addr as u32)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LpitInfo {
|
||||
pub entries: Vec<LpiEntry>,
|
||||
@@ -57,40 +69,31 @@ impl LpitInfo {
|
||||
let disabled = (flags & 1) != 0;
|
||||
let no_counter = (flags & 2) != 0;
|
||||
|
||||
if type_ == LPIT_TYPE_NATIVE_CSTATE && length >= 48 {
|
||||
if type_ == LPIT_TYPE_NATIVE_CSTATE && length >= 56 {
|
||||
let entry_trigger_space_id = data[offset + 16];
|
||||
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([
|
||||
let residency_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([
|
||||
let latency_us = u32::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([
|
||||
let residency_counter_addr = u64::from_le_bytes([
|
||||
data[offset + 40],
|
||||
data[offset + 41],
|
||||
data[offset + 42],
|
||||
@@ -100,8 +103,19 @@ impl LpitInfo {
|
||||
data[offset + 46],
|
||||
data[offset + 47],
|
||||
]);
|
||||
let counter_frequency = u64::from_le_bytes([
|
||||
data[offset + 48],
|
||||
data[offset + 49],
|
||||
data[offset + 50],
|
||||
data[offset + 51],
|
||||
data[offset + 52],
|
||||
data[offset + 53],
|
||||
data[offset + 54],
|
||||
data[offset + 55],
|
||||
]);
|
||||
entries.push(LpiEntry {
|
||||
unique_id,
|
||||
entry_trigger_space_id,
|
||||
entry_trigger_addr,
|
||||
residency_us,
|
||||
latency_us,
|
||||
@@ -134,6 +148,10 @@ impl LpitInfo {
|
||||
.filter(|e| !e.disabled)
|
||||
.max_by_key(|e| e.residency_us)
|
||||
}
|
||||
|
||||
pub fn best_mwait_hint(&self) -> Option<u32> {
|
||||
self.deepest_lpi().and_then(|e| e.mwait_hint())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -323,26 +341,36 @@ impl SleepStates {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn lpit_parse_native_cstate() {
|
||||
fn lpi_entry(unique_id: u16, residency: u32, space_id: u8, trigger: u64) -> Vec<u8> {
|
||||
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(&56u32.to_le_bytes());
|
||||
data.extend_from_slice(&unique_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(&300u32.to_le_bytes());
|
||||
data.push(space_id);
|
||||
data.extend_from_slice(&[0u8; 3]);
|
||||
data.extend_from_slice(&trigger.to_le_bytes());
|
||||
data.extend_from_slice(&residency.to_le_bytes());
|
||||
data.extend_from_slice(&10u32.to_le_bytes());
|
||||
data.extend_from_slice(&[0u8; 4]);
|
||||
data.extend_from_slice(&0x2000u64.to_le_bytes());
|
||||
data.extend_from_slice(&19200u64.to_le_bytes());
|
||||
data
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lpit_parse_native_cstate() {
|
||||
let data = lpi_entry(1, 300, GAS_SPACE_FIXED_HARDWARE, 0x60);
|
||||
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_eq!(info.entries[0].entry_trigger_addr, 0x60);
|
||||
assert!(!info.entries[0].disabled);
|
||||
assert!(!info.entries[0].no_counter);
|
||||
assert_eq!(info.entries[0].mwait_hint(), Some(0x60));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -359,21 +387,21 @@ mod tests {
|
||||
#[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());
|
||||
}
|
||||
data.extend(lpi_entry(1, 100, GAS_SPACE_FIXED_HARDWARE, 0x20));
|
||||
data.extend(lpi_entry(2, 500, GAS_SPACE_FIXED_HARDWARE, 0x60));
|
||||
data.extend(lpi_entry(3, 50, GAS_SPACE_FIXED_HARDWARE, 0x10));
|
||||
let info = LpitInfo::parse(&data).unwrap();
|
||||
let deepest = info.deepest_lpi().unwrap();
|
||||
assert_eq!(deepest.unique_id, 2);
|
||||
assert_eq!(deepest.residency_us, 500);
|
||||
assert_eq!(info.best_mwait_hint(), Some(0x60));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lpit_no_ffh_hint_when_not_fixed_hardware() {
|
||||
let data = lpi_entry(1, 300, 0x00, 0x60);
|
||||
let info = LpitInfo::parse(&data).unwrap();
|
||||
assert_eq!(info.entries[0].mwait_hint(), None);
|
||||
assert_eq!(info.best_mwait_hint(), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user