4. MCUboot引导加载程序深入

MCUboot是Nordic nRF54系列上DFU体系的引导核心,它并不只是“跳转到应用”的一段小程序,而是负责镜像校验、槽位选择、镜像搬运与回滚决策的完整引导加载程序。理解MCUboot,关键要抓住四个对象:镜像格式、槽位布局、交换算法、启动与回滚状态机。这四者共同决定了固件升级是否安全、断电是否可恢复、旧版本能否自动回退。

一、镜像格式:TLV、头部与尾部签名

MCUboot镜像由三部分组成:镜像头(image header)、镜像体(payload)和TLV尾部(trailer)。镜像头固定32字节,包含魔数、加载地址、镜像大小、标志位、版本号以及镜像体内各段哈希。TLV尾部则承载签名、公钥哈希、依赖关系等元数据。nRF54上通常使用ECDSA-P256或RSA-2048签名,签名覆盖镜像头与镜像体,因此任何字节改动都会导致校验失败。

镜像头中的ih_load_addr决定镜像运行地址,ih_img_size决定有效载荷长度,ih_flags中的IMAGE_F_NON_BOOTABLE等标志会影响引导决策。TLV区域以0x07(TLV_INFO)开始,以0xff结束,中间可包含0x01(KEYHASH)、0x10(SHA256)、0x20(RSA2048)、0x22(ECDSA256)等类型。升级时,新镜像先写入次槽,校验通过后才触发交换。

二、槽位管理:主槽、次槽与暂存区

nRF54的Flash通常划分为:Bootloader区、主槽(slot0)、次槽(slot1)、暂存区(scratch)以及配置区。主槽存放当前运行镜像,次槽存放待升级镜像。若启用交换(swap),还需要一个scratch区用于在交换过程中暂存被覆盖的扇区。若使用直接XIP模式,则镜像可在原地执行,无需搬运到RAM。

槽位大小由分区表决定,必须满足slot1_size ≥ slot0_size,否则无法完成交换。配置区保存magic、image_ok、copy_done等状态标志,这些标志是回滚判断的依据。每次升级前,Bootloader会检查次槽镜像的版本号是否高于主槽,若低于或相等,则拒绝升级,除非显式允许降级。

nRF54 MCUboot 槽位布局与交换流程 Bootloader 0x0000_0000 主槽 slot0 当前运行镜像 次槽 slot1 待升级镜像 scratch 暂存区 配置区 magic/ok 交换算法(swap-scratch)关键阶段 ① 校验次槽 签名+哈希 ② 搬入scratch 主槽首扇区 ③ 次槽→主槽 逐扇区搬运 ④ scratch→次槽 恢复旧镜像 ⑤ 置位 copy_done 启动与回滚状态机 上电复位 Bootloader运行 检查image_ok 主槽是否确认 校验主槽 签名+哈希 跳转应用 正常启动 校验失败 触发回滚 恢复旧镜像 从次槽/scratch 校验不通过

三、交换算法:swap-scratch与直接XIP

MCUboot支持两种主要升级模式:交换模式(swap)和直接XIP模式(direct-XIP)。交换模式又分为swap-scratch和swap-move。nRF54上常用swap-scratch,其过程是:先将主槽首扇区搬入scratch,再将次槽镜像逐扇区搬入主槽,最后将scratch中的旧镜像首扇区写入次槽。整个过程需要保证断电可恢复,因此每个扇区操作后都会更新状态标志。

直接XIP模式则不需要搬运镜像,次槽镜像可直接在Flash中执行。Bootloader只需校验次槽镜像,若通过则直接跳转到次槽地址运行。这种模式节省了交换时间和scratch空间,但要求次槽地址与主槽地址在链接时都有效,且镜像必须支持位置无关或双地址链接。nRF54的Flash控制器支持从任意地址XIP执行,因此直接XIP是可行的。

