2. PCIe 配置空间:配置空间布局、Capability 结构、BAR 空间与 MMIO 映射

PCIe 配置空间是设备驱动与硬件交互的“控制面板”。驱动通过读写配置空间来发现设备功能、分配资源、使能 DMA 和中断。本章深入解析配置空间的布局、Capability 链表机制、BAR 空间解码规则以及 MMIO 映射的完整流程。

2.1 配置空间布局与访问机制

PCIe 继承了 PCI 的配置空间规范,并将其扩展为 4 KB(PCI 仅为 256 字节)。前 256 字节(Offset 0x00–0xFF)为 PCI 兼容头区,后 3840 字节为 PCIe 扩展配置空间,存放 Capability 结构和 Vendor-Specific 扩展。

关键概念: 配置空间通过 ECAM(Enhanced Configuration Access Mechanism) 映射到内存地址空间。系统软件通过 MMIO 方式直接访问 Bus:Device:Function (BDF) 对应的配置寄存器,无需 I/O 端口。
PCIe 配置空间布局(4 KB) PCIe 配置空间布局(4 KB) 配置空间总大小:4,096 字节(0x000 – 0xFFF) PCI 兼容头区(256 字节) Vendor ID / Device ID / Status / Command / Class Code / Revision ID BAR0–BAR5 / Subsystem ID / Expansion ROM / Interrupt Pin & Line Offset: 0x00 – 0xFF PCIe 扩展配置空间(3,840 字节) Capability 链表(Power Management / MSI / MSI-X / PCIe Cap) Advanced Error Reporting (AER) / Virtual Channel (VC) SR-IOV / Vendor-Specific Extended Capabilities (VSEC) Offset: 0x100 – 0xFFF

2.2 Capability 结构:链表与关键 Cap

PCIe 使用 Capability 链表机制来扩展功能。每个 Capability 结构以 Cap ID 和 Next Cap Pointer 开头,形成单向链表。驱动通过遍历链表发现设备支持的特性。

Capability ID 名称 驱动关注点
0x01 Power Management (PM) 电源状态管理(D0/D1/D2/D3)
0x05 MSI (Message Signaled Interrupts) 传统 MSI 中断使能、向量配置
0x11 MSI-X 高密度中断向量,每个向量独立地址/数据
0x10 PCI Express Capability 链路状态、最大载荷、LTR、OBFF
0x14 Advanced Error Reporting (AER) 高级错误状态与恢复
0x1E Physical Layer 16.0 GT/s (Gen5) Gen5 速率协商与 Equalization
性能优化提示: 对于高吞吐 DMA 场景,务必确认设备支持 MSI-X(Cap ID 0x11)。MSI-X 允许每个队列拥有独立的中断向量,是实现中断合并与负载均衡的基础。
PCIe Capability 链表遍历 Capability 链表遍历流程 PCI 头区 Cap_Ptr = 0x40 Cap: PM Next = 0x50 Cap: MSI Next = 0x70 Cap: PCIe Next = 0xA0 Cap: MSI-X Next = 0x00 (End) 驱动从 Cap_Ptr 开始,依次读取 Cap ID + Next Pointer,直到 Next = 0x00 结束

2.3 BAR 空间与地址解码

Base Address Register (BAR) 是设备向系统声明所需 MMIO 或 I/O 空间大小的机制。每个 BAR 占用配置空间偏移 0x10–0x24 的 6 个 32 位寄存器(64 位 BAR 占用连续两个)。驱动通过 BAR 探测(BAR Sizing) 获取设备所需地址范围。

BAR 探测原理: 向 BAR 写入全 1(0xFFFFFFFF),然后回读。回读值中可写为 0 的位表示地址可编程范围,从而计算出所需空间大小(必须是 2 的幂且对齐)。
// BAR 探测示例(伪代码)
uint32_t bar_orig = pci_read_config_dword(dev, offset);
pci_write_config_dword(dev, offset, 0xFFFFFFFF);
uint32_t bar_sz = pci_read_config_dword(dev, offset);
pci_write_config_dword(dev, offset, bar_orig); // 恢复

