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 生命周期流程
- 分配:导出驱动分配物理内存(如通过
dma_alloc_coherent或 GEM 对象)。 - 导出:调用
dma_buf_export()创建struct dma_buf并获取文件描述符。 - 传递:通过文件描述符将缓冲区句柄传递给导入驱动或用户空间。
- 导入:导入驱动调用
dma_buf_get()获取struct dma_buf指针。 - 绑定:调用
dma_buf_attach()将设备与缓冲区绑定。 - 映射:调用
dma_buf_map_attachment()获取sg_table进行 DMA 操作。 - 解绑与释放:操作完成后依次调用
dma_buf_unmap_attachment()、dma_buf_detach()、dma_buf_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 图形栈分层架构
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 地址映射。