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:
@@ -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)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
|
||||
Reference in New Issue
Block a user