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:
Red Bear OS
2026-07-22 22:08:23 +09:00
parent 0485ae662a
commit b5f84b44df
6 changed files with 181 additions and 45 deletions
+55 -1
View File
@@ -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(&region.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`,
+19
View File
@@ -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,
}
}
}
+4
View File
@@ -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 {
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}