2. PCIe配置空间:配置空间布局、Type0和Type1配置头、Capability结构
PCIe配置空间是软件发现、枚举和配置PCIe设备的唯一入口。与传统的PCI总线不同,PCIe扩展了配置空间的容量,并引入了分层的能力结构(Capability)机制。理解配置空间的布局、两种配置头(Type0/Type1)以及Capability链表,是掌握设备枚举流程的基础。
2.1 配置空间整体布局
PCIe配置空间大小为4KB(4096字节),分为两个逻辑区域:前256字节(偏移0x00~0xFF)兼容传统的PCI配置空间,后3840字节(偏移0x100~0xFFF)为PCIe扩展配置空间。前256字节的布局与PCI规范完全一致,确保操作系统和固件能够使用传统的PCI配置访问机制(I/O端口CF8/CFC)读取设备信息。
2.2 Type0与Type1配置头
配置头位于配置空间偏移0x00~0x3F(64字节),根据设备类型分为两种:Type0用于Endpoint(端点设备),Type1用于Switch(交换机)和Root Port(根端口)。两者的前16字节(偏移0x00~0x0F)完全相同,后48字节存在差异。
| 偏移 | 寄存器名称 | Type0(Endpoint) | Type1(Switch/Root Port) |
|---|---|---|---|
| 0x00 | Vendor ID / Device ID | 厂商ID + 设备ID | 厂商ID + 设备ID |
| 0x04 | Command / Status | 命令寄存器 + 状态寄存器 | 命令寄存器 + 状态寄存器 |
| 0x08 | Revision ID / Class Code | 修订ID + 类别码(3字节) | 修订ID + 类别码(3字节) |
| 0x0C | Cache Line / Latency Timer / Header Type / BIST | Header Type = 0x00 | Header Type = 0x01 |
| 0x10~0x24 | Base Address Registers (BAR) | 6个BAR(每个32位) | 2个BAR(BAR0/BAR1) |
| 0x18 | Secondary Latency Timer / Subordinate Bus Number / Secondary Bus Number / Primary Bus Number | —(Type0无此字段) | 总线号寄存器(桥接用) |
| 0x34 | Capabilities Pointer | 指向第一个Capability结构 | 指向第一个Capability结构 |
2.3 Capability结构
Capability结构是PCI/PCIe配置空间中的一种扩展机制,允许设备声明其支持的高级功能。每个Capability结构以Capability ID(1字节)和Next Capability Pointer(1字节)开头,形成一个单向链表。链表头由配置头偏移0x34处的Capabilities Pointer指向。
PCIe规范定义了一系列标准Capability ID,其中与设备枚举和配置密切相关的包括:
- 0x10:PCI Express Capability — 所有PCIe设备必须实现,包含设备端口类型、链路状态、设备控制/状态寄存器。
- 0x01:Power Management Capability — 电源管理状态控制(D0~D3hot)。
- 0x05:MSI Capability — 传统消息信号中断支持。
- 0x11:MSI-X Capability — 扩展消息信号中断支持。
- 0x13:Advanced Error Reporting (AER) Capability — 高级错误报告,位于扩展配置空间。
- 0x16:ACS Capability — 访问控制服务,用于IOMMU和虚拟化隔离。
2.4 配置空间访问与枚举流程
在Linux内核中,PCIe配置空间的访问通过pci_read_config_*和pci_write_config_*系列函数完成。设备枚举阶段,内核使用以下步骤读取配置空间:
- 读取Vendor ID和Device ID(偏移0x00):判断设备是否存在(Vendor ID=0xFFFF表示无设备)。
- 读取Header Type(偏移0x0C):判断设备类型(Type0/Type1)。
- 读取Class Code(偏移0x09~0x0B):获取设备类别(如存储、网络、显示等)。
- 读取BAR寄存器(偏移0x10~0x24):获取设备请求的MMIO/I/O地址空间大小和类型。
- 遍历Capability链表:从Capabilities Pointer开始,读取每个Capability的ID和Next指针。
drivers/pci/probe.c中,函数pci_setup_device()负责读取配置头并初始化struct pci_dev。函数pci_find_capability()用于在Capability链表中查找指定的Capability ID。
/* 内核中读取配置头的典型代码路径 */
static int pci_setup_device(struct pci_dev *dev)
{
/* 读取Vendor ID和Device ID */
pci_read_config_word(dev, PCI_VENDOR_ID, &dev->vendor);
pci_read_config_word(dev, PCI_DEVICE_ID, &dev->device);
/* 读取Header Type */
pci_read_config_byte(dev, PCI_HEADER_TYPE, &dev->hdr_type);
/* 根据Header Type决定后续处理 */
switch (dev->hdr_type) {
case PCI_HEADER_TYPE_NORMAL: /* Type0 */
pci_read_bases(dev, 6, PCI_BASE_ADDRESS_0);
break;
case PCI_HEADER_TYPE_BRIDGE: /* Type1 */
pci_read_bases(dev, 2, PCI_BASE_ADDRESS_0);
pci_read_config_byte(dev, PCI_PRIMARY_BUS, &dev->primary_bus);
pci_read_config_byte(dev, PCI_SECONDARY_BUS, &dev->secondary_bus);
pci_read_config_byte(dev, PCI_SUBORDINATE_BUS, &dev->subordinate_bus);
break;
}
return 0;
}
2.5 配置空间与设备枚举的关系
配置空间是设备枚举的“数据源”。内核通过递归读取Type1配置头中的总线号寄存器,构建出完整的PCIe拓扑树。下表总结了配置空间中与枚举直接相关的关键字段:
| 配置空间字段 | 偏移 | 枚举用途 |
|---|---|---|
| Vendor ID / Device ID | 0x00 | 判断设备是否存在,匹配驱动 |
| Header Type | 0x0C | 区分Endpoint与Bridge,决定枚举策略 |
| Class Code | 0x09~0x0B | 设备分类,用于驱动匹配和资源分配 |
| BAR0~BAR5 | 0x10~0x24 | 获取设备地址空间需求,分配MMIO/I/O资源 |
| Primary/Secondary/Subordinate Bus Number | 0x18~0x1A (Type1) | 构建总线拓扑,递归枚举下游设备 |
| Capabilities Pointer | 0x34 | 遍历Capability链表,发现设备高级功能 |