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 升级流程
运行update程序,命令为update ota /path/to/update.img
update程序解析update.img文件,升级recovery分区
update程序将升级指令写入misc分区,重启
重启后进入uboot,uboot解析misc分区中的命令,如果是升级指令,则从recovery分区引导
进入recovery模式,执行rkupdate程序,该程序将update.img中包含的各分区包写入到对应位置,然后清除misc分区中的命令,完成升级
重启,进入正常使用
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程序主要功能是:

如果update.img文件中包括recovery分区内容,则升级recovery分区
将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程序的修改涉及以下:

获取当前系统是SLOT A还是SLOT B,可以从/proc/cmdline中读取,如:androidboot.slot_suffix=_a,该参数是uboot启动内核时设置的参数,可以参见uboot代码
由于现在有两个SLOT,因此修改更新flash的流程,如果当前系统是SLOT A,则更新SLOT B对应的数据
更新misc中struct AvbABData,位于misc分区2048字节位置。
————————————————
版权声明:本文为CSDN博主「小写的毛毛」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/bemind1/article/details/103067971

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值