DRM 驱动 mmap 详解:(二)CMA Helper_drm cma-程序员宅基地

技术标签: kernel  linux  DRM (Direct Rendering Manager)  drm  

没有 mmap 的 dumb buffer 是没有灵魂的!

前言

在之前的《DRM GEM 驱动程序开发(dumb)》中,我们学习了如何编写一个最简单的 GEM 驱动程序,该驱动程序直接使用了 DRM 现成的 CMA Helper 函数来实现 mmapdumb_create 回调接口。

在上一篇《DRM 驱动 mmap 详解:(一)预备知识》中,我们学习了 Linux 驱动常用的 mmap 实现方法,即 一次性映射(remap_pfn_range)按需映射(page fault)。在 DRM 驱动中,CMA Helper 是一次性映射的典型代表,因此本篇我们将仿照 CMA Helper 的具体实现,自己动手写一个 GEM CMA 驱动,以此来展示一次性映射在 DRM 驱动中的典型应用。

CMA Helper

CMA 是 Contiguous Memory Allocator 的缩写,它本身指代的是一种内存分配器(或内存分配策略),专用于分配物理连续的大块内存,以满足大内存需求的设备(如 Display、Camera)。CMA 除了具有内存分配的功能外,还具有内存迁移的功能,使得同一块 CMA 区域既可以被系统使用也可以被专用的 DMA 设备占用,从而大大提高了内存的使用率。要想使用 CMA 内存,需要在内核配置中开启 CONFIG_CMA 配置宏。

CMA helper 则是 DRM 驱动中一组通用的 GEM API,专用于分配、访问物理连续的系统内存,对于那些不带 IOMMU 或不具有专用显存的 Display 硬件而言,极大的方便了它们的 DRM 驱动开发,其中最典型的例子就是 tinydrm 驱动。CMA helper 最早由 Sascha Hauer(来自德国 Pengutronix 公司)基于三星 Exynos 平台开发,并于 2012 年 9 月合入 linux-3.7 主线。

需要注意的是,DRM 中的 CMA helper 和 CMA 本身没有直接关系, 即使当前内核没有使能 CONFIG_CMA,也不影响 DRM CMA helper 的使用。CMA helper 的内存分配接口使用的是 dma_alloc_wc(),而内核 CMA 分配器本身是和 DMA 子系统无缝衔接的,当内核开启 CONFIG_CMA 时,dma_alloc_wc() 后端可以采用 CMA 来分配一致性内存;当内核关闭 CONFIG_CMA 配置时,dma_alloc_wc() 则采用系统默认的页分配器来分配连续的物理内存。至于为何要用“CMA”关键字来给 DRM helper 函数命名,这里直接引用作者原话:

The code technically does not depend on CMA as the backend allocator, the name has been chosen because cma makes for a nice, short but still descriptive function prefix.

—— 摘自:[PATCH v4] DRM: add drm gem CMA helper
本来 Sascha 想用 “dma_alloc” 这个关键字,但后来发现这会使函数名变得特别啰嗦,因此最终决定使用 “CMA” 关键字代替。

DRM CMA helper 为我们提供了如下常用的 GEM API:

  • drm_gem_cma_mmap()
  • drm_gem_cma_dumb_create()
  • drm_gem_cma_free_object()

上一篇我们了解到,一次性映射的典型做法是在 mmap 回调函数中通过调用 remap_pfn_range() 这类映射函数,来直接将已分配好的物理内存一次性映射到用户空间。因此我们不难猜测,drm_gem_cma_mmap() 内部一定调用了 remap_pfn_range() 这类映射接口,而内存分配动作一定是在 drm_gem_cma_dumb_create() 中完成的,事实也确实如此。

接下来我们就自己动手写一个 DRM CMA 驱动。

驱动程序

本源码以《DRM GEM 驱动程序开发(dumb)》为模板,结合《DRM 驱动 mmap 详解:(一)预备知识》中的示例一,参照 drm_gem_cma_helper.c 文件修改而成。

