2. PCIe体系结构:三层次架构的拆解与落地
说实话,很多初学者一上来就盯着PCIe的寄存器或者DMA描述符猛啃,结果越学越乱。我个人习惯是先花点时间把PCIe的层次体系搞清楚。你只有理解了这三层各自干什么,出了问题才知道该查哪里。
PCIe的体系结构,简单说就是三层堆叠:事务层(Transaction Layer)、数据链路层(Data Link Layer)、物理层(Physical Layer)。每一层都有自己明确的职责,有点像公司里的前中后台:前台接单(事务层)、中台排期与核对(数据链路层)、后台发货走线(物理层)。
下面我按从上到下的顺序,逐层拆解。
2.1 总体层次:从软件请求到物理比特流
当你的驱动发起一次DMA读或者写请求时,数据并不是直接从内存飞到设备端的。它会经过一个完整的流水线处理。我画了一张图来帮你看清这个流程:
这张图要表达的逻辑很简单:数据发起端从上到下逐层打包,接收端从下到上逐层解包。每一层只关心自己那一层该怎么处理,这是一种非常好的分层解耦设计。
2.2 事务层:TLP 的装载与调度
事务层是离驱动最近的一层。我个人习惯把它理解为“订单中心”。当你的驱动要读一块设备内存时,事务层会把这件事封装成一个Memory Read Request TLP。
事务层主要干这几件事:
- TLP 类型识别:是内存读写(Memory)、IO读写(IO)、配置空间访问(Configuration)还是消息(Message)?
- 事务描述符生成:包含 Requester ID、Tag、Address、Length 等信息。
- 流量分类与排序:PCIe 有严格的流量优先级(VC 概念),防止低优先级请求堵塞高优先级事务。
| TLP 类型 | 用途场景 | 我见过的问题 |
|---|---|---|
| Memory Read (MRd) | DMA 读操作的核心包 | 曾经遇到读取地址返回全0,发现是TLP地址没对齐 |
| Memory Write (MWr) | DMA 写操作的核心包 | 写缓存未刷新导致数据丢失 |
| Completion (CplD) | 读操作的返回数据包 | Completer ID 不匹配导致超时 |
| Configuration (CfgRd/Wr) | 枚举与初始化阶段使用 | 配置空间长度字段填写错误导致挂死 |
2.3 数据链路层:可靠性的守护者
数据链路层主要做两件事:差错检测和流量控制。它负责把事务层打包好的TLP交给物理层发送,同时确保对端能正确收到。
具体来说:
- CRC 计算与校验:每个TLP都带一个32位的LCRC(链路层CRC),接收端数据链路层收到后重新算一遍,不匹配则会请求重传。
- ACK/NAK 重传协议:发送端会在内部缓冲一份拷贝,收到ACK后才丢弃;收到NAK则立刻重发。这里有一个关键点——重传缓冲区大小是有限的。
- 初始化与链路训练状态机:数据链路层也参与了链路初始化的状态转换,但那是物理层先做完 的底层握手之后的事。
你想想看,为什么 PCIe 能保证数据不丢?就是靠数据链路层这一套 ACK/NAK 机制。我在写驱动时曾遇到一个坑:数据链路层的重传缓冲区被撑满,导致发送端卡死。原因是我在一次 DMA 中连续发送了大量小包,但接收端处理太慢,ACK 迟迟不回来。
2.4 物理层:真正的比特搬运工
物理层,说白了就是那些电信号的事。PCIe 的物理层分为两个子层:
- 逻辑子层:负责字节拆包/打包、8b/10b 或 128b/130b 编码(看是哪一代)、加扰(Scrambling)、Ordered Set(SKP、COM等)插入。
- 电气子层:负责差分信号驱动、接收均衡、时钟恢复。这些东西跟驱动代码关系不大,但你要知道:物理层的链路速度(Gen1/Gen2/Gen3)和通道数(x1/x2/x4/x8/x16)是在 Link Training & Status State Machine (LTSSM) 中协商出来的。
我记得早期在一个项目上,用了很长的 PCIe 插槽转接线,结果设备一直工作在 Gen1 速度。当时我第一反应是驱动代码写错了,查了半天。后来发现是物理层的信号完整性太差,LTSSM 协商过程反复降速到 Gen1 才能稳定。嗯,这个坑给我的教训是:软件能做的事情有限,物理层的问题最终得回到硬件设计上解决。
2.5 三层之间的协作:一个完整的 DMA 写请求示例
假设你的驱动程序发起一个 256 字节的 DMA 写,目标是设备的内存空间。看看这三层是如何协作的:
- 驱动层:配置 DMA 引擎,发起写请求。
- 事务层:封装一个 Memory Write 64 TLP,包含目标地址(64-bit)、大小(256字节)、Requester ID(你的设备 BDF)、Tag(事务标签)。
- 数据链路层:在这个 TLP 前面加上 Sequence Number,末尾计算 LCRC。然后将整个包加入重传缓冲,准备发送。
- 物理层:将数据链路层传来的包进行 128b/130b 编码(如果是 Gen3+),加扰,然后串行化成差分信号,从 Tx 引脚发送出去。
- 接收端物理层:收到信号,解串,解码,去扰,恢复出数据包,交给接收端数据链路层。
- 接收端数据链路层:校验 LCRC,检查 Sequence Number 是否连续。通过后,发送 ACK DLLP 回去,同时把 TLP 递给接收端事务层。
- 接收端事务层:解析 TLP 类型,将数据写入目标内存地址。
好了,这就是 PCIe 三层次体系结构的大致模样。你如果能把这三层的职责边界和协作关系记在脑子里,后面学 DMA 引擎、中断处理、性能调优时会少走很多弯路。这部分内容我就讲到这里,记住:分层是 PCIe 强大能力的基础,也是调试时的第一把钥匙。