选择哪种模式取决于产品需求:若升级频率高、对升级时间敏感,直接XIP更优;若需要严格保证主槽始终可用、且Flash空间有限,swap-scratch更稳妥。无论哪种模式,image_ok标志都是回滚的关键:应用启动后必须主动调用boot_set_confirmed()确认镜像,否则下次复位时Bootloader会认为新镜像未确认,从而触发回滚。

四、nRF54上的启动流程与回滚机制

nRF54上电后,首先执行Bootloader。Bootloader读取配置区的magic,若为0x96f3b83d则进入正常引导流程;否则执行恢复操作。随后检查主槽镜像头,校验签名与哈希。若主槽有效且image_ok已置位,则直接跳转主槽。若主槽无效或image_ok未置位,则检查次槽:若次槽有效且版本更高,则执行交换或直接跳转;若次槽也无效,则进入串口恢复模式或停留在Bootloader。

回滚机制的核心是“未确认即回滚”。新镜像首次启动后,应用必须在规定时间内调用boot_set_confirmed(),将image_ok写入配置区。若应用崩溃、看门狗复位或未及时确认,Bootloader在下次启动时会发现image_ok未置位,于是将次槽中的旧镜像恢复回主槽,并清除交换状态。这样即使新固件有严重缺陷,设备也能自动回到已知可用的旧版本。

在实际工程中,建议在应用启动后尽早调用boot_set_confirmed(),但不要早于关键外设初始化完成。若应用需要长时间自检,可先置位一个“临时确认”标志,待自检通过后再正式确认。nRF54的配置区支持原子写入,确保断电时标志不会处于中间状态。

五、关键参数与配置示例

以下是一个典型的MCUboot配置片段,用于nRF54L15,采用swap-scratch模式,主槽512KB,次槽512KB,scratch 64KB:

CONFIG_BOOTLOADER_MCUBOOT=y
CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="root-ec-p256.pem"
CONFIG_BOOT_SWAP_USING_SCRATCH=y
CONFIG_BOOT_SWAP_SAVE_ENCTLV=y
CONFIG_MCUBOOT_IMAGE_NUMBER=1
CONFIG_PARTITION_MANAGER_ENABLED=y

/* partition layout */
slot0_partition: 0x00010000, size 0x80000
slot1_partition: 0x00090000, size 0x80000
scratch_partition: 0x00110000, size 0x10000

其中CONFIG_BOOT_SWAP_SAVE_ENCTLV决定是否将TLV加密后保存,适用于对安全性要求较高的场景。CONFIG_MCUBOOT_IMAGE_NUMBER为1表示单镜像升级,若需要多镜像(如网络核+应用核)则设为2。分区地址必须与链接脚本一致,否则Bootloader无法正确找到镜像。

六、调试与验证要点

验证MCUboot行为时,可通过以下方式观察:串口输出会打印Primary image: magic=good, swap_type=0x2, copy_done=0x1, image_ok=0x1等状态;使用nrfutil或mcumgr查询镜像列表;通过boot_set_confirmed()前后对比image_ok值。若发现设备反复回滚,应检查应用是否调用了确认函数,以及配置区是否被意外擦除。

回滚测试的典型步骤是:烧录旧固件并确认,再通过DFU升级新固件但不调用确认函数,复位后观察是否自动回到旧版本。若回滚成功,串口会显示Swap type: revert或类似信息。若未回滚,则需检查image_ok是否被错误置位,或配置区地址是否与分区表冲突。

注意:直接XIP模式下,次槽镜像的链接地址必须与主槽不同,且应用需支持从两个地址运行。若使用Zephyr,可通过CONFIG_MCUBOOT_DIRECT_XIP=y和CONFIG_MCUBOOT_DIRECT_XIP_REVERT=y启用回滚支持。但直接XIP的回滚需要额外的槽位管理,通常不如swap-scratch直观。

总结来说,MCUboot在nRF54上的可靠性来自“校验—交换—确认—回滚”的闭环。镜像格式保证完整性,槽位管理保证空间隔离,交换算法保证断电可恢复,启动流程与回滚机制保证设备始终可启动。掌握这些机制后,才能在实际DFU工程中正确配置分区、选择升级模式,并设计出可靠的确认与回滚策略。