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对象,完成跨模块引用。整个过程不复制像素数据,只传递“谁拥有这块内存”的元信息。

DMA-BUF 共享模型:Exporter / Importer / Attachment / Scatterlist Exporter(导出者) GPU / VPU / 摄像头驱动 dma_buf_export() dma_buf_ops 实现 dma_buf 核心对象 size / file / ops / priv 全局唯一 fd 句柄 引用计数 refcount Importer(导入者) DRM 显示控制器 / 其他设备 dma_buf_get(fd) dma_buf_attach() export import dma_buf_attachment dev / dir / priv / sgt 每个设备一次绑定 scatterlist (sgt) 物理页帧集合 → DMA 编程 attach map_dma_buf dma_buf_ops 回调 • attach / detach • map_dma_buf / unmap • begin_cpu_access • end_cpu_access • mmap / vmap / release • poll / vmap_atomic ops 绑定 Importer 侧动作 1. dma_buf_get(fd) 2. dma_buf_attach(dev) 3. dma_buf_map_attachment() 4. 得到 sgt → 配置 DMA 5. 使用完 unmap / detach 6. dma_buf_put() 释放引用 绑定关系

二、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 这一层公共契约。

关键点:Exporter 决定“内存长什么样”,Importer 决定“怎么用这块内存”。两者通过 dma_buf_ops 和 attachment 解耦,任何一方升级都不影响另一方。

三、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_bufExporter 创建size, ops, fileGPU 渲染目标导出
dma_buf_attachmentImporter 绑定dev, dir, sgtDE 控制器 attach
scatterlistExporter 填充page_link, offset, length, dma_addressVPU 解码帧物理页
dma_buf_opsExporter 实现map_dma_buf, mmap, releasesun4i 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 和显示控制器在同一块缓冲区上协同工作。