0%

【计算机体系结构】PCIe 架构

PCI Express 是继ISA和PCI总线之后的第三代I/O总线。 由Intel在2001年的IDF上提出,由 PCI-SIG 认证发布后才改名为“PCI-Express”。它的主要优势就是数据传输速率高,另外还有抗干扰能力强,传输距离远,功耗低等优点。

发展历程

注:第一代总线一般指ISA、EISA、VESA和Micro Platforms。第二代总线一般指PCI、AGP和PCI-X。

1.png

图中的PCI-E的传输速率指的是实际的有效传输速率,为RAW Data速率的80%,因为PCI-E(Gen1&Gen2,Gen3中使用了新的方式,即128b/130b)中使用了8b/10b编解码技术。

PCI-Express总线的Spec中明确规定了PCI-Express的缩写为PCIe,但很多情况下,大家为了方便常把它缩写为PCI-E。
PCI-E接口根据总线位宽不同而有所差异,一个PCI Express连接可以被配置成x1, x2, x4, x8, x12, x16和x32的数据带宽。 (x2 and x12 link widths are optional) PCI-E 各种位宽Device可以自由搭配使用,比如x1 的卡可以插到x8的插槽中使用, x8 的卡可以插到x16的插槽中使用,升级方便。

file://C:\Users\xli2\AppData\Local\Temp\ct_tmp/2.png

一些常见的PCI-E设备如下图所示:

file://C:\Users\xli2\AppData\Local\Temp\ct_tmp/3.png

目前PCI-E已经更新到第四代(即PCI-E 4.0,Gen4),很快Gen5也会到来:

由于PCI-E是从PCI/PCI-X继承发展而来,PCI-E在应用层(软件上)几乎是全完兼容PCI/PCI-X设备的。在硬件层面上,可以借助PCI-E to PCI/PCI-X桥来与其完成对接。并且PCI-E是一种非常复杂的总线,因此学习PCIe的同时也必须提前对PCI和PCI-X总线有一定的了解,所以下面的连载博文将先从PCI和PCI-X总线介绍开始。但是并不会详细的介绍,而只是提一下其基本的概念,以及PCI-E与PCI/PCI-X的继承与改进的关系。

PCI 概念

PCI是 Peripheral Component Interconnect 的缩写,它曾经是个人电脑中使用最为广泛的接口,几乎所有的主板产品上都带有这种插槽。目前该总线已经逐渐被PCI Express总线所取代。

PCI即Peripheral Component Interconnect,中文意思是“外围器件互联”,是由PCISIG (PCI Special Interest Group)推出的一种局部并行总线标准。PCI总线是由ISA(Industy Standard Architecture)总线发展而来的,是一种同步的独立于处理器的32位或64位局部总线。从结构上看,PCI是在CPU的供应商和原来的系统总线之间插入的一级总线,具体由一个桥接电路实现对这一层的管理,并实现上下之间的接口以协调数据的传送。从1992年创立规范到如今,PCI总线已成为了计算机的一种标准总线,广泛用于当前高档微机、工作站,以及便携式微机。主要用于连接显示卡、网卡、声卡。

注:ISA并行总线有8位和16位两种模式,时钟频率为8MHz,工作频率为33MHz/66MHz。

PCI总线是一种树型结构,并且独立于CPU总线,可以和CPU总线并行操作。PCI总线上可以挂接PCI设备和PCI桥,PCI总线上只允许有一个PCI主设备(同一时刻),其他的均为PCI 从设备,而且读写操作只能在主从设备之间进行,从设备之间的数据交换需要通过主设备中转。

注:这并不意味着所有的读写操作都需要通过北桥中转,因为PCI总线上的主设备和从设备属性是可以变化的。比如Ethernet和SCSI需要传输数据,可以通过一种叫做 Peer-to-Peer 的方式来完成,此时 Ethernet 或者 SCSI 则作为主机,其它的设备则为从机。具体会在后面的博文中详细介绍。

一个典型的33MHz的PCI总线系统如上图所示,处理器通过FSB与北桥相连接,北桥上挂载着图形加速器(显卡)、SDRAM(内存)和PCI总线。PCI总线上挂载着南桥、以太网、SCSI总线(一种老式的小型机总线)和若干个PCI插槽。CD和硬盘则通过IDE连接至南桥,音频设备以及打印机、鼠标和键盘等也连接至南桥,此外南桥还提供若干的USB接口。

PCI总线是一种共享总线,所以需要特定的仲裁器(Arbiter)来决定当前时刻的总线的控制权。一般该仲裁器位于北桥中,而仲裁器(主机)则通过一对引脚,REQ#(request) 和GNT# (grant)来与各个从机连接。如下图所示:

需要注意的是,并不是所有的设备都有能力成为仲裁器(Arbiter)或者initiator 。

最初的PCI总线的时钟频率为33MHz,但是随着版本的更新,时钟频率也逐渐的提高。但是由于PCI采用的是一种Reflected-Wave Signaling信号模型(后面会详细的介绍),导致了时钟频率越高,总线的最大负载越少,如下图所示:

image.png

到了PCI-X2.0版本,整个总线就只能插一个PCI卡了(相当于两个PCI负载),为了能够在主板上提供更多的插槽,则必须通过连接多个PCI桥来实现(后面会详细地介绍)。

PCI 问题:并行,始终频率限制

PCIe是从PCI发展过来的,PCIe的”e”是express的简称,快的意思。PCIe怎么就能比PCI(或者PCI-X)快呢?PCIe在物理传输上,跟PCI有着本质的区别:PCI使用并口传输数据,而PCIe使用的是串口传输。我PCI并行总线,单个时钟周期可以传输32bit或者64bit,怎么就比不了你单个时钟周期传输1个bit数据的串行总线呢?

在实际时钟频率比较低的情况下,并口因为可以同时传输若干比特,速率确实比串口快。随着技术的发展,数据传输速率要求越来越快,要求时钟频率也越来越快,但是,并行总线时钟频率不是想快就能快的。

img

在发送端,数据在某个时钟沿传出去(左边时钟第一个上升沿),在接收端,数据在下个时钟沿(右边时钟第二个上升沿)接收。因此,要在接收端能正确采集到数据,要求时钟的周期必须大于数据传输的时间(从发送端到接收端,flight time)。受限于数据传输时间(该时间还随着数据线长度的增加而增加),因此时钟频率不能做得太高。另外,时钟信号在线上传输的时候,也会存在相位偏移(clock skew ),影响接收端的数据采集;还有,并行传输,接收端必须等最慢的那个bit数据到了以后,才能锁住整个数据 (signal skew)。

PCIe使用串行总线进行数据传输就没有这些问题。它没有外部时钟信号,它的时钟信息通过8/10编码或者128/130编码嵌入在数据流,接收端可以从数据流里面恢复时钟信息,因此,它不受数据在线上传输时间的限制,你导线多长都没有问题,你数据传输频率多快也没有问题;没有外部时钟信号,自然就没有所谓的clock skew问题;由于是串行传输,只有一个bit传输,所以不存在signal skew问题。但是,如果使用多条lane传输数据(串行中又有并行,哈哈),这个问题又回来了,因为接收端同样要等最慢的那个lane上的数据到达才能处理整个数据。

