PCI Express 事务排序规则继承自 PCI 规范。这些规则主要基于生产者/消费者编程模型,该模型是 PCI 和 PCIe 中数据传输的常见方法。同时,排序规则还考虑了必须避免的死锁条件。
本章将讨论 PCI Express 拓扑中事务的排序要求,包括:
- 简化排序规则表
- 生产者/消费者模型
- 宽松排序 (Relaxed Ordering, RO)
- 弱排序 (Weak Ordering)
- 基于ID的排序 (ID-Based Ordering, IDO)
- 死锁避免机制
PCIe 2.1 修订版引入了简化版本的排序表(如表8-1所示)。该表可以按主题分段如下:
- 生产者/消费者规则(第290页)
- 宽松排序规则(第296页)
- 弱排序规则(第299页)
- ID排序规则(第301页)
- 死锁避免(第303页)
这些部分提供了与排序模型、操作、原理、条件和要求相关的详细信息。
PCI Express 排序规则适用于相同流量类别(TC, Traffic Class)的事务。在 fabric 中传输的不同 TC 的事务没有排序要求,被认为与无关的应用程序相关联。因此,不同 TC 的数据包之间不存在与事务排序相关的性能降级。
共享相同 TC 的数据包在流经 PCIe fabric 时可能会经历性能降级。这是因为交换机和设备必须支持排序规则,这可能要求数据包被延迟或转发到先前发送的数据包之前。
如第7章"服务质量"(第245页)所述,不同 TC 的事务可能映射到同一个 VC。TC到VC的映射配置决定了给定 TC 的数据包映射到哪个特定的 VC。尽管事务排序规则仅适用于相同 TC 的数据包,但设计端点设备/交换机/根复合体时,更简单的方法是将事务排序规则应用于 VC 内的所有数据包,即使多个 TC 映射到同一个 VC。
如预期的那样,无论其 TC 如何,映射到不同 VC 的数据包之间不存在排序关系。
PCIe 规范定义的排序关系基于 TLP 类型。TLP 分为三类:
- 发布事务 (Posted):包括存储器写请求 (MWr) 和消息 (Msg/MsgD)
- 完成事务 (Completion):包括 Cpl 和 CplD
- 非发布事务 (Non-Posted):包括 MRd、IORd、IOWr、CfgRd0、CfgRd1、CfgWr0 和 CfgWr1
事务排序规则在下一节"简化排序规则表"(第288页)中通过表格描述。该表按照上述三类列出 TLP,并定义了它们的排序关系。
该表按"行通过列"(Row Pass Column)的方式组织。所有规则在简化排序表后汇总。每条规则或规则组定义了所需的操作。
在表8-1(第289页)中,列2-5代表 PCI Express 设备先前已交付的事务,而行A-D代表刚刚到达的新事务。对于出站事务,该表指定行(A-D)中代表的事务是否允许通过列(2-5)中代表的先前列事务。
表项含义:
- 'No':行中的事务不允许通过列中的事务
- 'Yes':行中的事务必须通过列中的事务以避免死锁
- 'Yes/No':行中的事务允许通过列中的事务,但不是必须这样做
表8-1:简化排序规则表
列2 列3 列4 列5
发布请求 读请求 带数据的 完成
(Posted) (Read) NPR (Completion)
行A: 发布请求 • a) No Yes Yes • a) Y/N
(Posted) • b) Y/N • b) Yes
行B: 读请求 • a) No Y/N Y/N Y/N
(Read) • b) Y/N
行C: 带数据的NPR • a) No Y/N Y/N Y/N
(NPR w/ Data) • b) Y/N
行D: 完成 • a) No Yes Yes • a) Y/N
(Completion) • b) Y/N • b) No
非发布请求 (Non-Posted Request)
规则说明:
-
A2a, B2a, C2a, D2a:为了强制执行生产者/消费者模型,后续事务不允许通过发布请求。
-
A2, D2b:如果设置了 RO(宽松排序),则允许读完成通过先前排队的存储器写或消息请求。
-
A2b, B2b, C2b, D2b:如果使用可选的 IDO(基于ID的排序),只要请求者ID不同,就允许后续事务通过发布请求。
-
A3, A4:为了避免死锁,存储器写或消息请求必须通过非发布请求。
-
A5a:发布请求被允许但不是必须通过完成事务。
-
A5b:死锁避免情况。在 PCIe 到 PCI/PCI-X 桥中,对于从 PCIe 到 PCI 或 PCI-X 的事务,发布请求必须能够通过完成事务,否则可能发生死锁。
-
B3, B4, B5, C3, C4, C5:这些情况下实现弱排序,而不会产生任何排序相关问题。
-
D3, D4:完成事务必须通过读和 I/O 或配置写请求(非发布请求)以避免死锁。
-
D5a:具有不同事务ID的完成事务可以相互通过。
-
D5b:具有相同事务ID的完成事务不允许相互通过。这确保单个请求的多个完成事务将保持按地址升序排列。
本节描述生产者/消费者模型的操作以及正确操作所需的关联排序规则。
生产者/消费者模型是 PCI 和 PCIe 中数据传输的常见方法。该模型包含五个元素:
- 数据生产者 (Producer)
- 存储器数据缓冲区
- 标志信号量 (Flag):指示生产者已发送数据
- 数据消费者 (Consumer)
- 状态信号量 (Status):指示消费者已读取数据
规范指出,无论所涉及元素的排列如何,生产者/消费者模型都能正常工作。在以下示例中,标志和状态元素位于同一物理设备中,但也可以位于不同的设备中。
参考图8-2(第293页)进行以下讨论。该示例假设标志和状态元素开始时已被清零。本示例中这些信号量包含在同一设备中。以下描述中的编号事件序列和图8-2(第293页)中描述的部分1序列反映了正确的排序。
部分1序列:
-
称为生产者的设备执行一个或多个存储器写事务(发布请求),目标指向存储器中的数据缓冲区。
- 数据流经发布缓冲区时可能会产生一些延迟。
-
消费者通过发起存储器读事务(非发布请求)来定期检查标志,以确定生产者是否已交付数据。
-
设备读取标志信号量,并向消费者返回存储器读完成事务,指示生产者尚未执行数据交付通知(标志=0)。
-
生产者发送存储器写事务(发布请求)将标志更新为1。
-
消费者再次通过执行与步骤2相同的事务来检查标志。
-
当这次读取标志信号量时,标志被设置为1,通过完成事务向消费者指示生产者已将所有数据交付到存储器。
-
接下来,消费者执行存储器写事务(发布请求)将标志信号量清零。
图8-3(第294页)继续本示例的部分2序列。
部分2序列:
-
有更多数据要发送的生产者通过发起存储器读事务(非发布请求)定期检查状态信号量。
-
生产者读取状态信号量,并向生产者返回存储器读完成事务,指示消费者尚未读取存储器缓冲区内容并更新状态(状态=0)。
-
消费者知道存储器缓冲区有可用的数据,执行一个或多个存储器读请求(非发布请求)以从缓冲区获取内容。
-
读取存储器内容并返回给消费者。
-
完成数据传输后,消费者发起存储器写请求(发布请求)将状态信号量设置为1。
-
生产者再次通过发送存储器读请求(非发布请求)来检查状态信号量。
-
设备读取状态,这次状态被设置为1。完成事务返回给生产者,从而指示可以向存储器发送数据。
-
生产者发送存储器写请求将状态信号量清零。
-
生产者重复从步骤1开始的事件序列。
ASCII示意图:生产者/消费者拓扑示例
前面的示例在没有讨论排序规则的情况下正确处理了,但可能会出现竞争条件导致生产者/消费者序列失败。图8-4(第296页)说明了一个简单的序列,演示了在没有强制执行排序规则时可能出现的几个问题之一。
错误场景:
-
生产者执行存储器写请求(发布请求)到存储器缓冲区。假设存储器写数据暂时卡在交换机上行的发布流量控制缓冲区中。
-
生产者发送存储器写事务(发布请求)将标志更新为1。
-
消费者发起存储器读请求(非发布请求)检查标志是否已设置为1。
-
标志的内容通过完成事务返回给消费者。
-
知道数据已交付到存储器后,消费者执行存储器读请求以获取数据。然而,消费者不知道数据由于上行交换端口和根复合体之间链路的流量控制信用不足而暂时卡在发布流量控制缓冲区中。因此,当完成事务返回给消费者时,消费者收到的是旧数据。
解决方案:
通过拓扑中的虚拟 PCI 桥支持的排序规则可以避免此问题。在此示例中,当消费者在步骤3和4执行存储器读事务时,上行交换机端口的虚拟 PCI 桥不应允许标志内容(完成事务4)转发到先前发布的数据之前。
PCI Express 支持为 PCI-X 添加的宽松排序(RO, Relaxed Ordering)机制。RO 允许请求者和完成者之间路径上的交换机在重新排序某些事务能够提高性能时进行重新排序。
支持生产者/消费者模型的排序规则可能会导致事务被阻塞,而这些事务与任何生产者/消费者事务序列无关。为了缓解这个问题,事务可以设置其 RO 属性位,指示软件验证它与其他事务无关,从而允许它被重新排序到其他事务之前。例如,如果由于目标缓冲区空间不可用而导致发布写被延迟,则所有后续事务都必须等待,直到该问题最终解决并且写被交付。如果后续事务被软件知道与先前事务无关,并且设置了 RO 位来表明这一点,则可以允许它在写之前进行而不会产生问题。
RO 位(TLP 标头第0个双字第2字节的第5位,如图8-5所示)可以在设备驱动程序启用设备这样做时使用。然后,当软件请求发送数据包时,允许请求数据包使用此属性。当交换机或根复合体看到设置了此属性位的数据包时,它们有权重新排序它,尽管不是必须这样做。
图8-5:32位标头中的宽松排序位
交换机和根复合体必须遵守事务中 RO 位的设置。存储器写和消息都是发布写,都接收进入相同的发布缓冲区,都受到相同的排序要求约束。当设置 RO 位时,交换机按如下方式处理这些事务:
-
交换机允许将刚刚发布的存储器写事务重新排序到先前发布的存储器写事务或消息事务之前。同样,刚刚发布的消息事务可以排序到先前发布的存储器写或消息事务之前。交换机还必须不加修改地转发 RO 位。
-
RO 位被 PCI-X 桥忽略,它们总是按顺序转发写(如果由于某种原因一个被阻塞,下一个也会被阻塞,允许它们无序进行几乎没有意义)。另一个区别是消息事务也没有为 PCI-X 定义。
-
根复合体允许重新排序发布写事务(在这里是有意义的,因为根可以写入存储器的不同区域,因此如果一个区域繁忙,它可以写入另一个区域)。此外,当接收设置了 RO 的写时,允许根以任何地址顺序将每个字节写入存储器。
PCI Express 中的所有读事务都作为分割事务处理。当设备发出设置了 RO 位的存储器读请求时,完成者在一个或多个分割完成事务中返回请求的读数据,并使用与请求中相同的 RO 设置。交换机在此情况下的行为如下:
-
接收设置了 RO 的存储器读的交换机按接收顺序转发请求,不得将其重新排序到先前发布的存储器写事务之前。这保证了向读请求方向移动的所有写事务都被推到读之前。这是前面所示的生产者/消费者示例的一部分,软件可能依赖此刷新操作来正确运行。交换机不得修改 RO 位。
-
当完成者接收到存储器读时,它获取请求的数据并交付一个或多个同样设置了 RO 位的完成事务(其值从原始请求复制)。
-
接收完成事务的交换机允许将它们重新排序到先前发布的向完成事务方向移动的存储器写之前。如果写被阻塞(例如,由于流量控制),则完成事务将被允许在它们之前进行。在这种情况下,宽松排序提高了读性能。
表8-2:由于宽松排序可以重新排序的事务
| 设置了 RO=1 的这些事务可以通过 | 这些事务 |
|---|---|
| 存储器写请求 | 存储器写请求 |
| 消息请求 | 存储器写请求 |
| 存储器写请求 | 消息请求 |
| 消息请求 | 消息请求 |
| 读完成 | 存储器写请求 |
| 读完成 | 消息请求 |
当严格执行强排序规则时,可能会发生临时事务阻塞。不违反生产者/消费者编程模型的修改可以消除一些阻塞条件并提高链路效率。实现弱排序模型可以缓解这个问题。
将给定数量的 VC 缓冲区分割成受流量控制的子缓冲区 P、NP 和 CPL 的动机是,一旦 TLP 被解析或分箱到各自的缓冲区后,它简化了事务排序规则的处理。事务排序处理逻辑然后将排序规则应用于这三个子缓冲区之间或每个子缓冲区。
由于 TLP 被分箱到各自的三个子缓冲区以处理事务排序规则,因此有必要在链路两端相邻端口的每个虚拟通道子缓冲区(P、NP、CPL)之间定义流量控制机制。事实上,您可能还记得,每个虚拟通道号的每个子缓冲区类别(P、NP、CPL)的标头(Hdr)和数据(D)子缓冲区之间存在独立的流量控制机制。
强排序可能导致所有事务由于单个满接收缓冲区而被阻塞的实例。例如,生产者/消费者模型的排序要求不能更改,但不属于该模型的事务的排序可以更改。为了提高性能,让我们考虑一种弱排序方案;一种对事务排序施加最小要求的方案。
此示例描述了与单个 VC 的单方向事务交付相关联的发送和接收缓冲区。回想一下,每种事务类型(发布、非发布和完成)在同一 VC 内都有独立的流量控制。发送缓冲区中的数字表示这些事务发出的顺序,非发布接收缓冲区当前已满。考虑以下序列:
-
事务1(存储器读)是下一个要发送的事务,但没有足够的流量控制信用,因此它必须等待。
-
事务2(发布存储器写)是下一个后续事务。如果强制执行强排序,存储器写不得通过先前排队的读事务。
-
此限制也适用于所有后续事务,结果是它们都被阻塞,直到第一个事务完成。
图8-6:强排序示例导致临时阻塞
事务排序在虚拟通道缓冲区中管理。这些缓冲区被分组为发布、非发布和完成事务,并且对每个组独立管理流量控制。这使得弱排序更有用,因为在我们的示例中,即使一个缓冲区已满,其他缓冲区仍可能有可用空间。
优化排序和提高性能的另一个机会与流量流的性质有关。来自不同请求者的数据包非常不可能有依赖关系;毕竟,一个设备很难知道另一个设备何时已完成某些步骤,因为它们可能有不同的路径到达共享资源。考虑到这一点,PCIe 规范的 2.1 修订版引入了所谓的基于ID的排序(IDO, ID-Based Ordering)来提高性能。
如果在事务排序中不考虑数据包源,则性能可能会受到影响,如图8-7(第302页)所示。在图中,事务1到达交换机的上行端口,但由于根端口中该数据包类型的缓冲区满条件(这将由流量控制信用不足指示)而被阻止进一步前进。使用规范术语,来自同一请求者的数据包称为 TLP 流。在此示例中,事务1显示的路径可能包括多个 TLP 作为 TLP 流的一部分。然后事务2到达同一出口端口,并且由于它必须保持与事务1的顺序,也被阻止向前移动。
由于数据包来自不同的源(不同的 TLP 流),这种延迟几乎肯定是不必要的;它们之间不太可能有依赖关系,但正常的排序模型没有考虑到这一点。要获得改进的性能,我们需要另一个选项。
解决方案很简单:如果数据包不使用相同的请求者ID(或完成者ID,对于完成数据包),则允许重新排序数据包。这种可选功能允许软件启用设备使用 IDO,并且交换机端口可以识别数据包属于不同的 TLP 流。这是通过在设备控制2寄存器中设置使能位来完成的。
图8-7:不同源不太可能有依赖关系
规范强烈建议只要安全可行就使用 IDO 和 RO。例如,当端点仅与另一个实体(如根复合体)直接通信时,为所有 TLP 使用 IDO 应该是安全的。另一方面,如果端点与多个代理通信,则使用它是不安全的。
来自规范的一个失败示例:一个设备执行 DMA 写到存储器,然后执行对等写到另一个设备中的标志。当第二个设备接收到标志时,它还启动 DMA 写到存储器的同一区域。通常,两个 DMA 操作将保持顺序,但使用 IDO 时,无法保证排序,因为上游设备将看到它们来自不同的设备ID。同样,将 RO 用于参与控制流量的数据包是不安全的。
对于完成者,如果启用了 IDO,建议对所有完成事务使用它,除非有特定原因不这样做。
软件可以通过在设备控制2寄存器中设置适当的位来启用给定端口的请求或完成事务使用 IDO。与 RO 一样,没有能力位让软件发现设备支持什么,只有使能位,因此软件需要通过其他方式知道设备有能力这样做。这些位启用该数据包类型使用 IDO,但软件仍必须决定每个单独数据包是否设置其 IDO 位。
标头中的新属性位指示 TLP 是否正在使用 IDO,如图8-8(第303页)所示。这引出了另一个相关点:完成事务通常继承生成它们的请求的所有属性位,但对于 IDO 这可能不成立,因为这可以由完成者独立启用。换句话说,即使启动它们的请求没有使用 IDO,完成事务也可能使用 IDO。
图8-8:64位标头中的 IDO 属性
由于 PCI 总线采用延迟事务,或者由于 PCI Express 存储器读请求可能因缺乏流量控制信用而被阻塞,可能会产生几种死锁场景。这些死锁避免规则包含在 PCI Express 排序中,以确保无论拓扑如何都不会发生死锁。遵守排序规则可以防止由于意外拓扑(例如,两个 PCI Express 到 PCI 桥通过 PCI Express fabric 连接)导致的边界条件问题时出现问题。
表8-1(第289页)列出了死锁避免排序规则,标识为条目 A3、A4、D3、D4 和 A5b。注意,避免死锁涉及这5种情况中的每个"是"(Yes)条目。如果由于与非发布请求缓冲区(列3或4中标识)相关联的流量控制信用不足而发生阻塞,与行A相关联的发布请求或与行D相关联的完成事务必须移动到列3或4中指定"是"条目的非发布请求之前。还要注意,A5b中的"是"条目仅适用于 PCI Express 到 PCI 或 PCI-X 桥。
本质上,这种死锁避免规则可以总结为"后到达的存储器写请求或完成事务必须通过先前被阻塞的非发布请求,否则可能导致死锁"。
| 英文术语 | 中文翻译 |
|---|---|
| Transaction Ordering | 事务排序 |
| Posted Request | 发布请求 |
| Non-Posted Request | 非发布请求 |
| Completion | 完成事务 |
| Traffic Class (TC) | 流量类别 |
| Virtual Channel (VC) | 虚拟通道 |
| Producer/Consumer Model | 生产者/消费者模型 |
| Relaxed Ordering (RO) | 宽松排序 |
| Weak Ordering | 弱排序 |
| ID-Based Ordering (IDO) | 基于ID的排序 |
| Deadlock Avoidance | 死锁避免 |
| TLP Stream | TLP流 |
| Requester ID | 请求者ID |
| Completer ID | 完成者ID |
| Flow Control | 流量控制 |
| Split Transaction | 分割事务 |
| Flag Semaphore | 标志信号量 |
| Status Semaphore | 状态信号量 |
本节补充Linux内核中与事务排序相关的实现说明。
// include/uapi/linux/pci_regs.h
/* Device Control register - Relaxed Ordering and No Snoop */
#define PCI_EXP_DEVCTL_RELAX_EN 0x0010 /* Enable relaxed ordering */
#define PCI_EXP_DEVCTL_NOSNOOP_EN 0x0800 /* Enable No Snoop */
/* Device Control 2 register - ID-Based Ordering */
#define PCI_EXP_DEVCTL2_IDO_REQ_EN 0x0100 /* Allow IDO for requests */
#define PCI_EXP_DEVCTL2_IDO_CMP_EN 0x0200 /* Allow IDO for completions */// drivers/pci/probe.c
/**
* pcie_relaxed_ordering_enabled - 检查设备是否启用宽松排序
* @dev: 要查询的PCI设备
*
* 如果设备启用了宽松排序属性则返回true。
*/
bool pcie_relaxed_ordering_enabled(struct pci_dev *dev)
{
u16 v;
pcie_capability_read_word(dev, PCI_EXP_DEVCTL, &v);
return !!(v & PCI_EXP_DEVCTL_RELAX_EN);
}
EXPORT_SYMBOL(pcie_relaxed_ordering_enabled);
/**
* pci_configure_relaxed_ordering - 配置宽松排序
* @dev: 要配置的PCI设备
*
* 如果根端口不支持Relaxed Ordering,则在设备上禁用它。
*/
static void pci_configure_relaxed_ordering(struct pci_dev *dev)
{
struct pci_dev *root;
/* VFs中PCI_EXP_DEVICE_RELAX_EN是RsvdP */
if (dev->is_virtfn)
return;
if (!pcie_relaxed_ordering_enabled(dev))
return;
/* 只处理根端口相关的RO问题 */
root = pcie_find_root_port(dev);
if (!root)
return;
if (root->dev_flags & PCI_DEV_FLAGS_NO_RELAXED_ORDERING) {
pcie_capability_clear_word(dev, PCI_EXP_DEVCTL,
PCI_EXP_DEVCTL_RELAX_EN);
pci_info(dev, "Relaxed Ordering disabled because the Root Port didn't support it\n");
}
}某些设备在完成事务中不遵循PCIe规范复制请求的TLP属性:
// drivers/pci/quirks.c
/*
* 根据PCIe r3.0, sec 2.2.9:
* "完成头必须提供与相应请求头中提供的属性相同的值,
* 除非在使用IDO时明确允许。"
*
* 如果非合规设备生成的完成具有与请求不同的属性,
* 接收方可能将其作为Malformed TLP或Unexpected Completion处理,
* 这可能导致设备访问超时。
*
* 解决方法:在上游设备中禁用Relaxed Ordering和No Snoop属性,
* 使它们始终生成属性为零的请求。
*/
static void quirk_disable_root_port_attributes(struct pci_dev *pdev)
{
struct pci_dev *root_port = pcie_find_root_port(pdev);
if (!root_port) {
pci_warn(pdev, "PCIe Completion erratum may cause device errors\n");
return;
}
pci_info(root_port, "Disabling No Snoop/Relaxed Ordering Attributes to avoid PCIe Completion erratum in %s\n",
dev_name(&pdev->dev));
pcie_capability_clear_and_set_word(root_port, PCI_EXP_DEVCTL,
PCI_EXP_DEVCTL_RELAX_EN |
PCI_EXP_DEVCTL_NOSNOOP_EN, 0);
}
/* Chelsio T5芯片未能将TLP属性从请求复制到其生成的完成 */
DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_CHELSIO, 0x50a0,
quirk_chelsio_T5_disable_root_port_attributes);在Linux内核中,DMA操作通常需要遵循生产者/消费者模型:
// 典型的DMA操作序列
/* 1. 生产者: 写入数据到DMA缓冲区 */
dma_sync_single_for_device(dev, dma_handle, size, DMA_TO_DEVICE);
/* 执行DMA写操作 */
/* 2. 写入标志通知消费者数据就绪 */
writel(FLAG_READY, flag_addr);
/* 需要确保写操作按顺序完成 - 使用wmb() */
wmb();
/* 3. 消费者: 检查标志 */
if (readl(flag_addr) == FLAG_READY) {
/* 4. 读取数据 - 使用rmb()确保读顺序 */
rmb();
/* 读取DMA缓冲区数据 */
dma_sync_single_for_cpu(dev, dma_handle, size, DMA_FROM_DEVICE);
}Linux内核提供内存屏障原语来确保事务排序:
/* 写内存屏障 - 确保之前的写操作在后续写操作之前完成 */
#define wmb() asm volatile("dmb ishst" ::: "memory")
/* 读内存屏障 - 确保后续的读操作在之前的读操作之后执行 */
#define rmb() asm volatile("dmb ishld" ::: "memory")
/* 读写内存屏障 - 同时确保读写顺序 */
#define mb() asm volatile("dmb ish" ::: "memory")在PCIe环境中,这些内存屏障确保:
wmb():确保发布写操作按程序顺序完成rmb():确保读操作按程序顺序执行mb():确保读写操作之间的顺序
内核使用设备标志来记录排序相关的能力:
// include/linux/pci.h
/* 设备标志 */
#define PCI_DEV_FLAGS_NO_RELAXED_ORDERING 0x00000001
#define PCI_DEV_FLAGS_NO_MSI 0x00000002
#define PCI_DEV_FLAGS_NO_MSIX 0x00000004
#define PCI_DEV_FLAGS_INTX_DISABLE 0x00000008
#define PCI_DEV_FLAGS_NO_PM_RESET 0x00000010
#define PCI_DEV_FLAGS_NO_BUS_RESET 0x00000020
#define PCI_DEV_FLAGS_NO_PM 0x00000040
#define PCI_DEV_FLAGS_NO_AER 0x00000080参考来源:
- Linux内核: include/uapi/linux/pci_regs.h
- Linux内核: drivers/pci/probe.c
- Linux内核: drivers/pci/quirks.c
- Linux内核: include/linux/pci.h
- PCIe Base Specification 3.0, Section 2.2.9
本翻译内容基于《PCI Express Technology 3.0》第8章"Transaction Ordering"(第285-306页)。
[文档:MindShare_PCI Express Technology 3.0.pdf:第8章]
翻译完成日期:2026-03-17 翻译者:AI Assistant
以下是本章相关的原文图片:

