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 扩展。

访问方式:传统 I/O 端口(CF8h/CFCh)只能访问前 256 字节;MMIO(Memory-Mapped I/O)方式可访问完整 4 KB 配置空间,是 PCIe 原生推荐方式。

2.2 Type0 与 Type1 配置空间结构

PCIe 配置空间头部(Header)分为两种类型:Type0 用于 Endpoint(端点设备),Type1 用于 Bridge(桥设备,如 Root Port、Switch Port)。两者前 16 字节结构相同,从 Offset 0x10 开始布局不同。

2.2.1 Type0 配置空间(Endpoint)

Offset寄存器名称宽度说明
0x00Vendor ID16-bit厂商编号(如 0x8086 = Intel)
0x02Device ID16-bit设备编号
0x04Command16-bit控制 I/O、MEM、Bus Master 等使能
0x06Status16-bit中断状态、Capabilities 列表存在位
0x08Revision ID8-bit修订版本
0x09Class Code24-bit设备分类(Base Class / Sub Class / Prog IF)
0x0CCache Line Size8-bit缓存行大小(通常 64 字节)
0x0DLatency Timer8-bitPCI 兼容,PCIe 中通常忽略
0x0EHeader Type8-bit0x00 = Type0,0x01 = Type1
0x0FBIST8-bit内置自测试(可选)
0x10–0x24Base Address Registers (BAR0–BAR5)32-bit each6 个 BAR,用于 MMIO 或 I/O 空间映射
0x28CardBus CIS Pointer32-bitCardBus 兼容(通常为 0)
0x2CSubsystem Vendor ID16-bit子系统厂商编号
0x2ESubsystem Device ID16-bit子系统设备编号
0x30Expansion ROM Base Address32-bit扩展 ROM 基址
0x34Capabilities Pointer8-bit指向第一个 Capability 结构的偏移
0x35–0x3BReserved—保留
0x3CInterrupt Line8-bit中断向量号(由固件分配)
0x3DInterrupt Pin8-bit中断引脚(INTA#–INTD#)
0x3EMin_Gnt8-bitPCI 兼容,PCIe 中通常忽略
0x3FMax_Lat8-bitPCI 兼容,PCIe 中通常忽略

2.2.2 Type1 配置空间(Bridge)

Offset寄存器名称宽度说明
0x00–0x0F同 Type0—前 16 字节与 Type0 相同
0x10–0x14BAR0–BAR132-bit each仅 2 个 BAR(桥自身使用)
0x18Primary Bus Number8-bit本桥上游总线号
0x19Secondary Bus Number8-bit本桥下游总线号
0x1ASubordinate Bus Number8-bit下游最大总线号
0x1BSecondary Latency Timer8-bitPCI 兼容
0x1CI/O Base8-bit下游 I/O 空间基址高 8 位
0x1DI/O Limit8-bit下游 I/O 空间上限高 8 位
0x1ESecondary Status16-bit下游端口状态
0x20Memory Base16-bit下游 32-bit MMIO 基址高 16 位
0x22Memory Limit16-bit下游 32-bit MMIO 上限高 16 位
0x24Prefetchable Memory Base16-bit下游 Prefetchable MMIO 基址高 16 位
0x26Prefetchable Memory Limit16-bit下游 Prefetchable MMIO 上限高 16 位
0x28Prefetchable Base Upper 32 Bits32-bit64-bit Prefetchable 基址高 32 位
0x2CPrefetchable Limit Upper 32 Bits32-bit64-bit Prefetchable 上限高 32 位
0x30I/O Base Upper 16 Bits16-bitI/O 基址高 16 位(用于 32-bit I/O)
0x32I/O Limit Upper 16 Bits16-bitI/O 上限高 16 位
0x34Capabilities Pointer8-bit同 Type0
0x3CInterrupt Line / Pin—同 Type0
关键区别:Type0 有 6 个 BAR,Type1 只有 2 个 BAR;Type1 包含 Bus Number 寄存器及桥接地址窗口(I/O Base/Limit、Memory Base/Limit),用于转发下游事务。

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名称说明
0x01PM (Power Management)电源管理状态控制
0x05MSI (Message Signaled Interrupts)MSI 中断能力
0x09PCI-XPCI-X 模式(极少使用)
0x10PCIe CapabilityPCIe 核心能力结构(必选)
0x11MSI-XMSI-X 中断能力
0x12Advanced Error Reporting (AER)高级错误报告
0x13Virtual Channel (VC)虚拟通道管理
0x14Device Serial Number设备序列号
0x15Power Budgeting功率预算
0x16Root Complex Link DeclarationRC 链路声明
0x17Root Complex Internal Link ControlRC 内部链路控制
0x18Root Complex Event CollectorRC 事件收集
0x19Multi-Function多功能设备关联
0x1AVirtual Channel 2 (VC2)第二组虚拟通道
0x1BRCRB (Root Complex Register Block)RC 寄存器块
0x1CVendor-Specific Capability厂商自定义能力
0x1DACS (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;
}
注意:Capability 链表可能包含循环或无效指针,驱动遍历时应做深度限制(通常不超过 64 个节点),防止死循环。

2.4 BAR 空间与地址映射

Base Address Register (BAR) 是设备向系统声明所需地址空间的窗口。每个 BAR 可配置为 MMIO(Memory-Mapped I/O) 或 I/O 空间。系统固件(BIOS/UEFI)或操作系统在枚举时写入 BAR 以分配物理地址。

2.4.1 BAR 格式

Bit 范围含义说明
Bit 0Space Type0 = MMIO,1 = I/O
Bit 1–2MMIO Type00 = 32-bit,10 = 64-bit(仅 MMIO)
Bit 3Prefetchable1 = 可预取(仅 MMIO)
Bit 4–31Base 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 地址映射流程

  1. 枚举阶段:系统读取每个设备的 BAR,探测大小和类型。
  2. 分配地址:系统根据拓扑和地址空间布局,为每个 BAR 分配物理基址。
  3. 写入 BAR:系统将分配好的基址写入 BAR 寄存器。
  4. 驱动映射:驱动通过 ioremap() 将物理地址映射到内核虚拟地址空间,之后可通过 readl()/writel() 访问设备寄存器。
内核 API 示例:
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)

