2. PCIe 配置空间:配置空间的布局(Type0/Type1)、Capability 结构、扩展配置空间(ECAM)机制
PCIe 配置空间是软件与硬件之间最核心的“契约”接口。驱动程序通过读取和写入配置空间来发现设备、分配资源、使能中断以及控制链路状态。本章深入解析配置空间的布局、两种 Header 类型(Type0/Type1)、Capability 链表结构以及 ECAM 访问机制。
2.1 配置空间布局概述
PCIe 兼容传统 PCI 的 256 字节配置空间,并在此基础上扩展了 4 KB 的增强配置空间。前 64 字节为预定义的 Header 区域,根据设备类型分为 Type0(Endpoint)和 Type1(Bridge/Switch)。
关键概念: 配置空间的前 16 个 DW(双字)是标准 Header,其中第 0-1 DW 对所有设备通用,第 2-15 DW 根据 Type0/Type1 有不同的字段定义。
2.2 Type0 与 Type1 配置空间 Header
配置空间的前 64 字节(Offset 0x00 - 0x3F)为 Header 区域。根据设备角色,Header 分为两种类型:
| 偏移 (Offset) | 字段名称 | Type0 (Endpoint) | Type1 (Bridge/Switch) |
|---|---|---|---|
| 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 (BARs) | 6 个 BAR (0-5) | 2 个 BAR (0-1) |
| 0x28 | CardBus CIS Pointer | 有 | 保留 |
| 0x2C | Subsystem Vendor ID / Subsystem ID | 有 | 有 |
| 0x30 | Expansion ROM Base Address | 有 | 有 |
| 0x34 | Capabilities Pointer | 有 | 有 |
| 0x38 | 保留 | — | — |
| 0x3C | Interrupt Line / Interrupt Pin / Min Gnt / Max Lat | 有 | 有 |
| 0x18 | Primary Bus Number / Secondary Bus Number / Subordinate Bus Number / Secondary Latency Timer | — | Type1 特有 |
| 0x1C | I/O Base / I/O Limit / Secondary Status | — | Type1 特有 |
| 0x20 | Memory Base / Memory Limit | — | Type1 特有 |
| 0x24 | Prefetchable Memory Base / Prefetchable Memory Limit | — | Type1 特有 |
| 0x28 | Prefetchable Base Upper 32 Bits / Prefetchable Limit Upper 32 Bits | — | Type1 特有 |
| 0x30 | I/O Base Upper 16 Bits / I/O Limit Upper 16 Bits | — | Type1 特有 |
驱动开发提示: 通过读取
Header Type 寄存器(Offset 0x0E)可以判断设备类型:Bit 7 为 0 表示 Type0,为 1 表示 Type1。驱动程序在枚举总线时,必须根据 Header Type 解析不同的字段布局。
2.3 Capability 结构
PCIe 使用 Capability 链表机制来扩展配置空间的功能。每个 Capability 结构以一个 8 位的 Capability ID 开始,紧接着是一个 8 位的 Next Pointer,指向下一个 Capability 结构的偏移地址。链表以 Next Pointer 为 0x00 结束。
- Capability ID: 标识该 Capability 的类型(如 0x01 为 PM,0x05 为 MSI,0x10 为 PCIe Capability)。
- Next Pointer: 指向下一个 Capability 结构在配置空间中的字节偏移。
- Capability 特定数据: 每个 Capability 结构包含与该功能相关的寄存器。
| Capability ID | 名称 | 说明 |
|---|---|---|
| 0x01 | Power Management (PM) | 电源状态管理(D0, D1, D2, D3hot, D3cold) |
| 0x05 | MSI (Message Signaled Interrupts) | 传统 MSI 中断支持 |
| 0x10 | PCI Express Capability | PCIe 设备核心 Capability,包含链路状态、设备能力等 |
| 0x11 | MSI-X | 增强型 MSI 中断,支持更多向量 |
| 0x12 | Advanced Error Reporting (AER) Capability | 高级错误报告(扩展配置空间) |
注意: Capability 链表从配置空间 Offset 0x34 的 Capabilities Pointer 寄存器开始读取。驱动程序必须遍历链表来发现设备支持的所有 Capability,不能假设 Capability 的排列顺序。
2.4 扩展配置空间(ECAM)机制
传统 PCI 配置空间只有 256 字节,无法满足 PCIe 丰富的功能需求。PCIe 引入了 Enhanced Configuration Access Mechanism (ECAM),将配置空间扩展到 4 KB(4096 字节)。
ECAM 将 PCIe 配置空间映射到系统内存地址空间。每个 PCIe 功能(Function)占用 4 KB 的配置空间,通过内存映射方式进行访问。ECAM 地址映射公式如下:
// ECAM 地址计算公式
// 每个 Bus 最多 32 个 Device,每个 Device 最多 8 个 Function
// 每个 Function 占用 4 KB 配置空间
// 总大小 = 256 Buses × 32 Devices × 8 Functions × 4 KB = 256 MB
// 地址偏移计算:
offset = (bus_number << 20) | (device_number << 15) | (function_number << 12) | register_offset;
// 示例:读取 Bus 1, Device 2, Function 0 的 Vendor ID 寄存器
// Vendor ID 位于配置空间 Offset 0x00
uint32_t ecam_base = 0xE0000000; // MCFG 表指定的基地址
uint32_t addr = ecam_base + (1 << 20) | (2 << 15) | (0 << 12) | 0x00;
uint16_t vendor_id = *(volatile uint16_t *)addr;
ECAM 优势: 通过内存映射访问配置空间,避免了传统 PCI 的 I/O 端口访问(CF8/CFC)方式,大大提高了访问效率。操作系统在启动时通过 ACPI 的 MCFG 表获取 ECAM 基地址。
2.5 配置空间访问示例代码
以下代码演示了如何在 Linux 内核驱动中通过 ECAM 方式读取 PCIe 配置空间:
#include <linux/pci.h>
#include <linux/io.h>
/* 假设已经通过 MCFG 或 pci_mmap_cfg_space 获取到 ECAM 基地址 */
static void __iomem *ecam_base;
/* 读取配置空间 32 位寄存器 */
static u32 pcie_config_read32(struct pci_dev *dev, int where)
{
u32 bus = dev->bus->number;
u32 slot = PCI_SLOT(dev->devfn);
u32 func = PCI_FUNC(dev->devfn);
u32 offset = (bus << 20) | (slot << 15) | (func << 12) | where;
return readl(ecam_base + offset);
}
/* 写入配置空间 32 位寄存器 */
static void pcie_config_write32(struct pci_dev *dev, int where, u32 val)
{
u32 bus = dev->bus->number;
u32 slot = PCI_SLOT(dev->devfn);
u32 func = PCI_FUNC(dev->devfn);
u32 offset = (bus << 20) | (slot << 15) | (func << 12) | where;
writel(val, ecam_base + offset);
}
/* 遍历 Capability 链表示例 */
static void dump_pcie_capabilities(struct pci_dev *dev)
{
u8 cap_ptr;
u8 cap_id;
u8 next_ptr;
/* 从 Capabilities Pointer 寄存器读取第一个 Cap 偏移 */
pci_read_config_byte(dev, PCI_CAPABILITY_LIST, &cap_ptr);
while (cap_ptr != 0) {
pci_read_config_byte(dev, cap_ptr + PCI_CAP_ID, &cap_id);
pci_read_config_byte(dev, cap_ptr + PCI_CAP_NEXT, &next_ptr);
pr_info("Capability: ID=0x%02x, Offset=0x%02x\n", cap_id, cap_ptr);
cap_ptr = next_ptr;
}
}
最佳实践: 在 Linux 内核中,推荐使用
pci_read_config_* / pci_write_config_* 系列函数访问配置空间,这些函数会自动处理 ECAM 或传统 I/O 端口的差异。直接操作 ECAM 内存映射通常只在早期启动阶段或特殊场景下使用。
2.6 本章小结
- PCIe 配置空间分为传统 256 字节和扩展 3840 字节,总共 4 KB。
- Type0 Header 用于 Endpoint 设备,包含 6 个 BAR;Type1 Header 用于 Bridge/Switch,包含 2 个 BAR 和总线编号字段。
- Capability 结构通过链表组织,每个 Cap 包含 Cap ID 和 Next Pointer,支持 PM、MSI、MSI-X、PCIe Cap 等功能。
- ECAM 机制将配置空间映射到系统内存,通过内存访问指令直接读写,地址计算公式为:
Bus << 20 | Device << 15 | Function << 12 | Register。 - 驱动程序应优先使用内核提供的配置空间访问 API,确保跨平台兼容性。