前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解VFIO驱动框架

深入理解VFIO驱动框架

作者头像
Linux阅码场
发布2022-02-11 07:58:19
4.6K0
发布2022-02-11 07:58:19
举报
文章被收录于专栏:LINUX阅码场LINUX阅码场

作者介绍:

Jack,目前就职于通信行业某上市公司,主要从事Linux相关系统软件开发工作,负责基带芯片Soc芯片建模仿真以及虚拟化系统软件开发,基带芯片soc芯片BringUp及驱动开发,喜欢阅读内核源代码,在不断的学习和工作中深入理解外设虚拟化,网络虚拟化,用户态驱动等内核子系统。

VFIO(Virtual Function I/O)驱动框架是一个用户态驱动框架,在intel平台它充分利用了VT-d等技术提供的DMA Remapping和Interrupt Remapping特性, 在保证直通设备的DMA安全性同时可以达到接近物理设备的I/O的性能。VFIO是一个可以安全的把设备I/O、中断、DMA等暴露到用户空间,用户态进程可以直接使用VFIO驱动访问硬件,从而可以在用户空间完成设备驱动的框架。在内核源码附带的文档<<Documentation/vfio.txt>>中对VFIO描述的相关概念有比较清楚的描述,也对VFIO的使用方法有清楚的描述。对于VT-d,DMAR 等硬件技术细节,可以参考VT-d的SPEC等文档,本文不对具体的物理硬件特性进行描述,涉及到相关部分只是做简单介绍。

在VFIO驱动框架中,有几个核心概念需要理解。包括:IOMMU,device , group ,container。

IOMMU是一个硬件单元,它可以把设备的IO地址映射成虚拟地址,为设备提供页表映射,设备通过IOMMU将数据直接DMA写到用户空间。

Device 是指要操作的硬件设备,这里的设备需要从IOMMU拓扑的角度来看。如果device 是一个硬件拓扑上是独立那么这个设备构成了一个IOMMU group。如果多个设备在硬件是互联的,需要相互访问数据,那么这些设备需要放到一个IOMMU group 中隔离起来。

Group 是IOMMU 能进行DMA隔离的最小单元。一个group 可以有一个或者多个device。

container 是由多个group 组成。虽然group 是VFIO 的最小隔离单元,但是并不是最好的分割粒度,比如多个group 需要共享一个页表的时候。将多个group 组成一个container来提高系统的性能,也方便用户管理。一般一个进程作为一个container。

下图描述了device , group ,container和用户进程(app)的关系。

图1 container、group与device关系

01

VFIO 驱动框架设计分析

VFIO 驱动框架设计的比较清晰,最上层的vifo interface 是和用户进行ioctl 交互的接口。在内核源码中代码路径为:drivers\vfio\vfio.c。vifo_iommu 是对IOMMU driver 的封装,为vifo interface 提供IOMMU 功能,在内核源码中代码路径为:drivers\vfio\vfio_iommu_type1.c。iommu driver 是物理硬件的IOMMU 实现,例如intel VT-D。vfio_pci 是的device 驱动的封装,为vfio interface 提供设备的访问能力,例如访问设备的配置空间,bar空间。在内核源码中代码路径为:drivers\vfio \pci\ vfio_pci.c 。pci_bus driver 是物理PCI 设备的驱动。VFIO的中断重映射相关的部分需要有kvm 相关的代码分析,本文没有分析。

图2 vfio 驱动框架

02

VFIO 用户接口三个层面

VFIO 给用户空间提供的接口主要是有三个层面上的,第一个是container 层面,第二个是group 层面,第三个是device层面。

第一个层面,container的操作是通过打开/dev/vifo/vifo 文件对其执行ioctl操作,主要的操作有:

VFIO_GET_API_VERSION:获取VFIO版本信息

VFIO_CHECK_EXTENSION:检测是否支持特定扩展,支持哪些类型的IOMMU

VFIO_SET_IOMMU:设置指定的IOMMU类型

