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:
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user