2. Bootloader与系统启动流程:U-Boot移植与调试、ATF启动流程、内核启动参数与设备树调试
车载SoC的启动流程,说白了就是一段「从复位向量到用户态」的接力赛。每一棒跑砸了,系统就起不来。我这些年调过的板子,十有八九的启动问题都出在Bootloader、ATF或者设备树这三个环节上。今天咱们就把这条链路彻底捋清楚。
2.1 U-Boot移植与调试
U-Boot是嵌入式Linux的事实标准Bootloader。移植U-Boot到一块新板子,核心工作就三件:配置DDR时序、适配Flash/NAND/eMMC驱动、搞定网络/串口等基础外设。
2.1.1 移植第一步:板级目录与配置
我个人习惯从一颗已经支持的参考板开始。比如你的SoC是NXP i.MX8QM,那就先看 board/freescale/imx8qm_mek/ 这个目录。复制一份,改名字,然后修改 Kconfig 和 MAINTAINERS。
# 创建板级目录
cp -r board/freescale/imx8qm_mek board/freescale/my_car_board
# 创建板级配置
cp configs/imx8qm_mek_defconfig configs/my_car_board_defconfig
然后执行 make my_car_board_defconfig,再 make menuconfig 微调。这里有个坑——DDR参数必须跟硬件原理图严格对应。我曾经因为一个时序参数抄错了,板子死活跑不到DDR4的标称频率,查了三天才发现是寄存器配置里少写了一个bit。
board/<vendor>/<board>/ddr.c 或 drivers/ddr/ 下。不要直接改通用驱动,要新建板级文件。
2.1.2 调试手段:串口与JTAG
U-Boot早期阶段(SPL/TPL)还没有完整的驱动栈,调试基本靠串口打印。我建议你在 arch/arm/mach-<soc>/lowlevel_init.S 里加几个 putc 宏,确认CPU、时钟、DDR是否正常。
| 调试阶段 | 常用手段 | 典型问题 |
|---|---|---|
| SPL阶段 | 串口打印、GPIO电平 | DDR初始化失败、时钟配置错误 |
| U-Boot proper | 串口、网络tftp、mmc | 环境变量损坏、分区表错误 |
| 内核启动前 | bootm/booti命令、设备树检查 | 内核地址不对、设备树语法错误 |
bdinfo 可以查看板级信息,mmc info 看存储设备,printenv 看环境变量。这些命令在调试初期非常有用。
2.2 ATF(ARM Trusted Firmware)启动流程
ARMv8架构引入了异常级别(EL0-EL3)。ATF运行在最高权限的EL3,负责安全启动、PSCI电源管理、以及切换到下一个阶段。车载SoC对安全要求极高,ATF是绕不过去的坎。
2.2.1 ATF的启动顺序
ATF的启动流程可以概括为:BL1 → BL2 → BL31 → BL32(可选)→ BL33(U-Boot)。
- BL1(Boot ROM):固化在SoC内部,加载BL2到SRAM。
- BL2(Trusted Boot Firmware):验证并加载BL31和BL32。
- BL31(EL3 Runtime Firmware):提供PSCI、SMC等服务,然后跳转到BL33。
- BL33(Non-secure World):就是U-Boot或直接是内核。
嗯,这里要注意:BL2和BL31的链接地址必须跟硬件设计一致。我遇到过一块板子,BL31的基地址跟ATF源码默认值差了0x1000,结果每次启动到PSCI初始化就挂。后来用JTAG单步跟踪才发现是地址对齐问题。
核心配置项:在ATF的 plat/<vendor>/<platform>/platform_def.h 里,重点关注 PLAT_PHY_ADDR_SPACE_SIZE、BL31_BASE、BL32_BASE 这几个宏。
2.2.2 PSCI与CPU热插拔
PSCI(Power State Coordination Interface)是ATF提供给内核的电源管理接口。车载SoC经常需要动态开关CPU核心来省电。如果PSCI实现有问题,内核执行 cpu_suspend 或 cpu_off 时就会卡死。
// 在ATF的psci_statistics.c里,可以加打印确认状态机
void psci_print_state(unsigned int cpu_id) {
INFO("CPU%d: current state = %d\n", cpu_id, psci_cpu_state[cpu_id]);
}
我曾经在调试一个ADAS域控时,发现系统休眠后唤不醒。查了两天,最后定位到是ATF的 psci_validate_power_state 函数里,对电源域ID的校验写错了。修完那一行代码,问题解决。
2.3 内核启动参数与设备树调试
设备树(Device Tree)是描述硬件信息的结构化数据。内核通过它来知道「我这块板子上有哪些外设、它们怎么连接、寄存器地址是多少」。设备树写错了,驱动就加载不对。
2.3.1 内核启动参数
U-Boot通过 bootargs 环境变量传给内核。车载系统常用的参数包括:
console=ttyS0,115200:指定调试串口root=/dev/mmcblk0p2:根文件系统分区rw:以读写方式挂载quiet:减少内核打印(量产时用)isolcpus=2,3:隔离CPU核心给实时任务
quiet,把 loglevel=8 加上,能看到最详细的内核打印。量产时再改回 loglevel=4。
2.3.2 设备树调试三板斧
设备树调试,我总结了三步走:
- 检查语法:用
dtc -I dts -O dtb -o test.dtb test.dts编译,看有没有报错。 - 查看实际加载:在U-Boot里执行
fdt addr $fdt_addr然后fdt list,确认设备树被正确加载到内存。 - 对比寄存器:用
devmem2或mmc read读出硬件寄存器值,跟设备树里的reg属性比对。
为什么第三步很重要?因为有时候硬件设计改了,但设备树没同步更新。我见过一个案例,某款车载以太网PHY的复位GPIO从GPIO3_12改到了GPIO4_05,设备树里还是旧的,结果PHY死活不工作。查了整整一天,最后拿示波器量才发现复位引脚没拉高。
2.3.3 设备树覆盖(DT Overlay)
车载SoC经常有多个硬件变体。比如同一款域控制器,低配版用4GB内存,高配版用8GB。用设备树覆盖可以避免维护多份完整的dts文件。
// memory_overlay.dts
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = &memory;
__overlay__ {
reg = <0x0 0x80000000 0x0 0x80000000>; // 2GB
};
};
};
在U-Boot里用 fdt apply 命令加载覆盖文件。注意:覆盖文件的 target 必须跟基树里的节点标签完全一致。
interrupts 属性写错,会导致中断无法触发。用 cat /proc/interrupts 可以快速确认中断号是否匹配。
2.4 本章知识结构图
下面这张图展示了从SoC上电到内核启动的完整流程,以及每个阶段的关键交付物。
这张图把启动流程和调试手段对应起来了。你拿到一块新板子,按这个流程走一遍,基本能快速定位问题出在哪个阶段。
总结一下:U-Boot移植的核心是DDR和板级配置;ATF调试的关键是BL31的基地址和PSCI状态机;设备树调试要善用dtc编译和fdt命令。这三块搞定了,车载SoC的启动流程就稳了。