VFIO_IOMMU_GET_INFO:获取IOMMU的信息

VFIO_IOMMU_MAP_DMA:指定设备端看到的IO地址到进程的虚拟地址之间的映射

第二个层面,group的操作是通过打开/dev/vifo/<group_id>文件, 对其执行ioctl操作,主要的操作有:

VFIO_GROUP_GET_STATUS:获取group 的状态信息

VFIO_GROUP_SET_CONTAINER:设置group 和container 之间的绑定关系

VFIO_GROUP_GET_DEVICE_FD:获取device 的文件描述符fd.

第三个层面的是device ,通过group 层面的ioctl的VFIO_GROUP_GET_DEVICE_FD 命令获取fd, 对获取的fd执行ioctl操作,主要的操作有:

VFIO_DEVICE_GET_REGION_INFO:用来获得设备指定区域region的数据,这里的region 不仅仅是指bar 空间还包括rom空间和配置空间。

VFIO_DEVICE_GET_IRQ_INFO:得到设备的中断信息

VFIO_DEVICE_RESET:重置设备

下图展示了用户态app,内核态VFIO, vfio-pci驱动,VFIO IOMMU 驱动,PCI驱动,IOMMU 驱动以及内核态和用户态通过三个层面的接口示意图:

图3 应用程序和VFIO接口

03

VFIO 驱动主要数据结构

代码语言:javascript
复制
static struct vfio {

struct class*class;
struct list_headiommu_drivers_list;
struct mutexiommu_drivers_lock;
struct list_headgroup_list;
struct idrgroup_idr;
struct mutexgroup_lock;
struct cdevgroup_cdev;
dev_tgroup_devt;
wait_queue_head_trelease_q;
} vfio;

struct vfio 的主要成员变量有:iommu_drivers_list:挂接在container 上的所有vfio iommu_drivers,是对IOMMU driver 的一种封装。group_list:挂接所有 vfio_group, group_idr :idr 值,关联次设备号,group_devt:group 的设备号,group_cdev:表明为一个字符设备。

代码语言:javascript
复制
struct vfio_container {
struct krefkref;
struct list_headgroup_list;
struct rw_semaphoregroup_lock;
struct vfio_iommu_driver*iommu_driver;
void*iommu_data;
boolnoiommu;
};

struct vfio_container 的主要变量有:group_list:关联到vfio_container 上的所有vifo_ group, iommu_driver: vfio_container对iommu设备驱动的封装。iommu_data:iommu_driver->open()函数的返回值,vifo_iommu对象。

代码语言:javascript
复制
struct vfio_group {
struct krefkref;
intminor;
atomic_tcontainer_users;
struct iommu_group*iommu_group;
struct vfio_container*container;
struct list_headdevice_list;
struct mutexdevice_lock;
struct device*dev;
struct notifier_blocknb;
struct list_headvfio_next;
struct list_headcontainer_next;
struct list_headunbound_list;
struct mutexunbound_lock;
atomic_topened;
wait_queue_head_tcontainer_q;
boolnoiommu;
struct kvm*kvm;
struct blocking_notifier_headnotifier;
};

struct vfio_group 的主要变量有:minor为在注册group设备时的次设备号,container_users为该group的container的计数,iommu_group为该group封装的iommu-group, container为该group关联的container,device_list将属于该group下的所有设备连接起来,vfio_next 挂接在vfio. group_list 上,container_next挂接在vfio_container. group_list上,unbound_lock 是挂在vfio_unbound_dev. unbound_next 上,opened表明该group 是否初始化完成。

代码语言:javascript
复制
struct vfio_device {
struct krefkref;
struct device*dev;
const struct vfio_device_ops*ops;
struct vfio_group*group;
struct list_headgroup_next;
void*device_data;
};

vfio_device的主要变量有:ops,指向vfio_pci_ops,group表示所属group,group_next连接同一个group 中的设备,device_data指向vfio_pci_device.

