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 字节的整数倍。违反这些对齐约束会导致显示画面出现“条纹错位”或“颜色偏移”。
2.3 IOMMU映射:让非连续内存“看起来”连续
当显示引擎必须使用非连续物理内存(例如GPU渲染输出或视频解码输出)时,IOMMU(Input-Output Memory Management Unit)就派上了用场。全志平台通常集成一个系统IOMMU(也叫SMMU),可以为DE引擎提供虚拟连续地址空间。
IOMMU映射流程
- 分配SG表:从DMA-Heap分配出多个不连续的物理页,记录在scatterlist中。
- 映射到IOMMU:调用
iommu_map_sg()将SG表映射到IOMMU的I/O虚拟地址空间(IOVA)。 - 配置DE引擎:将IOVA地址写入DE图层的起始地址寄存器,而不是物理地址。
- 启用IOMMU:确保DE引擎的AXI总线端口连接到IOMMU,且IOMMU页表已生效。
常见陷阱:全志H6的IOMMU默认页大小为4KB,但DE3引擎的burst请求可能跨越页边界。如果IOMMU页表没有预先映射相邻的虚拟页,DE引擎在burst跨页时会触发IOMMU page fault,导致显示冻结。解决方案是使用
IOMMU_MAP_CONTIGUOUS 标志,确保IOMMU为SG表分配连续的IOVA空间。
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驱动中,显示缓存的生命周期管理遵循“分配 → 映射 → 使用 → 释放”的闭环。以下是全志平台上的典型流程:
- 分配:通过
dma_alloc_attrs()从CMA区域分配连续物理内存,或通过dma_buf_alloc()从DMA-Heap分配。 - 导入:将dma-buf导入到DRM framebuffer,调用
drm_gem_prime_import()。 - 映射:在plane更新时,通过
dma_buf_map_attachment()获取SG表,并映射到IOMMU(如果启用)。 - 使用:将映射后的地址写入DE引擎寄存器,启动显示。
- 释放:在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() 不配对。
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的分配和释放路径,定位内存泄漏。