深入到 TLP:PCI Express 设备如何通信(第二部分)

数据链路层数据包

除了用其标头(2 个字节)包装 TLP 并在末尾添加一个 CRC(LCRC 实际上是 4 个字节)之外,数据链路层还运行自己的数据包以保持可靠的传输。这些特殊数据包是数据链路层数据包 (DLLP)。我们很快就会列出它们:

  • Ack DLLP 用于确认已成功接收的 TLP。
  • Nack DLLP 用于指示 TLP 到达时已损坏,并且需要重新传输。请注意,还有一个超时机制,以防没有看起来像 TLP 的东西到达。
  • 流控制 DLLP:InitFC1、InitFC2 和 UpdateFC,用于宣布积分,如下所述。
  • 电源管理 DLLP。

流控制

如前所述,数据链路层具有流量控制 (FC) 机制,该机制确保仅当链路伙伴有足够的缓冲区空间来接受 TLP 时才传输它。

我故意使用了“链接合作伙伴”一词,而不是“目的地”。例如,当外围设备通过交换机连接到根复合体时,它会针对交换机而不是最终目的地运行其流量控制机制。换言之,一旦TLP从外设传输,它仍然受制于交换机和根复合体之间的流量控制机制。如果途中有更多的开关,则每条腿都有自己的流量控制。

机制不是最简单的,规范中的描述会让你起鸡皮疙瘩。所以我会尽量说得很清楚。

流控制机制独立运行,用于 6(<>!) 个不同的缓冲区使用者:

  1. Posted Requests TLP 的标头
  2. Posted Requests TLP 的数据
  3. Non-Posted请求 TLP 的标头
  4. Non-Posted请求 TLP 的数据
  5. 完成 TLP 的标头
  6. 完成 TLP 的数据

这是六种信用类型。

记帐是在流量控制单元中完成的,这些单元对应于 4 DW 的流量(16 字节),始终四舍五入到最接近的整数。由于报头的长度始终为 3 或 4 DW,因此传输的每个 TLP 都会从相应的报头信用中消耗一个单位。传输数据时,消耗的单位数为TLP中的数据DW数除以16,向上四舍五入。因此,我们可以想象每个接收器处的数据桶有 <> 个字节,我们不允许在其上混合来自不同 TLP 的数据。每个铲斗都是一个流量控制单元。

现在让我们想象一下,发射器上有一个门卫,它分别计算自链路建立以来消耗的流量控制单元总数,分别用于每个信用类型。这是要跟踪的六个数字。该门卫还具有有关允许达到的每种信用类型的最大数量的信息。如果用于传输的某个 TLP 会使这些计数单位中的任何一个超过其限制,则不允许通过。可以传输另一个 TLP(受重新排序规则的约束),或者门卫只是等待限制上升。

这就是流量控制的工作方式。建立链接后,双方交换其初始限制。当每个接收方处理传入的数据包时,它会更新其链路伙伴的限制,以便它可以使用释放的缓冲区空间。UpdateFC FLLP 数据包会定期发送,以宣布新的信用额度。

好吧,我忽略了一个小细节:由于我们正在计算自链接开始以来的单元总数,因此总是存在溢出的可能性。PCIe标准为每个信用类型计数器及其限制分配一定数量的位(8位用于标头信用,12位用于数据信用),知道它们很快就会溢出。通过简单的模算术比较每个计数器及其限制,可以解决此溢出问题。因此,考虑到一些限制,不能将限制设置得太高,流量控制机制实现了上述门卫。

允许总线实体为六种配额类型中的任何一种或所有配额宣布无限配额,这意味着该特定配额类型的流量控制被禁用。事实上,端点(与交换机和根复合体相反)必须为完成标头和数据通告无限信用。换言之,端点不能拒绝接受基于流控制的完成 TLP。因此,未过帐交易的请求者必须负责通过验证其在发出请求时是否有足够的缓冲区空间来接受完成。这也适用于不允许点对点交易的根复合体。

虚拟通道

在本指南的第一部分中,我将示例 TLP 中的 TC 字段标记为绿色,并表示这些字段几乎总是为零。TC 代表流量类,是用于创建虚拟通道的标识符。这些虚拟通道只是一组单独的数据缓冲区,具有单独的流量控制积分和计数器。因此,通过选择零以外的 TC(并相应地设置总线实体),可以使 TLP 受制于独立的流量控制系统,从而防止属于一个通道的 TLP 阻塞属于另一个通道的 TLP 的流量。

