2. PCIe 配置空间:配置空间布局、Capability 结构与扩展配置空间

PCIe 配置空间是软件与硬件交互的核心接口。Linux 内核通过访问配置空间来枚举设备、分配资源、配置中断以及初始化 DMA。本章节将深入解析 PCIe 配置空间的传统布局、Capability 链表机制以及 PCIe 独有的扩展配置空间。

2.1 配置空间布局(Legacy & PCIe)

PCI/PCIe 配置空间分为两个主要区域:标准配置空间(256 字节)和扩展配置空间(4096 字节)。标准配置空间兼容 PCI 规范,而扩展配置空间是 PCIe 引入的新特性。

关键概念: 标准配置空间的前 64 字节为“配置头”,包含 Vendor ID、Device ID、Class Code 等关键标识。剩余 192 字节为 Capability 结构预留。

下表展示了标准配置空间(0x00 - 0xFF)的布局:

偏移地址 寄存器名称 宽度 描述
0x00 Vendor ID 16-bit 厂商识别码(如 0x8086 为 Intel)
0x02 Device ID 16-bit 设备识别码
0x04 Command 16-bit 控制设备响应(I/O、Memory、Bus Master 使能)
0x06 Status 16-bit 设备状态(中断状态、Capabilities 列表)
0x08 Revision ID 8-bit 修订版本号
0x09 Class Code 24-bit 设备分类(基类、子类、编程接口)
0x0C Cache Line Size 8-bit 缓存行大小(系统相关)
0x0D Latency Timer 8-bit PCI 总线延迟定时器
0x0E Header Type 8-bit 配置头类型(0x00 为 Endpoint,0x01 为 Bridge)
0x0F BIST 8-bit 内建自测试
0x10 - 0x24 Base Address Registers (BAR0 - BAR5) 32-bit each 内存/I/O 地址映射(详见第 3 章)
0x2C Subsystem Vendor ID 16-bit 子系统厂商 ID
0x2E Subsystem ID 16-bit 子系统设备 ID
0x30 Expansion ROM Base Address 32-bit 扩展 ROM 基址
0x34 Capabilities Pointer 8-bit 指向第一个 Capability 结构的偏移
0x3C Interrupt Line 8-bit 中断向量号(由固件/OS 分配)
0x3D Interrupt Pin 8-bit 中断引脚(INTA# - INTD#)
0x3E - 0xFF Capability 结构区域 可变 存放 Power Management、MSI、PCIe 等 Capability
驱动开发提示: 在 Linux 驱动中,使用 pci_read_config_byte/word/dword() 系列函数读取配置空间。例如,读取 Vendor ID:pci_read_config_word(dev, PCI_VENDOR_ID, &vendor);

2.2 Capability 结构:链表机制

PCI 2.2 引入了 Capability 链表机制,用于扩展配置空间的功能。每个 Capability 结构以 Capability ID 和 Next Pointer 开头,形成一个单向链表。PCIe 设备必须实现 PCI Express Capability 结构。

Capability 链表遍历流程:

  1. 读取配置空间偏移 0x34(Capabilities Pointer),获取第一个 Capability 的偏移地址。
  2. 读取该偏移处的第一个字节(Capability ID),判断功能类型。
  3. 读取第二个字节(Next Pointer),获取下一个 Capability 的偏移地址。
  4. 如果 Next Pointer 为 0x00,表示链表结束。

常见的 Capability ID 包括:

Capability ID 名称 说明
0x01 PM (Power Management) 电源管理状态控制
0x05 MSI (Message Signaled Interrupts) 传统 MSI 中断
0x10 PCI Express PCIe 核心功能(链路状态、设备能力)
0x11 MSI-X 增强型 MSI 中断(支持更多向量)
0x12 ACS (Access Control Services) 访问控制服务(用于虚拟化)
注意事项: 并非所有 Capability 结构都存在于每个设备中。驱动在遍历链表时,必须检查 Capability ID 的有效性,并确保 Next Pointer 不指向无效地址(如超出 0xFF)。

下图展示了 Capability 链表的典型结构:

PCIe Capability 链表结构 配置头 (0x00-0x3F) Capabilities Pointer @0x34 Capability 1: PM Next Pointer -> 0xE0 Capability 2: MSI Next Pointer -> 0x100 Capability 3: PCIe Next Pointer -> 0x00 (结束) 链表遍历规则 1. 从 0x34 读取首指针 2. 每个 Cap 前 2 字节为 ID + Next

2.3 扩展配置空间(Extended Configuration Space)

PCIe 将配置空间从 256 字节扩展到 4096 字节(0x000 - 0xFFF)。扩展部分(0x100 - 0xFFF)称为 Extended Configuration Space,用于存放 PCIe 特有的 Extended Capability 结构。

Extended Capability 的链表机制与标准 Capability 类似,但使用 4 字节的 Capability Header:

  • Capability ID(16-bit):扩展功能标识。
  • Capability Version(4-bit):版本号。
  • Next Capability Offset(12-bit):指向下一个 Extended Capability 的偏移(相对于配置空间基址)。

常见的 Extended Capability ID 包括:

Capability ID 名称 说明
0x0001 Advanced Error Reporting (AER) 高级错误报告(Uncorrectable/Correctable 错误)
0x0002 Virtual Channel (VC) 虚拟通道配置
0x0003 Device Serial Number 64-bit 唯一设备序列号
0x0004 Power Budgeting 电源预算管理
0x0005 Root Complex Link Declaration Root Complex 链路声明
0x000B Latency Tolerance Reporting (LTR) 延迟容忍报告(用于电源管理)
0x0010 Physical Layer 16.0 GT/s (Gen4) Gen4 物理层扩展
驱动开发提示: 在 Linux 中,访问扩展配置空间需要使用 pci_read_config_dword() 配合偏移地址(如 0x100、0x200)。内核提供了 pci_find_ext_capability() 函数来搜索指定的 Extended Capability。

下图展示了标准配置空间与扩展配置空间的整体布局:

PCIe 配置空间完整布局(0x000 - 0xFFF) 标准配置空间 0x000 - 0x0FF (256 字节) 配置头 (0x00 - 0x3F) Capability 结构区域 (0x40 - 0xFF) 包含 PM、MSI、PCIe 等 Capability 扩展配置空间 0x100 - 0xFFF (3840 字节) Extended Capability 1 (如 AER) Extended Capability 2 (如 VC) Extended Capability N ... 直到 0xFFF 访问方式:标准配置空间通过 I/O 或 MMIO 访问;扩展配置空间仅通过 MMIO(ECAM)访问

2.4 配置空间访问机制:ECAM

PCIe 使用 Enhanced Configuration Access Mechanism (ECAM) 访问扩展配置空间。ECAM 将配置空间映射到系统内存地址空间,每个 PCIe 功能占用 4KB 的配置空间。

ECAM 地址映射公式:

配置空间基址 = ECAM_BASE + (Bus_Number << 20) | (Device_Number << 15) | (Function_Number << 12) | Register_Offset

其中:

  • ECAM_BASE:由 BIOS/固件分配的 MMIO 基址(通常位于 0xC0000000 以上)。
  • Bus_Number:8-bit,支持最多 256 条总线。
  • Device_Number:5-bit,每条总线最多 32 个设备。
  • Function_Number:3-bit,每个设备最多 8 个功能。
  • Register_Offset:12-bit,覆盖 4KB 配置空间。
Linux 内核视角: 内核通过 pci_mmap_config() 或直接内存映射访问 ECAM 区域。驱动开发者通常不需要直接操作 ECAM,而是使用 pci_read_config_* 系列函数,这些函数底层会自动选择正确的访问方式。

2.5 实战:使用 lspci 解析配置空间

在 Linux 系统中,lspci 工具是查看配置空间最直接的方式。以下命令可以解析 Capability 结构:

# 查看设备 0000:01:00.0 的详细 Capability 信息
lspci -s 01:00.0 -vvv

# 以十六进制形式 dump 配置空间
lspci -s 01:00.0 -xxx

# 查看扩展配置空间(需要 root 权限)
lspci -s 01:00.0 -xxxx

输出示例(部分):

Capabilities: [40] Power Management version 3
    Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0-,D1-,D2-,D3hot-,D3cold-)
    Status: D0 NoSoftRst+ PME-Enable- DSel=0 DScale=0 PME-

Capabilities: [50] MSI: Enable- Count=1/1 Maskable- 64bit+
    Address: 0000000000000000  Data: 0000

Capabilities: [70] Express (v2) Endpoint, MSI 00
    DevCap: MaxPayload 128 bytes, PhantFunc 0, Latency L0s <64ns, L1 <1us
    DevCtl: Report errors: Correctable- Non-Fatal- Fatal- Unsupported-
    LnkCap: Port #0, Speed 8GT/s, Width x4, ASPM L0s L1, Exit Latency L0s <1us
驱动开发提示: 在编写驱动时,可以使用 pci_find_capability() 和 pci_find_ext_capability() 函数快速定位 Capability 结构,避免手动遍历链表。