查看原文
其他

深入理解VFIO驱动框架

Jack Linux阅码场 2022-12-14

作者介绍:

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 驱动主要数据结构

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:表明为一个字符设备。

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对象。


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 是否初始化完成。


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.


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 条目数量。


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。


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个函数进行分析。


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。


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


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的绑定。


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 驱动上。

 

2vfio 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 的注册和注销。具体函数如下:

 

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。即:


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命令来完成。用户空间传递的参数结构体如下:


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 映射为例说明,解映射过程可以参考内核代码。


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()操作。


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提供接口操作。

 

3vfio group驱动分析

继续回到vfio_init()函数,

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()函数


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设备描述符号。

 

4vfio 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)。

 

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操作了。


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的操作。

 

5vfio-pci驱动分析

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

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 函数


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。


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 表示中断个数,后三个为输出参数。


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 设备文件中对应的偏移,输出参数。

 

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 表示对应索引中断个数,输出参数。


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 从用户空间设置给设备。


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驱动为用户空间操作外设提供了便利的接口,在设备虚拟化中具有广泛的应用。


如果大家对本文内容有任何疑问可以将您的疑问发送到(xzf20082004@163.com),我们会为您进行解答,欢迎大家踊跃提问


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存