2. 配置空间基础:PCIe配置空间结构(Type0/Type1)、Capability链表机制、配置空间访问方法(I/O与MMCFG)

2.1 PCIe配置空间概述

PCIe配置空间是设备与系统软件(如BIOS、操作系统内核、驱动)进行交互的核心接口。每个PCIe功能(Function)都拥有独立的配置空间,大小为4KB(0x000 – 0xFFF)。系统通过配置空间完成设备发现、资源分配、中断配置、错误报告等关键操作。

配置空间的前256字节(0x00 – 0xFF)与PCI规范完全兼容,称为PCI兼容配置空间;扩展部分(0x100 – 0xFFF)是PCIe新增的PCIe扩展配置空间,用于存放PCIe能力结构(PCIe Capability Structure)和高级功能。

关键概念:配置空间是“内存映射”或“I/O映射”的寄存器集合,驱动通过读写这些寄存器来获取设备信息、控制设备行为。枚举过程的核心就是遍历配置空间。

2.2 配置空间头部:Type0与Type1

配置空间头部(Configuration Space Header)位于偏移0x00 – 0x3F,共64字节。根据设备类型,头部分为两种:

  • Type0 Header:用于非桥设备(Endpoint、Legacy Endpoint、PCIe到PCI/PCI-X桥等)。
  • Type1 Header:用于PCIe桥设备(Root Port、Switch Upstream/Downstream Port、PCIe到PCI桥等)。

两种头部的区别在于基地址寄存器(BAR)的数量和用途,以及是否包含总线号寄存器。

偏移 寄存器名称 Type0 Type1
0x00 Vendor ID / Device ID 有 有
0x04 Command / Status 有 有
0x08 Revision ID / Class Code 有 有
0x0C Cache Line Size / Latency Timer / Header Type / BIST 有 有
0x10 – 0x24 Base Address Registers (BAR0 – BAR5) 6个BAR 2个BAR
0x28 CardBus CIS Pointer 有 无
0x2C Subsystem Vendor ID / Subsystem ID 有 有
0x30 Expansion ROM Base Address 有 有
0x34 Capabilities Pointer 有 有
0x38 Reserved – –
0x3C Interrupt Line / Interrupt Pin / Min Gnt / Max Lat 有 有
0x18 – 0x1C Primary Bus Number / Secondary Bus Number / Subordinate Bus Number 无 有
0x20 Memory Base / Memory Limit 无 有
0x24 Prefetchable Memory Base / Prefetchable Memory Limit 无 有
驱动开发要点:通过读取 Header Type 寄存器(偏移0x0E)的bit 7,可以判断设备是Type0(bit7=0)还是Type1(bit7=1)。Type1设备是桥设备,驱动需要处理总线号分配和子设备枚举。

2.3 Capability链表机制

PCI/PCIe引入了Capability链表(Capability List)机制,用于扩展配置空间的功能。每个Capability结构包含一个Capability ID(标识功能类型)和一个Next Capability Pointer(指向下一个Capability结构的偏移)。链表从配置空间偏移0x34处的Capabilities Pointer开始。

常见的Capability ID包括:

  • 0x01:Power Management Capability
  • 0x05:MSI Capability(Message Signaled Interrupts)
  • 0x10:PCI Express Capability(PCIe核心能力)
  • 0x11:MSI-X Capability
  • 0x13:Advanced Error Reporting Capability
  • 0x15:Virtual Channel Capability
  • 0x16:Device Serial Number Capability
PCIe Capability链表结构示意图 配置空间头部 (0x00-0x3F) Capabilities Pointer (0x34) Capability 1 (ID=0x01) Capability 2 (ID=0x05) Capability 3 (ID=0x10) Next = 0x00 (链表结束) 每个Capability结构包含:Capability ID (1字节) + Next Pointer (1字节) + 功能相关寄存器 驱动通过遍历链表发现设备支持的所有扩展功能
驱动开发陷阱:遍历Capability链表时,必须检查 Next Pointer 是否为0x00(链表结束),并防止出现死循环(如Next Pointer指向自身或形成环)。建议设置最大遍历深度(如20次)。

2.4 配置空间访问方法

系统软件访问PCIe配置空间有两种标准方法:I/O访问(Legacy PCI)和内存映射访问(MMCFG)。现代系统优先使用MMCFG,但驱动开发中仍需了解I/O访问以兼容旧平台。

2.4.1 I/O访问(Legacy PCI Configuration Mechanism)

