4. DMA-BUF核心概念
在全志DRM移植过程中,显示子系统需要把GPU渲染结果、VPU解码帧、摄像头采集数据送到显示控制器扫描输出。这些数据往往由不同驱动、不同硬件模块产生,如果每个环节都做一次内存拷贝,带宽和延迟都无法接受。DMA-BUF正是Linux内核为解决“跨设备零拷贝共享缓冲区”而设计的标准框架,它把一块物理内存抽象成可被多个驱动同时引用的共享对象。
一、DMA-BUF缓冲区共享模型
DMA-BUF的核心思想是:内存的分配者负责创建缓冲区并导出为一个全局唯一的文件描述符(fd),其他设备通过这个fd“导入”并映射到自己的地址空间。整个模型围绕三个关键对象展开:
- dma_buf:共享缓冲区的抽象实体,持有size、file、ops、priv等字段,是跨驱动传递的句柄载体。
- dma_buf_attachment:表示“某个设备”与“某块dma_buf”之间的一次绑定关系,记录设备指针、方向、私有数据。
- scatterlist(sgt):描述缓冲区在物理内存中的分散页帧集合,供DMA引擎直接编程使用。
用户空间通过ioctl(DMA_BUF_IOCTL_EXPINFO)或DRM的PRIME接口拿到fd,再把它传给另一个驱动。内核中则通过dma_buf_get()根据fd找到dma_buf对象,完成跨模块引用。整个过程不复制像素数据,只传递“谁拥有这块内存”的元信息。
二、Exporter 与 Importer 的角色分工
Exporter 是缓冲区的“生产者”,它必须实现一组dma_buf_ops回调,把底层内存的真实能力暴露给内核。在全志平台上,常见的 exporter 包括:
- sun4i-drm:显示驱动自身分配的 framebuffer,通过 GEM 对象导出为 dma_buf。
- Mali GPU 驱动:渲染目标缓冲区导出,供显示控制器扫描输出。
- cedar VPU 驱动:解码后的 YUV 帧导出,供显示或后续编码使用。
Importer 则是“消费者”,它不关心内存由谁分配,只通过标准接口拿到 scatterlist 并编程 DMA。显示控制器作为 importer 时,会把 sgt 转换成 DE 控制器可识别的地址寄存器配置。这种角色分离使得驱动之间不需要互相包含头文件或直接调用函数,只依赖 DMA-BUF 这一层公共契约。
三、dma_buf_ops 与 attachment 的协作流程
当 importer 调用 dma_buf_attach() 时,内核会创建 attachment 并回调 exporter 的 attach 操作。随后 importer 调用 dma_buf_map_attachment(),exporter 的 map_dma_buf 被触发,返回该设备可用的 scatterlist。这个 sgt 可能经过 IOMMU 映射,也可能是物理连续页,取决于 exporter 的实现和平台配置。
/* 典型 importer 侧调用序列 */
struct dma_buf *dbuf = dma_buf_get(fd);
struct dma_buf_attachment *attach;
struct sg_table *sgt;
attach = dma_buf_attach(dbuf, dev);
sgt = dma_buf_map_attachment(attach, DMA_TO_DEVICE);
/* 将 sgt 编程到显示控制器寄存器 */
sunxi_de_set_address(sgt->sgl->dma_address);
/* 使用完毕 */
dma_buf_unmap_attachment(attach, sgt, DMA_TO_DEVICE);
dma_buf_detach(dbuf, attach);
dma_buf_put(dbuf);
attachment 的生命周期必须与设备使用缓冲区的时间严格对齐。如果在 unmap 之后仍然让硬件访问该地址,就会出现显示花屏或 IOMMU fault。全志显示驱动在 commit 阶段持有 attachment,直到下一次 flip 完成才释放。
四、scatterlist 机制与全志平台实践
scatterlist 是 DMA-BUF 与 DMA 引擎之间的桥梁。一个 sgt 由若干 struct scatterlist 组成,每个条目描述一段物理地址和长度。对于全志 H3/H6 等平台,显示控制器 DE 支持线性地址扫描,因此 importer 通常只需要 sgt 的第一个条目或经过 IOMMU 合并后的连续 IOVA。
| 对象 | 所属角色 | 关键字段 | 全志场景示例 |
|---|---|---|---|
| dma_buf | Exporter 创建 | size, ops, file | GPU 渲染目标导出 |
| dma_buf_attachment | Importer 绑定 | dev, dir, sgt | DE 控制器 attach |
| scatterlist | Exporter 填充 | page_link, offset, length, dma_address | VPU 解码帧物理页 |
| dma_buf_ops | Exporter 实现 | map_dma_buf, mmap, release | sun4i GEM 回调 |
/sys/kernel/debug/dma_buf/bufinfo,确认缓冲区 size、attachment 数量和 exporter 名称是否与预期一致。再核对 sgt 的 dma_address 是否落在 IOMMU 映射范围内。
五、常见误区与复盘
误区一:认为 dma_buf 就是物理连续内存。实际上 dma_buf 只保证“可被共享”,是否连续由 exporter 和 IOMMU 决定。全志部分平台依赖 IOMMU 把分散页映射为连续 IOVA,驱动不能假设 sgl 只有一个条目。
误区二:attach 后忘记 detach。attachment 会持有设备引用和映射资源,泄漏后会导致缓冲区无法释放,表现为 DRM framebuffer 引用计数居高不下。复盘时应检查每个 dma_buf_attach 是否有配对的 dma_buf_detach。
误区三:在 CPU 访问前未调用 begin_cpu_access。如果 exporter 实现了缓存一致性回调,importer 在 CPU 读写前必须调用 dma_buf_begin_cpu_access(),否则可能读到旧数据。全志 VPU 解码帧导出时尤其要注意这一点。
理解 DMA-BUF 的共享模型、角色分工、ops 回调与 scatterlist 机制,是后续排查显示缓存泄漏、IOMMU fault 和零拷贝链路问题的基石。在全志 DRM 移植中,只有把 exporter 与 importer 的契约关系理清,才能让 GPU、VPU 和显示控制器在同一块缓冲区上协同工作。