diff --git a/local/recipes/system/iommu/source/src/lib.rs b/local/recipes/system/iommu/source/src/lib.rs index 8327904af1..fddf971513 100644 --- a/local/recipes/system/iommu/source/src/lib.rs +++ b/local/recipes/system/iommu/source/src/lib.rs @@ -191,6 +191,14 @@ impl IommuScheme { self.amd_units.len() + self.intel_units.len() } + /// Returns true if at least one AMD-Vi or Intel VT-d unit has been + /// successfully initialized. Used by the daemon to decide when to + /// signal the kernel that IOMMU interrupt remapping is active. + pub fn has_interrupt_remapping(&self) -> bool { + self.amd_units.iter().any(|unit| unit.initialized()) + || self.intel_units.iter().any(|unit| unit.initialized()) + } + fn insert_handle(&mut self, kind: HandleKind) -> usize { let id = self.next_id; self.next_id = self.next_id.saturating_add(1); diff --git a/local/recipes/system/iommu/source/src/main.rs b/local/recipes/system/iommu/source/src/main.rs index b4857aa411..51ecfad93e 100644 --- a/local/recipes/system/iommu/source/src/main.rs +++ b/local/recipes/system/iommu/source/src/main.rs @@ -377,6 +377,7 @@ fn run() -> Result<(), String> { info!("iommu: registered scheme:iommu"); let mut scheme = IommuScheme::with_units(amd_units, discovery.intel_units); + let mut remapping_signalled = false; loop { let request = match socket.next_request(SignalBehavior::Restart) { @@ -403,6 +404,16 @@ fn run() -> Result<(), String> { } }; + // After the daemon has initialized at least one interrupt-remapping + // capable unit, signal the kernel once. We poll the scheme's status + // (cheap, no IPC) instead of introspecting the request. Idempotent + // and harmless. The unconditional deactivation on exit (in main()) + // clears the kernel state if the daemon terminates unexpectedly. + if !remapping_signalled && scheme.has_interrupt_remapping() { + set_kernel_remapping(true); + remapping_signalled = true; + } + if let Err(e) = socket.write_response(response, SignalBehavior::Restart) { error!("iommu: failed to write response: {e}"); } @@ -524,12 +535,54 @@ fn main() { run() }; + // Always deactivate remapping on exit so the kernel does not retain + // stale "remapping active" state if the daemon dies. + set_kernel_remapping(false); + if let Err(e) = result { error!("iommu: fatal error: {e}"); process::exit(1); } } +/// Inform the kernel whether IOMMU interrupt remapping is currently active. +/// +/// The kernel exposes `/scheme/irq/remapping` for this purpose. Writing `b"1"` +/// activates the gate; writing `b"0"` deactivates it. When active, the kernel +/// trusts the IOMMU hardware to validate remapped MSI vectors instead of +/// attempting software-side validation. +/// +/// Failures are logged but not fatal: if the kernel scheme is not reachable +/// (e.g. early boot, QEMU without PCI passthrough), the daemon continues and +/// MSI delivery works without remapping — the kernel handles that path. +fn set_kernel_remapping(active: bool) { + let path = "/scheme/irq/remapping"; + let value: &[u8] = if active { b"1" } else { b"0" }; + match fs::OpenOptions::new().write(true).open(path) { + Ok(mut fd) => { + use std::io::Write; + match fd.write_all(value) { + Ok(_) => info!( + "iommu: kernel remapping {}", + if active { "ACTIVATED" } else { "DEACTIVATED" } + ), + Err(e) => warn!( + "iommu: failed to write {} to {}: {}", + String::from_utf8_lossy(value), + path, + e + ), + } + } + Err(e) => { + // Not fatal — the scheme may not be available in all environments. + // The kernel default (remapping=false) is safe for QEMU and bare metal + // without an IOMMU. + warn!("iommu: cannot open {}: {} (continuing)", path, e); + } + } +} + #[cfg(test)] mod tests { use super::{