请注意,本源码并没有引用 drm_gem_cma_helper.h 头文件

#include <drm/drmP.h>
#include <drm/drm_gem.h>

struct drm_gem_cma_object {
    
	struct drm_gem_object base;
	dma_addr_t paddr;
	void *vaddr;
};

static struct drm_device drm;

static int drm_gem_cma_mmap(struct file *filp, struct vm_area_struct *vma)
{
    
	struct drm_gem_cma_object *cma_obj;

	drm_gem_mmap(filp, vma);

	cma_obj = vma->vm_private_data;

	return remap_pfn_range(vma, vma->vm_start, cma_obj->paddr >> PAGE_SHIFT,
			vma->vm_end - vma->vm_start, vma->vm_page_prot);
}

static int drm_gem_cma_dumb_create(struct drm_file *file_priv,
			    struct drm_device *drm,
			    struct drm_mode_create_dumb *args)
{
    
	struct drm_gem_cma_object *cma_obj;

	args->pitch = args->width * args->bpp / 8;
	args->size = args->pitch * args->height;

	cma_obj = kzalloc(sizeof(*cma_obj), GFP_KERNEL);
	drm_gem_object_init(drm, &cma_obj->base, args->size);

	drm_gem_handle_create(file_priv, &cma_obj->base, &args->handle);

	cma_obj->vaddr = dma_alloc_wc(drm->dev, args->size, &cma_obj->paddr,
				      GFP_KERNEL | __GFP_NOWARN);

	return 0;
}

static const struct vm_operations_struct drm_gem_cma_vm_ops = {
    };

static const struct file_operations mygem_fops = {
    
	.owner = THIS_MODULE,
	.open = drm_open,
	.release = drm_release,
	.unlocked_ioctl = drm_ioctl,
	.mmap = drm_gem_cma_mmap,
};

static struct drm_driver mygem_driver = {
    
	.driver_features	= DRIVER_GEM,
	.fops			= &mygem_fops,

	.dumb_create	= drm_gem_cma_dumb_create,
	.gem_vm_ops		= &drm_gem_cma_vm_ops,

	.name			= "my-gem-cma",
	.desc			= "My GEM CMA Driver",
	.date			= "20200901",
	.major			= 1,
	.minor			= 0,
};

static int __init mygem_init(void)
{
    
	drm_dev_init(&drm, &mygem_driver, NULL);
	drm_dev_register(&drm, 0);

	return 0;
}

module_init(mygem_init);

重点

mmap 主要完成如下两步操作:

  1. drm_gem_mmap()
  2. remap_pfn_range()

drm_gem_mmap() 我们后面再讲,大家只需要记住所有的 drm mmap 都需要调用这个函数就可以了。而 remap_pfn_range() 在如今的 CMA Helper 中已看不到它的身影,其实它隐藏在了 dma_mmap_wc() 中。

dumb_create 主要完成如下三步操作:

  1. 创建 gem-object
  2. 创建 gem-handle
  3. 分配 buffer

第1、第2 步是所有 drm dumb_create 都必须实现的操作,而第3步则是可选的。由于一次性映射需要事先分配好所有的 buffer,因此该步骤放在这里是最合适的。

警告: 以上源码为了简单起见,没有实现 gem_free_object_unlocked 回调接口,因此该驱动具有内存泄露的风险。读者在实际项目中还是要把它加上的,具体可以参考原生的 drm_gem_cma_free_object() 函数实现,这里就不啰嗦了。

测试程序

这里直接使用《DRM GEM 驱动程序开发(dumb)》中的测试代码:

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <xf86drm.h>
#include <xf86drmMode.h>

