[Linux] VFIO

参考文章:VFIO Introductionkernel-doc/vfio.rstintel vt-dkernel-doc/intel-iommu.txt笔记intel vt-d

VFIO - "Virtual Function I/O"

许多硬件平台提供DMA和中断重映射功能,以确保I/O设备只在分配给它们的域内访问内存,一般这样的硬件单元称为IOMMU。VFIO驱动程序是一个与IOMMU具体硬件无关的框架,可以支持x86架构的VT-D、AMD-Vi,POWER PE,ARM等各种IOMMU硬件架构。使用VFIO可以实现安全的,非特权的,用户空间驱动程序。相较于VFIO,用UIO框架实现的用户空间驱动,将不受IOMMU的保护,且中断支持有限,并且需要root权限运行。

Groups, Devices, and IOMMUs

VFIO层有Group,Device及IOMMU的概念,以下分别对他们进行介绍。Device是所有IO驱动的主体。通常对它创建IO访问、中断和DMA的编程接口。如果对设备的DMA可以访问的内存空间不加限制;这将非常危险。IOMMU硬件可以将设备的可访问物理内存空间进行彼此的隔离,当在IOMMU硬件中创建好IO虚拟地址到物理地址的映射后,设备可使用IO虚拟地址访问物理内存。

有时候,相关的一组设备可能会使用同一块内存空间。因此IOMMU给内存创建隔离区的最小粒度不是Device,而是group。因此,group也是VFIO使用的最小粒度。虽然group是确保用户访问所必须使用的最小粒度,但它不一定是首选粒度。在使用页表的IOMMU中,可以在不同group之间共享一组页表,从而减少平台(减少TLB抖动、减少重复页表)和用户(仅编程一组地址转换)的开销。因此,VFIO使用container 类,该类可以包含一个或多个group。只需打开/dev/vfio/vfio字符设备即可创建container。

容器本身提供的功能很少,除了一对版本和扩展查询接口外,其他所有接口都被锁定。用户需要在容器中添加一个group以获得下一级功能,即IOMMU,不同架构的IOMMU可能提供的接口不同,因此container并未提供统一接口访问IOMMU。为此,用户首先需要标识(这个标识即后文的/dev/vfio/{group})与所需设备关联的group。这可以使用下面示例中描述的sysfs链接来查找。通过绑定设备到VFIO驱动程序,为该组添加一个新的VFIO组/dev/VFIO/{group}字符设备文件接口,其中{group}是设备所属的IOMMU组号。

一旦组准备好,就可以通过打开VFIO组字符设备(/dev/VFIO/$group)并使用VFIO_GROUP_SET_CONTAINER ioctl,传递先前打开的容器文件的文件描述符,将其添加到容器中。如果需要,并且IOMMU驱动程序支持在组之间共享IOMMU上下文,则可以将多个组设置为同一容器。如果组不能设置为具有现有组的容器,则需要使用新的空容器。将一个组(或多个组)附加到容器后,剩余的ioctl将变为可用,从而可以访问VFIO IOMMU接口。此外,现在可以使用VFIO组文件描述符上的ioctl为组中的每个设备获取文件描述符。
VFIO操作device的API包括用于设备的描述(PCI配置空间)、IO区域(BAR空间)及其在设备描述符上的读/写/mmap偏移量的ioctl,以及用于描述和注册中断通知的机制。

VFIO使用示例

以Intel平台下PCI 设备号0000:06:0.0举例。
某个设备所属的iommu组号由内核指定(?不会改变?)
使用如下命令获取设备的组id(该设备为26)

$ readlink /sys/bus/pci/devices/0000:06:0d.0/iommu_group
../../../../kernel/iommu_groups/26

对于PCI设备,需要加载内核模块vfio-pci,并将设备绑定到该驱动上,方法如下

$ modprobe vfio-pci
$ lspci -n -s 0000:06:0d.0 (查看设备的PCI vendor id和device id号)
06:0d.0 0401: 1102:0002 (rev 08)
$ echo 0000:06:0d.0 > /sys/bus/pci/devices/0000:06:0d.0/driver/unbind
$ echo 1102 0002 > /sys/bus/pci/drivers/vfio-pci/new_id

注意: iommu_group 26下的所有设备必须都绑定到vfio或者部分设备不绑定任何驱动。

最后修改字符设备/dev/vfio/26的owner。并确保/dev/vfio/vfio的权限是0666;保证非root用户有读写权限。之前的步骤都需要在root下执行,在此之后可以在非root用户下执行了

$ chown user:user /dev/vfio/26

在此之后,user用户就可以访问属于iommu group 26的所有设备了。

