3. PCIe 配置空间:配置空间结构(Type0/Type1)、Capability链表、BAR空间与地址映射

PCIe 配置空间是软件与硬件交互的核心接口,也是驱动开发者必须掌握的基础。本章深入分析 Type0/Type1 配置空间的布局差异、Capability 链表的遍历机制、BAR 空间的解码规则以及地址映射的完整流程。

3.1 配置空间结构:Type0 与 Type1

PCIe 配置空间大小为 4KB(0x000 – 0xFFF),前 256 字节(0x00 – 0xFF)为 PCI 兼容配置空间,后 3840 字节为 PCIe 扩展配置空间。根据设备类型,配置空间分为两种头部:Type0(Endpoint)和 Type1(Bridge/Root Complex)。

关键区分:Type0 头部用于非桥接设备(EP),Type1 头部用于桥接设备(RC、Switch)。驱动通过读取配置空间偏移 0x0E 处的 Header Type 寄存器(bit 6 指示多功能设备,bit 0-6 指示头部类型)来区分。

3.1.1 Type0 配置空间头部布局

偏移 寄存器名称 宽度 描述
0x00 Vendor ID / Device ID 32-bit 厂商与设备标识
0x04 Command / Status 32-bit 控制与状态寄存器
0x08 Revision ID / Class Code 32-bit 版本与分类代码
0x0C Cache Line / Latency Timer / Header Type / BIST 32-bit 缓存行、延迟、头部类型、自检
0x10 – 0x24 Base Address Registers (BAR0 – BAR5) 6 × 32-bit 6 个基地址寄存器,用于内存/IO 空间映射
0x28 CardBus CIS Pointer 32-bit CardBus 信息结构指针
0x2C Subsystem Vendor ID / Subsystem ID 32-bit 子系统厂商与设备标识
0x30 Expansion ROM Base Address 32-bit 扩展 ROM 基地址
0x34 Capabilities Pointer 8-bit Capability 链表头指针
0x38 – 0x3C Reserved / Interrupt Line / Interrupt Pin / Min Gnt / Max Lat 32-bit 中断配置与延迟参数

3.1.2 Type1 配置空间头部布局

偏移 寄存器名称 宽度 描述
0x00 – 0x0F 与 Type0 相同 — Vendor ID、Command、Status、Class Code、Header Type 等
0x10 – 0x14 BAR0 – BAR1 2 × 32-bit 桥接设备仅支持 2 个 BAR
0x18 Primary Bus Number 8-bit 主总线号
0x19 Secondary Bus Number 8-bit 次级总线号
0x1A Subordinate Bus Number 8-bit 从属总线号
0x1C I/O Base / I/O Limit 16-bit IO 地址范围
0x20 Memory Base / Memory Limit 32-bit 32 位内存地址范围
0x24 Prefetchable Memory Base / Limit 64-bit 预取内存地址范围
0x34 Capabilities Pointer 8-bit Capability 链表头指针
0x3C – 0x3F Interrupt Line / Interrupt Pin / Bridge Control 32-bit 中断与桥控制
Type0 vs Type1 核心差异:
  • BAR 数量:Type0 支持 6 个 BAR(BAR0-BAR5),Type1 仅支持 2 个 BAR(BAR0-BAR1)。
  • 桥接寄存器:Type1 包含 Primary/Secondary/Subordinate Bus Number、Memory Base/Limit、I/O Base/Limit 等桥接专用寄存器。
  • Expansion ROM:Type0 有 Expansion ROM Base Address,Type1 无此字段。

3.1.3 Type0 与 Type1 配置空间结构对比图

Type0 与 Type1 配置空间头部结构对比 Type0 (Endpoint) vs Type1 (Bridge) 配置空间头部结构 Type0 头部 (Endpoint) 0x00: Vendor ID / Device ID 0x04: Command / Status 0x08: Revision ID / Class Code 0x0C: Header Type / BIST 0x10 – 0x24: BAR0 – BAR5 (6个) 内存/IO 空间映射基地址 每个 BAR 32-bit,支持 64-bit 组合 0x2C: Subsystem Vendor ID / Subsystem ID 0x30: Expansion ROM Base Address 0x34: Capabilities Pointer → 链表头 0x3C: Interrupt Line / Interrupt Pin Type1 头部 (Bridge/RC) 0x00: Vendor ID / Device ID 0x04: Command / Status 0x08: Revision ID / Class Code 0x0C: Header Type / BIST 0x10 – 0x14: BAR0 – BAR1 (仅2个) 桥接设备仅需少量 BAR 0x18 – 0x1A: 总线号寄存器 Primary / Secondary / Subordinate 0x1C – 0x24: 内存/IO 地址范围寄存器 0x34: Capabilities Pointer → 链表头 0x3C: Interrupt Line / Bridge Control

3.2 Capability 链表

