Linux DMA-Buf驱动框架

news2024/11/25 14:41:51

一、DMABUF 框架

dmabuf 是一个驱动间共享buf 的机制,他的简单使用场景如下:

在这里插入图片描述

用户从DRM(显示驱动)申请一个dmabuf,把dmabuf 设置给GPU驱动,并启动GPU将数据输出到dmabuf,GPU输出完成后,再将dmabuf设置到DRM 驱动,完成画面的显示。

在这个过程中通过共享dmabuf的方式,避免了GPU输出数据拷贝到drm frame buff的动作。

如下所示,dmabuf 框架分为用户层和驱动层,用户层可以通过 /dev/dmabuf_heap/xxx节点,从名称为xxx的dma heap 中申请dmabuf。申请到的dmabuf 在用户层的视角就是一个文件,并由fd 标识一个dmabuf。将fd 通过DRM、GPU接口传给驱动,驱动就能共享这个dmabuf。

以下是一个简单的dmabuf 的示例代码:

int fd, dmabuf_fd;
struct dma_heap_allocation_data data;
struct pollfd fds;

data.len = 1024 * 1024 * 4;
//打开dma heap
fd = open("/dev/dma-heap/xxx");

//从dma heap 申请dmabuf
ioctl(fd, DMA_HEAP_IOCTL_ALLOC, &data);

//将dmabuf的fd 设置到gpu进行处理
set_dmabuf_to_gpu(data.fd);

//等待gpu 输出完毕
fds.fd = data.fd;
fds.events = POLLIN | POLLOUT;
poll(fds, 1, TIMEOUT);

//将dmabuf 设置到drm显示
set_dmabuf_to_drm(data.fd);

//等待显示完成
poll(fds, 1, TIMEOUT);

//释放dmabuf
close(data.fd);

二、DMA Heap

dma heap 就是一个dmabuf 内存池,让用户可以从内存池中申请dmabuf。其代码主要在dma-heap.c,设备驱动可以创建自己的dma heap,从而提供给用户申请dmabuf。例如DRM驱动可以创建一个DRM dma heap。DRM驱动最重要的就算实现struct dma_heap_ops 对象,这个对象需要实现allocate() 函数,即当用户从dma heap 申请dmabuf 时,DRM驱动要如何分配真实的物理内存。

struct dma_heap_ops {
	int (*allocate)(struct dma_heap *heap,
			unsigned long len,
			unsigned long fd_flags,
			unsigned long heap_flags);
};

struct dma_heap {
	const char *name;
	const struct dma_heap_ops *ops; //主要实现申请dmabuf的回调函数
	void *priv;
	dev_t heap_devt;
	struct list_head list;
	struct cdev heap_cdev;
};

dma-heap.c 中其他的代码主要是实现一个简单设备驱动,提供接口给用户。

三、dmabuf

3.1、dmabuf使用场景

在dmabuf 的使用场景中,有两种驱动:exporter 和 importer。

  • exporter 是dmabuf 的提供者,是实现dma heap的驱动程序,负责dmabuf 对应的物理内存的申请、释放、映射等实现。
  • importer 是dmabuf的使用者,是使用dmabuf 进行输入输出数据的驱动程序,他不关心dmabuf的申请释放,只需要往dmabuf 里读写数据即可。

像上述例子中,DRM驱动首先是exporter,允许用户从dma heap申请内存,又是importer,从dmabuf 中读取数据显示到屏幕。而GPU是纯纯的importer,向dmabuf 中写入数据。

这两种角色的关系如下图所示:

在这里插入图片描述

从上述图可见dma_buf_ops 的实现至关重要。所以接下来我们关注dmabuf是如何被创建的。

3.2、dmabuf的创建

dmabuf 是如何从dma heap 中被申请出来的?这部分主要是在allocate回调函数实现的,在大部分驱动中,allocate回调函数中会从物理内存中申请内存,并 调用dma_buf_export() 创建一个dmabuf 对象。

所以我们的重点将分析 dma_buf_export() 函数是如何创建一个dmabuf 对象的。

首先还是看dmabuf 的结构体定义:

struct dma_buf {
	size_t size;
	struct file *file;                 //匿名文件,代表该dmabuf,暴露给用户从而支持跨驱动传输
	struct list_head attachments;      //attachment 链表
	const struct dma_buf_ops *ops;     //重要的回调函数
	void *vmap_ptr;                    //dmabuf kernel 地址
	struct dma_resv *resv;             //保留区,用于存放dma fence对象
	/* poll support */
	wait_queue_head_t poll;            //等待队列,用于poll
	struct dma_buf_poll_cb_t {
		struct dma_fence_cb cb;
		wait_queue_head_t *poll;

		__poll_t active;
	} cb_excl, cb_shared;              //用于poll、dma fence
};

以下是dma_buf_export() 的简略版,很简单就是根据exp_info 初始化dmabuf对象,并创建一个文件,将dmabuf 与文件绑定起来。

struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
{
	//初始dmabuf 对象
	dmabuf = kzalloc(alloc_size, GFP_KERNEL);
	dmabuf->priv = exp_info->priv;
	dmabuf->ops = exp_info->ops;
	dmabuf->size = exp_info->size;
	dmabuf->exp_name = exp_info->exp_name;
	dmabuf->owner = exp_info->owner;
	spin_lock_init(&dmabuf->name_lock);
	init_waitqueue_head(&dmabuf->poll);
	dmabuf->cb_excl.poll = dmabuf->cb_shared.poll = &dmabuf->poll;
	dmabuf->cb_excl.active = dmabuf->cb_shared.active = 0;

	if (!resv) {
		resv = (struct dma_resv *)&dmabuf[1];
		dma_resv_init(resv);
	}
	dmabuf->resv = resv;

    //初始化file
	file = dma_buf_getfile(dmabuf, exp_info->flags);
	file->f_mode |= FMODE_LSEEK;
	dmabuf->file = file;

	mutex_init(&dmabuf->lock);
	INIT_LIST_HEAD(&dmabuf->attachments);
	
    //添加到全局链表
	mutex_lock(&db_list.lock);
	list_add(&dmabuf->list_node, &db_list.head);
	mutex_unlock(&db_list.lock);

	return dmabuf;
}

3.3、dma_buf_ops

exporter驱动只关注struct dma_buf_export_info 对象即可,最重要的是struct dma_buf_ops对象的实现,这点需要根据具体的驱动实现。所以下面分析这些回调函数的含义是什么:

struct dma_buf_ops {
	//判断当前设备是否能够访问dmabuf的物理内存,一些物理内存只能由指定的设备访问如vram。若设备可以访问改物理内存,则返回一个attachment代表此次访问
	int (*attach)(struct dma_buf *, struct dma_buf_attachment *);

	//释放之前获取的attachment
	void (*detach)(struct dma_buf *, struct dma_buf_attachment *);

	//importer 调用这个函数,锁定dmabuf的物理内存,使其不能被迁移
	int (*pin)(struct dma_buf_attachment *attach);

	//解锁物理内存
	void (*unpin)(struct dma_buf_attachment *attach);

    //将dmabuf的物理内存映射到importer的地址空间,表示importer要开始访问物理内存
    //因为exporter要让所以attach的设备都能访问,所以可能要将物理内存移动到合适的地址,所以函数可能休眠
    //返回一个sg_table,表示物理地址散列表
	struct sg_table * (*map_dma_buf)(struct dma_buf_attachment *,
					 enum dma_data_direction);

    //解除映射并释放sg_table
	void (*unmap_dma_buf)(struct dma_buf_attachment *,
			      struct sg_table *,
			      enum dma_data_direction);

	//释放dmabuf,exporter在这个函数释放私有数据
	void (*release)(struct dma_buf *);

	//importer在使用cpu读取dmabuf前,调用该接口让exporter 确保数据在内存上且cpu能读取到正确的数据
	int (*begin_cpu_access)(struct dma_buf *, enum dma_data_direction);

	//结束cpu 访问
	int (*end_cpu_access)(struct dma_buf *, enum dma_data_direction);