代码语言:javascript
复制
struct vfio_iommu {
struct list_headdomain_list;
struct vfio_domain*external_domain; /* domain for external user */
struct mutexlock;
struct rb_rootdma_list;
struct blocking_notifier_head notifier;
unsigned intdma_avail;
boolv2;
boolnesting;
};

vfio_iommu的主要变量有:domain_list 为该vfio_iommu下挂接的vfio_domain,external_domain 用于pci_mdev下的vfio_domain,dma_list为dma 的rb_root的根节点,dma_avail表示dma 条目数量。

代码语言:javascript
复制
struct vfio_domain {
struct iommu_domain*domain;
struct list_headnext;
struct list_headgroup_list;
intprot;/* IOMMU_CACHE */
boolfgsp;/* Fine-grained super pages */
};

vfio_domain的主要变量有:domain为对iommu_domain的封装,next挂接到vfio_iommu. domain_list, group_list是挂在该vfio_domain上的vfio_group。

代码语言:javascript
复制
struct vfio_group {
struct iommu_group*iommu_group;
struct list_headnext;
boolmdev_group;/* An mdev group */
};

vfio_group的主要成员变量有:iommu_group是对iommu_group的封装,next挂接到vfio_domain. group_list.

04

VFIO 驱动框架分析

1)vfio 驱动分析

在vfio.ko驱动加载和卸载的时候会执行vfio_init(),vfio_cleanup()函数。下面分步对这2个函数进行分析。

代码语言:javascript
复制
static int __init vfio_init(void)
{
………
ret = misc_register(&vfio_dev);
if (ret) {
pr_err("vfio: misc device register failed\n");
return ret;
}
……………
}
static void __exit vfio_cleanup(void)
{
…….
misc_deregister(&vfio_dev);
}
module_init(vfio_init);
module_exit(vfio_cleanup);

上面代码说明vifo 作为一个混杂字符设备注册进内核,混杂设备为定义为vfio_dev。

代码语言:javascript
复制
static struct miscdevice vfio_dev = {
.minor = VFIO_MINOR,
.name = "vfio",
.fops = &vfio_fops,
.nodename = "vfio/vfio",
.mode = S_IRUGO | S_IWUGO,
};

完成混杂设备注册后,会在/dev/vifo/fifo 创建设备节点,用户可以对其进行操作。具体的操作函数为vfio_fops

代码语言:javascript
复制
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()函数主要完成struct vfio_container 对象的实例化,并将实例化的对象放到filep->private_data中。

vfio_fops_release()函数主要完成vfio_container对象的释放。

vfio_fops_read(),vfio_fops_write(),vfio_fops_mmap()主要是对vfio_iommu_driver 的read(),write(),mmap()函数的封装。

vfio_fops_unl_ioctl()函数大部分cmd 也是对vfio_iommu_driver 的ioctl()函数的封装,主要有一个VFIO_SET_IOMMU命令,完成vfio_container和vfio_iommu_driver的绑定。

代码语言:javascript
复制
static long vfio_ioctl_set_iommu(struct vfio_container *container,
 unsigned long arg)
{
……
list_for_each_entry(driver, &vfio.iommu_drivers_list, vfio_next)
{
…………
if (driver->ops->ioctl(NULL, VFIO_CHECK_EXTENSION, arg) <= 0) {
module_put(driver->ops->owner);
continue;
}
data = driver->ops->open(arg);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
module_put(driver->ops->owner);
continue;
}
ret = __vfio_container_attach_groups(container, driver, data);
if (ret) {
driver->ops->release(data);
module_put(driver->ops->owner);
continue;
}
container->iommu_driver = driver;
container->iommu_data = data;
break;
}
………
}

