IOMMU DMA 重新映射

本页介绍 Windows 11 22H2 (WDDM 3.0) 中引入的 IOMMU DMA 重新映射功能 (IOMMUv2)。 有关 WDDM 3.0 之前的 IOMMU GPU 隔离的信息,请参阅基于 IOMMU 的 GPU 隔离

概述

直到 WDDM 3.0,Dxgkrnl 仅支持通过 1:1 物理重新映射实现 IOMMU 隔离,这意味着 GPU 访问的逻辑页被转换为相同的物理页码。 IOMMU DMA 重新映射允许 GPU 通过不再以 1:1 映射的逻辑地址访问内存。 相反,Dxgkrnl 能够提供逻辑上连续的地址范围。

Dxgkrnl 对 GPU 施加限制:GPU 必须能够访问所有物理内存才能启动设备。 如果 GPU 的最高可见地址不超过系统上安装的最高物理地址,则 Dxgkrnl 将无法初始化适配器。 即将推出的服务器和高端工作站可以配置超过 1 TB 的内存,突破了许多 GPU 常见的 40 位地址空间限制。 DMA 重新映射用作允许 GPU 在此环境中工作的机制。

在启动时,Dxgkrnl 通过将设备的最高可访问物理地址与系统上安装的内存进行比较来确定是否需要逻辑重新映射。 如有必要,DMA 重新映射用于将 GPU 可见边界内的逻辑地址范围映射到系统上的任何物理内存。 例如,如果 GPU 限制为 1 TB,则 Dxgkrnl 将从 [0, 1 TB) 分配逻辑地址,然后可以通过 IOMMU 映射到系统上的任何物理内存。

逻辑适配器与物理适配器

Dxgkrnl 区分逻辑适配器和物理适配器的概念。 物理适配器代表一个单独的硬件设备,它可能与 LDA 链中的其他设备链接。 逻辑适配器表示一个或多个链接的物理适配器。

每个逻辑适配器创建一个 IOMMU DMA 域,并将其连接到所有链接的物理适配器。 因此,所有物理适配器共享相同的域和相同的物理内存视图。

集成 GPU 与离散 GPU 支持

由于 IOMMU DMA 重新映射对集成 GPU 几乎没有价值,因此,根据定义,集成 GPU 应该已经被设计为访问系统中的所有物理内存,因此在集成部件上实现支持是可选的,但建议这样做。

离散 GPU 必须支持 IOMMU DMA 重新映射,这是 WDDM 3.0 认证的要求。

DDI 更改

为了支持 IOMMU DMA 重新映射,进行了以下 DDI 更改。

驱动程序功能

需要两组驱动器上限来支持线性重新映射:

Dxgkrnl 通过 DXGKDDI_START_DEVICE 启动设备之前,必须提供这两个上限,以便可以在访问任何内存之前创建设备并将其连接到 IOMMU 域。 只有当设备不引用任何现有物理内存时,才能进行线性重新映射。

独占访问

IOMMU 域的连接和分离速度极快,但目前还不是原子的。 这种情况意味着,在交换到具有不同映射的 IOMMU 域时,不能保证通过 PCIe 发出的事务能够正确转换。

为了处理这种情况,从 Windows 10 版本 1803 (WDDM 2.4) 开始,KMD 必须实现以下 DDI 对,以便 Dxgkrnl 调用:

每当设备切换到新的 IOMMU 域时,驱动程序必须确保其硬件保持无提示。 也就是说,驱动程序必须确保在这两次调用之间不会从设备读取或写入系统内存。

在这两个调用之间,Dxgkrnl 做出以下保证:

  • 计划程序已挂起。 所有活动工作负载都被刷新,并且不会向硬件发送或在硬件上计划新的工作负载。
  • 没有进行其他 DDI 调用。

作为这些调用的一部分,驱动程序可以选择在独占访问期间禁用和禁止中断(包括 Vsync 中断),即使没有 OS 的明确通知。

地址描述符列表

