缓冲区共享与同步 (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 中,

  • 决定此分配发生的实际后备存储,

  • 并负责所有(共享)用户的 scatterlist 的任何迁移。

缓冲区用户

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

  • 不需要担心如何分配缓冲区或在何处分配。

  • 并且需要一种机制来访问构成此缓冲区的内存中的 scatterlist,该 scatterlist 映射到其自身的地址空间中,以便它可以访问同一块内存区域。 此接口由 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 授予新执行的应用程序访问权限,访问它本来不应该被允许访问的缓冲区。

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

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

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

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

  • 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'ing 缓冲区。 这在许多处理管道(例如,将软件渲染的图像馈送到硬件管道、缩略图创建、快照等)中是必需的。 此外,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 或扫描输出设备消耗)

    • 一旦不再需要缓冲区,就取消映射

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

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

    与内核 cpu 访问的动机类似,导入子系统的给定用户空间代码可以使用与本机缓冲区对象相同的接口来处理导入的 dma-buf 缓冲区对象,这一点再次非常重要。 这对于 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 的完成,即 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 或已将缓冲区 mmapped 到其地址空间中的 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 使用情况的快照。 也可以通过遥测定期收集它以监视各种指标。

有关该接口的详细文档,请参见 ABI 文件测试/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 或其他设备驱动程序同步,则客户端有责任等待缓冲区准备好读取或写入,然后再使用 DMA_BUF_SYNC_START 调用此 ioctl。 同样,客户端必须确保在以 DMA_BUF_SYNC_END 调用此 ioctl 之后,不会将后续工作提交到 GPU 或其他设备驱动程序?

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

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)非常有用,这些 API 假定使用显式同步模型,但仍然需要与 dma-buf 互操作。

预期的使用模式如下

  1. 通过 DMA_BUF_IOCTL_EXPORT_SYNC_FILE 导出 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 的操作。 如果缺少操作或分配 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 相关的“释放”文件操作。 它进而调用 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

[in] 应该被锁定的附件

描述

只有动态导入器(使用 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

[in] 应该取消锁定的附件

描述

这将取消锁定由 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

[in] 要返回其散列表的附件

enum dma_data_direction direction

[in] 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

[in] 要返回其散列表的附件

enum dma_data_direction direction

[in] 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

[in] 要从中取消映射缓冲区的附件

struct sg_table *sg_table

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

enum dma_data_direction direction

[in] 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

[in] 要从中取消映射缓冲区的附件

struct sg_table *sg_table

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

enum dma_data_direction direction

[in] DMA 传输的方向

描述

dma_buf_unmap_attachment() 的非锁定变体。

void dma_buf_move_notify(struct dma_buf *dmabuf)

通知附件 DMA-buf 正在移动

参数

struct dma_buf *dmabuf

[in] 正在移动的缓冲区

描述

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

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

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

enum dma_data_direction direction

[in] 访问方向。

描述

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

[in] 要完成 CPU 访问的缓冲区。

enum dma_data_direction direction

[in] 访问方向。

描述

此函数终止以 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] 要取消映射的缓冲区

struct iosys_map *map

[in] 要取消映射的 vmap 指针

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

取消映射由 dma_buf_vmap 获取的 vmap。

参数

struct dma_buf *dmabuf

[in] 要取消映射的缓冲区

struct iosys_map *map

[in] 要取消映射的 vmap 指针

struct dma_buf_ops

struct dma_buf 上可能的操作

定义:

struct dma_buf_ops {
    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);
};

成员

attach

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

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

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

此回调是可选的。

返回值

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

detach

dma_buf_detach() 调用此函数以释放 dma_buf_attachment。 提供此函数是为了让导出器可以清理 dma_buf_attachment 的任何 housekeeping。

此回调是可选的。

pin

dma_buf_pin() 调用此函数,并让导出器知道 DMA-buf 不能再移动。 理想情况下,导出器应 pin 缓冲区,以便所有设备通常都可以访问它。

此函数使用锁定的 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_parameters,该参数可通过来自 dma_buf_attachmentdevice.dma_params 访问。 attach 回调还应检查这些约束。

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

根据 enum dma_data_direction,可能存在多个用户同时访问(对于读取,可能),或者导出器可能希望向缓冲区用户提供的任何其他类型的共享。

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

