[AI问答] VFIO操作的相关函数学习

VFIO 的 API 有个特点:没有独立的库函数,一切通过 ioctl 完成 (外加 open/close/mmap/pread/pwrite 这类基础系统调用)。所以"函数列表"实际上是三张 ioctl 命令表,按 container → group → device 三层对象组织。下面按学习顺序梳理。

一、总体模型:三个对象,一套 ioctl

复制代码
/dev/vfio/vfio   ──open()──►  container fd  (IOMMU 域容器,DMA 映射挂这)
/dev/vfio/N      ──open()──►  group fd       (IOMMU 组,设备必须按组隔离)
   └─ ioctl 拿到 ──►           device fd     (具体设备,BAR/中断/配置空间走这)

头文件:<linux/vfio.h>(Type1 IOMMU 的结构体也在其中,老内核在 <linux/vfio_iommu_type1.h>)。

二、Container 级 ioctl

命令 参数结构体 作用
VFIO_GET_API_VERSION 无(返回值) 返回内核 VFIO API 版本,应等于 VFIO_API_VERSION
VFIO_CHECK_EXTENSION int(扩展类型) 检查是否支持某 IOMMU 模型:VFIO_TYPE1_IOMMU / VFIO_TYPE1v2_IOMMU / VFIO_NOIOMMU_IOMMU
VFIO_SET_IOMMU int(模型类型) 绑定 IOMMU 模型。必须先挂至少一个 group,否则 EINVAL
VFIO_IOMMU_GET_INFO vfio_iommu_type1_info 查询页大小掩码、可用 IOVA 窗口(5.4+ 通过 cap 扩展)
VFIO_IOMMU_MAP_DMA vfio_iommu_type1_dma_map 建立 iova→物理页映射(你之前那段代码)
VFIO_IOMMU_UNMAP_DMA vfio_iommu_type1_dma_unmap 撤销映射,返回值里带实际解除的 unmapped 大小
关键结构体:
c 复制代码
struct vfio_iommu_type1_dma_map {
    __u32 argsz;
    __u32 flags;      // VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE
    __u64 vaddr;      // 进程虚拟地址
    __u64 iova;       // 设备侧地址
    __u64 size;
};
struct vfio_iommu_type1_dma_unmap {
    __u32 argsz;
    __u32 flags;
    __u64 iova;
    __u64 size;
    __u64 unmapped;   // 输出:实际解除的字节数
};

三、Group 级 ioctl

命令 参数 作用
VFIO_GROUP_GET_STATUS vfio_group_status 查询组是否可用(VFIO_GROUP_FLAGS_VIABLE)及是否已挂容器
VFIO_GROUP_SET_CONTAINER int container_fd 把组加入容器
VFIO_GROUP_UNSET_CONTAINER int container_fd 从容器移除
VFIO_GROUP_GET_DEVICE_FD char *设备名 核心命令 :按设备名(如 "0000:01:00.0")拿到 device fd

四、Device 级 ioctl

命令 结构体 作用
VFIO_DEVICE_GET_INFO vfio_device_info 设备类型标志、region 数、IRQ 数
VFIO_DEVICE_GET_REGION_INFO vfio_region_info 查每个 region 的 offset/size/flags(BAR、配置空间都算 region)
VFIO_DEVICE_GET_IRQ_INFO vfio_irq_info 查每个 IRQ index 的向量数(如 MSI-X 有多少条)
VFIO_DEVICE_SET_IRQS vfio_irq_set 配置中断:绑 eventfd、mask/unmask
VFIO_DEVICE_RESET 无 设备复位(PCI FLR),初始化前建议做一次
VFIO_DEVICE_GET_PCI_HOT_RESET_INFO / VFIO_DEVICE_PCI_HOT_RESET vfio_pci_hot_reset(_info) PCI 热复位(涉及同组设备时用)
关键结构体:
c 复制代码
struct vfio_device_info {
    __u32 argsz;
    __u32 flags;        // VFIO_DEVICE_FLAGS_PCI | VFIO_DEVICE_FLAGS_RESET
    __u32 num_regions;  // PCI 设备为 9(BAR0-5、ROM、CONFIG、VGA)
    __u32 num_irqs;     // PCI 设备为 5(INTX/MSI/MSIX/ERR/REQ)
};
struct vfio_region_info {
    __u32 argsz;
    __u32 flags;        // READ/WRITE/MMAP
    __u32 index;
    __u32 cap_offset;   // 稀疏 mmap 等扩展
    __u64 size;
    __u64 offset;       // 在 device fd 上的偏移,pread/mmap 都用它
};

