泰山派RK3566 OTA配置(小白踩坑记录)第一集:基于 Android A/B 思路划分 system_a / system_b 双 RootFS 分区

1.背景

在嵌入式 Linux 项目中,如果设备已经部署到现场,那么系统升级就不能简单地依赖人工重新烧录固件。尤其是工业设备、视觉终端、机器人、边缘计算盒子这类设备,一旦远程升级失败,设备可能无法启动,维护成本会非常高。
因此,OTA 的核心目标不是“能把新系统写进去”这么简单,而是:
升级过程中不破坏当前系统;
升级失败后能回滚;
新系统启动成功后再正式确认;
设备尽量避免因为一次升级变砖。
A/B RootFS OTA 就是解决这个问题的一种常见方案。
简单来说,就是准备两套 rootfs:
system_a
system_b
当前系统运行在 A 时,升级包写入 B;
当前系统运行在 B 时,升级包写入 A。
这样当前正在运行的系统不会被覆盖,新系统只有在重启后才会被尝试启动。如果新系统启动失败,还可以回滚到旧系统。

2.为什么要借用Android A/B 的分区思路

Rockchip 的 U-Boot 本身就带有 Android A/B 启动相关逻辑。
在启动时,U-Boot 会根据 A/B metadata 判断当前应该启动哪个槽位,并把分区名拼成带后缀的形式,例如:
system + _a -> system_a
system + _b -> system_b
所以如果我们在 Buildroot 系统中也按照 Android A/B 的方式命名分区,就可以复用 U-Boot 已有的 A/B slot 选择逻辑。
***也就是说,虽然我们跑的是 Buildroot,不是完整 Android 系统,但仍然可以利用 Android A/B 的分区命名和 bootloader slot 机制。

3.修改文件

我们一共要修改四个文件
BoardConfig-rk3566-tspi-v10.mk
parameter-buildroot-fit.txt
/linux/Linux_Pack_Firmware/rockdev/rk356x-package-file-ab-rootfs
/linux/u-boot/configs/rk3566.config

/linux/u-boot/configs/rk3566.config
改成

CONFIG_BASE_DEFCONFIG="rk3568_defconfig"
CONFIG_LOADER_INI="RK3566MINIALL.ini"
CONFIG_ANDROID_AB=y
CONFIG_CMD_ANDROID_AB_SELECT=y

执行:

./build.sh lunch

选择 RK3566 泰山派方案后,SDK 就会读取这个 BoardConfig-rk3566-tspi-v10.mk。
该文件是泰山派 RK3566 这块板子的板级构建配置文件。
修改完成后如下

#!/bin/bash

# Target arch
export RK_ARCH=arm64
# Uboot defconfig
export RK_UBOOT_DEFCONFIG=rk3566
# Uboot image format type: fit(flattened image tree)
export RK_UBOOT_FORMAT_TYPE=fit
# Kernel defconfig
export RK_KERNEL_DEFCONFIG=rockchip_linux_defconfig
# Kernel defconfig fragment
export RK_KERNEL_DEFCONFIG_FRAGMENT=
# Kernel dts
export RK_KERNEL_DTS=tspi-rk3566-user-v10-linux
# boot image type
export RK_BOOT_IMG=boot.img
# kernel image path
export RK_KERNEL_IMG=kernel/arch/arm64/boot/Image
# kernel image format type: fit(flattened image tree)
export RK_KERNEL_FIT_ITS=boot.its
# parameter for GPT table
export RK_PARAMETER=parameter-buildroot-fit.txt
export RK_PACKAGE_FILE=rk356x-package-file-ab-rootfs
# Buildroot config
export RK_CFG_BUILDROOT=rockchip_rk3566
# Recovery config
export RK_CFG_RECOVERY=rockchip_rk356x_recovery
# Recovery image format type: fit(flattened image tree)
export RK_RECOVERY_FIT_ITS=boot4recovery.its
# ramboot config
export RK_CFG_RAMBOOT=
# Pcba config
export RK_CFG_PCBA=
# Build jobs
export RK_JOBS=12
# target chip
export RK_TARGET_PRODUCT=rk356x
# Set rootfs type, including ext2 ext4 squashfs
export RK_ROOTFS_TYPE=ext4
# Set debian version (debian10: buster, debian11: bullseye)
export RK_DEBIAN_VERSION=buster
# yocto machine
export RK_YOCTO_MACHINE=rockchip-rk3566-evb
# rootfs image path
export RK_ROOTFS_IMG=rockdev/rootfs.${RK_ROOTFS_TYPE}
# Set ramboot image type
export RK_RAMBOOT_TYPE=
# Set oem partition type, including ext2 squashfs
export RK_OEM_FS_TYPE=ext2
# Set userdata partition type, including ext2, fat
export RK_USERDATA_FS_TYPE=ext2
#OEM config
export RK_OEM_DIR=oem_normal
# OEM build on buildroot
#export RK_OEM_BUILDIN_BUILDROOT=YES
#userdata config
export RK_USERDATA_DIR=userdata_normal
#misc image
export RK_MISC=wipe_all-misc.img
#choose enable distro module
export RK_DISTRO_MODULE=
# Define pre-build script for this board
export RK_BOARD_PRE_BUILD_SCRIPT=app-build.sh

