SMMUv3(2)

流的编号

做一次转换需要地址、size以及相关属性如读/写/安全域/非安全域/可共享性/可缓存性;如果超过1个client设备使用SMMU流量,那么他们还要有StreamID来区分;StreamID在系统里的构建传送是具体实现决定的,逻辑上讲,一个StreamID就关联到一个发起转换的设备。物理设备到StreamID的映射必须描述给系统软件,ARM推荐StreamID用密集命名空间,从0开始;每个SMMU都是独立的StreamID命名空间;一个设备可以不止使用一个StreamID触发流量,可以用多个StreamID来区分设备不同的状态。

StreamID用于从Stream table选出STE(Stream Table Entry),这个STE包含这个设备的配置;Stream table最大包含2的StreamIDSize条位于内存的配置数据结构。

另一个属性,SubstreamID可能选择性的提供给SMMU做一阶段页表转换;SubstreamID可以是0-20bit,用于区分组织自同一逻辑块但去往不同的程序地址空间,比如说一个有8context的计算加速器,每个context可能映射到不同的用户进程,但是只有一个设备且通用的配置,因此必须将其分配给整个VM;SubstreamID等同于PCIE里的PASID,因为也用于非PCIE系统,所以给了一个更通用的名字;SubstreamID最大值20bit也与PCIE的PASID一致。

这些属性和大小都可以通过SMMU_IDR1寄存器检测到。

StreamID是识别一个transaction所有配置的关键,一个StreamID可以配置成bypass或要转换的项目,而这就决定了要做1或2阶段页表转换。SubstreamID提供了一个修饰符,这个修饰符在StreamID指示的一组1阶段转换间做选择,但对有StreamID选择的2阶段转换没有影响。仅实现2阶段的SMMU不能接受SubstreamID,1阶段的实现不需要支持子流,因此不需要Substream输入。另外SMMU可以选择是否实现安全和非安全两个域,如果实现了,那么SEC_SID标志就是StreamID所属的域的标志;安全域的StreamID从安全域的Stream table中找STE,非安全域的StreamID从非安全域Stream table找STE。

ARM希望对于PCI,StreamID从PCI RequestID生成,这样StreamID[15:0]就是RequestID的[15:0],如果一个SMMU后有超过1个RootComplex,ARM推荐将这16bit的RequestID命名空间编入更大的StreamID命名空间来管理,高bit位来区分连续的RequestID命名空间,这样StreamID[N:16]就能指示哪个RootComplex是stream的源;在PCIE系统里,SubstreamID应该直接从PASID一对一的对应而来。因此,实现与PCI client一起用的SMMU需要支持StreamID最少16bit。

 

数据结构和转换过程

SMMU使用一组内存中的数据结构来定位翻译数据。寄存器持有初始根数据结构的基地址,也就是Stream Table。一个包含2阶段转换表基指针的STE,同样能定位1阶段配置数据结构,而这数据结构就包含转换表基地址。一个CD(Context Descriptor)就代表1阶段翻译,一个STE就代表2阶段翻译。因此SMMU使用两个不同组的数据结构:配置结构,从一次转换的StreamID映射到转换表基指针、配置和翻译表访问的context;转换表结构,用于表示VA-IPA和IPA-PA翻译。

一次转换的过程首先会定位这次转换的配置,根据StreamID,SubstreamID;然后用这个配置来定位这个地址要用的翻译。第一步是处理转换的STE,STE可以告诉SMMU这次转换需要其他什么配置;概念上,一个STE描述一个client设备的配置,是依据它是1阶段、2阶段还是两阶段转换;多个设备可以关联到一个VM,因此多个STE可以共享同一个2阶段翻译表;同样的多个设备(streams)可能共享同一个1阶段配置,因此多个STEs可能共享同一个CD。

Stream Table查找

根据StreamID可以定位到一个STE,Stream table支持两种格式,由Stream table基寄存器控制;当一次转换发起,StreamID会被检查是否符合设置的表长,如果StreamID超出配置好的Stream table范围,或是超出2级Stream表的跨度,则转换中止;另外安全域相关的SEC_SID标志也会在SMMU_S_IDR1.SECURE_IMPL==1时被检查。

Stream table-线性Stream table

一个线性Stream table是一个连续的STE数组,从0开始,由StreamID索引,占据空间大小可配置位2的n次方再乘上STE的size,最大为SMMU在硬件中支持的StreamID为的最大数目。n就是StreamID[n:0],如下图:

两级Stream table

就像是MMU的页表一般,包含两级,可以不用连续内存空间,并且节省内存空间,具体结构如下图:

1级表有StreamID[n:SPT]索引,n是StreamID的位数,SPT是可配置的切分点,️由SMMU_(S_)STRTAB_BASE_CFG.SPLIT,2级表有StreamID[SPT-1:0]索引,依赖于各个2级表跨度。两级Stream table的支持通过SMMU_IDR0.ST_LEVEL查到,如果支持,SPT(Split Point)可以用6,8,10;StreamID超过6bit的,也就是超过64个StreamID的,必须支持两级Stream table。1级Stream表项包含一个指向2级Stream表的指针,同步的还有这个StreamID下这个表的跨度。每个Stream table项都可以标记为invalid。

上图是一个Split point设为8的1级表项,StreamID有0-1023,4x8bit;StreamID的0-255由STE数组配置在0x1000;StreamID 256-259由STE数组配置在0x2f20;StreamID 512-767都是失效的;Stream_ID 768的STE位于0x4000。

一个split point为8的两级Stream table,可以比与PCIE同时使用的线性Stream table减少内存使用量;如果支持完整的256个PCIE总线号,则RequestID或StreamID空间为16位,但是由于通常每个物理链路只有一条PCIE总线,而每条总线可能只有一个设备,因此在最坏的情况下,有效的StreamID可能没256个StreamID仅出现一次。另外,Split point为6则可提供64个2级STE,从而为每个2级表使用4KB页面。

StreamID到CD(Context Descriptors)

STE包含每个流的配置:来自设备的流量是否使能;是否为1阶段转换;是否为2阶段转换,以及相关的翻译表;哪个数据结构定位1阶段翻译表。如果使用1阶段,STE会指示1到多个内存中的地址。CD将StreamID与1阶段翻译表的基指针、此流的配置、ASID联系起来;如果使用了Substream,就会由多个CD指示多个1阶段翻译,每个CD指向1个substream。提供有SubstreamID的转换在1阶段翻译关闭的情况下会被终止。

如果使用了2阶段转换,STE会包含2阶段翻译表的基指针和VMID;如果多个设备联系到了一个VM,这就意味着他们共享2阶段翻译表,多个STE可能映射到一个2阶段翻译表。ARM期望在有hypervisor时,Stream table和2阶段转换表应该有hypervisor管理,在Guest控制下的与设备相关的CD和1阶段翻译表应该由GuestOS管理,另外,hypervisor可以根据自己的要求使用分离的hypervisor 1阶段翻译。如果没有hypervisor,裸金属OS管理Stream table和CD。

当转换时带了SubstreamID,并且配置也使能了substream,SubstreamID就负责索引1阶段翻译context的CD,在这种配置里,如果转换时没有带SubstreamID,则其转换行为就会根据STE.S1DSS标志位的值:0xb00表示所有流量都应有SubstreamID,没有就会提错误,aborted,然后记录事件;0b01表示没有SubstreamID则bypass掉1阶段翻译;0b10表示没有SubstreamID则用Substream 0的CD,但是当SubstreamID为0时会abort并记录事件。

CD和STE数据结构提供的ASID和VMID值标记TLB条目,这个TLB条目是从CD和STE的配置执行转化查找并创建的;这些标记被用于查找识别不同流的地址空间,或者在接收到TLB维护操作的广播时需要去匹配并失效掉相关条目;具体的实现可能同样使用这些标记来允许识别不同流的翻译表。

上图给出了一个示例配置,StreamID从线性Stream table选出一个STE,这个STE指向一个2阶段的翻译表和一个1阶段配置的CD,然后这个CD只想一个1阶段转换表。下图给出了一个STE只想一个多CD数组的配置,根据输入SubstreamID才会选择一个CD,也就是SubstreamID决定所使用的1阶段翻译表的情况。

下图展示了一个更复杂的层次结构,用上了多级Stream table,其中一个STE指向了CD数组,第二个STE指向了单CD,第三个STE只想了一个多级CD表,基于此多级CD表,可以支持很多流和很多Substream而不需要物理连续的表内存。

