缓冲区共享和同步 (dma-buf)

dma-buf 子系统为跨多个设备驱动程序和子系统共享硬件(DMA)访问的缓冲区以及同步异步硬件访问提供了框架。

例如,它被 DRM 子系统广泛用于在进程、上下文、同一进程内的库 API 之间交换缓冲区,以及与 V4L2 等其他子系统交换缓冲区。

本文档描述了内核子系统如何使用和与 dma-buf 提供的三个主要原语交互

  • dma-buf,表示一个 sg_table 并作为文件描述符暴露给用户空间,以允许在进程、子系统、设备等之间传递;

  • dma-fence,提供一种机制来表示异步硬件操作何时完成;以及

  • dma-resv,它管理特定 dma-buf 的一组 dma-fences,允许对工作进行隐式(内核排序)同步,以保持一致访问的假象

用户空间 API 原则和使用

有关如何为 dma-buf 使用设计子系统的 API 的更多详细信息,请参阅 交换像素缓冲区

共享 DMA 缓冲区

本文档作为设备驱动程序编写者的指南,介绍什么是 dma-buf 缓冲区共享 API,以及如何使用它来导出和使用共享缓冲区。

任何希望成为 DMA 缓冲区共享一部分的设备驱动程序,都可以作为缓冲区的“导出器”或缓冲区的“用户”或“导入器”。

假设驱动程序 A 希望使用由驱动程序 B 创建的缓冲区,那么我们称 B 为导出器,A 为缓冲区用户/导入器。

导出器

  • struct dma_buf_ops 中实现和管理缓冲区的操作,

  • 允许其他用户通过使用 dma_buf 共享 API 来共享缓冲区,

  • 管理缓冲区分配的详细信息,包装在 struct dma_buf 中,

  • 决定实际的后备存储发生的位置,

  • 并负责任何散列表的迁移 - 对于此缓冲区的所有(共享)用户。

缓冲区用户

  • 是缓冲区的(多个)共享用户之一。

  • 不需要担心缓冲区是如何分配的,或者在哪里。

  • 需要一种机制来访问构成内存中此缓冲区的散列表,该散列表映射到其自己的地址空间中,以便它可以访问同一内存区域。此接口由 struct dma_buf_attachment 提供。

dma-buf 缓冲区共享框架的任何导出器或用户都必须在其各自的 Kconfig 中具有“select DMA_SHARED_BUFFER”。

用户空间接口说明

通常,DMA 缓冲区文件描述符对于用户空间来说只是一个不透明的对象,因此暴露的通用接口非常少。不过,有几件事需要考虑

  • 自内核 3.12 以来,dma-buf FD 支持 llseek 系统调用,但仅支持 offset=0 和 whence=SEEK_END|SEEK_SET。支持 SEEK_SET 以允许通常的大小发现模式 size = SEEK_END(0); SEEK_SET(0)。任何其他 llseek 操作都会报告 -EINVAL。

    如果 dma-buf FD 上不支持 llseek,则内核将在所有情况下报告 -ESPIPE。用户空间可以使用它来检测使用 llseek 发现 dma-buf 大小的支持。

  • 为了避免在 exec 时出现 fd 泄漏,必须在文件描述符上设置 FD_CLOEXEC 标志。这不仅仅是资源泄漏,而且是一个潜在的安全漏洞。它可以通过泄漏的 fd 使新 exec 的应用程序访问本不应允许访问的缓冲区。

    通过单独的 fcntl() 调用而不是在创建 fd 时原子地执行此操作的问题在于,这在多线程应用程序中本质上是竞争的 [3]。当它是库代码打开/创建文件描述符时,问题会更加严重,因为应用程序甚至可能不知道 fd 的存在。

    为了避免此问题,用户空间必须有一种方法来请求在创建 dma-buf fd 时设置 O_CLOEXEC 标志。因此,导出驱动程序提供的任何用于创建 dmabuf fd 的 API 都必须提供一种方法,让用户空间控制传递给 dma_buf_fd() 的 O_CLOEXEC 标志的设置。

  • 还支持内存映射 DMA 缓冲区的内容。有关详细信息,请参阅下面关于 CPU 访问 DMA 缓冲区对象的讨论。

  • DMA 缓冲区 FD 也是可轮询的,有关详细信息,请参阅下面的 隐式围栏轮询支持

  • DMA 缓冲区 FD 还支持一些特定于 dma-buf 的 ioctl,有关详细信息,请参阅下面的 DMA 缓冲区 ioctl

基本操作和设备 DMA 访问

对于设备 DMA 访问共享 DMA 缓冲区,通常的操作顺序非常简单

  1. 导出器使用 DEFINE_DMA_BUF_EXPORT_INFO() 定义其导出器实例,并调用 dma_buf_export() 将私有缓冲区对象包装到 dma_buf 中。然后,它通过调用 dma_buf_fd() 将该 dma_buf 作为文件描述符导出到用户空间。

  2. 用户空间将此文件描述符传递给它希望此缓冲区共享的所有驱动程序:首先,使用 dma_buf_get() 将文件描述符转换为 dma_buf。然后,使用 dma_buf_attach() 将缓冲区附加到设备。

    在此阶段,导出器仍然可以自由迁移或重新分配后备存储。

  3. 一旦缓冲区附加到所有设备,用户空间就可以启动对共享缓冲区的 DMA 访问。在内核中,这是通过调用 dma_buf_map_attachment()dma_buf_unmap_attachment() 来完成的。

  4. 一旦驱动程序完成了共享缓冲区,它需要调用 dma_buf_detach()(在清理任何映射后),然后通过调用 dma_buf_put() 来释放使用 dma_buf_get() 获取的引用。

有关导出器预期实现的详细语义,请参阅 dma_buf_ops

CPU 访问 DMA 缓冲区对象

支持 CPU 访问 dma 缓冲区对象的原因有很多

  • 内核中的回退操作,例如当设备通过 USB 连接时,内核需要先将数据来回移动,然后再发送出去。缓存一致性是通过使用对 dma_buf_begin_cpu_access()dma_buf_end_cpu_access() 访问的调用来括起来任何事务来处理的。

    由于大多数内核内部 dma-buf 访问都需要整个缓冲区,因此引入了 vmap 接口。请注意,在非常旧的 32 位体系结构上,vmalloc 空间可能有限,并导致 vmap 调用失败。

    接口

    void *dma_buf_vmap(struct dma_buf *dmabuf, struct iosys_map *map)
    void dma_buf_vunmap(struct dma_buf *dmabuf, struct iosys_map *map)
    

    如果导出器中没有 vmap 支持,或者 vmalloc 空间耗尽,则 vmap 调用可能会失败。请注意,dma-buf 层会为所有 vmap 访问维护一个引用计数,并且只有在不存在 vmapping 时才会调用导出器的 vmap 函数,并且只会取消映射一次。通过获取 dma_buf.lock 互斥锁来防止并发的 vmap/vunmap 调用。

  • 为了在导入器端与可能已经支持 mmap 缓冲区操作的现有用户空间接口完全兼容。这在许多处理管道中是必需的(例如,将软件渲染的图像馈送到硬件管道、缩略图创建、快照等)。此外,Android 的 ION 框架已经支持此功能,并且为了使 DMA 缓冲区文件描述符取代 ION 缓冲区,需要 mmap 支持。

    没有特殊的接口,用户空间只需对 dma-buf fd 调用 mmap。但是,与 CPU 访问一样,需要限定实际访问范围,这由 ioctl (DMA_BUF_IOCTL_SYNC) 处理。请注意,DMA_BUF_IOCTL_SYNC 可能会因 -EAGAIN 或 -EINTR 而失败,在这种情况下必须重新启动。

    某些系统可能需要某种缓存一致性管理,例如,当 CPU 和 GPU 域同时通过 dma-buf 访问时。为了规避这个问题,存在 begin/end 一致性标记,它们直接转发到现有的 dma-buf 设备驱动程序 vfunc 钩子。用户空间可以通过 DMA_BUF_IOCTL_SYNC ioctl 使用这些标记。序列将如下使用:

    • mmap dma-buf fd

    • 对于 CPU 中的每个绘制/上传周期:1. SYNC_START ioctl,2. 读取/写入 mmap 区域,3. SYNC_END ioctl。这可以根据需要重复多次(新数据会被 GPU 或扫描输出设备等消耗)。

    • 一旦不再需要缓冲区,则调用 munmap。

    为了保证正确性和最佳性能,在访问映射地址时,始终需要在访问之前和之后分别使用 SYNC_START 和 SYNC_END。即使在某些系统中,不调用这些 ioctl 也可以正常工作,用户空间也不能依赖于一致的访问。

  • 以及作为用户空间处理管道中的 CPU 回退。

    与内核 CPU 访问的动机类似,给定导入子系统的用户空间代码能够对导入的 dma-buf 缓冲区对象使用与本机缓冲区对象相同的接口再次非常重要。这对于 drm 尤其重要,在 drm 中,现代 OpenGL、X 和其他驱动程序的用户空间部分非常庞大,并且对其进行重构以使用不同的方式来 mmap 缓冲区会非常麻烦。

    当前 dma-buf 接口的假设是,只需要重定向初始 mmap。对一些现有子系统的调查表明,没有驱动程序似乎会进行任何恶意操作,例如与设备上未完成的异步处理同步或在发生错误时分配特殊资源。因此,希望这足够好,因为添加接口来拦截页错误并允许 pte 射击会大大增加复杂性。

    接口

    int dma_buf_mmap(struct dma_buf *, struct vm_area_struct *, unsigned long);
    

    如果导入子系统仅提供一个专用的 mmap 调用来在用户空间中建立映射,那么使用 dma_buf.file 调用 do_mmap 对于 dma-buf 对象也将同样实现此目的。

隐式 Fence 轮询支持

为了支持跨设备和跨驱动程序的缓冲区访问同步,可以将隐式 fence(在内核中用 struct dma_fence 表示)附加到 dma_bufdma_resv 结构中提供了相关的内容。

用户空间可以使用 poll() 和相关的系统调用来查询这些隐式跟踪的 fence 的状态。

  • 检查 EPOLLIN(即读取访问)可以用来查询最新的写入或独占 fence 的状态。

  • 检查 EPOLLOUT(即写入访问)可以用来查询所有附加的 fence(共享和独占的 fence)的状态。

请注意,这仅表示相应 fence 的完成,即 DMA 传输已完成。在 CPU 访问开始之前,仍需要进行缓存刷新和任何其他必要的准备工作。

作为 poll() 的替代方案,可以使用 dma_buf_sync_file_export 将 DMA 缓冲区上的 fence 集合导出为 sync_file

DMA-BUF 统计信息

/sys/kernel/debug/dma_buf/bufinfo 提供了系统中每个 DMA-BUF 的概览。但是,由于 debugfs 在生产环境中挂载是不安全的,因此可以使用 procfs 和 sysfs 来收集生产系统上的 DMA-BUF 统计信息。

procfs 中的 /proc/<pid>/fdinfo/<fd> 文件可以用来收集有关 DMA-BUF fd 的信息。有关该接口的详细文档位于 /proc 文件系统 中。

遗憾的是,现有的 procfs 接口只能提供有关进程拥有 fd 或已将缓冲区 mmap 到其地址空间的 DMA-BUF 的信息。这促使创建了 DMA-BUF sysfs 统计接口,以便在生产系统上提供每个缓冲区的信息。

启用 CONFIG_DMABUF_SYSFS_STATS 后,/sys/kernel/dmabuf/buffers 处的接口会公开有关每个 DMA-BUF 的信息。

该接口公开以下统计信息:

  • /sys/kernel/dmabuf/buffers/<inode_number>/exporter_name

  • /sys/kernel/dmabuf/buffers/<inode_number>/size

接口中的信息还可以用来获取每个导出器的统计信息。可以收集接口中的数据,以在出现错误情况或其他重要事件时提供 DMA-BUF 使用情况的快照。还可以通过遥测定期收集数据,以监控各种指标。

有关该接口的详细文档位于 Documentation/ABI/testing/sysfs-kernel-dmabuf-buffers 中。

DMA 缓冲区 ioctl

struct dma_buf_sync

与 CPU 访问同步。

定义:

struct dma_buf_sync {
    __u64 flags;
};

成员

flags

访问标志集

DMA_BUF_SYNC_START

指示映射访问会话的开始。

DMA_BUF_SYNC_END

指示映射访问会话的结束。

DMA_BUF_SYNC_READ

指示映射的 DMA 缓冲区将由客户端通过 CPU 映射读取。

DMA_BUF_SYNC_WRITE

指示映射的 DMA 缓冲区将由客户端通过 CPU 映射写入。

DMA_BUF_SYNC_RW

DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE 的别名。

描述

当通过 mmap 从 CPU 访问 DMA 缓冲区时,并不总是能够保证 CPU 可见映射与底层内存之间的一致性。为了管理一致性,必须使用 DMA_BUF_IOCTL_SYNC 来限定对 CPU 的任何访问,以便让内核有机会在需要时对内存进行重新排列。

在访问映射之前,客户端必须使用 DMA_BUF_SYNC_START 和相应的读/写标志调用 DMA_BUF_IOCTL_SYNC。一旦访问完成,客户端应使用 DMA_BUF_SYNC_END 和相同的读/写标志调用 DMA_BUF_IOCTL_SYNC。

通过 DMA_BUF_IOCTL_SYNC 提供的同步仅提供缓存一致性。它不能阻止其他进程或设备同时访问内存。如果需要与 GPU 或其他设备驱动程序同步,则客户端有责任在调用此 ioctl 并使用 DMA_BUF_SYNC_START 之前等待缓冲区准备好进行读取或写入。同样,客户端必须确保在调用此 ioctl 并使用 DMA_BUF_SYNC_END 后,不会将后续工作提交到 GPU 或其他设备驱动程序?

如果客户端与之交互的驱动程序或 API 使用隐式同步,则可以通过在 DMA 缓冲区文件描述符上执行 poll() 来等待先前的工作完成。如果驱动程序或 API 需要显式同步,则客户端可能需要在 DMA 缓冲区 API 范围之外等待 sync_file 或其他同步原语。

struct dma_buf_export_sync_file

从 dma-buf 获取 sync_file

定义:

struct dma_buf_export_sync_file {
    __u32 flags;
    __s32 fd;
};

成员

flags

读/写标志

必须为 DMA_BUF_SYNC_READ、DMA_BUF_SYNC_WRITE 或两者都设置。

如果设置了 DMA_BUF_SYNC_READ 且未设置 DMA_BUF_SYNC_WRITE,则返回的同步文件将等待 dma-buf 的任何写入程序完成。等待返回的同步文件等效于使用 POLLIN 执行 poll()。

如果设置了 DMA_BUF_SYNC_WRITE,则返回的同步文件将等待 dma-buf 的任何用户(读取或写入)完成。等待返回的同步文件等效于使用 POLLOUT 执行 poll()。如果同时设置了 DMA_BUF_SYNC_WRITE 和 DMA_BUF_SYNC_READ,则这等效于仅设置 DMA_BUF_SYNC_WRITE。

fd

