事务层是PCIe总线层次结构的最高层,该层次将接收PCIe设备核心层的数据请求,并将其转换为PCIe总线事务,PCIe总线使用的这些总线事务在TLP头中定义。PCIe总线继承了PCI/PCI-X总线的大多数总线事务,如存储器读写、I/O读写、配置读写总线事务,并增加了Message总线事务和原子操作等总线事务。
本节重点介绍与数据传送密切相关的总线事务,如存储器、I/O、配置读写总线事务。在PCIe总线中,Non-Posted总线事务分两部分进行,首先是发送端向接收端提交总线读写请求,之后接收端再向发送端发送完成(Completion)报文。PCIe总线使用Split传送方式处理所有Non-Posted总线事务,存储器读、I/O读写和配置读写这些Non-Posted总线事务都使用Split传送方式。PCIe的事务层还支持流量控制和虚通路管理等一系列特性,而PCI总线并不支持这些新的特性。
在PCIe总线中,不同的总线事务采用的路由方式不相同。PCIe总线继承了PCI总线的地址路由和ID路由方式,并添加了“隐式路由”方式。
PCIe总线使用的数据报文首先在事务层中形成,这个数据报文也被称之为事务层数据报文,即TLP,TLP在经过数据链路层时被加上Sequence Number前缀和CRC后缀,然后发向物理层。
数据链路层还可以产生DLLP(Data Link Layer Packet)。DLLP和TLP没有直接关系,DLLP是产生于数据链路层,终止于数据链路层,并不会传递到事务层。DLLP不是TLP加上前缀和后缀形成的。数据链路层的报文DLLP通过物理层时,需要经过8/10b编码,然后再进行发送。数据的接收过程是发送过程的逆过程,但是在具体实现上,接收过程与发送过程并不完全相同。
当处理器或者其他PCIe设备访问PCIe设备时,所传送的数据报文首先通过事务层被封装为一个或者多个TLP,之后才能通过PCIe总线的各个层次发送出去。TLP的基本格式如图5 1所示。
一个完整的TLP由1个或者多个TLP Prefix、TLP头、Data Payload(数据有效负载)和TLP Digest组成。TLP头是TLP最重要的标志,不同的TLP其头的定义并不相同。TLP头包含了当前TLP的总线事务类型、路由信息等一系列信息。在一个TLP中,Data Payload的长度可变,最小为0,最大为1024DW。
TLP Digest是一个可选项, 一个TLP是否需要TLP Digest由TLP头决定。Data Payload也是一个可选项,有些TLP并不需要Data Payload,如存储器读请求、配置和I/O写完成TLP并不需要Data Payload。
TLP Prefix由PCIe V2.1总线规范引入,分为Local TLP Prefix和EP-EP TLP Prefix两类。其中Local TLP Prefix的主要作用是在PCIe链路的两端传递消息,而EP-EP TLP Prefix的主要作用是在发送设备和接收设备之间传递消息。设置TLP Prefix的主要目的是为了扩展TLP头,并以此支持PCIe V2.1规范的一些新的功能。
TLP头由3个或者4个双字(DW)组成。其中第一个双字中保存通用TLP头,其他字段与通用TLP头的Type字段相关。一个通用TLP头由Fmt、Type、TC、Length等字段组成,如图5 2所示。
如果存储器读写TLP支持64位地址模式时,TLP头的长度为4DW,否则为3DW。而完成报文的TLP头不含有地址信息,使用的TLP头长度为3DW。其中Byte 4~Byte 15的格式与TLP相关,下文将结合具体的TLP介绍这些字段。
Fmt和Type字段确认当前TLP使用的总线事务,TLP头的大小是由3个双字还是4个双字组成,当前TLP是否包含有效负载。其具体含义如表5 1所示。
表5 1 Fmt[1:0]字段
Fmt[2:0] | TLP的格式 |
---|---|
0b000 | TLP大小为3个双字,不带数据。 |
0b001 | TLP大小为4个双字,不带数据。 |
0b010 | TLP大小为3个双字,带数据。 |
0b011 | TLP大小为4个双字,带数据。 |
0b100 | TLP Prefix |
其他 | PCIe总线保留 |
其中所有读请求TLP都不带数据,而写请求TLP带数据,而其他TLP可能带数据也可能不带数据,如完成报文可能含有数据,也可能仅含有完成标志而并不携带数据。在TLP的Type字段中存放TLP的类型,即PCIe总线支持的总线事务。该字段共由5位组成,其含义如表5 2所示。
表5 2 Type[4:0]字段
TLP类型 | Fmt[2:0] | Type[4:0] | 描述 |
---|---|---|---|
MRd | 0b000、0b001 | 0b0 0000 | 存储器读请求;TLP头大小为3个或者4个双字,不带数据。 |
MRdLk | 0b000、0b001 | 0b0 0001 | 带锁的存储器读请求;TLP头大小为3个或者4个双字,不带数据。 |
MWr | 0b010、0b011 | 0b0 0000 | 存储器写请求;TLP头大小为3个或者4个双字,带数据。 |
IORd | 0b000 | 0b0 0010 | IO读请求;TLP头大小为3个双字,不带数据。 |
IOWr | 0b010 | 0b0 0010 | IO写请求;TLP头大小为3个双字,带数据。 |
CfgRd0 | 0b000 | 0b0 0100 | 配置0读请求;TLP头大小为3个双字,不带数据。 |
CfgWr0 | 0b010 | 0b0 0100 | 配置0写请求;TLP头大小为3个双字,带数据。 |
CfgRd1 | 0b000 | 0b0 0101 | 配置1读请求;不带数据。 |
CfgWr1 | 0b010 | 0b0 0101 | 配置1写请求;带数据。 |
TCfgRd | 0b010 | 0b1 1011 | 本书对这两种总线事务不做介绍。 |
TCfgWr | 0b001 | 0b1 1011 | |
Msg | 0b001 | 0b1 0r2r1r0 | 消息请求;TLP头大小为4个双字,不带数据。“rrr”字段是消息请求报文的Route字段,下文将详细介绍该字段。 |
MsgD | 0b011 | 0b1 0r2r1r0 | 消息请求;TLP头大小为4个双字,带数据。 |
Cpl | 0b000 | 0b0 1010 | 完成报文;TLP头大小为3个双字,不带数据。包括存储器、配置和I/O写完成。 |
CplD | 0b010 | 0b0 1010 | 带数据的完成报文,TLP头大小为3个双字,包括存储器读、I/O读、配置读和原子操作读完成。 |
CplLk | 0b000 | 0b0 1011 | 锁定的完成报文,TLP头大小为3个双字,不带数据。 |
CplDLk | 0b010 | 0b0 1011 | 带数据的锁定完成报文,TLP头大小为3个双字,带数据。 |
FetchAdd | 0b010、0b011 | 0b0 1100 | Fetch and Add原子操作。 |
Swap | 0b010、0b011 | 0b0 1101 | Swap原子操作。 |
CAS | 0b010、0b011 | 0b0 1110 | CAS原子操作。 |
LPrfx | 0b100 | 0b0 L3L2L1L0 | Local TLP Prefix |
EPrfx | 0b100 | 0b1 E3E2E1E0 | End-End TLP Prefix |
由上表所示,存储器读和写请求,IO读和写请求,及配置读和写请求的type字段相同,如存储器读和写请求的Type字段都为0b0 0000。此时PCIe总线规范使用Fmt字段区分读写请求,当Fmt字段是“带数据”的报文,一定是“写报文”;当Fmt字段是“不带数据”的报文,一定是“读报文”。
PCIe总线的数据报文传送方式与PCI总线数据传送有类似之处。其中存储器写TLP使用Posted方式进行传送,而其他总线事务使用Non-Posted方式。
PCIe总线规定所有Non-Posted存储器请求使用Split总线方式进行数据传递。当PCIe设备进行存储器读、I/O读写或者配置读写请求时,首先向目标设备发送数据读写请求TLP,当目标设备收到这些读写请求TLP后,将数据和完成信息通过完成报文(Cpl或者CplD)发送给源设备。
其中存储器读、I/O读和配置读需要使用CplD报文,因为目标设备需要将数据传递给源设备;而I/O写和配置写需要使用Cpl报文,因为目标设备不需要将任何数据传递给源设备,但是需要通知源设备,写操作已经完成,数据已经成功地传递给目标设备。
在PCIe总线中,进行存储器或者I/O写操作时,数据与数据包头一起传递;而进行存储器或者I/O读操作时,源设备首先向目标设备发送读请求TLP,而目标设备在准备好数据后,向源设备发出完成报文。
PCIe总线规范还定义了MRdLk报文,该报文的主要作用是与PCI总线的锁操作相兼容,但是PCIe总线规范并不建议用户使用这种功能,因为使用这种功能将极大影响PCIe总线的数据传送效率。
与PCI总线并不相同,PCIe总线规范定义了Msg报文,即消息报文。分别为Msg和MsgD,这两种报文的区别在于一个报文可以传递数据,一个不能传递数据。
PCIe V2.1总线规范还补充了一些总线事务,如FetchAdd、Swap、CAS、LPrfx和EPrfx。其中LPrfx和EPrfx总线事务分别与Local TLP Prefix和EP-EP TLP Prefix对应。在PCIe总线规范V2.0中,TLP头的大小为1DW,而使用LPrfx和EPrfx总线事务可以对TLP头进行扩展,本节不对这些TLP Prefix做进一步介绍。PCIe设备可以使用FetchAdd、Swap和CAS总线事务进行原子操作。
TC字段表示当前TLP的传送类型,PCIe总线规定了8种传输类型,分别为TC0~TC7,缺省值为TC0,该字段与PCIe的QoS相关。PCIe设备使用TC区分不同类型的数据传递,而多数EP中只含有一个VC,因此这些EP在发送TLP时,也仅仅使用TC0,但是有些对实时性要求较高的EP中,含有可以设置TC字段的寄存器。
在Intel的高精度声卡控制器(High Definition Audio Controller)的扩展配置空间中含有一个TCSEL寄存器。系统软件可以设置该寄存器,使声卡控制器发出的TLP使用合适的TC。声卡控制器可以使用TC7传送一些对实时性要求较强的控制信息,而使用TC0传送一般的数据信息。在具体实现中,一个EP也可以将控制TC字段的寄存器放入到设备的BAR空间中,而不必和Intel的高精度声卡控制器相同,存放在PCI配置空间中。
目前许多处理器系统的RC仅支持一个VC通路,此时EP使用不同的TC进行传递数据的意义不大。x86处理器的MCH中一般支持两个VC通路,而多数PowerPC处理器仅支持一个VC通路。PLX公司的多数Switch也仅支持两个VC通路。
有些RC,如MPC8572处理器,也能决定其发出TLP使用的TC。在该处理器的PCIe Outbound窗口寄存器(PEXOWARn)中,含有一个TC字段,通过设置该字段可以确定RC发出的TLP使用的TC字段。不同的TC可以使用PCIe链路中的不同VC,而不同的VC的仲裁级别并不相同。EP或者RC通过调整其发出TLP的TC字段,可以调整TLP使用的VC,从而调整TLP的优先级。
Attr字段由3位组成,其中第2位表示该TLP是否支持PCIe总线的ID-based Ordering;第1位表示是否支持Relaxed Ordering;而第0位表示该TLP在经过RC到达存储器时,是否需要进行Cache共享一致性处理。Attr字段如图5 3所示。
一个TLP可以同时支持ID-based Ordering和Relaxed Ordering两种位序。Relaxed Ordering最早在PCI-X总线规范中提出,用来提高PCI-X总线的数据传送效率;而ID-based Ordering由PCIe V2.1总线规范提出。TLP支持的序如表5 3所示。
表5 3 TLP支持的序
Attr[2] | Attr[1] | 类型 |
---|---|---|
0 | 0 | 缺省序,即强序模型 |
0 | 1 | PCI-X Relaxed Ordering模型 |
1 | 0 | ID-Based Ordering(IDO)模型 |
1 | 1 | 同时支持Relaxed Ordering和IDO模型 |
当使用标准的强序模型时,在数据的整个传送路径中,PCIe设备在处理相同类型的TLP时,如PCIe设备发送两个存储器写TLP时,后面的存储器写TLP必须等待前一个存储器写TLP完成后才能被处理,即便当前报文在传送过程中被阻塞,后一个报文也必须等待。如果使用Relaxed Ordering模型,后一个存储器写TLP可以穿越前一个存储器写TLP,提前执行,从而提高了PCIe总线的利用率。有时一个PCIe设备发出的TLP,其目的地址并不相同,可能先进入发送队列的TLP,在某种情况下无法发送,但这并不影响后续TLP的发送,因为这两个TLP的目的地址并不相同,发送条件也并不相同。值得注意的是,在使用PCI总线强序模型时,不同种类的TLP间也可以乱序通过同一条PCIe链路,比如存储器写TLP可以超越存储器读请求TLP提前进行。而PCIe总线支持Relaxed Ordering模型之后,在TLP的传递过程中出现乱序种类更多,但是这些乱序仍然是有条件限制的。在PCIe总线规范中为了避免死锁,还规定了不同报文的传送数据规则,即Ordering Rules。
PCIe V2.1总线规范引入了一种新的“序”模型,即IDO(ID-Based Ordering)模型,IDO模型与数据传送的数据流相关,是PCIe V2.1规范引入的序模型。
Attr字段的第0位是“No Snoop Attribute”位。当该位为0时表示当前TLP所传送的数据在通过FSB时,需要与Cache保持一致,这种一致性由FSB通过总线监听自动完成而不需要软件干预;如果为1,表示FSB并不会将TLP中的数据与Cache进行一致,在这种情况下,进行数据传送时,必须使用软件保证Cache的一致性。
在PCI总线中没有与这个“No Snoop Attribute”位对应的概念,因此一个PCI设备对存储器进行DMA操作时会进行Cache一致性操作(1) ,这种“自动的”Cache一致性行为在某些特殊情况下并不能带来更高的效率。
当一个PCIe设备对存储器进行DMA读操作时,如果传送的数据非常大,比如512MB,Cache的一致性操作不但不会提高DMA写的效率,反而会降低。因为这个DMA读访问的数据在绝大多数情况下,并不会在Cache中命中,但是FSB依然需要使用Snoop Phase进行总线监听。而处理器在进行Cache一致性操作时仍然需要占用一定的时钟周期,即在Snoop Phase中占用的时钟周期,Snoop Phase是FSB总线事务的一个阶段,如图3 6所示。
对于这类情况,一个较好的做法是,首先使用软件指令保证Cache与主存储器的一致性,并置“No Snoop Attribute”位为1(2),然后再进行DMA读操作。同理使用这种方法对一段较大的数据区域进行DMA写时,也可以提高效率。
除此之外,当PCIe设备访问的存储器,不是“可Cache空间”时,也可以通过设置“No Snoop Attribute”位,避免FSB的Cache共享一致性操作,从而提高FSB的效率。“No Snoop Attribute”位是PCIe总线针对PCI总线的不足,所作出的重要改动。
除了Fmt和Type字段外,通用TLP头还含有以下字段。
TH位为1表示当前TLP中含有TPH(TLP Processing Hint)信息,TPH是PCIe V2.1总线规范引入的一个重要功能。TLP的发送端可以使用TPH信息,通知接收端即将访问数据的特性,以便接收端合理地预读和管理数据。
TD位表示TLP中的TLP Digest是否有效,为1表示有效,为0表示无效。而EP位表示当前TLP中的数据是否有效,为1表示无效,为0表示有效。
AT字段与PCIe总线的地址转换相关。在一些PCIe设备中设置了ATC(Address Translation Cache)部件,这个部件的主要功能是进行地址转换。只有在支持IOMMU技术的处理器系统中,PCIe设备才能使用该字段。
AT字段可以用作存储器域与PCI总线域之间的地址转换,但是设置这个字段的主要目的是为了方便多个虚拟主机共享同一个PCIe设备。对这个字段有兴趣的读者可以参考Address Translation Sevices规范,这个规范是PCI的IO Virtualization规范的重要组成部分。对虚拟化技术有兴趣的读者可以参考清华大学出版社的《系统虚拟化——原理与实现》,以获得基本的关于虚拟化的入门知识。
Length字段用来描述TLP的有效负载(Data Payload)大小(3).PCIe总线规范规定一个TLP的Data Payload的大小在1B~4096B之间。PCIe总线设置Length字段的目的是提高总线的传送效率。
当PCI设备在进行数据传送时,其目标设备并不知道实际的数据传送大小,这在一定程度上影响了PCI总线的数据传送效率。而在PCIe总线中,目标设备可以通过Length字段提前获知源设备需要发送或者请求的数据长度,从而合理地管理接收缓冲,并根据实际情况进行Cache一致性操作。
当PCI设备进行DMA写操作,将PCI设备中4KB大小的数据传送到主存储器时,这个PCI设备的DMA控制器将存放传送的目的地址和传送大小,然后启动DMA写操作,将数据写入到主存储器。由于PCI总线是一条共享总线,因此传送4KB大小的数据,可能会使用若干个PCI总线写事务才能完成(4),而每一个PCI总线写事务都不知道DMA控制器何时才能将数据传送完毕。
如果这些总线写事务还通过一系列PCI桥才能到达存储器,在这个路径上的每一个PCI桥也无法预知,何时这个DMA操作才能结束。这种“不可预知”将导致PCI总线的带宽不能被充分利用,而且极易造成PCI桥数据缓冲的浪费。
而PCIe总线通过TLP的Length字段,可以有效避免PCIe链路带宽的浪费。值得注意的是,Length字段以DW为单位,其最小单位为1个DW。如果PCIe主设备传送的单位小于1个DW或者传送的数据并不以DW对界时,需要使用字节使能字段,即“DW BE”字段。
(1)PowerPC处理器通过设置Inbound寄存器,也可以避免这个Cache一致性操作。(2)FSB收到这类TLP后,不进行Cache一致性操作。(3)存储器读请求TLP没有DataPayload字段,此时该TLP使用Length字段表示需要读取多少数据。(4)当多个PCI设备共享一条PCI总线时,一个设备不会长时间占用PCI总线,这个设备在使用这条PCI总线一定的时间后,将让出PCI总线的使用权。