跳到主要内容

A/B 系统

A/B 系统可以理解成板子里有两套可启动系统:A 槽和 B 槽。正常运行时从一个槽启动,升级时把新固件写到另一个槽。升级完成后切换启动槽,如果新系统启动失败,还可以回到旧槽。

它和传统 Recovery 升级不太一样:

方案特点
Recovery 模式有独立 recovery 分区,升级时重启到 recovery 环境,再执行升级。流程直观,但需要进入 recovery。
Linux A/B 模式有两套系统分区,当前系统可以升级非活动槽。适合需要失败回滚、减少升级风险的产品。

Neardi Pi 3 的 A/B 支持需要同时改 u-bootbuildrootdevice/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 派生板级配置,例如 lb200lz200,思路一样:修改对应的 .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 &

升级过程大致是:

  1. 下载或读取 update_ota.img
  2. 将新固件写入非活动槽。
  3. 设置下次启动槽。
  4. 重启进入新槽。
  5. 如果新槽启动正常,标记为可用;如果启动失败,按 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,可以在 Buildroot make menuconfig 里打开:Target packagesHardware PlatformsRockchip PlatformRockchip BSP packageschoice the update bin of recoveryupdateEngine
  • 如果 OTA 执行完但内容没有变化,到 U-Boot 源码里搜索 android_slotsuffix,确认已在两个验证位置改成 android_slotsufix
  • 如果新系统反复启动失败,先检查 slot 状态、/proc/cmdline、串口日志和 userdata/recovery/Log