请注意,对于非动态导出器,驱动程序必须保证内存可用且在此函数返回时已清除任何旧数据。 以流水线方式进行内部缓冲区移动的驱动程序必须等待所有移动和清除完成。 动态导出器不需要遵循此规则:对于非动态导入器,缓冲区已通过 pin 进行 pin,这具有相同的要求。 动态导入器 otoh 需要遵守 dma_resv 栅栏。

返回值

DMA 缓冲区的后备存储的 sg_table 散点列表,已经映射到连接了提供的 dma_buf_attachmentdevice 的设备地址空间中。 散点列表中的地址和长度是 PAGE_SIZE 对齐的。

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

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

unmap_dma_buf

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

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 共享同一个文件。

因此,导出器需要通过设置 vma->vm_file 并在 dma_buf mmap 回调中调整 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;
    struct list_head list_node;
    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

spinlock 用于保护名称访问以进行读取访问。

owner

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

list_node

用于 dma_buf 会计和调试的节点。

priv

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

resv

与此 dma-buf 关联的预留对象。

隐式同步规则

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

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

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

  • 驱动程序可以始终只添加一个写入 fence,因为它只会导致不必要的同步,但不会导致正确性问题。

  • 某些驱动程序只公开一个同步的用户空间 API,而没有跨驱动程序的流水线。 这些驱动程序不会为它们的访问设置任何 fence。 v4l 就是一个例子。

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

动态导入器规则

动态导入器(参见 dma_buf_attachment_is_dynamic())在如何设置 fence 方面有额外的约束。

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

  • 对于它们无法立即从其 dma_buf_attach_ops.move_notify 回调中禁用的任何访问,动态导入器应设置 fence。

重要提示

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

poll

用于用户空间 poll 支持

cb_in

用于用户空间 poll 支持

cb_out

用于用户空间 poll 支持

sysfs_entry

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

描述

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

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

设备 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 pages 的对等资源。

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;
    bool peer2peer;
    const struct dma_buf_attach_ops *importer_ops;
    void *importer_priv;
    void *priv;
};

成员

dmabuf

此附件的缓冲区。

dev

连接到缓冲区的设备。

node

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

peer2peer

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

importer_ops

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

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

指向导出器模块的指针 - 用于 refcounting 内核模块

ops

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

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

[in] 指向 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 导出器希望在调用映射/取消映射回调时持有 dma_resv 锁,则返回 true;如果它不希望持有该锁,则返回 false。

预留对象

预留对象提供了一种机制来管理与资源关联的 dma_fence 对象的容器。 一个预留对象可以附加任意数量的 fence。 每个 fence 都带有一个使用参数,该参数确定 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)

为 dma_resv 对象添加 fence 保留空间。

参数

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 obj

参数

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 obj 中的 fence

参数

struct dma_resv *obj

预留对象

uint64_t context

要替换的 fence 的上下文

struct dma_fence *replacement

要改用的新 fence

enum dma_resv_usage usage

如何使用新 fence,请参见 enum dma_resv_usage

描述

将具有指定上下文的 fence 替换为新的 fence。 仅当原始 fence 所代表的操作在新的 fence 完成时不再可以访问 dma_resv 对象所代表的资源时才有效。

使用此方法的一个示例是用页表更新 fence 替换抢占 fence,这会使资源无法访问。

struct dma_fence *dma_resv_iter_first_unlocked(struct dma_resv_iter *cursor)

未锁定的 dma_resv obj 中的第一个 fence。

参数

struct dma_resv_iter *cursor

带有当前位置的光标

描述

后续 fence 使用 dma_resv_iter_next_unlocked() 进行迭代。

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

返回来自未锁定的 dma_resv obj 的第一个 fence。

struct dma_fence *dma_resv_iter_next_unlocked(struct dma_resv_iter *cursor)

未锁定的 dma_resv obj 中的下一个 fence。

参数

struct dma_resv_iter *cursor

带有当前位置的光标

描述

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

返回来自未锁定的 dma_resv obj 的下一个 fence。

struct dma_fence *dma_resv_iter_first(struct dma_resv_iter *cursor)

从已锁定的 dma_resv 对象获取第一个 fence。