一次转换的处理需要一系列逻辑上的步骤:

1. 如果SMMU全局性的关闭了(如SMMU_CR0.SMMUEN==0),转换就会对地址不做处理直通SMMU;全局属性设置如内存类型、共享性可能还用SMMU的SMMU_GBPA寄存器的设置,当然可能SMMU_GBPA寄存器被配置成所有转换都abort;

2. 如果SMMU没关,配置就会由一下情况确定:

  a. STE被定位到

  b. STE是否使能2阶段翻译,STE还包含2阶段转换表基地址

  c. 如果STE使能1阶段翻译,定位到CD,如果STE里2阶段翻译也使能,CD由IPA空间使用2阶段翻译得到,否则CD会从PA地址空间得到。

3. 有转换表,且不是失效的

  a. 如果1阶段翻译被配置了,那么CD就有翻译表的基地址,然后去翻译一遍;如果2阶段翻译也在STE里使能了,这可能还需要2阶段翻译。如果1阶段翻译没使能,那就直接bypass掉,然后输入地址直接做2阶段翻译。

  b. 如果2阶段翻译被使能了,并且STE使能了1阶段翻译,那么STE就包含一个1阶段转换表地址,然后就会嵌入一个1阶段翻译,然后得到IPA;如果2阶段翻译没使能,2阶段被bypass掉,那么2阶段转换的输入地址就被直接作为输出

4. 一个有效转换意味着没有翻译失败,并且会应用并转发输出地址以及相应的内存属性。

具体的实现可能在上面这些步骤的任意位置做缓存;并且在这些步骤中的很多位置也可能产生转换失败,然后转换就会终止;如果一次转换没有定位到有效的配置或是类型不支持转换都会终止,然后报告事件;根据CD和STE配置,转换在失败时决定终止还是卡住并抛出软件失败结果。

 

配置和翻译搜索:

下图给出了配置搜索和翻译搜索的概念:

一次转换首先要搜索配置,然后SMMU决定如何开始翻译此次转换,这包含定位到STE,然后如果需要的话,在定位到CD;配置搜索阶段与输入地址无关,与SMMU全局寄存器配置,与要转换的StreamID,与要转换的SubstreamID(如果有的话)相关;配置的搜索结果是特定的stream或substream的配置,能定位翻译相关配置,包含1阶段转换表基指针,ASID和与转换表的解释相关的属性(如翻译粒度);还包含2阶段转换表基指针,VMID和与解释转换表相关属性;流相关的属性,如异常等级、与PE相关翻译域,还是要翻译到PE的内存空间里嘛。翻译搜索阶段与PE的差不多,最后就是输出个物理地址,与输入地址、Stream的安全域一场等级,ASID,VMID等。上图就给出了一个类似PE的使用TLB的翻译搜索步骤,ARM希望SMMU使用TLB来缓存翻译结果,而不是每次转换都要TTW,但是这不是固定的。

一次翻译有StreamWorld属性,表示转换方式,直接等效于PE的一场等级;所有翻译caches都标记了一个StreamWord,好在查找和失效的时候匹配;StreamWorld由插入和查找翻译的配置定义,由STE的安全态:STE.Config、STE.STRW、SMMU_CR2.E2H组合定义;除了插入和查找,StreamWorld/异常等级/翻译方式还会影响不同类型的TLB无效范围。

写到这里,反正我自己已经认为自己对SMMUv3的代码会怎么写有了大致的感觉,本来研究SMMUv3就是要去解掉一个显卡相关的BUG,虽然东西写的乱七八糟,但我又自认为总结完了,是时候去直面这个BUG了!

 


N天之后,偶然间看到了一位大神的blog,非常受教,原地址是:

https://kernelgo.org/armv8-virt-guide.html

我抄一段文字如下:

3. I/O Virtualization

设备直通的目的是能够让虚拟机直接访问到物理设备,从而提升IO性能。 在X86上使用VT-d技术就能够实现设备直通,这一切都得益于VFIO驱动和Intel IOMMU的加持。 那么在ARMv8-A上为了支持设备直通,又有哪些不同和改进呢?