	//将dmabuf 物理内存map 到用户地址空间
	int (*mmap)(struct dma_buf *, struct vm_area_struct *vma);

    //将dmabuf 物理内存map到内核地址空间
	void *(*vmap)(struct dma_buf *);
	void (*vunmap)(struct dma_buf *, void *vaddr);
};

dmabuf框架将一个驱动访问物理内存的动作拆分成这么多个步骤,目的就是为了多个设备能共享一个物理内存,而每个设备的访问能力,访问地址空间都可能不一样,这就需要将访问过程细细拆分,协调好每个设备的访问顺序和关系。

四、dma-fence

dma fence 是用于做同步的。考虑以下场景:

一个dmabuf,先由GPU完成渲染,然后再交给DRM进行显示输出。那么GPU渲染完成后,如何通知DRM进行显示输出呢?也就是GPU和DRM之前如何进行同步?这就需要引入fence用于设备间的同步,fence用于表示一个操作的完成状态,故fence有两个状态,not done和done。

首先GPU在开始渲染操作前,创建一个fence,注册回调函数,将fence添加到dmabuf 中,随后DRM 等待该fence done。当GPU渲染完成中断上来后,会通知fence done。随后DRM线程被唤醒,进行显示操作。

另外,dma fence还需要考虑多设备访问的情况,即可能有多个设备在等待fence完成,那么fence就必须支持多个设备的等待。

那么就先看dma fence的定义:

struct dma_fence {
	spinlock_t *lock;
	const struct dma_fence_ops *ops;
	union {
		struct list_head cb_list;  //回调函数链表,每个等待fence的驱动,都需要注册一个回调节点到该链表,当fence done时,会遍历该链表执行所有驱动的回调函数。
		/* @cb_list replaced by @timestamp on dma_fence_signal() */
		ktime_t timestamp;
		/* @timestamp replaced by @rcu on dma_fence_release() */
		struct rcu_head rcu;
	};
	u64 context;
	u64 seqno;
	unsigned long flags;
	struct kref refcount;
	int error;
};

如图所示:GPU线程会在操作dmabuf 前,创建fence,并等待fence完成,同时DRM也会等待该fence完成。当GPU渲染完成中断产生后,会调用fence done,依次唤醒GPU、DRM线程,GPU线程此时就可以继续下一帧图像的渲染,而DRM就可以将已经完成渲染的图像显示到屏幕。

在这里插入图片描述

这个过程中调用的接口有:

  1. dma_fence_init():初始化一个dma fence对象
  2. dma_resv_reserve_shared() :从dma resv 中保留一个share fence 指针
  3. dma_resv_add_shared_fence():将dma fence添加到resv 对象
  4. dma_fence_default_wait():向dma fence注册回调函数dma_fence_default_wait_cb,并睡眠等待dma fence完成
  5. dma_fence_signal():标志dma fence 完成,并回调dma fence 中的所有回调函数

其中有一个叫dma_resv的对象,简单来说dma_resv 是一个存放dma fence的地方,一个dmabuf 可能同时有若干个dma fence,且dma fence还有共享和独占两种。dma_resv可以理解为一块内存区域,专门存放dma fence的,故要将dma fence添加到dmabuf时,要先调用dma_resv_reserve_shared() 预留出dma fence的位置,然后再调用dma_resv_add_shared_fence() 添加到dma resv。

五、poll

前面所述都是在内核态,但对于用户来说,也希望获取到设备的同步信息。例如在本文一开始的例子中,用户会使用poll 系统调用等待gpu渲染完成。这一切都是由dma_buf_fops来实现的。

在3.2中提到dmabuf的创建中,有一个步骤会创建匿名文件,这个匿名文件就是用于暴露给用户的接口。这个文件代表了一个dmabuf,用户通过该文件的fd可以操作该dmabuf的一些功能,dma_buf_fops是所有dmabuf 共享的file_operations,其中就包括poll的实现。