参数

struct dma_resv_iter *cursor

用于记录当前位置的光标。

描述

后续 fence 使用 dma_resv_iter_next_unlocked() 进行迭代。

返回 dma_resv 对象中的第一个 fence,同时持有 dma_resv.lock

struct dma_fence *dma_resv_iter_next(struct dma_resv_iter *cursor)

从已锁定的 dma_resv 对象获取下一个 fence。

参数

struct dma_resv_iter *cursor

用于记录当前位置的光标。

描述

返回 dma_resv 对象中的下一个 fence,同时持有 dma_resv.lock

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

将所有 fence 从 src 复制到 dst。

参数

struct dma_resv *dst

目标 reservation 对象。

struct dma_resv *src

源 reservation 对象。

描述

将所有 fence 从 src 复制到 dst。必须持有 dst-lock。

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

获取对象的 fences,无需持有 update side lock。

参数

struct dma_resv *obj

预留对象

enum dma_resv_usage usage

控制包含哪些 fences,请参阅 enum dma_resv_usage

unsigned int *num_fences

返回的 fence 数量。

struct dma_fence ***fences

返回的 fence 指针数组 (数组将 krealloc’d 到所需大小,并且必须由调用者释放)

描述

从 reservation 对象检索所有 fence。返回零或 -ENOMEM。

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

获取表示所有 fences 的单个 fence。

参数

struct dma_resv *obj

预留对象

enum dma_resv_usage usage

控制包含哪些 fences,请参阅 enum dma_resv_usage

struct dma_fence **fence

生成的 fence。

描述

获取表示 resv 对象中所有 fences 的单个 fence。成功时返回 0,失败时返回负错误值。

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

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

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

等待 reservation 对象的 fences。

参数

struct dma_resv *obj

预留对象

enum dma_resv_usage usage

控制包含哪些 fences,请参阅 enum dma_resv_usage

bool intr

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

unsigned long timeout

jiffies 中的超时值,或零以立即返回。

描述

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

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

在 reservation 对象的 fences 上设置截止时间。

参数

struct dma_resv *obj

预留对象

enum dma_resv_usage usage

控制包含哪些 fences,请参阅 enum dma_resv_usage

ktime_t deadline

请求的截止时间 (MONOTONIC)。

描述

可以在不持有 dma_resv 锁的情况下调用。在所有由 **usage** 过滤的 fences 上设置 **deadline**。

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

测试 reservation 对象的 fences 是否已发出信号。

参数

struct dma_resv *obj

预留对象

enum dma_resv_usage usage

控制包含哪些 fences,请参阅 enum dma_resv_usage

描述

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

返回值

如果所有 fences 都已发出信号,则为 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 对象中的 fences 的文本描述转储到 seq_file 中。

enum dma_resv_usage

dma_resv 对象中的 fences 的使用方式。

常量

DMA_RESV_USAGE_KERNEL

仅用于内核内存管理。

这应该仅用于使用 DMA 硬件引擎复制或清除内存,以用于内核内存管理的情况。

驱动程序在访问受 dma_resv 对象保护的资源之前*始终*必须等待这些 fences。唯一的例外是已知资源通过先前固定它而被锁定到位的情况。

DMA_RESV_USAGE_WRITE

隐式写入同步。

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

DMA_RESV_USAGE_READ

隐式读取同步。

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

DMA_RESV_USAGE_BOOKKEEP

没有隐式同步。

这应该由不希望参与任何隐式同步的提交使用。

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

显式同步的用户提交可以根据需要使用 dma_buf_import_sync_file() 提升到 DMA_RESV_USAGE_READ 或 DMA_RESV_USAGE_WRITE,以便在初始添加 fence 后,隐式同步变得必要。

描述

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

一个重要的事实是,顺序为 KERNEL<WRITE<READ<BOOKKEEP,当 dma_resv 对象被要求提供一个用例的 fences 时,也会返回较低用例的 fences。

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

已经使用的 fences 可以通过再次添加此用法的 fence 来提升,例如,具有 DMA_RESV_USAGE_BOOKKEEP 的 fence 可以变为 DMA_RESV_USAGE_READ。但是,fences 永远无法降级,例如,具有 DMA_RESV_USAGE_WRITE 的 fence 无法变为 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