从 TC 到虚拟通道的映射由每个总线实体的软件完成。无论如何,到目前为止,我所看到的现实生活中的PCIe元件仅支持一个虚拟通道VC0,因此仅使用TC0,这是规范要求的最低要求。因此,除非某些特殊应用需要这样做,否则TC在所有TLP中都将保持为零,整个问题可以忽略不计。

数据包重新排序

在分组网络中,我想到的问题之一是 TLP 在多大程度上可能以与发送方式不同的顺序到达。例如,Internet 协议(IP,如 TCP/IP)允许在途中进行任何数据包重新洗牌。PCIe 规范允许一定程度的 TLP 重新排序,事实上,在某些情况下,为了避免死锁,重新排序是强制性的。

幸运的是,这个问题也考虑了遗留的PCI兼容性问题,除非在TLP中设置了“宽松排序”位,而这种情况很少发生。这是 Attr 字段中的位之一,在本指南第一部分的 TLP 示例中标记为绿色。因此,总而言之,人们可以相信,事情会像我们与之交谈的一辆好旧巴士一样顺利进行。我们这些写入几个寄存器,然后通过写入另一个寄存器来触发事件的人可以继续这样做。为了安全起见,我关闭了 BAR 的 Prefetch 位,尽管没有任何迹象表明它与写入有任何关系。

该规范详细定义了重新排序规则,但要获得底线并不容易。因此,我将提及这些规则的一些结果。这里所说的一切都假设在所有交易中都清除了宽松的排序位。我也完全忽略了 I/O 空间(为什么要使用它?

  • 已发布的写入和 MSI 按发送顺序到达。现在,所有内存写入都已发布,而 MSI 实际上是(已发布)内存写入。因此,我们确信内存写入是按顺序执行的,如果我们在填充缓冲区(写入...)后发出 MSI,它将在缓冲区实际写入后到达。
  • 读取请求永远不会在写入请求之前到达,也不会在写入请求之前发送 MSI 之前到达。事实上,执行读取请求是等待写入完成的一种安全方法。
  • 写入请求很可能先于在它们之前发送的读取请求。此机制可防止在某些特殊情况下出现死锁。不要在等待读取完成时写入某个内存区域。
  • 特定请求的读取完成(即具有相同的标签和请求者 ID)按发送顺序到达(因此它们按地址上升的顺序到达)。不同请求的读取完成可能会被重新排序(但谁在乎)。

除此之外,任何事情都可以更改顺序或到达,包括可以在它们之间重新排序并完成读取的读取请求。

为了消除对中断消息在之前的写入操作之前到达的任何偏执,规范中的第 2.2.7 节详细说明了这一点:

用于 MSI/MSI-X 事务的请求格式与上面定义的内存写入请求格式相同,并且 MSI/MSI-X 请求在排序、流量控制和数据完整性方面与内存写入没有区别。

零长度读取请求

如前所述,在写入总线实体后从总线实体读取数据是等待写入操作真正完成的安全方法。但是,如果我们对数据不感兴趣,为什么要阅读任何东西呢?因此,他们编造了一个零长度的请求,该请求什么也没读。所有四个字节使能都被分配了零,这意味着不会读取任何内容。至于完成,规范中的第 2.2.5 节说:

如果读取请求 1 DW 指定不启用读取字节(1st DW BE[3:0] 字段 = 0000b),则相应的完成必须指定 1 DW 的长度,并包含 1 DW 的数据有效负载

因此,我们在完成时有一个DW的垃圾数据。这很公平。

有效负载大小和边界

每个携带数据的 TLP 都必须将有效负载数据 DW 的数量限制为 Max_Payload_Size,这是在配置期间分配的数字(通常为 128 字节)。此数字仅适用于有效负载,不适用于 Length 字段本身:内存读取请求的长度不受Max_Payload_Size限制(根据规范 2.2.2),但Max_Read_Request_Size限制(根据规范 2.2.7)。

因此,内存读取请求请求的数据可能超过一个 TLP 中允许的数据,因此多个 TLP 完成是不可避免的。

无论Max_Payload_Size限制如何,(内存)读取请求的完成都可以拆分为多个完成 TLP。根据规范 128.64.2,切割必须位于由 RCB 字节对齐的地址中(请求完成边界,3 字节,对于根复合体可能为 11)。如果请求未跨越此类对齐边界,则只允许使用单个完成 TLP。单个读取请求的多个内存读取完成必须按地址递增顺序返回数据(该顺序将由交换网络保留)。

最后一句话,引用规范 2.2.7:请求不得指定地址/长度组合,这会导致内存空间访问跨越 4 KB 边界。

你可能感兴趣的:(PCIE,express,网络,服务器,PCIE)