2. PCIe 配置空间:配置空间布局、Capability 结构、BAR 空间与 MMIO 映射
PCIe 配置空间是设备驱动与硬件交互的“控制面板”。驱动通过读写配置空间来发现设备功能、分配资源、使能 DMA 和中断。本章深入解析配置空间的布局、Capability 链表机制、BAR 空间解码规则以及 MMIO 映射的完整流程。
2.1 配置空间布局与访问机制
PCIe 继承了 PCI 的配置空间规范,并将其扩展为 4 KB(PCI 仅为 256 字节)。前 256 字节(Offset 0x00–0xFF)为 PCI 兼容头区,后 3840 字节为 PCIe 扩展配置空间,存放 Capability 结构和 Vendor-Specific 扩展。
关键概念: 配置空间通过 ECAM(Enhanced Configuration Access Mechanism) 映射到内存地址空间。系统软件通过 MMIO 方式直接访问 Bus:Device:Function (BDF) 对应的配置寄存器,无需 I/O 端口。
2.2 Capability 结构:链表与关键 Cap
PCIe 使用 Capability 链表机制来扩展功能。每个 Capability 结构以 Cap ID 和 Next Cap Pointer 开头,形成单向链表。驱动通过遍历链表发现设备支持的特性。
| Capability ID | 名称 | 驱动关注点 |
|---|---|---|
| 0x01 | Power Management (PM) | 电源状态管理(D0/D1/D2/D3) |
| 0x05 | MSI (Message Signaled Interrupts) | 传统 MSI 中断使能、向量配置 |
| 0x11 | MSI-X | 高密度中断向量,每个向量独立地址/数据 |
| 0x10 | PCI Express Capability | 链路状态、最大载荷、LTR、OBFF |
| 0x14 | Advanced Error Reporting (AER) | 高级错误状态与恢复 |
| 0x1E | Physical Layer 16.0 GT/s (Gen5) | Gen5 速率协商与 Equalization |
性能优化提示: 对于高吞吐 DMA 场景,务必确认设备支持 MSI-X(Cap ID 0x11)。MSI-X 允许每个队列拥有独立的中断向量,是实现中断合并与负载均衡的基础。
2.3 BAR 空间与地址解码
Base Address Register (BAR) 是设备向系统声明所需 MMIO 或 I/O 空间大小的机制。每个 BAR 占用配置空间偏移 0x10–0x24 的 6 个 32 位寄存器(64 位 BAR 占用连续两个)。驱动通过 BAR 探测(BAR Sizing) 获取设备所需地址范围。
BAR 探测原理: 向 BAR 写入全 1(0xFFFFFFFF),然后回读。回读值中可写为 0 的位表示地址可编程范围,从而计算出所需空间大小(必须是 2 的幂且对齐)。
// BAR 探测示例(伪代码)
uint32_t bar_orig = pci_read_config_dword(dev, offset);
pci_write_config_dword(dev, offset, 0xFFFFFFFF);
uint32_t bar_sz = pci_read_config_dword(dev, offset);
pci_write_config_dword(dev, offset, bar_orig); // 恢复
// 计算大小:取反后 +1,再与 0xFFFFFFFF 取交
uint32_t size = (~(bar_sz & 0xFFFFFFF0)) + 1;
// 注意:bit 0 表示空间类型(0=MMIO, 1=I/O),bit 2:1 表示位宽(32/64 位)
| BAR 位域 | 含义 | 典型值 |
|---|---|---|
| Bit 0 | 空间类型:0 = MMIO, 1 = I/O | 0x0(MMIO) |
| Bit 2:1 | 位宽:00 = 32 位, 10 = 64 位 | 0x2(64 位 MMIO) |
| Bit 3 | Prefetchable(预取允许) | 1(允许预取,适合 DMA 缓冲区) |
| Bit 31:4 | 基地址(对齐到 16 字节) | 由 BIOS/OS 分配 |
2.4 MMIO 映射:从 BAR 到虚拟地址
系统固件(BIOS/UEFI)或操作系统为每个 BAR 分配物理地址范围。驱动通过 ioremap() 将物理 MMIO 地址映射到内核虚拟地址空间,之后才能通过 readl/writel 等访问寄存器。
性能陷阱: 默认 ioremap() 使用 Strongly-Ordered (Device memory) 属性,每次读写都直接访问 PCIe 总线,延迟较高。对于高性能 DMA 描述符环或门铃寄存器,可考虑使用 ioremap_wc()(Write-Combining)来合并写操作,但需确保硬件支持 WC 语义且驱动正确处理读-修改-写。
2.5 配置空间访问的驱动示例
以下代码展示如何在 Linux 内核驱动中读取 BAR0 并完成 MMIO 映射:
#include <linux/pci.h>
#include <linux/io.h>
static int my_pcie_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
int ret;
resource_size_t bar0_start, bar0_len;
void __iomem *regs;
// 1. 使能设备
ret = pci_enable_device(pdev);
if (ret)
return ret;
// 2. 设置 DMA 掩码(64 位)
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret)
goto err_disable;
// 3. 获取 BAR0 资源
bar0_start = pci_resource_start(pdev, 0);
bar0_len = pci_resource_len(pdev, 0);
// 4. 请求 MMIO 区域
ret = pci_request_region(pdev, 0, "my_pcie_bar0");
if (ret)
goto err_disable;
// 5. ioremap
regs = ioremap(bar0_start, bar0_len);
if (!regs) {
ret = -ENOMEM;
goto err_release;
}
// 6. 通过 regs 访问设备寄存器
writel(0x1, regs + 0x100); // 使能 DMA 引擎
// 保存 regs 到私有结构...
return 0;
err_release:
pci_release_region(pdev, 0);
err_disable:
pci_disable_device(pdev);
return ret;
}
2.6 本章小结
- 配置空间:4 KB 布局,前 256 字节 PCI 兼容,后 3,840 字节 PCIe 扩展。
- Capability 链表:驱动通过遍历 Cap ID 发现 MSI-X、AER 等关键特性。
- BAR 空间:通过 BAR Sizing 获取设备所需 MMIO 大小,注意 64 位 BAR 的处理。
- MMIO 映射:ioremap() 将物理地址转为虚拟地址,注意内存属性对性能的影响。
- 驱动实践:pci_enable_device → pci_request_region → ioremap 是标准流程。