1. 项目概述
在嵌入式系统开发,尤其是基于NXP Layerscape系列高性能SoC(如LS1043A、LS1088A、LX2160A等)的项目中,系统启动流程的稳定性和效率是项目成败的基石。而在这个流程中, DDR内存的初始化 无疑是其中最复杂、也最关键的环节之一。它直接决定了处理器能否在脱离片上SRAM后,正确、稳定地访问外部大容量RAM,进而加载并运行U-Boot、Linux内核等后续组件。过去,这项工作通常由U-Boot的板级初始化代码负责,但随着系统对安全启动(Secure Boot)和可信执行环境(TEE)的要求日益提高,ARM的 TF-A(Trusted Firmware-A) 成为了更现代、更标准化的选择。
一个显著的变化是,在较新的Layerscape SDK(LSDK)中,DDR初始化的职责已经从U-Boot移交给了TF-A的BL2阶段。这种架构迁移不仅仅是代码位置的改变,它意味着更早的内存可用性、更清晰的信任链划分,以及对不同启动介质(如QSPI NOR、SD卡、eMMC)更统一的管理。然而,这也给开发者带来了新的挑战:如何在TF-A的框架下,为千差万别的硬件板卡(可能使用标准DIMM、Mock DIMM或分立式DDR颗粒)正确配置DDR参数?生成的 bl2.pbl 和 fip.bin 镜像又该如何部署到不同的存储介质上?
本文将从一个资深嵌入式系统工程师的视角,深入剖析NXP Layerscape平台在TF-A启动流程下的DDR初始化机制与完整部署实践。我会结合官方文档和实际项目中的踩坑经验,不仅告诉你“怎么做”,更会解释“为什么这么做”,并分享那些在标准手册里不会写的调试技巧和注意事项。无论你是正在将旧项目迁移到TF-A流程,还是为新板卡进行启动适配,这篇文章都将提供一份可直接参考的“实战地图”。
2. 核心思路与架构迁移解析
2.1 为何要将DDR初始化移至TF-A?
在传统的“BootROM -> U-Boot (含DDR Init) -> Linux”启动流程中,U-Boot在完成DDR初始化后,自身会重定位到DDR中运行,然后再加载内核。这个模式简单直接,但也存在一些局限性:
- 安全边界模糊 :U-Boot通常被认为是“非安全世界”的软件,让非安全软件在最早阶段初始化关键硬件(如DDR),不利于构建从硬件信任根开始的完整信任链。
- BL2阶段能力浪费 :TF-A的BL2阶段运行在芯片的片上RAM(如OCRAM)中,其代码体积受限于SRAM大小。在BL2阶段初始化DDR,意味着BL2自身以及后续需要加载的BL31、BL32(如OP-TEE)、BL33(U-Boot)镜像,都可以被加载到容量大得多的DDR中,这极大地放宽了对每个阶段镜像大小的限制,为加入更复杂的固件功能(如更完备的硬件初始化、安全服务)提供了可能。
- 统一初始化入口 :对于支持多种启动介质(NOR/NAND/SD/eMMC)的SoC,BootROM会根据引脚配置选择介质并加载PBL(Pre-Boot Loader,通常由RCW配置生成)和BL2。将DDR初始化放在BL2中,使得无论从哪种介质启动,内存初始化的逻辑都是同一份代码,提高了代码的复用性和可维护性。
因此,NXP在新的LSDK中,为LS1012A、LS1043A、LS1046A、LS1088A、LS2088A、LX2160A等平台引入了“TF-A boot flow”。其核心变化就在于: DDR初始化(DDR Init)的代码从U-Boot移到了TF-A的BL2中执行 。新的启动链条变为: Boot ROM -> BL2 (DDR Init) -> BL31 -> BL33 (U-Boot/UEFI) -> Linux 。
2.2 TF-A启动镜像组成解析
理解镜像组成是进行部署的前提。在新的流程下,我们主要和两个由TF-A构建的镜像文件打交道:
-
bl2_<boot_mode>.pbl:这是系统的“第二级引导程序”。它不是一个单纯的BL2镜像,而是一个 复合镜像 。其生成过程是:将特定启动模式(如qspi,sd,nor)的RCW二进制文件(rcw_<boot_mode>.bin)与编译出的BL2二进制(bl2.bin)通过pbl工具拼接而成。BootROM会首先加载并运行这个.pbl文件。其中,BL2部分就包含了我们关注的DDR初始化代码。 -
fip.bin:即Firmware Image Package。这是一个由fiptool工具创建的容器镜像,内部打包了后续所有阶段的固件:-
BL31:EL3运行时固件,提供电源管理、安全监控等基础服务。 -
BL32(可选):可信操作系统(Trusted OS),例如OP-TEE,用于提供安全服务。 -
BL33:非安全世界的引导程序,即我们熟悉的U-Boot或UEFI镜像。
-
这种分离的设计带来了部署上的灵活性: bl2_.pbl 包含了硬件相关的早期初始化(如时钟、DDR),通常烧写在存储介质的起始固定位置;而 fip.bin 包含了系统服务和应用引导程序,可以单独更新。
2.3 板级支持的关键: _init_ddr 函数
TF-A的BL2是一个通用框架,它并不知道你的板子上用的是哪种DDR芯片、如何配置。这部分硬件相关的知识,通过一个 板级特定的函数 _init_ddr 来注入。
这个函数是连接TF-A框架和NXP私有DDR驱动程序的桥梁。它的核心职责是:
- 收集并填充DDR配置信息(如控制器数量、时钟频率、DIMM信息或静态参数)。
- 调用NXP提供的通用DDR初始化函数
dram_init()。 - 处理DDR初始化后的相关勘误(Errata)应用。
- 返回初始化成功的DDR总容量。
开发者需要根据自己板卡的硬件设计,在对应的板级目录(例如 plat/nxp/soc-ls1043/ls1043ardb/ )下实现这个函数。接下来,我们就深入三种最常见的硬件场景,看看具体该如何实现。
3. 三种DDR硬件配置的实战详解
NXP的DDR驱动层为不同的硬件形态提供了三种适配模式:DIMM(带SPD)、Mock DIMM(静态时序)和Discrete DDR(静态寄存器)。选择哪种模式,完全取决于你的硬件设计。
3.1 DIMM模式:让SPD说话
如果你的板卡使用了标准的 DDR4 DIMM内存条 ,那么恭喜你,这是最“省心”的模式。DIMM上的SPD(Serial Presence Detect)EEPROM芯片已经存储了该内存条的所有时序参数、容量信息。驱动的工作就是通过I2C总线读取这些信息,并自动配置DDR控制器。
你需要做的配置工作:
-
定义关键宏 :在板级的
platform_def.h文件中,你需要告诉驱动有几个DDR控制器,每个控制器上插了几个DIMM。// 示例:LS1088A有两个DDR控制器,每个控制器插了1条DIMM #define NXP_DDRCLK_FREQ 100000000 // DDR参考时钟频率,单位Hz #define NUM_OF_DDRC 2 // DDR控制器数量 #define DDRC_NUM_DIMM 1 // 每个控制器上的DIMM数量注意 :
DDRC_NUM_DIMM指的是每个控制器的DIMM数,不是总数。对于双通道板载内存(没有物理DIMM插槽),通常需要配置为1


1978


被折叠的 条评论
为什么被折叠?



