3. DRM PRIME API 概览

DRM PRIME 是 Linux 内核中用于在 DRM 和 V4L2 等不同子系统之间高效共享物理内存页面的基础设施。其核心思想是通过 dma-buf 文件描述符作为句柄,实现零拷贝内存共享。本章将深入剖析三个最关键的 API:gem_prime_export、gem_prime_import 以及用户空间常用的 drm_gem_prime_fd_to_handle。

核心概念: DRM PRIME 的本质是“导出方”将 GEM 对象转换为 dma-buf 文件描述符,“导入方”则从该文件描述符重建本地 GEM 句柄。整个过程避免了数据在用户空间与内核空间之间的冗余拷贝。

3.1 导出流程:gem_prime_export

gem_prime_export 是 DRM 驱动中用于将 GEM 对象导出为 dma-buf 文件描述符的回调函数。它通常由 DRM_IOCTL_PRIME_HANDLE_TO_FD 触发。

3.1.1 函数原型与职责

struct dma_buf *(*gem_prime_export)(struct drm_gem_object *obj, int flags);
  • obj:指向要导出的 GEM 对象的指针。
  • flags:文件描述符标志,通常为 O_CLOEXEC。
  • 返回值:成功时返回指向 dma_buf 结构的指针;失败返回 ERR_PTR。

驱动实现此回调时,需要完成以下关键步骤:

  1. 获取 GEM 对象底层的物理页面(scatterlist)。
  2. 调用 dma_buf_export() 创建 dma-buf 对象,并绑定自定义的 dma_buf_ops。
  3. 将 GEM 对象的引用计数传递给 dma-buf,确保在导出期间 GEM 对象不会被释放。
实战提示: 在嵌入式平台(如 i.MX8、RK3588)上,gem_prime_export 通常需要配合 IOMMU/IOMMU 地址映射,确保 dma-buf 可以被 V4L2 设备正确寻址。

3.1.2 导出流程 SVG 图

DRM PRIME 导出流程 用户空间应用 DRM 核心 GEM 对象 gem_prime_export dma_buf_export dma-buf fd IOCTL 触发 DRM_IOCTL_PRIME_HANDLE_TO_FD

3.2 导入流程:gem_prime_import

gem_prime_import 是 DRM 驱动中用于从 dma-buf 文件描述符导入并创建本地 GEM 对象的回调函数。它由 DRM_IOCTL_PRIME_FD_TO_HANDLE 触发。

3.2.1 函数原型与职责

struct drm_gem_object *(*gem_prime_import)(struct drm_device *dev, struct dma_buf *dma_buf);
  • dev:目标 DRM 设备。
  • dma_buf:指向要导入的 dma-buf 对象的指针。
  • 返回值:成功时返回指向新创建的 GEM 对象的指针;失败返回 ERR_PTR。

驱动实现此回调时,典型步骤包括:

  1. 调用 dma_buf_attach() 将 dma-buf 附加到本设备。
  2. 调用 dma_buf_map_attachment() 获取 scatterlist。
  3. 创建新的 GEM 对象,并将 scatterlist 关联到该对象。
  4. 返回 GEM 对象,DRM 核心会为其分配用户空间可见的句柄。
注意事项: 导入方必须确保其设备与导出方共享同一个 IOMMU 域,或者支持物理地址连续的内存访问。否则,dma_buf_map_attachment() 可能返回不连续的 SG 表,导致 V4L2 硬件无法正确 DMA。

3.2.2 导入流程 SVG 图

DRM PRIME 导入流程 用户空间应用 DRM 核心 dma-buf fd gem_prime_import dma_buf_attach GEM 句柄 IOCTL 触发 DRM_IOCTL_PRIME_FD_TO_HANDLE

3.3 用户空间桥接:drm_gem_prime_fd_to_handle