返回的同步文件描述符

描述

用户空间可以执行 DMA_BUF_IOCTL_EXPORT_SYNC_FILE 来检索 dma-buf 文件描述符上当前的一组 fence 作为 sync_file。通过 poll() 或其他驱动程序特定的机制进行的 CPU 等待通常会等待等待开始时 dma-buf 上的任何 fence。这类似,只不过它会获取 dma-buf 上当前 fence 的快照以供稍后等待,而不是立即等待。这对于现代图形 API(例如 Vulkan)非常有用,后者假设使用显式同步模型,但仍需要与 dma-buf 互操作。

预期的使用模式如下:

  1. 使用 DMA_BUF_IOCTL_EXPORT_SYNC_FILE 导出与预期的 GPU 使用情况相对应的标志的同步文件。

  2. 提交使用 dma-buf 的渲染工作。在渲染之前,该工作应等待导出的同步文件,并在完成后生成另一个同步文件。

  3. 使用 DMA_BUF_IOCTL_IMPORT_SYNC_FILE 将渲染完成的同步文件导入到 dma-buf 中,标志与 GPU 使用情况相对应。

与通过 GPU 内核驱动程序的 exec ioctl 进行隐式同步不同,上述操作不是单个原子操作。如果用户空间想要通过这些 fence 确保排序,则用户空间有责任使用锁或其他机制来确保在上述步骤 1 和 3 之间没有其他上下文添加 fence 或提交工作。

struct dma_buf_import_sync_file

将 sync_file 插入到 dma-buf 中

定义:

struct dma_buf_import_sync_file {
    __u32 flags;
    __s32 fd;
};

成员

flags

读/写标志

必须为 DMA_BUF_SYNC_READ、DMA_BUF_SYNC_WRITE 或两者都设置。

如果设置了 DMA_BUF_SYNC_READ 且未设置 DMA_BUF_SYNC_WRITE,则这会将 sync_file 作为只读 fence 插入。随后对该 dma-buf 的任何隐式同步写入都将等待此 fence,但读取不会等待。

如果设置了 DMA_BUF_SYNC_WRITE,则这会将 sync_file 作为写入 fence 插入。随后对该 dma-buf 的所有隐式同步访问都将等待此 fence。

fd

同步文件描述符

描述

用户空间可以执行 DMA_BUF_IOCTL_IMPORT_SYNC_FILE,以便将 sync_file 插入到 dma-buf 中,用于与其他 dma-buf 使用者进行隐式同步。这使得使用显式同步 API(例如 Vulkan)的客户端能够与期望隐式同步的 dma-buf 使用者(例如 OpenGL 或大多数媒体驱动程序/视频)进行互操作。

DMA-BUF 锁定约定

为了避免 dma-buf 导出器和导入器之间出现死锁情况,所有 dma-buf API 用户都必须遵循通用的 dma-buf 锁定约定。

导入器的约定

  1. 导入器在调用这些函数时必须持有 dma-buf 保留锁

  2. 导入器在调用以下函数时,不得持有 dma-buf 预留锁

导出器的约定

  1. 这些 dma_buf_ops 回调函数在未锁定的 dma-buf 预留情况下调用,导出器可以获取锁。

  2. 这些 dma_buf_ops 回调函数在锁定的 dma-buf 预留情况下调用,导出器不能获取锁。

  3. 导出器在调用以下函数时,必须持有 dma-buf 预留锁

内核函数和结构参考

struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)

创建一个新的 dma_buf,并将一个匿名文件与此缓冲区关联,以便可以导出它。还将分配器特定的数据和操作连接到缓冲区。此外,为导出器提供一个名称字符串;这在调试中很有用。

参数

const struct dma_buf_export_info *exp_info

[in] 保存导出器提供的所有导出相关信息。有关详细信息,请参阅 struct dma_buf_export_info

描述

成功时,返回一个新创建的 struct dma_buf 对象,该对象包装提供的私有数据和 struct dma_buf_ops 的操作。如果缺少 ops 或分配 struct dma_buf 时出错,将返回负错误。

在大多数情况下,创建 exp_info 的最简单方法是使用 DEFINE_DMA_BUF_EXPORT_INFO 宏。

int dma_buf_fd(struct dma_buf *dmabuf, int flags)

返回给定 struct dma_buf 的文件描述符

参数

struct dma_buf *dmabuf

[in] 指向需要 fd 的 dma_buf 的指针。

int flags

[in] 传递给 fd 的标志

描述

成功时,返回关联的 “fd”。否则,返回错误。

struct dma_buf *dma_buf_get(int fd)

返回与 fd 相关的 struct dma_buf

参数

int fd

[in] 与要返回的 struct dma_buf 关联的 fd

描述

成功时,返回与 fd 关联的 struct dma_buf;使用 fget 完成的文件引用计数来增加引用计数。否则返回 ERR_PTR。

void dma_buf_put(struct dma_buf *dmabuf)

减少缓冲区的引用计数

参数

struct dma_buf *dmabuf

[in] 要减少引用计数的缓冲区

描述

使用 fput() 隐式完成的文件引用计数。

如果由于此调用导致引用计数变为 0,则会调用与此 fd 相关的 “release” 文件操作。它反过来调用 dma_buf_ops.release vfunc,并释放导出时为 dmabuf 分配的内存。

struct dma_buf_attachment *dma_buf_dynamic_attach(struct dma_buf *dmabuf, struct device *dev, const struct dma_buf_attach_ops *importer_ops, void *importer_priv)

将设备添加到 dma_buf 的附件列表

参数

struct dma_buf *dmabuf

[in] 要附加设备的缓冲区。

struct device *dev

[in] 要附加的设备。

const struct dma_buf_attach_ops *importer_ops

[in] 附件的导入器操作

void *importer_priv

[in] 附件的导入器私有指针

描述

返回此附件的 struct dma_buf_attachment 指针。必须通过调用 dma_buf_detach() 来清理附件。

可选地,这将调用 dma_buf_ops.attach 以允许特定于设备的附加功能。

成功时指向新创建的 dma_buf_attachment 的指针,或失败时包装到指针中的负错误代码。

请注意,如果 dmabuf 的后备存储位于 dev 无法访问的位置,并且无法移动到更合适的位置,则此操作可能会失败。这用错误代码 -EBUSY 表示。

返回

struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf, struct device *dev)

dma_buf_dynamic_attach 的包装器

参数

struct dma_buf *dmabuf

[in] 要附加设备的缓冲区。

struct device *dev

[in] 要附加的设备。

描述

用于调用 dma_buf_dynamic_attach() 的包装器,用于仍然使用静态映射的驱动程序。

void dma_buf_detach(struct dma_buf *dmabuf, struct dma_buf_attachment *attach)

从 dmabuf 的附件列表中删除给定的附件

参数

struct dma_buf *dmabuf

[in] 要从中分离的缓冲区。

struct dma_buf_attachment *attach

[in] 要分离的附件;在此调用后会被释放。

描述

清理通过调用 dma_buf_attach() 获取的设备附件。

可以选择性地调用 dma_buf_ops.detach 进行设备特定的分离。

int dma_buf_pin(struct dma_buf_attachment *attach)

锁定 DMA-buf。

参数

struct dma_buf_attachment *attach

[输入] 要锁定的附件。

描述

只有动态导入器(使用 dma_buf_dynamic_attach() 设置 attach 的导入器)可以调用此函数,且仅限于扫描输出等有限的使用场景,不适用于临时锁定操作。不允许用户空间通过此接口锁定任意数量的缓冲区。

必须通过调用 dma_buf_unpin() 来解锁缓冲区。

返回

成功返回 0,失败返回负错误代码。

void dma_buf_unpin(struct dma_buf_attachment *attach)

解锁 DMA-buf。

参数

struct dma_buf_attachment *attach

[输入] 要解锁的附件。

描述

此函数解锁通过 dma_buf_pin() 锁定的缓冲区,并允许导出器再次移动 attach 的任何映射,并通过 dma_buf_attach_ops.move_notify 通知导入器。

struct sg_table *dma_buf_map_attachment(struct dma_buf_attachment *attach, enum dma_data_direction direction)

返回附件的散列表;映射到 _device_ 地址空间。是 dma_buf_ops 的 map_dma_buf() 的包装器。

参数

struct dma_buf_attachment *attach

[输入] 要返回其散列表的附件。

enum dma_data_direction direction

[输入] DMA 传输的方向。

描述

返回包含要返回的散列表的 sg_table;错误时返回 ERR_PTR。如果被信号中断,可能返回 -EINTR。

成功时,返回的散列表中的 DMA 地址和长度是 PAGE_SIZE 对齐的。

必须使用 dma_buf_unmap_attachment() 来取消映射。请注意,只要映射存在,底层后备存储就会被锁定,因此用户/导入器不应在不必要的时间内保留映射。

重要提示:动态导入器必须首先等待附加到 DMA-BUF 的 struct dma_resv 的独占栅栏。

struct sg_table *dma_buf_map_attachment_unlocked(struct dma_buf_attachment *attach, enum dma_data_direction direction)

返回附件的散列表;映射到 _device_ 地址空间。是 dma_buf_ops 的 map_dma_buf() 的包装器。

参数

struct dma_buf_attachment *attach

[输入] 要返回其散列表的附件。

enum dma_data_direction direction

[输入] DMA 传输的方向。

描述

dma_buf_map_attachment() 的解锁变体。

void dma_buf_unmap_attachment(struct dma_buf_attachment *attach, struct sg_table *sg_table, enum dma_data_direction direction)

取消映射并减少缓冲区的使用计数;可能会释放相关的散列表。是 dma_buf_ops 的 unmap_dma_buf() 的包装器。

参数

struct dma_buf_attachment *attach

[输入] 要从中取消映射缓冲区的附件。

struct sg_table *sg_table

[输入] 要取消映射的缓冲区的散列表信息。

enum dma_data_direction direction

[输入] DMA 传输的方向。

描述

此函数取消通过 dma_buf_map_attachment() 获取的 attached 的 DMA 映射。

void dma_buf_unmap_attachment_unlocked(struct dma_buf_attachment *attach, struct sg_table *sg_table, enum dma_data_direction direction)

取消映射并减少缓冲区的使用计数;可能会释放相关的散列表。是 dma_buf_ops 的 unmap_dma_buf() 的包装器。

参数

struct dma_buf_attachment *attach

[输入] 要从中取消映射缓冲区的附件。

struct sg_table *sg_table

[输入] 要取消映射的缓冲区的散列表信息。

enum dma_data_direction direction

[输入] DMA 传输的方向。

描述

dma_buf_unmap_attachment() 的解锁变体。

void dma_buf_move_notify(struct dma_buf *dmabuf)

通知附件 DMA-buf 正在移动。

参数

struct dma_buf *dmabuf

[输入] 正在移动的缓冲区。

描述

通知所有附件它们需要销毁并重新创建所有映射。

int dma_buf_begin_cpu_access(struct dma_buf *dmabuf, enum dma_data_direction direction)

必须在内核上下文中从 CPU 访问 dma_buf 之前调用。调用 begin_cpu_access 以允许导出器特定的准备工作。仅在指定的范围内为指定的访问方向保证一致性。

参数

struct dma_buf *dmabuf

[输入] 要准备 CPU 访问的缓冲区。

enum dma_data_direction direction

[输入] 访问的方向。

描述

CPU 访问完成后,调用者应调用 dma_buf_end_cpu_access()。只有当 CPU 访问被两个调用括起来时,才能保证与其他 DMA 访问的一致性。

此函数还会等待通过 dma_buf.resv 中的隐式同步跟踪的任何 DMA 事务。对于具有显式同步的 DMA 事务,此函数仅确保缓存一致性,调用者必须自行确保与此类 DMA 事务的同步。

可以返回负错误值,成功返回 0。

int dma_buf_end_cpu_access(struct dma_buf *dmabuf, enum dma_data_direction direction)

必须在内核上下文中从 CPU 访问 dma_buf 后调用。调用 end_cpu_access 以允许导出器特定的操作。仅在指定的范围内为指定的访问方向保证一致性。

参数

struct dma_buf *dmabuf

[输入] 完成 CPU 访问的缓冲区。

enum dma_data_direction direction

[输入] 访问的方向。

描述

这将终止使用 dma_buf_begin_cpu_access() 启动的 CPU 访问。

可以返回负错误值,成功返回 0。

int dma_buf_mmap(struct dma_buf *dmabuf, struct vm_area_struct *vma, unsigned long pgoff)

使用给定的 vma 设置用户空间 mmap

参数

struct dma_buf *dmabuf

[in] 应该支持 vma 的缓冲区

struct vm_area_struct *vma

[in] mmap 的 vma

unsigned long pgoff

[in] 此 mmap 应该在 dma-buf 缓冲区内开始的页偏移量。

描述

此函数调整传入的 vma,使其指向 dma_buf 操作的文件。它还调整起始 pgoff 并对 vma 的大小进行边界检查。然后它调用导出器的 mmap 函数来设置映射。

可以返回负错误值,成功返回 0。

int dma_buf_vmap(struct dma_buf *dmabuf, struct iosys_map *map)

在内核地址空间中为缓冲区对象创建虚拟映射。与 vmap 及其友元相同的限制适用。

参数

struct dma_buf *dmabuf

[in] 要 vmap 的缓冲区

struct iosys_map *map

[out] 返回 vmap 指针

描述

由于缺少虚拟映射地址空间,此调用可能会失败。这些调用在驱动程序中是可选的。它们的预期用途是映射内核空间中的线性对象以用于高使用率对象。

为确保一致性,用户必须在通过此映射执行任何 CPU 访问时调用 dma_buf_begin_cpu_access()dma_buf_end_cpu_access()

成功时返回 0,否则返回负 errno 代码。

int dma_buf_vmap_unlocked(struct dma_buf *dmabuf, struct iosys_map *map)

在内核地址空间中为缓冲区对象创建虚拟映射。与 vmap 及其友元相同的限制适用。

参数

struct dma_buf *dmabuf

[in] 要 vmap 的缓冲区

struct iosys_map *map

[out] 返回 vmap 指针

描述

dma_buf_vmap() 的解锁版本

成功时返回 0,否则返回负 errno 代码。

void dma_buf_vunmap(struct dma_buf *dmabuf, struct iosys_map *map)

取消映射 dma_buf_vmap 获取的 vmap。

参数

struct dma_buf *dmabuf

[in] 要 vunmap 的缓冲区

struct iosys_map *map

[in] 要 vunmap 的 vmap 指针

void dma_buf_vunmap_unlocked(struct dma_buf *dmabuf, struct iosys_map *map)

取消映射 dma_buf_vmap 获取的 vmap。

参数

struct dma_buf *dmabuf

[in] 要 vunmap 的缓冲区

struct iosys_map *map

[in] 要 vunmap 的 vmap 指针

struct dma_buf_ops

可以在 struct dma_buf 上执行的操作

定义:

struct dma_buf_ops {
    bool cache_sgt_mapping;
    int (*attach)(struct dma_buf *, struct dma_buf_attachment *);
    void (*detach)(struct dma_buf *, struct dma_buf_attachment *);
    int (*pin)(struct dma_buf_attachment *attach);
    void (*unpin)(struct dma_buf_attachment *attach);
    struct sg_table * (*map_dma_buf)(struct dma_buf_attachment *, enum dma_data_direction);
    void (*unmap_dma_buf)(struct dma_buf_attachment *,struct sg_table *, enum dma_data_direction);
    void (*release)(struct dma_buf *);
    int (*begin_cpu_access)(struct dma_buf *, enum dma_data_direction);
    int (*end_cpu_access)(struct dma_buf *, enum dma_data_direction);
    int (*mmap)(struct dma_buf *, struct vm_area_struct *vma);
    int (*vmap)(struct dma_buf *dmabuf, struct iosys_map *map);
    void (*vunmap)(struct dma_buf *dmabuf, struct iosys_map *map);
};

成员

cache_sgt_mapping

如果为 true,则框架将缓存为每个附加项进行的第一次映射。这避免了多次为附件创建映射。

attach

这是从 dma_buf_attach() 调用的,以确保给定的 dma_buf_attachment.dev 可以访问提供的 dma_buf。在特殊位置(如 VRAM 或特定于设备的雕刻区域)中支持缓冲区对象的导出器应检查缓冲区是否可以移动到系统内存(或由提供的设备直接访问),否则需要使附加操作失败。

导出器通常还应检查当前分配是否满足新设备的 DMA 约束。如果不是这种情况,并且分配无法移动,它也应该使附加操作失败。

任何导出器私有内务处理数据都可以存储在 dma_buf_attachment.priv 指针中。

此回调是可选的。

返回

成功时返回 0,失败时返回负错误代码。它可能会返回 -EBUSY,以指示后备存储已分配且与请求设备的的要求不兼容。

detach

这是由 dma_buf_detach() 调用的,用于释放 dma_buf_attachment。提供此功能是为了使导出器可以清除 dma_buf_attachment 的任何内务处理。

此回调是可选的。

pin

这是由 dma_buf_pin() 调用的,让导出器知道 DMA-buf 不能再移动。理想情况下,导出器应固定缓冲区,以便所有设备都可以普遍访问它。

这是使用锁定的 dmabuf.resv 对象调用的,并且与 cache_sgt_mapping 互斥。

对于来自 dma_buf_attach() 的非动态导入器,会自动调用此函数。

请注意,与非动态导出器在其 map_dma_buf 回调中类似,驱动程序必须保证在函数返回时,内存可供使用并且已清除所有旧数据。内部流水线其缓冲区移动的驱动程序必须等待所有移动和清除操作完成。

返回

成功返回 0,失败返回负错误代码。

unpin

这是由 dma_buf_unpin() 调用的,让导出器知道 DMA-buf 可以再次移动。

这是使用 dmabuf->resv 对象锁定的调用的,并且与 cache_sgt_mapping 互斥。

此回调是可选的。

map_dma_buf

这是由 dma_buf_map_attachment() 调用的,用于将共享 dma_buf 映射到设备地址空间,这是强制性的。只有在成功调用 attach 后才能调用它。

此调用可能会休眠,例如,当需要首先分配后备存储或将其移动到适合当前所有附加设备的位置时。

请注意,此函数所需的任何特定缓冲区属性都应添加到可通过 device.dma_paramsdma_buf_attachment 访问的 device_dma_parameters 中。attach 回调也应检查这些约束。

如果这是第一次调用,则导出器现在可以选择扫描此缓冲区的附件列表,整理所附加设备的要求,并为缓冲区选择合适的后备存储。

基于 enum dma_data_direction,可能允许多个用户同时访问(用于读取,也许),或者导出器可能希望提供给缓冲区用户的任何其他类型的共享。

当 dynamic_mapping 标志为 true 时,始终使用 dmabuf->resv 对象锁定来调用此函数。

请注意,对于非动态导出器,驱动程序必须保证在函数返回时,内存可供使用并且已清除所有旧数据。内部流水线其缓冲区移动的驱动程序必须等待所有移动和清除操作完成。动态导出器不需要遵循此规则:对于非动态导入器,缓冲区已通过 pin 固定,其具有相同的要求。另一方面,动态导入器必须遵守 dma_resv 栅栏。

返回

已映射到 device 地址空间的 DMA 缓冲区的后备存储的 sg_table 散列表,该设备使用提供的 dma_buf_attachment 进行附加。散列表中的地址和长度是 PAGE_SIZE 对齐的。

如果失败,则返回包装在指针中的负错误值。当在被阻塞时收到信号时,也可能返回 -EINTR。

请注意,导出器不应尝试缓存散列表,或为多次调用返回同一个散列表。缓存由 DMA-BUF 代码(对于非动态导入器)或导入器完成。散列表的所有权将转移给调用者,并由 unmap_dma_buf 返回。

unmap_dma_buf

此函数由 dma_buf_unmap_attachment() 调用,应取消映射并释放 map_dma_buf 中分配的 sg_table,这是强制性的。对于静态 dma_buf 处理,如果这是 DMA 缓冲区的最后一次映射,则可能还会取消固定后备存储。

release

在最后一次 dma_buf_put 之后调用,以释放 dma_buf,这是强制性的。

begin_cpu_access

此函数从 dma_buf_begin_cpu_access() 调用,允许导出器确保内存对于 CPU 访问实际上是连贯的。导出器还需要确保 CPU 访问对于访问方向是连贯的。导出器可以使用该方向来优化缓存刷新,即具有不同方向的访问(读取而不是写入)可能会返回过时甚至错误的数据(例如,当导出器需要将数据复制到临时存储时)。

请注意,这既是通过 DMA_BUF_IOCTL_SYNC IOCTL 命令为通过 mmap 建立的用户空间映射调用的,也是为通过 vmap 建立的内核映射调用的。

此回调是可选的。

返回

成功时返回 0,失败时返回负错误代码。例如,当无法分配后备存储时可能会失败。当调用被中断并且需要重新启动时,也可能返回 -ERESTARTSYS 或 -EINTR。

end_cpu_access

当导入器完成 CPU 访问时,此函数从 dma_buf_end_cpu_access() 调用。导出器可以使用此函数来刷新缓存并撤消在 begin_cpu_access 中执行的任何操作。

此回调是可选的。

返回

成功时返回 0,失败时返回负错误代码。当调用被中断并且需要重新启动时,可以返回 -ERESTARTSYS 或 -EINTR。

mmap

此回调函数由 dma_buf_mmap() 函数使用

请注意,映射需要是不连贯的,用户空间应使用 DMA_BUF_IOCTL_SYNC 接口来限定 CPU 访问。

由于 dma-buf 缓冲区在其生命周期内具有不变的大小,dma-buf 核心会检查 vma 是否过大并拒绝此类映射。因此,导出器不需要重复此检查。驱动程序不需要自己检查。

如果导出器需要手动刷新缓存,因此需要为 mmap 支持伪造连贯性,则它需要能够删除所有指向后备存储的 pte。现在 linux mm 需要一个与 vma->vm_file 中存储的 struct file 关联的 struct address_space,以便使用函数 unmap_mapping_range 执行此操作。但是 dma_buf 框架仅使用 anon_file struct file 来支持每个 dma_buf fd,即所有 dma_buf 共享同一个文件。

因此,导出器需要通过在 dma_buf mmap 回调中设置 vma->vm_file 并调整 vma->vm_pgoff 来建立自己的文件(和 address_space)关联。在 gem 驱动程序的特定情况下,导出器可以使用 gem 已经提供的 shmem 文件(并设置 vm_pgoff = 0)。然后,导出器可以通过取消映射与其自己文件关联的 struct address_space 的相应范围来删除 pte。

此回调是可选的。

返回

成功时返回 0,失败时返回负错误代码。

vmap

[可选] 为缓冲区在内核地址空间中创建虚拟映射。适用与 vmap 及其朋友相同的限制。

vunmap

[可选] 取消映射缓冲区中的 vmap

struct dma_buf

共享缓冲区对象

定义:

struct dma_buf {
    size_t size;
    struct file *file;
    struct list_head attachments;
    const struct dma_buf_ops *ops;
    unsigned vmapping_counter;
    struct iosys_map vmap_ptr;
    const char *exp_name;
    const char *name;
    spinlock_t name_lock;
    struct module *owner;
#if IS_ENABLED(CONFIG_DEBUG_FS);
    struct list_head list_node;
#endif;
    void *priv;
    struct dma_resv *resv;
    wait_queue_head_t poll;
    struct dma_buf_poll_cb_t {
        struct dma_fence_cb cb;
        wait_queue_head_t *poll;
        __poll_t active;
    } cb_in, cb_out;
#ifdef CONFIG_DMABUF_SYSFS_STATS;
    struct dma_buf_sysfs_entry {
        struct kobject kobj;
        struct dma_buf *dmabuf;
    } *sysfs_entry;
#endif;
};

成员

size

缓冲区的大小;在缓冲区的生命周期内是不变的。

file

用于跨多个组件共享缓冲区以及进行引用计数的 文件指针。请参阅 dma_buf_get()dma_buf_put()

attachments

dma_buf_attachment 列表,表示所有附加的设备,受 dma_resvresv 保护。

ops

与此缓冲区对象关联的 dma_buf_ops。

vmapping_counter

在内部用于引用计数 dma_buf_vmap() 返回的 vmap。受 lock 保护。

vmap_ptr

如果 vmapping_counter > 0,则为当前 vmap 指针。受 lock 保护。

exp_name

导出器的名称;对调试很有用。不能为 NULL

name

用户空间提供的名称。默认值为 NULL。如果不是 NULL,则长度不能超过 DMA_BUF_NAME_LEN,包括 NIL 字符。对记帐和调试很有用。读取/写入访问受 name_lock 保护

请参阅 IOCTL DMA_BUF_SET_NAME 或 DMA_BUF_SET_NAME_A/B

name_lock

用于保护读取访问名称的自旋锁。

owner

指向导出器模块的指针;当导出器是内核模块时用于引用计数。

list_node

用于 dma_buf 记帐和调试的节点。

priv

此缓冲区对象的导出器特定私有数据。

resv

链接到此 dma-buf 的预留对象。

隐式同步规则

支持缓冲访问隐式同步的驱动程序(例如在 隐式栅栏轮询支持中公开的)必须遵循以下规则。

  • 对于用户空间 API 认为的任何读取访问,驱动程序必须通过带有 DMA_RESV_USAGE_READ 标志的 dma_resv_add_fence() 添加读取栅栏。这高度取决于 API 和窗口系统。

  • 类似地,对于用户空间 API 认为的任何写入访问,驱动程序必须通过带有 DMA_RESV_USAGE_WRITE 标志的 dma_resv_add_fence() 添加写入栅栏。

  • 驱动程序可以始终只添加写入栅栏,因为这只会导致不必要的同步,而不会导致正确性问题。

  • 某些驱动程序只公开一个同步用户空间 API,并且跨驱动程序没有流水线。这些驱动程序不会为其访问设置任何栅栏。这里的一个示例是 v4l。

  • 驱动程序在检索栅栏作为隐式同步的依赖项时应使用 dma_resv_usage_rw()

动态导入器规则

动态导入器(请参阅 dma_buf_attachment_is_dynamic())在如何设置栅栏方面有额外的约束

  • 动态导入器必须遵守写入栅栏,并在允许通过设备访问缓冲区的底层存储之前等待它们发出信号。

  • 动态导入器应为其 dma_buf_attach_ops.move_notify 回调中无法立即禁用的任何访问设置栅栏。

重要

所有驱动程序和内存管理相关函数都必须遵守 struct dma_resv 规则,特别是更新和遵守栅栏的规则。有关进一步的说明,请参阅 enum dma_resv_usage

poll

用于用户空间轮询支持

cb_in

用于用户空间轮询支持

cb_out

用于用户空间轮询支持

sysfs_entry

用于在 sysfs 中公开有关此缓冲区的信息。另请参阅 DMA-BUF 统计信息,了解此功能启用的 uapi。

描述

这表示一个共享缓冲区,通过调用 dma_buf_export() 创建。用户空间表示是一个普通的文件描述符,可以通过调用 dma_buf_fd() 创建。

使用 dma_buf_put()get_dma_buf() 对共享的 dma 缓冲区进行引用计数。

设备 DMA 访问由单独的 struct dma_buf_attachment 处理。

struct dma_buf_attach_ops

附件的导入器操作

定义:

struct dma_buf_attach_ops {
    bool allow_peer2peer;
    void (*move_notify)(struct dma_buf_attachment *attach);
};

成员

allow_peer2peer

如果设置为 true,则导入器必须能够处理没有 struct page 的对等资源。

move_notify

[可选] DMA-buf 正在移动的通知

如果提供了此回调,则框架可以避免在存在映射时固定后备存储。

此回调函数在持有与 dma_buf 关联的预留对象的锁的情况下调用,并且映射函数也必须在持有此锁的情况下调用。这确保在正在进行的移动操作期间不会同时创建任何映射。

映射保持有效,并且不受此回调的直接影响。但是,DMA-buf 现在可能位于不同的物理位置,因此应尽快销毁并重新创建所有映射。

在此回调返回后可以创建新映射,并且这些映射将指向 DMA-buf 的新位置。

描述

由导入器实现的附件操作。

struct dma_buf_attachment

保存设备缓冲区附件数据

定义:

struct dma_buf_attachment {
    struct dma_buf *dmabuf;
    struct device *dev;
    struct list_head node;
    struct sg_table *sgt;
    enum dma_data_direction dir;
    bool peer2peer;
    const struct dma_buf_attach_ops *importer_ops;
    void *importer_priv;
    void *priv;
};

成员

dmabuf

此附件的缓冲区。

dev

附加到缓冲区的设备。

node

dma_buf_attachment 列表,受 dmabuf 的 dma_resv 锁保护。

sgt

缓存的映射。

dir

缓存映射的方向。

peer2peer

如果导入器可以处理没有页面的对等资源,则为 true。

importer_ops

此附件的导入器操作,如果提供,则必须在持有 dma_resv 锁的情况下调用 dma_buf_map/unmap_attachment()。

importer_priv

导入器特定的附件数据。

priv

导出器特定的附件数据。

描述

此结构保存 dma_buf 缓冲区及其用户设备之间的附件信息。该列表包含每个附加到缓冲区的设备的附件结构。

通过调用 dma_buf_attach() 创建一个附件,并通过调用 dma_buf_detach() 释放它。发起传输所需的 DMA 映射本身是通过 dma_buf_map_attachment() 创建的,并通过调用 dma_buf_unmap_attachment() 释放。

struct dma_buf_export_info

保存导出 dma_buf 所需的信息

定义:

struct dma_buf_export_info {
    const char *exp_name;
    struct module *owner;
    const struct dma_buf_ops *ops;
    size_t size;
    int flags;
    struct dma_resv *resv;
    void *priv;
};

成员

exp_name

导出器的名称 - 用于调试。

owner

指向导出器模块的指针 - 用于内核模块的引用计数

ops

将分配器定义的 dma buf 操作附加到新的缓冲区

size