通常,用户态程序使用如下步骤:

  1. 创建新的container,并检查版本(保证兼容性),以及支持的iommu驱动类型
int container

container = open("/dev/vfio/vfio", O_RDWR);

if (ioctl(container, VFIO_GET_API_VERSION) != VFIO_API_VERSION)
        /* Unknown API version */

if (!ioctl(container, VFIO_CHECK_EXTENSION, VFIO_TYPE1_IOMMU))
        /* Doesn't support the IOMMU driver we want. */
  1. 打开iommu group 26并添加到刚才创建的container中
int group;
struct vfio_group_status group_status =
                                { .argsz = sizeof(group_status) };

/* Open the group */
group = open("/dev/vfio/26", O_RDWR);

/* Test the group is viable and available */
ioctl(group, VFIO_GROUP_GET_STATUS, &group_status);

if (!(group_status.flags & VFIO_GROUP_FLAGS_VIABLE))
        /* Group is not viable (ie, not all devices bound for vfio) */

/* Add the group to the container */
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
  1. 因为不同硬件的iommu实现不同,因此内核支持多种iommu的类型。在使用iommu之前需要设置iommu的类型。也可以查询该iommu的硬件信息(例如iommu的page size)
struct vfio_iommu_type1_info iommu_info = { .argsz = sizeof(iommu_info) };

/* Enable the IOMMU model we want */
ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);

/* Get addition IOMMU info */
ioctl(container, VFIO_IOMMU_GET_INFO, &iommu_info);
  1. 现在可以使用vfio的iommu做映射了。通常首先使用mmap的匿名映射方式在内核中分配内存,并完成映射。注意:匿名内存映射方式创建的页表项位于内核的全局页表中,在iommu映射的时候,使用iommu返回给进程的虚拟地址(vaddr)来查找物理页框号(pfn)。
/* Allocate some space and setup a DMA mapping */
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;

/* intel平台在内核中使用函数vfio_iommu_type1_ioctl处理,
 * 其调用函数vfio_dma_do_map完成具体工作,过程分两步:
 * 1. 使用vaddr在全局页表中找到其第一个页的页框号pfn
 * 2. 使用vfio_iommu_map建立iommu的映射,映射iova<-->页框号的对应关系
 * 映射完成后,进程就可以通过vaddr,设备可以通过iova来访问pfn中的内存了。
 */
ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
  1. 在第四步中申请和映射了iommu的DMA内存。这些内存必须要给设备使用才有意义。因此首先获取VFIO的设备文件描述符;并通过设备的文件描述符获取设备的PCI BAR信息和IRQ信息。当然也可以对设备做复位操作
int device, i;
struct vfio_device_info device_info = { .argsz = sizeof(device_info) };

/* Get a file descriptor for the device */
device = ioctl(group, VFIO_GROUP_GET_DEVICE_FD, "0000:06:0d.0");

/* Test and setup the device */
ioctl(device, VFIO_DEVICE_GET_INFO, &device_info);

for (i = 0; i < device_info.num_regions; i++) {
        struct vfio_region_info reg = { .argsz = sizeof(reg) };

        reg.index = i;

        ioctl(device, VFIO_DEVICE_GET_REGION_INFO, &reg);

        /* Setup mappings... read/write offsets, mmaps
         * For PCI devices, config space is a region */
}

for (i = 0; i < device_info.num_irqs; i++) {
        struct vfio_irq_info irq = { .argsz = sizeof(irq) };

        irq.index = i;

        ioctl(device, VFIO_DEVICE_GET_IRQ_INFO, &irq);

        /* Setup IRQs... eventfds, VFIO_DEVICE_SET_IRQS */
}

/* Gratuitous device reset and go... */
ioctl(device, VFIO_DEVICE_RESET);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 一、Docker 简介 Docker 两个主要部件:Docker: 开源的容器虚拟化平台Docker Hub: 用...
    R_X阅读 9,864评论 0 27
  • 首先要明确两个概念:Linux内核 PCI设备驱动和设备本身驱动两部分。工作中所谓的编写设备驱动,其实就是编写设备...
    Leon_Geo阅读 8,791评论 0 6
  • 本文开启 linux 内核 V4L2 框架部分的学习之旅,本文仅先对 V4L2 的框架做一个综述性的概括介绍,然后...
    yellowmax阅读 12,301评论 0 13
  • feisky云计算、虚拟化与Linux技术笔记posts - 1014, comments - 298, trac...
    不排版阅读 9,465评论 0 5
  • 1.DPDK 简介 DPDK(Data Plane Development Kit)是数据平面开发工具包,由用于加...
    古埃尔公园阅读 12,833评论 1 8

友情链接更多精彩内容