仓库源文

.. Kenneth Lee 版权所有 2018-2020

:Authors: Kenneth Lee :Version: 1.0

PCIE总线的地址问题


本文总结一下PCIE的地址使用问题,这个问题在PCIE标准中语焉不详,很多东西是留给实 现者的,所以这个问题就值得探讨了。

PCIE的总线的体系结构示例抽象如下(只能用示例才比较好表述,否则更难理解):

    .. figure:: _static/pcie1.png

我特别高亮这里的三个特征:

第一,PCIE总线是个点到点的串行总线。现在的高速总线基本上都是串行总线,这是硬件 工艺决定的,并行总线速度做不上去。所以这个topo模型(hierarchy)中,每个End Point都可以用一个地址来标识(所以才有本文的地址问题),每个Switch或者网桥可以看 作是一个调度器,它根据地址来调度读写请求发到哪里。连在RC上的EP称为RCiEP,一般都 不是什么正常的东西,我们后面讨论中忽略它。

第二,我们注意到,PCIE标准演进到现在,从来没有认真讨论过RC是什么东西,这个部分 是实现相关的,什么逻辑搞不定了,都说,这玩意儿RC提供,RC怎么提供呢?那是实现者 的事,你问他们去。我们得对这东西有这样的认识。

第三,PCIE总线是一个子总线,是和别人共存的,我们把和CPU发出地址的那个总线称为系 统总线,和RC一样,系统总线也是个实现相关的概念。

从软件的角度来说,对某个设备寻址,就是往系统总线里面写一个地址。对系统总线来说 ,如果这个地址在RC的范围内,就把这个读写请求发到RC上,RC怎么处理剩下的细节,那 就是PCIE标准要解决的问题了。

本文要探讨的,就是从软件角度怎么理解这个地址的使用问题。

PCIE总线体系把地址空间分成两个部分,第一个部分叫ECAM空间,是PCIE的标准配置空间 ,提供标准的控制整个PCIE功能的基本语义,它的地址组成是“RC基地址+16位BDF+偏移”( BDF是Bus,Device,Function的简称,在Linux上lspci就能看见)。通过对这个空间寻址 ,就可以实现对PCIE总线系统的配置。

第二个部分我这里简称BAR空间,这个空间是RC和系统总线设计者讨论决定的,在对ECAM进 行配置的时候,软件和硬件进行协商,最后在某个BDF的ECAM空间的Base Address Register(BAR)中指向分配好的,给这个EP的一组IO空间。

所以RC是个很吃资源的玩意儿,总线的地址空间是有限的,但总线上会插入什么EP,插入 多少个EP呢,这些都不确定。你可能插入3张高速网卡,也可能插入20个NVMe控制器,每个 要多少IO空间呢?用的时候才知道。但RC需要多大的ECAM和BAR空间,这在设计阶段就要决 定。

当然,那个问题我们可以留给总线设计者。我们现在关心的问题是地址。CPU发出一个物理 地址,落入分配给RC的空间,这个地址就进入PCIE的调度体系。就可以从RC开始,变成 PCIE自己的消息(TLP)。

TLP主要依靠BDF(在TLP中称为RequesterID和CompleterID)来寻址,因为无论如何,一个 地址请求,终究目的是发给一个设备的某个Function,然后寻址这个Function里面的某个 偏移。

如果CPU发出的地址落在ECAM的范围内,就变成配置消息,基于解码出来的BDF就可以发到 对应的EP上了。调度系统也可以基于配置找到对应的BDF来发送。

如果是BAR空间,这个东西如何分配给每个BDF的,RC肯定也是清楚的,同样可以转化为BDF ,发送给对应的EP,这个过程没有问题。

现在的问题是:反过来呢?CPU把一个物理地址交给一个EP,EP访问这个地址,怎么回到总 线上?

EP本身就是TLP Aware的。所以它可以直接控制它发出的消息类型,不需要匹配这个地址是 个BAR空间还是ECAM空间。如果它发出的是内存寻址,这个很简单,直接回到RC,RC自己和 系统总线搞定就好了。

