2. PCIe 配置空间:结构、寄存器与访问机制
PCIe 配置空间是软件与硬件交互的“契约书”。Linux 内核通过读取和写入配置空间来枚举设备、分配资源、配置 DMA 引擎。本章深入解析配置空间的结构(Type0/Type1)、关键寄存器(VID/DID/Class Code/BAR)以及访问机制(CFG 读写)。
2.1 配置空间结构:Type0 与 Type1
PCIe 配置空间大小为 4KB(4096 字节),分为 256 字节的 PCI 兼容配置空间(Header)和 3840 字节的 PCIe 扩展配置空间(Capability 结构)。根据设备类型,Header 分为两种:Type0(Endpoint 设备)和 Type1(Bridge/交换机设备)。
Type0 配置空间 Header(Endpoint)
| 偏移 (Byte) | 寄存器名称 | 宽度 | 说明 |
|---|---|---|---|
| 0x00 | Vendor ID (VID) | 16-bit | 厂商编号,如 0x8086 为 Intel |
| 0x02 | Device ID (DID) | 16-bit | 设备编号,如 0x10FB |
| 0x04 | Command | 16-bit | 控制 I/O、内存、总线主控等使能 |
| 0x06 | Status | 16-bit | 中断状态、Capabilities 列表等 |
| 0x08 | Revision ID | 8-bit | 修订版本 |
| 0x09 | Class Code | 24-bit | 设备分类(基类/子类/编程接口) |
| 0x0C | Cache Line Size | 8-bit | 缓存行大小(通常 64 字节) |
| 0x0D | Latency Timer | 8-bit | PCI 兼容延迟定时器 |
| 0x0E | Header Type | 8-bit | 0x00 为 Type0,0x01 为 Type1 |
| 0x0F | BIST | 8-bit | 内建自测试 |
| 0x10-0x24 | Base Address Registers (BAR0-BAR5) | 32-bit each | 内存/I/O 地址映射(DMA 关键) |
| 0x28 | Cardbus CIS Pointer | 32-bit | Cardbus 信息结构 |
| 0x2C | Subsystem Vendor ID | 16-bit | 子系统厂商 |
| 0x2E | Subsystem ID | 16-bit | 子系统设备 |
| 0x30 | Expansion ROM Base Address | 32-bit | 扩展 ROM 基址 |
| 0x34 | Capabilities Pointer | 8-bit | 指向 Capability 链表首项 |
| 0x3C | Interrupt Line | 8-bit | 中断线号(IRQ) |
| 0x3D | Interrupt Pin | 8-bit | 中断引脚(INTA#/INTB#/INTC#/INTD#) |
Type1 配置空间 Header(Bridge)
| 偏移 (Byte) | 寄存器名称 | 宽度 | 说明 |
|---|---|---|---|
| 0x00-0x0F | 同 Type0 | - | VID/DID/Command/Status/Class Code 等 |
| 0x10-0x14 | BAR0-BAR1 | 32-bit each | 桥自身寄存器映射(仅 2 个 BAR) |
| 0x18 | Primary Bus Number | 8-bit | 上游总线号 |
| 0x19 | Secondary Bus Number | 8-bit | 下游总线号 |
| 0x1A | Subordinate Bus Number | 8-bit | 下游最大总线号 |
| 0x1C | I/O Base | 8-bit | I/O 地址范围基址 |
| 0x1D | I/O Limit | 8-bit | I/O 地址范围上限 |
| 0x20 | Memory Base | 16-bit | 内存地址范围基址(高 16 位) |
| 0x22 | Memory Limit | 16-bit | 内存地址范围上限(高 16 位) |
| 0x24 | Prefetchable Memory Base | 16-bit | 预取内存基址 |
| 0x26 | Prefetchable Memory Limit | 16-bit | 预取内存上限 |
| 0x3C | Interrupt Line | 8-bit | 中断线号 |
| 0x3D | Interrupt Pin | 8-bit | 中断引脚 |
lspci -vvv 输出的 VID/DID 是否匹配驱动表。如果 BAR 地址为 0,说明设备未被正确分配资源,需检查 BIOS/UEFI 或内核 PCI 分配策略。
2.2 配置空间寄存器详解
VID / DID / Class Code
- Vendor ID (VID): 由 PCI SIG 分配的唯一厂商标识。例如 Intel=0x8086,AMD=0x1022,NVIDIA=0x10DE。
- Device ID (DID): 厂商内部定义的设备型号。驱动通过 VID+DID 组合匹配设备。
- Class Code: 3 字节,分为 Base Class(基类)、Sub Class(子类)、Programming Interface(编程接口)。例如 NVMe 为 0x010802(Base=01 大容量存储,Sub=08 非易失性,ProgIF=02 NVMe)。
Base Address Register (BAR)
BAR 是 DMA 传输的核心寄存器。设备通过 BAR 向系统报告所需的地址空间大小和类型(内存空间或 I/O 空间)。Linux 内核在枚举时写入全 1 到 BAR,然后读取回值来确定大小。
Capabilities Pointer 与 Capability 链表
PCIe 扩展功能通过 Capability 链表实现。Capabilities Pointer(偏移 0x34)指向第一个 Capability 结构。每个 Capability 包含 Cap ID、Next Pointer 和数据区。常见 Capability 包括:
- 0x10: PCI Express Capability(必备)
- 0x05: MSI Capability(消息信号中断)
- 0x11: MSI-X Capability(多向量消息中断)
- 0x01: Power Management Capability
- 0x09: Vendor-Specific Capability(厂商自定义)
2.3 配置空间访问机制
Linux 内核通过两种方式访问 PCIe 配置空间:CFG 读写(I/O 端口或内存映射) 和 PCIe 原生 ECAM(Enhanced Configuration Access Mechanism)。
传统 CFG 读写(I/O 端口方法)
仅用于 PCI 兼容设备。通过 I/O 端口 0xCF8(配置地址端口)和 0xCFC(配置数据端口)进行 32 位访问。
// 读取配置空间 32 位值
uint32_t pci_cfg_read(uint8_t bus, uint8_t dev, uint8_t func, uint8_t offset) {
uint32_t address = (1 << 31) | (bus << 16) | (dev << 11) | (func << 8) | (offset & 0xFC);
outl(0xCF8, address);
return inl(0xCFC);
}
PCIe ECAM(内存映射方法)
PCIe 原生机制。将配置空间映射到系统内存地址空间,每个设备占用 4KB,通过 MMIO 直接访问。地址格式:
// ECAM 地址计算
// 基址 + (bus << 20) | (device << 15) | (function << 12) | offset
// 例如:MMCFG 基址为 0xF0000000,访问 Bus 0, Dev 0, Func 0, Offset 0x10
// 物理地址 = 0xF0000000 + (0 << 20) | (0 << 15) | (0 << 12) | 0x10
// = 0xF0000010
pci_read_config_dword() 返回 0xFFFFFFFF,检查内核参数 pci=noecam 是否被设置,或 BIOS 中 PCIe MMIO 映射是否开启。
Linux 内核配置空间访问 API
| 函数 | 说明 | 头文件 |
|---|---|---|
pci_read_config_byte/dev/dword() | 读取 8/16/32 位配置空间 | <linux/pci.h> |
pci_write_config_byte/dev/dword() | 写入 8/16/32 位配置空间 | <linux/pci.h> |
pci_find_capability() | 查找指定 Capability ID | <linux/pci.h> |
pci_resource_start/end/len() | 获取 BAR 资源地址/长度 | <linux/pci.h> |
pci_iomap() | 将 BAR 映射到内核虚拟地址 | <linux/io.h> |
2.4 配置空间结构总览图
2.5 配置空间访问流程
Linux 内核在启动时通过以下流程枚举 PCIe 设备并读取配置空间:
- 扫描总线: 从 Bus 0 开始,读取每个 Device/Function 的 Vendor ID。如果 VID != 0xFFFF,则设备存在。
- 读取 Header Type: 判断是 Type0 还是 Type1。如果是 Type1(Bridge),递归扫描 Secondary Bus。
- 分配资源: 读取 BAR 寄存器,确定设备需要的地址空间大小,分配物理地址并写回 BAR。
- 加载驱动: 根据 VID/DID 匹配驱动表,调用驱动的 probe() 函数。
- 驱动初始化: 驱动通过 pci_iomap() 映射 BAR,配置 DMA 引擎,注册中断。
lspci -vvv -s 01:00.0 查看指定设备的完整配置空间。关注 Region 0(BAR0)的地址和大小,以及 Capabilities 列表中的 MSI-X 和 Express 部分。
2.6 配置空间与 DMA 的关联
DMA 传输依赖配置空间中的以下关键信息:
| 配置空间元素 | DMA 关联 | 故障影响 |
|---|---|---|
| BAR 寄存器 | 提供设备寄存器基址,DMA 引擎控制寄存器通常映射在 BAR 中 | BAR 地址错误导致 DMA 无法启动 |
| MSI/MSI-X Capability | 配置 DMA 完成中断向量 | 中断未配置导致 DMA 传输无法感知完成 |
| Class Code | 驱动匹配依据,决定 DMA 引擎初始化方式 | Class Code 错误导致驱动加载失败 |
| Command 寄存器 | Bus Master Enable (bit 2) 必须置位才能发起 DMA | 未使能 Bus Master 导致 DMA 操作被阻塞 |
| PCIe Capability | 包含 Max Payload Size、Max Read Request Size 等 DMA 性能参数 | Payload 不匹配导致 DMA 性能下降或错误 |
pci_set_master() 使能 Bus Master。如果忘记,DMA 写请求将被 PCIe 交换机拒绝,导致传输超时。使用 cat /sys/bus/pci/devices/0000:01:00.0/config | xxd 可查看原始配置空间二进制内容。