vfio_ioctl_set_iommu()函数的参数arg 是用户态进程指定vfio_iommu驱动类型,例如VFIO_TYPE1_IOMMU等。vfio_ioctl_set_iommu()会遍历vfio.iommu_drivers_list,如果driver->ops->ioctl(NULL, VFIO_CHECK_EXTENSION, arg)大于0,表明支持用户态所指定的驱动,否则继续遍历。如果遍历到了,调用vfio_iommu 驱动函数open()返回一个vfio_iommu 对象,然后执行__vfio_container_attach_groups(container, driver, data)将container 上所有group都附加到该vfio_iommu 驱动上,并且设置container 的成员变量。其中__vfio_container_attach_groups()函数最终是调用vfio_iommu驱动的attach_group()函数完成该container 上的group都附加到该vfio_iommu 驱动上。

2)vfio iommu驱动分析

以intel iommu 为例,vfio_iommu_type1.c 为vfio驱动对iommu驱动的封装。vfio_iommu_type1_init(),vfio_iommu_type1_cleanup()为vfio_iommu_type1.ko模块的加载和卸载时调用的函数。这2个函数分别主要是执行vfio_register_iommu_driver(),vfio_unregister_iommu_driver()完成vifo iommu driver 的注册和注销。具体函数如下:

代码语言:javascript
复制
static int __init vfio_iommu_type1_init(void)
{
return vfio_register_iommu_driver(&vfio_iommu_driver_ops_type1);
}
static void __exit vfio_iommu_type1_cleanup(void)
{
vfio_unregister_iommu_driver(&vfio_iommu_driver_ops_type1);
}
int vfio_register_iommu_driver(const struct vfio_iommu_driver_ops *ops)
{
struct vfio_iommu_driver *driver, *tmp;
driver = kzalloc(sizeof(*driver), GFP_KERNEL);
if (!driver)
return -ENOMEM;
driver->ops = ops;
mutex_lock(&vfio. iommu_drivers_lock);
/* Check for duplicates */
list_for_each_entry(tmp, &vfio.iommu_drivers_list, vfio_next) {
if (tmp->ops == ops) {
mutex_unlock(&vfio.iommu_drivers_lock);
kfree(driver);
return -EINVAL;
}
}
list_add(&driver->vfio_next, &vfio.iommu_drivers_list);
mutex_unlock(&vfio.iommu_drivers_lock);
return 0;
}

vfio_register_iommu_driver()函数申请一个vfio_iommu_driver 对象,并将传入的ops 挂在对象上,将vfio_iommu_driver 对象添加到vfio.iommu_drivers_list链表上。Container在执行相关操作的时候会通过vfio.iommu_drivers_list链表找到最终的操作函数ops。即:

代码语言:javascript
复制
static const struct vfio_iommu_driver_ops vfio_iommu_driver_ops_type1 = {
.name= "vfio-iommu-type1",
.owner= THIS_MODULE,
.open= vfio_iommu_type1_open,
.release= vfio_iommu_type1_release,
.ioctl= vfio_iommu_type1_ioctl,
.attach_group= vfio_iommu_type1_attach_group,
.detach_group= vfio_iommu_type1_detach_group,
.pin_pages= vfio_iommu_type1_pin_pages,
.unpin_pages= vfio_iommu_type1_unpin_pages,
.register_notifier= vfio_iommu_type1_register_notifier,
.unregister_notifier= vfio_iommu_type1_unregister_notifier,
};

vfio_iommu_driver_ops_type1 是对intel iommu 驱动的封装,最主要的功能是完成group 内的设备的内存映射和解映射。这个功能是通过vfio_iommu_type1_ioctl 的VFIO_IOMMU_MAP_DMA和VFIO_IOMMU_UNMAP_DMA命令来完成。用户空间传递的参数结构体如下:

代码语言:javascript
复制
struct vfio_iommu_type1_dma_map {
__u32argsz;
__u32flags;
#define VFIO_DMA_MAP_FLAG_READ (1 << 0)/* readable from device */
#define VFIO_DMA_MAP_FLAG_WRITE (1 << 1)/* writable from device */
__u64vaddr;/* Process virtual address */
__u64iova;/* IO virtual address */
__u64size;/* Size of mapping (bytes) */
};