// 计算大小:取反后 +1,再与 0xFFFFFFFF 取交
uint32_t size = (~(bar_sz & 0xFFFFFFF0)) + 1;
// 注意:bit 0 表示空间类型(0=MMIO, 1=I/O),bit 2:1 表示位宽(32/64 位)
BAR 位域 含义 典型值
Bit 0 空间类型:0 = MMIO, 1 = I/O 0x0(MMIO)
Bit 2:1 位宽:00 = 32 位, 10 = 64 位 0x2(64 位 MMIO)
Bit 3 Prefetchable(预取允许) 1(允许预取,适合 DMA 缓冲区)
Bit 31:4 基地址(对齐到 16 字节) 由 BIOS/OS 分配

2.4 MMIO 映射:从 BAR 到虚拟地址

系统固件(BIOS/UEFI)或操作系统为每个 BAR 分配物理地址范围。驱动通过 ioremap() 将物理 MMIO 地址映射到内核虚拟地址空间,之后才能通过 readl/writel 等访问寄存器。

MMIO 映射流程:从 BAR 到虚拟地址 MMIO 映射流程:从 BAR 到虚拟地址 步骤 1 读取 BAR 寄存器 步骤 2 获取物理基地址 + 大小 步骤 3 ioremap(phys, size) 步骤 4 通过虚拟地址访问寄存器(readl/writel) ioremap() 建立页表映射,保证 CPU 访问 MMIO 时不会经过 Cache(除非显式配置为 WC)
性能陷阱: 默认 ioremap() 使用 Strongly-Ordered (Device memory) 属性,每次读写都直接访问 PCIe 总线,延迟较高。对于高性能 DMA 描述符环或门铃寄存器,可考虑使用 ioremap_wc()(Write-Combining)来合并写操作,但需确保硬件支持 WC 语义且驱动正确处理读-修改-写。

2.5 配置空间访问的驱动示例

以下代码展示如何在 Linux 内核驱动中读取 BAR0 并完成 MMIO 映射:

#include <linux/pci.h>
#include <linux/io.h>

static int my_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
    int ret;
    resource_size_t bar0_start, bar0_len;
    void __iomem *regs;

    // 1. 使能设备
    ret = pci_enable_device(pdev);
    if (ret)
        return ret;

    // 2. 设置 DMA 掩码(64 位)
    ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
    if (ret)
        goto err_disable;

    // 3. 获取 BAR0 资源
    bar0_start = pci_resource_start(pdev, 0);
    bar0_len   = pci_resource_len(pdev, 0);

    // 4. 请求 MMIO 区域
    ret = pci_request_region(pdev, 0, "my_pcie_bar0");
    if (ret)
        goto err_disable;

    // 5. ioremap
    regs = ioremap(bar0_start, bar0_len);
    if (!regs) {
        ret = -ENOMEM;
        goto err_release;
    }

    // 6. 通过 regs 访问设备寄存器
    writel(0x1, regs + 0x100); // 使能 DMA 引擎

    // 保存 regs 到私有结构...
    return 0;

err_release:
    pci_release_region(pdev, 0);
err_disable:
    pci_disable_device(pdev);
    return ret;
}

2.6 本章小结

  • 配置空间:4 KB 布局,前 256 字节 PCI 兼容,后 3,840 字节 PCIe 扩展。
  • Capability 链表:驱动通过遍历 Cap ID 发现 MSI-X、AER 等关键特性。
  • BAR 空间:通过 BAR Sizing 获取设备所需 MMIO 大小,注意 64 位 BAR 的处理。
  • MMIO 映射:ioremap() 将物理地址转为虚拟地址,注意内存属性对性能的影响。
  • 驱动实践:pci_enable_device → pci_request_region → ioremap 是标准流程。