【摘要】:发送端使用计数器NEXT_TRANSMIT_SEQ的当前值设置TLP的Sequence号,该计数器的初始值为0。发送端为处理来自接收端的ACK/NAK DLLP,设置了一个12位的计数器ACKD_SEQ。这个计数器记载最近接收到的ACK/NAK DLLP的AckNak_Seq_Num字段。发送端将TLP发送出去之后,将等待来自接收端的应答,接收端使用ACK/NAK DLLP发送这个应答。接收端收到这些报文后将发送ACK DLLP作为回应,其详细步骤如下。此时发送端的NEXT_TRANSMIT_SEQ计数器为8,表示即将填入到Replay Buffer中的报文序列号为8。
数据链路层在发送TLP之前,发送端首先需要将TLP进行封装,加上Sequence前缀和LCRC后缀,之后再将这个TLP放入Replay Buffer中。发送端设置了一个12位的计数器NEXT_TRANSMIT_SEQ,这个计数器的初始值为0,当数据链路层处于DL_Inactive状态时,该计数器将保持为0。为简化起见,本节只讲述数据链路层处于DL_Active状态时的情况,而不讲述处于DL_Inactive状态时的情况。
发送端使用计数器NEXT_TRANSMIT_SEQ的当前值设置TLP的Sequence号,该计数器的初始值为0。PCIe设备每发送完毕一个TLP,这个计数器将加1,直到该计数器的值为4095(NEXT_TRANSMIT_SEQ的最大值)。当计数器的值为4095后,再进行加1操作时,该计数器将回归为0。而LCRC是根据TLP的内容计算出来的,用来保证数据传递的完整性,本节不介绍LCRC的计算过程。对此有兴趣的读者请参考PCIe总线规范。
与此对应,接收端也设置了一个12位的计数器NEXT_RCV_SEQ。这个计数器记录接收端即将接收的TLP的Sequence号。这个计数器的初始值为0,当数据链路层处于DL_Inactive状态时,该计数器保持为0。在正常情况下,到达接收端的TLP,其Sequence号和这个计数器中的内容一致。当接收端将这个TLP转发到事务层后,这个计数器将加1,当计数器的值为4095后,再进行加1操作时,该计数器将回归为0。如果到达接收端的TLP,其序号与这个计数器中的值不一致时,接收端需要进行特殊处理,详见下文。
发送端为处理来自接收端的ACK/NAK DLLP,设置了一个12位的计数器ACKD_SEQ。这个计数器记载最近接收到的ACK/NAK DLLP的AckNak_Seq_Num字段。这个计数器的初始值为全1,当数据链路层处于Inactive状态时,该计数器保持为全1。发送端收到ACK/NAK DLLP后,将使用这些DLLP中的AckNak_Seq_Num字段更新ACKD_SEQ计数器。
如果(NEXT_TRANSMIT_SEQ-ACKD_SEQ)mod 4096>=2048时,发送端将不会从事务层继续接收新的TLP,因为此时发送端已经发送了许多TLP,但是接收端可能并没有成功接收这些TLP,因此并没有及时发送ACK/NAK DLLP作为回应。在多数情况下,当PCIe链路出现了某些问题时,才可能导致该公式成立。此外ACKD_SEQ计数器还可以帮助发送端重发错误的TLP,下文将详细解释这个功能。
发送端首先将从事务层获得的TLP存放到Replay Buffer中,在Relpay Buffer中可以存放多个TLP,这个Replay Buffer为发送端使用的发送窗口。PCIe总线并没有规定在Replay Buffer中存放TLP的个数,不同的设计可以采用的大小不同,其中有一个重要的原则就是不能使Replay Buffer成为整个设计的瓶颈,Replay Buffer应该始终保证有足够的空间接收来自事务层的报文。
TLP进入Replay Buffer之后,发送端首先将这个TLP封装,然后从Replay Buffer中发送到物理层,最终达到接收端。发送端将TLP发送出去之后,将等待来自接收端的应答,接收端使用ACK/NAK DLLP发送这个应答。发送端根据应答结果决定是将TLP从Replay Buff-er中清除,还是重发在Replay Buffer中的TLP。下文将以几个实例说明发送端如何处理来自接收端的应答。
1.发送端收到ACK DLLP报文
如图7-6所示,假设发送端从Replay Buffer中向接收端发送Sequence号为3~7的报文。接收端收到这些报文后将发送ACK DLLP作为回应,其详细步骤如下。

