2. PCIe配置空间:配置空间布局(Type0/Type1)、Capability结构、扩展配置空间(ECAM)机制
PCIe配置空间是软件与硬件交互的核心接口,驱动通过读写配置空间完成设备发现、资源分配、中断配置与错误处理。本章深入解析Type0/Type1配置空间的布局差异、Capability链表的遍历方法以及ECAM(Enhanced Configuration Access Mechanism)的地址映射原理。
2.1 配置空间布局:Type0与Type1
PCIe配置空间大小为4KB,前256字节(Offset 0x00–0xFF)兼容传统PCI配置空间,后3840字节(Offset 0x100–0xFFF)为PCIe扩展配置空间。根据设备类型,配置空间分为两种头部格式:Type0(Endpoint设备)和Type1(Switch/Root Port设备)。
2.1.1 Type0配置空间头部结构
| 偏移(Offset) | 寄存器名称 | 宽度(Byte) | 描述 |
|---|---|---|---|
| 0x00 | Vendor ID | 2 | 厂商识别码(如0x8086为Intel) |
| 0x02 | Device ID | 2 | 设备识别码 |
| 0x04 | Command | 2 | 控制I/O、Memory、Bus Master等使能 |
| 0x06 | Status | 2 | 中断状态、Capability List存在位等 |
| 0x08 | Revision ID | 1 | 修订版本号 |
| 0x09 | Class Code | 3 | 设备分类(Base/Sub/Prog IF) |
| 0x0C | Cache Line Size | 1 | 缓存行大小(通常为64字节) |
| 0x0D | Latency Timer | 1 | PCI兼容延迟定时器 |
| 0x0E | Header Type | 1 | 0x00=Type0, 0x01=Type1, Bit7=多功能设备 |
| 0x0F | BIST | 1 | 内建自测试 |
| 0x10–0x27 | BAR0–BAR5 | 6×4 | 基地址寄存器(Memory或I/O空间) |
| 0x28 | CardBus CIS Pointer | 4 | CardBus信息结构指针 |
| 0x2C | Subsystem Vendor ID | 2 | 子系统厂商ID |
| 0x2E | Subsystem ID | 2 | 子系统设备ID |
| 0x30 | Expansion ROM Base Address | 4 | 扩展ROM基地址 |
| 0x34 | Capabilities Pointer | 1 | Capability链表首指针(仅当Status[4]=1) |
| 0x3C | Interrupt Line | 1 | 中断线号(由固件分配) |
| 0x3D | Interrupt Pin | 1 | 中断引脚(INTA#=1, INTB#=2, INTC#=3, INTD#=4) |
| 0x3E | Min Gnt | 1 | PCI兼容:最小授权周期 |
| 0x3F | Max Lat | 1 | PCI兼容:最大延迟 |
2.1.2 Type1配置空间头部结构
Type1头部专用于Bridge设备,其核心差异在于BAR数量减少、增加总线号寄存器与桥控制寄存器。
| 偏移(Offset) | 寄存器名称 | 宽度(Byte) | 描述 |
|---|---|---|---|
| 0x10–0x17 | BAR0–BAR1 | 2×4 | 仅2个BAR(Type0为6个) |
| 0x18 | Primary Bus Number | 1 | 本桥上游总线号 |
| 0x19 | Secondary Bus Number | 1 | 本桥下游总线号 |
| 0x1A | Subordinate Bus Number | 1 | 下游最大总线号 |
| 0x1B | Secondary Latency Timer | 1 | 下游总线延迟定时器 |
| 0x1C | I/O Base | 1 | 下游I/O空间基地址高8位 |
| 0x1D | I/O Limit | 1 | 下游I/O空间限制地址高8位 |
| 0x1E | Secondary Status | 2 | 下游总线状态 |
| 0x20 | Memory Base | 2 | 下游Memory空间基地址 |
| 0x22 | Memory Limit | 2 | 下游Memory空间限制地址 |
| 0x24 | Prefetchable Memory Base | 2 | 预取Memory基地址 |
| 0x26 | Prefetchable Memory Limit | 2 | 预取Memory限制地址 |
| 0x28 | Prefetchable Base Upper 32 Bits | 4 | 64位预取基地址高32位 |
| 0x2C | Prefetchable Limit Upper 32 Bits | 4 | 64位预取限制高32位 |
| 0x30 | I/O Base Upper 16 Bits | 2 | I/O基地址高16位(32位I/O空间) |
| 0x32 | I/O Limit Upper 16 Bits | 2 | I/O限制高16位(32位I/O空间) |
| 0x34 | Capabilities Pointer | 1 | Capability链表首指针 |
| 0x3C | Interrupt Line | 1 | 中断线号 |
| 0x3D | Interrupt Pin | 1 | 中断引脚 |
| 0x3E | Bridge Control | 2 | 桥控制寄存器(SERR、ISA、VGA等) |
2.2 Capability结构:链表遍历与关键Capability
PCIe设备通过Capability链表扩展功能,每个Capability结构以Cap ID(1字节)和Next Pointer(1字节)开头。Next Pointer指向下一个Capability的偏移地址,链表以Next Pointer=0x00结束。
2.2.1 Capability链表遍历流程
- 读取Status寄存器(Offset 0x06),检查Bit4(Capabilities List)是否为1。
- 若为1,读取Capabilities Pointer(Offset 0x34),获得第一个Capability的偏移地址。
- 进入循环:读取当前偏移处的Cap ID和Next Pointer。
- 根据Cap ID解析对应结构(如PM、MSI、MSI-X、PCIe Capability)。
- 将Next Pointer赋值给当前偏移,若Next Pointer=0x00则退出循环。
- 0x01:PM(Power Management)
- 0x05:MSI(Message Signaled Interrupt)
- 0x10:PCIe Capability(PCI Express Extended Capability)
- 0x11:MSI-X
- 0x12:Advanced Error Reporting(AER)
2.2.2 PCIe Capability结构(Cap ID=0x10)
PCIe Capability是PCIe设备必须实现的核心Capability,位于配置空间Offset 0x40之后。其结构包含PCIe Capability ID、Next Pointer、PCIe Capabilities Register、Device Capabilities Register等。
| 偏移(相对Capability起始) | 寄存器名称 | 宽度(Byte) | 关键字段 |
|---|---|---|---|
| 0x00 | PCIe Capability ID | 1 | 固定值0x10 |
| 0x01 | Next Capability Pointer | 1 | 指向下一个Capability |
| 0x02 | PCIe Capabilities Register | 2 | Device/Port Type(0=EP, 1=RC, 4=Switch Upstream等) |
| 0x04 | Device Capabilities Register | 4 | Max Payload Size、Phantom Functions、L0s/L1支持 |
| 0x08 | Device Control Register | 2 | Max Read Request Size、Enable Relaxed Ordering等 |
| 0x0A | Device Status Register | 2 | UR/CA/CTO状态、Transaction Pending |
| 0x0C | Link Capabilities Register | 4 | Max Link Speed、Max Link Width、ASPM支持 |
| 0x10 | Link Control Register | 2 | ASPM控制、Retrain Link、Link Disable |
| 0x12 | Link Status Register | 2 | Current Link Speed、Current Link Width、Link Training |
2.3 扩展配置空间(ECAM)机制
传统PCI通过I/O端口(CF8h/CFCh)访问配置空间,仅能访问256字节。PCIe引入Memory-Mapped Configuration Access,将整个配置空间映射到系统物理地址空间,支持4KB完整访问。ECAM是PCIe枚举的核心机制。
2.3.1 ECAM地址映射公式
ECAM将每个设备的配置空间映射到一段连续的物理地址区域。地址计算公式如下:
物理地址 = ECAM基地址 + (Bus_Number << 20) | (Device_Number << 15) | (Function_Number << 12) | Register_Offset
其中:
- ECAM基地址:由系统固件(BIOS/UEFI)在MCFG ACPI表中指定,通常为0xE0000000或0xF0000000。
- Bus_Number:8位,范围0–255,左移20位(1MB对齐)。
- Device_Number:5位,范围0–31,左移15位(32KB对齐)。
- Function_Number:3位,范围0–7,左移12位(4KB对齐)。
- Register_Offset:12位,范围0x000–0xFFF,直接访问4KB空间。
2.3.2 ECAM访问示例代码
以下Linux内核代码展示了通过ECAM读取配置空间寄存器的典型实现:
// 定义ECAM访问结构
struct pci_ecam_ops {
void __iomem *base; // ECAM基地址(已映射)
unsigned int bus_start;
unsigned int bus_end;
};
// 读取32位配置寄存器
static u32 ecam_read_config(struct pci_ecam_ops *ecam,
unsigned int bus,
unsigned int devfn,
unsigned int where)
{
void __iomem *addr;
u32 val;
// 计算ECAM物理地址对应的虚拟地址
addr = ecam->base + (bus << 20) | (PCI_SLOT(devfn) << 15)
| (PCI_FUNC(devfn) << 12) | (where & 0xFFC);
// 读取32位数据(对齐到4字节)
val = readl(addr);
return val;
}
// 写入32位配置寄存器
static void ecam_write_config(struct pci_ecam_ops *ecam,
unsigned int bus,
unsigned int devfn,
unsigned int where,
u32 val)
{
void __iomem *addr;
addr = ecam->base + (bus << 20) | (PCI_SLOT(devfn) << 15)
| (PCI_FUNC(devfn) << 12) | (where & 0xFFC);
writel(val, addr);
}
2.4 配置空间访问流程SVG图
下图展示了从驱动发起配置空间读取请求到硬件响应的完整流程,涵盖ECAM地址映射与Capability链表遍历:
2.5 配置空间初始化配置工程案例
以下是一个实际工程案例,展示在Linux内核驱动中如何读取Type0配置空间、遍历Capability链表并配置MSI中断:
#include <linux/pci.h>
#include <linux/msi.h>
static int my_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int ret;
u16 vendor, device, command;
u8 cap_ptr, cap_id, next_ptr;
u32 bar0;
// 1. 读取Vendor ID和Device ID
pci_read_config_word(pdev, PCI_VENDOR_ID, &vendor);
pci_read_config_word(pdev, PCI_DEVICE_ID, &device);
dev_info(&pdev->dev, "Vendor=0x%04x, Device=0x%04x\n", vendor, device);
// 2. 使能Memory和Bus Master
pci_read_config_word(pdev, PCI_COMMAND, &command);
command |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER;
pci_write_config_word(pdev, PCI_COMMAND, command);
// 3. 读取BAR0(假设为64位Memory BAR)
pci_read_config_dword(pdev, PCI_BASE_ADDRESS_0, &bar0);
dev_info(&pdev->dev, "BAR0 = 0x%08x\n", bar0);
// 4. 遍历Capability链表,查找MSI Capability
pci_read_config_byte(pdev, PCI_CAPABILITY_LIST, &cap_ptr);
while (cap_ptr != 0) {
pci_read_config_byte(pdev, cap_ptr, &cap_id);
if (cap_id == PCI_CAP_ID_MSI) {
dev_info(&pdev->dev, "Found MSI Capability at offset 0x%02x\n", cap_ptr);
// 配置MSI:使能64位地址、多向量
pci_write_config_word(pdev, cap_ptr + PCI_MSI_FLAGS,
PCI_MSI_FLAGS_ENABLE | PCI_MSI_FLAGS_64BIT | (2 << 1));
break;
}
pci_read_config_byte(pdev, cap_ptr + 1, &next_ptr);
cap_ptr = next_ptr;
}
// 5. 注册设备并分配MSI中断
ret = pci_enable_msi(pdev);
if (ret) {
dev_err(&pdev->dev, "MSI enable failed: %d\n", ret);
return ret;
}
ret = request_irq(pdev->irq, my_interrupt_handler, 0, "my_pcie", pdev);
if (ret) {
pci_disable_msi(pdev);
return ret;
}
return 0;
}
- 读取BAR前必须先使能Memory空间(Command[1]=1),否则BAR返回0。
- MSI Capability的Next Pointer位于Cap ID之后1字节,遍历时注意偏移。
- 64位BAR占用连续两个32位寄存器,读取BAR0后需读取BAR1获取高32位。
- ECAM基地址可通过ACPI MCFG表或设备树获取,驱动中通常使用pci_ecam_ops结构体封装。
2.6 本章小结
本章详细阐述了PCIe配置空间的Type0/Type1布局差异、Capability链表的遍历机制以及ECAM地址映射原理。驱动开发者必须掌握:
- 通过Header Type区分设备类型,正确解析BAR或总线号。
- 利用Capability链表发现MSI、PCIe Capability等扩展功能。
- 理解ECAM地址计算公式,实现高效的MMIO配置空间访问。