vfio_sys/cdev.rs
1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! VFIO cdev (per-device fd) support.
5//!
6//! VFIO cdev is the modern device-access interface (`/dev/vfio/devices/vfioN`)
7//! that replaces the legacy group/container model. Each device gets its own
8//! character device node. The device is bound to an iommufd instance via
9//! `VFIO_DEVICE_BIND_IOMMUFD`, and DMA is configured by attaching an iommufd
10//! IOAS or HWPT via `VFIO_DEVICE_ATTACH_IOMMUFD_PT`.
11//!
12//! Once bound and attached, the device fd supports the same `VFIO_DEVICE_*`
13//! ioctls as the legacy group path (get_info, get_region_info, set_irqs,
14//! reset, mmap). The [`CdevDevice`] type wraps the fd and provides these
15//! operations, producing a [`super::Device`] for the common ioctl surface.
16
17use anyhow::Context as _;
18use std::fs;
19use std::os::unix::prelude::*;
20
21mod ioctl {
22 use nix::request_code_none;
23 use vfio_bindings::bindings::vfio::VFIO_BASE;
24 use vfio_bindings::bindings::vfio::VFIO_TYPE;
25
26 // VFIO_DEVICE_BIND_IOMMUFD = _IO(VFIO_TYPE, VFIO_BASE + 18)
27 nix::ioctl_readwrite_bad!(
28 vfio_device_bind_iommufd,
29 request_code_none!(VFIO_TYPE, VFIO_BASE + 18),
30 super::VfioDeviceBindIommufd
31 );
32
33 // VFIO_DEVICE_ATTACH_IOMMUFD_PT = _IO(VFIO_TYPE, VFIO_BASE + 19)
34 nix::ioctl_readwrite_bad!(
35 vfio_device_attach_iommufd_pt,
36 request_code_none!(VFIO_TYPE, VFIO_BASE + 19),
37 super::VfioDeviceAttachIommufdPt
38 );
39
40 // VFIO_DEVICE_DETACH_IOMMUFD_PT = _IO(VFIO_TYPE, VFIO_BASE + 20)
41 nix::ioctl_readwrite_bad!(
42 vfio_device_detach_iommufd_pt,
43 request_code_none!(VFIO_TYPE, VFIO_BASE + 20),
44 super::VfioDeviceDetachIommufdPt
45 );
46}
47
48// Kernel ABI structs — must match `include/uapi/linux/vfio.h` exactly.
49
50#[repr(C)]
51pub struct VfioDeviceBindIommufd {
52 pub argsz: u32,
53 pub flags: u32,
54 pub iommufd: i32,
55 pub out_devid: u32,
56}
57
58#[repr(C)]
59pub struct VfioDeviceAttachIommufdPt {
60 pub argsz: u32,
61 pub flags: u32,
62 pub pt_id: u32,
63}
64
65#[repr(C)]
66pub struct VfioDeviceDetachIommufdPt {
67 pub argsz: u32,
68 pub flags: u32,
69}
70
71/// A VFIO device opened via the cdev interface (`/dev/vfio/devices/vfioN`).
72///
73/// This is the modern per-device access path. After opening, the device must
74/// be bound to an iommufd fd via [`bind_iommufd`](Self::bind_iommufd) and
75/// then attached to an IOAS or HWPT via [`attach_ioas`](Self::attach_ioas)
76/// before any DMA can occur.
77///
78/// Once bound and attached, call [`into_device`](Self::into_device) to get
79/// the standard [`Device`](super::Device) for config space, BAR, IRQ, and
80/// mmap operations.
81pub struct CdevDevice {
82 file: fs::File,
83}
84
85impl CdevDevice {
86 /// Wrap a pre-opened VFIO cdev file descriptor.
87 pub fn from_file(file: fs::File) -> Self {
88 Self { file }
89 }
90
91 /// Bind this device to an iommufd instance.
92 ///
93 /// Returns the kernel-assigned device ID within the iommufd context.
94 /// This must be called before any DMA operations.
95 pub fn bind_iommufd(&self, iommufd_fd: RawFd) -> anyhow::Result<u32> {
96 let mut cmd = VfioDeviceBindIommufd {
97 argsz: size_of::<VfioDeviceBindIommufd>() as u32,
98 flags: 0,
99 iommufd: iommufd_fd,
100 out_devid: 0,
101 };
102 // SAFETY: Both fds are valid, struct correctly constructed.
103 unsafe {
104 ioctl::vfio_device_bind_iommufd(self.file.as_raw_fd(), &mut cmd)
105 .context("VFIO_DEVICE_BIND_IOMMUFD failed")?;
106 }
107 Ok(cmd.out_devid)
108 }
109
110 /// Attach the device to an IOAS or HWPT by its iommufd object ID.
111 ///
112 /// Pass an IOAS ID for identity DMA translation, or a HWPT ID for
113 /// nested translation.
114 ///
115 /// Returns the attached page table ID (may differ from input if the
116 /// kernel auto-created a HWPT for the IOAS).
117 pub fn attach_ioas(&self, pt_id: u32) -> anyhow::Result<u32> {
118 attach_iommufd_pt(self.file.as_fd(), pt_id)
119 }
120
121 /// Detach the device from its current IOAS/HWPT.
122 ///
123 /// After detaching, the device is in a blocking DMA state.
124 pub fn detach_ioas(&self) -> anyhow::Result<()> {
125 detach_iommufd_pt(self.file.as_fd())
126 }
127
128 /// Convert to a standard [`Device`](super::Device) for config space,
129 /// BAR, IRQ, and mmap operations.
130 ///
131 /// The cdev fd supports the same `VFIO_DEVICE_*` ioctls as the legacy
132 /// group path, so the [`Device`](super::Device) type works unchanged.
133 pub fn into_device(self) -> super::Device {
134 super::Device { file: self.file }
135 }
136}
137
138impl AsRef<fs::File> for CdevDevice {
139 fn as_ref(&self) -> &fs::File {
140 &self.file
141 }
142}
143
144impl AsFd for CdevDevice {
145 fn as_fd(&self) -> BorrowedFd<'_> {
146 self.file.as_fd()
147 }
148}
149
150impl super::Device {
151 /// Attach this VFIO device to an iommufd page table (IOAS or HWPT) by its
152 /// object ID, via `VFIO_DEVICE_ATTACH_IOMMUFD_PT`.
153 ///
154 /// Only valid for a device opened through the cdev path and bound to
155 /// iommufd (see [`CdevDevice::bind_iommufd`]); legacy group-path devices
156 /// use the container/IOAS model instead. If the device is already attached,
157 /// the kernel replaces the page table atomically. Returns the attached
158 /// page table ID (the kernel may substitute an auto-created HWPT for an
159 /// IOAS).
160 pub fn attach_pt(&self, pt_id: u32) -> anyhow::Result<u32> {
161 attach_iommufd_pt(self.as_fd(), pt_id)
162 }
163
164 /// Detach this VFIO device from its current iommufd page table, via
165 /// `VFIO_DEVICE_DETACH_IOMMUFD_PT`. Afterward the device is in the blocking
166 /// DMA state (abort). Only valid for iommufd-bound (cdev) devices.
167 pub fn detach_pt(&self) -> anyhow::Result<()> {
168 detach_iommufd_pt(self.as_fd())
169 }
170}
171
172/// Attach a VFIO cdev device (by fd) to an iommufd page table (IOAS or HWPT)
173/// via `VFIO_DEVICE_ATTACH_IOMMUFD_PT`.
174///
175/// Shared implementation behind [`CdevDevice::attach_ioas`] and
176/// [`super::Device::attach_pt`]. If the device is already attached, the kernel
177/// performs an atomic page-table replacement. Returns the attached page table
178/// ID (the kernel may substitute an auto-created HWPT for an IOAS).
179fn attach_iommufd_pt(device_fd: BorrowedFd<'_>, pt_id: u32) -> anyhow::Result<u32> {
180 let mut cmd = VfioDeviceAttachIommufdPt {
181 argsz: size_of::<VfioDeviceAttachIommufdPt>() as u32,
182 flags: 0,
183 pt_id,
184 };
185 // SAFETY: fd is valid (caller holds BorrowedFd), struct correctly
186 // constructed.
187 unsafe {
188 ioctl::vfio_device_attach_iommufd_pt(device_fd.as_raw_fd(), &mut cmd)
189 .context("VFIO_DEVICE_ATTACH_IOMMUFD_PT failed")?;
190 }
191 Ok(cmd.pt_id)
192}
193
194/// Detach a VFIO cdev device (by fd) from its current iommufd page table via
195/// `VFIO_DEVICE_DETACH_IOMMUFD_PT`. Afterward the device is in the blocking DMA
196/// state (abort).
197///
198/// Shared implementation behind [`CdevDevice::detach_ioas`] and
199/// [`super::Device::detach_pt`].
200fn detach_iommufd_pt(device_fd: BorrowedFd<'_>) -> anyhow::Result<()> {
201 let mut cmd = VfioDeviceDetachIommufdPt {
202 argsz: size_of::<VfioDeviceDetachIommufdPt>() as u32,
203 flags: 0,
204 };
205 // SAFETY: fd is valid (caller holds BorrowedFd), struct correctly
206 // constructed.
207 unsafe {
208 ioctl::vfio_device_detach_iommufd_pt(device_fd.as_raw_fd(), &mut cmd)
209 .context("VFIO_DEVICE_DETACH_IOMMUFD_PT failed")?;
210 }
211 Ok(())
212}