PCIe 配置空间结构总览(4 KB) PCI 兼容配置空间 Offset 0x000 – 0x0FF Header (64B) Vendor/Device ID Command/Status Class Code / Rev ID Header Type BAR0–BAR5 (Type0) 或 Bus# + Windows (Type1) Capabilities Pointer Interrupt Line/Pin Capability 链表 PM Cap (ID=0x01) MSI Cap (ID=0x05) PCIe Cap (ID=0x10) MSI-X Cap (ID=0x11) AER Cap (ID=0x12) PCIe 扩展配置空间 Offset 0x100 – 0xFFF PCIe 扩展 Cap Advanced Error Reporting Virtual Channel ACS / Vendor-Specific Vendor-Specific 厂商自定义寄存器 调试/诊断接口 BAR 空间与地址映射 BAR 探测 写全 1 → 读回 → 计算大小 地址分配 系统固件 / OS 分配物理基址 ioremap() 物理地址 → 内核虚拟地址 寄存器访问 readl() / writel() 操作 BAR 类型判断 Bit 0: 0=MMIO, 1=I/O Bit 1-2: 00=32-bit, 10=64-bit Bit 3: Prefetchable

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));
实战建议:在调试新设备时,先通过 lspci -vvv 确认 Capability 链表是否完整、BAR 是否被正确分配。若 BAR 显示为 0 或全 F,说明设备未被正确枚举或固件未分配资源,需检查 PCIe 链路状态及复位时序。