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, 1TB) 分配逻辑地址,然后可以通过 IOMMU 映射到系统上的任何物理内存。

逻辑适配器与物理适配器

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

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

集成 GPU 支持与离散 GPU 支持

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

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

DDI 更改

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

驱动程序功能

需要两组驱动程序上限才能支持线性重新映射:

Dxgkrnl 通过 DXGKDDI_START_DEVICE 启动设备之前,必须提供这两个上限,以便创建设备并将其附加到 IOMMU 域,然后才能访问任何内存。 仅当设备未引用任何现有物理内存时,才能完成线性重新映射。

独占访问

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

若要处理这种情况,从 Windows 10 版本 1803 (WDDM 2.4) 开始,KMD 必须实现以下 DDI 对才能调用 Dxgkrnl

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

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

  • 计划程序已暂停。 将刷新所有活动工作负载,并且不会向硬件发送新工作负载,也不会在硬件上计划任何新工作负载。
  • 不会进行其他 DDI 调用。

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

地址描述符列表

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

DxgkddiBuildpagingbuffer 的DXGK_OPERATION_MAP_APERTURE_SEGMENT2操作

VidMm 提供 DXGK_OPERATION_MAP_APERTURE_SEGMENT2 分页缓冲区模式,用于将内存映射到光圈段,因为以前的版本使用与逻辑地址不兼容的 MDL。 支持逻辑地址重新映射的 WDDM 3.0 驱动程序的 DxgkddiBuildpagingbuffer 回调接收对 DXGK_OPERATION_MAP_APERTURE_SEGMENT2模式的 调用,并且不再接收对原始 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 可见的逻辑地址。 这将为调用方提供逻辑页列表, (与可以编程为访问 GPU 的 MDL) 的 PFN 数组非常类似。 调用这些函数将保证基础物理页处于锁定状态且不可分页。

  3. 能够在用户模式和内核模式下映射物理内存中的 CPU 虚拟地址,并具有指定的缓存类型 (Cached 和 WriteCombined) 。

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

必须在 IRQL <= APC_LEVEL 调用回调函数。 从 WDDM 3.2 开始,调用任一函数的驱动程序将针对此要求进行验证,并在 IRQL DISPATCH_LEVEL或更高版本时进行 bug 检查。

回调 关联的回调结构
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 上,但值“3”从Windows 10版本 1803 (WDDM 2.4) 开始是唯一的,在不支持它的旧版本上被忽略。 这将允许驱动程序在 INF 中设置此密钥,而无需担心兼容性问题级别较低。