为了同时支持物理和逻辑访问模式,并在运行时无缝切换这两种模式,Dxgkrnl 提供了一个描述地址描述符列表 (ADL) 的 DXGK_ADL 结构。 此数据结构类似于 MDL,但描述了一个可以是物理或逻辑的页面数组。 由于这些页可以是逻辑页,因此不能将 ADL 描述的地址映射到虚拟地址以直接访问 CPU。

DxgkddiBuildpagingbuffer 的 DXGK_OPERATION_MAP_APERTURE_SEGMENT2 操作

VidMm 提供 DXGK_OPERATION_MAP_APERTURE_SEGMENT2 分页缓冲区模式,用于将内存映射到光圈段,因为以前的版本使用与逻辑地址不兼容的 MDL。 支持逻辑地址重新映射到 DXGK_OPERATION_MAP_APERTURE_SEGMENT2 模式的 WDDM 3.0 驱动程序的 DxgkddiBuildpagingbuffer 回调,并且不再接收对原始 DXGK_OPERATION_MAP_APERTURE_SEGMENT 模式的调用。

此操作是支持逻辑 DMA 重新映射所必需的。 它的行为与原始操作类似,但提供 DXGK_ADL 而不是 MDL

typedef enum _DXGK_BUILDPAGINGBUFFER_OPERATION
{
#if (DXGKDDI_INTERFACE_VERSION >= DXGKDDI_INTERFACE_VERSION_WDDM2_9)
    DXGK_OPERATION_MAP_APERTURE_SEGMENT2 = 17,
#endif //  DXGKDDI_INTERFACE_VERSION
};

// struct _DXGKARG_BUILDPAGINGBUFFER:
struct
{
    HANDLE  hDevice;
    HANDLE  hAllocation;
    UINT    SegmentId;
    SIZE_T  OffsetInPages;
    SIZE_T  NumberOfPages;
    DXGK_ADL Adl;
    DXGK_MAPAPERTUREFLAGS Flags;
    ULONG   AdlOffset;
    PVOID   CpuVisibleAddress;
} MapApertureSegment2;

若要选择加入 DXGK_OPERATION_MAP_APERTURE_SEGMENT2 操作,驱动程序必须在内存管理上限中指示支持 MapApertureSegment2 调用:

