From 22c112c57da9cb608cbbdd05a38d82d17458208d Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Fri, 9 Oct 2026 02:48:25 -0700 Subject: [PATCH 1/4] iommufd-ioctls: Introduce IommufdIoas abstraction By having a dedicated structure for IOMMUFD IOAS instances, we follow the IOMMUFD model better and allow for appropriate teardown. By having this new IommufdObject, we can ensure it's not destroyed before the page tables are released. Signed-off-by: Sebastien Boeuf --- iommufd-ioctls/src/iommufd_ioctls.rs | 105 +++++++++++++++++++-------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/iommufd-ioctls/src/iommufd_ioctls.rs b/iommufd-ioctls/src/iommufd_ioctls.rs index 5a1fa02..f514af1 100644 --- a/iommufd-ioctls/src/iommufd_ioctls.rs +++ b/iommufd-ioctls/src/iommufd_ioctls.rs @@ -181,8 +181,42 @@ pub enum IommufdViommuData { Tegra241Cmdqv { mmap_offset: u64, mmap_length: u64 }, } -pub struct IommufdVIommu { +pub struct IommufdIoas { iommufd: Arc, + ioas_id: u32, +} + +impl IommufdIoas { + pub fn new(iommufd: Arc) -> Result { + let mut alloc_data = iommu_ioas_alloc { + size: size_of::() as u32, + flags: 0, + out_ioas_id: 0, + }; + + iommufd.alloc_iommu_ioas(&mut alloc_data)?; + + Ok(Self { + iommufd, + ioas_id: alloc_data.out_ioas_id, + }) + } + + pub fn id(&self) -> u32 { + self.ioas_id + } +} + +impl Drop for IommufdIoas { + fn drop(&mut self) { + if let Err(e) = self.iommufd.destroy_iommu_object(self.ioas_id) { + error!("Failed to destroy IOAS id {}: {e}", self.ioas_id); + } + } +} + +pub struct IommufdVIommu { + ioas: Arc, viommu_id: u32, s2_hwpt_id: u32, bypass_hwpt_id: u32, @@ -193,8 +227,9 @@ pub struct IommufdVIommu { impl IommufdVIommu { #[allow(non_upper_case_globals)] - pub fn new(iommufd: Arc, ioas_id: u32, dev_id: u32, hw_queue: bool) -> Result { - let kind = IommuKind::query(&iommufd, dev_id)?; + pub fn new(ioas: Arc, dev_id: u32, hw_queue: bool) -> Result { + let iommufd = &ioas.iommufd; + let kind = IommuKind::query(iommufd, dev_id)?; if hw_queue && kind != IommuKind::Smmuv3Cmdqv { return Err(IommufdError::HwQueueUnsupported); } @@ -227,7 +262,7 @@ impl IommufdVIommu { size: std::mem::size_of::() as u32, flags: iommufd_hwpt_alloc_flags_IOMMU_HWPT_ALLOC_NEST_PARENT, dev_id, - pt_id: ioas_id, + pt_id: ioas.id(), data_type: iommu_hwpt_data_type_IOMMU_HWPT_DATA_NONE, ..Default::default() }; @@ -261,12 +296,12 @@ impl IommufdVIommu { let viommu_id = viommu_alloc.out_viommu_id; let bypass_hwpt_id = - Self::alloc_s1_hwpt_on(&iommufd, kind, viommu_id, dev_id, &bypass_s1_hwpt_data)?; + Self::alloc_s1_hwpt_on(iommufd, kind, viommu_id, dev_id, &bypass_s1_hwpt_data)?; let abort_hwpt_id = - Self::alloc_s1_hwpt_on(&iommufd, kind, viommu_id, dev_id, &abort_s1_hwpt_data)?; + Self::alloc_s1_hwpt_on(iommufd, kind, viommu_id, dev_id, &abort_s1_hwpt_data)?; Ok(IommufdVIommu { - iommufd, + ioas, viommu_id, s2_hwpt_id, bypass_hwpt_id, @@ -277,7 +312,7 @@ impl IommufdVIommu { } pub fn alloc_s1_hwpt(&self, dev_id: u32, hwpt_data: &IommufdHwptData) -> Result { - Self::alloc_s1_hwpt_on(&self.iommufd, self.kind, self.viommu_id, dev_id, hwpt_data) + Self::alloc_s1_hwpt_on(self.iommufd(), self.kind, self.viommu_id, dev_id, hwpt_data) } fn alloc_s1_hwpt_on( @@ -315,7 +350,7 @@ impl IommufdVIommu { } pub fn iommufd(&self) -> &Arc { - &self.iommufd + &self.ioas.iommufd } pub fn attach_bypass(&self, device: &dyn AttachHwpt) -> Result<()> { @@ -345,7 +380,7 @@ impl IommufdVIommu { data_uptr: data as *mut iommu_viommu_arm_smmuv3_invalidate as u64, ..Default::default() }; - self.iommufd.invalidate_hwpt(&mut hw_invalidate)?; + self.iommufd().invalidate_hwpt(&mut hw_invalidate)?; if hw_invalidate.entry_num == 1 { Ok(true) @@ -393,7 +428,7 @@ impl IommufdVIommu { veventq_depth: depth, ..Default::default() }; - let (veventq_id, file) = self.iommufd.alloc_veventq(&mut veventq_alloc)?; + let (veventq_id, file) = self.iommufd().alloc_veventq(&mut veventq_alloc)?; Ok(IommufdVEventQ { viommu: Arc::clone(self), @@ -426,7 +461,7 @@ impl IommufdVIommu { length, ..Default::default() }; - self.iommufd.alloc_hw_queue(&mut hw_queue_alloc)?; + self.iommufd().alloc_hw_queue(&mut hw_queue_alloc)?; Ok(IommufdHwQueue { viommu: Arc::clone(self), @@ -442,7 +477,7 @@ pub struct IommufdHwQueue { impl Drop for IommufdHwQueue { fn drop(&mut self) { - if let Err(e) = self.viommu.iommufd.destroy_iommu_object(self.hw_queue_id) { + if let Err(e) = self.viommu.iommufd().destroy_iommu_object(self.hw_queue_id) { error!("Failed to destroy HW queue id {}: {e}", self.hw_queue_id); } } @@ -456,7 +491,7 @@ impl Drop for IommufdVIommu { (self.viommu_id, "vIOMMU"), (self.s2_hwpt_id, "s2_hwpt"), ] { - if let Err(e) = self.iommufd.destroy_iommu_object(id) { + if let Err(e) = self.iommufd().destroy_iommu_object(id) { error!("Failed to destroy {what} id {id}: {e}"); } } @@ -581,7 +616,7 @@ impl Drop for IommufdVEventQ { // SAFETY: `file` is live and never dropped anywhere else. unsafe { ManuallyDrop::drop(&mut self.file) }; - if let Err(e) = self.viommu.iommufd.destroy_iommu_object(self.veventq_id) { + if let Err(e) = self.viommu.iommufd().destroy_iommu_object(self.veventq_id) { error!("Failed to destroy vEVENTQ id {}: {e}", self.veventq_id); } } @@ -607,6 +642,7 @@ pub trait AttachHwpt: Send + Sync { pub struct IommufdVDevice { viommu: Arc, + device: Arc, dev_id: u32, virt_id: u64, vdevice_id: u32, @@ -614,7 +650,12 @@ pub struct IommufdVDevice { } impl IommufdVDevice { - pub fn new(viommu: Arc, dev_id: u32, virt_id: u64) -> Result { + pub fn new( + viommu: Arc, + device: Arc, + dev_id: u32, + virt_id: u64, + ) -> Result { let mut vdevice_alloc = iommu_vdevice_alloc { size: std::mem::size_of::() as u32, viommu_id: viommu.viommu_id, @@ -622,10 +663,11 @@ impl IommufdVDevice { virt_id, ..Default::default() }; - viommu.iommufd.alloc_iommu_vdevice(&mut vdevice_alloc)?; + viommu.iommufd().alloc_iommu_vdevice(&mut vdevice_alloc)?; Ok(IommufdVDevice { viommu, + device, dev_id, virt_id, vdevice_id: vdevice_alloc.out_vdevice_id, @@ -646,31 +688,27 @@ impl IommufdVDevice { fn destroy_s1_hwpt(&mut self) -> Result<()> { if let Some(s1_hwpt_id) = self.s1_hwpt_id { - self.viommu.iommufd.destroy_iommu_object(s1_hwpt_id)?; + self.viommu.iommufd().destroy_iommu_object(s1_hwpt_id)?; self.s1_hwpt_id = None; } Ok(()) } - pub fn install_s1_hwpt( - &mut self, - device: &dyn AttachHwpt, - hwpt_data: &IommufdHwptData, - ) -> Result<()> { + pub fn install_s1_hwpt(&mut self, hwpt_data: &IommufdHwptData) -> Result<()> { let s1_hwpt_id = self.allocate_s1_hwpt(hwpt_data)?; - device + self.device .attach_hwpt(s1_hwpt_id) .map_err(IommufdError::AttachHwpt)?; Ok(()) } - pub fn uninstall_s1_hwpt(&mut self, device: &dyn AttachHwpt, abort: bool) -> Result<()> { + pub fn uninstall_s1_hwpt(&mut self, abort: bool) -> Result<()> { if abort { - self.viommu.attach_abort(device)?; + self.viommu.attach_abort(&*self.device)?; } else { - self.viommu.attach_bypass(device)?; + self.viommu.attach_bypass(&*self.device)?; } self.destroy_s1_hwpt() @@ -683,7 +721,7 @@ impl IommufdVDevice { } self.viommu - .iommufd + .iommufd() .device_hw_info(self.dev_id, hw_info_data) } @@ -694,13 +732,20 @@ impl IommufdVDevice { impl Drop for IommufdVDevice { fn drop(&mut self) { + if let Err(e) = self.device.detach_hwpt() { + error!( + "Failed to detach the device of vDevice id {}: {e}", + self.vdevice_id + ); + } + if let Some(s1_hwpt_id) = self.s1_hwpt_id - && let Err(e) = self.viommu.iommufd.destroy_iommu_object(s1_hwpt_id) + && let Err(e) = self.viommu.iommufd().destroy_iommu_object(s1_hwpt_id) { error!("Failed to destroy s1_hwpt id {s1_hwpt_id}: {e}"); } - if let Err(e) = self.viommu.iommufd.destroy_iommu_object(self.vdevice_id) { + if let Err(e) = self.viommu.iommufd().destroy_iommu_object(self.vdevice_id) { error!("Failed to destroy vDevice id {}: {e}", self.vdevice_id); } } From ab5cb8aa2a2bfb04a5865256dea04a8c70bdd741 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Fri, 9 Oct 2026 06:36:07 -0700 Subject: [PATCH 2/4] iommufd-ioctls: Map a file into an IOAS Implementing IOMMU_IOAS_MAP_FILE so that a caller can map a file into an IOMMU IOAS. This can be required in the context of dma-buf when we need to map specific buffers into a device's IOAS so that it can reach out to these buffers directly without going through physical RAM. Signed-off-by: Sebastien Boeuf --- iommufd-ioctls/src/iommufd_ioctls.rs | 62 ++++++++++++++++++++++++++++ iommufd-ioctls/src/lib.rs | 2 + 2 files changed, 64 insertions(+) diff --git a/iommufd-ioctls/src/iommufd_ioctls.rs b/iommufd-ioctls/src/iommufd_ioctls.rs index f514af1..2ca09a3 100644 --- a/iommufd-ioctls/src/iommufd_ioctls.rs +++ b/iommufd-ioctls/src/iommufd_ioctls.rs @@ -51,6 +51,10 @@ impl IommuFd { iommufd_syscall::map_iommu_ioas(self, map) } + pub fn map_iommu_ioas_file(&self, map: &iommu_ioas_map_file) -> Result<()> { + iommufd_syscall::map_iommu_ioas_file(self, map) + } + pub fn unmap_iommu_ioas(&self, unmap: &mut iommu_ioas_unmap) -> Result<()> { iommufd_syscall::unmap_iommu_ioas(self, unmap) } @@ -205,6 +209,44 @@ impl IommufdIoas { pub fn id(&self) -> u32 { self.ioas_id } + + pub fn map_file( + &self, + fd: RawFd, + start: u64, + length: u64, + iova: u64, + writeable: bool, + ) -> Result<()> { + let mut flags = iommufd_ioas_map_flags_IOMMU_IOAS_MAP_READABLE + | iommufd_ioas_map_flags_IOMMU_IOAS_MAP_FIXED_IOVA; + if writeable { + flags |= iommufd_ioas_map_flags_IOMMU_IOAS_MAP_WRITEABLE; + } + + let map = iommu_ioas_map_file { + size: size_of::() as u32, + flags, + ioas_id: self.ioas_id, + fd, + start, + length, + iova, + }; + + self.iommufd.map_iommu_ioas_file(&map) + } + + pub fn unmap(&self, iova: u64, length: u64) -> Result<()> { + let mut unmap = iommu_ioas_unmap { + size: size_of::() as u32, + ioas_id: self.ioas_id, + iova, + length, + }; + + self.iommufd.unmap_iommu_ioas(&mut unmap) + } } impl Drop for IommufdIoas { @@ -764,6 +806,11 @@ ioctl_io_nr!( IOMMUFD_CMD_IOAS_ALLOC ); ioctl_io_nr!(IOMMU_IOAS_MAP, IOMMUFD_TYPE as u32, IOMMUFD_CMD_IOAS_MAP); +ioctl_io_nr!( + IOMMU_IOAS_MAP_FILE, + IOMMUFD_TYPE as u32, + IOMMUFD_CMD_IOAS_MAP_FILE +); ioctl_io_nr!( IOMMU_IOAS_UNMAP, IOMMUFD_TYPE as u32, @@ -847,6 +894,20 @@ pub(crate) mod iommufd_syscall { } } + pub(crate) fn map_iommu_ioas_file(iommufd: &IommuFd, map: &iommu_ioas_map_file) -> Result<()> { + // SAFETY: + // 1. The file descriptor provided by 'iommufd' is valid and open. + // 2. The 'map' points to initialized memory with expected data structure, + // and remains valid for the duration of syscall. + // 3. The return value is checked. + let ret = unsafe { ioctl_with_ref(iommufd, IOMMU_IOAS_MAP_FILE(), map) }; + if ret < 0 { + Err(IommufdError::IommuIoasMapFile(SysError::last())) + } else { + Ok(()) + } + } + pub(crate) fn map_iommu_ioas(iommufd: &IommuFd, map: &iommu_ioas_map) -> Result<()> { // SAFETY: // 1. The file descriptor provided by 'iommufd' is valid and open. @@ -1006,6 +1067,7 @@ mod tests { assert_eq!(IOMMU_HWPT_ALLOC(), 15241); assert_eq!(IOMMUFD_GET_HW_INFO(), 15242); assert_eq!(IOMMUFD_HWPT_INVALIDATE(), 15245); + assert_eq!(IOMMU_IOAS_MAP_FILE(), 15247); assert_eq!(IOMMU_VIOMMU_ALLOC(), 15248); assert_eq!(IOMMU_VDEVICE_ALLOC(), 15249); assert_eq!(IOMMU_VEVENTQ_ALLOC(), 15251); diff --git a/iommufd-ioctls/src/lib.rs b/iommufd-ioctls/src/lib.rs index 2babe95..c9df961 100644 --- a/iommufd-ioctls/src/lib.rs +++ b/iommufd-ioctls/src/lib.rs @@ -26,6 +26,8 @@ pub enum IommufdError { IommuIoasAlloc(#[source] SysError), #[error("failed to map an IOVA range to the IOAS: {0}")] IommuIoasMap(#[source] SysError), + #[error("failed to map a file into the IOAS: {0}")] + IommuIoasMapFile(#[source] SysError), #[error("failed to unmap an IOVA range from the IOAS: {0}")] IommuIoasUnmap(#[source] SysError), #[error("failed to allocate HWPT: {0}")] From b7ee8da512197d08b895371297ce60375098b833 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Wed, 14 Oct 2026 17:34:36 -0700 Subject: [PATCH 3/4] iommufd-ioctls: Replace a vDevice's S1 HWPT atomically Make install_s1_hwpt() to atomically update s1 hwpt instead of the caller explicitly uninstall/install s1 hwpt. Signed-off-by: Isaku Yamahata --- iommufd-ioctls/src/iommufd_ioctls.rs | 28 +++++++++++++--------------- iommufd-ioctls/src/lib.rs | 2 -- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/iommufd-ioctls/src/iommufd_ioctls.rs b/iommufd-ioctls/src/iommufd_ioctls.rs index 2ca09a3..32d06ab 100644 --- a/iommufd-ioctls/src/iommufd_ioctls.rs +++ b/iommufd-ioctls/src/iommufd_ioctls.rs @@ -717,17 +717,6 @@ impl IommufdVDevice { }) } - fn allocate_s1_hwpt(&mut self, hwpt_data: &IommufdHwptData) -> Result { - if self.s1_hwpt_id.is_some() { - return Err(IommufdError::S1HwptAlreadyAllocated(self.vdevice_id)); - } - - let s1_hwpt_id = self.viommu.alloc_s1_hwpt(self.dev_id, hwpt_data)?; - self.s1_hwpt_id = Some(s1_hwpt_id); - - Ok(s1_hwpt_id) - } - fn destroy_s1_hwpt(&mut self) -> Result<()> { if let Some(s1_hwpt_id) = self.s1_hwpt_id { self.viommu.iommufd().destroy_iommu_object(s1_hwpt_id)?; @@ -737,11 +726,20 @@ impl IommufdVDevice { } pub fn install_s1_hwpt(&mut self, hwpt_data: &IommufdHwptData) -> Result<()> { - let s1_hwpt_id = self.allocate_s1_hwpt(hwpt_data)?; + let s1_hwpt_id = self.viommu.alloc_s1_hwpt(self.dev_id, hwpt_data)?; + + if let Err(e) = self.device.attach_hwpt(s1_hwpt_id) { + if let Err(e) = self.viommu.iommufd().destroy_iommu_object(s1_hwpt_id) { + error!("Failed to destroy s1_hwpt id {s1_hwpt_id}: {e}"); + } + return Err(IommufdError::AttachHwpt(e)); + } - self.device - .attach_hwpt(s1_hwpt_id) - .map_err(IommufdError::AttachHwpt)?; + if let Some(old_s1_hwpt_id) = self.s1_hwpt_id.replace(s1_hwpt_id) + && let Err(e) = self.viommu.iommufd().destroy_iommu_object(old_s1_hwpt_id) + { + error!("Failed to destroy s1_hwpt id {old_s1_hwpt_id}: {e}"); + } Ok(()) } diff --git a/iommufd-ioctls/src/lib.rs b/iommufd-ioctls/src/lib.rs index c9df961..1e1acf0 100644 --- a/iommufd-ioctls/src/lib.rs +++ b/iommufd-ioctls/src/lib.rs @@ -56,8 +56,6 @@ pub enum IommufdError { UnsupportedIommu(iommu_hw_info_type), #[error("the data does not match the backing IOMMU: {0:?}")] IommuTypeMismatch(IommuKind), - #[error("S1 HWPT already allocated with for the given vDevice: {0}")] - S1HwptAlreadyAllocated(u32), #[error("failed to attach the device to a page table: {0}")] AttachHwpt(#[source] io::Error), } From ac268acfe2192d15fefcc4df6f218d0c6764dd01 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Thu, 15 Oct 2026 00:03:05 -0700 Subject: [PATCH 4/4] iommufd-ioctls: Prepare 0.4.0 release Releasing a new version of the crate so that consumers get the address space abstraction benefit. It keeps an IOAS valid for longer than the page tables associated with it, which avoids teardown issues. The new release also added the support for mapping a file representing memory ranges into a given address space. And it added support for an atomic page table replacement. Signed-off-by: Sebastien Boeuf --- iommufd-ioctls/CHANGELOG.md | 20 ++++++++++++++++++++ iommufd-ioctls/Cargo.toml | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/iommufd-ioctls/CHANGELOG.md b/iommufd-ioctls/CHANGELOG.md index cd2384e..3a65d44 100644 --- a/iommufd-ioctls/CHANGELOG.md +++ b/iommufd-ioctls/CHANGELOG.md @@ -6,6 +6,26 @@ ## Fixed +# [v0.4.0] + +## Changed + +- `IommufdVIommu::new()` takes an `Arc` rather than an `IommuFd` + and an IOAS identifier +- `IommufdVDevice::new()` takes the device it stands for, which + `install_s1_hwpt()` and `uninstall_s1_hwpt()` no longer take +- Dropping an `IommufdVDevice` now also detaches the device from the page + table it is bound to, so the caller no longer has to order teardown itself +- `install_s1_hwpt()` now replaces the page table atomically +- Removed `IommufdError::S1HwptAlreadyAllocated` + +## Added + +- Add IommufdIoas abstraction +- Add IOMMU_IOAS_MAP_FILE support + +## Fixed + # [v0.3.1] ## Changed diff --git a/iommufd-ioctls/Cargo.toml b/iommufd-ioctls/Cargo.toml index 5718043..10b19bd 100644 --- a/iommufd-ioctls/Cargo.toml +++ b/iommufd-ioctls/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "iommufd-ioctls" -version = "0.3.1" +version = "0.4.0" authors = ["The Cloud Hypervisor Authors"] license = "Apache-2.0" description = "Safe wrappers over IOMMUFD uAPIs"