VFIO为多用户在用户态访问设备提供了支持,这为用户态驱动和设备虚拟化奠定了基础。VFIO抽象了三个层次:device, group, container:
注:device属于group,每个group可以关联到一个container。
首先,container负责管理内存资源,和IOMMU、DMA及地址空间相关。用户空间通过文件/dev/vfio/vfio获取对应的文件描述符。具体操作如下:
//打开container文件
container = open("/dev/vfio/vfio", O_RDWR);
//通过ioctl设置IOMMU。此外,还可以通过ioctl做诸如获取container信息或其他设置
ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);
//让iommu建立 “dma_map.iova -> dma_map.vaddr对应的物理地址” 的映射
dma_map.vaddr = mmap(0, 1024 * 1024, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
dma_map.size = 1024 * 1024;
dma_map.iova = 0; /* 1MB starting at 0x0 from device view */
dma_map.flags = VFIO_DMA_MAP_FLAG_READ | VFIO_DMA_MAP_FLAG_WRITE;
ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
group是透传的最小单位。假设lspci获取需要透传的设备id为05:00.0,操作group前需要做如下准备:
然后,用户空间就可以通过打开文件/dev/vfio/29访问该group,具体操作如下:
// 打开group文件
group = open("/dev/vfio/29", O_RDWR);
// 通过ioctl将group关联到container,另外还可以作信息获取或其他设置
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
通过group可以打开device文件,然后可以对文件进行读写和mmap。
// 通过group打开设备
device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:05:00.0");
ioctl(device, VFIO_DEVICE_GET_INFO, &device_info);
for (int i = 0; i < device_info.num_regions; i++) {
reg.index = i;
... ...
ioctl(device, VFIO_DEVICE_GET_REGION_INFO, ®);
... ...
}
for (i = 0; i < device_info.num_irqs; i++) {
irq.index = i;
... ...
ioctl(device, VFIO_DEVICE_GET_IRQ_INFO, &irq);
... ...
}
ioctl(device, VFIO_DEVICE_RESET);
在vfio_init函数中,将vfio_dev注册为misc设备,本质上是一个字符设备。vfio_dev的fops回调为:
static const struct file_operations vfio_fops = {
.owner = THIS_MODULE,
.open = vfio_fops_open,
.release = vfio_fops_release,
.read = vfio_fops_read,
.write = vfio_fops_write,
.unlocked_ioctl = vfio_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vfio_fops_compat_ioctl,
#endif
.mmap = vfio_fops_mmap,
};
在vfio_fops_open中,通过kzalloc申请一片内存将其作为struct vfio_container。最后将filep->private_data设置为新申请的struct vfio_container内存指针。
在vfio_fops_release中,释放filep->private_data指向的内存、并将filep->private_data置NULL。
后者是对前者的封装。在vfio_fops_unl_ioctl中,实现了如下的ioctl :
注:当设置了IOMMU类型后,container的iommu_driver和iommu_data也就被设置。然后其他ioctl命令则由container->iommu_driver->ops->ioctl处理。
目前有三种iommu实现,vfio_noiommu_ops、tce_iommu_driver_ops和vfio_iommu_driver_ops_type1。
在vfio_init函数中,为group字符设备注册了vfio_group_fops文件操作回调:
static const struct file_operations vfio_group_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = vfio_group_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vfio_group_fops_compat_ioctl,
#endif
.open = vfio_group_fops_open,
.release = vfio_group_fops_release,
};
在vfio_add_group_dev中,如果dev的group不存在,则会通过vfio_create_group创建一个group。vfio_create_group逻辑如下:
该函数执行了vfio_create_group的反向释放操作,当group引用计数为0时释放。
当打开group文件的时候,会调用该回调。其逻辑如下:
该函数是文件关闭时的释放操作。
后者是对前者的封装。在vfio_group_fops_unl_ioctl中,实现了如下的ioctl :
注:vfio可以实现多种不同功能的设备,目前主要实现了三类:mdev、pci、platform设备。后面以vfio-pci设备为例。
首先,在vfio-pci驱动模块初始化的时候,注册了一个pci驱动vfio_pci_driver:
static struct pci_driver vfio_pci_driver = {
.name = "vfio-pci",
.id_table = NULL, /* only dynamic ids */
.probe = vfio_pci_probe,
.remove = vfio_pci_remove,
.err_handler = &vfio_err_handlers,
};
vfio_pci_ops的文件操作设置如下:
static const struct vfio_device_ops vfio_pci_ops = {
.name = "vfio-pci",
.open = vfio_pci_open,
.release = vfio_pci_release,
.ioctl = vfio_pci_ioctl,
.read = vfio_pci_read,
.write = vfio_pci_write,
.mmap = vfio_pci_mmap,
.request = vfio_pci_request,
};
对group文件调用ioctl执行VFIO_GROUP_GET_DEVICE_FD命令,将调用vfio_group_get_device_fd从struct vfio_group.device_list链表中查询设备,并创建一个文件对象,将struct vfio_device赋值给file->private_data;然后调用vfio_pci_ops.open方法;同时设置file的操作函数集合为vfio_device_fops:
static const struct file_operations vfio_device_fops = {
.owner = THIS_MODULE,
.release = vfio_device_fops_release,
.read = vfio_device_fops_read,
.write = vfio_device_fops_write,
.unlocked_ioctl = vfio_device_fops_unl_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vfio_device_fops_compat_ioctl,
#endif
.mmap = vfio_device_fops_mmap,
};
注:上述vfio_device_fops_*函数集合是对vfio_pci_*的包装,或者说代理。