2. 配置空间与BAR基础:PCIe配置空间结构、Type0/Type1配置头、BAR寄存器的作用与类型
PCIe配置空间是软件与硬件交互的基石。操作系统或驱动程序通过读取和写入配置空间,完成设备枚举、资源分配、中断配置以及错误处理。本章深入剖析配置空间的布局、Type0与Type1配置头的差异,以及BAR(Base Address Register)寄存器的工作原理与类型。
2.1 PCIe配置空间结构
PCIe继承了PCI的配置空间模型,并将其扩展至4KB(PCI仅为256字节)。前256字节(Offset 0x00-0xFF)与PCI配置空间完全兼容,称为PCI兼容配置空间;后3840字节(Offset 0x100-0xFFF)为PCIe扩展配置空间,用于存放与PCIe特性相关的能力结构(Capability Structures)。
关键概念: 配置空间通过配置读写请求(Configuration Read/Write Request)访问。在x86系统中,传统访问方式使用I/O端口0xCF8/0xCFC(CF8指定总线/设备/功能/寄存器号,CFC读写数据);现代系统则使用内存映射配置访问(MMCFG,Memory-Mapped Configuration),将配置空间映射到一段物理内存地址区域。
下图展示了PCIe配置空间的整体布局:
2.2 Type0与Type1配置头
PCIe配置空间的前64字节(Offset 0x00-0x3F)称为配置头(Configuration Header)。根据设备类型,配置头分为两种:
- Type0配置头:用于非桥设备(Endpoint),如网卡、显卡、NVMe SSD等。
- Type1配置头:用于桥设备(Switch Upstream Port、Downstream Port、Root Port),用于连接下级总线。
两种配置头的差异主要体现在BAR数量、总线号寄存器以及子总线号寄存器上。下表对比了关键字段:
| 偏移(Offset) | 寄存器名称 | Type0(Endpoint) | Type1(Bridge) |
|---|---|---|---|
| 0x00 | Vendor ID / Device ID | 有 | 有 |
| 0x04 | Command / Status | 有 | 有 |
| 0x08 | Revision ID / Class Code | 有 | 有 |
| 0x10 - 0x24 | Base Address Registers (BAR0-BAR5) | 6个BAR | 2个BAR(BAR0-BAR1) |
| 0x18 | Primary Bus Number | 无 | 有 |
| 0x19 | Secondary Bus Number | 无 | 有 |
| 0x1A | Subordinate Bus Number | 无 | 有 |
| 0x3C | Interrupt Line / Interrupt Pin | 有 | 有 |
驱动开发提示: 驱动程序通过读取
Header Type 寄存器(Offset 0x0E,Bit 7)判断设备类型。若Bit 7为0,则为Type0;若为1,则为Type1。该寄存器位于配置头偏移0x0E处,低7位表示多功能设备信息。
下图展示了Type0与Type1配置头的结构差异:
2.3 BAR寄存器的作用与类型
BAR(Base Address Register)是配置空间中最关键的寄存器之一。它用于向系统软件(如BIOS、Linux内核PCI子系统)声明设备所需的地址空间大小和类型。系统软件通过写入BAR寄存器来分配实际的物理地址基址,从而将设备寄存器映射到CPU的物理地址空间。
2.3.1 BAR的工作原理
BAR寄存器的工作原理遵循一个经典的“写全1读回”探测流程:
- 保存原始值: 驱动或PCI子系统先读取BAR的当前值并保存。
- 写入全1: 向BAR写入
0xFFFFFFFF。 - 读回值: 读取BAR,硬件会返回一个值,其中低若干位为0,表示地址空间的大小。
- 计算大小: 对读回值取反后加1,即可得到所需地址空间的大小。
- 恢复原始值: 将保存的原始值写回BAR。
示例: 假设BAR0读回值为
0xFFF00000(低20位为0),则所需大小为 0x00100000 = 1MB。系统软件随后会分配一个1MB的连续物理地址区域,并将基址写入BAR0。
2.3.2 BAR的类型
BAR寄存器支持三种地址空间类型,通过读回值的低3位(Bit 2-0)进行区分:
| 类型 | Bit 2-0 编码 | 地址范围 | 说明 |
|---|---|---|---|
| 32位内存空间 | 000 | 32位物理地址(0 - 4GB) | 最常见的BAR类型,用于映射设备寄存器、FIFO等 |
| 64位内存空间 | 100 | 64位物理地址(>4GB) | 占用连续两个BAR寄存器(如BAR0+BAR1),支持大地址空间 |
| I/O空间 | 001 | 16位I/O地址空间 | 用于传统设备,x86平台特有,现代PCIe设备已很少使用 |
驱动兼容性陷阱: 64位内存BAR会占用两个连续的BAR槽位(例如BAR0和BAR1)。驱动程序在遍历BAR时,必须检查当前BAR是否为64位类型,如果是,则需跳过下一个BAR槽位,否则会导致错误的地址解析。Linux内核的
pci_resource_start() 和 pci_resource_len() 函数已自动处理此逻辑。
下图展示了BAR类型探测与地址分配流程:
2.4 BAR的Prefetchable与Non-Prefetchable属性
除了地址空间类型,BAR还通过Bit 3标识预取属性(Prefetchable):
- Prefetchable (Bit 3 = 1): 表示该地址空间可以预取(Prefetch),即读取操作没有副作用(Side Effect),允许CPU缓存和合并写操作。通常用于内存映射的缓冲区或显存。
- Non-Prefetchable (Bit 3 = 0): 表示读取操作可能有副作用(如清空状态寄存器),不允许预取和写合并。通常用于设备控制寄存器。
驱动开发建议: 对于Non-Prefetchable BAR,驱动程序应使用
ioremap() 映射,并配合 readl()/writel() 等严格顺序的访问函数。对于Prefetchable BAR,可以使用 ioremap_wc()(写合并映射)以提高性能,但需确保设备支持。
2.5 本章小结
- PCIe配置空间从256字节扩展至4KB,前256字节与PCI兼容,后3840字节为PCIe扩展能力。
- Type0配置头用于Endpoint,拥有6个BAR;Type1配置头用于Bridge,拥有2个BAR及总线号寄存器。
- BAR寄存器通过“写全1读回”机制向系统声明所需地址空间大小,支持32位内存、64位内存和I/O空间三种类型。
- 64位内存BAR占用连续两个BAR槽位,驱动程序需注意跳过处理。
- Prefetchable属性影响CPU访问策略,驱动应根据属性选择合适的映射和访问函数。