嵌入式视频处理内存优化:DMM/TILER硬件配置与缓冲区布局实战
1. 项目概述:为什么我们需要DMM/TILER?
在嵌入式多媒体处理,尤其是高清视频编解码、图像分析这类对内存带宽和延迟极其敏感的应用里,工程师们常常面临一个核心矛盾:算法需要的是二维空间上连续的数据块(比如一个1920x1080的图像帧),但SDRAM(同步动态随机存储器)的物理特性决定了其访问效率与数据排布方式紧密相关。传统的线性(Raster Scan)存储方式在处理图像的行数据时,会频繁触发SDRAM的行激活(RAS)和预充电(Precharge)操作,导致大量的延迟和带宽浪费,这就是所谓的“内存墙”瓶颈。
这时,像德州仪器(TI)某些高性能SoC(如OMAP、DaVinci系列)中集成的DMM(动态内存管理器)和TILER(平铺引擎)模块,就成了破局的关键。它们不是简单的内存分配器,而是一套硬件加速的地址映射与数据重组引擎。简单来说,DMM/TILER在软件(CPU/驱动)看到的内存地址(系统地址)和硬件(SDRAM控制器)实际访问的物理地址之间,插入了一层智能的“翻译官”和“搬运工”。
它的核心价值在于“空间局部性”优化。想象一下,一个图像处理算法(如运动估计、滤波)通常需要访问一个宏块(例如16x16像素)及其周围的像素。如果数据按行线性存储,要取一个16x16的块,你可能需要访问16个不同的SDRAM行,每次都有开销。而TILER通过将内存划分为固定大小的“瓦片”(Tile,例如64x64像素),并将二维图像数据按瓦片顺序存储,使得一个宏块内的像素更可能集中在少数几个甚至一个SDRAM行中。这样,DMA引擎(如VPDMA)在搬运数据时,就能以更高的效率进行突发传输,显著减少访问次数和延迟。
你提供的资料正是深入这一核心机制的宝贵实践指南。它不仅仅介绍了概念,更聚焦于如何通过配置一系列精密的硬件寄存器,将理论转化为实际可用的视频缓冲区。从设置内存区域(LISA MAP)到定义地址转换视图(PAT View),再到利用查找表(LUT)进行复杂映射,最后落实到具体H.264视频缓冲区(Luma/Chroma)的布局计算,这是一条完整的从硬件原理到软件实践的工程路径。对于从事嵌入式多媒体系统开发、驱动开发或性能优化的工程师而言,理解并掌握DMM/TILER的配置,是释放芯片图像处理单元(如HDVICP)全部潜力的必修课。
2. 核心概念与硬件架构解析
在动手配置寄存器之前,我们必须先厘清几个核心概念以及它们在硬件中是如何协作的。如果把DMM/TILER看作一个内存访问的“调度中心”,那么以下几个部分就是它的核心部门。
2.1 DMM与TILER的分工
首先,DMM和TILER虽然常常被一起提及,但职能有清晰划分:
- DMM:可以看作是总调度和资源规划局。它负责管理整个系统地址空间到物理SDRAM的映射关系。它定义了大的内存区域(Section),决定了一段系统地址是映射到EMIF0、EMIF1,还是以交错(Interleave)的方式同时映射到两者。这解决了“数据放在哪块物理内存上”的问题,直接影响内存控制器的负载均衡和总带宽。
- TILER:则是专门针对二维数据(如图像、视频帧)的“空间规划师”。它不关心数据最终在哪个EMIF,只关心数据在系统地址空间内的排布方式。TILER将线性的系统地址空间,虚拟成一个巨大的二维网格,并按照特定的瓦片格式(8-bit, 16-bit, 32-bit Tiled或Paged模式)来组织数据。这解决了“数据以何种结构存放”的问题,旨在优化访问局部性。
两者协同工作:CPU或DMA引擎发出一个针对某图像缓冲区的系统地址访问请求,这个请求先经过TILER,根据其配置的格式,将系统地址转换成另一个“中间”地址(这个过程可能涉及复杂的二维到一维映射)。然后,这个“中间”地址再交给DMM,由DMM根据LISA MAP的配置,最终翻译成目标EMIF上的物理地址,完成访问。
2.2 PAT与LUT:地址翻译的两种模式
这是DMM/TILER提供灵活性的关键。PAT(物理地址转换)提供了两种地址翻译路径:
直接映射模式:这是最简单直接的模式。通过配置
DMM_PAT_VIEW_MAP等寄存器,可以将特定的TILER格式(如8-bit模式)的整个地址范围,固定映射到一段连续的物理内存上。你资料中“PAT Direct Access Translation with Distinct 128 MB Containers”就是一个典型例子。这种模式无需LUT,延迟最低,但灵活性也最差,通常用于静态分配、格式固定的缓冲区。LUT(查找表)模式:这是功能最强大的模式。DMM内部维护着一个或多个二维的查找表(例如256x128条目)。每个LUT条目存储了一个物理页地址。当TILER转换出一个地址后,其高位(可视为页号)作为索引去查询LUT,取出对应的物理页地址,再与地址低位拼成最终物理地址。这意味着,你可以实现非连续的、任意形状的、甚至动态变化的物理内存映射。你资料中详细描述的几种“Refill”方式,就是教我们如何高效地配置和更新这张表。
为什么需要LUT?考虑一个复杂场景:系统需要同时处理多个不同分辨率、不同格式的视频流,并且内存可能因为碎片化而无法提供大块连续物理空间。直接映射无能为力,而LUT模式可以像“拼图”一样,将多个分散的物理内存页,在系统地址空间中“拼接”成一个连续的、平铺的虚拟缓冲区,供算法直接使用。
2.3 LISA MAP:系统内存的顶层规划图
DMM_LISA_MAP__0到__3这四个寄存器,定义了最多4个内存区域。它们回答了最根本的问题:系统地址空间的哪一段,对应到哪块(或哪两块)物理SDRAM。
- SYS_ADDR/SYS_SIZE:定义了本区域在系统地址空间中的起始地址和大小。
- SDRC_MAP:指定映射到哪个内存控制器(EMIF0, EMIF1, 或两者交错)。
- SDRC_INTL:如果映射到两个控制器,则定义交错粒度(128B, 256B, 512B)。交错访问能均匀利用两个EMIF的带宽,是提升性能的关键。
- SDRC_ADDR:指定该区域在目标EMIF地址空间中的起始地址。
你资料中的三个用例(对称2GB、对称1152MB、非对称1152MB)正是LISA MAP配置艺术的体现。特别是非对称配置,资料给出了明确警告:除非受限于系统成本和约束,否则强烈不推荐。原因在于,非对称配置会破坏内存访问的均衡性,可能导致一个EMIF过载而另一个闲置,无法充分发挥双通道内存的带宽优势,极易成为性能瓶颈。
3. 实战配置:从寄存器设置到缓冲区布局
现在,我们结合你提供的详细用例,一步步拆解如何配置DMM/TILER来管理实际的视频缓冲区。我们以一个典型的1080p H.264编解码场景为例,需要分配YUV420格式的亮度和色度缓冲区。
3.1 基础寄存器初始化流程
在开始任何复杂操作前,需要先搭建好DMM/TILER的工作环境。以下是基于你资料6.3.1节的标准化初始化序列,我补充了每一步的意图和注意事项:
设置PAT视图基地址:
// 将DMM_PAT_VIEW_MAP_BASE设置为0x80000000 // 这意味着所有PAT视图的偏移计算都基于此系统地址。这通常是SDRAM在系统内存地图中的起始地址。 WRITE_REG(DMM_PAT_VIEW_MAP_BASE, 0x80000000);注意:此地址必须与
DMM_LISA_MAP中定义的某个区域的SYS_ADDR对齐或在其范围内,否则后续映射无效。配置四个PAT视图:
// 默认情况下,DMM_PAT_VIEW_MAP__0~3均为0,意味着所有TILER模式都直接映射到基地址。 // 这是一个安全的初始状态,但通常我们需要为不同模式指定不同的内存容器。 // 例如,设置视图0,将不同模式映射到不同的128MB对齐区域: // 值 0x03020100 的含义(按字节从低到高看): // 0x00: 8-bit模式容器 -> 0x80000000 + (0x00 << 28) = 0x80000000 // 0x01: 16-bit模式容器 -> 0x80000000 + (0x01 << 28) = 0x88000000 // 0x02: 32-bit模式容器 -> 0x80000000 + (0x02 << 28) = 0x90000000 // 0x03: Paged模式容器 -> 0x80000000 + (0x03 << 28) = 0x98000000 WRITE_REG(DMM_PAT_VIEW_MAP__0, 0x03020100); // 视图1~3可根据需要配置,例如用于不同的任务或数据流。意图:这步建立了“格式”到“内存区域”的绑定关系。例如,我们决定所有8-bit平铺的数据(如YUV的Luma分量)都放在从0x80000000开始的128MB空间里。
为所有发起者(Initiator)分配PAT视图:
// 通过DMM_PAT_VIEW__0和__1寄存器,为每个ConnID(连接ID,代表一个硬件模块如VPDMA、CPU等)指定它使用哪个PAT视图。 // 默认值0表示所有发起者都使用PAT视图0。 WRITE_REG(DMM_PAT_VIEW__0, 0x00000000); WRITE_REG(DMM_PAT_VIEW__1, 0x00000000);实操心得:在多任务或异构计算场景中,可以为不同的硬件加速器分配不同的PAT视图,实现内存访问策略的隔离。例如,让视频编码器使用视图0(映射到高效连续内存),而图形渲染器使用视图1(映射到另一块内存),避免冲突。
配置发起者优先级:
// 通过DMM_PEG_PRIO_0/1和DMM_PEG_PRIO_PAT寄存器,调整不同发起者访问TILER和PAT的仲裁优先级。 // 在实时性要求高的系统中,应赋予视频处理单元(如HDVICP)高于通用CPU的优先级,确保其数据吞吐不受影响。 // 默认优先级均为0。注意事项:优先级设置不当可能导致低优先级模块(如CPU)访问内存时被长时间阻塞,影响系统整体响应。需要根据实际业务流量进行权衡和测试。
配置TILER方向:
// 通过DMM_TILER_OR__0/1寄存器,控制每个发起者访问平铺数据时的“方向”。 // 方向0是正常视图。某些图像处理算法可能需要旋转90/180/270度访问数据,可以通过修改此配置实现,而无需在内存中物理旋转数据,节省时间和带宽。 WRITE_REG(DMM_TILER_OR__0, 0x00000000); // 所有ConnID使用方向0 WRITE_REG(DMM_TILER_OR__1, 0x00000000);配置LISA内存区域: 这是最关键的一步,决定了系统内存的宏观布局。以你资料中Case 1: 两个EMIF,对称2GB DDR为例:
// 假设系统有2GB DDR,均匀分布在两个EMIF上,我们希望整个2GB空间(0x80000000 - 0xFFFFFFFF)以256字节粒度交错访问。 // 计算寄存器值:0x80640300 // 分解: // SYS_ADDR = 0x80 (系统地址高位,对应0x80000000) // SYS_SIZE = 0x7 (表示2GB区域) // SDRC_INTL = 0x2 (256字节交错) // SDRC_MAP = 0x3 (映射到两个EMIF) // SDRC_ADDR = 0x00 (在EMIF上的起始地址为0) WRITE_REG(DMM_LISA_MAP__0, 0x80640300); // 由于2GB空间一个区域已覆盖全部,可以将1~3区域配置为相同或禁用(SDRC_MAP=0)。 WRITE_REG(DMM_LISA_MAP__1, 0x80640300); WRITE_REG(DMM_LISA_MAP__2, 0x80640300); WRITE_REG(DMM_LISA_MAP__3, 0x80640300);配置后必须锁定(可选但推荐):
// 防止配置被意外修改,写入1锁定LISA MAP寄存器。 WRITE_REG(DMM_LISA_LOCK, 0x1);
3.2 视频缓冲区布局计算详解
完成基础初始化后,我们来解决一个具体问题:在128MB的8-bit模式容器中,能放下多少个带填充(Padding)的1080p Luma缓冲区?你资料中的计算过程非常经典,我们一步步拆解:
已知条件:
- 分辨率:1920x1080 (HD)
- 像素格式:YUV420, Luma分量8-bit/像素。
- TILER 8-bit模式:每个4KB页被组织为64像素宽 x 64像素高。
- 算法要求:每个缓冲区四周需要32字节的填充(Padding)。
- 容器大小:128MB, 对应TILER坐标空间为256页宽 x 128页高(因为每页4KB, 2561284096 = 128MB)。
计算步骤:
计算单个缓冲区所需的宽度(页数):
- 有效宽度:1920像素 = 1920字节。
- 左右填充:各32字节,总64字节。
- 总字节宽度:1920 + 64 = 1984 字节。
- 每页宽度为64字节。
- 所需页宽= 1984 / 64 =31页。
- 容器宽度可容纳缓冲区数= 256 / 31 ≈8.26,向下取整为8个。
计算单个缓冲区所需的高度(页数):
- 有效高度:1080像素 = 1080行。
- 上下填充:各32字节。注意,在垂直方向,填充也是按像素行计算的,但TILER按页组织,我们需要将像素高度转换为页高度。1080行加上下各32行填充,总高度为1080 + 64 = 1144像素行。
- 为了满足页对齐(这是使用LUT或简化地址计算的关键),需要将1144向上对齐到最近的页边界。每个TILER页高是64像素行,所以对齐到1152像素行(因为1152 / 64 = 18, 是整数)。
- 所需页高= 1152 / 64 =18页。
- 容器高度可容纳缓冲区数= 128 / 18 ≈7.11,向下取整为7个。
计算总容量:
- 总缓冲区数量 = 8 (宽) * 7 (高) =56个。
- 每个缓冲区大小 = 31页宽 * 18页高 * 4096字节/页 = 31184096 ≈ 2.25MB。
- 总占用空间 ≈ 56 * 2.25MB ≈ 126MB, 小于128MB容器,验证可行。
色度(Chroma)缓冲区的计算同理,但参数不同:
- 格式:YUV420色度分量,分辨率960x540, 16-bit/像素(Cb和Cr交错存储)。
- TILER 16-bit模式:每页为64像素宽 x 32像素高(因为每个像素16-bit=2字节, 64322=4096字节)。
- 填充:每边16字节。
- 计算后可得,在128MB的16-bit容器中,可容纳16个(宽) x 7个(高) = 112个色度缓冲区。
布局优化提示:你资料末尾提到,如果使用LUT进行地址翻译,可以进一步优化,允许缓冲区在子瓦片边界(sub-tile boundary)而非页边界对齐。这能减少因页对齐造成的内部碎片,在容器内塞入更多缓冲区。但这需要更精细的LUT条目编程,计算每个缓冲区的起始坐标会更复杂。
3.3 LUT的编程:五种模式实战
当直接映射不能满足需求时,就需要动用LUT。你资料中列出了5种LUT重填(Refill)模式,其复杂度和适用场景递增。我们重点分析最常用的两种:手动重填和链式自动重填。
核心数据结构 - PAT描述符: 所有模式都围绕这个16字节对齐的结构体展开。它本质上是一个链表节点,描述了要重填的LUT区域、如何重填以及数据在哪。
typedef struct { struct PAT_desc *next; // 指向下一个描述符的指针(用于链式) uint32_t area; // 定义LUT中要更新的矩形区域 (x0,y0,x1,y1) uint32_t ctrl; // 控制字:方向(DIR)、同步(SYNC)、启动(START)等 uint32_t *data; // 指向物理���址转换表(即要写入LUT的数据)的指针 } __attribute__((aligned(16))) PAT_desc_t;模式一:简单手动区域重填这是最基础的模式,适用于静态或更新不频繁的映射。
- 准备数据表:在DDR中分配一块16字节对齐的内存,填充好要写入LUT的所有条目值(每个条目32位,仅[30:12]有效,代表物理页号)。
- 配置区域:将矩形区域的左上角(x0,y0)和右下角(x1,y1)写入
DMM_PAT_AREA_i寄存器。 - 提供数据指针:将数据表的物理地址写入
DMM_PAT_DATA_i寄存器。 - 启动重填:配置方向并置位
START位,写入DMM_PAT_CTRL_i寄存器。 - 轮询状态:检查
DMM_PAT_STATUS_i寄存器的DONE位,等待完成。
模式四:链式自动配置区域重填这是功能强大且常用的模式,适用于需要一次性建立多个非连续区域映射的场景(例如,为多个视频帧缓冲区建立映射)。
- 准备多个描述符和数据表:为每个需要映射的LUT区域创建一个描述符。在第一个描述符的
next字段填入第二个描述符的物理地址,第二个指向第三个,以此类推,最后一个的next为NULL。每个描述符的data字段指向各自的数据表。 - 提交链表头:将第一个描述符的物理地址写入
DMM_PAT_DESCR_i寄存器。 - 自动执行:硬件会自动从第一个描述符开始,依次处理链表上的每一个区域重填任务。
- 状态检查:每个区域完成时
DONE位会置起。当整个链表完成后,READY位会置起。
模式五:循环同步自动配置区域重填是最复杂的模式,它将描述符链表首尾相连形成一个环,并且可以与某个硬件发起者(如VPDMA)的访问同步。这常用于双缓冲或多缓冲场景:当硬件处理完缓冲区A并开始访问缓冲区B时,自动触发LUT将缓冲区C的映射加载到A的位置,实现“乒乓”操作,无缝切换,是实现零开销缓冲区轮转的关键。
4. 高级应用与性能调优指南
掌握了基础配置和缓冲区计算后,我们可以探讨一些高级话题和调优技巧,这些往往是在数据手册中不会明说,但在实际项目中至关重要的经验。
4.1 内存交错策略的选择与影响
在DMM_LISA_MAP中配置SDRC_INTL(交错粒度)对性能有直接影响。你资料中提到了128字节、256字节和512字节交错。
- 原理:交错访问意味着系统地址空间连续的一小段数据(一个“块”)依次分布在不同的EMIF上。例如256字节交错,则地址0-255在EMIF0,256-511在EMIF1,512-767在EMIF0,以此类推。
- 如何选择?
- 访问模式:分析你的主数据流发起者(如VPDMA)的典型访问突发长度(Burst Length)。如果DMA引擎通常以128字节为突发进行传输,那么128字节交错可能最匹配,能最大化并行性。
- 总线位宽与DRAM颗粒:考虑EMIF控制器的位宽(如32位)和DRAM的列访问长度。256字节可能是一个在多种场景下均衡的选择。
- 实测为王:最可靠的方法是进行基准测试。编写一个简单的内存带宽测试程序,用不同的交错粒度去连续读取/写入大块数据,测量实际带宽。在复杂系统中,最优值可能与理论推测有出入。
4.2 多视频流与复杂场景下的内存规划
在实际产品中(如多路视频录像机、视频会议终端),往往需要同时处理多个视频流(如画中画、多视角编码)。这时,DMM/TILER的规划就变得复杂。
分区策略:
- 按格式分区:这是最直观的,如你资料所述,用不同的PAT视图将8-bit、16-bit容器映射到不同的物理内存段。
- 按数据流分区:更高级的策略是为每个独立的视频流分配独立的PAT视图甚至LUT。例如,主码流使用视图0和LUT0,子码流使用视图1和LUT1。这可以实现流之间的内存访问隔离,避免相互干扰,也便于独立管理和释放资源。
- 混合策略:在内存充足的情况下,可以为每个流在8-bit和16-bit容器内划分独立的“子区域”。这需要精心计算每个缓冲区的位置,确保不会越界。
动态内存管理:
- 对于帧缓冲区这种生命周期明确(分配、填充、编码、释放)的大块内存,避免在系统运行时频繁进行
malloc/free。标准的做法是在系统启动时,就从Linux内核或RTOS的预留内存(Reserved Memory)或CMA(连续内存分配器)区域中,一次性分配出一大块连续物理内存。 - 然后,在这块大内存内部,由驱动或中间件实现一个简单的池分配器。池中的每个元素就是一个计算好尺寸和位置的视频缓冲区。申请和释放只是对池索引的操作,速度极快,且无碎片化问题。DMM/TILER的LUT可以预先为池中所有缓冲区建立好映射关系。
- 对于帧缓冲区这种生命周期明确(分配、填充、编码、释放)的大块内存,避免在系统运行时频繁进行
4.3 调试技巧与常见问题排查
当视频处理出现花屏、卡顿或DMA错误时,DMM/TILER配置可能是嫌疑对象之一。以下是一些实用的调试手段:
寄存器状态检查:
- 首先,在初始化后和出问题时,dump出所有关键的DMM/TILER寄存器,与预期值对比。重点关注
DMM_LISA_MAP_x、DMM_PAT_VIEW_x、DMM_PAT_VIEW_MAP_x以及正在使用的DMM_PAT_STATUS_x(查看READY,DONE,ERROR位)。 DMM_PAT_STATUS_i.ERROR位被置起,通常意味着有发起者尝试访问了一个尚未被LUT重填的区域(即“页错误”)。这往往是由于LUT重填速度跟不上访问速度,或者链式描述符配置错误。
- 首先,在初始化后和出问题时,dump出所有关键的DMM/TILER寄存器,与预期值对比。重点关注
LUT内存转储:
- 你资料中6.3.3.1.3节描述的“PAT Memory Dump”功能是终极调试利器。通过将PAT重填引擎设置为直接访问模式,可以直接读取LUT中任意坐标的值。
- 操作流程: a. 设置
DMM_PAT_CONFIG寄存器中对应引擎为直接访问模式(DIRECT LUT ACCESS)。 b. 在DMM_PAT_AREA_x中设置要读取的LUT坐标(只需设置x0, y0)。 c. 在DMM_PAT_CTRL_x中选择LUT_ID(0或1)。 d. 从DMM_PAT_DATA_x寄存器中读取值。 - 将读出的物理页地址与你的预期进行比对,可以精确发现是哪一块映射出了问题。
性能 profiling:
- 如果怀疑性能未达预期,可以使用芯片的性能监控单元(PMU)或EMIF控制器自身的计数器,监控两个EMIF的读写带宽、利用率、冲突率等。
- 对比启用TILER和禁用TILER(使用线性缓冲区)时的带宽数据,可以直观验证TILER带来的收益。通常,对于二维行访问,性能提升可达30%甚至更高。
典型问题速查表:
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 图像错位、撕裂 | LUT映射错误,或缓冲区地址/尺寸计算错误 | 1. 检查缓冲区宽高、填充值计算。2. 使用LUT Dump功能,核对关键缓冲区的映射条目。3. 检查PAT描述符中的area字段是否定义正确。 |
| 系统访问某地址时挂死或报总线错误 | 地址映射缺失或越界。系统地址未命中任何LISA MAP区域,或映射到了无效的物理地址。 | 1. 检查DMM_LISA_MAP配置,确保访问的系统地址落在已定义的Section内。2. 检查Section映射的SDRC_MAP和SDRC_ADDR是否有效。 |
| 视频处理帧率下降,DMA效率低 | 内存访问模式未优化,或交错配置不当。 | 1. 确认使用的的确是Tiled缓冲区而非线性缓冲区。2. Profiling EMIF带宽,尝试调整SDRC_INTL交错粒度。3. 检查是否因内存碎片导致缓冲区物理不连续,破坏了TILER的访问局部性。 |
| 多路流中某一路异常 | PAT视图或LUT配置被意外修改,或资源冲突。 | 1. 为每路流单独配置PAT视图和LUT,确保隔离。2. ���查是否有其他任务或驱动错误地改写了共享的DMM/TILER寄存器。 |
5. 从理论到实践:一个简化的驱动设计示例
最后,我们勾勒一个在Linux驱动中初始化DMM/TILER并分配一个Tiled视频缓冲区的简化流程,将前面的所有知识点串联起来。请注意,这是概念性代码,省略了错误处理和硬件特定细节。
#include <linux/io.h> #include <linux/dma-mapping.h> // 假设寄存器基地址已映射到 dmm_base void *dmm_base; // 1. 初始化LISA MAP (对称2GB,256B交错) void dmm_lisa_map_init(void) { // 解锁LISA MAP寄存器(如果需要) writew(0x0, dmm_base + DMM_LISA_LOCK); // 配置Section 0: 0x80000000 - 0xFFFFFFFF, 2GB, 交错到两个EMIF u32 lisa_map_val = (0x80 << 24) | (0x7 << 20) | (0x2 << 18) | (0x3 << 8); writel(lisa_map_val, dmm_base + DMM_LISA_MAP_0); // Section 1-3 配置为相同或禁用 writel(0x0, dmm_base + DMM_LISA_MAP_1); // 禁用 // 锁定配置 writew(0x1, dmm_base + DMM_LISA_LOCK); } // 2. 初始化PAT视图 (直接映射模式) void dmm_pat_view_init(void) { // 设置基地址 writel(0x80000000, dmm_base + DMM_PAT_VIEW_MAP_BASE); // 配置视图0: 8-bit,16-bit,32-bit,paged模式分别映射到不同的128MB块 writel(0x03020100, dmm_base + DMM_PAT_VIEW_MAP_0); // 所有发起者使用视图0 writel(0x00000000, dmm_base + DMM_PAT_VIEW_0); writel(0x00000000, dmm_base + DMM_PAT_VIEW_1); } // 3. 分配并映射一个Tiled视频缓冲区 struct tiled_buffer { dma_addr_t phys_addr; // 缓冲区的物理地址(CPU视角,线性) void *virt_addr; // 缓冲区的虚拟地址 size_t size; // 缓冲区大小(字节) u32 sys_start; // 缓冲区在Tiled系统地址空间中的起始地址 u32 width_pages; // 宽度(页数) u32 height_pages; // 高度(页数) }; int alloc_tiled_buffer(struct tiled_buffer *buf, int width_pixels, int height_pixels, int bit_mode) { // 步骤1: 根据像素宽高、bit_mode(8/16)、填充大小,计算所需页宽和页高 // ... (计算过程如3.2节所述) ... buf->width_pages = calc_width_pages(width_pixels, bit_mode); buf->height_pages = calc_height_pages(height_pixels, bit_mode); // 步骤2: 通过DMA API分配一块连续的、页对齐的物理内存 buf->size = buf->width_pages * buf->height_pages * 4096; buf->virt_addr = dma_alloc_coherent(dev, buf->size, &buf->phys_addr, GFP_KERNEL); if (!buf->virt_addr) return -ENOMEM; // 步骤3: 计算该缓冲区在Tiled系统地址空间中的位置。 // 这里假设使用直接映射模式,且我们在8-bit容器内有一个简单的分配器。 // 我们需要维护一个容器内的“当前空闲位置”指针(如current_x_pages, current_y_pages)。 static u32 curr_x = 0, curr_y = 0; // 检查是否超出容器边界 (256x128 pages)... if (curr_x + buf->width_pages > 256) { curr_x = 0; curr_y += buf->height_pages; } if (curr_y + buf->height_pages > 128) { // 容器已满,分配失败 dma_free_coherent(dev, buf->size, buf->virt_addr, buf->phys_addr); return -ENOMEM; } // 步骤4: 计算系统起始地址。 // 对于直接映射,系统地址 = PAT_VIEW_MAP_BASE + (container_base_offset) + (y * 256 + x) * 4096 // 假设8-bit容器在0x80000000,且容器内布局是简单的二维数组。 u32 page_offset = curr_y * 256 + curr_x; // 在容器内的页索引 buf->sys_start = 0x80000000 + (page_offset * 4096); // 步骤5: 如果是LUT模式,则需要编程LUT。 // 我们需要为这个缓冲区覆盖的每一个页(buf->width_pages * buf->height_pages个)创建一个LUT条目。 // 每个条目值 = (buf->phys_addr >> 12) + 页偏移。 // 然后通过PAT描述符链表,将这片LUT区域重填。 // 此处省略复杂的LUT编程代码... // 步骤6: 更新分配器指针 curr_x += buf->width_pages; // 步骤7: 将buf->sys_start这个地址返回给视频算法或DMA引擎使用。 // 对HDVICP/VPDMA来说,它们使用这个地址,经过TILER和DMM转换后,最终访问到buf->phys_addr处的物理内存。 return 0; } // 4. 主初始化函数 int dmm_tiler_driver_init(struct platform_device *pdev) { // 映射寄存器地址空间 dmm_base = ioremap(regs->start, resource_size(regs)); // 初始化DMM/TILER硬件 dmm_lisa_map_init(); dmm_pat_view_init(); // 可以继续配置TILER方向、优先级等... // 为视频管道分配Tiled缓冲区 struct tiled_buffer y_buf, uv_buf; alloc_tiled_buffer(&y_buf, 1920, 1080, 8); // Luma, 8-bit模式 alloc_tiled_buffer(&uv_buf, 960, 540, 16); // Chroma, 16-bit模式 // 将y_buf.sys_start和uv_buf.sys_start配置到VPDMA的描述符中... // ... return 0; }这段示例代码展示了从硬件初始化到缓冲区分配的逻辑闭环。在实际项目中,你需要一个更健壮的管理器来跟踪容器内的空闲区域,处理LUT的动态更新,并集成到具体的内存分配框架(如ION、DMA-BUF)中。但万变不离其宗,核心依然是理解DMM/TILER如何将系统地址、Tiler格式、物理地址这三者精巧地联系起来,从而为数据流提供一条高效、可控的通道。
