2. PCIe配置空间的基本概念:配置寄存器、配置周期
这一节我们聊聊配置空间。说白了,它就是PCIe设备里的一块"名片区"。你想想看,系统上电后,CPU完全不认识挂在上面的各种设备——显卡、网卡、NVMe硬盘。它怎么知道这些设备是谁?需要多少资源?
答案就在配置空间里。每个PCIe功能都有一套配置空间,可以理解为设备向系统"报到"的地方。
2.1 配置空间概述
PCIe的配置空间大小为4KB,由前256字节的标准配置头部和后续的扩展配置空间组成。嗯,这个结构是从PCI时代继承并扩展而来的。
标准配置头部(前64字节) 是所有PCIe设备必须实现的,包含最基本的设备信息和资源需求。包括Vendor ID、Device ID、Command Register、Status Register、Class Code、BAR寄存器等等。
扩展配置空间(后面3968字节) 是PCIe新增的,包含能力结构(Capability Structures),如MSI中断、Power Management、AER高级错误报告、SR-IOV等功能。我个人习惯,遇到复杂问题时先去看扩展空间里的能力寄存器。
2.2 配置寄存器
我挑几个最关键的说,这些在项目中我几乎天天打交道。
2.2.1 Vendor ID 和 Device ID
这两个寄存器合起来唯一标识一个设备。
- Vendor ID:厂商编号,比如Intel是0x8086,AMD是0x1022。由PCI SIG分配,每个厂商一个。
- Device ID:设备型号编号,由厂商自行定义。
我在项目中遇到过一块网卡怎么都枚举不到,读Vendor ID全是0xFF,查了半天发现是复位时序没满足。说白了,设备还没就绪,你读到的配置空间全是"没设备"的状态。
2.2.2 BAR寄存器(Base Address Register)
BAR是配置空间里最重要的资源描述寄存器。它告诉系统:我需要多大的MMIO/IO空间。
BAR的读写行为很有意思。你写全1进去,再读回来,就能算出它请求的内存空间大小。嗯,这个"测BAR大小"的手法我在调试中经常用。
| 偏移量 | 寄存器名称 | 宽度 | 描述 |
|---|---|---|---|
| 0x00 | Vendor ID | 16位 | 厂商编号 |
| 0x02 | Device ID | 16位 | 设备编号 |
| 0x04 | Command | 16位 | 设备控制(IO/内存使能、总线主控等) |
| 0x06 | Status | 16位 | 设备状态 |
| 0x08 | Revision ID | 8位 | 版本号 |
| 0x09 | Class Code | 24位 | 设备类别(Base/Sub/Prog IF) |
| 0x10-0x24 | BAR0~BAR5 | 32位 | 基地址寄存器,最多6个 |
| 0x3C | Interrupt Line | 8位 | 中断线映射(Legacy中断) |
| 0x3D | Interrupt Pin | 8位 | 中断引脚(INTA~INTD) |
2.3 配置周期
系统怎么读取或写入某个设备的配置空间?这就是配置周期的作用。CPU其实不会直接发配置请求——它通过某种机制让RC(Root Complex)代为发起。
2.3.1 配置周期的类型
配置周期分两种:Type 0 和 Type 1。
- Type 0配置周期:用来访问端点的配置空间。发出的目标就是具体的设备功能。
- Type 1配置周期:用来访问桥下游设备的配置空间。桥会接收Type 1,如果目标设备在桥的次级总线上,就把Type 1翻译成Type 0;否则继续向下转发。
你想想看,枚举过程中,系统就是从RC出发,逐级发送Type 1配置请求,直到找到端点设备。我曾经在一个多级Switch的拓扑里被这个问题卡住过,Type 1的转换没搞对,导致下游设备全部不可见。
2.3.2 配置周期的访问机制
有两种方式发起配置周期:
- I/O映射的配置空间(Legacy方式):通过CF8h/CFCh两个I/O端口来发起。写Configuration Address到CF8h,然后读/写CFCh来访问数据。这种方式在x86上仍然存在,但PCIe已经不推荐使用。
- 内存映射配置空间(MMCFG,也叫ECAM):把配置空间映射到一段内存区域,直接通过Load/Store指令访问。这是PCIe的推荐方式,也叫Enhanced Configuration Access Mechanism。
MMCFG的映射关系很简单。每个总线的配置空间占1MB(256个设备功能×4KB),总线的编号不同,映射地址也不同。举个例子,访问Bus 0 Device 0 Function 0的配置空间,地址就是MMCFG基址 + 0x000000。
2.4 配置空间访问机制图解
下面这张图展示了配置空间结构、BDF地址与配置周期类型之间的关系。说实话,这个图我画了好几次才理清楚,尤其是Type 0和Type 1在不同段的分工。
2.5 配置空间访问的注意事项
还有几点:
- 配置空间是小端序的,多字节寄存器要注意字节序。我习惯在代码里做个字节交换宏来统一处理。
- 扩展配置空间(0x100之后的区域)必须通过MMCFG方式访问,Legacy CF8/CFC只能访问前256字节。
- 配置写操作不能保证立即生效,可能需要等待事务完成。尤其是写Command寄存器使能总线主控时,建议后面跟一个配置读来确认。
2.6 小结
这一节我们搞清楚了配置空间的结构——标准头部加扩展空间;也明白了配置周期怎么走——Type 0直达端点,Type 1过桥转换。再加上MMCFG和Legacy两种访问方式,你已经具备了理解枚举流程的基础。
嗯,配置空间这部分我当年花了很长时间才真正摸透。现在回过头看,其实就是一张表、一套地址、两种周期。你把它当作设备向系统自我介绍的标准表格,后面所有操作都围绕这张表展开。