int main(int argc, char **argv)
{
    
        int fd;
        char *vaddr;
        struct drm_mode_create_dumb create_req = {
    };
        struct drm_mode_destroy_dumb destroy_req = {
    };
        struct drm_mode_map_dumb map_req = {
    };

        fd = open("/dev/dri/card0", O_RDWR);

        create_req.bpp = 32;
        create_req.width = 240;
        create_req.height = 320;
        drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_req);
        printf("create dumb: handle = %u, pitch = %u, size = %llu\n",
                create_req.handle, create_req.pitch, create_req.size);

        map_req.handle = create_req.handle;
        drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map_req);
        printf("get mmap offset 0x%llx\n", map_req.offset);

        vaddr = mmap(0, create_req.size, PROT_WRITE, MAP_SHARED, fd, map_req.offset);
        strcpy(vaddr, "This is a dumb buffer!");
        munmap(vaddr, create_req.size);

        vaddr = mmap(0, create_req.size, PROT_READ, MAP_SHARED, fd, map_req.offset);
        printf("read from mmap: %s\n", vaddr);
        munmap(vaddr, create_req.size);

        getchar();

        destroy_req.handle = create_req.handle;
        drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_req);
        close(fd);

        return 0;
}   

运行结果

root@ubuntu:~# ./dumb 
create dumb: handle = 1, pitch = 960, size = 307200
get mmap offset 0x10000000
read from mmap: This is a dumb buffer!

源码下载

Github: cma/dumb.c
测试平台:QEMU vexpress-a9

结语

通过对 CMA Helper 的简化处理,希望本文能帮助大家更好的理解一次性映射在 DRM 驱动中的应用。下一篇,我们将以 vgem 驱动为例,为大家展示按需映射在 DRM 驱动中的具体实现,敬请期待!

参考资料

  1. [PATCH v4] DRM: add drm gem CMA helper
  2. LoyenWang:(十六)Linux内存管理之CMA
  3. 术道经纬:Linux中的Memory Compaction[二] - CMA
  4. 宋宝华:论Linux的页迁移(Page Migration)完整版

上一篇:DRM 驱动 mmap 详解:(一)预备知识
文章汇总: DRM (Direct Rendering Manager) 学习简介

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/hexiaolong2009/article/details/108655052

智能推荐

在ubuntu 8.04下安装Oracle 11g二-程序员宅基地

文章浏览阅读408次。 在ubuntu 8.04下安装Oracle 11g2008年05月22日 星期四 11:02oracle 11g 数据库虽然提供了linux x86的版本,但是支持的linux版本只有Red Hat,Novell and Solaris 这几个,debian 和 ubuntu 不在支持之列,所以在ubuntu下安装就相对麻烦一些,请照着下文的方法一步一步的安装,不

初一计算机知识点下册,初一英语下册语法知识点全汇总-程序员宅基地

文章浏览阅读166次。新东方在线中考网整理了《初一英语下册语法知识点全汇总》,供同学们参考。一. 情态动词can的用法can+动词原形,它不随主语的人称和数而变化。1. 含有can的肯定句:主语+can+谓语动词的原形+其他。2. 含有can的否定句:主语+can't+动词的原形+其他。3. 变一般疑问句时,把can提前:Can+主语+动词原形+其他? 肯定回答:Yes,主语+can。否定回答:No,主语+can't...._七年级下册计算机知识点

NX/UG二次开发—其他—UFUN函数调用Grip程序_uf调用grip-程序员宅基地

文章浏览阅读3k次。在平时开发中,可能会遇到UFUN函数没有的功能,比如创建PTP的加工程序(我目前没找到,哪位大神可以指点一下),可以使用Grip创建PTP,然后用UFUN函数UF_call_grip调用Grip程序。具体如下截图(左侧UFUN,右侧Grip程序):..._uf调用grip

Android RatingBar的基本使用和自定义样式,kotlin中文教程_ratingbar样式修改-程序员宅基地

文章浏览阅读156次。第一个:原生普通样式(随着主题不同,样式会变)第二个:原生普通样式-小icon第三个:自定义RatingBar 颜色第四个:自定义RatingBar DrawableRatingBar 各样式实现===============原生样式原生样式其实没什么好说的,使用系统提供的style 即可<RatingBarstyle="?android:attr/ratingBarStyleIndicator"android:layout_width=“wrap_cont.._ratingbar样式修改