内核在接收这个参数后调用vfio_dma_do_map(),vfio_dma_do_unmap()完成对内存的映射/解映射。这里以dma 映射为例说明,解映射过程可以参考内核代码。

代码语言:javascript
复制
static int vfio_dma_do_map(struct vfio_iommu *iommu,struct vfio_iommu_type1_dma_map *map)
{
dma_addr_t iova = map->iova;
unsigned long vaddr = map->vaddr;
size_t size = map->size;
int ret = 0, prot = 0;
uint64_t mask;
struct vfio_dma *dma;
……
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
if (!dma) {
ret = -ENOMEM;
goto out_unlock;
}
iommu->dma_avail--;
dma->iova = iova;
dma->vaddr = vaddr;
dma->prot = prot;
get_task_struct(current->group_leader);
dma->task = current->group_leader;
dma->lock_cap = capable(CAP_IPC_LOCK);
dma->pfn_list = RB_ROOT;
ret = vfio_pin_map_dma(iommu, dma, size);
……
}

首先对入参进行检查,再申请一个dma 对象并填充其成员变量,然后对填充完了的dma 进行vfio_pin_map_dma()操作。

代码语言:javascript
复制
static int vfio_pin_map_dma(struct vfio_iommu *iommu, struct vfio_dma *dma,
    size_t map_size)
{
dma_addr_t iova = dma->iova;
unsigned long vaddr = dma->vaddr;
size_t size = map_size;
long npage;
unsigned long pfn, limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT;
int ret = 0;
while (size) {
/* Pin a contiguous chunk of memory */
npage = vfio_pin_pages_remote(dma, vaddr + dma->size,
      size >> PAGE_SHIFT, &pfn, limit);
if (npage <= 0) {
WARN_ON(!npage);
ret = (int)npage;
break;
}
/* Map it! */
ret = vfio_iommu_map(iommu, iova + dma->size, pfn, npage,
     dma->prot);
if (ret) {
vfio_unpin_pages_remote(dma, iova + dma->size, pfn,
npage, true);
break;
}
size -= npage << PAGE_SHIFT;
dma->size += npage << PAGE_SHIFT;
}
dma->iommu_mapped = true;
if (ret)
vfio_remove_dma(iommu, dma);
return ret;
}

vfio_pin_pages_remote()函数是将将要映射的npage页pin 到该用户态应用程序中,然后调用vfio_iommu_map()进行dma 内存映射,vfio_iommu_map()函数调用iommu_map()函数,该函数最终使用ops->map()完成硬件的映射操作。对于vfio_iommu_driver_ops_type1,Ops为intel_iommu_ops。同理vfio_iommu_driver_ops_type1的其他成员函数最终调用intel_iommu_ops,完成对真实iommu 硬件的操作。

从上面分析可知,vfio_iommu主要功能就是完成dma 映射内存页的pin操作和相关内存管理以及对硬件iommu驱动的封装,同时也为vfio 的container提供接口操作。

3)vfio group驱动分析

继续回到vfio_init()函数,

代码语言:javascript
复制
static int __init vfio_init(void)
{
………
/* /dev/vfio/$GROUP */
vfio.class = class_create(THIS_MODULE, "vfio");
if (IS_ERR(vfio.class)) {
ret = PTR_ERR(vfio.class);
goto err_class;
}
vfio.class->devnode = vfio_devnode;
ret = alloc_chrdev_region(&vfio.group_devt, 0, MINORMASK + 1, "vfio");
if (ret)
goto err_alloc_chrdev;
cdev_init(&vfio.group_cdev, &vfio_group_fops);
ret = cdev_add(&vfio.group_cdev, vfio.group_devt, MINORMASK + 1);
if (ret)
goto err_cdev_add;
……………
}
static void __exit vfio_cleanup(void)
{
……
idr_destroy(&vfio. group_idr);
cdev_del(&vfio.group_cdev);
unregister_chrdev_region(vfio.group_devt, MINORMASK + 1);
class_destroy(vfio.class);
vfio.class = NULL; 
………
}
module_init(vfio_init);
module_exit(vfio_cleanup);

