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 配置空间结构对比图
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 链表遍历流程
- 读取 Capabilities Pointer:从配置空间偏移 0x34 读取 8 位值,得到第一个 Capability 结构的偏移地址。
- 读取 Cap ID:在偏移地址处读取 8 位 Cap ID,识别能力类型。
- 读取 Next Cap Pointer:在偏移地址 + 1 处读取 8 位 Next Cap Pointer,指向下一个 Capability 结构。
- 处理 Capability 数据:根据 Cap ID 解析对应的能力寄存器。
- 迭代:将 Next Cap Pointer 作为新的偏移地址,重复步骤 2-4,直到 Next Cap Pointer 为 0x00。
注意:Capability 链表中的偏移地址必须为偶数(DWORD 对齐),且不能超出配置空间范围(0xFFF)。驱动在遍历时应做边界检查,防止硬件错误导致死循环。
3.2.3 Capability 链表结构图
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 的大小:
- 保存原始值:读取 BAR 当前值并保存。
- 写入全 1:向 BAR 写入 0xFFFFFFFF。
- 读回结果:读回 BAR 值,低 N 位为 0 表示地址对齐,高位为 1 的部分表示可编程位。
- 计算大小:对读回值取反并加 1,得到 BAR 所需地址空间的大小。
- 恢复原始值:将保存的原始值写回 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() 将系统物理地址映射到内核虚拟地址空间,驱动通过该虚拟地址访问设备寄存器。