3. 嵌入式Linux系统启动流程与产测模式设计
STM32MP1 系列处理器的启动链路与通用 Cortex-A 应用处理器类似,但多级固件分工明确,每一级都有独立的加载地址、校验方式和可干预点。理解这条链路,是设计产测模式触发条件的前提:只有知道在哪一级可以插入判断、在哪一级可以改写启动参数,才能让设备在"正常启动"和"进入产测"之间可靠切换。
一、五级启动链路的分工
从上电到根文件系统挂载,STM32MP1 依次经历 ROM Code、FSBL(TF-A)、SSBL(U-Boot)、Linux Kernel、Rootfs 五个阶段。
ROM Code 固化在芯片内部,上电后首先运行。它读取 BOOT 引脚电平决定启动介质(eMMC、SD 卡、NAND、串口/USB),从介质固定偏移处加载 FSBL 到 SYSRAM,并校验头部签名。这一级不可修改,但 BOOT 引脚状态是产测可以借助的硬件输入。
TF-A(Trusted Firmware-A) 作为 FSBL,运行在 SYSRAM 中,负责 DDR 初始化、时钟树配置、安全域划分,然后加载 U-Boot 到 DDR。STM32MP1 的 TF-A 分为 BL2 和 BL32(OP-TEE)两部分,BL2 完成基础初始化,BL32 提供安全服务。
U-Boot 作为 SSBL,运行在 DDR 中,是启动流程中可干预程度最高的一级。它读取环境变量、加载内核镜像和设备树、拼接 bootargs,最终跳转到内核入口。产测模式的触发判断通常放在这一级。
Linux Kernel 解压后初始化子系统,解析设备树,挂载根文件系统。内核命令行中的 root=、init=、console= 等参数直接决定后续行为。
Rootfs 挂载后执行 init 进程,启动用户空间服务。产测程序通常作为 init 脚本中的一个分支被拉起。
二、产测模式的触发条件设计
产测模式不能依赖人工输入命令,必须在无人值守的产线环境下自动判断。常见的触发条件有三类,实际项目中通常组合使用。
GPIO 电平触发是最直接的方式。产测治具上设置一个拉高/拉低的检测引脚,例如 PA13,U-Boot 启动时读取该引脚状态。若为有效电平,则设置 factory_mode=1。这种方式响应快、不受存储介质影响,但需要硬件配合。
按键长按触发适合没有治具的场景。U-Boot 在启动延时阶段检测某个按键(如 USER1)是否持续按下超过 2 秒。实现上可在 board_late_init() 中轮询 GPIO,或利用 bootdelay 期间的 ctrlc 机制扩展。
环境变量或标志分区触发适合远程切换。在 eMMC 中划分一个 misc 分区,写入 factory_flag 字节。U-Boot 读取该分区,若标志有效则进入产测。这种方式可以在系统运行时通过 reboot factory 命令写入标志后重启生效。
三、U-Boot 中的实现要点
在 U-Boot 源码的板级文件中,核心逻辑集中在 board_late_init() 或自定义的 board_factory_check() 函数。以下是一个可参考的实现框架:
/* board/st/stm32mp1/factory_check.c */
static int board_factory_check(void)
{
int gpio_val;
int ret;
/* 1. 读取产测检测GPIO */
gpio_val = gpio_get_value(FACTORY_DETECT_GPIO);
if (gpio_val == FACTORY_ACTIVE_LEVEL) {
env_set("factory_mode", "1");
return 0;
}
/* 2. 读取misc分区标志 */
ret = read_misc_flag(&flag);
if (ret == 0 && flag == FACTORY_FLAG_MAGIC) {
env_set("factory_mode", "1");
return 0;
}
env_set("factory_mode", "0");
return 0;
}
随后在环境变量中定义两套 bootargs,由 factory_mode 决定引用哪一套:
# 正常启动
bootargs_normal=root=/dev/mmcblk0p5 rootwait rw console=ttySTM0,115200
# 产测启动
bootargs_factory=root=/dev/mmcblk0p7 rootwait rw console=ttySTM0,115200 init=/sbin/factory_init
# 选择逻辑
bootargs=${bootargs_normal}
if test "${factory_mode}" = "1"; then
setenv bootargs ${bootargs_factory}
fi
这里的关键是产测根文件系统与正常根文件系统分离。产测 rootfs 通常是一个精简的 BusyBox 环境,只包含测试程序、必要的驱动模块和结果上报工具,避免正常系统的服务干扰测试。
四、启动参数与产测程序的衔接
产测 rootfs 的 init 脚本需要完成三件事:挂载必要的虚拟文件系统、加载测试所需的驱动模块、启动产测主程序。一个典型的 /sbin/factory_init 脚本结构如下:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
# 加载测试所需模块
modprobe stm32_i2c
modprobe stm32_spi
modprobe dwc2
# 启动产测主程序,结果写入日志分区
/usr/bin/factory_test --log /var/log/factory_result.json
# 测试完成后根据结果决定是否重启
if [ $? -eq 0 ]; then
echo "PASS" > /dev/mmcblk0p8
else
echo "FAIL" > /dev/mmcblk0p8
fi
poweroff -f
产测结果写入独立分区而非 rootfs,是为了保证即使 rootfs 被重新烧录,测试记录仍然保留,便于产线追溯。
五、设计检查清单
| 检查项 | 要求 | 验证方式 |
|---|---|---|
| GPIO 触发 | 有效电平与硬件原理图一致 | 万用表测量治具引脚 |
| 标志分区 | misc 分区可读写,magic 值唯一 | U-Boot 下 mmc read 验证 |
| bootargs 切换 | 两套参数互不干扰 | 串口打印确认 root= 路径 |
| 产测 rootfs | 独立分区,不依赖正常系统 | 拔掉正常 rootfs 仍能启动 |
| 结果持久化 | 写入独立分区,掉电不丢失 | 重启后读取结果分区 |
saveenv 才会写入存储介质。产测模式下如果频繁写环境变量,需评估 eMMC 寿命,建议将 factory_mode 作为运行时变量而非持久化变量。整体来看,产测模式的设计本质是在启动链路的 U-Boot 阶段插入一个可控的分支判断。触发条件决定"是否进入",bootargs 决定"进入后加载什么",rootfs 中的 init 脚本决定"进入后做什么"。三者配合,才能形成一条从硬件信号到测试结果的完整通路。