同X86上一样,ARM上的设备直通关键也是要解决DMA重映射和直通设备中断投递的问题。 但和X86上不一样的是,ARMv8-A上使用的是SMMU v3.1来处理设备的DMA重映射, 中断则是使用GICv3中断控制器来完成的,SMMUv3和GICv3在设计的时候考虑了更多跟虚拟化相关的实现, 针对虚拟化场景有一定的改进和优化。

先看下SMMUv3.1的在ARMv8-A中的使用情况以及它为ARM设备直通上做了哪些改进[Ref3]。 SMMUv3规定必须实现的特性有:

  • SMMU支持2阶段地址翻译,这和内存虚拟化场景下MMU支持2阶段地址翻译类似, 第一阶段的地址翻译被用做进程(software entity)之间的隔离或者OS内的DMA隔离, 第二阶段的地址翻译被用来做DMA重映射,即将Guest发起的DMA映射到Guest的地址空间内。
  • 支持16bit的ASIDs
  • 支持16bit的VMIDs
  • 支持SMMU页表共享,允许软件选择一个已经创建好的共享SMMU页表或者创建一个私有的SMMU页表
  • 支持49bit虚拟地址 (matching ARMv8-A’s 2×48-bit translation table input sizes),SMMUv3.1支持52bit VA,IPA,PA

SMMUv3支持的可选特性有:

  • Stage1和Stage2同时支持AArch32(LPAE: Large Page Address Extension)和AArch64地址翻译表格式(兼容性考虑)
  • 支持Secure Stream (安全的DMA流传输)
  • 支持SMMU TLB Invalidation广播
  • 支持HTTU(Hardware Translation Table Update)硬件自动刷新页表的Access/Dirty标志位
  • 支持PCIE ATS和PRI(PRI特性非常厉害,后面单独介绍)
  • 支持16K或者64K页表粒度

我们知道,一个平台上可以有多个SMMU设备,每个SMMU设备下面可能连接着多个Endpoint, 多个设备互相之间可能不会复用同一个页表,需要加以区分,SMMU用StreamID来做这个区分, 通过StreamID去索引Stream Table中的STE(Stream Table Entry)。 同样x86上也有类似的区分机制,不同的是x86是使用Request ID来区分的,Request ID默认是PCI设备分配到的BDF号。 不过看SMMUv3 Spec,又有说明:对于PCI设备StreamID就是PCI设备的RequestID, 好吧,两个名词其实表示同一个东西,只是一个是从SMMU的角度去看就成为StreamID,从PCIe的角度去看就称之为RequestID。 同时,一个设备可能被多个进程使用,多个进程有多个页表,设备需要对其进行区分,SMMU使用SubstreamID来对其进行表示。 SubstreamID的概念和PCIe PASID是等效的,这只不过又是在ARM上的另外一种称呼而已。 SubstreamID最大支持20bit和PCIe PASID的最大宽度是一致的。

STE里面都有啥呢?Spec里面有说明:

  • STE里面包含一个指向stage2地址翻译表的指针,并且同时还包含一个指向CD(Context Descriptor)的指针
  • CD是一个特定格式的数据结构,包含了指向stage1地址翻译表的基地址指针

理论上,多个设备可以关联到一个虚拟机上,所以多个STE可以共享一个stage2的翻译表。 类似的,多个设备(stream)可以共享一个stage1的配置,因此多个STE可以共享同一个CD。

Stream Table是存在内存中的一张表,在SMMU设备初始化的时候由驱动程序创建好。 Stream Table支持2种格式,Linear Stream Table 和 2-level Stream Table, Linear Stream Table就是将整个Stream Table在内存中线性展开为一个数组,优点是索引方便快捷,缺点是当平台上外设较少的时候浪费连续的内存空间。 2-level Stream Table则是将Stream Table拆成2级去索引,优点是更加节省内存。

arm_strtab.pnguploading.4e448015.gif正在上传…重新上传取消2-level Stream Table

在使能SMMU两阶段地址翻译的情况下,stage1负责将设备DMA请求发出的VA翻译为IPA并作为stage2的输入, stage2则利用stage1输出的IPA再次进行翻译得到PA,从而DMA请求正确地访问到Guest的要操作的地址空间上。