缓冲区的大小 - 在缓冲区的生命周期内是不变的

flags

文件的模式标志

resv

保留对象,NULL 表示分配默认对象

priv

将分配器的私有数据附加到此缓冲区

描述

此结构保存导出缓冲区所需的信息。仅与 dma_buf_export() 一起使用。

DEFINE_DMA_BUF_EXPORT_INFO

DEFINE_DMA_BUF_EXPORT_INFO (name)

用于导出器的辅助宏

参数

name

导出信息名称

描述

DEFINE_DMA_BUF_EXPORT_INFO 宏定义 struct dma_buf_export_info,将其清零并在其中预先填充 exp_name。

void get_dma_buf(struct dma_buf *dmabuf)

get_file 的便捷包装器。

参数

struct dma_buf *dmabuf

[输入] 指向 dma_buf 的指针

描述

增加 dma-buf 上的引用计数,以防驱动程序需要在内核端创建对 dmabuf 的额外引用。例如,需要保留 dmabuf 指针的导出器,以便后续导出不会创建新的 dmabuf。

bool dma_buf_is_dynamic(struct dma_buf *dmabuf)

检查 DMA-buf 是否使用动态映射。

参数

struct dma_buf *dmabuf

要检查的 DMA-buf

描述

如果 DMA-buf 导出器希望在为 map/unmap 回调锁定 dma_resv 的情况下调用,则返回 true;如果它不希望在持有锁的情况下调用,则返回 false。

bool dma_buf_attachment_is_dynamic(struct dma_buf_attachment *attach)

检查 DMA-buf 附件是否使用动态映射

参数

struct dma_buf_attachment *attach

要检查的 DMA-buf 附件

描述

如果 DMA-buf 导入器希望在持有 dma_resv 锁的情况下调用 map/unmap 函数,则返回 true。

保留对象

保留对象提供了一种机制来管理与资源关联的 dma_fence 对象容器。一个保留对象可以附加任意数量的 fence。每个 fence 都有一个 usage 参数,用于确定 fence 代表的操作如何使用资源。RCU 机制用于保护对 fence 的读取访问,使其免受锁定的写入端更新的影响。

有关详细信息,请参阅 struct dma_resv

void dma_resv_init(struct dma_resv *obj)

初始化保留对象

参数

struct dma_resv *obj

保留对象

void dma_resv_fini(struct dma_resv *obj)

销毁保留对象

参数

struct dma_resv *obj

保留对象

int dma_resv_reserve_fences(struct dma_resv *obj, unsigned int num_fences)

保留空间,以将 fence 添加到 dma_resv 对象。

参数

struct dma_resv *obj

保留对象

unsigned int num_fences

我们要添加的 fence 的数量

描述

应在 dma_resv_add_fence() 之前调用。必须通过 dma_resv_lock() 锁定 obj 的情况下调用。

请注意,如果在调用 dma_resv_add_fence() 之前的任何时候解锁 obj,则需要重新保留预分配的槽。启用 CONFIG_DEBUG_MUTEXES 时会对此进行验证。

返回:成功返回零,否则返回 -errno

void dma_resv_reset_max_fences(struct dma_resv *obj)

重置 fence 以进行调试

参数

struct dma_resv *obj

要重置的 dma_resv 对象

描述

重置预保留 fence 槽的数量,以测试驱动程序是否使用 dma_resv_reserve_fences() 正确分配槽。另请参阅 dma_resv_list.max_fences

void dma_resv_add_fence(struct dma_resv *obj, struct dma_fence *fence, enum dma_resv_usage usage)

将 fence 添加到 dma_resv 对象

参数

struct dma_resv *obj

保留对象

struct dma_fence *fence

要添加的 fence

enum dma_resv_usage usage

fence 的使用方式,请参阅 enum dma_resv_usage

描述

将 fence 添加到槽,必须使用 dma_resv_lock() 锁定 obj,并且已调用 dma_resv_reserve_fences()

有关语义的讨论,另请参阅 dma_resv.fence

void dma_resv_replace_fences(struct dma_resv *obj, uint64_t context, struct dma_fence *replacement, enum dma_resv_usage usage)

替换 dma_resv 对象中的 fence

参数

struct dma_resv *obj

保留对象

uint64_t context

要替换的 fence 的上下文

struct dma_fence *replacement

要使用的新 fence

enum dma_resv_usage usage

新 fence 的使用方式,请参阅 enum dma_resv_usage

描述

将指定上下文中的栅栏替换为新的栅栏。只有当新栅栏完成时,原始栅栏所代表的操作不再访问 dma_resv 对象所代表的资源时,此操作才有效。

使用此方法的一个例子是用页表更新栅栏替换抢占栅栏,这使得资源不可访问。

struct dma_fence *dma_resv_iter_first_unlocked(struct dma_resv_iter *cursor)

未锁定的 dma_resv 对象中的第一个栅栏。

参数

struct dma_resv_iter *cursor

带有当前位置的游标

描述

后续的栅栏使用 dma_resv_iter_next_unlocked() 进行迭代。

请注意,迭代器可以重新启动。积累统计信息或类似操作的代码需要使用 dma_resv_iter_is_restarted() 进行检查。因此,尽可能首选锁定的 dma_resv_iter_first()

返回未锁定的 dma_resv 对象中的第一个栅栏。

struct dma_fence *dma_resv_iter_next_unlocked(struct dma_resv_iter *cursor)

未锁定的 dma_resv 对象中的下一个栅栏。

参数

struct dma_resv_iter *cursor

带有当前位置的游标

描述

请注意,迭代器可以重新启动。积累统计信息或类似操作的代码需要使用 dma_resv_iter_is_restarted() 进行检查。因此,尽可能首选锁定的 dma_resv_iter_next()

返回未锁定的 dma_resv 对象中的下一个栅栏。

struct dma_fence *dma_resv_iter_first(struct dma_resv_iter *cursor)

来自锁定的 dma_resv 对象的第一个栅栏

参数

struct dma_resv_iter *cursor

记录当前位置的游标

描述

后续的栅栏使用 dma_resv_iter_next_unlocked() 进行迭代。

返回持有 dma_resv.lock 的 dma_resv 对象中的第一个栅栏。

struct dma_fence *dma_resv_iter_next(struct dma_resv_iter *cursor)

来自锁定的 dma_resv 对象的下一个栅栏

参数

struct dma_resv_iter *cursor

记录当前位置的游标

描述

返回持有 dma_resv.lock 的 dma_resv 对象中的下一个栅栏。

int dma_resv_copy_fences(struct dma_resv *dst, struct dma_resv *src)

将所有栅栏从 src 复制到 dst。

参数

struct dma_resv *dst

目标预留对象

struct dma_resv *src

源预留对象

描述

将所有栅栏从 src 复制到 dst。必须持有 dst 锁。

int dma_resv_get_fences(struct dma_resv *obj, enum dma_resv_usage usage, unsigned int *num_fences, struct dma_fence ***fences)

获取对象的栅栏,无需持有更新侧锁

参数

struct dma_resv *obj

保留对象

enum dma_resv_usage usage

控制要包括哪些栅栏,请参见 enum dma_resv_usage

unsigned int *num_fences

返回的栅栏数量

struct dma_fence ***fences

返回的栅栏指针数组(数组会根据需要进行 krealloc,并且必须由调用者释放)

描述

从预留对象检索所有栅栏。返回零或 -ENOMEM。

int dma_resv_get_singleton(struct dma_resv *obj, enum dma_resv_usage usage, struct dma_fence **fence)

为所有栅栏获取单个栅栏

参数

struct dma_resv *obj

保留对象

enum dma_resv_usage usage

控制要包括哪些栅栏,请参见 enum dma_resv_usage

struct dma_fence **fence

结果栅栏

描述

获取代表 resv 对象内所有栅栏的单个栅栏。成功返回 0,失败返回 -ENOMEM。

警告:当将栅栏添加回 resv 对象时,不能这样使用,因为这可能会在完成 dma_fence_array 时导致堆栈损坏。

成功返回 0,失败返回负错误值。

long dma_resv_wait_timeout(struct dma_resv *obj, enum dma_resv_usage usage, bool intr, unsigned long timeout)

等待预留对象的栅栏

参数

struct dma_resv *obj

保留对象

enum dma_resv_usage usage

控制要包括哪些栅栏,请参见 enum dma_resv_usage

bool intr

如果为 true,则执行可中断等待

unsigned long timeout

超时值(以节拍为单位),或立即返回时为零

描述

调用者不需要持有特定的锁,但可能已经持有 dma_resv_lock()。如果中断,则返回 -ERESTARTSYS,如果等待超时,则返回 0,成功则返回大于零的值。

void dma_resv_set_deadline(struct dma_resv *obj, enum dma_resv_usage usage, ktime_t deadline)

设置预留对象栅栏的截止时间

参数

struct dma_resv *obj

保留对象

enum dma_resv_usage usage

控制要包括哪些栅栏,请参见 enum dma_resv_usage

ktime_t deadline

请求的截止时间 (MONOTONIC)

描述

可以在不持有 dma_resv 锁的情况下调用。根据 usage 筛选,在所有栅栏上设置 deadline

bool dma_resv_test_signaled(struct dma_resv *obj, enum dma_resv_usage usage)

测试预留对象的栅栏是否已发出信号。

参数

struct dma_resv *obj

保留对象

enum dma_resv_usage usage

控制要包括哪些栅栏,请参见 enum dma_resv_usage

描述

调用者不需要持有特定的锁,但可以持有 dma_resv_lock()

返回值

如果所有栅栏都已发出信号,则返回 True,否则返回 False。

void dma_resv_describe(struct dma_resv *obj, struct seq_file *seq)

将 resv 对象的描述转储到 seq_file 中

参数

struct dma_resv *obj

保留对象

struct seq_file *seq

要将描述转储到的 seq_file

描述

将 dma_resv 对象内的栅栏的文本描述转储到 seq_file 中。

enum dma_resv_usage

dma_resv 对象中的栅栏如何使用

常量

DMA_RESV_USAGE_KERNEL

仅用于内核内存管理。

这应该仅用于通过 DMA 硬件引擎复制或清除内存以进行内核内存管理。

驱动程序总是必须在访问 dma_resv 对象保护的资源之前等待这些栅栏。唯一的例外是当已知资源通过预先固定来锁定到位时。

DMA_RESV_USAGE_WRITE

隐式写入同步。

这应该仅用于添加隐式写入依赖的用户空间命令提交。

DMA_RESV_USAGE_READ

隐式读取同步。

这应该仅用于添加隐式读取依赖的用户空间命令提交。

DMA_RESV_USAGE_BOOKKEEP

无隐式同步。

这应该用于不想参与任何隐式同步的提交。

最常见的情况是抢占栅栏、页表更新、TLB 刷新以及显式同步的用户提交。

显式同步的用户提交可以使用 dma_buf_import_sync_file() 根据需要提升为 DMA_RESV_USAGE_READ 或 DMA_RESV_USAGE_WRITE,当在最初添加栅栏后有必要进行隐式同步时。

描述

此枚举描述了 dma_resv 对象不同的使用案例,并控制查询时返回哪些栅栏。

一个重要的事实是,存在 KERNEL<WRITE<READ<BOOKKEEP 的顺序,并且当 dma_resv 对象被要求为一个使用案例提供栅栏时,也会返回较低使用案例的栅栏。

例如,当请求 WRITE 栅栏时,也会返回 KERNEL 栅栏。类似地,当请求 READ 栅栏时,也会返回 WRITE 和 KERNEL 栅栏。

已经使用的栅栏可以被提升,比如通过再次使用该用法添加,使具有 DMA_RESV_USAGE_BOOKKEEP 的栅栏变成 DMA_RESV_USAGE_READ。但是栅栏永远不会降级,比如使具有 DMA_RESV_USAGE_WRITE 的栅栏变成 DMA_RESV_USAGE_READ。

enum dma_resv_usage dma_resv_usage_rw(bool write)

用于隐式同步的辅助函数

参数

bool write

如果创建新的隐式同步写入,则为 true

描述

这返回用于写入或读取访问的隐式同步用法,请参阅 enum dma_resv_usagedma_buf.resv

struct dma_resv

预留对象管理缓冲区的栅栏

定义:

struct dma_resv {
    struct ww_mutex lock;
    struct dma_resv_list __rcu *fences;
};

成员

更新侧锁。不要直接使用,而应该使用诸如 dma_resv_lock()dma_resv_unlock() 之类的包装器函数。

使用预留对象来动态管理内存的驱动程序也会使用此锁来保护缓冲区对象的状态,如放置、分配策略或整个命令提交。

栅栏

添加到 dma_resv 对象的栅栏数组

通过调用 dma_resv_add_fence() 添加新栅栏。由于这通常需要在命令提交中到达不归路之后才能完成,因此它不能失败,因此需要通过调用 dma_resv_reserve_fences() 预留足够的槽。

描述

这是 dma_fence 对象的容器,需要处理多个用例。

一种用法是同步跨驱动程序对 struct dma_buf 的访问,用于动态缓冲区管理或仅仅处理用户空间中缓冲区的不同用户之间的隐式同步。有关更深入的讨论,请参阅 dma_buf.resv

另一个主要用途是在基于缓冲区的内存管理器中管理驱动程序内的访问和锁定。struct ttm_buffer_object 是这里的典型示例,因为这是预留对象的起源地。但是驱动程序中的使用正在扩展,一些驱动程序也使用相同的方案管理 struct drm_gem_object

struct dma_resv_iter

dma_resv 栅栏中的当前位置

定义:

struct dma_resv_iter {
    struct dma_resv *obj;
    enum dma_resv_usage usage;
    struct dma_fence *fence;
    enum dma_resv_usage fence_usage;
    unsigned int index;
    struct dma_resv_list *fences;
    unsigned int num_fences;
    bool is_restarted;
};

成员

obj

我们正在迭代的 dma_resv 对象

usage

返回具有此用法或更低用法的栅栏。

fence

当前处理的栅栏

fence_usage

当前栅栏的用法

index

共享栅栏中的索引

栅栏

共享栅栏;私有,必须不能解引用

num_fences

栅栏数量

is_restarted

如果这是返回的第一个栅栏,则为 true

描述

请勿在驱动程序中直接操作此值,请使用访问器函数代替。

重要

当使用诸如 dma_resv_iter_next_unlocked()dma_resv_for_each_fence_unlocked() 之类的无锁迭代器时,请注意迭代器可能会重新启动。积累统计信息或类似情况的代码需要使用 dma_resv_iter_is_restarted() 检查这一点。

void dma_resv_iter_begin(struct dma_resv_iter *cursor, struct dma_resv *obj, enum dma_resv_usage usage)

初始化 dma_resv_iter 对象

参数

struct dma_resv_iter *cursor

要初始化的 dma_resv_iter 对象

struct dma_resv *obj

我们想要迭代的 dma_resv 对象

enum dma_resv_usage usage

控制要包括哪些栅栏,请参见 enum dma_resv_usage

void dma_resv_iter_end(struct dma_resv_iter *cursor)

清理 dma_resv_iter 对象

参数

