文章目录
- VFIO PassThrough
- 1.config空间透传实现
- qemu实现
- vfio实现
- 2.BAR空间透传
- 3.vfio中断实现
- 4.透传设备具体实现
- 在这里插入图片描述
- 参考资料
VFIO PassThrough
对于VFIO的概述可以参考文章vfio 概述。这里以PCI设备为例讲述VFIO PassThrough具体实现(VFIO不仅仅支持PCI设备)。对于一个透传给虚机的PCI设备,主要处理config空间透传、BAR空间透传和中断三方面,下面分别讲述如何实现这两方面的透传。
1.config空间透传实现
对于config空间,或者是模拟,或者是透传,具体行为由qemu和vfio两个模块共同决定。qemu中可以有选择的进行模拟,在qemu不模拟的情况下调用vfio提供的io操作对设备进行读写,然后再由vfio决定是否模拟。整体软件实现框图如下:
qemu实现
当虚机访问透传设备的config空间时,触发vm_exit,然后qemu会调用被访问设备的vfio_pci_read_config或vfio_pci_write_config操作。对于读操作,会查看当前读取的config空间是否被qemu模拟,如果模拟,则直接读取qemu中保存的config值,否则调用vfio提供的read接口进行读取。对于写操作,会直接调用vfio提供的write接口,对于不可写的操作在vfio中会有屏蔽。然后在回到qemu中,更新qemu模拟位状态。qemu处在用户态,必须经由vfio完成与物理设备的交互。目前qemu中模拟的比较重要的就是msi相关的。
vfio实现
对于vfio的读写最终会调用perm->readfn或perm->writefn,对于这两个操作的实质就是或者模拟config空间,或者透传config空间。对于每一个cap都定了一个perm选项,其中说明了哪些属性需要模拟,是否可写等。如果可写,且不模拟,则直接透传到物理pci设备上去,否则进行模拟。以cap_perms[PCI_CAP_ID_EXP]
为例,对于PCI_EXP_DEVCTL_BCR_FLR、PCI_EXP_DEVCTL_PAYLOAD、PCI_EXP_DEVCTL_READRQ是需要模拟的,这里又分为两种情况,对于PCI_EXP_DEVCTL_BCR_FLR和PCI_EXP_DEVCTL_READRQ这种,软件进行了模拟,但是确没有满足虚机的需求,所以在vfio_exp_config_write中会执行对物理设备的操作(这里对物理设备进行了操作,但是却没有直接将config透传,是因为对物理设备操作前还需要进行检查)。对于PCI_EXP_DEVCTL_PAYLOAD,因为这个属性和物理机的pci拓扑相关,如果按照虚机拓扑对物理设备修改了就会出现问题,所以这个字段需要模拟,并且不需要物理设备有任何响应。
2.BAR空间透传
在qemu中,vfio_map_bars
函数会调用vfio提供的vfio_pci_mmap
,进而将透传pci设备的bar地址空间(HPA)映射出的HVA返回给qemu,在虚机内部,也会对pci设备枚举并初始化bar空间,这个初始化的值就是GPA,将这个GPA与上面的HPA建立EPT映射,即可完成BAR空间的透传,不过这里需要注意下:对于支持MSI-X的设备,由于MSI-X的table表会存在于bar地址空间中,而MSI-X的配置不能直接给虚机,否则虚机可以通过配置这个中断影响物理机,所以在对bar空间做透传时要除掉MSI-X table占用的bar空间。
Q:HVA与GPA啥时建立关系的
A:虚机写入pci设备bar空间时,这个写入地址为GPA,此时将这个GPA与真实物理设备的BAR地址建立映射关系。
3.vfio中断实现
目前内核使用的是Posted Interrupt方式,对于Posted Interrupt具体实现见Posted Interrupt。以MSI中断为例,当虚机内部为透传设备配置中断时,会触发vm_exit,这时会调用qemu中vfio_pci_write_config->vfio_msi_enable
,在vfio_msi_enable中,主要做了两件事:
- vfio_add_kvm_msi_virq向KVM中注入监听事件,当虚机写入MSI配置空间时,可以获取到MSI message(包括addredd和data,data中包含了设备在虚机内部使用的中断号),在向KVM注入事件时需要传入虚机内部使用的中断号,这个中断号在Posted Interrupt时会使用。
- vfio_enable_vectors利用vfio提供的VFIO_DEVICE_SET_IRQS命令为透传设备申请真正的中断,因为中断相关的配置都不允许虚机直接访问,必须借助VFIO提供的IO接口实现。此时,会在物理机内申请中断,中断服务程序在VFIO内实现,服务程序主要激活步骤1中的监听事件,然后再调用Posted Interrupt。
整个中断初始化和触发流程如下:
当透传设备产生中断时,vfio_msihandler ISR执行,该函数不做实际的服务程序处理,仅仅通过eventfd_signal激活irqfd_inject,然后最终调用deliver_posted_interrupt向虚机注入中断,中断号即为虚机配置透传设备时的中断号。
4.透传设备具体实现
下面从一个物理机启动->虚机启动整个过程描述设备如何透传到虚机,并正常工作的,以昆仑MPW卡为例。
step 1,物理机启动,BIOS完成PCI设备的配置,包括初始化config空间,分配BAR地址空间
step 2,由于内核开启IOMMU支持,会为当前设备分配iommu group
step 3,加载vfio驱动,并与mpw卡关联
step 4,qemu启动虚机,并将mpw卡设备透传给虚机
step 5,前面4个步骤中不涉及虚机,纯粹的物理机操作。当虚机启动后(假设虚机运行linux内核),会根据qemu构建的虚机PCI拓扑为pci设备初始化,包括配置config空间、分配BAR地址空间,这里过程与step1类似,但是行为差别很大。这里说明一下,对于透传设备、virtio设备等,不同的设备实现也不同,这里仅讲述对于透传设备的实现
step 6,当虚机配置mpw卡的config空间时,会用到in、out系列IO指令,这样会造成虚机的vm_exit,qemu中可以截获这个行为,并选择是采取模拟的方式还是利用vfio提供的io接口实现,详细过程见第1节。这里有两个特殊处理,一个是BAR空间的配置,一个是中断的配置
Step 7, 首先透传的优势是高效,在虚机使用设备时,触发vm_exit的情况越少越好,但是又不能放给虚机过大权限,否则会影响到物理机,比如一些特殊的config寄存器,如Max Payload,中断等。在虚机利用in、out指令时会vm_exit,这个影响不大,目前大部分PCI设备的BAR空间都是采用MMIO的方式访问,当设备正常工作时,大部分采用的是mmio的方式。所以要尽可能的保证在mmio访问时,不会vm_exit。也就是将BAR空间透传给虚机,根据EPT映射关系,如果物理机建立好了GPA到HPA的映射,就不会vm_exit,以此来提供效率。先来说HPA,HPA即为物理机在step1中为mpw卡分配的bar地址。GPA为step5-6中,虚机为透传设备分配的BAR空间,在step6中,已经记录了虚机对BAR空间的配置,所以也可以获取到GPA,有了HPA和GPA,建立EPT映射就可以实现BAR地址空间的透传。不过有一种情况需要特殊考虑,就是MSI-X。因为MSI-X的table会放在BAR空间上,而虚机是不允许直接访问中断相关配置的,所以对于MSI-X table的相关BAR空间是不允许直接透传给虚机的
step 8,以使用MSI中断的情况为例,当虚拟配置透传设备的MSI相关寄存器时,会vm_exit。qemu会记录虚机内透传设备使用的中断号并传递给kvm,同时利用vfio的VFIO_DEVICE_SET_IRQS命令在物理机中注册中断,当硬件设备产生中断时,首先物理机会执行服务程序,服务程序主要工作是发送eventfd_signal,激活kvm中的irqfd_inject,最终调用deliver_posted_interrupt向虚机注入中断,祥见第3节。
通过以上步骤,虚机可以访问设备的config空间(模拟或直接访问物理设备),可以访问BAR空间(除MSI-X table外都可透传),中断(利用vfio VFIO_DEVICE_SET_IRQS、kvm协同实现)。
参考资料
http://www.linux-kvm.org/images/b/b4/2012-forum-VFIO.pdf