PCIe学习(二)
2 事务层
事务层需要完成的事情:
- TLP的生成(发送)和解析(接收)
- TLP排序机制
- 基于信用值的流量控制机制
- 数据中毒(Data Poisoning)和ECRC完整性检测
事务层向上要与应用层通信,不同设备的应用层天差地别,在做芯片设计时要根据具体情况而定。PCIe规范没有对应用层提出约定。
2.1 地址空间
PCIe协议中有四类地址空间,对应四种请求事务,如下表:
Address Space** | Transaction Types** | Basic Usage** |
Memory | Read/Write | 数据传输到/从(to/from)存储映射位置 |
I/O** | Read/Write | 数据传输到/从(to/from)I/O映射位置 |
Configuration | Read/Write | Function配置 |
Message** | Baseline | 从事件信令机制到通用消息传递 |
前三种是PCI协议中已有的事务类型,最后一种是PCIe协议中增加的事务类型。
2.1.1 存储事务
存储事务最常用,支持32-bit和64-bit两种地址格式,包括以下这些事务:
- 读取请求,读取完成(Read Request/Completion)
- 写入请求(Write Request)
- 原子请求,原子完成(Atomic Request/Completion)
#
2.1.2 I/O事务
I/O事务是PCI时代的产物,PCIe规范中支持I/O事务只是因为需要兼容已有的PCI设备。PCIe原生设备本身不再支持I/O事务,既不会发出I/O请求,也不会响应主机的I/O请求。
I/O事务仅支持32-bit地址格式,包括以下这些事务:
读取请求,读取完成(Read Request/Completion)
写入请求,写入完成(Write Request/Completion)
在后续的学习中,可以暂时忽略I/O事务。
2.1.3 配置事务
配置事务是主机(也就是RC)用来配置Function,访问Function配置寄存器。在系统平台上电后,所有的PCIe链路自动完成链路训练(Link Training);随后主机软件(不区分固件/设备驱动/操作系统,统称为软件)对PCIe拓扑结构进行探索,已发现所有的可用设备,并分配唯一标识ID,这一过程称为“枚举(Enumeration)”;必要时,主机软件需要通过配置事务来请求PCIe设备开启或禁用某些功能,配置设备参数等等。
配置事务包括以下这些事务:
读取请求,读取完成(Read Request/Completion)
写入请求,写入完成(Write Request/Completion)
只有RC才有权限发出配置请求事务;EP或者Switch只可以接收配置请求事务,不允许发出配置请求。
2.1.4 消息事务
PCI时代有大量的引脚信号用于设备通信,这些引脚信号称作“边带(Side-Band)”信号。PCIe协议为了简化接口的引脚,将这些边带信号全部拿掉,设备间需要靠“带内(In-Band)”消息来通信。
消息事务是PCIe规范中新增加的事务类型,也就是说PCI设备不支持此类事务,如果系统中有PCI设备,则需要PCIe-PCI桥接设备做转换。
除去PCIe规范定义的消息类型,还支持厂商自定义消息,需要厂商自行定义和实现,并在其驱动软件中加以支持。
2.2 事务类型
上面是从地址空间出发,对PCIe事务进行了分类。接下来我们从事务发送和完成的角度看一下这些事务。
先区分几个概念。事务的发起者叫做请求者(Requester),事务的响应者叫做完成者(Completer)。有些事务需要完成者给请求者一个完成响应(Completion),将事务完成的状态通知请求者;而有些事务则不需要完成者返回完成响应,请求者发出事务以后即认为事务已经完成。不需要完成响应的事务是Posted类型,需要完成响应的事务是Non-Posted类型。所谓Posted,就像是寄信件一样,当我们把邮件放入邮筒的那一刻起,就标志着信件已发出。至于信件最终是不是会送达到收件人手中,需要邮局系统来保证。在PCIe协议中,这一保证机制是由数据链路层的ACK/NAK协议来完成。对于Non-Posted类型来说,如果请求者在发送完请求事务后一直等待完成者返回完成事务,必然造成链路上的停顿,白白浪费了链路资源。因此PCIe协议规定,请求者发送完一个Non-Posted类型的事务后,可以继续发送其它的事务,而不必一直等待完成者的完成事务。这一过程也称为分离事务(Split Transaction),即请求和完成分离开来。
需要注意的是,请求者不一定就是RC,完成者不一定就是EP或Switch。RC也可以是完成者,EP或Switch也可以是请求者。只有在发送配置事务时,RC与请求者是一致的,EP/Switch与完成者是一致的。
用表格总结一下事务的类型:
Request Type** | Non‐Posted or Posted** | Abbreviation** |
Memory Read | Non-Posted | MRd |
Memory Write** | Posted | MWr |
AtomicOp | Non-Posted | AtomicOp |
IO Read** | Non-Posted | IORd |
IO Write** | Non-Posted | IOWr |
Configuration Read** | Non-Posted | CfgRd |
Configuration Write | Non-Posted | CfgWr |
Message** | Posted | Msg/MsgD |
为了简化学习,可以忽略表中的IO Read/Write。
对于存储读取请求(MRd)和配置读取请求(CfgRd),必然需要有相应的完成响应,否则完成者怎么返回给请求者需要的数据呢。也就是说MRd和CfgRd是Non-Posted类型。参考下图,是一个存储读取请求的例子,首先是EP发起MRd,经过Switch B和A到达RC;RC从相应的内存中将数据取出,并通过CplD将数据发送给EP。
配置写入请求(CfgWr),是RC发给Switch或者EP的,因为Switch或EP的功能取决于该设备是否被配置成功,所以Switch或EP必须返回完成响应给RC,这样RC才能进行后续的操作。CfgWr也是Non-Posted类型。
IORd和IOWr是Non-Posted类型,不再展开了。下图是IOWr的例子。
最后,只有存储写入请求(MWr)和消息请求(Msg/MsgD)是Posted类型,请求者发出请求即认为请求已完成,这样可以大大提高链路效率。至于请求是否真正被发送到了完成者,需要其它的机制保证,后面会介绍。
细心的读者可能发现了一个问题,如果请求者发送了多个MWr或者是多个Msg,那么完成者是以什么顺序接收到的呢?这涉及到PCIe规范中的事务顺序问题。其实这个问题牵扯到所有事务的顺序,后面会详细讲。
2.3 事务层协议-数据包格式
TLP的串行格式如下图所示,包括TLP前缀(Prefixes),TLP包头(Header),有效数据负载(Data Payload)和TLP摘要(Digest)。其中只有TLP Header是必选项,其它三项都是可选项,根据数据包的实际需求而定。PCIe协议是串行总线,单比特传输,也就说发送端的物理层完成TLP并/串转换后,从TLP Prefixes(如果有的话)开始传输,最后传输TLP Digest(如果有的话)。这样做的一个好处是接收端可以提前知道这个数据包的类型,而不用等待全部接收完数据包。
构成TLP的四部分里面,除了TLP Header,其它的都不是必选项,甚至是数据部分。TLP里面最为复杂的也是Header。
TLP的并行格式如下图所示,PCIe协议里面用DW(Double Word,32-bit)来表示并行结构。后面会反复用到DW。
从上图可以看出,长度固定只有Digest部分,为1-DW。其它的三部分,长度都不固定,但都是以DW对齐的。
2.3.1 TLP Header
前面提到过,存储读取/写入事务有两种地址格式:32-bit和64-bit。这个地址包含在TLP Header中,所以对应的有两种TLP Header格式。32-bit地址的Header是3-DW长度,64-bit地址的Header是4-DW(多出来的1-DW正好对应多出来的32-bit地址),如下图。
所有类型的TLP,其Header中的第一个DW格式都是相同的,而第二个DW中的字段随TLP类型不同而变化。
2.3.1.1 通用的Header字段
不管是哪种类型的请求,TLP的第一个DW,也就是前4 Byte格式是通用的,见下图。
其中各字段的含义如下:
Fmt[2:0]:TLP的格式,指示TLP的地址格式,或者是否为Prefix
Type[4:0]:TLP的类型,Type字段通常与Fmt字段一起编码,来表示TLP的格式
TC[2:0]:流量等级(Traffic Class),PCIe协议支持最多8个TC值,通过TC来区分TLP的优先级,从而完成TLP保序和QoS的功能
T8 & T9:Tag[8]和Tag[9],最初的Tag只有8-bit,后来增加了2-bit。Tag[7:0]在Header的第二个DW中。此字段由请求者分配,用于区分不同的具体请求,完成者返回完成响应时需带上Tag,这样请求者就知道哪些发出去的请求已经完成,哪些尚未完成
Attr[2:0]:3-bit的属性定义,这三个字段有各自的含义,并没有一起做编码,其中Attr[2]表示基于ID排序(ID-Based Ordering),Attr[1]表示松散排序(Relaxed Ordering),Attr[0]表示非监听(No Snoop)。
LN:轻量级通知(Lightweight Notification),这是Gen 4中加入的特性,在Gen 6中被取消
TH:TLP处理提示(Processing Hints)
TD:TLP Digest,指示TLP中是否存在有ECRC部分
EP:错误中毒(Error Poisoned),指示TLP Data部分的数据负载是否为“中毒”
AT[1:0]:地址类型(Address Type),即地址是否经过转换,仅对带有地址的事务有效,如MRd,MWr,AtomicOp。对其它的事务,该字段保留。在轻量级通知中,该字段有其它含义
Length[9:0]:TLP Data部分的大小,数据是DW对齐的,如果实际数据是Byte,则需要借助Header中的其它字段指示。
本节先详细分析一些常用字段,有些不常用字段跟某些特性功能有关,留待后面章节分析。
Fmt[2:0]的编码如下表所示:
Fmt[2:0]** | TLP format** |
000b | 3 DW header, no data |
001b** | 4 DW header, no data |
010b | 3 DW header, with data |
011b** | 4 DW header, with data |
100b | TLP Prefixes |
Other** | 保留 |
Fmt字段通常和Type字段一起,表示TLP类型,这两个字段是Header中最重要的字段。
TLP Type** | Fmt[2:0]** | Type[4:0]** | Description** |
MRd | 000001 | 0 0000 | 存储读取请求 |
MRdLk** | 000001 | 0 0001 | 存储读取锁定请求 |
MWr | 010011 | 0 0000 | 存储写入请求 |
IORd** | 000 | 0 0010 | I/O读取请求 |
IOWr | 010 | 0 0010 | I/O写入请求 |
CfgRd0** | 000 | 0 0100 | Type 0配置读取请求 |
CfgWr0 | 010 | 0 0100 | Type 0配置写入请求 |
CfgRd1** | 000 | 0 0101 | Type 1配置读取请求 |
CfgWr1 | 010 | 0 0101 | Type 1配置写入请求 |
TCfgRd** | 000 | 1 1011 | 不推荐的TLP类型,以前用于可信配置空间(Trusted Configuration Space),现在不再支持 |
TCfgWr | 000 | 1 1011 | 不推荐的TLP类型,以前用于可信配置空间(Trusted Configuration Space),现在不再支持 |
Msg** | 001 | 1 0r2r1r0 | 不带数据的消息请求,r[2:0]指明消息路由机制 |
MsgD | 011 | 1 0r2r1r0 | 带数据的消息请求,r[2:0]指明消息路由机制 |
Cpl** | 000 | 0 1010 | 不带数据的完成响应事务 |
CplD | 010 | 0 1010 | 带数据的完成响应事务 |
CplLk** | 000 | 0 1011 | 用于存储读锁定,不带数据的完成响应事务 |
CplDLk | 010 | 0 1011 | 用于存储读锁定,带数据的完成响应事务 |
FetchAdd** | 010011 | 0 1100 | 先获取后相加的原子操作请求 |
Swap | 010011 | 0 1101 | 无条件交换的原子操作请求 |
CAS** | 010011 | 0 1110 | 比较并交换的原子操作请求 |
LPrfx | 100 | 0 L3L2L1L0 | 本地Prefix,L[3:0]表示具体的Prefix类型 |
EPrfx** | 100 | 1 E3E2E1E0 | 端到端Prefix,E[3:0]表示具体的Prefix类型 |
— | — | — | Reserved |
链路双方可以借助TC字段实现事务的保序要求。PCIe协议规定,相同TC值的事务需要遵守严格的先后顺序(不开启基于ID排序和松散排序,即不使能Attr[2:1])。同时,PCIe协议还规定,不同TC值的事务之间不需要保序。如果事务的发起方对于事务有顺序要求,则需要将这些事务分配相同的TC值。借助TC,还可以实现QoS功能,不过这需要硬件逻辑配合,放到QoS章节详细讲解。
前面说过,为了提高链路性能,PCIe将Non-posted类型的请求拆分,请求者发出一个请求后即可以继续发送请求,而不必等待完成响应。也就是说请求者可以连续发送多笔请求,比如可以连续发送若干的MRd。至于具体数目,取决于硬件设计的超发(Outstanding)缓冲区深度。此时的一个问题是,这么多超发请求,完成者返回完成响应时,请求者怎么知道对应的是哪笔请求。这就需要请求者给每个请求事务分配一个唯一的标识符,完成响应中带上这个标识符,请求者就知道对应哪笔请求了。这个标识符就是Tag。可能有的人会问,对于Posted类型的请求,比如MWr,不需要完成响应,是不是Tag字段就没有用了呢?的确如此,不过这时PCIe协议对Tag字段另有用处。
AT[1:0]字段表示地址类型,编码如下表。后面讲地址翻译服务ATS时会用到。
AT[1:0]** | Mnemonic | Description** |
00b | Untranslated | TLP Header中的地址是未经翻译的,需要主机侧进行地址翻译 |
01b** | Translation request | 地址翻译请求,主机侧的地址翻译代理(Translation Agent)需要返回地址翻译后的页表项给设备(如果支持的话) |
10b | Translated | TLP Header中的地址已经过翻译,主机侧可以直接响应该请求 |
11b** | 保留 |
Length[9:0]字段的意思比较简单,就是TLP Data的长度。数据是以DW对齐的,PCIe协议规定,数据最大可以是4KB。在实际应用中,Data的最大长度需要链路双方协商而定,很多时候并不是4KB。
2.3.1.2 存储事务的Header
存储事务支持32-bit地址和64-bit地址两种格式,其完整的TLP Header格式定义如下图。
DW 1,2,3中增加的字段有:
Requester ID[15:0]:请求者ID,这是主机分配的PCIe拓扑结构中的唯一值。Requester ID字段与Tag字段合起来组成了事务ID(Transaction ID)
Tag[7:0]:事务标签,前面介绍过了
Last DW BE[3:0]:数据负载中最后一个DW中的字节使能,用于存储,I/O,配置请求
First DW BE[3:0]:数据负载中第一个DW中的字节使能,用于存储,I/O,配置请求
Address[64:2]:地址,最低两位强制是00b
PH[1:0]:处理提示(Processing Hint),需要配合TH字段使用。如果TH无效,则PH[1:0]保留;TH有效,则PH字段编码表示处理提示,具体含义放到TPH功能部分讲解
这里需要解释一下Last DW BE[3:0]字段和First DW BE[3:0]字段。前面一直在说,TLP Data是DW对齐的,如果实际数据不是DW对齐,则可以借助这两个字段,指出实际数据的起始和结束位置。细心的人可能会问,对于一大段中间有空洞的数据段,该怎么处理?有两个办法,一是将这段数据拆开,用多个TLP传输;二是主机软件或设备软件知道哪些数据无效,在一个TLP将整个数据段传输,然后通过在软件中处理这段数据。
2.3.1.3 I/O事务的Header
I/O事务仅支持32-bit地址(在PCI时代不需要64-bit地址),其完整的TLP Header格式定义如下图。
PCIe协议对I/O事务TLP Header中的字段有一定规则:
TC[2:0]字段必须是000b
LN字段不适用,保留
TH字段不适用,保留
Attr[2]保留
Attr[1:0]必须是00b
AT[1:0]必须是00b
Length[9:0]必须是00 0000 0001b
Last DW BE[3:0]必须是0000b(I/O事务只有一个DW的数据负载,这个字段没有意义)
2.3.1.4 配置事务的Header
配置事务采用ID路由,因此不需要地址信息,其完整的TLP Header格式定义如下图。
第三个DW中增加的字段有:
Bus Number[7:0]:总线编号
Device Number[4:0]:设备编号
Fcn Number[2:0]:Function编号,与上面两组编号合起来称为BDF,是PCIe拓扑结构中的唯一标识
Register Number[5:0]:寄存器编号,与下面的字段一起使用,指示要访问的配置空间中的寄存器地址
Extended Register Number[3:0]:扩展寄存器编号
配置事务对TLP Header中各字段的限制:
TC[2:0]必须是000b
LN字段不适用,保留
TH字段不适用,保留
Attr[2]保留
Attr[1:0]必须是00b
AT[1:0]必须是00b
Length[9:0]必须是00_0000_0001b
Last DW BE[3:0]必须是0000b
2.3.1.5 消息事务的Header
消息事务的路由方式有多种,可以是地址路由,ID路由,隐式路由,其完整的TLP Header格式定义如下图。
新增加的字段是Message Code[7:0],表示消息编码。对于不同的消息类型,DW 2和3(即Byte 8-15)的含义不同。
Type字段的后三位,是消息事务的路由方式,编码见下表。
Type[2:0]** | Description** | Byte 8-15** |
000 | 路由到RC | 保留 |
001** | 地址路由 | 地址 |
101 | ID路由 | BDF |
011** | RC广播消息 | 保留 |
100 | 本地路由,终止于接收端 | 保留 |
101** | 收集并路由到RC,仅适用于PME_TO_Ack消息 | 保留 |
110/111 | 保留 | 保留 |
消息编码整理如下:
Message Name** | Message Code[7:0]** |
Assert_INTA | 0010 0000 |
Assert_INTB** | 0010 0001 |
Assert_INTC | 0010 0010 |
Assert_INTD** | 0010 0011 |
Deassert_INTA | 0010 0100 |
Deassert_INTB** | 0010 0101 |
Deassert_INTC | 0010 0110 |
Deassert_INTD** | 0010 0111 |
PM_Active_State_Nak | 0001 0100 |
PM_PME** | 0001 1000 |
PME_Turn_Off | 0001 1001 |
PME_TO_Ack** | 0001 1011 |
ERR_COR | 0011 0000 |
ERR_NONFATAL** | 0011 0001 |
ERR_FATAL | 0011 0011 |
Unlock** | 0000 0000 |
Set_Slot_Power_Limit | 0101 0000 |
Vendor_Defined Type 0** | 0111 1110 |
Vendor_Defined Type 1 | 0111 1111 |
Ignored Message** | 0100 0001 |
Ignored Message | 0100 0011 |
Ignored Message** | 0100 0000 |
Ignored Message | 0100 0101 |
Ignored Message** | 0100 0111 |
Ignored Message | 0100 0100 |
Ignored Message** | 0100 1000 |
LTR | 0001 0000 |
OBFF** | 0001 0010 |
PTM Request | 0101 0010 |
PTM Response** | 0101 0011 |
PTM ResponseD | 0101 0011 |
对于每种类型的消息,TLP header的Byte 8-15各不相同,这里不再赘述,后面用到哪种消息再具体分析。
2.3.1.6 完成事务的Header
对于Non-Posted类型请求,需要完成者返回完成响应事务。完成事务是ID路由方式,其完整的TLP Header格式定义如下图。
新增的字段有:
Cpl Status[2:0]:完成状态
BCM(Byte Count Modified):仅对PCI完成者有意义
Byte Count[11:0]:请求的剩余字节数
Lower Address[7:0]:低位地址,对于存储读取请求,该字段是完成事务返回的第一个数据的第一个启用字节的地址
Cpl Status字段的编码见下表:
Cpl Status[2:0]** | Completion Status** |
000 | 成功完成(Successful Completion,SC) |
001** | 不支持的请求(Unsupported Request,UR) |
010 | 配置请求重试状态(Configuration Request Retry Status,CRS) |
100** | 完成者中止(Completer Abort,CA) |
Others | 保留 |
2.3.2 TLP Prefix
TLP Prefix分为两种,Local TLP Prefix和End-End TLP Prefix。
Prefix的用途是扩展TLP。在PCI时代,TLP的格式就规定好了,随着技术的不断进步,协议中需要添加更多的扩展字段以用于扩展功能。聪明的协议制定者们想出了给TLP加前缀这种方式。在TLP前面添加扩展字段,既不影响旧有的设备和驱动软件,又能增加新特性。
一个Prefix长度是固定1 DW,格式定义如同TLP Header的第一个DW的格式定义。
Fmt字段和Type字段编码指示是哪种Prefix。
Type[4]用来区分是本地Prefix(0b)还是端到端Prefix(1b)。
目前在Gen 5.0中,本地Prefix有以下几种:
端到端的Prefix有以下几种:
除去上面的几种端到端Prefix,还有一种IDE Prefix,用于数据完整性和加密功能。IDE(Integrity and Data Encryption)功能是Gen 5中以ECN(Engineering Change Notice)形式发布的,在Gen 6的基本规范中正式引入。
不同类型的Prefix,Byte 1-3的含义不同,以后用到的时候再具体分析。
2.3.3 TLP Data
Data部分格式很简单,前面已经讲过很多了。Header中的Length[9:0]字段指示数据的大小,编码如下:
大家可以想想,为什么00_0000_0000b表示的是1024 DW,而不是0 DW呢?因为带不带数据是事务类型决定的,所以不需要表示0 DW。
在实际的设备中,很可能不会支持4KB这么大的数据负载。而且,对于存储读取事务的完成事务,PCIe协议规定数据为64-Byte或128-Byte,称为“读取完成边界(Read Completion Boundary,RCB)”。
关于Data的具体规则,这里不详细列出了。
2.3.4 TLP Digest
TLP Digest也叫做ECRC(End-End Cyclic Redundancy Check),如果启用此功能,则PCIe设备对所有发出的TLP增加一个DW,用于端到端的循环冗余校验。
端到端连接是一个网络连接术语,在PCIe中指的是请求者与完成者的连接,不管中间是不是有Switch。点到点连接指的是链路两端的直接连接,比如RC与Switch,Switch与EP的连接。
ECRC顾名思义,完成者负责最后用CRC对TLP其余部分做校验,中间的Switch会将ECRC直接转发(虽然允许Switch检查ECRC,如发现错误后上报错误,但Switch一般不会这么做)。
能看出来,ECRC解决的不是链路传输错误问题,实际上链路传输错误是通过链路CRC(Link CRC,LCRC)来检查的。这会放到数据链路层中介绍。
那么ECRC是用来干什么的呢?在事务层生成TLP一直到送给物理端口发出,中间的过程可能会存在逻辑错误,ECRC就是用来检测这个错误的。
具体的CRC算法就不介绍了。
【待续】