PCI总线是一种地址和数据复用的总线,即地址和数据占用同一组信号线AD。PCI总线的所有信号都与时钟信号同步,及所有的信号的变化都发生在时钟的上升沿,或者在时钟上升沿进行采样。

如下图所示,除了时钟信号CLK和数据地址复用信号AD之外,PCI总线至少还应包括FRAME#(用于表示一次数据传输的起始)、C/BE#(Command/Byte Enable)、IRDY#(Initiator Ready for data)、TRDY#(Target ready)、DESEL#(Device Selec,片选信号,用于选择PCI设备)和GNT#(Grant)信号等。

注:完整的信号时序图,请参考PCI Spec。信号名后面的#表示该信号低电平有效。

下面来介绍一个简单的例子,主机接收来自特定从机的数据。

blob.png

如上图所示:

1、在第一个时钟上升沿,FRAME#和IRDY#都为inactive,表明总线当前处于空闲状态。与此同时,某个设备的GNT#信号处于active,表明总线总裁器已经选定当前设备为下一个initiator(可以理解为主机)。

2、在第二个时钟上升沿,FRAME#被initiator拉低,表明新的事务(Transaction)已经开始。与此同时,地址和命令被依次发送到AD上,总线上面的所有其他设备(从机)都会锁存这些信息,并检查地址和命令是否与自己匹配。

3、在第三个时钟上升沿,IRDY#处于active状态,表明主机准备就绪,可以接收数据了。AD信号上的旋转的箭头表示AD信号目前处于三态状态(处于输出和输入的转换状态),即Turn‐around cycle。需要注意的是,此时的TRDY#应当处于inactive状态,以保证Turn‐around cycle顺利进行。

4、在第四个时钟上升沿,PCI总线上的某个从机确认身份,并依次将DEVSEL#信号和TRDY#拉低,并将相应的数据输出到AD上。此时,FRAME#信号为active状态,表明这并不是最后一个数据。

5、在第五个时钟上升沿,TRDY#处于inactive状态,表明从机尚未就绪,因此所有的操作暂缓一个时钟周期(或者说插入了一个Wait State)。PCI总线最多允许8个这样的Wait State。

6、在第六个时钟上升沿,从机向主机发送第二个数据。此时,FRAME#信号依旧为active状态,表明这并不是最后一个数据。

7、在第七个时钟上升沿,IRDY#处于inactive状态,表明主机尚未就绪,再次插入一个Wait State。但是此时从机依旧可以向AD上发送数据。

8、在第八个时钟上升沿,AD上的第三个数据被发送至主机,由于此时FRAME#信号被拉高,即inactive,表明这是本次事务(Transaction)的最后一个数据。此后,所有的控制信号均被拉高,处于inactive状态,AD、FRAME#和C/BE#处于三态状态。

PCI Spec规定了每个PCI总线上最多可以连接多达32个PCI设备,但是实际上却远远达不到32个,33MHz的32位PCI总线一般只能连接10到12个负载。

注:如果使用插槽连接,则一个连接算两个PCI设备,插槽和PCI卡分别算作一个PCI设备。也就是说一个33MHz的PCI总线最多只能连接4到5个插槽即PCI卡。

这是因为PCI总线在设计的时候,为了降低功耗,采用了一种叫做reflected‐wave signaling的技术,如下图所示:

image.png

由图可知,为了降低功耗PCI设备的发送端采用了一种 weak transmit buffers,其只能驱动信号电平达到实际需求的一半。然后依靠反射回来的信号叠加到原本的信号上,使得信号电平达到实际的需求。当然,所有的这些过程都要求在一个时钟周期内完成,这种机制也限制了PCI总线频率的提高,也限制了单个PCI总线上的最大连接设备的数量。如果需要连接更多的PCI设备,则需要借助PCI-to-PCI桥,每个桥的内部都有隔离,这保证了每个桥又可以连接额外的10~12个负载。但是PCI Spec规定了,一个PCI总线系统中,最多只能有256个子总线。

注:熟悉信号完整性分析的朋友应该知道,在高速信号传输中,反射往往会导致信号的形变,使得信号质量变差,误码率变高,甚至信号收发异常,传输中断。然而,在一些特定的领域(比如PCI总线),合理地使用反射机制,可以达到降低功耗等效果。不过,相对于目前的很多高速串行信号,PCI总线算是很慢的信号了,像PCI总线这种利用反射机制降低功耗的,几乎不可能在高速信号得到应用。

image.png

此外,PCI总线的 Input Buffer 还没有加输入寄存器,这对信号的Setup时间提出了更高的要求。

一个包含PCI-to-PCI桥的33MHz PCI总线系统的架构图如下所示:

image.png

拓扑结构:总线型

计算机网络的最主要的拓扑结构有总线型拓扑、环形拓扑、树形拓扑、星形拓扑、混合型拓扑以及网状拓扑。

PCI采用的是总线型拓扑结构,一条PCI总线上挂着若干个PCI终端设备或者PCI桥设备,大家共享该条PCI总线,哪个人想说话,必须获得总线使用权,然后才能发言。下面是一个基于PCI的传统计算机系统:

img

北桥下面的那根PCI总线,挂载了以太网设备、SCSI设备、南桥以及其他设备,他们共享那条总线,某个设备只有获得总线使用权才能进行数据传输。

数据传输模型

本文来简单地介绍一下PCI Spec规定的三种数据传输模型:Programmed I/O(PIO),Peer-to-Peer和DMA。

三种数据传输模型的示意图如下图所示:

image.png

Programmed I/O

PIO在早期的PC中被广泛使用,因外当时的处理器的速度要远远大于任何其他外设的速度,所以PIO足以胜任所有的任务。举一个例子,比如说某一个PCI设备需要向内存(SDRAM)中写入一些数据,该PCI设备会向CPU请求一个中断,然后CPU首先先通过PCI总线把该PCI设备的数据读取到CPU内部的寄存器中,然后再把数据从内部寄存器写入到内存(SDRAM)中。

现在看来,这种传输方式的效率还是很低的。首先,每次CPU和PCI设备以及SDRAM通信都需要额外的时钟周期(相对于DMA);其次,这种传输方式还需要长时间地占用CPU,影响CPU的使用率。试想一下,你在用PC在线观看一个1080p60的高清视频,这需要以太网连续地向内存(SDRAM)中写入数据,如果使用PIO的方式的话,将难以保证数据的写入速度。随着目前的PCI外设速度越来越高,PIO已经逐渐被DMA传输方式所取代,但是为了兼容早期的一些设备,PCI Spec依然保留了PIO。

DMA

DMA是一种在传输过程中,几乎不需要CPU进行干预的数据传输方式。如上面的图片所示,以太网可以直接向内存(SDRAM)中写入数据,而几乎不需要CPU的干预。实际上,DMA不仅仅应用于PCI总线系统中,它是一种更为广泛应用的数据传输方式。目前,几乎所有的CPU,甚至是MCU都支持DMA。

Peer-to-Peer

前面的文章中,我们介绍过PCI总线系统中的主机身份并不是固定不变的,而是可以切换的(借助仲裁器),但是同一时刻只能存在一个主机。完成Peer-to-Peer这一传输方式的前提是,PCI总线系统中至少存在一个有能力成为主机的设备。在仲裁器的控制下,完成主机身份的切换,进而获得PCI总线的控制权,然后与总线上的其他PCI设备进行通信。不过,需要注意的是,在实际的系统中,Peer-to-Peer这一传输方式却很少被使用,这是因为获得主机身份的PCI设备(Initiator)和另一个PCI设备(Target)通常采用不同的数据格式,除非他们是同一个厂家的设备。


