diff --git a/local/recipes/system/iommu/source/src/intel_vtd.rs b/local/recipes/system/iommu/source/src/intel_vtd.rs new file mode 100644 index 0000000000..f091269266 --- /dev/null +++ b/local/recipes/system/iommu/source/src/intel_vtd.rs @@ -0,0 +1,399 @@ +use log::{debug, info}; +use redox_driver_sys::memory::{CacheType, MmioProt, MmioRegion}; + +const INTEL_VTD_MMIO_BYTES: usize = 0x1000; + +/// Intel VT-d register offsets. +mod offsets { + pub const VERSION: usize = 0x00; + pub const CAP: usize = 0x08; + pub const EXT_CAP: usize = 0x10; + pub const GL_CMD: usize = 0x18; + pub const GL_STS: usize = 0x1C; + pub const ROOT_TABLE: usize = 0x20; + pub const CTX_CMD: usize = 0x28; + pub const FAULT_STS: usize = 0x34; + pub const FAULT_CTRL: usize = 0x38; + pub const FAULT_DATA: usize = 0x3C; + pub const FAULT_ADDR_LO: usize = 0x40; + pub const FAULT_ADDR_HI: usize = 0x44; + pub const INV_QUEUE_HEAD: usize = 0x80; + pub const INV_QUEUE_TAIL: usize = 0x88; + pub const INV_QUEUE_ADDR: usize = 0x90; + pub const INV_CMPL_STS: usize = 0x9C; + pub const INV_CMPL_CTRL: usize = 0xA0; + pub const INV_CMPL_DATA: usize = 0xA4; + pub const INV_CMPL_ADDR_LO: usize = 0xA8; + pub const INV_CMPL_ADDR_HI: usize = 0xAC; + pub const INTR_TABLE: usize = 0xB8; +} + +/// Global Command Register bits. +mod gl_cmd { + pub const SET_ROOT_TABLE: u32 = 1 << 30; + pub const SET_INTR_TABLE: u32 = 1 << 27; + pub const SET_FAULT_LOG: u32 = 1 << 26; + pub const ENABLE_ADVANCED_FAULT_LOGGING: u32 = 1 << 25; + pub const ENABLE_INTR_REMAPPING: u32 = 1 << 24; + pub const SET_INTR_REMAP_TABLE: u32 = 1 << 23; + pub const SET_QUEUED_INVALIDATION: u32 = 1 << 26; + pub const ENABLE_TRANSLATION: u32 = 1 << 31; + pub const REQ_GCMD_COMPAT: u32 = 1 << 30; + pub const SET_ROOT_TABLE_POINTER: u32 = 1 << 26; + pub const SET_CMD_BUFFER: u32 = 1 << 29; +} + +/// Global Status Register bits. +mod gl_sts { + pub const ROOT_TABLE_READY: u32 = 1 << 30; + pub const INTR_TABLE_READY: u32 = 1 << 27; + pub const FAULT_LOG_READY: u32 = 1 << 26; + pub const ADVANCED_FAULT_LOGGING_ENABLED: u32 = 1 << 25; + pub const INTR_REMAPPING_ENABLED: u32 = 1 << 24; + pub const QUEUED_INVALIDATION_ENABLED: u32 = 1 << 26; + pub const TRANSLATION_ENABLED: u32 = 1 << 31; + pub const COMPATIBILITY_FORMAT: u32 = 1 << 30; + pub const ROOT_TABLE_POINTER_READY: u32 = 1 << 26; + pub const CMD_BUFFER_ENABLED: u32 = 1 << 29; +} + +/// Capability Register bits. +mod cap { + pub const ND_MASK: u64 = 0x7; + pub const ND_SHIFT: u64 = 0; + pub const AFL: u64 = 1 << 3; + pub const RWBF: u64 = 1 << 4; + pub const PLMR: u64 = 1 << 5; + pub const PHMR: u64 = 1 << 6; + pub const CM: u64 = 1 << 7; + pub const SAGAW_MASK: u64 = 0x1F; + pub const SAGAW_SHIFT: u64 = 8; + pub const MGAW_MASK: u64 = 0x3F; + pub const MGAW_SHIFT: u64 = 16; + pub const ZLR: u64 = 1 << 22; + pub const FRO_MASK: u64 = 0x3FF; + pub const FRO_SHIFT: u64 = 24; + pub const SLLPS_MASK: u64 = 0xF; + pub const SLLPS_SHIFT: u64 = 34; + pub const PSI: u64 = 1 << 39; + pub const NFR_MASK: u64 = 0xFF; + pub const NFR_SHIFT: u64 = 40; + pub const MAMV_MASK: u64 = 0x3F; + pub const MAMV_SHIFT: u64 = 48; + pub const DWD: u64 = 1 << 54; + pub const DRD: u64 = 1 << 55; + pub const FL1GP: u64 = 1 << 56; + pub const PI: u64 = 1 << 59; +} + +/// Extended Capability Register bits. +mod ext_cap { + pub const C: u64 = 1 << 0; + pub const QI: u64 = 1 << 1; + pub const DT: u64 = 1 << 2; + pub const IR: u64 = 1 << 3; + pub const EIM: u64 = 1 << 4; + pub const CH: u64 = 1 << 5; + pub const PT: u64 = 1 << 6; + pub const SC: u64 = 1 << 7; + pub const IRO_MASK: u64 = 0x3FF; + pub const IRO_SHIFT: u64 = 8; + pub const MHMV_MASK: u64 = 0xF; + pub const MHMV_SHIFT: u64 = 20; +} + +/// Parsed DMAR DRHD entry info. +#[derive(Clone, Debug)] +pub struct IntelVtdUnitInfo { + pub segment: u16, + pub mmio_base: u64, + pub include_pci_all: bool, + pub device_scope: Vec, +} + +/// DMAR device scope entry. +#[derive(Clone, Debug)] +pub struct VtdDeviceScope { + pub typ: u8, + pub enumeration_id: u8, + pub start_bus: u8, + pub path: Vec, +} + +/// PCI device path entry (dev, func). +#[derive(Clone, Debug)] +pub struct VtdDevicePath { + pub device: u8, + pub function: u8, +} + +impl IntelVtdUnitInfo { + pub fn handles_device(&self, bdf: crate::acpi::Bdf) -> bool { + if self.include_pci_all { + return true; + } + for scope in &self.device_scope { + if scope.start_bus == bdf.bus() { + for path in &scope.path { + if path.device == bdf.device() && path.function == bdf.function() { + return true; + } + } + } + } + false + } +} + +struct MmioMapping { + region: MmioRegion, +} + +impl MmioMapping { + fn read32(&self, offset: usize) -> u32 { + self.region.read32(offset) + } + fn read64(&self, offset: usize) -> u64 { + let lo = self.region.read32(offset) as u64; + let hi = self.region.read32(offset + 4) as u64; + lo | (hi << 32) + } + fn write32(&mut self, offset: usize, val: u32) { + self.region.write32(offset, val); + } + fn write64(&mut self, offset: usize, val: u64) { + self.region.write32(offset, val as u32); + self.region.write32(offset + 4, (val >> 32) as u32); + } +} + +pub struct IntelVtdUnit { + info: IntelVtdUnitInfo, + mmio: Option, + initialized: bool, +} + +impl IntelVtdUnit { + pub fn from_info(info: IntelVtdUnitInfo) -> Self { + Self { + info, + mmio: None, + initialized: false, + } + } + + pub fn info(&self) -> &IntelVtdUnitInfo { + &self.info + } + + pub fn initialized(&self) -> bool { + self.initialized + } + + pub fn handles_device(&self, bdf: crate::acpi::Bdf) -> bool { + self.info.handles_device(bdf) + } + + pub fn init(&mut self) -> Result<(), String> { + if self.initialized { + return Ok(()); + } + + let region = MmioRegion::map( + self.info.mmio_base, + INTEL_VTD_MMIO_BYTES, + CacheType::Uncacheable, + MmioProt::READ_WRITE, + ) + .map_err(|err| { + format!( + "failed to map Intel VT-d MMIO {:#x}: {err}", + self.info.mmio_base + ) + })?; + self.mmio = Some(MmioMapping { region }); + + let version = self.read_version(); + info!( + "Intel VT-d {}.{} at {:#x}", + version >> 4, + version & 0xF, + self.info.mmio_base + ); + + let cap = self.read_cap(); + let ext_cap = self.read_ext_cap(); + + debug!("VT-d CAP: {:#018x}", cap); + debug!("VT-d ECAP: {:#018x}", ext_cap); + + let nd = ((cap >> cap::ND_SHIFT) & cap::ND_MASK) as u8; + let sagaw = ((cap >> cap::SAGAW_SHIFT) & cap::SAGAW_MASK) as u8; + let mgaw = ((cap >> cap::MGAW_SHIFT) & cap::MGAW_MASK) as u8; + + info!("VT-d: ND={} SAGAW={:#x} MGAW={}", nd, sagaw, mgaw); + + if ext_cap & ext_cap::QI != 0 { + info!("VT-d: Queued Invalidation supported"); + } + if ext_cap & ext_cap::IR != 0 { + info!("VT-d: Interrupt Remapping supported"); + } + if ext_cap & ext_cap::EIM != 0 { + info!("VT-d: Extended Interrupt Mode supported"); + } + + self.disable_translation()?; + + // TODO: Allocate and program root table, context entries, page tables. + // TODO: Set up queued invalidation command buffer. + // TODO: Enable translation. + + self.initialized = true; + Ok(()) + } + + fn read_version(&self) -> u32 { + self.mmio.as_ref().map(|m| m.read32(offsets::VERSION)).unwrap_or(0) + } + + fn read_cap(&self) -> u64 { + self.mmio.as_ref().map(|m| m.read64(offsets::CAP)).unwrap_or(0) + } + + fn read_ext_cap(&self) -> u64 { + self.mmio.as_ref().map(|m| m.read64(offsets::EXT_CAP)).unwrap_or(0) + } + + fn read_gl_cmd(&self) -> u32 { + self.mmio.as_ref().map(|m| m.read32(offsets::GL_CMD)).unwrap_or(0) + } + + fn write_gl_cmd(&mut self, val: u32) { + if let Some(ref mut m) = self.mmio { + m.write32(offsets::GL_CMD, val); + } + } + + fn read_gl_sts(&self) -> u32 { + self.mmio.as_ref().map(|m| m.read32(offsets::GL_STS)).unwrap_or(0) + } + + fn disable_translation(&mut self) -> Result<(), String> { + let mut cmd = self.read_gl_cmd(); + if cmd & gl_cmd::ENABLE_TRANSLATION != 0 { + cmd &= !gl_cmd::ENABLE_TRANSLATION; + self.write_gl_cmd(cmd); + // Wait for status to clear. + for _ in 0..1000 { + if self.read_gl_sts() & gl_sts::TRANSLATION_ENABLED == 0 { + return Ok(()); + } + } + return Err("VT-d: timeout disabling translation".to_string()); + } + Ok(()) + } + + /// Program the root table address. + fn set_root_table(&mut self, addr: u64) -> Result<(), String> { + if let Some(ref mut m) = self.mmio { + m.write64(offsets::ROOT_TABLE, addr); + } + let mut cmd = self.read_gl_cmd(); + cmd |= gl_cmd::SET_ROOT_TABLE_POINTER; + self.write_gl_cmd(cmd); + for _ in 0..1000 { + if self.read_gl_sts() & gl_sts::ROOT_TABLE_POINTER_READY != 0 { + return Ok(()); + } + } + Err("VT-d: timeout setting root table".to_string()) + } +} + +/// Parse DMAR table bytes and extract DRHD units. +pub fn parse_dmar(dmar: &[u8]) -> Result, String> { + if dmar.len() < 48 { + return Err("DMAR table too short".to_string()); + } + let signature = &dmar[0..4]; + if signature != b"DMAR" { + return Err(format!("Expected DMAR signature, got {:?}", signature)); + } + let length = u32::from_le_bytes([dmar[4], dmar[5], dmar[6], dmar[7]]) as usize; + if length > dmar.len() { + return Err("DMAR length exceeds buffer".to_string()); + } + + let host_addr_width = dmar[36]; + let flags = dmar[37]; + info!("DMAR: host_addr_width={} flags={:#x}", host_addr_width, flags); + + let mut units = Vec::new(); + let mut offset = 48; + while offset + 16 <= length { + let typ = dmar[offset]; + let entry_len = u16::from_le_bytes([dmar[offset + 2], dmar[offset + 3]]) as usize; + if entry_len == 0 || offset + entry_len > length { + break; + } + match typ { + 0 => { + // DRHD + let flags = dmar[offset + 1]; + let segment = u16::from_le_bytes([dmar[offset + 4], dmar[offset + 5]]); + let mmio_base = u64::from_le_bytes([ + dmar[offset + 8], + dmar[offset + 9], + dmar[offset + 10], + dmar[offset + 11], + dmar[offset + 12], + dmar[offset + 13], + dmar[offset + 14], + dmar[offset + 15], + ]); + let include_pci_all = flags & 0x1 != 0; + let mut device_scope = Vec::new(); + let mut scope_offset = offset + 16; + while scope_offset + 6 <= offset + entry_len { + let scope_typ = dmar[scope_offset]; + let scope_len = + u16::from_le_bytes([dmar[scope_offset + 2], dmar[scope_offset + 3]]) + as usize; + if scope_len == 0 || scope_offset + scope_len > offset + entry_len { + break; + } + let enumeration_id = dmar[scope_offset + 1]; + let start_bus = dmar[scope_offset + 4]; + let mut path = Vec::new(); + let mut path_offset = scope_offset + 6; + while path_offset + 2 <= scope_offset + scope_len { + path.push(VtdDevicePath { + device: dmar[path_offset], + function: dmar[path_offset + 1], + }); + path_offset += 2; + } + device_scope.push(VtdDeviceScope { + typ: scope_typ, + enumeration_id, + start_bus, + path, + }); + scope_offset += scope_len; + } + units.push(IntelVtdUnitInfo { + segment, + mmio_base, + include_pci_all, + device_scope, + }); + } + _ => {} + } + offset += entry_len; + } + + Ok(units) +} diff --git a/local/recipes/system/iommu/source/src/lib.rs b/local/recipes/system/iommu/source/src/lib.rs index 44e5c1a3d7..8327904af1 100644 --- a/local/recipes/system/iommu/source/src/lib.rs +++ b/local/recipes/system/iommu/source/src/lib.rs @@ -4,6 +4,7 @@ pub mod acpi; pub mod amd_vi; pub mod command_buffer; pub mod device_table; +pub mod intel_vtd; pub mod interrupt; pub mod mmio; pub mod page_table; @@ -12,6 +13,7 @@ use std::collections::BTreeMap; use acpi::{parse_bdf, Bdf}; use amd_vi::AmdViUnit; +use intel_vtd::IntelVtdUnit; use page_table::{DomainPageTables, MappingFlags}; use redox_scheme::SchemeBlockMut; use syscall::data::Stat; @@ -161,7 +163,8 @@ struct Handle { } pub struct IommuScheme { - units: Vec, + amd_units: Vec, + intel_units: Vec, next_id: usize, handles: BTreeMap, domains: BTreeMap, @@ -170,12 +173,13 @@ pub struct IommuScheme { impl IommuScheme { pub fn new() -> Self { - Self::with_units(Vec::new()) + Self::with_units(Vec::new(), Vec::new()) } - pub fn with_units(units: Vec) -> Self { + pub fn with_units(amd_units: Vec, intel_units: Vec) -> Self { Self { - units, + amd_units, + intel_units, next_id: 0, handles: BTreeMap::new(), domains: BTreeMap::new(), @@ -184,7 +188,7 @@ impl IommuScheme { } pub fn unit_count(&self) -> usize { - self.units.len() + self.amd_units.len() + self.intel_units.len() } fn insert_handle(&mut self, kind: HandleKind) -> usize { @@ -216,40 +220,67 @@ impl IommuScheme { } fn ensure_unit_initialized(&mut self, unit_index: usize) -> core::result::Result<(), i32> { - let Some(unit) = self.units.get_mut(unit_index) else { - return Err(ENODEV as i32); - }; - - if unit.initialized() { - return Ok(()); + if let Some(unit) = self.amd_units.get_mut(unit_index) { + if unit.initialized() { + return Ok(()); + } + return unit.init().map_err(|err| { + log::error!( + "iommu: failed to initialize AMD-Vi unit {} at MMIO {:#x}: {}", + unit_index, + unit.info().mmio_base, + err + ); + EIO as i32 + }); } - - unit.init().map_err(|err| { - log::error!( - "iommu: failed to initialize unit {} at MMIO {:#x}: {}", - unit_index, - unit.info().mmio_base, - err - ); - EIO as i32 - }) + let intel_index = unit_index.saturating_sub(self.amd_units.len()); + if let Some(unit) = self.intel_units.get_mut(intel_index) { + if unit.initialized() { + return Ok(()); + } + return unit.init().map_err(|err| { + log::error!( + "iommu: failed to initialize Intel VT-d unit {} at MMIO {:#x}: {}", + intel_index, + unit.info().mmio_base, + err + ); + EIO as i32 + }); + } + Err(ENODEV as i32) } fn root_listing(&self) -> Vec { let mut listing = String::from("control\n"); - for (index, unit) in self.units.iter().enumerate() { + for (index, unit) in self.amd_units.iter().enumerate() { let state = if unit.initialized() { "initialized" } else { "detected" }; listing.push_str(&format!( - "unit/{index} {} mmio={:#x} state={}\n", + "unit/{index} {} mmio={:#x} state={} type=amd\n", unit.info().iommu_bdf, unit.info().mmio_base, state )); } + let intel_offset = self.amd_units.len(); + for (index, unit) in self.intel_units.iter().enumerate() { + let state = if unit.initialized() { + "initialized" + } else { + "detected" + }; + listing.push_str(&format!( + "unit/{} mmio={:#x} state={} type=intel\n", + intel_offset + index, + unit.info().mmio_base, + state + )); + } for domain_id in self.domains.keys() { listing.push_str(&format!("domain/{domain_id}\n")); } @@ -295,19 +326,31 @@ impl IommuScheme { requested_unit: Option, ) -> core::result::Result { if let Some(index) = requested_unit { - let Some(unit) = self.units.get(index) else { + if let Some(unit) = self.amd_units.get(index) { + if unit.handles_device(bdf) { + return Ok(index); + } return Err(ENODEV as i32); - }; - if unit.handles_device(bdf) { - return Ok(index); + } + let intel_index = index.saturating_sub(self.amd_units.len()); + if let Some(unit) = self.intel_units.get(intel_index) { + if unit.handles_device(bdf) { + return Ok(index); + } } return Err(ENODEV as i32); } - self.units - .iter() - .position(|unit| unit.handles_device(bdf)) - .ok_or(ENODEV as i32) + if let Some(index) = self.amd_units.iter().position(|unit| unit.handles_device(bdf)) { + return Ok(index); + } + let intel_offset = self.amd_units.len(); + if let Some(index) = self.intel_units.iter().position(|unit| { + unit.handles_device(bdf) + }) { + return Ok(intel_offset + index); + } + Err(ENODEV as i32) } fn dispatch_request(&mut self, kind: HandleKind, request: IommuRequest) -> IommuResponse { @@ -327,10 +370,11 @@ impl IommuScheme { match request.opcode { opcode::QUERY => IommuResponse::success( request.opcode, - self.units.len() as u32, + self.unit_count() as u32, self.domains.len() as u64, self.device_assignments.len() as u64, - self.units.iter().filter(|unit| unit.initialized()).count() as u64, + self.amd_units.iter().filter(|unit| unit.initialized()).count() as u64 + + self.intel_units.iter().filter(|unit| unit.initialized()).count() as u64, ), opcode::INIT_UNITS => { let requested_index = if request.arg0 == u32::MAX { @@ -341,17 +385,18 @@ impl IommuScheme { let mut initialized_now = 0u32; let mut attempted = 0u64; - for index in 0..self.units.len() { + let total_units = self.unit_count(); + for index in 0..total_units { if requested_index.is_some() && requested_index != Some(index) { continue; } attempted += 1; - let was_initialized = self - .units - .get(index) - .map(|unit| unit.initialized()) - .unwrap_or(false); + let was_initialized = if index < self.amd_units.len() { + self.amd_units.get(index).map(|unit| unit.initialized()).unwrap_or(false) + } else { + self.intel_units.get(index - self.amd_units.len()).map(|unit| unit.initialized()).unwrap_or(false) + }; if let Err(errno) = self.ensure_unit_initialized(index) { return IommuResponse::error(request.opcode, errno); @@ -363,7 +408,8 @@ impl IommuScheme { } let initialized_total = - self.units.iter().filter(|unit| unit.initialized()).count() as u64; + self.amd_units.iter().filter(|unit| unit.initialized()).count() as u64 + + self.intel_units.iter().filter(|unit| unit.initialized()).count() as u64; IommuResponse::success( request.opcode, @@ -425,7 +471,7 @@ impl IommuScheme { let mut first_device = 0u64; let mut first_address = 0u64; - for (index, unit) in self.units.iter_mut().enumerate() { + for (index, unit) in self.amd_units.iter_mut().enumerate() { if requested_index.is_some() && requested_index != Some(index) { continue; } @@ -577,22 +623,25 @@ impl IommuScheme { return IommuResponse::error(request.opcode, ENOENT as i32); }; - let Some(unit) = self.units.get_mut(unit_index) else { - return IommuResponse::error(request.opcode, ENODEV as i32); - }; - - match unit.assign_device(bdf, domain) { - Ok(()) => { - self.device_assignments.insert(bdf, (domain_id, unit_index)); - IommuResponse::success( - request.opcode, - domain_id as u32, - unit_index as u64, - u64::from(bdf.raw()), - 0, - ) + if unit_index < self.amd_units.len() { + let Some(unit) = self.amd_units.get_mut(unit_index) else { + return IommuResponse::error(request.opcode, ENODEV as i32); + }; + match unit.assign_device(bdf, domain) { + Ok(()) => { + self.device_assignments.insert(bdf, (domain_id, unit_index)); + IommuResponse::success( + request.opcode, + domain_id as u32, + unit_index as u64, + u64::from(bdf.raw()), + 0, + ) + } + Err(_) => IommuResponse::error(request.opcode, EIO as i32), } - Err(_) => IommuResponse::error(request.opcode, EIO as i32), + } else { + IommuResponse::error(request.opcode, ENODEV as i32) } } opcode::UNASSIGN_DEVICE => { @@ -600,14 +649,16 @@ impl IommuScheme { return IommuResponse::error(request.opcode, ENOENT as i32); }; - let unit = self.units.get_mut(unit_index); - if let Some(unit) = unit { - if unit.initialized() { - if let Err(err) = unit.unassign_device(bdf) { - log::error!( - "iommu: failed to invalidate DTE for {bdf} on unit {unit_index}: {err}" - ); - return IommuResponse::error(request.opcode, EIO as i32); + if unit_index < self.amd_units.len() { + let unit = self.amd_units.get_mut(unit_index); + if let Some(unit) = unit { + if unit.initialized() { + if let Err(err) = unit.unassign_device(bdf) { + log::error!( + "iommu: failed to invalidate DTE for {bdf} on unit {unit_index}: {err}" + ); + return IommuResponse::error(request.opcode, EIO as i32); + } } } } diff --git a/local/recipes/system/iommu/source/src/main.rs b/local/recipes/system/iommu/source/src/main.rs index 159b70f0cb..4ea0ddac33 100644 --- a/local/recipes/system/iommu/source/src/main.rs +++ b/local/recipes/system/iommu/source/src/main.rs @@ -9,6 +9,7 @@ use std::path::PathBuf; use std::process; use iommu::amd_vi::AmdViUnit; +use iommu::intel_vtd::{IntelVtdUnit, parse_dmar}; #[cfg(target_os = "redox")] use iommu::IommuScheme; use log::{error, info, LevelFilter, Metadata, Record}; @@ -27,7 +28,8 @@ struct StderrLogger { #[cfg_attr(not(target_os = "redox"), allow(dead_code))] struct DiscoveryResult { - units: Vec, + amd_units: Vec, + intel_units: Vec, source: DiscoverySource, kernel_acpi_status: &'static str, ivrs_path: Option, @@ -196,6 +198,17 @@ fn detect_dmar_from_kernel_acpi() -> Result { Ok(find_kernel_acpi_table(b"DMAR")?.is_some()) } +#[cfg(target_os = "redox")] +fn detect_intel_units_from_kernel_acpi() -> Result, String> { + match find_kernel_acpi_table(b"DMAR")? { + Some(table) => { + let infos = parse_dmar(&table).map_err(|err| format!("failed to parse DMAR: {err}"))?; + Ok(infos.into_iter().map(IntelVtdUnit::from_info).collect()) + } + None => Ok(Vec::new()), + } +} + #[cfg(target_os = "redox")] fn discover_units() -> Result { let dmar_present = match detect_dmar_from_kernel_acpi() { @@ -206,9 +219,18 @@ fn discover_units() -> Result { } }; + let intel_units = match detect_intel_units_from_kernel_acpi() { + Ok(units) => units, + Err(err) => { + info!("iommu: Intel VT-d discovery unavailable: {err}"); + Vec::new() + } + }; + match detect_units_from_kernel_acpi() { Ok(units) if !units.is_empty() => Ok(DiscoveryResult { - units, + amd_units: units, + intel_units, source: DiscoverySource::KernelAcpi, kernel_acpi_status: "ok", ivrs_path: None, @@ -222,7 +244,8 @@ fn discover_units() -> Result { } else { DiscoverySource::None }, - units, + amd_units: units, + intel_units, kernel_acpi_status: "empty", ivrs_path, dmar_present, @@ -237,7 +260,8 @@ fn discover_units() -> Result { } else { DiscoverySource::None }, - units, + amd_units: units, + intel_units, kernel_acpi_status: "error", ivrs_path, dmar_present, @@ -255,7 +279,8 @@ fn discover_units() -> Result { } else { DiscoverySource::None }, - units, + amd_units: units, + intel_units: Vec::new(), kernel_acpi_status: "unsupported", ivrs_path, dmar_present: false, @@ -265,9 +290,9 @@ fn discover_units() -> Result { #[cfg(target_os = "redox")] fn run() -> Result<(), String> { let discovery = discover_units()?; - if discovery.units.is_empty() { + if discovery.amd_units.is_empty() && discovery.intel_units.is_empty() { info!( - "iommu: no AMD-Vi units found (source={}, kernel_acpi_status={}, ivrs_path={})", + "iommu: no IOMMU units found (source={}, kernel_acpi_status={}, ivrs_path={})", discovery.source.as_str(), discovery.kernel_acpi_status, discovery @@ -277,20 +302,35 @@ fn run() -> Result<(), String> { .unwrap_or_else(|| "none".to_string()) ); } else { + if !discovery.amd_units.is_empty() { + info!( + "iommu: detected {} AMD-Vi unit(s) via {}", + discovery.amd_units.len(), + discovery.source.as_str() + ); + } + if !discovery.intel_units.is_empty() { + info!( + "iommu: detected {} Intel VT-d unit(s)", + discovery.intel_units.len() + ); + } + } + if discovery.dmar_present && discovery.intel_units.is_empty() { info!( - "iommu: detected {} AMD-Vi unit(s) via {}", - discovery.units.len(), - discovery.source.as_str() + "iommu: detected kernel ACPI DMAR table but failed to parse DRHD entries" ); } - if discovery.dmar_present { + for (index, unit) in discovery.amd_units.iter().enumerate() { info!( - "iommu: detected kernel ACPI DMAR table; Intel VT-d runtime ownership should converge here rather than remain in acpid" + "iommu: discovered AMD-Vi unit {} at MMIO {:#x}; initialization is deferred until first use", + index, + unit.info().mmio_base ); } - for (index, unit) in discovery.units.iter().enumerate() { + for (index, unit) in discovery.intel_units.iter().enumerate() { info!( - "iommu: discovered unit {} at MMIO {:#x}; initialization is deferred until first use", + "iommu: discovered Intel VT-d unit {} at MMIO {:#x}; initialization is deferred until first use", index, unit.info().mmio_base ); @@ -300,7 +340,7 @@ fn run() -> Result<(), String> { Socket::create("iommu").map_err(|e| format!("failed to register iommu scheme: {e}"))?; info!("iommu: registered scheme:iommu"); - let mut scheme = IommuScheme::with_units(discovery.units); + let mut scheme = IommuScheme::with_units(discovery.amd_units, discovery.intel_units); loop { let request = match socket.next_request(SignalBehavior::Restart) { @@ -338,7 +378,9 @@ fn run() -> Result<(), String> { #[cfg(target_os = "redox")] fn run_self_test() -> Result<(), String> { let discovery = discover_units()?; - let mut units = discovery.units; + let mut amd_units = discovery.amd_units; + let mut intel_units = discovery.intel_units; + let total_units = amd_units.len() + intel_units.len(); println!("discovery_source={}", discovery.source.as_str()); println!("kernel_acpi_status={}", discovery.kernel_acpi_status); @@ -351,19 +393,20 @@ fn run_self_test() -> Result<(), String> { .map(|path| path.display().to_string()) .unwrap_or_else(|| "none".to_string()) ); - println!("units_detected={}", units.len()); - if units.is_empty() { - return Err("iommu self-test detected zero AMD-Vi unit(s)".to_string()); + println!("amd_units_detected={}", amd_units.len()); + println!("intel_units_detected={}", intel_units.len()); + if total_units == 0 { + return Err("iommu self-test detected zero IOMMU units".to_string()); } let mut initialized_now = 0u32; let mut events_drained = 0u32; - for (index, unit) in units.iter_mut().enumerate() { + for (index, unit) in amd_units.iter_mut().enumerate() { let was_initialized = unit.initialized(); unit.init().map_err(|err| { format!( - "iommu self-test failed to initialize unit {} at MMIO {:#x}: {}", + "iommu self-test failed to initialize AMD-Vi unit {} at MMIO {:#x}: {}", index, unit.info().mmio_base, err @@ -376,7 +419,7 @@ fn run_self_test() -> Result<(), String> { let drained = unit.drain_events().map_err(|err| { format!( - "iommu self-test failed to drain events for unit {} at MMIO {:#x}: {}", + "iommu self-test failed to drain events for AMD-Vi unit {} at MMIO {:#x}: {}", index, unit.info().mmio_base, err @@ -385,9 +428,26 @@ fn run_self_test() -> Result<(), String> { events_drained = events_drained.saturating_add(drained.len() as u32); } - let initialized_after = units.iter().filter(|unit| unit.initialized()).count() as u64; + for (index, unit) in intel_units.iter_mut().enumerate() { + let was_initialized = unit.initialized(); + unit.init().map_err(|err| { + format!( + "iommu self-test failed to initialize Intel VT-d unit {} at MMIO {:#x}: {}", + index, + unit.info().mmio_base, + err + ) + })?; + + if !was_initialized { + initialized_now = initialized_now.saturating_add(1); + } + } + + let initialized_after = amd_units.iter().filter(|unit| unit.initialized()).count() as u64 + + intel_units.iter().filter(|unit| unit.initialized()).count() as u64; println!("units_initialized_now={}", initialized_now); - println!("units_attempted={}", units.len()); + println!("units_attempted={}", total_units); println!("units_initialized_after={}", initialized_after); println!("events_drained={}", events_drained); @@ -398,8 +458,9 @@ fn run_self_test() -> Result<(), String> { fn run() -> Result<(), String> { let discovery = discover_units()?; info!( - "iommu: host build stub active; parsed {} AMD-Vi unit(s) via {}", - discovery.units.len(), + "iommu: host build stub active; parsed {} AMD-Vi and {} Intel VT-d unit(s) via {}", + discovery.amd_units.len(), + discovery.intel_units.len(), discovery.source.as_str() ); Ok(())