DRM PRIME跨设备DMA-BUF共享驱动开发 - 第1节

1.1 DMA-BUF 是什么

DMA-BUF(Direct Memory Access Buffer)是 Linux 内核提供的一种跨设备、跨驱动共享 DMA 缓冲区的框架。它允许不同设备驱动程序之间高效地共享物理内存,而无需进行多余的数据拷贝。DMA-BUF 的核心思想是将一块物理内存抽象为一个缓冲区对象,并通过文件描述符(fd)在进程间或驱动间传递。

关键定义: DMA-BUF 是一个内核对象,代表一块连续的物理内存区域,支持通过 dma_buf_export() 导出、dma_buf_get() 导入,并配合 dma_buf_attach() 和 dma_buf_map_attachment() 实现设备间的 DMA 访问。

1.1.1 核心数据结构

结构体 作用 所属模块
struct dma_buf 代表一个导出的 DMA 缓冲区,包含引用计数、文件指针、导出驱动私有数据 内核核心
struct dma_buf_attachment 记录一个导入设备与 DMA-BUF 的绑定关系,包含设备指针和映射状态 内核核心
struct sg_table 散列表,描述 DMA 地址映射后的物理页列表,供 DMA 引擎使用 DMA 映射层
struct dma_buf_export_info 导出时传入的参数集合,包含大小、标志、私有数据和操作函数表 导出驱动

1.1.2 生命周期流程

  1. 分配:导出驱动分配物理内存(如通过 dma_alloc_coherent 或 GEM 对象)。
  2. 导出:调用 dma_buf_export() 创建 struct dma_buf 并获取文件描述符。
  3. 传递:通过文件描述符将缓冲区句柄传递给导入驱动或用户空间。
  4. 导入:导入驱动调用 dma_buf_get() 获取 struct dma_buf 指针。
  5. 绑定:调用 dma_buf_attach() 将设备与缓冲区绑定。
  6. 映射:调用 dma_buf_map_attachment() 获取 sg_table 进行 DMA 操作。
  7. 解绑与释放:操作完成后依次调用 dma_buf_unmap_attachment()、dma_buf_detach()、dma_buf_put()。
DMA-BUF 生命周期流程图 1. 分配物理内存 2. dma_buf_export 3. 传递 fd 4. dma_buf_get 5. dma_buf_attach 6. map_attachment 7. DMA 读写操作 8. unmap/detach/put 说明:导出驱动负责分配和导出,导入驱动负责绑定和映射。 整个生命周期由引用计数管理,确保缓冲区在所有使用者释放后才被回收。

1.2 为什么需要 DMA-BUF

在嵌入式图形系统中,多个硬件设备(GPU、显示控制器、视频编解码器、相机 ISP)需要频繁访问同一块帧缓冲或图像数据。传统方案存在以下痛点:

  • 数据拷贝开销:设备间通过 CPU 拷贝数据,消耗带宽和功耗,降低实时性。
  • 内存碎片化:每个驱动独立分配内存,无法复用,导致内存浪费。
  • 缓存一致性问题:不同设备对缓存一致性要求不同,缺乏统一管理机制。
  • 缺少标准化接口:各厂商自行实现私有共享方案,代码不可移植。

1.2.1 传统方案 vs DMA-BUF 方案

对比维度 传统方案(CPU 拷贝) DMA-BUF 方案
数据传输方式 CPU 逐帧拷贝 硬件 DMA 直接访问
内存占用 每设备独立缓冲区,至少 2 份 共享同一缓冲区,仅 1 份
延迟 高(微秒级拷贝延迟) 低(零拷贝,纳秒级映射延迟)
功耗 高(CPU 频繁参与) 低(硬件直接传输)
接口标准化 私有 API,不可移植 内核标准框架,跨平台
缓存一致性 需手动维护 由 dma_buf 框架自动处理
性能收益: 在 4K 分辨率、60fps 的显示场景中,使用 DMA-BUF 零拷贝方案相比 CPU 拷贝可节省约 70% 的内存带宽,功耗降低 40% 以上。

1.3 DMA-BUF 在图形栈中的角色

在 Linux 图形栈中,DMA-BUF 是连接 DRM/KMS、GPU、V4L2、ALSA 等子系统的关键纽带。它实现了从图像生成到显示输出的全链路零拷贝。

1.3.1 图形栈分层架构

