深入解析U-Boot TPL代码:嵌入式启动的“第一棒”背后的秘密 电子说
在嵌入式系统启动过程中,从按下电源键到操作系统开始运行,中间藏着一系列精密的初始化步骤。今天我们就来拆解 Rockchip 平台 U-Boot 中的 TPL(Tiny Program Loader)阶段核心代码tpl.c,看看这个 "启动第一棒" 是如何为整个系统保驾护航的。

TPL 是 U-Boot 启动流程中最早执行的阶段之一,全称 Tiny Program Loader(小型程序加载器)。不同于后续的 SPL(Secondary Program Loader)和 U-Boot 主体,TPL 运行在系统资源极其有限的环境中(通常依赖片内 SRAM),却要完成最关键的硬件初始化工作。
可以把嵌入式启动比作一场接力赛:
•BootROM(芯片内置的启动程序)是 "发令员"
•TPL 是 "第一棒选手",负责启动最基础的硬件
•SPL 是 "第二棒",完成更多初始化
•U-Boot 主体则是 "最后一棒",最终引导操作系统
而tpl.c正是 "第一棒选手" 的核心操作手册。
我们通过代码结构,一步步揭开 TPL 的工作内容:
void puts(const char *str){while (*str)putc(*str++);}void putc(char c){if (c == 'n')NS16550_putc((NS16550_t)(CONFIG_SYS_NS16550_COM1), 'r');NS16550_putc((NS16550_t)(CONFIG_SYS_NS16550_COM1), c);}
这段代码实现了最基础的字符串输出功能。在 TPL 阶段,系统还没有加载完整的函数库,因此需要直接操作 NS16550 串口控制器实现putc(输出字符)和puts(输出字符串)函数。
特别注意到对换行符n的处理—— 自动转换为rn(回车 + 换行),这是为了保证在串口终端上能正确换行,细节处体现了调试友好性。
int __weak timer_init(void) { return 0; }void __weak __udelay(unsigned long usec){u64 i, j, count;asm volatile ("MRS %0, CNTPCT_EL0" : "=r"(count));i = count;j = usec * 24; // 24MHz计数器i += j;while (1) {asm volatile ("MRS %0, CNTPCT_EL0" : "=r"(count));if (count > i) break;}}// 非ARM64架构的延迟实现void udelay(unsigned long usec) { __udelay(usec); }
时间延迟是硬件初始化的关键需求(比如等待外设上电稳定)。这段代码通过直接操作 CPU 计数器寄存器:
•为 ARM64 架构读取CNTPCT_EL0寄存器
•为其他架构使用mrrc p15指令
•基于 24MHz 的基准频率计算延迟时间
这种底层实现确保了在没有操作系统支持的情况下,系统仍能获得精确的微秒级延迟能力。
作为 TPL 阶段的主入口函数,board_init_f串起了整个初始化流程:
void board_init_f(ulong dummy){rockchip_stimer_init(); // 系统定时器初始化arch_cpu_init(); // CPU架构初始化debug_uart_init(); // 调试串口初始化printascii("U-Boot TPL " PLAIN_VERSION "...n"); // 版本信息打印timer_init(); // 定时器初始化// DRAM初始化(根据配置选择设备模型或直接调用)ret = uclass_get_device(UCLASS_RAM, 0, &dev);sdram_init();// 条件允许时返回BootROM执行下一阶段back_to_bootrom(BROM_BOOT_NEXTSTAGE);}
这个函数的执行逻辑清晰展现了 TPL 的核心任务:
1.初始化系统定时器,建立时间基准
2.完成 CPU 架构相关初始化
3.启动调试串口,输出版本信息(关键的调试点)
4.初始化 DRAM(内存),为后续阶段提供运行空间
5.交接控制权到下一阶段
TPL 作为启动的 "第一棒",其工作质量直接决定了系统能否正常启动:
1.硬件最小化初始化:在资源受限的环境下,优先初始化串口、定时器、内存等关键硬件,为后续阶段铺路
2.启动流程衔接:通过back_to_bootrom函数与 BootROM 协作,确保启动流程按顺序推进
3.兼容性适配:通过条件编译(如CONFIG_ARM64、CONFIG_TPL_LIBCOMMON_SUPPORT)支持不同架构和配置,提高代码复用性
4.故障隔离:如果 TPL 阶段失败(如 DRAM 初始化出错),系统会在早期挂起,避免错误扩散
对于嵌入式开发者来说,TPL 阶段的调试支持堪称 "救命稻草":
1.早期日志输出:通过debug_uart_init和printascii实现的早期打印,能帮助定位启动失败的第一现场。想象一下,如果连 "U-Boot TPL 版本信息" 都打印不出来,排查问题会有多困难!
2.时序问题排查:精确的udelay函数确保硬件初始化时序正确,减少因时序错误导致的 "偶发故障"
3.版本追溯:启动时打印的版本号(PLAIN_VERSION)和编译时间,能快速确认是否使用了正确的代码版本
4.错误定位:hang函数在出错时进入死循环,配合 JTAG 等工具可捕获现场状态,避免系统 "crash 后无痕迹"
tpl.c虽然代码量不大,却承载了嵌入式系统启动最基础也最关键的工作。它像一位严谨的 "系统初始化工程师",在资源极度有限的环境下,有条不紊地完成硬件唤醒、环境准备和流程交接。
对于开发者而言,理解 TPL 代码不仅能帮助我们快速定位启动问题,更能深入理解嵌入式系统从 "断电" 到 "运行" 的整个唤醒过程。下次调试启动故障时,不妨先看看 TPL 阶段的日志输出 —— 答案往往就藏在这些最早期的信息里。
全部0条评论
快来发表一下你的评论吧 !