如果发出的地址是IO空间内的,桥就需要知道这在不在自己下属的EP上。所以桥需要知道 自己和下属的所有BAR空间的范围——很幸运,这个在枚举设备,指配总线ID的时候是可以给 出来的(因为配置枚举配置桥的时候,是要给定桥的BAR空间的,而桥的BAR空间,就是它 下属所有节点的预留空间)。换句话说,桥是知道某个地址是属于本桥之内的,还是必须 往回送的。

这样,如果CPU和EP都使用物理地址,整个寻址就没有问题了。

现在看虚拟地址的情况,PCIE通过ATS支持虚拟地址。ATS服务对应着MMU和IOMMU地址翻译 服务。

MMU给CPU提供了虚拟地址能力,这种能力让CPU可以给每个进程,或者每个虚拟机分配独立 的地址空间。使用了MMU,CPU可以发出虚拟地址VA,这个地址通过MMU系统翻译成物理地址 PA。

现代MMU可以提供多级的地址翻译能力,把虚拟机中的进程,从进程虚拟地址翻译为虚拟机 虚拟地址,最后变成物理地址。

IOMMU提供设备一侧的翻译能力,让设备直接访问虚拟地址,经过一样的翻译过程,让设备 可以直接认知和使用CPU一侧提供的虚拟地址。

这种能力Map到PCIE中,就是ATS服务了。ATS服务在上面的Topo中是这样的定义的:

    .. figure:: _static/pcie2.png

AT是地址翻译代理,正如前面说的,它的行为特征,实现方法,这都是RC的烂事,PCIE标 准是不管的。PCIE标准只关心请求到了RC,你给我搞定它。在具体实现的时候,这个东西 可能实现为x86的IOMMU或者ARM的SMMU等具体的地址翻译机制(但请注意,IOMMU,SMMU会 实现AT要求之外的功能,比如安全保护)。

而ATC相当于CPU的TLB,就是地址翻译的Cache。这就是说,有ATS能力的EP,自带地址翻译 功能,如果它要发出一个地址,进入PCIE总线的时候,一定程度上可以认为就是物理地址 了,这就完全和我们前面谈到的调度逻辑自恰了。因为参与总线topo调度的都是物理地址 。

当然,我们这里说“一定程度”。这还是需要解释一下的,实际情况是,EP在发出ATC中没有 Cache的地址的时候,是直接发出VA的,只是在这个访问请求中带了一个标记(AT),说明 我这个是个VA,请求RC支援。这样的地址桥就直接向上送,一直通到RC,让RC给它响应, 更新ATC,下次再发访问请求,就可以使用不带AT标记的PA地址了。

但ATS并不是必须的特性,某个EP可以没有实现ATC,但如果RC上有地址翻译功能,比如实 现了SMMU,那么EP也可以发出虚拟地址,让它一路直接上传到RC上,然后让RC来完成地址 翻译。

但如果这样做,有一个严重的问题,如果这个虚拟地址正好落在桥的BAR空间的范围内怎么 办呢?这你就需要ACS服务了,ACS服务作用在所有有调度功能的节点上,包括桥,Switch 乃至带VF的PF。你可以通过ACS服务要求禁止P2P消息发送,这样所有的地址请求都会路由 回RC,由RC来解释(比如基于SMMU来解释)。

另外谢谢@Byron的信息: https://cloudplatform.googleblog.com/2017/02/fuzzing-PCI-Express-security-in-plaintext.html ,从这个博客上我们还看到,虽然IOMMU等设计可以在RC以上控制设备写入内存的范围,但 由于P2P消息的存在,这些设备有可能攻击其他EP,这也需要ACS服务对这种高危设备进行 隔离。ACS还可以用于禁止ATS服务。ATS服务最大的问题在于它是基于信任的,是ATC自己 声称它发出的地址是经过翻译还是没有经过翻译的,如果ATC生成这个地址已经翻译,就可 以越过IOMMU的隔离(fixme:不知道这个问题在SMMU上是不是同样存在?),这个问题也 可以通过ACS来保护(ACS可以在桥上禁止ATS消息)。