第2章 PCIe协议基础回顾:TLP、DLLP与Flow Control
做PCIe驱动,说白了就是在跟这三层协议打交道:事务层、数据链路层、物理层。很多初学者一上来就啃Spec,结果被各种TLP类型搞晕。我个人习惯是先把这三层的职责理清楚,再深入细节。
这一章我们重点回顾事务层的Memory Read/Write TLP、Completion TLP,以及数据链路层的Flow Control机制。这些是驱动开发中最常打交道的部分。
2.1 PCIe协议栈的三层架构
PCIe采用分层架构,从下往上依次是:
- 物理层(PHY):负责比特传输、串并转换、链路训练。说白了就是管“怎么把0和1发出去”。
- 数据链路层(Data Link Layer):负责可靠传输、错误检测、Flow Control。它保证TLP能完整无误地到达对端。
- 事务层(Transaction Layer):负责TLP的组装与解析。驱动开发者主要跟这一层打交道。
嗯,这里要注意:驱动开发中你看到的寄存器、DMA描述符、中断,最终都会映射到事务层的TLP上。
核心观点:驱动工程师的日常工作,就是生成和解析TLP。理解TLP的结构,比背Spec更重要。
2.2 事务层核心:TLP(Transaction Layer Packet)
TLP是PCIe通信的基本单元。驱动发一个Memory Read,底层就会生成一个对应的TLP发出去。TLP的结构如下:
| 字段 | 长度 | 说明 |
|---|---|---|
| STP(起始) | 1 Byte | 帧起始标识 |
| TLP Prefix | 可选 | 扩展头,用于高级功能 |
| TLP Header | 12/16 Byte | 包含类型、长度、地址、Tag等 |
| Data Payload | 0~4096 Byte | 读写数据内容 |
| ECRC | 4 Byte | 端到端CRC校验(可选) |
| END(结束) | 1 Byte | 帧结束标识 |
我个人习惯把TLP Header看作“信封”,Data Payload看作“信纸”。驱动要做的就是填好信封,把信寄出去。
2.3 Memory Read/Write TLP
这是驱动开发中最常用的两种TLP类型。你想想看,读写BAR空间、配置DMA、访问设备寄存器,底层全是Memory Read/Write TLP。
2.3.1 Memory Write TLP(MWr)
MWr是“发出去就不管了”的类型。驱动写一个寄存器,底层生成MWr TLP发出去,不需要对方回复。好处是快,坏处是没有确认机制。
实战经验:我在调试FPGA加速卡时,发现写配置寄存器后读回来总是旧值。后来发现是MWr被PCIe桥缓存了,需要加Memory Barrier或者改用Read-Modify-Write模式。
2.3.2 Memory Read TLP(MRd)
MRd需要对方回复一个Completion TLP(CplD)。驱动发一个读请求,设备收到后把数据打包成CplD发回来。
这里有个关键点:MRd必须带Tag。Tag用来匹配请求和响应。同一个Requestor可以同时发出多个MRd,每个带不同的Tag,Completion回来时根据Tag找到对应的请求。
避坑指南:我曾经遇到过Tag耗尽导致读请求卡死的问题。RC(Root Complex)端Tag数量有限,如果驱动频繁发MRd而不及时回收Tag,就会触发Flow Control的信用不足,导致后续请求被阻塞。
2.4 Completion TLP(Cpl/CplD)
Completion是PCIe协议中“有来有回”的体现。只有Non-Posted请求(如MRd、IORd、CfgRd)才需要Completion。
Completion TLP有两种:
- Cpl(Completion without Data):用于I/O读完成或错误响应。
- CplD(Completion with Data):用于Memory Read完成,携带读取的数据。
Completion Header中有一个重要字段:Requester ID + Tag。这两个字段组合起来唯一标识一个请求。驱动在发送MRd时,必须记录Tag和对应的缓冲区地址,Completion回来时才能正确分发数据。
关键点:Completion的到达顺序不一定和请求顺序一致。PCIe允许乱序完成,驱动必须做好Tag到缓冲区的映射管理。
2.5 数据链路层与Flow Control机制
数据链路层有两个核心职责:保证TLP可靠传输,以及管理Flow Control。
2.5.1 DLLP(Data Link Layer Packet)
DLLP是数据链路层的“内部通信”包,对事务层透明。常见的DLLP类型:
| DLLP类型 | 作用 |
|---|---|
| Ack | 确认TLP已正确接收 |
| Nak | 拒绝TLP(CRC错误),请求重传 |
| InitFC1/InitFC2 | 初始化Flow Control信用值 |
| UpdateFC | 更新Flow Control信用值 |
嗯,这里要注意:驱动开发者通常不需要直接处理DLLP,但理解Flow Control机制对性能调优至关重要。
2.5.2 Flow Control(流量控制)
Flow Control是PCIe防止接收端缓冲区溢出的机制。每个接收端(RC或EP)都会向对端通告自己的缓冲区剩余容量,用“信用(Credit)”来表示。
Flow Control分为两类:
- Posted Credit(P):用于MWr、Message等不需要Completion的请求。
- Non-Posted Credit(NP):用于MRd、IORd等需要Completion的请求。
- Completion Credit(Cpl):用于Cpl/CplD。
发送端在发出TLP前,必须检查对应的Credit是否足够。如果不够,TLP会被阻塞在发送队列中,直到收到对端发送的UpdateFC DLLP更新信用值。
调优经验:我在做NVMe SSD驱动时,发现顺序读性能上不去。抓PCIe Trace发现大量MRd因为NP Credit不足被阻塞。后来调整了驱动中未完成读请求的数量上限,让Credit利用率更均衡,性能提升了15%。
2.6 本章小结
这一章我们回顾了PCIe协议栈的三层架构,重点讲了事务层的TLP类型和数据链路层的Flow Control机制。记住几个关键点:
- Memory Write是Posted请求,不需要回复;Memory Read是Non-Posted请求,必须等Completion。
- Tag是匹配请求和响应的关键,驱动必须管理好Tag池。
- Flow Control的Credit机制决定了你能同时发出多少个未完成的请求,直接影响吞吐量。