31. 使用 ENQCMD 的共享虚拟寻址 (SVA)¶
31.1. 背景¶
共享虚拟寻址 (SVA) 允许处理器和设备使用相同的虚拟地址,从而避免了软件将虚拟地址转换为物理地址的需求。 SVA 是 PCIe 所谓的共享虚拟内存 (SVM)。
除了设备使用应用程序虚拟地址的便利之外,它也不需要为 DMA 固定页面。 PCIe 地址转换服务 (ATS) 以及页面请求接口 (PRI) 允许设备的功能与 CPU 处理应用程序页面错误的方式非常相似。 有关更多信息,请参阅 PCIe 规范第 10 章:ATS 规范。
使用 SVA 需要平台中的 IOMMU 支持。 IOMMU 还需要支持 PCIe 功能 ATS 和 PRI。 ATS 允许设备缓存虚拟地址的转换。 IOMMU 驱动程序使用 mmu_notifier() 支持来保持设备 TLB 缓存和 CPU 缓存同步。 当 ATS 查找虚拟地址失败时,设备应使用 PRI 来请求将虚拟地址分页到 CPU 页表中。 设备必须再次使用 ATS 才能在使用前获取转换。
31.2. 共享硬件工作队列¶
与单根 I/O 虚拟化 (SR-IOV) 不同,可扩展 IOV (SIOV) 允许应用程序和虚拟机 (VM) 使用共享工作队列 (SWQ)。 与可能导致利用率不足的硬分区资源相比,这可以更好地利用硬件。 为了允许硬件区分硬件中通过 SWQ 接口执行工作的上下文,SIOV 使用进程地址空间 ID (PASID),这是一个由 PCIe SIG 定义的 20 位数字。
PASID 值编码在来自设备的所有事务中。 这允许 IOMMU 除了使用 PCIe 资源标识符 (RID)(即总线/设备/功能)之外,还可以按 PASID 粒度跟踪 I/O。
31.3. ENQCMD¶
ENQCMD 是 Intel 平台上的一个新指令,可原子地将工作描述符提交到设备。 描述符包括要执行的操作、所有参数的虚拟地址、完成记录的虚拟地址以及当前进程的 PASID(进程地址空间 ID)。
ENQCMD 使用非发布语义,并且如果硬件接受了该命令,则会返回状态。 这允许提交者知道是否需要重试提交,或者应提供其他特定于设备的机制来实现公平性或确保向前进展。
ENQCMD 是确保应用程序可以直接将命令提交到硬件的粘合剂,并且还允许硬件了解应用程序上下文以通过使用 PASID 执行 I/O 操作。
31.4. 进程地址空间标记¶
一个新的线程范围 MSR (IA32_PASID) 提供了用户进程和其余硬件之间的连接。 当应用程序首次访问支持 SVA 的设备时,此 MSR 将使用新分配的 PASID 进行初始化。 设备的驱动程序调用特定于 IOMMU 的 API,该 API 设置 DMA 和页面请求的路由。
例如,Intel 数据流加速器 (DSA) 使用 iommu_sva_bind_device(),它将执行以下操作
分配 PASID,并在 PASID 上下文条目中编程进程页表(%cr3 寄存器)。
注册 mmu_notifier() 以跟踪任何页表失效,以保持设备 TLB 同步。 例如,当页表条目失效时,IOMMU 会将失效传播到设备 TLB。 这将强制设备将来对该虚拟地址的任何访问参与 ATS。 如果 IOMMU 返回正确的响应,指示页面不存在,则设备将通过 PCIe PRI 协议请求将页面分页到内存中,然后再执行 I/O。
此 MSR 使用 XSAVE 功能集作为“管理程序状态”进行管理,以确保在上下文切换期间更新 MSR。
31.5. PASID 管理¶
内核必须代表每个将使用 ENQCMD 的进程分配一个 PASID,并将其编程到新的 MSR 中,以将进程标识传达给平台硬件。 ENQCMD 使用存储在此 MSR 中的 PASID 来标记来自此进程的请求。 当用户使用 ENQCMD 指令将工作描述符提交到设备时,描述符中的 PASID 字段会自动填充 MSR_IA32_PASID 中的值。 来自设备的 DMA 请求也使用相同的 PASID 进行标记。 平台 IOMMU 使用事务中的 PASID 来执行地址转换。 IOMMU API 使用 CPU 使用的进程地址(例如,x86 中的 %cr3 寄存器)在 IOMMU 中设置相应的 PASID 条目。
在任何应用程序线程可以与设备交互之前,必须在每个逻辑 CPU 上配置 MSR。 属于同一进程的线程共享相同的页表,因此共享相同的 MSR 值。
31.6. PASID 生命周期管理¶
创建进程时,PASID 初始化为 IOMMU_PASID_INVALID (-1)。
只有访问支持 SVA 的设备的进程才需要分配 PASID。 当进程打开/绑定支持 SVA 的设备但未找到该进程的 PASID 时,才会发生此分配。 同一设备或其他设备的后续绑定将共享相同的 PASID。
尽管 PASID 是通过打开设备分配给进程的,但它在任何进程的线程中都不是活动的。 当线程尝试使用 ENQCMD 将工作描述符提交到设备时,它会懒惰地加载到 IA32_PASID MSR 中。
第一次访问将触发 #GP 错误,因为 IA32_PASID MSR 尚未初始化为分配给进程的 PASID 值(打开设备时)。 Linux #GP 处理程序注意到已为进程分配了一个 PASID,因此初始化 IA32_PASID MSR 并返回,以便重新执行 ENQCMD 指令。
在 fork(2) 或 exec(2) 上,PASID 从进程中删除,因为它不再具有打开设备时的相同地址空间。
在 clone(2) 上,新任务共享相同的地址空间,因此将能够使用分配给进程的 PASID。 IA32_PASID 不会抢先初始化,因为 PASID 值可能尚未分配,或者内核不知道该线程是否将访问该设备,并且清除的 IA32_PASID MSR 会通过 xstate 初始化优化来减少上下文切换开销。 由于在将 PASID 分配给进程的 mm 之前创建的任何线程都必须处理 #GP 错误,因此新创建的线程也应该以一致的方式处理。
由于在取消绑定中释放 PASID 和清除所有线程中的所有 IA32_PASID MSR 的复杂性,因此仅在 mm 退出时才懒惰地释放 PASID。
如果进程对设备文件描述符执行 close(2) 并且对设备 MMIO 门户执行 munmap(2),则驱动程序将取消绑定设备。 对于进程中访问该设备的任何线程,PASID 仍标记为 PASID_MSR 中的 VALID。 但这是无害的,因为如果没有 MMIO 门户,它们将无法向设备提交新工作。
31.7. 关系¶
每个进程都有多个线程,但只有一个 PASID。
设备具有有限数量(约 10 到 1000 个)的硬件工作队列。 设备驱动程序管理硬件工作队列的分配。
单个 mmap() 将单个硬件工作队列映射为“门户”,并且每个门户都映射到单个工作队列。
对于进程与之交互的每个设备,必须有一个或多个 mmap()’d 门户。
进程中的多个线程可以共享单个门户来访问单个设备。
多个进程可以分别 mmap() 同一个门户,在这种情况下,它们仍然共享一个设备硬件工作队列。
所有线程都使用单个进程范围的 PASID 与所有设备交互。 例如,没有每个线程或每个线程<->设备对的 PASID。
31.8. 常见问题解答¶
什么是 SVA/SVM?
共享虚拟寻址 (SVA) 允许 I/O 硬件和处理器在同一地址空间中工作,即共享它。 有些人称其为共享虚拟内存 (SVM),但 Linux 社区希望避免将其与 POSIX 共享内存和安全虚拟机混淆,这些术语已经在流通。
什么是 PASID?
进程地址空间 ID (PASID) 是 PCIe 定义的事务层数据包 (TLP) 前缀。 PASID 是一个由 OS 分配和管理的 20 位数字。 PASID 包含在平台和设备之间的所有事务中。
共享工作队列有何不同?
传统上,为了使用户空间应用程序与硬件交互,每个进程都需要一个单独的硬件实例。 例如,将门铃视为通知硬件有关要处理的工作的机制。 每个门铃需要间隔 4k(或页面大小),以实现进程隔离。 这需要硬件来配置该空间并在 MMIO 中保留它。 随着线程数量变得非常大,这无法扩展。 硬件还管理共享工作队列 (SWQ) 的队列深度,并且使用者无需跟踪队列深度。 如果没有空间接受命令,设备将返回一个指示重试的错误。
用户应检查设备上的可延期内存写入 (DMWr) 功能,并且仅当设备支持它时才提交 ENQCMD。 在新的 DMWr PCIe 术语中,设备需要支持 DMWr 完成器功能。 此外,它需要所有交换机端口都支持 DMWr 路由,并且必须由 PCIe 子系统启用,就像 PCIe 原子操作的托管方式一样。
SWQ 允许硬件仅在设备中配置单个地址。 当与 ENQCMD 一起提交工作时,设备可以区分提交工作的进程,因为它将包含分配给该进程的 PASID。 这有助于设备扩展到大量进程。
这与用户空间设备驱动程序相同吗?
通过共享工作队列与设备通信比完整的用户空间驱动程序简单得多。 内核驱动程序完成硬件的所有初始化。 用户空间只需要担心提交工作和处理完成。
这与 SR-IOV 相同吗?
单根 I/O 虚拟化 (SR-IOV) 侧重于为虚拟化硬件提供独立的硬件接口。 因此,它需要成为一个几乎完全功能的软件接口,支持传统的 BAR、通过 MSI-X 中断的空间以及其自身的寄存器布局。 虚拟功能 (VF) 由物理功能 (PF) 驱动程序协助。
可扩展 I/O 虚拟化建立在 PASID 概念的基础上,为虚拟化创建设备实例。 SIOV 需要主机软件来协助创建虚拟设备; 每个虚拟设备由 PASID 以及设备的总线/设备/功能表示。 这允许设备硬件优化设备资源的创建,并且可以根据需要动态增长。 SR-IOV 的创建和管理本质上非常静态。 有关更多详细信息,请参阅下面的参考。
为什么不为每个应用程序创建一个虚拟功能?
创建 PCIe SR-IOV 类型虚拟功能 (VF) 的成本很高。 VF 需要复制硬件以用于 PCI 配置空间和中断,例如 MSI-X。 在创建时,必须在 VF 之间对中断等资源进行硬分区,并且无法根据需要动态扩展。 VF 并不完全独立于物理功能 (PF)。 大多数 VF 都需要来自 PF 驱动程序的某种通信和协助。 相比之下,SIOV 创建了一个软件定义的设备,其中所有配置和控制方面都通过慢速路径进行调解。 工作提交和完成的发生没有任何调解。
这是否支持虚拟化?
ENQCMD 可以从来宾 VM 中使用。 在这些情况下,VMM 有助于设置转换表以从来宾 PASID 转换为主机 PASID。 请参阅 ENQCMD 指令集参考以获取更多详细信息。
是否需要固定内存?
当设备支持 SVA 以及支持此类设备的平台硬件(例如 IOMMU)时,无需为 DMA 目的固定内存。 支持 SVA 的设备也支持其他 PCIe 功能,这些功能消除了对内存的固定要求。
设备 TLB 支持 - 设备请求 IOMMU 在通过地址转换服务 (ATS) 请求使用之前查找地址。 如果映射存在但 OS 未分配页面,则 IOMMU 硬件会返回不存在映射。
设备请求通过页面请求接口 (PRI) 映射虚拟地址。 OS 成功完成映射后,它会将响应返回给设备。 设备再次请求转换并继续。
IOMMU 与 OS 协作以管理设备页表的一致性。 删除页面时,它与设备交互以删除可能已缓存的任何设备 TLB 条目,然后再从 OS 中删除映射。
31.9. 参考¶
SIOV: https://01.org/blogs/2019/assignable-interfaces-intel-scalable-i/o-virtualization-linux
ISE 中的 ENQCMD: https://software.intel.com/sites/default/files/managed/c5/15/architecture-instruction-set-extensions-programming-reference.pdf
DSA 规范: https://software.intel.com/sites/default/files/341204-intel-data-streaming-accelerator-spec.pdf