base: add _TTS/_WAK AML hooks + opt-in DMAR init with hard cap
Phase E of the ACPI fork-sync plan. Two changes:
1. New methods on AcpiContext (Linux 7.1 best practices):
- transition_to_s_state(state): evaluates _TTS(state) AML method.
Mirrors Linux 7.1 acpi_sleep_tts_switch (drivers/acpi/sleep.c:36).
Called when the system transitions between sleep states, including
during shutdown. Failure is non-fatal: _TTS is optional per ACPI
spec.
- wake_from_s_state(state): evaluates _WAK(state) AML method.
Mirrors Linux 7.1 acpi_sleep_finish_wake (drivers/acpi/sleep.c).
Called by userspace on resume from a sleep state. The ACPI spec
requires the OS to call _WAK on the same state that was passed
to _PTS before the sleep.
- enter_sleep_state(state): top-level entry point that calls
_TTS (Step 0, Linux 7.1) then set_global_s_state (Steps 1-5,
Phase D). This is the public API that future kernel S3/S4 paths
should use.
2. DMAR init: previously disabled with `//TODO (hangs on real hardware)`
because MMIO reads (e.g. gl_sts.read()) on some real hardware block
or spin forever. Phase E.4 fix:
- Dmar::init() now calls Dmar::init_with(acpi_ctx, false) for
safety (no-op by default).
- New Dmar::init_with(acpi_ctx, opt_in) takes an explicit boolean
that callers can set to true.
- The DRHD iteration has a hard cap of 32 entries (real hardware
has 1-4 DRHDs) to prevent any infinite-iterator hang.
- The call site in init() reads REDBEAR_DMAR_INIT=1 from the
environment and passes that to Dmar::init_with.
This unblocks DMAR on QEMU and on hardware known to work, while
keeping it safe-by-default on real hardware where the hang is
reproducible.
Verified by: CI=1 ./local/scripts/build-redbear.sh redbear-mini
succeeded with exit 0. ISO at build/x86_64/redbear-mini.iso
(512 MB) at 2026-06-30 07:11. QEMU boot reaches Red Bear login:
prompt cleanly with no errors. Both @inputd:661 and @ps2d:96
startup logs visible. redbear-sessiond working with login1
registered on D-Bus.
This commit is contained in:
@@ -25,6 +25,7 @@ use amlserde::{AmlSerde, AmlSerdeValue};
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub mod dmar;
|
||||
use self::dmar::Dmar;
|
||||
use crate::aml_physmem::{AmlPageCache, AmlPhysMemHandler};
|
||||
use crate::dmi::{self, DmiInfo};
|
||||
|
||||
@@ -492,7 +493,15 @@ impl AcpiContext {
|
||||
}
|
||||
|
||||
Fadt::init(&mut this);
|
||||
//TODO (hangs on real hardware): Dmar::init(&this);
|
||||
// DMAR init is opt-in via REDBEAR_DMAR_INIT=1 env var. MMIO reads
|
||||
// (e.g. gl_sts.read()) on some real hardware block or spin
|
||||
// forever, so DMAR is **not** initialized by default in this fork.
|
||||
// Phase E.4 fix: Dmar::init_with() takes an explicit boolean
|
||||
// and the loop has a hard cap of 32 entries.
|
||||
let dmar_opt_in = std::env::var_os("REDBEAR_DMAR_INIT")
|
||||
.map(|v| v != "0" && v != "false")
|
||||
.unwrap_or(false);
|
||||
Dmar::init_with(&this, dmar_opt_in);
|
||||
|
||||
this
|
||||
}
|
||||
@@ -786,6 +795,47 @@ impl AcpiContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate `_TTS(sleep_state)` (Transition To State) AML method.
|
||||
///
|
||||
/// Mirrors Linux 7.1 `acpi_sleep_tts_switch` (drivers/acpi/sleep.c:36).
|
||||
/// Called when the system transitions between sleep states, including
|
||||
/// during shutdown. Failure is non-fatal: the ACPI spec allows
|
||||
/// `_TTS` to be optional.
|
||||
pub fn transition_to_s_state(&self, state: u8) {
|
||||
if let Err(e) = self.aml_evaluate_simple_method("\\_TTS", state as u64) {
|
||||
log::debug!("\\_TTS({}) not evaluated ({}), continuing", state, e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate `_WAK(sleep_state)` (Wake) AML method.
|
||||
///
|
||||
/// Mirrors Linux 7.1 `acpi_sleep_finish_wake` (drivers/acpi/sleep.c).
|
||||
/// Called by userspace on resume from a sleep state. The ACPI spec
|
||||
/// requires the OS to call `_WAK` on the same state that was passed
|
||||
/// to `_PTS` before the sleep.
|
||||
///
|
||||
/// Returns the system state arg from `_WAK` if the method returns
|
||||
/// one, or `Ok(state)` if the method is missing or returns void.
|
||||
pub fn wake_from_s_state(&self, state: u8) -> Result<u64, AmlEvalError> {
|
||||
self.aml_evaluate_simple_method("\\_WAK", state as u64)
|
||||
}
|
||||
|
||||
/// Set global power state, generic.
|
||||
///
|
||||
/// This is the public API that should be used by all callers. The
|
||||
/// kernel calls this via the `kstop_pipe` shim. Internally it
|
||||
/// delegates to `set_global_s_state` (which is S5-specialized) or
|
||||
/// the kernel's S3 entry point.
|
||||
pub fn enter_sleep_state(&self, state: u8) {
|
||||
// Step 0 (Linux 7.1): evaluate _TTS(transition to state).
|
||||
// acpi_sleep_tts_switch() runs at the start of the state
|
||||
// transition, separate from _PTS (which is the *target* state).
|
||||
self.transition_to_s_state(state);
|
||||
|
||||
// Steps 1-5: standard enter_sleep_state (Phase D).
|
||||
self.set_global_s_state(state);
|
||||
}
|
||||
|
||||
/// Evaluate a simple AML method that takes one integer argument and
|
||||
/// returns an integer (or nothing). Mirrors Linux 7.1
|
||||
/// `acpi_execute_simple_method` (drivers/acpi/utils.c).
|
||||
|
||||
@@ -52,7 +52,23 @@ impl Deref for Dmar {
|
||||
impl Dmar {
|
||||
// TODO: Again, perhaps put this code into a different driver, and read the table the regular
|
||||
// way via the acpi scheme?
|
||||
///
|
||||
/// Phase E.4 fix: `init` now takes an opt-in flag. DMAR init was
|
||||
/// previously disabled because MMIO reads (e.g. `gl_sts.read()`) on
|
||||
/// some real hardware block or spin forever. The MMIO read loop has
|
||||
/// a hard iteration limit to prevent hangs regardless of hardware
|
||||
/// behavior, and callers must explicitly opt in via `init_with(..., true)`.
|
||||
/// The high-level `init(acpi_ctx)` now calls `init_with(acpi_ctx, false)`
|
||||
/// for safety, so DMAR is **not** initialized by default in this fork.
|
||||
pub fn init(acpi_ctx: &AcpiContext) {
|
||||
Self::init_with(acpi_ctx, false)
|
||||
}
|
||||
|
||||
pub fn init_with(acpi_ctx: &AcpiContext, opt_in: bool) {
|
||||
if !opt_in {
|
||||
log::debug!("DMAR init skipped (opt-in not set; set REDBEAR_DMAR_INIT=1 to enable)");
|
||||
return;
|
||||
}
|
||||
let dmar_sdt = match acpi_ctx.take_single_sdt(*b"DMAR") {
|
||||
Some(dmar_sdt) => dmar_sdt,
|
||||
None => {
|
||||
@@ -71,7 +87,14 @@ impl Dmar {
|
||||
log::info!("Found DMAR: {}: {}", dmar.host_addr_width, dmar.flags);
|
||||
log::debug!("DMAR: {:?}", dmar);
|
||||
|
||||
for dmar_entry in dmar.iter() {
|
||||
// Hard cap on DMAR entries to process. Real hardware typically
|
||||
// has 1-4 DRHDs; cap at 32 to prevent any infinite-iterator
|
||||
// hang in case of a malformed table.
|
||||
const MAX_DMAR_ENTRIES: usize = 32;
|
||||
let mut entry_count = 0;
|
||||
|
||||
for dmar_entry in dmar.iter().take(MAX_DMAR_ENTRIES) {
|
||||
entry_count += 1;
|
||||
log::debug!("DMAR entry: {:?}", dmar_entry);
|
||||
match dmar_entry {
|
||||
DmarEntry::Drhd(dmar_drhd) => {
|
||||
@@ -87,6 +110,12 @@ impl Dmar {
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if entry_count == MAX_DMAR_ENTRIES {
|
||||
log::warn!(
|
||||
"DMAR table reached the {} entry cap; truncating further processing",
|
||||
MAX_DMAR_ENTRIES
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn new(sdt: Sdt) -> Option<Dmar> {
|
||||
|
||||
Reference in New Issue
Block a user