这部分代码是对vfio 中vfio_group设备的初始化相关的代码。为创建/dev/vfio/GROUP 设备文件准备好对应的操作数据,GROUP是对应的group id。最终使用vfio_group_fops 函数的是vfio_create_group()函数

代码语言:javascript
复制
static struct vfio_group *vfio_create_group(struct iommu_group *iommu_group)
{
……
dev = device_create(vfio.class, NULL,
    MKDEV(MAJOR(vfio.group_devt), minor),
    group, "%s%d", group->noiommu ? "noiommu-" : "",
    iommu_group_id(iommu_group));
if (IS_ERR(dev)) {
vfio_free_group_minor(minor);
vfio_group_unlock_and_free(group);
return ERR_CAST(dev);
}
……
}
创建好/dev/vfio/$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_group_fops_open()函数完成对vfio_group 对象的检查,该对象是vfio_create_group()中创建,同时设置该对象正在使用并加入到container 中,最后将其对象放入filep->private_data中。

vfio_group_fops_release()函数完成从container中去掉该vfio_group对象,并取消该对象正在使用。

vfio_group_fops_unl_ioctl()函数完成对vfio_group控制,其中

VFIO_GROUP_GET_STATUS:获取vfio_group 的状态

VFIO_GROUP_SET_CONTAINER:将vfio_group 和vfio_iommu 绑定

VFIO_GROUP_UNSET_CONTAINER:将vfio_group 和vfio_iommu 解绑定

VFIO_GROUP_GET_DEVICE_FD:获取vifo_device设备描述符号。

4)vfio device驱动分析

vifo_device 的创建函数为struct vfio_device *vfio_group_create_device(struct vfio_group *group,struct device *dev,const struct vfio_device_ops *ops,void *device_data)。

vifo_device 表示在VFIO 层面的设备,dev 为物理设备,ops为物理设备的操作回调,这里是vfio_pci_ops,group 表示设备所属的vfio_group,device_data保存私有数据,这里是vfio_pci_device结构体。

在vfio_group 设备文件操作的ioctl命令中,使用VFIO_GROUP_GET_DEVICE_FD 来获取设备描述符,该命令调用的函数为:static int vfio_group_get_device_fd(struct vfio_group *group, char *buf),buf中保存了用户空间传过来的设备地址(pci设备地址:domain:bus:dev:function)。

代码语言:javascript
复制
static int vfio_group_get_device_fd(struct vfio_group *group, char *buf)
{
……
device = vfio_device_get_from_name(group, buf);
if (!device)
return -ENODEV;
ret = device->ops->open(device->device_data);
if (ret) {
vfio_device_put(device);
return ret;
}
ret = get_unused_fd_flags(O_CLOEXEC);
if (ret < 0) {
device->ops->release(device->device_data);
vfio_device_put(device);
return ret;
}
filep = anon_inode_getfile("[vfio-device]", &vfio_device_fops,
   device, O_RDWR);
if (IS_ERR(filep)) {
put_unused_fd(ret);
ret = PTR_ERR(filep);
device->ops->release(device->device_data);
vfio_device_put(device);
return ret;
}
/*
 * TODO: add an anon_inode interface to do this.
 * Appears to be missing by lack of need rather than
 * explicitly prevented.  Now there's need.
 */
filep->f_mode |= (FMODE_LSEEK | FMODE_PREAD | FMODE_PWRITE);
atomic_inc(&group->container_users);
fd_install(ret, filep);
……
}

首先通过group和设备名称找到设备(用户空间传进来的buf),找到后打开该设备。接着使用get_unused_fd_flags 获取一个空闲的fd,调用anon_inode_getfile获取一个文件结构体并设置该文件结构体的操作函数为vfio_device_fops,调用fd_install将空闲fd 和文件结构关联起来,最后返回该fd 给用户空间,用户空间操作该fd 的操作函数变成了vfio_device_fops操作了。