PCI 设备的 region/IRQ 索引宏:

c 复制代码
VFIO_PCI_BAR0_REGION_INDEX ~ VFIO_PCI_BAR5_REGION_INDEX   // 0~5
VFIO_PCI_ROM_REGION_INDEX                                 // 6
VFIO_PCI_CONFIG_REGION_INDEX                              // 7
VFIO_PCI_INTX_IRQ_INDEX, MSI, MSIX, ERR, REQ              // IRQ: 0~4

五、两个高频操作:访问 BAR 和收中断

1. 映射 BAR0 到用户态(访问设备寄存器)

c 复制代码
struct vfio_region_info reg = {
    .argsz = sizeof(reg),
    .index = VFIO_PCI_BAR0_REGION_INDEX,
};
ioctl(dev_fd, VFIO_DEVICE_GET_REGION_INFO, &reg);
// MMIO 寄存器:mmap 后直接读写
void *bar0 = mmap(NULL, reg.size, PROT_READ | PROT_WRITE,
                  MAP_SHARED, dev_fd, reg.offset);
// 配置空间等慢速访问:用 pread/pwrite(注意 offset 是 reg.offset + 寄存器偏移)
uint16_t vendor_id;
pread(dev_fd, &vendor_id, 2, reg_cfg.offset + 0x00);

2. 绑定 MSI-X 中断到 eventfd

c 复制代码
int efd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
struct vfio_irq_set *irq_set = calloc(1, sizeof(*irq_set) + sizeof(int));
irq_set->argsz = sizeof(*irq_set) + sizeof(int);
irq_set->flags = VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
irq_set->index = VFIO_PCI_MSIX_IRQ_INDEX;
irq_set->start = 0;          // 从第 0 个向量开始
irq_set->count = 1;          // 绑 1 个向量
*(int *)irq_set->data = efd; // data 区就是 fd 数组
ioctl(dev_fd, VFIO_DEVICE_SET_IRQS, irq_set);
// 之后 epoll/read(efd) 即可收到中断通知

六、完整初始化序列(把所有命令串起来)

c 复制代码
struct vfio_dev {
    int container_fd, group_fd, device_fd;
};
int vfio_init(struct vfio_dev *d, int group_id, const char *dev_name)
{
    // 1. 打开 container
    d->container_fd = open("/dev/vfio/vfio", O_RDWR);
    // 2. 验证 API 版本和 IOMMU 模型
    if (ioctl(d->container_fd, VFIO_GET_API_VERSION) != VFIO_API_VERSION)
        return -1;
    if (!ioctl(d->container_fd, VFIO_CHECK_EXTENSION, VFIO_TYPE1v2_IOMMU))
        return -1;
    // 3. 打开 group(如 /dev/vfio/42)
    char path[64];
    snprintf(path, sizeof(path), "/dev/vfio/%d", group_id);
    d->group_fd = open(path, O_RDWR);
    // 4. 组挂入容器(此时才允许 SET_IOMMU)
    ioctl(d->group_fd, VFIO_GROUP_SET_CONTAINER, &d->container_fd);
    ioctl(d->container_fd, VFIO_SET_IOMMU, VFIO_TYPE1v2_IOMMU);
    // 5. 拿设备 fd
    d->device_fd = ioctl(d->group_fd, VFIO_GROUP_GET_DEVICE_FD, dev_name);
    // 6. 复位设备
    ioctl(d->device_fd, VFIO_DEVICE_RESET);
    return 0;
}
// 之后:mmap 大页 → VFIO_IOMMU_MAP_DMA(你已有的代码)
//       → GET_REGION_INFO + mmap BAR → SET_IRQS → 驱动运行

七、系统侧准备(API 之外的事)

