Linux DMA-Buf驱动框架

一、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;            //等待队列,用于pollstruct 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;//初始化filefile = 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 *);//释放之前获取的attachmentvoid (*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_tablevoid (*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.rhkb.cn/news/350771.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

javaWeb项目-ssm+vue网上租车系统功能介绍

本项目源码:java-基于ssmvue的网上租车系统源码说明文档资料资源-CSDN文库 项目关键技术 开发工具:IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架:ssm、Springboot 前端:Vue、ElementUI 关键技术:springboot、…

Perl 语言学习进阶

一、如何深入 要深入学习Perl语言的库和框架,可以按照以下步骤进行: 了解Perl的核心模块:Perl有许多核心模块,它们提供了许多常用的功能。了解这些模块的功能和用法是深入学习Perl的第一步。一些常用的核心模块包括:S…

ArcGIS 10.2软件安装包下载及安装教程!

今日资源:ArcGIS 适用系统:WINDOWS 软件介绍: ArcGIS是一款专业的电子地图信息编辑和开发软件,提供一种快速并且使用简单的方式浏览地理信息,无论是2D还是3D的信息。软件内置多种编辑工具,可以轻松的完成…

docker-compose部署FastDFS分布式文件系统

文章目录 一、技术选型二、fastDFS组成部分三、docker-compose文件四、客户端nginx配置五、存储器spring Boot集成参考文献 一、技术选型 还有一个更好的google FS(但是他不开源,我也没找到社区版一类的可以不要钱使用的)。 最后考虑到我们存…

非对称加密系统解析

目录 1. 概述 2. 非对称加密标准 2.1 RSA 2.2 SM2 2.2.1 SM2私钥 2.2.2 SM2公钥 2.2.3 加密数据格式 2.2.4 签名数据格式 1. 概述 非对称加密中,密钥分为加密密钥和解密密钥两种。发送者用加密密钥对消息进行加密,接收者用解密密钥对密文进行解密…

【redis的基本数据类型】

基本数据类型 Redis的基本数据类型有五种,分别是 StringListHashSetSortedSet 这些基本的数据类型构成了其他数据类型的基石,而这些基本数据类型又对应着不同的底层实现,不同的底层实现往往是针对不同的使用场景做的特殊的优化,…

# RocketMQ 实战:模拟电商网站场景综合案例(六)

RocketMQ 实战:模拟电商网站场景综合案例(六) 一、RocketMQ 实战 :项目公共类介绍 1、ID 生成器 :IDWorker:Twitter 雪花算法。 在 shop-common 工程模块中,IDWorker.java 是 ID 生成器公共类…

第 18章 安全架构设计理论与实践

安全架构是架构面向安全性方向上的一种细分,可关注三个安全方面,即产品安全架构、安全技术体系架构和审计架构,这三个方面可组成三道安全防线。本章主要分析安全威胁、介绍安全模型,在此基础上,就系统、信息、网络和数…

mysql和redis的双写一致性问题

一,使用方案 在使用redis作为缓存的场景下,我们一般使用流程如下 二,更新数据场景 我们此时修改个某条数据,如何保证mysql数据库和redis缓存中的数据一致呢? 按照常规思路有四种办法,1.先更新mysql数据&a…

tcp协议机制的总结(可靠性,提高性能),基于tcp的应用层协议,用udp如何实现可靠传输

目录 总结 引入 可靠性 ​编辑 分析 三次握手 提高性能 其他 常见的基于tcp应用层协议 用udp实现可靠传输 总结 引入 为什么tcp要比udp复杂的多? 因为它既要保证可靠性,又要兼顾性能 可靠性 分析 其中,序列号不止用来排序,还可以用在重传时去重 确认应答是机制中的…

嵌入式软件工程师入何突破瓶颈?

各位关注嵌入式软件工程师发展的朋友们,下面来探讨一下嵌入式软件工程师该如何突破瓶颈。首先要强调的是,不要仅仅将自己局限在嵌入式软件工程师这一角色定位上。 事实上,嵌入式软件工程师已经掌握了诸多业务层面的内容,完全有能力…

硬件SPI读写W25Q64

硬件SPI读写W25Q64 接线图(和软件SPI一样) 使用SPI1,SCK,接PA5;MISO,接PA6;MOSI,接PA7;NSS,可接PA4。 接线图对应:PA5接CLK引脚,PA6…

34 Debian如何配置ELK群集

作者:网络傅老师 特别提示:未经作者允许,不得转载任何内容。违者必究! Debian如何配置ELK群集 《傅老师Debian知识库系列之34》——原创 ==前言== 傅老师Debian知识库特点: 1、拆解Debian实用技能; 2、所有操作在VMware虚拟机实测完成; 3、致力于最终形成Debian知识手…

ChatGPT魔法背后的原理:如何做到词语接龙式输出?

介绍 我们都知道 ChatGPT 是 AIGC 工具,其实就是生成式人工智能。大家有没有想过这些问题 🤔️: 1、我们输入一段话,就可以看见它*噼里啪啦的一顿输出*,那么它的原理到底是什么? 2、到底它是怎么锁定这些…

GitLab教程(二):快速上手Git

文章目录 1.将远端代码克隆到本地2.修改本地代码并提交到远程仓库3.Git命令总结git clonegit statusgit addgit commitgit pushgit log 首先,我在Gitlab上创建了一个远程仓库,用于演示使用Gitlab进行版本管理的完整流程: 1.将远端代码克隆到本…

宝藏速成秘籍(7)堆排序法

一、前言 1.1、概念 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法 。堆是一个近似 完全二叉树 的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。 1.2、排…

重生之 SpringBoot3 入门保姆级学习(19、场景整合 CentOS7 Docker 的安装)

重生之 SpringBoot3 入门保姆级学习(19、场景整合 CentOS7 Docker 的安装) 6、场景整合6.1 Docker 6、场景整合 6.1 Docker 官网 https://docs.docker.com/查看自己的 CentOS配置 cat /etc/os-releaseStep 1: 安装必要的一些系统工具 sudo yum insta…

React state(及组件) 的保留与重置

当在树中相同的位置渲染相同的组件时&#xff0c;React 会一直保留着组件的 state return (<div><Counter />{showB && <Counter />} </div> ) // 当 showB 为 false, 第二个计数器停止渲染&#xff0c;它的 state 完全消失了。这是因为 React…

Github 2024-06-14 开源项目日报Top10

根据Github Trendings的统计,今日(2024-06-14统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量JavaScript项目2Python项目2非开发语言项目2TypeScript项目1Dart项目1Rust项目1Lua项目1Java项目1Jupyter Notebook项目1从零开始构建你喜爱的技…