一个 reservation 对象,用于管理缓冲区的 fences。

定义:

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

成员

lock

更新侧锁。不要直接使用,而是使用包装器函数,例如 dma_resv_lock()dma_resv_unlock()

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

fences

已添加到 dma_resv 对象的 fences 数组。

通过调用 dma_resv_add_fence() 添加新的 fence。由于这通常需要在命令提交中无法返回的点之后完成,因此它不能失败,因此需要通过调用 dma_resv_reserve_fences() 保留足够的 slots。

描述

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

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

另一个主要用途是在基于缓冲区的内存管理器中管理驱动程序内的访问和锁定。struct ttm_buffer_object 是这里的规范示例,因为这是 reservation 对象最初的来源。但在驱动程序中的使用正在扩展,并且一些驱动程序还使用相同的方案管理 struct drm_gem_object

struct dma_resv_iter

dma_resv fences 中的当前位置。

定义:

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

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

fence

当前处理的 fence。

fence_usage

当前 fence 的用法。

index

共享 fences 的索引。

fences

共享 fences;私有,*必须* 不取消引用。

num_fences

fences 的数量。

is_restarted

如果这是第一个返回的 fence,则为 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

控制包含哪些 fences,请参阅 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 的 usage。

参数

struct dma_resv_iter *cursor

当前位置的光标。

描述

返回当前处理的 fence 的 usage。

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 迭代 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。

描述

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

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

锁定 reservation 对象。

参数

struct dma_resv *obj

预留对象

struct ww_acquire_ctx *ctx

锁定上下文。

描述

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

由于 reservation 对象可能被多个参与者以未定义的顺序锁定,因此传递一个 #ww_acquire_ctx,以便在检测到循环时进行回退。 请参阅 ww_mutex_lock() 和 ww_acquire_init()。 reservation 对象可以通过传递 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)

锁定 reservation 对象。

参数

struct dma_resv *obj

预留对象

struct ww_acquire_ctx *ctx

锁定上下文。

描述

以可中断的方式锁定 reservation 对象以进行独占访问和修改。 请注意,该锁仅针对其他写入者,读取者将与 RCU 下的写入者并发运行。 seqlock 用于在读者与作者重叠时通知读者。

由于 reservation 对象可能被多个参与者以未定义的顺序锁定,因此传递一个 #ww_acquire_ctx,以便在检测到循环时进行回退。 请参阅 ww_mutex_lock() 和 ww_acquire_init()。 reservation 对象可以通过传递 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)

slowpath 锁定 reservation 对象。

参数

struct dma_resv *obj

预留对象

struct ww_acquire_ctx *ctx

锁定上下文。

描述

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

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

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

slowpath 锁定 reservation 对象,可中断。

参数

struct dma_resv *obj

预留对象

struct ww_acquire_ctx *ctx

锁定上下文。

描述

在死锁情况后以可中断的方式获取 reservation 对象。 此函数将休眠直到锁变为可用。 另请参阅 dma_resv_lock_interruptible()

bool dma_resv_trylock(struct dma_resv *obj)

尝试锁定 reservation 对象。

参数

struct dma_resv *obj

预留对象

描述

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

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

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

bool dma_resv_is_locked(struct dma_resv *obj)

reservation 对象是否已锁定。

参数

struct dma_resv *obj

预留对象

描述

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

struct ww_acquire_ctx *dma_resv_locking_ctx(struct dma_resv *obj)

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

参数

struct dma_resv *obj

预留对象

描述

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

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

void dma_resv_unlock(struct dma_resv *obj)

解锁 reservation 对象。

参数

struct dma_resv *obj

预留对象

描述

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

DMA Fences

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

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

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

  • 单个 fences 可以作为 sync_file 公开,作为用户空间的 file descriptor 访问,通过调用 sync_file_create() 创建。 这被称为显式 fencing,因为用户空间传递显式同步点。

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

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

