2、显示缓存管理原理:显存分配策略、连续物理内存与IOMMU映射、全志DE2/DE3显示引擎对缓存的要求

显示缓存管理是DRM/KMS驱动中最核心、最易出问题的环节。在全志平台上,显示引擎(DE2/DE3)对缓冲区的物理连续性、对齐方式和访问带宽有严格约束,而系统内存分配器(如CMA、DMA-Heap)与IOMMU映射策略直接决定了显示缓存能否被正确使用。本节从分配策略、物理连续性、IOMMU映射和DE引擎约束四个维度展开。

2.1 显存分配策略:从系统内存到显示专用缓冲区

嵌入式Linux中,显示缓冲区通常不通过标准malloc或vmalloc分配,而是使用内核提供的连续物理内存分配器。全志平台常用的分配路径如下:

分配器 内存类型 物理连续性 适用场景 全志平台使用情况
CMA (Contiguous Memory Allocator) 预留大块连续物理内存 完全连续 Framebuffer、UI图层 DE2/DE3 主图层默认使用
DMA-Heap (Ion/DMABUF Heaps) 系统内存或预留区域 连续或通过SG表 视频解码、GPU渲染、Camera预览 Android/Linux 通用方案
DMA Pool 小尺寸连续内存 完全连续 硬件描述符、小尺寸控制缓冲区 DE 通道配置寄存器备份
kmalloc (GFP_DMA32) 低32位地址空间 连续但碎片化 小尺寸、低延迟分配 不推荐用于显示帧缓冲
实战提示:在全志H6/H616/H313等平台上,DRM primary plane 的 framebuffer 必须从 CMA 区域分配。如果使用 DMA-Heap 分配的缓冲区作为 primary plane 的 source,需要确保该 heap 底层也是从 CMA 区域申请的,否则 DE 引擎在访问时可能产生总线错误(bus error)。

2.2 连续物理内存:为什么显示引擎离不开它?

全志DE2/DE3显示引擎内部没有复杂的MMU或TLB,其UI图层(Vi通道)和视频图层(UI通道)的DMA引擎只能处理物理连续的帧缓冲区。原因如下:

  • 突发传输(Burst Transfer):DE引擎在读取一行像素时,会以128字节或256字节的突发长度从DDR中读取数据。如果物理地址不连续,突发传输会被截断,导致总线效率骤降甚至数据错误。
  • 硬件管线无分页支持:DE2/DE3的fetch单元只接受起始地址和行跨度(stride),不支持按页表跳转。这意味着每一帧的像素数据在物理地址空间上必须是线性连续的。
  • 带宽保障:连续物理内存允许DE引擎使用固定的DDR burst通道,避免因页表切换引入的额外延迟,这对4K@60fps的显示输出至关重要。
关键指标:全志DE3要求每个图层的帧缓冲物理地址必须按 4KB 对齐(即低12位为0),且行跨度(pitch)必须是 16 字节的整数倍。违反这些对齐约束会导致显示画面出现“条纹错位”或“颜色偏移”。
连续物理内存 vs 非连续内存的DE访问对比 ✅ 连续物理内存 物理页 0x4800_0000 物理页 0x4800_1000 物理页 0x4800_2000 物理页 0x4800_3000 DE引擎:一次burst读取整行,无中断 Burst ❌ 非连续(Scatter-Gather) 物理页 0x7200_0000 物理页 0x8100_5000 物理页 0x5500_2000 物理页 0x9000_3000 DE引擎:burst跨页中断,数据错乱 Burst失败

2.3 IOMMU映射:让非连续内存“看起来”连续

当显示引擎必须使用非连续物理内存(例如GPU渲染输出或视频解码输出)时,IOMMU(Input-Output Memory Management Unit)就派上了用场。全志平台通常集成一个系统IOMMU(也叫SMMU),可以为DE引擎提供虚拟连续地址空间。

IOMMU映射流程

  1. 分配SG表:从DMA-Heap分配出多个不连续的物理页,记录在scatterlist中。
  2. 映射到IOMMU:调用 iommu_map_sg() 将SG表映射到IOMMU的I/O虚拟地址空间(IOVA)。
  3. 配置DE引擎:将IOVA地址写入DE图层的起始地址寄存器,而不是物理地址。
  4. 启用IOMMU:确保DE引擎的AXI总线端口连接到IOMMU,且IOMMU页表已生效。
常见陷阱:全志H6的IOMMU默认页大小为4KB,但DE3引擎的burst请求可能跨越页边界。如果IOMMU页表没有预先映射相邻的虚拟页,DE引擎在burst跨页时会触发IOMMU page fault,导致显示冻结。解决方案是使用 IOMMU_MAP_CONTIGUOUS 标志,确保IOMMU为SG表分配连续的IOVA空间。
IOMMU映射:非连续物理页 → 连续IOVA空间 物理内存(非连续) 物理页 0x7200_0000 物理页 0x8100_5000 物理页 0x5500_2000 IOMMU 页表映射 IOVA 虚拟地址(连续) IOVA 0x1000_0000 IOVA 0x1000_1000 IOVA 0x1000_2000 DE引擎看到的是连续虚拟地址 映射

2.4 全志DE2/DE3显示引擎对缓存的具体要求

DE2和DE3是两代不同的显示引擎,对缓存的要求有显著差异。下表总结了关键参数:

参数项 DE2 (H3/H5/A64) DE3 (H6/H616/H313) 说明
最大图层数 4 (VI:2 + UI:2) 6 (VI:3 + UI:3) VI为视频图层,UI为UI图层
物理地址对齐 32字节 4KB DE3要求更严格的对齐
行跨度对齐 8字节 16字节 pitch必须是16的倍数
支持IOMMU 否(仅物理连续) 是(通过SMMU) DE3可映射非连续内存
最大分辨率 4K@30fps 4K@60fps DE3带宽更高
缓存格式 ARGB8888, XRGB8888, YUV420 ARGB8888, XRGB8888, YUV420, NV12, NV16 DE3支持更多半平面格式
DMA burst大小 128字节 256字节 DE3单次读取更大

DE3图层配置示例(DRM驱动中)

在DRM驱动中,配置DE3图层时,需要将dma-buf的物理地址或IOVA地址写入硬件寄存器。以下是一个简化的图层配置代码片段:

/* 全志DE3图层配置结构体 */
struct de3_plane_config {
    u32 addr;       /* 起始物理地址或IOVA地址 */
    u32 stride;     /* 行跨度(字节) */
    u32 size;       /* 总大小(字节) */
    u32 format;     /* 像素格式 */
    u32 alpha;      /* 全局alpha值 */
};

/* 配置UI图层0 */
static void de3_ui_plane_set(struct de3_plane *plane,
                             struct drm_framebuffer *fb)
{
    struct de3_plane_config cfg = {0};
    struct dma_buf_attachment *attach = fb->obj[0]->import_attach;
    struct sg_table *sgt;

    /* 获取dma-buf的SG表 */
    sgt = dma_buf_map_attachment(attach, DMA_TO_DEVICE);
    if (IS_ERR(sgt))
        return;

    /* 对于DE3,如果使用IOMMU,取sg_dma_address(sgt->sgl) */
    /* 如果不使用IOMMU,取sg_phys(sgt->sgl) */
    if (plane->use_iommu)
        cfg.addr = sg_dma_address(sgt->sgl);
    else
        cfg.addr = sg_phys(sgt->sgl);

    cfg.stride = fb->pitches[0];
    cfg.size = fb->pitches[0] * fb->height;
    cfg.format = de3_format_from_drm(fb->format->format);
    cfg.alpha = 0xff;

    /* 写入硬件寄存器 */
    de3_write_plane_regs(plane, &cfg);

    dma_buf_unmap_attachment(attach, sgt, DMA_TO_DEVICE);
}
调试要点:在全志DE3平台上,如果显示画面出现“撕裂”或“闪烁”,首先检查 stride 是否为16的倍数。其次,使用 cat /sys/kernel/debug/dri/0/state 查看每个plane的物理地址,确认地址是否在CMA区域内且低12位为0。如果地址正确但画面异常,则可能是IOMMU映射问题,检查 /sys/kernel/debug/iommu/ 下的页表状态。

2.5 缓存管理实战:分配、映射与释放流程

在DRM驱动中,显示缓存的生命周期管理遵循“分配 → 映射 → 使用 → 释放”的闭环。以下是全志平台上的典型流程:

  1. 分配:通过 dma_alloc_attrs() 从CMA区域分配连续物理内存,或通过 dma_buf_alloc() 从DMA-Heap分配。
  2. 导入:将dma-buf导入到DRM framebuffer,调用 drm_gem_prime_import()。
  3. 映射:在plane更新时,通过 dma_buf_map_attachment() 获取SG表,并映射到IOMMU(如果启用)。
  4. 使用:将映射后的地址写入DE引擎寄存器,启动显示。
  5. 释放:在framebuffer销毁时,调用 dma_buf_unmap_attachment() 和 dma_buf_put() 释放资源。
内存泄漏风险:在全志平台上,如果dma-buf的引用计数管理不当,会导致CMA区域无法回收,最终出现“alloc_contig_range: failed”错误。务必在DRM framebuffer的 destroy 回调中正确释放所有dma-buf附件。建议使用 drm_gem_prime_import() 自动管理引用计数,避免手动调用 dma_buf_get() 和 dma_buf_put() 不配对。
显示缓存生命周期流程图 1. 分配缓存 2. 导入DRM 3. 映射IOMMU 4. 显示输出 5. 释放缓存 关键:步骤3中,如果IOMMU映射失败,DE引擎将无法访问缓存,导致黑屏或画面冻结

2.6 常见问题与调试方法

问题现象 可能原因 调试方法
画面全黑,无信号 DE引擎未收到有效帧地址 检查 /sys/kernel/debug/dri/0/state 中plane的 src_x/src_y 和 crtc_x/crtc_y 是否正确
画面撕裂(tearing) 行跨度不对齐或DMA burst跨页 使用 devmem2 读取DE寄存器,确认stride值是否为16的倍数
颜色偏移或花屏 像素格式配置错误或物理地址错位 打印 fb->format->format 和 sg_phys(sgt->sgl),与硬件手册对比
IOMMU page fault IOVA映射不连续或页表未刷新 查看 /sys/kernel/debug/iommu/ 下的fault日志,使用 iommu_domain_alloc() 时指定 IOMMU_DOMAIN_DMA
CMA分配失败 CMA区域耗尽或碎片化 使用 cat /proc/meminfo | grep Cma 查看剩余CMA大小,调整内核cmdline中的 cma= 参数
实战工具推荐:在全志平台上,使用 sunxi_dump 工具可以直接读取DE引擎的寄存器状态。例如 sunxi_dump 0x01000000 0x100 可以dump出DE3的全局配置寄存器,快速确认图层地址是否已正确写入。另外,trace-cmd 配合 dma_buf 事件可以跟踪dma-buf的分配和释放路径,定位内存泄漏。