PCIe 扩展功能通过 Capability 链表机制组织。每个 Capability 结构包含 Cap ID(1 字节)、Next Cap Pointer(1 字节)和 Capability 特定数据。链表从配置空间偏移 0x34 处的 Capabilities Pointer 开始,Next Cap Pointer 为 0x00 表示链表结束。

3.2.1 常见 Capability ID

Cap ID 名称 描述
0x01 PM (Power Management) 电源管理能力
0x05 MSI (Message Signaled Interrupts) 消息信号中断
0x10 PCIe Capability PCIe 设备核心能力(设备类型、链路状态等)
0x11 MSI-X MSI-X 中断扩展
0x12 Advanced Error Reporting (AER) 高级错误报告
0x13 Virtual Channel (VC) 虚拟通道管理
0x14 Device Serial Number 设备序列号
0x16 Power Budgeting 功率预算

3.2.2 Capability 链表遍历流程

  1. 读取 Capabilities Pointer:从配置空间偏移 0x34 读取 8 位值,得到第一个 Capability 结构的偏移地址。
  2. 读取 Cap ID:在偏移地址处读取 8 位 Cap ID,识别能力类型。
  3. 读取 Next Cap Pointer:在偏移地址 + 1 处读取 8 位 Next Cap Pointer,指向下一个 Capability 结构。
  4. 处理 Capability 数据:根据 Cap ID 解析对应的能力寄存器。
  5. 迭代:将 Next Cap Pointer 作为新的偏移地址,重复步骤 2-4,直到 Next Cap Pointer 为 0x00。
注意:Capability 链表中的偏移地址必须为偶数(DWORD 对齐),且不能超出配置空间范围(0xFFF)。驱动在遍历时应做边界检查,防止硬件错误导致死循环。

3.2.3 Capability 链表结构图

PCIe Capability 链表结构 PCIe Capability 链表遍历结构 配置空间头部 0x34: Cap Ptr = 0x40 (指向第一个 Cap) Cap 1: PM (0x01) Next Ptr = 0x80 电源管理能力数据 Cap 2: MSI (0x05) Next Ptr = 0xC0 MSI 中断能力数据 Cap 3: PCIe (0x10) Next Ptr = 0x00 PCIe 核心能力数据 链表结束

3.3 BAR 空间与地址映射

Base Address Register(BAR)是 PCIe 设备与系统内存/IO 空间之间的桥梁。每个 BAR 定义了一段连续的地址空间,系统软件(BIOS/UEFI 或 Linux PCI 子系统)负责为 BAR 分配物理地址,并将设备内存映射到 CPU 的地址空间。

3.3.1 BAR 寄存器格式

Bit 位置 字段 描述
Bit 0 Space Type 0 = 内存空间,1 = IO 空间
Bit 1 Memory Type (仅内存空间) 00 = 32-bit,10 = 64-bit
Bit 2 Prefetchable (仅内存空间) 0 = 不可预取,1 = 可预取
Bit 3 – 31 Base Address (对齐后) 实际基地址,低 N 位为 0 表示地址对齐

3.3.2 BAR 大小探测机制

Linux PCI 子系统通过“写全 1 再读回”的方式探测 BAR 的大小:

  1. 保存原始值:读取 BAR 当前值并保存。
  2. 写入全 1:向 BAR 写入 0xFFFFFFFF。
  3. 读回结果:读回 BAR 值,低 N 位为 0 表示地址对齐,高位为 1 的部分表示可编程位。
  4. 计算大小:对读回值取反并加 1,得到 BAR 所需地址空间的大小。
  5. 恢复原始值:将保存的原始值写回 BAR。
示例:假设 BAR0 读回值为 0xFFF00000,取反得 0x000FFFFF,加 1 得 0x00100000 = 1MB。该 BAR 需要 1MB 的地址空间,且必须 1MB 对齐。

3.3.3 地址映射流程

从 BAR 配置到 CPU 可访问的虚拟地址,经历以下映射链:

  • 设备物理地址:BAR 中存储的基地址(Bus Address),由 PCI 域内使用。
  • 系统物理地址:通过 PCIe 桥接/RC 的地址转换,将 Bus Address 映射到系统物理地址空间。
  • CPU 虚拟地址:通过 ioremap() / ioremap_wc() 将系统物理地址映射到内核虚拟地址空间,驱动通过该虚拟地址访问设备寄存器。

3.3.4 BAR 空间与地址映射完整流程

BAR 空间探测与地址映射完整流程 阶段 1: 硬件 BAR 设备固件/硬件预设 BAR 初始值(未分配) Space Type / Size 编码 阶段 2: 大小探测 写 0xFFFFFFFF 读回 计算地址空间大小 确定对齐要求 阶段 3: 地址分配 BIOS/Linux PCI 分配 Bus Address 写入 BAR 寄存器 阶段 4: 桥接映射 PCIe RC/Bridge Bus Address → 系统物理地址 地址窗口/重映射 阶段 5: ioremap ioremap(phys_addr, size) 系统物理地址 → 内核虚拟地址 页表建立 / Cache 策略 阶段