当用户调用poll 系统调用等待dmabuf时,会遍历dmabuf 上的所有fence,并将回调函数dma_buf_poll_cb注册到每一个fence上,并进入休眠。当有任意一个fence done时,就会唤醒用户线程,从而退出poll。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1826451.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Node.js和npm的安装及配置

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞 I/O 的模型。 npm(node package manager)是一个 Node.js 包管理和分发工具,也是整个 Node.js 社区最流行、支持第三方模块最多的包管理器。使…

HTML前端

html 超文本标记语言 文本&#xff1a;文字字符 超文本&#xff1a;网页内容 标记&#xff1a;标签 标识 提供许多标签&#xff0c;不同标签功能不同&#xff0c;网页就是通过这些标签描述出来的&#xff0c;最终由浏览器解释运行我们看到的网页 <!-- html注释<!DO…

kettle从入门到精通 第七十课 ETL之kettle kettle数据校验,脏数据清洗轻松拿捏

场景&#xff1a;输入在指定的错误(错误应涵盖数据类型不匹配的情况)行数内&#xff0c;trans不报错&#xff0c;但通过错误处理步骤捕捉&#xff0c;并记入文件&#xff0c;整个数据管线正常完成直至处理完最后一个输入行。 解决方案&#xff1a;使用步骤【数据检验】进行处理…

鸿蒙轻内核M核源码分析系列二一 03 文件系统LittleFS

2.2 文件信息数组操作 函数LfsAllocFd()设置文件信息数组元素信息。参数fileName为文件路径信息&#xff0c;传出参数fd为文件描述符即数组索引。遍历文件信息数组&#xff0c;遍历到第一个未使用的元素标记其为已使用状态&#xff0c;设置文件路径信息&#xff0c;把数组索引…

2000-2023年各省年末常住人口数据(无缺失)

2000-2023年各省年末常住人口数据&#xff08;无缺失&#xff09; 1、时间&#xff1a;2000-2023年 2、来源&#xff1a;国家统计局、各省年鉴 3、指标&#xff1a;年末常住人口 4、范围&#xff1a;31省 5、指标解释&#xff1a; 年末人口数指每年12月31日24时的人口数。…

对抗式生成模仿学习(GAIL)

目录 1 预先基础知识 1.1 对抗生成网络&#xff08;GAN&#xff09; 1.1.1 基本概念 1.1.2 损失函数 1.1.2.1 固定G&#xff0c;求解令损失函数最大的D 1.1.2.2 固定D&#xff0c;求解令损失函数最小的G 1.2 对抗式生成模仿学习特点 2 对抗式生成模仿学习&#xff08;…

Java数据库编程

引言 在现代应用开发中&#xff0c;与数据库交互是不可或缺的一部分。Java提供了JDBC&#xff08;Java Database Connectivity&#xff09; API&#xff0c;允许开发者方便地连接到数据库并执行SQL操作。本文将详细介绍Java数据库编程的基础知识&#xff0c;包括JDBC的基本概念…

为什么 JavaScript 在国外逐渐用于前端+后端开发

这个问题其实没人能给出可证伪的结论&#xff0c;那不如干脆给一个感性的答案: 因为阿里“不争气”。 确切的说&#xff0c;因为阿里的nodejs团队没卷赢&#xff0c;至少暂时还没卷赢&#xff0c;没拿到真正有价值的业务场景&#xff0c;做出真正有说服力的案例项目。刚好我有…

如何进行LLM大模型推理优化

解密LLM大模型推理优化本质 一、LLM推理的本质以及考量点 LLM推理聚焦Transformer架构的Decoder以生成文本。过程分两步&#xff1a;首先&#xff0c;模型初始化并加载输入文本&#xff1b;接着&#xff0c;进入解码阶段&#xff0c;模型自回归地生成文本&#xff0c;直至满足…

邮件钓鱼--有无SPF演示--Swaks

目录 临时邮箱网址: Swaks 简单使用说明&#xff1a;(kali自带) 操作流程: 无SPF:(直接伪造发信人) 演示1 演示2 演示3 ​编辑 有SPF:--演示 临时邮箱网址: http://24mail.chacuo.net/ https://www.linshi-email.com/ Swaks 简单使用说明&#xff1a;(kali自带) -t –t…

