1use anyhow::Context as _;
18use std::fs;
19use std::os::unix::prelude::*;
20use zerocopy::FromBytes;
21use zerocopy::Immutable;
22use zerocopy::IntoBytes;
23use zerocopy::KnownLayout;
24
25const IOMMUFD_TYPE: u8 = b';';
27
28const IOMMUFD_CMD_BASE: u8 = 0x80;
30
31const IOMMUFD_CMD_DESTROY: u8 = IOMMUFD_CMD_BASE;
33const IOMMUFD_CMD_IOAS_ALLOC: u8 = IOMMUFD_CMD_BASE + 1;
34const IOMMUFD_CMD_IOAS_MAP: u8 = IOMMUFD_CMD_BASE + 5;
35const IOMMUFD_CMD_IOAS_UNMAP: u8 = IOMMUFD_CMD_BASE + 6;
36const IOMMUFD_CMD_HWPT_ALLOC: u8 = IOMMUFD_CMD_BASE + 9;
37const IOMMUFD_CMD_IOAS_MAP_FILE: u8 = IOMMUFD_CMD_BASE + 15;
38const IOMMUFD_CMD_GET_HW_INFO: u8 = IOMMUFD_CMD_BASE + 0x0a;
39const IOMMUFD_CMD_HWPT_INVALIDATE: u8 = IOMMUFD_CMD_BASE + 0x0d;
40const IOMMUFD_CMD_VIOMMU_ALLOC: u8 = IOMMUFD_CMD_BASE + 0x10;
41const IOMMUFD_CMD_VDEVICE_ALLOC: u8 = IOMMUFD_CMD_BASE + 0x11;
42const IOMMUFD_CMD_VEVENTQ_ALLOC: u8 = IOMMUFD_CMD_BASE + 0x13;
43
44pub const IOMMU_IOAS_MAP_FIXED_IOVA: u32 = 1 << 0;
46pub const IOMMU_IOAS_MAP_WRITEABLE: u32 = 1 << 1;
47pub const IOMMU_IOAS_MAP_READABLE: u32 = 1 << 2;
48
49mod ioctl {
50 use nix::request_code_none;
51
52 nix::ioctl_readwrite_bad!(
55 iommu_destroy,
56 request_code_none!(
57 super::IOMMUFD_TYPE as u32,
58 super::IOMMUFD_CMD_DESTROY as u32
59 ),
60 super::IommuDestroy
61 );
62 nix::ioctl_readwrite_bad!(
63 iommu_ioas_alloc,
64 request_code_none!(
65 super::IOMMUFD_TYPE as u32,
66 super::IOMMUFD_CMD_IOAS_ALLOC as u32
67 ),
68 super::IommuIoasAlloc
69 );
70 nix::ioctl_readwrite_bad!(
71 iommu_ioas_map,
72 request_code_none!(
73 super::IOMMUFD_TYPE as u32,
74 super::IOMMUFD_CMD_IOAS_MAP as u32
75 ),
76 super::IommuIoasMap
77 );
78 nix::ioctl_readwrite_bad!(
79 iommu_ioas_map_file,
80 request_code_none!(
81 super::IOMMUFD_TYPE as u32,
82 super::IOMMUFD_CMD_IOAS_MAP_FILE as u32
83 ),
84 super::IommuIoasMapFile
85 );
86 nix::ioctl_readwrite_bad!(
87 iommu_ioas_unmap,
88 request_code_none!(
89 super::IOMMUFD_TYPE as u32,
90 super::IOMMUFD_CMD_IOAS_UNMAP as u32
91 ),
92 super::IommuIoasUnmap
93 );
94 nix::ioctl_readwrite_bad!(
95 iommu_hwpt_alloc,
96 request_code_none!(
97 super::IOMMUFD_TYPE as u32,
98 super::IOMMUFD_CMD_HWPT_ALLOC as u32
99 ),
100 super::IommuHwptAlloc
101 );
102 nix::ioctl_readwrite_bad!(
103 iommu_get_hw_info,
104 request_code_none!(
105 super::IOMMUFD_TYPE as u32,
106 super::IOMMUFD_CMD_GET_HW_INFO as u32
107 ),
108 super::IommuGetHwInfo
109 );
110 nix::ioctl_readwrite_bad!(
111 iommu_hwpt_invalidate,
112 request_code_none!(
113 super::IOMMUFD_TYPE as u32,
114 super::IOMMUFD_CMD_HWPT_INVALIDATE as u32
115 ),
116 super::IommuHwptInvalidate
117 );
118 nix::ioctl_readwrite_bad!(
119 iommu_viommu_alloc,
120 request_code_none!(
121 super::IOMMUFD_TYPE as u32,
122 super::IOMMUFD_CMD_VIOMMU_ALLOC as u32
123 ),
124 super::IommuViommuAlloc
125 );
126 nix::ioctl_readwrite_bad!(
127 iommu_vdevice_alloc,
128 request_code_none!(
129 super::IOMMUFD_TYPE as u32,
130 super::IOMMUFD_CMD_VDEVICE_ALLOC as u32
131 ),
132 super::IommuVdeviceAlloc
133 );
134 nix::ioctl_readwrite_bad!(
135 iommu_veventq_alloc,
136 request_code_none!(
137 super::IOMMUFD_TYPE as u32,
138 super::IOMMUFD_CMD_VEVENTQ_ALLOC as u32
139 ),
140 super::IommuVeventqAlloc
141 );
142}
143
144#[repr(C)]
147struct IommuDestroy {
148 size: u32,
149 id: u32,
150}
151
152#[repr(C)]
153struct IommuIoasAlloc {
154 size: u32,
155 flags: u32,
156 out_ioas_id: u32,
157}
158
159#[repr(C)]
160struct IommuIoasMap {
161 size: u32,
162 flags: u32,
163 ioas_id: u32,
164 __reserved: u32,
165 user_va: u64,
166 length: u64,
167 iova: u64,
168}
169
170#[repr(C)]
171struct IommuIoasMapFile {
172 size: u32,
173 flags: u32,
174 ioas_id: u32,
175 fd: i32,
176 start: u64,
177 length: u64,
178 iova: u64,
179}
180
181#[repr(C)]
182struct IommuIoasUnmap {
183 size: u32,
184 ioas_id: u32,
185 iova: u64,
186 length: u64,
187}
188
189pub const IOMMU_HWPT_ALLOC_NEST_PARENT: u32 = 1 << 0;
193
194pub const IOMMU_HWPT_DATA_NONE: u32 = 0;
196pub const IOMMU_HWPT_DATA_ARM_SMMUV3: u32 = 2;
198
199#[repr(C)]
200struct IommuHwptAlloc {
201 size: u32,
202 flags: u32,
203 dev_id: u32,
204 pt_id: u32,
205 out_hwpt_id: u32,
206 __reserved: u32,
207 data_type: u32,
208 data_len: u32,
209 data_uptr: u64,
210 fault_id: u32,
211 __reserved2: u32,
212}
213
214#[repr(C)]
219pub struct IommuHwptArmSmmuv3 {
220 pub ste: [u64; 2],
221}
222
223pub const IOMMU_HW_INFO_TYPE_ARM_SMMUV3: u32 = 2;
227
228#[repr(C)]
229struct IommuGetHwInfo {
230 size: u32,
231 flags: u32,
232 dev_id: u32,
233 data_len: u32,
234 data_uptr: u64,
235 out_data_type: u32,
236 out_max_pasid_log2: u8,
237 __reserved: [u8; 3],
238 out_capabilities: u64,
239}
240
241#[repr(C)]
246pub struct IommuHwInfoArmSmmuv3 {
247 pub flags: u32,
248 pub __reserved: u32,
249 pub idr: [u32; 6],
250 pub iidr: u32,
251 pub aidr: u32,
252}
253
254pub const IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3: u32 = 1;
258
259#[repr(C)]
260struct IommuHwptInvalidate {
261 size: u32,
262 hwpt_id: u32,
263 data_uptr: u64,
264 data_type: u32,
265 entry_len: u32,
266 entry_num: u32,
267 __reserved: u32,
268}
269
270#[derive(Debug, thiserror::Error)]
273#[error("IOMMU_HWPT_INVALIDATE failed (kernel handled {handled} entries)")]
274pub struct HwptInvalidateError {
275 #[source]
277 pub errno: nix::errno::Errno,
278 pub handled: u32,
283}
284
285pub const IOMMU_VIOMMU_TYPE_ARM_SMMUV3: u32 = 1;
289
290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
292pub enum ViommuAlloc {
293 Allocated(u32),
295 Incompatible,
298}
299
300#[repr(C)]
301struct IommuViommuAlloc {
302 size: u32,
303 flags: u32,
304 r#type: u32,
305 dev_id: u32,
306 hwpt_id: u32,
307 out_viommu_id: u32,
308 data_len: u32,
309 __reserved: u32,
310 data_uptr: u64,
311}
312
313#[repr(C)]
316struct IommuVdeviceAlloc {
317 size: u32,
318 viommu_id: u32,
319 dev_id: u32,
320 out_vdevice_id: u32,
321 virt_id: u64,
322}
323
324pub const IOMMU_VEVENTQ_TYPE_ARM_SMMUV3: u32 = 1;
328
329pub const IOMMU_VEVENTQ_FLAG_LOST_EVENTS: u32 = 1 << 0;
332
333#[repr(C)]
334struct IommuVeventqAlloc {
335 size: u32,
336 flags: u32,
337 viommu_id: u32,
338 r#type: u32,
339 veventq_depth: u32,
340 out_veventq_id: u32,
341 out_veventq_fd: u32,
342 __reserved: u32,
343}
344
345#[repr(C)]
350#[derive(Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
351pub struct IommufdVeventHeader {
352 pub flags: u32,
353 pub sequence: u32,
354}
355
356#[repr(C)]
363#[derive(Copy, Clone, FromBytes, Immutable, IntoBytes, KnownLayout)]
364pub struct IommuVeventArmSmmuv3 {
365 pub evt: [u64; 4],
366}
367
368pub struct IommufdCtx {
373 file: fs::File,
374}
375
376impl IommufdCtx {
377 pub fn new() -> anyhow::Result<Self> {
379 let file = fs::OpenOptions::new()
380 .read(true)
381 .write(true)
382 .open("/dev/iommu")
383 .context("failed to open /dev/iommu")?;
384 Ok(Self { file })
385 }
386
387 pub fn from_file(file: fs::File) -> Self {
389 Self { file }
390 }
391
392 pub fn ioas_alloc(&self) -> anyhow::Result<u32> {
396 let mut cmd = IommuIoasAlloc {
397 size: size_of::<IommuIoasAlloc>() as u32,
398 flags: 0,
399 out_ioas_id: 0,
400 };
401 unsafe {
403 ioctl::iommu_ioas_alloc(self.file.as_raw_fd(), &mut cmd)
404 .context("IOMMU_IOAS_ALLOC failed")?;
405 }
406 Ok(cmd.out_ioas_id)
407 }
408
409 pub unsafe fn ioas_map(
419 &self,
420 ioas_id: u32,
421 iova: u64,
422 user_va: u64,
423 length: u64,
424 writable: bool,
425 ) -> anyhow::Result<()> {
426 let mut flags = IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_READABLE;
427 if writable {
428 flags |= IOMMU_IOAS_MAP_WRITEABLE;
429 }
430 let mut cmd = IommuIoasMap {
431 size: size_of::<IommuIoasMap>() as u32,
432 flags,
433 ioas_id,
434 __reserved: 0,
435 user_va,
436 length,
437 iova,
438 };
439 unsafe {
442 ioctl::iommu_ioas_map(self.file.as_raw_fd(), &mut cmd)
443 .context("IOMMU_IOAS_MAP failed")?;
444 }
445 Ok(())
446 }
447
448 pub fn ioas_map_file(
457 &self,
458 ioas_id: u32,
459 iova: u64,
460 fd: RawFd,
461 start: u64,
462 length: u64,
463 writable: bool,
464 ) -> anyhow::Result<()> {
465 let mut flags = IOMMU_IOAS_MAP_FIXED_IOVA | IOMMU_IOAS_MAP_READABLE;
466 if writable {
467 flags |= IOMMU_IOAS_MAP_WRITEABLE;
468 }
469 let mut cmd = IommuIoasMapFile {
470 size: size_of::<IommuIoasMapFile>() as u32,
471 flags,
472 ioas_id,
473 fd,
474 start,
475 length,
476 iova,
477 };
478 unsafe {
481 ioctl::iommu_ioas_map_file(self.file.as_raw_fd(), &mut cmd)
482 .context("IOMMU_IOAS_MAP_FILE failed")?;
483 }
484 Ok(())
485 }
486
487 pub fn ioas_unmap(&self, ioas_id: u32, iova: u64, length: u64) -> anyhow::Result<u64> {
491 let mut cmd = IommuIoasUnmap {
492 size: size_of::<IommuIoasUnmap>() as u32,
493 ioas_id,
494 iova,
495 length,
496 };
497 unsafe {
499 ioctl::iommu_ioas_unmap(self.file.as_raw_fd(), &mut cmd)
500 .context("IOMMU_IOAS_UNMAP failed")?;
501 }
502 Ok(cmd.length)
503 }
504
505 pub fn destroy(&self, id: u32) -> anyhow::Result<()> {
507 let mut cmd = IommuDestroy {
508 size: size_of::<IommuDestroy>() as u32,
509 id,
510 };
511 unsafe {
513 ioctl::iommu_destroy(self.file.as_raw_fd(), &mut cmd)
514 .context("IOMMU_DESTROY failed")?;
515 }
516 Ok(())
517 }
518
519 pub fn hwpt_alloc(
530 &self,
531 flags: u32,
532 dev_id: u32,
533 pt_id: u32,
534 data_type: u32,
535 data: Option<&IommuHwptArmSmmuv3>,
536 ) -> anyhow::Result<u32> {
537 let (data_uptr, data_len) = match data {
538 Some(data) => (
539 std::ptr::from_ref(data) as u64,
540 size_of::<IommuHwptArmSmmuv3>() as u32,
541 ),
542 None => (0, 0),
543 };
544 let mut cmd = IommuHwptAlloc {
545 size: size_of::<IommuHwptAlloc>() as u32,
546 flags,
547 dev_id,
548 pt_id,
549 out_hwpt_id: 0,
550 __reserved: 0,
551 data_type,
552 data_len,
553 data_uptr,
554 fault_id: 0,
555 __reserved2: 0,
556 };
557 unsafe {
562 ioctl::iommu_hwpt_alloc(self.file.as_raw_fd(), &mut cmd)
563 .context("IOMMU_HWPT_ALLOC failed")?;
564 }
565 Ok(cmd.out_hwpt_id)
566 }
567
568 pub fn get_hw_info(
573 &self,
574 dev_id: u32,
575 out_info: &mut IommuHwInfoArmSmmuv3,
576 ) -> anyhow::Result<(u32, u64)> {
577 let mut cmd = IommuGetHwInfo {
578 size: size_of::<IommuGetHwInfo>() as u32,
579 flags: 0,
580 dev_id,
581 data_len: size_of::<IommuHwInfoArmSmmuv3>() as u32,
582 data_uptr: std::ptr::from_mut(out_info) as u64,
583 out_data_type: 0,
584 out_max_pasid_log2: 0,
585 __reserved: [0; 3],
586 out_capabilities: 0,
587 };
588 unsafe {
595 ioctl::iommu_get_hw_info(self.file.as_raw_fd(), &mut cmd)
596 .context("IOMMU_GET_HW_INFO failed")?;
597 }
598 Ok((cmd.out_data_type, cmd.out_capabilities))
599 }
600
601 pub fn hwpt_invalidate(
620 &self,
621 hwpt_id: u32,
622 data_type: u32,
623 entries: &[[u64; 2]],
624 ) -> Result<u32, HwptInvalidateError> {
625 let entry_num = u32::try_from(entries.len()).map_err(|_| HwptInvalidateError {
626 errno: nix::errno::Errno::EINVAL,
627 handled: 0,
628 })?;
629 let mut cmd = IommuHwptInvalidate {
630 size: size_of::<IommuHwptInvalidate>() as u32,
631 hwpt_id,
632 data_uptr: entries.as_ptr() as u64,
633 data_type,
634 entry_len: size_of::<[u64; 2]>() as u32,
635 entry_num,
636 __reserved: 0,
637 };
638 let res = unsafe { ioctl::iommu_hwpt_invalidate(self.file.as_raw_fd(), &mut cmd) };
643 match res {
646 Ok(_) => Ok(cmd.entry_num),
647 Err(errno) => Err(HwptInvalidateError {
648 errno,
649 handled: cmd.entry_num,
650 }),
651 }
652 }
653
654 pub fn viommu_alloc(
667 &self,
668 viommu_type: u32,
669 dev_id: u32,
670 hwpt_id: u32,
671 ) -> anyhow::Result<ViommuAlloc> {
672 let mut cmd = IommuViommuAlloc {
673 size: size_of::<IommuViommuAlloc>() as u32,
674 flags: 0,
675 r#type: viommu_type,
676 dev_id,
677 hwpt_id,
678 out_viommu_id: 0,
679 data_len: 0,
680 __reserved: 0,
681 data_uptr: 0,
682 };
683 let r = unsafe { ioctl::iommu_viommu_alloc(self.file.as_raw_fd(), &mut cmd) };
685 match r {
686 Ok(_) => Ok(ViommuAlloc::Allocated(cmd.out_viommu_id)),
687 Err(nix::errno::Errno::EINVAL) => Ok(ViommuAlloc::Incompatible),
688 Err(err) => Err(err).context("IOMMU_VIOMMU_ALLOC failed"),
689 }
690 }
691
692 pub fn vdevice_alloc(&self, viommu_id: u32, dev_id: u32, virt_id: u64) -> anyhow::Result<u32> {
698 let mut cmd = IommuVdeviceAlloc {
699 size: size_of::<IommuVdeviceAlloc>() as u32,
700 viommu_id,
701 dev_id,
702 out_vdevice_id: 0,
703 virt_id,
704 };
705 unsafe {
707 ioctl::iommu_vdevice_alloc(self.file.as_raw_fd(), &mut cmd)
708 .context("IOMMU_VDEVICE_ALLOC failed")?;
709 }
710 Ok(cmd.out_vdevice_id)
711 }
712
713 pub fn veventq_alloc(
721 &self,
722 viommu_id: u32,
723 veventq_type: u32,
724 depth: u32,
725 ) -> anyhow::Result<(u32, fs::File)> {
726 let mut cmd = IommuVeventqAlloc {
727 size: size_of::<IommuVeventqAlloc>() as u32,
728 flags: 0,
729 viommu_id,
730 r#type: veventq_type,
731 veventq_depth: depth,
732 out_veventq_id: 0,
733 out_veventq_fd: 0,
734 __reserved: 0,
735 };
736 unsafe {
738 ioctl::iommu_veventq_alloc(self.file.as_raw_fd(), &mut cmd)
739 .context("IOMMU_VEVENTQ_ALLOC failed")?;
740 }
741 let veventq_file = unsafe { fs::File::from_raw_fd(cmd.out_veventq_fd as RawFd) };
743 Ok((cmd.out_veventq_id, veventq_file))
744 }
745}
746
747impl AsFd for IommufdCtx {
748 fn as_fd(&self) -> BorrowedFd<'_> {
749 self.file.as_fd()
750 }
751}
752
753impl AsRawFd for IommufdCtx {
754 fn as_raw_fd(&self) -> RawFd {
755 self.file.as_raw_fd()
756 }
757}