DMA Fence 跨驱动程序约定

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

  • Fences 必须在合理的时间内完成。 代表用户空间提交的 kernels 和 shaders 的 fences,可能会永远运行,必须由超时和 gpu hang recovery 代码支持。 至少该代码必须阻止进一步的命令提交并强制完成所有正在运行的 fences,例如,当驱动程序或硬件不支持 gpu 重置,或者如果由于某种原因 gpu 重置失败时。 理想情况下,驱动程序支持 gpu recovery,它只影响有问题的用户空间上下文,而不影响其他用户空间提交。

  • 驱动程序可能对在合理时间内完成意味着什么有不同的想法。 一些 hang recovery 代码使用固定的超时,另一些则混合使用观察前进进度和越来越严格的超时。 驱动程序不应试图猜测来自其他驱动程序的 fences 的超时处理。

  • 为了确保 dma_fence_wait() 不会死锁与其他锁,驱动程序应使用 dma_fence_begin_signalling()dma_fence_end_signalling() 注释所有需要到达 dma_fence_signal() 的代码,该代码完成 fences。

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

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

  • 驱动程序允许从他们的 mmu_notifier 分别是 mmu_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 Signaling 注释

对于一些原因,通过代码审查和测试来证明围绕 dma_fence 的所有内核代码的正确性是很棘手的。

  • 这是一个跨驱动程序约定,因此所有驱动程序必须遵循相同的锁定嵌套顺序规则,各种函数的调用上下文以及对内核内接口有意义的任何其他内容。 但也不可能在一台机器中测试所有驱动程序,因此不可能对所有组合进行暴力 N vs. N 测试。 即使只是限制可能的组合也是不可行的。

  • 涉及大量的驱动程序代码。 对于渲染驱动程序,在发布 fences 之后,命令提交的尾部、调度程序代码、中断和处理 job 完成的 workers 以及超时、gpu 重置和 gpu hang recovery 代码。 此外,为了与核心 mm 集成,我们有 mmu_notifier,分别是 mmu_interval_notifiershrinker。 对于 modesetting 驱动程序,在发布原子 modeset 的 fences 和相应的 vblank 完成之间,有 commit 尾部函数,包括任何中断处理和相关的 workers。 审计所有驱动程序上的所有这些代码是不可行的。

  • 由于涉及许多其他子系统以及由此引入的锁定层次结构,因此驱动程序特定的差异几乎没有回旋余地。 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 的完成。通常的例子是等待 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 Fences 函数参考

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)

发出 fence 完成信号

参数

struct dma_fence *fence

要发出信号的 fence

ktime_t timestamp

内核 CLOCK_MONOTONIC 时域中的 fence 信号时间戳

描述

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

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

成功时返回 0,如果已发出 fence 信号,则返回负错误值。

int dma_fence_signal_timestamp(struct dma_fence *fence, ktime_t timestamp)

发出 fence 完成信号

参数

struct dma_fence *fence

要发出信号的 fence

ktime_t timestamp

内核 CLOCK_MONOTONIC 时域中的 fence 信号时间戳

描述

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

成功时返回 0,如果已发出 fence 信号,则返回负错误值。

int dma_fence_signal_locked(struct dma_fence *fence)

发出 fence 完成信号

参数

struct dma_fence *fence

要发出信号的 fence

描述

发出 fence 上软件回调的完成信号,这将解除 dma_fence_wait() 调用的阻止,并运行使用 dma_fence_add_callback() 添加的所有回调。可以多次调用,但由于 fence 只能从未发出信号的状态变为已发出信号的状态,而不能返回,因此它只会在第一次生效。

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

成功时返回 0,如果已发出 fence 信号,则返回负错误值。

int dma_fence_signal(struct dma_fence *fence)

发出 fence 完成信号

参数

struct dma_fence *fence

要发出信号的 fence

描述

发出 fence 上软件回调的完成信号,这将解除 dma_fence_wait() 调用的阻止,并运行使用 dma_fence_add_callback() 添加的所有回调。可以多次调用,但由于 fence 只能从未发出信号的状态变为已发出信号的状态,而不能返回,因此它只会在第一次生效。

成功时返回 0,如果已发出 fence 信号,则返回负错误值。

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

睡眠,直到 fence 发出信号或直到超时时间过去

参数

struct dma_fence *fence

要等待的 fence

bool intr

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

signed long timeout

jiffies 中的超时值,或 MAX_SCHEDULE_TIMEOUT

描述

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

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

另请参阅 dma_fence_wait()dma_fence_wait_any_timeout()

