DRM 驱动 mmap 详解:(二)CMA Helper

!!!声明!!!
本文章转自:何小龙
链接:https://blog.csdn.net/hexiaolong2009/article/details/87392266
转载只是为了学习备份。

没有 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) 学习简介

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值