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。
驱动实现此回调时,需要完成以下关键步骤:
- 获取 GEM 对象底层的物理页面(scatterlist)。
- 调用
dma_buf_export()创建 dma-buf 对象,并绑定自定义的dma_buf_ops。 - 将 GEM 对象的引用计数传递给 dma-buf,确保在导出期间 GEM 对象不会被释放。
实战提示: 在嵌入式平台(如 i.MX8、RK3588)上,
gem_prime_export 通常需要配合 IOMMU/IOMMU 地址映射,确保 dma-buf 可以被 V4L2 设备正确寻址。
3.1.2 导出流程 SVG 图
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。
驱动实现此回调时,典型步骤包括:
- 调用
dma_buf_attach()将 dma-buf 附加到本设备。 - 调用
dma_buf_map_attachment()获取 scatterlist。 - 创建新的 GEM 对象,并将 scatterlist 关联到该对象。
- 返回 GEM 对象,DRM 核心会为其分配用户空间可见的句柄。
注意事项: 导入方必须确保其设备与导出方共享同一个 IOMMU 域,或者支持物理地址连续的内存访问。否则,
dma_buf_map_attachment() 可能返回不连续的 SG 表,导致 V4L2 硬件无法正确 DMA。
3.2.2 导入流程 SVG 图
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 内部工作流程
- 通过
fdget()从文件描述符获取内核struct file指针。 - 检查该文件是否为 dma-buf 文件(通过
file->f_op判断)。 - 调用
gem_prime_import驱动回调,获取 GEM 对象。 - 调用
drm_gem_handle_create()为 GEM 对象分配用户空间句柄。 - 将句柄写入
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 到句柄的便捷转换。