DMA-BUF 在 Linux 图形栈中的角色 用户空间 Wayland / X11 / Weston / GStreamer / FFmpeg 系统调用层:DRM_IOCTL_PRIME_* / V4L2_DMABUF DRM/KMS 显示控制器 帧缓冲管理 GPU / Render 3D 渲染 GEM 对象管理 V4L2 / Codec 视频编解码 相机 ISP DMA-BUF 核心框架 dma_buf_export / dma_buf_get / dma_buf_attach / dma_buf_map_attachment Display Controller GPU Video Decoder Camera ISP DMA-BUF 作为内核中间层,屏蔽硬件差异,为上层提供统一的缓冲区共享接口

1.3.2 典型应用场景

  • GPU 渲染 → 显示输出:GPU 渲染完成后,将 GEM 对象导出为 DMA-BUF,直接传递给 DRM/KMS 的 framebuffer,无需拷贝。
  • 视频解码 → 显示:V4L2 解码器输出帧导出为 DMA-BUF,KMS 直接扫描显示,实现零拷贝视频播放。
  • 相机预览 → 编码:ISP 输出帧通过 DMA-BUF 同时传递给显示控制器和视频编码器,实现预览与录制并行。
  • GPU 合成 → 编码:合成后的帧通过 DMA-BUF 传递给硬件编码器,用于屏幕录制或直播推流。

1.3.3 与 DRM 子系统的关系

在 DRM 框架中,DMA-BUF 通过 drm_prime 模块与 GEM(Graphics Execution Manager)深度集成:

DRM 组件 与 DMA-BUF 的交互
drm_gem_prime_export 将 GEM 对象导出为 DMA-BUF,生成文件描述符
drm_gem_prime_import 从文件描述符导入 DMA-BUF,创建对应的 GEM 对象
drm_gem_object 作为 GEM 对象的核心结构,包含 dma_buf 指针和导出/导入回调
drm_driver.gem_prime_export 驱动层实现的导出回调,负责分配 sg_table
drm_driver.gem_prime_import_sg_table 驱动层实现的导入回调,将 sg_table 转换为 GEM 对象
注意事项: DMA-BUF 的导出和导入操作必须考虑缓存一致性。在 ARM 架构下,导出驱动应在 dma_buf_map_attachment 时调用 dma_sync_sg_for_device,导入驱动在访问前调用 dma_sync_sg_for_cpu,避免数据错乱。

1.3.4 实际代码示例

以下是一个简化的 DRM 驱动导出 GEM 对象为 DMA-BUF 的核心流程:

// 导出端:将 GEM 对象导出为 DMA-BUF
struct dma_buf *my_gem_prime_export(struct drm_gem_object *obj, int flags)
{
    struct dma_buf_export_info exp_info = {
        .exp_name = KBUILD_MODNAME,
        .owner = THIS_MODULE,
        .ops = &my_dma_buf_ops,   // 自定义 dma_buf_ops
        .size = obj->size,
        .flags = flags,
        .priv = obj,              // 私有数据指向 GEM 对象
    };
    return dma_buf_export(&exp_info);
}

// 导入端:从 DMA-BUF 创建 GEM 对象
struct drm_gem_object *my_gem_prime_import(struct drm_device *dev,
                                           struct dma_buf *dma_buf)
{
    struct dma_buf_attachment *attach;
    struct sg_table *sgt;
    struct drm_gem_object *obj;

    attach = dma_buf_attach(dma_buf, dev->dev);
    if (IS_ERR(attach))
        return ERR_CAST(attach);

    sgt = dma_buf_map_attachment(attach, DMA_BIDIRECTIONAL);
    if (IS_ERR(sgt)) {
        dma_buf_detach(dma_buf, attach);
        return ERR_CAST(sgt);
    }

    // 将 sg_table 转换为驱动私有的 GEM 对象
    obj = my_gem_create_from_sgt(dev, sgt);

    dma_buf_unmap_attachment(attach, sgt, DMA_BIDIRECTIONAL);
    dma_buf_detach(dma_buf, attach);
    dma_buf_put(dma_buf);

    return obj;
}
实战要点: 在实现 dma_buf_ops 时,必须提供 attach、detach、map_dma_buf、unmap_dma_buf 和 release 回调。其中 map_dma_buf 返回的 sg_table 必须使用 dma_map_sg 进行 DMA 地址映射。