板子启动卡在黑屏,串口日志停在内存初始化,屏幕那头的程序员盯着“uboot dram”几个字,心里琢磨着今晚又得加班了——这场景,搞嵌入式开发的谁没经历过呢?
说到U-Boot里的DRAM配置,搞嵌入式开发的小伙伴们肯定有一肚子话要说。这玩意吧,说白了就是让开发板的大脑(CPU)能正常用上内存(RAM)的过程,听着简单,实际调试起来能让人头发掉一地-7。

首先咱们得搞明白,为啥U-Boot非要去初始化DRAM不可。

你想啊,芯片一上电,里头那小脑袋瓜子是一片空白,啥也不知道。它得有个地方存放要执行的指令、要处理的数据吧?DRAM就是这个“记忆宫殿”。
不过这个宫殿在刚通电时是锁着的,门都没开。U-Boot的任务就是找到钥匙,把门打开,弄清楚宫殿里到底有多少个房间(内存容量),每个房间怎么布置(内存地址映射)。
这里头门道可多了,不同架构的芯片玩法完全不同。比如x86平台就搞得很复杂,它得依赖一个叫MRC(Memory Reference Code)的二进制文件来做初始化-1。
这MRC文件就像个神秘的黑盒子,你给它一些参数,它咕噜咕噜一顿算,告诉你内存有多大、时序怎么设。
更逗的是,这MRC还得配合Intel Management Engine(ME)的二进制文件一起工作,真是“二进制大礼包”啊-1。
早期的U-Boot里,DRAM参数基本都是硬编码在板级配置文件里的。比如某个开发板有512MB内存,起始地址是0x20000000,那就直接在代码里写死。
这方法简单粗暴,但有个大问题——不灵活。后来,更聪明的办法出现了:用设备树(Device Tree)来管理这些信息-2。
Clément Léger那哥们儿2021年给Atmel SAMA5D2系列板子提交的补丁就是个好例子。他干了个漂亮活儿——把原来硬编码的DRAM地址和大小都移到设备树里去了-2。
这么一来,想改内存配置就不需要重新编译U-Boot了,直接改设备树文件就行,方便多了。
特别是那些要用OP-TEE(一个安全执行环境)的系统,OP-TEE自己会占掉一块内存区域,U-Boot就得绕着这块地方用内存。
用设备树管理后,系统就能自动适应这种“内存被挖掉一块”的情况-2。
说到uboot dram的实际配置,这里头的坑可真不少。有时候你以为配好了,结果系统跑起来各种诡异问题。
有个经典情况就是:开发板上明明插了4GB内存条,但系统里只显示2GB左右。这通常是因为用了32位的U-Boot,地址空间有限,无法直接访问全部内存-6。
这时候U-Boot会显示两个数字:一个是真实的物理内存大小(比如4GB),另一个是能实际使用的“有效”内存大小(可能只有2GB多)。不了解这点的开发者可能会懵-6。
还有更隐蔽的bug。2023年有人发现i.MX8M和i.MX9芯片在特定模式下,dram_init_banksize()函数里有个计算错误,会导致DRAM起始地址和大小算错。
这个bug在普通启动模式下不明显,但在Falcon Mode(一种快速启动模式)下就会暴露出来-9。
现代嵌入式系统经常要给某些特殊功能预留内存,就像在大停车场里划出几个“专属停车位”。
比如视频解码要一大块连续内存,显示屏的帧缓冲也要固定位置,快启动时RTOS还需要自己的内存区域-4。
uboot dram的配置里就要考虑这些需求。以君正AD100平台为例,开发者可以在U-Boot配置里设置各种预留内存:VPU_MEM给H.264解码用,RMEM给LCD、JPEG编解码和2D加速用,SHARE_MEM给快启动时的RTOS显示用-4。
每种预留内存的计算都有门道。比如要给720p的LCD预留双缓冲内存,得这么算:(720×1280×4)×2,大约是7.3MB。
H.264解码内存的计算更复杂,得考虑分辨率、参考帧数量、码流缓存等多种因素-4。
当你真的面对一块新板子,要调通uboot dram时,那感觉就像在玩一种高难度的玄学游戏。
2016年,一位开发者分享了他的A10芯片调试经历。他用的是基于Cubieboard1的自定义硬件,DDR3芯片是MT41J64M16。
结果U-Boot启动时随机卡死,最远能跑到校验内核CRC32的地方,然后就停住了-10。
他试了一堆DRAM参数组合,发现当设置DRAM时钟为240MHz时,能有80%左右的概率跑到校验步骤;设为360MHz时,情况更糟-10。
这种情况很可能是DDR时序参数没配准。每个DDR芯片都有自己的一堆时序参数,什么行地址到列地址延迟、行预充电时间、行有效到行有效间隔等等。
这些参数通常需要从芯片数据手册里查,然后转换成控制器能理解的周期数-10。
随着芯片越来越复杂,uboot dram的配置方法也在进化。一种趋势是从硬件寄存器自动获取信息,而不是全靠人工配置。
比如Rockchip RK3328的U-Boot驱动就能从系统寄存器读取正确的DRAM大小,这样U-Boot就能和Rockchip的加载器或SPL更好地协作-8。
还有一种思路是保存初始化结果,避免每次启动都重新初始化。x86平台上那500多毫秒的DRAM初始化时间实在是太长了,有人就想到把初始化后的参数保存到SPI闪存里,下次启动时直接读取,能大大加快启动速度-1。
对了,U-Boot自己也会在启动时显示DRAM信息。新版代码会同时显示真实内存大小和可用内存大小,让开发者一目了然-6。
这个改进虽然小,但对调试帮助很大——至少你能一眼看出是不是32位限制导致的内存“缩水”。
调试U-Boot时,开发者在串口终端里看到“DRAM:”字样后显示的不仅是内存大小,更是无数个调试夜晚的结晶。当uboot dram最终被正确初始化,系统顺利启动,那一刻的成就感,足以抵消之前所有的挫败。