PCIe学习(四)
3 数据链路层
数据链路层是PCIe协议的中间一层,起着承上启下的作用。
3.1 DLLP
本节分析一下数据链路层的数据包(Data Link Layer Packet,DLLP)。
DLLP只用于链路两端的数据链路层通信。DLLP的主要用途是TLP流量控制,链路初始化,电源管理,事务层与物理层之间信息传递等。与TLP不同的是,DLLP不需要路由,只是在一条链路的两端传递。
DLLP的大小固定,只有6-Byte的有效信息。其中Byte-0是DLLP类型编码,Byte-1/2/3根据DLLP不同类型有不同含义,Byte-4/5是16-bit的CRC。
Gen 5中的DLLP类型编码整理如下表:
Encoding** | DLLP Type** |
0000 0000 | Ack |
0000 0001 | MRInit |
0000 0010 | Data_Link_Feature |
0001 0000 | Nak |
0010 0001 | PM_Enter_L1 |
0010 0010 | PM_Enter_L23 |
0010 0011 | PM_Active_State_Request_L1 |
0010 0100 | PM_Request_Ack |
0011 0000 | Vendor-specific |
0011 0001 | NOP |
0100 0v2v1v 0 | InitFC1-P |
0101 0v2v1v0 | InitFC1-NP |
0110 0v2v1v0 | InitFC1-Cpl |
0111 0v2v1v0 | MRInitFC1 |
1100 0v2v1v0 | InitFC2-P |
1101 0v2v1v0 | InitFC2-NP |
1110 0v2v1v0 | InitFC2-Cpl |
1111 0v2v1v0 | MRInitFC2 |
1000 0v2v1v0 | UpdateFC-P |
1001 0v2v1v0 | UpdateFC-NP |
1010 0v2v1v0 | UpdateFC-Cpl |
1011 0v2v1v0 | MRUpdateFC |
All other encodings | Reserved |
表中带有MR的DLLP都是为多根虚拟化(MR-IOV)准备的,暂且忽略。
Ack/Nak DLLP格式
Ack/Nak DLLP用于数据链路层的Ack/Nak协议。
AckNak_Seq_Num字段指示哪些TLP被影响,成功(Ack)或者不成功(Nak)。下一节详细分析数据链路层的Ack/Nak协议。
NOP DLLP格式
接收端接收到NOP,检查CRC,然后放弃该DLLP,不做任何动作。
InitFC1/InitFC2/UpdateFC格式
InitFC1/InitFC2用于流量控制的初始化;UpdateFC用于更新流量控制的信用值。
Byte-0的高4-bit指示是哪种子缓冲区。
DataFC字段指示事务层中Data缓冲区的信用值,HdrFC指示Header缓冲区的信用值。
DataScale和HdrScale字段分别是Data和Header缓冲区信用值放大系数。
V[2:0]字段是虚拟通道(VC)的ID。
各字段具体含义可参考流量控制一节。
PM DLLP格式
PM DLLP是一组DLLP的统称,用于电源管理。Byte-0的最第三位是不同的编码,目前Gen 5中的PM DLLP包括:PM_Enter_L1,PM_Enter_L23,PM_Active_State_Request_L1,PM_Request_Ack。
具体含义留到电源管理章节分析。
Vendor-specific DLLP格式
Vendor-specific DLLP的Byte-1/2/3定义取决于产商。
Data Link Feature DLLP格式
Feature Support字段指示发送端口支持的特性,该值与其Data Link Feature Capability寄存器对应。
3.2 ACK/NAK协议
3.2.1 Ack/Nak协议原理
数据链路层的一个重要职责是保证TLP正确传输。尤其是Posted类型的事务,请求者发出事务后就认为事务结束,但完成者是否真的接收到了事务吗?
数据链路层在向物理层传输TLP之前,给TLP前面加上序列号(Sequence Number),后面加上LCRC。序列号是连续递增的,每个TLP分配一个。有了序列号和LCRC,链路两端的数据链路层可以使用Ack/Nak协议来保证链路上的传输。
发送端为每个TLP加上序列号和LCRC,同时将要发送的数据包存储在本地(数据链路层)的重试缓冲区(Replay Buffer)。接收端解析收到的数据包,首先计算LCRC,如果没发现有错误,向发送端发送Ack DLLP,如果有错误,向发送端发送Nak DLLP。不管是Ack DLLP还是Nak DLLP,其中都包含有序列号。
这里需要注意,接收端并不是对每个接收到的TLP都会返回Ack/Nak DLLP,为了节省链路资源,通常是是攒齐了几个再发Ack/Nak DLLP。发送端在接到Ack DLLP后,得到其中的序列号,知道此序列号之前的TLP都已经被正确接收,因此将本地Replay Buffer中保存的这些TLP都清除掉;如果是接收到Nak DLLP,说明此DLLP中的序列号之后的TLP中,至少有一个发生传输错误,因此需要将序列号之后的TLP全部重新传输一次。
上图是Ack/Nak DLLP的格式,Byte-0是Ack/Nak的编码。Byte-2和3是序列号字段,共有12-bit,因此序列号的最大数值是4095。初始化后,序列号计数器从0开始递增,到了4095后,下一个序列号是0,也就是说序列号的数值是回滚(Roll-back)式的。所以,对于发送端来说,有可能发生上一次正确接收的序列号是4093,下一个Ack/Nak返回的序列号是2的这种情况。
LCRC是32-bit,对序列号和整个TLP进行校验。DLLP 的CRC是16-bit,是对DLLP的校验。不要混淆了两个CRC。
为了实现Ack/Nak机制,PCIe设备需要在数据链路层实现相关的逻辑设计。
3.2.2 发送端设计
对于发送端,需要实现:
- 12-bit的NEXT_TRANSMIT_SEQ计数器。此计数器生成将分配给下一个传入TLP的序列号。在系统复位期间,此计数器值复位成0,随后在系统正常工作期间递增,到了4095后回滚到0,继续递增
- LCRC生成器,用于产生32-bit的CRC,对序列号和TLP进行校验
- Replay Buffer,此缓冲区按照传输顺序存储TLP,包括序列号和LCRC。当发送端接收到Ack时,它从重试缓冲器中清除序列号等于或早于Ack中编号的TLP。通过这种方式,该设计允许一个Ack代表几个成功的TLP,从而减少了必须发送的Ack的数量。如果接收到Nak,在Nak中的序列号仍然指示接收到的最后一个成功传输的数据包。因此,即使接收到Nak也会导致发射端的Replay Buffer中清除TLP,即Nak DLLP中的序列号之前的TLP(包含该序列号指示的TLP)都会从Replay Buffer中清除,而序列号之后的TLP会被重新传输
- REPLAY_TIMER计数器,代表的是计时器。如果超时,说明发送端发送了一个或多个TLP,但是在预期的时间内没有得到Ack或者Nak。超时会触发Replay Buffer中的所有内容被重新传输,并重置此计数器
- 2-bit的REPLAY_NUM,用于跟踪接收到Nak或REPLAY_TIMER超时后的重试次数。当REPLAY_NUM计数从11b回滚到00b(表示4次尝试传递同一组TLP失败)时,数据链路层自动强制物理层重新训练链路(LTSSM进入恢复状态)
- 12-bit的ACKD_SEQ寄存器,用于保存最近一次接收到的Ack/Nak DLLP中的序列号
- DLLP CRC检查模块,校验接送端返回的Ack/Nak DLLP中的CRC值
对于接收端,需要实现:
- LCRC检查模块,校验TLP的LCRC值
- 12-bit的NEXT_RCV_SEQ计数器,用于跟踪预期序列号,验证顺序数据包接收。在系统复位时或数据链路层处于非活动状态时,它被初始化为0,并且对于转发到事务层的每个良好TLP,它会递增一次
- 序列号检查模块
- NAK_SCHEDULED标志,当接收端准备返回Nak给发送端时,置高该标志位,当接收端成功的接收到TLP时,清除该标志位
- AckNak_LATENCY_TIMER,每当接收端成功接收到尚未确认的TLP时,此计时器就会运行。一旦计时到期,接收端就需要发送一个Ack或Nak。
- Ack/Nak生成器,对正常接收的TLP产生Ack,对失败的TLP产生Nak。
实际上,PCIe是双单工传输,从PCIe组件角度看需要实现上述的全部逻辑。
到这里,我们总结一下PCIe链路上的数据包,有TLP/DLLP/Ordered Sets三种,Ordered Sets是在物理层,后面再讲。PCIe协议建议的处理优先级如下(从高到底):
- Completion of any TLP or DLLP currently in progress (highest priority)
- Ordered Sets
- Nak DLLP
- Ack DLLP
- Flow Control
- Replay Buffer re‐transmissions
- TLPs that are waiting in the Transaction Layer
- All other DLLP transmissions (lowest priority)
3.2.5 直通模式
最后,提一下Switch的直通模式(Cut-Through Mode)。对于Switch,只是负责转发TLP,正常做法是Switch的TLP从入口(Ingress)进入Switch,Switch检查接收到的TLP,如果没有传输问题,则向出口(Egress)转发TLP。如果TLP的数据载荷很大,这种方式无疑会增加延迟。另外一种选择是,Switch很快从TLP Header中得到路由信息(PCIe是串行总线,TLP Header先于Data传输),接下来Switch可以假设数据包是好的,直接向出口转发。这种方式就是Switch直通模式。
不过直通模式存在一个问题,如果随后在Switch入口端口发生了传输错误,入口可以发送Nak要求重新传输。但是对于出口端口,该如何处理?不能简单粗暴的清除出口的Replay Buffer,因为出口端口的链路对端还在等待继续接收。这时出口需要向对端指出该TLP“失效(nullified)”。失效数据包以EDB符号而不是END符号终止,TLP的32位LCRC从原始计算值反转(1的补码)。接收到无效数据包的接收端则会直接丢弃该数据包,不做任何处理。
下面是一个Switch直通的示例,TLP传输方向从左向右。
- 一个TLP发送到Switch的Ingress端口,该TLP在传输过程中被损坏,但是现在还不知道
- Switch解析TLP Header,向Egress端口转发
- Switch接收到完整的TLP,通过LCRC发现传输错误,向上游发送Nak
- 在Switch的Egress端口,Switch用EDB替换坏TLP末端的END帧符号,并反转计算的LCRC值。TLP现在变成“失效”,Switch将其从Replay Buffer中丢弃。
- Switch下游的EP检测到EDB符号和反转的LCRC,知道这是一个无效数据包,直接丢弃数据包。注意,EP不会返回Nak。
【待续】