1. PCIe基础回顾:从体系结构到配置空间
各位同学,大家好。我是你们的PCIe系统架构讲师。今天咱们正式开始《PCIe Switch非透明桥与隔离技术实战》的第一章——基础回顾。
说实话,每次开课我都要纠结一下:基础讲多深?讲太浅,有经验的同学觉得浪费时间;讲太深,新手又跟不上。我的习惯是——把核心概念讲透,把容易踩坑的地方点出来。至于那些手册上随便就能查到的寄存器位定义,咱们就不啰嗦了。
1.1 PCIe体系结构概述
PCIe,全称Peripheral Component Interconnect Express。说白了,就是电脑里连接CPU和各种外设的高速总线。
你想想看,从最早的PCI并行总线,到PCI-X,再到今天的PCIe,为什么PCIe能成为绝对主流?核心原因就两个:串行传输和点对点连接。
串行传输意味着不需要像PCI那样同步所有设备的时钟,频率可以跑得更高。点对点连接意味着每个设备都有自己的专用通道,不用像PCI那样共享总线带宽。
我记得刚入行那会儿,有个老工程师跟我说过一句话,我一直记到现在:「PCIe的本质,就是一组高速串行差分对,加上一套复杂的协议栈」。嗯,这句话虽然简单,但确实点到了精髓。
核心要点:
- PCIe是串行总线,使用差分信号对(Tx+/Tx-, Rx+/Rx-)
- 点对点连接,每个链路(Link)由多条通道(Lane)组成
- 支持1、2、4、8、16、32通道宽度(x1, x2, x4, x8, x16, x32)
- 每个通道速率从Gen1的2.5GT/s到Gen6的64GT/s
1.2 分层模型:事务层、数据链路层、物理层
PCIe协议栈分为三层,从高到低分别是:事务层(Transaction Layer)、数据链路层(Data Link Layer)、物理层(Physical Layer)。
为什么要分层?说白了就是为了解耦。每一层只管自己的事,上层不用关心底层怎么传,底层也不用关心上层传的是什么数据。
我画了一张图,帮大家理解这三层的关系:
每一层都有自己特定的职责:
- 事务层:负责生成和解析TLP(事务层包)。比如CPU要读一个Endpoint的寄存器,事务层就会构造一个Memory Read TLP。我习惯把事务层理解为「翻译官」——把上层要做的读写操作翻译成PCIe协议能理解的格式。
- 数据链路层:负责可靠传输。它会给TLP加上序列号和CRC校验,接收方收到后要回复ACK或NAK。说白了,这一层就是「快递员」,保证包裹不丢、不坏。
- 物理层:负责真正的电气信号传输。包括串行化/解串行化、编码(Gen1/2用8b/10b,Gen3+用128b/130b)、时钟恢复等。这一层是「搬运工」,把0和1变成电压信号在线路上跑。
我的经验之谈:
调试PCIe问题的时候,一定要先搞清楚问题出在哪一层。链路训练不上?查物理层。TLP超时?查事务层。CRC错误?查数据链路层。我曾经花了两天时间在事务层找问题,最后发现是物理层的信号完整性不行——这种教训,一次就够了。
1.3 PCIe拓扑结构:Root Complex、Switch、Endpoint
PCIe的拓扑结构,说白了就是一个树形结构。根节点是Root Complex(RC),叶子节点是Endpoint(EP),中间可以有Switch做扩展。
咱们来看一下典型的拓扑:
三种设备类型,各有各的定位:
- Root Complex (RC):系统的根节点,通常集成在CPU内部。RC负责连接CPU、内存和PCIe子系统。说白了,RC就是PCIe世界的「交通指挥中心」。
- Switch:相当于PCIe的「交换机」。它有一个上行端口(Upstream Port)连RC,多个下行端口(Downstream Port)连Endpoint。Switch的作用就是扩展端口数量,让一个RC能带更多的设备。
- Endpoint (EP):功能设备,比如显卡、NVMe SSD、网卡等。EP是PCIe事务的发起者或目标者。
这里有个容易混淆的点:
很多人以为Switch就是「一分多」的Hub。其实不然。PCIe Switch内部是一个路由矩阵,它根据TLP中的地址或ID信息,把事务转发到正确的端口。而且Switch本身不处理事务内容,它只负责转发。
1.4 PCIe配置空间与BAR空间基础
这部分是咱们课程的重中之重。非透明桥的核心操作,说白了就是操作配置空间和BAR空间。
每个PCIe设备(包括RC、Switch、Endpoint)都有一个配置空间,大小是4KB(PCI时代只有256字节,PCIe扩展到了4KB)。配置空间里存放着设备的类型、厂商ID、设备ID、状态寄存器、控制寄存器等信息。
系统启动时,BIOS或操作系统会枚举所有PCIe设备,读取它们的配置空间,分配资源(比如BAR地址、中断号等)。这个过程叫PCIe枚举。
咱们来看一下配置空间的前64字节(Header Type 0,适用于Endpoint):
| 偏移地址 | 寄存器名称 | 说明 |
|---|---|---|
| 0x00 | Vendor ID / Device ID | 厂商ID(如8086h=Intel)和设备ID |
| 0x04 | Command / Status | 命令寄存器(控制IO/Mem访问等)和状态寄存器 |
| 0x08 | Revision ID / Class Code | 版本号和类别码(如010000h=大容量存储控制器) |
| 0x0C | Cache Line Size / Latency Timer / Header Type | Header Type指示设备类型(0=EP,1=Switch上行端口,2=桥) |
| 0x10-0x24 | Base Address Registers (BAR0-BAR5) | 6个BAR寄存器,每个32位或64位,用于地址映射 |
| 0x3C | Interrupt Line / Interrupt Pin | 中断配置 |
BAR空间是啥?说白了,就是设备暴露给系统的一块「窗口」。系统给设备分配一段物理地址空间,设备把内部的寄存器或内存映射到这段地址上。CPU读写这段地址,就相当于读写设备内部的寄存器。
举个例子:
// 假设系统给设备分配了BAR0,基地址为0xF0000000,大小为1MB
// 设备内部寄存器偏移如下:
// 0x0000 - 控制寄存器
// 0x0004 - 状态寄存器
// 0x1000 - 数据缓冲区
// CPU要写控制寄存器:
*(volatile uint32_t *)(0xF0000000) = 0x01; // 写BAR0基地址+0x0000
// CPU要读状态寄存器:
uint32_t status = *(volatile uint32_t *)(0xF0000004); // 读BAR0基地址+0x0004
这段代码看起来简单,但实际项目中坑不少。我遇到过最典型的问题:BAR地址对齐。BAR的大小必须是2的幂,而且基地址必须按大小对齐。比如1MB的BAR,基地址必须是1MB的整数倍。如果你在FPGA里实现PCIe设备,BAR大小设错了或者对齐不对,系统枚举时就会分配失败。
避坑指南:
我曾经在一个项目里,把BAR的大小设成了0x100000(1MB),但实际需要的空间只有0x80000(512KB)。结果系统给分配了1MB空间,但设备只解码了512KB。CPU访问高512KB时,设备没响应,导致系统挂死。嗯,从那以后我每次都会仔细核对BAR的大小和实际解码范围。
对于Switch来说,配置空间稍微复杂一些。Switch的每个端口都有自己的配置空间,而且上行端口和下行端口的Header Type不同。上行端口是Type 1(桥),下行端口也是Type 1。Type 1的配置空间里包含Primary Bus Number、Secondary Bus Number、Subordinate Bus Number,这些是Switch做总线号路由的关键。
咱们后面讲非透明桥的时候,会频繁操作这些配置空间寄存器。所以现在打好基础,后面才能跟得上。
小结
这一章咱们回顾了PCIe的基础知识:分层模型、拓扑结构、配置空间和BAR空间。这些东西看起来简单,但都是后面非透明桥技术的基石。
我个人觉得,学PCIe最好的方法就是「动手」。找个带PCIe的开发板,用lspci命令看看设备的配置空间,用devmem2工具读写BAR空间,比看十遍手册都管用。
好,第一章就到这里。下一章咱们开始进入正题——非透明桥的原理和应用场景。
公众号:蓝海资料掘金营,微信deep3321