struct dma_resv_iter *cursor

应该清理的 dma_resv_iter 对象

描述

确保正确释放光标中对 fence 的引用。

enum dma_resv_usage dma_resv_iter_usage(struct dma_resv_iter *cursor)

返回当前 fence 的使用情况

参数

struct dma_resv_iter *cursor

当前位置的光标

描述

返回当前处理的 fence 的使用情况。

bool dma_resv_iter_is_restarted(struct dma_resv_iter *cursor)

测试这是否是重启后的第一个 fence

参数

struct dma_resv_iter *cursor

带有当前位置的游标

描述

如果这是重启后迭代中的第一个 fence,则返回 true。

dma_resv_for_each_fence_unlocked

dma_resv_for_each_fence_unlocked (cursor, fence)

解锁的 fence 迭代器

参数

光标

一个 struct dma_resv_iter 指针

fence

当前的 fence

描述

在不持有 struct dma_resv 对象的 dma_resv.lock 并使用 RCU 的情况下,迭代 struct dma_resv 对象中的 fences。光标需要使用 dma_resv_iter_begin() 初始化,并使用 dma_resv_iter_end() 清理。在迭代器内部,会持有对 dma_fence 的引用,并释放 RCU 锁。

注意,当修改 cursorstruct dma_resv 时,迭代器可能会重新启动。累积统计数据或类似的代码需要使用 dma_resv_iter_is_restarted() 来检查这种情况。因此,尽可能优先使用锁迭代器 dma_resv_for_each_fence()

dma_resv_for_each_fence

dma_resv_for_each_fence (cursor, obj, usage, fence)

fence 迭代器

参数

光标

一个 struct dma_resv_iter 指针

obj

一个 dma_resv 对象指针

usage

控制返回哪些 fences

fence

当前的 fence

描述

在持有 dma_resv.lock 的情况下,迭代 struct dma_resv 对象中的 fences。 all_fences 控制是否也返回共享的 fences。光标初始化是迭代器的一部分,并且只要持有锁,fence 就保持有效,因此不会对 fence 进行额外的引用。

int dma_resv_lock(struct dma_resv *obj, struct ww_acquire_ctx *ctx)

锁定保留对象

参数

struct dma_resv *obj

保留对象

struct ww_acquire_ctx *ctx

锁定上下文

描述

锁定保留对象以进行独占访问和修改。请注意,该锁仅针对其他写入者,读取者将在 RCU 下与写入者并发运行。 seqlock 用于通知读取器它们是否与写入器重叠。

由于保留对象可能由多个参与者以未定义的顺序锁定,因此会传递一个 #ww_acquire_ctx 以便在检测到循环时回滚。 请参阅 ww_mutex_lock() 和 ww_acquire_init()。通过传递 NULL 作为 ctx,保留对象可以由其自身锁定。

当返回 -EDEADLK 指示死锁情况时,必须解锁 ctx 持有的所有锁,然后在 obj 上调用 dma_resv_lock_slow()

通过调用 dma_resv_unlock() 解锁。

另请参阅可中断变体 dma_resv_lock_interruptible()

int dma_resv_lock_interruptible(struct dma_resv *obj, struct ww_acquire_ctx *ctx)

锁定保留对象

参数

struct dma_resv *obj

保留对象

struct ww_acquire_ctx *ctx

锁定上下文

描述

可中断地锁定保留对象以进行独占访问和修改。请注意,该锁仅针对其他写入者,读取者将在 RCU 下与写入者并发运行。 seqlock 用于通知读取器它们是否与写入器重叠。

由于保留对象可能由多个参与者以未定义的顺序锁定,因此会传递一个 #ww_acquire_ctx 以便在检测到循环时回滚。 请参阅 ww_mutex_lock() 和 ww_acquire_init()。通过传递 NULL 作为 ctx,保留对象可以由其自身锁定。

当返回 -EDEADLK 指示死锁情况时,必须解锁 ctx 持有的所有锁,然后在 obj 上调用 dma_resv_lock_slow_interruptible()

通过调用 dma_resv_unlock() 解锁。

void dma_resv_lock_slow(struct dma_resv *obj, struct ww_acquire_ctx *ctx)

慢速路径锁定保留对象

参数

struct dma_resv *obj

保留对象

struct ww_acquire_ctx *ctx

锁定上下文

描述

在死锁情况后获取保留对象。 此函数将休眠,直到该锁变为可用。另请参阅 dma_resv_lock()

另请参阅可中断变体 dma_resv_lock_slow_interruptible()

int dma_resv_lock_slow_interruptible(struct dma_resv *obj, struct ww_acquire_ctx *ctx)

慢速路径可中断地锁定保留对象

参数

struct dma_resv *obj

保留对象

struct ww_acquire_ctx *ctx

锁定上下文

描述

在发生死锁情况后可中断地获取保留对象。 此函数将休眠,直到该锁变为可用。另请参阅 dma_resv_lock_interruptible()

bool dma_resv_trylock(struct dma_resv *obj)

尝试锁定保留对象

参数

struct dma_resv *obj

保留对象

描述

尝试锁定保留对象以进行独占访问和修改。 请注意,该锁仅针对其他写入者,读取者将在 RCU 下与写入者并发运行。 seqlock 用于通知读取器它们是否与写入器重叠。

另请注意,由于未提供上下文,因此无法进行死锁保护,这对于 trylock 也是不需要的。

如果获取到锁,则返回 true,否则返回 false。

bool dma_resv_is_locked(struct dma_resv *obj)

判断预留对象是否被锁定

参数

struct dma_resv *obj

保留对象

描述

如果互斥锁被锁定,则返回 true;如果未锁定,则返回 false。

struct ww_acquire_ctx *dma_resv_locking_ctx(struct dma_resv *obj)

返回用于锁定该对象的上下文

参数

struct dma_resv *obj

保留对象

描述

返回用于锁定预留对象的上下文,如果未使用上下文或者对象根本未锁定,则返回 NULL。

警告:此接口非常糟糕,但 TTM 需要它,因为它在一些非常长的调用链中没有传递 struct ww_acquire_ctx。其他所有人都只是用它来检查他们是否持有预留。

void dma_resv_unlock(struct dma_resv *obj)

解锁预留对象

参数

struct dma_resv *obj

保留对象

描述

在独占访问之后解锁预留对象。

DMA Fence

DMA fence,由 struct dma_fence 表示,是内核内部用于 DMA 操作的同步原语,例如 GPU 渲染、视频编码/解码或在屏幕上显示缓冲区。

fence 使用 dma_fence_init() 初始化,并使用 dma_fence_signal() 完成。fence 与通过 dma_fence_context_alloc() 分配的上下文相关联,并且同一上下文中的所有 fence 都是完全有序的。

由于 fence 的目的是促进跨设备和跨应用程序同步,因此有多种使用方式

  • 单独的 fence 可以作为 sync_file 公开,作为用户空间的 文件描述符 访问,通过调用 sync_file_create() 创建。这称为显式 fencing,因为用户空间传递显式同步点。

  • 一些子系统也有它们自己的显式 fencing 原语,如 drm_syncobj。与 sync_file 相比,drm_syncobj 允许更新底层 fence。

  • 然后还有隐式 fencing,其中同步点作为共享 dma_buf 实例的一部分隐式传递。此类隐式 fence 通过 dma_buf.resv 指针存储在 struct dma_resv 中。

DMA Fence 跨驱动程序约定

由于 dma_fence 提供跨驱动程序约定,因此所有驱动程序都必须遵循相同的规则

  • fence 必须在合理的时间内完成。表示用户空间提交的内核和着色器的 fence(可能会永远运行)必须由超时和 gpu 挂起恢复代码支持。最基本的要求是该代码必须阻止进一步的命令提交并强制完成所有正在进行的 fence,例如,当驱动程序或硬件不支持 gpu 重置时,或者如果 gpu 重置因某种原因失败时。理想情况下,驱动程序支持仅影响有问题的用户空间上下文的 gpu 恢复,而不影响其他用户空间的提交。

  • 驱动程序对于在合理的时间内完成可能具有不同的理解。一些挂起恢复代码使用固定的超时,另一些则混合使用观察前进进度和越来越严格的超时。驱动程序不应尝试猜测其他驱动程序的 fence 超时处理。

  • 为了确保 dma_fence_wait() 与其他锁之间没有死锁,驱动程序应使用 dma_fence_begin_signalling()dma_fence_end_signalling() 来注释所有需要到达 dma_fence_signal() 的代码,后者会完成 fence。

  • 允许驱动程序在持有 dma_resv_lock() 时调用 dma_fence_wait()。这意味着完成 fence 所需的任何代码都不能获取 dma_resv 锁。请注意,这也引入了围绕 dma_resv_lock()dma_resv_unlock() 建立的整个锁定层次结构。

  • 允许驱动程序从其 shrinker 回调中调用 dma_fence_wait()。这意味着完成 fence 所需的任何代码都不能使用 GFP_KERNEL 分配内存。

  • 允许驱动程序分别从其 mmu_notifiermmu_interval_notifier 回调中调用 dma_fence_wait()。这意味着完成 fence 所需的任何代码都不能使用 GFP_NOFS 或 GFP_NOIO 分配内存。只允许使用 GFP_ATOMIC,这可能会失败。

请注意,只有 GPU 驱动程序才有合理的理由同时需要 mmu_interval_notifiershrinker 回调,以及必须使用 dma_fence 跟踪异步计算工作。 drivers/gpu 之外的任何驱动程序都不应在此类上下文中调用 dma_fence_wait()

DMA Fence 发出信号注释

通过代码审查和测试来证明围绕 dma_fence 的所有内核代码的正确性非常棘手,原因如下:

  • 这是一个跨驱动程序约定,因此所有驱动程序都必须遵循相同的规则,包括锁嵌套顺序、各种函数的调用上下文以及内核接口的任何其他重要事项。但是,也不可能在一台机器上测试所有驱动程序,因此对所有组合进行 N 对 N 的蛮力测试是不可能的。即使仅限于可能的组合也是不可行的。

  • 涉及大量的驱动程序代码。对于渲染驱动程序,存在命令提交的尾部、fence 发布后的调度程序代码、中断和用于处理作业完成的工作程序,以及超时、gpu 重置和 gpu 挂起恢复代码。此外,为了与核心 mm 集成,我们有 mmu_notifiermmu_interval_notifiershrinker。对于模式设置驱动程序,存在原子模式设置的 fence 发布后到相应的 vblank 完成之间的提交尾部函数,包括任何中断处理和相关的工作程序。审核所有驱动程序的这些代码是不可行的。

  • 由于涉及许多其他子系统以及由此引入的锁定层次结构,因此驱动程序特定的差异的回旋余地非常小。dma_fence 通过 dma_resvdma_resv_lock()dma_resv_unlock() 与几乎所有的核心内存处理(通过页面错误处理程序)进行交互。另一方面,它还通过 mmu_notifiershrinker 与所有分配站点进行交互。

此外,lockdep 不处理跨发布依赖关系,这意味着 dma_fence_wait()dma_fence_signal() 之间的任何死锁都无法通过一些快速测试在运行时捕获。最简单的例子是一个线程在持有锁的同时等待 dma_fence

lock(A);
dma_fence_wait(B);
unlock(A);

而另一个线程则卡在尝试获取相同的锁,这会阻止它发出上一个线程正在等待的 fence 信号

lock(A);
unlock(A);
dma_fence_signal(B);

通过手动注释所有与信号发送 dma_fence 相关的代码,我们可以让 lockdep 了解这些依赖关系,这也有助于验证工作,因为现在 lockdep 可以检查所有规则。

cookie = dma_fence_begin_signalling();
lock(A);
unlock(A);
dma_fence_signal(B);
dma_fence_end_signalling(cookie);

要使用 dma_fence_begin_signalling()dma_fence_end_signalling() 注释关键代码段,需要遵守以下规则:

  • 所有完成 dma_fence 所必需的代码都必须进行注释,从 fence 可被其他线程访问的点,到调用 dma_fence_signal() 的点。未注释的代码可能包含死锁问题,而且由于规则非常严格且存在许多边界情况,因此仅通过审查或正常的压力测试无法捕获这些问题。

  • struct dma_resv 值得特别注意,因为读者仅受 rcu 保护。这意味着信号发送关键代码段在新 fence 安装后立即开始,即使在 dma_resv_unlock() 被调用之前。

  • 唯一的例外是快速路径和机会性信号发送代码,它们调用 dma_fence_signal() 纯粹是为了优化,但不是保证 dma_fence 完成所必需的。常见的例子是 wait IOCTL 调用 dma_fence_signal(),而强制完成路径通过硬件中断和可能的作业完成工作程序。

  • 为了帮助代码的可组合性,只要整体锁定层次结构一致,注释就可以自由嵌套。注释也适用于中断和进程上下文。由于实现细节,这要求调用者将来自 dma_fence_begin_signalling() 的不透明 cookie 传递给 dma_fence_end_signalling()

  • 通过在启动时用相关的层次结构来初始化 lockdep 来实现针对跨驱动程序合同的验证。这意味着即使仅使用单个设备进行测试也足以验证驱动程序,至少就 dma_fence_wait()dma_fence_signal() 之间的死锁而言。

DMA Fence 截止时间提示

在理想情况下,应该可以充分流水线化工作负载,以便基于利用率的设备频率调速器可以达到满足用例要求的最低频率,以最大限度地降低功耗。但在现实世界中,有许多工作负载与这种理想背道而驰。例如,但不限于:

  • 在设备和 CPU 之间来回切换的工作负载,CPU 等待设备和设备等待 CPU 的时间交替出现。这可能导致 devfreq 和 cpufreq 在其各自的域中看到空闲时间,从而降低频率。

  • 与基于时间的周期性截止时间交互的工作负载,例如双缓冲 GPU 渲染与 vblank 同步页面翻转。在这种情况下,错过 vblank 截止时间会导致 GPU 的空闲时间增加(因为它必须等待额外的 vblank 周期),从而向 GPU 的 devfreq 发送信号以降低频率,而实际上需要的是相反的情况。

为此,可以通过 dma_fence_set_deadline(或间接地通过面向用户的 ioctl,如 sync_set_deadline)在 dma_fence 上设置截止时间提示。截止时间提示为等待驱动程序或用户空间提供了一种向信号发送驱动程序传达适当的紧急感的方式。

截止时间提示以绝对 ktime 给出(面向用户空间的 API 使用 CLOCK_MONOTONIC)。时间可以是未来的某个点(例如页面翻转基于 vblank 的截止时间,或合成器的合成周期的开始),也可以是当前时间,以指示立即的截止时间提示(即,在此 fence 发出信号之前无法取得进展)。

可以在给定的 fence 上设置多个截止时间,甚至是并行设置。请参阅 dma_fence_ops.set_deadline 的文档。

截止时间提示只是一个提示。创建 fence 的驱动程序可能会通过提高频率、做出不同的调度选择等来做出反应。或者什么都不做。

DMA Fence 函数参考

struct dma_fence *dma_fence_get_stub(void)

返回已发出信号的 fence

参数

void

无参数

描述