typedef struct _DXGK_VIDMMCAPS {
  union {
    struct {
        ...
        UINT MapAperture2Supported : 1;
        ...
    }
    ...
} DXGK_VIDMMCAPS;

DXGK_VIDMMCAPS 内存管理上限是 DXGK_DRIVERCAPS 数据结构的一部分。 如果不启用此支持,驱动程序将无法使用 DMA 重新映射(即逻辑地址重新映射)功能。

MapApertureSegment2 调用期间,某些驱动程序可能需要 CPU 访问内存。 此功能(可选)通过另一个 MapApertureSegment2.CpuVisibleAddress 参数提供。 此地址是一个内核模式虚拟地址,只要分配被映射到光圈段中,该地址就有效。 也就是说,在对相同的分配进行相应的 DXGK_OPERATION_UNMAP_APERTURE_SEGMENT 调用后,此地址将立即被释放。

并非所有分配都需要此地址。 分配标志中添加了 MapApertureCpuVisible 标志,以指示何时需要此地址。

如果未指定 MapApertureCpuVisible,则 MapApertureSegment2.CpuVisibleAddress 对于 DXGK_OPERATION_MAP_APERTURE_SEGMENT2 操作为 NULL。

MapApertureCpuVisibleDxgkDdiBuildPagingBufferMapAperatureSegment2 功能的一部分,因此驱动程序必须设置 DXGK_VIDMMCAPS MapAperature2Supported 才能使用此字段。 如果未设置 MapAperature2Supported,但驱动程序指定 MapApertureCpuVisible,则对 DxgkDdiCreateAllocation 的调用将失败。

此外,为了接收 DXGK_OPERATION_MAP_APERTURE_SEGMENT2 操作,驱动程序必须设置 DXGK_ALLOCATIONINFOFLAGS_WDDM2_0 AccessedPhysically 标志。 如果未设置 AccessedPhysically,则任何在其支持的段集中指定光圈段的分配都将升级为隐式系统内存段,该内存段不会接收 MAP_APERTURE 调用(因为没有要映射的光圈范围)。

总之,若要正确接收系统内存分配的 CPU 地址,驱动程序必须设置以下标志/上限:

  • DXGK_DRIVERCAPS::MemoryManagementCaps.MapAperture2Supported = 1
  • DXGK_ALLOCATIONINFOFLAGS_WDDM2_0::MapApertureCpuVisible = 1
  • DXGK_ALLOCATIONINFOFLAGS_WDDM2_0::AccessedPhysically = 1

对于 MapApertureSegment2 调用,启用逻辑映射时,ADL 始终被初始化并作为连续传递。 驱动程序必须检查 ADL 标志,以确定分配是否连续,并相应地采取行动。

内存管理服务

内存管理功能有三个基本要求:

  1. 管理物理内存的能力。 此功能可能包括通过非分页内存函数(例如 MmAllocatePagesforMdlMmAllocateContiguousMemory)和分页内存函数(如 ZwCreateSectionZwAllocateVirtualMemory)分配内存。 还需要表达 IO 空间范围的能力。

  2. 从物理内存映射 GPU 可见逻辑地址的能力。 此功能将为调用方提供逻辑页列表(非常类似于 MDL 的 PFN 数组),GPU 可以对其进行编程以访问这些页面。 调用这些函数可以保证基础物理页已锁定且不可分页。

  3. 在用户模式和内核模式下,使用指定的缓存类型 (Cached vs WriteCombined) 从物理内存映射 CPU 虚拟地址的能力。

下表列出了用于描述物理内存分配和逻辑/虚拟视图映射的 DDI 和相关输入结构。 这些 DDI 是一个更新的集合,用于替换之前为管理 IOMMU 映射而提供给驱动程序的回调(DxgkCbAllocatePagesforMdlDxgkCbAllocateContiguousMemoryDxgkCbMapMdlToIoMmu)。 对于支持逻辑重新映射的 WDDM 3.0 驱动程序,这些旧的回调函数已被弃用,无法使用。 驱动程序应改为使用以下内存管理回调函数。

回调函数必须在 IRQL <= APC_LEVEL 时调用。 从 WDDM 3.2 开始,调用这些函数中的任何一个驱动程序都会根据此要求进行验证,并在 IRQL 为 DISPATCH_LEVEL 或更高时进行错误检查。

回调 关联的回调结构
DXGKCB_CREATEPHYSICALMEMORYOBJECT DXGKARGCB_CREATE_PHYSICAL_MEMORY_OBJECT
DXGKCB_DESTROYPHYSICALMEMORYOBJECT DXGKARGCB_DESTROY_PHYSICAL_MEMORY_OBJECT
DXGKCB_MAPPHYSICALMEMORY DXGKARGCB_MAP_PHYSICAL_MEMORY
DXGKCB_UNMAPPHYSICALMEMORY DXGKARGCB_UNMAP_PHYSICAL_MEMORY
DXGKCB_ALLOCATEADL DXGKARGCB_ALLOCATE_ADL
DXGKCB_FREEADL
DXGKCB_OPENPHYSICALMEMORYOBJECT DXGKARGCB_OPEN_PHYSICAL_MEMORY_OBJECT
DXGKCB_CLOSEPHYSICALMEMORYOBJECT DXGKARGCB_CLOSE_PHYSICAL_MEMORY_OBJECT

INF 更改

每个受支持的设备类型都必须将以下注册表项和值添加到 INF 的相应部分:

[DMAr.reg]
; Add REG_DWORD 'DmaRemappingCompatible' with value of 3 
HKR,Parameters,DmaRemappingCompatible,0x00010001,```3

此值通知 PnP 设备支持 DMA 重新映射。 然后,Dxgkrnl 和 HAL 进行协调,以确定应使用哪种类型的映射模式(重新映射、直通等)。

尽管此注册表项存在于较旧版本的 Windows 上,但从 Windows 10 版本 1803 (WDDM 2.4) 开始,值“3”是唯一的,在不支持它的旧版本上会被忽略。 此唯一值允许驱动程序在 INF 中设置此键,而不必担心兼容性问题。