图7-6 发送端收到ACK DLLP
(1)发送端向接收端发送TLP3~7,其中TLP3是第一个报文,TLP7是最后一个报文。此时发送端的NEXT_TRANSMIT_SEQ计数器为8,表示即将填入到Replay Buffer中的报文序列号为8。
(2)接收端按序收到TLP3~5,而TLP6和7仍在传送过程中。接收端的NEXT_RCV_SEQ计数器为6,表示即将接收的报文序列号为6。
(3)接收端通过报文检查决定接收TLP3~5,然后发送ACK DLLP,此时这个ACK DLLP的AckNak_Seq_Num字段为5。为了提高总线的利用率,接收端不会为每一个接收到的TLP都做出应答。在这个例子中,AckNak_Seq_Num字段为5表示TLP3~5都已经被接收。
(4)发送端收到AckNak_Seq_Num字段为5的ACK DLLP后,得知TLP3~5都被成功接收。此时发送端将TLP3~5从Replay Buffer中清除。
(5)接收端陆续收到TLP6~7后,接收端的NEXT_RCV_SEQ计数器为8,表示即将接收的报文序列号为8。然后接收端向发送端发送ACK DLLP,这个DLLP的AckNak_Seq_Num字段为7,即为NEXT_RCV_SEQ-1。
(6)发送端收到AckNak_Seq_Num字段为7的ACK DLLP后,得知TLP6~7都被成功接收。此时发送端将TLP6~7从Replay Buffer中清除。
2.发送端收到NAK DLLP报文
如图7-7所示,假设发送端从Replay Buffer中向接收端发送Sequence号为3~7的报文。接收端收到这些报文后,发现有错误的TLP,此时将发送NAK DLLP而不是ACK DLLP,其详细步骤如下。

