计算机系统复位或者上电之后,软件首先要对 PCIe 总线进行扫描来枚举该总线下连接的所有设备。
在扫描之前,软件唯一能感知到的设备就是 Host/PCI 桥,同时还知道 Host/PCI 桥下面的总线号是 0,如下图所示:

bios枚举设备 pcie设备枚举_pci-e

对于桥设备(bridge),它们的上游端口(upstream side)连接的总线称为 primary bus,下游端口(downstream side)连接的总线称为 secondary bus。


扫描并发现 PCIe 总线上所有设备的过程称为 PCIe 枚举过程。

1. 确认 PCIe 总线上 Function 是否存在

众所周知,每个 PCIe Function 的配置空间寄存器中都有一个叫做 Vendor ID 的只读寄存器。Vendor ID 是由 PCI-SIG 为每个厂商分配的 16 位的特定值。


枚举过程中,系统软件会遍历所有可能的 Bus & Device & Function 的组合,尝试去读取每个 Bus & Device & Function 位置的 Vendor ID 寄存器。根据读取到的结果,就能判断某个 Bus & Device & Function 所定位的 Function 是否真实存在。


这种枚举过程的逻辑十分简单,只要处理两种异常情况即可:

  • 异常1:Function 确实不存在。
  • 异常2:Function 存在,但是还没准备好,所以不能正常响应对 Vendor ID 的读请求。

1.1 异常:设备不存在

对于PCI 总线,如果一个设备不存在,那么当系统软件尝试去读这个设备的配置空间寄存器时,会出现读请求超时并产生 Master Abort 错误。与此同时,由于没有连接设备,对应的总线信号(包括数据信号)处于上拉(pulled up)状态。因此,在设备不存在的情况下,读 Vendor ID 寄存器的操作会得到特定值:0xFFFF。当系统软件读到某个设备的 Vendor ID 是 0xFFFF 时,就会知道该设备不存在。


对于 PCIe 总线,当尝试读取一个不存在的设备的配置空间寄存器时,会得到一个不带数据的 PCIe 完成消息(Completion without data)。比较特殊的是,这个完成消息会携带一个 UR (Unsupported Request) 状态标识。为了与 PCI 总线兼容,PCIe Root Complex 在枚举阶段收到这种特殊的完成消息时,会向处理器返回特定值:0xFFFF。


综上所述,从系统软件的角度来看,枚举过程就是尝试去读取所有可能存在的设备的 vendor ID,如果读到的值是 0xFFFF,那么就可以认为该设备不存在。

1.2 异常:设备还没准备好

设备上电之后,需要一段时间的初始化之后才能响应外部的访问请求。针对这个需求,处理方法是:

  • 当传输速率小于等于 5 GT/s (PCIe Gen1/2)时,系统软件在复位完成后,需要等待 100 ms 再尝试去读取设备的 Vendor ID 寄存器。
  • 当传输速率大于 5 GT/s (PCIe Gen3)时,系统软件在 link training 完成后,需要等待 100 ms 再尝试去读取设备的 Vendor ID 寄存器。

在 PCI 2.3 协议中规定,初始化时间(Initialization Time)是从 RST# 信号无效(deasserted)开始,持续 2 个 PCI 时钟,大约 1 秒钟时间。在这 1 秒钟时间内,PCIe Function 进行必要的初始化,以准备接受来自外部的访问请求。在 PCIe 协议里,这个时间为 1 秒(+50% / -0%)。举例来说,Function 可用这段初始化时间(Initialization Time)从外部的 EEPROM 读取一些配置参数,并根据这些参数配置 Function 初始化配置空间寄存器。在 PCI 总线中,如果 Function 在准备好之前就接收到了配置空间寄存器的访问请求,那么它有三种处理方式:(1)忽略请求;(2)重试请求;(3)接收请求并等待准备好之后再响应。在热插拔系统中,第三种处理方式会带来问题。由于 PCI 总线是共享总线,第三种处理方式会导致在请求被正确响应之前,PCI 总线没办法处理其他请求。


在 PCIe 系统中,处理的过程和 PCI 有些区别。在 PCIe 总线中,如果 Function 在准备好之前就收到了配置空间寄存器的访问请求,那么它必须返回一个特殊的完成消息(Completion)。这个完成消息的状态必须被设置为 Configuration Request Retry Status(CRS)。CRS 状态只有对配置空间寄存器访问请求(configuration request)是合法的。如果其他类型的请求的响应消息中 CRS 被置位,应该当做错误处理。同时,这种特殊的响应只有在复位之后的 1 秒之内可以被认为是合法的,如果没有在规定时间内响应,就可以认为设备不存在。


在复位后的 1 秒内,PCIe Root Complex 需要根据 Root Control 寄存器中 CRS Software Visibility 位的值对 CRS 状态位为 1 的完成消息(completion)进行处理:

  • 处理方式1:如果 CRS Software Visibility 位的值是 1,如果系统软件发出一个配置空间寄存器读请求并且该请求的数据中包含两个字节的 Vendor ID,那么 Root Complex 在收到 CRS 状态位为 1 的响应时,会将 Vendor ID 对应的数据设置为 0x0001 返回给处理器(系统软件)。同时,该请求中的其他数据都会被设置为 0xff。鉴于 0x0001 并不是一个合法的 Vendor ID 值,因此,系统软件在收到该值后,就知道需要过一会再来尝试读取该设备,可以暂时先去执行其他任务。
  • 处理方式2:如果 CRS Software Visibility 位的值是 0,那么 Root Complex 在收到 CRS 状态位为 1 的响应时,会立即重新发起配置空间访问请求,直到收到 CRS 状态位不是 1 的响应。这种方式会导致处理器(系统软件)一直这里等待,浪费资源。

bios枚举设备 pcie设备枚举_bios枚举设备_02

注意:CRS Software Visibility 位只会对配置空间寄存器读请求产生影响,对于其他类型的配置空间访问请求,Root Complex 会立即重复发送请求,直到收到 CRS 状态位不是 1 的响应。



2. 判定 Function 是 Endpoint 还是 Bridge

在 PCIe 设备配置空间寄存器中,有一个 Header Type 寄存器。Header Type 寄存器的低 7 位能够用来判定 Function 的类型:

  • 0 = Endpoint
  • 1 = PCI-PCI 桥(用于连接 2 条总线)
  • 2 = Cardbus 桥(老式接口,现在用的很少)

bios枚举设备 pcie设备枚举_bios枚举设备_03