缓冲区共享和同步 (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 都有一个 usage 参数,用于确定 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)¶
保留空间,以将 fence 添加到 dma_resv 对象。
参数
struct dma_resv *obj
保留对象
unsigned int num_fences
我们要添加的 fence 的数量
描述
应在 dma_resv_add_fence()
之前调用。必须通过 dma_resv_lock()
锁定 obj 的情况下调用。
请注意,如果在调用 dma_resv_add_fence()
之前的任何时候解锁 obj,则需要重新保留预分配的槽。启用 CONFIG_DEBUG_MUTEXES 时会对此进行验证。
返回:成功返回零,否则返回 -errno
参数
struct dma_resv *obj
要重置的 dma_resv 对象
描述
重置预保留 fence 槽的数量,以测试驱动程序是否使用 dma_resv_reserve_fences()
正确分配槽。另请参阅 dma_resv_list.max_fences
。
-
void dma_resv_add_fence(struct dma_resv *obj, struct dma_fence *fence, enum dma_resv_usage usage)¶
将 fence 添加到 dma_resv 对象
参数
struct dma_resv *obj
保留对象
struct dma_fence *fence
要添加的 fence
enum dma_resv_usage usage
fence 的使用方式,请参阅
enum dma_resv_usage
描述
将 fence 添加到槽,必须使用 dma_resv_lock()
锁定 obj,并且已调用 dma_resv_reserve_fences()
。
有关语义的讨论,另请参阅 dma_resv.fence
。
-
void dma_resv_replace_fences(struct dma_resv *obj, uint64_t context, struct dma_fence *replacement, enum dma_resv_usage usage)¶
替换 dma_resv 对象中的 fence
参数
struct dma_resv *obj
保留对象
uint64_t context
要替换的 fence 的上下文
struct dma_fence *replacement
要使用的新 fence
enum dma_resv_usage usage
新 fence 的使用方式,请参阅
enum dma_resv_usage
描述
将指定上下文中的栅栏替换为新的栅栏。只有当新栅栏完成时,原始栅栏所代表的操作不再访问 dma_resv 对象所代表的资源时,此操作才有效。
使用此方法的一个例子是用页表更新栅栏替换抢占栅栏,这使得资源不可访问。
-
struct dma_fence *dma_resv_iter_first_unlocked(struct dma_resv_iter *cursor)¶
未锁定的 dma_resv 对象中的第一个栅栏。
参数
struct dma_resv_iter *cursor
带有当前位置的游标
描述
后续的栅栏使用 dma_resv_iter_next_unlocked()
进行迭代。
请注意,迭代器可以重新启动。积累统计信息或类似操作的代码需要使用 dma_resv_iter_is_restarted()
进行检查。因此,尽可能首选锁定的 dma_resv_iter_first()
。
返回未锁定的 dma_resv 对象中的第一个栅栏。
-
struct dma_fence *dma_resv_iter_next_unlocked(struct dma_resv_iter *cursor)¶
未锁定的 dma_resv 对象中的下一个栅栏。
参数
struct dma_resv_iter *cursor
带有当前位置的游标
描述
请注意,迭代器可以重新启动。积累统计信息或类似操作的代码需要使用 dma_resv_iter_is_restarted()
进行检查。因此,尽可能首选锁定的 dma_resv_iter_next()
。
返回未锁定的 dma_resv 对象中的下一个栅栏。
-
struct dma_fence *dma_resv_iter_first(struct dma_resv_iter *cursor)¶
来自锁定的 dma_resv 对象的第一个栅栏
参数
struct dma_resv_iter *cursor
记录当前位置的游标
描述
后续的栅栏使用 dma_resv_iter_next_unlocked()
进行迭代。
返回持有 dma_resv.lock
的 dma_resv 对象中的第一个栅栏。
-
struct dma_fence *dma_resv_iter_next(struct dma_resv_iter *cursor)¶
来自锁定的 dma_resv 对象的下一个栅栏
参数
struct dma_resv *dst
目标预留对象
struct dma_resv *src
源预留对象
描述
将所有栅栏从 src 复制到 dst。必须持有 dst 锁。
-
int dma_resv_get_fences(struct dma_resv *obj, enum dma_resv_usage usage, unsigned int *num_fences, struct dma_fence ***fences)¶
获取对象的栅栏,无需持有更新侧锁
参数
struct dma_resv *obj
保留对象
enum dma_resv_usage usage
控制要包括哪些栅栏,请参见
enum dma_resv_usage
。unsigned int *num_fences
返回的栅栏数量
struct dma_fence ***fences
返回的栅栏指针数组(数组会根据需要进行 krealloc,并且必须由调用者释放)
描述
从预留对象检索所有栅栏。返回零或 -ENOMEM。
-
int dma_resv_get_singleton(struct dma_resv *obj, enum dma_resv_usage usage, struct dma_fence **fence)¶
为所有栅栏获取单个栅栏
参数
struct dma_resv *obj
保留对象
enum dma_resv_usage usage
控制要包括哪些栅栏,请参见
enum dma_resv_usage
。struct dma_fence **fence
结果栅栏
描述
获取代表 resv 对象内所有栅栏的单个栅栏。成功返回 0,失败返回 -ENOMEM。
警告:当将栅栏添加回 resv 对象时,不能这样使用,因为这可能会在完成 dma_fence_array 时导致堆栈损坏。
成功返回 0,失败返回负错误值。
-
long dma_resv_wait_timeout(struct dma_resv *obj, enum dma_resv_usage usage, bool intr, unsigned long timeout)¶
等待预留对象的栅栏
参数
struct dma_resv *obj
保留对象
enum dma_resv_usage usage
控制要包括哪些栅栏,请参见
enum dma_resv_usage
。bool intr
如果为 true,则执行可中断等待
unsigned long timeout
超时值(以节拍为单位),或立即返回时为零
描述
调用者不需要持有特定的锁,但可能已经持有 dma_resv_lock()
。如果中断,则返回 -ERESTARTSYS,如果等待超时,则返回 0,成功则返回大于零的值。
-
void dma_resv_set_deadline(struct dma_resv *obj, enum dma_resv_usage usage, ktime_t deadline)¶
设置预留对象栅栏的截止时间
参数
struct dma_resv *obj
保留对象
enum dma_resv_usage usage
控制要包括哪些栅栏,请参见
enum dma_resv_usage
。ktime_t deadline
请求的截止时间 (MONOTONIC)
描述
可以在不持有 dma_resv 锁的情况下调用。根据 usage 筛选,在所有栅栏上设置 deadline。
-
bool dma_resv_test_signaled(struct dma_resv *obj, enum dma_resv_usage usage)¶
测试预留对象的栅栏是否已发出信号。
参数
struct dma_resv *obj
保留对象
enum dma_resv_usage usage
控制要包括哪些栅栏,请参见
enum dma_resv_usage
。
描述
调用者不需要持有特定的锁,但可以持有 dma_resv_lock()
。
返回值
如果所有栅栏都已发出信号,则返回 True,否则返回 False。
参数
struct dma_resv *obj
保留对象
struct seq_file *seq
要将描述转储到的 seq_file
描述
将 dma_resv 对象内的栅栏的文本描述转储到 seq_file 中。
-
enum dma_resv_usage¶
dma_resv 对象中的栅栏如何使用
常量
DMA_RESV_USAGE_KERNEL
仅用于内核内存管理。
这应该仅用于通过 DMA 硬件引擎复制或清除内存以进行内核内存管理。
驱动程序总是必须在访问 dma_resv 对象保护的资源之前等待这些栅栏。唯一的例外是当已知资源通过预先固定来锁定到位时。
DMA_RESV_USAGE_WRITE
隐式写入同步。
这应该仅用于添加隐式写入依赖的用户空间命令提交。
DMA_RESV_USAGE_READ
隐式读取同步。
这应该仅用于添加隐式读取依赖的用户空间命令提交。
DMA_RESV_USAGE_BOOKKEEP
无隐式同步。
这应该用于不想参与任何隐式同步的提交。
最常见的情况是抢占栅栏、页表更新、TLB 刷新以及显式同步的用户提交。
显式同步的用户提交可以使用
dma_buf_import_sync_file()
根据需要提升为 DMA_RESV_USAGE_READ 或 DMA_RESV_USAGE_WRITE,当在最初添加栅栏后有必要进行隐式同步时。
描述
此枚举描述了 dma_resv 对象不同的使用案例,并控制查询时返回哪些栅栏。
一个重要的事实是,存在 KERNEL<WRITE<READ<BOOKKEEP 的顺序,并且当 dma_resv 对象被要求为一个使用案例提供栅栏时,也会返回较低使用案例的栅栏。
例如,当请求 WRITE 栅栏时,也会返回 KERNEL 栅栏。类似地,当请求 READ 栅栏时,也会返回 WRITE 和 KERNEL 栅栏。
已经使用的栅栏可以被提升,比如通过再次使用该用法添加,使具有 DMA_RESV_USAGE_BOOKKEEP 的栅栏变成 DMA_RESV_USAGE_READ。但是栅栏永远不会降级,比如使具有 DMA_RESV_USAGE_WRITE 的栅栏变成 DMA_RESV_USAGE_READ。
-
enum dma_resv_usage dma_resv_usage_rw(bool write)¶
用于隐式同步的辅助函数
-
struct dma_resv¶
预留对象管理缓冲区的栅栏
定义:
struct dma_resv {
struct ww_mutex lock;
struct dma_resv_list __rcu *fences;
};
成员
锁
更新侧锁。不要直接使用,而应该使用诸如
dma_resv_lock()
和dma_resv_unlock()
之类的包装器函数。使用预留对象来动态管理内存的驱动程序也会使用此锁来保护缓冲区对象的状态,如放置、分配策略或整个命令提交。
栅栏
添加到 dma_resv 对象的栅栏数组
通过调用
dma_resv_add_fence()
添加新栅栏。由于这通常需要在命令提交中到达不归路之后才能完成,因此它不能失败,因此需要通过调用dma_resv_reserve_fences()
预留足够的槽。
描述
这是 dma_fence 对象的容器,需要处理多个用例。
一种用法是同步跨驱动程序对 struct dma_buf
的访问,用于动态缓冲区管理或仅仅处理用户空间中缓冲区的不同用户之间的隐式同步。有关更深入的讨论,请参阅 dma_buf.resv
。
另一个主要用途是在基于缓冲区的内存管理器中管理驱动程序内的访问和锁定。struct ttm_buffer_object 是这里的典型示例,因为这是预留对象的起源地。但是驱动程序中的使用正在扩展,一些驱动程序也使用相同的方案管理 struct drm_gem_object
。
-
struct dma_resv_iter¶
dma_resv 栅栏中的当前位置
定义:
struct dma_resv_iter {
struct dma_resv *obj;
enum dma_resv_usage usage;
struct dma_fence *fence;
enum dma_resv_usage fence_usage;
unsigned int index;
struct dma_resv_list *fences;
unsigned int num_fences;
bool is_restarted;
};
成员
obj
我们正在迭代的 dma_resv 对象
usage
返回具有此用法或更低用法的栅栏。
fence
当前处理的栅栏
fence_usage
当前栅栏的用法
index
共享栅栏中的索引
栅栏
共享栅栏;私有,必须不能解引用
num_fences
栅栏数量
is_restarted
如果这是返回的第一个栅栏,则为 true
描述
请勿在驱动程序中直接操作此值,请使用访问器函数代替。
重要
当使用诸如 dma_resv_iter_next_unlocked()
或 dma_resv_for_each_fence_unlocked()
之类的无锁迭代器时,请注意迭代器可能会重新启动。积累统计信息或类似情况的代码需要使用 dma_resv_iter_is_restarted()
检查这一点。
-
void dma_resv_iter_begin(struct dma_resv_iter *cursor, struct dma_resv *obj, enum dma_resv_usage usage)¶
初始化 dma_resv_iter 对象
参数
struct dma_resv_iter *cursor
要初始化的 dma_resv_iter 对象
struct dma_resv *obj
我们想要迭代的 dma_resv 对象
enum dma_resv_usage usage
控制要包括哪些栅栏,请参见
enum dma_resv_usage
。
-
void dma_resv_iter_end(struct dma_resv_iter *cursor)¶
清理 dma_resv_iter 对象
参数
struct dma_resv_iter *cursor
应该清理的 dma_resv_iter 对象
描述
确保正确释放光标中对 fence 的引用。
-
enum dma_resv_usage dma_resv_iter_usage(struct dma_resv_iter *cursor)¶
返回当前 fence 的使用情况
参数
struct dma_resv_iter *cursor
当前位置的光标
描述
返回当前处理的 fence 的使用情况。
-
bool dma_resv_iter_is_restarted(struct dma_resv_iter *cursor)¶
测试这是否是重启后的第一个 fence
参数
struct dma_resv_iter *cursor
带有当前位置的游标
描述
如果这是重启后迭代中的第一个 fence,则返回 true。
-
dma_resv_for_each_fence_unlocked¶
dma_resv_for_each_fence_unlocked (cursor, fence)
解锁的 fence 迭代器
参数
光标
一个
struct dma_resv_iter
指针fence
当前的 fence
描述
在不持有 struct dma_resv
对象的 dma_resv.lock
并使用 RCU 的情况下,迭代 struct dma_resv
对象中的 fences。光标需要使用 dma_resv_iter_begin()
初始化,并使用 dma_resv_iter_end()
清理。在迭代器内部,会持有对 dma_fence 的引用,并释放 RCU 锁。
注意,当修改 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
描述
在持有 dma_resv.lock
的情况下,迭代 struct dma_resv
对象中的 fences。 all_fences 控制是否也返回共享的 fences。光标初始化是迭代器的一部分,并且只要持有锁,fence 就保持有效,因此不会对 fence 进行额外的引用。
参数
struct dma_resv *obj
保留对象
struct ww_acquire_ctx *ctx
锁定上下文
描述
锁定保留对象以进行独占访问和修改。请注意,该锁仅针对其他写入者,读取者将在 RCU 下与写入者并发运行。 seqlock 用于通知读取器它们是否与写入器重叠。
由于保留对象可能由多个参与者以未定义的顺序锁定,因此会传递一个 #ww_acquire_ctx 以便在检测到循环时回滚。 请参阅 ww_mutex_lock() 和 ww_acquire_init()。通过传递 NULL 作为 ctx,保留对象可以由其自身锁定。
当返回 -EDEADLK 指示死锁情况时,必须解锁 ctx 持有的所有锁,然后在 obj 上调用 dma_resv_lock_slow()
。
通过调用 dma_resv_unlock()
解锁。
另请参阅可中断变体 dma_resv_lock_interruptible()
。
参数
struct dma_resv *obj
保留对象
struct ww_acquire_ctx *ctx
锁定上下文
描述
可中断地锁定保留对象以进行独占访问和修改。请注意,该锁仅针对其他写入者,读取者将在 RCU 下与写入者并发运行。 seqlock 用于通知读取器它们是否与写入器重叠。
由于保留对象可能由多个参与者以未定义的顺序锁定,因此会传递一个 #ww_acquire_ctx 以便在检测到循环时回滚。 请参阅 ww_mutex_lock() 和 ww_acquire_init()。通过传递 NULL 作为 ctx,保留对象可以由其自身锁定。
当返回 -EDEADLK 指示死锁情况时,必须解锁 ctx 持有的所有锁,然后在 obj 上调用 dma_resv_lock_slow_interruptible()
。
通过调用 dma_resv_unlock()
解锁。
参数
struct dma_resv *obj
保留对象
struct ww_acquire_ctx *ctx
锁定上下文
描述
在死锁情况后获取保留对象。 此函数将休眠,直到该锁变为可用。另请参阅 dma_resv_lock()
。
另请参阅可中断变体 dma_resv_lock_slow_interruptible()
。
-
int dma_resv_lock_slow_interruptible(struct dma_resv *obj, struct ww_acquire_ctx *ctx)¶
慢速路径可中断地锁定保留对象
参数
struct dma_resv *obj
保留对象
struct ww_acquire_ctx *ctx
锁定上下文
描述
在发生死锁情况后可中断地获取保留对象。 此函数将休眠,直到该锁变为可用。另请参阅 dma_resv_lock_interruptible()
。
参数
struct dma_resv *obj
保留对象
描述
尝试锁定保留对象以进行独占访问和修改。 请注意,该锁仅针对其他写入者,读取者将在 RCU 下与写入者并发运行。 seqlock 用于通知读取器它们是否与写入器重叠。
另请注意,由于未提供上下文,因此无法进行死锁保护,这对于 trylock 也是不需要的。
如果获取到锁,则返回 true,否则返回 false。
参数
struct dma_resv *obj
保留对象
描述
如果互斥锁被锁定,则返回 true;如果未锁定,则返回 false。
参数
struct dma_resv *obj
保留对象
描述
返回用于锁定预留对象的上下文,如果未使用上下文或者对象根本未锁定,则返回 NULL。
警告:此接口非常糟糕,但 TTM 需要它,因为它在一些非常长的调用链中没有传递 struct ww_acquire_ctx。其他所有人都只是用它来检查他们是否持有预留。
参数
struct dma_resv *obj
保留对象
描述
在独占访问之后解锁预留对象。
DMA Fence¶
DMA fence,由 struct dma_fence
表示,是内核内部用于 DMA 操作的同步原语,例如 GPU 渲染、视频编码/解码或在屏幕上显示缓冲区。
fence 使用 dma_fence_init()
初始化,并使用 dma_fence_signal()
完成。fence 与通过 dma_fence_context_alloc()
分配的上下文相关联,并且同一上下文中的所有 fence 都是完全有序的。
由于 fence 的目的是促进跨设备和跨应用程序同步,因此有多种使用方式
单独的 fence 可以作为
sync_file
公开,作为用户空间的 文件描述符 访问,通过调用sync_file_create()
创建。这称为显式 fencing,因为用户空间传递显式同步点。一些子系统也有它们自己的显式 fencing 原语,如
drm_syncobj
。与sync_file
相比,drm_syncobj
允许更新底层 fence。然后还有隐式 fencing,其中同步点作为共享
dma_buf
实例的一部分隐式传递。此类隐式 fence 通过dma_buf.resv
指针存储在struct dma_resv
中。
DMA Fence 跨驱动程序约定¶
由于 dma_fence
提供跨驱动程序约定,因此所有驱动程序都必须遵循相同的规则
fence 必须在合理的时间内完成。表示用户空间提交的内核和着色器的 fence(可能会永远运行)必须由超时和 gpu 挂起恢复代码支持。最基本的要求是该代码必须阻止进一步的命令提交并强制完成所有正在进行的 fence,例如,当驱动程序或硬件不支持 gpu 重置时,或者如果 gpu 重置因某种原因失败时。理想情况下,驱动程序支持仅影响有问题的用户空间上下文的 gpu 恢复,而不影响其他用户空间的提交。
驱动程序对于在合理的时间内完成可能具有不同的理解。一些挂起恢复代码使用固定的超时,另一些则混合使用观察前进进度和越来越严格的超时。驱动程序不应尝试猜测其他驱动程序的 fence 超时处理。
为了确保
dma_fence_wait()
与其他锁之间没有死锁,驱动程序应使用dma_fence_begin_signalling()
和dma_fence_end_signalling()
来注释所有需要到达dma_fence_signal()
的代码,后者会完成 fence。允许驱动程序在持有
dma_resv_lock()
时调用dma_fence_wait()
。这意味着完成 fence 所需的任何代码都不能获取dma_resv
锁。请注意,这也引入了围绕dma_resv_lock()
和dma_resv_unlock()
建立的整个锁定层次结构。允许驱动程序从其
shrinker
回调中调用dma_fence_wait()
。这意味着完成 fence 所需的任何代码都不能使用 GFP_KERNEL 分配内存。允许驱动程序分别从其
mmu_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 发出信号注释¶
通过代码审查和测试来证明围绕 dma_fence
的所有内核代码的正确性非常棘手,原因如下:
这是一个跨驱动程序约定,因此所有驱动程序都必须遵循相同的规则,包括锁嵌套顺序、各种函数的调用上下文以及内核接口的任何其他重要事项。但是,也不可能在一台机器上测试所有驱动程序,因此对所有组合进行 N 对 N 的蛮力测试是不可能的。即使仅限于可能的组合也是不可行的。
涉及大量的驱动程序代码。对于渲染驱动程序,存在命令提交的尾部、fence 发布后的调度程序代码、中断和用于处理作业完成的工作程序,以及超时、gpu 重置和 gpu 挂起恢复代码。此外,为了与核心 mm 集成,我们有
mmu_notifier
、mmu_interval_notifier
和shrinker
。对于模式设置驱动程序,存在原子模式设置的 fence 发布后到相应的 vblank 完成之间的提交尾部函数,包括任何中断处理和相关的工作程序。审核所有驱动程序的这些代码是不可行的。由于涉及许多其他子系统以及由此引入的锁定层次结构,因此驱动程序特定的差异的回旋余地非常小。
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
完成所必需的。常见的例子是 wait IOCTL 调用dma_fence_signal()
,而强制完成路径通过硬件中断和可能的作业完成工作程序。为了帮助代码的可组合性,只要整体锁定层次结构一致,注释就可以自由嵌套。注释也适用于中断和进程上下文。由于实现细节,这要求调用者将来自
dma_fence_begin_signalling()
的不透明 cookie 传递给dma_fence_end_signalling()
。通过在启动时用相关的层次结构来初始化 lockdep 来实现针对跨驱动程序合同的验证。这意味着即使仅使用单个设备进行测试也足以验证驱动程序,至少就
dma_fence_wait()
与dma_fence_signal()
之间的死锁而言。
DMA Fence 截止时间提示¶
在理想情况下,应该可以充分流水线化工作负载,以便基于利用率的设备频率调速器可以达到满足用例要求的最低频率,以最大限度地降低功耗。但在现实世界中,有许多工作负载与这种理想背道而驰。例如,但不限于:
在设备和 CPU 之间来回切换的工作负载,CPU 等待设备和设备等待 CPU 的时间交替出现。这可能导致 devfreq 和 cpufreq 在其各自的域中看到空闲时间,从而降低频率。
与基于时间的周期性截止时间交互的工作负载,例如双缓冲 GPU 渲染与 vblank 同步页面翻转。在这种情况下,错过 vblank 截止时间会导致 GPU 的空闲时间增加(因为它必须等待额外的 vblank 周期),从而向 GPU 的 devfreq 发送信号以降低频率,而实际上需要的是相反的情况。
为此,可以通过 dma_fence_set_deadline
(或间接地通过面向用户的 ioctl,如 sync_set_deadline
)在 dma_fence
上设置截止时间提示。截止时间提示为等待驱动程序或用户空间提供了一种向信号发送驱动程序传达适当的紧急感的方式。
截止时间提示以绝对 ktime 给出(面向用户空间的 API 使用 CLOCK_MONOTONIC)。时间可以是未来的某个点(例如页面翻转基于 vblank 的截止时间,或合成器的合成周期的开始),也可以是当前时间,以指示立即的截止时间提示(即,在此 fence 发出信号之前无法取得进展)。
可以在给定的 fence 上设置多个截止时间,甚至是并行设置。请参阅 dma_fence_ops.set_deadline
的文档。
截止时间提示只是一个提示。创建 fence 的驱动程序可能会通过提高频率、做出不同的调度选择等来做出反应。或者什么都不做。
DMA Fence 函数参考¶
参数
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
要发出信号的栅栏
ktime_t timestamp
栅栏信号的时间戳,以内核的 CLOCK_MONOTONIC 时钟域为准
描述
为栅栏上的软件回调发出完成信号,这将解除 dma_fence_wait()
调用的阻塞,并运行所有通过 dma_fence_add_callback()
添加的回调。可以多次调用,但由于栅栏只能从无信号状态变为有信号状态,而不能返回,因此只有第一次调用有效。将提供的时间戳设置为栅栏信号时间戳。
与 dma_fence_signal_timestamp()
不同,此函数必须在持有 dma_fence.lock
的情况下调用。
成功时返回 0,当 fence 已经被发出信号时返回负错误值。
参数
struct dma_fence *fence
要发出信号的栅栏
ktime_t timestamp
栅栏信号的时间戳,以内核的 CLOCK_MONOTONIC 时钟域为准
描述
为栅栏上的软件回调发出完成信号,这将解除 dma_fence_wait()
调用的阻塞,并运行所有通过 dma_fence_add_callback()
添加的回调。可以多次调用,但由于栅栏只能从无信号状态变为有信号状态,而不能返回,因此只有第一次调用有效。将提供的时间戳设置为栅栏信号时间戳。
成功时返回 0,当 fence 已经被发出信号时返回负错误值。
参数
struct dma_fence *fence
要发出信号的栅栏
描述
为栅栏上的软件回调发出完成信号,这将解除 dma_fence_wait()
调用的阻塞,并运行所有通过 dma_fence_add_callback()
添加的回调。可以多次调用,但由于栅栏只能从无信号状态变为有信号状态,而不能返回,因此只有第一次调用有效。
与 dma_fence_signal()
不同,此函数必须在持有 dma_fence.lock
的情况下调用。
成功时返回 0,当 fence 已经被发出信号时返回负错误值。
参数
struct dma_fence *fence
要发出信号的栅栏
描述
为栅栏上的软件回调发出完成信号,这将解除 dma_fence_wait()
调用的阻塞,并运行所有通过 dma_fence_add_callback()
添加的回调。可以多次调用,但由于栅栏只能从无信号状态变为有信号状态,而不能返回,因此只有第一次调用有效。
成功时返回 0,当 fence 已经被发出信号时返回负错误值。
-
signed long dma_fence_wait_timeout(struct dma_fence *fence, bool intr, signed long timeout)¶
睡眠直到栅栏发出信号或直到超时时间耗尽
参数
struct dma_fence *fence
要等待的栅栏
bool intr
如果为 true,则进行可中断等待
signed long timeout
超时值,以 jiffies 为单位,或 MAX_SCHEDULE_TIMEOUT
描述
如果被中断则返回 -ERESTARTSYS,如果等待超时则返回 0,如果成功则返回剩余的超时时间(以 jiffies 为单位)。自定义实现可能会返回其他错误值。
对此栅栏执行同步等待。假设调用方直接或间接地(预订和提交之间的 buf-mgr)持有对栅栏的引用,否则栅栏可能会在返回之前被释放,从而导致未定义的行为。
-
int dma_fence_add_callback(struct dma_fence *fence, struct dma_fence_cb *cb, dma_fence_func_t func)¶
添加一个回调,以便在栅栏发出信号时调用
参数
struct dma_fence *fence
要等待的栅栏
struct dma_fence_cb *cb
要注册的回调
dma_fence_func_t func
要调用的函数
描述
向栅栏添加软件回调。调用者应保持对栅栏的引用。
cb 将由 dma_fence_add_callback()
初始化,不需要调用方进行初始化。可以向一个栅栏注册任意数量的回调,但一个回调一次只能注册到一个栅栏。
如果栅栏已经发出信号,则此函数将返回 -ENOENT(并且不调用回调)。
请注意,可以从原子上下文或 irq 上下文调用回调。
成功时返回 0,如果栅栏已发出信号则返回 -ENOENT,如果发生错误则返回 -EINVAL。
参数
struct dma_fence *fence
要查询的 dma_fence
描述
此函数包装 dma_fence_get_status_locked()
,以返回发出信号的栅栏的错误状态条件。有关更多详细信息,请参见 dma_fence_get_status_locked()
。
如果栅栏尚未发出信号,则返回 0;如果栅栏已发出信号且没有错误条件,则返回 1;如果栅栏已完成并出现错误,则返回负错误代码。
-
bool dma_fence_remove_callback(struct dma_fence *fence, struct dma_fence_cb *cb)¶
从信号列表中删除回调
参数
struct dma_fence *fence
要等待的栅栏
struct dma_fence_cb *cb
要删除的回调
描述
从栅栏中删除先前排队的回调。如果回调成功删除,则此函数返回 true,如果栅栏已发出信号,则返回 false。
警告:取消回调函数应仅在您确实了解自己在做什么的情况下进行,因为死锁和竞争条件很容易发生。因此,只有在硬件锁死恢复时,才应该使用对 fence 的引用进行取消。
如果事先没有使用 dma_fence_add_callback()
将 cb 添加到 fence 中,则行为是未定义的。
-
signed long dma_fence_default_wait(struct dma_fence *fence, bool intr, signed long timeout)¶
默认情况下,睡眠直到 fence 被触发或直到超时时间结束。
参数
struct dma_fence *fence
要等待的栅栏
bool intr
如果为 true,则进行可中断等待
signed long timeout
超时值,以 jiffies 为单位,或 MAX_SCHEDULE_TIMEOUT
描述
如果中断则返回 -ERESTARTSYS,如果等待超时则返回 0,或者成功时返回剩余的 jiffies 超时时间。如果超时时间为零,则如果 fence 已经触发,为了与其他采用 jiffies 超时时间的函数保持一致,将返回 1。
-
signed long dma_fence_wait_any_timeout(struct dma_fence **fences, uint32_t count, bool intr, signed long timeout, uint32_t *idx)¶
睡眠直到任何 fence 被触发或直到超时时间结束。
参数
struct dma_fence **fences
要等待的 fence 数组
uint32_t count
要等待的 fence 数量
bool intr
如果为 true,则进行可中断等待
signed long timeout
超时值,以 jiffies 为单位,或 MAX_SCHEDULE_TIMEOUT
uint32_t *idx
用于存储第一个被触发的 fence 的索引,仅在正返回时有意义。
描述
在自定义 fence 等待实现上返回 -EINVAL,如果中断则返回 -ERESTARTSYS,如果等待超时则返回 0,或者成功时返回剩余的 jiffies 超时时间。
同步等待数组中第一个 fence 被触发。调用者需要持有数组中所有 fence 的引用,否则 fence 可能会在返回之前被释放,从而导致未定义的行为。
参数
struct dma_fence *fence
要等待的 fence。
ktime_t deadline
等待器希望 fence 在此时间之前被触发的时间。
描述
向 fence 发信号器提供有关即将到来的截止时间(例如,垂直同步)的提示,等待器希望 fence 在此时间点之前被触发。这旨在向 fence 发信号器提供反馈,以帮助进行电源管理决策,例如,如果定期的垂直同步截止时间即将来临但 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;
};
成员
锁
用于锁定的 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_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 (*fence_value_str)(struct dma_fence *fence, char *str, int size);
void (*timeline_value_str)(struct dma_fence *fence, char *str, int size);
void (*set_deadline)(struct dma_fence *fence, ktime_t deadline);
};
成员
use_64bit_seqno
如果此 dma_fence 实现使用 64 位 seqno,则为 True,否则为 false。
get_driver_name
返回驱动程序名称。这是一个回调,允许驱动程序在运行时计算名称,而无需为每个 fence 永久存储,或构建某种缓存。
此回调是强制性的。
get_timeline_name
返回此 fence 所属上下文的名称。这是一个回调,允许驱动程序在运行时计算名称,而无需为每个 fence 永久存储,或构建某种缓存。
此回调是强制性的。
enable_signaling
启用 fence 的软件信号。
对于具有硬件 -> 硬件信号能力的 fence 实现,它们可以实现此操作以启用必要的中断,或将命令插入 cmdstream 等,以避免在只需要硬件 -> 硬件同步的常见情况下进行这些代价高昂的操作。这是在第一个
dma_fence_wait()
或dma_fence_add_callback()
路径中调用的,以让 fence 实现知道有另一个驱动程序正在等待信号(即硬件 -> 软件情况)。此函数可以从原子上下文中调用,但不能从 irq 上下文中调用,因此可以使用普通自旋锁。
返回值 false 表示 fence 已通过,或发生了一些错误导致无法启用信号。 True 表示成功启用。
dma_fence.error
可能会在 enable_signaling 中设置,但仅当返回 false 时。由于许多实现即使在调用 enable_signaling 之前也可以调用
dma_fence_signal()
,所以存在一个竞争窗口,其中dma_fence_signal()
可能会导致释放最终的 fence 引用并释放其内存。为了避免这种情况,此回调的实现应使用dma_fence_get()
获取自己的引用,以便在 fence 被发出信号时释放(例如,通过中断处理程序)。此回调是可选的。如果此回调不存在,则驱动程序必须始终启用信号。
signaled
窥视 fence 是否已发出信号,作为
dma_fence_wait()
或dma_fence_add_callback()
等的快速路径优化。请注意,此回调不需要做出任何保证,超过 fence 一旦指示为已发出信号,必须始终从此回调返回 true。即使 fence 已经完成,此回调也可能返回 false,在这种情况下,信息尚未通过系统传播。另请参阅dma_fence_is_signaled()
。如果返回 true,则可能会设置
dma_fence.error
。此回调是可选的。
wait
自定义等待实现,如果未设置,则默认为
dma_fence_default_wait()
。已弃用,新实现不应使用。仅由需要对其硬件复位过程进行特殊处理的现有实现使用。
如果等待是 intr = true 且等待被中断,则必须返回 -ERESTARTSYS;如果 fence 已发出信号,则返回剩余的 jiffies;如果等待超时,则返回 0。也可以在自定义实现上返回其他错误值,这些值应视为 fence 已发出信号。例如,可以像这样报告硬件锁定。
release
在 fence 销毁时调用以释放其他资源。可以从 irq 上下文中调用。此回调是可选的。如果为 NULL,则改为调用
dma_fence_free()
作为默认实现。fence_value_str
回调以填充特定于此 fence 的自由形式的调试信息,例如序列号。
此回调是可选的。
timeline_value_str
将时间线的当前值填充为字符串,例如序列号。请注意,传递给此函数的特定 fence 应该无关紧要,驱动程序应仅使用它来查找相应的时间线结构。
set_deadline
回调以允许 fence 等待者通知 fence 发信号者即将到来的截止日期,例如垂直同步,等待者希望在此之前发出 fence 信号。这旨在向 fence 发信号者提供反馈,以帮助进行电源管理决策,例如提高 GPU 频率。
这是在不持有
dma_fence.lock
的情况下调用的,它可以多次从任何上下文调用。如果被调用者有一些状态要管理,则需要锁定。如果设置了多个截止日期,则期望跟踪最早的截止日期。如果截止日期在当前时间之前,则应将其解释为立即截止日期。此回调是可选的。
参数
struct dma_fence *fence
要减少引用计数的 fence
参数
struct dma_fence *fence
要增加引用计数的 fence
描述
返回相同的 fence,引用计数增加 1。
参数
struct dma_fence *fence
要增加引用计数的 fence
描述
如果无法获得引用计数,或 fence,则函数返回 NULL。
参数
struct dma_fence __rcu **fencep
指向要增加引用计数的 fence 的指针
描述
如果无法获得引用计数,或 fence,则函数返回 NULL。此函数处理获取对可能在 RCU 宽限期内重新分配的 fence 的引用(例如,使用 SLAB_TYPESAFE_BY_RCU),只要调用者在指向 fence 的指针上使用 RCU 即可。
另一种机制是采用 seqlock 来保护一组 fence,例如 struct dma_resv
所使用的。使用 seqlock 时,必须在获取对 fence 的引用之前获取 seqlock,并在之后进行检查(如此处所示)。
调用者需要持有 RCU 读取锁。
参数
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。两个 fence 必须来自同一上下文,因为 seqno 不在上下文之间通用。
参数
struct dma_fence *f1
来自同一上下文的第一个 fence
struct dma_fence *f2
来自同一上下文的第二个 fence
描述
如果 f1 在时间顺序上晚于 f2,则返回 true。两个 fence 必须来自同一上下文,因为 seqno 不会在上下文之间重用。
-
bool dma_fence_is_later_or_same(struct dma_fence *f1, struct dma_fence *f2)¶
如果 f1 晚于或等于 f2,则返回 true。
参数
struct dma_fence *f1
来自同一上下文的第一个 fence
struct dma_fence *f2
来自同一上下文的第二个 fence
描述
如果 f1 在时间顺序上晚于 f2 或与 f2 是同一个 fence,则返回 true。两个 fence 必须来自同一上下文,因为 seqno 不会在上下文之间重用。
参数
struct dma_fence *f1
来自同一上下文的第一个 fence
struct dma_fence *f2
来自同一上下文的第二个 fence
描述
如果两个 fence 都已发出信号,则返回 NULL,否则返回最后一个发出信号的 fence。两个 fence 必须来自同一上下文,因为 seqno 不会在上下文之间重用。
参数
struct dma_fence *fence
要查询的 dma_fence
描述
驱动程序可以在发出 fence 信号之前提供可选的错误状态条件(以指示 fence 是由于错误而不是成功完成的)。仅当 fence 已发出信号时,状态条件的值才有效,dma_fence_get_status_locked()
首先检查信号状态,然后再报告错误状态。
如果栅栏尚未发出信号,则返回 0;如果栅栏已发出信号且没有错误条件,则返回 1;如果栅栏已完成并出现错误,则返回负错误代码。
参数
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
要等待的栅栏
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
[输入] 要在数组中添加的 fence 的数量
描述
成功时返回 dma fence 数组,失败时返回 NULL
-
void dma_fence_array_init(struct dma_fence_array *array, int num_fences, struct dma_fence **fences, u64 context, unsigned seqno, bool signal_on_any)¶
初始化一个自定义的 fence 数组
参数
struct dma_fence_array *array
[输入] 要 arm 的 dma fence 数组
int num_fences
[输入] 要在数组中添加的 fence 的数量
struct dma_fence **fences
[输入] 包含 fence 的数组
u64 context
[输入] 要使用的 fence 上下文
unsigned seqno
[输入] 要使用的序列号
bool signal_on_any
[输入] 数组中任何 fence 发出信号时发出信号
描述
实现不带分配的 dma_fence_array_create。在回收或 dma fence 发信号路径中初始化预先分配的 dma fence 数组很有用。
-
struct dma_fence_array *dma_fence_array_create(int num_fences, struct dma_fence **fences, u64 context, unsigned seqno, bool signal_on_any)¶
创建一个自定义的 fence 数组
参数
int num_fences
[输入] 要在数组中添加的 fence 的数量
struct dma_fence **fences
[输入] 包含 fence 的数组
u64 context
[输入] 要使用的 fence 上下文
unsigned seqno
[输入] 要使用的序列号
bool signal_on_any
[输入] 数组中任何 fence 发出信号时发出信号
描述
分配一个 dma_fence_array 对象,并使用 dma_fence_init()
初始化基础 fence。如果出错,则返回 NULL。
调用者应分配大小为 num_fences 的 fences 数组,并用想要添加到对象的 fence 填充它。将获取此数组的所有权,并在释放时对每个 fence 使用 dma_fence_put()
。
如果 signal_on_any 为 true,则如果数组中任何 fence 发出信号,则 fence 数组会发出信号,否则,当数组中所有 fence 发出信号时,它会发出信号。
参数
struct dma_fence *fence
[输入] fence 或 fence 数组
u64 context
[输入] 要针对所有 fence 检查的 fence 上下文
描述
针对给定上下文检查提供的 fence,或对于 fence 数组,检查数组中的所有 fence。如果任何 fence 来自不同的上下文,则返回 false。
-
struct dma_fence_array_cb¶
fence 数组的回调助手
定义:
struct dma_fence_array_cb {
struct dma_fence_cb cb;
struct dma_fence_array *array;
};
成员
cb
用于发信号的 fence 回调结构
array
对父 fence 数组对象的引用
-
struct dma_fence_array¶
表示 fence 数组的 fence
定义:
struct dma_fence_array {
struct dma_fence base;
spinlock_t lock;
unsigned num_fences;
atomic_t num_pending;
struct dma_fence **fences;
struct irq_work work;
struct dma_fence_array_cb callbacks[] ;
};
成员
base
fence 基类
锁
用于 fence 处理的自旋锁
num_fences
数组中 fence 的数量
num_pending
数组中仍在挂起的 fence
栅栏
fence 数组
work
内部 irq_work 函数
callbacks
回调助手数组
-
struct dma_fence_array *to_dma_fence_array(struct dma_fence *fence)¶
将 fence 强制转换为 dma_fence_array
参数
struct dma_fence *fence
要强制转换为 dma_fence_array 的 fence
描述
如果 fence 不是 dma_fence_array,则返回 NULL;否则返回 dma_fence_array。
-
dma_fence_array_for_each¶
dma_fence_array_for_each (fence, index, head)
遍历数组中的所有 fence
参数
fence
当前 fence
index
数组中的索引
head
潜在的 dma_fence_array 对象
描述
测试 array 是否为 dma_fence_array 对象,如果是,则遍历数组中的所有 fence。如果不是,则仅遍历 array 本身中的 fence。
有关深入迭代器,请参阅 dma_fence_unwrap_for_each()
。
DMA Fence 链¶
参数
struct dma_fence *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;
};
成员
base
fence 基类
prev
链的上一个 fence
prev_seqno
垃圾回收之前的原始上一个 seqno
fence
封装的 fence
{unnamed_union}
匿名
cb
用于发信号的回调
这用于添加回调以发信号通知 fence 链的完成。永远不会与 irq 工作同时使用。
work
用于发信号的 irq 工作项
Irq 工作结构,允许我们在不发生锁反转的情况下添加回调。永远不会与回调同时使用。
锁
用于 fence 处理的自旋锁
-
struct dma_fence_chain *to_dma_fence_chain(struct dma_fence *fence)¶
将 fence 强制转换为 dma_fence_chain
参数
struct dma_fence *fence
要强制转换为 dma_fence_array 的 fence
描述
如果 fence 不是 dma_fence_chain,则返回 NULL;否则返回 dma_fence_chain。
参数
struct dma_fence *fence
要测试的 fence
描述
如果 fence 是 dma_fence_chain,则此函数返回链对象内部包含的 fence,否则返回 fence 本身。
-
dma_fence_chain_alloc¶
dma_fence_chain_alloc ()
描述
返回新的
struct dma_fence_chain
对象,失败时返回 NULL。这个特殊的分配器必须是一个宏,以便其分配可以单独计算(拥有单独的 alloc_tag)。类型转换是有意为之,以强制类型安全。
-
void dma_fence_chain_free(struct dma_fence_chain *chain)¶
参数
struct dma_fence_chain *chain
要释放的链节点
描述
释放已分配但未使用的 struct dma_fence_chain
对象。由于 fence 从未初始化或发布,因此不需要 RCU 宽限期。在调用 dma_fence_chain_init()
之后,必须通过调用 dma_fence_put()
来释放 fence,而不是通过此函数。
-
dma_fence_chain_for_each¶
dma_fence_chain_for_each (iter, head)
遍历链中的所有 fence
参数
iter
当前 fence
head
起始点
描述
遍历链中的所有 fence。在循环内部,我们保持对当前 fence 的引用,当跳出循环时必须释放该引用。
有关深入迭代器,请参阅 dma_fence_unwrap_for_each()
。
DMA Fence 解包¶
-
struct dma_fence_unwrap¶
容器结构的游标
定义:
struct dma_fence_unwrap {
struct dma_fence *chain;
struct dma_fence *array;
unsigned int index;
};
成员
链
潜在的 dma_fence_chain,但也可能是其他 fence
array
潜在的 dma_fence_array,但也可能是其他 fence
index
如果 array 实际上是 dma_fence_array,则返回最后一个索引
描述
应与 dma_fence_unwrap_for_each()
迭代器宏一起使用。
-
dma_fence_unwrap_for_each¶
dma_fence_unwrap_for_each (fence, cursor, head)
遍历容器中的所有 fence
参数
fence
当前 fence
光标
容器内部的当前位置
head
迭代器的起始点
描述
解开 dma_fence_chain 和 dma_fence_array 容器,并深入探索其中的所有潜在 fence。如果 head 只是一个普通的 fence,则只返回这一个 fence。
-
dma_fence_unwrap_merge¶
dma_fence_unwrap_merge (...)
解开并合并 fence
参数
...
可变参数
描述
所有作为参数给出的 fence 都被解开,并作为扁平的 dma_fence_array 合并在一起。如果需要将多个容器合并在一起,则很有用。
作为宏实现,以便在堆栈上分配必要的数组,并将堆栈帧大小计入调用方。
如果内存分配失败,则返回 NULL;否则,返回一个表示所有给定 fence 的 dma_fence 对象。
DMA Fence 同步文件¶
参数
struct 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
sync_file 的标志
fence
包含 sync_file 中 fence 的 fence
cb
fence 回调信息
描述
标志:POLL_ENABLED:表示用户空间当前是否正在进行 poll() 操作
DMA Fence 同步文件 uABI¶
-
struct sync_merge_data¶
SYNC_IOC_MERGE:合并两个 fence
定义:
struct sync_merge_data {
char name[32];
__s32 fd2;
__s32 fence;
__u32 flags;
__u32 pad;
};
成员
name
新 fence 的名称
fd2
第二个 fence 的文件描述符
fence
将新 fence 的 fd 返回给用户空间
flags
merge_data 标志
pad
用于 64 位对齐的填充,应始终为零
描述
创建一个新的 fence,其中包含调用 fd 和 sync_merge_data.fd2 中 sync_pts 的副本。在 sync_merge_data.fence 中返回新 fence 的 fd
-
struct sync_fence_info¶
详细的 fence 信息
定义:
struct sync_fence_info {
char obj_name[32];
char driver_name[32];
__s32 status;
__u32 flags;
__u64 timestamp_ns;
};
成员
obj_name
父 sync_timeline 的名称
driver_name
实现父对象的驱动程序的名称
status
fence 的状态:0:活动,1:已发出信号,<0:错误
flags
fence_info 标志
timestamp_ns
状态更改的时间戳(以纳秒为单位)
-
struct sync_file_info¶
SYNC_IOC_FILE_INFO:获取有关 sync_file 的详细信息
定义:
struct sync_file_info {
char name[32];
__s32 status;
__u32 flags;
__u32 num_fences;
__u32 pad;
__u64 sync_fence_info;
};
成员
name
fence 的名称
status
fence 的状态。1:已发出信号,0:活动,<0:错误
flags
sync_file_info 标志
num_fences
sync_file 中 fence 的数量
pad
用于 64 位对齐的填充,应始终为零
sync_fence_info
指向 struct
sync_fence_info
数组的指针,其中包含 sync_file 中的所有 fence
描述
接受一个 struct sync_file_info
。如果 num_fences 为 0,则该字段将更新为 fence 的实际数量。如果 num_fences > 0,系统将使用 sync_fence_info 上提供的指针返回最多 num_fences 个 struct sync_fence_info
,其中包含详细的 fence 信息。
-
struct sync_set_deadline¶
SYNC_IOC_SET_DEADLINE - 在栅栏上设置截止时间提示
定义:
struct sync_set_deadline {
__u64 deadline_ns;
__u64 pad;
};
成员
deadline_ns
截止时间的绝对时间
pad
必须为零
描述
允许用户空间在栅栏上设置截止时间,请参阅 dma_fence_set_deadline
截止时间的时间基准是 CLOCK_MONOTONIC(与垂直同步相同)。例如
clock_gettime(CLOCK_MONOTONIC,
t
); deadline_ns = (t.tv_sec * 1000000000L) + t.tv_nsec + ns_until_deadline
无限 DMA 栅栏¶
在各种时候,已经提出了在 struct dma_fence
上,直到 dma_fence_wait()
完成之前,时间都是无限的。示例包括
未来的栅栏,在 HWC1 中用于指示显示器不再使用缓冲区的时间,并且是在使缓冲区可见的屏幕更新时创建的。此栅栏完成的时间完全由用户空间控制。
代理栅栏,提议用于处理尚未设置栅栏的 &drm_syncobj。用于异步延迟命令提交。
用户空间栅栏或 GPU futexes,是命令缓冲区内的细粒度锁定,用户空间将其用于跨引擎或与 CPU 的同步,然后将其作为 DMA 栅栏导入,以集成到现有的 winsys 协议中。
长时间运行的计算命令缓冲区,同时仍然使用传统的批处理结束 DMA 栅栏进行内存管理,而不是在重新调度计算作业时重新附加的上下文抢占 DMA 栅栏。
所有这些方案的共同之处在于,用户空间控制这些栅栏的依赖关系并控制它们何时触发。将无限栅栏与正常的内核 DMA 栅栏混合使用不起作用,即使包含回退超时以防止恶意用户空间也是如此。
只有内核知道所有 DMA 栅栏依赖关系,用户空间不知道由于内存管理或调度器决策而注入的依赖关系。
只有用户空间知道无限栅栏中的所有依赖关系以及它们何时完成,内核不可见。
此外,内核必须能够为了内存管理需求而阻止用户空间命令提交,这意味着我们必须支持依赖于 DMA 栅栏的无限栅栏。如果内核也支持内核中的无限栅栏(如 DMA 栅栏),就像上述任何提议一样,则可能出现死锁。
这意味着内核可能会意外地通过用户空间不知道的内存管理依赖关系创建死锁,这会随机挂起工作负载,直到超时生效。从用户空间的角度来看,工作负载不包含死锁。在这种混合栅栏架构中,没有一个实体知道所有依赖关系。因此,无法从内核内部防止此类死锁。
避免依赖循环的唯一解决方案是不允许在内核中使用无限栅栏。这意味着
没有未来栅栏、代理栅栏或作为 DMA 栅栏导入的用户空间栅栏,无论有没有超时。
没有 DMA 栅栏可以指示用户空间允许使用用户空间栅栏或长时间运行的计算工作负载进行命令提交的批处理缓冲区结束。这也意味着在这些情况下没有用于共享缓冲区的隐式栅栏。
可恢复的硬件页面错误影响¶
现代硬件支持可恢复的页面错误,这对 DMA 栅栏有很多影响。
首先,挂起的页面错误显然会阻止加速器上运行的工作,并且通常需要内存分配才能解决该错误。但是不允许内存分配来限制 DMA 栅栏的完成,这意味着任何使用可恢复页面错误的工作负载都不能使用 DMA 栅栏进行同步。必须改用用户空间控制的同步栅栏。
在 GPU 上,这会造成问题,因为当前 Linux 上的桌面合成器协议依赖于 DMA 栅栏,这意味着如果没有完全基于用户空间栅栏构建的新用户空间堆栈,它们就无法从可恢复页面错误中获益。具体而言,这意味着隐式同步将不可能。例外情况是,页面错误仅用作迁移提示,而绝不用作按需填充内存请求。目前,这意味着 GPU 上的可恢复页面错误仅限于纯计算工作负载。
此外,GPU 通常在 3D 渲染和计算端之间共享资源,如计算单元或命令提交引擎。如果具有 DMA 栅栏的 3D 作业和使用可恢复页面错误的计算工作负载都在挂起,则它们可能会死锁
3D 工作负载可能需要等待计算作业完成并首先释放硬件资源。
计算工作负载可能卡在页面错误中,因为内存分配正在等待 3D 工作负载的 DMA 栅栏完成。
有几种方法可以防止此问题,其中驱动程序需要确保
计算工作负载始终可以被抢占,即使页面错误挂起且尚未修复。并非所有硬件都支持此功能。
DMA 栅栏工作负载和需要页面错误处理的工作负载具有独立的硬件资源,以保证向前进展。这可以通过例如专用引擎和 DMA 栅栏工作负载的最小计算单元预留来实现。
可以通过仅在 DMA 栅栏处于运行状态时才为 DMA 栅栏工作负载预留硬件资源来进一步优化预留方法。这必须涵盖从 DMA 栅栏对其他线程可见,直到通过
dma_fence_signal()
完成栅栏的时间。作为最后的手段,如果硬件不提供有用的预留机制,则在需要 DMA 栅栏或需要页面错误处理的作业之间切换时,必须从 GPU 中刷新所有工作负载:这意味着所有 DMA 栅栏必须在具有页面错误处理的计算作业可以插入调度程序队列之前完成。反之亦然,在系统中的任何位置显示 DMA 栅栏之前,必须抢占所有计算工作负载,以保证刷新所有挂起的 GPU 页面错误。
一个相当理论的选择是在分配内存以修复硬件页面错误时,通过单独的内存块或对所有 DMA 栅栏的完整依赖关系图进行运行时跟踪来消除这些依赖关系。这对内核的影响非常广泛,因为在 CPU 端解决页面错误本身可能会涉及页面错误。将处理硬件页面错误的影响限制在特定驱动程序中更可行和可靠。
请注意,在独立硬件(如复制引擎或其他 GPU)上运行的工作负载没有任何影响。这使我们即使在解决硬件页面错误时,也可以继续在内核内部使用 DMA 栅栏,例如,使用复制引擎来清除或复制解决页面错误所需的内存。
在某些方面,此页面错误问题是无限 DMA 栅栏讨论的一个特例:来自计算工作负载的无限栅栏可以依赖于 DMA 栅栏,但反之则不然。甚至页面错误问题也不是新的,因为用户空间中的某些其他 CPU 线程可能会遇到页面错误,这会阻止用户空间栅栏 - 在 GPU 上支持页面错误并没有带来任何根本性的新东西。