返回一个已发出信号的存根 fence。fence 的时间戳对应于启动后首次调用此函数的时间。

struct dma_fence *dma_fence_allocate_private_stub(ktime_t timestamp)

返回一个私有的已发出信号的 fence

参数

ktime_t timestamp

fence 发出信号的时间戳

描述

返回一个新分配和已发出信号的存根 fence。

u64 dma_fence_context_alloc(unsigned num)

分配一个 fence 上下文数组

参数

unsigned num

要分配的上下文数量

描述

此函数将返回分配的 fence 上下文数量的第一个索引。fence 上下文用于通过将上下文传递给 dma_fence_init(),为 dma_fence.context 设置一个唯一的数字。

bool dma_fence_begin_signalling(void)

开始一个关键的 DMA fence 信号发送段

参数

void

无参数

描述

驱动程序应该使用此函数来注释最终需要通过调用 dma_fence_signal() 来完成 dma_fence 的任何代码段的开头。

这些关键段的结尾使用 dma_fence_end_signalling() 进行注释。

实现所需的不透明 cookie,需要传递给 dma_fence_end_signalling()

返回

void dma_fence_end_signalling(bool cookie)

结束一个关键的 DMA fence 信号发送段

参数

bool cookie

来自 dma_fence_begin_signalling() 的不透明 cookie

描述

关闭由 dma_fence_begin_signalling() 打开的关键段注释。

int dma_fence_signal_timestamp_locked(struct dma_fence *fence, ktime_t timestamp)

发出栅栏完成的信号

参数

struct dma_fence *fence

要发出信号的栅栏

ktime_t timestamp

栅栏信号的时间戳,以内核的 CLOCK_MONOTONIC 时钟域为准

描述

为栅栏上的软件回调发出完成信号,这将解除 dma_fence_wait() 调用的阻塞,并运行所有通过 dma_fence_add_callback() 添加的回调。可以多次调用,但由于栅栏只能从无信号状态变为有信号状态,而不能返回,因此只有第一次调用有效。将提供的时间戳设置为栅栏信号时间戳。

dma_fence_signal_timestamp() 不同,此函数必须在持有 dma_fence.lock 的情况下调用。

成功时返回 0,当 fence 已经被发出信号时返回负错误值。

int dma_fence_signal_timestamp(struct dma_fence *fence, ktime_t timestamp)

发出栅栏完成的信号

参数

struct dma_fence *fence

要发出信号的栅栏

ktime_t timestamp

栅栏信号的时间戳,以内核的 CLOCK_MONOTONIC 时钟域为准

描述

为栅栏上的软件回调发出完成信号,这将解除 dma_fence_wait() 调用的阻塞,并运行所有通过 dma_fence_add_callback() 添加的回调。可以多次调用,但由于栅栏只能从无信号状态变为有信号状态,而不能返回,因此只有第一次调用有效。将提供的时间戳设置为栅栏信号时间戳。

成功时返回 0,当 fence 已经被发出信号时返回负错误值。

int dma_fence_signal_locked(struct dma_fence *fence)

发出栅栏完成的信号

参数

struct dma_fence *fence

要发出信号的栅栏

描述

为栅栏上的软件回调发出完成信号,这将解除 dma_fence_wait() 调用的阻塞,并运行所有通过 dma_fence_add_callback() 添加的回调。可以多次调用,但由于栅栏只能从无信号状态变为有信号状态,而不能返回,因此只有第一次调用有效。

dma_fence_signal() 不同,此函数必须在持有 dma_fence.lock 的情况下调用。

成功时返回 0,当 fence 已经被发出信号时返回负错误值。

int dma_fence_signal(struct dma_fence *fence)

发出栅栏完成的信号

参数

struct dma_fence *fence

要发出信号的栅栏

描述

为栅栏上的软件回调发出完成信号,这将解除 dma_fence_wait() 调用的阻塞,并运行所有通过 dma_fence_add_callback() 添加的回调。可以多次调用,但由于栅栏只能从无信号状态变为有信号状态,而不能返回,因此只有第一次调用有效。

成功时返回 0,当 fence 已经被发出信号时返回负错误值。

signed long dma_fence_wait_timeout(struct dma_fence *fence, bool intr, signed long timeout)

睡眠直到栅栏发出信号或直到超时时间耗尽

参数

struct dma_fence *fence

要等待的栅栏

bool intr

如果为 true,则进行可中断等待

signed long timeout

超时值,以 jiffies 为单位,或 MAX_SCHEDULE_TIMEOUT

描述

如果被中断则返回 -ERESTARTSYS,如果等待超时则返回 0,如果成功则返回剩余的超时时间(以 jiffies 为单位)。自定义实现可能会返回其他错误值。

对此栅栏执行同步等待。假设调用方直接或间接地(预订和提交之间的 buf-mgr)持有对栅栏的引用,否则栅栏可能会在返回之前被释放,从而导致未定义的行为。

另请参见 dma_fence_wait()dma_fence_wait_any_timeout()

void dma_fence_release(struct kref *kref)

栅栏的默认释放函数

参数

struct kref *kref

dma_fence.recfount

描述

这是 dma_fence 的默认释放函数。驱动程序不应直接调用此函数,而应调用 dma_fence_put()

void dma_fence_free(struct dma_fence *fence)

dma_fence 的默认释放函数。

参数

struct dma_fence *fence

要释放的栅栏

描述

这是 dma_fence_ops.release 的默认实现。它对 fence 调用 kfree_rcu()

void dma_fence_enable_sw_signaling(struct dma_fence *fence)

启用栅栏上的信号

参数

struct dma_fence *fence

要启用的栅栏

描述

这将请求启用软件信号,以使栅栏尽快完成。这会在内部调用 dma_fence_ops.enable_signaling

int dma_fence_add_callback(struct dma_fence *fence, struct dma_fence_cb *cb, dma_fence_func_t func)

添加一个回调,以便在栅栏发出信号时调用

参数

struct dma_fence *fence

要等待的栅栏

struct dma_fence_cb *cb

要注册的回调

dma_fence_func_t func

要调用的函数

描述

向栅栏添加软件回调。调用者应保持对栅栏的引用。

cb 将由 dma_fence_add_callback() 初始化,不需要调用方进行初始化。可以向一个栅栏注册任意数量的回调,但一个回调一次只能注册到一个栅栏。

如果栅栏已经发出信号,则此函数将返回 -ENOENT(并且调用回调)。

请注意,可以从原子上下文或 irq 上下文调用回调。

成功时返回 0,如果栅栏已发出信号则返回 -ENOENT,如果发生错误则返回 -EINVAL。

int dma_fence_get_status(struct dma_fence *fence)

返回完成时的状态

参数

struct dma_fence *fence

要查询的 dma_fence

描述

此函数包装 dma_fence_get_status_locked(),以返回发出信号的栅栏的错误状态条件。有关更多详细信息,请参见 dma_fence_get_status_locked()

如果栅栏尚未发出信号,则返回 0;如果栅栏已发出信号且没有错误条件,则返回 1;如果栅栏已完成并出现错误,则返回负错误代码。

bool dma_fence_remove_callback(struct dma_fence *fence, struct dma_fence_cb *cb)

从信号列表中删除回调

参数

struct dma_fence *fence

要等待的栅栏

struct dma_fence_cb *cb

要删除的回调

描述

从栅栏中删除先前排队的回调。如果回调成功删除,则此函数返回 true,如果栅栏已发出信号,则返回 false。

警告:取消回调函数应仅在您确实了解自己在做什么的情况下进行,因为死锁和竞争条件很容易发生。因此,只有在硬件锁死恢复时,才应该使用对 fence 的引用进行取消。

如果事先没有使用 dma_fence_add_callback()cb 添加到 fence 中,则行为是未定义的。

signed long dma_fence_default_wait(struct dma_fence *fence, bool intr, signed long timeout)

默认情况下,睡眠直到 fence 被触发或直到超时时间结束。

参数

struct dma_fence *fence

要等待的栅栏

bool intr

如果为 true,则进行可中断等待

signed long timeout

超时值,以 jiffies 为单位,或 MAX_SCHEDULE_TIMEOUT

描述

如果中断则返回 -ERESTARTSYS,如果等待超时则返回 0,或者成功时返回剩余的 jiffies 超时时间。如果超时时间为零,则如果 fence 已经触发,为了与其他采用 jiffies 超时时间的函数保持一致,将返回 1。

signed long dma_fence_wait_any_timeout(struct dma_fence **fences, uint32_t count, bool intr, signed long timeout, uint32_t *idx)

睡眠直到任何 fence 被触发或直到超时时间结束。

参数

struct dma_fence **fences

要等待的 fence 数组

uint32_t count

要等待的 fence 数量

bool intr

如果为 true,则进行可中断等待

signed long timeout

超时值,以 jiffies 为单位,或 MAX_SCHEDULE_TIMEOUT

uint32_t *idx

用于存储第一个被触发的 fence 的索引,仅在正返回时有意义。

描述

在自定义 fence 等待实现上返回 -EINVAL,如果中断则返回 -ERESTARTSYS,如果等待超时则返回 0,或者成功时返回剩余的 jiffies 超时时间。

同步等待数组中第一个 fence 被触发。调用者需要持有数组中所有 fence 的引用,否则 fence 可能会在返回之前被释放,从而导致未定义的行为。

另请参阅 dma_fence_wait()dma_fence_wait_timeout()

void dma_fence_set_deadline(struct dma_fence *fence, ktime_t deadline)

设置所需的 fence 等待截止时间提示。

参数

struct dma_fence *fence

要等待的 fence。

ktime_t deadline

等待器希望 fence 在此时间之前被触发的时间。

描述

向 fence 发信号器提供有关即将到来的截止时间(例如,垂直同步)的提示,等待器希望 fence 在此时间点之前被触发。这旨在向 fence 发信号器提供反馈,以帮助进行电源管理决策,例如,如果定期的垂直同步截止时间即将来临但 fence 尚未被触发,则可以提高 GPU 频率。

void dma_fence_describe(struct dma_fence *fence, struct seq_file *seq)

将 fence 描述转储到 seq_file 中。

参数

struct dma_fence *fence

要描述的 fence。

struct seq_file *seq

将文本描述放入的 seq_file。

描述

将 fence 的文本描述及其状态转储到 seq_file 中。

void dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops, spinlock_t *lock, u64 context, u64 seqno)

初始化自定义 fence。

参数

struct dma_fence *fence

要初始化的 fence。

const struct dma_fence_ops *ops

此 fence 上操作的 dma_fence_ops。

spinlock_t *lock

用于锁定此 fence 的 irqsafe 自旋锁。

u64 context

此 fence 在其上运行的执行上下文。

u64 seqno

此上下文的线性递增序列号。

描述

初始化已分配的 fence,调用者在通过此 fence 提交后无需保留其引用计数,但如果调用 dma_fence_ops.enable_signaling,则需要再次保持引用计数。

context 和 seqno 用于在 fence 之间进行轻松比较,从而可以通过简单地使用 dma_fence_later() 来检查哪个 fence 更晚触发。

struct dma_fence

软件同步原语。

定义:

struct dma_fence {
    spinlock_t *lock;
    const struct dma_fence_ops *ops;
    union {
        struct list_head cb_list;
        ktime_t timestamp;
        struct rcu_head rcu;
    };
    u64 context;
    u64 seqno;
    unsigned long flags;
    struct kref refcount;
    int error;
};

成员

用于锁定的 spin_lock_irqsave。

ops

与此 fence 关联的 dma_fence_ops。

{unnamed_union}

匿名

cb_list

要调用的所有回调函数的列表。

timestamp

fence 被触发时的时间戳。

rcu

用于使用 kfree_rcu 释放 fence。

context

此 fence 所属的执行上下文,由 dma_fence_context_alloc() 返回。

seqno

此 fence 在执行上下文中的序列号,可以进行比较以决定哪个 fence 会稍后被触发。

flags

下面定义的 DMA_FENCE_FLAG_* 的掩码。

refcount

此 fence 的引用计数。

error

可选,仅在 < 0 时有效,必须在调用 dma_fence_signal 之前设置,表示 fence 已完成且发生错误。

描述

必须使用适当的原子操作(bit_*)来操作和读取 flags 成员,因此大多数时候不需要使用自旋锁。

DMA_FENCE_FLAG_SIGNALED_BIT - fence 已被触发。 DMA_FENCE_FLAG_TIMESTAMP_BIT - 已记录 fence 触发的时间戳。 DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT - 可能已调用 enable_signaling。 DMA_FENCE_FLAG_USER_BITS - 未使用的位的起始位置,可以由 fence 的实现者用于其自己的目的。不同的 fence 实现者可以使用不同的方式,因此不要依赖它。

由于使用了原子位操作,因此不保证这种情况。特别是,如果设置了位,但在设置此位之前立即调用了 dma_fence_signal,则它将能够在调用 enable_signaling 之前设置 DMA_FENCE_FLAG_SIGNALED_BIT。在设置 DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT 之后添加对 DMA_FENCE_FLAG_SIGNALED_BIT 的检查可以解决此竞争问题,并确保在调用 dma_fence_signal 之后,任何 enable_signaling 调用都已完成,或者根本未被调用。

struct dma_fence_cb

dma_fence_add_callback() 的回调函数。

定义:

struct dma_fence_cb {
    struct list_head node;
    dma_fence_func_t func;
};

成员

node

dma_fence_add_callback() 使用,将此结构体追加到 fence::cb_list

func

要调用的 dma_fence_func_t

描述

此结构体将由 dma_fence_add_callback() 初始化,可以通过将 dma_fence_cb 嵌入到另一个结构体中来传递其他数据。

struct dma_fence_ops

为 fence 实现的操作

定义:

struct dma_fence_ops {
    bool use_64bit_seqno;
    const char * (*get_driver_name)(struct dma_fence *fence);
    const char * (*get_timeline_name)(struct dma_fence *fence);
    bool (*enable_signaling)(struct dma_fence *fence);
    bool (*signaled)(struct dma_fence *fence);
    signed long (*wait)(struct dma_fence *fence, bool intr, signed long timeout);
    void (*release)(struct dma_fence *fence);
    void (*fence_value_str)(struct dma_fence *fence, char *str, int size);
    void (*timeline_value_str)(struct dma_fence *fence, char *str, int size);
    void (*set_deadline)(struct dma_fence *fence, ktime_t deadline);
};

成员

use_64bit_seqno

如果此 dma_fence 实现使用 64 位 seqno,则为 True,否则为 false。

get_driver_name

返回驱动程序名称。这是一个回调,允许驱动程序在运行时计算名称,而无需为每个 fence 永久存储,或构建某种缓存。

此回调是强制性的。

get_timeline_name

返回此 fence 所属上下文的名称。这是一个回调,允许驱动程序在运行时计算名称,而无需为每个 fence 永久存储,或构建某种缓存。

此回调是强制性的。

enable_signaling

启用 fence 的软件信号。

