2. PCIe 配置空间:配置空间结构(Type0/Type1)、Capability 结构、BAR 空间与地址映射
PCIe 配置空间是软件与硬件交互的核心接口,驱动通过读取/写入配置空间来发现设备、分配资源、使能中断及控制链路状态。本章深入解析 Type0/Type1 配置空间布局、Capability 链表机制、BAR 空间定义及地址映射原理,为后续驱动加载与调试打下坚实基础。
2.1 配置空间访问机制
PCIe 保留了 PCI 的配置空间访问模型,但扩展为 4 KB(PCI 仅为 256 字节)。前 256 字节(Offset 0x00–0xFF)称为 PCI 兼容配置空间,与 PCI 标准完全兼容;扩展部分(Offset 0x100–0xFFF)称为 PCIe 扩展配置空间,用于存放 PCIe 能力结构(Capability Structure)及 Vendor-Specific 扩展。
2.2 Type0 与 Type1 配置空间结构
PCIe 配置空间头部(Header)分为两种类型:Type0 用于 Endpoint(端点设备),Type1 用于 Bridge(桥设备,如 Root Port、Switch Port)。两者前 16 字节结构相同,从 Offset 0x10 开始布局不同。
2.2.1 Type0 配置空间(Endpoint)
| Offset | 寄存器名称 | 宽度 | 说明 |
|---|---|---|---|
| 0x00 | Vendor ID | 16-bit | 厂商编号(如 0x8086 = Intel) |
| 0x02 | Device ID | 16-bit | 设备编号 |
| 0x04 | Command | 16-bit | 控制 I/O、MEM、Bus Master 等使能 |
| 0x06 | Status | 16-bit | 中断状态、Capabilities 列表存在位 |
| 0x08 | Revision ID | 8-bit | 修订版本 |
| 0x09 | Class Code | 24-bit | 设备分类(Base Class / Sub Class / Prog IF) |
| 0x0C | Cache Line Size | 8-bit | 缓存行大小(通常 64 字节) |
| 0x0D | Latency Timer | 8-bit | PCI 兼容,PCIe 中通常忽略 |
| 0x0E | Header Type | 8-bit | 0x00 = Type0,0x01 = Type1 |
| 0x0F | BIST | 8-bit | 内置自测试(可选) |
| 0x10–0x24 | Base Address Registers (BAR0–BAR5) | 32-bit each | 6 个 BAR,用于 MMIO 或 I/O 空间映射 |
| 0x28 | CardBus CIS Pointer | 32-bit | CardBus 兼容(通常为 0) |
| 0x2C | Subsystem Vendor ID | 16-bit | 子系统厂商编号 |
| 0x2E | Subsystem Device ID | 16-bit | 子系统设备编号 |
| 0x30 | Expansion ROM Base Address | 32-bit | 扩展 ROM 基址 |
| 0x34 | Capabilities Pointer | 8-bit | 指向第一个 Capability 结构的偏移 |
| 0x35–0x3B | Reserved | — | 保留 |
| 0x3C | Interrupt Line | 8-bit | 中断向量号(由固件分配) |
| 0x3D | Interrupt Pin | 8-bit | 中断引脚(INTA#–INTD#) |
| 0x3E | Min_Gnt | 8-bit | PCI 兼容,PCIe 中通常忽略 |
| 0x3F | Max_Lat | 8-bit | PCI 兼容,PCIe 中通常忽略 |
2.2.2 Type1 配置空间(Bridge)
| Offset | 寄存器名称 | 宽度 | 说明 |
|---|---|---|---|
| 0x00–0x0F | 同 Type0 | — | 前 16 字节与 Type0 相同 |
| 0x10–0x14 | BAR0–BAR1 | 32-bit each | 仅 2 个 BAR(桥自身使用) |
| 0x18 | Primary Bus Number | 8-bit | 本桥上游总线号 |
| 0x19 | Secondary Bus Number | 8-bit | 本桥下游总线号 |
| 0x1A | Subordinate Bus Number | 8-bit | 下游最大总线号 |
| 0x1B | Secondary Latency Timer | 8-bit | PCI 兼容 |
| 0x1C | I/O Base | 8-bit | 下游 I/O 空间基址高 8 位 |
| 0x1D | I/O Limit | 8-bit | 下游 I/O 空间上限高 8 位 |
| 0x1E | Secondary Status | 16-bit | 下游端口状态 |
| 0x20 | Memory Base | 16-bit | 下游 32-bit MMIO 基址高 16 位 |
| 0x22 | Memory Limit | 16-bit | 下游 32-bit MMIO 上限高 16 位 |
| 0x24 | Prefetchable Memory Base | 16-bit | 下游 Prefetchable MMIO 基址高 16 位 |
| 0x26 | Prefetchable Memory Limit | 16-bit | 下游 Prefetchable MMIO 上限高 16 位 |
| 0x28 | Prefetchable Base Upper 32 Bits | 32-bit | 64-bit Prefetchable 基址高 32 位 |
| 0x2C | Prefetchable Limit Upper 32 Bits | 32-bit | 64-bit Prefetchable 上限高 32 位 |
| 0x30 | I/O Base Upper 16 Bits | 16-bit | I/O 基址高 16 位(用于 32-bit I/O) |
| 0x32 | I/O Limit Upper 16 Bits | 16-bit | I/O 上限高 16 位 |
| 0x34 | Capabilities Pointer | 8-bit | 同 Type0 |
| 0x3C | Interrupt Line / Pin | — | 同 Type0 |
2.3 Capability 结构
PCIe 使用 Capability 链表 来扩展功能,每个 Capability 结构以 Cap ID(1 字节)和 Next Cap Pointer(1 字节)开头,形成单向链表。Capabilities Pointer 寄存器(Offset 0x34)指向第一个 Capability 的偏移。
2.3.1 常见 Capability ID
| Cap ID | 名称 | 说明 |
|---|---|---|
| 0x01 | PM (Power Management) | 电源管理状态控制 |
| 0x05 | MSI (Message Signaled Interrupts) | MSI 中断能力 |
| 0x09 | PCI-X | PCI-X 模式(极少使用) |
| 0x10 | PCIe Capability | PCIe 核心能力结构(必选) |
| 0x11 | MSI-X | MSI-X 中断能力 |
| 0x12 | Advanced Error Reporting (AER) | 高级错误报告 |
| 0x13 | Virtual Channel (VC) | 虚拟通道管理 |
| 0x14 | Device Serial Number | 设备序列号 |
| 0x15 | Power Budgeting | 功率预算 |
| 0x16 | Root Complex Link Declaration | RC 链路声明 |
| 0x17 | Root Complex Internal Link Control | RC 内部链路控制 |
| 0x18 | Root Complex Event Collector | RC 事件收集 |
| 0x19 | Multi-Function | 多功能设备关联 |
| 0x1A | Virtual Channel 2 (VC2) | 第二组虚拟通道 |
| 0x1B | RCRB (Root Complex Register Block) | RC 寄存器块 |
| 0x1C | Vendor-Specific Capability | 厂商自定义能力 |
| 0x1D | ACS (Access Control Services) | 访问控制服务 |
2.3.2 Capability 链表遍历示例
/* 伪代码:遍历 PCIe Capability 链表 */
uint8_t cap_ptr = pci_read_config_byte(dev, 0x34);
while (cap_ptr != 0) {
uint8_t cap_id = pci_read_config_byte(dev, cap_ptr);
uint8_t next = pci_read_config_byte(dev, cap_ptr + 1);
printf("Capability ID 0x%02X at offset 0x%02X\n", cap_id, cap_ptr);
if (cap_id == 0x10) { /* PCIe Capability */
uint16_t cap_reg = pci_read_config_word(dev, cap_ptr + 2);
uint8_t pcie_cap_type = (cap_reg >> 4) & 0x0F;
printf(" PCIe Capability Type: %d\n", pcie_cap_type);
}
cap_ptr = next;
}
2.4 BAR 空间与地址映射
Base Address Register (BAR) 是设备向系统声明所需地址空间的窗口。每个 BAR 可配置为 MMIO(Memory-Mapped I/O) 或 I/O 空间。系统固件(BIOS/UEFI)或操作系统在枚举时写入 BAR 以分配物理地址。
2.4.1 BAR 格式
| Bit 范围 | 含义 | 说明 |
|---|---|---|
| Bit 0 | Space Type | 0 = MMIO,1 = I/O |
| Bit 1–2 | MMIO Type | 00 = 32-bit,10 = 64-bit(仅 MMIO) |
| Bit 3 | Prefetchable | 1 = 可预取(仅 MMIO) |
| Bit 4–31 | Base Address | 对齐后的基地址(低 4 位固定为 0) |
2.4.2 BAR 大小探测方法
驱动通过 写全 1 再读回 的方式探测 BAR 所需空间大小:
/* 探测 BAR0 大小(32-bit MMIO 示例) */
uint32_t bar_orig = pci_read_config_dword(dev, 0x10);
pci_write_config_dword(dev, 0x10, 0xFFFFFFFF);
uint32_t bar_mask = pci_read_config_dword(dev, 0x10);
pci_write_config_dword(dev, 0x10, bar_orig); /* 恢复原值 */
/* 计算大小:取反后 +1,再与 ~0xF 对齐 */
uint32_t size = (~(bar_mask & 0xFFFFFFF0)) + 1;
printf("BAR0 size = 0x%X (%d bytes)\n", size, size);
2.4.3 地址映射流程
- 枚举阶段:系统读取每个设备的 BAR,探测大小和类型。
- 分配地址:系统根据拓扑和地址空间布局,为每个 BAR 分配物理基址。
- 写入 BAR:系统将分配好的基址写入 BAR 寄存器。
- 驱动映射:驱动通过
ioremap()将物理地址映射到内核虚拟地址空间,之后可通过readl()/writel()访问设备寄存器。
void __iomem *bar0_virt = ioremap(pci_resource_start(dev, 0),
pci_resource_len(dev, 0));
if (!bar0_virt) {
dev_err(&dev->dev, "ioremap failed for BAR0\n");
return -ENOMEM;
}
/* 访问设备寄存器 */
uint32_t val = readl(bar0_virt + REG_OFFSET);
writel(0x1, bar0_virt + REG_CTRL);
2.5 配置空间结构总览(SVG)
2.6 配置空间调试实用技巧
- lspci -vvv: 查看完整配置空间及 Capability 列表,包括 BAR 地址、大小、中断信息。
- setpci: 直接读写配置空间寄存器,适合快速验证 BAR 探测或 Capability 遍历。
- /sys/bus/pci/devices/.../config: 通过 sysfs 导出配置空间二进制文件,可用 hexdump 分析。
- 内核调试: 在驱动中打印 BAR 资源:
dev_info(&dev->dev, "BAR0: phys=%pa, size=%#x\n", &pci_resource_start(dev, 0), pci_resource_len(dev, 0));