A/B 系统
A/B 系统可以理解成板子里有两套可启动系统:A 槽和 B 槽。正常运行时从一个槽启动,升级时把新固件写到另一个槽。升级完成后切换启动槽,如果新系统启动失败,还可以回到旧槽。
它和传统 Recovery 升级不太一样:
| 方案 | 特点 |
|---|---|
| Recovery 模式 | 有独立 recovery 分区,升级时重启到 recovery 环境,再执行升级。流程直观,但需要进入 recovery。 |
| Linux A/B 模式 | 有两套系统分区,当前系统可以升级非活动槽。适合需要失败回滚、减少升级风险的产品。 |
Neardi Pi 3 的 A/B 支持需要同时改 u-boot、buildroot 和 device/rockchip。下面这三部分补丁已经在 RK3576 Linux 6.1 R6 SDK 上验证过。
U-Boot 配置
U-Boot 需要打开 Android A/B 和 AVB 相关配置:
diff --git a/configs/rk3576_defconfig b/configs/rk3576_defconfig
@@ -227,3 +227,10 @@ CONFIG_RK_AVB_LIBAVB_USER=y
CONFIG_OPTEE_CLIENT=y
CONFIG_OPTEE_V2=y
CONFIG_OPTEE_ALWAYS_USE_SECURITY_PARTITION=y
+
+CONFIG_AVB_LIBAVB=y
+CONFIG_AVB_LIBAVB_AB=y
+CONFIG_AVB_LIBAVB_ATX=y
+CONFIG_AVB_LIBAVB_USER=y
+CONFIG_RK_AVB_LIBAVB_USER=y
+CONFIG_ANDROID_AB=y
同时,当前 RK3576 SDK 的 A/B 流程使用 android_slotsufix 这个启动参数名。注意��这里是 slotsufix,需要和现有 SDK 流程保持一致:
-char slot_info[21] = "android_slotsuffix=";
+char slot_info[21] = "android_slotsufix=";
-env_set("android_slotsuffix", slot_suffix);
+env_set("android_slotsufix", slot_suffix);
Buildroot 配置
Buildroot 需要打开 recovery、bootcontrol 和 updateEngine。Pi 3 当前建议使用 retry 模式,这样新槽启动失败时可以按尝试次数回退:
diff --git a/configs/rockchip_rk3576_defconfig b/configs/rockchip_rk3576_defconfig
@@ -22,3 +22,13 @@
#include "network/chromium.config"
#include "npu2.config"
#include "gui/weston.config"
+
+BR2_PACKAGE_RECOVERY=y
+BR2_PACKAGE_RECOVERY_BOOTCONTROL=y
+BR2_PACKAGE_RECOVERY_RETRY=y
+BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE=y
+BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN=y
+BR2_PACKAGE_RECOVERY_NO_UI=y
几个配置的含义:
| 配置 | 说明 |
|---|---|
BR2_PACKAGE_RECOVERY | 打开升级相关功能。 |
BR2_PACKAGE_RECOVERY_BOOTCONTROL | 打开 slot / boot control 控制脚本。 |
BR2_PACKAGE_RECOVERY_RETRY | 使用 retry 方式,启动失败可按次数回退;不配置时默认偏向 successful_boot 模式。 |
BR2_PACKAGE_RECOVERY_USE_UPDATEENGINE | 使用新的 updateEngine 升级程序。 |
BR2_PACKAGE_RECOVERY_UPDATEENGINEBIN | 编译 updateEngine。 |
BR2_PACKAGE_RECOVERY_NO_UI | 关闭 recovery UI,避免无屏设备进入 recovery 后因 DRM/display 打开失败导致升级异常。 |
改完 recovery 相关配置后,建议先重新编译 recovery,再打包固件:
make recovery-dirclean
make recovery
./build.sh
如果 SDK 版本比较新,也可以尝试下面这种方式:
./build.sh external/recovery
./build.sh
Device 配置
板级配置需要打开 A/B 更新,并切换到 A/B 分区表。下面是已验证的 rk3576_neardi_lz201 示例:
diff --git a/.chips/rk3576/rockchip_rk3576_neardi_lz201_defconfig b/.chips/rk3576/rockchip_rk3576_neardi_lz201_defconfig
@@ -1,4 +1,7 @@
RK_UBOOT_SPL=y
+RK_KERNEL_CFG_FRAGMENTS="rk3576_neardi_lz201.config"
RK_KERNEL_DTS_NAME="rk3576-neardi-lz201-linux"
RK_USE_FIT_IMG=y
-RK_KERNEL_CFG_FRAGMENTS="rk3576_neardi_lz201.config"
+RK_AB_UPDATE=y
+RK_OTA_PACKAGE_FILE_CUSTOM=y
+RK_PARAMETER="parameter-ab.txt"
如果项目使用的是其他 Neardi Pi 3 派生板级配置,例如 lb200 或 lz200,思路一样:修改对应的 .chips/rk3576/rockchip_rk3576_neardi_xxx_defconfig,打开 RK_AB_UPDATE,并使用 A/B 分区表。
有些 SDK 项目在编译前还会手动指定 A/B 分区表:
export RK_PARAMETER=parameter-ab-64bit.txt
这里要按实际板级配置和存储布局选择对应的 parameter 文件。普通分区表和 A/B 分区表不要混用。
编译和首次烧录
U-Boot、Buildroot、device 配置都准备好后,执行:
./build.sh
然后把生成的完整固件烧录到板子。不同 SDK 打包脚本显示出来的名字可能是 update.img,也可能是 update_ab.img。首次切换到 A/B 系统时,用完整固件烧录,不要用 OTA 包。
启动过程中,串口日志里应该能看到类似下面的信息:
Trying to boot from MMC1
SPL: A/B-slot: _b, successful: 0, tries-remain: 7
Trying fit image at 0x4000 sector
能看到 A/B slot 日志,说明 bootloader 侧的 A/B 流程已经生效。
固件输出
A/B 打开后,固件输出一般会分成两类:
| 固件 | 用途 |
|---|---|
update.img / update_ab.img | 用于整机烧录,首次刷入 A/B 分区布局时使用。具体名字取决于 SDK 的打包脚本。 |
update_ota.img | 用于 OTA 升级,通常包含要写入非活动槽的分区镜像。 |
也就是说,首次切换到 A/B 系统时,先使用完整固件烧录;后续在线升级或本地升级再使用 update_ota.img。
打包 OTA 固件
打包 OTA 前,可以先选择本次 OTA 要升级哪些分区:
./build.sh edit-ota-package-file
然后打包 OTA 镜像:
./build.sh ota-updateimg
生成的 OTA 镜像会放在 rockdev/ 目录下,一般叫 update_ota.img 或类似名字。执行升级前,把它拷贝到板端。
执行 OTA 升级
A/B 升级时,当前系统保持运行,updateEngine 会把新固件写入非活动槽,然后设置下次启动槽并重启。
Neardi Pi 3 当前验证的方式是:先把 OTA 镜像拷贝到 /userdata,然后执行:
updateEngine --image_url=/userdata/update_ota.img --update --reboot
部分 Rockchip SDK 使用的是旧一点的命令格式。如果 updateEngine --help 里能看到 --misc 和 --savepath,可以用下面这种写法:
updateEngine --image_url=/userdata/update_ota.img \
--misc=update \
--savepath=/userdata/update_ota.img \
--reboot &
旧格式的网络升级示例:
updateEngine --image_url=http://server/update_ota.img \
--misc=update \
--savepath=/userdata/update_ota.img \
--reboot &
升级过程大致是:
- 下载或读取
update_ota.img。 - 将新固件写入非活动槽。
- 设置下次启动槽。
- 重启进入新槽。
- 如果新槽启动正常,标记为可用;如果启动失败,按 retry 机制回退到旧槽。
验证升级结果
如果只是验证 OTA 流程,可以在打包 OTA 前临时改一下 DTS 里的 compatible,例如:
compatible = "rockchip,rk3576-neardi-lz201-v10-test", "rockchip,rk3576";
OTA 升级并重启后,在板端查看:
cat /proc/device-tree/compatible
如果升级前后 compatible 内容发生了预期变化,就说明系统已经启动到升级后的 slot。
Slot 和回滚
A/B 系统会维护 slot 状态,例如当前启动槽、优先级、剩余尝试次数、是否成功启动等。实际项目中建议使用 retry 模式,这样新版本异常时更容易回到旧版本。
调试时可以重点确认:
cat /proc/cmdline
ls -l /dev/block/by-name/
如果需要看升级日志,可以从串口日志和用户数据分区中排查:
cat /userdata/recovery/Log
常见注意事项
- A/B 分区表和普通分区表不同,首次启用 A/B 时要完整烧录整包固件。
- OTA 包要使用
update_ota.img,不要把首次烧录用的完整固件直接当 OTA 包使用。 parameter-ab.txt要和实际分区镜像匹配,否则可能出现分区不存在或写错槽的问题。- 无屏设备建议打开
BR2_PACKAGE_RECOVERY_NO_UI,避免 recovery 尝试打开显示设备失败。 - 如果系统里没有
updateEngine,可以在 Buildrootmake menuconfig里打开:Target packages→Hardware Platforms→Rockchip Platform→Rockchip BSP packages→choice the update bin of recovery→updateEngine。 - 如果 OTA 执行完但内容没有变化,到 U-Boot 源码里搜索
android_slotsuffix,确认已在两个验证位置改成android_slotsufix。 - 如果新系统反复启动失败,先检查 slot 状态、
/proc/cmdline、串口日志和userdata/recovery/Log。