原文:Mindshare
译者:Michael ZZY
校对: LJGibbs
欢迎参与 《Mindshare PCI Express Technology 3.0 一书的中文翻译计划》
https://gitee.com/ljgibbs/chinese-translation-of-pci-express-technology
如图2‑12 所示,PCIe 定义了一种分层的体系结构。可以认为这些层在逻辑上是相互独立的部分,因为他们各自都有一个用于发出信息流的发送端和一个接收信息流的接收端。这种分层的设计方法对硬件设计者来说有不少的优点,因为如果在设计中对逻辑进行了仔细的划分,那么就可以在以后升级到新的协议规范版本时仅改变原设计中的某一层即可,而不会影响或者变动其它层。虽然如此,但是需要注意的是,这些层只是定义了接口的工作职责,而实际设计并不要求为了符合规范而将设计严格按照层级来分成几个部分。本节的目的在于描述各个层的功能职责,以及描述完成一次数据传输时的事件流程。
img
图2‑12 PCI Express 设备层次示意图
图2‑12 所展示的 PCIe 设备内部层次包括:
每个 PCIe 接口都支持这些层的功能,包括交换机端口,如图2‑13 所示。在早期大家经常会产生一个疑问,那就是一个交换机端口是否还需要实现所有的层次呢,毕竟它通常只用于转发数据包。答案是需要,因为对包的内容进行解析来确定它们的路由是需要查看数据包的内部细节的,而这件事情是在事务层中完成的。
图2‑13 交换机端口的层次结构
原则上,本设备上的每个层都与链路对端设备的各自对应层进行通信,例如本设备的事务层与对端的事务层通信、本设备的数据链路层与对端的数据链路层通信。上面的两层通过在数据包内添加一串特定的比特信息,产生一个接收端的对应层可以识别的字段样式来实现对应层之间的通信。数据包通过其他层的转发,从而做到到达或者离开链路。物理层也直接与另一个设备中的物理层通信,但是方式与此不同。
在我们讲的更深入之前,先大致了解一下这些层是如何交互的。从广义上说,设备所发出的请求包或者完成包是在事务层进行组包的,组包所用到的信息是由设备核心层所提供的,有时我们将设备核心层称为软件层(尽管协议规范中并没有使用这一术语)。它提供的组包信息通常包括期望的命令类型、目标设备的地址、请求的属性特征等等。刚组好的数据包会被存入一个被称为虚拟通道缓存,直到这个包可以被发往下一个层级。当这个包被向下发给数据链路层后,数据链路层会在数据包中加入额外的信息以供对端的接收方进行错误检查,而且这个数据包会在本地储存下来,这样我们就可以在对端检测到传输出错时重新发送这个数据包。当数据包到达物理层后,它被编码,并使用链路上的所有可用的通道、以差分信号的形式进行传输。
图2‑14 PCIe 设备层次结构的详细框图
接收端对物理层的输入比特进行解码,检查本层级所能发现的错误,如果没有检查到错误,那么就将接收到的数据包向上转发到数据链路层。在数据链路层,数据包进行不同于物理层的错误检查,如果没有发现错误就向上转发给事务层。在事务层数据包被缓存,检查错误,并拆解成原始信息(命令、属性等),以便将这些数据传输到接收设备的核心层。接下来,让我们更深入的探索每一个层必须要做什么才能让上述过程正常工作,这个操作过程在图2‑14 进行了展示。我们从最顶端开始。
设备核心层是一个设备的核心功能,例如网络接口或是硬盘驱动控制器。它并不是 PCIe 协议规范中所定义的一个层级,但是我们可以把它当做一个 PCIe 的层级,这是因为它位于事务层的上方,而且它是所有请求的源头或是目的地。它为事务层提供了需要发送的请求信息,其中的信息包括事务类型、地址、需要传输的数据量等等。当事务层接收到输入数据包时,它也是事务层向上转发输入数据包信息的目的地。
为了响应来自软件层的请求,事务层生成出站数据包(outbound packet)。它也会检查入站数据包(inbound packet),并将入站数据包内包含的信息向上转发给软件层。事务层支持非报告式请求(non‐posted transaction)的拆分事务协议,并将入站完成包(inbound Completion)与先前传输的出站非报告式请求包关联起来,即知道这个完成包是对应到哪个非报告式请求包。事务层所处理的事务使用的数据包种类为 TLP,TLP 可以分为四个请求种类:
前三种在 PCI 和 PCI-X 中就已经得到支持,但是消息是 PCIe 中的一个新的请求种类。一个请求包向目标设备传送命令,目标设备作为响应请求而发回的一个或多个完成包,这二者组合起来就是对一个事务的定义,即一个事务由一个请求包以及所有返回的完成包共同组成。如表2‑2 列出了 PCIe 请求的类型。
表2‑2 PCIe请求类型
这些请求还可以被归为两类,如上表的右边一列:非报告和报告(non‐posted and posted)。对于非报告式请求,发起方首先向完成方发送一个请求数据包,完成方应该产生一个完成包作为响应。读者们应该认出来了,这就是从 PCI-X 那里继承来的拆分事务协议。例如,所有的读请求都是非报告式的,因为这个读请求所请求的数据需要通过完成包来返回给发起方。可能令你感到出乎意料,IO 写请求和配置写请求也是非报告式的。尽管它们在发送给目标设备的命令中就已经包含了要写入目标设备的数据,但是这两种请求依然需要目标设备在写入完成后返回完成包,以此来让发起方确认数据被正确无误的写入到目的设备中。
与上述的请求相反地,内存写请求和消息请求都是报告式的,这意味着完成方在完成这些请求后不需要向发起方返回完成包 TLP。报告式事务对整体性能提升是有好处的,因为发起方不需要等待响应,也不需要承担对完成包进行处理的额外开销。这里做出的取舍就是发起方无法得到写请求是否被正确无误的完成的反馈信息。报告式这种操作行为继承自 PCI,它依然被认为是一个不错的操作方法,虽然无法得到反馈信息,但是发生错误的可能性比较小并且使用报告式得到的性能提升也比较明显。需要注意的是,尽管它们不要求返回完成包,报告式写操作仍然要参与数据链路层的 Ack/Nak 协议,以此来保证较为可靠的数据包传输。关于这一点的更多内容,请见第十章“Ack/Nak 协议”。
PCIe 请求包和完成包的包类型被罗列在表2‑3 中。
表2‑3 PCIe 的 TLP 类型
TLPs 起始于发送方的事务层,终止于接收方的事务层,如图2‑15 所示。当 TLP 途经发送方的数据链路层以及物理层时,这两层分别会向数据包中添加一些信息,接收方的数据链路层和物理层会分别根据发送方对应层所添加的信息来进行校验,以此确认数据包是否在链路传输中依旧保持正确没有出错。
图2‑15 TLP 的起点与目的地
TLP 组包. 如图2‑16 展示了一个封装完成的 TLP 的各个部分是如何在链路上进行传输的,我们可以从中发现这个数据包中的不同部分是分别由不同的层来添加的。为了更容易看出这个数据包是怎么构成的,我们将 TLP 的不同部分用不同的颜色进行标识,以此来表示对应的部分是由哪一层添加的:红色代表事务层,蓝色代表数据链路层,绿色代表物理层。
图2‑16 TLP 组包形式
设备核心层负责将组成 TLP 的核心部分的信息发送给事务层,即上图中的首部和数据。每个 TLP 都会有一个首部,而有些 TLP 会没有数据部分,例如读请求。
事务层还可以选择添加 ECRC(End-to-End CRC, 端到端 CRC)字段,这个 ECRC 由事务层进行计算并附加在数据包的后面。CRC 的意思是循环冗余校验码,它被几乎所有的串行传输架构所使用,原因很简单,它实现起来比较简单同时又能提供很强的错误检测功能。CRC 还可以检测“突发错误”,即一串重复的错误比特,这一串比特的长度取决于 CRC 的长度(对于 PCIe 来说是 32比特)。因为这种类型的错误在发送一长串比特时会遇到,因此 CRC 的这种特性非常适用于串行传输。ECRC 字段区域在通过发送方和接收方之间的任何服务点(服务点(service point),通常指的是交换机或者根组件的端口这些有 TLP 路由功能的地方)时都不改变,这使得目的端可以用它来验证在整个传输过程中都没有发生错误。
对于 TLP 的传输,TLP 的核心部分由事务层转发至数据链路层,数据链路层负责在 TLP 中添加一个序列号和另外一个被称为 LCRC(Link CRC, 链路CRC) CRC 字段区域。LCRC 被对端接收方用来进行错误检查,并将链路上传输的每个数据包的检查结果都汇报给发送方。善于思考的读者可能会有疑问,如果 LCRC 已经证明了这次链路传输是无差错的,而 LCRC 又是数据包必需含有的字段,那么 ECRC 还有什么作用呢?这个疑问的回答是,还使用 ECRC 是因为还有一个地方的传输错误没有被检查,那就是负责路由数据包的设备内部。当一个数据包到达一个端口,并进行错误和路由检查,然后当它被从另一个端口发出时,设备会计算出一个新的 LCRC 值并添加在数据包中,新的LCRC 会取代老的 LCRC 。内部端口之间的转发可能会遇到 PCIe 协议没有检查到的错误,这时候就需要 ECRC 来发挥它的作用了。这里可以理解为,当 TLP 到达交换机的一个接收端口时,交换机会在接收端口对其 LCRC 进行校验,然后根据路由信息对数据包进行转发,但是这个转发过程中是不对 LCRC 进行校验的,直到转发到对应的输出端口,然后在输出端口给数据包加上新的 LCRC 后发送出去,不难发现因为在内部转发过程中不对 LCRC 进行校验,因此若转发过程中出现了错误则就需要通过更内层封装的 ECRC 来进行校验了,否则内部转发过程是否出错将无从可知。
最后,数据链路层封装好的数据包被转发给物理层,物理层将其他一些字符添加到数据包中,这些字符可以让接收方知道接下来将会接收什么(例如标识一个数据包的开始或结尾)。对于前两代的 PCIe,物理层将在数据包的头和尾添加控制字符。而在第三代 PCIe 中不再使用控制字符,而是在数据包中添加一些额外的比特来提供关于数据包的信息。经过这些处理后,数据包被编码,然后在链路上所有可用通道中以差分形式传输。
TLP 拆包. 当对端的接收方看到了输入的比特流时,它需要对此前组包时添加的那些部分进行识别和剥除,这样就能恢复出发送方设备的设备核心层的原始请求信息。如图2‑17 所示,物理层将会确认当前比特流中是否存在正确的“起始”、“结束”或者其他字符,并将它们剥除,然后将剥除了这些字符后的 TLP 转发给数据链路层。数据链路层将首先进行 LCRC 以及序列号的错误校验。如果并未发现错误,那么数据链路层将会把 LCRC 和序列号从 TLP 剥除,并将 TLP 转发给事务层。如果接收方是一个交换机,那么将在事务层对这个数据包进行解析评估,从它的数据包头中找到路由信息来确定这个数据包要被转发到哪一个端口。即使这个交换机并不是 TLP 最终的目的地,它也可以对这个 TLP 进行 ECRC 校验以及在发现错误时进行 ECRC 错误汇报。但是,交换机不能更改这个 TLP 中的 ECRC,这是希望最终的目标设备也可以检测到这个 ECRC 错误。
如果目标设备有能力并且启用了 ECRC 校验的功能,那么它将对 ECRC 进行错误校验。若 TLP 到达了最终的目的设备,而且校验无错,那么在事务层中将把这个 ECRC 字段剥除,使得整个数据包只剩下首部和数据部分,并将这些剩余部分转发给软件层。
图2‑17 TLP 的拆包
普通读(Ordinary Reads). 如图2‑18 展示了一个内存读请求,这个请求由端点发往系统内存。在内存读请求 TLP 中有一个很重要的部分,那就是目标地址(关于更多有关 TLP 内容的详细讲解,请见第五章“TLP 元素”)。一个内存请求的地址可以是 32 位或者 64 位的,这也决定了数据包的路由方式。在这个例子中,这个请求事务通过两个交换机的路由后向上转发给了目标设备,例子中的目标设备为根组件。当根组件对请求包进行译码,并识别出了数据包中的地址指向了系统内存,它将从那段系统内存中取出端点所请求的数据。为了将这些从内存中取出来的数据返回给发起方,根组件端口的事务层将产生足够多的完成包,这些数量的完成包足以将发起方所请求的全部数据都返回回去。PCIe 中规定一个数据包中数据荷载最大为 4KB,但是实际设计的设备中一般会使用的数据荷载会比 4KB 小,因此需要好几个完成包才能足够将比较大量的数据返回给发起方。
这些完成包内也包含了路由信息,用于将它们指引回到发起方,这是因为发起方在原先的请求包中就附带了需要返回的地址的信息,所以完成方就可以将这个地址用来放在完成包中作为完成包的路由信息。这个“返回地址”其实很简单,它就是 PCI 中定义的设备 ID,这个设备 ID 由三个东西组成:发起方所属 PCI 总线在系统中的 PCI 总线号、发起方在所属 PCI 总线上的设备号、发起方在所属设备中的功能号。这样的总线号、设备号、功能号组合起来的信息(有时被缩写为 BDF,Bus、Device、Function)就是完成包用来返回到发起方的路由信息。
正如 PCI-X 所做的那样,发起方可以同时有多个正在进行的拆分事务,它必须能够将输入的完成包和正确的请求关联起来。为了便于实现这一点,发起方在原先的请求包中加入了一个值称为 Tag,这个 Tag 对于每一个请求而言都是独一无二的,也就是说每一个未完成的请求都有一个与其他未完成的请求不同的 Tag 号。完成方会将这个事务的 Tag 拷贝进完成包中,这样发起方就可以快速地通过完成包中的 Tag 来将这个完成包与正确的请求关联起来,也就是找到了这个完成包是用来服务哪个请求的。
最后还要说一点,完成方还可以设置完成包中的完成状态字段中的比特,以此来表示出事务的错误情况。这至少可以让发起方对哪里可能出错了有一个大致的了解。发起方如何去处理大多数的错误,这个事情一般是由软件来决定,这并不在 PCIe 协议规范的范围内。
图2‑18 非报告式读的示例
锁定读(Locked Reads). 锁定的内存读这种读请求是为了支持原子读-修改-写操作,所谓原子操作就是一种不可中断的事务,处理器使用这种事务来进行测试以及设置信号标志等任务。当测试和设置信号标正在进行中时,不允许其他对信号标的访问发生,不允许发生竞争的情况。为了避免发生竞争情况,处理器使用一个锁定指示符(例如并行总线前段的一个单独的 pin),来阻止总线上的其他事务,直到这个锁定的事务完成。接下来是对这个主题内容的一个高层次介绍。有关被锁定事务的更多信息,请参阅附录 D,“锁定事务”。
回顾一下历史,在 PCI 的早期,协议规范的制定者们预期 PCI 将会实际取代处理器总线。因此,PCI 协议规范中要求总线要能支持处理器要完成的事情,比如锁定事务。然而,PCI 很少以这种方式使用,最终,这种处理器总线支持的东西大部分都被丢弃了。不过,锁定周期依然存在,用以支持一些特殊情况,在更进一步的 PCIe 的发展中也保留了它,以实现对一些遗留事务的支持。也许是为了加速向 PCIe 的迁移,在新的 PCIe 设备中禁止接收锁定请求,只有那些传统设备才能够使用它。在图2‑19 所示的例子中,一个发起方发出了一个 MRdLk(锁定读)来发起事务。根据定义这样的请求仅允许来自 CPU,因此在 PCIe 中仅有根组件的端口可以发起这种事务。
这个锁定请求使用目标内存地址作为路由信息,并最终到达了传统设备的端点。当数据包经过沿途的每个路由设备时(称为服务点),数据包的出口端口被锁定,这意味着在被解锁之前这条路径都不能通过其他的数据包。
图2‑19 非报告式锁定读事务协议
当完成方接收到这个请求包并将其内容进行译码,它将收集请求所需要的数据,并使用这些数据产生一个或多个锁定完成包。这些完成包将会被路由回到发起方,使用的路由信息是发起方 ID,完成包路径上的出口端口也会被锁定。
如果完成方遇到了一些问题,无法正常响应请求,那么它将向发起方返回一个不含有数据的锁定完成包(正常的读请求完成包应该含有要返回的数据,而这里不包含数据那么我们就知道出现了一些问题),并使用完成包的状态指示区域来标识发生的错误的一些信息。发起方接收到这样的完成包就明白了这个锁定请求失败了,它就会取消这个事务的执行,然后由软件来决定接下来要做什么。
IO和配置写(IO and Configuration Writes). 如图2‑20 中展示了一个非报告式 IO 写事务。与锁定请求相似,这样的一套 IO 操作流程的目的设备只能是一个传统端点(Legacy Endpoint)。请求包使用 IO 地址作为路由信息在交换机中被路由转发,直到它到达目标端点。当完成方接收到这个写请求包,它接收请求包内的数据并返回一个单独的不含有数据的完成包,以此来确认自己收到了这个写请求包。完成包中的状态标识区域将会指示出是否发生了错误,如果发生了错误,发起方的软件需要对错误进行处理。
如果完成包中显示并未出现错误,那么发起方就认为写数据被成功的送达目标设备并成功写入,可以允许针对这一个完成方的指令序列继续向下执行。也就是说,在发起方中有一系列的针对完成方的指令,当知道这一个 IO 写请求完成了之后,允许执行指令序列中的下一个指令,即想表达的是需要确认这个写请求成功了才允许继续执行下一条指令。这很好的总结了非报告式写的目的:不同于一个内存写(MWr 是报告式的),非报告式写不能仅仅知道数据被送往了目的方,还必须要知道数据真的到达了目的方才行,否则在逻辑上不能继续执行下一步。又如同锁定操作一样,非报告式写请求只能由处理器发出。
图2‑20 非报告式写事务协议
内存写(Memory Writes). 内存写请求必然是报告式类型的,它不需要返回完成包。一旦这种写请求包被发出,发起方不需要等到任何反馈就可以继续去进行下一条请求,不需要在返回完成包这件事情上花费时间和带宽。因此,报告式写会比非报告式写更加快速且高效,并很好的提升了系统的性能。如图2‑21 所示,请求包使用内存地址作为路由信息在系统中进行路由转发,并最终到达完成方。一旦链路成功的将这个请求发送过去(这里的成功是指把数据包传输了过去,而不需要得到完成方的成功确认),这个事务在链路上就已经结束了,链路上就可以继续传输其他的数据包了。最终,完成方接收写请求包中的数据,这个事务才真正完成。当然,这种事务执行方法在提升效率的同时也舍弃了一些东西,因为完成方不需要发送完成包,所以这也意味着它无法将错误报告给发起方。如果完成方发生了错误,那么它可以记录下这个错误,并向 RC 发一个消息来通知系统软件这些情况,但是发起方对这些是完全不知情的。
图2‑21 报告式内存写事务协议
消息写(Message Writes). 十分有趣的是,消息不同于我们前面所说的那些请求事务,它有好几种路由方法(前面的都是通过内存地址、IO 地址中的一种),在消息内部专门有一个区域来标识使用的是哪一种路由方式。例如,有一些消息是报告式写请求,其目的方为特定的完成方;有一些是根组件向所有端点广播的请求;还有一些是端点发出的要自动路由到根组件的请求。想要学习更多关于不同的路由类型的内容,请参阅第四章“地址空间和事务路由”。
消息在 PCIe 中很有用,它可以使得 PCIe 达到减少引脚数量的设计目标。它使得 PCIe 不再需要边带信号,而在 PCI 中则是需要用边带信号来报告中断、功耗管理事项以及错误信息,而在 PCIe 中使用消息则可以将这些信息使用数据包来进行报告,数据包是直接在一般数据路径上传输的,因此不再需要额外的边带信号。
PCIe 从一开始就被设计为能够支持时间敏感(time‐sensitive)事务,例如流视频、音频应用程序,对于这些应用程序来说数据必须被及时传输才能保证数据有效。这被称为提供了 QoS,它是通过添加一些东西来实现的。首先,每个数据包都被软件分配了一个优先级,这个优先级是通过设置数据包内的一个 3 比特的字段区域来进行标识的,称之为 TC (Traffic class, 流量类型)。一般来说,给一个数据包分配一个编号较大的 TC 表示希望给与这个数据包一个更高的优先级。第二,使用多缓冲区,称为 VC (Virtual Channels, 虚拟通道),将其构建在硬件的每一个端口中,数据包会根据其 TC 值,来被放入相应的 VC 中(相应的缓存区中)。第三,由于现在对于一个端口来说,在一个时刻将会有多个缓冲区都存在可以进行传输的数据包,因此需要有对 VC 进行选择的仲裁逻辑。最后,交换机必须在相竞争的各个输入端口间做出选择,以便访问相应端口的 VC 。这一步被称为端口仲裁,它可以由硬件来进行分配或是由软件来进行编程配置。(译注:这与第三点的不同之处在于,第三点是在一个端口内对多个 VC 进行仲裁选择,而端口仲裁是指各个端口已经选择好了此次访问的 VC,需要由交换机仲裁选择访问哪一个端口。)所有的这些硬件部件都必须要能够支持系统对数据包进行优先级排序。如果一个这样的系统被正确的编程配置,它可以为给定的路径提供有保障的服务。
图2‑22阐述了 QoS 的概念,其中的视频摄像头以及 SCSI 设备都需要向系统 DRAM 发送数据。这二者的不同之处在于,摄像头是对时效性要求严格的,如果传输路径无法满足摄像头的带宽,那么将出现丢帧的现象。因此系统需要保障摄像头所需要的最小带宽,否则它捕捉的视频画面将会出现不稳定。与此同时,SCSI 数据需要正确无误的进行传输,但是对于它来说传输需要的时间长短并不是那么重要。显然,当视频数据和SCSI数据同时需要被发送时,视频数据流应当具有更高的优先级。QoS 指的是系统的一种能力,是系统为数据包分配不同优先级并将这些数据包按照确定性的延时、带宽在系统拓扑中进行路由的能力。想了解更多关于 QoS 的细节,请参阅第七章“服务质量”。
图2‑22 QoS 示例
在一个 VC (Virtual Channels) 内,数据包一般全都按照它们到达的先后顺序进行排序和移动,但是也有例外情况。PCIe 协议继承了 PCI 的事务排序模型,包括支持 PCI-X 中加入的宽松排序。这些排序规则保证了使用相同 TC 的数据包都会按照正确的顺序在拓扑结构中被路由转发,防止潜在的死锁或者活锁情况。需要注意的一点是,由于排序规则仅应用于 VC,且使用不同 TC 的数据包一般不会被划分进同一个 VC,因此使用不同的 TC 的数据包在软件看来就不存在排序关系。这种排序在事务层中的 VC 中维护。
串行传输所使用的一个典型协议是,要求发送方仅在对端有足够的缓冲区接收时才发送数据包。这样的规定删去了总线上浪费性能的操作事件,比如 PCI 中允许进行的断开与重试( disconnects and retries),这使得这类问题在传输中得到消除。这样做的代价是,接收方必须足够频繁的报告它的可用缓冲区空间来避免不必要的传输停顿,而且这样的报告也需要占用接收方自己的一点带宽。在 PCIe 中,这个可用缓冲区空间的报告是由 DLLP(Data Link Layer Packet)来完成的,我们将在下一节讲到 DLLP。这里不使用 TLP 的原因是为了避免可能出现的死锁现象,如果使用TLP则可能出现,例如一个设备 A 作为发送方需要对接收方 B 的可用缓冲区空间进行更新,但是当A的接收缓冲区满导致它又无法接收来自B的可用缓冲区报告,这样就出现了一个死循环。而 DLLP 可以不管缓冲区状态就进行收发,这样就避免了死锁的问题。这样的流量控制协议由硬件级进行自动管理,对于软件来说是透明的。
图2‑23 流量控制基础操作
如图2‑23 所示,接收方内的 VC 缓冲区内缓存了接收到的 TLP。接收方将自己的缓冲区大小通过流量控制 DLLP 来告知发送方。发送方将会跟踪接收方的可用缓冲区空间的值,并且不允许发出大于这个可用空间的数据包。当接收方对缓冲区中的 TLP 进行了处理,并将这个 TLP 移出了缓冲区,此时缓冲区中就空闲出了新的可用空间,那么接收方将会定期的发送流量控制更新 DLLP ,保持发送方能够获取到最新的可用空间的值。想了解更多关于这方面的内容,请见第六章“流量控制”。
原文:Mindshare
译者:Michael ZZY
校对:XTang
欢迎参与 《Mindshare PCI Express Technology 3.0 一书的中文翻译计划》
https://gitee.com/ljgibbs/chinese-translation-of-pci-express-technology
END