Skip to main content

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}