void dma_fence_release(struct kref *kref)

fence 的默认释放函数

参数

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

要释放的 fence

描述

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

void dma_fence_enable_sw_signaling(struct dma_fence *fence)

启用 fence 上的信令

参数

struct dma_fence *fence

要启用的 fence

描述

这将请求启用 sw 信令,以便尽快完成 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)

添加要在发出 fence 信号时调用的回调

参数

struct dma_fence *fence

要等待的 fence

struct dma_fence_cb *cb

要注册的回调函数

dma_fence_func_t func

要调用的函数

描述

向 fence 添加一个软件回调。调用者应保持对 fence 的引用。

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

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

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

成功时返回 0,如果 fence 已经发出信号则返回 -ENOENT,如果出错则返回 -EINVAL。

int dma_fence_get_status(struct dma_fence *fence)

返回完成时的状态

参数

struct dma_fence *fence

要查询的 dma_fence

描述

这封装了 dma_fence_get_status_locked() 以在已发出信号的 fence 上返回错误状态条件。有关更多详细信息,请参见 dma_fence_get_status_locked()

如果 fence 尚未发出信号,则返回 0;如果 fence 已发出信号且没有错误条件,则返回 1;如果 fence 已在 err 中完成,则返回负错误代码。

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

从信令列表中删除回调

参数

struct dma_fence *fence

要等待的 fence

struct dma_fence_cb *cb

要删除的回调

描述

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

警告:只有在你真正知道自己在做什么的情况下,才应该取消回调,因为很容易发生死锁和竞争条件。因此,它应该只在硬件锁定恢复时进行,并且持有对 fence 的引用。

如果 cb 之前未使用 dma_fence_add_callback() 添加到 fence,则行为未定义。

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

默认睡眠,直到 fence 发出信号或直到超时时间过去

参数

struct dma_fence *fence

要等待的 fence

bool intr

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

signed long timeout

jiffies 中的超时值,或 MAX_SCHEDULE_TIMEOUT

描述

如果被中断,则返回 -ERESTARTSYS;如果等待超时,则返回 0;如果成功,则返回 jiffies 中剩余的超时时间。如果超时时间为零,则如果 fence 已经发出信号,则返回值 1,以便与其他采用 jiffies 超时的函数保持一致。

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-wait 截止日期提示

参数

struct dma_fence *fence

要等待的 fence

ktime_t deadline

等待者希望 fence 发出信号的时间

描述

向 fence 发送者提供有关即将到来的截止日期(例如 vblank)的提示,到那时,等待者希望 fence 发出信号。 这旨在向 fence 发送者提供反馈,以帮助进行电源管理决策,例如,如果定期 vblank 截止日期即将到来但 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;
};

成员

lock

用于锁定的 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 (*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 的软件信令。

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

这在禁用 irq 的情况下调用,因此只有禁用 IRQ 的自旋锁才能在此回调之外的代码中使用。

返回值 false 表示 fence 已经通过,或者发生了某些故障,导致无法启用信令。 True 表示成功启用。

可以在 enable_signaling 中设置 dma_fence.error,但仅在返回 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()

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

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

release

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

set_deadline

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

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

此回调是可选的。

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

描述

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

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

获取对 RCU 跟踪的 fence 的引用

参数

struct dma_fence __rcu **fencep

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

描述

如果无法获得引用计数,则函数返回 NULL;否则返回 fence。此函数处理获取对可能在 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。由于 seqno 在上下文之间不通用,因此两个 fence 必须来自同一上下文。

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。由于 seqno 不会在上下文之间重复使用,因此两个 fence 必须来自同一上下文。

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 相同,则返回 true。由于 seqno 不会在上下文之间重复使用,因此两个 fence 必须来自同一上下文。

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。由于 seqno 不会在上下文之间重复使用,因此两个 fence 必须来自同一上下文。

int dma_fence_get_status_locked(struct dma_fence *fence)

返回完成时的状态

参数

struct dma_fence *fence

要查询的 dma_fence

描述

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

如果 fence 尚未发出信号,则返回 0;如果 fence 已发出信号且没有错误条件,则返回 1;如果 fence 已在 err 中完成,则返回负错误代码。

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

要等待的 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

[in] 要添加到数组中的 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

[in] 要武装的 dma fence 数组

int num_fences

[in] 要添加到数组中的 fence 的数量

struct dma_fence **fences

[in] 包含 fence 的数组

u64 context

[in] 要使用的 fence 上下文

unsigned seqno

[in] 要使用的序列号

bool signal_on_any

[in] 如果数组中的任何 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

[in] 要添加到数组中的 fence 的数量

struct dma_fence **fences

[in] 包含 fence 的数组

u64 context

[in] 要使用的 fence 上下文

unsigned seqno

[in] 要使用的序列号

bool signal_on_any

[in] 如果数组中的任何 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

[in] fence 或 fence 数组

u64 context

[in] 用于检查所有 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 回调结构

数组

指向父 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[] ;
};

