Skip to content

Commit eabe752

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 1cbd2a5 commit eabe752

File tree

2 files changed

+314
-0
lines changed

2 files changed

+314
-0
lines changed

iommufd-ioctls/src/iommufd_ioctls.rs

Lines changed: 308 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;
@@ -67,6 +68,313 @@ impl IommuFd {
6768
}
6869
}
6970

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

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)