缓冲区共享与同步 (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_fence 对象的容器。 一个预留对象可以附加任意数量的 fence。 每个 fence 都带有一个使用参数,该参数确定 fence 所代表的操作如何使用该资源。 RCU 机制用于保护对 fence 的读取访问,防止锁定的写入端更新。
有关更多详细信息,请参见 struct dma_resv
。
参数
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
参数
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 *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。
参数
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_usage
和 dma_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 锁。
请注意,当修改 cursor 的 struct 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 进行额外的引用。
参数
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()
。
参数
struct dma_resv *obj
预留对象
描述
尝试锁定 reservation 对象以进行独占访问和修改。 请注意,该锁仅针对其他写入者,读取者将与 RCU 下的写入者并发运行。 seqlock 用于在读者与作者重叠时通知读者。
另请注意,由于未提供上下文,因此无法进行死锁保护,对于 trylock 而言也不需要死锁保护。
如果获取了锁,则返回 true,否则返回 false。
参数
struct dma_resv *obj
预留对象
描述
如果互斥锁已锁定,则返回 true,如果未锁定,则返回 false。
参数
struct dma_resv *obj
预留对象
描述
返回用于锁定 reservation 对象的上下文,如果未使用上下文或对象根本未锁定,则返回 NULL。
警告:此接口非常糟糕,但 TTM 需要它,因为它没有在一些非常长的调用链中传递 struct ww_acquire_ctx。 其他人都只是用它来检查他们是否持有 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_notifier
和 shrinker
回调,同时还必须使用 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_notifier
和shrinker
。 对于 modesetting 驱动程序,在发布原子 modeset 的 fences 和相应的 vblank 完成之间,有 commit 尾部函数,包括任何中断处理和相关的 workers。 审计所有驱动程序上的所有这些代码是不可行的。由于涉及许多其他子系统以及由此引入的锁定层次结构,因此驱动程序特定的差异几乎没有回旋余地。
dma_fence
通过dma_resv
、dma_resv_lock()
和dma_resv_unlock()
与几乎所有核心内存处理(通过页面错误处理程序)进行交互。另一方面,它还通过mmu_notifier
和shrinker
与所有分配点进行交互。
此外,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 函数参考¶
参数
void
没有参数
描述
返回已发出信号的存根 fence。该 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()
打开的关键部分注释。
参数
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 信号,则返回负错误值。
参数
struct dma_fence *fence
要发出信号的 fence
ktime_t timestamp
内核 CLOCK_MONOTONIC 时域中的 fence 信号时间戳
描述
发出 fence 上软件回调的完成信号,这将解除 dma_fence_wait()
调用的阻止,并运行使用 dma_fence_add_callback()
添加的所有回调。可以多次调用,但由于 fence 只能从未发出信号的状态变为已发出信号的状态,而不能返回,因此它只会在第一次生效。将提供的时间戳设置为 fence 信号时间戳。
成功时返回 0,如果已发出 fence 信号,则返回负错误值。
参数
struct dma_fence *fence
要发出信号的 fence
描述
发出 fence 上软件回调的完成信号,这将解除 dma_fence_wait()
调用的阻止,并运行使用 dma_fence_add_callback()
添加的所有回调。可以多次调用,但由于 fence 只能从未发出信号的状态变为已发出信号的状态,而不能返回,因此它只会在第一次生效。
与 dma_fence_signal()
不同,必须在持有 dma_fence.lock
的情况下调用此函数。
成功时返回 0,如果已发出 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 可能会在返回之前被释放,从而导致未定义的行为。
参数
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。
参数
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 可能会在返回之前被释放,从而导致未定义的行为。
参数
struct dma_fence *fence
要等待的 fence
ktime_t deadline
等待者希望 fence 发出信号的时间
描述
向 fence 发送者提供有关即将到来的截止日期(例如 vblank)的提示,到那时,等待者希望 fence 发出信号。 这旨在向 fence 发送者提供反馈,以帮助进行电源管理决策,例如,如果定期 vblank 截止日期即将到来但 fence 尚未发出信号,则提高 GPU 频率。
参数
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¶
定义:
struct dma_fence_cb {
struct list_head node;
dma_fence_func_t func;
};
成员
node
由
dma_fence_add_callback()
使用以将此结构附加到 fence::cb_listfunc
要调用的 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 决定。 如果设置了多个截止日期,则期望跟踪最早的截止日期。 如果截止日期早于当前时间,则应将其解释为立即截止日期。此回调是可选的。
参数
struct dma_fence *fence
要减少引用计数的 fence
参数
struct dma_fence *fence
要增加引用计数的 fence
描述
返回相同的 fence,引用计数增加 1。
参数
struct dma_fence *fence
要增加引用计数的 fence
描述
如果无法获得引用计数,则函数返回 NULL 或 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 读取锁。
参数
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()
。
参数
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,这样可以通过在调用特定于硬件的等待指令之前检查此函数的返回值来防止发布时间和使用时间之间的环绕问题。
-
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 必须来自同一上下文。
参数
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 *f1
来自同一上下文的第一个 fence
struct dma_fence *f2
来自同一上下文的第二个 fence
描述
如果两个 fence 都已发出信号,则返回 NULL;否则返回将最后发出信号的 fence。由于 seqno 不会在上下文之间重复使用,因此两个 fence 必须来自同一上下文。
参数
struct dma_fence *fence
要查询的 dma_fence
描述
驱动程序可以在发出 fence 信号之前提供一个可选的错误状态条件(以指示 fence 的完成是由于错误而不是成功)。只有在 fence 已发出信号时,状态条件的值才有效,dma_fence_get_status_locked()
首先检查信号状态,然后报告错误状态。
如果 fence 尚未发出信号,则返回 0;如果 fence 已发出信号且没有错误条件,则返回 1;如果 fence 已在 err 中完成,则返回负错误代码。
参数
struct dma_fence *fence
dma_fence
int error
要存储的错误
描述
驱动程序可以在发出 fence 信号之前提供一个可选的错误状态条件,以指示 fence 的完成是由于错误而不是成功。这必须在发出信号之前设置(以便在唤醒信号回调上的任何等待者之前,该值是可见的)。此帮助程序的存在是为了帮助捕获 #dma_fence.error 的错误设置。
驱动程序应使用的错误代码示例
-ENODATA
此操作未生成任何数据,没有其他操作受到影响。-ECANCELED
来自同一上下文的所有操作都已取消。-ETIME
操作导致超时,并可能导致设备重置。
参数
struct dma_fence *fence
从中获取时间戳的 fence。
描述
在发出 fence 信号后,时间戳会使用信号时间进行更新,但设置时间戳可能会与等待信号的任务发生竞争。此帮助程序会忙等待,直到出现正确的时间戳。
参数
struct dma_fence *fence
要等待的 fence
bool intr
如果为 true,则进行可中断的等待
描述
如果被信号中断,此函数将返回 -ERESTARTSYS;如果 fence 已发出信号,则返回 0。自定义实现可能会返回其他错误值。
对此 fence 执行同步等待。假定调用者直接或间接持有对 fence 的引用,否则 fence 可能会在返回之前被释放,从而导致未定义的行为。
另请参见 dma_fence_wait_timeout()
和 dma_fence_wait_any_timeout()
。
参数
struct dma_fence *fence
要测试的 fence
描述
如果是 dma_fence_array,则返回 true,否则返回 false。
参数
struct dma_fence *fence
要测试的 fence
描述
如果是 dma_fence_chain,则返回 true,否则返回 false。
参数
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 发出信号时,它会发出信号。
参数
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 *fence
当前链节点
描述
遍历链到下一个节点。如果到达链的末尾,则返回下一个 fence 或 NULL。垃圾回收已发出信号的链节点。
参数
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 *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 dma_fence *fence
要添加到 sync_fence 的 fence
描述
创建一个包含 fence 的 sync_file。 如果成功,此函数会为新创建的 sync_file
获取 fence 的附加引用。 可以使用 fput(sync_file->file) 释放 sync_file。 返回 sync_file,如果出错则返回 NULL。
参数
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,就像上述任何提案一样,则存在死锁的可能性。
无限 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 上支持页面错误并没有带来任何根本性的新东西。