首页 > 解决方案 > 如何使用 x86-64 和 Linux 在 PCIE 总线上生成零长度读取?

问题描述

在 PCI Express Base 规范第 2.2.5 节“第一个/最后一个 DW 字节启用规则”中,它说零长度读取可以用作刷新请求。但是,在 linux 内核文档中,大多数示例只使用 1B 或 4B 读取请求:

独立于总线的设备访问

如何编写 Linux PCI 驱动程序

我想知道 x86-64 架构是否有可能生成一条导致 PCI 上读取长度为零的指令,如果可以,是否有一些 linux 内核函数可以创建该指令。

标签: linuxlinux-kernelx86linux-device-driverpci-e

解决方案


您提到的两个示例涉及从 CPU 到 I/O 设备的 MMIO 访问或传统 I/O 端口访问,但 PCIe 规范第 2.2.5 节中的零长度读取实现说明是关于来自 I/O 的访问设备。PCIe 规范和 Intel/AMD64 x86 手册明显不同,它们使用不同的术语,所以我不明白你是如何混淆这两者的。不,x86 中不存在零长度读取。

第一个链接的代码如下:

WRT_REG_WORD(&reg->ictrl, 0);
/*
 * The following read will ensure that the above write
 * has been received by the device before we return from this
 * function.
 */
RD_REG_WORD(&reg->ictrl);

有一个 16 位 MMIO 写入,然后是一个 16 位 MMIO 读取到同一地址。目标位置的内存类型很可能是 UC,这样可以确保所有 UC 访问按程序顺序出现在系统总线上。这意味着它按顺序到达 PCIe 根复合体(集成在现代处理器上)。处理器的 I/O 单元将 MMIO 写入转换为后写 PCIe 事务,而读取则转换为非后读 PCIe 事务。这两个交易都将具有流量类别并禁用轻松排序。根据事务顺序规则,这样的非发布读取不能与任何先前发布的写入重新排序。总体效果是,当 UC 读取取回结果时,前面的 UC 写入必须已经在目标 I/O 设备上完成。

您提供的第二个链接还包括一个工作方式完全相同的 MMIO 排序示例。在发布写入后发出读取是确定写入何时完成的常用技术。UC 读取不是 x86 中的完全序列化操作。如果您不希望在读取完成之前执行任何后续指令(不是 UC 访问),则需要在读取后添加完全序列化指令。Linux 内核本身定义了许多在不同情况下使用的 MMIO 屏障。

第二个链接还提到传统 I/O 写入不需要后续读取,因为“I/O 端口空间保证写入事务在 CPU 可以继续之前到达 PCI 设备。” I/O 指令提供比 UC 访问更多的顺序保证,但它们仍然没有完全序列化。这些保证包括在执行 I/O 指令之前等待先前的指令提交,以及在 I/O 指令完成之前不允许执行后面的指令。这些保证与 I/O 控制器将 I/O 指令转换为 PCIe I/O 事务(其中 I/O 写事务是非发布事务)这一事实相结合,确保了当下一条指令执行时,它得到保证I/O 写入已在目标 I/O 设备上完成。

I/O 设备可以使用零长度读取来确定较早的写入已在目的地完成。例如,这就是 I/O 设备如何确保写入已到达支持异步 DRAM 刷新 (ADR) 的平台上的持久域或设备驱动程序可观察到的写入。


推荐阅读