Skip to content

Commit 31b9806

Browse files
committed
iommufd-ioctls: Add vIOMMU and vDevice abstraction layer
Introduce `IommufdVIommu` and `IommufdVDevice` structs to provide high-level abstractions for managing IOMMUFD virtual IOMMU instances and virtual devices [1]. These structures encapsulate the multi-phase workflow required for hardware-accelerated nested translation — including S2 HWPT infrastructure setup, vIOMMU/vDevice allocation, and runtime S1 HWPT management — into clean and ergonomic Rust APIs. This enables VMMs to build accelerated virtual IOMMUs without directly managing low-level IOMMUFD object lifecycles and sequencing constraints. Key changes: - `IommufdVIommu`: Manages the vIOMMU lifecycle, including Stage-2 HWPT allocation and default Stage-1 HWPT configuration (bypass/abort mode). - `IommufdVDevice`: Represents devices attached to a vIOMMU, supporting dynamic Stage-1 HWPT allocation and hardware info queries. - Type Safety: Add `IommufdInvalidateData`, `IommufdHwInfoData`, and `IommufdHwptData` enums to handle architecture-specific data (e.g., ARM SMMUv3, Intel VT-d). - Public interfaces: Provide methods for physical device info retrieval, Stage-1 HWPT configuration, and invalidation. - Resource Management: Implement `Drop` traits to ensure proper resource release within the IOMMUFD context. Note: This implementation primarily targets ARM SMMUv3; Intel VT-d structures are currently placeholders for future implementation. [1] https://docs.kernel.org/userspace-api/iommufd.html Signed-off-by: Bo Chen <bchen@crusoe.ai>
1 parent 870fe23 commit 31b9806

File tree

2 files changed

+316
-0
lines changed

2 files changed

+316
-0
lines changed

iommufd-ioctls/src/iommufd_ioctls.rs

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use std::fs::{File, OpenOptions};
77
use std::os::unix::io::{AsRawFd, RawFd};
8+
use std::sync::Arc;
89

910
use iommufd_bindings::iommufd::*;
1011
use vmm_sys_util::errno::Error as SysError;
@@ -68,6 +69,313 @@ impl IommuFd {
6869
}
6970
}
7071

