rk3288 linux OTA A/B升级分析

最近在做rk3288 linux的OTA A/B升级方案,因此研究了一下rk3288自带的OTA升级流程,将其记录下来。

1.概述

1.1 什么是OTA升级?

OTA是Over-the-Air的简称,OTA升级可以理解为用户正常使用过程中进行升级,OTA 升级旨在升级基础操作系统、系统分区上安装的只读应用和/或时区规则。

1.2 什么是OTA A/B升级?

A/B 系统升级(也称为无缝更新)的目标是确保在OTA升级期间在磁盘上保留一个可正常启动和使用的系统。

1.3 rk3288分区信息

参考: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)。

1.4 rk3288 linux SDK获取

参考https://github.com/rockchip-linux/docs/blob/master/Rockchip_Developer_Guide_Linux_Software_CN.pdf

以下代码路径均以SDK为根

2.rk3288 OTA升级

2.1 升级流程

  1. 运行update程序,命令为update ota /path/to/update.img
  2. update程序解析update.img文件,升级recovery分区
  3. update程序将升级指令写入misc分区,重启
  4. 重启后进入uboot,uboot解析misc分区中的命令,如果是升级指令,则从recovery分区引导
  5. 进入recovery模式,执行rkupdate程序,该程序将update.img中包含的各分区包写入到对应位置,然后清除misc分区中的命令,完成升级
  6. 重启,进入正常使用

rk3288 OTA升级流程涉及到update程序,misc分区,uboot,recovery分区,rkupdate程序。

2.2 update程序

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程序主要功能是:

  1. 如果update.img文件中包括recovery分区内容,则升级recovery分区
  2. 将android_bootloader_message写入misc分区16KB偏移处,android_bootloader_message中包含了下次启动时供uboot读取的升级指令

2.3 uboot启动流程

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分区引导。

2.4 recovery分区

recovery分区启动后会启动recovery程序,recovery程序的源码位于external/recovery/目录下,recovery程序是AOSP(Android Open Source Project)提供的,rockchip在其基础上改动,增加了对rkupdate程序的调用。具体的recovery分区和recovery程序的代码由于没有走读,故此略过。

2.5 rkupdate程序

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()

3.OTA A/B升级方案

通过前面的分析,参考AOSP的A/B升级方案(https://source.android.google.cn/devices/tech/ota/ab)rk3288 OTA A/B升级可以基于现有的OTA升级流程进行修改实现。

3.1 分区信息修改

修改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分区,在实施时可以去掉。

3.2 uboot修改

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");
	}

3.3 rkupdate修改

rkupdate程序的修改涉及以下:

  1. 获取当前系统是SLOT A还是SLOT B,可以从/proc/cmdline中读取,如:androidboot.slot_suffix=_a,该参数是uboot启动内核时设置的参数,可以参见uboot代码
  2. 由于现在有两个SLOT,因此修改更新flash的流程,如果当前系统是SLOT A,则更新SLOT B对应的数据
  3. 更新misc中struct AvbABData,位于misc分区2048字节位置。

 

你可能感兴趣的:(嵌入式)