代码语言:javascript
复制
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_ops的操作。

5)vfio-pci驱动分析

通过上面对group ,device 的介绍,应用程序最终的是需要操作具体的物理设备的,具体的物理设备是挂在pci 总线上,vfio-pci 是对pci 设备的封装。vfio_pci.ko模块的加载和卸载执行函数为:

代码语言:javascript
复制
static int __init vfio_pci_init(void)
{
……
ret = pci_register_driver(&vfio_pci_driver);
……
}
static void __exit vfio_pci_cleanup(void)
{
pci_unregister_driver(&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_driver驱动的id_table= NULL,这就表明在驱动加载的时候无法通过pci 总线match 到该设备驱动,需要用户主动绑定/解绑vfio-pci 设备才能调用pci 设备probe/remove 函数。先看probe 函数

代码语言:javascript
复制
static int vfio_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct vfio_pci_device *vdev;
……
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
if (!vdev) {
vfio_iommu_group_put(group, &pdev->dev);
return -ENOMEM;
}
……
ret = vfio_add_group_dev(&pdev->dev, &vfio_pci_ops, vdev);
if (ret) {
vfio_iommu_group_put(group, &pdev->dev);
kfree(vdev);
return ret;
}
……
}

vfio_pci_probe 函数主要处理的是struct vfio_pci_device *vdev,vfio_pci_device 结构体是对实际的pci设备的抽象,通过vfio_add_group_dev()函数完成vfio_pci_device和vfio_grop,vfio_device 的绑定,也实现对vifo_group,vifo_device 的创建,还实现将vifo_device的操作方法挂接到vfio_pci_device的操作方法上。

int vfio_add_group_dev(struct device *dev,const struct vfio_device_ops *ops, void *device_data) 函数的三个入参:第一个表示物理设备,第二个参数是作为vfio_device 的回调,第三个参数是vfio_pci_device 作为vfio_device一个私有数据保存下来。对于第二个参数,最终调用的是vfio_pci_ops。

代码语言:javascript
复制
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,
};

vfio_pci_open(),vfio_pci_release(),vfio_pci_read(),vfio_pci_write()是对物理设备pci 操作的封装。vfio_pci_mmap()是对pci 的空间进行映射,当用户空间执行mmap 操作是实现地址转换。vfio_pci_request()是通过eventfd_signal()向用户空间发生请求信号。最主要的操作是vfio_pci_ioctl()函数,通过不同命令字,对物理pci设备的操作。主要有以下操作:

VFIO_DEVICE_GET_INFO:获取设备信息,通过struct vfio_device_info 返回给用户空间。argsz表示参数大小,输入参数,flags表示设备支持的特性,regions表示区域个数,num_irqs 表示中断个数,后三个为输出参数。

代码语言:javascript
复制
struct vfio_device_info {
__u32argsz;
__u32flags;
#define VFIO_DEVICE_FLAGS_RESET(1 << 0)/* Device supports reset */
#define VFIO_DEVICE_FLAGS_PCI(1 << 1)/* vfio-pci device */
#define VFIO_DEVICE_FLAGS_PLATFORM (1 << 2)/* vfio-platform device */
#define VFIO_DEVICE_FLAGS_AMBA  (1 << 3)/* vfio-amba device */
#define VFIO_DEVICE_FLAGS_CCW(1 << 4)/* vfio-ccw device */
#define VFIO_DEVICE_FLAGS_AP(1 << 5)/* vfio-ap device */
__u32num_regions;/* Max region index + 1 */
__u32num_irqs;/* Max IRQ index + 1 */
};

VFIO_DEVICE_GET_REGION_INFO:获取设备内存区域信息,通过struct vfio_region_info返回给用户空间。argsz表示参数大小,输入参数,flags表示内存区域支持的操作,输出参数,index表示区域索引,输入参数,size 表示region大小,输出参数,cap_offset,对第一个cap 的偏移,输出参数,offset表示内存区域在fd 设备文件中对应的偏移,输出参数。

