第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更重要。

PCIe三层协议栈架构 PCIe 协议栈三层架构 事务层(Transaction Layer) TLP 组装/解析 | 事务排序 | 流量分类(VC) 数据链路层(Data Link Layer) DLLP | Ack/Nak | CRC校验 | Flow Control 物理层(PHY) 串并转换 | 链路训练 | 电气特性 驱动开发重点:事务层 → 数据链路层(Flow Control)→ 物理层(链路状态) TLP 是驱动与硬件通信的“语言”,DLLP 是保证语言不失真的“协议”

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%。

PCIe Flow Control 信用机制 Flow Control 信用机制流程 发送端(Requester) TLP发送队列 Credit计数器 TLP 接收端(Completer) 接收缓冲区 可用Credit UpdateFC DLLP(信用更新) Flow Control 关键规则 1. 发送前必须检查对应类型的Credit是否充足 2. Credit不足时TLP在发送队列中等待,不会丢失 3. 接收端处理完TLP后,通过UpdateFC通告新的可用Credit 4. 三种Credit独立管理:Posted / Non-Posted / Completion

2.6 本章小结

这一章我们回顾了PCIe协议栈的三层架构,重点讲了事务层的TLP类型和数据链路层的Flow Control机制。记住几个关键点:

  • Memory Write是Posted请求,不需要回复;Memory Read是Non-Posted请求,必须等Completion。
  • Tag是匹配请求和响应的关键,驱动必须管理好Tag池。
  • Flow Control的Credit机制决定了你能同时发出多少个未完成的请求,直接影响吞吐量。