先来看下驱动程序:
#include <drm/drmP.h>#include <drm/drm_gem_cma_helper.h>static struct drm_device drm;static const struct file_operations mygem_fops = {.owner = THIS_MODULE,.open = drm_open,.release = drm_release,.unlocked_ioctl = drm_ioctl,.poll = drm_poll,.read = drm_read,.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,.gem_free_object_unlocked = drm_gem_cma_free_object,.name = "my-gem",.desc = "My GEM Driver",.date = "20200601",.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);
DRIVER_GEM:该 feature 告诉 DRM 框架本驱动支持 GEM 操作,如 buffer 的分配和释放,以及 GEM OPEN/FLINK/CLOSE 等操作。
dumb_create:分配 dumb buffer 的回调接口,主要完成三件事:
创建 gem object
创建 gem handle
分配物理 buffer (也可以等到后面再分配),本例中直接使用 CMA helper 函数实现,该函数内部会分配最终的物理 buffer。
mmap:创建 dumb buffer 的目的就是要拿去给 CPU 画图,因此没有 mmap 的 dumb buffer 是没有灵魂的,所以必须实现。通常使用 drm_gem_mmap() 来实现。
gem_vm_ops:主要为 mmap 服务,必须实现。
再来看下测试程序:
#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 = %llun",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%llxn", 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: %sn", 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;}
这个测试程序的主要工作是:先创建一个 dumb buffer,然后将其 mmap 到 user-space,往里写入一串字符串,然后重新映射,读取并打印 buffer 中的内容。最后运行结果:
root@ubuntu:~# ./dumbcreate dumb: handle = 1, pitch = 960, size = 307200get mmap offset 0x10000000read from mmap: This is a dumb buffer!
为什么要执行 DRM_IOCTL_MODE_MAP_DUMB ?
许多开发者初次编写 dumb buffer 应用时,常对 mmap与 DRM_IOCTL_MODE_MAP_DUMB的分工感到困惑——明明已有 mmap函数,为何需额外调用该 ioctl?核心矛盾在于:当进程仅持有 DRM 设备(如 card0)的文件描述符(fd)时,若创建多个 dumb buffer,mmap无法通过 fd 区分目标 buffer(因所有 buffer 共享同一设备 fd)。此时,size和 flag等参数无法灵活调整,只能依赖 offset参数传递标识。但 mmap接收的 offset并非真实内存偏移,而是 DRM 驱动定义的“gem object 索引”(伪偏移),用于定位具体的 buffer。
为解决“如何通过设备 fd 关联目标 gem object”的问题,DRM_IOCTL_MODE_MAP_DUMB应运而生:其功能是接收一个 gem handle(buffer 的唯一标识),返回该 buffer 对应的“伪偏移”(如示例中的 0x10000000)。这一偏移值本质是 gem object 在 DRM 驱动内部的索引标记,驱动通过该索引可精准找到对应的物理 buffer,最终完成真实的 mmap映射。简言之,该 ioctl 的核心作用是“将用户态的 gem handle 转换为 mmap 可用的伪偏移”,其底层由驱动的 dumb_map_offset回调实现(若命名为 DRM_IOCTL_MODE_CREATE_MAP_OFFSET或更直观)。
129