3. PCIe 枚举与资源分配

PCIe 枚举是系统上电后由 Root Complex (RC) 发起的拓扑发现过程,其核心目标是为每个连接的设备分配唯一的 BDF 编号,并为其所需的 MMIO/PIO 地址空间分配连续的物理地址窗口。本章深入解析枚举流程、BDF 分配机制及地址空间布局。

3.1 系统上电枚举流程

枚举过程遵循深度优先遍历算法,从 Bus 0 开始,逐级扫描每个 Bus 上的 Device,并递归扫描下游 Bridge 所挂接的 Subordinate Bus。具体步骤如下:

  1. 初始化 Root Complex:RC 配置空间中的 Bus Number 寄存器初始化为 0,作为枚举起点。
  2. 扫描 Bus 0:遍历 Bus 0 上所有可能的 Device (0-31),读取每个 Device 的 Vendor ID 寄存器。若返回 0xFFFF,表示该 Device 不存在。
  3. 识别 Type 1 Header:若 Device 的 Header Type 为 0x01 (PCIe-to-PCIe Bridge),则进入递归枚举流程。
  4. 分配 Secondary/Subordinate Bus:为每个新发现的 Bridge 分配一个未使用的 Bus Number 作为 Secondary Bus,并预留一个 Subordinate Bus 范围。
  5. 递归扫描下游:对 Secondary Bus 重复步骤 2-4,直到所有叶子设备被枚举。
  6. 分配 MMIO/PIO 窗口:根据设备请求的 BAR 大小,在系统地址空间中分配连续的物理地址区域。
关键时序: 枚举通常在 BIOS/UEFI 阶段完成,操作系统驱动加载时通过读取配置空间获取已分配的资源信息,不再重新枚举。
枚举深度: PCIe 规范支持最多 256 个 Bus,每个 Bus 最多 32 个 Device,每个 Device 最多 8 个 Function。理论最大设备数为 256 × 32 × 8 = 65536。
PCIe 枚举流程图 PCIe 枚举流程图 开始枚举 (Bus 0) 扫描 Bus N 上 Device 0-31 Vendor ID != 0xFFFF? 是 分配 BDF 编号 Header Type = 0x01? 是 分配 Secondary/Sub Bus 递归扫描 Secondary Bus 递归 否 下一个 Device 枚举完成 分配 MMIO/PIO 资源

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 拓扑示例:

BDF 拓扑结构图 BDF 拓扑结构图 Root Complex (Bus 0) Bridge 0 (BDF 00:00.0) Bus 1 (Secondary Bus) Device 1.0 (01:00.0) Device 1.1 (01:01.0) Function 0 (01:00.0) Function 0 (01:01.0)

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 典型地址空间布局

PCIe 地址空间布局图 PCIe 地址空间布局图 64-bit MMIO 地址空间 (Above 4 GB) 典型范围: 0x1_0000_0000 - 0xFFFF_FFFF_FFFF_FFFF 32-bit MMIO 地址空间 (Below 4 GB) 典型范围: 0x8000_0000 - 0xFFFF_FFFF PIO 地址空间 (I/O Space) 典型范围: 0x0000 - 0xFFFF (64 KB 限制) 说明: 64-bit MMIO 优先分配,32-bit MMIO 从高地址向下分配,PIO 仅用于 Legacy 兼容

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 地址空间。