parameter-buildroot-fit.txt是Rockchip 平台的分区表描述文件。它决定了板子上有哪些分区。
它在 A/B OTA 中的作用

parameter-buildroot-fit.txt 的作用是:

定义 system_a 分区
定义 system_b 分区
定义 misc 分区
定义 userdata 分区
定义每个分区大小和起始地址
定义 system_a/system_b 的 UUID

其中:

分区 作用
misc 保存 A/B metadata,U-Boot 根据它判断启动哪个 slot
boot 内核、dtb、ramdisk 等启动镜像
recovery recovery 系统
system_a A 槽 RootFS
system_b B 槽 RootFS
oem OEM 资源
userdata 用户数据、OTA 包、日志等

所以它是 A/B RootFS 的“硬盘规划图”。
修改parameter-buildroot-fit.txt(要根据自己原来的RootFS大小做修改,我这里是平均分成了2半

FIRMWARE_VER: 1.0
MACHINE_MODEL: RK3568
MACHINE_ID: 007
MANUFACTURER: RK3568
MAGIC: 0x5041524B
ATAG: 0x00200800
MACHINE: 0xffffffff
CHECK_MASK: 0x80
PWR_HLD: 0,0,A,0,1
TYPE: GPT
CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00002000@0x00006000(misc),0x00020000@0x00008000(boot),0x00020000@0x00028000(recovery),0x00010000@0x00048000(backup),0x00600000@0x00058000(system_a),0x00600000@0x00658000(system_b),0x00040000@0x00c58000(oem),-@0x00c98000(userdata:grow)
uuid:system_a=614e0000-0000-4b53-8000-1d28000054a9
uuid:system_b=614e0001-0000-4b53-8000-1d28000054a9

还要复制rk356x-package-file

tools/linux/Linux_Pack_Firmware/rockdev/rk356x-package-file-ab-rootfs

为rk356x-package-file-ab-rootfs,其内容为

# NAME		Relative path
#
#HWDEF		HWDEF
package-file	package-file
bootloader	Image/MiniLoaderAll.bin
parameter	Image/parameter.txt
#trust		Image/trust.img
uboot		Image/uboot.img
misc		Image/misc.img
#resource	Image/resource.img
#kernel		Image/kernel.img
boot		Image/boot.img
recovery	Image/recovery.img
system_a    Image/rootfs.img
system_b    Image/rootfs.img
oem		Image/oem.img
userdata	Image/userdata.img
# Ҫд��backup�������ļ�����������update.img��
# SELF �ǹؼ��֣���ʾ�����ļ���update.img������
# �����������ļ�ʱ��������SELF�ļ������ݣ�����ͷ����Ϣ���м�¼
# �ڽ�������ļ�ʱ�������SELF�ļ������ݡ�
backup		RESERVED
#update-script	update-script
#recover-script	recover-script

注:同时里面还有
rk356x-package-file
rk356x-package-file-ab-rootfs
rk356x-package-file-nvr-emmc
rk356x-package-file-nvr-spi-nand
rk356x-package-file-spi-nand
rk356x-package-file-spi-nor
它们都是不同场景的打包映射文件。大概可以这样理解:

文件名 大概含义
rk356x-package-file RK356x 默认 eMMC/NAND 打包方案
rk356x-package-file-ab-rootfs 当前用的 A/B RootFS 打包方案
rk356x-package-file-nvr-emmc NVR 产品、eMMC 存储方案
rk356x-package-file-nvr-spi-nand NVR 产品、SPI NAND 存储方案
rk356x-package-file-spi-nand SPI NAND 存储打包方案
rk356x-package-file-spi-nor SPI NOR 存储打包方案

4.编译和验证

./mkfirmware.sh后我们可以检验是否编译进固件

builder@tspi-ubuntu18:/work/linux$ grep -n "system_a\|system_b" rockdev/parameter.txt
11:CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00002000@0x00006000(misc),0x00020000@0x00008000(boot),0x00020000@0x00028000(recovery),0x00010000@0x00048000(backup),0x00600000@0x00058000(system_a),0x00600000@0x00658000(system_b),0x00040000@0x00c58000(oem),-@0x00c98000(userdata:grow)
12:uuid:system_a=614e0000-0000-4b53-8000-1d28000054a9
13:uuid:system_b=614e0001-0000-4b53-8000-1d28000054a9`

4.1. 板端验证步骤

烧录后进入板端:

adb shell

4.1.2 验证 by-name 分区

ls -l /dev/block/by-name/

期望看到:

system_a -> ../../mmcblk0p6
system_b -> ../../mmcblk0p7

这一步证明:

分区表已经生效

4.1.3 验证当前启动 slot

cat /proc/cmdline

当前从 A 槽启动时,应看到:

android_slotsufix=_a

当前从 B 槽启动时,应看到:

android_slotsufix=_b

这一步证明:

U-Boot 已经把当前 slot 信息传给 Linux 内核

4.2.3 验证实际挂载的 RootFS

mount | grep ' / '

A 槽应类似:

/dev/mmcblk0p6 on / type ext4 (rw,relatime)

B 槽应类似:

/dev/mmcblk0p7 on / type ext4 (rw,relatime)

这一步证明:

Linux 实际运行的根文件系统与 U-Boot 选择的 slot 对得上

4.1.4 验证 misc 中存在 A/B metadata

dd if=/dev/block/by-name/misc bs=1 skip=2048 count=64 2>/dev/null | hexdump -C

如果看到类似:

00 41 42 30

说明 misc 的 2048 偏移附近存在 Android A/B metadata 的特征值。

这一步证明:

misc 分区不是空的,后续可以围绕 A/B metadata 做 ota_slotctl

注:查找U-Boot源码,查看是怎么利用的system_a/system_b

这一部分的目的不是继续改分区表,而是从源码层面确认一件事:

system_a / system_b 不是随便起名,
是为了匹配 Android A/B slot 机制和 Rockchip U-Boot 已有的启动选择逻辑。

在 Rockchip SDK 根目录执行:

pwd
find . -maxdepth 3 -type d \( -name "u-boot" -o -name "u-boot-*" \)

builder@tspi-ubuntu18:/work/linux$ find . -maxdepth 3 -type d \( -name "u-boot" -o -name "u-boot-*" \)
./u-boot
./u-boot/include/u-boot

cd /work/linux

echo "===== android_ab.c key words ====="
grep -RIn "slot\|suffix\|misc\|priority\|tries\|successful\|magic\|AB0" \
u-boot/common/android_ab.c

echo "===== bootargs slot suffix ====="
grep -RIn "android_slotsufix\|androidboot.slot_suffix\|slot_suffix\|slotsufix" \
u-boot/common u-boot/cmd u-boot/arch/arm/mach-rockchip u-boot/include \
2>/dev/null

echo "===== partition slot logic ====="
grep -RIn "system_a\|system_b\|slot_suffix\|part_get_info\|android_part_get_info" \
u-boot/disk u-boot/common u-boot/cmd u-boot/arch/arm/mach-rockchip \
2>/dev/null

echo "===== important code ranges ====="
nl -ba u-boot/disk/part.c | sed -n '680,740p'
nl -ba u-boot/include/configs/evb_rk3568.h | sed -n '1,100p'
nl -ba u-boot/common/android_bootloader.c | sed -n '930,990p'
posted @ 2026-06-06 00:10  Smango  阅读(52)  评论(0)    收藏  举报