OpenGL环境搭建:vs2017+glfw3.2.1+glad4.5_vs2017的opengl环境搭建(完整篇)-程序员宅基地

文章浏览阅读4.6k次,点赞6次,收藏11次。安装vs2017:参考vs2017下载和安装。安装cmake3.12.3:cmake是一个工程文件生成工具。用户可以使用预定义好的cmake脚本,根据自己的选择(像是Visual Studio, Code::Blocks, Eclipse)生成不同IDE的工程文件。可以从它官方网站的下载页上获取。这里我选择的是Win32安装程序,如图所示:然后就是运行安装程序进行安装就行。配置glfw3...._vs2017的opengl环境搭建(完整篇)

在linux-4.19.78中使用UBIFS_ubifs warning-程序员宅基地

文章浏览阅读976次。MLC NAND,UBIFS_ubifs warning

随便推点

计算机系统内存储器介绍,计算机系统的两种存储器形式介绍-程序员宅基地

文章浏览阅读2.2k次。计算机系统的两种存储器形式介绍时间:2016-1-6计算机系统的存储器一般应包括两个部分;一个是包含在计算机主机中的主存储器,简称内存,它直接和运算器,控制器及输入输出设备联系,容量小,但存取速度快,一般只存放那些急需要处理的数据或正在运行的程序;另一个是包含在外设中的外存储器,简称外存,它间接和运算器,控制器联系,存取速度虽然慢,但存储容量大,是用来存放大量暂时还不用的数据和程序,一旦要用时,就..._计算机存储器系统采用的是主辅结构,主存速度快、容量相对较小,用于 1 分 程序,外

西门子PLC的编程工具是什么?_西门子plc编程软件-程序员宅基地

文章浏览阅读5.6k次。1. STEP 7(Simatic Manager):STEP 7或者Simatic Manager是西门子PLC编程最常用的软件开发环境。4. STEP 7 MicroWin:STEP 7 MicroWn是一款专门针对微型PLC(S7-200系列PLC)的编程软件,是Simatic Manager的简化版。如果需要与PLC系统配合使用,则需要与PLC编程工具进行配合使用。除了上述软件之外,西门子还提供了一些配套软件和工具,如PLC模拟器、硬件调试工具等,以帮助PLC编程人员快速地进行调试和测试。_西门子plc编程软件

HashMap扩容_hashma扩容-程序员宅基地

文章浏览阅读36次。【代码】HashMap扩容。_hashma扩容

Eclipse maven项目中依赖包不全,如何重新加载?_maven资源加载不全,怎么重新加载-程序员宅基地

文章浏览阅读2.9k次。1mvn dependency:copy-dependencies2 项目右键 -> Maven -> Disable Maven Nature3 项目右键 -> Configure -> Convert to Maven Project_maven资源加载不全,怎么重新加载

mysql dml全称中文_MySQL语言分类——DML-程序员宅基地

文章浏览阅读527次。DMLDML的全称是Database management Language,数据库管理语言。主要包括以下操作:insert、delete、update、optimize。本篇对其逐一介绍INSERT数据库表插入数据的方式:1、insert的完整语法:(做项目的过程中将字段名全写上,这样比较容易看懂)单条记录插入语法:insert into table_name (column_name1,......_dml的全称是

【小工匠聊Modbus】04-调试工具-程序员宅基地

文章浏览阅读136次。可以参考: http://git.oschina.net/jrain-group/ 组织下的Java Modbus支持库Modbus-系列文章1、虚拟成对串口(1)下载虚拟串口软件VSPD(可在百度中搜索)image.png(2)打开软件,添加虚拟串口。在设备管理中,看到如下表示添加成功。..._最好用的 modebus调试工具

推荐文章

热门文章

相关标签