iommu功能
IOMMU主要功能包括DMA Remapping和Interrupt Remapping,这里主要讲解DMA Remapping,Interrupt Remapping会独立讲解。对于DMA Remapping,IOMMU与MMU类似。IOMMU可以将一个设备访问地址转换为存储器地址,下图针对有无IOMMU情况说明IOMMU作用。
在没有IOMMU的情况下,网卡接收数据时地址转换流程,RC会将网卡请求写入地址addr1直接发送到DDR控制器,然后访问DRAM上的addr1地址,这里的RC对网卡请求地址不做任何转换,网卡访问的地址必须是物理地址。
对于有IOMMU的情况,网卡请求写入地址addr1会被IOMMU转换为addr2,然后发送到DDR控制器,最终访问的是DRAM上addr2地址,网卡访问的地址addr1会被IOMMU转换成真正的物理地址addr2,这里可以将addr1理解为虚机地址。
左图是没有IOMMU的情况,对于虚机无法实现设备的透传,原因主要有两个:
- 一是因为在没有IOMMU的情况下,设备必须访问真实的物理地址HPA,而虚机可见的是GPA;
- 二是如果让虚机填入真正的HPA,那样的话相当于虚机可以直接访问物理地址,会有安全隐患。
所以针对没有IOMMU的情况,不能用透传的方式,对于设备的直接访问都会有VMM接管,这样就不会对虚机暴露HPA。右图是有IOMMU的情况,虚机可以将GPA直接写入到设备,当设备进行DMA传输时,设备请求地址GPA由IOMMU转换为HPA(硬件自动完成),进而DMA操作真实的物理空间。IOMMU的映射关系是由VMM维护的,HPA对虚机不可见,保障了安全问题,利用IOMMU可实现设备的透传。
iommu原理
在没有IOMMU的情况下,设备的DMA操作可以访问整个物理地址空间,所以理论上设备可以向操作系统的代码段、数据段等内存区域做DMA,从而破坏整个系统。当然,通常来说不会有这样的设备。IOMMU的出现,可以实现地址空间上的隔离,使设备只能访问规定的内存区域。下面简要说一下intel的IOMMU怎么做到这点的:
目前PC架构最多有256条PCI总线,于是IOMMU用一个称为root entry的数据结构描述PCI总线,总共256个root entry构成一张表。每条PCI总线最多允许256个设备,IOMMU用context entry描述一个PCI设备(或者是PCI桥),256个context entry构成一张表。所以就有了如图的关系。我们知道,PCI设备用 {BUSEV:FUNC}描述一个设备。所以对于一个特定设备,用bus号做索引root entry表,用dev号索引context entry表可以找到描述该设备的的context entry。context entry中有一个指针指向一章I/O页表,当设备发起DMA操作时,IOMMU会根据该页表把设备的DMA地址转换成该设备可以访问内存区域的地址。
所以只要为设备建一张I/O页表,就可以使设备只能访问规定的内存区域了。当然,也可以把该页表当成跳板,让只能寻址32bit地址空间的设备访问到64bit地址空间中去。
iommu可以建立连续虚拟地址到不连续多个物理地址的映射,对于dpdk,始终按照1:1的关系进行映射。对于不能寻址全部物理地址空间的设备,通过IOMMU的重映射,从而避免了将数据从设备可访问的外围地址空间拷入拷出设备无法访址的物理地址空间的额外开销。
IOMMU是一个硬件单元,它可以把设备的IO地址映射成虚拟地址,为设备提供页表映射,设备通过IOMMU将数据直接DMA写到用户空间。之所以不共用MMU单元,是为了保证和进程的页表相互独立,防止设备访问进程的任意地址空间。所以VFIO的IOMMU功能保障了安全的非特权级别的用户态设备驱动机制。
IOMMU的主要功能就是完成映射,类比MMU利用页表实现VA->PA的映射,IOMMU也需要用到页表,那么下一个问题就是如何找到页表。在设备发起DMA请求时,会将自己的Source Identifier(包含Bus、Device、Func)包含在请求中,IOMMU根据这个标识,以RTADDR_REG指向空间为基地址,然后利用Bus、Device、Func在Context Table中找到对应的Context Entry,即页表首地址,然后利用页表即可将设备请求的虚拟地址翻译成物理地址。
vfio iommu 驱动
vfio iommu驱动是vfio接口与底层iommu驱动之间的桥梁。它向上接收vfio的接口请求,向下利用iommu完成重定向。以默认常用的x86的vfio iommu type1为例。
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,
.dma_rw = vfio_iommu_type1_dma_rw,
每一个container都会open一个vfio_iommu_driver与底层的iommu打交道。