Fix IOMMU unassign bug and add translate opcode

unassign_device: clear DTE and submit hardware INVALIDATE_DEVTAB_ENTRY
and INVALIDATE_INTERRUPT_TABLE commands with completion wait (was
previously only clearing the software HashMap).

TRANSLATE opcode (0x0012): walk IOMMU page tables for IOVA-to-physical
address resolution.

fstat: return proper MODE_DIR/MODE_FILE and sizes for all handle kinds
(Root, Control, Domain, Device).

Remove #TODO from recipe.toml.
This commit is contained in:
2026-04-25 18:07:58 +01:00
parent 530eb841db
commit 02ff2f554f
4 changed files with 82 additions and 11 deletions
+3 -2
View File
@@ -1,5 +1,6 @@
#TODO: IOMMU daemon — needs hardware validation with QEMU amd-iommu device
# Provides scheme:iommu for DMA remapping and device isolation.
# IOMMU daemon — provides scheme:iommu for DMA remapping and device isolation.
# Supports AMD-Vi units with domain management, IOVA mapping, device assignment,
# interrupt remapping, and IOVA-to-physical address translation.
[source]
path = "source"
@@ -165,6 +165,29 @@ impl AmdViUnit {
Ok(())
}
pub fn unassign_device(&mut self, bdf: Bdf) -> Result<(), String> {
if !self.initialized {
return Err("AMD-Vi unit is not initialized".to_string());
}
if !self.handles_device(bdf) {
return Err(format!(
"AMD-Vi unit {} does not cover device {bdf}",
self.info.unit_id()
));
}
let device_table = self
.device_table
.as_mut()
.ok_or_else(|| "device table not initialized".to_string())?;
device_table.clear_entry(bdf.raw());
self.submit_command(CommandEntry::invalidate_devtab_entry(bdf.raw()))?;
self.submit_command(CommandEntry::invalidate_interrupt_table(bdf.raw()))?;
self.wait_for_completion()?;
Ok(())
}
pub fn drain_events(&mut self) -> Result<Vec<AmdViEvent>, String> {
let mut drained = Vec::new();
if !self.initialized {
+52 -9
View File
@@ -27,6 +27,7 @@ pub mod opcode {
pub const INIT_UNITS: u16 = 0x0003;
pub const MAP: u16 = 0x0010;
pub const UNMAP: u16 = 0x0011;
pub const TRANSLATE: u16 = 0x0012;
pub const ASSIGN_DEVICE: u16 = 0x0020;
pub const UNASSIGN_DEVICE: u16 = 0x0021;
pub const DRAIN_EVENTS: u16 = 0x0030;
@@ -512,6 +513,22 @@ impl IommuScheme {
Err(_) => IommuResponse::error(request.opcode, ENOENT as i32),
}
}
opcode::TRANSLATE => {
let iova = request.arg1;
let Some(domain) = self.domains.get(&domain_id) else {
return IommuResponse::error(request.opcode, ENOENT as i32);
};
match domain.translate(iova) {
Some(phys) => IommuResponse::success(
request.opcode,
domain_id as u32,
iova,
phys,
0,
),
None => IommuResponse::error(request.opcode, ENOENT as i32),
}
}
_ => IommuResponse::error(request.opcode, EINVAL as i32),
}
}
@@ -579,10 +596,29 @@ impl IommuScheme {
}
}
opcode::UNASSIGN_DEVICE => {
if self.device_assignments.remove(&bdf).is_none() {
let Some((domain_id, unit_index)) = self.device_assignments.remove(&bdf) else {
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);
}
}
}
IommuResponse::success(request.opcode, 0, u64::from(bdf.raw()), 0, 0)
IommuResponse::success(
request.opcode,
domain_id as u32,
u64::from(bdf.raw()),
unit_index as u64,
0,
)
}
_ => IommuResponse::error(request.opcode, EINVAL as i32),
}
@@ -716,14 +752,21 @@ impl SchemeBlockMut for IommuScheme {
stat.st_mode = MODE_DIR | 0o555;
stat.st_size = self.root_listing().len() as u64;
}
_ => {
let response_len = self
.handles
.get(&id)
.map(|handle| handle.response.len())
.ok_or(Error::new(EBADF))?;
HandleKind::Control => {
stat.st_mode = MODE_FILE | 0o666;
stat.st_size = response_len as u64;
stat.st_size = IommuResponse::SIZE as u64;
}
HandleKind::Domain(domain_id) => {
stat.st_mode = MODE_FILE | 0o666;
stat.st_size = self
.domains
.get(&domain_id)
.map(|domain| domain.mapping_count() as u64)
.unwrap_or(0);
}
HandleKind::Device(_) => {
stat.st_mode = MODE_FILE | 0o666;
stat.st_size = 0;
}
}
stat.st_blksize = 4096;
@@ -628,6 +628,10 @@ impl DomainPageTables {
pub fn mapping_count(&self) -> usize {
self.mappings.len()
}
pub fn translate(&self, iova: u64) -> Option<u64> {
self.page_table.translate(iova)
}
}
fn align_up(value: u64, align: u64) -> Option<u64> {