SMMU软件指南之使用案例(Stage 1使用场景)
安全之安全(security²)博客目录导读
目录
一、Stage 1使用场景
1、Scatter-gather
2、64 位操作系统中的 32 位设备
3、操作系统设备隔离
4、用户空间设备驱动程序
5、用户空间共享虚拟地址 (SVA)
本博客描述以下使用场景:
- 一级转换使用场景Stage 1 use cases
本节中的 Linux 描述基于版本 6.4。某些使用场景尚未在此版本中实现,可能在后续版本中实现或仍在开发中。相关信息在每个使用场景中注明。
一、Stage 1使用场景
本节描述单一一级转换(single stage 1 translation)的使用。
1、Scatter-gather
一些高级设备可以使用scatter-gather列表请求 DMA,这些列表包含多个非连续缓冲区地址,组成单个请求。然而,简单设备通常不支持scatter-gather,它们只能请求连续缓冲区的 DMA。当系统运行时,物理内存可能变得碎片化,因此不一定能保证连续缓冲区。操作系统可能需要将较大范围的 DMA 请求分成多个较小范围的请求,这会影响性能。
SMMU 可以解决这个问题。操作系统使用 SMMU 向设备提供连续缓冲区,即使该缓冲区在物理上并不连续。通过在转换表中分配具有连续虚拟地址的页面,并将它们映射到非连续的物理地址,操作系统可以使用虚拟地址编程 DMA 地址。
在 Linux 中,如果设备驱动程序为不支持scatter-gather的设备使用 DMA API 分配分散页面,SMMU 驱动程序会执行“gather”工作,对设备驱动程序透明。
由于该地址空间仅为设备创建,与应用进程虚拟内存地址空间无关,不能使用广播 TLB 维护(Broadcast TLB maintenance)从 PE 失效 SMMU 的 TLB。虽然 SMMU 也使用 ASID 来区分不同的地址空间,但 SMMU 的 ASID 命名空间与 PE 的 ASID 命名空间是独立的。为了确保它们在不同的 ASID 命名空间中,操作系统需要设置 CD.ASET = 1,以便由该上下文创建的 SMMU TLB 条目不需要通过某些广播失效来失效。
2、64 位操作系统中的 32 位设备
一些传统设备仅支持 32 位地址的 DMA。由于操作系统可能会将 DMA 缓冲区分配到超出 32 位范围的物理地址,因此需要一种解决方案来确保这些传统设备仍然可以工作。SMMU 通过基于设备的 StreamID 创建一个独立的虚拟内存映射,允许设备在物理地址超出 32 位范围时在 32 位范围内发出 DMA 请求。在 Linux 中,如果满足以下两个条件:
- 设备驱动程序使用 DMA API 为设备分配页面。
- 页面物理地址超过 4GB。
SMMU驱动程序执行地址映射工作,将低于4GB的IOVA映射到超过4GB的分配页面。这项工作对设备驱动程序是透明的。像前面的scatter-gather使用场景一样,该地址空间仅为设备创建,与应用进程虚拟内存地址空间无关,不能使用广播 TLB 维护从 PE 失效 SMMU 的 TLB。
支持 64 位系统上的 32 位传统设备的替代方案是 OS 在低地址范围内维护一个 DMA “跳板缓冲区”池,并在缓冲区与原始 64 位位置之间复制数据。例如,SWIOTLB 在没有 SMMU 的系统或 SMMU 在 Linux 中被禁用时执行此工作。对于大规模传输,使用 SMMU 明显更快。
3、操作系统设备隔离
恶意设备可能会通过互连发起不当的 DMA 传输,导致数据被损坏或被读取。具有 SMMU 的系统中,设备只能访问为 DMA 映射的页面。许多操作系统提供通用 API,设备驱动程序在执行 DMA 传输时使用这些 API。例如,在 Linux 中,这些 DMA API 安排分配、确保适当的地址限制,并在使用非缓存一致 DMA 时执行缓存维护操作。
这些 API 可跟踪任何特定设备访问的页面,并配置 SMMU 以映射正确引用的地址,同时阻止对其他地址的访问。任何编程错误都可以被检测并隔离。失效的设备或驱动程序错误不会影响未配置为 DMA 的页面集之外的数据。这比允许设备进行不受限制的 DMA 更可靠。当存在不受信任或恶意设备时(例如通过外部互连连接),这对安全尤为重要。
在此使用场景中,SMMU 转换表不与 PE 共享,因此不需要共享 ASID。
4、用户空间设备驱动程序
某些系统可能需要用户空间设备驱动程序。用户空间驱动程序用于性能、灵活性、鲁棒性或安全性。例如,应用程序可能需要低延迟地访问加速器,而无需使用系统调用,避免在用户空间和内核空间之间复制数据的开销。
为此,操作系统将设备 MMIO 寄存器映射到用户进程虚拟地址空间。如果用户空间控制的设备能够请求 DMA,则其传输具有与用户进程相同的信任等级。该设备必须置于 SMMU 后,以确保设备 DMA 访问仅在用户进程地址映射的许可区域内正确执行。SMMU 保护系统不受由恶意或不正确的用户空间程序控制的 DMA 影响。
当设备分配给应用程序时,软件会分配用于 DMA 的特殊内存区域。操作系统随后为该设备在 SMMU 设置一个私有的转换表,映射这些内存区域以供设备访问。建议该内存出现在进程 VA 空间和设备地址空间中的相同地址上,以便编程人员能够轻松配置 DMA 指针,但这不是强制性的。
在 Linux 中,VFIO 子系统允许应用程序使用设备。VFIO 允许应用程序选择一个 IOVA,将其映射到进程中的选定内存区域,并在将 DMA 指针编程到设备中时使用 IOVA。SMMU 使设备的 DMA 可以访问进程选定区域的内存,设备和 PE 通过该内存共享数据。SMMU 阻止设备访问进程地址空间的其他部分或其他进程的地址空间。
由于用户空间内存通常按需分配和交换,因此必须考虑 DMA 的页面错误。在许多系统中,与转换相关的错误对 DMA 传输是致命的。许多设备驱动程序通过固定 DMA 目标页面来避免与转换相关的错误。由于 DMA 仅发生在特定的内存区域,这些区域通常较小,因此固定内存是合理的要求。
5、用户空间共享虚拟地址 (SVA)
用户空间设备驱动程序使用场景介绍了用户空间设备驱动程序,但有一些限制:SMMU 转换表必须将特殊的 DMA 区域映射到用户空间设备驱动程序的地址空间中。
此使用场景考虑了与设备共享整个用户进程地址空间的替代方案,而不是特定的区域。与设备共享进程地址空间依赖于 CPU 内核内存管理来支持 DMA。这减少了应用程序和设备驱动程序的一些复杂性。设备的 MMIO 寄存器通过 PE 以与用户空间设备驱动程序相同的方式进行虚拟映射。但是,现在 SMMU 将设备与整个进程地址空间关联,而不是其子集。设备需要能够物理访问任何用户空间虚拟地址。例如,当设备只能使用 32 位地址,且进程使用 48 位虚拟地址时,SVA 就无法实现。
为了实现共享地址空间,SMMU 可以直接使用进程的转换表和 ASID。由于使用了公共 ASID,且虚拟地址在 PE 和 SMMU 上具有相同的含义,因此可以使用广播 TLB 维护来使 PE 失效 SMMU 转换。我们推荐使用广播失效,因为手动 TLB 失效命令的慢速往返可以避免。使用公共失效方法可能还会带来软件上的简化。
将每个页面固定内存以防进程尝试将 DMA 编程到它们上是不切实际的。DMA 可能会访问任何地址,无论页面表是否已经映射该地址,因此设备和系统必须支持 DMA 访问时的页面错误。这意味着:
- 当检测到页面错误时,设备 DMA 流必须不会被致命地中止。
- 需要一种机制通知操作系统,以便恢复 DMA 传输:
- 发生了错误。
- 需要采取措施使所需页面可访问。
SMMU 和设备协同工作,支持 DMA 上的页面错误非致命的要求。例如,设备 DMA 流可能会在 SMMU 中配置为在错误发生时暂停。当错误发生时,SMMU 将向软件报告错误描述符,并且该错误事务会暂停,直到页面错误被解决。软件发出命令,要求 SMMU 重试事务,或者如果错误是由于不正确访问引起的,则中止事务。设备在响应 DMA 请求时会经历一个小的延迟。
另外,一个更智能的设备如果得知错误,可能能够有效地调度流量。例如,SMMU 被配置为中止错误的事务,并将错误通知设备。设备将事件报告给其设备驱动程序,后者解决页面错误的原因。这种方法的优点是避免了在 SMMU 中暂停流量,这可能会影响其他事务。然而,使用这种方法需要特定设备的软件代码。例如,Linux 不支持 SVA 对于仅支持中止错误事务的非 PCIe 设备。
类似的方法也被 PCIe PRI 使用,PRI 在 ATS 响应中将错误报告回设备。PRI 提供了一个标准的页面请求接口(Page Request Interface),使设备能够请求软件通过根端口和 SMMU 解决页面错误。PRI 不要求设备特定的页面错误解决例程,这有助于减少标准软件(如虚拟机管理程序)中的设备特定软件需求。
在 Linux 中,VFIO 子系统提供了一个接口,以启用应用程序的 SVA。要支持 SVA,设备、总线和 SMMU 必须支持以下功能:
- 每个设备多个地址空间,例如使用 PCI PASID 扩展。SMMU 驱动程序分配 PASID,设备在 DMA 事务中使用它。
- I/O 页面错误:
- 对于 PCIe 设备,需要 PRI。
- 对于非 PCIe 设备,需要使用 Stall 模型。
- 核心内存管理处理来自 SMMU 的转换错误。
- MMU 和 SMMU 实现兼容的页面表格式。