对于具有硬件 -> 硬件信号能力的 fence 实现,它们可以实现此操作以启用必要的中断,或将命令插入 cmdstream 等,以避免在只需要硬件 -> 硬件同步的常见情况下进行这些代价高昂的操作。这是在第一个 dma_fence_wait()dma_fence_add_callback() 路径中调用的,以让 fence 实现知道有另一个驱动程序正在等待信号(即硬件 -> 软件情况)。

此函数可以从原子上下文中调用,但不能从 irq 上下文中调用,因此可以使用普通自旋锁。

返回值 false 表示 fence 已通过,或发生了一些错误导致无法启用信号。 True 表示成功启用。

dma_fence.error 可能会在 enable_signaling 中设置,但仅当返回 false 时。

由于许多实现即使在调用 enable_signaling 之前也可以调用 dma_fence_signal(),所以存在一个竞争窗口,其中 dma_fence_signal() 可能会导致释放最终的 fence 引用并释放其内存。为了避免这种情况,此回调的实现应使用 dma_fence_get() 获取自己的引用,以便在 fence 被发出信号时释放(例如,通过中断处理程序)。

此回调是可选的。如果此回调不存在,则驱动程序必须始终启用信号。

signaled

窥视 fence 是否已发出信号,作为 dma_fence_wait()dma_fence_add_callback() 等的快速路径优化。请注意,此回调不需要做出任何保证,超过 fence 一旦指示为已发出信号,必须始终从此回调返回 true。即使 fence 已经完成,此回调也可能返回 false,在这种情况下,信息尚未通过系统传播。另请参阅 dma_fence_is_signaled()

如果返回 true,则可能会设置 dma_fence.error

此回调是可选的。

wait

自定义等待实现,如果未设置,则默认为 dma_fence_default_wait()

已弃用,新实现不应使用。仅由需要对其硬件复位过程进行特殊处理的现有实现使用。

如果等待是 intr = true 且等待被中断,则必须返回 -ERESTARTSYS;如果 fence 已发出信号,则返回剩余的 jiffies;如果等待超时,则返回 0。也可以在自定义实现上返回其他错误值,这些值应视为 fence 已发出信号。例如,可以像这样报告硬件锁定。

release

在 fence 销毁时调用以释放其他资源。可以从 irq 上下文中调用。此回调是可选的。如果为 NULL,则改为调用 dma_fence_free() 作为默认实现。

fence_value_str

回调以填充特定于此 fence 的自由形式的调试信息,例如序列号。

此回调是可选的。

timeline_value_str

将时间线的当前值填充为字符串,例如序列号。请注意,传递给此函数的特定 fence 应该无关紧要,驱动程序应仅使用它来查找相应的时间线结构。

set_deadline

回调以允许 fence 等待者通知 fence 发信号者即将到来的截止日期,例如垂直同步,等待者希望在此之前发出 fence 信号。这旨在向 fence 发信号者提供反馈,以帮助进行电源管理决策,例如提高 GPU 频率。

这是在不持有 dma_fence.lock 的情况下调用的,它可以多次从任何上下文调用。如果被调用者有一些状态要管理,则需要锁定。如果设置了多个截止日期,则期望跟踪最早的截止日期。如果截止日期在当前时间之前,则应将其解释为立即截止日期。

此回调是可选的。

void dma_fence_put(struct dma_fence *fence)

减少 fence 的引用计数

参数

struct dma_fence *fence

要减少引用计数的 fence

struct dma_fence *dma_fence_get(struct dma_fence *fence)

增加 fence 的引用计数

参数

struct dma_fence *fence

要增加引用计数的 fence

描述

返回相同的 fence,引用计数增加 1。

struct dma_fence *dma_fence_get_rcu(struct dma_fence *fence)

使用 rcu 读取锁从 dma_resv_list 获取 fence

参数

struct dma_fence *fence

要增加引用计数的 fence

描述

如果无法获得引用计数,或 fence,则函数返回 NULL。

struct dma_fence *dma_fence_get_rcu_safe(struct dma_fence __rcu **fencep)

获取对 RCU 跟踪的 fence 的引用

参数

struct dma_fence __rcu **fencep

指向要增加引用计数的 fence 的指针

描述

如果无法获得引用计数,或 fence,则函数返回 NULL。此函数处理获取对可能在 RCU 宽限期内重新分配的 fence 的引用(例如,使用 SLAB_TYPESAFE_BY_RCU),只要调用者在指向 fence 的指针上使用 RCU 即可。

另一种机制是采用 seqlock 来保护一组 fence,例如 struct dma_resv 所使用的。使用 seqlock 时,必须在获取对 fence 的引用之前获取 seqlock,并在之后进行检查(如此处所示)。

调用者需要持有 RCU 读取锁。

bool dma_fence_is_signaled_locked(struct dma_fence *fence)

返回一个指示,说明 fence 是否已发出信号。

参数

struct dma_fence *fence

要检查的 fence

描述

如果 fence 已经发出信号,则返回 true;否则返回 false。由于此函数不启用信号,因此如果之前未调用 dma_fence_add_callback()dma_fence_wait()dma_fence_enable_sw_signaling(),则不能保证返回 true。

此函数需要持有 dma_fence.lock

另请参阅 dma_fence_is_signaled()

bool dma_fence_is_signaled(struct dma_fence *fence)

返回一个指示,说明 fence 是否已发出信号。

参数

struct dma_fence *fence

要检查的 fence

描述

如果 fence 已经发出信号,则返回 true;否则返回 false。由于此函数不启用信号,因此如果之前未调用 dma_fence_add_callback()dma_fence_wait()dma_fence_enable_sw_signaling(),则不能保证返回 true。

建议 seqno 类型的 fence 在操作完成时调用 dma_fence_signal,这使得在调用硬件相关的等待指令之前,可以通过检查此函数的返回值来防止在发布时间和使用时间之间出现回绕问题。

另请参阅 dma_fence_is_signaled_locked()

bool __dma_fence_is_later(u64 f1, u64 f2, const struct dma_fence_ops *ops)

如果 f1 在时间顺序上晚于 f2,则返回 true。

参数

u64 f1

第一个 fence 的 seqno

u64 f2

来自同一上下文的第二个 fence 的 seqno

const struct dma_fence_ops *ops

与 seqno 关联的 dma_fence_ops

描述

如果 f1 在时间顺序上晚于 f2,则返回 true。两个 fence 必须来自同一上下文,因为 seqno 不在上下文之间通用。

bool dma_fence_is_later(struct dma_fence *f1, struct dma_fence *f2)

如果 f1 在时间顺序上晚于 f2,则返回 true。

参数

struct dma_fence *f1

来自同一上下文的第一个 fence

struct dma_fence *f2

来自同一上下文的第二个 fence

描述

如果 f1 在时间顺序上晚于 f2,则返回 true。两个 fence 必须来自同一上下文,因为 seqno 不会在上下文之间重用。

bool dma_fence_is_later_or_same(struct dma_fence *f1, struct dma_fence *f2)

如果 f1 晚于或等于 f2,则返回 true。

参数

struct dma_fence *f1

来自同一上下文的第一个 fence

struct dma_fence *f2

来自同一上下文的第二个 fence

描述

如果 f1 在时间顺序上晚于 f2 或与 f2 是同一个 fence,则返回 true。两个 fence 必须来自同一上下文,因为 seqno 不会在上下文之间重用。

struct dma_fence *dma_fence_later(struct dma_fence *f1, struct dma_fence *f2)

返回时间顺序上较晚的 fence

参数

struct dma_fence *f1

来自同一上下文的第一个 fence

struct dma_fence *f2

来自同一上下文的第二个 fence

描述

如果两个 fence 都已发出信号,则返回 NULL,否则返回最后一个发出信号的 fence。两个 fence 必须来自同一上下文,因为 seqno 不会在上下文之间重用。

int dma_fence_get_status_locked(struct dma_fence *fence)

返回完成时的状态

参数

struct dma_fence *fence

要查询的 dma_fence

描述

驱动程序可以在发出 fence 信号之前提供可选的错误状态条件(以指示 fence 是由于错误而不是成功完成的)。仅当 fence 已发出信号时,状态条件的值才有效,dma_fence_get_status_locked() 首先检查信号状态,然后再报告错误状态。

如果栅栏尚未发出信号,则返回 0;如果栅栏已发出信号且没有错误条件,则返回 1;如果栅栏已完成并出现错误,则返回负错误代码。

void dma_fence_set_error(struct dma_fence *fence, int error)

在 fence 上标记错误条件

参数

struct dma_fence *fence

dma_fence

int error

要存储的错误

描述

驱动程序可以在发出 fence 信号之前提供可选的错误状态条件,以指示 fence 是由于错误而不是成功完成的。必须在发出信号之前设置此项(以便在唤醒任何等待信号回调的进程之前,该值可见)。此辅助函数用于帮助捕获 #dma_fence.error 的错误设置。

驱动程序应使用的错误代码示例

  • -ENODATA 此操作未产生任何数据,没有其他操作受到影响。

  • -ECANCELED 来自同一上下文的所有操作都已取消。

  • -ETIME 操作导致超时,并可能导致设备重置。

ktime_t dma_fence_timestamp(struct dma_fence *fence)

用于获取 fence 完成时间戳的辅助函数

参数

struct dma_fence *fence

要获取时间戳的 fence。

描述

在发出 fence 信号后,时间戳会更新为发出信号的时间,但是设置时间戳可能会与等待信号的任务发生竞争。此辅助函数会忙等待正确的时间戳出现。

signed long dma_fence_wait(struct dma_fence *fence, bool intr)

睡眠直到 fence 发出信号

参数

struct dma_fence *fence

要等待的栅栏

bool intr

如果为 true,则进行可中断等待

描述

如果被信号中断,此函数将返回 -ERESTARTSYS,如果 fence 已发出信号,则返回 0。在自定义实现中可能会返回其他错误值。

在此 fence 上执行同步等待。假定调用方直接或间接持有对 fence 的引用,否则 fence 可能会在返回之前被释放,从而导致未定义的行为。

另请参阅 dma_fence_wait_timeout()dma_fence_wait_any_timeout()

bool dma_fence_is_array(struct dma_fence *fence)

检查 fence 是否来自数组子类

参数

struct dma_fence *fence

要测试的 fence

描述

如果它是 dma_fence_array,则返回 true,否则返回 false。

bool dma_fence_is_chain(struct dma_fence *fence)

检查 fence 是否来自链子类

参数

struct dma_fence *fence

要测试的 fence

描述

如果它是 dma_fence_chain,则返回 true,否则返回 false。

bool dma_fence_is_container(struct dma_fence *fence)

检查一个 fence 是否是其他 fence 的容器

参数

struct dma_fence *fence

要测试的 fence

描述

如果此 fence 是其他 fence 的容器,则返回 true,否则返回 false。这很重要,因为我们不能构建大型 fence 结构,否则在对这些 fence 进行操作时会陷入递归。

DMA Fence 数组

struct dma_fence_array *dma_fence_array_alloc(int num_fences)

分配一个自定义的 fence 数组

参数

int num_fences

[输入] 要在数组中添加的 fence 的数量

描述

成功时返回 dma fence 数组,失败时返回 NULL

void dma_fence_array_init(struct dma_fence_array *array, int num_fences, struct dma_fence **fences, u64 context, unsigned seqno, bool signal_on_any)

初始化一个自定义的 fence 数组

参数

struct dma_fence_array *array

[输入] 要 arm 的 dma fence 数组

int num_fences

[输入] 要在数组中添加的 fence 的数量

struct dma_fence **fences

[输入] 包含 fence 的数组

u64 context

[输入] 要使用的 fence 上下文

unsigned seqno

[输入] 要使用的序列号

bool signal_on_any

[输入] 数组中任何 fence 发出信号时发出信号

描述

实现不带分配的 dma_fence_array_create。在回收或 dma fence 发信号路径中初始化预先分配的 dma fence 数组很有用。

struct dma_fence_array *dma_fence_array_create(int num_fences, struct dma_fence **fences, u64 context, unsigned seqno, bool signal_on_any)

创建一个自定义的 fence 数组

参数

int num_fences

[输入] 要在数组中添加的 fence 的数量

struct dma_fence **fences

[输入] 包含 fence 的数组

u64 context

[输入] 要使用的 fence 上下文

unsigned seqno

[输入] 要使用的序列号

bool signal_on_any

[输入] 数组中任何 fence 发出信号时发出信号

描述

分配一个 dma_fence_array 对象,并使用 dma_fence_init() 初始化基础 fence。如果出错,则返回 NULL。

调用者应分配大小为 num_fences 的 fences 数组,并用想要添加到对象的 fence 填充它。将获取此数组的所有权,并在释放时对每个 fence 使用 dma_fence_put()

如果 signal_on_any 为 true,则如果数组中任何 fence 发出信号,则 fence 数组会发出信号,否则,当数组中所有 fence 发出信号时,它会发出信号。

bool dma_fence_match_context(struct dma_fence *fence, u64 context)

检查所有 fence 是否来自给定的上下文

参数

struct dma_fence *fence

[输入] fence 或 fence 数组

u64 context

[输入] 要针对所有 fence 检查的 fence 上下文

描述

针对给定上下文检查提供的 fence,或对于 fence 数组,检查数组中的所有 fence。如果任何 fence 来自不同的上下文,则返回 false。

struct dma_fence_array_cb

fence 数组的回调助手

定义:

struct dma_fence_array_cb {
    struct dma_fence_cb cb;
    struct dma_fence_array *array;
};

成员

cb

用于发信号的 fence 回调结构

array

对父 fence 数组对象的引用

struct dma_fence_array

表示 fence 数组的 fence

定义:

struct dma_fence_array {
    struct dma_fence base;
    spinlock_t lock;
    unsigned num_fences;
    atomic_t num_pending;
    struct dma_fence **fences;
    struct irq_work work;
    struct dma_fence_array_cb callbacks[] ;
};

成员

base

fence 基类

用于 fence 处理的自旋锁

num_fences

数组中 fence 的数量

num_pending

数组中仍在挂起的 fence

栅栏

fence 数组

work

内部 irq_work 函数

callbacks

回调助手数组

struct dma_fence_array *to_dma_fence_array(struct dma_fence *fence)

将 fence 强制转换为 dma_fence_array

参数

struct dma_fence *fence

要强制转换为 dma_fence_array 的 fence

描述

如果 fence 不是 dma_fence_array,则返回 NULL;否则返回 dma_fence_array。

dma_fence_array_for_each

dma_fence_array_for_each (fence, index, head)

遍历数组中的所有 fence

参数

fence

当前 fence

index

数组中的索引

head

潜在的 dma_fence_array 对象

描述

测试 array 是否为 dma_fence_array 对象,如果是,则遍历数组中的所有 fence。如果不是,则仅遍历 array 本身中的 fence。

有关深入迭代器,请参阅 dma_fence_unwrap_for_each()

DMA Fence 链

struct dma_fence *dma_fence_chain_walk(struct dma_fence *fence)

链式遍历函数

参数

struct dma_fence *fence

当前链节点

描述

将链遍历到下一个节点。返回下一个 fence,如果到达链的末尾,则返回 NULL。垃圾回收已发出信号的链节点。

int dma_fence_chain_find_seqno(struct dma_fence **pfence, uint64_t seqno)

按 seqno 查找 fence 链节点

参数

struct dma_fence **pfence

指向链节点的指针,从该节点开始

uint64_t seqno

要搜索的序列号

描述

将 fence 指针提前到将发出此序列号信号的链节点。如果未提供序列号,则此操作为空操作。

如果 fence 不是链节点,或者序列号尚未提前足够远,则返回 EINVAL。

void dma_fence_chain_init(struct dma_fence_chain *chain, struct dma_fence *prev, struct dma_fence *fence, uint64_t seqno)

初始化一个 fence 链

参数

struct dma_fence_chain *chain

要初始化的链节点

struct dma_fence *prev

上一个 fence

struct dma_fence *fence

当前的 fence

uint64_t seqno

用于 fence 链的序列号

描述

初始化一个新的链节点,并启动一个新链,或将该节点添加到上一个 fence 的现有链中。

struct dma_fence_chain

表示 fence 链节点的 fence

定义:

struct dma_fence_chain {
    struct dma_fence base;
    struct dma_fence __rcu *prev;
    u64 prev_seqno;
    struct dma_fence *fence;
    union {
        struct dma_fence_cb cb;
        struct irq_work work;
    };
    spinlock_t lock;
};

成员

base

fence 基类

prev

链的上一个 fence

prev_seqno

垃圾回收之前的原始上一个 seqno

fence

封装的 fence

{unnamed_union}

匿名

cb

用于发信号的回调

这用于添加回调以发信号通知 fence 链的完成。永远不会与 irq 工作同时使用。

work

用于发信号的 irq 工作项

Irq 工作结构,允许我们在不发生锁反转的情况下添加回调。永远不会与回调同时使用。

用于 fence 处理的自旋锁

struct dma_fence_chain *to_dma_fence_chain(struct dma_fence *fence)

将 fence 强制转换为 dma_fence_chain

参数

struct dma_fence *fence

要强制转换为 dma_fence_array 的 fence

描述

如果 fence 不是 dma_fence_chain,则返回 NULL;否则返回 dma_fence_chain。

struct dma_fence *dma_fence_chain_contained(struct dma_fence *fence)

返回包含的 fence

参数

struct dma_fence *fence

要测试的 fence

描述

如果 fence 是 dma_fence_chain,则此函数返回链对象内部包含的 fence,否则返回 fence 本身。

dma_fence_chain_alloc

dma_fence_chain_alloc ()

描述

返回新的 struct dma_fence_chain 对象,失败时返回 NULL。

这个特殊的分配器必须是一个宏,以便其分配可以单独计算(拥有单独的 alloc_tag)。类型转换是有意为之,以强制类型安全。

void dma_fence_chain_free(struct dma_fence_chain *chain)

参数

struct dma_fence_chain *chain

要释放的链节点

描述

释放已分配但未使用的 struct dma_fence_chain 对象。由于 fence 从未初始化或发布,因此不需要 RCU 宽限期。在调用 dma_fence_chain_init() 之后,必须通过调用 dma_fence_put() 来释放 fence,而不是通过此函数。

dma_fence_chain_for_each

dma_fence_chain_for_each (iter, head)

遍历链中的所有 fence

参数

iter

当前 fence

head

起始点

描述

遍历链中的所有 fence。在循环内部,我们保持对当前 fence 的引用,当跳出循环时必须释放该引用。

有关深入迭代器,请参阅 dma_fence_unwrap_for_each()

DMA Fence 解包

struct dma_fence_unwrap

容器结构的游标

定义:

struct dma_fence_unwrap {
    struct dma_fence *chain;
    struct dma_fence *array;
    unsigned int index;
};

成员

潜在的 dma_fence_chain,但也可能是其他 fence

array

潜在的 dma_fence_array,但也可能是其他 fence

index

如果 array 实际上是 dma_fence_array,则返回最后一个索引

描述

应与 dma_fence_unwrap_for_each() 迭代器宏一起使用。

dma_fence_unwrap_for_each

dma_fence_unwrap_for_each (fence, cursor, head)

遍历容器中的所有 fence

参数

fence

当前 fence

光标

容器内部的当前位置

head

迭代器的起始点

描述

解开 dma_fence_chain 和 dma_fence_array 容器,并深入探索其中的所有潜在 fence。如果 head 只是一个普通的 fence,则只返回这一个 fence。

dma_fence_unwrap_merge

dma_fence_unwrap_merge (...)

解开并合并 fence

参数

...

可变参数

描述

所有作为参数给出的 fence 都被解开,并作为扁平的 dma_fence_array 合并在一起。如果需要将多个容器合并在一起,则很有用。

作为宏实现,以便在堆栈上分配必要的数组,并将堆栈帧大小计入调用方。

如果内存分配失败,则返回 NULL;否则,返回一个表示所有给定 fence 的 dma_fence 对象。

DMA Fence 同步文件

struct sync_file *sync_file_create(struct dma_fence *fence)

创建同步文件

参数

struct dma_fence *fence

要添加到 sync_fence 的 fence

描述

创建一个包含 fence 的 sync_file。此函数为新创建的 sync_file 获取 fence 的额外引用(如果成功)。可以使用 fput(sync_file->file) 释放 sync_file。如果发生错误,则返回 sync_file 或 NULL。

struct dma_fence *sync_file_get_fence(int fd)

获取与 sync_file fd 相关的 fence

参数

int fd

从中获取 fence 的 sync_file fd

描述

确保 fd 引用有效的 sync_file,并返回一个表示 sync_file 中所有 fence 的 fence。如果发生错误,则返回 NULL。

struct sync_file

要导出到用户空间的同步文件

定义:

struct sync_file {
    struct file             *file;
    char user_name[32];
#ifdef CONFIG_DEBUG_FS;
    struct list_head        sync_file_list;
#endif;
    wait_queue_head_t wq;
    unsigned long           flags;
    struct dma_fence        *fence;
    struct dma_fence_cb cb;
};

成员

file

表示此 fence 的文件

user_name

用户空间提供的同步文件的名称,用于合并的 fence。否则,通过驱动程序回调生成(在这种情况下,整个数组为 0)。

sync_file_list

全局文件列表中的成员资格

wq

fence 信号的等待队列

flags

sync_file 的标志

fence

包含 sync_file 中 fence 的 fence

cb

fence 回调信息

描述

标志:POLL_ENABLED:表示用户空间当前是否正在进行 poll() 操作

DMA Fence 同步文件 uABI

struct sync_merge_data

SYNC_IOC_MERGE:合并两个 fence

定义:

struct sync_merge_data {
    char name[32];
    __s32 fd2;
    __s32 fence;
    __u32 flags;
    __u32 pad;
};

成员

name

新 fence 的名称

fd2

第二个 fence 的文件描述符

fence

将新 fence 的 fd 返回给用户空间

flags

merge_data 标志

pad

用于 64 位对齐的填充,应始终为零

描述

创建一个新的 fence,其中包含调用 fd 和 sync_merge_data.fd2 中 sync_pts 的副本。在 sync_merge_data.fence 中返回新 fence 的 fd

struct sync_fence_info

详细的 fence 信息

定义:

struct sync_fence_info {
    char obj_name[32];
    char driver_name[32];
    __s32 status;
    __u32 flags;
    __u64 timestamp_ns;
};

成员

obj_name

父 sync_timeline 的名称

driver_name

实现父对象的驱动程序的名称

status

fence 的状态:0:活动,1:已发出信号,<0:错误

flags

fence_info 标志

timestamp_ns

状态更改的时间戳(以纳秒为单位)

struct sync_file_info

SYNC_IOC_FILE_INFO:获取有关 sync_file 的详细信息

定义:

struct sync_file_info {
    char name[32];
    __s32 status;
    __u32 flags;
    __u32 num_fences;
    __u32 pad;
    __u64 sync_fence_info;
};

成员

name

fence 的名称

status

fence 的状态。1:已发出信号,0:活动,<0:错误

flags

sync_file_info 标志

num_fences

sync_file 中 fence 的数量

pad

用于 64 位对齐的填充,应始终为零

sync_fence_info

指向 struct sync_fence_info 数组的指针,其中包含 sync_file 中的所有 fence

描述

接受一个 struct sync_file_info。如果 num_fences 为 0,则该字段将更新为 fence 的实际数量。如果 num_fences > 0,系统将使用 sync_fence_info 上提供的指针返回最多 num_fences 个 struct sync_fence_info,其中包含详细的 fence 信息。

struct sync_set_deadline

SYNC_IOC_SET_DEADLINE - 在栅栏上设置截止时间提示

定义:

struct sync_set_deadline {
    __u64 deadline_ns;
    __u64 pad;
};

成员

deadline_ns

截止时间的绝对时间

pad

必须为零

描述

允许用户空间在栅栏上设置截止时间,请参阅 dma_fence_set_deadline

截止时间的时间基准是 CLOCK_MONOTONIC(与垂直同步相同)。例如

clock_gettime(CLOCK_MONOTONIC, t); deadline_ns = (t.tv_sec * 1000000000L) + t.tv_nsec + ns_until_deadline

无限 DMA 栅栏

在各种时候,已经提出了在 struct dma_fence 上,直到 dma_fence_wait() 完成之前,时间都是无限的。示例包括

  • 未来的栅栏,在 HWC1 中用于指示显示器不再使用缓冲区的时间,并且是在使缓冲区可见的屏幕更新时创建的。此栅栏完成的时间完全由用户空间控制。

  • 代理栅栏,提议用于处理尚未设置栅栏的 &drm_syncobj。用于异步延迟命令提交。

  • 用户空间栅栏或 GPU futexes,是命令缓冲区内的细粒度锁定,用户空间将其用于跨引擎或与 CPU 的同步,然后将其作为 DMA 栅栏导入,以集成到现有的 winsys 协议中。

  • 长时间运行的计算命令缓冲区,同时仍然使用传统的批处理结束 DMA 栅栏进行内存管理,而不是在重新调度计算作业时重新附加的上下文抢占 DMA 栅栏。

所有这些方案的共同之处在于,用户空间控制这些栅栏的依赖关系并控制它们何时触发。将无限栅栏与正常的内核 DMA 栅栏混合使用不起作用,即使包含回退超时以防止恶意用户空间也是如此。

  • 只有内核知道所有 DMA 栅栏依赖关系,用户空间不知道由于内存管理或调度器决策而注入的依赖关系。

  • 只有用户空间知道无限栅栏中的所有依赖关系以及它们何时完成,内核不可见。

此外,内核必须能够为了内存管理需求而阻止用户空间命令提交,这意味着我们必须支持依赖于 DMA 栅栏的无限栅栏。如果内核也支持内核中的无限栅栏(如 DMA 栅栏),就像上述任何提议一样,则可能出现死锁。

Indefinite Fencing Dependency Cycle

无限栅栏依赖循环

这意味着内核可能会意外地通过用户空间不知道的内存管理依赖关系创建死锁,这会随机挂起工作负载,直到超时生效。从用户空间的角度来看,工作负载不包含死锁。在这种混合栅栏架构中,没有一个实体知道所有依赖关系。因此,无法从内核内部防止此类死锁。

避免依赖循环的唯一解决方案是不允许在内核中使用无限栅栏。这意味着

  • 没有未来栅栏、代理栅栏或作为 DMA 栅栏导入的用户空间栅栏,无论有没有超时。

  • 没有 DMA 栅栏可以指示用户空间允许使用用户空间栅栏或长时间运行的计算工作负载进行命令提交的批处理缓冲区结束。这也意味着在这些情况下没有用于共享缓冲区的隐式栅栏。

可恢复的硬件页面错误影响

现代硬件支持可恢复的页面错误,这对 DMA 栅栏有很多影响。

首先,挂起的页面错误显然会阻止加速器上运行的工作,并且通常需要内存分配才能解决该错误。但是不允许内存分配来限制 DMA 栅栏的完成,这意味着任何使用可恢复页面错误的工作负载都不能使用 DMA 栅栏进行同步。必须改用用户空间控制的同步栅栏。

在 GPU 上,这会造成问题,因为当前 Linux 上的桌面合成器协议依赖于 DMA 栅栏,这意味着如果没有完全基于用户空间栅栏构建的新用户空间堆栈,它们就无法从可恢复页面错误中获益。具体而言,这意味着隐式同步将不可能。例外情况是,页面错误仅用作迁移提示,而绝不用作按需填充内存请求。目前,这意味着 GPU 上的可恢复页面错误仅限于纯计算工作负载。

此外,GPU 通常在 3D 渲染和计算端之间共享资源,如计算单元或命令提交引擎。如果具有 DMA 栅栏的 3D 作业和使用可恢复页面错误的计算工作负载都在挂起,则它们可能会死锁

  • 3D 工作负载可能需要等待计算作业完成并首先释放硬件资源。

  • 计算工作负载可能卡在页面错误中,因为内存分配正在等待 3D 工作负载的 DMA 栅栏完成。

有几种方法可以防止此问题,其中驱动程序需要确保

  • 计算工作负载始终可以被抢占,即使页面错误挂起且尚未修复。并非所有硬件都支持此功能。

  • DMA 栅栏工作负载和需要页面错误处理的工作负载具有独立的硬件资源,以保证向前进展。这可以通过例如专用引擎和 DMA 栅栏工作负载的最小计算单元预留来实现。

  • 可以通过仅在 DMA 栅栏处于运行状态时才为 DMA 栅栏工作负载预留硬件资源来进一步优化预留方法。这必须涵盖从 DMA 栅栏对其他线程可见,直到通过 dma_fence_signal() 完成栅栏的时间。

  • 作为最后的手段,如果硬件不提供有用的预留机制,则在需要 DMA 栅栏或需要页面错误处理的作业之间切换时,必须从 GPU 中刷新所有工作负载:这意味着所有 DMA 栅栏必须在具有页面错误处理的计算作业可以插入调度程序队列之前完成。反之亦然,在系统中的任何位置显示 DMA 栅栏之前,必须抢占所有计算工作负载,以保证刷新所有挂起的 GPU 页面错误。

  • 一个相当理论的选择是在分配内存以修复硬件页面错误时,通过单独的内存块或对所有 DMA 栅栏的完整依赖关系图进行运行时跟踪来消除这些依赖关系。这对内核的影响非常广泛,因为在 CPU 端解决页面错误本身可能会涉及页面错误。将处理硬件页面错误的影响限制在特定驱动程序中更可行和可靠。

请注意,在独立硬件(如复制引擎或其他 GPU)上运行的工作负载没有任何影响。这使我们即使在解决硬件页面错误时,也可以继续在内核内部使用 DMA 栅栏,例如,使用复制引擎来清除或复制解决页面错误所需的内存。

在某些方面,此页面错误问题是无限 DMA 栅栏讨论的一个特例:来自计算工作负载的无限栅栏可以依赖于 DMA 栅栏,但反之则不然。甚至页面错误问题也不是新的,因为用户空间中的某些其他 CPU 线程可能会遇到页面错误,这会阻止用户空间栅栏 - 在 GPU 上支持页面错误并没有带来任何根本性的新东西。