Pcie :
Configuration space register PCIe配置空间是 PCI/PCIe 设备的一组标准化寄存器,用于存储设备的硬件信息、资源需求和功能配置。是PCI/PCIe 设备与系统(如操作系统或 BIOS)之间交互的核心机制,支持设备的发现、资源分配和功能控制等。配置空间为4KB大小,采用链表管理。前256B与pci一样,64B的Header和192B的Capability数据结构,Header中Type 0 是终端的配置header,Type1是交换机的配置header

CPU只能通过RC去访问设备的空间
BAR基址寄存器,上电后,系统读取BAR,并分配对应的系统内存地址空间,然后把相应的内存基地址写回BAR
一个PCIe设备至少有一个配置空间,可能有多个功能,一个功能对应一个配置空间。
只要知道总线序号、设备序号和功能序号,就能找到唯一的功能。寻址基本单元是功能,它的id由总线、设备、功能组成(BDF)

TLP header中有一个3-bit的域段,叫TC,用以指示对应TLP报文的traffic class(共8种)。TC要和Virtual Channel (VC)配合使用以区分不同TLP报文的服务等级(可以理解为PCIe的QoS,Quality of Service
如果PCIe系统不支持Virtual Channel Capability structure or Multi-Function Virtual Channel Capability structure,报文需要遵从以下原则以避免异常:
A. 发送端只能发送TC0的报文;
B. 接收端/完成端需要能够接受所有TC labels的报文,并返回对应TC label的完成报文(completion which preserves the TC label);
C. Switch需要将所有的TC labels转换成TC0,并转发报文。

不同的TC的报文会被映射到不同的VC上进行处理,1个或者多个TC可以映射到一个VC上。TC0和VC0的映射关系是固定的(hardwired),并且所有设备都必须支持


Flow control接收者的每个端口都要报告自己的流量控制 Buffer 的大小,单位为 credit(信用)。一个 Buffer 中 credit 的数量会从自身接收侧事务层发送至发送侧数据链路层。在合适的时间,数据链路层将会产生一个流量控制 DLLP 来将这个 credit 信息转发至每个流量控制 Buffer 的链路对端的接收者
一个端口所支持的每个 VC(Virtual Channel)资源都会实现流量控制 Buffer。

DLLP(Data Link Layer Packets)和TLP(Transaction Layer Packets)是两种不同层次的数据包结构,它们分别对应于数据链路层(Data Link Layer)和事务层(Transaction Layer)

North Bridge 北桥,连接处理器与外设总线
South Bridge 南桥,连接PCI与系统外设

用于描述设备之间信号传输路径的术语为“链路(Link)”,它由一个或以上的接收发送对组成。这样的一对接收和发送被称为一个“通道(Lane)”,协议规范允许一条链路内有 1、2、4、8、12、16 或 32 个通道。链路内通道的数量称为链路宽度,通常用 x1、x2、x4、x8、x16 以及 x32 来进行表示。
克服问题.我们知道,并行总线的性能被一些问题所限制,图2‑3 展示了其中的三个问题。首先,回想一下,并行总线使用公共时钟;信号在一个时钟沿被输出,然后在下一个时钟沿被接收方接收。这个模型的第一个问题来自于信号从发送端传输到接收端所花费的时间,称为渡越时间。渡越时间必须小于一个时钟周期,否则将会出问题,这使得难以通过继续减小时钟周期来提升速度。因为若需要继续减小时钟周期,为了让信号渡越时间依然小于时钟周期,需要更短的布线并减少负载的设备数量,但是最终这样的做法都会到达极限并且越来越不现实。第二个造成并行模型性能受限的因素是使用公共时钟时,时钟到达发送方和接收方的时刻不一致,这称为时钟偏斜。电路板设计人员尽力去减小时钟偏斜的值,因为时钟偏斜将会降低信号传输时序预算,但是这种偏斜永远无法彻底消除。第三个因素是信号偏斜,它指的是多比特位宽数据的各个位到达接收端的时刻存在差异。显然,这样的多位宽数据在所有的比特都到达且稳定之前都不能被接收方采样,这使得我们必须去等待最慢的那一比特。
 
每个通道都使用差分信号进行传输,差分信号是指每次传输一个信号时同时发送它的正信号和负信号当然,这样会将引脚增加一倍,但是相对于单端信号而言,差分信号在高速传输上的两个明显的优点足以抵消其引脚数方面的不足:它提高了噪声容限,并降低了信号电压

PCIe 链路不再像 PCI 一样使用公共时钟,它使用了一个源同步模型,这意味着需要由发送端给接收端提供一个时钟来用于对输入数据进行锁存采样。对于 PCIe 链路来说,并不包括输出时钟信号。相反地,发送端会将时钟通过 8b/10b 编码来嵌入数据流中,然后接收端将会从数据流中恢复出这个时钟,并用于对输入数据进行锁存。
关于时钟恢复,有一件需要注意的事情,PLL 需要输入端的信号跳变来完成相位比较。如果很长一段时间数据都没有任何跳变,那么 PLL 的恢复时钟可能会偏离正确的时钟频率。为了避免这种问题,8b/10b 编码中的设计目标之一就是要确保比特流中连续的 1 或者 0 的数量不能超过 5 个

刚组好的数据包会被存入一个被称为虚拟通道缓存,直到这个包可以被发往下一个层级。当这个包被向下发给数据链路层后,数据链路层会在数据包中加入额外的信息以供对端的接收方进行错误检查,而且这个数据包会在本地储存下来,这样我们就可以在对端检测到传输出错时重新发送这个数据包。当数据包到达物理层后,它被编码,并使用链路上的所有可用的通道、以差分信号的形式进行传输。

设备核心层/软件层(Device Core/Software Layer)
设备核心层是一个设备的核心功能,例如网络接口或是硬盘驱动控制器。它并不是 PCIe 协议规范中所定义的一个层级,但是我们可以把它当做一个 PCIe 的层级,这是因为它位于事务层的上方,而且它是所有请求的源头或是目的地。它为事务层提供了需要发送的请求信息,其中的信息包括事务类型、地址、需要传输的数据量等等。当事务层接收到输入数据包时,它也是事务层向上转发输入数据包信息的目的地。

事务层(Transaction Layer)
为了响应来自软件层的请求,事务层生成出站数据包(outbound packet)。它也会检查入站数据包(inbound packet),并将入站数据包内包含的信息向上转发给软件层。事务层支持非报告式请求(non‐posted transaction)的拆分事务协议,并将入站完成包(inbound Completion)与先前传输的出站非报告式请求包关联起来,即知道这个完成包是对应到哪个非报告式请求包。事务层所处理的事务使用的数据包种类为 TLP,TLP 可以分为四个请求种类:
内存(Memory)
IO
配置(Configuration)
消息( Messages)
前三种在 PCI 和 PCI-X 中就已经得到支持,但是消息是 PCIe 中的一个新的请求种类。一个请求包向目标设备传送命令,目标设备作为响应请求而发回的一个或多个完成包,这二者组合起来就是对一个事务的定义,即一个事务由一个请求包以及所有返回的完成包共同组成。
 
 
QoS,它是通过添加一些东西来实现的。首先,每个数据包都被软件分配了一个优先级,这个优先级是通过设置数据包内的一个 3 比特的字段区域来进行标识的,称之为 TC (Traffic class, 流量类型)。一般来说,给一个数据包分配一个编号较大的 TC 表示希望给与这个数据包一个更高的优先级。第二,使用多缓冲区,称为 VC (Virtual Channels, 虚拟通道),将其构建在硬件的每一个端口中,数据包会根据其 TC 值,来被放入相应的 VC 中(相应的缓存区中)。第三,由于现在对于一个端口来说,在一个时刻将会有多个缓冲区都存在可以进行传输的数据包,因此需要有对 VC 进行选择的仲裁逻辑。最后,交换机必须在相竞争的各个输入端口间做出选择,以便访问相应端口的 VC 。这一步被称为端口仲裁,它可以由硬件来进行分配或是由软件来进行编程配置。
 

流量控制(Flow Control)
串行传输所使用的一个典型协议是,要求发送方仅在对端有足够的缓冲区接收时才发送数据包。这样的规定删去了总线上浪费性能的操作事件,比如 PCI 中允许进行的断开与重试( disconnects and retries),这使得这类问题在传输中得到消除。这样做的代价是,接收方必须足够频繁的报告它的可用缓冲区空间来避免不必要的传输停顿,而且这样的报告也需要占用接收方自己的一点带宽。在 PCIe 中,这个可用缓冲区空间的报告是由 DLLP(Data Link Layer Packet)来完成的


非报告式事务(Non-Posted Transactions)
非报告式事务发出后,目标端必须返回一个 Completion 作为响应,用来携带读取数据、报告写入结果或提供错误状态。这类事务虽然延迟更高,但能为发起方提供明确的完成确认。

1. 普通读(Ordinary Reads)
普通读指常规的读操作,包括内存读(Memory Read)、IO 读(IO Read)和配置读(Configuration Read)。
发起方发送读请求 TLP 给目标端,目标端随后返回 Completion TLP。Completion 中携带读出的数据;若发生错误,也会通过 Completion 状态字段传达。普通读属于非报告式事务,因此读操作必须在收到响应后才能继续执行后续依赖该数据的工作。

2. 锁定读(Locked Reads)
锁定读源自 PCI 总线时代,用于在多主设备环境下对一段内存或 IO 空间进行互斥的原子访问:发起锁定读后,总线资源被锁定,直到锁定写或解锁操作完成,期间其他主设备不能访问该资源。
不过在 PCIe 中,由于采用分组交换和转发式架构,标准 PCIe 规范不再支持锁定读事务。它在 PCIe 中已被废弃,无法作为普通 TLP 发出。需要原子操作时可以改用 PCIe 2.0 引入的原子操作(AtomicOps),或依靠软件层面的锁机制实现。

3. IO 和配置写(IO and Configuration Writes)
IO 写和配置写同样属于非报告式事务,原因是 IO 端口与配置空间通常具有副作用(例如改变设备行为),写入后必须有确认才可靠。因此,这类写请求不能做成“发完即忘”的报告式事务,目标端需要返回 Completion 来表示写操作成功或失败。
需要注意的是,内存写虽然是写操作,却被归类为报告式事务;但 IO 写和配置写由于可靠性要求,仍然保持非报告式。

报告式写(Posted Writes)
报告式事务发送后无需等待 Completion,发起方发出 TLP 即视为完成。这样可以降低延迟、提高吞吐量,适合对数据正确性由底层传输保障、不需要高层反馈的场景。

1. 内存写(Memory Writes)
内存写是对系统内存空间进行写操作的事务,例如 DMA 写、CPU 向显存/网卡寄存器写入数据等。
它采用报告式设计,因为内存写的目标是内存或支持 posted 写入的 MMIO 设备,写入过程不要求对方返回完成响应。PCIe 链路层的 ACK/NAK 机制仍然确保 TLP 被接收,只是事务层不单独发送 Completion。因此内存写具有很高的带宽和较低的延迟。

2. 消息写(Message Writes)
消息写是专门用于传递“消息”的报告式事务,和普通内存写不同,它的目标不是一个地址,而是一组消息编码(Message Code)。消息写主要包括以下用途:
中断通知(如传统 INTx 虚拟化)
错误报告(AER 错误消息)
电源管理事件(PM 消息)
锁定/解锁协议消息(供兼容软件使用)

Ack/Nak 协议(Ack/Nak Protocol)
如图2‑25 所示是一种基于硬件的自动重试机制。如图2‑26 所示,每一个被发送方发出的 TLP 都被加上了 LCRC 与序列号,在接收方会对这两个信息进行检查。发送方的重传缓存保存着每个被发送的 TLP 的副本,直到对端确认成功收到了这个 TLP。这个确认成功收到的过程是通过接收方发送 Ack DLLP 来实现的,在这个 Ack DLLP 中包含有接收方成功接收的上一个 TLP 的序列号。当发送方收到这个 Ack DLLP,它将会把里面序列号所对应的 TLP、以及这个 TLP 之前的所有 TLP,都从重传缓存内清除。

如果接收方检测到了一个 TLP 错误,它将会把这个 TLP 丢弃,并向发送方返回一个 Nak DLLP,以期望发送方能对未确认成功接收的 TLP 进行重传,并通过重传获得一个完好的 TLP。由于错误检测通常是十分迅速的操作,因此从错误检测开始到发起重传的时间也比较短,这样就可以在较短的时间内纠正发生的错误。这一操作过程被称为 Ack/Nak(Acknowledge/NotAcknowledge) 协议

物理层(Physical Layer)
TLP 和 DLLP 这两种类型的数据包都需要从数据链路层向下转发至物理层,这样才能通过链路传输至对端接收方设备,并从接收方的物理层向上转发至它的数据链路层。
协议规范中将关于物理层的讨论分为两部分:逻辑部分和电气部分。我们这里也将保留这种划分方式。逻辑物理层包含了一系列的数字逻辑,这些数字逻辑是关于准备将数据包在链路上进行串行传输的逻辑以及相反的输入数据包的处理逻辑。电气物理层是物理层的模拟电路接口,它与链路直接相连,它为每个通道提供差分驱动器以及差分接收器
逻辑物理层(Physical Layer-Logical)
由数据链路层转发来的 TLP 与 DLLP 被物理层中的缓存所记录,在这个缓存中会对这些数据包加上包起始字符和包结束字符,这两个字符将有助于接收端用来检测数据包的边界。由于包起始字符和包结束字符分别出现在数据包的两端(头和尾),因此他们也被称作“组帧”字符。
在物理层中,数据包中的每一个字节都将被分割到链路所使用的所有通道,这一过程被称为字节条带化(byte striping)。实际上,每个通道在链路上都扮演着一个独立的串行传输路径,这些通道们各自传输的数据会在接收端被聚合。每个字节都进行了扰码,以此来减少在传输线上传输连续重复的“0”或“1”,这有助于减少链路上的 EMI(electro-magnetic interference,电磁干扰)。
链路训练和初始化(Link Training and Initialization)
物理层的另一个任务就是负责链路的初始化以及训练过程。在这个全自动化的过程中,为了让链路准备好进行正常工作,我们采用了几个步骤,主要为确定几个可选条件的状态。例如,链路宽度可以从 1 通道到 32 通道,并且可以提供多种速度。链路训练过程将会发现这些可选项,并通过一个状态机来寻求一个建立最佳连接的组合,也就是说链路训练将会确认这些可选项具体的值(链路宽度、速率等)。
电气物理层(Physical Layer-Electrical)
物理的发送器和接收器是通过一个 AC 耦合链路相连接,如图2‑29 所示。所谓“AC 耦合(交流耦合)”,意思是两个设备相连接的物理路径上放置有电容,它用于让信号的高频部分(AC 交流)通过,而阻塞低频(DC 直流)部分。许多串行传输方式中都使用了这种方法,因为它允许发送端和接收端的共模电压不同(共模电压指的是0、1电平交界处的电压),这意味着发送端和接收端可以使用不同的参考电压。

 
PCI特性
PCI 设备的每一个 function 都需要 256 字节的配置空间。
每一个 pci 设备最多包含 8 个 functions, 每一个功能都有一个编号:[0:7].
pci 驱动首先需要做的事就是查找 function 的 Header,并判断 Header 类。这一步称做枚举。(通过访问配置空间完成.)
PCI-X 与 PCI 只有硬件上的区别。PCI-x 频率更高,带宽更大
PCIe特性
PCIE 是由 PCI 并行总线发展而来的串行总线。pcie 是使用差分线传输的串行全双工总线。PCIE 使用与 PCI 一样的配置 Header,所以可以保证软件的兼容性
   
 
PCIE 1.0 运行在 2.5GT/s 频率下。
PCIE 2.0 运行在 5 GT/s 频率下;
PCIE 3.0 运行在 8 GT/s 频率下;
PCIe 1.0/2.0 使用 8b/10b 编码。
PCIe 3.0 使用 128b/130b 编码
PCIE 不需要公共时钟。PCIE 使用源同步模型,也就是由数据发送方提供时钟给接收方,发送方会将时钟信号编码进差分数据中,接收方通过PLL对比、锁频来确定时钟.
PCIE 使用基于包的数据传输协议,物理底层使用数据包的形式进行数据交换
PCIE 链路可以使用多个 lane,两个 PCIE 设备之间的链路, 称为 link. link 可以会使用多个通道, 称为 lane.
PCIE link 必须是点对点的连接,PCIE 需要使用 switch/bridge 来建立更灵活的树形拓扑结构
CPU使用 Root Complex 与外部 PCIE 设备或总线连接
CPU与PCIE总线之间也许包含几个额外的组件(处理器接口, DRAM 接口或其他芯片), 这些额外组件统称为Root Complex.
Root Complex 代表 CPU 与 PCIE 树形拓扑结构中的剩余设备通信
Root Complex 内部存在一个 Host-PCI Bridge 作为 PCIE 0 号虚拟总线, 并存在额外的 PCIE bridges 用于创建新的总线
 
Switch 与 Bridge
Switch 和 Bridge 感觉没啥区别. 都可以拓展 PCIE 总线,与 Root Complex 一样,switch 内部也存在一条虚拟总线,以及多个 pcie bridge,用于创建新的总线或下挂设备
 
 
 
 
内存读请求(Memory Read Request)
关于我们开始讨论的第一部分,请参考图2‑32。发起方的设备核心层或者说是软件层将会向事务层发起一个请求,这个请求中包含了这些信息:32 比特或 64 比特的内存地址、事务类型、需要读取的数量总量(以dw为单位计数)、流量类型、字节使能、属性等等。
事务层使用上面所述的那些信息来构建一个 MRd TLP。关于 TLP 内部格式的细节我们稍后再进行描述,目前我们只需要知道 TLP 的 Header 大小为 3DW 或 4DW,这取决于它的地址字段的大小(32 位地址对应 3DW Header,64 位地址对应 4DW Header)。此外,在 Header 中还有事务层加入的发起方 ID字段(bus#,device#,function#),完成方可以通过这个发起方 ID 字段来返回完成包。TLP 随后被置入相应优先级的虚拟通道缓存中,等待轮到它被发送。一旦这个 TLP 被选中,流量控制逻辑将会确认对端设备的接收缓存(虚拟通道)有足够的可用空间来接收这个 TLP,然后 MRd TLP 就被向下发送至数据链路层。

在数据链路层中,数据包被加上 12 比特的序列号以及32 比特的 LCRC。并在重传缓存中保存这个TLP的一个副本,然后这个数据包就被向下转发至物理层。

在物理层中,数据包被加上包起始字符以及包结束字符,然后在所有可用的通道上进行字节条带化,再进行扰码、8b/10b 编码。最终这个数据包的比特在链路上的每个通道内以串行差分的形式传输到对端。

完成方接收到输入的比特流,它将这个比特流先进行串并转换将比特流恢复成 10 比特符号,然后让它们通过一个弹性缓存。随后 10 比特符号被解码后恢复成字节,然后每个通道上的字节都被解扰以及反条带化。接下来完成方物理层检测到包起始字符和包结束字符,并将它们从 TLP 中剥除。剩余的 TLP 被向上转发至数据链路层。

完成方的数据链路层将对接收到的 TLP 进行 LCRC 错误校验,并检查 TLP 序列号以确定是否存在 TLP 丢失或 TLP 失序。若这些步骤都无错,数据链路层将生成一个 Ack 信息,里面包含了与这个 MRd TLP 中相同的序列号。然后再给这个 Ack 信息加上 16 比特的 CRC,这样就组成了一个 Ack DLLP,将这个 Ack DLLP 送回物理层,由物理层加上相应的组帧符号,并把 Ack DLLP 传输给发起方。

发起方的物理层接收到 Ack DLLP 后,对其组帧符号进行检查和剥除,然后向上转发至数据链路层。如果数据链路层对其 CRC 校验无错,它将会用 Ack DLLP 内的序列号与重传缓存中保存的 TLP 副本的序列号进行比较,并将与 Ack DLLP 中序列号相匹配的 TLP 副本从重传缓存中清除。相反地,若发起方接收到的是一个 Nak DLLP,那么它将把序列号匹配的这个 MRd TLP 进行重传。由于 DLLP 仅对数据链路层有意义,所以在这些 DLLP 的操作中将不会有东西向上转发给事务层。

除了上述的产生 Ack DLLP 之外,完成方的数据链路层还将把 TLP 向上转发给事务层。在完成方的事务层中,TLP 被置于与其优先级相对应的虚拟通道接收缓存中,等待被处理。然后可以对 TLP 进行 ECRC 校验(ECRC 为可选项),若校验无错,那么 TLP Header 的内容(地址、发起方 ID、MRd 事务类型、请求数据总量、流量类型等)将被向上转发至完成方的软件层。
 

Cpld(Completion with Data, 带有数据的完成包)
下面开始介绍此次举例回顾 PCIe 协议的另一半内容,请参考图2‑33。为了服务 MRd 请求,完成方的设备核心层/软件层将会向它的事务层发送一个 CplD(Completion with Data,带有数据的完成包)请求,这个完成包请求中包含了与 MRd 请求中相同的发起方 ID、Tag,还包含了事务类型以及另外的完成包头内容,并且完成包中还包含了被请求的数据。
事务层使用 CplD 请求中的这些信息来构建一个 CplD TLP,这种 TLP 的 Header 固定为3DW(这是由于 CplD TLP 使用发起方 ID作为路由信息,因此不会需要用到 64 比特的地址)。事务层也会将自身的完成方 ID添加到 CplD TLP Header 中。这个数据包随后被放置入一个合适的虚拟通道的发送缓存中,一旦这个虚拟通道被仲裁选中并要发送这个 CplD TLP,流量控制逻辑将会确认对端设备有足够的可用缓冲区空间来接收它,当确认足够后则将这个 CplD TLP 向下转发至数据链路层。

如前文所述那样,数据链路层给数据包加上 12 比特的序列号和 32 比特的 LCRC。然后将添加完这些信息的 TLP 保存一份副本在重传缓存中,之后变将数据包向下转发至物理层。

依然如前文所述那样,物理层给数据包加上包起始字符和包结束字符,并将其在所有可用通道上进行字节条带化,再进行扰码、8b/10b 编码。最终,这个 CplD TLP 数据包在链路的所有通道上以串行差分的形式被传输至对端。

当发起方接收到这个 CplD TLP 的输入比特流时,它将比特流转换恢复成 10 比特符号(10bit symbol),并让 10bit 符号流通过一个弹性缓存。随后 10bit 符号被解码恢复为字节,并进行解扰和字节反条带化。然后物理层就能检测到这个 CplD TLP 的包起始字符和包结束字符,并将这两个字符剥去。之后便将剩余的 CplD TLP 向上转发至数据链路层。

和之前一样,数据链路层对 CplD TLP 进行 LCRC 校验,并检查序列号以确定是否存在 TLP 丢失或出现 TLP 失序。如果并未出现错误,数据链路层将产生一个 Ack DLLP,其中包含了与 CplD TLP 中相同的序列号,并给这个 Ack DLLP 加上 16 比特 CRC,然后将其送回给物理层加上相应的组帧字符并将这个 Ack DLLP 传输给完成方。

完成方的物理层将会检测并剥除 Ack DLLP 的组帧字符,并将剩余的部分向上转发给数据链路层,在这里对 Ack DLLP 的 CRC 进行校验。若校验无错,完成方的数据链路层将 Ack DLLP 中的序列号与重传缓存中的 TLP 的序列号进行对比。与 Ack DLLP 序列号相匹配的 TLP 将被从重传缓存中清除。反之,若完成方接收到的是一个 Nak DLLP,那么它将使用重传缓存中存储的 CplD TLP 副本来进行重传。

与此同时,发起方的事务层在某个虚拟通道的缓存中接收到了这个 CplD TLP。作为一个可选操作,事务层可以校验这个 TLP 的 ECRC。若校验无错,事务层将这个 CplD TLP 的 Header 内容、数据荷载以及请求完成状态信息向上呈交给发起方的软件层。


Transaction Layer
响应软件层的请求,传输层生成待发送的数据包,也会解析输入的数据包,并上传给软件层;
传输层使用 TLP 进行数据处理,每一个 TLP 属于下列帧类型之一:
Memory
I/O
Configuration
Message
 
Data Link Layer
数据链路层负责链路管理,并完成如下三个功能:

TLP 错误检测;
流控;
链路功耗管理;
 ¬¬¬

Paysical Layer
物理层是使用数据包的数据交换协议.
 
 

CH3
本章节将对如何对PCIe环境进行配置来展开介绍。内容包括了实现Function配置寄存器的空间、如何发现一个Function、如何产生并路由转发配置事务、 PCI兼容配置空间(PCI-compatible configuration space)与PCIe扩展配置空间的不同点(PCIe extended configuration space),以及软件是如何区分开EP和Bridge的。

每个PCIe功能(Function)的标识在其所在的设备内,以及这个设备所连接的总线内,都是唯一的。其标识符一般被称为“BDF”。对于任意一个 PCIe 拓扑结构,配置软件负责检测出拓扑中的每个Bus、Device和Function,缩写为BDF。
PCIe总线
第一个总线号,Bus 0,通常由硬件分配给RC(Root Complex)。Bus 0由一个集成有EP的虚拟PCI总线,一到多个虚拟PCI-to-PCI Bridges(P2P)组成。其中的P2P Bridges拥有不可更改、硬件编码(hard-coded)的设备号和功能号。每个P2P Bridge都会产生一个新的总线,其他PCIe设备可以连接在到这些新产生的总线上去。每个总线都必须被分配一个唯一的总线号。配置软件分配总线号的过程中,首先从Bus 0/Device 0/Function 0开始搜索其他的Bridges。当找到一个Bridge之后,软件就给这个Bridge产生的新总线分配一个与上一级总线的总线号不同的、数字更大的编号。一旦新总线被分配了一个总线号之后,软件就会从新总线继续搜索更新的Bridges,而不是在上一级总线上继续搜索。这被称为深度优先搜索

PCIe允许在单个PCI总线上最多挂载32个设备,然而PCIe点对点(point-to-point)的性质意味着只有一个设备可以直接连接在PCIe链路上,也就是Device 0。

Function被设计为每个设备中之内的一个逻辑层次。这些Function可能包含硬盘驱动接口、显示控制器、以太网控制器、USB控制器等等。多Function的设备不需要依次按照编号逐个实现 Function。例如,一个设备可以只实现Function 0、2、7。因此,当配置软件检测到了一个多Function设备时,必须检查所有可能的Function,以了解当前Device存在哪些Function。每个Function都有它们自己的配置地址空间

PCI为每个Function都定义了一个专用的配置地址空间块。映射在这个配置空间中的寄存器们使得软件可以发现这个Function的存在,并对这个Function进行一般操作和状态检查。大多数需要标准化的基本功能都存在于配置寄存器块的Header中,但是PCI架构师意识到若将可选功能(option feature,区别于前面的基本功能)也标准化可以带来很多好处,这些可选功能标准化后的结构被称为“能力结构”,(capability structures,例如电源管理、热插拔等等)。对于每个Function,都含有256byte的PCI兼容配置空间(PCI-Compatible configuration space)。
PCI兼容空间(PCI-Compatible Space)
之所以将这256Byte命名为PCI-compatible configuration space(PCI兼容配置空间),是因为这些配置空间原本就是为PCI所设计的。这个配置空间的前16DW(64bytes)是配置头部(header),有两种类型的Header,分别为Type 0和Type 1。Type 0 Header对于每个Function都是必须含有的,除了Bridge,对于Bridge Function来说它使用的是Type 1 Header。剩余的48DW是一些可选寄存器,包括PCI能力结构(capability structure)。对于PCIe Functions而言,一部分 capability structure也是必须的
当引入PCIe之后,最初始的256byte配置空间已经不足以放下所有新需要的Capability Structure了。因此配置空间的大小从原先的每个Function 256Byte扩展至了每个Function 4KByte。新增加出来的960DW扩展配置空间只能通过增强配置机制(Enhanced configuration mechanism)来进行访问,因为传统的PCI软件无法发现这个区域并进行访问,所以这部分区域对于 PCI 是不可见的。
 
Host-to-PCI Bridge配置寄存器
在内存地址空间中,它通常映射为设备特定寄存器,这对平台固件是已知的。然而,它的配置寄存器格式排布和用法都必须遵从PCI 2.3协议规范中所定义的Type 0模板
只有RC发送配置请求(Only the Root Sends Configuration Request)
在协议规范中声明了,只有RC可以发起配置请求。之所以限制 CPU 只能通过RC发起配置事务,是因为要是其他设备也有这种能力,那么他们可以任意改变配置内容,这样就会带来混乱。
由于只有RC能发起这些配置请求,所以这些配置请求只能在拓扑中向下转发,意味着Peer-to-Peer的配置请求是被无法实现的。请求包使用目的设备ID作为路由信息进行路由转发,这个目的设备ID就是它的BDF(拓扑中的Bus编号,Bus上的Device编号,Device中的Function编号)
生成配置事务(Generating Configuration Transactions)
处理器一般无法直接进行配置读写请求,因为他们只能产生memory请求和IO请求。这意味着RC需要将 CPU 的其他类型访问转换成配置请求,这样才能进行配置的操作过程。配置空间可以通过以下两种机制进行访问:
传统的PCI配置机制,使用IO间接访问(IO-indirect access)
增强型配置机制,使用内存映射访问(memory-mapped access)

使用间接地址映射(indirect address mapping)。需要使用一个寄存器保存目标地址,同时用第二个寄存器保存来自或是发往目标的数据。一次对目标Function的一次读写事务,需要先将待访问的地址写入地址寄存器,随后再读写数据寄存器。这很好的解决了地址空间有限的问题,但是这意味着产生一次配置访问需要两次IO访问。
PCI兼容机制使用RC的Host Bridge中的两个32bit的IO端口。它们分别是配置地址端口(Configuration Address Port),位于IO地址区域0CF8h-0CFBh,和配置数据端口(Configuration Data Port),位于IO地址区域0CFCh-0CFFh。
要访问一个Function的PCI兼容配置寄存器,首先要将目标的Bus、Device、Function和DW号写入配置地址端口,并将其使能bit置为有效。然后第二步,一个1或2或4Byte的IO读或写将会发送到配置数据端口。RC的Host Bridge将对给定的目标总线和在Bridge下现存的总线范围进行比较。若目标总线在这个范围内,这个Bridge则会发起配置读或写请求
配置地址端口(Configuration Address Port)
配置地址端口仅在处理器对其完成一个完整的32bit写操作时,锁存住写入的信息
     
Bits[1:0]固定为0不变,且只读的,在读取时只能返回0。它的位置是DW对齐的,不允许指定字节(byte-specific)偏移量。
Bits[7:2]用于标识目标Function的PCI兼容配置空间内的目标DW(target dword,其也被称作寄存器号),也就是用来标识Function内的寄存器号,要求这个寄存器必须位于兼容配置空间中。这种机制仅限于兼容 PCI 的配置空间中使用。(例如一个Function配置空间的前64DW)。
Bits[10:8]用于标识目标Device内的目标Function号(0-7)。
Bits[15:11]用于标识目标Device号(0-31)。
Bits[23:16]用于标识目标Bus号(0-255)。
Bits[30:24]为保留字段,必须为0。
Bit[31]为使能位,若要将随后的对配置数据端口的IO访问转换为配置访问则必须要将该bit置为1。当该bit为0时,若有IO读或者IO写被发送到配置数据端口,那么这些事务都会被当成普通的IO请求来处理。


总线比较和数据端口的使用(Bus Compare and Data Port Usage)
如图 3‑5,RC内的Host Bridge实现了一个次级总线号(Secondary Bus Number)寄存器和一个从属总线号(Subordinate Bus Number)寄存器。次级总线号(图 3-5 中的 Sec)指的是当前Bridge下直接连接的总线的编号,例如图中RC的Host/PCI Bridge产生了Bus 0,因此Host/PCI Bridge的次级总线号就是0。从属总线号(图 3-5 中的 Sub)指的是Bridge之下的可作为目标总线的总线号,例如图中Device 1的Sub=9,那么就说明在Device 1下游最大的总线号是 Bus 9,而它的Sec=5则说明其下游的总线号编号从 Bus 5 开始,简单来说就是Sec和Sub共同指定出了这个设备下可访问总线的范围(对于Device 1来说就是5-9)。
 
在一个单RC的系统中,Host-Bridge的次级总线号应该被固定为0,也就是它的可读可写的次级总线号寄存器从一复位就被强制置为0,或者说,Host-Bridge知道它访问到的第一个总线一定是Bus 0。若配置地址端口的bit 31被置为1,那么Bridge就会将目标总线号与Bridge之下的从属总线范围进行比较,以此来检查这个目标总线是否从属于当前Bridge之下。
当Bridge收到一个请求,它将会评估目标总线号是否在其下的从属总线号范围内,这个范围大于等于从次级总线号(Sec),小于等于从属总线号(Sub)。若目标总线号与当前的次级总线号相匹配,那么说明当前的次级总线就是目标总线,这个请求就会被作为一个Type 0配置请求来传输。当设备们收到一个Type 0请求,它们就知道当前一级本地总线上的某个设备就是这个请求的目标设备(而不是在更下一级的总线所属的设备)。
若目标总线号要大于Bridge的次级总线号(Sec),但是小于或者等于Bridge的从属总线号(Sub),那么这个请求将会作为Type 1配置请求被转发到Bridge的次级总线上。对于一个Type 1配置请求,可以这样理解它:尽管这个请求需要经过这条总线,但是它的目标设备并不在这一级总线上,相反地,这个请求将会由当前总线上的Bridge们向下转发到各自的下一级总线上,当然转发该Type 1配置请求的Bridge必须是从属总线范围包含了目标总线的。所有,Type 1配置请求只对Bridge设备有作用。

单Host系统(Single Host System)
RC中的Host/PCI Bridge会将写入配置地址端口(Configuration Address Port)的信息锁存起来,若bit 31被置为1且目标总线在当前Bridge下方从属总线范围内,那么Bridge将把接下来的处理器对配置数据端口(Configuration Data Port)的访问转换成针对Bus 0的配置请求。处理器将会向配置数据端口(0CFCh)发起一个IO读请求或者一个IO写请求。这促使Bridge生成一个配置请求,这个配置请求是读请求还是写请求取决于IO访问是读还是写。若目标总线为Bus 0,那么它将是一个Type 0配置请求。若目标总线是从属总线范围内的其他总线,那么它将是一个Type 1配置请求。若目标总线不在从属总线范围内,那么这个Bridge将不会对这个请求进行转发操作。


多Host系统(Multi-Host System)
PCI时期
若在一个系统中存在多个RC,那么配置地址端口和配置数据端口将被这两个RC各自的Host/PCI Bridge复用,且两种配置端口各自的IO地址在两个Host/PCI Bridge中相同,也就是两个RC使用相同的一个IO地址来访问各自配置地址寄存器,访问配置数据寄存器也是。为了防止争用,在两个Host/PCI Bridge中同时只能有一个响应处理器对配置端口的访问。
PCIe 多 Host 系统的实际做法
在 PCIe 时代,传统 I/O 配置端口逐渐被 MMCFG(ECAM) 取代:
每个 RC 有自己独立的内存映射配置空间基地址;
通过不同的内存地址区域访问不同 RC 下的设备;
不再依赖 I/O 端口 0xCF8/0xCFC,因此天然避免多 RC 争用问题。
增强型配置访问机制(Enhanced Configuration Access Mechanism)
当协议制定者在选择PCI-X和之后的PCIe该如何访问配置空间时,有两个考虑。第一个,每个Function的256Byte空间限制了那些想要在这个空间内放一些专有信息的厂商,而且未来的协议制定者可能也需要更多的空间来放置更多的能力结构(capability structure)。为了解决这个问题,这个空间从每个Function 256Bytes扩展至4Kbytes。第二个要考虑的是,PCI协议应用的时代多处理器系统还很少。对于旧的模型来说,当系统中仅有一个CPU且其只有一个线程时,生成一次访问需要两步并不会有什么问题。但是对于使用多核多线程CPU的计算机来说,使用IO间接访问模型就会产生一些问题,因为没有机制能够阻止多线程同一时间访问配置空间。
为了解决这个新问题,协议制定者决定采用一个不同于IO间接访问的方法。他们不再尝试去保留地址空间,而是通过将所有配置空间都映射到内存地址,以此来创造出一个单步(single-step)、不可中断(uninterruptable)的访问过程。由于一个针对特定地址范围的memory请求会在总线上产生一个配置请求,所以这种访问方式只需要一个命令序列即可。这种方式带来的开销考量(trade-off)在于地址大小。每个Function映射地址空间需要4KB,实现最大数量的 function 需要256MB的memory(内存)地址空间。
每个Function的4KB配置空间都以一个4KB对齐的地址作为起始地址,且这些4KB配置空间所映射的地址都要位于那段为了配置访问而预留的256MB内存地址空间内。并且现在的地址中的bit还携带了识别信息(identifying information),用于表示哪一个Function是访问目标
 
 
配置请求(Configuration Requests)
Bridge为了响应配置请求,将会产生两种类型的配置请求,分别为Type 0和Type 1。具体产生哪一种类型的配置请求,取决于目标总线号是否与当前Bridge的次级总线号(Secondary Bus Number)相匹配
Type 0配置请求(Type 0 Configuration Request)
如果目标总线号与次级总线号相匹配,Bridge将产生一个Type 0配置读/写,并转发到它的次级总线上,并:
总线上的设备们检查配置请求中的Device Number(设备号),看谁才是这个配置请求的目标设备。需要注意,外部链路(external Link)上的EP总是Device 0。
被选中的设备检查Function Number,看看设备内的哪个Function被选中。
被选中的Function使用Register Number字段域来选择它配置空间中的DW,这个DW就是目标DW,并使用First Dword Byte Enable字段域来选出这个DW中要被进行读写操作的字节们。
 
Type 1配置请求(Type 1 Configuration Request)
当Bridge得到的配置请求的目标总线号并不是自己的次级总线号,但是目标总线号又在从属总线(Subordinate Bus)的范围内,那么Bridge将向次级总线转发一个Type 1请求包。次级总线上的非Bridge的设备(EP)将会忽略Type 1请求,因为这个请求的目标总线并不是当前总线。但是次级总线上的Bridge看到Type 1请求则将会进行与上一级Bridge相同的比对操作,当目标总线在从属总线范围内时:

如果目标总线就是当前Bridge的次级总线,这个请求包将从Type 1被转换成Type 0,并转发到次级总线上。次级总线上的本地设备将会像此前描述的一样检查请求包的Header。
若目标总线号不是当前Bridge的次级总线,但是在从属总线范围内,请求包将会继续以Type 1请求的格式被转发到当前Bridge的次级总线上。
 
枚举——搜索发现拓扑(Enumeration-Discovering the Topology)

在完成了系统上电或是复位之后,配置软件需要扫描PCIe网络结构,来搜索发现整个机器的拓扑,并学习这个网络结构是如何被填充的(例如里面都有多少总线、多少设备以及它们的编号等等)。在这进行之前,如图 3‑10所示,软件唯一知道的就是拓扑中有一个Host/PCI Bridge以及这个Bridge的次级总线Bus 0。需要注意,一个Bridge自身上方相连的总线称为主总线(Primary Bus),而这个Bridge自身下方相连的总线称为次级总线(Secondary Bus)。扫描PCIe结构来发现整体拓扑结构的过程被称为枚举过程。
搜索某个Function是否存在(Discovering the Presence or Absence of a Function)
处理器上运行的配置软件发现一个Function的方式一般是读取这个Function的Vendor ID寄存器。可能会出现两个问题:目标设备可能不存在,或者它虽然存在但是没有准备好响应事务请求
对于PCIe来说,针对一个不存在的设备的配置读请求将使得目标设备上方连接的Bridge返回一个不携带数据的完成包,这个完成包的状态字段将被置为UR(Unsupported Request,不被支持的请求)。为了向后兼容传统的枚举模型,若RC在枚举过程中收到这样的完成包,它将会给处理器返回数据FFFFh。注意,枚举软件是依赖于接收到一个返回值为全1(FFFFh)的配置读请求来判定目标设备是不存在的,而系统中的在读取这样一个不存在的设备Function的Vendor ID时,其实是返回了一个状态为Unsupported Request的不携带数据的完成包。
当出现设备不存在的情况时,也需要避免意外地发送错误报告.为了能更简单的避免这种混乱,设备们一般在这过程中都不启用错误信号,直到枚举完成才启用。对于PCIe来说,记录这种事件(目标设备不存在)仍然是有作用的,这也就是为什么PCIe能力寄存器块(PCIe Capability register block)中存在第4个“错误”状态位,被称为Unsupported Request Status,不受支持的请求状态.由于有这个状态位的存在,发生目标设备不存在的情况就可以被记录下来,而且不会被当做是一个错误,这一点非常重要,因为若检测到一个错误那么枚举过程将被停止并调用系统错误处理程序。在枚举还没进行完的这个时间段,错误处理软件的能力可能十分有限,使得问题无法被解决。这将造成枚举软件运行失败,因为通常是在操作系统(OS)或者其他错误处理软件可用之前就已经执行枚举软件。为了避免这种风险,在枚举期间,通常不应该报告错误。
另一个问题就是目标设备虽然存在,但是可能并未准备好响应配置访问。对于配置操作来说,需要考虑发起配置的时间点,因为设备准备好被访问是需要时间的。如果数据速率小于等于5.0GT/s,软件必须在复位后等待100ms再发起配置请求。如果数据速率高于5.0GT/s(Gen3速率),软件必须在链路训练(Link Training)完成100ms之后再尝试发起配置操作。之所以更高的速率需要更多的延时(Gen3需要在链路训练完成后等待100ms而不是复位完成等待100ms),这是因为Gen3的链路训练中的均衡过程(Equalization Process)可能需要比较长的时间. 在PCI 2.3中定义了初始化时间(Initialization Time,Trhfa-从复位释放到第一个访问所经历的时间),初始化时间起始于RST#被置为无效,结束于225个PCI时钟周期之后。
在PCI中,若在Function准备好之前就收到了一个配置访问,那么它有三个选择:忽略这个请求、重试(Retry)这个请求、接收这个请求但是延期响应直到Function自身完全准备好为止。最后一种选择的响应可能会给热插拔(Hot-plug)系统带来麻烦,因为延期响应的时长可能会达到1s,在这1s中总线都是停止的,直到这个请求被执行完,总线才重新工作。
确认某个Function是EP还是Bridge
枚举过程的一个关键部分就是可以确定一个Function是Bridge还是EP。Header类型寄存器(Header Type Register,位于配置空间Header的偏移地址0Eh)的低7bit用于标识这个Function的基本种类,总共定义了三种不同的值:

0 = 不是一个Bridge(那也就是一个PCIe EP)
1 = PCI-to-PCI Bridge(缩写为P2P),用于连接两条总线
2 = 插件卡Bridge(CardBus Bridge,现在很少使用的历史遗留接口)

单RC枚举示例(Single Root Enumeration Example)
采用深度优先的搜索(depth-first search),向下寻找,直到遇到EP。在Header寄存器中的Header Type字段的值为0(0000000b),这表示它是一个Endpoint Function(EP)。由于这个Function是一个EP而不是一个Bridge,它有一个Type 0 Header,且在这个EP之下就没有别的PCI兼容总线了(PCI-Compatible Bus)。这个EP的bit 7的值为1(Multifunction bit=1),表示这个EP是一个多Function设备。
多RC枚举示例(Multi-Root Enumeration Example
他们的bus会分的很开,所以只有一个RC会接收请求,可以找到要访问的EP的function
 
Ch4
描述一个Function通过Base Address Registers(BARs,基地址寄存器)来请求地址空间(memory地址空间或IO地址空间)的目的和方法,以及软件需要怎样设置所有的Bridge内的Base/Limit寄存器来使得TLPs能从一个源端口被路由转发至正确的目的端口。
PCIe支持三个地址空间,与PCI中的三个地址空间完全相同:
配置空间(Configuration)
内存地址空间(Memory)
IO地址空间(IO)

配置空间(Configuration Space)
配置空间是由PCI引入的,软件通过配置空间就可以用一种标准化的方法来对设备的状态进行控制和检查。PCIe对PCI软件具有向后兼容性,所以PCIe中仍然支持配置空间,并且支持它的原因也和PCI一样,即用一种标准化的方法来对设备的状态进行控制和检查。
内存和IO地址空间(Memory and IO Address Spaces)
IO设备的内部寄存器/存储被映射到了memory地址空间(Memory Address Space,内存地址空间),一般被称作内存映射IO或者简称MMIO(Memory-Mapped IO)
基地址寄存器BARs(Base Address Registers)
基于PCI的设备不允许自己来决定哪些地址可以用来访问它们内部的位置,做这些决定是系统软件负责的工作(例如BIOS和操作系统内核)。因此设备必须为系统软件提供一个途径用来确定设备对地址空间的需求。一旦软件知道了设备对地址空间的需求是什么样的,并假设这个需求是可以被满足的,软件就会给对应的设备分配一段可用的地址范围和相应的地址空间类型(IO、NP-MMIO、P-MMIO)
 
系统软件必须首先确定设备所需地址空间的大小和类型。设备的设计者知道设备中需要通过IO或者MMIO访问的内部寄存器/存储的总体大小。设备的设计者还知道当这些寄存器被访问时设备将会如何工作(例如读取操作是否有副作用)。这将决定设备所需要的是可预取(prefetchable)MMIO(读取操作无副作用)还是不可预取(nonprefetchable)MMIO(读取操作有副作用)
BARs的高位bit是软件可进行写入的。一旦系统软件通过检查BARs的低位bit确定了设备所请求的地址空间的大小和类型,系统软件就会将分配给这个设备的地址范围的基地址写入BAR中。由于一个EP(使用Type 0 Header)拥有6个BARs,它最多可以请求6个不同的地址空间。

32bit内存地址空间请求
任何时候,当设备发现一个请求事务的地址是映射到自己的一个BAR时,它就会接收这个请求事务,因为它自己就是这个请求的目标设备。
写全1:软件向所有BAR的可写bit写入1,低位固化的bit不受影响,以此标记出哪些bit是可写的。
读回探测:从BAR0开始逐个读取,根据最低可写bit的位置算出设备请求的地址空间大小(如bit 12 → 4KB),同时低位固化bit还编码了地址空间类型(内存/IO、可预取/不可预取)。
分配地址:软件根据探测到的大小和类型,从系统地址池中划出一块区域,将起始地址写入BAR的高位bit,配置完成。
 
64bit内存地址空间请求
写1探测:软件向BAR 1和BAR 2的所有可写bit写入全1。BAR 1的低位bit由硬件固定(编码了空间大小和类型),不受影响;BAR 2全部可写,也被写成全1。
读取确认需求:软件按顺序读到BAR 1,发现它请求的是可预取内存(P-MMIO)且支持64bit地址。因此紧随其后的BAR 2自动充当高32bit,两个BAR组合成一个64bit地址请求。软件读取BAR 2但不分析其低位bit含义,因为它只是BAR 1的"上半截"。
写入实际地址:软件已确定需求是64MB可预取内存,于是将分配的64bit起始地址拆成两半分别写入BAR 1(低32bit)和BAR 2(高32bit)。本例中起始地址为2_4000_0000h,配置完成后设备响应2_4000_0000h–2_43FF_FFFFh范围内的访问。
 
IO地址空间请求
写全1 — 软件把BAR 3所有可写位写成1,固定低位不受影响。
回读判断 — 读回BAR 3,根据最低可写bit的位置确定设备要请求的空间大小(256 byte)和类型(IO)。
写基地址 — 软件分配起始地址4000h(16KB)写入BAR 3,然后打开命令寄存器中的IO译码使能,设备就开始响应4000h–40FFh范围内的IO事务。
 
即使软件发现了一个BAR并没有被使用,所有的BAR也都必须被评估。在PCI或者PCIe中,并没有规定说一定要以BAR 0来作为第一个用来请求地址空间的BAR。
Base寄存器和Limit寄存器(Base and Limit Registers)
在Bridge的Type 1 Header中有Base寄存器和Limit寄存器,而Bridge下方的地址范围将会被编程写入这两种寄存器。
Bridge必须通过软件把自己的Base和Limit寄存器配好,从而"记住"它下方所有设备占用的地址范围。当请求从Bridge上方进来时,Bridge拿目标地址跟自己的Base/Limit一比,落在范围内就往下方转发,不在就放行不管。也就是说,设备的BAR定义了"我认领哪些地址",而Bridge的Base/Limit定义了"我下方有哪些地址",请求能层层传递到目标设备,靠的正是这条路径上每一层Bridge都认得这个地址并逐级转发。
在每个Type 1 Header中可以看到有3套Base和Limit寄存器。之所以需要三套是因为一个Bridge下的地址范围可以被分成三类:n 可预取内存空间(P-MMIO)n 不可预取内存空间(NP-MMIO)n IO空间(IO)
P-MMIO可预取范围(Prefetchable Range)
Type 1 Header有两对可预取内存base/limit寄存器。被称为Prefetchable Memory Base/Limit的寄存器要储存可预取地址范围的低32bit地址信息。如果这个Bridge支持对64bit地址进行译码,那么另一个被称为Prefetchable Memory Base/Limit Upper 32bits寄存器就也需要被启用,要用它来储存地址范围的高32bit,也就是bit[63:32]。Base和Limit各拆成"低32位+高32位"两半,分别存进四个寄存器
NP-MMIO不可预取范围(Non-Prefetchable Range)
不可预取内存空间只支持32bit地址,这一点不同于可预取内存空间范围。因此对于不可预取内存空间来说,只有1个base寄存器和1个limit寄存器。
IO范围(IO Range)
类似于可预取内存范围,Type 1 Header中IO base/limit寄存器也有两对。被称为IO Base/Limit的寄存器用于存放IO地址范围的低16bit地址信息。
未被使用的Base和Limit寄存器
如果一个EP并没有请求IO地址空间,那么这个Function上方直接相连的Bridge将会把IO Limit寄存器写为00h,将IO Base寄存器写为F0h。由于Base地址大于Limit地址,Bridge会知道这是一个无效的设置,并以此来认定其下方没有Function拥有IO地址空间。
TLP路由基础(TLP Routing Basics)
设置建立BARs和Base/Limit寄存器的目的,是为了确保流量(traffic)可以被正确的路由到它的目的Function,之后目的Function就可以看见这些流量中的事务并声明对这些事务的所有权。
一个PCIe拓扑使用独立的、点对点的链路来将各个设备与它们的一个或多个邻设备(neighbors)相连接。当流量到达链路接口的入站端(inbound side,或称为ingress port入口端口),这个端口将会进行错误校验,然后做出如下的三个决定中的一个:

\1. 接收这个流量并在自身内部使用它。

\2. 将这个流量转发至相应的出站(outbound,或者egress出口)端口。

\3. 拒绝这个流量,因为入口端口自身既不是这个流量的预期的目标,也不是一个用来对它进行转发的接口。(注意,还存在其他可能的原因导致流量被拒绝

路由元件(Routing Elements)
对于像RC和Switch这样具有多个端口的设备来说,它们可以在端口之间转发TLPs,它们有时也被称作Routing Agent(路由媒介)或是Routing Element(路由元件)。它们会接受目标为自身内部资源的TLP,也会将TLP在自身入口端口和出口端口之间进行转发。

TLP路由的三种方法(Three Methods of TLP Routing)
TLP可以被基于地址路由(Memory或IO),可以被基于ID路由(Bus—Device—Function),或者还可以被隐式路由(routed implicitly)。
 
Message是唯一一种支持多种路由方法的TLP类型。PCIe协议中定义的大多数message使用的是隐式路由
在隐式路由中,不使用地址或者ID这种路由信息;相反,这个数据包是根据包头中的一个代码(code)来进行路由的,这个代码指示的目的地是拓扑结构中一个已知的位置
隐式路由是怎么提供帮助的?(How Implicit Routing Helps?)
要使用带内Message来替代边带信号,就需要一种方法来将这些Message路由到正确的收件方(recipient)那里去,而Message的收件方位于一个由数量众多的点到点链路组成的拓扑结构中。隐式路由利用了这样一个事实:Switch和其他的路由元件是可以理解上行(upstream)和下行(downstream)的概念的,且RC是位于整个拓扑结构的最顶端,而EP则是位于拓扑结构的底部。因此举例来说,一个Message可以使用一个简单的代码来表示它应该被送往RC,或者是要被送往下行的所有设备。上述的这种能力能够有效的减少对定义地址范围或是ID列表的需要,而原本是需要针对不同的Message来定义特定的地址范围或是ID列表才能完成路由
拆分事务协议(Split Transaction Protocol)
PCIe使用拆分事务协议(Split Transaction Protocol),它允许一个目标设备接收一个或多个请求,然后再使用单独的完成包来对这些请求进行响应。分事务协议不再使用通过试探目标是否处于ready才进行访问的高延迟传输机制,而是使用目标设备自己发起响应的方式,无论何时只要目标设备处于ready,它都能自己发起响应,而这个响应所对应的请求是此前就已经给到目标设备的了。这种方式使得每个事务的完成都至少需要两个单独的TLP——Request(请求包)和Completion(完成包)
Header中定义数据包格式和类型的字段
 
每个TLP都含有一个3个或4个DW(12或16byte)的Header。
TLP Header概述
当TLP在入口端口(ingress port)被接收到,首先要在物理层和数据链路层对TLP进行错误检查。如果检查无错,那么再由事务层来检查当前TLP使用的是哪种路由方法。基础的步骤为:

\1. Format和Type字段确定了TLP Header的大小、数据包的格式和类型。

\2. 根据TLP所使用的路由方法(与Type相关),设备将确定这个TLP的预期接收者是否就是自己。如果确实就是自己,那么设备将接受(或者说是消耗)这个TLP,否则设备将会把这个TLP转发到相应的出口端口(egress port)——根据该出口的排序和流控规则。

\3. 如果设备既不是TLP的预期接收方,且设备也并不位于前往预期接收方的路径上,那么它通常会通过一个状态字段为Unsupported Request(UR)的完成包来拒绝这个TLP
ID路由(ID Routing)
ID路由是用来指向一个逻辑位置——Bus Num,Device Num,Function Num,或者一般称为BDF
TLP Header中有关ID路由的关键字段
如果一个收到的TLP中的Type字段表示了这个TLP要使用ID路由,那么TLP Header中的ID字段(Bus,Device,Function)就要用来执行路由检查(routing check)。
EP进行一次检查(Endpoint:One Check)
对于ID路由,一个EP只会简单地根据自己的BDF来检查TLP Header中的ID字段。每当Function在自己的链路上接收到一个Type 0配置写时,Function将会从配置写事务的TLP Header的byte8-9中“捕获(Capture)”到自己的Bus号和Device号。Function必须将Bus号和Device号存储下来,而具体是存在什么地方,协议中并没有特别指定。保存下来的Bus和Device号被可以被用作Requester ID,例如当这个EP发起请求后,请求的Completer可以将这个Requester ID放进完成包内来让完成包被顺利路由返回,Requester ID就是这个完成包用来路由的信息。
Switches(Bridges)每个端口进行两次检查
对于一个使用ID路由的TLP来说,一个Switch端口首先要检查这个TLP的预期目的地是不是端口自身,这种检查是通过将端口自身的BDF与TLP Header中的目标ID进行比较来进行的
通过检查次级总线号寄存器(Secondary Bus Number Register)和从属总线号(Subordinate Bus Number Register)来查看TLP的目标总线号是否在Switch端口下方的从属总线范围内(包括范围边界)。如果在范围内,那么就将这个TLP向下转发。
Switch中的每一个端口(Port)都是一个Bridge,因此它们都有自己的配置空间,并且它们的配置空间Header必然是Type 1 Header。
地址路由(Address Routing)
使用地址路由的TLP引用的内存(系统内存和内存映射IO)和IO地址映射和PCI/PCI-X中事务引用的是一样的。如果一个Memory请求的目标地址小于4GB(即一个32bit地址),那么这个TLP必须使用3DW Header。而如果它的目标地址大于4GB(即一个64bit地址),那么这个TLP就必须使用4DW Header。IO请求的地址被限制为32bit
当TLP Header中的Type字段表示这个TLP使用地址路由时,那么就要用Header中的Address字段来执行路由检查
 
 
EP对地址的检查
如果一个EP收到了一个地址路由的TLP,那么EP将会用其自身配置Header中的每一个BAR(Base Address Register基地址寄存器)与TLP Header中的Address字段进行比对,由于EP只有一个链路接口,因此它只会接受或者拒绝数据包,而不会对其进行转发。如果TLP Header中的Address字段与EP的某一个BAR所指示的地址范围相匹配,那么EP就会接受这个TLP。
 
Switch进行路由(Switch Routing
先查自身 BAR:将 TLP 中的目标地址与端口自身 Type 1 Header 里的两个 BAR 地址范围比对。匹配则说明自己就是目的地,直接消耗掉这个包。
不匹配则查 Base/Limit 寄存器对:判断目标是否在本端口下方的从属总线上,分两种情况:
目标是 IO 空间 → 查 IO Base / IO Limit 寄存器
目标是 Memory 空间 → 查不可预取 Base/Limit 和可预取 Base/Limit 寄存器
如果都不匹配,说明目标既不是自己、也不在自己下方,TLP 会被当作 UR(Unsupported Request) 处理或向上行转发
Switch 端口(即 Bridge)对地址路由 TLP 的两个方向的处理逻辑,总结如下:
一、TLP 向下行(从主接口进,准备往下转发)
查 BAR:目标地址匹配自身 BAR → 自己是目的地,消耗掉
查 Base/Limit:目标地址落在下方管辖范围 → 向下转发到次级接口
都不匹配:没人认领 → 当作 UR 处理
二、TLP 向上行(从次级接口进,准备往上转发)
查 BAR:目标地址匹配自身 BAR → 自己是目的地,消耗掉
查 Base/Limit:目标地址竟然落在自己下方范围内 → 说明路由有问题,当作 UR 处理
例外:如果这个端口是上行端口,那可能是 Peer-to-Peer 事务(端到端通信),会转发到另一个下行端口而非原路返回
都不匹配:目标既不是自己、也不在自己下方 → 向上转发到主接口
一句话概括:不管哪个方向,都是先查 BAR 看是不是给自己的,再查 Base/Limit 判断目标在不在自己管辖范围内——向下行时在范围内就继续往下传,向上行时在范围内反而是异常,不在范围内就继续往上走
多播能力(Multicast Capabilities)
在PCIe 2.1版本中,新加入了通过指定一个地址范围来进行多播的能力。任何接收到的TLP,只要其地址落在被指定为多播范围的地址范围之内,它的路由或者接受(routed/accepted)就要根据多播规则来处理。
隐式路由(Implicit Routing)
隐式路由通常被一些Message数据包所使用,它是基于路由元件的感知能力,即路由元件知道拓扑结构具有上行和下行方向,并且拓扑的最顶部是RC。这使得一些简单的路由方法可以不需要去分配目标地址或是ID就能进行路由。由于RC一般都集成了电源管理(power management)、中断(interrupt)和错误处理逻辑(error handling logic),这使得RC基本上是大多数PCIe Message的发送者或者接收者。
在PCIe中被定义了Message的事件类型有:

电源管理(Power Management)
INTx传统中断信号(INTx legacy interrupt signaling)
错误信号(Error signaling)
锁定事务的支持(Locked Transaction support)
热插拔信号(Hot Plug signaling)
厂商特定信号(Vendor-specific signaling)
插入卡插槽功耗限制设置(Slot Power Limit settings)
Message的Type字段总结
表 4‑10展示了一个Message的TLP Header中Type字段的含义。如表中所示,最高的2bit用于指示这个数据包是一个Message,剩下的低3bit用于指示这个Message使用的路由方法。需要注意,Message TLP不管使用哪种路由操作,都只会使用4DW Header。若使用地址路由,那么就用byte8-15来组成64bit地址;若使用ID路由,那么就用byte8和9来组成目标BDF。
 
EP的处理
对于隐式路由,EP会简单的检查路由子字段(sub-field)是否适合它。
Switch的处理
像Switch这样的路由元件(Routing Element)需要考虑TLP到达了哪个端口,以及TLP的路由子字段是否对这个端口来说是合适的

DLLP和Ordered Set不会被路由
DLLP和Ordered Set的流量并不会被从Switch或者RC的入口端口路由到出口端口。这些数据包只会通过物理层到物理层的链路进行传输,从一个端口移动到另一个端口。

DLLP起源于PCIe端口的数据链路层,先经过物理层,接着离开端口并在链路上传输,然后到达对端端口。在对端端口上,DLLP先经过物理层,然后最终到达数据链路层,DLLP在这里被处理和消耗。DLLP不会继续沿着端口向上到达事务层,因此它并不存在路由。

相似的,Ordered-Set数据包起源于物理层,然后离开端口并通过链路传输,最终到达对端端口。在对端端口上,Ordered-Set在物理层就被处理和消耗。由于Ordered-Set也不会继续沿着端口向上到达数据链路层或者事务层,因此它也不存在路由
Ch5
在 PCIe 设备之间,信息是以包的形式进行传输的,包主要分为三类:TLP(Transaction Layer Packet,事务层包)、DLLP(Data Link Layer Packet,数据链路层包)和 Ordered Set(命令集,它应用于物理层)。讲述 TLP 的用法、格式和不同种类的定义,并将详细讲解 TLP的相关字段含义。
由COM开头组成的一系列字符,组成了有序集(Ordered Sets),用于链路管理等特殊功能。有序集又叫做物理层报文(PLP:Physical Layer Packet)
除了逻辑空闲符号(Logical Idle symbol)和 Ordered Set 的物理层包外,在活跃的 PCIe 链路上传输的信息的基本组块被称为 Packet(包),包是由符号组成的。链路上交换的两类主要的数据包为高层的 TLP(Transaction Layer Packet,事务层包),和低层的用于链路维护的包称为 DLLP(Data Link Layer Packet,数据链路层包)。物理层的 Ordered Set 也是一种包,但是它并不像 TLP 和 DLLP 一样会被封装上包起始符号和包结束符号,并且 Ordered Set 也并没有像 TLP 和 DLLP 一样的字节条带化过程,相反地,Ordered Set 会在链路的每个通道(lane)上都复制一份,而不是像字节条带化一样把信息按字节分配到各个通道上。
PCIe 链路通常由多个 lane(通道)组成,比如 x4 链路就有 4 条 lane。对于 TLP 和 DLLP 这两类包,物理层会做"字节条带化"——把包的内容按字节轮流分配到各条 lane 上,第一条 lane 拿第 1 个字节,第二条 lane 拿第 2 个字节,依此类推,就像发扑克牌一样把信息摊开到所有通道上。这样能充分利用带宽,提高吞吐量。
但 Ordered Set 不走这个路线。它是物理层自己用的特殊包(比如链路初始化、训练序列等),内容本身就与每条 lane 的物理状态息息相关。所以它不走条带化,而是在每条 lane 上都完整复制一份相同的内容。也就是说,不管是 x1、x4 还是 x16 链路,每条 lane 收到的 Ordered Set 都是完整且一模一样的。
在 PCIe Gen1 和 Gen2 操作模式中使用的是 8b/10b 编码,因此在这 Gen1 和 Gen2 中每个 TLP 和 DLLP 在发送前都会使用起始符号(Start)和结束符号(End)这两种控制符号来进行组帧,这样就可以给接收方清晰地定义出包的边界。对于 PCIe Gen3 使用的 128b/130b 编码来说,控制字符不再被使用,并且也没有组帧符号了。
PCIe 中使用带内(in-band)的 CRC 值来验证整个数据包是否进行了无错误的传输。同时 TLP 还会被发送方的数据链路层添加上一个序列号(Sequence Number),这使得当这个序列号的数据包传输出错时可以很简单的定位到它,并进行自动的重传。发送方会在自己的 Retry Buffer 内保存每个 TLP 的一个副本,直到接收方确认了这个 TLP 成功无错传输后才会将副本清除。这种 TLP 的确认机制被称为 Ack/Nak 协议
发送
设备 A 的 Device Core 向它的 PCIe 接口发送一个请求
这个请求中包括:
目标地址或者 ID(也就是路由信息)
源端信息,例如 Requester ID 和 Tag
事务类型/数据包类型(需要执行的命令,例如一个内存读取 MRd)
数据荷载大小 payload size 和数据荷载内容(如果 TLP 需要带数据)
流量类型(Traffic Class,用于分配数据包的优先级)
请求的自身属性(No Snoop 无窥探、Relaxed Ordering 宽松排序,等等)

基于这个请求,事务层将会组建 TLP Header,并在其后附上数据荷载(如果有),以及如果启用并支持可选项的话也可以再附上 ECRC(End-to-End CRC)。随后 TLP 就会被放入一个虚拟通道 Buffer。这个虚拟通道会根据事务排序规则来管理 TLP 的顺序,并在向下转发 TLP 到数据链路层之前,确认接收方有足够的 Buffer 来接收一个 TLP。
当 TLP 到达数据链路层,它会被分配一个序列号(Sequence Number),并基于 TLP 的内容和序列号来计算出一个 LCRC(Link CRC)来附加在原 TLP 后。然后会将经过这些处理过程之后的 TLP 保存一个副本,这个副本会保存在数据链路层的重传 Buffer(Replay Buffer,也可称为 Retry Buffer)中,这是为了应对传输出错的情况。与此同时,这个 TLP 也会被向下转发至物理层。
物理层将会进行一系列的操作来准备对这个数据包进行串行传输,包括字节条带化(Byte Striping)、扰码(Scrambling)、编码(Encoding)以及并串转换(Serializing)。对于 Gen1 和 Gen2 的设备,当进行 8b/10b 编码时,会将 STP 和 END 这两个控制字符分别加在 TLP 的首端和尾端。最后,这个数据包通过链路进行传输。在 Gen3 操作模式中,STP 令牌(STP token)会被添加在 TLP 的首端,但是并不会在尾端加上 END,而是在 STP 令牌中包含 TLP 大小的信息来判断 TLP 的尾部位置。
接收方
在接收方,为了发送包所做的一切准备现在都必须撤销。物理层将对比特流进行串并转换(deserialize)、对串并转换后的符号进行解码,然后再进行字节反条带化(un-stripes)。控制字符将被移除,因为它们仅在物理层有意义,然后这个数据包就会被向上转发至数据链路层。
数据链路层将会计算 CRC(具体一点是 LCRC)并与 TLP 中的 CRC 进行比较。如果 CRC 比较结果相同,那么就再检查序列号(Sequence Number)。如果都没有出现错误,那就把 CRC 和序列号都从 TLP 剥除,并随后将 TLP 向上转发给接收方事务层,与此同时要通过返回给发送方一个 Ack DLLP 来通知发送方这个 TLP 被成功接收。相反地,如果前面的过程中检查出了错误,那么就要返回给发送方一个 Nak DLLP,这样发送方将会使用它的重传 Buffer 来对 TLP 进行重传。
在事务层,TLP 被进行解码,并将 TLP 内的信息传递给 Device Core 来进行相应的操作。如果当前接收设备就是数据包的最终目的地,那么它可以检查 ECRC 错误,并在发现任何 ECRC 错误时报告给 Device Core。
 
通用 TLP Header 格式
 
  
   
   


ECRC 的预定目标是 TLP 的最终接收者。对 LCRC(Link CRC)的校验是用来验证在当前给定链路上的传输没有出错,但是会在 Switch/Bridge 的出口端口(egress port)重新计算一个 LCRC,然后再转发给下一级链路,也就是说同一个 LCRC 的作用范围不会跨 Switch/Bridge,那么这将会使得 Switch/Bridge 这些路由元件内部发生的错误被掩盖。为了解决这个问题,就在 TLP 在 Requester 到 Completer 的整个传输过程中都加上一个不会改变的 ECRC(也就是这个过程中 ECRC 都不会被其他设备重新计算和替换)。当目标设备检查 ECRC 时,任何在整个传输过程中发生的错误都会极容易被发现。
字节使能规则
字节使能地各个 bit 是高有效的。如果某个字节使能 bit 值为 0,那么就表示数据荷载中相应的字节不应该被 Completer 使用,因为这个字节是无效的。若某个字节使能 bit 值为 1,那么就说明相应的字节应该被 Completer 使用
请求 TLP 和完成 TLP
PCIe 的 Memory 事务包含两种类型:第一种是读请求及其相对应的完成包,第二种是写请求。系统内存映射如所示,展示了 3DW 与 4DW 的 Memory 请求包。需要记住协议规范中多次重申的一点,Memory 传输绝对不允许跨 4KB 地址边界
Configuration 请求(Configuration Request)
PCIe 像 PCI 一样使用 Type 0 和 Type 1 配置请求,以此来保持向后兼容性。一个 Type 1 配置请求会向下行传播,直到一个 Bridge 的次级总线(Secondary Bus)正好是这个 Type 1 配置请求的目标总线,在这时这个 Bridge 就会把这个配置事务从 Type 1 转换为 Type 0。Bridge 是通过此前编程写入的总线号寄存器来得知何时应该对配置事务进行 Type 转换以及继续转发,其中总线号寄存器包括主总线号(Primary Bus Number)、次级总线号(Secondary Bus Number)以及从属总线号(Subordinate Bus Number)
Configuration Request 注意事项
配置请求的 Header 总为 3DW 格式,路由信息为 Bus Number、Device Number 和 Function Number。所有的设备都会在接收到一个 Type 0 配置写请求时从请求包中捕获出它们自己的 Bus Number 和 Device Number。
消息请求(Message Requests)
Message 请求取代了 PCI/PCI-X 中使用的许多中断(interrupt)、错误(error)和电源管理(power management)的边带信号。所有的 Message 请求使用的都是 4DW Header 格式,但是并不是每种 Message 都使用了 Header 中的每一个字段。
 3DW与4DW Memory请求包介绍
PCIe Memory请求包用于对内存地址空间进行读写操作。TLP头部可以是3DW(12字节)或4DW(16字节),分别对应32位和64位地址寻址。头部中的Fmt字段决定了头部长度以及是否携带数据

Ch6
流量控制是用来确保在接收者无法接收 TLP 时,发送方不会再继续发送 TLP。这避免了接收 Buffer 溢出,也消除了原本 PCI 工作方式中的一些低效行为,比如断开(disconnect)、重试(retry)和等待态(wait-state)。
流量控制机制可以改善当多个虚拟通道(Virtual Channel,VC)被占用时的传输效率。每个虚拟通道自己的事务是与其他 VC 的事务流量相独立的,因为流量控制 Buffer 是 VC 各自单独维护的。
流量控制机制使用一种基于信用(Credit-based)的机制,使得发送端口可以知道接收端口有多少可用的 Buffer 空间。作为它们初始化的一部分,每个接收者都要将自己的 Buffer 大小报告给链路对端的发送者,并在运行过程中使用 DLLP 来定期地更新
 
在流量控制的的工作过程中:
设备报告可用的 Buffer 空间:接收者的每个端口都要报告自己的流量控制 Buffer 的大小,单位为 credit(信用)。一个 Buffer 中 credit 的数量会从自身接收侧事务层发送至发送侧数据链路层。在合适的时间,数据链路层将会产生一个流量控制 DLLP 来将这个 credit 信息转发至每个流量控制 Buffer 的链路对端的接收者。
接收者寄存 Credit:接收者收到流量控制 DLLP,并将 credit 值传输至自身发送侧事务层。这就完成了 credit 从链路的一端到对端的传输。这些操作是双向进行的,直到所有的流量控制信息都完成了交换。
发送者检查 Credit:在发送者可以发送一个 TLP 之前,它需要检查流量控制计数器(Flow Control Counter),以此来知道对方是否有足够的 credit。如果 credit 数量足够,那么就将 TLP 转发至数据链路层,但是如果 credit 不足,那么 TLP 的发送操作就会被阻塞直至获知的更多的足够的流量控制 credit。
流量控制 Buffer 和 Credit(Flow Control Buffer and Credits)
一个端口所支持的每个 VC(Virtual Channel)资源都会实现流量控制 Buffer。回想一下,链路两端的端口可能支持的 VC 数量是不同的,因此由软件进行配置和启用的 VC 的最大数量就是两个端口间的最大公共 VC 数量
VC 流量控制 Buffer 的组成(VC Flow Control Buffer Organization)
接收者的每个 VC 流控 buffer 是按照通过 VC 的每种事务类别来进行管理的。这些类别为:
Posted 事务——MWr(Memory Write)和 Message
Non-Posted 事务——MRd(Memory Read)、配置读/写、IO 读/写
完成包——读和写完成包
对于有些事务,例如读请求,它只由 Header 组成,而像写请求就既有 Header 也有数据荷载。发送者必须确认 Header 的 Buffer 空间和数据荷载的 Buffer 空间都满足了事务传输的需求,才能发送事务。
流控 Credit(Flow Control Credits)
接收者需要报告 Buffer 空间的大小,使用的单位是 Credit,或者叫流量控制 Credit。Header 和数据荷载 Buffer 的一个流控 Credit(FCC,Flow Control Credit)的大小值分别为:
Header Credit:Header 大小的最大值+digest
— 完成包为 4DW
— 请求包为 5DW
数据 Credit:4DW(16byte 对齐)
这是 PCIe 流控中 接收端为 Header Credit 分配 buffer 大小 的描述:每个请求包的 Header+ECRC 最多占 5 DW,每个完成包的 Header+ECRC 最多占 4 DW,接收端据此预留对应空间,再折算成可用的 Credit 数量通告给发送方。
流量控制 DLLP 会传达这个信息,而 DLLP 自身的传输是不需要流控 Credit 的。这是因为 DLLP 产生和终结的地方都是在数据链路层,并不需要使用到事务层 Buffer。
在发送任何事务之前,都需要进行流控初始化(flow control initialization)。事实上,在流控初始化成功完成之前,TLP 是不能在链路上进行发送的。流控初始化会发生于系统中的每一个链路,其过程主要为链路两端设备之间的一次握手。这一过程会在物理层的链路训练(link training)完成后就开始进行。链路层观察 LinkUp 信号是否有效,获知物理层何时处于 Ready 状态
流控初始化流程(The FC Initialization Sequence)
流控初始化过程涉及到链路层的 DLCMSM(Data Link Control and Management State Machine,数据链路控制与管理状态机)。复位后状态机将处于 DL_Inactive 状态。在 DL_Inactive 状态中,会发出 DL_Down 信号,这个信号会作用于链路层和事务层。与此同时,也会在此状态中等待物理层的 LinkUp 信号,此信号是用于表示 LTSSM(Link Training and Status State Machine,链路训练和状态状态机)已经完成了工作,也就是物理层已经处于 Ready 状态。当物理层 Ready 后,将会使得 DLCMSM 跳转至 DL_Init 状态,它包含两级子状态用于处理流控初始化:FC_INIT1 和 FC_INIT2。
 
PCIe 流控初始化总结
一、整体流程
DL_Inactive → DL_Init (FC_INIT1 → FC_INIT2) → DL_Active
复位后,DLCMSM 状态机从 DL_Inactive 出发,等待物理层 LTSSM 完成(LinkUp 信号有效),随后进入 DL_Init 状态,依次执行两个子阶段,最终跳转到 DL_Active,链路层初始化完成。
二、FC_INIT1 —— "通告阶段"
要点    说明
做什么    设备连续发送 3 种 InitFC1 DLLP,通告自身接收 Buffer 的大小(Credit 数量)
发送顺序    Posted → Non-Posted → Completion,固定顺序,不可打乱
退出条件    ① 已发送自身 Buffer 值;② 收到对端足够次数的完整 DLLP 序列,确信值正确
退出动作    将对端 Buffer 值写入传输计数器(transmit counter),置起 FL1 flag,跳转 FC_INIT2
简单说:双方互相告知"我的接收 Buffer 有多大"。
三、FC_INIT2 —— "确认阶段"
要点    说明
做什么    连续发送 InitFC2 DLLP,内容和顺序与 FC_INIT1 相同,但作用是确认初始化成功
关键区别    此时不再需要 Credit 信息(已在 INIT1 中寄存),收到 InitFC1 DLLP 会忽略
可发 TLP    设备在等待 InitFC2 期间已经可以发送 TLP,并通过 DL_Up 信号 告知事务层初始化状态
退出条件    收到任意 Buffer 种类的 InitFC2 DLLP(或 UpdateFC / TLP),即置起 FL2 flag
最终跳转    两端都发送完 InitFC2 后 → DL_Active,初始化结束
为什么要两阶段?因为链路两端设备完成速度可能不同,两步机制确保快的一端完成后,慢的一端仍能持续收到流控信息。
流控机制的介绍(Introduction to Flow Control Mechanism)
协议规范定义了流量控制机制所要求的寄存器、计数器,以及一系列的机制用于报告(reporting)、追踪(tracking)和计算(calculating)一个事务是否可以被发出。

发送方流控元素(Transmitter Elements)
事务等待缓冲区(Transactions Pending Buffer):把将要发送进统一虚拟通道的事务暂存起来。
Credit 消耗计数器(Credits Consumed counter):该计数器是记录发送给对端当前类型 Buffer 的所有事务的 Credit 总和(也就是消耗了接收端多少 Credit),这个计数值也可以被缩写成“CC”。
Credit 额度计数器(Credit Limit count):该计数器的初始值由对端接收者相应类型的 Buffer 大小而决定。在初始化之后,对端接收者会定期的发送流控更新包(FC Update),以便在接收方 Buffer 可用时更新流控 Credit。这个值可以被缩写为“CL”。
流控门控逻辑(Flow Control Gating Logic):它用于进行计算来确定对端接受者是否有足够的流控 Credit 来接收等待发送中的 TLP(Pending TLP,PTLP)。本质上来说,这个逻辑就是检查 CREDITS_CONSUMED(CC)加上下一个 PTLP 所需要消耗的 Credit 是否大于 CREDIT_LIMIT(CL)。协议规范中定义方程,以此来完成上述的检查,方程中所有的值的单位都是 Credit

接收方流控元素(Receiver Elements)
流控缓冲区(Flow Control Buffer):储存输入的 Header 或者数据。
Credit 分配计数(Credit Allocated):记录已分配(可用)的全部的流控 Credit 数量。它是由硬件进行初始化的,反映了已分配的流控 Buffer 的大小。这些 Buffer 会在事务到达时被填充,但是填充进去的事务最终都会被接收端取出,也就是从 Buffer 中移除。当它们被移除时,这部分的 Buffer 空间 Credit 数量将会被加到 CREDIT_ALLOCATED 计数值上。因此这个计数器会记录并表征当前可用的 Credit 数量。
已接收 Credit 计数器(Credit Received counter,optional):这是一个可选项,它用于记录进入流控 Buffer 的所有 TLP 的总 Credit 数量。当流控功能正常时,CREDIT_RECEIVED 计数值应该小于等于 CREDIT_ALLOCTED 计数值。如果并未满足这二者的“小于等于”条件,那么就会发生 buffer 溢出并检测到出错。协议规范推荐设计者实现这一可选的机制,并且需要注意的是这里若出现违例则应该将其视作是一个 Fatal Error,即非常严重的错误。
PCIe 流控示例总结
整个示例用 Non-Posted Header 流控 Buffer(2KB) 作为载体,分四个阶段演示了流控机制的工作过程。
核心前提
Buffer 大小 = 2KB = 2048 字节
Header 类型下,1 Credit = 5 dword = 20 字节
因此可用 Credit 总数 = 2048 / 20 = 102 个(66h)
发送方每次发一个 TLP 只消耗 1 个 Header Credit
阶段一:初始化后的第一次发送
初始化完成后,各计数器状态:
计数器    含义    初始值
CC(Credits Consumed)    发送方已消耗的 Credit    00h
CL(Credit Limit)    接收端分配的可用额度    66h
发送前需要做 Credit 检查,公式本质上是判断:
CL − (CC + 下一个TLP所需Credit) ≥ 0 ?
文中用 2 的补码无符号减法 来实现这个判断,并且规定结果 ≤ 80h(128) 才算通过。用计数器最大值的一半作为阈值,是为了防止翻转时的歧义。
CR = CC + 1 = 01h
CL − CR = 66h − 01h = 65h ≤ 80h ✅ → 可以发送
发送后 CC 从 00h 变为 01h。
阶段二:Buffer 被填满
接收方忙不过来,Buffer 被塞满,CC 持续增长到等于 CL:
CL = 66h
CR = CC + 1 = 67h(因为已消耗 102 个,再发一个需要 103)
CL − CR = 66h − 67h = FFh
FFh > 80h ❌ → 不能发送,通道被阻塞。
直到接收方腾出空间,发来 FC Update DLLP 把 CL 更新为 67h 或更大值,检查才能重新通过。
💡 关键设计:FC Update 报告的是 CA(Credits Allocated)的绝对值而非增量。这样即使某个 DLLP 丢了,下一个 DLLP 仍能完成同步,不会造成永久的信息丢失。
阶段三:计数器翻转(Roll Over)
CL 和 CR 只增不减,最终会从 FFh 溢回到 00h。问题在于:如果 CL 已经翻转为小值,而 CR 还停在翻转前的大值,就会出现"小 CL 减大 CR"的情况。
但得益于 2 的补码无符号运算,这种跨越翻转边界的减法依然能得到正确结果。同时,阶段一里那个"结果 ≤ 80h"的半阈值判断,也正是为了处理这种翻转场景而设计的——只要实际差值不超过 128,无论计数器是否翻转,结果都正确。
阶段四:Buffer 溢出错误检查(可选)
接收端额外维护两个计数器来交叉验证:
计数器    含义
CR(Credits Received)    实际收到的 Credit 数
CA(Credits Allocated)    可分配的 Credit 总额

CREDITS_ALLOCATED(可缩写为 CA)是 PCIe 接收方流控机制中的一个计数器,记录的是当前流控 Buffer 中可用的 Credit 总量。
发送方用的是 CL - CC:
CC(Credits Consumed)记录"我已经发了多少 Credit 出去"
CL(Credit Limit)记录"对端告诉我还能用多少"
CL - CC = 当前真正还能发多少
机制
接收方从 Buffer 中取走事务后,CA 值增加,然后通过 FC Update DLLP 把 CA 的当前绝对值发给发送方,写入 CL 寄存器。发送方拿到新 CL 后就能继续发包。
频率
根据 Max Payload Size 和链路宽度算出更精确的更新间隔,公式本质是:链路越宽、Payload 越大,更新频率可以越低
约束条件
流控更新仅在链路 active 状态(L0/L0s)可用,其他低功耗状态不行
实际发送频率可以比规范要求更频繁,但不能更慢
错误检测计时器(可选但强烈建议)
为每种 Credit 类型设独立计时器,超时界限 200μs(规范要求的正常间隔上限是 120μs,留了缓冲)。
收到对应类型的 FC DLLP 就复位计时器
无限 Credit 类型不计超时
一旦超时,说明链路有严重问题,强制物理层进入 Recovery 状态重新训练链路

CH9
 
Data Link Layer Packet(DLLP,数据链路层包)DLLP可以被用来支持 Ack/Nak 协议、电源管理、流控机制,还可用来实现一些厂商自定义的用途。
数据链路层可以被认为是更低逻辑层次上的链路协议管理者。数据链路层的主要职责是确保 TLP 在设备之间的完整传输,并且也参与到 TLP 流量控制、链路初始化、功耗管理以及为其上层的事务层和下层的物理层之间传递信息等功能。数据链路层通过与其他设备之间交换 DLLP(Data Link Layer Packet),数据链路层包,来完成上述功能。DLLP 会在各个设备的数据链路层之间传输通信
DLLP 是本地流量(DLLPs Are Local Traffic)
DLLP 的数据包格式非常简单,算上组帧字节,整个 DLLP 的长度固定为 8 字节。与TLP 不同,DLLP不携带任何目的地址或者路由信息,因为他们只用于本地相邻设备之间的直接通信,无需进行路由。同时,DLLP 对于上层的事务层也是不可见的,因为他们不会参与设备事务层间的信息交换

DLLP 接收与处理(Receiver handling of DLLPs)
当接收方收到 DLLP 之后,会根据以下规则进行处理:
DLLP 会在收到后被立即处理,无需受到 TLP 的流量控制机制的管理
DLLP 会受到物理层和数据链路层的两次错误检测。链路层会重新计算包的 CRC 校验结果,并将其和包自身携带的 16 比特 CRC 结果进行比较。校验失败的 DLLP 会被丢弃。那么问题来了:链路如何从 DLLP 校验错误中恢复?答案是后续的 DLLP 仍然会周期性地到达,接下来接收的同类 DLLP 会更新缺失的信息。
DLLP 与 TLP 不同,没有接收确认 Ack 机制,取而代之的是协议定义的超时机制。超时机制负责从 DLLP 接收错误中恢复。
校验通过的 DLLP 将根据其类型,传输给相应的内部逻辑,完成如下功能
通知 TLP Ack/Nak 状态的更新
通知流量控制机制中的可用缓存大小信息的更新
电源管理设置
厂商自定义信息管理

 
Ack/Nak DLLP Format
用于通知对端设备,确认接收(Ack)或者接收错误(Nak)的 DLLP
Flow Control DLLP Format
和其他许多串行传输协议类似, PCIe 通过基于 credit 的流量控制机制提高了传输的效率。DLLP 在流量控制机制中用于通信双方 credit 信息的交换。有多种不同的 DLLP 包用于流量控制机制的 credit 信息初始化,以及另一类用于传输过程中 credit 更新的 DLLP,当接收方缓存恢复时,将用这类 DLLP 通知对端 credit 信息的更新。共有两种流控初始化 DLLP,分别为 InitFC1 和 InitFC2,以及一种流控更新 DLLP,称为 UpdateFC。


CH10


CH14
物理层链路训练与状态状态机(LTSSM, Link Traning and Status State Machine)的行为,包括从上电或者复位开始,到链路进入正常工作状态(L0),报文流量开始的整个初始化过程。此外,本章还将讨论链路电源状态 L0s,L1,L2,L3 以及这些状态之间的转换。本章也会讨论链路恢复状态(Recovery)中,重新完成位锁定(Bit Lock)、符号锁定(Symbol Lock)或者块锁定(Block Lock)的过程。最后,还讨论了用于链路带宽管理的链路速率以及位宽转换。
链路初始化和训练是由物理层控制的,基于硬件 (而非软件) 的过程。该过程配置和初始化设备的链路和端口,使链路上能够进行正常的数据报文通信。
 
完整的训练过程在复位后由硬件自动启动,并由 LTSSM 管理

在链路初始化和训练过程中包括了几项步骤。让我们思考下它们都有哪些作用,并提前引入一些术语。
位锁定,Bit Lock:当链路训练开始时,接收端的时钟尚未与接收信号的发送时钟同步,无法可靠地采样输入信号的数据比特。在链路训练期间,接收端的时钟和数据恢复(CDR,Clock and Data Recovery)逻辑通过使用数据比特流作为时钟的参考信号,来重建发送端的时钟。一旦从数据流中恢复了时钟,就可以说接收端已经完成了位锁定,然后能够正确地采样输入数据比特。
CDR(Clock and Data Recovery,时钟数据恢复)电路的核心思路是:从数据信号本身提取时钟信息。
原理是,数字信号在 0→1 或 1→0 跳变时会产生边沿,这些边沿携带着发送端时钟的频率和相位信息。CDR 电路内部的锁相环(PLL)会持续监测这些跳变边沿,把自己的本地振荡器频率和相位锁定到与发送端一致。
一旦 PLL 锁定成功,接收端就拥有了一个与发送端同步的恢复时钟,用这个时钟去采样数据,就能在每个比特的中心位置精准地读出正确的值。这就是"位锁定"——比特级别的时钟同步完成了

符号锁定,Symbol Lock:对于8b/10b编码 (Gen1/Gen2) 而言,训练的下一步是进行符号锁定。同位锁定类似,虽然接收端目前可以识别单个比特,但是不知道由 10 个比特组成的符号的边界在哪里。当发送端和接收端交换训练有序集 TS1 和 TS2 时,接收端在比特流中搜索可识别的数据图样(pattern)。通过查找 COM 符号的位置可以较为简单地实现这一点。COM 符号独特的编码使得它很容易识别,识别 COM 字符后,接收方不但定位到了两个符号之间的边界,而且也能定位到两个有序集之间的边界

在 PCIe 链路训练中,位锁定(Bit Lock)解决了"能不能识别每一个比特"的问题,但还差一步——接收端不知道哪 10 个比特构成一个完整符号。8b/10b 编码下每个符号是 10 比特,接收端拿到一连串比特后,如果不知道从哪里切开,就无法还原出原始的字节。符号锁定就是来解决这个"在哪里下刀"的问题的。
具体做法是利用训练有序集 TS1/TS2。这两个有序集的起始符号是一个叫 COM(K28.5)的特殊字符,它的 10 比特编码模式(1010000011)在正常数据中极不可能出现,因此非常容易被识别。接收端只要在比特流中搜索到这个独特的图样,就知道找到了一个符号的起始边界。既然知道了符号从哪里开始,10 个比特一组往后切,整个符号边界就确定了。
块锁定,Block Lock:对于8.0 GT/s(Gen3) 所使用的块锁定,处理过程与符号锁定略有不同,因为没有使用8b/10b 编码方案,所以也就不存在 COM 字符。然而,接收端仍然需要在接收比特流中找到可识别的报文边界。解决方案是在训练有序集中包含更多的电气空闲退出有序集( EIEOS,Electrical Idle Exit Ordered Set),用于定位边界。可以通过查找 00h 和 FFh 字节交替的数据图样来找到 EIEOS ,并且 EIEOS 的位置定义了块之间的边界,因为根据协议,当该有序集结束后,下一个块必须紧随其后开始。
数据图样(pattern)其实就是一段具有特定比特排列的序列,接收端可以用它作为"模板"去匹配比特流,从而定位关键边界。
链路宽度,Link Width:具有多个通道的 PCIe 设备可以使用不同的链路宽度。例如,具有 x2 端口的设备可以与 x4 端口的设备连接。在链路训练期间,两台设备的物理层会测试链路,并将链路宽度设置为两者能接受的最大值。
「通道翻转」(Lane Reversal)是PCIe总线规范中一项可选的功能,它允许在逻辑上颠倒设备端口多个通道的编号顺序,以适配物理连接时颠倒的通道布线。
极性反转是PCIe高速总线设计中,允许差分对信号线D+和D-互换连接的特性,目的是简化PCB电路板的布线设计。任何接收端都需要单独地检查差分信号的连接情况,并在极性翻转的情况下,在训练期间自动纠正,
链路数据速率,Link Data Rate: 在复位后,链路初始化与训练状态机总是会先将数据速率设置为默认的 2.5Gbit/s,以实现对第一代协议的后向兼容(第一代协议速率为 2.5Gbit/s)。如果链路宣称可以实现更高的速率,那么在训练时双方会通告自己支持的最高速率,完成训练后,LSTTM 会自动重新进行一次过程更短的训练,将链路速率改变为双方都支持的最高速率
多 lane 间信号去偏移,Lane‐to‐Lane De‐skew: 各个通道之间的传输线长度差异或者其他因素,会导致多通道链路中原本同时传输的多个并行比特,到达接收方的时间有所差异,这被称之为信号偏斜(signal skew)现象。接收方需要通过延迟较早到达的通道,以对齐所有通道上的信号的到达时间,补偿通道之间信号传输快慢差异。在协议规定的可允许的信号偏移范围内,(对于 2.5Gbps 速率来说,最大信号偏斜是 20ns),接收方需要能够自动对偏移进行补偿
链路训练中的有序集
 
S1 以及 TS2 有序集
从图中 14-4 中可以看到,TS1/2 由 16 个符号(译注:字节)组成。在 LTSSM 的轮询、配置以及恢复状态中,通信双方会交换 TS1/2 有序集。
有序集字段
Symbol 0:
Gen1/2,所有有序集的首个符号都是 K28.5(COM) 字符。接收方通过 COM 字符实现符号锁定(译注:即接收方通过正确接收的 COM,确认符号的边界)。因为 COM 字符需要同时出现在所有通道上,因此可以用于多通道间信号去偏斜。
Gen3,有序集所在的 Block 之前是 2 比特同步头,同步头之后的首个符号用于表示有序集的类型。TS1 的首个符号是 1Eh, TS2 首个符号是 2Dh。
Symbol 1(Link #):链路编号,在轮询状态中使用填充字符填充,其他状态中为链路被分配的编号
Symbol 2(Lane #):通道编号,在轮询状态中使用填充字符填充,其他状态中为通道被分配的编号
Symbol 3 (N_FTS): 表示当前链路速率下,接收方从 L0s 电源状态退出返回 L0 状态所需要接收的快速训练序列(FTS)数量。在退出 L0s 状态时,发送方至少会发送 N_FTS 个 FTS。这一过程所需的时间取决于所需 FTS 的数量以及当前的链路速率。
Symbol 4 (Rate ID): 设备报告其所支持的数据速率,以及一些提供给由硬件发起的带宽改变功能的信息。所有设备必须支持 2.5GT/s 速率,并且链路在复位后始终会自动被训练为 2.5GT/s 速率,所以任何新的设备都需要后向兼容这个速率。如果设备支持 8GT/s 速率,那么它也需要支持 5GT/s 速率。此外,这个符号中还包括的其他信息有:
Autonomous Change:当该比特置为 1 时,任何带宽改变的请求都是基于电源管理方面的原因而发起的。如果在发出带宽改变请求时,该比特未置为 1,那么代表设备在较高的速率或者较宽的链路上检测到工作不稳定的情况,需要改变带宽配置(降低速率或者减小链路宽度)来解决这些问题。
Selectable De‐emphasis(可选去加重)的用法。这个比特只在 5GT/s 速率下有意义。高速信号在通过 PCB 走线和连接器后会高频衰减,导致接收端眼图变差。去加重(De-emphasis)是一种预补偿手段——发送端在发送时主动压低连续相同电平的幅度,让信号经过信道衰减后,接收端看到的眼图反而更好。PCIe 在 5GT/s 下定义了两档去加重水平(-3.5dB 和 -6dB),设备需要协商用哪一档,这就是这个比特的作用。

上游端口(Upstream Port,比如 EP 端): 上游端口在发送 TS1/TS2 时主动把自己期望的去加重水平写进这个比特。下游端口收到后,在 Recovery.RcvrCfg 状态中锁存这个值,存到内部变量 select_deemphasis 里,之后就用这个值来配置自己的发送端去加重。简单说就是"上游端口提需求,下游端口照办"。
下游端口(Downstream Port,比如 RC 端)和根端口: 这里的逻辑是反过来的——下游端口自己的去加重水平不由对端决定,而是由 Link Control 2 寄存器中对应比特控制。下游端口在 Recovery.RcvrCfg 状态发 TS2 时,把这个寄存器里的值写进 TS2 的这个比特发出去。协议特别强调这个寄存器的初始值是硬件初始化的,所以实际工程中要么在上电后由 BIOS/固件写入一个合适的值,要么让硬件 strapping 给一个保守但安全的默认值。另外在 Polling.Compliance 状态中,下游端口也会接收对端 TS1 中的这个比特来设置 select_deemphasis 变量,主要用于 Compliance 测试场景。
回环模式(Loopback): 在 5GT/s 回环测试中,主机会通过 TS1 把自己期望的去加重水平告诉从机,从机据此设置自己的去加重。这和正常工作模式的协商路径略有不同,但核心逻辑一致——都是一方通过这个比特告诉另一方该用哪一档去加重。
总结一下,这个比特本质上是一个"去加重档位偏好"的传达机制:上游端口表达自己希望对端用哪一档,下游端口通过寄存器配置来声明自己要用哪一档,双方在 Recovery.RcvrCfg 阶段完成确认并锁定。

上游端口(Upstream Ports)设置该域表示他们在 5GT/s 速率下期望的去加重水平设置,具体的预期设置取决于实现。在 Recovery.RcvrCfg 状态中,设备锁存接收到的该域的值,存放在设备内部。(协议描述中,这个值会被保存在一个名为 select_deemphasis 的变量中)
下游端口(Downstream Ports)和根节点端口(Root Ports):在 Polling.Compliance 状态中,基于接收到的该比特数值,设置 select_deemphasis 变量。在 Recovery.RcvrCfg 状态中,发送端设置发送的 TS2 中的该比特信息,数值基于 Link Control 2 寄存器相关比特。由于该寄存器的初始值是由硬件初始化的,所以预期需要在上电阶段后通过固件配置恰当的数值,或者将其硬件初始化值设置为一个保守但是安全(Strapping)的数值。
在 5.0 GT/s 回环模式中,从机去加重水平数值由主机发送的 TS1 中的对应比特设置。
Link Upconfigure Capability:该域表示了一个较宽的链路在减少链路宽度后,是否有能力重新恢复原先较宽的链路宽度。
Symbol 5 (Training Control): 交流链路双方的一些特殊情况,比如进行一次热复位(Hot Reset),使能回环模式,关闭链路或者关闭加扰
符号 6-9:均衡控制(Equalization Control)
这块在不同速率下角色完全不同:
Gen1/Gen2(2.5/5 GT/s):
符号 7-9 就是纯标识符,没什么特殊含义。
符号 6 通常也是标识符,但它的 Bit 7(Use Preset) 是个开关:
= 0:普通 TS,没什么特别的。
= 1:这个 TS 变成了 EQ TS(均衡 TS),是下游端口(DSP)发给上游端口(USP)的,意思是"我们要升速到 8 GT/s 了,这是均衡参数,你用上"。
EQ TS 的符号 6 里携带了发送端 Preset 值和接收端 Preset Hint,供 USP 进行 8 GT/s 均衡时使用。
只有支持 8 GT/s 的端口才需要能识别 EQ TS;不支持的端口可以无视。

Gen3(8 GT/s):
符号 6-9 全部用来传递均衡参数(Preset 或 Coefficients),信息更丰富。
TS2 的符号 6 Bit 7(Request Equalization):USP 用它来主动请求"重新做一次均衡"——比如当前均衡参数效果不好,信号质量出了问题。
TS2 的符号 6 Bit 6(Quiesce Guarantee):配合上面的请求一起用,意思是"放心去做,重新均衡很快(1ms 内回到 L0),不会导致完成包超时等副作用"。
DSP 也可以主动用这两个 bit 要求 USP 发起重新均衡,USP 不需要回复确认,直接照做就行。
二、符号 10-13:纯标识符
没什么好说的,就是 TS1 或 TS2 的标识字符,用来让接收方确认"这确实是个 TS 有序集"。
三、符号 14-15:DC 均衡(DC Balance)
Gen1/Gen2:
同样只是标识符。因为 8b/10b 编码本身已经保证了 DC 平衡(0 和 1 数量基本均等),不需要额外处理。
Gen3(8 GT/s):
Gen3 用的是 128b/130b 编码,不再有 8b/10b 的 DC 平衡保证,所以需要手动追踪。
发送方在每个通道上独立维护一个"运行中 DC 均衡计数器",记录当前发送的 1 和 0 的数量差。差值范围 ±511,记满后保持不变,但差值减小时会更新。
在发送 TS1/TS2 时,根据符号 11 结束时的 DC 差值,动态决定符号 14 和 15 填什么值:
差值 > 31:如果 1 多了,就填 20h 和 08h(偏多 0);如果 0 多了,就填 DFh 和 F7h(偏多 1)。用力拉回来。
差值 > 15:符号 14 保持正常标识符,符号 15 用 08h 或 F7h 做轻度补偿。
差值 ≤ 15:不补偿,正常发标识符。
计数器在退出电气空闲或收到 EIEOS 时复位。
DC 均衡符号绕过加扰,保证发送的是你真正想发的值。
一句话总结
符号 6-9 管均衡参数的传递和重新均衡的请求;符号 10-13 是纯标识符;符号 14-15 在 Gen3 下负责动态 DC 均衡补偿,确保信号中 0 和 1 的数量不会跑偏太多。Gen1/Gen2 因为有 8b/10b 编码兜底,后两块都很轻松,到 Gen3 才需要真正干活。

链路训练与状态控制状态机(LTSSM)
 
每个 LTSSM 的状态中又划分为若干子状态。在基础复位(Fundmental Reset),即冷复位(Cold Reset)和暖复位(Warm Reset),或者热复位(Hot Reset)释放后,进入的第一个状态是 Detect 状态。

LTSSM 总共有 11 个顶层状态,(所谓的顶层状态,即与顶层状态下的子状态区分),他们分别是:
Detect
Polling
Configuration
Recovery
L0、L0s、L1、L2
Hot Reset
Loopback
Disable
他们可以划分为五大类:
链路训练状态
重训练状态,即 Recovery
软件驱动的电源管理状态
主动电源管理(ASPM,Active-State Power Management)状态(即硬件驱动的电源管理状态)
其他状态
在任意复位释放后,LTSSM 即进入了训练类状态(Link Training states),一切正常的话,会按照 Detect => Polling => Configuration => L0 的顺序跳转状态。待进入 L0 状态后,即可以进行正常的数据报文收发操作。
进入链路重训练状态,也即是链路恢复(Recovery) 状态的原因很多,比如从像 L1 这样的低功耗链路状态(low-power Link state)中恢复,或者正准备进行链路带宽切换(速率或者链路宽度切换)。在链路重训练状态中,链路会重复类似于训练状态的操作,来解决链路中的问题,并最终回到 L0,这一正常工作的状态。
设备中的功耗管理软件在进入低功耗设备状态(low-power device state)后,比如 D1、D2、D3Hot 或者 D3Cold 之后,会强制链路进入低功耗软件管理链路状态(low-power Management Link state),比如 L1 或者 L2 。
如果链路上很长时间都没有报文需要发送,ASPM 硬件逻辑会使链路自动进入低功耗 ASPM 状态(low-power ASPM Link state),比如 L0s 或者 ASPM L1。此外,软件可以直接使链路进入一些其他状态,比如禁用状态(Disabled),回环状态(Loopback)或者热复位(Hot Reset)状态。这里,这些状态被归纳为其他状态。

检测状态 Detect: 复位释放后进入的初始状态。在这个状态中,本方设备从电气特性的角度,检测链路对端设备是否存在。设置 Detect 状态的目的在于增加测试的便利性(It's done to facilitate testing),我们将在 Detect 之后的 Polling 阶段验证这一观点。除了复位释放之外,还可能从别的 LTSSM 状态进入 Detect。

轮询状态 Polling:在轮询状态中,发送方将以 2.5Gbps 的速率向对端发送 TS1 以及 TS2 序列,使用协议最低速率以实现对早期协议的后向兼容。接收端可以使用接收的 TS1/2 序列实现以下功能:
完成位锁定
完成符号锁定或者块锁定(Gen3)
如有必要,校正通道极性翻转
得知通道支持的链路数据速率
在测试条件下,发起兼容性测试序列

配置状态 Configuration:上游(Upstream)和下游(Downstream)器件将分别按照他们上下游的角色,以 2.5Gbps 速率,交换 TS1 和 TS2 序列,来实现以下的目标:
协商决定链路的宽度
为各通道指派编号
检测通道是否需要顺序或者极性交换,在本地恢复这些交换
补偿各个通道之间的时序偏斜
 
Configuration 就是双方确认好"用几条通道、每条通道编号几号、通道顺序对不对、各通道时间对不对",把这些链路层面的"排兵布阵"搞定之后,就可以进入 L0 正常工作状态了

从这个状态开始,可以关闭加扰,并可进入 Disable 或者 Loopback 状态。此外,会记录在 TS1 和 TS2 序列交换时达成共识的 N_FTS,也就是从 L0s 状态进入 L0 状态所需的 FTS 序列数量。FTS 是用来快速恢复链路的训练序列

L0:链路正常全速运行,收发 TLP、DLLP 报文和有序集。链路初始训练完成后速率是 2.5 GT/s,想提速得进 Recovery 走一轮速率切换程序。
Recovery(恢复):不是低功耗状态,而是"重训练"状态。触发原因包括 L0 中出错、从 L1 恢复、从 L0s 恢复时 FTS 锁定失败。过程和 Polling 类似地重新做比特锁定和符号/块锁定,但通常更快。
L0s:硬件自动管理的 ASPM 低功耗状态。一方发 EIOS 序列即可进入,省电幅度小,但恢复快——通过 FTS 序列重新锁定就回到 L0。是这几个状态中恢复最快的。
L1:比 L0s 更省电,但恢复更慢。需要双方协商一起进入,有两种触发方式:
ASPM 自动触发:上游端口没报文要发时,主动找下游端口商量进 L1,下游同意就一起进,不同意就上游单方面退而求其次进 L0s。
软件命令触发:软件让设备进低功耗状态(D1/D2/D3Hot),上游通知下游必须一起进 L1,下游必须响应。
L2:深度低功耗,主电源切断,大部分逻辑关闭,仅靠辅助电源 Vaux 维持唤醒检测逻辑。设备可通过上游端口发 Beacon 信号或边带信号 WAKE# 触发系统唤醒,恢复主电源。
L3:最彻底的断电,主电源和辅助电源全断,不响应任何唤醒事件,也和 LTSSM 状态机无关了。
Loopback(回环):测试状态。发起方发带 Loopback 比特置位的 TS1,接收方收到 2 个后进入回环模式,把收到的内容原样回传。发起方对比发送和接收的内容,一致就说明链路完整性没问题。本质就是发个数据绕一圈看看有没有出错。
Disable(禁用):链路"下线"状态。发送端电气空闲,接收端低阻。触发方式有两种:
意外情况:链路不可靠、对端被拔走等
软件主动配置:通过链路控制寄存器置位 Disable 比特,设备连发 16 个 Disable 比特置位的 TS1 通知对端一起禁用
Hot Reset(热复位):带内复位,不走物理复位线,直接通过链路上的 TS1 序列传递。软件置位桥控制寄存器的 Secondary Bus Reset 比特后,下游端口发 Hot Reset 比特置位的 TS1,接收方收到 2 个后必须复位自身。

每个设备都必须以基础的 2.5GT/S 速率,进行初始链路训练。支持更高速率 5.0 或者 8.0 GT/s 的设备,必须进行 Recovery 状态,才能够进行速率切换。
Detect State // 检测状态
Detect 状态下的链路行为是发送方不断检测链路对端是否有接收方存在。 Detect 只有 2 个次状态
 
Detect.Quiet
etect.Quiet 是 Detect 状态的第一个子状态,可以理解为整个链路训练流程的"起点站"。总结如下:
进入条件
冷启动/复位后:除了 FLR(功能级复位)之外的所有复位和上电事件,都会在 20ms 内进入此状态
异常回退:从 Disabled、Loopback、L2、Polling、Configuration、Recovery 等状态"碰壁"后,退回这里重新来过
做了什么
发送端:电气空闲状态,不发任何信号,但直流共模电压此时可以不在规范范围内。
速率归零:目标速率强制设为 Gen1 的 2.5 GT/s。如果进来时不是这个速率,等 1ms 再切回去——不管之前跑的是 5G 还是 8G,回到这里一律从最低速率重新开始。
链路状态标志:LinkUp = 0,告诉数据链路层"链路还没好,别急着发数据"。等训练完成进 L0 后 LinkUp 才变为 1。
清除均衡状态:4 个均衡寄存器(Eq.Phase 1/2/3 Successful、Eq.Complete)全部清零,相当于把之前的高速均衡成绩抹掉,从头来
Detect.Quiet 就是个"清零复位台"——把速率降到最低,把状态标志和均衡成绩全部清零,发送端电气空闲,安静地等待下一步检测对端是否存在。
Detect.Active
Active 状态由 Quiet 状态进入
发送端改变直流共模电压,观察电压变化的速率(充电快慢):
没有接收端:通道终端是开路,电压充电很快
有接收端:接收端的终结阻抗拉低了充电速度,电压变化明显变慢
两者差距很大,所以检测起来很可靠——本质上就是一个阻抗感知的过程。
 
部分检测到的情况
比如 x4 设备连 x2 设备,4 条通道只有 2 条有对端:
等 12ms 再测一次:确认这不是抖动或接触不良
结果不变:接受现实,进 Polling,未检测到的通道有两种命运:
能独立运作的 → 另开一个 LTSSM 单独管理
不能独立运作的 → 设为电气空闲,踢出链路
结果变了:说明链路状态不稳定,退回 Quiet 重新来
电压约束
进入 Polling 前,各通道直流共模电压必须落在 0–3.6V(V_TX-CM-DC)范围内,不能乱来。
一句话总结:Detect.Active 通过"试探电压充电快慢"来判断对端有没有设备,根据检测结果决定是退回去等、还是继续往前走到 Polling。 全靠就进,一个没有就等,只有一部分就再确认一次再决定。
Polling State // 轮询状态
LTSSM 进入轮询状态时,链路处于电气空闲状态,不过,轮询状态期间会在链路两端之间交换 TS1 和 TS2 有序集。轮询状态的主要目的是使链路两端的设备能够听懂对方在说些什么,换句话说,他们需要各自在对方的发送比特流上,建立比特和符号锁定状态,并且解决极性翻转(polarity inversion)恢复之类的事宜。在这些工作完成后,设备们能够正确地接收对方发出的 TS1 和 TS2 有序集。
 
Polling.Active 子状态总结
一、核心目的
让链路两端通过交换 TS1 有序集,建立比特/符号锁定(<8GT/s)或块锁定(8GT/s),解决极性翻转等问题,使双方能正确接收对方的训练序列。
二、进入后做什么
共模电压稳定后,发送方至少发送 1024 个连续 TS1(Gen1 下约 64μs)
TS1 中 Lane/Link 编号用填充字符(PAD)
设备需通告自己支持的所有速率(即使不打算用)
接收方利用收到的 TS1 完成比特锁定和符号锁定
三、三个可能的退出方向
退出目标    触发条件
Polling.Configuration            发完 1024 个 TS1 后,所有通道收到 8 个连续 TS1/TS2,且 Lane/Link 字段为 PAD,且 Compliance Receive=0 或 Loopback=1 或收到 TS2。超时 24ms 后条件略微放宽。若部分通道检测到退出电气空闲,也可进入。
Polling.Compliance            Link Control 2 寄存器的 "Enter Compliance" 比特=1 → 直接进入。或 24ms 超时后:所有通道未检测到对端退出电气空闲(对端是被动负载),或收到 8 个连续 TS1 且 Compliance Receive=1、Loopback=0。
Detect(回退)              24ms 超时后,既没满足 Configuration 条件,也没满足 Compliance 条件 → 打回 Detect 重新开始。
四、一句话概括
Polling.Active 就是链路双方互发 TS1 "打招呼",锁定时钟和符号;顺利就进 Configuration 握手,被要求测试就进 Compliance,啥都没对上就退回 Detect 重来。整个状态有一个 24ms 的总超时兜底。
Polling.Configuration
在 Configuration 次状态中,发送方停止发送 TS1 序列,转而发送 TS2 序列,TS2 序列中的链路和通道(lane)字段仍然使用填充字段填充。该状态中,发送方转而发送 TS2 的目的是通知链路对端的设备:本方已经做好准备进入状态机中的下一个状态。转而发送 TS2 是为了使链路两端的设备 LTSSM 同步而设计的握手机制。双方设备都无法独自进入下一状态,除非链路两端的设备都准备就绪。开始发送 TS2 序列是通知对端本方准备就绪的方式。所以一旦设备同时发送并且接收到 TS2 序列,就代表本方和对端设备都已经就绪, 设备可以进入下一状态。
Polling.Configuration 子状态总结
一、核心目的
链路双方的握手确认——通过从 TS1 切换为 TS2,通知对端"我准备好了",等双方都发出并收到 TS2 后,才能同步进入下一状态。
二、期间做什么
停止发 TS1,改为发 TS2,Lane/Link 字段仍填 PAD
通告自己支持的所有数据速率(即使不打算用)
每个通道接收方独立恢复差分信号极性(如有需要)
Transmit Margin 字段重置为 000b
三、两个退出方向
退出目标    触发条件
Configuration        收到 8 个连续 TS2(Lane/Link 为 PAD),且从收到第一个 TS2 起已发送至少 16 个 TS2
Detect(回退)    上述条件 48ms 超时仍未满足
四、历史遗留:Polling.Speed(已废弃)
这部分很值得说道一下,它体现了 PCIe 协议设计的演进思路:
最初设想:在 Polling 阶段就一次性切到最高速率
实际问题:Polling 阶段会清除大量链路配置,重新进入调节速率代价太高
最终方案:动态速率调节功能移到了 Recovery 状态,而非 Polling
现行行为:复位后始终先训练为 2.5GT/s,进入 L0 后若支持更高速率,再通过 Recovery 状态提速
协议规范中仍保留 Polling.Speed 条目,但标注为 unreachable(不会进入),属于历史兼容的摆设。
五、一句话概括
Polling.Configuration 就是链路双方用 TS2 互喊"我好了",确认双向就绪后一起进入 Configuration 真正分配 Lane 编号。48ms 内没握上手就退回 Detect。速率切换早就不归它管了,搬到 Recovery 那边去了
Polling.Compliance
Compliance 次状态用于测试。发送方会发送特定的码字组合(pattern),构建一种码间干扰和串扰接近最严重的场景,用于链路信号质量分析。在该次状态中,可以发送两类码字,分别是 Compliance pattern 和 modified compliance pattern

Configuration State // 配置状态
设备初始化时,Configuration 状态在 2.5GT/s 速率下配置链路以及通道编号。5GT/s 和 8GT/s 速率时,设备也可能从 Recovery 状态进入 Configuration 状态。此时状态转换的主要目的是为了进行多通道设备的链路位宽动态转换。动态转换仅支持 5GT/s 和 8GT/s 速率的设备。
本状态的主要目标是弄清楚设备端口(Port)和各个通道(Lane)的连接情况,以及为连接的通道分配通道编号。和其他状态不同的是,端口在此状态的行为区分为面向上游端口或者面向下游端口两种情况,两类端口在 Configuration 状态中被定义为不同的角色。
DSP (Downstream Port,向下游发送数据)端口在剩余的链路初始化过程中扮演 “领导者”,而 USP (Upstream Port,向上游发送数据)端口的角色是 “跟随者”。"领导者",也就是 DSP,会为 USP 确定链路以及通道的编号,USP 则只是简单地重复他接收到的值给 DSP
设计支持链路合并的设备
设计者一般根据性能与成本的考虑,决定在一条链路上实现多少个通道。PCIe 链路支持多个窄链路聚合成一个更宽的链路,同样的,较宽的链路也可以分拆成多个窄链路。
 
在 Configuration 状态中,链路以及通道的编号工作由 DSP 作为 “领导者” 发起(比如根节点端口或者交换机的面向下游端口)。而终端(Endpoint)以及交换机面向上游端口无法发起这一过程,只能作为 “跟随者” 响应。
链路配置在这个例子中假定双方设备都只支持单个链路,但通道宽度可以是 x4,x2 或者 x1。通道编号分配在设备内部完成,必须从 0 开始连续编号
链路编号(Link Numbering)协商阶段的描述,核心目标是把多个物理通道(Lane)绑定到同一条逻辑链路上
Lane(通道):PCIe 的基本数据传输单元,一条链路可以由多个 Lane 组成(如 x4 = 4 条 Lane)。
TS1 Ordered Set:Training Sequence 1 有序集,是链路训练过程中用来交换控制信息的特殊数据包。里面包含链路编号和通道编号两个字段。
协商流程
第一步:DSP 发起
DSP 在所有通道上广播 TS1,其中:
链路编号 = N(一个具体值)
通道编号 = PAD(还没到分配通道编号的阶段,先留空)
意思就是:"我要建立一条编号为 N 的链路,你们所有通道都听着。"
第二步:USP 回应
USP 一开始也在发 TS1,但链路编号和通道编号都是 PAD(相当于"我还不知道,先空着")。当它收到 DSP 的 TS1 后,发现链路编号有实际值 N,于是:
在所有已连接的通道上回复 TS1
链路编号 = N(确认收到,同意用这个编号)
通道编号 = PAD(仍然留空,因为还没到通道编号阶段)
第三步:DSP 确认
DSP 收到 USP 在四个通道上的回复,发现:
四个通道都响应了
都用了相同的链路编号 N 
于是 DSP 的 LTSSM(链路训练状态机)判定:这四个通道属于同一条链路 N,接下来会继续进入通道编号协商阶段,给每个通道分配 0/1/2/3 等编号。通道编号的值 'N' 是一个由具体实现决定的值,这个值不会保存在任何协议定义的寄存器中,并且与通道编号等其他值无关。
 
前一步 DSP 已经确认了四个通道都属于同一条链路,现在它给每个通道分配编号 0、1、2、3,通过 TS1 发出去。USP 收到后做的第一件事是验证:DSP 发来的通道编号是否和我自己物理通道的编号对得上。比如 DSP 在物理通道 0 上发的是编号 0,我这条物理通道也是 0,那就匹配。验证通过后 USP 才回复,把同样的编号回传给 DSP。
DSP 拿到 USP 的回复后再比对一轮——我发的编号和你回的编号是否一致。一致就万事大吉,进入下一步。不一致就要处理异常:如果只是部分通道不通,就缩减链路宽度(比如 x4 降到 x2);如果通道物理连线接反了(比如 DSP 的通道 0 接到了 USP 的通道 3),那需要设备支持"通道顺序颠倒"这个可选特性来自动纠正。但正因为它是可选的,两边设备都可能不支持,一旦接反又没人能纠正,链路就彻底配不上了,属于电路板布线层面的严重错误。

 
确认链路与通道编号
DSP 发起确认:前面通道编号已经协商一致了,DSP 主动改发 TS2 序列,里面带着最终敲定的链路编号和通道编号,等于在说"我这边没问题,准备收工进 L0 了"。
USP 跟进确认:USP 收到 TS2,核对编号都对得上,也开始发 TS2 回应,表示"我也一样,随时可以进 L0"。
双方互收确认后进入 L0:双方各自收到至少 8 个 TS2、并且自己发了至少 16 个 TS2 之后,再补发一段逻辑空闲数据做过渡,然后同时切到 L0 状态,链路正式开始正常传输数据。
 
这个示例比示例 1 复杂的地方在于:DSP 的四个通道不一定全连到同一个对端,也不一定组成一条链路。它可能是拆成多条链路的。整个过程本质上就是 DSP 在"试探"对端到底是怎么连的。
 
第一阶段:DSP 试探链路关系(链路编号协商)
DSP 一开始不知道对端长什么样,所以它故意给四个通道各发不同的链路编号:通道 0 发 N,通道 1 发 N+1,通道 2 发 N+2,通道 3 发 N+3。相当于在每条通道上贴了不同的标签,看对端怎么回应。
USP 收到后,会把收到的链路编号原样回传。这时 DSP 看回复就明白了:通道 0 和 1 回传的是同一个链路编号 N,通道 2 和 3 回传的是另一个链路编号 N+2。说明四条通道分成了两组,分别连到了两个不同的 USP。
于是 DSP 在逻辑上把自己拆成两个独立的 DSP:左边的管通道 0/1,右边的管通道 2/3,各自独立协商。
 
第二阶段:通道编号协商(两边各自进行)
左侧链路(通道 0/1):DSP 给通道 0 分配编号 0,通道 1 分配编号 1。USP 收到后验证,发现自己物理通道编号也对得上,直接原样回传。两边一致,顺利通过。
右侧链路(通道 2/3):DSP 同样分配编号 0 和 1 发出去。但这里有个问题——物理连线上,DSP 的通道 0 接到了 USP 的通道 1,通道 1 接到了 USP 的通道 0,顺序是反的。
USP 收到后发现编号对不上,接下来有两种可能:
USP 支持通道翻转:USP 内部把自己的通道 0 和 1 逻辑编号互换,然后回传的 TS1 编号跟 DSP 发的一致。DSP 收到后一切看起来正常,根本不知道出过问题。
USP 不支持通道翻转:USP 按自己的物理编号原样回传,DSP 收到后发现编号是反的。这时轮到 DSP 检查自己是否支持翻转,如果支持,DSP 在内部做翻转,然后用 TS2 确认。
本例中右侧链路走的是第一种情况——USP 自己做了翻转,DSP 全程不知情。
 
第三阶段:确认并进入 L0
两边各自独立走 TS2 确认流程。DSP 收到 USP 回传的编号和协商结果一致,开始发 TS2;USP 收到 TS2 后也回复 TS2。双方各收到 8 个 TS2、各发 16 个 TS2 之后,发一段空闲数据,然后同时进入 L0。
总结一下这个示例的核心要点:
DSP 用不同的链路编号"试探"对端连接关系,根据 USP 回复判断几条链路、每条链路几个通道。
拆分后的多条链路各自独立协商通道编号,互不干扰。
通道物理接线如果反了,靠"通道翻转"这个可选特性兜底——USP 能翻就 USP 翻,USP 不能翻就轮到 DSP 翻,总之一边能翻就行。
翻转是可选的,两边都不支持就没办法了,链路配不上
链路配置示例 3:如果通道配置失败
基于 USP 的通道 2 不能正常工作. 第一阶段:DSP 照常发起,通道 2 掉链子
DSP 不通道 2 坏了,按正常流程在四条通道上发链路编号 N、通道编号填充的 TS1。
通道 0、1、3:USP 正常收到,原样回传。
通道 2:USP 收不到,所以它的发送端只能继续发全填充的 TS1
 
第二阶段:超时后 DSP 接受现实,缩减链路宽度
DSP 收到通道 0、1、3 的回复后,等着通道 2 也跟上,结果一直等到超时。这时 DSP 确认:通道 2 确实不行。
关键决策:PCIe 规定通道编号必须从 0 开始连续,不能跳号。通道 2 坏了,你不可能用 0、1、3 组成一条链路(缺了 2,不连续)。所以唯一可行的方案是:只用通道 0 和 1 组成 x2 链路。
 
于是 DSP 改变了行为:
通道 0、1:发送有效的通道编号 0、1。
通道 2、3:链路编号和通道编号都改回填充符号,等于"放弃这两条通道"。
USP 这边也跟着变:
通道 0、1:收到有效通道编号,正常回复。
通道 3:本来是好的,但现在 DSP 不要它了,链路编号变回填充,所以通道 3 也只能开始发填充符号。
通道 2:继续发填充符号(它本来就在发)
第三阶段:确认并进入 L0
通道 0、1 上的协商结果一致,DSP 发 TS2,USP 回 TS2,双方互收够了就进 L0。
通道 2 和 3 不参与 TS2 交换,继续发填充 TS1,然后转入电气空闲状态(休眠)。
 
被踢出链路的通道 2 和 3 并没有被永久放弃。下一次 DSP 发起链路训练时(比如系统复位、从 Recovery 状态重新进入 Configuration),会再次尝试把所有通道纳入训练。如果届时通道 2 的故障排除了(比如是温度导致暂时性故障),就可能重新配置为 x4 链路。
Configuration 状态各子状态详细讨论
 
Linkwidth.start 
DSP 做的事
DSP 是链路训练的发起者,在所有工作通道上发 TS1,链路编号填有效值,通道编号仍是填充。同一条链路的所有通道共用一个链路编号,不同链路用不同编号。链路编号只是两端之间的本地值,软件不用管,也不需要全系统统一。
几个附带行为:必须在 TS1 中通告自己支持的所有速率,包括不打算用的。如果支持 upconfigure(链路宽度恢复),也会对非工作通道发 TS1,前提是那些通道之前收到过填充 TS1,说明对端还在线。如果是从 Recovery 进来的,可以尝试重新激活之前休眠的通道恢复更大宽度,但激活前要等发送共模电压稳定才能退出电气空闲。
正常出口:收到 USP 回复的链路编号有效、通道编号仍为填充的 TS1 后,跳到 Linkwidth.Accept 继续往下走。
异常出口有四个。第一个是 Crosslink(交叉链路):如果发现对端发来的 TS1 链路编号已经非填充,说明对面也是 DSP,两边都是领导没人跟。解决办法是双方都转为 USP,各等一个随机长度的超时,先超时的那个转成 DSP 重新发起训练。超时必须随机,否则两边实现完全一样就会死锁。第二个是 Disable:收到 Disable Link 比特置位的 TS1。第三个是 Loopback:收到 Loopback 比特置位的 TS1。第四个是 Detect:24ms 超时到了还没收到任何有效回应,退回 Detect 从头来。
USP 做的事
USP 是跟随者,持续发全填充的 TS1,等 DSP 发来链路编号非填充的 TS1。收到后选取一个链路编号,在所有收到有效链路编号的通道上回传该编号,通道编号继续填充。没收到有效链路编号的通道继续发填充。
如果 USP 想恢复链路宽度,会等待所有待激活通道都收到 DSP 的有效链路编号 TS1,或者任意一条待激活通道在本状态超过 1ms,然后才开始回复有效链路编号。协议还建议发现通道错误时延迟一会儿再降级——8b/10b 编码等至少 2 个 TS1,128b/130b 编码等至少 34 个 TS1,但不超过 1ms——避免误降级。
如果进 Configuration 的原因不是超时触发的,USP 要在 TS1 中置位 Autonomous Change 比特,表示是自己主动想改变链路宽度。
异常出口和 DSP 一样有四个:Disable、Loopback、Detect(24ms 超时)、以及 Crosslink 场景下的角色反转——两个 USP 相连时,等随机超时后发填充 TS2,然后转成 DSP 重新进 Linkwidth.Start。
DSP 和 USP 的关键区别
角色上,DSP 主动分配链路编号,USP 被动回应收到的值。TS1 初始内容上,DSP 发链路编号有效加通道编号填充,USP 全部填充。Crosslink 处理上,DSP 发现对面也是 DSP 就转为 USP 等随机超时;USP 在两个 USP 相连时等超时后转成 DSP。upconfigure 主动权在 DSP 手里,它决定激活哪些通道、分配编号;USP 等到 DSP 的有效编号才回应。另外 Autonomous Change 比特只有 USP 需要关心。
Linkwidth.Accept
位置在上一个状态确认了"哪些通道属于同一条链路"之后,开始分配每条通道各自的具体编号。是链路编号协商和通道编号协商之间的过渡环节。
上一步 DSP 发了链路编号,USP 回传了相同的链路编号。到这里,DSP 通过数有多少通道回传了同一个链路编号,就算出了链路宽度——比如四条通道都回了编号 N,那就是一条 x4 链路。
确认宽度之后,DSP 立刻在 TS1 中给每条通道分配各自的通道编号(0、1、2、3…),不再用填充符号。链路编号保持不变,还是之前那个值。发完之后立刻跳到下一个状态 Lanenum.Wait,等 USP 确认。
USP 这边的动作就是回应:收到 DSP 的有效链路编号就回传相同的链路编号,没收到有效链路编号的通道继续发填充。等到 DSP 开始发带通道编号的 TS1 后,USP 收到两个连续的、链路编号相同且通道编号有效的 TS1,就回传相同的通道编号表示接受。如果需要翻通道顺序(通道接反了),就回传不同的编号值提示 DSP。
DSP 和 USP 的角色
DSP 还是主导者,负责给通道编编号、发起提议,然后马上走人去下一个状态等回复。
USP 还是跟随者,负责回应。正常情况下原样回传表示接受;如果通道接反了需要翻转,就回传不同的编号值让 DSP 知道

Lanenum.Wait
背景规则:通道编号必须连续
通道编号从 0 开始,必须连续不间断。
x8 链路 → 通道 0-7;若降级为 x4 → 只能用 0-3,不能用 0,1,3,4 这种跳号组合。
通道 0 是底线:如果 lane 0 坏了且没启用通道翻转,整条链路无法配置,直接回 Detect。
二、时序保护:别急着砍带宽
如果多通道链路中某些通道出现错误或失去 Block Alignment,不要立刻缩减链路宽度。
先等一等:8b/10b 至少等 2 个 TS1,128b/130b 至少等 34 个 TS1,但不超过 1ms。
给故障通道一个恢复的机会,避免不必要的带宽损失。DSP 和 USP 都要遵守。
三、DSP(下游端口)行为
持续发送带有效链路号 + 通道号的 TS1,等待 USP 回应。
跳到 Accept 的两种条件:
所有通道都回了匹配的 TS1(完全一致)。
某个通道回的通道号和刚进来时不一样(说明 USP 在调整方案,双方在协商中达成一致)。
回 Detect 的条件:2ms 超时,或所有通道都回全填充符号的 TS1(彻底没戏)。
四、USP(上游端口)行为
同样持续发送带有效编号的 TS1。
跳到 Accept 的两种条件:
所有通道收到连续两个 TS2(DSP 已经发确认了)。
某个通道收到的通道号和刚进来时不一样(和 DSP 的第 2 条同理)。
回 Detect 的条件:和 DSP 一样,2ms 超时或全填充

Configuration.Lanenum.Accept
进 Complete(成功): 所有通道回的编号和发的完全一致,双方达成一致,进入最终确认。DSP 还有一个可选特性:如果通道顺序完全颠倒(lane 0 收到 N-1,lane N-1 收到 0),且 DSP 支持通道翻转,也可以进 Complete。但只允许正序或完全颠倒两种,不支持乱序混编。
回 Lanenum.Wait(缩减重来): 只有部分通道能用时,对这部分通道重新编号,从 0 开始连续递增,跳回 Wait 再等一轮确认。遇到没收到 TS1 的通道就中断递增,该通道之后的通道不能编入链路。比如 8 条通道中 lane 2 坏了,就只能组 x2(lane 0,1)或 x1(lane 0),组不了 x4 或 x8。未使用的通道发全填充符号的 TS1。
回 Detect(彻底失败): 没有链路可配,或所有通道都回全填充符号,推倒重建。
DSP 和 USP 的核心差异在于:DSP 进 Complete 的信号是收到匹配的 TS1,USP 则是收到匹配的 TS2。另外,如果是从 Recovery 进入且链路宽度发生变化,DSP 需要记录变更原因——可靠性问题置 Bandwidth Management Status bit,电源管理等原因置 Autonomous Bandwidth Status bit。USP 没有这个职责。
Configuration.Complete
这是整个 Configuration 状态中唯一交换 TS2 的阶段。TS2 是最终握手确认:双方互相确认链路编号、通道编号、速率、能力等所有协商结果,准备进入正常工作状态 L0
关键动作
双方都要做的事:
发送 TS2,其中携带协商好的链路编号和通道编号。
8b/10b 编码下必须完成通道间去偏移(lane-to-lane de-skew),然后才能离开本状态。
如果所有通道收到两个连续 Disable Scrambling 比特为 1 的 TS2,就停止加扰。但 128b/130b 编码无法关闭加扰,因为扰码对信号完整性不可或缺。
记录对端的 N_FTS 值,表示对端退出 L0s 状态需要的 FTS 数量,留到以后用。
如果是 x1 链路且支持链路宽度恢复(upconfigure),可以置位 TS2 中的 Upconfigure Capability 比特。进入本状态后这个能力设置就不能再改了,因为双方会在此刻记录对方的能力。
正常进 Idle(成功):
所有通道收到 8 个匹配的 TS2,且每条通道在收到第一个 TS2 后已发够 16 个 TS2。匹配条件包括链路编号、通道编号、速率标识符、Upconfigure Capability 比特全部一致。此时清除 changed_speed_recovery 变量,根据双方 Upconfig 比特更新 upconfigure_capable 变量,速率支持记录也要更新。
超时后也有机会进 Idle:
2ms 超时后,如果 idle_to_rlock_transitioned 变量小于 FFh 且速率为 8GT/s,仍可进 Idle。这个变量记录了因配置失败而跳转 Recovery 的次数,到 FFh(255 次)就不再给机会了。
回 Detect(彻底失败):
超时后不满足上述条件,推倒重建。
未被配置进链路的通道怎么处理
这些通道脱离当前 LTSSM 关联,转入电气空闲。但有一个例外:如果这些通道曾经在 L0 状态中是链路的一部分,且 LinkUp 一直为 1,且链路支持 upconfigure,它们仍需留在原 LTSSM 下,并建议保持接收端终结特性打开,以便将来恢复链路宽度时重新加入。进入电气空闲前不需要发 EIOS,状态转换也不必在符号边界上发生。
DSP 和 USP 的差异
几乎完全对称,唯一小区别是:USP 如果支持 crosslink 特性,未配置通道可以可选地与新的 crosslink LTSSM 关联;DSP 没有这个选项。两者进 Idle 和回 Detect 的条件完全相同。
Configuration.Idle
这是 Configuration 的最后一个子状态。双方持续发送空闲数据,等收到足够数量的空闲数据后就进入正常工作状态 L0。此时物理层向上层报告链路就绪(LinkUp = 1)
编码差异
8b/10b: 发送的就是零值经加扰和编码后的空闲符号。
128b/130b: 先发一个 SDS 有序命令集,再紧跟空闲数据。通道 0 上的第一个空闲符号是整个数据流的起始。
进 L0 的条件
8b/10b: 所有已配置通道收到 8 个连续空闲符号,且每条通道在收到第一个空闲符号后已发够 16 个空闲符号,就进 L0。
128b/130b: 同样的 8/16 条件,但额外要求不是从 Configuration.Complete 超时进入本状态的。
另外几个注意点:通道间去偏斜必须在处理数据流前完成;空闲符号必须在数据块内收到;如果是软件主动触发重新训练链路,DSP 要置位带宽管理比特表示非硬件自主发起;进 L0 前清零 idle_to_rlock_transitioned 变量。
超时后的两条退路
如果 2ms 内没满足进 L0 的条件:
去 Recovery(再试试): 如果 idle_to_rlock_transitioned 小于 FFh,跳转到 Recovery.Rcvrlock 尝试修复。这个变量记录了配置失败跳转 Recovery 的次数。8GT/s 每次跳过去自增 1,2.5/5GT/s 直接设为 FFh(因为低速不存在均衡问题,没救)。如果 256 次后还是不行,不再给机会。
回 Detect(推倒重建): idle_to_rlock_transitioned 已到 FFh 时,回 Detect 从头来。

L0 State // L0 状态
L0 状态是链路的全功能正常工作状态, 虽然刚进入此状态时处于逻辑空闲状态,但之后链路两端的设备会交换 TLP 和 DLLP
退出至 Recovery 状态
如果本方设备提议改变链路速率或者宽度,或者对端设备已经进入了 Recovery 或者电气空闲状态,表示提议发起链路速率或者宽度改变,那么状态机的下一个状态是 Recovery 状态。
Speed Change // 速率切换
情况一:升级到更高速率
前提:链路双方都支持 2.5 GT/s 以上的速率,且链路处于活跃状态(DL_Active)。
触发方式:软件配置链路控制寄存器——使能 Retrain Link 比特,同时设定一个与当前不同的 Target Link Speed。
典型场景:链路初始训练时默认跑 2.5 GT/s,但对端声明支持更高速率(如 5.0 或 8.0 GT/s),软件就可以发起一次速率转换,把链路提速。
情况二:进行 Tx 均衡(Transmitter Equalization)
前提:链路双方都支持 8 GT/s 速率。
触发方式:一方需要执行发送端均衡(Tx Equalization),高速信号下为了保证信号完整性而进行的预加重/去加重调节。
说明:8 GT/s 及以上速率下信号失真严重,Tx 均衡是必经步骤,因此需要触发速率改变流程来完成均衡训练。
这两种情况下变量 directed_speed_change 都会被设置为 1b,并且 changed_speed_recovery 比特会被清除为 0b。‘

Link Width Change // 链路宽度切换
1. 减少宽度——随时可以
上层逻辑一般只会减少链路宽度,用来恢复链路的原始配置宽度。这是常规操作,门槛很低。
2. 禁止硬件自主后的限制
如果 Hardware Autonomous Width Disable 比特被置为 1,端口就不能自己随便改宽度了,只能在上层逻辑指示下减少宽度,用来解决可靠性问题——比如某条通道信号质量恶化,先把宽度降下来保命。
3. 增加宽度——条件严格
要发起增加链路宽度,必须同时满足两个条件:对端通告了自己具备链路宽度恢复能力(即 upconfigure_capable 置 1),且当前链路宽度还没到最大配置宽度,有余量可加。缺一个都不行。

Link Partner Initiated //链路伙伴发起链路改变的情形
该操作具体包含三种情形:
本方端口检测到对端所有通道进入电气空闲状态,且未收到退出有序集,可选择留在当前状态或进入重新训练状态。
本方端口收到对端发送的训练序列,表示对端已经启动重新训练,由对端主导本次改变。
本方端口收到对端发送的退出有序集,表示对端要切换电源状态,若本方不支持对应低功耗状态,则需要进入重新训练。

三种触发情形
第一种:电气空闲 本方在所有通道上检测或推断出电气空闲,且此前没收到过 EIOS。说明对端可能已经静默。本方可以选择留在 L0,也可以进 Recovery。如果产生了错误,就通过置位 Retrain Link 比特进入 Recovery。
第二种:收到训练序列 本方在已配置的通道上收到 TS1 或 TS2(128b/130b 编码时收到 EIEOS),说明对端已经主动进入 Recovery。因为是对端发起的,本方发送端被允许先完成正在传输的 TLP 或 DLLP,然后再跟进。
第三种:收到 EIOS 但不支持对应状态 本方收到 EIOS,表示对端要切换电源状态,但本方不支持 L0s,也没被要求进 L1 或 L2,那就没得选,只能进 Recovery。
共同点:三种情形都是对端发起、本方被动响应。对端发的信号不同(电气空闲 / TS1/TS2 / EIOS),处理路径也不同,但配合不上的时候最终都走 Recovery。
三种电源状态退出路径
L0s:发送方收到上层指示后发 EIOS,接收方收到 EIOS 后进入。这个状态比较轻量,不需要双方握手,协议允许一方在 L0s 的同时另一方还在 L0,两边的状态机可以不同步。
L1:被命令发起后,在所有通道发 EIOS(5.0GT/s 时发两个),收到 EIOS 后进入。但必须双方事先同意,靠数据链路层握手机制来保证同步。
L2:流程和 L1 一样——发 EIOS、收到后进入,同样需要双方事先同意和握手。区别在于 L2 比 L1 更深,省电程度更高。
核心区别:L0s 是轻量省电,允许非对称(一方睡一方醒);L1 和 L2 是深度省电,必须双方同意,需要握手保证同步。

Recovery State // 恢复状态
两种情况下一定需要进入 Recovery 状态。
首先一种情况,如果在 Configuration.Idle 状态中没能成功接收到正确的链路符号,那么 LTSSM 会进入 Recovery 状态,尝试纠正链路中的信号质量问题,比如通过调整均衡参数的方式。
第二种情况,在链路以 2.5GT/s 速率训练完成,进入 L0 状态后,如果双方都支持更高的速率,那么 LTSSM 会进入 Recovery 状态,将速率调整为双方同时支持或者通告的最高速率。这种情况下,LTSSM 必须重新进行比特锁定和符号或者块锁定,以及链路间的去除偏斜操作。
链路编号以及通道编号正常情况下需要保持不变,除非需要改变链路宽度。这种情况下 LTSSM 进入 Configuration 状态,重新进行链路宽度协商。

Recovery 状态进入条件总结
一、从低功耗状态退出时
L1 退出:协议没有提供快速重训练选项(如 FTS 序列),所以必须进入 Recovery 完整重新训练
L0s 退出:接收方在规定时间内未能靠 FTS 序列完成同步,快速恢复失败,退入 Recovery 兜底
二、在 L0 正常工作状态中主动触发
提速:初次训练完成后,想把链路升到更高速率
速率/宽度变更:上层逻辑(功耗管理或稳定性需求)要求改变链路速度或宽度
软件手动触发:软件写 Link Control Register 的 Retrain Link bit,主动要求重训练修复传输问题
链路层自动触发:Ack/Nak 协议出现重放计数器翻转等错误,数据链路层自动控制物理层重新训练
三、被动检测到异常信号
收到 TS1/TS2:在已完成配置的链路上收到 TS1/TS2 有序集,说明对端已进入 Recovery,本端跟随
异常电气空闲:未收到 EIOS 序列却检测到电气空闲状态,说明链路异常


初始化 Recovery 状态
 
链路中的任意一方都可以通过向对端发送 TS1 序列来发起进入 Recovery 状态的请求。当一个 PCIe 端口接收到 TS1 时,他便获知对端已经进入了 Recovery 状态,随后其跟随对端进入 Recovery 状态。进入 Recovery 状态之后,本方端口也开始向对方回复 TS1 序列。双方接收端都通过对方的 TS1 序列完成锁定(如果有需要的话),然后按照需求进入各个次状态。
Recovery.RcvrLock 状态
一、发送内容与编号
无论链路速率是多少,发送方在所有已配置通道上始终发送 TS1 序列
TS1 中的链路编号和通道编号沿用 Configuration 状态中已设置的值
二、速率切换协商
如果进入 Recovery 的目的是切换速率,发起方在 TS1 中将 speed_change 比特置为 1,同时内部变量 directed_speed_change 也置为 1
对端收到 speed_change 为 1 的 TS1 后,同样将 directed_speed_change 置为 1
进入该状态时,successful_speed_negotiation 变量清零
三、去加重等级(De-emphasis)指定
USP 可以通过 TS1 中的 Selectable de-emphasis 比特指定 DSP 的去加重等级
由于比特错误可能导致 DSP 收不到该信息,USP 在因速率切换进入 Recovery 后可以再次指定
DSP 如果打算采用,必须在 RcvrLock 状态中记录接收到的去加重等级比特值
四、发送电压(Transmit Margin)
进入 RcvrLock 状态时,接收方采样一次 Link Control 2 Register 中的 Transmit Margin 比特,并保持有效
该值一直生效,直到下一次从 L0/L0s/L1 进入 Recovery 时才重新采样
五、8GT/s 速率切换与均衡
DSP 要切到 8GT/s 并重新均衡:发送 speed_change=1 且速率声明为 8GT/s 的 EQ TS1 序列
USP 连续收到 8 个 speed_change=1 且支持 8GT/s 的 EQ TS1/TS2 后,宣告支持 8GT/s
例外:USP 确认该速率下存在可靠性问题且均衡无法解决时,可以拒绝
六、速率通告的变更规则
仅允许在进入 Recovery 状态的那一刻改变自己通告支持的速率,且必须确保自身能稳定支持
在 RcvrLock、RcvrCfg、Equalization 这三个子状态期间,不得再改变通告速率
一句话概括
Recovery.RcvrLock 本质上是一个协商与准备阶段:双方通过 TS1 序列交换速率切换意图(speed_change)、去加重等级、发送电压等信息,并就是否切到 8GT/s 做均衡达成共识。核心原则是"进状态时定好规矩,状态中不许再改"。
转移至 Recovery.RcvrCfg 状态
进入 RcvrCfg 的核心条件是"收齐 8 个匹配的 TS1/TS2 且无需均衡",但如果之前已经走过均衡又退回来了,还要额外检查均衡参数有没有变化——变了就得在 TS2 里举旗请求重新均衡。本质上是确保双方对"要不要再均衡一次"达成共识后,才继续往下走。
转移至 Recovery.Equalization 状态
PCIe LTSSM 在 Recovery.RcvrLock 状态结束后,根据不同条件跳转到哪个后续状态。简单梳理一下:
一、什么时候进入 Recovery.Equalization(均衡)状态?
进入均衡状态有两条路径:
变量 start_equalization_w_preset = 1 时:这是首次切到 8GT/s 的场景。USP 先从收到的 TS2 里采样 preset 值并应用发送端 preset;DSP 则从自己的通道均衡控制寄存器中取发送端 preset。双方各自决定是否同时采用接收端 preset。
变量 start_equalization_w_preset ≠ 1 时:说明之前已经做过一次均衡,这次直接复用上次协商好的 coefficient 参数,不需要从头来。但此时如果 USP 收到 8 个连续的 TS1(speed_change=0 且 EC≠0),说明 DSP 主动要求重新做部分均衡流程。DSP 可以是软件触发或硬件实现自行决定,但必须确保链路上没有正在进行的传输。
需要特别注意:不能从 Configuration.Idle 或 Recovery.Idle 直接跳进均衡状态,必须经过 Recovery.RcvrLock 中转。而且 DSP 在发 EC≠0 的 TS1 请求重做均衡之前,不允许发超过两个 EC=00b 的 TS1。
二、24ms 超时后的四条出路
如果在 Recovery.RcvrLock 状态中待了 24ms 仍没满足上述任何均衡条件,就根据以下逻辑分流:
转 Recovery.RcvrCfg(速率协商成功路径):收到了 8 个连续的 TS1/TS2,链路和通道编号对得上,且 speed_change=1,同时当前速率已高于 2.5GT/s 或 TS 中表示还支持更高速率。简单说就是——双方同意换速率,准备走流程。
转 Recovery.Speed(速率回退路径):有两种子情况——
当前速率高于 2.5GT/s 但从来没在这个速率下成功工作过(changed_speed_recovery=0),那就退回 2.5GT/s。
之前某个高于 2.5GT/s 的速率是能正常工作的(changed_speed_recovery=1),但切换到新的协商速率后链路跑不起来,那就恢复成进 Recovery 之前用的速率。
转 Configuration(不需要换速率):没有发起速率改变请求(directed_speed_change=0 且 speed_change=0),或者双方共同支持的最高速率就是 2.5GT/s。这时候回到 Configuration 状态去重新做链路配置。
转 Detect(兜底):以上所有条件都不满足,说明链路已经没法正常通信了,回 Detect 重新开始检测。
一句话概括:Recovery.RcvrLock 的出口逻辑本质上是一个决策树——先看要不要做均衡,不做就看要不要换速率,换速率成功走 RcvrCfg,换失败走 Speed 回退,不换速率走 Configuration,啥都做不了就回 Detect 重新来过。

这是 PCIe 协议中一个速率变换的完整示例,讲的是两台设备从复位释放后,如何从 2.5GT/s 逐步尝试升到更高速率的过程。梳理如下:
第一阶段:初始训练
两台设备都支持 5GT/s 和 8GT/s,但复位后总是先以 2.5GT/s 完成链路训练进入 L0。训练过程中双方通过 TS 有序集的速率标识符互相告知"我支持更高速率",彼此心里有数。
第二阶段:发起升速
设备 A 主动发起,设 directed_speed_change=1,进入 Recovery.RcvrLock 并发出 speed_change=1 的 TS1。如果目标是 8GT/s 且链路从没到过这个速率,双方会交换 EQ TS1 来传递 TX 均衡 preset。
设备 B 收到 8 个连续 speed_change=1 的 TS1 后回应同样比特,进入 Recovery.Speed。设备 A 等到 B 回应后,经 Recovery.RcvrCfg 也进入 Recovery.Speed。
第三阶段:切换速率
Recovery.Speed 状态中双方发送端进入电气空闲,链路速率设为双方共同支持的最高值(8GT/s),directed_speed_change 清零。等待后双方回到 Recovery.RcvrLock,以新速率重新发 TS1(此时 speed_change 已为 0)。
第四阶段:成功或失败的分支
成功:新速率下链路正常,经 Recovery.RcvrCfg 回到 L0,以 8GT/s 稳定工作。
失败(比如设备 B 比特锁定失败):触发超时,B 回退到 Recovery.Speed。A 检测到对端电气空闲信号也跟着回退,双方速率恢复到 2.5GT/s,回到 Recovery.RcvrLock。
第五阶段:重试与降级策略
第一次失败后设备 A 可以再试一次。如果第二次还是失败,A 会把 8GT/s 从自己的支持速率列表中删掉,以 5GT/s 为目标重新走流程。如果 5GT/s 也跑不起来,就彻底放弃,老老实实待在 2.5GT/s。
至于什么时候放弃、怎么更新速率列表,协议没有硬性规定,留给具体实现自己决定。
一句话概括:先从 2.5 升 8GT/s,失败了退回来重试,再不行就降级到 5GT/s 再试,还不行就认命留在 2.5——一个先冲高、退而求其次的逐级降速策略

链路均衡
PCIe 8GT/s 链路均衡的完整四阶段流程。核心思路很简单:接收端评估信号质量,反过来告诉发送端该怎么调,双方迭代直到信号达标。PCIe 3.0 引入了主动握手式均衡:接收端实测信号,动态建议发送端该用什么参数,实现"量身定制"。
Phase 0:交接初始参数(DSP → USP)
DSP 在切到 8GT/s 之前,通过 EQ TS2 把均衡控制寄存器里的 Tx Preset 和 Rx Hint 发给 USP。这些是"出厂默认值"——一个粗略的起点,保证双方至少能勉强读到对方的信号。
USP 收到后把 preset 应用到自己的发送端,然后开始发 TS1 回应。如果 USP 能连续读出两个 TS1,说明信号质量达到了最低门槛(误码率 ≤ 10⁻⁴,即每万个比特错误不超过一个)。达标后 USP 把 EC 改成 01b,表示"初始参数能用,进入下一阶段"。
一句话:发初始参数,确认双方能互相读到信号
Phase 1:DSP 侧确认链路可用(DSP 主导)
DSP 做和 USP 一样的事——检测连续 TS1 来验证 10⁻⁴ 误码率。同时双方交换三个关键数值:
FS(Full Swing):发送端最大电压,是个相对参考值,最大取 63
LF(Low Frequency):参与定义最小电压,最小电压比例 = LF/FS
Post-cursor coefficient:三抽头均衡器的一个参数
这三个值的作用是让双方用整数来交流本质上带有小数精度的电压比例信息。
DSP 确认链路可用后,把 EC 设为 10b 发起 Phase 2。如果觉得信号已经够好了不需要继续调,直接发 EC=00b 退出均衡。一句话:确认链路基本可用,交换电压参数信息,为精细调节做准备
phase 2:USP 调优 DSP 的发送端(USP 主导)
此时信号能读但不够稳。USP 开始迭代调优 DSP 的发送参数,有两种调节粒度:
Tx Preset(粗调):USP 设定一个 preset 值,通过 Use Preset=1 告诉 DSP"用这个 preset"
Coefficients(细调):如果 Use Preset=0,DSP 保持 preset 不变,改调三个系数——
Pre-Cursor:调节采样点前一个时刻的信号影响
Cursor:调节采样点本身的信号,始终为正
Post-Cursor:调节采样点后一个时刻的信号影响
三抽头均衡器的本质就是:不只是看当前这一刻的信号,还兼顾前后时刻的信号残留,把码间干扰压到最小。
USP 不断改参数、评估、再改、再评估,直到信号质量达标。满意后把 EC 设为 11b,表示"DSP 侧调好了,该轮到你调我了"。
一句话:USP 反复试参数,把 DSP 的发送端调到最佳。
Phase 3:DSP 调优 USP 的发送端(DSP 主导)
和 Phase 2 完全对称,只是角色反转。DSP 现在当"评估者",用同样的 Preset 或 Coefficient 方式要求 USP 调整其发送端参数。每次变更至少持续 1μs,评估需要等 500ns 加上来回延迟。
DSP 不断尝试不同设置,直到找到让信号质量达标的参数组合。满意后发 EC=00b,均衡过程正式结束,链路以 8GT/s 稳定运行。
一句话:Phase 2 的镜像,DSP 调 USP 的发送端,调完收工。
整体逻辑:Phase 0 发默认值 → Phase 1 双方确认能通信 → Phase 2 USP 调 DSP → Phase 3 DSP 调 USP → 退出,链路就绪。本质上是一个"我先调你、你再调我"的乒乓式收敂过程,每一步都靠接收端实测信号质量来驱动下一步的参数调整。

链路均衡时涉及的状态。
DSP 侧(面向下游)
DSP 从 Phase 1 开始,进入时先把所有均衡状态标志位清零,equalization_done_8GT 置 1。
Phase 1(EC=01b):DSP 从均衡控制寄存器取 Tx Preset + FS/LF/Post-cursor 填进 TS1 发出去。收到的 TS1 中 EC=01b 就说明 USP 也在 Phase 1。此时 DSP 记下 USP 的 LF/FS 值,留给 Phase 3 用。如果 DSP 觉得信号已经够好不需要继续调,可以直接把所有状态位都设为 1 跳过后两阶段。24ms 超时未收到 TS1 则回退到 Recovery.Speed。
Phase 2(EC=10b):DSP 此阶段是"被动方"——根据 USP 的要求改自己的发送参数。收到合法参数就在 500ns 内应用,回 TS1 中反映变化,拒绝比特清零;收到非法参数就不改,但回 TS1 中仍原样回显参数值,拒绝比特置 1。32ms 超时未收到 USP 的 EC=11b 则回退 Recovery.Speed。
Phase 3(EC=11b):DSP 此阶段是"主动方"——调 USP 的发送端。可以发 Preset 变更(Use Preset=1)或 coefficient 变更(Use Preset=0)。每次变更至少持续 1μs,评估等 500ns 加往返延迟。收到 USP 回显的参数一致且拒绝比特=0 说明采纳了,可以评估;拒绝比特=1 说明 USP 拒了,建议换个值重试(非强制)。单次请求不超 2ms,整个 Phase 不超 24ms(允许两次例外延期)。全部通道调完后进 Recovery.RcvrLock,标志位置 1。24ms 超时则回退
USP 侧(面向上游)
USP 从 Phase 0 开始,进入时同样清零所有标志位。
Phase 0(EC=00b):USP 用 DSP 之前通过 EQ TS2 传来的 preset 发 TS1。如果没收到 EQ TS2 或 preset 非法,由具体实现自行决定用什么值。收到两个连续 EC=01b 的 TS1 说明能正确识别 DSP 信号,进 Phase 1。同时记录 DSP 的 LF/FS 留给 Phase 2 用。12ms 超时则回退。
Phase 1(EC=01b):USP 用 Phase 0 决定的发送设置继续发 TS1,带 FS/LF/Post-cursor。收到 EC=10b 的 TS1 说明 DSP 要进 Phase 2,跟着进;收到 EC=00b 说明 DSP 觉得不用调了,直接结束均衡。12ms 超时则回退。
Phase 2(EC=10b):USP 此阶段是"主动方"——调 DSP 的发送端。机制和 DSP Phase 3 完全对称:发 Preset 或 coefficient 变更请求,至少持续 1μs,等 500ns 加往返延迟后评估。收到的回显参数一致且拒绝比特=0 说明采纳了;拒绝比特=1 说明被拒,建议重试。所有通道调完后进 Phase 3。24ms 超时则回退。
Phase 3(EC=11b):USP 此阶段是"被动方"——根据 DSP 的要求改自己的发送参数。收到合法参数 500ns 内应用,回显参数值,拒绝比特清零;收到非法参数不改,但回显参数值,拒绝比特置 1。收到 DSP 发来的 EC=00b 说明均衡结束,标志位置 1,进 Recovery 次状态。32ms 超时则回退。
整体:DSP 先给 USP 初始值 → 双方互验信号可读 → USP 调 DSP → DSP 调 USP → 收工
几个贯穿全程的机制:
参数拒绝机制:收到非法/不支持的参数不改设置,但仍原样回显参数值并把拒绝比特置 1,让对方知道"你发的我不接受"
超时兜底:每个阶段都有超时(12ms/24ms/32ms 不等),超了就回 Recovery.Speed 降速率,不死等
通道独立:每条通道独立调参,某条通道不改就不用发变更请求,但变更请求要同时在所有需要的通道上发
时间约束:单次参数变更请求到评估完成不超 2ms,整个 Phase 不超 24ms(最多两次例外延期),参数变化不能导致超过 1ns 的非法电压
拒绝比特(Reject Coefficient)是 TS1 序列中均衡信息域里的一个标志位,用来告诉对方"你刚才要求的参数我不接受"。、、、
Recovery.Speed” 状态

如果直到 32ms 超时,仍然没能满足上述条件,那么接下来转入 Recovery.Speed 状态,successful_speed_negotiation 标志清除为 0b,并将均衡完成(Equalization Complete)状态比特设置为 1b
进入时做什么
设备先让自己的发送端进入电气空闲(发 EIOS),然后等接收端也进入电气空闲。只有双方都"安静"下来了,才能改速率。
进入电气空闲前发几个 EIOS 取决于当前速率:2.5GT/s 和 8GT/s 发一个,5GT/s 发两个。
等多久
等待时间取决于速率协商是否成功:
成功(successful_speed_negotiation=1):至少等 800ns,因为双方还能正常通信,情况乐观
失败(successful_speed_negotiation=0):至少等 6μs,因为至少一方读不到对方的 TS 序列,需要更长时间确保链路真的静下来了
两种情况都不要超过额外 1ms。
怎么判断电气空闲
这里分两种情况,对应"还能通信"和"已经不能通信":
协商成功时:双方还能互相读到 TS1/TS2,所以只要一段时间没看到 TS 序列,就推断对方已经进入电气空闲了。不同状态和速率下的判断窗口不同,比如 Recovery.RcvrCfg 中 2.5/5GT/s 是 1280 UI 没看到 TS1/2,8GT/s 是 4ms 没看到。
协商失败时:至少一方读不到对方的 TS 序列,没法靠"没看到 TS"来判断了。改用"一段时间内没看到电气空闲退出序列"来反推对方已经静下来。这个窗口更长,2.5GT/s 是 2000 UI,5/8GT/s 是 16000 UI。
改速率时的一些细节
如果已经是双方支持的最高速率,进来也不会改
如果新速率是 5GT/s,去加重水平由 select_deemphasis 变量决定:0 表示用 -6dB,1 表示用 -3.5dB
协议没有要求在此状态保持共模电压在规定范围内——这是个有意思的例外
改完后更新什么
directed_speed_change 清零,新速率写入链路状态寄存器的 Current Link Speed 域。
带宽状态标志位的设置逻辑:如果是 DSP 硬件自发发起的速率改变,或者 TS2 中 Autonomous Change 比特连续 8 个都为 1,设为"链路自发带宽状态"位;否则(可靠性问题或软件触发的)设为"链路带宽管理状态"位。简单说就是区分"我自己想换的"和"被动换的"。
离开时去哪
超时后回到 Recovery.RcvrLock,但新速率怎么定有三种情况:
第一次进来且协商成功:速率设为双方共同支持的最高值,changed_speed_recovery 置 1,回去以新速率重新训练
第二次进来(changed_speed_recovery 已经是 1):说明上次换到新速率后跑不起来又回来了,恢复成进 Recovery 之前用的速率,changed_speed_recovery 清零
第一次进来但协商失败:直接降到 2.5GT/s 保底,changed_speed_recovery 保持 0
如果以上条件都不满足,说明链路双方已经彻底无法通信,回 Detect 重新从零开始检测。
一句话概括:Recovery.Speed 是"换挡室"——双方先安静下来,根据协商结果决定升速、回退还是保底 2.5GT/s,换完挡回 Recovery.RcvrLock 用新速率重新锁定。第一次冲高失败就回退到之前的速率,再不行就降到 2.5GT/s 兜底,实在不行就回 Detect 重新来过。

Recovery.RcvrCfg:决策路口
只能从 Recovery.RcvrLock 跳进来,前提是已经重新建立了比特和符号锁定。进来之后要决定"接下来干啥"。
在本状态中做的事:
发 TS2,带协商好的链路和通道编号。如果 directed_speed_change=1,TS2 中 speed_change 也置 1
更新 N_FTS 为当前速率匹配值,供将来退出 L0s 时用
start_equalization_w_preset 清零
successful_speed_negotiation 置 1(表示速率协商成功了)
8b/10b 编码下离开前必须完成通道间去偏斜
提取并保存对端的速率标识符,覆盖之前的值
128b/130b 编码下记录 Request Equalization 比特
如果要切到 8GT/s,DSP 发 EQ TS2,USP 收到后把 start_equalization_w_preset 置 1 并更新均衡寄存器
如果某个通道没收到 EQ TS2,该通道自己决定用什么 preset
从这里出去有五条路:
1. 转 Recovery.Idle(不需要换速率):收到 8 个连续 TS2,链路/通道/速率标识符都对得上,speed_change=0 或者双方共同最高速率就是 2.5GT/s。发够 16 个 TS2 且没被 EIEOS 打断。changed_speed_recovery 和 directed_speed_change 清零。
2. 转 Recovery.Speed(要换速率):收到 8 个连续 TS2 且 speed_change=1,双方共同最高速率高于 2.5GT/s。编码方式不同发 TS2 的数量要求也不同:8b/10b 至少 32 个,128b/130b 至少 128 个,都不能被 EIEOS 打断。还有两种被动回退的情况:一种是之前已经成功换到高速率(changed_speed_recovery=1),但对方突然发 EIOS 或进入电气空闲且没回 TS2,说明对方在新速率下跑不起来,退回之前的速率;另一种是从来没成功换过高速率(changed_speed_recovery=0),但当前速率高于 2.5GT/s 时对方掉线了,说明当前速率也撑不住,降到 2.5GT/s。
3. 转 Configuration(要改链路宽度):收到 8 个连续 TS1 但链路或通道编号跟自己发的不一致,且 speed_change=0,说明对方想重新配置链路。变量清零,N_FTS 如果更新了留着给 L0s 用。
4. 转 Detect(兜底):48ms 超时什么都没满足。2.5/5GT/s 直接回 Detect。8GT/s 有个特殊缓冲机制:如果 idle_to_rlock_transitioned 还没到 FFh,先回 Recovery.Idle 再试一轮,只有试到 FFh(255 次)才彻底放弃回 Detect。
Recovery.Idle:收尾缓冲区
发 Idle 序列作为进入 L0 前的过渡。8b/10b 直接发 Idle 数据;128b/130b 先发 SDS 序列开始数据流,再发 Idle 符号。
从这里出去有六条路:
1. 转 L0(正常进入工作状态):收到连续 8 个周期空闲数据,且收到第一个空闲数据后已发够 16 个空闲数据符号。128b/130b 编码下不能从 Recovery.RcvrCfg 直接跳 L0,必须经过本状态中转。空闲数据必须在数据块中,去偏斜必须在数据流开始前完成。进入 L0 时 idle_to_rlock_transitioned 清零。如果之前设过 Retrain Link 比特,DSP 会把链路带宽管理状态比特置 1。
2. 转 Configuration(改链路宽度):上层指令要重新配置链路,或者收到两个连续 TS1 且通道编号为 PAD(对方想改链路宽度的信号)。协议建议直接转以省时间。
3. 转 Disable(链路关闭):收到两个连续 TS1 中 Disable Link 比特都为 1。
4. 转 Hot Reset(热复位):收到两个连续 TS1 中 Hot Reset 比特都为 1。
5. 转 Loopback(回环测试):自己有当 Loopback 主机的能力且收到上层指令使能 Loopback 比特,或者收到对方两个连续 TS1 中 Loopback 比特为 1,则自己当从机。
6. 转 Detect(兜底):以上都没满足,2ms 超时后回 Detect。但 8GT/s 下如果 idle_to_rlock_transitioned < FFh,不直接放弃,而是回 Recovery.RcvrLock 再试一轮,并且每试一次递增 1。5GT/s 和 2.5GT/s 直接设为 FFh,不享受这个重试缓冲。
一句话概括:Recovery.RcvrCfg 是"锁定后的决策站"——不需要换速率的去 Idle 准备进 L0,要换速率的去 Speed 换挡,要改宽度的回 Configuration,搞不定的回 Detect 重新检测。Recovery.Idle 则是"进 L0 前的缓冲带"——除了正常进 L0,还能被上层指令拉去改宽度、关闭、热复位、回环测试,或者超时兜底回 Detect。


L0s State // L0s 状态
L0s 是一个低功耗链路状态,L0s 返回 L0 状态的退出延迟最短。设备通过硬件逻辑自动控制进出 L0s 状态的行为,无需软件参与。链路双方都可以单独地进入或者退出 L0s 状态
 
 

L0s 发送端状态机
链路地发送端和接收端有不同的 L0s 次状态,首先讨论发送端的 L0s 各次状态
Tx_L0s.Entry(进入态)
触发条件:上层的电源管理逻辑发出命令,要求进入 L0s。协议本身没有硬性规定触发条件,但实际设计中通常是——该方向空闲了一段时间(既没发 TLP 也没发 DLLP),就主动进入 L0s。
动作:发送端发出 EIOS(Electrical Idle Ordered Set),使链路进入电气空闲状态。在 5GT/s 速率下需要发送两个 EIOS。
关键细节:进入电气空闲后,发送端并没有完全关闭,仍然需要维持共模电压在协议规定范围内。这一点很重要——电气空闲 ≠ 掉电,差分线对的电压差为零,但共模电平还在保持,这样才能保证接收端的电路正常工作。
状态转移:经过 T_TX-IDLE-MIN(20ns) 的延时后,转入 Tx_L0s.Idle 状态。这个 20ns 的延时是为了确保发送端确实已经稳定地进入了电气空闲状态,避免在过渡期间产生毛刺或误判。
Tx_L0s.Idle(空闲保持态)
状态描述:发送端持续保持电气空闲状态,直到上层逻辑命令退出。
核心作用:由于这个方向的差分驱动器处于电气空闲,不再翻转,功耗显著降低——这就是 L0s 状态的主要节能来源。
与 L0 的区别:L0s 只让一个方向的发送端空闲(比如 Tx 方向),接收端(Rx 方向)可能仍在正常工作。所以它是非对称的,链路两端可以各自独立进入 L0s,互不影响。
状态转移:当上层逻辑需要恢复发送时(比如有 TLP 要发出),状态机转入 Tx_L0s.FTS 状态。具体的退出触发机制由设计实现决定,协议给了自由度。
Tx_L0s.FTS(快速恢复训练态)
这是从 L0s 恢复回 L0 正常工作的关键过渡态:
核心动作:发送端持续发送 FTS(Fast Training Sequence)有序集,用来重新训练对端接收端,使其恢复比特锁定和符号对齐。
FTS 数量的确定:
正常情况下,发送 N_FTS 个 FTS 有序集。这个 N_FTS 值来自——对方在上次进入 L0 状态的训练过程中,通过 TS 训练序列通告的 N_FTS 字段。也就是说,每台设备会告诉链路对端:"我从 L0s 恢复时,需要你给我发多少个 FTS"。
容错机制:如果对端响应超时(即发了 N_FTS 个之后对端还没恢复锁定),发送端可以增加 FTS 数量,超过对方在 Recovery 状态中通告的 N_FTS 值。这是协议允许的弹性处理。
Extended Synch 特性:如果 Extended Synch 比特被置位,发送端必须发送 4096 个 FTS(而非 N_FTS 个)。这个特性的目的是给外部测试/分析设备更长的同步时间——这些设备可能不像正常 PCIe 对端那样能快速恢复比特锁定,需要更多训练序列来"跟上节奏"。
序列前的特殊符号要求(不能乱发 FTS,前面要先发特定符号):
任何速率下:FTS 序列之前不能发 SOS(Start of Stream)序列。
5GT/s 速率:必须在 FTS 之前发送 4~8 个 EIE 符号(Electrical Idle Escape)。EIE 的作用是"唤醒"对端的电气空闲检测电路,让它知道接下来要来真信号了。
128b/130b 编码(8GT/s 及以上):必须在 FTS 之前发送一个 EIEOS 序列(Electrical Idle Exit Ordered Set),这是 128b/130b 编码下退出电气空闲的标准信号。
转移回 L0 状态
发送端在发完所有必需的 FTS 序列后,还需满足以下条件才能正式转回 L0 正常工作状态
8b/10b 编码下(2.5GT/s 和 5GT/s):在 FTS 序列发送完毕后,需要在所有已配置的 Lane 上再发送一个 SOS(Start of Stream)序列。SOS 的作用是告诉接收端"训练结束了,后面开始是正常数据流"。
128b/130b 编码下(8GT/s 及以上):首先发送一个 EIEOS 序列,然后跟一个 SDS(Start of Data Stream)序列,最后接上正常数据流。EIEOS 负责退出电气空闲,SDS 则标记数据流的起始边界。
简单说就是——FTS 发完不算完,还得补一个"开始正常数据"的标志序列,编码方式不同,这个标志的形态也不同。

L0s 接收端状态机
 
收到对端的 EIOS
       ↓
  Rx_L0s.Entry
  (等待 20ns 稳定)
       ↓
  Rx_L0s.Idle
  (电气空闲,等待退出信号)
       ↓  (检测到 EIEOS / 电压恢复)
  Rx_L0s.FTS
  (吃 FTS,恢复比特和符号锁定)
       ↓                    ↓ (超时失败)
    回到 L0            进入 Recovery
  (收到 SOS/SDS)       (完整重新训练)


L0s 状态中接收端(Rx)的各次状态。接收端是被动的一方——它不像发送端那样主动决定"我要进入 L0s",而是检测到对端的行为后做出响应。
Rx_L0s.Entry(进入态)
前提条件:接收端必须支持 L0s,并且当前不满足进入 L1 或 L2 的条件(因为 L1/L2 优先级更高,能进更深的节能态就不会停留在 L0s)。
触发条件:接收端收到对端发来的 EIOS(Electrical Idle Ordered Set)。这就是发送端进入 L0s 时发出的那个信号——发送端发 EIOS 表示"我这边要空闲了",接收端收到后就知道链路要进入电气空闲了。
状态转移:经过 T_TX-IDLE-MIN(20ns) 的延时后,进入 Rx_L0s.Idle 状态。这个延时和发送端那边一模一样,都是 20ns,目的是确保电气空闲状态稳定下来。
Rx_L0s.Idle(空闲保持态)
状态描述:接收端处于电气空闲状态,等待对端恢复发送时退出。
状态转移:接收端检测到 EIEOS(或者旧式电路检测到电压恢复),转入 Rx_L0s.FTS 状态。
Rx_L0s.FTS(恢复训练态)
状态描述:接收端已经检测到链路退出电气空闲,现在正在从输入比特流中重建比特锁定和符号/块锁定。此时输入的比特流就是对端发送的 FTS 有序集——发送端在 Tx_L0s.FTS 中不断发 FTS,接收端在这里"吃"这些 FTS 来恢复接收能力。
锁定的意义:高速串行链路中,接收端需要从连续比特流中找到比特边界(哪个比特是一个符号/块的起点)和符号边界(一组比特组成一个有效符号)。电气空闲期间没有信号,锁相环可能漂移,所以需要 FTS 重新训练。
从 Rx_L0s.FTS 转移到 L0 状态
接收端在接收 FTS 的过程中,如果满足以下条件就成功回到 L0:
8b/10b 编码:在所有已配置 Lane 上收到 SOS 有序集。 128b/130b 编码:在所有已配置 Lane 上收到 SDS 有序集。
SOS 和 SDS 的意义和发送端那边说的完全对应——发送端发完 FTS 后发 SOS/SDS,接收端收到 SOS/SDS 就知道"训练结束,后面是有效数据了"。
两个额外要求:
接收端必须有能力在转入 L0 后迅速接收有效数据,不能有额外延迟。
在状态转移之前必须完成Lane 间的去偏斜(deskew)操作——因为多 Lane 链路中各 Lane 的走线长度不完全一致,信号到达时间有微小差异,接收端需要通过 FTS 把这些偏斜补偿掉,确保各 Lane 的数据对齐。
从 Rx_L0s.FTS 转移到 Recovery 状态(恢复失败兜底)
如果对端发了 N_FTS 个 FTS,但接收端仍然没能完成锁定、没能满足进入 L0 的条件,说明恢复失败了。这时候:
接收端进入 Recovery 状态,发起完整的链路重新训练流程——L0s 的快速恢复路径走不通了,退而求其次走更彻底但更慢的 Recovery。
发送端也需要进入 Recovery,不过在此之前可以先把在途的 TLP 或 DLLP 发完,不浪费已发出的数据。
协议建议:发送端在超时后提高 N_FTS 的值,降低下次再次超时的概率。
8b/10b 编码的超时计算
最小超时 = 40 × (N_FTS + 3) × UI,最大超时 = 最小超时的两倍。
其中 UI 是 1 bit 的传输时间。换算成符号数(每个符号 10 bit):
40 × (N_FTS + 3) × UI ÷ 10 = 4 × (N_FTS + 3) = 4×N_FTS + 12 个符号。
这 12 个额外符号的构成:
6 个符号:SOS 有序集的最大长度(SOS 最长就是 6 个符号)。
4 个符号:额外的 FTS 容量——给发送端一点余量,万一需要多发几个。
2 个符号:符号间的间隔余量,吸收实现中的微小抖动偏差。
一句话概括:最小超时时间 = 发送 N_FTS 个 FTS + 12 个符号所需的时间;最大超时 = 这个值的 2 倍。
Extended Synch 置位时:最小超时对应 2048 个 FTS,最大超时对应 4096 个 FTS(因为此时必须发 4096 个 FTS,给一半到全部的时间窗口)。
超过 2.5GT/s 时:还要额外加上 4~8 个 EIE 符号的时间,因为发送端在 FTS 之前要先发 EIE 符号。
128b/130b 编码的超时计算
最小超时 = 130 × (N_FTS + 5 + 12 + Floor(N_FTS/32)) × UI,最大超时 = 前者的两倍。
130 × UI 是一个块(block)的传输时间,所以换算后就是 N_FTS + 5 + 12 + Floor(N_FTS/32) 个块。
各项的含义:
Floor(N_FTS/32):发送端需要每隔 32 个 FTS 插入一个 EIEOS,所以发 N_FTS 个 FTS 期间需要插入 Floor(N_FTS/32) 个周期性 EIEOS。
5 个块:分别对应以下 5 个额外 EIEOS 相关的开销——第一个 EIEOS(序列开头)、最后一个 EIEOS(序列结尾)、SDS 有序集、周期性 EIEOS 的额外预留、以及一个额外 EIEOS(防止发送端选择在 SDS 之后发送两个 EIEOS 的极端情况)。
12 个块:当 Extended Synch 置位时,需要发送 12 个 SOS(128b/130b 编码下 Extended Synch 的特殊要求)。此时 N_FTS 被设定为 4096。
超时计算的共同逻辑
不管哪种编码,超时窗口的设计思路是一致的:最小超时 = 正常完成恢复所需的理论时间(含所有必需符号),最大超时 = 最小值的 2 倍。这样既给了足够的容忍度,又不会让故障检测无限拖延。

L1 State // L1 状态
L1 是一个相较于 L0s 状态更加激进地减少功耗的链路电源状态,作为代价,L1 会有更长的低功耗状态退出延迟。和 L0s 一样,L1 是 ASPM 可选支持的一项状态,也就是说设备可以在没有软件参与的情况下,由硬件自动进入或者退出低功耗状态。然而,和 L0s 不同的是,软件也可以直接命令 USP 发起链路进入 L1 状态的改变,通过将设备电源状态改变为更低功耗的状态(D1,D2,或者 D3)做到这一点。L1 状态和 L0s 状态的不同点还在于,L1 状态会影响链路双向的状态,而 L0s 只作用于链路单个方向。
 
当设备在检测到链路进入电气空闲状态时,会认为链路伙伴希望将链路转为 L0s,L1 或者 L2 等低功耗状态,为了区分到底应该进入哪种低功耗状态,在进入 L1 状态之前,链路双方会首先达到一致。通过握手机制使链路双方明白对方已经准备就绪,此时可以安全地继续进入 L1 的流程。
L1 Entry(进入态)
进入 L1 有两条路径,取决于你是上游端口(USP)还是下游端口(DSP),流程相反:
USP 的进入流程:
USP 主动发起进入 L1 的请求(触发来源可以是 ASPM 自动发起,也可以是软件/驱动主动配置)。
发出请求后,等待对端(DSP)的回复。
收到 DSP "可以进入 L1" 的肯定答复后,USP 进入 L1.Entry 次状态。
DSP 的进入流程:
DSP 处于被动地位——它必须收到 USP 发来的 L1 请求。
DSP 做出决定,回复"同意进入 L1"。
之后 DSP 等待链路上出现 EIOS,在收到 EIOS 的 Lane 上使这些 Lane 进入电气空闲,然后进入 L1.Entry 次状态。
进入电气空闲后的要求:所有已配置 Lane 的发送端发 EIOS 进入电气空闲,但和 L0s 一样——此时主电源没断,所以必须保持合适的直流共模电压。差分为零不代表共模也为零,共模电平仍然在维持。
从 L1.Entry 到 L1.Idle
经过 T_TX-IDLE-MIN(20ns) 延时后,转入 L1.Idle 次状态。这个 20ns 的含义和 L0s 完全一致——确保发送端已经稳定地进入了电气空闲状态,避免过渡期的毛刺被误判。
L1.Idle(空闲保持态)
发送端继续保持电气空闲。但这里多了一条 L0s 中没有的规定:
当速率超过 2.5GT/s 时,LTSSM 必须在该次状态至少停留 40ns。
这 40ns 的用途是:给接收端的电气空闲检测电路留出准备时间。想象一个场景——链路刚进 L1,20ns 后又因为某种原因要退出,如果检测电路还没"热身"好,就可能完全检测不到这次 L1 的进出,像是从未发生过。这 40ns 就是防止这种"闪进闪出"导致状态丢失的安全垫。
从 L1.Idle 退出
触发条件:发送端收到上层指令退出 L1,或者接收端检测到链路退出了电气空闲。常见的退出原因包括:
发送端有 TLP 或 DLLP 要发送了。
希望改变链路宽度或速率。
关键区别——L1 退出直接进 Recovery,不走 L0s 那种 FTS 快速恢复路径。 这是因为 L1 是更深的节能状态,电气空闲持续时间可能很长,锁相环已经漂移得很厉害,光靠 FTS 不一定能恢复,需要走完整的链路重新训练流程。
速率改变的快捷通道:如果退出的目的是为了改变链路速率,端口可以:
设置 directed_speed_change = 1b("我要改速率")。
清除 changed_speed_recovery = 0b("还没改完,标记复位")。
然后直接从 L1 进入 Recovery,而不是像通常那样从 L0 进 Recovery。这是一条捷径——省去了"先回 L0、再从 L0 进 Recovery"的绕路。

L1(也叫 L1.0):PCIe 1.0 就定义的基础深度低功耗状态。链路空闲时通过 DLLP 协商进入,关闭 TX 和 RX 的高速模拟电路,但参考时钟(Refclk)和 PLL 保持运行,共模电压也保持。
L1.1:PCIe 2.0 引入的 L1 子状态(L1 Substate)。在 L1.0 基础上进一步关闭部分 PHY 电路和参考时钟,但共模电压仍保持。
L1.2:最深的 ASPM 低功耗状态。在 L1.1 基础上进一步关闭共模电压、PLL、PHY 几乎所有有源逻辑,甚至参考时钟也可以完全停止,仅保留 CLKREQ# 唤醒监听和必要的配置状态。
状态转换关系
L0 (正常工作)
 ↓  (链路空闲,ASPM 或软件触发)
L1.0 (基础 L1)
 ↓  (CLKREQ# deassert,L1.1 Enable)
L1.1 (关闭 Refclk,保持共模)
 ↓  (CLKREQ# deassert,L1.2 Enable + LTR 条件满足)
L1.2 (关闭一切,仅留 CLKREQ# 监听)
退出时反向走:L1.2 → L1.0 → Recovery → L0,而 L1.2 的退出往往需要更完整的链路恢复过程(甚至可能经过 Detect → Polling → Configuration → Recovery → L0)。
四、关键机制:CLKREQ#
L1.1 和 L1.2 的进入和退出都依赖 CLKREQ# 这个边带信号:
进入:链路双方都同意进入子状态后,各自将 CLKREQ# 释放(deassert,即开漏输出变为输入),当 CLKREQ# 线被上拉电阻拉高时,表示双方一致同意,开始进入 L1.1 或 L1.2。
退出:任一方需要通信时,拉低 CLKREQ#(assert),上游设备检测后重新开启 Refclk → PHY 恢复供电 → PLL 重新锁定 → 链路进入 Recovery → 恢复 L0。
五、LTR 机制对 L1.2 的约束
L1.2 不是想进就能进的。ASPM 方式进入 L1.2 需要满足 LTR(Latency Tolerance Reporting) 条件:
设备上报的 snoop / non-snoop 服务延迟必须 ≥ L1.2_THRESHOLD 寄存器设定的值
如果设备对延迟敏感(如实时音频设备),LTR 值较小,就会阻止链路进入 L1.2,只允许进入 L1.1
这是系统在功耗和延迟之间做平衡的关键机制

二、L2 状态
L2 vs L1 的本质飞跃
L1 节能靠的是"电气空闲但不掉电",主电源还开着。L2 更狠——直接切断主电源,设备只剩备用电源 Vaux 维持最低限度的逻辑运行。这是一个系统级的功耗管理状态,涉及的不只是 PCIe 链路层,还牵涉到系统电源管理软件和主电源控制。
进入 L2 的前提条件
设备必须处于 D3Cold 电源状态(设备级功耗管理中最深的状态)。
完成相应的链路握手流程。
电源管理软件下达命令。
由 USP 发起进入 L2 的请求(和 L1 类似,USP 是发起方)。
链路两个方向都进入 L2。
主电源切断后的分支
主电源被切掉后,设备的命运取决于备用电源 Vaux 是否可用:
Vaux 可用 → 进入 L2:设备用 Vaux 维持最低限度的唤醒检测逻辑,等待唤醒信号。
Vaux 不可用 → 进入 L3:连备用电源都没有,设备彻底断电,什么都做不了,只能等系统重新上电。
L2 的唤醒机制
设备在 L2 中虽然主电源断了,但 Vaux 还在,它需要监听"是否该唤醒系统"。有两种标准方法:
方法一——WAKE# 边带信号:通过一根专门的引脚 WAKE# 发出唤醒信号。这是边带(side-band)方式,独立于 PCIe 链路的差分线对。WAKE# 对 L2 来说是可选的——你可以用,也可以不用。
方法二——Beacon 链路内信号:直接在 PCIe 链路的差分线上发出一种低频信号(30 KHz ~ 500 MHz),不借助额外引脚。这是链路内(in-band)方式。
关于 Beacon 有几个重要细节:
WAKE# 和 Beacon 的关系:如果设备使用 Beacon,那么 WAKE# 是必须的;反过来 WAKE# 不要求 Beacon。逻辑是——Beacon 需要额外的接收能力来检测低频信号,而 WAKE# 是更通用的方式。
Beacon 的方向:只有 USP 可以发 Beacon(DSP 只能收)。这是因为唤醒信号要往上游走——最终目标是到达 RC(Root Complex),因为 RC 是系统电源控制逻辑的所在之处,只有它才能决定"要不要给主电源上电"。
交换机的转发职责:如果交换机的 DSP(下游端口)收到了下游设备发来的 Beacon,交换机必须通过其 USP(上游端口)继续往上游转发,让 Beacon 一级一级传到 RC。
Beacon 是过时特性:协议明确指出,工作在 5.0 或 8.0 GT/s 的设备无需支持 Beacon。所以 Beacon 本质上是一项 legacy 特性,只服务于 2.5GT/s 的老设备。新设备走 WAKE# 就够了。
如何区分 L0s、L1、L2?
发送端进入电气空闲可能代表想进 L0s、L1 或 L2 中的任何一种,那对端怎么知道到底是哪种?答案是——在进入 L2 之前,链路双方会先通过握手机制达成一致,明确知道对方要进入的是 L2 而不是 L0s 或 L1。L0s 是单方面决定的(不需要握手),L1 是双方协商的,L2 则需要更完整的握手——确保双方都准备好了才切电源。
L2.Idle 次状态
进入 L2.Idle 前,必须满足:
所有必须的握手流程已在链路两个端口之间完成。
端口已经发送或接收到所需的 EIOS 序列。
在 L2.Idle 中的行为:
发送端:进入电气空闲,至少等待 T_TX-IDLE-MIN(20ns)。与 L0s/L1 的关键区别——此时主电源已被关闭,所以发送端无需保持直流共模电压在协议规定范围内。电源都没了,共模维持无从谈起。
接收端:在 20ns 延时结束前不会开始检测链路是否退出电气空闲。所有接收端必须打开终结(termination)功能,处于低阻抗模式——这是为了让链路在主电源断开后保持一个已知的电气状态,防止浮空导致噪声。
从 L2.Idle 的两条出路
转至 L2.TransmitWake:如果 USP 被上层命令开始发送 Beacon,进入 L2.TransmitWake 状态。Beacon 会沿着交换机一级一级往上游传,最终送到 RC。如果交换机的 DSP 收到下游来的 Beacon,交换机必须使其 USP 转入 L2.TransmitWake 并继续往上游发 Beacon。
转至 Detect:当主电源恢复后,转至 Detect 状态——这是链路从零开始重新初始化的起点。具体来说:主电源恢复后,如果端口在预定义的 Lane 上检测到电气空闲退出(意味着对端也上电了),就把这些 Lane 协商为 Lane 0(多 Lane 链路必须至少有 2 个预定义 Lane),然后进入 Detect。如果是交换机的 USP 经历了这个过程,它还必须将其 DSP 也设为 Detect 状态——确保交换机上下两侧都开始重新初始化。
L2.TransmitWake 次状态
在这个状态中,发送端(具体说是 USP)至少在 Lane 0 上持续发送 Beacon。注意这个状态仅作用于 USP——因为只有 USP 才有向 RC 唤醒的需求,DSP 不需要发 Beacon。
退出条件:如果 USP 的任意接收端检测到链路退出了电气空闲,转至 Detect 状态。当然,此时双方设备的主电源必须已经恢复——没有电,电气空闲退出无从谈起,谁也退出不了。
三、整体功耗状态层次总结
节能深度浅 ──────────────────────────────────→ 节能深度深

  L0s          L1              L2              L3
  ─────         ─────           ─────           ─────
  单方决定      双方协商        系统级断电      彻底断电
  电气空闲      电气空闲        主电源切断      无Vaux
  保留共模      保留共模        无需共模        全断
  FTS快速恢复   Recovery恢复    Detect重初始化  系统重启
  ~μs级恢复    ~数十μs恢复     ~ms级恢复       不可恢复
几个贯穿三层的设计递进逻辑:
协商复杂度递增:L0s 不需要协商,L1 需要双方握手,L2 需要系统级握手+切电源。节能越深,进入门槛越高。
共模电压的处理递进:L0s/L1 维持共模(主电源在),L2 不维持(主电源断了),L3 无所谓(什么都没了)。
恢复路径递增:L0s 走 FTS(秒级恢复,最快),L1 走 Recovery(完整链路训练),L2 走 Detect(从零开始的链路初始化),L3 靠系统重启。节能越深,恢复代价越大。
唤醒机制只在 L2 出现:L0s 和 L1 不需要唤醒——因为主电源一直在,随时能退出。只有 L2 把主电源切了,才需要 Beacon/WAKE# 这种专门的唤醒手段把"我需要电"的消息送到 RC。
USP 始终是发起方:无论是 L1 的进入请求还是 L2 的进入请求,都是 USP 先发起。Beacon 也是 USP 发。这反映了 PCIe 拓扑中"下游设备向上游提出需求、上游设备做决策"的基本交互模式。


Dynamic Bandwidth Changes // 动态带宽改变
允许链路在运行过程中动态调整速率和宽度,按需在性能和功耗之间切换。
两大能力
速率改变——切换链路速率(比如从高代降到低代),发起方通过发送 TS1 驱动双方 LTSSM 进入 Recovery 状态,完成切换后返回 L0。
宽度改变——切换链路宽度(比如 x16 降到 x1),流程比速率改变多一步,LTSSM 需要先经过 Recovery 再进入 Configuration 状态重新协商宽度,之后以新宽度返回 L0。
三大优势
中断时间短——带宽改变期间链路始终在工作,对业务传输的中断非常短,远好于直接切电源状态。
省电更狠——一条 x16 链路缩成 x1 工作时,功耗甚至比 x16 处于 L0s 状态还低。
可靠性兜底——如果过高的速率导致信号完整性问题,设备可以从自己通告的支持速率列表中移除较高的速率来降速稳链路。协议本身不规定设备如何做出这个可靠性判断,而且除了走带宽改变流程,设备也可以直接进 Recovery 重新通告不同的速率支持列表来解决,算是留了两条路。

Dynamic Link Speed Changes //动态链路速率改变
Gen1 协议原本把速率切换放在 Polling 状态进行,到了 Gen2 改为在 Recovery 状态完成,这也是目前速率改变流程所走的状态路径。
TS1 中的关键字段
速率改变的核心信息都携带在链路双方交换的 TS1 序列中,其中最关键的是 Byte4 的速率标识符(Rate Identifier):
比特 1/2/3:分别表示设备支持哪些速率。规则是所有设备必须支持 2.5 GT/s;如果支持 8.0 GT/s,则必须也支持 5.0 GT/s,呈递进关系。
比特 6:含义取决于端口朝向(上游/下游)和当前 LTSSM 状态。在速率改变场景下,只有 USP(上游端口)发出的 TS1 中该比特有意义,表示这次速率改变是否为自发行为——即出于自身硬件原因主动请求,而非为了解决可靠性问题。
比特 7:USP 将其置 1 来正式发起速率改变请求。
TS1 与 TS2 的区别
TS1 和 TS2 各字段含义非常接近,但 TS2 的比特 6 含义不同,它关联的是自发的链路宽度改变,跟速率改变无关,速率改变的本质就是:上游端口在 TS1 中通过速率标识符通告支持的速率列表,通过比特 7 发起请求,通过比特 6 标明是否自发,驱动双方进入 Recovery 完成切换。

Upstream Port Initiates Speed Change // USP 发起的速率改变
速率改变必须由 USP(上游端口,通常是 EP 设备)发起,DSP 不能主动发起。USP 内部的 Directed Speed Change 标志位在硬件条件满足后被置起,触发整个流程。链路最终会选择双方共同支持的最高速率。
完整流程
发起阶段——USP 的 LTSSM 跳入 Recovery 状态的 Recovery.RcvrLock 次状态,开始发送携带 Speed Change 比特置起的 TS1,其中包含自己的支持速率列表。DSP 收到后同样进入 Recovery,开始返回 TS1,并将其 Directed Speed Change 标志位也置起。如果某设备不想用更高速率,只要在通告的速率列表中不列出即可。
第二阶段——USP 收到返回的 TS1 后跳到 Recovery.RcvrCfg 次状态,开始发 TS2(Speed Change 比特仍置起)。如果这次速率改变不是因可靠性问题引起的,TS2 中的 Autonomous Change 比特置为 1。DSP 收到后同样进入 Recovery.RcvrCfg,返回 TS2,但其 Autonomous Change 比特取与收到的相反值(此例中为 0)。
第三阶段——双方各收到 8 个连续的 Speed Change 比特置起的 TS2 后,一起进入 Recovery.Speed 次状态。此时 DSP 记录收到的 TS2 中 Autonomous Change 比特的值,写入 Link Status 寄存器中的带宽改变状态域。区分两种情况:自主带宽改变(非可靠性问题引起)和带宽管理(为解决可靠性问题引起)。如果硬件支持且已使能,这个状态变化还能触发中断通知软件。
结束阶段——进入 Recovery.Speed 后,链路两个方向都进入电气空闲状态,调整内部电路到双方共同支持的最高速率(本例为 5 GT/s)。等待延时后双方回到 Recovery.RcvrLock,重新发 TS1 退出电气空闲。USP 收到后跳到 Recovery.RcvrCfg 发 TS2,但此时 Speed Change 比特不再置起。最终双方经 Recovery.Idle 返回 L0,速率改变完成。
如果目标速率是 8 GT/s
流程完全相同,只是在最后增加进入 Recovery.Equalization 次状态进行均衡的过程。
失败处理
如果速率改变失败,设备在返回 L0 后的 200ms 内不允许重新协商该速率或更高速率,除非等到链路对端通告支持新的更高速率。

Software Control of Speed Changes // 速率改变的软件控制
速率改变流程本身由硬件执行,软件无法干预过程,但软件可以限制、禁止或主动触发速率改变。
限制速率上限——通过链路控制 2 寄存器中的目标链路速率(Target Link Speed)字段,设定 USP 能发起速率改变的最高目标速率,也可以用来锁定当前速率不变。
强制指定速率——先设置目标链路速率字段为期望速率,再置位链路控制寄存器中的 Retrain Link 比特,驱动硬件尝试切到指定速率。
关闭自动速率改变——通过置位 Hardware Autonomous Speed Disable 比特,禁止硬件自发的速率改变行为。

Dynamic Link Width Changes // 动态链路宽度改变
和速率改变一样,只有 USP 能发起链路宽度改变。但宽度改变比速率改变更复杂,因为不仅要走 Recovery 状态,还要额外经过 Configuration 状态重新协商通道。
软件在使能宽度改变前有一件重要的事要确认:对端是否支持 Link Upconfiguring,即能否把窄链路恢复回宽链路。设备在训练期间发送的 TS2 中的 Rate ID 域比特 6 通告这一能力。如果对端不支持,那缩窄就是一锤子买卖,无法恢复,所以这种情况下只有出现可靠性问题时才会缩窄链路。
开始阶段——USP 进入 Recovery 状态,发送 TS1,但 Speed Change 比特不置起,链路和通道编号保持原值。DSP 同样回应 TS1,双方经 Recovery.RcvrLock 到 Recovery.RcvrCfg 交换 TS2,再进入 Recovery.Idle。
Recovery.Idle 阶段——这是宽度改变的关键起点。正常情况下双方在 Idle 阶段发送逻辑空闲信号(全零),但 USP 不发空闲信号,而是发送链路和通道编号都为 PAD 的 TS1。DSP 识别到已配置好的通道编号变成了 PAD,意识到对端要改变宽度,于是进入 Configuration 状态的第一个次状态 Config.Linkwidth.Start。
Configuration 状态——DSP 在所有通道上发送链路编号为原值、通道编号为 PAD 的 TS1。USP 在想要激活的通道上回应相同的 TS1,在不想激活的通道上发链路和通道编号均为 PAD 的 TS1。DSP 收到后进入 Config.Linkwidth.Accept,然后在激活通道上发带正确通道编号的 TS1,非激活通道发 PAD。USP 回应相同内容,进入 Config.Lanenum.Accept。此时所有 TS1 的 Autonomous Change 比特均为 1,DSP 更新寄存器状态标记检测到一次自主带宽改变,进入 Config.Complete。
结束阶段——DSP 在激活通道上发 TS2,非激活通道进入电气空闲。USP 以相同方式回应。DSP 检测到后进入 Config.Idle,在激活通道上发逻辑空闲信号,USP 同样回应后回到 L0 状态。链路恢复正常工作,只是通道数减少了。
与速率改变的对比
流程框架相似——都是 USP 发起,都经 Recovery 状态,都有 Autonomous Change 比特区分是否自发。但有两个重要区别:一是宽度改变多了 Configuration 状态这一整段重新协商通道的过程;二是速率改变时软件可以通过目标链路速率字段和 Retrain Link 比特强制指定速率,而宽度改变没有对应的软件机制来设定特定宽度,软件只能通过链路控制寄存器禁止硬件发起宽度改变请求。

Link Capabilities Register // 链路能力寄存器
 
Max Link Speed [3:0](最高链路速率)
本质是一个指针,指向 Link Capabilities 2 寄存器 中 Supported Link Speeds Vector 域的某个比特位。指到的那个 bit 所代表的速率就是该链路的最高速率。
Maximum Link Width [9:4](最大链路宽度)
均为只读,且同一端口下各 function 必须保持一致。
Link Capabilities 2 Register // 链路能力 2 寄存器
 
链路寄存器中的最高链路速率域,是一个指向本寄存器链路支持速率向量域的指针

Link Status Register // 链路状态寄存器
 
Current Link Speed [3:0](当前链路速率)
只读,表示链路当前实际运行的速率。链路首次进入 L0 状态时,速率始终为 2.5 GT/s(PCIe Gen1 基础速率)。之后双方若支持更高速率,会通过 Recovery 状态尝试升速。
编码方式与 Link Capabilities 中的 Max Link Speed 相同,也是指向 Link Capabilities 2 中 Supported Link Speeds Vector 域的指针
Negotiated Link Width [9:4](协商链路宽度)
表示链路双方实际协商出的通道宽度(可能小于各自支持的最大宽度)
Undefined [10]
当前协议版本中无定义(早期版本中用于标记链路训练错误)。
软件可写入任意值,但读取值必须被忽略。
Link Training [11]
只读,为 1 时表示 LTSSM 正处于训练过程中(具体为 Configuration 或 Recovery 状态,或 Retrain Link 比特已置 1 但训练尚未实际开始)。
LTSSM 退出 Configuration / Recovery 后清除为 0。
仅对 DSP(Downstream Port)有意义;在 EP、桥设备、交换机的 USP 中必须硬连线为 0b。
PCIe 链路能力寄存器(Link Capabilities)以只读方式描述链路的规格上限。Max Link Speed 通过指针索引 Link Capabilities 2 中的速率向量,指示链路支持的最高速率等级;Maximum Link Width 直接编码最大通道宽度,从 x1 到 x32。两者均为只读,且同一 USP 下各 function 必须报告一致的值。
链路状态寄存器(Link Status)以只读方式反映链路的实时运行快照。Current Link Speed 和 Negotiated Link Width 以相同编码方式呈现链路当前实际运行的速率和宽度,但链路未完成 link up 时两者均未定义。Link Training 位为 1 时表示 LTSSM 正处于 Configuration 或 Recovery 训练状态,仅对 DSP 有意义,USP 侧硬连线为 0。另有已废弃的 Undefined 位,软件可写入任意值但读取值须忽略。
链路控制寄存器(Link Control)提供三个软件可写的操作比特。Link Disable 置 1 可禁用链路,Retrain Link 置 1 可强制将 LTSSM 推入 Recovery 状态发起重训练且不等待 Completion 返回,Extended Synch 置 1 可在退出 L0s 时发送 4096 个 FTS、在 Recovery 进入 RcvrCfg 前发送 1024 个 TS1,以帮助较慢的外部测试设备建立同步。这三个控制比特同样仅对 DSP 有效,EP、桥设备和交换机的 USP 不可使用。
 

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