[转]iommu和vfio概念解构
Posted yi-mu-xi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[转]iommu和vfio概念解构相关的知识,希望对你有一定的参考价值。
[转自 https://zhuanlan.zhihu.com/p/27026590]
我们首先得厘清两个概念,当我们说SMMU的时候,硬件设计同学心中是那个进行设备地址翻译,做DMA操作的那个硬件。软件驱动同学心中是控制SMMU硬件的那个软件。你们之间唯一的接口是:中断,配置空间和内存,没有其他了。所以,当我们讨论这个问题的时候,一定首先分清楚对方在说的是哪个概念空间的概念,因为两者是不一样的。硬件的同学说“设备发出的VA”,包括Substream ID和软件指配给它VA地址,这个地址远远比64位长。但软件指配地址的时候,可不认为自己发出了Substream ID的。
本文要讨论的是软件的概念空间,所以,我们先抽象一下硬件的模型:
SMMU的作用是把CPU提交给设备的VA地址(软件概念的VA),直接作为设备发出的地址,变成正确的物理地址,访问到物理内存上。
MMU通过页表完成这个翻译,SMMU也一样,但SMMU的页表比MMU复杂得多,这受两个要素限制:
- 第一,一个SMMU控制器可能有多个设备连着,两个设备互相之间可能不会复用同一个页表,需要区分。SMMU用stream id来做这个区分
- 第二,一个设备可能被多个进程使用。多个进程有多个页表,设备需要对此进行区分。SMMU通过substream id来进行区分
这样,设备发出一个地址请求,它的地址就包括stream id, substream id和VA三个部分。
前两者定位一个类似MMU的页表体系,后者的执行一个类似MMU的翻译过程。
要SMMU硬件正确动作,就需要CPU(就是软件了)给它提供正确的信息,用我以前提过的DFD方法就可以把对应数据模型的充要性建立出来(注意下面这个不是DFD,它是基于AID的一个数据充要性说明):
至于很多人想引入到这个模型的“虚拟化”的概念,我认为你急了,虚拟化的概念在MMU的页表翻译机制中是自恰的,根本没有必要引入到这里来。??
好了,现在轮到软件。软件的概念空间就比这个概念复杂得多了,这是因为软件要扭合多个硬件平台的意见,有人DMA是64位的,但还有人是32位的啊。而且,软件是有版本概念的,每个版本都和原来有偏离(我这里用主线Linux内核4.11来作为分析对象),而且都有多个硬件厂商的角力,我们不但要看到现在如何,还要看到未来的走向。
我们先抛开VFIO,首先建立iommu的概念空间:
在Linux的概念空间中,硬件的设备,用device来代表。设备用总线类型bus_type来区分。比如众所皆知的pci_device,或者没爹没娘的platform_device。smmu控制器,本身是一种device,对于ARM来说,它现在是一种平台设备。不同总线有什么设备,可以从/sys/bus/<bus>/devices/目录中全部列出来。
bus_type说明的是发现设备的方法,不表明设备的功能,设备的功能用class来表达,/sys/class列出了从功能角度对设备的表达。从这个角度来收,smmu是一种iommu class的设备(之所以不叫smmu而叫iommu,因为intel先上的主线,所以用intel的名字。现在商业公司开源,主要是抢名称空间,因为名称空间意味着整个行业为谁服务)
所以我们现在有了device和iommu两个概念了。下一步是device和iommu的关联,从硬件的逻辑空间,应该是每个device有一个指向iommu的指针,但如果软件也这样做,就掩盖了一个事实:如果两个设备共享同一个streamid,那么修改其中一个设备的页表体系,也就相当于修改了另一个设备的页表体系。所以,修改页表的最小单位不是设备,而是stream id。为此,Linux的模型是增加一个iommu_group的概念,iommu_group代表共享同一个streamid的一组device(表述在/sys/kernel/iommu_group中)。
所以,整个概念空间就是:
从SMMU控制器自身发现的角度:总线扫描发现设备(device)(SMMU是一种platform_device),设备匹配SMMU驱动(SMMU的驱动是drivers/iommu/arm-smmu*.c),SMMU驱动probe的时候给自己支持的总线设置bus->iommu_ops,让总线具有了iommu attach的能力。
设备的SMMU关联:总线扫描发现了一般的设备,总线的发现流程负责调用iommu_ops给这个设备加上iommu_group,由于iommu_ops指向SMMU驱动,该驱动可以根据设备的配置(比如DTS或者ACPI中的Topo信息),设置iommu_group的共享关系,并让其指向对应的iommu控制器。
其中,固件(比如DTS或者ACPI)对Topo和smmu的描述,从软件管理的角度,称为(定义为)iommu_fwspec,属于device,在device发现的时候就可以生成(比如pcie扫描或者devicetree/ACPI扫描的时候)。
这样,整个框架的模型就是这样的:
注意这个图右下角的那个对象,ARM是用arm_smmu_domain来管理驱动和设备之间的关联的,它为每个domain申请了一个独立的asid(和进程的asid完全无关)。也就是说,ARM认为,一个group只能服务一个进程。
IOMMU的整个框架,首先提供的是针对设备的DMA能力,也就是说,当我们发起dma_map的时候,设备定位了streamid和group;group定位了iommu_device和iommu_domain,iommu_domain定位了asid,这样,硬件要求的全部信息都齐了。
(另,特别说明一下,仅仅这些框架并不能把dma_map导过来,ARM平台是通过一个平台初始化, arch/arm64/mm/dma-mapping.c,来把两者关联起来的。dma-mapping负责提供全系统的dma-mapping函数,如果设备的iommu机制存在,就用设备的一套,如果不存在,就用总线的,如果总线也不存在,就会回到最初的全系统的基本方法)
综合起来,我们可以认为ARM也支持这个概念:一个group就是一个独立的隔离空间,是隔离的最小单位,如果你希望通过两个asid区分两个设备,你必须想办法建立两个group。
然后我们看VFIO。
VFIO的目的(从SMMU的角度来说)是把设备的DMA能力直接暴露到用户态。这个最初的设想,我猜是为虚拟化服务的,假设我把一个硬件功能(PF),分解成多个软件化的功能VF,比如把一张网卡模拟成多张,或者把一个显卡模拟成多个虚拟的显示界面。我就需要让PF(透过VF)直接看到虚拟机的空间。也就是说,当设备做DMA操作的时候,它必须认知虚拟空间的地址。这个恰恰就是IOMMU要做的事情,所以VFIO正好就基于iommu的逻辑空间来工作了。
VFIO本身是driver,或者说严格来说,这个子系统提供了两个driver(更严格来说,现在有三个,mdev我们晚点来说):vfio-platform和vfio-pci。他们属于什么总线你们猜都能猜到。但这两个driver不匹配总线上的任何设备。它的作用是通过device的override_driver接口(通过/sys直接强行重新绑定一个设备的驱动),让自己成为那个设备的驱动,在这个驱动中,把这个设备的io空间和iommu_group直接暴露到用户态。
被暴露的设备,通过vfio_group的形式暴露给用户程序。这个应用过程大致是这样的:
container = open("/dev/vfio/vfio", O_RDWR);
group = open("/dev/vfio/26", O_RDWR);
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container);
ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU);
ioctl(container, VFIO_IOMMU_MAP_DMA, &dma_map);
很明显,这个概念空间包括container和group两个概念,vfio_container是访问的上下文,vfio_group是vfio对iommu_group的表述。
如果我们再看仔细一点,我们应该要注意到,这里还有一个针对iommu_group的driver,这个driver决定如何使用IOMMU(前面过程中的VFIO_SET_IOMMU的概念),现在只有两个driver:vfio-iommu-type1和iommu-vfio-powerpc。但很明显,你可以有更多的type。
综合起来,这个名称空间是这样的:
最后看看vfio-mdev。vfio-platform和vfio-pci都是针对静态设备的,前者描述在DTS或者ACPI DSDT表中。后者描述在PCI的硬件枚举中。但如果我的设备是动态创建出来的呢?比如我动态让显卡给我创建3个虚拟屏幕,这怎么说?我不可能给DTS/DSDT或者PCI总线动态增加这个设备吧?mdev就是解决这个问题的。
它的模式是通过mdev_device_create()为某个设备创建一个动态VF创建点,然后你向/sys/devices/...<you_device>/dev_supported_type/<type>/create中写入一个UUID来动态创建设备(如何创建看你如何实现create回调函数了)(注2),这个设备的iommu_group默认是空的(也就是说,你要用其他手段来做DMA),但很明显,你很容易自己在Create的时候主动创建一个自己的iommu_group,然后设置为你想要的工作模式(注:当前4.11的代码不行,需要对mdev做一点点修改才行)。之后的事情,就和前面的名称空间可以拟合了。
综上,我觉得如果要实现一个设备(拥有一个独立的streamid)动态提供多个VF功能,最简单的办法是把VF创建为mdev,然后人为加入一个iommu_group,并在这个group中创建独立的asid,这样,是最容易被接受的。
上面的整个表述,有些地方并非实际情况(实际上这些地方都可以Bargain),这也是一个很多好的架构设计的思维模型:我们看到的“工作量深度”,我们说的“可行”和“不可行”,不是被当前代码严格限定的,我们是用工作量来限定的。
注1:本文的概念仅仅是工作过程的副产品,不保证正确性。
注2:mdev的总线是mdev_bus_type,所以mdev->dev是被mdev_driver驱动的。但mdev_device_create本身会在/sys/class/mdev_bus中创建一个母设备的链接,这个不是总线,而是class,不要被它的名称骗了。mdev如果要给它创建一个独特的iommu_group,就需要使用总线上的iommu_ops,但mdev_bus_type上是没有设置这个域的,所以不能依靠这个总线来分配这个空间,要想办法用父类的总线来完成这个功能。
以上是关于[转]iommu和vfio概念解构的主要内容,如果未能解决你的问题,请参考以下文章