专题六——模拟

目录 一替换所有的问号 二提莫攻击 三N字形变换 四外观数列 五数青蛙 一替换所有的问号 oj链接&#xff1a;替换所有的问号 思路&#xff1a;简单模拟&#xff1b;注意i0和in是处理越界问题就行&#xff01;&#xff01; class Solution { public:string modifyString…

基于scikit-learn的机器学习分类任务实践——集成学习

一、传统机器学习分类流程与经典思想算法简述 传统机器学习是指&#xff0c;利用线性代数、数理统计与优化算法等数学方式从设计获取的数据集中构建预测学习器&#xff0c;进而对未知数据分类或回归。其主要流程大致可分为七个部分&#xff0c;依次为设计获取数据特征集&#x…

Reactor 网络模型、Java代码实例

文章目录 1. 概述2. Reactor 单线程模型2.1 ByteBufferUtil2.2 服务端代码2.3 客户端2.4 运行截图 3. Reactor多线程模型3.1 服务端代码3.2 运行截图 4. 主从 Reactor多线程模型4.1 服务端代码4.2 运行截图 参考文献 1. 概述 在 I/O 多路复用的场景下&#xff0c;当有数据处于…

ChatTTS-WebUI测试页面项目

概述 分享可以一个专门为对话场景设计的文本转语音模型ChatTTS&#xff0c;例如LLM助手对话任务。它支持英文和中文两种语言。最大的模型使用了10万小时以上的中英文数据进行训练。在HuggingFace中开源的版本为4万小时训练且未SFT的版本. 该模型能够预测和控制细粒度的韵律特…

跪求大数据把我推给做投资交易的红薯!

在qq群里认识了君诺金融Juno Markets外汇交易平台的业务经理&#xff0c;平台上大剌剌的打出20%交易返现活动&#xff0c;一时听信了他们的话在该平台有开户入金做交易&#xff0c;做了这家平台的代理&#xff0c;然而君诺金融Juno Markets平台却不给佣金&#xff0c;我都是属于…

浏览器必备插件:最新Allow copy万能网页复制下载,解锁网页限制!

今天阿星给大家安利一个超级实用的小工具&#xff0c;专治那些“禁止复制”的网页文字。学生党、资料搜集狂人&#xff0c;你们有福了&#xff01; 想象一下&#xff0c;你在网上冲浪&#xff0c;突然遇到一篇干货满满的文章&#xff0c;正想复制下来慢慢品味&#xff0c;结果…

值传递和址传递

值传递 上面的代码是想要交换x&#xff0c;y的值&#xff0c;把x&#xff0c;y传递给swap函数之后&#xff0c;执行下面的操作&#xff1a; 在swap中a和b交换了&#xff0c;但是和x&#xff0c;y没有关系&#xff0c;所以x&#xff0c;y在main中不会变。 址传递 下面再看把x…

springcloud gateway转发websocket请求的404问题定位

一、问题 前端小程序通过springcloud gateway接入并访问后端的诸多微服务&#xff0c;几十个微服务相关功能均正常&#xff0c;只有小程序到后端推送服务的websocket连接建立不起来&#xff0c;使用whireshark抓包&#xff0c;发现在小程序通过 GET ws://192.168.6.100:8888/w…

Apple Intelligence 横空出世!它的独家秘诀在哪里?

在 WWDC 2024 大会上&#xff0c;苹果公司揭晓了自家的生成式 AI 项目——Apple Intelligence&#xff0c;其策略核心在于采用 ⌈ 更为聚焦的小型模型 ⌋ &#xff0c;而非盲目追求大模型的普遍趋势。横空出世的它究竟有什么过人之处&#xff1f;一文带你探究竟&#xff01;生成…

[DDR4] DDR1 ~ DDR4 发展史导论

依公知及经验整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《深入理解DDR4》 内存和硬盘是电脑的左膀右臂&#xff0c; 挑起存储的大梁。因为内存的存取速度超凡地快&#xff0c; 但内存上的数据掉电又会丢失&#xff0c;一直其中缓存的作用&#xff0c;就像是我们的工…