72+
#[derive(Debug, Copy, Clone)]
73+
pub enum IommufdInvalidateData {
74+
Smmuv3(iommu_viommu_arm_smmuv3_invalidate),
75+
Vtd(iommu_hwpt_vtd_s1_invalidate),
76+
}
77+
78+
#[derive(Clone)]
79+
pub struct IommufdVIommu {
80+
pub iommufd: Arc<IommuFd>,
81+
pub viommu_id: u32,
82+
pub dev_id: u32,
83+
pub s2_hwpt_id: u32,
84+
pub bypass_hwpt_id: u32,
85+
pub abort_hwpt_id: u32,
86+
}
87+
88+
impl IommufdVIommu {
89+
/// Create a new vIOMMU instance
90+
/// # Arguments
91+
/// * `iommufd` - The iommufd instance to use
92+
/// * `ioas_id` - The IOAS ID to associate with the vIOMMU
93+
/// * `dev_id` - The device ID of the VFIO device
94+
/// * `s1_hwpt_data_type` - The s1 hwpt data type
95+
pub fn new(
96+
iommufd: Arc<IommuFd>,
97+
ioas_id: u32,
98+
dev_id: u32,
99+
s1_hwpt_data_type: iommu_hwpt_data_type,
100+
) -> Result<Self> {
101+
if s1_hwpt_data_type != iommu_hwpt_data_type_IOMMU_HWPT_DATA_ARM_SMMUV3 {
102+
return Err(IommufdError::UnsupportedS1HwptDataType(s1_hwpt_data_type));
103+
}
104+
105+
// Refer to “5.2 Stream Table Entry” in SMMUv3 HW Specification
106+
const SMMU_STE_VALID: u64 = 1 << 0;
107+
const SMMU_STE_CFG_BYPASS: u64 = 1 << 3;
108+
109+
// Allocate s2_hwpt who will be shared for all devices behind this vIOMMU instance
110+
let mut s2_iommufd_hwpt_alloc = iommu_hwpt_alloc {
111+
size: std::mem::size_of::<iommu_hwpt_alloc>() as u32,
112+
flags: iommufd_hwpt_alloc_flags_IOMMU_HWPT_ALLOC_NEST_PARENT,
113+
dev_id,
114+
pt_id: ioas_id,
115+
data_type: iommu_hwpt_data_type_IOMMU_HWPT_DATA_NONE,
116+
..Default::default()
117+
};
118+
iommufd.alloc_iommu_hwpt(&mut s2_iommufd_hwpt_alloc)?;
119+
let s2_hwpt_id = s2_iommufd_hwpt_alloc.out_hwpt_id;
120+
121+
// Allocate vIOMMU
122+
let mut viommu_alloc = iommu_viommu_alloc {
123+
size: std::mem::size_of::<iommu_viommu_alloc>() as u32,
124+
type_: iommu_viommu_type_IOMMU_VIOMMU_TYPE_ARM_SMMUV3,
125+
hwpt_id: s2_hwpt_id,
126+
dev_id,
127+
..Default::default()
128+
};
129+
iommufd.alloc_iommu_viommu(&mut viommu_alloc)?;
130+
let viommu_id = viommu_alloc.out_viommu_id;
131+
132+
// ALlocate bypass s1_hwpt which will be used when the virtual IOMMU
133+
// is not initilized by the guest
134+
let bypass_s1_hwpt_data = iommu_hwpt_arm_smmuv3 {
135+
ste: [SMMU_STE_CFG_BYPASS | SMMU_STE_VALID, 0x0],
136+
};
137+
let mut bypass_iommufd_hwpt_alloc = iommu_hwpt_alloc {
138+
size: std::mem::size_of::<iommu_hwpt_alloc>() as u32,
139+
dev_id,
140+
pt_id: s2_hwpt_id,
141+
data_type: s1_hwpt_data_type,
142+
data_len: std::mem::size_of::<iommu_hwpt_arm_smmuv3>() as u32,
143+
data_uptr: &bypass_s1_hwpt_data as *const iommu_hwpt_arm_smmuv3 as u64,
144+
..Default::default()
145+
};
146+
iommufd.alloc_iommu_hwpt(&mut bypass_iommufd_hwpt_alloc)?;
147+
let bypass_hwpt_id = bypass_iommufd_hwpt_alloc.out_hwpt_id;
148+
149+
// Allocate abort s1_hwpt which will be used when the virtual IOMMU
150+
// is configured in such mode
151+
let abort_s1_hwpt_data = iommu_hwpt_arm_smmuv3 {
152+
ste: [SMMU_STE_VALID, 0x0],
153+
};
154+
let mut abort_iommufd_hwpt_alloc = iommu_hwpt_alloc {
155+
size: std::mem::size_of::<iommu_hwpt_alloc>() as u32,
156+
dev_id,
157+
pt_id: s2_hwpt_id,
158+
data_type: s1_hwpt_data_type,
159+
data_len: std::mem::size_of::<iommu_hwpt_arm_smmuv3>() as u32,
160+
data_uptr: &abort_s1_hwpt_data as *const iommu_hwpt_arm_smmuv3 as u64,
161+
..Default::default()
162+
};
163+
iommufd.alloc_iommu_hwpt(&mut abort_iommufd_hwpt_alloc)?;
164+
let abort_hwpt_id = abort_iommufd_hwpt_alloc.out_hwpt_id;
165+
166+
Ok(IommufdVIommu {
167+
iommufd,
168+
viommu_id,
169+
dev_id,
170+
s2_hwpt_id,
171+
bypass_hwpt_id,
172+
abort_hwpt_id,
173+
})
174+
}
175+
176+
/// Invalidate a hwpt entry
177+
/// # Arguments
178+
/// * `cmd` - The invalidate data
179+
/// Returns:
180+
/// * `Ok(true)` if the entry is invalidated
181+
/// * `Ok(false)` if the entry is not invalidated
182+
pub fn invalidate_hwpt(&self, cmd: &mut IommufdInvalidateData) -> Result<bool> {
183+
match cmd {
184+
IommufdInvalidateData::Smmuv3(data) => {
185+
let mut hw_invalidate = iommu_hwpt_invalidate {
186+
size: std::mem::size_of::<iommu_hwpt_invalidate>() as u32,
187+
hwpt_id: self.viommu_id,
188+
data_type:
189+
iommu_hwpt_invalidate_data_type_IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3,
190+
entry_len: std::mem::size_of::<iommu_viommu_arm_smmuv3_invalidate>() as u32,
191+
entry_num: 1,
192+
data_uptr: data as *mut iommu_viommu_arm_smmuv3_invalidate as u64,
193+
..Default::default()
194+
};
195+
self.iommufd.invalidate_hwpt(&mut hw_invalidate)?;
196+
197+
if hw_invalidate.entry_num == 1 {
198+
Ok(true)
199+
} else {
200+
Ok(false)
201+
}
202+
}
203+
IommufdInvalidateData::Vtd(_) => {
204+
unimplemented!()
205+
}
206+
}
207+
}
208+
}
209+
210+
impl Drop for IommufdVIommu {
211+
fn drop(&mut self) {
212+
self.iommufd
213+
.destroy_iommufd(self.viommu_id)
214+
.inspect_err(|e| {
215+
eprintln!("Failed to destroy vIOMMU id {}: {}", self.viommu_id, e);
216+
})
217+
.unwrap();
218+
219+
self.iommufd
220+
.destroy_iommufd(self.s2_hwpt_id)
221+
.inspect_err(|e| {
222+
eprintln!("Failed to destroy s2_hwpt id {}: {}", self.s2_hwpt_id, e);
223+
})
224+
.unwrap();
225+
226+
self.iommufd
227+
.destroy_iommufd(self.bypass_hwpt_id)
228+
.inspect_err(|e| {
229+
eprintln!(
230+
"Failed to destroy bypass_hwpt id {}: {}",
231+
self.bypass_hwpt_id, e
232+
);
233+
})
234+
.unwrap();
235+
236+
self.iommufd
237+
.destroy_iommufd(self.abort_hwpt_id)
238+
.inspect_err(|e| {
239+
eprintln!(
240+
"Failed to destroy abort_hwpt id {}: {}",
241+
self.abort_hwpt_id, e
242+
);
243+
})
244+
.unwrap();
245+
}
246+
}
247+
248+
#[derive(Debug, Copy, Clone)]
249+
pub enum IommufdHwInfoData {
250+
Smmuv3(iommu_hw_info_arm_smmuv3),
251+
Vtd(iommu_hw_info_vtd),
252+
}
253+
254+
#[derive(Debug, Copy, Clone)]
255+
pub enum IommufdHwptData {
256+
Smmuv3(iommu_hwpt_arm_smmuv3),
257+
Vtd(iommu_hwpt_vtd_s1),
258+
}
259+
260+
#[derive(Clone)]
261+
pub struct IommufdVDevice {
262+
pub viommu: Arc<IommufdVIommu>,
263+
pub dev_id: u32,
264+
pub virt_id: u64,
265+
pub vdevice_id: u32,
266+
pub s1_hwpt_id: Option<u32>,
267+
}
268+
269+
impl IommufdVDevice {
270+
/// Create a new vDevice instance
271+
/// # Arguments
272+
/// * `viommu` - The vIOMMU instance the vDevice is associated with
273+
/// * `dev_id` - The device ID of the vDevice
274+
/// * `virt_id` - The virtual Stream ID of the vDevice
275+
pub fn new(viommu: Arc<IommufdVIommu>, dev_id: u32, virt_id: u64) -> Result<Self> {
276+
let mut vdevice_alloc = iommu_vdevice_alloc {
277+
size: std::mem::size_of::<iommu_vdevice_alloc>() as u32,
278+
viommu_id: viommu.viommu_id,
279+
dev_id,
280+
virt_id,
281+
..Default::default()
282+
};
283+
viommu.iommufd.alloc_iommu_vdevice(&mut vdevice_alloc)?;
284+
285+
Ok(IommufdVDevice {
286+
viommu,
287+
dev_id,
288+
virt_id,
289+
vdevice_id: vdevice_alloc.out_vdevice_id,
290+
s1_hwpt_id: None,
291+
})
292+
}
293+
294+
/// Allocate s1 hwpt for the vDevice
295+
pub fn allocate_s1_hwpt(&mut self, hwpt_data: &IommufdHwptData) -> Result<u32> {
296+
if self.s1_hwpt_id.is_some() {
297+
return Err(IommufdError::S1HwptAlreadyAllocated(self.vdevice_id));
298+
}
299+
300+
match hwpt_data {
301+
IommufdHwptData::Smmuv3(data) => {
302+
let mut s1_iommufd_hwpt_alloc = iommu_hwpt_alloc {
303+
size: std::mem::size_of::<iommu_hwpt_alloc>() as u32,
304+
dev_id: self.dev_id,
305+
pt_id: self.viommu.viommu_id,
306+
data_type: iommu_hwpt_data_type_IOMMU_HWPT_DATA_ARM_SMMUV3,
307+
data_len: std::mem::size_of::<iommu_hwpt_arm_smmuv3>() as u32,
308+
data_uptr: data as *const iommu_hwpt_arm_smmuv3 as u64,
309+
..Default::default()
310+
};
311+
self.viommu
312+
.iommufd
313+
.alloc_iommu_hwpt(&mut s1_iommufd_hwpt_alloc)?;
314+
315+
let s1_hwpt_id = s1_iommufd_hwpt_alloc.out_hwpt_id;
316+
self.s1_hwpt_id = Some(s1_hwpt_id);
317+
318+
Ok(s1_hwpt_id)
319+
}
320+
IommufdHwptData::Vtd(_) => unimplemented!(),
321+
}
322+
}
323+
324+
/// Destroy s1 hwpt for the vDevice
325+
pub fn destroy_s1_hwpt(&mut self) -> Result<()> {
326+
if let Some(s1_hwpt_id) = self.s1_hwpt_id {
327+
self.viommu.iommufd.destroy_iommufd(s1_hwpt_id)?;
328+
self.s1_hwpt_id = None;
329+
}
330+
Ok(())
331+
}
332+
333+
/// Get device hardware information
334+
pub fn get_device_hw_info(
335+
&self,
336+
hw_info_data: &mut IommufdHwInfoData,
337+
) -> Result<iommu_hw_info> {
338+
let mut hw_info = match hw_info_data {
339+
IommufdHwInfoData::Smmuv3(data) => iommu_hw_info {
340+
size: std::mem::size_of::<iommu_hw_info>() as u32,
341+
dev_id: self.dev_id,
342+
data_len: std::mem::size_of::<iommu_hw_info_arm_smmuv3>() as u32,
343+
data_uptr: data as *mut _ as u64,
344+
..Default::default()
345+
},
346+
IommufdHwInfoData::Vtd(_) => {
347+
unimplemented!()
348+
}
349+
};
350+
351+
self.viommu.iommufd.get_hw_info(&mut hw_info)?;
352+
353+
Ok(hw_info)
354+
}
355+
}
356+
357+
impl Drop for IommufdVDevice {
358+
fn drop(&mut self) {
359+
self.viommu
360+
.iommufd
361+
.destroy_iommufd(self.vdevice_id)
362+
.inspect_err(|e| {
363+
eprintln!("Failed to destroy vDevice id {}: {}", self.vdevice_id, e);
364+
})
365+
.unwrap();
366+
367+
if let Some(s1_hwpt_id) = self.s1_hwpt_id {
368+
self.viommu
369+
.iommufd
370+
.destroy_iommufd(s1_hwpt_id)
371+
.inspect_err(|e| {
372+
eprintln!("Failed to destroy s1_hwpt id {}: {}", s1_hwpt_id, e);
373+
})
374+
.unwrap();
375+
}
376+
}
377+
}
378+
71379
impl AsRawFd for IommuFd {
72380
fn as_raw_fd(&self) -> RawFd {
73381
self.iommufd.as_raw_fd()
@@ -112,6 +420,8 @@ ioctl_io_nr!(
112420
IOMMUFD_CMD_VDEVICE_ALLOC
113421
);
114422

423+
// IOMMU_HW_QUEUE_ALLOC
424+
115425
// Safety:
116426
// - absolutely trust the underlying kernel
117427
// - absolutely trust data returned by the underlying kernel

iommufd-ioctls/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ pub mod iommufd_ioctls;
1414

1515
pub use iommufd_ioctls::*;
1616

17+
use iommufd_bindings::iommufd::*;
18+
1719
#[derive(Debug, Error)]
1820
pub enum IommufdError {
1921
#[error("failed to open /dev/iommufd: {0}")]
@@ -36,6 +38,10 @@ pub enum IommufdError {
3638
IommuGetHwInfo(#[source] SysError),
3739
#[error("failed to invalidate HWPT: {0}")]
3840
IommuHwptInvalidate(#[source] SysError),
41+
#[error("unsupported S1 HWPT data type: {0}")]
42+
UnsupportedS1HwptDataType(iommu_hwpt_data_type),
43+
#[error("S1 HWPT already allocated with for the given vDevice: {0}")]
44+
S1HwptAlreadyAllocated(u32),
3945
}
4046

4147
pub type Result<T> = std::result::Result<T, IommufdError>;

0 commit comments

Comments
 (0)