OTA到一半断电变砖怎么自救?——AB双分区无缝切换 电子说
问题场景
手机OTA更新时突然断电,重启后设备变砖——这是单分区系统的经典死法。AB双分区机制让设备永远有一套"已知良好"的系统做保底。核心问题在于:SPL是怎么判断该启动A槽还是B槽的?BCB数据存在哪、长什么样?如果AB和Falcon模式一起开,谁听谁的?本文对照RK3576源码,把AB系统的实际实现梳理清楚。
勘误说明:本文已对照RK3576 SDK实际代码进行审核修正。主要修正:spl_next_stage() 函数中并不包含AB切换逻辑——AB的实际处理在 spl_load_simple_fit() 和 spl_kernel_partition() 中完成;BCB数据结构的实际字段与Android标准BCB有差异。
一、为什么需要AB系统?
传统的单分区系统在OTA(Over-The-Air)更新时面临致命风险:

二、BCB消息格式与Rockchip的实际实现
AB切换的核心数据存储在misc分区中,格式为BCB(Bootloader Control Block)。注意:RK3576实际使用的BCB结构与Android标准 bootloader_message_ab 有差异——SPL中读取BCB的入口是 spl_kernel_partition()。
// arch/arm/mach-rockchip/spl.cconst char *spl_kernel_partition(struct spl_image_info *spl, struct spl_load_info *info){ struct bootloader_message *bmsg; u32 sector = 0;
// 1. 查找misc分区起始扇区 ret = part_get_info_by_name(info->dev, PART_MISC, &part_info); if (ret >= 0) sector = part_info.start; else sector = CONFIG_SPL_MISC_SECTOR; // 回退到固定扇区
// 2. 读取bootloader_message(标准AB结构) bmsg = memalign(ARCH_DMA_MINALIGN, cnt * info->bl_len); ret = info->read(info, sector + BCB_MESSAGE_BLK_OFFSET, cnt, bmsg);
// 3. 检查是否进入recovery模式 if (!strcmp(bmsg->command, "boot-recovery")) return PART_RECOVERY;
// 4. 检查boot_mode寄存器 boot_mode = readl((void *)CONFIG_ROCKCHIP_BOOT_MODE_REG); return (boot_mode == BOOT_RECOVERY) ? PART_RECOVERY : PART_BOOT;}
实际的BCB结构(来自Android标准)为:
// bootable/recovery/bootloader_message/include/bootloader_message.hstruct bootloader_message { char command[32]; // 命令: "boot-recovery" / "" char status[32]; // 状态 char recovery[1024]; // 恢复模式参数字符串 char misc[1024]; // misc数据
// AB扩展字段(由 spl_ab 子系统单独解析) struct bootloader_message_ab ab_data;};struct bootloader_message_ab { struct { uint8_t priority:4; // 优先级 (0-15) uint8_t tries_remaining:3; // 剩余尝试次数 uint8_t successful:1; // 是否启动成功 uint8_t ver; // 版本 } slot_suffix[2]; // slot[0]=_a, slot[1]=_b uint32_t crc32;};
三、AB切换的真实控制流
修正一个常见误解:AB切换逻辑不在 spl_next_stage() 中,而是在 spl_load_simple_fit() 中通过 CONFIG_SPL_AB 宏控制。
3.1 spl_next_stage() 的实际代码
spl_next_stage() 只负责决定下一阶段是U-Boot还是Kernel(Falcon模式),它不处理AB槽位选择:
// arch/arm/mach-rockchip/spl.c — 实际的 spl_next_stagevoid spl_next_stage(struct spl_image_info *spl){ const char *reason[] = { "Recovery key", "Ctrl+c", "LowPwr", "Other" }; uint32_t reg_boot_mode; int i = 0;
// ── 条件1: 音量键按下(recovery按键)── if (spl_rockchip_dnl_key_pressed()) { i = 0; spl->next_stage = SPL_NEXT_STAGE_UBOOT; goto out; }
// ── 条件2: Ctrl+C 中断 ── if (gd->console_evt == 0x03) { i = 1; spl->next_stage = SPL_NEXT_STAGE_UBOOT; goto out; }
// ── 条件3: 低电量 ── if (spl_is_low_power()) { i = 2; spl->next_stage = SPL_NEXT_STAGE_UBOOT; goto out; }
// ── 条件4: boot_mode特殊标志 ── reg_boot_mode = readl((void *)CONFIG_ROCKCHIP_BOOT_MODE_REG); switch (reg_boot_mode) { case BOOT_LOADER: case BOOT_FASTBOOT: case BOOT_CHARGING: case BOOT_UMS: case BOOT_DFU: i = 3; spl->next_stage = SPL_NEXT_STAGE_UBOOT; break; default: // 无特殊条件 → Falcon模式直接启动内核 spl->next_stage = SPL_NEXT_STAGE_KERNEL; }
out: if (spl->next_stage == SPL_NEXT_STAGE_UBOOT) printf("Enter uboot reason: %sn", reason[i]);}
关键理解:spl_next_stage() 只回答"去U-Boot还是去Kernel",不回答"去A槽还是B槽"。
3.2 AB槽位切换的实际位置
AB槽位选择发生在 spl_load_simple_fit() 的尾部:
// common/spl/spl_fit.cint spl_load_simple_fit(struct spl_image_info *spl_image, struct spl_load_info *info, ulong sector, void *fit){ // ... FIT加载逻辑 ...
#ifdef CONFIG_SPL_AB // ★ AB的关键处理在这里 if (ret) { // 加载失败 → AB尝试次数减1,不够则切槽或复位 return spl_ab_decrease_reset(info->dev); } if (spl_image->next_stage == SPL_NEXT_STAGE_KERNEL) { // Falcon模式下,启动成功后递减尝试次数 spl_ab_decrease_tries(info->dev); }#endif
return ret;}
AB的槽位切换并不是在SPL中通过 next_stage 完成的,而是在U-Boot proper中通过Android的 boot_android() 或 avb 流程,根据BCB数据决定加载 boot_a 还是 boot_b 分区。
四、AB状态转换
AB槽位的生命周期是一个有限状态机:

五、AB系统的完整交互

六、SPL_AB vs SPL_KERNEL_BOOT 的交互
当同时启用AB系统和Falcon模式时,交互逻辑在 spl_load_simple_fit() 中体现:
// common/spl/spl_fit.c — 实际交互逻辑int spl_load_simple_fit(...){ // 1. 先尝试加载FIT ret = spl_internal_load_simple_fit(spl_image, info, sector, fit); if (!ret) {#ifdef CONFIG_SPL_KERNEL_BOOT // 2. Falcon模式额外加载kernel FIT ret = spl_load_kernel_fit(spl_image, info);#endif }
#ifdef CONFIG_SPL_AB // 3. AB交互: // - FIT加载失败 → spl_ab_decrease_reset() (尝试减1/切槽/复位) // - Falcon模式成功 → spl_ab_decrease_tries() (递减尝试次数) if (ret) return spl_ab_decrease_reset(info->dev); if (spl_image->next_stage == SPL_NEXT_STAGE_KERNEL) spl_ab_decrease_tries(info->dev);#endif return ret;}
| AB配置 | Falcon配置 | 正常启动 | 恢复按键 | boot_mode特殊值 | FIT加载失败 |
|---|---|---|---|---|---|
| 开启 | 开启 | Kernel(Falcon)+AB减tries | U-Boot | U-Boot | AB切槽 |
| 开启 | 关闭 | U-Boot | U-Boot | U-Boot | AB切槽 |
| 关闭 | 开启 | Kernel(Falcon) | U-Boot | U-Boot | 直接挂起 |
| 关闭 | 关闭 | U-Boot | U-Boot | U-Boot | 直接挂起 |
七、AB系统配置
# RK3576 AB系统配置CONFIG_SPL_AB=y # 在SPL中启用AB切换CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_PARTITION=0x1 # misc分区号CONFIG_ANDROID_AB_SLOT_SELECT=y # Android AB槽位选择逻辑CONFIG_ROCKCHIP_BOOT_MODE_REG=0xFF8E0060 # Boot模式寄存器
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !