I915 VM_BIND 特性设计和用例

VM_BIND 特性

DRM_I915_GEM_VM_BIND/UNBIND ioctl 允许 UMD 在指定地址空间(VM)的指定 GPU 虚拟地址上绑定/解绑 GEM 缓冲区对象(BO)或 BO 的一部分。这些映射(也称为持久映射)将在 UMD 发出的多个 GPU 提交(execbuf 调用)中保持持久,而用户无需在每次提交期间提供所有必需的映射列表(如旧的 execbuf 模式所要求的)。

VM_BIND/UNBIND 调用允许 UMD 请求时间线输出栅栏,以指示绑定/解绑操作的完成。

VM_BIND 特性通过 I915_PARAM_VM_BIND_VERSION 向用户通告。用户必须在 VM 创建时通过 I915_VM_CREATE_FLAGS_USE_VM_BIND 扩展选择地址空间(VM)的 VM_BIND 绑定模式。

并发执行在不同 CPU 线程上的 VM_BIND/UNBIND ioctl 调用不排序。此外,当指定有效的输出栅栏时,VM_BIND/UNBIND 操作的部分可以异步完成。

VM_BIND 特性包括:

  • 多个虚拟地址(VA)映射可以映射到对象的相同物理页面(别名)。

  • VA 映射可以映射到 BO 的部分区域(部分绑定)。

  • 支持在 GPU 错误发生时捕获转储中的持久映射。

  • 支持 userptr gem 对象(无需特殊的 uapi)。

TLB 刷新考虑

i915 驱动程序在每次提交时以及当对象的页面被释放时刷新 TLB。VM_BIND/UNBIND 操作不会执行任何额外的 TLB 刷新。任何添加的 VM_BIND 映射都将存在于该 VM 的后续提交的工作集中,并且不会存在于当前正在运行的批处理的工作集中(这将需要额外的 TLB 刷新,这是不支持的)。

VM_BIND 模式下的 Execbuf ioctl

处于 VM_BIND 模式的 VM 将不支持旧的 execbuf 绑定模式。VM_BIND 模式下的 execbuf ioctl 处理与旧的 execbuf2 ioctl 有很大不同(请参阅struct drm_i915_gem_execbuffer2)。因此,添加了一个新的 execbuf3 ioctl 以支持 VM_BIND 模式。(请参阅struct drm_i915_gem_execbuffer3)。execbuf3 ioctl 将不接受任何执行列表。因此,不支持隐式同步。预计以下工作将能够支持所有用例中的对象依赖关系设置要求:

“dma-buf:添加用于导出同步文件的 API”(https://lwn.net/Articles/859290/

新的 execbuf3 ioctl 仅在 VM_BIND 模式下工作,而 VM_BIND 模式仅与用于提交的 execbuf3 ioctl 一起工作。在 execbuf3 调用时映射到该 VM 上的所有 BO(通过 VM_BIND 调用)都被视为该提交所必需的。

execbuf3 ioctl 直接指定批处理地址,而不是像 execbuf2 ioctl 中那样作为对象句柄。execbuf3 ioctl 也将不支持许多旧的功能,如输入/输出/提交栅栏、栅栏数组、默认 gem 上下文等等(请参阅struct drm_i915_gem_execbuffer3)。

在 VM_BIND 模式下,VA 分配完全由用户管理,而不是由 i915 驱动程序管理。因此,所有 VA 分配和逐出都不适用于 VM_BIND 模式。此外,为了确定对象的活动性,VM_BIND 模式将不使用 i915_vma 活动引用跟踪。它将改为使用 dma-resv 对象(请参阅VM_BIND dma_resv 用法)。

因此,许多支持 execbuf2 ioctl 的现有代码,如重定位、VA 逐出、vma 查找表、隐式同步、vma 活动引用跟踪等,都不适用于 execbuf3 ioctl。因此,所有特定于 execbuf3 的处理都应放在单独的文件中,并且只有这些 ioctl 共有的功能才可以尽可能地成为共享代码。

VM_PRIVATE 对象

默认情况下,BO 可以映射到多个 VM 上,也可以导出 dma-buf。因此,这些 BO 被称为共享 BO。在每次 execbuf 提交期间,必须将请求栅栏添加到映射到 VM 上的所有共享 BO 的 dma-resv 栅栏列表中。

VM_BIND 特性引入了一种优化,用户可以通过在 BO 创建期间使用 I915_GEM_CREATE_EXT_VM_PRIVATE 标志来创建特定于指定 VM 的私有 BO。与共享 BO 不同,这些 VM 私有 BO 只能映射到它们所属的 VM 上,并且不能导出 dma-buf。VM 的所有私有 BO 共享 dma-resv 对象。因此,在每次 execbuf 提交期间,它们只需要更新一个 dma-resv 栅栏列表。因此,快速路径(其中所需的映射已经绑定)提交延迟相对于 VM 私有 BO 的数量为 O(1)。

VM_BIND 锁定层次结构

此处的锁定设计支持旧的(基于执行列表)execbuf 模式、较新的 VM_BIND 模式、具有 GPU 页面错误的 VM_BIND 模式以及未来可能的系统分配器支持(请参阅共享虚拟内存 (SVM) 支持)。旧的 execbuf 模式和没有页面错误的较新 VM_BIND 模式使用 dma_fence 管理后备存储的驻留。带有页面错误的 VM_BIND 模式和系统分配器支持根本不使用任何 dma_fence。

VM_BIND 锁定顺序如下。

  1. 锁-A:vm_bind 互斥锁将保护 vm_bind 列表。此锁在 vm_bind/vm_unbind ioctl 调用、execbuf 路径以及释放映射时获取。

    将来,当支持 GPU 页面错误时,我们可能会使用 rwsem 代替,以便多个页面错误处理程序可以获取读取侧锁来查找映射,从而可以并行运行。旧的 execbuf 绑定模式不需要此锁。

  2. 锁-B:对象的 dma-resv 锁将保护 i915_vma 状态,并且在异步工作器中绑定/解绑 vma 时以及在更新对象的 dma-resv 栅栏列表时需要保持该锁。请注意,VM 的私有 BO 将共享一个 dma-resv 对象。

    未来的系统分配器支持将改用 HMM 规定的锁定。

  3. 锁-C:自旋锁/s 用于保护 VM 的一些列表,例如无效的 vma 列表(由于逐出和 userptr 无效等)。

当支持 GPU 页面错误时,execbuf 路径不获取任何这些锁。在那里,我们只需将新的批处理缓冲区地址粉碎到环中,然后告诉调度程序运行它。锁获取仅发生在页面错误处理程序中,我们在其中以读取模式获取锁-A,无论我们需要哪个锁-B 来找到后备存储(gem 对象的 dma_resv 锁,以及系统分配器的 hmm/核心 mm)和一些额外的锁(锁-D)来处理页表竞争。页面错误模式应该永远不需要操作 vm 列表,因此永远不需要锁-C。

VM_BIND LRU 处理

我们需要确保 VM_BIND 映射的对象被正确地标记为 LRU,以避免性能下降。我们还需要支持 VM_BIND 对象的批量 LRU 移动,以避免在 execbuf 路径中产生额外的延迟。

页表页面类似于 VM_BIND 映射的对象(请参阅可逐出的页表分配),并且按 VM 维护,当 VM 被激活时(即,在对该 VM 进行 execbuf 调用时)需要在内存中固定。因此,还需要页表页面的批量 LRU 移动。

VM_BIND dma_resv 用法

栅栏需要添加到所有 VM_BIND 映射的对象中。在每次 execbuf 提交期间,它们都以 DMA_RESV_USAGE_BOOKKEEP 用法添加,以防止过度同步(请参阅enum dma_resv_usage)。您可以在显式对象依赖关系设置期间使用 DMA_RESV_USAGE_READ 或 DMA_RESV_USAGE_WRITE 用法覆盖它。

请注意,DRM_I915_GEM_WAIT 和 DRM_I915_GEM_BUSY ioctl 不检查 DMA_RESV_USAGE_BOOKKEEP 用法,因此不应用于批处理结束检查。相反,execbuf3 输出栅栏应用于批处理结束检查(请参阅struct drm_i915_gem_execbuffer3)。

此外,在 VM_BIND 模式下,使用 dma-resv api 来确定对象的活动性(请参阅dma_resv_test_signaled()dma_resv_wait_timeout()),并且不要使用已弃用的旧的 i915_vma 活动引用跟踪。这应该更容易使其与当前的 TTM 后端一起工作。

Mesa 用例

VM_BIND 可以潜在地减少 Mesa(包括 Vulkan 和 Iris)中的 CPU 开销,从而提高 CPU 密集型应用程序的性能。它还允许我们实现 Vulkan 的稀疏资源。随着 GPU 硬件性能的提高,降低 CPU 开销变得更有影响力。

其他 VM_BIND 用例

长时间运行的计算上下文

使用 dma-fence 期望它们在合理的时间内完成。另一方面,计算可以长时间运行。因此,计算适合使用用户/内存栅栏(请参阅用户/内存栅栏),并且 dma-fence 的使用必须仅限于内核内消耗。

在 GPU 页面错误不可用的情况下,内核驱动程序在缓冲区失效时将启动长时间运行上下文的挂起(抢占),完成失效,重新验证 BO,然后恢复计算上下文。这是通过为每个上下文设置一个抢占栅栏来实现的,当有人尝试等待它时,该栅栏会启用并触发上下文抢占。

用户/内存栅栏

用户/内存栅栏是一个 <地址, 值> 对。要发出用户栅栏信号,指定的值将被写入指定的虚拟地址,并唤醒等待的进程。用户栅栏可以由 GPU 或内核异步工作线程(如绑定完成时)发出信号。用户可以使用新的用户栅栏等待 ioctl 来等待用户栅栏。

以下是一些关于此的先前工作:https://patchwork.freedesktop.org/patch/349417/

低延迟提交

允许计算 UMD 直接提交 GPU 作业,而不是通过 execbuf ioctl。这得益于 VM_BIND 不与 execbuf 同步。VM_BIND 允许绑定/解绑直接提交作业所需的映射。

调试器

借助调试事件接口,用户空间进程(调试器)能够跟踪和处理由另一个进程(被调试的进程)创建并通过 vm_bind 接口附加到 GPU 的资源。

GPU 页面错误

GPU 页面错误(如果将来支持)将仅在 VM_BIND 模式下受支持。虽然较旧的 execbuf 模式和较新的 VM_BIND 模式的绑定都需要使用 dma-fence 来确保驻留,但 GPU 页面错误模式(如果支持)将不使用任何 dma-fence,因为驻留完全通过安装和删除/失效页表条目来管理。

页面级提示设置

VM_BIND 允许为每个映射设置任何提示,而不是为每个 BO 设置。可能的提示包括放置和原子性。随着即将到来的 GPU 按需页面错误支持,子 BO 级别的放置提示将更加相关。

页面级缓存/CLOS 设置

VM_BIND 允许为每个映射设置缓存/CLOS 设置,而不是为每个 BO 设置。

可驱逐的页表分配

使页表分配可驱逐,并以类似于 VM_BIND 映射对象的方式管理它们。页表页面类似于 VM 的持久映射(这里的区别在于页表页面将没有 i915_vma 结构,并且在将页面换入后,需要更新父页面链接)。

共享虚拟内存 (SVM) 支持

可以使用 HMM 接口直接映射系统内存(无需 gem BO 抽象)来使用 VM_BIND 接口。SVM 仅在启用 GPU 页面错误时受支持。

VM_BIND UAPI

I915_PARAM_VM_BIND_VERSION

支持的 VM_BIND 功能版本。请参阅 typedef drm_i915_getparam_t 参数。

指定支持的 VM_BIND 功能版本。已定义以下版本的 VM_BIND

0:不支持 VM_BIND。

1:在 VM_UNBIND 调用中,UMD 必须指定先前使用 VM_BIND 创建的确切映射,ioctl 将不支持解绑多个映射或拆分它们。同样,VM_BIND 调用将不会替换任何现有映射。

先前使用 VM_BIND 创建的确切映射,ioctl 将不支持解绑多个映射或拆分它们。同样,VM_BIND 调用将不会替换任何现有映射。

2:取消了对解绑部分或多个映射的限制,同样,绑定将替换给定范围内的任何映射。

取消了对解绑部分或多个映射的限制,同样,绑定将替换给定范围内的任何映射。

请参阅 struct drm_i915_gem_vm_bindstruct drm_i915_gem_vm_unbind

I915_VM_CREATE_FLAGS_USE_VM_BIND

在 VM 创建期间选择加入 VM_BIND 绑定模式的标志。请参阅 struct drm_i915_gem_vm_control 标志。

较旧的 execbuf2 ioctl 将不支持 VM_BIND 模式的操作。对于 VM_BIND 模式,我们有新的 execbuf3 ioctl,它将不接受任何 execlist(有关详细信息,请参阅 struct drm_i915_gem_execbuffer3)。

struct drm_i915_gem_timeline_fence

输入或输出时间线栅栏。

定义:

struct drm_i915_gem_timeline_fence {
    __u32 handle;
    __u32 flags;
#define I915_TIMELINE_FENCE_WAIT            (1 << 0);
#define I915_TIMELINE_FENCE_SIGNAL          (1 << 1);
#define __I915_TIMELINE_FENCE_UNKNOWN_FLAGS (-(I915_TIMELINE_FENCE_SIGNAL << 1));
    __u64 value;
};

成员

handle

用户用于等待或发出信号的 drm_syncobj 句柄。

flags

支持的标志是

I915_TIMELINE_FENCE_WAIT:在操作之前等待输入栅栏。

I915_TIMELINE_FENCE_SIGNAL:将操作完成栅栏作为输出返回。

value

时间线中的一个点。对于二进制 drm_syncobj,值必须为 0。对于时间线 drm_syncobj,值为 0 无效,因为它会将 drm_syncobj 转换为二进制 drm_syncobj。

描述

该操作将等待输入栅栏发出信号。

返回的输出栅栏将在操作完成后发出信号。

struct drm_i915_gem_vm_bind

要绑定的 VA 到对象的映射。

定义:

struct drm_i915_gem_vm_bind {
    __u32 vm_id;
    __u32 handle;
    __u64 start;
    __u64 offset;
    __u64 length;
    __u64 flags;
#define I915_GEM_VM_BIND_CAPTURE        (1 << 0);
    struct drm_i915_gem_timeline_fence fence;
    __u64 extensions;
};

成员

vm_id

要绑定的 VM(地址空间)ID

handle

对象句柄

start

要绑定的虚拟地址起始位置

offset

要绑定的对象中的偏移量

length

要绑定的映射的长度

flags

支持的标志是

I915_GEM_VM_BIND_CAPTURE:在 GPU 错误时将此映射捕获到转储中。

请注意,栅栏带有其自己的标志。

fence

用于绑定完成信号的时间线栅栏。

时间线栅栏的格式为 struct drm_i915_gem_timeline_fence

它是输出栅栏,因此使用 I915_TIMELINE_FENCE_WAIT 标志无效,并且将返回错误。

如果未设置 I915_TIMELINE_FENCE_SIGNAL 标志,则不请求输出栅栏,并且绑定同步完成。

extensions

以零结尾的扩展链。

用于未来的扩展。请参阅 struct i915_user_extension

描述

此结构被传递给 VM_BIND ioctl,并指定 GPU 虚拟地址 (VA) 范围到应绑定在指定地址空间 (VM) 的设备页表中的对象部分的映射。指定的 VA 范围必须是唯一的(即,当前未绑定),并且可以映射到整个对象或对象的一部分(部分绑定)。可以创建多个 VA 映射到对象的同一部分(别名)。

startoffsetlength 必须是 4K 页对齐的。但是,DG2 对于设备本地内存具有 64K 页大小,并且具有紧凑的页表。在该平台上,对于绑定设备本地内存对象,startoffsetlength 必须是 64K 对齐的。此外,UMD 不应在相同的 2M 范围内混合本地内存 64K 页面和系统内存 4K 页面绑定。

如果 startoffsetlength 未正确对齐,则将返回错误代码 -EINVAL。在版本 1 中(请参阅 I915_PARAM_VM_BIND_VERSION),如果无法保留指定的 VA 范围,则将返回错误代码 -ENOSPC。

在不同的 CPU 线程上并发执行的 VM_BIND/UNBIND ioctl 调用是无序的。此外,如果指定了有效的 fence,则可以异步完成 VM_BIND 操作的一部分。

struct drm_i915_gem_vm_unbind

要解绑的 VA 到对象的映射。

定义:

struct drm_i915_gem_vm_unbind {
    __u32 vm_id;
    __u32 rsvd;
    __u64 start;
    __u64 length;
    __u64 flags;
    struct drm_i915_gem_timeline_fence fence;
    __u64 extensions;
};

成员

vm_id

要绑定的 VM(地址空间)ID

rsvd

保留,MBZ

start

要解绑的虚拟地址起始位置

length

要解绑的映射的长度

flags

当前保留,MBZ。

请注意,栅栏带有其自己的标志。

fence

用于解绑完成信号的时间线栅栏。

时间线栅栏的格式为 struct drm_i915_gem_timeline_fence

它是输出栅栏,因此使用 I915_TIMELINE_FENCE_WAIT 标志无效,并且将返回错误。

如果未设置 I915_TIMELINE_FENCE_SIGNAL 标志,则不请求输出栅栏,并且解绑同步完成。

extensions

以零结尾的扩展链。

用于未来的扩展。请参阅 struct i915_user_extension

描述

此结构被传递给 VM_UNBIND ioctl,并指定应从指定地址空间 (VM) 的设备页表中解绑的 GPU 虚拟地址 (VA) 范围。VM_UNBIND 将强制从设备页表中解绑指定的范围,而无需等待任何 GPU 作业完成。UMD 有责任确保在调用 VM_UNBIND 之前该映射不再使用。

如果未找到指定的映射,则 ioctl 将直接返回,而不返回任何错误。

在不同的 CPU 线程上并发执行的 VM_BIND/UNBIND ioctl 调用是无序的。此外,如果指定了有效的 fence,则可以异步完成 VM_UNBIND 操作的一部分。

struct drm_i915_gem_execbuffer3

用于 DRM_I915_GEM_EXECBUFFER3 ioctl 的结构。

定义:

struct drm_i915_gem_execbuffer3 {
    __u32 ctx_id;
    __u32 engine_idx;
    __u64 batch_address;
    __u64 flags;
    __u32 rsvd1;
    __u32 fence_count;
    __u64 timeline_fences;
    __u64 rsvd2;
    __u64 extensions;
};

成员

ctx_id

上下文 ID

仅允许具有用户引擎映射的上下文。

engine_idx

引擎索引

ctx_id 指定的上下文的用户引擎映射中的索引。

batch_address

批处理 GPU 虚拟地址/们。

对于正常提交,它是批处理缓冲区的 GPU 虚拟地址。对于并行提交,它是指向批处理缓冲区 GPU 虚拟地址数组的指针,数组大小等于参与该提交的(并行)引擎的数量(请参阅 struct i915_context_engines_parallel_submit)。

flags

当前保留,MBZ

rsvd1

保留,MBZ

fence_count

timeline_fences 数组中的栅栏数。

timeline_fences

指向时间线栅栏数组的指针。

时间线栅栏的格式为 struct drm_i915_gem_timeline_fence

rsvd2

保留,MBZ

extensions

以零结尾的扩展链。

用于未来的扩展。请参阅 struct i915_user_extension

描述

DRM_I915_GEM_EXECBUFFER3 ioctl 仅在 VM_BIND 模式下工作,而 VM_BIND 模式仅使用此 ioctl 进行提交。请参阅 I915_VM_CREATE_FLAGS_USE_VM_BIND。

struct drm_i915_gem_create_ext_vm_private

使对象对指定 VM 私有的扩展。

定义:

struct drm_i915_gem_create_ext_vm_private {
#define I915_GEM_CREATE_EXT_VM_PRIVATE          2;
    struct i915_user_extension base;
    __u32 vm_id;
};

成员

base

扩展链接。请参阅 struct i915_user_extension

vm_id

对象对其私有的 VM 的 ID

描述

请参阅 struct drm_i915_gem_create_ext