2.4 事务路由
讲事务类型的时候提到了事务路由(Transaction Routing)。所谓路由,就是在PCIe拓扑结构中,事务如何从请求者正确走到完成者。PCIe有三种路由方式:地址路由,ID路由,隐式(Implicit)路由。
2.4.1 地址路由
地址路由好理解,就是按照地址去找。SoC设计中,地址都被映射到存储器地址空间(Memory Mapped)。那么,接下来的问题是PCIe设备的地址从哪里来。这个地址是主机侧的处理器分配的。首先,主机的处理器要知道设备想要展现给主机的空间,然后再结合实际情况分配相应大小的存储空间给设备。这一过程是主机向设备发送配置读取/写入请求完成的,具体放在配置空间章节介绍。
支持地址路由的事务有:
存储读取/写入请求
I/O读取/写入请求
消息请求
需要注意的是,在Gen 5的基础规范中特别注明,没有消息事务是使用地址路由的。也就是说消息事务的TLP Header中的地址字段要么是保留不用,要么是用作其它用途。这里说的消息事务支持地址路由应该沿用是Gen 5之前规范的说法。
2.4.2 ID路由
Function是PCIe设备中的物理实体,负责完成一项功能。只包含一个Function的设备叫做SFD(Single-Function Device),包含多个Function的设备叫做MFD(Multi-Function Device)。在PCIe拓扑结构中,为了区分不同的Function,每个Function被分配了一个唯一的标识ID,此ID是由8-bit的总线编号,5-bit的设备编号,3-bit的Function编号组成。正因为此ID是唯一的,所以可以通过ID路由方式传输TLP。
PCI总线是共享总线,设备编号有其作用。但是PCIe是点到点连接,设备编号就没有太大意义了。PCIe协议推出了ARI(Alternative Routing-ID, ARI)功能,将5-bit的设备编号取消,分配给了Function。也就是说支持ARI的话,此ID依然是16-bit,由8-bit总线编号和8-bit的Function编号组成。在后面分析ARI的时候会详细介绍。至此,大家记得ID在PCIe拓扑结构中是唯一的就可以了。至于主机怎么分配这些编号,留待分析PCIe枚举的时候再说。
对于EP来说,接到ID路由的TLP后,判断ID值是否与自己的ID相同,如果相同就接受此TLP,并完成相应的动作;如果不同,则拒绝TLP。
对于Switch来说,情况复杂一些。Switch接到ID路由的TLP后,判断ID值是否与自己的ID匹配,如果匹配则接受此TLP;如果不匹配,则继续判断ID值是否在其下游端口的连接的设备的ID范围内,如果在则说明TLP是发给Switch下游的设备,Switch转发即可。
支持ID路由的事务有:
配置读取/写入事务
完成事务
2.4.3 隐式路由
隐式路由,顾名思义,就是不需要指定TLP的地址或者ID。之所以隐式路由不需要地址或ID,是由PCIe拓扑结构决定的。在PCIe拓扑结构中,RC是唯一的根节点,当EP或Switch想发送消息给RC,就不需要指明了,只需要向上游端口转发就好了。相反,如果RC向EP/Switch广播消息,只要不断向下游端口转发。对于本地消息,接收方就是链路对端,因此也不需要地址或者ID。
对于消息TLP,Type[2:0]字段用于指示路由方式,除去编码001b(地址路由)和010b(ID路由),其它几种都是隐式路由。还记得前面提到过,Gen 5中消息事务已不再使用地址路由。
只有消息事务支持隐式路由。
最后,总结一下PCIe中的TLP路由方式。
2.5 虚拟通道
虚拟信道(Virtual Channel,VC)机制支持在PCIe拓扑结构中使用TC标签来区分数据流量。
VC的基础是独立的结构资源(队列/缓冲区和相关的控制逻辑)。这些资源用于通过不同VC之间完全独立的流量控制。
通过将TC映射到VC来将数据流量等级与VC相关联,这被称为TC/VC映射。TC0/VC0映射是固定的,不可更改的;其它映射可以通过配置软件来设置。
下图是VC概念的示意图。在发送端,数据流量被复用到物理链路;在接收端,数据流量被解复用到独立的VC路径。
在Switch内部,每个端口的虚拟信道都需要专用的物理资源。下图是Switch内部虚拟通道的示意图。
2.5.1 虚拟通道ID (VC ID)
PCIe端口最多可以支持8个VC,每个端口都是独立配置和管理的。不同VC使用唯一的ID值来标识。
支持多VC的端口需要同时实现VC能力结构。对于仅支持默认TC0/VC0配置的端口,是否提供这些扩展结构是可选的。配置软件负责为链路两侧的端口匹配VC数量。
为端口内的VC硬件资源分配VC ID的规则如下:
每个端口的VC ID分配必须是唯一的,同一个VC ID不能分配给同一端口内的不同VC硬件资源 链路两侧的两个端口的VC ID分配必须相同 如果MFD实现MFVC能力结构,则其VC硬件资源不同于与其Function的VC硬件资源。VC ID唯一性要求仍然单独适用于MFVC和任何VC能力结构 VC ID 0被分配并固定为默认VC
2.5.2 TC to VC Mapping
除了TC0必须映射到VC0,其它TC可以映射到不同的VC。TC/VC的映射关系可以由系统软件决定,但必须遵守:
允许将一个或多个TC映射到同一个VC 不允许一个TC映射到多个VC 链路两侧端口的TC/VC映射必须相同
下图是PCIe Gen-5基本规范中给出的TC/VC映射示例。
2.5.3 VC and TC Rules
以下是与TC/VC机制的关键规则:
所有设备必须支持TC0,并且必须实现默认的VC0 每个虚拟通道都有独立的流量控制 不同TC之间不需要保序 不同VC之间不需要保序 Switch的对等功能适用于Switch支持的所有虚拟信道 MFD在不同Function之间的对等功能适用于MFD支持的所有虚拟通道 TC未映射到入口端口中任何已启用VC的事务被接收设备视为格式错误的TLP 对于Switch,TC未映射到目标出口端口中任何已启用VC的事务被视为格式错误的TLP 对于根端口,TC未映射到目标RCRB中任何已启用VC的事务被视为格式错误的TLP 对于具有MFVC能力结构的MFD,TC未映射到MFVC能力架构中已启用VC的事务被视为格式错误的TLP Switch必须支持每个端口的独立TC/VC映射配置 RC必须支持每个RCRB、根端口和RCiEP的独立TC/VC映射配置
2.5.4 VC能力寄存器
支持多VC,或者虽然只有一个VC但是需要支持TC过滤的设备需要实现VC能力寄存器,在6.3.7章节详细介绍。
2.6 流量控制
2.6.1 基于信用的流量控制
PCIe最多支持八个虚拟通道,每个虚拟通道负责自己的流量控制。
PCIe的流量控制基于信用(Credit-Based)机制。在链路初始化阶段,链路的接收端需要向发送端报告自己的接收缓冲区大小,即接收能力,也称作信用值;在链路正常工作期间,接收端需要有规律的向发送端告知当前的接收缓冲区情况。具体做法是通过数据链路层的流量控制DLLP来完成。这个DLLP无疑是系统开销,但是其不携带有效数据,所以这个DLLP很小,只有8个符号(Symbol),因此对链路性能的影响可以忽略不记。
参照下图,归纳一下PCIe流量控制的基本原理。
PCIe设备在事务层中实现接收缓冲区(FC Buffer)和流量计数器(FC Counter)。链路双方将自己的接收缓冲区的信用值发送给对方。发送端通过自己的计数器记录接收端的信用值,发送端在发送事务前需要检查计数器,如果发现计数器为0,说明接收端的缓冲区已满,不能再接收。此时发送端需要等待接收端处理事务;如果计数器不为0,发送端将事务向数据链路层传递,发送端每发送一个事务,计数器减1。
流量缓冲区分为三类:
P:用于MWr和Msg NP:用于MRd,CfgRd,CfgWr,IORd,IOWr,AtomicOp CPL:用于Cpl,Cpld
上述三种类型中,又按照Header和Data进行拆分。因此,缓冲区有六种:PH,PD,NPH,NPD,CPLH,CPLD。
Header的1个信用值对应4DW(Completion)或者5DW(Request);Data的1个信用值对应4DW,即数据信用是16-Byte对齐的。
PCIe 5.0中,TLP消耗信用的情况见下表:
2.6.2 流量控制初始化
当系统平台上电,物理层完成链路训练后,物理层通过LinkUp信号通知数据链路层。随后,开始流量控制初始化。虚拟通道VC0是默认启用的,所以VC0的流量控制初始化是自动完成的,无需软件接入。其它虚拟通道的初始化需要等待配置软件启用这些VC。
数据链路层的控制和管理状态机(Data Link Control and Management State Machine,DLCMSM)负责流量控制初始化的过程。
1. 系统复位,DLCMSM进入DL_Inactive状态。当物理层完成链路训练,拉高LinkUp信号,DLCMSM检测到LinkUp后跳转到DL_init状态。DL_init有两个子状态FC_INIT1,FC_INIT2。
2. 在FC_INIT1阶段,设备连续发送3个InitFC1流量控制DLL的序列,报告其接收缓冲区大小。按照协议,其报告的先后顺序是Posted,Non-Posted,Completion。当设备发送了自己的值并接收到足够多次的完整序列来确信这些值被正确地看到,它就可以退出FC_INIT1进入FC_INIT2了。下图中的HdrFC字段表示的是Header流量控制信用值,DataFC表示的是Data流量控制信用值。
3. 在FC_INIT2阶段,设备连续发送InitFC2 DLLP。由于设备已经注册了来自链路对端的信用值,因此在等待查看InitFC2s时可以忽略任何对端发过来的InitFC1s。两步初始化过程将不强制链路的两端在相同时间内完成初始化。
4. 当设备完成FC_INIT2,进入DL_Active阶段,通过DL_Up信号通知事务层。
5. PCIe设备的接收逻辑必须定期向链路对端发送FC_Update DLLP,更新当前的流量控制信用值。
2.6.3 缩放流量控制机制
PCIe Gen 3中规定,Header流量控制信用值最大值是127,Data流量控制信用值最大值是2047。当没有足够的流量控制信用,链路性能可能会受到影响。这种影响在更高的链路速率下变得更加明显,127个Header信用和2047个Data信用的限制可能会限制性能。因此,在PCIe Gen 4中引入缩放流量控制(Scaled Flow Control)机制,旨在解决这个问题。
PCIe Gen 4提供了x1, x4, x16三种放大系数。这样, 对于Header流量信用值有 127/508/2032三种选择,对于Data流量信用值有2047/8188/32752三种选择。
可能大家已经注意到上面的FC DLLP中,在HdrFC和DadaFC前面各有2-bit保留。其实上图的FC DLLP是Gen 3的格式,在Gen 4中,FC DLLP是下图格式
HdrScale字段和DataScale就是指示放大系数。
2.7 事务顺序
为了提高PCIe链路性能,设备可以连续发出事务,而不必等待上一个事务完成才能发送下一个。设备连续发送事务的能力取决于硬件设计,不在本节讨论范围内。
本节要分析的是这些事务的排序关系。如果严格按照时间先后,可能会造成死锁。如果完全乱序,则可能会造成逻辑错误。PCIe协议的制定者们为此花费了大量心思。
2.7.1 生产者/消费者模型
PCIe基于生产者/消费者(Producer/Consumer)模型。下图描述生产者/消费者模型,其中有五个重要的组成部分:
数据生产者(Producer of Data) 数据缓冲区(Memory Buffer of Data) 标志信号(Flag Semaphore),指示数据生产者已经发送完数据 数据消费者(Consumer of Data) 状态信号(Status Semaphore),指示数据消费者已经取走数据
参照下图,在这个示例中,左下角的PCIe设备是数据生产者,主机的处理器是数据消费者,数据缓冲区在主机内存中,标志信号和状态信号存储在右下角的物理设备中。
如果以下列的顺序处理事务,不会产生问题:
1. 生产者通过MWr将数据写入到内存中,这一过程需要一些时间才能完成
2. 消费者采用轮询的方式,通过MRd去读取标志信号Flag,检查当前否有可用数据
3. 通过CplD将Flag=0的信息返回给消费者,消费者知道暂时没有可用数据
4. 生产者通过MWr写入Flag=1
5. 消费者等待一段时间后,重复第2步
6. 这次消费者读取到Flag=1
7. 消费者通过MWr将Flag再次写成0
8. 生产者还有其它数据需要发送,所以需要周期性发送MRd去读取Status信号
9. 生产者读取到Status=0,说明消费者还没有取走数据,继续等待
10. 此时消费者知道内存中有数据可用,因此通过MRd来取走数据
11. 数据全部被消费者取走
12. 第11步结束后,消费者通过MWr写入Status=1,指示数据已被全部取走
13. 生成者重复第8步,继续轮询Status
14. 这次消费者读取到了Status=1,说明内存中的数据已经被消费者读走,可以继续发送数据了
15. 生产者通过MWr清除Status
16. 一次的数据生产/消费结束,生产者可以从第1步重复,继续数据生成/消费
上诉过程如下两图中的编号所示。
上面的例子是在一切正常的情况下,按照事务顺序执行。来看另一个的例子。
1. 生产者通过MWr将数据发送给主机内存,但这次出现了一些问题,MWr事务被阻塞在Switch的上游端口,并没有真的写入主机内存
2. 生产者通过MWr写入Flag=1
3. 消费者通过MRd读取Flag
4. Flag=1通过CplD返回给消费者
5. 消费者发现Flag=1,随即去读取内存中的数据,但是消费者并不在知道真正的数据还被阻塞在Switch的上游端口,并没有写入到内存,因此消费者读取到的数据是以前旧数据。数据消费过程出现错误。
要解决上述的问题,有很多办法,比如消费者向生产者发出一个“空读”事务,生产者返回完成响应事务给消费者。如果能保证该完成响应事务不会超越先前的写内存事务,也就是消费者接到完成响应就意味着前面的写内存事务已经完成,即可解决。
综上,必要的时候需要保证事务的顺序关系,即保序;非必要的时候,可以打乱事务顺序,提高链路效率,避免死锁发生。
2.7.2 PCIe的排序模型
PCIe中有三种排序模型:
强排序(Strong Ordering):相同TC值的事务需要严格遵守时间先后顺序,不同TC的事务不需要遵守顺序要求。PCIe协议允许多个TC映射到相同VC,在同一个VC内不同TC的事务也不需要遵守顺序要求。 弱排序(Weak Ordering):除非重新排序有帮助,否则事务将按顺序进行。保持事物之间的强序要求可能会带来一些不必要的依赖关系,而这些依赖关系可能会造成死锁,这时对事务进行重新排序会解决这个问题。 松散排序(Relaxed Ordering):在特定的受控条件下对事务进行重新排序。好处是提高了性能,就像弱排序模型一样,但只有在由软件指定时,才能避免依赖性问题。缺点是只有一些事务会针对性能进行优化。
前面讲虚拟通道和流量控制的时候提到过,VC缓冲区分成P,NP,CPL三个子缓冲区;在流量控制机制中,将TLP的Header和Data拆分成两个子缓冲区。这样,VC中实际有6个子缓冲区。这些子缓冲区的设计有助于简化事务排序规则的处理。
强排序模型会事务停顿(Transaction Stall),参照下图,左侧是发送端,右侧是接收端。事务编号表示事务发送的时间顺序。如果遵守强排序,由于接收端的NP缓冲区满了,不能接收新的事务,发送端的事务1被迫停止,等待接收端的NP缓冲区有空间。事务2-8也不得不停止,等待事务1发送完成以后才能继续进行。
实际上,这些子缓冲区的事务不存在依赖关系,比如上图中的事务2不依赖事务1,因此事务2可以继续发送。这就实现了事务弱排序。
此外,PCIe支持松散排序,可以让软件来决定TLP是否需要遵守强序模型。如果软件认为TLP可以被重新排序,则启用TLP Header的Attr[1]字段的RO位。MWr和Msg都是Posted类型,都被存在Posted子缓冲区,因此遵守相同的排序要求。当Switch或RC发现MWr/Msg的RO被启用,则:
Switch允许后发出的MWr/Msg超车前面的MWr/Msg,并且Switch转发TLP时保持RO不变 允许RC对TLP重新排序。此外,在接收到MWr(RO=1)后,允许RC以任何地址顺序将每个字节写入内存。
对于Read事务,PCIe按照拆分事务来处理,当设备开启RO发出MRd,完成者需要返回ClpD(RO被设置)。Switch的行为如下:
Switch不可以将后发的MRd超车前面的MWr,这保证了在读取请求的方向上的所有写事务都被推到读事务之前。 完成者收到MRd后,返回Completion,这些Completion的RO也被置位。 Switch接收到Completion(RO=1),允许将这些Completion超车完成者发出的MWr。这样即使MWr被阻塞,Completion仍可执行,有助于提高读取性能。
下表总结了Switch的松散排序下可超车的事务。
另一种优化排序和提高性能的做法是基于ID排序,其基本思想是不同请求者不在同一线程的话,之间的事务没有依赖关系。下图中,事务1先到达Switch的上游端口,但由于某些原因被阻塞。随后事务2也到达Switch的上游端口,按照强序要求,事务2必须停下来等待事务1先通过。但是,如果事务1和事务2之间没有依赖关系,强制事务2等待必然造成系统性能下降。解决办法是允许使用不同ID(Requester ID或者Completer ID)的事务重新排序,在本示例中也就是允许事务2超越事务1。使用此功能需要软件开启TLP Header的Attr[2]字段的IDO位。
2.7.3 PCIe排序规则
PCIe协议规定,事务排序规则仅在事务使用相同TC值时适用,对于不相同TC值则不适用。也就是说,不同TC值的事务不需要遵守排序规则。PCIe事务可以分为Posted类型和Non-Posted类型。因此,全部TLP可以分为三类:
Posted:包括MWr,Msg/MsgD Non-Posted:包括MRd,IORd,IOWr,CfgRd0,CfgRd1,CfgWr0,CfgWr1,AtomicOp。Non-Posted请求又被分为两部分:Read Request(MRd,IORd,CfgRd0,CfgRd1)和NPR with Data(IOWr,CfgWr0,CfgWr1,AtomicOp)。 Completion:包括Cpl/CplD
TLP排序规则见下表:
表中的Col 2-5表示是时间维度上先发出的TLP,Row A-D表示的是时间维度上后发出的TLP。表中间的Yes表示后发出的TLP(Row A-D)必须被允许“超车(overtake)“先发出的TLP(Col 2-5),即Row中的事务虽然比Col中的事务发出时间晚,但是可以先被接收端接收,以避免死锁发生;No表示不允许后发出的TLP超车先发出的TLP,即后发出的TLP必须遵守强排序要求排在先发出的TLP后面被接收;Y/N表示后发出的TLP可以但不强制超车先发出的TLP。
逐一解释一下这些表项:
A2a:Row事务不可以超越前Col事务,除非是A2b所允许的情况。 A2b:RO开启的Posted Request可以超车前一个Posted Request;如果两个请求者ID不同,或者两个请求都包含PASID TLP Prefix并且两个PASID值不同,则允许IDO开启的Posted Request超车前一个Posted Request A3,A4:Posted Request必须可以超车前一个Non-Posted Request,以避免死锁。比如MWr可以超车MRd或者CfgWr A5a:允许Posted Request超越Completion,但不要求。比如MWr超车CplD A5b:对于PCIe到PCI方向上传输的事务,Posted Request必须可以超越Completion。 B2a:Read Request不可以超越前一个Posted Request除非B2b所允许的情况 B2b:如果两个请求者ID不同,或者两个请求都包含PASID TLP Prefix并且两个PASID值不同,则允许IDO开启的Read Request超越前一个Posted Request C2a:NPR with Data不可以超越Posted Request,除非C2b所允许的情况 C2b:RO开启的NPR with Data可以超越前一个Posted Request;如果两个请求者ID不同,或者两个请求都包含PASID TLP Prefix并且两个PASID值不同,则允许IDO开启的NPR with Data超越前一个Posted Request B3,B4,C3,C4:允许NPR超越另一个NPR B5,C5:允许NPR超越Completion D2a:不允许Completion超越Posted Request D2b:允许IO和configuration write completion超越Posted Request;RO开启的completion可以超越Posted Request;如果完成者ID与Posted Request的请求者ID不同,则允许IDO开启的Completion超越Posted Request D3,D4:Completion必须可以超越NPR,以避免死锁 D5a:允许不同事务ID的completion互相超越 D5b:相同事务ID的completion必须遵守顺序,这是为了确保与单个内存读取请求相关联的多个completion保持地址升序顺序。
这一部分有点绕,不过很多是软件需要考虑的,比如在开启IDO或RO时,软件必须保证事务之间确实没有依赖关系,否则重新排序可能会造成功能错误。
2.8 服务质量,QoS
QoS这个概念在很多总线标准中都会提到。QoS的基本原理是让软件对不同的传输需求划分出优先级,优先级高的会分配更多的资源。
PCIe中为了实现QoS,首先提出了流量等级(Traffic Class,TC)概念。在TLP Header的第一个DW中有3-bit的TC字段,也就是说TC从0到7有八个等级。其中,TC0是默认等级。
然后,PCIe又提出了虚拟通道(Virtual Channel,VC)概念。VC虽然挂着虚拟的名号,却是实打实的硬件缓冲区。前面讲流量控制时,提到设备中会实现缓冲区,这里的VC代表可用于传出数据包的不同路径。打个比方,PCIe设备是一条高速公路且有多条车道,车道就是VC,而行驶的车辆就是TLP,这些TLP最终汇聚到物理层发送出去,物理层链路就是一个检查站。软件可以指定TLP不同的TC等级,相同TC值的TLP行驶在同一个VC上,VC等级高的车道具有优先通过检查站的权利。
VC最多也可以有8个,即0-7。实际上,考虑到VC是硬件逻辑资源,所以不同的PCIe设备很可能不会实现全部的VC。这里就涉及到一个问题,如何分配TC到VC上,即TC/VC映射。PCIe协议中做了如下的约束:
对于连接在同一链路任一端的两个端口,TC/VC映射必须相同 TC0将自动映射到VC0 其它TC可以映射到任何VC 一个TC不能映射到一个以上的VC
PCIe设备关于VC的信息存放在扩展能力(Capability)寄存器中,其中每个VC有单独的一组寄存器。主机软件获得设备的VC数目,并为这些VC分配ID。
VC可以有多个,但是物理链接只有一条,这就出现了一个问题,如何分配链路资源给VC,或者说哪些VC车道上的TLP可以优先其它VC上的TLP,发给链路的对端。PCIe提供三种VC仲裁方法:
严格优先级仲裁:优先级最高的VC始终会优先通过 分组仲裁:将VC分为高优先级组和低优先级组,其中高优先级组执行严格优先级仲裁,低优先级组执行软件指定的仲裁方式 硬件固定仲裁:仲裁机制由硬件实现
下图是一个VC仲裁的例子,VC1和VC0采用了3:1的比例,即每发送3个VC1上的数据包以后,发送1个VC0上的数据包。这种仲裁机制的好处是,优先级低的VC也有机会获得链路资源。
关于虚拟通道,详见后面第六章中的部分。
【待续】