PCIe初始化扫描以及路由
PC或服务器中,每个PCIe设备的地址为BDF, 本文要说明两个事情,这些地址是怎么来的,是PCIe外设固件固定的还是系统启动时分配的?二,BDF怎么路由到正确的PCIe外设?
开宗明义地先给出第一个问题的答案,外设的BDF是BIOS分配的。既然是分配的,那么在没有BDF的情况下,是怎么能够分配给PCIe外设的,具体的过程是怎样的?
PCIe BDF 初始化扫描
BIOS上电后,开始循环扫描PCIe总线,RC以及与RC相连的第一个RP0所在的总线定义为BUS 0,每个RP就是一个PCIe bridge,即一个device,RP的device号是从1开始,因为dev 0是固定留给RC的。
循环扫描的代码片段类似如下:

PCIe总线和以太网等一样,上面是有自己的协议的,协议分层如下图:

从下往上,依次为物理层、数据链据层、事务层(transaction),其中事务层的协议为TLP。
TLP, 全称为transaction layer protocol, 结构如下图:



其中,tlp.type用来指示请求command. CfgRd0, CfgRd1, CfgWr0, CfgWr1, Cpl, CPlD在初始化扫描中就用到了。
我们继续初始化扫描过程。
下图示意为开始扫描前的拓扑以及信息

当BIOS扫描target BDF 01:00.0时,
1. RC判断target BDF的bus == RP0的secondary(都为1),从RP0 发出CfgRd0请求,即读取target BDF的配置空间数据。
这里用CfgRd0而不用CfgRd1,是因为target bus为RP0的secondary bus, 即目标对象是RP0的下级总线上的设备,由于PCIe是点对点的结构,目标设备必定是secondary bus直连的设备,而不需要转发。如果目标设备的bus是secondary bus直连的switch下的PCIe设备,则需要switch的转发,此时 RP0.secondary < target BDF的bus <= RP0.subordinate, RP0发送的就是CfgRd1了。
2. RP0直连的设备ep0,收到CfgRd0请求,判断请求中的BDF中的device.func是否为自己(硬件固件固定,PCIe device为0,func依据自己的功能,可以有8个func, 从0-7).
如果匹配自己的device.func,则将自己的配置空间的数据通过completion响应(CplD)返回。

BIOS收到CplD后,记录下设备的VID、BAR、Class Code等信息, 判断出该设备是endpoint, 然后继续扫描下一个BDF。
假设bus 1都扫描完了,只有一个ep0,RP0就被更新了Subordinate bus number为1 (对于bridge, subordinate为子树的最大bus号;对于switch UP, subordinate为所有DP分支的最大bus,包括下级、下下级的switch/ep;对于switch DP, subordinate为直连的bus号),如下图:

3. 接下去就是扫描BDF 02:00.0.
此时程序设定从RP1开始扫描,RP1的BDF为00:02.0, RP1的primary bus为0,secondary bus为RP0.subordinate bus number + 1,即为2, 它的subordinate bus number初始化为255. 此时要去扫描的target BDF为02:00.0。
由于target BDF的bus == RP1.secondary(都为2),RC就从RP1发出CfgRd0, 读取RP1直连设备sw0的配置空间数据。

RP1直连的PCIe switch匹配请求的device.func后,将配置数据通过cplD返回。
PCIe switch使用的配置空间结构为type 1配置空间结构,如下:

BIOS收到CplD后,保存必要的数据,然后发送CfgWr0, 配置PCIe switch的UP、DP0-DP1的primary为2,DP0的secondary为3,DP0的subordinate bus number初始化为255, DP1的secondary为-1, DP1的subordinate bus nubmer为-1.(DP0-DP1的配置在capabilities扩展里面,通过port号来区分)。
BIOS通过返回的PCIe switch的配置数据中的class code得知,这是一个switch设备,则在配置完SW0后,接着扫描sw0.dp0, 扫描的target BDF为03:00.0, 此时RP1.secondary < target BDF bus 3 <= RP1.suordinate(初始值255), 则从RP1发送CfgRd1到sw0, sw0判断target BDF bus 3 == sw0.dp0.secondary,则将请求转成CfgRd0,从DP0发出,SW1收到这个CfgRd0, 判断出是CfgRd0, 且dev.func匹配,则将自己的配置数据通过CplD返回。BIOS/RC然后发送CfgWr1, 通过sw0转发,变成CfgWr0发送给SW1,这样就配置了SW1的up,dp0,dp1的primary,secondary,subordinate,此时sw1.dp0.secondary为4,sw1.dp1.secondary未知,为-1。
接着,BIOS接着扫描 target BDF 04:00.0,
RC判断 RP1.secondary < target BDF bus 4 <= RP1.suordinate(初始值255), 则从RP1发送CfgRd1到sw0,
sw0判断 sw0.dp0.secondary < <target BDF bus 4 <= sw0.dp0.subordinate, 则从sw0.dp0转发CfgRd1到SW1.
SW1收到请求后,判断出target BDF bus 4 == sw1.dp0.secondary,则将CfgRD1转成CfgRd0,从sw1.dp0转发给ep3, ep3则将配置数据返回,同时,中间的sw更新自己的up和dp的subordinate.其他分支的扫描都如上描述一样,BIOS就通过这种方式来完成PCIe设备的枚举以及bridge/sw的primary,secondary,subordinate的更新。
如下图:


PCIe设备路由
假设系统的PCIe拓扑如下:

现在要路由到 BDF 08:00.0, 怎么路由的呢?
RC拿到target BDF后,判断出 RP1.secondary < target BDF bus 08 <= RP1.ordinate bus number, 则从RP1发送RD1/WR1请求。
RD1/WR1请求到达SW0后,SW0判断出SW0.UP.secondary < target BDF bus 08 <= SW0.UP.ordinate bus number,则接着判断SW0.DP0.secondary < SW0.DP1.secondary < target BDF bus 08, 则从SW0.DP1转发RD1/WR1请求。
RD1/WR1请求到达 SW2后,SW2判断出SW2.UP.secondary < target BDF bus 08 <= SW2.UP.ordinate bus number,则接着判断SW2.DP1.secondary == target BDF bus 08, SW2于是把RD1/WR1请求转成RD0/WR0请求,从SW2.DP1发出。
RD0/WR0请求到达ep6后,ep6判断是type0请求,且dev.func匹配自己,则响应这个请求。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)