drm_gem_prime_fd_to_handle 是 DRM 核心提供的一个辅助函数,用于在用户空间已经持有 dma-buf 文件描述符的情况下,将其转换为当前 DRM 设备的 GEM 句柄。它内部封装了 gem_prime_import 的调用逻辑。

3.3.1 函数原型

int drm_gem_prime_fd_to_handle(struct drm_device *dev, struct drm_file *file_priv, int fd, u32 *handle);
参数 说明
dev DRM 设备指针
file_priv DRM 文件私有数据(对应一个打开的设备节点)
fd 要导入的 dma-buf 文件描述符
handle 输出参数,返回新分配的 GEM 句柄
返回值 0 表示成功;负数表示错误码

3.3.2 内部工作流程

  1. 通过 fdget() 从文件描述符获取内核 struct file 指针。
  2. 检查该文件是否为 dma-buf 文件(通过 file->f_op 判断)。
  3. 调用 gem_prime_import 驱动回调,获取 GEM 对象。
  4. 调用 drm_gem_handle_create() 为 GEM 对象分配用户空间句柄。
  5. 将句柄写入 handle 输出参数。
关键区别: drm_gem_prime_fd_to_handle 是 DRM 核心的通用实现,而 gem_prime_import 是驱动需要具体实现的回调。前者负责“文件描述符 → GEM 句柄”的通用逻辑,后者负责“dma-buf → GEM 对象”的硬件相关逻辑。

3.4 API 对比总结

API 方向 输入 输出 触发 IOCTL
gem_prime_export 导出 GEM 对象 dma-buf 对象 DRM_IOCTL_PRIME_HANDLE_TO_FD
gem_prime_import 导入 dma-buf 对象 GEM 对象 DRM_IOCTL_PRIME_FD_TO_HANDLE
drm_gem_prime_fd_to_handle 导入(用户空间桥接) dma-buf fd GEM 句柄 DRM_IOCTL_PRIME_FD_TO_HANDLE

3.5 典型使用模式

在 V4L2 与 DRM 协同工作的场景中,典型的 PRIME 使用模式如下:

// 步骤1:DRM 导出 — 将 GEM 句柄转换为 dma-buf fd
int dma_buf_fd;
struct drm_prime_handle args = {
    .handle = gem_handle,
    .flags = DRM_CLOEXEC,
    .fd = 0
};
ioctl(drm_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &args);
dma_buf_fd = args.fd;

// 步骤2:将 dma-buf fd 传递给 V4L2 设备(通过 V4L2 DMABUF 接口)
struct v4l2_exportbuffer expbuf = {
    .type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
    .index = 0,
    .flags = O_CLOEXEC,
    .fd = 0
};
ioctl(v4l2_fd, VIDIOC_EXPBUF, &expbuf);
// 注意:这里 V4L2 导出自己的 dma-buf,与 DRM 导出不同

// 步骤3:V4L2 导入 — 将 DRM 导出的 dma-buf fd 用于 V4L2 队列
struct v4l2_buffer buf;
struct v4l2_plane planes[1];
memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_DMABUF;
buf.index = 0;
buf.m.planes = planes;
buf.length = 1;
planes[0].m.fd = dma_buf_fd;  // 使用 DRM 导出的 fd
planes[0].length = buffer_size;
ioctl(v4l2_fd, VIDIOC_QBUF, &buf);
实战要点: 上述代码中,步骤1和步骤3展示了 DRM 作为“导出方”、V4L2 作为“导入方”的典型协作模式。实际项目中,需要确保 dma-buf 的大小和内存对齐满足 V4L2 硬件的要求。

3.6 本章小结

DRM PRIME 的三个核心 API 构成了跨设备内存共享的基石:

  • gem_prime_export:驱动层实现,将 GEM 对象转化为 dma-buf。
  • gem_prime_import:驱动层实现,将 dma-buf 转化为 GEM 对象。
  • drm_gem_prime_fd_to_handle:核心层辅助函数,为用户空间提供从 fd 到句柄的便捷转换。