VFIO is an important technology to give user space (applications or virtual machines) access to devices such as NICs or GPUs. Here is the blurb from the Linux kernel documentation:
Many modern systems now provide DMA and interrupt remapping facilities to help ensure I/O devices behave within the boundaries they’ve been allotted. This includes x86 hardware with AMD-Vi and Intel VT-d, POWER systems with Partitionable Endpoints (PEs), and embedded PowerPC systems such as Freescale PAMU. The VFIO driver is an IOMMU/device-agnostic framework for exposing direct device access to userspace, in a secure, IOMMU-protected environment. In other words, this allows safe [2], non-privileged, userspace drivers.
Why do we want that? Virtual machines often make use of direct device access (“device assignment”) when configured for the highest possible I/O performance. From a device and host perspective, this simply turns the VM into a userspace driver, with the benefits of significantly reduced latency, higher bandwidth, and direct use of bare-metal device drivers.
Prep Work Link to heading
First, list all network devices to locate the target PCI address. This gives the PCI ID 0000:00:14.3
:
lspci -nn | grep -i Net
0000:00:14.3 Network controller: Intel Corporation Wi-Fi 6 AX201
Make sure to load the VFIO module:
modprobe vfio-pci
lsmod | grep vfio
Bind Device to User Space VFIO Link to heading
To allow VFIO to take over the device, unbind it from its current driver. Now bind the device to the vfio-pci driver:
echo "vfio-pci" > /sys/bus/pci/devices/0000:00:14.3/driver_override
echo "0000:00:14.3" > /sys/bus/pci/devices/0000:00:14.3/driver/unbind
echo "0000:00:14.3" > /sys/bus/pci/drivers/vfio-pci/bind
The device is now under user-space control and not managed by the kernel network stack.
Compile and Run Simple VFIO Application Link to heading
This is a simple VFIO application that does the following steps:
Container
from/dev/vio/vfio
Group
based on the number with the VFIO driver that took over the NICDevice
- Memory region for BAR
- mmap the memory region to user space
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/vfio.h>
int main() {
int container, group, device;
int group_id = 0; // Replace with actual group ID
const char *device_name = "0000:00:14.3"; // Replace with device ID
// Open the VFIO container
container = open("/dev/vfio/vfio", O_RDWR);
if (container < 0) {
perror("Failed to open /dev/vfio/vfio");
return 1;
}
// Check extension support
if (!ioctl(container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU)) {
fprintf(stderr, "VFIO_TYPE1_IOMMU not supported\n");
return 1;
}
// Open the IOMMU group
char group_path[64];
snprintf(group_path, sizeof(group_path), "/dev/vfio/%d", group_id);
group = open(group_path, O_RDWR);
if (group < 0) {
perror("Failed to open VFIO group");
return 1;
}
// Set the group to the container
if (ioctl(group, VFIO_GROUP_SET_CONTAINER, &container)) {
perror("Failed to set group container");
return 1;
}
// Enable IOMMU
if (ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU)) {
perror("Failed to set IOMMU");
return 1;
}
// Get device FD
device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, device_name);
if (device < 0) {
perror("Failed to get device FD");
return 1;
}
// Get region info
struct vfio_region_info reg = {
.argsz = sizeof(reg),
.index = VFIO_PCI_BAR0_REGION_INDEX,
};
if (ioctl(device, VFIO_DEVICE_GET_REGION_INFO, ®)) {
perror("Failed to get BAR0 info");
return 1;
}
// Map BAR0 region
void *bar0 = mmap(0, reg.size, PROT_READ | PROT_WRITE, MAP_SHARED, device, reg.offset);
if (bar0 == MAP_FAILED) {
perror("mmap failed");
return 1;
}
// Read first few bytes
uint32_t *ptr = (uint32_t *)bar0;
printf("BAR0 first dword: 0x%08x\n", ptr[0]);
// Cleanup
munmap(bar0, reg.size);
close(device);
close(group);
close(container);
return 0;
}
Build and run the application (as root!) and it will read BAR0:
gcc -o vfio_nic vfio_nic.c
sudo ./vfio_nic
BAR0 first dword: 0x18880000
Release Device Link to heading
Finally, we can return the device to iwlwifi:
echo "0000:00:14.3" > /sys/bus/pci/drivers/iwlwifi/bind