title: dma-buf
categories:
- linux
- drivers
- dma
tags: - linux
- drivers
- dma
abbrlink: 1d3fd482
date: 2025-10-03 09:01:49

文章目录
- [Linux 内核 dma-buf 核心实现(drivers/dma-buf/dma-buf.c)全面解析](#Linux 内核 dma-buf 核心实现(drivers/dma-buf/dma-buf.c)全面解析)
- [[drivers/dma-buf/dma-buf.c] [dma-buf 共享缓冲区框架核心] [跨子系统/设备共享 DMA 缓冲区 + 同步与 CPU 访问支持的核心对象与文件接口]](#[drivers/dma-buf/dma-buf.c] [dma-buf 共享缓冲区框架核心] [跨子系统/设备共享 DMA 缓冲区 + 同步与 CPU 访问支持的核心对象与文件接口])
-
- 介绍
- 历史与背景
- 核心原理与设计
- 使用场景
- 对比分析
-
- 1) dma-buf vs "普通共享内存/文件(memfd/shm/tmpfs)" dma-buf vs “普通共享内存/文件(memfd/shm/tmpfs)”)
- 2) dma-buf vs "每设备独立分配 + 自定义传递" dma-buf vs “每设备独立分配 + 自定义传递”)
- 3) dma-buf vs dma-heap(补充理解边界) dma-buf vs dma-heap(补充理解边界))
- 总结
- [`dma-heap` 与 `dma-buf` 的职责关系:分配入口/内存池策略 vs 共享对象/语义核心](#
dma-heap与dma-buf的职责关系:分配入口/内存池策略 vs 共享对象/语义核心) -
- 1) `dma-buf` 是"共享对象与语义核心"
dma-buf是“共享对象与语义核心”) - 2) `dma-heap` 更像"面向用户态申请 dma-buf 的分配入口/内存池策略"
dma-heap更像“面向用户态申请 dma-buf 的分配入口/内存池策略”) - 3) 两者如何"经常一起出现" 两者如何“经常一起出现”)
- 1) `dma-buf` 是"共享对象与语义核心"
- [DMA-BUF 初始化与生命周期管理:`dma_buf_init` / `dmabuffs_dname` / `dma_buf_release` / `dma_buf_file_release`](#DMA-BUF 初始化与生命周期管理:
dma_buf_init/dmabuffs_dname/dma_buf_release/dma_buf_file_release) -
- [`dma_buf_init`: 在子系统初始化阶段建立 dma-buf 的 sysfs/debugfs 与伪文件系统挂载](#
dma_buf_init: 在子系统初始化阶段建立 dma-buf 的 sysfs/debugfs 与伪文件系统挂载) - [`dmabuffs_dname`: 为 dma-buf dentry 动态生成可读的路径名称](#
dmabuffs_dname: 为 dma-buf dentry 动态生成可读的路径名称) - [`dma_buf_release`: dentry 释放时销毁 dma-buf 对象并回收所有关联资源](#
dma_buf_release: dentry 释放时销毁 dma-buf 对象并回收所有关联资源) - [`dma_buf_file_release`: dma-buf 文件关闭路径中的全局列表摘除](#
dma_buf_file_release: dma-buf 文件关闭路径中的全局列表摘除)
- [`dma_buf_init`: 在子系统初始化阶段建立 dma-buf 的 sysfs/debugfs 与伪文件系统挂载](#
- [drivers/dma-buf/dma-fence.c DMA Fence(DMA 栅栏)同步机制](#drivers/dma-buf/dma-fence.c DMA Fence(DMA 栅栏)同步机制)
-
- 介绍
- 总结
- [`dma_fence_init_stub`: 初始化并立即完成一个全局 stub fence,用于提供可用的默认同步对象](#
dma_fence_init_stub: 初始化并立即完成一个全局 stub fence,用于提供可用的默认同步对象) - [`__dma_fence_init` / `dma_fence_init`: 初始化 dma_fence 的身份、并发保护与回调容器](#
__dma_fence_init/dma_fence_init: 初始化 dma_fence 的身份、并发保护与回调容器) - [`dma_fence_signal_timestamp_locked` / `dma_fence_signal_timestamp` / `dma_fence_signal_locked` / `dma_fence_signal`: 以原子方式将 fence 从"未完成"转换为"已完成",并执行回调链](#
dma_fence_signal_timestamp_locked/dma_fence_signal_timestamp/dma_fence_signal_locked/dma_fence_signal: 以原子方式将 fence 从“未完成”转换为“已完成”,并执行回调链)
Linux 内核 dma-buf 核心实现(drivers/dma-buf/dma-buf.c)全面解析
[drivers/dma-buf/dma-buf.c] [dma-buf 共享缓冲区框架核心] [跨子系统/设备共享 DMA 缓冲区 + 同步与 CPU 访问支持的核心对象与文件接口]
介绍
dma-buf 子系统提供一种跨多个驱动/子系统共享同一块底层缓冲区 的内核框架,并配套处理异步硬件访问的同步 (基于 dma-resv/dma-fence)以及CPU 访问一致性 (begin/end CPU access、mmap、poll/notify 等)。官方文档把其定位为:共享用于硬件(DMA)访问的缓冲区,并同步异步硬件访问。
drivers/dma-buf/dma-buf.c 则是**dma-buf 核心对象(struct dma_buf)与其"文件描述符语义"**的主要实现:全局跟踪、fd 导出、attachment 管理、map/unmap、CPU access、poll、ioctl 等。
历史与背景
这项技术是为了解决什么特定问题而诞生的?
在图形、多媒体、相机、编解码等场景中,常见"一个子系统分配 buffer,另一个子系统/设备使用同一 buffer 做 DMA"。若各驱动各自定义共享方式,会导致:
- 缓冲区传递机制碎片化(每对驱动一套接口)
- 同步机制不统一(谁负责等待/信号?)
- 缓冲区生命周期难以管理(引用计数/关闭 fd/释放顺序)
dma-buf 用统一的文件描述符(fd)载体 来传递 buffer,并用 dma-resv/dma-fence 体系做隐式同步,降低跨子系统互操作成本。
发展里程碑或版本迭代(从代码/文档可直接看到的能力点)
不在这里强行给出"某版本引入某特性"的时间线(那需要进一步查 git log 才严谨),但从当前主线实现可确认这些关键能力已经稳定存在:
- 全局 dma-buf 列表与迭代接口 :
dmabuf_list+dma_buf_iter_begin()/dma_buf_iter_next(),并强调 list mutex 不保护 refcount,需要file_ref_get()方式安全拿引用。) - 明确的锁定约定:文档化说明"哪些 API 调用方必须/必须不持有 reservation lock",将 deadlock 风险前置到接口契约层。)
- map_attachment 路径集成 pin 语义 :
dma_buf_map_attachment()里might_sleep()、dma_resv_assert_held(),并在需要时走ops->pin(),对导出者错误行为做WARN。) - CPU 访问路径显式等待隐式 fence :在
dma_buf_begin_cpu_access()相关逻辑里,会等待隐式渲染 fences(通过dma_resv_wait_timeout)。) - 官方文档持续更新(发布时间为近一周),说明仍是活跃维护的主流框架。
社区活跃度与主流应用情况
dma-buf 是 Linux 图形/多媒体栈里的核心基础设施,DRM、V4L2 等广泛依赖它进行 buffer 交换与同步(官方文档也明确点到这些典型使用)。
核心原理与设计
核心工作原理是什么?
可以按"对象模型 + 文件语义 + 同步模型 + 映射模型"来理解:
- 对象模型:struct dma_buf 作为共享对象
- 导出者(exporter)实现
struct dma_buf_ops,决定底层内存如何分配、如何 map 成 sg_table、如何处理 CPU access 等。 - 导入者(importer/user)通过 attachment 把该 dma-buf 绑定到某个设备,再把它映射成该设备可 DMA 的形式。
- 文件语义:dma-buf 作为 fd 传递
- dma-buf 通过匿名 inode/伪文件系统的 file 对象呈现为 fd,从而在进程/子系统之间传递。代码中可以看到创建 pseudo file 的路径(
alloc_file_pseudo),并设置 inode size 等信息。) - 代码维护全局 dmabuf 列表,并提供迭代:迭代时需要安全地"提升引用计数以防销毁"。)
- 同步模型:dma-resv / dma-fence 隐式同步
- dma-buf 的
resv(reservation object)聚合 fences:读/写访问顺序由 fences 表达。 - CPU 访问路径会等待隐式 fences,确保 CPU 读写看到一致数据(至少在指定范围/方向语义内)。)
- 映射模型:attachment + map/unmap
dma_buf_map_attachment()要求调用方持有 reservation lock(代码里dma_resv_assert_held),并可能 sleep。)- 它通过 exporter 的
ops->map_dma_buf()返回sg_table,供设备 DMA 使用;并在需要时结合pin语义提升"底层存储不可迁移/可访问性"约束。)
- 锁定约定(非常关键)
源码中专门列出导入者约定:例如dma_buf_map_attachment()/vmap()这类需要持有 reservation lock,而dma_buf_attach()/export()/fd/get/put等则要求不持有。这个约定是排查死锁的第一优先级依据。)
主要优势体现在哪些方面?
- 跨子系统统一共享模型:fd 传递 + 统一 attachment/map 语义,降低耦合(官方文档描述的核心目标)。
- 统一的隐式同步基础设施 :围绕
dma-resv/dma-fence组织同步,CPU 访问路径也能通过等待 fences 保证一致性。) - 明确的调用方锁约束:把"谁持锁调用什么"写进契约,降低系统集成时的不可控并发问题。)
劣势、局限性或不适用性
- 复杂度高:必须正确处理 exporter/importer 角色、reservation lock、fence 语义、CPU access begin/end、mmap/ioctl/poll 等一整套机制;错误通常表现为隐式同步失效或死锁。)
- 不是"通用共享内存"接口:它面向 DMA buffer 共享与同步;若只是 CPU 侧共享数据结构,可能更适合其他 IPC/共享内存机制(取决于需求)。这一点需要你基于目标场景做判断。
- 关键路径可能 sleep :例如
dma_buf_map_attachment()明确might_sleep(),因此调用上下文要满足可睡眠条件。)
使用场景
首选场景(举例)
- DRM ↔ V4L2 / 编解码器 / ISP / 显示:图形与多媒体流水线中,一个组件产出帧缓冲,另一个组件消费;dma-buf 用 fd 传递 buffer,fence 做隐式同步,是典型路径(官方文档点名 DRM、V4L2 等互通)。
- 跨设备共享同一底层存储:例如 GPU 渲染结果直接给显示/视频编码设备 DMA 使用,通过 attachment/map 取 sg_table。
不推荐使用的场景(原因)
- 不涉及 DMA 的纯 CPU 数据共享:dma-buf 引入的同步/映射语义与实现复杂度可能是负担。
- 无法接受隐式同步模型的系统:如果你需要显式控制每一步同步、或系统中同步策略与 dma-resv 机制冲突,使用 dma-buf 可能会增加调试难度(需要你对 fence/resv 语义非常熟)。
对比分析
这里选 3 类"相似但定位不同"的技术路线做对比(你学习时容易混淆的点):
1) dma-buf vs "普通共享内存/文件(memfd/shm/tmpfs)"
-
实现方式
- dma-buf:面向 DMA 缓冲区共享,核心是 exporter ops + attachment + sg_table + fence/resv。
- memfd/shm:面向 CPU 地址空间共享,不提供统一的设备 DMA 映射与隐式同步语义。
-
性能开销
- dma-buf:在 map/unmap、同步等待、poll/notify 上有额外机制成本,但换来跨设备共享与正确同步。)
-
资源占用
- dma-buf:底层存储由 exporter 决定(可能是 pages、carveout、IOMMU 映射等),并伴随 resv/fence 元数据。
-
隔离级别
- dma-buf:以 fd 为句柄,权限由 fd 传递控制;设备访问能力通过 attachment 绑定设备。
-
启动速度
- dma-buf:建立共享关系通常要 export→fd→get→attach→map,多步骤;但这是为了明确控制共享与同步。
2) dma-buf vs "每设备独立分配 + 自定义传递"
- dma-buf 的主要优势就是避免 N×M 的私有接口组合;并统一同步。
- 代价是所有参与者都要遵循锁定约定与同步模型,否则系统性问题更隐蔽。)
3) dma-buf vs dma-heap(补充理解边界)
- dma-heap 更像"面向用户态申请 dma-buf 的分配入口/内存池策略",而 dma-buf.c 是"共享对象与语义核心"。两者经常一起出现,但职责不同。你在继续读源码时会很快感受到这一点(
drivers/dma-buf/dma-heap.c是另一条线)。
总结
关键特性
dma_buf作为可通过 fd 传递的共享 DMA 缓冲区对象,并支持跨设备 attachment、映射为 sg_table。- 以
dma-resv/dma-fence为核心的隐式同步,CPU 访问路径会等待相关 fences。) - 明确的锁定约定,是正确使用与排障的第一要点。)
- 核心实现还包含全局列表迭代、poll 支持、命名与统计等工程性能力。
dma-heap 与 dma-buf 的职责关系:分配入口/内存池策略 vs 共享对象/语义核心
1) dma-buf 是"共享对象与语义核心"
dma-buf.c 这类代码主要在做两件事:
- 定义共享对象
struct dma_buf的生命周期与规则
例如:引用计数、release 路径、attachments 管理、fence/同步语义、统计与 debug 导出等。 - 提供跨驱动共享的通用接口
例如:导出(export)、导入(attach/map/detach)、文件描述符关联、dentry/vfs 相关回调等。
也就是说:
dma-buf 不关心"这块内存来自哪个池子、怎么分配出来的",它关心的是:
一旦你给我一个 buffer,我怎么把它变成一个可共享、可同步、可回收的'共享对象'。
2) dma-heap 更像"面向用户态申请 dma-buf 的分配入口/内存池策略"
dma-heap 的定位更偏"上游入口 + 策略":
- 它提供给用户态一个标准入口(通常是
/dev/dma_heap/<heap>这样的设备节点)
用户态通过 ioctl 之类请求"给我分配一个 dma-buf"。 - 它把"分配策略"抽象成不同 heap
例如:system heap、cma heap、carveout heap(具体有哪些取决于内核实现/配置)。 - 它最终会创建一个 dma-buf(或者包装成 dma-buf 导出的对象)返回给用户态。
也就是说:
dma-heap 的职责是:
决定从哪里分配、用什么策略分配、给用户态一个可用的 dma-buf fd。
3) 两者如何"经常一起出现"
典型链路(概念上)是:
- 用户态:打开某个 heap 设备节点,请求分配
dma-heap:选择对应 heap 的分配器,分配物理/页面/SG 列表等底层内存dma-heap:把分配结果"导出"为一个dma-buf对象(绑定 ops,形成共享语义)- 返回给用户态:一个 dma-buf 的 fd
- 后续共享:别的驱动/子系统通过 dma-buf attach/map 等使用同一份底层内存
这里的分工就是:
- dma-heap:分配与池化策略(入口、选择、分配)
- dma-buf:共享与生命周期语义(对象、同步、引用、回收)
DMA-BUF 初始化与生命周期管理:dma_buf_init / dmabuffs_dname / dma_buf_release / dma_buf_file_release
先说明适用边界(与你要求的 STM32H750 单核无 MMU 视角直接相关):
这段代码属于 Linux 内核 dma-buf 子系统 + VFS(挂载、dentry、文件释放)+ sysfs/debugfs 。它依赖内核的挂载体系、dentry 生命周期、模块引用计数、以及通常与虚拟内存映射相关的约束(例如
vmapping_counter)。因此在 STM32H750(ARMv7-M、无 MMU、裸机/RTOS) 场景中无法直接运行;但其中的"资源生命周期一致性""引用计数与不变量检查""并发保护(spinlock)"思想仍可用于你在嵌入式驱动/中间件里设计资源管理。
dma_buf_init: 在子系统初始化阶段建立 dma-buf 的 sysfs/debugfs 与伪文件系统挂载
此函数是dma-buf 子系统的启动入口 ,通过 subsys_initcall 在内核子系统初始化阶段执行。它的核心动作是:
- 初始化统计 sysfs 节点 :
dma_buf_init_sysfs_statistics()负责把 dma-buf 的统计信息以 sysfs 形式对外发布(失败即直接退出,避免半初始化状态)。 - 挂载 dma-buf 专用伪文件系统 :
kern_mount(&dma_buf_fs_type)创建并挂载一个仅供内核使用的 mount,用于承载 dma-buf 的匿名文件/dentry 管理(这是后续 dentry 回调能够生效的前提)。 - 初始化 debugfs :
dma_buf_init_debugfs()用于调试信息导出;此处不影响核心功能但方便定位问题。
该函数体现的关键技巧是:把一个核心资源(dma_buf_mnt 挂载点)作为全局根对象建立起来,后续所有 dma-buf 文件对象的 dentry 生命周期都依赖它。
dmabuffs_dname: 为 dma-buf dentry 动态生成可读的路径名称
此函数是 dentry 的 .d_dname 回调,用于按需生成 dentry 的显示名称。其核心机制是:
- 从
dentry->d_fsdata取回struct dma_buf:驱动/子系统把私有对象指针挂在 dentry 上,VFS 回调时再取回,这是典型的"对象挂接"模式。 - 用
spin_lock保护 name 读取 :dmabuf->name可能被并发更新或释放,因此通过dmabuf->name_lock在读取期间保持一致性。 - 使用
dynamic_dname输出格式化名称 :返回值不是静态缓冲区,而是通过dynamic_dname在 VFS 需要时格式化输出(避免长期保存字符串导致额外生命周期管理复杂度)。
dma_buf_release: dentry 释放时销毁 dma-buf 对象并回收所有关联资源
此函数是 dentry 的 .d_release 回调,是 dma-buf 对象最终销毁路径 的关键部分。它的核心原则是:在释放前强制验证不变量,防止"仍在使用的对象被释放"。
关键点(只讲关键技巧,不讲"代码怎么跑"的基础):
-
不变量检查(BUG_ON)
BUG_ON(dmabuf->vmapping_counter);:要求不存在仍在进行的 vmap 映射计数,否则释放会导致地址空间/引用悬挂。BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active);:要求不存在仍活跃的 fence 回调状态,否则说明异步完成路径存在引用不平衡或状态机错误。
-
先拆统计与驱动资源,再拆通用资源
dma_buf_stats_teardown(dmabuf):撤销统计信息,避免 sysfs/debugfs 仍引用已释放对象。dmabuf->ops->release(dmabuf):调用导出方提供的 release,释放与底层 exporter 相关的资源。
-
处理内嵌 resv 对象的析构
- 若
dmabuf->resv指向&dmabuf[1](即紧随主结构体的内嵌对象),则需要dma_resv_fini()做对象析构。
- 若
-
确保附件链表为空并维护模块引用计数
WARN_ON(!list_empty(&dmabuf->attachments));:释放时不应仍有 attachment。module_put(dmabuf->owner);:归还 exporter 模块引用,保证模块可卸载性与引用一致性。
-
最终释放内存:释放 name 字符串与 dmabuf 本体。
dma_buf_file_release: dma-buf 文件关闭路径中的全局列表摘除
此函数是 file 的释放路径(通常由 file_operations->release 触发),它的核心动作很集中:
is_dma_buf_file(file):确认该 file 确实是 dma-buf 文件,避免误删。__dma_buf_list_del(file->private_data):从 dma-buf 的全局跟踪链表中移除该对象(属于"框架侧 bookkeeping")。
c
/**
* @brief dma-buf 子系统初始化入口函数
*
* 通过 subsys_initcall 在内核子系统初始化阶段执行,用于:
* - 初始化 sysfs 统计导出
* - 挂载 dma-buf 伪文件系统并保存挂载点
* - 初始化 debugfs 调试导出
*
* @return 成功返回 0,失败返回负错误码
*/
static int __init dma_buf_init(void) /* 子系统初始化函数,仅在 init 阶段使用 */
{
int ret; /* 保存各初始化步骤的返回值 */
/* 初始化 dma-buf 统计的 sysfs 导出;失败则终止,避免进入半初始化状态 */
ret = dma_buf_init_sysfs_statistics(); /* 建立 sysfs 统计节点 */
if (ret) /* 若失败则直接返回错误码 */
return ret; /* 向上层传播错误 */
/* 挂载 dma-buf 的伪文件系统;该挂载点承载 dma-buf 文件对象的 dentry 生命周期 */
dma_buf_mnt = kern_mount(&dma_buf_fs_type); /* 创建并挂载内核私有 mount */
if (IS_ERR(dma_buf_mnt)) /* 若挂载返回错误指针 */
return PTR_ERR(dma_buf_mnt); /* 返回对应负错误码 */
/* 初始化 debugfs 导出;用于调试观测,不影响核心功能语义 */
dma_buf_init_debugfs(); /* 建立 debugfs 节点 */
return 0; /* 初始化成功 */
}
subsys_initcall(dma_buf_init); /* 指定在 subsys 初始化阶段调用 dma_buf_init */
/**
* @brief 生成 dma-buf dentry 的动态显示名称
*
* 该函数被 VFS 在需要展示 dentry 名称时调用,通过 dentry->d_fsdata 取回 dmabuf,
* 并在 name_lock 保护下读取 dmabuf->name,最终使用 dynamic_dname 格式化输出。
*
* @param dentry 需要生成名称的目录项
* @param buffer 输出缓冲区
* @param buflen 输出缓冲区长度
* @return 返回由 dynamic_dname 生成的名称指针
*/
static char *dmabuffs_dname(struct dentry *dentry, char *buffer, int buflen)
{
struct dma_buf *dmabuf; /* dma-buf 核心对象指针(挂在 dentry->d_fsdata) */
char name[DMA_BUF_NAME_LEN]; /* 临时名称缓冲,用于拷贝 dmabuf->name */
ssize_t ret = 0; /* strscpy 返回值:>0 表示成功拷贝长度,<=0 表示失败或空 */
/* 从 dentry 私有数据取回 dmabuf 对象 */
dmabuf = dentry->d_fsdata; /* d_fsdata 由创建 dentry 时设置,指向 struct dma_buf */
/* 读取 dmabuf->name 需要并发保护,避免与更新/释放竞争 */
spin_lock(&dmabuf->name_lock); /* 自旋锁保护 name 指针与其内容的一致性 */
if (dmabuf->name) /* 若名称存在则拷贝到本地缓冲 */
ret = strscpy(name, dmabuf->name, sizeof(name)); /* 拷贝名称,避免直接暴露指针生命周期 */
spin_unlock(&dmabuf->name_lock); /* 结束保护区 */
/* 生成形如 "/<dentry-name>:<dmabuf-name>" 的动态名称;若无 dmabuf->name 则使用空串 */
return dynamic_dname(buffer, buflen, "/%s:%s",
dentry->d_name.name, ret > 0 ? name : ""); /* ret>0 才认为 name 有效 */
}
/**
* @brief dentry 释放回调:销毁 dma-buf 对象并回收资源
*
* 该回调在 dentry 生命周期结束时触发。释放前必须满足关键不变量:
* - 不存在仍在进行的 vmap 计数
* - 不存在仍活跃的回调状态(cb_in/cb_out)
* - attachments 列表应为空(否则说明仍被外部持有)
*
* @param dentry 被释放的目录项
*/
static void dma_buf_release(struct dentry *dentry)
{
struct dma_buf *dmabuf; /* 待释放的 dma-buf 对象 */
/* 从 dentry 私有数据取回 dmabuf */
dmabuf = dentry->d_fsdata; /* d_fsdata 指向 struct dma_buf */
if (unlikely(!dmabuf)) /* 若为空则无需处理(防御性检查) */
return; /* 直接返回 */
/* 强制要求不存在仍在进行的 vmap 映射引用 */
BUG_ON(dmabuf->vmapping_counter); /* 若非 0,说明映射引用不平衡,释放会导致严重错误 */
/* 强制要求不存在仍活跃的 fence 回调状态 */
BUG_ON(dmabuf->cb_in.active || dmabuf->cb_out.active); /* 若为真,说明异步状态机不一致 */
/* 拆除统计信息导出,避免外部观测接口继续引用该对象 */
dma_buf_stats_teardown(dmabuf); /* 释放与统计相关的资源 */
/* 调用 exporter 提供的 release,用于释放底层导出方持有的资源 */
dmabuf->ops->release(dmabuf); /* exporter 释放:通常涉及底层 buffer/fence/映射等 */
/* 若 resv 对象为内嵌(紧随 dmabuf 分配),则需要在此执行析构 */
if (dmabuf->resv == (struct dma_resv *)&dmabuf[1]) /* 判断 resv 是否指向内嵌区域 */
dma_resv_fini(dmabuf->resv); /* 完成 resv 对象析构 */
/* 释放时不应仍存在 attachment;若存在,提示严重生命周期错误 */
WARN_ON(!list_empty(&dmabuf->attachments)); /* attachment 非空意味着仍有外部引用关系未解除 */
/* 归还 exporter 模块引用,维护模块卸载语义一致性 */
module_put(dmabuf->owner); /* owner 为 exporter 模块,释放对应引用 */
/* 释放名称字符串(若存在) */
kfree(dmabuf->name); /* dmabuf->name 由 kmalloc 分配 */
/* 释放 dmabuf 主对象内存 */
kfree(dmabuf); /* dmabuf 本体释放,生命周期结束 */
}
/**
* @brief file release 回调:从 dma-buf 全局列表中摘除该文件关联对象
*
* @param inode 文件 inode
* @param file 文件对象
* @return 成功返回 0;若 file 不是 dma-buf 文件则返回 -EINVAL
*/
static int dma_buf_file_release(struct inode *inode, struct file *file)
{
/* 确认该 file 为 dma-buf file,避免误删 */
if (!is_dma_buf_file(file)) /* 检查 file 类型 */
return -EINVAL; /* 非 dma-buf 文件,拒绝处理 */
/* 从 dma-buf 全局列表中移除与该 file 关联的对象 */
__dma_buf_list_del(file->private_data); /* private_data 持有列表节点或关联对象指针 */
return 0; /* 释放流程完成 */
}
/**
* @brief dma-buf dentry 操作集
*
* 将 dentry 的动态命名与释放回调绑定到 dma-buf 的 dentry 上:
* - d_dname:用于动态生成显示名称
* - d_release:用于在 dentry 释放时销毁 dmabuf
*/
static const struct dentry_operations dma_buf_dentry_ops = {
.d_dname = dmabuffs_dname, /* 动态名称生成回调 */
.d_release = dma_buf_release, /* dentry 释放回调 */
};
/** @brief dma-buf 伪文件系统的挂载点,全局唯一,用于承载 dma-buf 的 dentry/文件对象 */
static struct vfsmount *dma_buf_mnt; /* 由 dma_buf_init 中 kern_mount 初始化 */
drivers/dma-buf/dma-fence.c DMA Fence(DMA 栅栏)同步机制
[功能概述] 为异步 DMA 硬件操作提供内核内的统一同步原语(可跨驱动、可跨进程边界通过用户态载体传递),并与 dma-buf/dma-resv 形成显式/隐式同步体系。
介绍
dma-fence 用 struct dma_fence 表示,是 Linux 内核中面向异步硬件工作(GPU 渲染、视频编解码、显示等)的同步原语:通过 dma_fence_init() 初始化,通过 dma_fence_signal()(或带时间戳的变体)标记完成;同一 context 上的 fence 具有全序关系,并可用于显式 fencing(sync_file)、子系统封装(如 DRM syncobj),以及隐式 fencing(挂在 dma_buf 的 dma_resv 上)。
历史与背景
这项技术为了解决什么问题而诞生
核心问题是:共享 buffer 的多设备/多驱动异步访问需要统一的"完成信号"语义,否则容易出现数据竞争、重复写、读到未完成结果等问题。dma-buf 文档明确把 dma-fence 定义为"指示异步硬件操作何时完成"的机制,并与 dma-resv 一起实现隐式同步。
重要里程碑或迭代点(从源码注释与近期讨论归纳)
- 2012 年左右进入主线生态:源码头部版权与作者信息显示该机制至少在 2012 年已形成并由多方维护。
- 跨驱动契约(cross-driver contract)与 lockdep 注解体系 :源码文档强调"跨驱动一致规则"、超时/恢复、以及用
dma_fence_begin_signalling()/dma_fence_end_signalling()标注可能影响dma_fence_signal()路径的临界区,以便 lockdep 检查死锁风险。 - 显式同步用户态载体 sync_file 成熟:sync_file 文档把它定义为 fences 的载体,用于驱动与用户态之间传递同步点,从而实现跨进程边界的显式 fencing。
- 新增"deadline hint"提示机制(优化调度/等待体验) :源码中专门描述了可通过
dma_fence_set_deadline(以及用户态 ioctl 间接路径)提供"紧迫性提示"。 - 2025 年关于 dma_fence 模块卸载/RCU 保护的持续改进 :LWN 转发的补丁讨论指出:fence 可能泄漏到外部驱动并在 signaled 后仍延迟释放,导致发行者模块卸载后
dma_fence_ops不可用而崩溃,补丁集尝试用 RCU 保护 ops 并允许 fence 更"自包含"。
社区活跃度与主流应用情况
- 主流应用:dma-buf 文档点名 DRM 大量使用 dma-buf 交换 buffer,并与 V4L2 等子系统交互;其中同步体系的三大原语就包含 dma-fence。
- 活跃度:内核官方文档页面近期仍在更新(docs.kernel.org 页面显示近期发布/抓取),并且 2025 年仍有围绕 dma_fence 的设计问题在 dri-devel 等社区持续讨论。
核心原理与设计
核心工作原理
- 对象模型 :
struct dma_fence表示一个同步点,具有"未完成/已完成"状态;初始化用dma_fence_init(),完成用dma_fence_signal()(或dma_fence_signal_timestamp()记录完成时间戳)。 - context + seqno 的全序语义 :通过
dma_fence_context_alloc()分配 context,同一 context 上 fence 全序。源码用一个全局atomic64计数器分配 context。 - 等待与唤醒路径 :
dma_fence_wait_timeout()会进行可睡眠检查,并在等待前调用dma_fence_enable_sw_signaling()触发"尽快完成"的软件信号支持(内部会调用dma_fence_ops.enable_signaling)。 - 回调机制:fence 支持注册 callback;源码也明确警告取消 callback 风险高(容易引入死锁/竞态),通常仅用于硬件卡死恢复等场景。
- 跨驱动契约约束:源码文档强调"必须在合理时间内完成""需要 hang recovery""等待场景下的锁/内存分配限制"等,目的是避免跨子系统组合时产生不可控死锁与回收路径问题。
主要优势
- 跨驱动同步契约统一:把"异步硬件完成"抽象成跨驱动可理解的对象,降低多子系统协作成本。
- 显式/隐式同步都能承载 :既能通过
sync_file走显式 fencing(跨进程传递),也能通过dma_resv走隐式 fencing(buffer 附带同步点)。 - 调试与可观测性 :源码导出 tracepoint(如
dma_fence_emit/dma_fence_signaled等),便于跟踪同步行为。
已知劣势、局限性与不适用点
- 设计上强依赖"发行者必须可靠完成/恢复":如果硬件/驱动无法保证 fence 在合理时间完成,或缺失 hang recovery,系统整体等待链条会放大风险。
- 回调取消与复杂锁层级容易出错:源码对取消 callback 的风险做了强提示;此外还需要遵循锁注解与 dma_resv 锁层级规则。
- 模块卸载边界问题:共享 buffer 会让 fence "流入外部驱动",signaled 后仍可能延迟释放,导致发行者模块卸载带来 ops 指针失效风险;社区仍在通过 RCU 等手段改进。
使用场景
首选场景(举例)
- 图形/显示管线:DRM 在进程/上下文之间交换 buffer,需要用 fence 表达"GPU 渲染完成/scanout 完成"等同步点。
- 媒体处理:与 V4L2 等协作时,在 producer/consumer 间传递 buffer,并用 fence 保证读写顺序。
- 用户态显式同步(Vulkan/OpenGL/媒体栈互操作) :用
sync_file把 fence 作为 fd 传递,实现跨进程显式 fencing。
不推荐使用的场景
- 纯驱动内部、无跨设备共享需求的简单同步:优先用 completion/waitqueue 等更直接的内核同步原语(更少契约约束、更少与 dma_resv/用户态交互复杂度)。
- 无法提供可靠超时与恢复路径的硬件任务:因为跨驱动等待链条会把"不可完成 fence"的风险放大到系统层面。
对比分析
| 维度 | dma_fence | completion / waitqueue(典型内核同步) | sync_file |
|---|---|---|---|
| 定位 | 异步 DMA 同步点(跨驱动契约) | 通用内核同步(多用于同一子系统/驱动内部) | fence 的用户态载体(fd 传递) |
| 实现方式 | struct dma_fence + ops + callback + wait |
waitqueue/completion 结构 + 唤醒 | file 对象包装一个/多个 fence,并提供导入导出流程 |
| 性能开销 | 创建轻量,但启用 signalling/等待会触发 ops 与回调管理;强调可观测性与契约约束 | 通常更直接、更少跨子系统约束 | 增加 file/fd 管理与跨进程传递成本 |
| 资源占用 | fence 对象 + 回调链 + 可能的追踪/注解;共享场景下生命周期更复杂 | 一般更少对象间外溢 | 额外的 file 结构与 fd 生命周期 |
| 隔离级别 | 可跨驱动、可跨进程边界(借助载体) | 通常局限在内核内部调用关系 | 面向用户态边界的显式同步 |
| "启动速度"(可理解为创建/投入使用的时延) | dma_fence_init() 初始化即可发布;但要遵循发布时机与 lockdep 注解规则 |
通常更快更直接 | 需要创建 file、安装 fd 等步骤 |
补充:drm_syncobj 属于 DRM 子系统提供的显式 fencing 原语,相比 sync_file,其特点是允许底层 fence 被更新(源码在 overview 中点到这一差异)。
总结
关键特性
struct dma_fence作为异步 DMA 完成信号的统一抽象;context 保证同一执行序列全序。- 支持等待/回调;等待前可触发软件 signalling;取消回调需极谨慎。
- 面向跨驱动组合的严格契约:超时与恢复、锁与内存分配上下文约束、lockdep 注解。
- 与 dma-buf/dma-resv 形成显式(sync_file)与隐式同步体系。
dma_fence_init_stub: 初始化并立即完成一个全局 stub fence,用于提供可用的默认同步对象
在内核子系统初始化阶段,构造一个全局的"stub fence" ,并把它立即 signal(完成) 。这样做的目的不是表示真实的异步 DMA 工作,而是提供一个始终已完成 、且具备合法 dma_fence 语义的默认对象,供框架在某些"需要 fence 但当前没有真实 fence"或"需要一个占位且永不阻塞"的路径中使用。
这段代码背后的关键点是 dma_fence 的跨驱动契约:
- fence 必须可等待、可 signal、可识别(driver/timeline 名称)
- fence 必须遵循统一的锁与回调规则,以便跨设备、跨子系统进行同步
stub fence 的存在可以让框架在缺省情况下仍保持这些规则成立。
核心原理概述
-
提供最小可用的
dma_fence_ops通过
dma_fence_stub_ops提供.get_driver_name与.get_timeline_name,确保该 fence 在调试/追踪时具备可识别的名称。 -
初始化 fence 的核心字段与锁
dma_fence_init()会把 fence 与 ops、lock、context、seqno 绑定,建立 fence 的基础身份与并发保护策略。 -
打开 signal 能力位并立即 signal
DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT允许 fence 走 signal 路径;随后立刻dma_fence_signal(),使该 fence 永远处于"已完成"状态。 -
在 subsys_initcall 阶段完成初始化
通过
subsys_initcall(dma_fence_init_stub)保证该 stub 在大量子系统使用 fence 之前就已就绪,避免早期路径拿到未初始化对象。
c
/*
* dma_fence_stub_get_name: 返回 stub fence 的固定名称
* @fence: 当前 fence 对象
*
* 该函数用于同时作为 driver name 与 timeline name 的获取回调,
* 以保证 stub fence 在调试、追踪与输出中具备可识别的名字。
*/
static const char *dma_fence_stub_get_name(struct dma_fence *fence)
{
return "stub";
}
/*
* dma_fence_stub_ops: stub fence 的操作集
*
* stub fence 并不表示真实硬件队列或真实时间线,
* 其目的只是提供最小的可识别信息,使其符合 dma_fence 的基本接口契约。
*/
static const struct dma_fence_ops dma_fence_stub_ops = {
.get_driver_name = dma_fence_stub_get_name, /* 返回驱动名称 */
.get_timeline_name = dma_fence_stub_get_name, /* 返回时间线名称 */
};
/*
* dma_fence_init_stub: 初始化并完成全局 stub fence
*
* 在内核子系统初始化阶段构造一个全局 dma_fence:
* - 使用 stub ops 与全局锁初始化其核心字段
* - 设置允许 signal 的标志位
* - 立即 signal,使其永久处于完成状态
*/
static int __init dma_fence_init_stub(void)
{
/*
* 初始化 dma_fence 核心字段:
* - &dma_fence_stub: 全局 stub fence 对象
* - &dma_fence_stub_ops: 操作集,用于获取名称等
* - &dma_fence_stub_lock: fence 内部并发保护锁
* - context=0, seqno=0: 使用固定上下文与序号作为占位身份
*
* 对单核 STM32H750 视角的关键点:
* - 单核不意味着无并发:内核仍存在抢占/中断等并发语义
* - 因此 fence 仍必须依赖锁来保证状态转换一致性
*/
dma_fence_init(&dma_fence_stub, &dma_fence_stub_ops,
&dma_fence_stub_lock, 0, 0);
/*
* 设置允许 signal 的标志位。
*
* DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT 用于控制 fence 是否允许进入 signal 路径,
* 避免某些 fence 类型在未准备好回调机制时被错误地 signal。
*/
set_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT,
&dma_fence_stub.flags);
/*
* 立即 signal,使该 fence 永久处于"已完成"状态。
*
* 这样任何对该 fence 的 wait 都不会阻塞,
* 可用于框架内部需要 fence 语义但不希望引入等待的缺省路径。
*/
dma_fence_signal(&dma_fence_stub);
return 0;
}
subsys_initcall(dma_fence_init_stub);
__dma_fence_init / dma_fence_init: 初始化 dma_fence 的身份、并发保护与回调容器
这段代码的核心作用是:把一个已分配的 struct dma_fence 对象初始化成"可被框架安全使用的同步原语实例"。它不负责 signal、不负责等待,只负责建立最关键的三类基础设施:
- 生命周期基础 :初始化引用计数
refcount,让 fence 能被跨子系统安全持有与释放。 - 并发一致性基础 :绑定
lock,规定后续所有状态转换(signal、回调链处理等)必须在该锁保护下完成。 - 语义比较基础 :设置
context与seqno,让 fence 之间可以通过dma_fence_later()这类比较函数建立有序关系(同一 context 内要求完全有序)。
核心原理概述
-
严格的前置条件检查(BUG_ON)
lock必须存在:否则后续 signal/wait/callback 的并发一致性不可保证。ops必须提供get_driver_name/get_timeline_name:这是跨驱动契约的一部分,用于诊断、跟踪与一致的接口语义。缺失会导致框架在 debug/trace/输出路径上出现不可接受的不确定性。
-
引用计数初始化(kref_init)
- fence 的引用计数是其"跨模块共享"的基本机制。
- 后续任何 attach/等待/回调注册都可能持有 fence,必须依赖 refcount 保证对象不被提前释放。
-
回调链表容器初始化(cb_list)
INIT_LIST_HEAD(&fence->cb_list)建立回调链表头。- 这使得
dma_fence_add_callback()能在 fence 未 signal 时挂回调;signal 时可遍历并执行。
-
并发保护锁绑定(fence->lock)
- 该指针定义 fence 的"互斥域"。
- signal 与 add_callback 的竞态处理依赖这把锁的语义:必须是 irqsafe,以便在可能的中断上下文中也能安全使用。
-
context/seqno 初始化(有序语义基础)
context表示执行上下文(例如某个硬件队列/时间线)。seqno在同一 context 内单调递增,用于比较"哪个 fence 更晚"。- 这为
dma_fence_later()提供数学意义上的偏序关系:同一 context 可全序比较,不同 context 不保证可比。
-
flags 与 error 初始化
flags保存 fence 的状态标志(例如是否允许 enable_signaling 等)。error = 0表示初始无错误,后续可以通过 signal 带错误完成语义。
-
trace 钩子
trace_dma_fence_init(fence)用于追踪初始化事件,便于定位生命周期与竞态问题。
c
/*
* __dma_fence_init: 初始化 dma_fence 的内部字段
* @fence: 需要初始化的 fence 对象
* @ops: fence 的操作集,提供跨驱动统一的接口实现入口
* @lock: fence 的 irqsafe 自旋锁,用于保护该 fence 的状态与回调链
* @context: fence 所属执行上下文(同 context 内的 fence 必须全序)
* @seqno: 该 context 内单调递增的序列号,用于 fence 先后比较
* @flags: fence 初始标志位集合
*/
static void
__dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops,
spinlock_t *lock, u64 context, u64 seqno, unsigned long flags)
{
/* lock 必须存在,否则无法保证 signal/callback 路径的并发一致性 */
BUG_ON(!lock);
/*
* ops 必须存在,且至少要能提供 driver 名与 timeline 名:
* 这既是调试/追踪要求,也是跨驱动契约的一部分。
*/
BUG_ON(!ops || !ops->get_driver_name || !ops->get_timeline_name);
/* 初始化引用计数,支持 fence 在跨子系统共享时的生命周期管理 */
kref_init(&fence->refcount);
/* 绑定操作集:后续查询、打印、以及可选的 enable_signaling 等行为通过 ops 扩展 */
fence->ops = ops;
/* 初始化回调链表头:用于 dma_fence_add_callback() 注册回调 */
INIT_LIST_HEAD(&fence->cb_list);
/* 绑定并发保护锁:规定 fence 的状态转换与回调链操作必须在该锁保护下进行 */
fence->lock = lock;
/* 设置 fence 的执行上下文与序列号,用于同一 context 内的先后比较 */
fence->context = context;
fence->seqno = seqno;
/* 设置初始标志位 */
fence->flags = flags;
/* 初始错误码为 0;若后续异步任务失败,可通过 error 语义完成 */
fence->error = 0;
/* 记录初始化事件,便于追踪 fence 生命周期 */
trace_dma_fence_init(fence);
}
/*
* dma_fence_init: 初始化一个自定义 fence(对外 API)
* @fence: 需要初始化的 fence 对象
* @ops: fence 的操作集
* @lock: fence 的 irqsafe 自旋锁
* @context: fence 所属执行上下文
* @seqno: 该 context 内单调递增序列号
*
* 该函数是对 __dma_fence_init 的封装,flags 固定为 0。
*/
void
dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops,
spinlock_t *lock, u64 context, u64 seqno)
{
/* flags=0 表示以默认初始标志初始化 fence */
__dma_fence_init(fence, ops, lock, context, seqno, 0UL);
}
EXPORT_SYMBOL(dma_fence_init);
dma_fence_signal_timestamp_locked / dma_fence_signal_timestamp / dma_fence_signal_locked / dma_fence_signal: 以原子方式将 fence 从"未完成"转换为"已完成",并执行回调链
这组函数实现的是 dma_fence 最核心的状态转换:signal(完成)。它们的共同语义是:
- 将 fence 的状态从"未 signaled"变为"已 signaled"(只允许一次)
- 设置完成时间戳(timestamp 版本)
- 解除所有
dma_fence_wait()的阻塞条件 - 执行所有通过
dma_fence_add_callback()注册的回调 - 并且用锁与原子位操作保证:signal 与回调注册之间不会出现丢回调、重复回调或释放后访问
这段代码最关键的技巧有三个:
- 一次性状态转换的原子保证 :
test_and_set_bit(SIGNALED_BIT)保证只有第一个调用者能成功 signal,后续调用直接失败返回。 - 回调链"先摘链后执行" :用
list_replace把fence->cb_list原子地换成一个临时链表cb_list,然后在锁保护外(或锁内的后半段)安全遍历执行,避免回调执行过程中破坏 fence 内部链表结构或造成死锁/递归。 - 锁的两层封装 :
*_locked版本要求调用者已持锁,非 locked版本负责spin_lock_irqsave/restore,保证在中断上下文与普通上下文都成立。
在 STM32H750(单核、无 MMU)视角下需要强调:
- 单核不意味着没有并发:中断、抢占、软中断/工作队列都能产生并发交错,因此
spin_lock_irqsave与原子位仍然必要。 irqsave的意义不是"多核同步",而是"禁止本 CPU 中断导致的重入竞态"。
c
/*
* dma_fence_signal_timestamp_locked: 在已持 fence->lock 的条件下 signal fence 并设置时间戳
* @fence: 需要 signal 的 fence
* @timestamp: 使用 CLOCK_MONOTONIC 域的完成时间戳
*
* 返回:
* 0 - 成功 signal(第一次生效)
* -EINVAL- fence 已经 signaled(再次调用不生效)
*/
int dma_fence_signal_timestamp_locked(struct dma_fence *fence,
ktime_t timestamp)
{
struct dma_fence_cb *cur, *tmp; /* 回调节点遍历指针 */
struct list_head cb_list; /* 临时回调链表头,用于"摘链执行" */
/* 断言调用者已持有 fence->lock,用于保证状态转换与回调链操作的原子性 */
lockdep_assert_held(fence->lock);
/*
* 将 SIGNALED 位从 0 原子设置为 1,并返回旧值。
* - 若旧值为 1,说明已经 signal 过,本次返回 -EINVAL
* - 若旧值为 0,本次成为"唯一有效 signal"
*
* 这是 fence "只能从未完成->已完成一次"的硬性语义保证。
*/
if (unlikely(test_and_set_bit(DMA_FENCE_FLAG_SIGNALED_BIT,
&fence->flags)))
return -EINVAL;
/*
* 在写入 timestamp 前,先把 fence->cb_list "摘出来"。
*
* list_replace 的效果:
* - 把 fence->cb_list 的链表内容整体移交给局部变量 cb_list
* - fence->cb_list 自身被替换成一个空链表头(仍然可用)
*
* 这样做的关键目的:
* 1) 回调执行过程中不再触碰 fence->cb_list,避免回调递归/并发修改破坏内部结构
* 2) signal 与 add_callback 的竞态能被严格界定:signal 成功后,后续回调注册必须走"立即执行/失败返回"的路径
*/
list_replace(&fence->cb_list, &cb_list);
/* 写入完成时间戳,并设置"timestamp 有效"标志位 */
fence->timestamp = timestamp;
set_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &fence->flags);
/* 记录 trace:用于性能分析与事件追踪 */
trace_dma_fence_signaled(fence);
/*
* 执行所有回调(安全遍历)。
*
* list_for_each_entry_safe 允许在遍历时删除节点;
* 这里先把 cur->node 重新 INIT_LIST_HEAD,确保该回调节点脱离链表,避免重复执行与悬挂指针。
*/
list_for_each_entry_safe(cur, tmp, &cb_list, node) {
INIT_LIST_HEAD(&cur->node); /* 标记该回调节点已从链表摘除 */
cur->func(fence, cur); /* 执行回调:由注册方提供的函数 */
}
return 0;
}
EXPORT_SYMBOL(dma_fence_signal_timestamp_locked);
/*
* dma_fence_signal_timestamp: 在未持锁条件下 signal fence,并设置指定时间戳
* @fence: 需要 signal 的 fence
* @timestamp: 完成时间戳(CLOCK_MONOTONIC 域)
*
* 该函数负责加锁(irqsave)并调用 *_locked 版本完成实际工作。
*/
int dma_fence_signal_timestamp(struct dma_fence *fence, ktime_t timestamp)
{
unsigned long flags; /* irqsave 保存的中断标志 */
int ret; /* 返回值 */
/* fence 为空属于调用者错误,返回 -EINVAL 并产生警告 */
if (WARN_ON(!fence))
return -EINVAL;
/*
* 使用 spin_lock_irqsave:
* - 自旋锁保证与其他上下文对同一 fence 的并发操作互斥
* - irqsave 防止本 CPU 中断打断导致的重入竞态
*/
spin_lock_irqsave(fence->lock, flags);
ret = dma_fence_signal_timestamp_locked(fence, timestamp); /* 真正的状态转换与回调执行 */
spin_unlock_irqrestore(fence->lock, flags);
return ret;
}
EXPORT_SYMBOL(dma_fence_signal_timestamp);
/*
* dma_fence_signal_locked: 在已持锁条件下 signal fence(使用当前时间戳)
* @fence: 需要 signal 的 fence
*
* 这是对 dma_fence_signal_timestamp_locked 的简化封装。
*/
int dma_fence_signal_locked(struct dma_fence *fence)
{
/* 使用 ktime_get() 获取当前 CLOCK_MONOTONIC 时间戳 */
return dma_fence_signal_timestamp_locked(fence, ktime_get());
}
EXPORT_SYMBOL(dma_fence_signal_locked);
/*
* dma_fence_signal: 在未持锁条件下 signal fence(使用当前时间戳),并标注 signalling 区间
* @fence: 需要 signal 的 fence
*
* 相比 dma_fence_signal_timestamp,该函数额外使用:
* - dma_fence_begin_signalling / dma_fence_end_signalling
* 用于标注"可能导致 fence 完成"的代码区间,辅助避免死锁与锁依赖问题(lockdep/规则契约)。
*/
int dma_fence_signal(struct dma_fence *fence)
{
unsigned long flags; /* irqsave 保存的中断标志 */
int ret; /* signal 返回值 */
bool tmp; /* begin_signalling 的返回值,用于 end_signalling 配对 */
/* fence 为空属于调用者错误 */
if (WARN_ON(!fence))
return -EINVAL;
/*
* 标注进入 signalling 区间。
* 该机制属于跨驱动契约的一部分,用于帮助识别"等待路径与完成路径"的锁依赖关系,降低死锁风险。
*/
tmp = dma_fence_begin_signalling();
/* 加锁并完成实际 signal 逻辑 */
spin_lock_irqsave(fence->lock, flags);
ret = dma_fence_signal_timestamp_locked(fence, ktime_get());
spin_unlock_irqrestore(fence->lock, flags);
/* 标注退出 signalling 区间,与 begin 成对 */
dma_fence_end_signalling(tmp);
return ret;
}
EXPORT_SYMBOL(dma_fence_signal);