VFIO 内核文档 (翻译)

本文是VFIO内核文档[1]的翻译。

很多现代系统提供DMA和中断重映射工具来帮助保证IO在被指定的界限中进行。包括x86硬件的AMD-Vi和Intel VT-d,POWER系统的Partitionable Endpoints (PEs)和嵌入式的PowerPC系统(如Freescale PAMU)。VFIO driver是IOMMU/设备不可知的一个框架,它只是专门用于将设备在安全的、IOMMU保护的环境下直接暴露给userspace。换句话说,VFIO允许安全且非特权的用户态驱动。

我们为什么需要VFIO?一个原因是虚拟机经常时用直接设备访问(“device assignment”)来获得尽可能高的IO性能。从设备和host的角度,这其实就是把VM变成了一个用户态驱动,VM也因此获得了这个IO设备的低延迟、高带宽和全虚拟化原生(bare-metal)设备驱动的直接应用。

一些应用场景中(特别是这高性能计算领域),也会从低开销的从用户空间的直接设备访问获得好处。例子包括网卡(通常基于非TCP/IP)和计算加速器等IO设备。在VFIO之前,这些驱动必须经过很长的开发周期来成为上游驱动、单独分支维护,或者使用UIO框架(UIO并不支持IOMMU保护,并且对中断支持有限,还需要root权限来访问PCI配置空间等东西)。

VFIO驱动框架用来将所有东西统一起来,代替KVM PCI设备assignment的代码,并提供一个比UIO更安全、功能更丰富的用户态驱动环境。

Groups、Devices和IOMMUs

设备是任何IO驱动的主要目标。设备一般会创建包括IO访问、中断和DMA在内的编程接口。不讨论每个驱动的细节,DMA通常是保证安全环境的最重要的部分,这是由于如果不对设备向物理内存的读写操作不设限制,将会造成对整个系统造成极大威胁。

为了减小这种风险,很多现代IOMMU将隔离特性加入到负责地址转换的接口中,这解决了设备在受限制的地址空间的寻址问题。有了这种介质,设备之间或者设备和某块内存间可以实现有效的隔离,这也允许了设备到虚拟机的安全直接管理。

这种隔离性的粒度并不总是单个设备,即使IOMMU可以做到这点,设备的属性、连接方式和IOMMU拓扑结构都可能会减弱这种隔离性。比如,一个独立的设备可能是一个更大范围设备集合的子集,那么即使IOMMU可以辨识出在这一集合中的不同设备,这个集合中的transactions也不会需要经过IOMMU。例如,从一个functions之间有后门的多function PCI设备,到一个non-PCI-ACS (Access Control Services)bridge的任何东西都允许不经过IOMMU的重定向。拓扑结构也在隐藏设备这件事中扮演着很重要的角色。一个PCIe-to-PCI的bridge隐藏了它之后的所有设备,让transaction看起来就来自bridge本身。显然,IOMMU也承担了主要的任务。

因此,虽然大多数情况下IOMMU可以达到设备级的隔离粒度,系统一般也是容忍这个粒度被放宽的。IOMMU API也因此支持IOMMU group的概念。一个group就是一组设备的集合,这组设备隔离于系统的其他设备。Group因此也是VFIO所用的ownership的单元。

虽然group是保证用户访问安全的最小粒度,它并不一定是最好的粒度。在使用page tables的IOMMU中,多个groups之间共享一组page table是可能的,这减小了硬件平台的开销(减少TLB thrashing、减少重复page table等),也减小了用户的开销(只编写一个set的转换即可)。因此,VFIO使用了一个container的概念,一个class可以包括一个或者多个groups。创建一个container很简单,只要打开/dev/vfio/vfio字符设备即可。

Container自身只提供很少的功能,。。。 用户需要将group加到container中来获得下一级的功能。要这样做,用户首先需要找到所关心设备所属的group,这可以用下边例子中的sysfs链接办到。通过将设备从host驱动解绑并绑定到VFIO驱动上,一个新的VFIO group会出现为/dev/vfio/$GROUP,其中$GROUP是IOMMU的group number,目标的设备是这个group的成员。如果IOMMU group有多个设备,那么这个VFIO group可用前,每个设备都需要绑定到一个VFIO驱动上(只是将所有设备从host驱动解绑也可以,这也会让group可用,但是没有绑定VFIO设备的特定设备不可用)。待定:禁用驱动probing/locking一个设备的接口。

如果group准备好了,可以通过open这个VFIO group字符设备(/dev/vfio/$GROUP)将这个group加入到container中,并用ioctlVFIO_GROUP_SET_CONTAINER参数将打开的container文件描述符fd传入。如果需要在多个group间分享IOMMU上下文中,过个group可以被设置(set)到一个相同的container。如果一个group无法被set到一个container,那么一个空的container将被使用。

如果一个或多个group被加入到一个container,那么剩下的ioctl参数就可用了,可以访问VFIO IOMMU接口了。而且,现在在VFIO group的fd上用ioctl可以得到每个设备的文件描述符。

VFIO设备的API包括描述设备、描述IO region、描述设备描述符上read/write/mmap偏移量的ioctl参数,也包括描述和注册中断通知的机制。

VFIO使用实例

假如我们想访问PCI设备0000:06:0d.0

因此这个设备在IOMMU group 26。这个设备在pci bus上,所以用户会用vfio-pci管理这个组:

绑定这个设备到vfio-pci驱动并为这个group创建VFIO group字符设备:

现在我们需要看下group中的其他设备并且释放他们以使用VFIO:

这个设备在PCIe-to-PCI桥后,因此我们也需要根据以前的步骤增加设备0000:06:0d.1到组中。设备0000:00:1e.0是一个当前没有host driver的bridge,所以不要将这个设备绑定到vfio-pci驱动 (vfio-pci当前不支持PCI桥)。

最后一步是,如果非特权操作被需要,给用户到group的访问权限,(注意/dev/vfio/vfio自身并没有这个能力,所以需要被系统设置为0666权限):

现在用户可以访问所有设备和这个group的IOMMU了:

VFIO API

API的详细文档,请看include/linux/vfio.h[2] 和include/uapi/linux/vfio.h[3]。前者是内核态API,后者是用户态API。

VFIO bus驱动API

VFIO bus driver,比如vfio-pci只用几个接口来与VFIO core交互。驱动会调用vfio_add_group_dev()或者vfio_del_group_dev()来bound或者unbound到驱动:

vfio_add_group_dev()让VFIO core去开始追踪一个特定dev的iommu_group并将这个dev注册为一个VFIO bus driver。这个driver提供一个ops结构来提供类似file operations的回调结构:

vfio-pci这个bus驱动(drivers/vfio/pci/vfio_pci.c)为例:

每个函数都被传入在vfio_add_group_dev()中注册过的device_data。这允许bus driver成为容易存储opaque、私有数据的地方。当为一个设备一个新的fd被创建时,open/release回调函数将被调用(用VFIO_GROUP_GET_DEVICE_FD)。ioctl接口为 VFIO_DEVICE_xxx这些参数提供直接的passthrough。read/write/mmap这些接口实现设备自己VFIO_DEVICE_GET_REGION_INFO这个ioctl定义的region的访问。


[1] https://github.com/torvalds/linux/blob/master/Documentation/vfio.txt

[2] https://github.com/torvalds/linux/blob/master/include/linux/vfio.h

[3] https://github.com/torvalds/linux/blob/master/include/uapi/linux/vfio.h

发表评论

电子邮件地址不会被公开。 必填项已用*标注