最近在做rk3288 linux的OTA A/B升级方案,因此研究了一下rk3288自带的OTA升级流程,将其记录下来。
OTA是Over-the-Air的简称,OTA升级可以理解为用户正常使用过程中进行升级,OTA 升级旨在升级基础操作系统、系统分区上安装的只读应用和/或时区规则。
A/B 系统升级(也称为无缝更新)的目标是确保在OTA升级期间在磁盘上保留一个可正常启动和使用的系统。
参考:https://github.com/rockchip-linux/docs/blob/master/Tools/Rockchip-Parameter-File-Format-Version1.4.pdf
Rockchip android系统平台使用parameter文件来配置一些系统参数,比如固件版本,存储器分区信息等。
以下是一个典型的parameter.txt文件内容:
FIRMWARE_VER: 8.1
MACHINE_MODEL:rk3288
MACHINE_ID:007
MANUFACTURER:RK3288
MAGIC: 0x5041524B
ATAG: 0x00200800
MACHINE: 3288
CHECK_MASK: 0x80
PWR_HLD: 0,0,A,0,1
TYPE: GPT
CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00002000@0x00006000(trust),0x00002000@0x00008000(misc),0x00010000@0x0000a000(boot),0x00010000@0x0001a000(recovery),0x00010000@0x0002a000(backup),0x00020000@0x0003a000(oem),0x00700000@0x0005a000(rootfs),-@0x0075a000(userdata:grow)
uuid:rootfs=614e0000-0000-4b53-8000-1d28000054a9
这里重点关注其中的CMDLINE中的MTD分区定义:
例如:0x00700000@0x0005a000(rootfs),@符号之前的数值是分区大小,@符号之后的数值是分区的起始位置,括号里面的字符是分区的名字。所有数值的单位是sector,1个sector为512Bytes。此处定义了一个名为rootfs的分区,其大小为0x00700000个sector(3GB),起始位置为0x0005a000个sector(180MB)。
参考https://github.com/rockchip-linux/docs/blob/master/Rockchip_Developer_Guide_Linux_Software_CN.pdf
以下代码路径均以SDK为根
rk3288 OTA升级流程涉及到update程序,misc分区,uboot,recovery分区,rkupdate程序。
update程序的源码位于buildroot/package/rockchip/update/路径下,进行OTA升级时其代码调用路径如下:
main()->WriteFwData()->GetFwSize() # get update.img's size
->GetFwOffset() # get firmware offset in update.img
->if recovery partition exist both in flash and update.img, read it from update.img and write to flash
->CheckFwData() # binary check recovery partition's data
->rebootUpdate()->installPackage()->bootCommand() # write struct android_bootloader_message to misc partition offset 16 *1024, then reboot
struct android_bootloader_message {
char command[32];
char status[32];
char recovery[768];
/* The 'recovery' field used to be 1024 bytes. It has only ever
* been used to store the recovery command line, so 768 bytes
* should be plenty. We carve off the last 256 bytes to store the
* stage string (for multistage packages) and possible future
* expansion. */
char stage[32];
/* The 'reserved' field used to be 224 bytes when it was initially
* carved off from the 1024-byte recovery field. Bump it up to
* 1184-byte so that the entire bootloader_message struct rounds up
* to 2048-byte. */
char reserved[1184];
};
可见,update程序主要功能是:
uboot源码位于u-boot/目录下,rk3288开机默认执行的是boot_android指令,该指令对应的入口为do_boot_android(),位于u-boot/cmd/boot_android.c
do_boot_android()->android_bootloader_boot_flow()->part_get_info_by_name("misc", ...)
->android_bootloader_load_and_clear_mode()
->if ANDROID_BOOT_MODE_NORMAL->boot part is ANDROID_PARTITION_BOOT
->if ANDROID_BOOT_MODE_RECOVERY->boot part is ANDROID_PARTITION_RECOVERY
->if ANDROID_BOOT_MODE_BOOTLOADER->android_bootloader_boot_bootloader()
->android_image_load(boot_part_info, ...)
->android_assemble_cmdline()
->android_bootloader_boot_kernel()->do_bootm()->...
可见,uboot启动时从misc分区中获取command用以决定从哪个分区读取内核。OTA升级时会从recovery分区引导。
recovery分区启动后会启动recovery程序,recovery程序的源码位于external/recovery/目录下,recovery程序是AOSP(Android Open Source Project)提供的,rockchip在其基础上改动,增加了对rkupdate程序的调用。具体的recovery分区和recovery程序的代码由于没有走读,故此略过。
rkupdate程序位于external/rkupdate/目录下,OTA升级时,其调用流程如下:
main()->do_rk_firmware_upgrade()->CRKAndroidDevice::GetFlashInfo()
->CRKAndroidDevice::DownloadImage()->get struct STRUCT_RKIMAGE_HDR from update.img
->for item in update.img do CRKAndroidDevice::RKA_File_Download()
->for item in update.img do CRKAndroidDevice::RKA_File_Check()
通过前面的分析,参考AOSP的A/B升级方案(https://source.android.google.cn/devices/tech/ota/ab)rk3288 OTA A/B升级可以基于现有的OTA升级流程进行修改实现。
修改parameter.txt,支持slot A和slot B,slot A包括boot_a分区和system_a分区,slot B包括boot_b分区和system_b分区。
典型的A/B parameter.txt如下:
FIRMWARE_VER: 8.1
MACHINE_MODEL:rk3288
MACHINE_ID:007
MANUFACTURER:RK3288
MAGIC: 0x5041524B
ATAG: 0x00200800
MACHINE: 3288
CHECK_MASK: 0x80
PWR_HLD: 0,0,A,0,1
TYPE: GPT
CMDLINE: mtdparts=rk29xxnand:0x00002000@0x00004000(uboot),0x00002000@0x00006000(trust),0x00002000@0x00008000(misc),0x00010000@0x0000a000(boot_a),0x00010000@0x0001a000(boot_b),0x00010000@0x0002a000(recovery),0x00010000@0x0003a000(backup),0x00400000@0x0004a000(system_a),0x00400000@0x0044a000(system_b),0x00020000@0x0084a000(oem),-@0x0086a000(userdata:grow)
uuid:rootfs=614e0000-0000-4b53-8000-1d28000054a9
上面的分区信息中保留了recovery分区,在实施时可以去掉。
uboot中原本包含了A/B启动的代码,主要需要将u-boot/configs/rk3288_defconfig修改;由于现在不再进入recovery模式,还需要修改u-boot/common/android_bootloader.c中android_bootloader_boot_flow()中对启动模式的判断部分。其中u-boot/configs/rk3288_defconfig需要增加以下内容:
+CONFIG_SHA256=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
打开CONFIG_ANDROID_AB后,android_bootloader_boot_flow()中会增加对A/B分区的选择,其调用流程如下:
android_bootloader_boot_flow()->rk_avb_get_current_slot()->rk_avb_ab_slot_select()->avb_ab_data_read()->read_from_partition()
->avb_ab_data_verify_and_byteswap()->avb_crc32()
/* Struct used for recording per-slot metadata.
*
* When serialized, data is stored in network byte-order.
*/
typedef struct AvbABSlotData {
/* Slot priority. Valid values range from 0 to AVB_AB_MAX_PRIORITY,
* both inclusive with 1 being the lowest and AVB_AB_MAX_PRIORITY
* being the highest. The special value 0 is used to indicate the
* slot is unbootable.
*/
uint8_t priority;
/* Number of times left attempting to boot this slot ranging from 0
* to AVB_AB_MAX_TRIES_REMAINING.
*/
uint8_t tries_remaining;
/* Non-zero if this slot has booted successfully, 0 otherwise. */
uint8_t successful_boot;
/* Reserved for future use. */
uint8_t reserved[1];
} AVB_ATTR_PACKED AvbABSlotData;
/* Struct used for recording A/B metadata.
*
* When serialized, data is stored in network byte-order.
*/
typedef struct AvbABData {
/* Magic number used for identification - see AVB_AB_MAGIC. */
uint8_t magic[AVB_AB_MAGIC_LEN];
/* Version of on-disk struct - see AVB_AB_{MAJOR,MINOR}_VERSION. */
uint8_t version_major;
uint8_t version_minor;
/* Padding to ensure |slots| field start eight bytes in. */
uint8_t reserved1[2];
/* Per-slot metadata. */
AvbABSlotData slots[2];
/* Reserved for future use. */
uint8_t reserved2[12];
/* CRC32 of all 28 bytes preceding this field. */
uint32_t crc32;
} AVB_ATTR_PACKED AvbABData;
android_bootloader_boot_flow()流程中从misc分区的2048字节处读取struct AvbABData结构,然后根据SLOT的priority,tries_remaining,和successful_boot判断从SLOT A还是SLOT B启动,具体的判断逻辑代码如下:
static bool slot_is_bootable(AvbABSlotData* slot) {
return (slot->priority > 0) &&
(slot->successful_boot || (slot->tries_remaining > 0));
}
if (slot_is_bootable(&ab_data.slots[0]) && slot_is_bootable(&ab_data.slots[1])) {
if (ab_data.slots[1].priority > ab_data.slots[0].priority) {
slot_index_to_boot = 1;
} else {
slot_index_to_boot = 0;
}
} else if(slot_is_bootable(&ab_data.slots[0])) {
slot_index_to_boot = 0;
} else if(slot_is_bootable(&ab_data.slots[1])) {
slot_index_to_boot = 1;
} else {
avb_error("No bootable slots found.\n");
ret = AVB_AB_FLOW_RESULT_ERROR_NO_BOOTABLE_SLOTS;
goto out;
}
if (slot_index_to_boot == 0) {
strcpy(select_slot, "_a");
} else if(slot_index_to_boot == 1) {
strcpy(select_slot, "_b");
}
rkupdate程序的修改涉及以下: