diff --git a/drivers/acpid/src/acpi.rs b/drivers/acpid/src/acpi.rs index 92226c4273..7b4c3ea8fb 100644 --- a/drivers/acpid/src/acpi.rs +++ b/drivers/acpid/src/acpi.rs @@ -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 { + 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). diff --git a/drivers/acpid/src/acpi/dmar/mod.rs b/drivers/acpid/src/acpi/dmar/mod.rs index c42b379a10..17ffde7044 100644 --- a/drivers/acpid/src/acpi/dmar/mod.rs +++ b/drivers/acpid/src/acpi/dmar/mod.rs @@ -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 {