代码语言:javascript
复制
struct vfio_region_info {
__u32argsz;
__u32flags;
#define VFIO_REGION_INFO_FLAG_READ(1 << 0) /* Region supports read */
#define VFIO_REGION_INFO_FLAG_WRITE(1 << 1) /* Region supports write */
#define VFIO_REGION_INFO_FLAG_MMAP(1 << 2) /* Region supports mmap */
#define VFIO_REGION_INFO_FLAG_CAPS(1 << 3) /* Info supports caps */
__u32index;/* Region index */
__u32cap_offset;/* Offset within info struct of first cap */
__u64size;/* Region size (bytes) */
__u64offset;/* Region offset from start of device fd */
};

VFIO_DEVICE_GET_IRQ_INFO:获取设备中断信息,通过struct vfio_irq_info info返回给用户空间。argsz表示参数大小,输入参数,flags表示中断支持的特性,index表示中断索引,输入参数,count 表示对应索引中断个数,输出参数。

代码语言:javascript
复制
struct vfio_irq_info {
__u32argsz;
__u32flags;
#define VFIO_IRQ_INFO_EVENTFD(1 << 0)
#define VFIO_IRQ_INFO_MASKABLE(1 << 1)
#define VFIO_IRQ_INFO_AUTOMASKED(1 << 2)
#define VFIO_IRQ_INFO_NORESIZE(1 << 3)
__u32index;/* IRQ index */
__u32count;/* Number of IRQs within this index */
};

VFIO_DEVICE_SET_IRQS:设置设备中断信息,通过struct vfio_irq_set 从用户空间设置给设备。

代码语言:javascript
复制
struct vfio_irq_set {
__u32argsz;
__u32flags;
#define VFIO_IRQ_SET_DATA_NONE(1 << 0) /* Data not present */
#define VFIO_IRQ_SET_DATA_BOOL(1 << 1) /* Data is bool (u8) */
#define VFIO_IRQ_SET_DATA_EVENTFD(1 << 2) /* Data is eventfd (s32) */
#define VFIO_IRQ_SET_ACTION_MASK(1 << 3) /* Mask interrupt */
#define VFIO_IRQ_SET_ACTION_UNMASK(1 << 4) /* Unmask interrupt */
#define VFIO_IRQ_SET_ACTION_TRIGGER(1 << 5) /* Trigger interrupt */
__u32index;
__u32start;
__u32count;
__u8data[];
};

VFIO_DEVICE_RESET:复位设备

vfio-pci驱动作为一个中间桥梁,是vfio模块和物理pci 设备之间的纽带。向下提供控制pci物理设备的行为,向上提供vfio的接口信息。对pci 设备的控制主要是获取和配置pci 的配置空间寄存器信息,中断信息等操作。

通过对vfio驱动框架中的vfio_container,vfio_iommu, vfio_group, vfio_device, vfio_pci的分析,可以看出如下操作调用关系:

图4 VFIO驱动框架通信接口

05

VFIO 驱动框架总结

VFIO驱动是内核提供的用户态驱动的一种,本文介绍了VFIO驱动框架中各个层次模块之间的调用关系,以及VFIO框架中各个层次的主要数据结构和这些数据结构的相关关系。VFIO驱动为用户空间操作外设提供了便利的接口,在设备虚拟化中具有广泛的应用。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-01-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux阅码场 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • VFIO 驱动框架设计分析
  • VFIO 用户接口三个层面
  • VFIO 驱动主要数据结构
  • VFIO 驱动框架分析
    • 1)vfio 驱动分析
      • 2)vfio iommu驱动分析
        • 3)vfio group驱动分析
          • 4)vfio device驱动分析
            • 5)vfio-pci驱动分析
            • VFIO 驱动框架总结
            相关产品与服务
            腾讯云代码分析
            腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档