3. PCIe 配置空间与 BAR 空间
PCIe 配置空间是主机软件与端点设备之间最基础的通信接口。通过配置空间,软件可以识别设备类型、分配资源(内存地址、中断)、控制电源状态,并最终使能 DMA 引擎。本章将深入解析 Type 0/Type 1 配置空间结构、BAR 映射机制以及两种 IO 访问方式的差异。
3.1 配置空间结构:Type 0 与 Type 1
PCIe 配置空间大小为 4 KB(传统 PCI 为 256 字节),前 64 字节为配置头,后 4 KB 包含能力链表。根据设备类型,配置头分为两种:Type 0(端点设备)和 Type 1(桥/交换机)。
关键概念: Type 0 设备是 PCIe 树中的叶子节点(如 DMA 控制器、NVMe SSD、网卡);Type 1 设备是内部节点(如 Root Port、Switch),用于转发事务和实现总线层级。
3.1.1 Type 0 配置头结构
| 偏移 (字节) | 寄存器名称 | 宽度 | 说明 |
|---|---|---|---|
| 0x00 | Vendor ID / Device ID | 16+16 bit | 厂商与设备标识 |
| 0x04 | Command / Status | 16+16 bit | 控制 IO/MMIO 使能、总线主控、中断等 |
| 0x08 | Revision ID / Class Code | 8+24 bit | 设备功能分类(如 0x010802 为 NVMe) |
| 0x0C | Cache Line / Latency Timer / Header Type / BIST | 8+8+8+8 bit | Header Type 0x00 表示 Type 0 |
| 0x10–0x24 | BAR0–BAR5 | 6×32 bit | 基地址寄存器,共 6 个 |
| 0x28 | CardBus CIS Pointer | 32 bit | CardBus 兼容指针(通常为 0) |
| 0x2C | Subsystem Vendor ID / Subsystem ID | 16+16 bit | 子系统标识 |
| 0x30 | Expansion ROM Base Address | 32 bit | 可选 ROM 映射 |
| 0x34 | Capabilities Pointer | 8 bit | 指向能力链表首项 |
| 0x3C | Interrupt Line / Interrupt Pin / Min Gnt / Max Lat | 8+8+8+8 bit | 中断路由与延迟信息 |
3.1.2 Type 1 配置头差异
Type 1 头用于桥设备,其偏移 0x10–0x24 区域包含 桥专用寄存器:
- BAR0–BAR1:仅 2 个 BAR,用于桥自身控制寄存器。
- Primary Bus Number / Secondary Bus Number / Subordinate Bus Number:定义桥下挂的总线范围。
- Memory Base / Memory Limit:定义桥转发的 32 位内存地址窗口。
- Prefetchable Memory Base / Prefetchable Memory Limit:定义预取内存窗口。
- I/O Base / I/O Limit:定义 IO 地址窗口。
工程提示: 在 DMA 驱动开发中,通常只关心 Type 0 设备的 BAR 空间。桥的 BAR 和窗口寄存器用于配置 PCIe 拓扑中的地址路由,驱动开发者一般不需要直接操作。
3.2 BAR 映射机制
BAR(Base Address Register)是设备向主机软件声明资源需求的接口。每个 BAR 可以映射为 Memory Space 或 I/O Space,并指定地址宽度(32 位或 64 位)、预取属性。
3.2.1 BAR 编码规则
| Bit 范围 | 含义 | 说明 |
|---|---|---|
| Bit 0 | 空间类型 | 0 = Memory Space;1 = I/O Space |
| Bit 2:1 | 地址宽度(Memory 类型) | 00 = 32 位;10 = 64 位;01/11 保留 |
| Bit 3 | 预取属性(Memory 类型) | 1 = 可预取(Prefetchable);0 = 不可预取 |
| Bit 31:4 | 基地址 | 对齐到 16 字节边界,实际对齐由 BAR 大小决定 |
3.2.2 BAR 大小探测流程
- 读取原始 BAR 值:保存当前值。
- 写入全 1:向 BAR 写入 0xFFFFFFFF。
- 回读:读取 BAR 值,低有效位表示所需地址空间大小。
- 恢复原始值:写回保存的原始 BAR 值。
- 计算大小:对回读值取反并加 1,得到 BAR 所需地址空间大小。
示例: 假设 BAR0 回读值为 0xFFF00000(低 20 位为 0),则 BAR0 大小为 220 = 1 MB。软件随后在系统物理地址空间中分配 1 MB 连续区域,并将基地址写入 BAR0。
3.2.3 典型 DMA 控制器的 BAR 布局
| BAR 编号 | 地址宽度 | 大小 | 映射内容 |
|---|---|---|---|
| BAR0 | 64 位 | 64 KB | 控制/状态寄存器、门铃寄存器、中断控制 |
| BAR1 | 64 位 | 4 MB | DMA 描述符环形缓冲区(Host Memory 映射) |
| BAR2 | 32 位 | 256 B | MSI-X 表与 PBA(可选) |
常见错误: 64 位 BAR 占用连续两个 BAR 槽位(如 BAR0+BAR1)。如果设备声明 64 位 BAR,驱动必须将 BAR0 和 BAR1 视为一个整体,不能单独配置。错误配置会导致地址空间分裂,设备无法正常工作。
3.3 内存映射 IO 与 IO 映射 IO
PCIe 支持两种 IO 访问方式:MMIO(Memory-Mapped I/O) 和 PIO(Port I/O / I/O-Mapped I/O)。现代 DMA 控制器几乎全部使用 MMIO。
3.3.1 对比分析
| 特性 | MMIO | PIO |
|---|---|---|
| 地址空间 | 系统物理地址空间(统一编址) | 独立 IO 地址空间(0–64K) |
| 访问指令 | 普通 load/store(MOV, LDR, STR) | 专用 IN/OUT 指令(x86) |
| 性能 | 高,支持 burst、预取、缓存 | 低,每次访问需 CPU 参与 |
| 地址宽度 | 32/64 位 | 16 位(x86) |
| 预取支持 | 可配置(Prefetchable / Non-Prefetchable) | 不支持 |
| DMA 适用性 | 适合描述符、门铃、状态轮询 | 仅适合少量控制寄存器 |
| 现代趋势 | 主流,ARM/RSIC-V 仅支持 MMIO | 逐渐淘汰,x86 保留兼容 |
3.3.2 MMIO 访问注意事项
- Ordering & Coherency:MMIO 写操作必须使用
wmb()(写内存屏障)确保在门铃触发前所有描述符已写入 Host Memory。 - Non-Prefetchable:控制寄存器(如门铃、中断掩码)必须映射为 Non-Prefetchable,防止 CPU 或硬件预取导致副作用。
- 访问粒度:DMA 控制器的门铃寄存器通常要求 4 字节对齐写,不支持部分写。
// 典型 MMIO 门铃操作(Linux 驱动片段)
void dma_ring_doorbell(struct dma_dev *dev, int channel)
{
// 确保所有描述符已写入 Host Memory
wmb();
// 写门铃寄存器(MMIO 地址)
iowrite32(channel, dev->bar0 + DOORBELL_OFFSET);
}
3.4 配置空间与 BAR 映射关系图
下图展示了从主机 CPU 视角,通过 PCIe 配置空间读取 BAR,然后建立 MMIO 映射,最终访问 DMA 控制器内部寄存器的完整路径。
3.5 配置空间能力链表
PCIe 配置空间扩展了能力链表(Capabilities List),用于支持高级功能。DMA 控制器常用的能力包括:
| 能力 ID | 名称 | 对 DMA 的意义 |
|---|---|---|
| 0x01 | Power Management | 控制设备电源状态,DMA 引擎休眠/唤醒 |
| 0x05 | MSI | 传统消息信号中断,用于 DMA 完成通知 |
| 0x11 | MSI-X | 多向量中断,每个 DMA 通道独立中断 |
| 0x10 | PCI Express Capability | 设备类型、链路状态、最大载荷大小 |
| 0x12 | Advanced Error Reporting | DMA 传输错误记录与恢复 |
| 0x16 | Virtual Channel | 多虚拟通道,支持 QoS 和隔离 |
| 0x1B | Address Translation Services | IOMMU 地址翻译,用于用户态 DMA |
驱动开发要点: 在初始化 DMA 控制器时,驱动必须遍历能力链表找到 MSI-X 能力,配置中断向量;同时读取 PCI Express Capability 中的 Max Payload Size,以优化 DMA 读写事务的载荷大小。
3.6 本章小结
- PCIe 配置空间分为 Type 0(端点)和 Type 1(桥),DMA 控制器使用 Type 0。
- BAR 是设备向主机声明资源需求的接口,支持 32/64 位地址、Memory/IO 空间、预取属性。
- BAR 大小探测通过“写全 1 回读”实现,驱动必须正确处理 64 位 BAR 的连续槽位占用。
- MMIO 是 DMA 控制器的主流访问方式,性能高、支持 burst;PIO 仅用于兼容场景。
- 配置空间中的 能力链表 提供 MSI-X、ATS、VC 等关键功能,驱动必须正确解析。