3. PCIe 枚举与资源分配
PCIe 枚举是系统上电后由 Root Complex (RC) 发起的拓扑发现过程,其核心目标是为每个连接的设备分配唯一的 BDF 编号,并为其所需的 MMIO/PIO 地址空间分配连续的物理地址窗口。本章深入解析枚举流程、BDF 分配机制及地址空间布局。
3.1 系统上电枚举流程
枚举过程遵循深度优先遍历算法,从 Bus 0 开始,逐级扫描每个 Bus 上的 Device,并递归扫描下游 Bridge 所挂接的 Subordinate Bus。具体步骤如下:
- 初始化 Root Complex:RC 配置空间中的 Bus Number 寄存器初始化为 0,作为枚举起点。
- 扫描 Bus 0:遍历 Bus 0 上所有可能的 Device (0-31),读取每个 Device 的 Vendor ID 寄存器。若返回 0xFFFF,表示该 Device 不存在。
- 识别 Type 1 Header:若 Device 的 Header Type 为 0x01 (PCIe-to-PCIe Bridge),则进入递归枚举流程。
- 分配 Secondary/Subordinate Bus:为每个新发现的 Bridge 分配一个未使用的 Bus Number 作为 Secondary Bus,并预留一个 Subordinate Bus 范围。
- 递归扫描下游:对 Secondary Bus 重复步骤 2-4,直到所有叶子设备被枚举。
- 分配 MMIO/PIO 窗口:根据设备请求的 BAR 大小,在系统地址空间中分配连续的物理地址区域。
关键时序: 枚举通常在 BIOS/UEFI 阶段完成,操作系统驱动加载时通过读取配置空间获取已分配的资源信息,不再重新枚举。
枚举深度: PCIe 规范支持最多 256 个 Bus,每个 Bus 最多 32 个 Device,每个 Device 最多 8 个 Function。理论最大设备数为 256 × 32 × 8 = 65536。
3.2 BDF(Bus:Device.Function)分配
BDF 是 PCIe 设备的唯一硬件标识符,由 16 位组成:Bus Number (8位)、Device Number (5位)、Function Number (3位)。枚举过程中,固件为每个设备分配 BDF,并写入配置空间。
| 字段 | 位宽 | 范围 | 说明 |
|---|---|---|---|
| Bus Number | 8 bit | 0 - 255 | 标识总线编号,0 为 Root Bus,由 Bridge 的 Secondary Bus Number 寄存器分配 |
| Device Number | 5 bit | 0 - 31 | 标识总线上的设备编号,对应 AD[31:16] 的 IDSEL 信号 |
| Function Number | 3 bit | 0 - 7 | 标识多功能设备内的功能单元,Function 0 必须存在 |
注意: 在 PCIe 中,Device Number 不再由 IDSEL 信号决定,而是由 RC 在枚举时通过配置写入决定。但 BDF 格式与 PCI 兼容,确保软件透明。
3.2.1 BDF 分配示例
以下是一个典型系统枚举后的 BDF 拓扑示例:
3.3 MMIO/PIO 地址空间分配
每个 PCIe 设备通过 Base Address Register (BAR) 向系统声明其需要的地址空间大小和类型。固件根据 BAR 的请求,在系统物理地址空间中分配连续的窗口。
3.3.1 BAR 类型与属性
| BAR 类型 | 地址空间 | 可预取 | 典型用途 |
|---|---|---|---|
| Memory (32-bit) | MMIO 32-bit | 可选 | 寄存器、FIFO、控制状态 |
| Memory (64-bit) | MMIO 64-bit | 通常可预取 | 大容量显存、DMA 缓冲区 |
| I/O (PIO) | PIO 16-bit | 否 | 传统设备、Legacy 兼容 |
BAR 大小探测: 固件通过向 BAR 写入全 1 (0xFFFFFFFF),然后回读,根据低比特位被硬件清零的位数来确定 BAR 大小。例如,若回读值为 0xFFFF0000,则 BAR 大小为 64 KB。
3.3.2 地址空间分配策略
固件按照以下优先级和策略分配地址空间:
- 64-bit MMIO 优先:64-bit BAR 分配在 64-bit 地址窗口(通常 > 4 GB),避免 32-bit 地址空间耗尽。
- 32-bit MMIO 次之:32-bit BAR 分配在 4 GB 以下的地址空间,通常从高地址向下分配。
- PIO 最后:PIO 地址空间有限(64 KB),仅分配给 Legacy 设备或特定功能。
- 对齐要求:BAR 基地址必须对齐到 BAR 大小,例如 64 KB BAR 必须对齐到 64 KB 边界。
3.3.3 典型地址空间布局
3.4 枚举与资源分配实战案例
以下是一个实际系统枚举后的 BDF 与资源分配表,展示典型配置:
| BDF | 设备类型 | BAR0 (MMIO) | BAR2 (MMIO 64-bit) | PIO |
|---|---|---|---|---|
| 00:00.0 | Root Complex | — | — | — |
| 00:01.0 | PCIe Bridge | 0xE000_0000 (16 MB) | — | — |
| 01:00.0 | GPU | 0xE000_0000 (4 MB) | 0x1_0000_0000 (256 MB) | — |
| 01:00.1 | GPU Audio | 0xE040_0000 (4 KB) | — | — |
| 02:00.0 | NVMe SSD | 0xE050_0000 (8 KB) | — | — |
| 03:00.0 | Ethernet | 0xE060_0000 (1 MB) | — | 0x1000 (256 B) |
驱动加载视角: 操作系统驱动通过读取配置空间中的 BAR 寄存器获取已分配的物理地址,然后调用 ioremap() 将 MMIO 地址映射到内核虚拟地址空间,实现寄存器访问。
3.5 常见问题与调试方法
- BAR 冲突:两个设备被分配了重叠的 MMIO 窗口,导致访问异常。通过 lspci -vvv 检查 BAR 地址是否唯一。
- 64-bit BAR 分配失败:固件未启用 Above 4G Decoding,导致 64-bit BAR 被截断。检查 BIOS 设置中的 "Above 4G MMIO" 选项。
- PIO 耗尽:传统设备占用过多 PIO 地址,导致新设备无法分配。优先使用 MMIO 替代 PIO。
- 枚举不完整:Bridge 的 Subordinate Bus Number 设置错误,导致下游设备不可见。使用 lspci -t 查看树形拓扑。
调试命令: 在 Linux 下使用
lspci -vvv 查看 BDF 和 BAR 分配;使用 cat /proc/iomem 查看 MMIO 地址空间;使用 cat /proc/ioports 查看 PIO 地址空间。