bash 复制代码
# 1. 开 IOMMU(Intel 需要内核参数;AMD 默认开)
#    grub: intel_iommu=on iommu=pt
# 2. 确认设备属于哪个组
readlink /sys/bus/pci/devices/0000:01:00.0/iommu_group
# 3. 组内所有设备必须都从原驱动解绑(否则组不可用)
echo 0000:01:00.0 > /sys/bus/pci/drivers/ixgbe/unbind
# 4. 绑到 vfio-pci
echo vfio-pci > /sys/bus/pci/devices/0000:01:00.0/driver_override
echo 0000:01:00.0 > /sys/bus/pci/drivers/vfio-pci/bind
# 5. 权限(非 root 跑时)
chmod 666 /dev/vfio/vfio /dev/vfio/42

八、常见错误速查

报错位置 errno 原因
open("/dev/vfio/N") ENOENT IOMMU 没开启,组不存在
VFIO_GROUP_SET_CONTAINER EINVAL container fd 无效
VFIO_SET_IOMMU EINVAL 没先挂任何 group,或模型不支持
VFIO_GROUP_GET_DEVICE_FD ENODEV 设备不在该组 / 没绑到 vfio-pci
打开 group 后状态不 viable --- 组内还有设备被原驱动占用(最常见!)
VFIO_IOMMU_MAP_DMA EINVAL iova 区间与已有映射重叠
VFIO_IOMMU_MAP_DMA ENOMEM pin 物理页失败(内存不足/ulimit -l 太小)

九、进阶方向(了解即可)

  • No-IOMMU 模式 :VFIO_NOIOMMU_IOMMU,无 IOMMU 平台的逃生通道,不安全(设备可 DMA 任意物理内存),需内核参数 enable_unsafe_noiommu_mode=1。此模式下用不了 MAP_DMA,回到 virt_to_phys 路线。
  • 脏页跟踪、热迁移 :VFIO_IOMMU_DIRTY_PAGES 等扩展,虚拟化场景用。
  • iommufd(内核 6.0+) :新的 /dev/iommu 框架逐步替代 Type1 后端,接口变了(VFIO_DEVICE_BIND_IOMMUFD),但目前主流发行版和 DPDK 仍以 Type1 为主。
  • 不想手写 :DPDK 的 rte_vfio_container_dma_map() / rte_vfio_get_group_num() 等封装就是上面这套流程的成熟实现,源码在 lib/eal/linux/eal_vfio.c,是最好的学习范例。

学习路径建议

按你已有的代码进度,下一步依次是:GET_REGION_INFO + mmap BAR (能读写寄存器)→ SET_IRQS 绑 eventfd (能收中断)→ 用寄存器/描述符环驱动设备。对照第六节序列,你当前处于第 5 步之后,MAP_DMA 已就位------剩下的就是设备本身的协议了。

相关推荐
Century_Dragon2 小时前
把一辆智能汽车“搬“进教室—智能网联汽车自动驾驶虚拟教学实训
学习
兔头?2 小时前
JVM学习文档
jvm·学习
无心水2 小时前
15、云原生架构设计理论与实践 核心考点与学习笔记
笔记·学习·云原生·架构师·软考·系统架构设计师·软考系统架构设计师
动词ing3 小时前
【学习笔记】51单片机 独立按键控制LED显示二进制
笔记·学习·51单片机
叼烟扛炮3 小时前
Linux 学习笔记(三十九):网络基石——TCP/IP 模型与 UDP 实战
linux·运维·网络·笔记·学习·tcp/ip
前进的李工3 小时前
Agent 学习笔记 04:KV Cache 与上下文缓存
笔记·学习·缓存·agent·注意力机制·kv cache·prompt cache
Z21-X3 小时前
云原生运维笔记:打通OpenStack的任督二脉——从Neutron网络踩坑到Swift与Heat硬核实战
运维·笔记·学习·云原生
旖旎夜光4 小时前
LCR 170:交易逆序对的总数(归并排序) —— 题解
c++·学习·算法·leetcode·排序算法
小向是个Der12 小时前
嵌入式进阶——MCU上电后的动作
c语言·单片机·mcu·学习·链接脚本