PCI总线使用INTA#、INTB#、INTC#和INTD#信号向处理器发出中断请求。这些中断请求信号为低电平有效,并与处理器的中断控制器连接。在PCI体系结构中,这些中断信号属于边带信号(Sideband Signals),PCI总线规范并没有明确规定在一个处理器系统中如何使用这些信号,因为这些信号对于PCI总线是可选信号。所谓边带信号是指这些信号在PCI总线中是可选信号,而且只能在一个处理器系统的内部使用,并不能离开这个处理器环境。

注:PCI Spec对边带信号的定义如下:

Any signal not part of the PCI specification that connects two or more PCI-compliant agents and has meaning only to those agents.

完整的PCI信号结构图如下:

image.png

中断信号与中断控制器的连接关系

PCI总线规范没有规定PCI设备的INTx信号如何与中断控制器的IRQ_PINx#信号相连,这为系统软件的设计带来了一定的困难,为此系统软件使用中断路由表存放PCI设备的INTx信号与中断控制器的连接关系。在x86处理器系统中,BIOS可以提供这个中断路由表,而在PowerPC处理器中Firmware也可以提供这个中断路由表。

在一些简单的嵌入式处理器系统中,Firmware并没有提供中断路由表,此时系统软件开发者需要事先了解PCI设备的INTx信号与中断控制器的连接关系。此时外部设备与中断控制器的连接关系由硬件设计人员指定。

