深入解析 OpenWrt Chainloader 引导机制:以 AN7581 和 MT7988A 平台为例
在嵌入式 OpenWrt 设备中,引导加载程序(Bootloader)的定制和升级一直是高风险操作。传统方案需要完全替换原厂 U-Boot,一旦失败设备即变砖。Chainloader(链式加载)机制提供了一种优雅的解决方案:在不覆盖原厂 Bootloader 的前提下,通过二级引导程序加载 OpenWrt 内核,既保留了原厂救砖能力,又能享受开源 U-Boot 的新特性。
本文将结合 Airoha AN7581 和 MediaTek MT7988A 两个平台的实际案例,系统讲解 Chainloader 的技术原理、打包方法、分区布局和刷写实践。
为什么需要 Chainloader?
原厂 Bootloader 的局限性
功能缺失:不支持 FIT 镜像、HTTP 恢复、网卡驱动缺陷等
闭源不透明:难以调试和扩展
更新风险高:直接替换原厂 U-Boot,操作复杂,每个机型需要一套教程,且设备容易变砖
Chainloader 的核心思想
保留原厂第一阶段引导程序不变,在其基础上通过**二级引导程序(Secondary U-Boot)**接管启动流程:
1 | [原厂 Bootloader] -> [加载 Chainloader U-Boot] -> [加载 OpenWrt FIT 内核镜像] |
这种方案的优点:
安全性:原厂 Bootloader 保留,可通过串口救砖
灵活性:二级 U-Boot 可以自由定制、升级、增加功能
可回滚:Chainloader 分区独立,不影响原厂分区
Chainloader 镜像打包方式
OpenWrt 标准方式:Build/an7581-chainloader 宏
这是 OpenWrt 构建系统中通用的 Chainloader 打包模板,被 AN7581 平台采用:
1 | define Build/an7581-chainloader |
工作流程解析:
检测压缩格式:优先使用
.lzma压缩的 U-Boot,否则使用原始.bin生成 ITS 源文件:通过
mkits.sh生成 FIT 描述文件- 加载地址/入口地址:
0x80200000(AN7581 的 DDR 加载基址) - 启动脚本地址:
0x82000000 - 包含设备树 DTB
- 加载地址/入口地址:
打包 FIT 镜像:使用
mkimage将 ITS 编译为.itb文件追加到内核尾部:通过
cat将.itb附加到 Linux 内核镜像末尾
最终产物结构:
1 | +----------------------------------+ |
这种方案的特点是 U-Boot 与内核合并在一个文件中,刷写内核分区即同时更新 U-Boot。
XR1710G 定制方案:双入口 Slot 镜像
XR1710G 的方案更加复杂,它需要兼容原厂 ECNT/AXON U-Boot 的两种启动地址:
最终产物 xr1710g-chainloader-slot.bin 的结构:
1 | +---------------------------------------------------+ <-- 文件开头 |
为什么是 0x2100 字节偏移?
原厂 ECNT U-Boot 环境变量中通常有两种 bootcmd:
1 | # 方式一:从基址读取(会加载 Legacy Shim) |
通过在文件开头放置 Shim 并在偏移 0x2100 放置真正的 FIT 镜像,无论原厂使用哪种读取方式,都能正常启动。
打包脚本核心逻辑:
1 | # 1. 生成 FIT 镜像 |
MT7988A 平台的 Chainloader
在 tplink_be805 的设备定义中,Chainloader 的生成被拆解为清晰的流水线操作,核心代码如下:
1 | ARTIFACTS := second-u-boot.bin |
这套流水线按顺序执行了以下三步操作:
| 步骤 | 命令 | 作用 |
|---|---|---|
| 1. 提取原始载荷 | mt7988-uboot-raw tplink_be805 | 这是一个自定义的构建命令,本质上是执行 cat $(STAGING_DIR_IMAGE)/mt7988_tplink_be805-u-boot.bin >> $@,即获取刚刚编译好的、未经任何包装的原始 u-boot.bin 二进制文件。 |
| 2. 压缩体积 | libdeflate-gzip | 使用高效的 libdeflate 算法对 U-Boot 二进制文件进行 Gzip 压缩。这对于 NAND 闪存空间紧张的设备至关重要,能显著减小二级引导程序占用的存储空间。 |
| 3. 添加 U-Boot 标准头 | uImage gzip -T standalone ... | 这是最关键的一步。它使用 mkimage 工具将压缩后的数据打包成一个标准的 U-Boot 镜像(uImage)。参数含义:- -T standalone:指定镜像类型为独立应用程序(Standalone Application),这是 U-Boot 可执行 payload 的标准类型。- -a 0x44000000:指定加载地址(Load Address),即原厂 Bootloader 需要将此镜像复制到内存的 0x44000000 处。- -e 0x44000000:指定入口地址(Entry Point),即解压或跳转执行的起点。- -n "OpenWrt U-Boot Chainloader":镜像描述名称。 |
最终,构建系统会生成一个名为 openwrt-mediatek-filogic-tplink_be805-v1-second-u-boot.bin 的独立工件(Artifact)。
2. 固件与 Chainloader 的“解耦”设计
与 XR1710G 最大的区别在于 sysupgrade.bin 固件与 second-u-boot.bin 是逻辑分离的。
在 XR1710G 方案中,U-Boot 被强行合并(cat)进内核镜像或生成一个混合的 slot.bin。而在 MT7988 方案中:
second-u-boot.bin:作为ARTIFACT独立生成,单独提供给用户或刷写脚本,用于写入闪存的特定uboot分区(或通过 U-Boot 的fitblk挂载)。sysupgrade.bin:仅包含 Linux 内核(通过fit gzip ...打包成 FIT)和根文件系统(RootFS)。注意其KERNEL变量定义为kernel-bin | libdeflate-gzip,并不包含任何 U-Boot 代码。
这种设计的好处是:
职责单一:Chainloader 更新与系统更新完全解耦。你可以在不重刷整个固件的情况下单独升级二级 U-Boot,反之亦然,极大地降低了变砖风险。
更符合 OpenWrt 哲学:U-Boot 作为引导程序,本就不应频繁变动。将其从系统固件中剥离,使得
sysupgrade过程更轻量、更快速。
3. 引导链的最终形态
结合 KERNEL_IN_UBI := 1 配置(内核存放在 UBI 卷中),完整的 MT7988 启动流如下:
1 | [原厂 Bootloader (BootROM)] |
分区布局设计
XR1710G 的 NAND 分区方案
1 | 0x00000000 +----------------------------------+ |
关键设计思路:
vendor分区只读:保存原厂 U-Boot 和硬件校准数据,是救砖的最后防线chainloader分区只读:存放二级 U-Boot,避免系统内误覆盖
两种的 Chainloader 实现对比
| 对比维度 | 分离的 Chainloader | 整合的 Chainloader |
|---|---|---|
| 分区独立性 | 独立 chainloader 分区 | 嵌入固件镜像,无独立分区 |
| 构建集成度 | 需单独编译 U-Boot + 手动打包 | 完全集成于 image/filogic.mk |
| 兼容性处理 | 双入口 Shim (0x600000 / 0x602100) | 通过 FIT 标准机制处理 |
总结
Chainloader 机制为 OpenWrt 设备提供了一种安全、灵活、可回滚的 Bootloader 定制方案。其核心价值在于:
不破坏原厂引导链,保留救砖能力
二级 U-Boot 可独立升级,不受原厂限制
支持 FIT 镜像,实现内核、DTB、启动脚本的统一管理
不同平台方案可复用,从手动脚本到主线集成逐步演进
虽然看起来 Chainloader 机制很完美,但是其实没有什么实际的必要性。因为 Airoha/MediaTek 平台具备便捷的救砖机制,而且其uboot/atf都存在开源实现。
但是我们终于可以不用去备份某些数据了(例如 MAC 地址、无线校准数据等等)。如果原厂的引导支持恢复模式,那么 Chainloader 机制才真正有意义。