在stage1地址翻译阶段:硬件先通过StreamID索引到STE,然后用SubstreamID索引到CD, CD里面包含了stage1地址翻译(把进程的GVA/IOVA翻译成IPA)过程中需要的页表基地址信息、per-stream的配置信息以及ASID。 在stage1翻译的过程中,多个CD对应着多个stage1的地址翻译,通过Substream去确定对应的stage1地址翻译页表。 所以,Stage1地址翻译其实是一个(RequestID, PASID) => GPA的映射查找过程。 注意:只有在使能了stage1地址翻译的情况下,SubstreamID才有意义,否则该DMA请求会被丢弃。

在stage2地址翻译阶段:STE里面包含了stage2地址翻译的页表基地址(IPA->HPA)和VMID信息。 如果多个设备被直通给同一个虚拟机,那么意味着他们共享同一个stage2地址翻译页表[Ref4]。

arm_smmu_2stage_translation.pnguploading.4e448015.gif正在上传…重新上传取消arm_smmu_2stage_translation.png

值得注意的是:CD中包含一个ASID,STE中包含了VMID,CD和VMID存在的目的是作为地址翻译过程中的TLB Tag,用来加速地址翻译的过程。

系统软件通过Command Queue和Event Queue来和SMMU打交道,这2个Queue都是循环队列。 系统软件将Command放到队列中SMMU从队列中读取命令来执行,同时设备在进行DMA传输或者配置发生错误的时候会上报事件, 这些事件就存放在Event Queue当中,系统软件要及时从Event Queue中读取事件以防止队列溢出。

arm_smmu_all.PNGuploading.4e448015.gif正在上传…重新上传取消arm smmu all

SMMU支持两阶段地址翻译的目的只有1个,那就是为了支持虚拟化场景下的SVM特性(Shared Virtual Memory)。 SVM特性允许虚拟机内的进程都能够独立的访问直通给虚拟机的直通设备,在进程自己的地址空间内向设备发起DMA。 SVM使得虚拟机里面的每个进程都能够独立使用某个直通设备,这能够降低应用编程的复杂度,并提升安全性。

为了实现虚拟化场景下的SVM,QEMU需要模拟一个vSMMU(或者叫vIOMMU)的设备。 虚拟机内部进程要访问直通设备的时候,会调用Guest驱动创建PASID Table(虚拟化场景下这个表在Guest内部), 在这个场景下PASID将作为虚拟机内进程地址空间的一个标志,设备在发起DMA请求的时候会带上PASID Prefix,这样SMMU就知道如何区分了。 创建PASID Table的时候会访问vSMMU,这样Guest就将PASID Table的地址(GPA)传给了QEMU, 然后QEMU再通过VFIO的IOCTL调用(VFIO_DEVICE_BIND_TASK)将表的信息传给SMMU, 这样SMMU就获得了Guest内部进程的PASID Table的shadow信息,它就知道该如何建立Stage1地址翻译表了。

所以,在两阶段地址翻译场景下,Guest内部DMA请求的处理步骤

Step1:  Guest驱动发起DMA请求,这个DMA请求包含GVA + PASID Prefix
Step2: DMA请求到达SMMU,SMMU提取DMA请求中的RequestID就知道这个请求是哪个设备发来的,然后去StreamTable索引对应的STE
Step3:  从对应的STE表中查找到对应的CD,然后用PASID到CD中进行索引找到对应的S1 Page Table
Step4: IOMMU进行S1 Page Table Walk,将GVA翻译成GPA(IPA)并作为S2的输入
Step5: IOMMU执行S2 Page Table Walk,将GPA翻译成HPA,done!

纵观SMMUv3,从设计上来和Intel IOMMU的设计和功能基本类似,毕竟这里没有太多可以创新的地方。 但ARM SMMUv3有2个比较有意思的改进点: 一个是支持Page Request Interface(PRI),PRI是对ATS的进一步改进。当设备支持PRI特性的时候, 设备发送DMA请求的时候可以缺页IOPF(IO Page Fault),这就意味着直通虚拟机可以不需要进行内存预占, DMA缺页的时候SMMU会向CPU发送一个缺页请求,CPU建立好页表之后对SMMU进行回复,SMMU这时候再将内容写到DMA Buffer中。 另外一个改进就是,DMA写内存之后产生脏页可以由硬件自动更新Access/Dirty Bit, 这样就对直通设备热迁移比较友好,但这个功能是需要厂商选择性支持的, 而且在这种场景下如何解决SMMU和MMU的Cache一致性是最大的挑战。

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值