【摘要】:如果CRC检查成功,接收端将根据接收缓冲的阈值发送ACK DLLP给发送端,并将这个TLP传给事务层。如果接收端决定发送ACK DLLP,则该ACK DLLP的AckNak_Seq_Num字段为NEXT_RCV_SEQ-1,即已经正确接收TLP的Sequence号。为此接收端使用了一个ACKNAK_LATENCY_TIMER计数器,当这个计数器超时或者接收的报文数超过一个阈值后,向发送端发送一个ACK DLLP回应。这样接收端只为已经进入Receive Buffer中的TLP发出ACK/NAK回应。
接收端首先从物理层获得TLP,此时在这个TLP中包含Sequence号前缀和LCRC后缀。接收端收到这个TLP后,首先将这个报文放入Receive Buffer中,然后进行CRC检查。如果CRC检查成功,接收端将根据接收缓冲的阈值发送ACK DLLP给发送端,并将这个TLP传给事务层。除了CRC校验外,接收端还需要做其他检查,本节对此不进行介绍。
1.接收端发送ACK DLLP
当接收端收到的TLP没有出现LCRC错误,而且TLP的Sequence号和NEXT_RCV_SEQ计数器的值相同时,接收端将正确接收这个TLP,并将其转发给事务层,随后接收端将NEXT_RCV_SEQ计数器加1。
接收端根据具体情况,决定向发送端立即发送ACK DLLP,还是等待接收到更多的TLP后再发送ACK DLLP。如果接收端决定发送ACK DLLP,则该ACK DLLP的AckNak_Seq_Num字段为NEXT_RCV_SEQ-1,即已经正确接收TLP的Sequence号。
接收端不会对每一个正确接收的TLP发出ACK DLLP回应,因为这样将严重影响PCIe总线链路的使用效率,而是收集一定数量的TLP后,统一发出一个ACK DLLP回应表示之前的TLP都已正确接收。
为此接收端使用了一个ACKNAK_LATENCY_TIMER计数器,当这个计数器超时或者接收的报文数超过一个阈值后,向发送端发送一个ACK DLLP回应。此时这个ACK DLLP的AckNak_Seq_Num字段为在这段时间以来,最后一个被正确接收的TLP的Sequence号。不同的设计在此处的实现不尽相同。但是这些实现都要遵循以下两个原则。
(1)接收端在收到一定数量的报文后,统一发送一个ACK DLLP做为回应。
(2)接收端收到的报文虽然没有到达阈值,但是ACKNAK_LATENCY_TIMER计数器超时后,仍然要发出ACK DLLP作为回应。在某些情况下,发送端可能在发送一个TLP后,在很长一段时间内,都不会发送新的TLP,此时接收端必须及时给出ACK DLLP回应,以免发送端的REPLAY_TIMER计数器溢出。
下面将以一个实例说明接收端如何发送ACK DLLP回应,该实例如图7-8所示,其描述如下所示。

