一、事务层概述
事务层在响应软件层的请求时,会生成出站数据包。同时,它也会检查入站数据包,并将其中包含的信息传递到软件层。事务层支持非发布事务的分割事务协议,能够将入站的完成数据包与之前传输的非发布请求相关联。该层处理的事务使用事务层数据包(TLPs,Transaction Layer Packets),这些事务可以分为四类请求:
- 内存(Memory)
- IO(Input/Output)
- 配置(Configuration)
- 消息(Messages)
前面三类请求在PCI和PCI-X中已经支持,而消息是PCIe中特有的一种请求类型。一个事务被定义为:包含一个向目标设备发送命令的请求数据包,和目标设备返回的任何完成数据包的组合。表2-2列出了不同的请求类型。
二、事务层数据包(Transaction Layer Packet,TLP)
2.1 non-posted与posted
在PCIe中,事务层通过这种分组方式有效地管理数据传输,并且能够处理来自不同源的各种请求。
请求可以分为两类,如表格右列所示:non-posted 和 posted 请求。对于non-posted请求,发送方(Requester)会发送一个数据包,接收方(Completer)需要生成一个完成数据包(Completion packet)作为回应。你可能会发现这与PCI-X继承的分割事务协议类似。例如,任何读取请求(Read)都是 non-posted 的,因为请求的数据需要在完成数据包中返回。可能让人意外的是,IO写入(IO Write)和配置写入(Configure Write)也是 non-posted 的,尽管这些请求在传输写入的数据时,它们仍然期望接收方返回一个完成数据包,确认写入的数据已经成功传输到目标设备并且没有发生错误。
与此相对的是,内存写入(Memory Write)和消息(Message)是 posted 请求,这意味着目标设备不会返回完成数据包给发送方。posted 事务可以提升性能,因为发送方不必等待回复,也不需要处理完成数据包带来的额外开销。权衡之下,posted 事务的缺点是发送方不会收到写入是否成功或遇到错误的反馈。这种行为从PCI继承而来,仍然被认为是一种合理的做法,因为发生故障的可能性较小,而性能提升显著。尽管 posted 写入不需要完成数据包,它们仍然会参与数据链路层(Data Link Layer)的Ack/Nak协议,该协议确保数据包可靠传输。
表2- 3列出了所有PCIe请求和完成数据包类型的列表。
2.2 TLP传输
TLPs 起始于发送方的事务层,终止于接收方的事务层,如图 2-15 所示。
当 TLP 途经发送方的数据链路层以及物理层时,这两层分别会向数据包中 添加一些信息,接收方的数据链路层和物理层会分别根据发送方对应层所添加的 信息来进行校验,以此确认数据包是否在链路传输中依旧保持正确没有出错。
2.3 TLP组包
TLP(Transaction Layer Packet)数据包的组装过程如图2-16所示。在TLP通过链路传输时,不同部分的数据包由各层添加。为了便于理解数据包的构建过程,各部分以不同颜色标示:红色代表事务层,蓝色代表数据链路层,绿色代表物理层。
Device Core 将 TLP 核心部分所需的信息发送到事务层。在事务层,每个TLP都会包含一个Header(头部),但是数据却不是必须的,例如读请求,就可以没有数据。事务层还可以选择添加 ECRC(End-to-End CRC)字段,这 个 ECRC 由事务层进行计算并附加在数据包的后面。ECRC 字段区域在通过发送方和接收方之间的任何服务点(service point,通常指的是 Switch 或者 Root 端口这些有 TLP 路由功能的地方)时都不改变,这使得目的端可以用它来验证在整个传输过程中都没有发生错误。
在传输过程中,TLP的核心部分被传递到数据链路层,该层负责添加序列号(Sequence Number)和另一种CRC字段,称为链路CRC(LCRC)。LCRC由邻近的接收端用来检测错误,并将检测结果报告给发送端。LCRC用于确保每个通过链路发送的数据包无误传输。
可能有人会疑问,如果LCRC已经验证了链路上的无误传输,为什么还需要ECRC呢?原因在于,数据包在路由设备内部的转发过程中可能会发生未被检查的错误。例如,数据包到达一个端口时会进行错误检查,然后根据路由信息发送到另一个端口,新的LCRC值会被重新计算并添加。而在端口间的内部转发过程中,错误不会被常规的PCIe协议检测到,因此ECRC在这种情况下非常有用。
最终,数据包被传递到物理层,在该层中添加其他字符,用于告知接收端接下来会发生什么。在PCIe的前两代中,这些字符是添加到数据包开头和结尾的控制字符。在第三代中,控制字符不再使用,但会在数据块中附加其他比特,用于传递关于数据包的必要信息。然后,数据包经过编码,并通过链路上的所有可用通道进行差分传输。
2.4 TLP解包
当接收端看到传入的TLP比特流时,它需要识别并移除之前添加的部分,以恢复发送端核心部分请求的原始信息。如图2-17所示,物理层首先验证TLP数据包中的起始和结束字符或其他字符是否正确,并移除它们,然后将剩余的TLP传递给数据链路层。数据链路层会首先检查链路CRC(LCRC)和序列号是否有错误。如果未发现错误,数据链路层会移除这些字段并将数据包传递给事务层。
如果接收端是一个交换机,事务层会评估数据包,从TLP的头部提取路由信息,确定数据包应该转发到哪个端口。即使交换机不是目标设备,它仍可以检查并报告ECRC错误,但它不能修改ECRC,以便目标设备也能检测到此类错误。如果目标设备具备检查ECRC的能力且ECRC功能已启用,它可以检测ECRC错误。
如果这是目标设备且没有发现错误,ECRC字段将被移除,剩余的头部和数据部分将被传递到软件层,供进一步处理。这样,接收端可以恢复并处理原始的事务请求或完成信息。
三、Non-Posted 事务
3.1 普通读(Ordinary Reads)
普通读取请求的过程如图2-18所示,展示了一个从终端设备(Endpoint)发送到系统内存的内存读取请求(MRd)。内存读取请求中的一个重要部分是目标地址。内存请求的地址可以是32位或64位,这个地址决定了数据包的路由。在此例子中,请求通过两个交换机进行路由,最终被转发到目标设备,这里目标是RC。当RC解码该请求并识别到数据包中的地址指向系统内存(memory)时,它会获取所请求的数据。
为了将数据返回给请求方,RC端口的事务层(Transaction Layer)会根据需要生成多个完成数据包(Completions,CplD),以便将所有请求的数据传递给请求方。PCIe中单个数据包的最大有效负载是4KB,但很多设备的设计使用的负载通常小于4KB,因此,如果请求的数据量较大,可能需要多个完成数据包才能返回所有数据。
完成数据包(Completions,CplD)也包含路由信息,用于将数据包返回给发起请求的设备。请求方在最初的请求中包含了一个“返回地址”,以便完成数据包能够准确送达。这个“返回地址”就是请求方的设备ID(Device ID),它由三个部分组成:系统中的PCI总线号、该总线上的设备号、以及该设备内的功能号,统称为BDF(Bus, Device, Function)。这个BDF信息就是完成数据包在返回时使用的路由信息。
如同在PCI-X中一样,请求方可能同时处理多个分割事务,因此必须能够将收到的完成数据包与相应的请求关联起来。为此,原始请求中添加了一个唯一标识的值,称为Tag。这个Tag在每个请求中都是独特的,完成方会将该Tag复制到完成数据包中,使请求方能够快速识别这个完成数据包对应的是哪个请求。
此外,完成方还可以通过在完成状态字段中设置特定位来指示错误条件。这些错误信息可以让请求方大致了解问题出在哪里,但如何处理这些错误将由软件决定,这部分超出了PCIe规范的范围。
3.2 锁定读(Locked Reads)
锁定内存读取(Locked Memory Reads)是用于支持原子读-修改-写操作的一种事务类型。这类操作主要用于处理器执行不可中断的任务,例如测试和设置信号量。当测试和设置操作正在进行时,其他对该信号量的访问都会被阻止,以避免产生竞争条件(race condition)。为了实现这种互斥,处理器使用锁定指示器(例如在并行前端总线上使用一个独立的引脚)来防止其他事务在锁定事务完成之前进行。
这种锁定机制最早在PCI规范中提出,原因是当时的规范编写者预计PCI有可能取代处理器总线,因此在PCI规范中加入了处理器在总线上可能需要执行的操作(如锁定事务)的支持。不过,PCI在实践中很少被用作处理器总线,因此大部分处理器总线支持功能最终被移除。但锁定周期仍被保留下来,以支持少数特殊情况,PCIe也将这一机制向后兼容,主要用于遗留设备(Legacy Devices)的支持。
为了加速锁定事务的逐步淘汰,新的PCIe设备被禁止接受锁定请求,只有那些自我识别为遗留设备的设备可以合法地处理锁定请求。图2-19中的示例展示了一个请求方如何发起锁定请求(MRdLk)。根据定义,这类请求只能来自CPU,因此在PCIe中,只有根端口(Root Port)会发起锁定请求。锁定请求会通过目标内存地址在拓扑结构中路由,最终到达遗留终端(Legacy Endpoint)。当数据包在各个路由设备(称为服务点)中传递时,数据包的出口端口将被锁定,意味着在路径解锁之前,任何其他数据包都无法沿该方向传递。
这种锁定机制确保了关键事务在整个系统中可以安全地执行,而不会受到其他事务的干扰。
当完成方(Completer)收到锁定请求包并解码其内容后,它会收集所需的数据并生成一个或多个带有数据的锁定完成数据包(Locked Completions)。这些完成数据包使用请求方ID进行路由,返回给请求方。完成数据包经过的每个出口端口也会被锁定,直到路径完成数据包的传输。
如果完成方在处理过程中遇到问题,它将返回一个没有数据的锁定完成数据包(因为原始的读取请求应该会返回数据,如果没有数据就意味着出现了问题),并且状态字段会指示该错误的相关信息。请求方接收到这个完成数据包后,会明白锁定操作没有成功,这意味着事务将被取消,后续该如何处理将由软件决定。
这一机制确保了在事务出现问题时,系统能够及时捕捉到错误并采取适当的措施,而不会继续进行有潜在问题的操作。
3.3 IO 和配置写(IO and Configuration Writes)
IO和配置写入(IO and Configuration Writes)是一种 non-posted 事务。图2-20展示了一个non-posted 的IO写入事务。与锁定请求类似,IO周期也只能合法地指向遗留终端(Legacy Endpoint)。请求包通过交换机,基于IO地址进行路由,直到到达目标终端。
当完成方(Completer)收到请求后,它会接受数据,并返回一个不带数据的完成数据包,确认接收到了请求包。完成数据包的状态字段会报告是否发生了错误,如果发生了错误,请求方的软件将负责处理。如果完成数据包报告没有错误,请求方就知道写入的数据已经成功传送到目的地,接下来可以继续执行目标设备的下一步指令。
这种 non-posted 事务写入的主要动机是:不同于内存写入,单单知道数据将在某个时间点到达目的地还不够。相反,后续的操作逻辑上不能进行,直到确认数据已经成功写入。因此,non-posted 写入主要来自处理器,用于确保关键操作顺序的执行。
四、Posted Writes
4.1 Memory Writes
内存写入(Memory writes)始终是posted requests,即不会收到完成数据包。一旦请求发送后,请求方(Requester)不会等待任何反馈,就可以继续处理下一个请求,这样也不会浪费时间或带宽来传输完成数据包。
如图2-21所示,内存写入请求通过目标内存地址在系统中进行路由,最终到达完成方(Completer)。一旦链路成功发送了请求,该事务在该链路上就已完成,链路可以开始处理其他数据包。最终,完成方接受数据,该事务才算真正完成。
当然,这种方法的一个折衷是,由于没有发送完成数据包,因此请求方不会收到错误报告。如果完成方遇到错误,它可以记录错误并向RC发送一个消息,通知系统软件发生了错误,但请求方不会直接看到这些错误。这种方式虽然牺牲了错误报告的能力,但在没有出错的情况下,极大地提高了数据传输效率。
4.2 Message Writes
与之前讨论的请求不同,消息(Messages)有多种可能的路由方式,消息中的一个字段会指示应该使用哪种路由方法。例如,有些消息是针对特定完成方的发布写入请求,有些是从RC广播到所有终端设备的广播消息,还有一些是从终端设备发送并自动路由到RC的消息。
在PCIe中,消息非常有用,它们有助于实现减少引脚数量的设计目标。通过消息,可以消除PCI中用于报告中断、功率管理事件和错误的旁路信号,因为这些信息可以通过正常的数据路径以数据包的形式传输。这大大简化了硬件设计,同时提高了系统的灵活性和效率。
五、QoS
服务质量(Quality of Service, QoS)是PCIe从设计之初就考虑到的一个特性,特别是为了支持诸如流媒体音频或视频等对时间敏感的应用场景。在这些应用中,数据的及时交付至关重要。为了实现QoS,PCIe引入了一些关键机制:
- 首先,每个数据包由软件分配优先级,这通过在数据包中设置一个3位的字段流量类别(Traffic Class, TC)来实现。通常来说,较高的TC编号表示该数据包在系统中拥有更高的优先级。
- 其次,硬件为每个端口内置了多个缓冲区,称为虚拟通道(Virtual Channels, VC)。数据包会根据其TC编号被放置到适当的虚拟通道中。
- 第三,既然每个端口有多个缓冲区同时准备传输数据包,系统需要使用仲裁逻辑来选择从哪个虚拟通道发送数据包。
- 最后,在多个输入端口争夺一个输出端口的虚拟通道时,端口仲裁(Port Arbitration)机制负责选择哪一个输入端口优先。这种仲裁可以是硬件分配的,也可以由软件编程控制。
这些硬件机制必须协同工作,才能使系统根据不同数据包的优先级进行处理,从而实现QoS。如果系统被正确编程和配置,它甚至可以为特定路径提供保证服务(Guaranteed Service),确保流量具有确定的延迟和带宽。
例如,图2-22展示了一个视频摄像头和SCSI设备同时向系统DRAM传输数据的场景。视频数据具有时间敏感性,如果传输路径的带宽跟不上摄像头的需求,视频帧将被丢弃,导致捕捉的视频看起来不流畅。相比之下,SCSI数据传输的时间要求不那么严格,只要确保数据没有错误即可。因此,当视频数据包和SCSI数据包同时需要传输时,系统应该优先传输视频数据包,以确保服务质量。
六、 事务排序(Transaction Ordering)
在虚拟通道(VC)内,数据包通常按照到达的顺序依次传输,但也有一些例外情况。PCI Express协议继承了PCI事务排序模型,包括PCI-X架构中添加的支持松散排序的情况。这些排序规则确保使用相同流量类别(TC)的数据包能够按照正确的顺序通过系统拓扑,避免潜在的死锁或活锁问题。值得注意的是,由于排序规则仅适用于同一虚拟通道内的数据包,而不同流量类别的包可能不会映射到同一个虚拟通道,因此,使用不同TC的数据包在软件看来不存在排序关系。这种排序控制是在事务层的虚拟通道内保持的,从而确保系统内不同优先级的数据流能够正确传输。
七、流量控制(Flow Control)
串行传输通常使用的一种协议要求发送方只有在确认接收方有足够的缓冲空间时,才可以发送数据包给它的邻居。这种机制减少了总线上不必要的性能浪费事件,比如在PCI中常见的断开和重试操作,从而消除了这一类问题。与此相对的折衷是,接收方必须足够频繁地报告其缓冲区的空间情况,以避免不必要的停滞,而这种报告本身也会占用一些带宽。
在PCIe中,这种报告通过数据链路层数据包(Data Link Layer Packets, DLLPs)来完成,避免了使用事务层数据包(TLPs)可能引发的死锁问题。在某些情况下,TLPs可能无法发送或接收缓冲区大小更新,因为接收方的缓冲区已满,而DLLPs可以在任何缓冲状态下发送和接收,从而避免了这一问题。
这种流控制协议在硬件层面自动管理,对软件是透明的,因此,软件无需干预就能确保高效的流控制。
如图2-23所示,接收端包含用于存储接收到的TLP(事务层数据包)的虚拟通道(VC)缓冲区。接收端通过流控制DLLP(数据链路层数据包)向发送端通告这些缓冲区的大小。发送端会跟踪接收端VC缓冲区中的可用空间,只有在接收端有足够空间时,发送端才允许发送更多数据包。当接收端处理完TLP并从缓冲区中移除它们时,接收端会定期发送流控制更新DLLP,以便让发送端及时了解可用的缓冲空间。