我们假设在一个处理器系统中,共有3个PCI插槽(分别为PCI插槽A、B和C),这些PCI插槽与中断控制器的IRQ_PINx引脚(分别为IRQW#、IRQX#、IRQY#和IRQZ#)可以按照下图所示的拓扑结构进行连接。

image.png

此时,PCI插槽A、B、C的INTA#、INTB#和INTC#信号将分散连接到中断控制器的IRQW#、IRQX#和IRQY#信号,而所有INTD#信号将共享一个IRQZ#信号。采用这种连接方式时,整个处理器系统使用的中断请求信号,其负载较为均衡。而且这种连接方式保证了每一个插槽的INTA#信号都与一根独立的IRQx#信号对应,从而提高了PCI插槽中断请求的效率。

在一个处理器系统中,多数PCI设备仅使用INTA#信号,很少使用INTB#和INTC#信号,而INTD#信号更是极少使用。在PCI总线中,PCI设备配置空间的Interrupt Pin寄存器记录该设备究竟使用哪个INTx信号。

中断信号与PCI总线的连接关系

在PCI总线中,INTx信号属于边带信号。PCI桥也不会处理这些边带信号。这给PCI设备将中断请求发向处理器带来了一些困难,特别是给挂接在PCI桥之下的PCI设备进行中断请求带来了一些麻烦。

在一些嵌入式处理器系统中,这个问题较易解决。因为嵌入式处理器系统很清楚在当前系统中存在多少个PCI设备,这些PCI设备使用了哪些中断资源。在多数嵌入式处理器系统中,PCI设备的数量小于中断控制器提供的外部中断请求引脚数,而且在嵌入式系统中,多数PCI设备仅使用INTA#信号提交中断请求。

在这类处理器系统中,可能并不含有PCI桥,因而PCI设备的中断请求信号与中断控制器的连接关系较易确定。而在这类处理器系统中,即便存在PCI桥,来自PCI桥之下的PCI设备的中断请求也较易处理。

在多数情况下,嵌入式处理器系统使用的PCI设备仅使用INTA#信号进行中断请求,所以只要将这些INTA#信号挂接到中断控制器的独立IRQ_PIN#引脚上即可。这样每一个PCI设备都可以独占一个单独的中断引脚。

而在x86处理器系统中,这个问题需要BIOS参与来解决。在x86处理器系统中,有许多PCI插槽,处理器系统并不知道在这些插槽上将要挂接哪些PCI设备,而且也并不知道这些PCI设备到底需不需要使用所有的INTx#信号线。因此x86处理器系统必须要对各种情况进行处理。

x86处理器系统还经常使用PCI桥进行PCI总线扩展,扩展出来的PCI总线还可能挂接一些PCI插槽,这些插槽上INTx#信号仍然需要处理。PCI桥规范并没有要求桥片传递其下PCI设备的中断请求。事实上多数PCI桥也没有为下游PCI总线提供中断引脚INTx#,管理其下游总线的PCI设备。但是PCI桥规范推荐使用下面的表建立下游PCI设备的INTx信号与上游PCI总线INTx信号之间的映射关系。

image.png

我们举例说明该表的含义。在PCI桥下游总线上的PCI设备,如果其设备号为0,那么这个设备的INTA#引脚将和PCI总线的INTA#引脚相连;如果其设备号为1,其INTA#引脚将和PCI总线的INTB#引脚相连;如果其设备号为2,其INTA#引脚将和PCI总线的INTC#引脚相连;如果其设备号为3,其INTA#引脚将和PCI总线的INTD#引脚相连。

在x86处理器系统中,由BIOS或者APCI表记录PCI总线的INTA~D#信号与中断控制器之间的映射关系,保存这个映射关系的数据结构也被称为中断路由表。大多数BIOS使用表中的映射关系,这也是绝大多数BIOS支持的方式。如果在一个x86处理器系统中,PCI桥下游总线的PCI设备使用的中断映射关系与此不同,那么系统软件程序员需要改动BIOS中的中断路由表。

BIOS初始化代码根据中断路由表中的信息,可以将PCI设备使用的中断向量号写入到该PCI设备配置空间的Interrupt Line register寄存器中。

PCI总线的错误处理

image.png

PCI设备可以通过奇偶校检来检测到来自AD上的地址或者数据的错误,并通过PERR#或者SERR#报告错误。但是需要注意的是,PCI Spec并未规定任何硬件层面上的错误处理或者恢复机制,因此,这些错误都只能通过软件进行处理。


PCI总线具有32位数据/地址复用总线,所以其存储地址空间为2的32次方=4GB。也就是PCI上的所有设备共同映射到这4GB上,每个PCI设备占用唯一的一段PCI地址,以便于PCI总线统一寻址。每个PCI设备通过PCI寄存器中的基地址寄存器来指定映射的首地址。如下图所示:

1.png

注:需要注意的是PCI的地址空间和x86系统中的FSB并不是对等的,而是具有一定的映射关系。

PCI体系结构中,一共支持三种地址空间:Memory Address Space、I/O Address Space和Configuration Address Space。其中x86处理器可以直接访问的只有Memory Address Space和I/O Address Space。而访问Configuration Address Space则需要通过索引IO寄存器来完成。

注:在PCIe中,则引入了一种新的Configuration Address Space访问方式:将其直接映射到了Memory Address Space当中。

2.png

如上图所示,最左边的即为Memory Address Space,其中包括了多个PCI Memory、AGP Video(显卡)Memory以及Extended Memory、Boot ROM等。中间的为I/O Address Space,需要注意的是,虽然PCI支持32位的地址,但是由于x86的CPU只支持16位的I/O空间,这就限制了PCI的I/O Address Space最大只有64KB。最右边的则为Configuration Address Space,由于每一个PCI设备最多支持8种功能(Function),每一条PCI总线最多支持32个设备,而每一个PCI总线系统最多又支持256个子总线(通过PCI桥)。因此,总的Configuration Address Space的大小为:256 Bytes/function x 8 functions/device x 32 devices/bus x 256 buses/system = 16MB。

如图中所示,Configuration Address Space 所使用的IO寄存器范围为0xCF8~0xCFF。其中0xCF8~0xCFB为端口地址,0xCFC~0xCFF为配置数据。

PCIe 基础

特征

  • 双单工
  • 点对点
  • 串行连接
  • 基于 Packet 的传输协议
  • Scalable Link Width
  • Scalable Link Speeds

基本术语

  • Link
  • Lane
  • Wire
  • Signal

两个设备之间的PCIe连接,叫做一个Link,如下图所示:

从A到B,之间是个双向连接,车可以从A驶向B,同时,车也可以从B驶向A,各行其道。两个PCIe设备之间,有专门的发送和接收通道,数据可以同时往两个方向传输,PCIe spec称这种工作模式为双单工模式(dual-simplex),可以理解为全双工模式。

SATA是什么工作模式呢?

img

和PCIe一样,SATA也有独立的发送和接收通道,但与PCIe工作模式不一样:同一时间,只有一条道可以进行数据传输,也就是说,你在一条道上发送数据,另外一条道上不能接收数据,反之亦然。这种工作模式应该是半双工模式。PCIe犹如我们的手机,双方可以同时讲话,而SATA就是对讲机了,一个人在说话,另外一个人就只能听不能说。

Link Width这一行,我们看到X1,X2,X4…,这是什么意思?这是指PCIe连接的通道数(Lane)。就像高速一样,有单根道,有2根道的,有4根道的,不过像8根道或者更多道的公路不常见,但PCIe是可以最多32条道的。

速度计算

PCIe发展到现在,从PCIe 1.0,PCIe 2.0,到现在的PCIe 5.0,速度一代比一代快。

这里解释下PCIe带宽的计算方法:

在 PCIe 1.0 版本,物理层采用 8b/10b 编码,也就是说8比特的数据实际在物理线路上需要传输10比特。与此同时,PCIe 1.0 版本中线上传输速率为 2.5 GT/s,也就是串行传输数据速率为 2.5 Gb/s。那么,在单通道的情况下,也就是 PCIe Gen 1x1,两个PCIe设备间 Link 每个方向传输带宽为:

如果是 16 通道,则传输带宽为单通道传输带宽乘以通道数目,也即是 4 GB/s。

PCIe 2.0 线上比特传输速率在 PCIe 1.0 的基础上翻了一倍,为5Gb/s,物理层同样使用 8b/10b 编码,所以 PCIe Gen 2x1 的传输带宽为 0.5 GB/s。

PCIe 3.0 的线上比特传输速率没有在 PCIe 2.0 的基础上翻倍,不是10Gb/s,而是8Gb/s,但物理层使用的是 128b/130b 编码进行数据传输,所以在单通道的情况下,也就是 PCIe Gen 3x1,两个PCIe设备间 Link 传输带宽为

由于采用了128/130编码,128比特的数据,只额外增加了2bit的开销,有效数据传输比率增大,虽然线上比特传输率没有翻倍,但有效数据带宽还是在PCIe2.0的基础上做到翻倍。

拓扑结构:树形

PCIe 则采用 树形 拓扑结构,一个简单而又典型的PCIe拓扑结构如下:整个PCIe拓扑结构是一个树形结构,Root Complex(RC)是树的根。RC为CPU代言,与整个计算机系统其它部分通讯,比如CPU通过它访问内存,通过它访问PCIe系统中的设备。CPU像皇上一样高高在上,而RC好比皇上身边当红的太监,皇上想叫下面的人做点事情,通过太监传达;下面的人也是通过太监,向皇上反应一些情况。不过,这个太监不寻常,它是有根(root)的。

  • Root Complex:RC的内部实现很复杂,PCIe Spec也没有规定RC该做什么,还是不该做什么。我们也不需要知道那么多,只需清楚:它一般实现了一条内部PCIe总线(BUS 0),以及通过若干个PCIe bridge,扩展出一些PCIe Port
    • RC是树的根,或者主干,它为CPU代言,与PCIe系统其它部分通讯,一般为通讯的发起者
  • Switch:Switch用于扩展链路,提供更多的端口用以连接Endpoint。Switch扩展了PCIe端口,
    • Upstream Port:靠近RC的那个端口,一个Switch只有一个上游端口,可以扩展出若干个下游端口。
    • Downstream Port:Switch分出来的其他端口,下游端口可以直接连接Endpoint,也可以连接Switch,扩展出更多的PCIe端口。
    • 对每个Switch来说,它下面的Endpoint或者Switch,都是归他管的:上游下来的数据,它需要甄别数据是传给它下面哪个设备,然后进行转发;下面设备向RC传数据,也要通过Switch代为转发的。因此,Switch的作用就是扩展PCIe端口,并为挂在它上面的设备(endpoint 或者switch)提供路由和转发服务。
    • 每个Switch内部,也是有一根内部PCIe总线的,然后通过若干个Bridge,扩展出若干个下游端口
    • Switch是树枝,树枝上有叶子(Endpoint),也可节外生枝,Switch上连Switch,归根结底,是为了连接更多的Endpoint。 Switch为它下面的Endpoint或Switch提供路由转发服务;
  • Endpoint:这些Endpoint可以直接连在RC上,也可以通过Switch连到PCIe总线上。
    • Endpoint是树叶,诸如SSD,网卡,显卡等等,实现某些特定功能(function)。
    • PCIe Endpoint:就是PCIe终端设备,比如PCIe SSD,PCIe网卡等等
    • Legacy Endpoint:接口是PCIe,但是内部的行为却和传统的PCI或者PCI-x一样(比如支持IO空间)
  • 我们还看到有所谓的Bridge,用以将PCIe总线转换成PCI总线,或者反过来,不是我们要讲的重点,忽略之。PCIe与采用总线共享式通讯方式的PCI不同,PCIe采用点到点(Endpoint to Endpoint)通讯方式,每个设备独享通道带宽,速度和效率都比PCI好。

最后,以一个实际的计算机系统例子结束本文:

img

分层结构

绝大多数的总线或者接口,都是采用分层实现的。PCIe也不例外,它的层次结构如下,PCIe传输的数据从上到下,都是以packet的形式传输的,每个packet都是有其固定的格式的。

PCIe Packet-Based Communication

  • Transaction Layer:创建(发送)或者解析(接收)TLP (Transaction Layer packet),流量控制,QoS,事务排序等。
  • Data Link Layer:创建(发送)或者解析(接收)DLLP(Data Link Layer packet),Ack/Nak协议(链路层检错和纠错),流控,电源管理等。
  • Physical Layer:处理所有的Packet数据物理传输,发送端数据分发到各个Lane传输(stripe),接收端把各个Lane上的数据汇总起来(De-stripe),每个Lane上加扰(Scramble,目的是让0和1分布均匀,去除信道的电磁干扰EMI)去扰(De-scramble),以及8/10或者128/130编码解码,等等。

总结上面的层次结构,如下图所示:

PCIe 地址空间

配置空间

每个PCIe设备,有这么一段空间,Host软件可以读取它获得该设备的一些信息,也可以通过它来配置该设备,这段空间就叫做PCIe的配置空间。不同于每个设备的其它空间,PCIe设备的配置空间是协议规定好的,哪个地方放什么内容,都是有定义的。PCI或者PCI-X时代就有配置空间的概念,那时的配置空间如下:

img

整个配置空间就是一系列寄存器的集合,其中Type 0是Endpoint的配置,Type 1是Bridge(PCIe时代就是Switch)的配置,都由两部分组成:64 Bytes的Header+192Bytes的Capability结构,后者是设备告诉Host它有多牛逼,都会什么绝活。

进入PCIe时代,PCIe能耐更大,192 Bytes不足以罗列它的绝活。为了保持后向兼容,又要不把绝活落下,怎么办?很简单,我扩展后者的空间,整个配置空间由256 Bytes扩展成4KB,前面256 Bytes保持不变:

img

PCIe有什么能耐(Capability)我们不看,我们先挑软柿子捏,先看看只占64 Bytes的Configuration Header。

img

像Device ID,Vendor ID,Class Code和Revision ID,是只读寄存器,PCIe设备通过这些寄存器告诉Host软件,这是哪个厂家的设备、设备ID是多少、以及是什么类型的(网卡?显卡?桥?)设备。

其它的我们暂时不看,我们看看重要的BAR(Base Address Register)。

对Endpoint Configuration(Type 0),提供了最多6个BAR,而对Switch(Type 1)来说,只有2个。BAR是干什么用的?

每个PCIe设备,都有自己的内部空间,这部分空间如果开放给Host(软件或者CPU)访问,那么Host怎样才能往这部分空间写入数据,或者读数据呢?

我们知道,CPU只能直接访问Host内存(Memory)空间(或者IO空间,我们不考虑),不对PCIe等外设直接操作。怎么办?记得皇帝身边那个有根的太监吗?Root Complex,RC。RC可以为CPU分忧。

解决办法是:CPU如果想访问某个设备的空间,由于它不能(或者不屑)亲自跟那些PCIe外设打交道,因此叫太监RC去办。比如,如果CPU想读PCIe外设的数据,先叫RC通过TLP把数据从PCIe外设读到Host内存,然后CPU从Host内存读数据;如果CPU要往外设写数据,则先把数据在内存中准备好,然后叫RC通过TLP写入到PCIe设备。完美!

img

上图例子中,最左边虚线的表示CPU要读Endpoint A的数据,RC则通过TLP(经历Switch)数据交互获得数据,并把它写入到系统内存中,然后CPU从内存中读取数据(紫色箭头所示),从而CPU间接完成对PCIe设备数据的读取。

地址空间

具体实现就是上电的时候,系统把PCIe设备开放的空间(系统软件可见)映射到内存空间,CPU要访问该PCIe设备空间,只需访问对应的内存空间。RC检查该内存地址,如果发现该内存空间地址是某个PCIe设备空间的映射,就会触发其产生TLP,去访问对应的PCIe设备,读取或者写入PCIe设备。

一个PCIe设备,可能有若干个内部空间(属性可能不一样,比如有些可预读,有些不可预读)需要映射到内存空间,设备出厂时,这些空间的大小和属性都写在Configuration BAR寄存器里面,然后上电后,系统软件读取这些BAR,分别为其分配对应的系统内存空间,并把相应的内存基地址写回到BAR。(BAR的地址其实是PCI总线域的地址,CPU访问的是存储器域的地址,CPU访问PCIe设备时,需要把总线域地址转换成存储器域的地址。)

img

如上图例子,一个Native PCIe Endpoint,只支持Memory Map,它有两个不同属性的内部空间要开放给系统软件,因此,它可以分别映射到系统内存空间的两个地方;还有一个Legacy Endpoint,它既支持Memory Map,还支持IO Map,它也有两个不同属性的内部空间,分别映射到系统内存空间和IO空间。

来个例子,看一下一个PCIe设备,系统软件是如何为其分配映射空间的。

img

上电时,系统软件首先会读取PCIe设备的BAR0,得到数据:

img

然后系统软件往该BAR0写入全1,得到:

img

BAR寄存器有些bit是只读的,是PCIe设备在出厂前就固定好的bit,写全1进去,如果值保持不变,就说明这些bit是厂家固化好的,这些固化好的bit提供了这块内部空间的一些信息:

怎么解读?低12没变,表明该设备空间大小是4KB(2的12次方),然后低4位表明了该存储空间的一些属性(IO映射还是内存映射,32bit地址还是64bit地址,能否预取?做过单片机的人可能知道,有些寄存器只要一读,数据就会清掉,因此,对这样的空间,是不能预读的,因为预读会改变原来的值),这些都是PCIe设备在出厂前都设置好的,提供给系统软件的信息。

然后系统软件根据这些信息,在系统内存空间找到这样一块地方来映射这4KB的空间,把分配的基地址写入到BAR0:

img

从而最终完成了该PCIe空间的映射。一个PCIe设备可能有若干个内部空间需要开放出来,系统软件依次读取BAR1,BAR2。。。,直到BAR5,完成所有内部空间的映射。

上面主要讲了Endpoint的BAR,Switch也有两个BAR,今天不打算讲,下节讲TLP路由,再回过头来讲。继续说配置空间。

前面说每个PCIe设备都有一个配置空间,其实这样说是不准确的,而是每个PCIe设备至少有一个配置空间。一个PCIe设备,它可能具有多个功能(function),比如既能当硬盘,还能当网卡。每个功能对应一个配置空间。

在一个PCIe拓扑结构里,一条总线下面可以挂几个设备,而每个设备可以具有几个功能,如下所示:

img

因此,在整个PCIe系统中,只要知道了Bus+Device+Function,就能找到对应的Function。寻址基本单元是功能(function),它的ID就由Bus+Device+Function组成 (BDF)。一个PCIe系统,可以最多有256条Bus,每条Bus上可以挂最多32个Device,而每个Device最多又能实现8个Function,而每个Function对应着4KB的配置空间。上电的时候,这些配置空间都是需要映射到Host的内存空间,因此,需要占用内存空间是:256328*4KB =256MB。在这个动辄4GB、8GB内存的时代,256MB算不了什么。

系统软件是如何读取Configuration空间呢?不能通过BAR中的地址,为什么?别忘了BAR是在Configuration中的,你首先要读取Configuration,才能得到BAR。前面不是系统为所有可能的Configuration预留了256MB内存空间吗?系统软件想访问哪个Configuration,只需指定相应Function对应的内存空间地址,RC发现这个地址是Configuration映射空间,就会产生相应的Configuration Read TLP去获得相应Function的Configuration。

再回想一下前面介绍的Configuration Read TLP的Header格式:

img

Bus Number + Device + Function就唯一决定了目标设备; Ext Reg Number + Register Number相当于配置空间的偏移。找到了设备,然后指定了配置空间的偏移,就能找到具体想访问的配置空间的某个位置。

结束前,强调一下,只有RC才能发起Configuration的访问请求,其他设备是不允许对别的设备进行Configuration读写的。

TLP

TLP 结构

Host与PCIe设备之间,或者PCIe设备与设备之间,数据传输都是以Packet形式进行的。事务层根据上层(软件层或者应用层)请求(Request)的类型、目的地址和其它相关属性,把这些请求打包,产生TLP,也就是Transaction Layer Packet。然后这些TLP往下,经历数据链路层,物理层,最终到达目标设备。

根据软件层的不同请求,事务层产生四种不同的TLP请求:

  1. Memory

  2. IO

  3. Configuration

  4. Message

前三种分别用于访问内存空间、IO空间、配置空间,这三种请求在PCI或者PCI-X时代就有了;最后的Message请求是PCIe新加的。在PCI或者PCI-X时代,像中断、错误以及电源管理相关信息,都是通过边带信号(sideband signal)进行传输的,但PCIe干掉了这些边带信号线,所有的通讯都是走带内信号,即通过Packet传输,因此,过去一些由边带信号线传输的数据,比如中断信息、错误信息等,现在就交由Message来传输了。

我们知道,一个设备的物理空间,可以通过内存映射(Memory map)的方式映射到Host的主存,有些空间还可以映射到Host的IO空间(如果Host存在IO空间的话)。但新的PCIe设备(区别于Legacy PCIe设备)只支持内存映射,之所以还存在访问IO空间的TLP,完全是为了照顾那些老设备。以后IO映射的方式会逐渐取消,为减轻学习压力,我们以后看到IO 相关的东西,大可或略之。

所有的配置空间(Configuration)的访问,都是Host发起的,确切的说是RC发起的,往往只在上电枚举和配置阶段会发起Configuration的访问,这样的TLP很重要,但不是常态; Message也是一样,只有有中断,或者有错误等情况下,才会有Message TLP,属非主流。PCIe线上主流传输的是Memory访问相关的TLP,Host与device,或者device与device之间,数据都是在彼此的Memory之间(抛掉IO)交互,因此,这种TLP是我们最常见的。

这四种请求,如果需要对方响应的,我们叫做Non-Posted的TLP;如果不期望对方给响应的,我们称之为Posted TLP。Post,有”邮政”的意思,我们只管把信投到邮箱,能不能到达对方,就取决于邮递员了。Posted TLP,就是不指望对方回复(信能不能收到都是个问题);Non-Posted TLP,就是要求对方务必回复。

哪些TLP是Posted,哪些又是non-posted的呢?像Configuration和IO访问,无论读写,都是Non-posted的,这样的请求必须得到设备的响应;Message TLP是Posted;Memory Read必须是Non-posted,我读你数据,你不返回数据(返回数据也是响应),那肯定不行的。所以,Memory Read必须得到响应。而Memory Write是Posted,我数据传给你,无需回复,这样Host或者Device可以不等对方回复,趁早把下一笔数据写下去,这样一定程度上提高了写的性能。有人会担心如果没有得到对方的响应,发送者就没有办法知道数据究竟有没有成功写入,就有丢数据的风险。虽然这个风险存在(概率很小),但数据链路层提供了ACK/NAK机制,一定程度上能保证TLP正确交互,因此能很大程度减小数据写失败的可能。

Request Type Non-Posted or Posted
Memory Read Non-Posted
Memory Write Posted
Memory Read Lock Non-Posted
IO Read Non-Posted
IO Write Non-Posted
Configuration Read (Type 0 and Type 1) Non-Posted
Configuration Write (Type 0 and Type 1) Non-Posted
Message Posted

所以,只要记住只有Memory Write和Message两种TLP是Posted就可以了。

Memory Read Lock是历史的遗留物,Native PCIe设备已经抛弃了这个,存在的意义完全是为了兼容Legacy PCIe设备。和IO一样,我们以后也忽略。能不看的就不看,PCIe东西本来就多,不要被这些过时没用的东西挡着我们学习的道路。

Configuration一栏,看到有Type 0和Type 1。我们在之前的拓扑结构中,看到除了Endpoint之外,还有Switch,他们都是PCIe设备,但配置种类不同,因此用Type 0和Type 1区分。

Request Type Non-Posted or Posted
Memory Read Non-Posted
Memory Write Posted
Configuration Read (Type 0 and Type 1) Non-Posted
Configuration Write (Type 0 and Type 1) Non-Posted
Message Posted

这样,Request TLP是不是清爽点?

对Non-Posted的Request,是一定需要对方响应的,对方是通过返回一个Completion TLP来作为响应的。对Read Request,响应者通过Completion TLP返回请求者所需的数据,这种Completion TLP包含有效数据;对Write Request(现在只有Configuration Write了)来说,响应者通过Completion TLP告诉请求者执行状态,这样的Completion TLP不含有效数据。

因此,PCIe里面所有的TLP = Request TLP + Completion TLP。

TLP Packet Type Abbreviated Name
Memory Read MRd
Memory Write MWr
Configuration Read(Type 0 and Type 1) CfgRd0, CfgRd
Configuration Write(Type 0 and Type 1) CfgWr0,CfgWr1
Message Request with Data MsgD
Message Request without Data Msg
Completion with Data CplD
Completion without Data Cpl

看个Memory Read的例子:

img

例子中,Switch B下面的某个Endpoint想读Host内存的数据,因此,它在事务层上生成一个Memory Read TLP,该MRd一路向上,翻过B,越过A,最终到达RC。RC收到该Request,就到内存中取该Endpoint所需的数据,RC通过Completion with Data TLP(CplD)返回数据,原路返回,直到Endpoint。

一个TLP,最多只能携带4KB有效数据,因此,上例子,如果Endpoint需要读16KB的数据,RC必须返回4个CplD给Endpoint。注意,Endpoint只需发1个MRd就可以了。

再看个Memory Write的例子:

img

该例子中,Host想往某个Endpoint写入数据,因此RC在其事务层生成一个Memory Write TLP(要写的数据在该TLP中),翻过A,越过B,直到目的地。前面说过Memory Write TLP是Posted的,因此,Endpoint收到数据后,是不需要返回Completion TLP(如果这个时候返回Completion TLP,反而是画蛇添足)。

同样的,由于一个TLP只能携带 4KB数据,因此Host想往Endpoint上写入16KB数据,RC必须发送4个MWr TLP。

TLP 包结构

数据从上到下,一层一层打包,上层打包完的数据,作为下层的原始数据,再打包。就像人穿衣服一样,穿了内衣穿衬衫,穿了衬衫穿外套。

img

红色的是TLP的格式,Data是事务层上层给的数据,事务层给它头上加个Header,然后尾巴上再加个CRC校验,就构成了一个TLP;这个TLP下传到数据链路层,又被数据链路层头上加了个包序列号,尾巴再加个CRC校验,构成一个DLLP;然后DLLP下传到物理层,头上加个Start,尾巴加个End符号,把这些数据分派到各个Lane上,然后每个Lane上加扰码,经8/10或128/130编码,最后通过物理传输介质传输给接收方。

接收方物理层是最先接收到这些数据的,然后执行逆操作;在数据链路层,校验序列号和LCRC,如果没错,剥掉序列号和LCRC,往事务层走;如果校验出差,通知对方重传;在事务层,校验ECRC,有错,数据抛弃,没错,去掉ECRC,获得数据。整个过程犹如脱衣睡觉,外套脱了,衬衫脱了,内衣也脱了,光溜溜钻进被窝。

img

和PCI数据裸奔不同,PCIe的数据是穿有衣服的。PCIe数据以packet的形式传输,比起PCI冷冰冰的数据,PCIe的数据是鲜活有生命的。

每个Endpoint都需要实现这三层,每个Switch的每个Port也是需要实现这三层的:

img

上图中,如果RC要与EP1通信,中间要经历怎样的一个过程?

img

如果把前述的数据发送和接收过程,我们通俗的叫做穿衣脱衣,那么,RC与EP1数据传输过程中,则存在好几次这样穿衣脱衣过程:RC跟数据穿好衣服,发送给Switch的上游端口,A为了知道该笔数据发送给谁,就需要脱掉该数据的衣服,找到里面的地址信息。衣服脱光后,Switch发现它是往EP1的,又帮它换了身新衣服,发送给端口B。B又不嫌麻烦的脱掉它的衣服,换上新衣服,最后发送给EP1。

Switch的主要功能是转发数据,为什么还需要实现事务层?Switch必须实现这三层,因为数据的目的地信息是在TLP中的,如果不实现这一层,就无法知道目的地址,也就无法实现数据寻址路由。

TLP 协议

无论Request TLP,还是作为回应的Completion TLP,它们模样都差不多:

img

TLP主要由三部分组成:Header,Data和CRC。TLP都是生于发送端的事务层(Transaction Layer),终于接收端的事务层。

每个TLP都有一个Header,跟动物一样,没有头就活不了,所以TLP可以没手没脚,但不能没有头。事务层根据上层请求内容,生成TLP Header。Header内容包括发送者的相关信息、目标地址(该TLP要发给谁)、TLP类型(前面提到的诸如Memory read,Memory Write之类的)、数据长度(如果有的话)等等。

Data Payload域,用以放有效载荷数据。该域不是必须的,因为并不是每个TLP都必须携带数据的,比如Memory Read TLP,它只是一个请求,数据是由目标设备通过Completion TLP返回的。后面我们会整理哪些TLP需要携带数据,哪些TLP不带数据的。前面也提到,一个TLP最大载重是4KB,数据长度大于4KB的话,就需要分几个TLP传输。

ECRC(End to End CRC)域,它对之前的Header和Data(如果有的话)生成一个CRC,在接收端然后根据收到的TLP,重新生成Header和Data(如果有的话)的CRC,和收到的CRC比较,一样则说明数据在传输过程中没有出错,否则就有错。它也是可选的,可以设置不加CRC。

img

Data域和CRC域没有什么好说的,有花头的是Header域,我们要深入其中看看。

一个Header大小可以是3DW,也可以是4DW。以4DW的Header为例,TLP的Header长下面样子:

img

红色区域为所有TLP Header公共部分,所有Header都有这些;其它则是跟具体的TLP相关。

稍微解释一下:

Fmt:Format, 表明该TLP是否带有数据,Header是3DW还是4DW;

Type:TLP类型,上一节提到的,Memory Read, Memory Write, Configuration Read, Configuration Write, Message和Completion,等等;

R: Reserved,为0;

TC: Traffic Class,TLP也分三六九等,优先级高的先得到服务。这里是3比特,说明可以分为8个等级,0-7,TC默认是0,数字越大,优先级越高;

Attr: Attrbiute, 属性,前后共三个bit,先不说;

TH: TLP Processing Hints,先不说;

TD: TLP Digest,之前说ECRC可选,如果这个这个bit置起来,说明该TLP包含ECRC,接收端应该做CRC校验;

EP: Poisoned data, 有毒的数据,远离,哈哈;

AT: Address Type,地址种类,先不说;

Length: Payload数据长度,10个bit,最大1024,单位DW,所以TLP最大数据长度是4KB; 该长度总是DW的整数倍,如果TLP的数据不是DW的整数倍(不是4Byte的整数倍),则需要用到下面两个域:

Last DW BE 和 1st DW BE

我觉得,到目前为止,对于Header,我们只需知道它大概有什么内容,没有必要记住每个域是什么。

这里重点讲讲Fmt和Type,看看不同的TLP(精简版的,Native PCIe设备所有)其Fmt和Type应该怎样编码:

TLP Fmt Type Comment
Memory Read Request 000=3DW,no data 001=4DW,no data 0 0000 3DW或者4DW是指Header大小,Memory Read不带数据
Memory Write Request 010=3DW,with data 011=4DW,with data 0 0000 Memory Write必须带数据
Configuration Type 0 Read Request 000=3DW,no data 0 0100 读Endpoint的Configuration,不带数据,Header总是3DW
Configuration Type 0 Write Request 010=3DW,with data 0 0100 写Endpoint的Configuration,带数据,Header总是3DW
Configuration Type 1 Read Request 000=3DW,no data 0 0101 读Switch的Configuration,不带数据,Header总是3DW
Configuration Type 1 Write Request 010=3DW,with data 0 0101 写Switch的Configuration,带数据,Header总是3DW
Message Request 001 = 4DW, no data 1 0rrr Message的Header总是4DW
Message Request with Data 011 = 4DW, with data 1 0rrr Message的Header总是4DW
Completion 000=3DW,no data 0 1010 Completion的Header总是3DW
Completion with Data 010=3DW,with data 0 1010 Completion的Header总是3DW

从上可以看出,Configuration和Completion 的TLP(以C打头的TLP),其Header大小总是3字节;Message TLP的Header总是4字节;而Memory相关的TLP取决于地址空间的大小,地址空间小于4GB的,Header大小为3DW,大于4GB的,Header大小则为4DW。

上面介绍了几个TLP Header的通用部分,下面分别介绍具体TLP的Header。

  • Memory TLP

有两个重要的东西在前面没有提到,那就是TLP的源和目标,即,该TLP是哪里产生的,它要到哪里去,它们都包含在Header里面的。因为不同的TLP类型,寻址方式不同,因此要具体TLP具体来看这两个东西。

img

对一个PCIe设备来说,它开放给Host访问的设备空间首先会映射到Host的内存空间,Host如果想访问设备的某个空间,TLP Header当中的地址应该设置为该访问空间在Host内存的映射地址。如果Host内存空间小于4GB,则Memory读写TLP的Header大小为3DW,大于4GB,则为4DW。那是因为,对4GB内存空间,32bit的地址用1DW就可以表示,该地址位于Byte8-11;而4GB以上的内存空间,需要2DW表示地址,该地址位于Byte8-15。

该TLP经过Switch的时候,Switch会根据地址信息,把该TLP转发到目标设备。之所以能唯一的找到目标设备,那是因为不同的Endpoint设备空间会映射到Host内存空间的不同位置。

关于TLP路由,后面还会专门讲。

Memory TLP的目标是通过内存地址告知的,而源是通过”Requester ID”告知。每个设备在PCIe系统中都有唯一的ID,该ID由总线(Bus)、设备(Device)、功能(Function)三者唯一确定。这个后面也会专门讲,这里只需知道一个PCIe组成有唯一的ID,不管是RC,Switch还是Endpoint。

  • Configuration TLP

Endpoint和Switch的配置(Configuration)格式不一样,分别为Type 0和 Type 1来表示。配置可以认为是一个Endpoint或者Switch的一个标准空间,这段空间在初始化时也需要映射到Host的内存空间。与设备的其他空间不同,该空间是标准化的,即不管哪个厂家生产的设备,都需要有这么段空间,而且哪个地方放什么东西,都是协议规定好的,Host按协议访问这部分空间。由于每个设备ID唯一,而其Configuration又是固定好的,因此,Host访问PCIe设备的配置空间,只需指定目标设备的ID就可以了,不需要内存地址。

下面是访问Endpoint的配置空间的TLP Header (Type 0):

img

Bus Number + Device + Function就唯一决定了目标设备; Ext Reg Number + Register Number相当于配置空间的偏移。找到了设备,然后指定了配置空间的偏移,就能找到具体想访问的配置空间的某个位置。

  • Message TLP

Message TLP用以传输中断、错误、电源管理等信息,取代PCI时代的边带信号传输。Message TLP的Header 大小总是4DW。

img

Message Code来指定该Message的类型,具体如下:

img

不同的Message Code,最后两个DW的意义也不同,这里不展开。

  • Completion TLP

有non-posted request TLP,才有Completion TLP。有因才有果。前面看到,Requester 的TLP当中都有Requester ID和Tag,来告诉接收者发起者是谁。那么响应者的目标地址就很简单,照抄发起者的源地址就可以了。因此,Completion TLP的Header如下:

img

Completion TLP,一方面,可以返回请求者的数据,比如作为Memory或者Configuration Read的响应;另一方面,还可以返回该事务(Transaction)的状态,因此,在Completion TLP的Header里面有一个Completion Status,用以返回事务状态:

img

TLP 路由

一个TLP,是怎样经历千山万水,最后顺利抵达目的地呢?

img

今天就以上图的简单拓扑结构为例,讨论一个TLP是怎样从发起者到达接收者,即TLP路由问题。

PCIe共有三种路由方式:基于地址(Address)路由,基于设备ID(Bus number + Device number + Function Number)路由,还有就是隐式(Implicit)路由。

不同类型的TLP,其寻址方式也不同,下表总结了每种TLP对应的路由方式:

TLP类型 路由方式
Memory Read/Write TLP 地址路由
Configuration Read/Write TLP ID路由
Completion TLP ID路由
Message TLP 地址路由或者ID路由或者隐式路由

下面分别讲讲这几种路由方式。

地址路由

前面提到,Switch负责路由和TLP的转发,而路由信息是存储在Switch的Configuration空间的,因此,很有必要先理解Switch的Configuration。

img

BAR0和BAR1没有什么好说,跟前一节讲的Endpoint的BAR意义一样,存放Switch内部空间在Host内存空间映射基址。

Switch有一个上游端口(靠近RC)和若干个下游端口,每个端口其实是一个Bridge,都是有一个Configuration的,每个Configuration描述了其下面连接设备空间映射的范围,分别由Memory Base和Memory Limit来表示。对上游端口,其Configuration描述的地址范围是它下游所有设备的映射空间范围,而对每个下游端口的Configuration,描述了连接它端口设备的映射空间范围。大家看看下面这张图,理解一下我刚才说的。(Range由Memory Base和Memory Limit限定)

img

前面我们看到,Memory Read 或者Memory Write TLP的Header里面都有一个地址信息,该地址是PCIe设备内部空间在内存中的映射地址。

img

  • 当一个Endpoint收到一个Memory Read或者Memory Write TLP,它会把TLP Header中的地址跟它Configuration当中的所有BAR寄存器比较,如果TLP Header中的地址落在这些BAR的地址空间,那么它就认为该TLP是发给它的,于是接收该TLP,否则就忽略。

img

  • 当一个Switch上游端口收到一个Memory Read或者Memory Write TLP,它首先把TLP Header中的地址跟它自己Configuration当中的所有BAR寄存器比较,如果TLP Header当中的地址落在这些BAR的地址空间,那么它就认为该TLP是发给它的,于是接收该TLP(这个过程与Endpoint的处理方式一样);如果不是,然后看这个地址是否落在其下游设备的地址范围内(是否在memory base 和memory limit之间),如果是,说明该TLP是发给它下游设备的,因此它要完成路由转发;如果地址不落在下游设备的地方范围内,说明该TLP不是发给它下面设备的,因此不接受该TLP。imgimg

img

刚才的描述是针对TLP从Upstream流到Downstream的路由。如果是TLP从下游往上走呢?

它首先把TLP Header中的地址跟它自己Configuration当中的所有BAR寄存器比较,如果TLP Header当中的地址落在这些BAR的地址空间,那么它就认为该TLP是发给它的,于是接收该TLP(跟前面描述一样);如果不是,然后看这个地址是否落在其下游设备的地址范围内(是否在memory base 和memory limit之间),如果是,这个时候不是接受,而是拒绝;相反,如果地址不落在下游设备的地方范围内,Switch则把该TLP传上去。

ID路由

在一个PCIe拓扑结构中,由ID = Bus number+Device number+Function Number(BDF)能唯一找到某个设备的某个功能。这种按设备ID号来寻址的方式叫做ID路由。Configuration TLP和Completion TLP(以C打头的TLP)按ID路由,Message在某些情况下也是ID路由。

使用ID路由的TLP,其TLP Header中含有BDF信息:

img

当一个Endpoint收到一个这样的TLP,它用自己的ID和收到TLP Header中的BDF比较,如果是给自己的,就收下TLP,否则就拒绝。

如果是一个Switch收到这样的一个TLP,怎么处理?我们再回头去看看Switch的Configuration Header。

img

看三个寄存器:Subordinate Bus Number,Secondary Bus Number和Primary Bus Number,看下图就知道这几个寄存器是什么意思:

img

对一个Switch来说,每个Port靠近RC(上游)的那根Bus叫做Primary Bus,其Number写在其Configuration Header中的Primary Bus Number寄存器;每个Port下面的那根Bus叫做Secondary Bus,其Number写在其Configuration Header中的Secondary Bus Number寄存器;对上游端口,Subordinate Bus是其下游所有端口连接的Bus 编号最大的那个Bus,Subordinate Bus Number写在每个Port的Configuration Header中的Subordinate Bus Number寄存器。

当一个Switch收到一个基于ID寻址的TLP,首先检查TLP中的BDF是否与自己的ID匹配,如匹配,说明该TLP是给自己的,收下;否则,则检查该TLP中的Bus Number是否落在Secondary Bus Number和Subordinate Bus Number之间,如果是,说明该TLP是发给其下游设备的,然后转发到对应的下游端口;如果其他情况,则拒绝这些TLP。

img

隐式路由

只有Message TLP才支持隐式路由。在PCIe总线中,有些Message是与RC通信的,RC是该TLP的发送者或者接收者,因此没有必要明明白白的指定地址或者ID,而是采用”你懂的”的方式进行路由,这种路由方式为隐式路由。Message TLP还支持地址路由和ID路由,但以隐式路由为主。

Message TLP的Header总是4DW,如下图所示:

img

Type字段,低三位,用rrr表示的,指明该Message的路由方式,具体如下:

img

当一个Endpoint收到一个Message TLP,检查TLP Header,如果是RC的广播Message(011b)或者该Message终结于它(100b),它就接受该Message。

当一个Switch收到一个Message TLP,检查TLP Header,如果是RC的广播Message(011b),则往它每个下游端口复制该Message然后转发;如果该Message终结于它(100b),则接受该TLP;如果下游端口收到发给RC的message,往上游端口转发便是。

上面说的是Message使用隐式路由的情况。如果是地址路由或者ID路由,Message TLP的路由跟别的TLP一样,不赘述。

参考资料