图7-8 接收端发送ACK DLLP
(1)发送端发送TLP3~7给接收端,其中TLP3是第一个报文,而TLP7是最后一个报文。
(2)接收端按序收到TLP3~5,而TLP6和7仍在传送过程中。此时NEXT_RCV_SEQ的值被更新为6,表示下一个即将接收的TLP,其Sequence号为6。
(3)接收端通过报文检查决定接收TLP3~5,然后发送ACK DLLP,这个DLLP的Ack-Nak_Seq_Num字段为5。为了提高总线的利用率,接收端不会对每一个接收到的TLP都做出应答。在这个例子中,AckNak_Seq_Num字段为5表示TLP3~5都已经被接收。
(4)接收端将接收到的TLP3~5传递给事务层。
(5)接收端陆续收到TLP6~7后,继续执行步骤3~4。
2.接收端发送NAK DLLP
接收端设置了一个NAK_SCHEDULED位,该位用来判断接收端如何发送NAK DLLP,该位的初始值为0。当接收端接收TLP出现错误时,将该位置为1;当出现“错误的TLP”被重新接收成功后,该位被置为0。
如果接收端发现TLP的CRC错误后,将丢弃这个TLP,并发送NAK DLLP,这个DLLP的AckNak_Seq_Num字段为NEXT_RCV_SEQ-1,即最后一个正确接收TLP的Sequence号。此时NEXT_RCV_SEQ计算器将保持不变,NAK_SCHEDULED位将被置为1。
当这个NAK DLLP到达发送端之前,PCIe链路还有一些正在传送的TLP,这些报文将陆续到达接收端,这些报文的Sequence号都将大于NEXT_RCV_SEQ,此时接收端不会接收这些报文,而且接收端也不会为这些报文发送NAK DLLP,因为此时NAK_SCHEDULED位继续保持有效,即为1。
PCIe总线规范没有规定接收端如何拒收后续的TLP报文,较为合理的实现方式是这些后续的报文无需进入接收端的Receive Buffer就被拒绝。这样接收端只为已经进入Receive Buffer中的TLP发出ACK/NAK回应。(www.chuimin.cn)
如果接收端收到一个重试的TLP报文,其Sequence号与NEXT_RCV_SEQ相等,而且报文没有出现错误,接收端将NEXT_RCV_SEQ加1同时清除NAK_SCHEDULED位。此时表示重试的报文已经被正确接收。
如果接收端收到的TLP,其Sequence号小于NEXT_RCV_SEQ,这种情况通常是由于TLP传送过程中的延时,产生的重复TLP,此时接收端将丢弃这个报文,NEXT_RCV_SEQ计数器的值也不会改变,随后接收端将向对端发送ACK DLLP,这个DLLP的AckNak_Seq_Num为NEXT_RCV_SEQ-1。
[Ravi Budruk,Don Anderson and Tom Shanley]列举了一个实例说明在某种情况下,接收端将收到此类报文。接收端正确接收到TLP后,将发送ACK DLLP,但是由于链路故障,这个报文并没有到达发送端,此时已经发送的TLP将不会从发送端的Replay Buffer中清除,最终REPLAY_TIMER将溢出,此时发送端有可能重新进行链路训练,当链路恢复正常后,发送端将重新发送Replay Buffer中的所有TLP。在这种情况下,接收端将收到Sequence号比NEXT_RCV_SEQ小的TLP。
下面将使用一个实例进一步说明接收端如何发送NAK DLLP,该实例如图7-9所示,其描述如下所示。
(1)发送端向接收端发送TLP3~7,其中TLP3是第一个报文,而TLP7是最后一个报文。
(2)接收端按序收到TLP3~5,并将这些报文放入Receive Buffer,当然也可以在这些报文通过完整性检查后,再决定是否将这些TLP放入Receive Buffer中,而TLP6和7仍在传送过程中。
(3)接收端通过报文检查决定接收TLP3~4,此时NEXT_RCV_SEQ为5,表示即将接收TLP5。此时接收端将TLP3~4传递给事务层。

图7-9 接收端如何发送NAK DLLP
(4)而TLP5没有通过完整性验证,此时接收端将发送NAK DLLP,这个DLLP的Ack-Nak_Seq_Num字段为4,即NEXT_RCV_SEQ-1。AckNak_Seq_Num字段为4表示接收端最后一个正确接收的TLP,其Sequence号为4。此时接收端将设置NAK_SCHEDULED位为1,而NEXT_RCV_SEQ保持不变,即为5。
(5)接收端将丢弃TLP5。当TLP6~7到达时,接收端仍然丢弃这些报文,即便这些报文通过了完整性检查,因为这些报文的Sequence号大于NEXT_RCV_SEQ。接收端不会为TLP6~7发送NAK DLLP,因为此时NAK_SCHEDULED位有效。
(6)发送端收到NAK DLLP,其序号为4,此时发送端首先将TLP3~4从Replay Buffer中清除,因为TLP3~4已经被接收端正确接收,然后重新发送TLP5~7。
(7)接收端如果正确接收到TLP5时,发现其Sequence号与NEXT_RCV_SEQ相等,将清除NAK_SCHEDULED位。
(8)接收端陆续接收到TLP6~7,并根据CRC的检查结果决定发送ACK DLLP或者NAK DLLP。
在某些情况下,接收端发送的NAK DLLP可能并没有被发送端正确接收,因此接收端在很长一段时间内都不会得到“发送端重试的”TLP。此时接收端将会择时重发NAK DLLP,为此接收端设置了一个AckNak_LATENCY_TIMER计数器,当该计数器溢出时,接收端将重发NAK DLLP。该计数器的更新规则如下。
(1)当接收端发送ACK或者NAK DLLP时,该计数器重置并开始计数。
(2)接收端为“所有已接收的TLP”发送了ACK DLLP报文时,或者数据链路层状态为DL_Inactive时,该计数器被重置且保持为0。
AckNak_LATENCY_TIMER计数器的阈值是REPLAY_TIMER计数器阈值的1/3[31]。当接收端等待的时间超过AckNak_LATENCY_TIMER计数器的阈值后,接收端将重发一个ACKDLLP[32]。
当发送端发送若干个TLP之后,接收端将发送一个ACK DLLP作为回应。但是在某些情况下,发送端并没有收到接收端的ACK DLLP。此时接收端需要在AckNak_LATENCY_TIMER计数器溢出时,重新发送ACK DLLP。从而防止“因为发送端的REPLAY_TIMER计数器溢出”,重新进行PCIe链路训练,重发更多的TLP。
相关推荐