图7-7 发送端收到NAK DLLP
(1)发送端向接收端发送TLP3~7,其中TLP3是第一个报文,而TLP7是最后一个报文。(www.chuimin.cn)
(2)接收端按序收到TLP3~5,而TLP6和7仍在传送过程中。
(3)接收端通过报文检查决定接收TLP3~4,此时NEXT_RCV_SEQ为5,表示即将接收TLP5。
(4)TLP5没有通过完整性验证,此时接收端将向对端发送NAK DLLP,这个DLLP的AckNak_Seq_Num字段为4,即为NEXT_RCV_SEQ-1。AckNak_Seq_Num字段为4表示接收端最后一个接收正确的TLP,其Sequence号为4。
(5)发送端收到AckNak_Seq_Num字段为4的NAK DLLP后,得知TLP3~4已被成功接收。此时发送端首先停止从事务层接收新的TLP,之后将TLP3~4从Replay Buffer中清除。
(6)发送端重新发送在Replay Buffer中从TLP5开始的报文。在这个例子中,发送端将重新发送TLP5~7。
发送端每一次收到NAK DLLP后,都将重发在Replay Buffer中剩余的TLP。但是发送端不能无限次重发同一个TLP,因为出现这种情况意味着链路出现了某些问题,必须修复这些问题后,才能继续重发这些TLP。为此在发送端中设置了一个2位计数器REPLAY_NUM,这个计数器的初始值为0,当数据链路层处于Inactive状态时,该计数器保持为0。
REPLAY_NUM计数器按照以下几个原则进行更新。
●当发送端第一次收到NAK DLLP后,REPLAY_NUM计数器将加1,此时ACKD_SEQ计数器被赋值为这个NAK DLLP的AckNak_Seq_Num字段。之后当发送端收到新的ACK/NAK DLLP,而且其AckNak_Seq_Num字段大于ACKD_SEQ计数器的值时(表示发送端至少重传成功一个TLP),REPLAY_NUM计数器将被重置为0,ACKD_SEQ计数器的值也更新为相应的值。
●PCIe总线规定发送端新收到的ACK/NAK DLLP,其AckNak_Seq_Num字段不能小于ACKD_SEQ计数器,如果出现这种问题,将是芯片的Bug。
●如果新的ACK/NAK DLLP,其AckNak_Seq_Num字段值等于ACKD_SEQ计数器的值时,表示发送端正在反复地重新发送同一个TLP,此时REPLAY_NUM计数器将加1,
当这个计数器溢出时,发送端将不再重复发送这个TLP,而是重新进行链路训练,当PCIe链路恢复正常后,再重新发送这个TLP。
发送端除了设置REPLAY_NUM计数器,判断PCIe链路可能出现的故障之外,还设置了另一个计数器REPLAY_TIMER,进一步识别PCIe链路可能出现的故障。因为使用ACKD_SEQ计数器判断链路故障的基础是发送端可以收到ACK/NAK DLLP。但是在某种情况下,发送端虽然发送了TLP,但是接收端没有回应ACK/NAK DLLP,或者由于PCIe链路故障发送端没有收到这个回应。
此时发送端需要使用REPLAY_TIMER计数器,以判断在TLP的传送过程中是否出现异常。REPLAY_TIMER计数器记载一个TLP报文从发送到获得ACK/NAK DLLP回应的时间,当这个时间过长时,发送端认为PCIe链路出现故障。REPLAY_TIMER计数器的更新规则如下所示。
(1)REPLAY_TIMER计数器的初始值为0,在发送端发出或者重新发出一个TLP后以一个固定的时钟频率开始计数,当REPLAY_TIMER计数器到达设定的阈值时,将认为PCIe链路出现故障,此时PCIe设备将进行PCIe链路的恢复工作。如果发送端可以正常收到ACK/NAK DLLP时,该计数器不会溢出。
(2)发送端收到ACK DLLP,而且在Replay Buffer中没有“已经发送而且尚未被确认的TLP”后,REPLAY_TIMER计数器被重置并重新开始计数。在正常情况下,发送端每发送一个TLP后,REPLAY_TIMER计数器将被重置并重新计数,但是有时在Replay Buffer中存在一些TLP,这些TLP已经被发送出去,但是并没有收到相应的ACK DLLP,此时REPLAY_TIMER计数器不能被重置。
(3)当Replay Buffer中没有任何TLP时,REPLAY_TIMER计数器将被重置而且其值保持不变。
(4)发送端收到NAK DLLP之后,REPLAY_TIMER计数器将被重置而且其值保持不变。当数据链路层重新发送TLP时,REPLAY_TIMER计数器才重新开始计数。
(5)REPLAY_TIMER计数器到达设定的阈值后,该计数器的值将被重置,且保持不变。此时PCIe链路可能出现某种异常,当这些异常被处理完毕后,REPLAY_TIMER计数器才可能重新计数。
(6)PCIe链路重新训练时,REPLAY_TIMER计数器的值保持不变。准确地说,PCIe链路处于Recovery或者Configuration状态时,REPLAY_TIMER计数器的值保持不变。有关Re-covery或者Configuration状态的详细说明见第8.2节。
PCIe总线规范提供了计算REPLAY_TIMER计数器阈值的经验公式,这个阈值和PCIe设备和主桥提供的Max_Payload_Size成正比,与PCIe链路宽度成反比,本节对此公式不进行详细说明。值得注意的是,这个经验公式是基于x86处理器计算得出的,不同的处理器在此处的实现不尽相同。
相关推荐