通过两个I/O端口寄存器实现:

  • CONFIG_ADDRESS (0xCF8):32位寄存器,用于指定目标设备的Bus/Device/Function/Register。
  • CONFIG_DATA (0xCFC):32位寄存器,用于读写配置数据。

访问流程:

  1. 构造地址值:0x80000000 | (bus << 16) | (device << 11) | (function << 8) | register
  2. 将地址值写入 0xCF8。
  3. 从 0xCFC 读取或写入数据。
/* I/O访问配置空间示例(x86) */
static inline uint32_t pci_config_read_long(uint8_t bus, uint8_t dev, uint8_t func, uint8_t reg) {
    uint32_t address = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | (reg & 0xFC);
    outl(address, 0xCF8);
    return inl(0xCFC);
}

static inline void pci_config_write_long(uint8_t bus, uint8_t dev, uint8_t func, uint8_t reg, uint32_t value) {
    uint32_t address = 0x80000000 | (bus << 16) | (dev << 11) | (func << 8) | (reg & 0xFC);
    outl(address, 0xCF8);
    outl(value, 0xCFC);
}

2.4.2 内存映射访问(MMCFG – Memory Mapped Configuration)

MMCFG将PCIe配置空间映射到系统物理内存地址空间,每个功能占用4KB,每个总线占用256个功能 × 4KB = 1MB。系统通过ACPI的MCFG表获取MMCFG基地址。

访问公式:

address = mmcfg_base + (bus << 20) | (device << 15) | (function << 12) | register

驱动通过 ioremap 将物理地址映射到内核虚拟地址空间,然后直接读写。

/* MMCFG访问配置空间示例(内核驱动) */
static void __iomem *mmcfg_virt_base;

void pcie_mmcfg_init(phys_addr_t mmcfg_phys_base) {
    mmcfg_virt_base = ioremap(mmcfg_phys_base, 256 * 1024 * 1024); /* 256MB覆盖所有总线 */
}

uint32_t pcie_config_read_long(uint8_t bus, uint8_t dev, uint8_t func, uint16_t reg) {
    void __iomem *addr = mmcfg_virt_base + (bus << 20) | (dev << 15) | (func << 12) | (reg & 0xFFC);
    return readl(addr);
}

void pcie_config_write_long(uint8_t bus, uint8_t dev, uint8_t func, uint16_t reg, uint32_t value) {
    void __iomem *addr = mmcfg_virt_base + (bus << 20) | (dev << 15) | (func << 12) | (reg & 0xFFC);
    writel(value, addr);
}
MMCFG地址映射结构 系统物理内存地址空间 MMCFG区域 (基地址: 0xE0000000, 大小: 256MB) Bus 0 (1MB) Bus 1 (1MB) Bus N (1MB) 每个总线包含:256个功能 × 4KB = 1MB 功能地址偏移 = (bus << 20) | (device << 15) | (function << 12) | register

2.5 两种访问方法对比

特性 I/O访问 (0xCF8/0xCFC) MMCFG访问
访问方式 I/O端口指令(in/out) 内存映射读写(load/store)
性能 较低(每次访问需两次I/O操作) 较高(直接内存访问)
地址范围 仅支持前256字节(PCI兼容空间) 支持全部4KB(含PCIe扩展空间)
平台依赖 x86原生支持,其他架构需模拟 所有架构均支持(需MCFG表)
配置空间大小 256字节 4096字节
驱动使用场景 旧平台兼容、早期枚举 现代系统首选、PCIe扩展功能访问
驱动开发最佳实践:在Linux内核中,pci_read_config_* 和 pci_write_config_* 系列函数封装了底层访问细节,驱动开发者应优先使用这些内核API,而非直接操作I/O端口或MMCFG地址。内核会根据系统配置自动选择最优访问方法。

2.6 配置空间访问的字节序与对齐

PCIe配置空间使用小端字节序(Little-Endian)。驱动在读写多字节寄存器时,必须注意字节序转换。x86平台原生小端,无需额外处理;ARM等大端平台需使用 le32_to_cpu / cpu_to_le32 等宏。

对齐要求:

  • 32位寄存器必须4字节对齐(寄存器偏移为0x00、0x04、0x08等)。
  • 16位寄存器必须2字节对齐。
  • 8位寄存器无对齐要求。

违反对齐要求可能导致总线错误或数据错位。

常见错误:使用MMCFG访问时,如果寄存器偏移未按4字节对齐(如读取偏移0x02的16位寄存器),必须使用 readw 而非 readl,否则会读取错误数据或触发异常。