PCI总线中定义两个边带信号(PERR#和SERR#)来处理总线错误。 其中PERR#主要对应的是普通数据奇偶校检错误(Parity Error),而SERR#主要对应的是系统错误(System Error)。具体如下:
- 普通的数据奇偶校检错误——通过PERR#报告
- 在多任务事务(Multi-task Transaction,又称为Special Cycles)时的奇偶校检错误——通过SERR#报告
- 地址和命令的奇偶校检错误——通过SERR#报告
- 其他错误——通过SERR#报告
一个简单的例子如下图所示:
PCIe作为一种高速串行总线,取消了PCI总线中的这两个边带信号,采用错误消息的方式来实现错误报告。 但是,在软件层面上,PCIe仍是兼容PCI总线的
在PCIe总线的错误报告机制中,有如下四个比较重要的概念:
- 错误检测(Error Detection):指的是检测某个错误是否存在的过程。
- 错误登记(Error Logging):指的是将相关寄存器(配置空间中的)的对应位置位,以等待软件中的相关错误处理程序来处理该错误。
- 错误报告(Error Reporting):通知系统某个(或多个)错误发生了。在PCIe总线中, 发生错误的设备会通过错误消息(Error Message)逐级将错误信息发送至Root,Root接收到错误消息后,会产生对应的中断通知系统。
- 错误发送(Error Signaling):指的是通过发送错误消息(或者带有UR,CA的Completion和Poisoned TLP)来传递错误信息的过程。 “Poisoned TLP”是PCIe总线错误报告机制中的Error Forwarding的方式
PCIe总线Spec定义了两个错误报告等级。 第一个为基本的(Baseline Capability),是所有PCIe设备都需要支持的功能。 第二个是可选的,称之为高级错误报告(Advanced Error Reporting Capability)(AER)。
在基本的错误报告机制中,有两组相关的配置寄存器(配置空间中),分别为:
- 兼容PCI总线的寄存器(PCI-compatible Registers)
- PCIe总线中新增的寄存器(PCI Express Capability Registers)
高级错误报告机制(AER)中,又使用了一组专用的配置寄存器(配置空间中)。借助AER可以获得更多的错误信息,有助于软件定位错误源和分析错误原因。
PCIe总线的错误可以分为(Correctable Errors)和不可校正错误(Uncorrectable Errors)。 其中,可校正错误可以自动地被硬件识别并被自动的校正或恢复。而不可校正错误又被分为非致命的(Non-Fatal)和致命的(Fatal)。 非致命的错误一般有设备驱动软件(Device Specific Software)直接处理,且链路(Link)可恢复,甚至链路上的数据有可能得到恢复(不丢失数据)。 致命的错误只能由系统软件(System Software)处理,且一般需要进行复位等操作,因此链路上的数据必然会丢失。
错误检测机制
PCIe总线错误检测囊括了链路(Link)上的错误以及包传递过程中的错误,如下图所示。用户设计的应用程序层中的错误不属于链路传输中的错误, 不应当通过PCIe的错误检测与处理机制处理,一般可借助设备特殊中断(Device Specific Interrupt)等合适的方式进行报告与处理。
包传递过程的错误主要通过CRC编码来检测。PCIe定义了两种CRC——LCRC和ECRC。 其中LCRC(Link CRC)由数据链路层产生和校检,用于检测从一端的数据链路层发送到另一端的数据链路层的TLP是否发生的错误。 而ECRC(End-to-end CRC)由事务层产生和校检,且ECRC是可选的。
有人可能会质疑ECRC存在的必要性,因为LCRC已经对TLP进行了CRC校检,在此基础上多加一层ECRC可能是没有必要的。 这里来简单地说明一下,一般情况下(尤其是没有Switch的简单PCIe总线系统中),ECRC的确是没有必要存在的。 ECRC主要为解决Switch中传输的可能存在的传输错误问题的, 如果用户的设计中并没有Switch(只是简单的Root与Endpoint的端对端直连),完全可以不使用ECRC。
如下图所示,假设来自Endpoint的TLP被正确地传输到Switch的Downstream输入端口(Ingress Port), Downstream输入端口中的数据链路层也完成了对其的LCRC校检,且未发现错误。然后Switch会将该LCRC移除, 并添加新的序列号(Sequence Number),随后重新计算LCRC,再将该TLP发送至Switch的Upstream输出端口(Egress Port)。 显然,在此过程中TLP是不受保护的,一旦期间数据传输遇到错误等异常,可能会导致重新计算LCRC前的数据已经受到了破坏,且仅仅使用LCRC是无法发现这样的错误的。
需要注意的是,ECRC是AER中的一部分,要想使用ECRC,该PCIe设备必须是支持AER的。
如果按照错误产生的层(Layer)来分,则可以分为物理层错误,数据链路层错误和事务层错误。
物理层错误(Physical Layer Errors)主要有:
- 8b/10b编解码异常
- Framing异常(8b/10b编码中是可选的,128b/130b中是必选的)
- Elastic Buffer错误(可选的)
- 起始字符失锁(Loss of Symbol Lock)或者通道对齐失锁(Lane Deskew)(可选的)
数据链路层错误(Data Link Layer Errors)主要有:
- LCRC校检失败
- 序列号(Sequence Number)异常
- DLLP中的16-bit CRC校检失败
- 链路层协议错误(Link Layer Protocol Errors)
事务层错误(Transaction Layer Errors)主要有:
- ERCR校检失败(可选的)
- 异常的TLP(Malformed TLP)(即TLP的格式异常)
- 流量控制协议异常(Flow Control Protocol Violation)
- 不支持的请求
- 数据损坏(Data Corruption,又称为Poisoned Packet)
- Completer Abort(可选的)
- 接收端溢出(Receiver Overflow)(可选的)
- 返回包超时(Completion Timeout)
- 不对应的返回包(Unexpected Completion,即Completion与发出的Request不一致)
当接收端的物理层检测到TLP存在错误时,如果再将该TLP继续传送至数据链路层和事务层必然也会发现错误。 而过多的错误会让错误分析与处理变得困难。因此,没有必要在向上传递该TLP,而是将其直接扔掉,并报告相应的错误。
然而,即使这样,PCIe总线的错误报告中也有很多错误源自同一个错误源。因此需要对错误进行优先级排序, 使得错误源(最底层的错误)的优先级更高,能够最先得到处理。PCIe总线中的错误优先级排序如下(优先级从高到低):
- 不可更正的内部错误(Uncorrectable Internal Error)
- 接收端Buffer溢出
- 流量控制协议错误
- ECRC校检失败
- 异常的TLP(Malformed TLP)
- AtomicOp Egress Blocked
- TLP包头异常(TLP Prefix Blocked)
- 访问控制服务(Access Control Services,ACS)异常
- MC(Multi-cast) Blocked TLP
- 不支持的请求(Unsupported Request,UR),Completer Abort(CA)或者不对应的返回包(Unexpected Completion)
- 接收到损坏的数据包(Poisoned Packet)
错误源
ECRC
前面的文章中提到过,ECRC是可选的,主要用于包含有Switch的PCIe总线系统中。且只有支持AER的PCIe设备才有能力支持ECRC功能。 配置软件通过检查配置空间,确认PCIe设备的某个功能(Function)支持ECRC后, 可以通过向错误功能控制寄存器(Error Capability and Control Register)中的响应为写0或者1来禁止或者使能ECRC功能。
如果使能了ECRC功能,可以通过TLP包头中的TD(TLP Digest,ECRC也被称为Digest)为来标记当前的TLP是否使用ECRC,如下图所示。 需要特别注意的是,如果TD为1(表示使用ECRC),但是TLP中却没有ECRC;或者TD为0,TLP中却包含了ECRC,则会被判定为TLP格式错误,即Malformed TLP错误。
ECRC是基于TLP的包头和数据(Header and Data Payload)计算的,接收端会重新基于这些内容计算并与收到的TLP中的ECRC(发送端计算的)作对比, 如果不一致,则认为数据传输过程中发生了问题,数据被破坏了,进而产生ECRC校检错误。 需要注意的是,在TLP包头中,有两位实际上是不参与ERCR计算的——Type域的bit0和EP位。 这两位通常被称为Variant bits,且在ECRC计算的时候,这两位的对应位置始终被认为是1,而非使用实际的数值。
当接收端(Completer)接收到的请求(Request)TLP中存在ECRC校检错误时,接收端通常会选择不对该请求发送返回TLP(Completion), 并将ECRC错误状态位(配置空间中的)置位。发送端由于长时间未接收到Completion,进而会产生Completion超时错误(Timeout Error)。 而大部分发送端,会选择重新发送先前的请求Request。
当发送端(Requester)在发送完请求后收到了来自接收端返回的TLP(Completion)时,却发现该Completion TLP中存在ECRC校检错误, 会将ECRC错误状态位(配置空间中的)置位。发送端可以选择重新发送先前的请求Request,还可以选择通过特殊功能中断(Function Specific Interrupt)向系统报告错误。
以上两种情况中,如果使能了错误消息报告功能的话,不可校正的非致命错误消息(Uncorrectable Non-fatal Error Message)会被发送至系统。
Data Poisoning(Poisoned Data or Error Forwarding)
Data Poisoning也被称为错误传递(Error Forwarding),指的是在已知TLP Data Payload被破坏(Corrupted)的情况下, 该TLP仍然被发送至其他的PCIe设备。此时,该TLP包头的EP位(Error Poisoned)被置位为1,表明该TLP已经被破坏。如下图所示:
既然都已经知道该TLP Data Payload被破坏了,为什么还要再将其进一步传递呢?实际上,这样做主要是针对某些特殊的应用的:
- 便于发送端(Request)和系统分析错误:假设发送端(Request)向接收端(Completer)发送了读数据请求, 接收端从某个内存设备中读取数据后通过Completion返回数据给发送端。但是在此过程中发生了错误, 接收端(Completer)因此不向发送端(Request)返回Completion,则发送端只会产生Completion Timeout错误, 却难以分析错误原因。如果接收端返回Poisoned Completion TLP给发送端(TLP包头中EP为1), 则发送端至少可以确认接收端正确地接收到了其发出的请求(Request)。
- 便于发现Switch(或其他桥设备)中的错误:假设TLP中的Data Payload是在Switch中被破坏的,采用错误传递的方式有助于发现该错误。
- 有些应用允许接收存在错误的数据:比如实时的音频或者视频传输,其宁可接收到有些许错误的数据,也需要尽量保证数据传输的实时性。
- 数据可能通过应用层恢复:有些应用可能采用了特殊的编码 ,该编码可以恢复某些被破坏的数据(如ECC可恢复1位的错误)。
需要特别注意的是,错误传递(Data Poisoning or Error Forwarding)只是针对TLP中的Data Payload是否被破坏,和TLP包头的内容无关。 也就是说错误传递只是针对那些带有Data Payload的TLP的,如Memory、Configuration、I/O写或者带有返回数据的Completion。 PCIe Spec没有定义对没有Data Payload的TLP,其TLP包头中的EP却为1的情况,应当如何处理。
注:需要注意的是,Poisoning操作只能在事务层进行。原因很简单:数据链路层和物理层在任何情况下,都不会检查TLP包头的内容,更不会修改TLP包头。
事务(Transaction )错误
事务错误主要包括不支持的请求(Unsupported Request)、Completer Abort、非预期的Completion和Completion超时。 该错误类型主要通过返回的Completion TLP包头中的Compl. Status告知Requester,如下图所示。
不支持的请求(Unsupported Request)主要包括:
- 请求类型不被当前PCIe设备支持
- 消息中使用了不支持或者未定义的消息编码
- 请求的地址空间超出(或者不在)设备的地址空间中
- 针对Completer的IO或者存储映射控制空间(Memory-mapped Control Space)进行的Poisoned写操作(EP=1)
- Root或者Switch的Downstream端口接收到针对其二级总线(Secondary Bus)上的不存在的设备的配置请求(Configuration Request)
- Endpoint接收到Type1型的配置请求
- Completion中使用了保留的Completion状态编码(参考上面的表格)
- 设备(的某个功能,Function)处于D1、D2或者D3hot电源管理状态时,却接收到了除了配置请求和消息之外的内容
Completer Abort(CA)主要包括:
- Completer接收的特殊请求,只有在违背其规则的情况下才能对该请求进行响应(返回Completion)
- 因为某些恒定的错误状态(Permanent Error Condition),导致Completer无法响应接收到的请求
- Completer接收到存在访问控制服务错误(Access Control Services Error,ACS Error)的请求
- PCIe-to-PCI桥接收到针对其连接的PCI设备的请求,但是该PCI设备无法处理该请求
非预期的Completion主要包括:
- Requester接收到的Completion和其发出的Request不一致
Completion超时
所有的PCIe设备都必须支持Completion超时定时器,除非该设备只是用于初始化配置事务的。 需要注意的是,PCIe设备必须能够针对多个事务(Transaction)分别计时。 PCIe 1.x和2.0的Spec建议超时时间最好设置为10ms至50ms之间,对于一些特殊情况,超时时间最低可设置为30us。 PCIe 2.1 Spec开始,增加了第二设备控制寄存器(Device Control Register 2)用于查看和控制超时时间的值。如下图所示:
如果,某个请求对应多个Completion,那么除了最后一个Completion,其他的Completion不会造成该请求的定时器停止计时。
链路流量控制(Link Flow Control)相关的错误
链路流量控制相关的错误主要有:
- 在FC初始化时,链路相邻设备无法完成针对任何一个VC的,最小的FC Credits的交换更新(Advertises)
- 链路相邻设备交换更新(Advertises)的FC Credits超过了最大值(Data Payload最大为2047,Header最大为127)
- 链路相邻设备交换更新时,FC Credits为非零值,且该链路的FC Credits之前已经被初始化为无限值了
- 接收端Buffer溢出,导致数据丢失(可选的,但是如果使能,则认为是Fatal Error)
异常的TLP(Malformed TLP)
异常的TLP(Malformed TLP)错误主要有:
- Data Payload超过了最大值(Max Payload Size)
- 数据长度(Data Length)与包头中的长度值不一致
- 存储地址起始位置跨越了4KB边界(Naturally-aligned 4KB Boundary)
- TD(TLP Digest)的值与ECRC是否使用不一致
- 字节使能冲突(Byte Enable Violation)
- 未定义的类型值(Type Field Values)
- Completion违反了RCB(Read Completion Boundary)值
- 针对非配置请求返回的Completion中的状态为配置请求重试状态(Configuration Request Retry Status)
- TC域包含了一个未被分配到当前使能的VC的值(也被称为TC Filtering)
- IO或者配置请求冲突(可选的)
- 中断Emulation消息向下发送(可选的)
- TLP前缀错误(具体请参考PCIe Spec V2.0的2.2~2.6相关章节)
内部错误(Internal Errors)
一般指的是Switch等桥设备内部产生的错误
错误报告机制
PCIe总线有三种错误报告方式,分别是:
- Completions:通过Completion中的状态位向Requestor返回错误信息
- Poisoned Packet(又称为错误传递,Error Forwarding):告知接收端当前TLP的Data Payload已经被破坏
- Error Message(错误消息):向主机报告错误信息
为了兼容PCI总线的错误报告机制(使用PERR#和SERR#),PCIe设备会自动将CA、UR和Poisoned TLP转换为对应的错误信息。
PCIe设备的配置空间中的状态与控制寄存器如上图所示,通过这些寄存器可以 使能(或禁止)通过错误消息(Error Message)发送错误报告、查询错误状态信息,以及链路训练和初始化状态等。
默认的错误分类如下表所示
这些错误类型可以通过设备控制寄存器(Device Control Register)中的相关位,进行使能或者禁止
也可以通过设备状态寄存器(Device Status Registers)相关位查询错误状态:
当然,当Root接收到错误消息后,怎么处理还要取决于Root Control Register的设置:
链路错误(Link Errors)一般发生在物理层与数据链路层通信的过程中。 对于Downstream的设备,如果链路上发生了Fatal错误,此时,该设备并不能够向Root报告错误。 这种情况下,需要Upstream设备向Root来报告错误。为了消除链路错误,一般需要对链路进行重新训练(Retrain)。 如下图所示,在链路控制寄存器中,可以通过往Retrain Link这一位写1,来强制进行链路重训练。
当发起重训练请求后,软件可以检查链路状态寄存器(Link Status Register)中的Link Training位, 来确认链路训练是否已经完成,如下图所示。当该位为1时,表明链路训练尚未完成(或者还没有开始),如果链路训练已经完成,硬件会自动将该位清零。