iommu: Add Intel VT-d daemon foundation (Phase 3.2)

- Add intel_vtd.rs module with DMAR parsing, DRHD discovery,
  register definitions, and basic unit initialization
- Update iommu daemon discovery to detect both AMD-Vi (IVRS)
  and Intel VT-d (DMAR) units
- Update IommuScheme to track both amd_units and intel_units
- Intel VT-d init: version check, capability read, disable
  translation, report supported features (QI, IR, EIM)

Full DMA remapping enablement (root table, context entries,
page tables, command buffer) remains as TODO for follow-up.
This commit is contained in:
2026-05-20 21:13:03 +03:00
parent b2eaa8d96f
commit ac2f1ccbc2
3 changed files with 601 additions and 90 deletions
@@ -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<VtdDeviceScope>,
}
/// DMAR device scope entry.
#[derive(Clone, Debug)]
pub struct VtdDeviceScope {
pub typ: u8,
pub enumeration_id: u8,
pub start_bus: u8,
pub path: Vec<VtdDevicePath>,
}
/// 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<MmioMapping>,
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<Vec<IntelVtdUnitInfo>, 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)
}
+115 -64
View File
@@ -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<AmdViUnit>,
amd_units: Vec<AmdViUnit>,
intel_units: Vec<IntelVtdUnit>,
next_id: usize,
handles: BTreeMap<usize, Handle>,
domains: BTreeMap<u16, DomainPageTables>,
@@ -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<AmdViUnit>) -> Self {
pub fn with_units(amd_units: Vec<AmdViUnit>, intel_units: Vec<IntelVtdUnit>) -> 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<u8> {
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<usize>,
) -> core::result::Result<usize, i32> {
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);
}
}
}
}
+87 -26
View File
@@ -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<AmdViUnit>,
amd_units: Vec<AmdViUnit>,
intel_units: Vec<IntelVtdUnit>,
source: DiscoverySource,
kernel_acpi_status: &'static str,
ivrs_path: Option<PathBuf>,
@@ -196,6 +198,17 @@ fn detect_dmar_from_kernel_acpi() -> Result<bool, String> {
Ok(find_kernel_acpi_table(b"DMAR")?.is_some())
}
#[cfg(target_os = "redox")]
fn detect_intel_units_from_kernel_acpi() -> Result<Vec<IntelVtdUnit>, 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<DiscoveryResult, String> {
let dmar_present = match detect_dmar_from_kernel_acpi() {
@@ -206,9 +219,18 @@ fn discover_units() -> Result<DiscoveryResult, String> {
}
};
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<DiscoveryResult, String> {
} 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<DiscoveryResult, String> {
} 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<DiscoveryResult, String> {
} 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<DiscoveryResult, String> {
#[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(())