1#![expect(missing_docs)]
5#![cfg(unix)]
6#![expect(unsafe_code)]
8
9pub mod cdev;
10pub mod iommufd;
11
12use anyhow::Context;
13use bitfield_struct::bitfield;
14use headervec::HeaderVec;
15use libc::c_void;
16use memory_range::MemoryRange;
17use pal_async::driver::Driver;
18use pal_async::timer::PolledTimer;
19use std::ffi::CString;
20use std::fs;
21use std::fs::File;
22use std::io::BufRead;
23use std::io::BufReader;
24use std::os::unix::prelude::*;
25use std::path::Path;
26use std::time::Duration;
27use vfio_bindings::bindings::vfio::VFIO_IRQ_SET_ACTION_TRIGGER;
28use vfio_bindings::bindings::vfio::VFIO_IRQ_SET_DATA_EVENTFD;
29use vfio_bindings::bindings::vfio::VFIO_IRQ_SET_DATA_NONE;
30use vfio_bindings::bindings::vfio::VFIO_PCI_MSIX_IRQ_INDEX;
31use vfio_bindings::bindings::vfio::VFIO_REGION_INFO_CAP_SPARSE_MMAP;
32use vfio_bindings::bindings::vfio::vfio_device_info;
33use vfio_bindings::bindings::vfio::vfio_group_status;
34use vfio_bindings::bindings::vfio::vfio_info_cap_header;
35use vfio_bindings::bindings::vfio::vfio_irq_info;
36use vfio_bindings::bindings::vfio::vfio_irq_set;
37use vfio_bindings::bindings::vfio::vfio_region_info;
38use vfio_bindings::bindings::vfio::vfio_region_info_cap_sparse_mmap;
39use vfio_bindings::bindings::vfio::vfio_region_sparse_mmap_area;
40
41pub fn host_page_size() -> u64 {
43 use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
44 static PAGE_SIZE: AtomicU64 = const { AtomicU64::new(0) };
45
46 let page_size = PAGE_SIZE.load(Relaxed);
47 if page_size == 0 {
48 let raw = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
50 assert!(raw > 0, "sysconf(_SC_PAGESIZE) failed: {raw}");
51 let page_size = raw as u64;
52 PAGE_SIZE.store(page_size, Relaxed);
53 page_size
54 } else {
55 page_size
56 }
57}
58
59mod ioctl {
60 use nix::request_code_none;
61 use std::os::raw::c_char;
62 use std::os::raw::c_int;
63 use vfio_bindings::bindings::vfio::VFIO_BASE;
64 use vfio_bindings::bindings::vfio::VFIO_TYPE;
65 use vfio_bindings::bindings::vfio::vfio_device_info;
66 use vfio_bindings::bindings::vfio::vfio_group_status;
67 use vfio_bindings::bindings::vfio::vfio_iommu_type1_dma_map;
68 use vfio_bindings::bindings::vfio::vfio_iommu_type1_dma_unmap;
69 use vfio_bindings::bindings::vfio::vfio_irq_info;
70 use vfio_bindings::bindings::vfio::vfio_irq_set;
71 use vfio_bindings::bindings::vfio::vfio_region_info;
72
73 const VFIO_PRIVATE_BASE: u32 = 200;
74
75 nix::ioctl_write_int_bad!(vfio_set_iommu, request_code_none!(VFIO_TYPE, VFIO_BASE + 2));
76 nix::ioctl_read_bad!(
77 vfio_group_get_status,
78 request_code_none!(VFIO_TYPE, VFIO_BASE + 3),
79 vfio_group_status
80 );
81 nix::ioctl_write_ptr_bad!(
82 vfio_group_set_container,
83 request_code_none!(VFIO_TYPE, VFIO_BASE + 4),
84 c_int
85 );
86 nix::ioctl_write_ptr_bad!(
87 vfio_group_get_device_fd,
88 request_code_none!(VFIO_TYPE, VFIO_BASE + 6),
89 c_char
90 );
91 nix::ioctl_read_bad!(
92 vfio_device_get_info,
93 request_code_none!(VFIO_TYPE, VFIO_BASE + 7),
94 vfio_device_info
95 );
96 nix::ioctl_readwrite_bad!(
97 vfio_device_get_region_info,
98 request_code_none!(VFIO_TYPE, VFIO_BASE + 8),
99 vfio_region_info
100 );
101 nix::ioctl_readwrite_bad!(
102 vfio_device_get_irq_info,
103 request_code_none!(VFIO_TYPE, VFIO_BASE + 9),
104 vfio_irq_info
105 );
106 nix::ioctl_write_ptr_bad!(
107 vfio_device_set_irqs,
108 request_code_none!(VFIO_TYPE, VFIO_BASE + 10),
109 vfio_irq_set
110 );
111 nix::ioctl_none_bad!(
112 vfio_device_reset,
113 request_code_none!(VFIO_TYPE, VFIO_BASE + 11)
114 );
115 nix::ioctl_write_ptr_bad!(
116 vfio_group_set_keep_alive,
117 request_code_none!(VFIO_TYPE, VFIO_PRIVATE_BASE),
118 c_char
119 );
120 nix::ioctl_write_ptr_bad!(
122 vfio_iommu_map_dma,
123 request_code_none!(VFIO_TYPE, VFIO_BASE + 13),
124 vfio_iommu_type1_dma_map
125 );
126 nix::ioctl_readwrite_bad!(
128 vfio_iommu_unmap_dma,
129 request_code_none!(VFIO_TYPE, VFIO_BASE + 14),
130 vfio_iommu_type1_dma_unmap
131 );
132 nix::ioctl_write_ptr_bad!(
136 vfio_device_feature_dma_buf,
137 request_code_none!(VFIO_TYPE, VFIO_BASE + 17),
138 super::VfioDeviceFeatureDmaBuf
139 );
140}
141
142const VFIO_DEVICE_FEATURE_GET: u32 = 1 << 16;
144const VFIO_DEVICE_FEATURE_PROBE: u32 = 1 << 18;
145const VFIO_DEVICE_FEATURE_DMA_BUF: u32 = 11;
147
148#[repr(C)]
157struct VfioDeviceFeatureDmaBuf {
158 argsz: u32,
160 flags: u32,
161 region_index: u32,
163 open_flags: u32,
164 dma_buf_flags: u32,
165 nr_ranges: u32,
166 range_offset: u64,
168 range_length: u64,
169}
170
171pub fn fd_identity(fd: BorrowedFd<'_>) -> std::io::Result<(u64, u64)> {
177 let mut stat = unsafe { std::mem::zeroed::<libc::stat>() };
180 let ret = unsafe { libc::fstat(fd.as_raw_fd(), &mut stat) };
183 if ret != 0 {
184 return Err(std::io::Error::last_os_error());
185 }
186 Ok((stat.st_dev as u64, stat.st_ino as u64))
187}
188
189pub struct Container {
190 file: File,
191}
192
193impl Container {
194 pub fn new() -> anyhow::Result<Self> {
195 let file = fs::OpenOptions::new()
196 .read(true)
197 .write(true)
198 .open("/dev/vfio/vfio")
199 .context("failed to open /dev/vfio/vfio")?;
200
201 Ok(Self { file })
202 }
203
204 pub fn set_iommu(&self, iommu: IommuType) -> anyhow::Result<()> {
205 unsafe {
207 ioctl::vfio_set_iommu(self.file.as_raw_fd(), iommu as i32)
208 .context("failed to set iommu")?;
209 }
210 Ok(())
211 }
212
213 pub unsafe fn map_dma(
227 &self,
228 iova: u64,
229 vaddr: *const u8,
230 size: u64,
231 writable: bool,
232 ) -> anyhow::Result<()> {
233 use vfio_bindings::bindings::vfio::VFIO_DMA_MAP_FLAG_READ;
234 use vfio_bindings::bindings::vfio::VFIO_DMA_MAP_FLAG_WRITE;
235
236 let page_size = host_page_size();
237 let page_mask = page_size - 1;
238 let vaddr = vaddr as u64;
239 anyhow::ensure!(
240 iova & page_mask == 0 && vaddr & page_mask == 0 && size & page_mask == 0,
241 "VFIO DMA mapping requires page-aligned iova ({iova:#x}), vaddr ({vaddr:#x}), and size ({size:#x}), page size {page_size:#x}"
242 );
243
244 let mut flags = VFIO_DMA_MAP_FLAG_READ;
245 if writable {
246 flags |= VFIO_DMA_MAP_FLAG_WRITE;
247 }
248
249 let dma_map = vfio_bindings::bindings::vfio::vfio_iommu_type1_dma_map {
250 argsz: size_of::<vfio_bindings::bindings::vfio::vfio_iommu_type1_dma_map>() as u32,
251 flags,
252 vaddr,
253 iova,
254 size,
255 };
256 unsafe {
259 ioctl::vfio_iommu_map_dma(self.file.as_raw_fd(), &dma_map)
260 .context("VFIO_IOMMU_MAP_DMA failed")?;
261 }
262 Ok(())
263 }
264
265 pub fn unmap_dma(&self, iova: u64, size: u64) -> anyhow::Result<()> {
273 let mut dma_unmap = vfio_bindings::bindings::vfio::vfio_iommu_type1_dma_unmap {
274 argsz: size_of::<vfio_bindings::bindings::vfio::vfio_iommu_type1_dma_unmap>() as u32,
275 flags: 0,
276 iova,
277 size,
278 };
279 unsafe {
282 ioctl::vfio_iommu_unmap_dma(self.file.as_raw_fd(), &mut dma_unmap)
283 .context("VFIO_IOMMU_UNMAP_DMA failed")?;
284 }
285 Ok(())
286 }
287}
288
289#[repr(u32)]
294pub enum IommuType {
295 Type1v2 = vfio_bindings::bindings::vfio::VFIO_TYPE1v2_IOMMU,
296 NoIommu = vfio_bindings::bindings::vfio::VFIO_NOIOMMU_IOMMU,
297}
298
299pub struct Group {
300 file: File,
301}
302
303impl Group {
304 pub fn from_file(file: File) -> Self {
306 Self { file }
307 }
308
309 pub fn open(group: u64) -> anyhow::Result<Self> {
310 Self::open_path(format!("/dev/vfio/{group}").as_ref())
311 }
312
313 pub fn open_noiommu(group: u64) -> anyhow::Result<Self> {
314 Self::open_path(format!("/dev/vfio/noiommu-{group}").as_ref())
315 }
316
317 fn open_path(group: &Path) -> anyhow::Result<Self> {
318 let file = fs::OpenOptions::new()
319 .read(true)
320 .write(true)
321 .open(group)
322 .with_context(|| format!("failed to open group {}", group.display()))?;
323
324 Ok(Self { file })
325 }
326
327 pub fn find_group_for_device(device_sysfs_path: &Path) -> anyhow::Result<u64> {
328 let group = device_sysfs_path.join("iommu_group");
329 let group = fs::read_link(group).context("failed to read iommu group")?;
330 let group: u64 = group
331 .file_name()
332 .and_then(|s| s.to_str())
333 .context("invalid group link")?
334 .parse()
335 .context("failed to parse iommu group")?;
336
337 Ok(group)
338 }
339
340 pub fn open_device(&self, device_id: &str) -> anyhow::Result<Device> {
341 let id = CString::new(device_id)?;
342 let file = unsafe {
344 let fd = ioctl::vfio_group_get_device_fd(self.file.as_raw_fd(), id.as_ptr())
345 .with_context(|| format!("failed to get device fd for {device_id}"))?;
346 File::from_raw_fd(fd)
347 };
348
349 Ok(Device { file })
350 }
351
352 pub fn set_container(&self, container: &Container) -> anyhow::Result<()> {
353 unsafe {
355 ioctl::vfio_group_set_container(self.file.as_raw_fd(), &container.file.as_raw_fd())
356 .context("failed to set container")?;
357 }
358 Ok(())
359 }
360
361 pub fn try_set_container(&self, container: &Container) -> anyhow::Result<bool> {
367 let result = unsafe {
369 ioctl::vfio_group_set_container(self.file.as_raw_fd(), &container.file.as_raw_fd())
370 };
371 match result {
372 Ok(_) => Ok(true),
373 Err(nix::errno::Errno::EINVAL) => Ok(false),
374 Err(e) => Err(e).context("failed to set container"),
375 }
376 }
377
378 pub fn status(&self) -> anyhow::Result<GroupStatus> {
379 let mut status = vfio_group_status {
380 argsz: size_of::<vfio_group_status>() as u32,
381 flags: 0,
382 };
383 unsafe {
385 ioctl::vfio_group_get_status(self.file.as_raw_fd(), &mut status)
386 .context("failed to get group status")?;
387 };
388 Ok(GroupStatus::from(status.flags))
389 }
390
391 pub fn set_keep_alive(&self, device_id: &str) -> anyhow::Result<()> {
395 let id = CString::new(device_id)?;
396 unsafe {
398 ioctl::vfio_group_set_keep_alive(self.file.as_raw_fd(), id.as_ptr())
399 .with_context(|| format!("failed to set keep-alive for {device_id}"))?;
400 }
401 Ok(())
402 }
403}
404
405pub struct VfioRetry<'a> {
407 driver: &'a dyn Driver,
408 device_id: &'a str,
409 sleep_duration: Duration,
410 max_retries: u32,
411}
412
413impl<'a> VfioRetry<'a> {
414 const SLEEP_DURATION: Duration = Duration::from_millis(250);
415 const MAX_RETRIES: u32 = 1;
416
417 pub fn new(driver: &'a dyn Driver, device_id: &'a str) -> Self {
418 Self {
419 driver,
420 device_id,
421 sleep_duration: Self::SLEEP_DURATION,
422 max_retries: Self::MAX_RETRIES,
423 }
424 }
425
426 pub async fn retry<T, E>(
429 &self,
430 mut op: impl FnMut() -> Result<T, E>,
431 should_retry: impl Fn(&E) -> bool,
432 context: &str,
433 ) -> Result<T, E>
434 where
435 E: std::fmt::Display,
436 {
437 let mut attempt = 0;
438 loop {
439 match op() {
440 Ok(val) => return Ok(val),
441 Err(err) => {
442 if attempt >= self.max_retries || !should_retry(&err) {
443 return Err(err);
444 }
445 attempt += 1;
446 tracelimit::warn_ratelimited!(
447 device_id = self.device_id,
448 operation = context,
449 attempt,
450 "retrying after transient error: {err}"
451 );
452 }
453 }
454 PolledTimer::new(self.driver)
455 .sleep(self.sleep_duration)
456 .await;
457 }
458 }
459}
460
461#[bitfield(u32)]
462pub struct GroupStatus {
463 pub viable: bool,
464 pub container_set: bool,
465
466 #[bits(30)]
467 _reserved: u32,
468}
469
470pub struct Device {
471 file: File,
472}
473
474#[derive(Debug)]
475pub struct DeviceInfo {
476 pub flags: DeviceFlags,
477 pub num_regions: u32,
478 pub num_irqs: u32,
479}
480
481#[bitfield(u32)]
482pub struct DeviceFlags {
483 pub reset: bool,
484 pub pci: bool,
485 pub platform: bool,
486 pub amba: bool,
487 pub ccw: bool,
488 pub ap: bool,
489
490 #[bits(26)]
491 _reserved: u32,
492}
493
494#[derive(Debug)]
495pub struct RegionInfo {
496 pub flags: RegionFlags,
497 pub size: u64,
498 pub offset: u64,
499}
500
501#[bitfield(u32)]
502pub struct RegionFlags {
503 read: bool,
504 write: bool,
505 mmap: bool,
506 caps: bool,
507
508 #[bits(28)]
509 _reserved: u32,
510}
511
512#[derive(Debug)]
513pub struct IrqInfo {
514 pub flags: IrqFlags,
515 pub count: u32,
516}
517
518#[bitfield(u32)]
519pub struct IrqFlags {
520 eventfd: bool,
521 maskable: bool,
522 automasked: bool,
523 pub noresize: bool,
524
525 #[bits(28)]
526 _reserved: u32,
527}
528
529impl Device {
530 pub fn info(&self) -> anyhow::Result<DeviceInfo> {
531 let mut info = vfio_device_info {
532 argsz: size_of::<vfio_device_info>() as u32,
533 flags: 0,
534 num_regions: 0,
535 num_irqs: 0,
536 };
537 unsafe {
539 ioctl::vfio_device_get_info(self.file.as_raw_fd(), &mut info)
540 .context("failed to get device info")?;
541 }
542 Ok(DeviceInfo {
543 flags: DeviceFlags::from(info.flags),
544 num_regions: info.num_regions,
545 num_irqs: info.num_irqs,
546 })
547 }
548
549 pub fn region_info(&self, index: u32) -> anyhow::Result<RegionInfo> {
550 let mut info = vfio_region_info {
551 argsz: size_of::<vfio_region_info>() as u32,
552 index,
553 flags: 0,
554 cap_offset: 0,
555 size: 0,
556 offset: 0,
557 };
558 unsafe {
560 ioctl::vfio_device_get_region_info(self.file.as_raw_fd(), &mut info)
561 .context("failed to get region info")?;
562 };
563 Ok(RegionInfo {
564 flags: RegionFlags::from(info.flags),
565 size: info.size,
566 offset: info.offset,
567 })
568 }
569
570 pub fn region_mmap_areas(&self, index: u32) -> anyhow::Result<Vec<MemoryRange>> {
578 let mut info = vfio_region_info {
579 argsz: size_of::<vfio_region_info>() as u32,
580 index,
581 flags: 0,
582 cap_offset: 0,
583 size: 0,
584 offset: 0,
585 };
586 unsafe {
588 ioctl::vfio_device_get_region_info(self.file.as_raw_fd(), &mut info)
589 .context("failed to get region info")?;
590 };
591
592 let flags = RegionFlags::from(info.flags);
593
594 if flags.caps() && info.argsz > size_of::<vfio_region_info>() as u32 {
598 let buf_size = info.argsz as usize;
599 let tail_len = buf_size - size_of::<vfio_region_info>();
600 let mut buf = HeaderVec::<vfio_region_info, u8, 0>::with_capacity(
601 vfio_region_info {
602 argsz: buf_size as u32,
603 index,
604 flags: 0,
605 cap_offset: 0,
606 size: 0,
607 offset: 0,
608 },
609 tail_len,
610 );
611 unsafe {
614 ioctl::vfio_device_get_region_info(self.file.as_raw_fd(), buf.as_mut_ptr())
615 .context("failed to get region info with capabilities")?;
616 }
617 let actual_tail = buf.head.argsz as usize - size_of::<vfio_region_info>();
620 unsafe { buf.set_tail_len(actual_tail.min(tail_len)) };
622 if let Some(areas) = parse_sparse_mmap_caps(&buf) {
623 return Ok(areas);
624 }
625 }
626
627 if flags.mmap() {
628 let page_mask = host_page_size() - 1;
636 let aligned_size = info.size & !page_mask;
637 if aligned_size == 0 {
638 Ok(Vec::new())
639 } else {
640 Ok(vec![MemoryRange::new(0..aligned_size)])
641 }
642 } else {
643 Ok(Vec::new())
644 }
645 }
646
647 pub fn irq_info(&self, index: u32) -> anyhow::Result<IrqInfo> {
648 let mut info = vfio_irq_info {
649 argsz: size_of::<vfio_irq_info>() as u32,
650 index,
651 flags: 0,
652 count: 0,
653 };
654 unsafe {
656 ioctl::vfio_device_get_irq_info(self.file.as_raw_fd(), &mut info)
657 .context("failed to get irq info")?;
658 }
659 Ok(IrqInfo {
660 flags: IrqFlags::from(info.flags),
661 count: info.count,
662 })
663 }
664
665 pub fn map(&self, offset: u64, len: usize, write: bool) -> anyhow::Result<MappedRegion> {
666 let mut prot = libc::PROT_READ;
667 if write {
668 prot |= libc::PROT_WRITE;
669 }
670 let addr = unsafe {
673 libc::mmap(
674 std::ptr::null_mut(),
675 len,
676 prot,
677 libc::MAP_SHARED,
678 self.file.as_raw_fd(),
679 offset as i64,
680 )
681 };
682 if addr == libc::MAP_FAILED {
683 return Err(std::io::Error::last_os_error()).context("failed to map region");
684 }
685 Ok(MappedRegion { addr, len })
686 }
687
688 pub fn supports_dma_buf(&self) -> anyhow::Result<bool> {
695 let feature = VfioDeviceFeatureDmaBuf {
696 argsz: size_of::<VfioDeviceFeatureDmaBuf>() as u32,
697 flags: VFIO_DEVICE_FEATURE_PROBE
698 | VFIO_DEVICE_FEATURE_GET
699 | VFIO_DEVICE_FEATURE_DMA_BUF,
700 region_index: 0,
701 open_flags: 0,
702 dma_buf_flags: 0,
703 nr_ranges: 0,
704 range_offset: 0,
705 range_length: 0,
706 };
707 match unsafe { ioctl::vfio_device_feature_dma_buf(self.file.as_raw_fd(), &feature) } {
710 Ok(_) => Ok(true),
711 Err(nix::errno::Errno::ENOTTY | nix::errno::Errno::EOPNOTSUPP) => Ok(false),
713 Err(e) => Err(e).context("VFIO_DEVICE_FEATURE_DMA_BUF probe failed"),
714 }
715 }
716
717 pub fn export_dma_buf(
725 &self,
726 region_index: u32,
727 offset: u64,
728 length: u64,
729 ) -> anyhow::Result<OwnedFd> {
730 let feature = VfioDeviceFeatureDmaBuf {
731 argsz: size_of::<VfioDeviceFeatureDmaBuf>() as u32,
732 flags: VFIO_DEVICE_FEATURE_GET | VFIO_DEVICE_FEATURE_DMA_BUF,
733 region_index,
734 open_flags: (libc::O_RDWR | libc::O_CLOEXEC) as u32,
735 dma_buf_flags: 0,
736 nr_ranges: 1,
737 range_offset: offset,
738 range_length: length,
739 };
740 let fd = unsafe {
744 ioctl::vfio_device_feature_dma_buf(self.file.as_raw_fd(), &feature)
745 .context("VFIO_DEVICE_FEATURE_DMA_BUF export failed")?
746 };
747 Ok(unsafe { OwnedFd::from_raw_fd(fd) })
749 }
750
751 pub fn map_msix<I>(&self, start: u32, eventfd: I) -> anyhow::Result<()>
752 where
753 I: IntoIterator,
754 I::Item: AsFd,
755 {
756 let fds: Vec<_> = eventfd.into_iter().collect();
759
760 #[repr(C)]
769 #[derive(Copy, Clone)]
770 struct VfioIrqSetHeader {
771 argsz: u32,
772 flags: u32,
773 index: u32,
774 start: u32,
775 count: u32,
776 }
777 const _: () = assert!(
778 size_of::<VfioIrqSetHeader>() == size_of::<vfio_irq_set>(),
779 "VfioIrqSetHeader must match the fixed prefix of vfio_irq_set"
780 );
781
782 let mut param = HeaderVec::<VfioIrqSetHeader, i32, 0>::with_capacity(
783 VfioIrqSetHeader {
784 argsz: 0, flags: VFIO_IRQ_SET_ACTION_TRIGGER,
786 index: VFIO_PCI_MSIX_IRQ_INDEX,
787 start,
788 count: 0,
789 },
790 fds.len(),
791 );
792 for fd in &fds {
793 param.push_tail(fd.as_fd().as_raw_fd());
794 }
795
796 let argsz = param.total_byte_len() as u32;
798 param.head.argsz = argsz;
799 param.head.count = fds.len() as u32;
800 if fds.is_empty() {
801 param.head.flags |= VFIO_IRQ_SET_DATA_NONE;
802 } else {
803 param.head.flags |= VFIO_IRQ_SET_DATA_EVENTFD;
804 }
805
806 unsafe {
810 ioctl::vfio_device_set_irqs(self.file.as_raw_fd(), param.as_ptr().cast())
811 .context("failed to set msi-x trigger")?;
812 }
813 Ok(())
814 }
815
816 pub fn unmap_msix(&self) -> anyhow::Result<()> {
827 let header = vfio_irq_set {
828 argsz: size_of::<vfio_irq_set>() as u32,
829 flags: VFIO_IRQ_SET_ACTION_TRIGGER | VFIO_IRQ_SET_DATA_NONE,
830 index: VFIO_PCI_MSIX_IRQ_INDEX,
831 start: 0,
832 count: 0,
833 data: Default::default(),
834 };
835
836 unsafe {
838 ioctl::vfio_device_set_irqs(self.file.as_raw_fd(), &header)
839 .context("failed to unmap msix vectors")?;
840 }
841 Ok(())
842 }
843
844 pub fn reset(&self) -> anyhow::Result<()> {
849 unsafe {
851 ioctl::vfio_device_reset(self.file.as_raw_fd()).context("VFIO_DEVICE_RESET failed")?;
852 }
853 Ok(())
854 }
855
856 pub fn file(&self) -> &File {
863 &self.file
864 }
865}
866
867fn parse_sparse_mmap_caps(buf: &HeaderVec<vfio_region_info, u8, 0>) -> Option<Vec<MemoryRange>> {
873 let mut offset = buf.head.cap_offset as usize;
874
875 let bytes =
877 unsafe { std::slice::from_raw_parts(buf.as_ptr().cast::<u8>(), buf.total_byte_len()) };
878
879 while offset != 0 {
880 if offset + size_of::<vfio_info_cap_header>() > bytes.len() {
881 tracing::warn!(offset, "VFIO cap header extends beyond buffer");
882 break;
883 }
884
885 let header = unsafe { &*bytes.as_ptr().add(offset).cast::<vfio_info_cap_header>() };
888
889 if header.id as u32 == VFIO_REGION_INFO_CAP_SPARSE_MMAP {
890 if offset + size_of::<vfio_region_info_cap_sparse_mmap>() > bytes.len() {
891 tracing::warn!("VFIO sparse mmap cap truncated");
892 break;
893 }
894 let cap = unsafe {
896 &*bytes
897 .as_ptr()
898 .add(offset)
899 .cast::<vfio_region_info_cap_sparse_mmap>()
900 };
901 let n = cap.nr_areas as usize;
902 let areas_end = offset
903 + size_of::<vfio_region_info_cap_sparse_mmap>()
904 + n * size_of::<vfio_region_sparse_mmap_area>();
905 if areas_end > bytes.len() {
906 tracing::warn!(n, "VFIO sparse mmap areas extend beyond buffer");
907 break;
908 }
909 let areas = unsafe { cap.areas.as_slice(n) };
911 return Some(
912 areas
913 .iter()
914 .filter(|a| a.size > 0)
915 .map(|a| MemoryRange::new(a.offset..a.offset + a.size))
916 .collect(),
917 );
918 }
919
920 offset = header.next as usize;
921 }
922
923 None
925}
926
927impl AsRef<File> for Device {
928 fn as_ref(&self) -> &File {
929 &self.file
930 }
931}
932
933impl AsFd for Device {
934 fn as_fd(&self) -> BorrowedFd<'_> {
935 self.file.as_fd()
936 }
937}
938
939pub fn find_msix_irq(pci_id: &str, index: u32) -> anyhow::Result<u32> {
941 let buffered = BufReader::new(File::open("/proc/interrupts")?);
942
943 let id = format!("vfio-msix[{}]({})", index, pci_id);
944 let match_str = buffered
945 .lines()
946 .map_while(Result::ok)
947 .find(|line| line.contains(&id))
948 .with_context(|| format!("cannot find interrupt {id} in /proc/interrupts"))?;
949
950 let irq = match_str.trim_start().split(':').next().unwrap();
952 let irq: u32 = irq
953 .parse()
954 .with_context(|| format!("unexpected irq format {}. Expecting 'irq#:'", irq))?;
955
956 Ok(irq)
957}
958
959pub fn print_relevant_params() {
960 #[derive(Debug)]
961 struct Param {
962 _name: &'static str,
963 _value: Option<String>,
964 }
965
966 let vfio_params = [
967 "/sys/module/vfio/parameters/enable_unsafe_noiommu_mode",
968 "/sys/module/driver/parameters/async_probe",
969 ]
970 .iter()
971 .map(|path| Param {
972 _name: path,
973 _value: fs::read_to_string(path).ok().map(|s| s.trim().to_string()),
974 })
975 .collect::<Vec<_>>();
976
977 tracing::debug!(
978 vfio_params = ?vfio_params,
979 "Relevant VFIO module parameters"
980 );
981}
982
983pub struct MappedRegion {
984 addr: *mut c_void,
985 len: usize,
986}
987
988unsafe impl Send for MappedRegion {}
990unsafe impl Sync for MappedRegion {}
992
993impl MappedRegion {
994 pub fn as_ptr(&self) -> *mut c_void {
995 self.addr
996 }
997
998 pub fn len(&self) -> usize {
999 self.len
1000 }
1001
1002 pub fn read_u32(&self, offset: usize) -> u32 {
1003 assert_eq!(offset % 4, 0);
1004 assert!(offset.saturating_add(4) <= self.len);
1005 unsafe { std::ptr::read_volatile(self.addr.byte_add(offset).cast()) }
1007 }
1008
1009 pub fn read_u64(&self, offset: usize) -> u64 {
1010 assert_eq!(offset % 8, 0);
1011 assert!(offset.saturating_add(8) <= self.len);
1012 unsafe { std::ptr::read_volatile(self.addr.byte_add(offset).cast()) }
1014 }
1015
1016 pub fn write_u32(&self, offset: usize, data: u32) {
1017 assert_eq!(offset % 4, 0);
1018 assert!(offset.saturating_add(4) <= self.len);
1019 unsafe {
1021 std::ptr::write_volatile(self.addr.byte_add(offset).cast(), data);
1022 }
1023 }
1024
1025 pub fn write_u64(&self, offset: usize, data: u64) {
1026 assert_eq!(offset % 8, 0);
1027 assert!(offset.saturating_add(8) <= self.len);
1028 unsafe {
1030 std::ptr::write_volatile(self.addr.byte_add(offset).cast(), data);
1031 }
1032 }
1033}
1034
1035impl Drop for MappedRegion {
1036 fn drop(&mut self) {
1037 unsafe {
1039 libc::munmap(self.addr, self.len);
1040 }
1041 }
1042}