OTA到一半断电变砖怎么自救?——AB双分区无缝切换

电子说

1.4w人已加入

描述

问题场景

手机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)更新时面临致命风险:

OTA

二、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槽位的生命周期是一个有限状态机:

OTA

五、AB系统的完整交互

OTA

六、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模式寄存器
 

 

审核编辑 黄宇

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 相关推荐
  • 热点推荐
  • OTA

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分