成员

基类

fence 基类

lock

用于 fence 处理的自旋锁

num_fences

数组中 fence 的数量

num_pending

数组中仍在挂起的 fence 数量

fences

fence 的数组

工作项

内部 irq_work 函数

回调

回调助手数组

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;
};

成员

基类

fence 基类

prev

链的先前 fence

prev_seqno

垃圾回收前的原始先前 seqno

fence

封装的 fence

{unnamed_union}

匿名

cb

用于发出信号的回调

这用于添加用于发出 fence 链完成信号的回调。 永远不要与 irq work 同时使用。

工作项

用于发出信号的 irq work 项目

允许我们添加回调而不遇到锁反转的 Irq work 结构。 永远不要与回调同时使用。

lock

用于 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 对象。 这不需要 RCU 宽限期,因为 fence 从未初始化或发布。 在调用 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;
};

成员

chain

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

数组

潜在的 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

同步文件的标志

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:active 1:signaled <0:error

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: signaled 0:active <0:error

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 - 在 fence 上设置截止时间提示

定义:

struct sync_set_deadline {
    __u64 deadline_ns;
    __u64 pad;
};

成员

deadline_ns

截止时间的绝对时间

pad

必须为零

描述

允许用户空间在 fence 上设置截止时间,请参见 dma_fence_set_deadline

截止时间的时间基准为 CLOCK_MONOTONIC(与 vblank 相同)。 例如

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

无限 DMA Fence

在不同的时间,已经提出了 struct dma_fence,它具有无限的时间,直到 dma_fence_wait() 完成。 示例包括

  • Future fence,在 HWC1 中用于发出信号,表明缓冲区不再被显示器使用,并且使用使缓冲区可见的屏幕更新创建。 此 fence 完成的时间完全由用户空间控制。

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

  • 用户空间 fence 或 gpu futex,命令缓冲区内的细粒度锁定,用户空间使用它来进行跨引擎或与 CPU 的同步,然后将其作为 DMA fence 导入,以便集成到现有的 winsys 协议中。

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

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

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

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

此外,内核必须能够为内存管理需求阻止用户空间命令提交,这意味着我们必须支持依赖于 DMA fence 的无限 fence。 如果内核还支持内核中的无限 fence,就像上述任何提案一样,则存在死锁的可能性。

Indefinite Fencing Dependency Cycle

无限 Fencing 依赖循环

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

避免依赖循环的唯一方法是不允许内核中的无限 fence。这意味着

  • 没有未来的 fence、代理 fence 或导入为 DMA fence 的用户空间 fence,无论是否设置超时。

  • 没有 DMA fence 用于标记命令提交的批处理缓冲区结束,而用户空间允许使用用户空间 fencing 或长时间运行的计算工作负载。 这也意味着在这些情况下,不允许共享缓冲区的隐式 fencing。

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

现代硬件支持可恢复的页面错误,这对 DMA fence 产生了许多影响。

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

在 GPU 上,这带来了一个问题,因为 Linux 上当前的桌面合成器协议依赖于 DMA fence,这意味着如果没有构建在用户空间 fence 之上的全新用户空间堆栈,它们就无法从可恢复的页面错误中受益。具体来说,这意味着不可能进行隐式同步。唯一的例外是页面错误仅用作迁移提示,而从不用作按需填充内存请求。目前,这意味着 GPU 上的可恢复页面错误仅限于纯计算工作负载。

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

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

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

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

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

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

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

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

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

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

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