4.4.9.1. OTA 功能介绍

概述介绍

OTA :( Over-the-Air Technology,空中下载技术)是通过无线网络实现远程软件升级技术。最早由安卓系统引入手机设备中, OTA 技术大幅简化传统软件升级过程,无需通过计算机连接设备,用户直接设备下载安装更新。这一技术极大地方便用户,提高设备维护效率。

ota_intro

  • 在 OTA 的广义应用中,可以划分云端设备两个主要组成部分。云端部分负责处理设备升级请求,包括执行升级校验、下发升级包以及收集升级结果任务。而设备端则主要依赖云端下发升级包,完成系统软件( FOTA, Firmware Over-the-Air)或应用程序( SOTA, Software Over-the-Air)的更新升级。

  • 本文旨在提供底层设备端 OTA 的用户手册,详细阐述 OTA 在底层系统软件应用程序升级机制及其实现方法,同时提供相关开发指导。需要特别指出是,通过 OTA 升级系统软件应用程序,主要更新存储外部存储器(如 eMMC )中数据。

  • OTA 的对外交付主要一套 API 及其相应实现库(如 libupdate.so),该库实现底层烧写校验关键功能。上层的 OTA 服务架构客户实现,用以对接客户云端服务。在成功云端下载升级包后, OTA 服务通过调用 libupdate.so 中接口,实现版本升级和校验操作,从而确保设备能够顺利、安全完成软件更新。

缩略语

缩略语 英文全称 中文解释
SoC System on Chip 片上系统
BL[x] Boot Loader Stage [x] 启动阶段 x
SPL Secondary Program Loader 二级程序加载
GPT GUID Partition Table GUID 磁盘分区
GUID Globally Unique IDentifier 全局唯一标识符
RSA RSA Algorithm RSA 公开密钥密码体制
eMMC embedded Multi-Media Card 嵌入式非易失性存储器

系统分区表

外部存储器 eMMC 采用 GPT 的形式组织分区管理数据。 OTA 时,将分区单位目标分区进行更新。这些分区按照不同类型如下区分:

分区类型 属性 升级方式 举例
持久分区 参数分区:一般存储系统运行需要加载一些配置文件参数数据。如分区表的 ubootenv 等

用户分区:指系统启动无关分区。一般系统启动挂载,如 userdata 分区
分区一般没有镜像, 分区数据需要长期保存, 不支持 OTA 升级。 ubootenv, misc, userdata
AB 分区 前缀同名尾缀带 _a 和_b 的分区称做 AB 分区。 AB 分区交替升级。 boot_a, boot_b
BAK 分区 前缀同名尾缀带 _bak <num> 的分区称作 BAK 分区,主要指 miniboot 分区,主要构成为 1 个主分区若干个备份分区。 BAK 分区升级分区,主分区升级验证成功后,将主分区内容同步备份分区。 miniboot, miniboot_bak1
分区 支持 OTA 升级分区( GOLDEN) 在 recovery 模式可以对 GOLDEN 分区进行 OTA 升级, GOLDEN 分区升级当仅适用于 NAND 和 eMMC。 hbre, system
  • X5 标准 eMMC 分区表(单分区形式)

序号 分区 大小 文件系统 用途
1 gpt 20k none 通用分区表
2 mbr 4k none 存放 bl2 加载镜像信息
3 miniboot 1280k none bl2,bl31,bl32
4 miniboot_bak1 1280k none bl2,bl31,bl32
5 misc 4k none 存储 slot ab 状态机
6 uboot 2m none Uboot
7 ubootenv 256k none 储存 Uboot 环境变量系统配置, Uboot 环境变量占 192k,其余的 64k 为系统配置
8 vbmeta 16k none 用于系统 AVB 启动校验签名镜像
9 boot 32m none Image.lz4 和 dtb
10 system 250m ext4 系统运行必要库、命令等, system
11 hbre 200m ext4 HOBOT 工具分区,挂载到 /usr/hobot
12 app 700m ext4 测试用例和 sample
13 private 256k ext4 存放不可更改设备数据等,如 MAC 地址、 SN 号
14 userdata - ext4 存放用户数据,在 Uboot 会扩展到 eMMC 最后位置
  • X5 标准 eMMC 分区表( AB 分区形式)

序号 分区 大小 文件系统 用途
1 gpt 20k none 通用分区表
2 mbr 4k none 存放 bl2 加载镜像信息
3 miniboot 1280k none bl2,bl31,bl32
4 miniboot_bak1 1280k none bl2,bl31,bl32
5 misc 4k none 存储 slot ab 状态机
6 uboot_a 2m none Uboot
7 uboot_b 2m none Uboot
8 ubootenv 256k none 储存 Uboot 环境变量系统配置, Uboot 环境变量占 192k,其余的 64k 为系统配置
9 vbmeta_a 16k none 用于系统 AVB 启动校验签名镜像
10 vbmeta_b 16k none 用于系统 AVB 启动校验签名镜像
11 boot_a 32m none Image.lz4 和 dtb
12 boot_b 32m none Image.lz4 和 dtb
13 system_a 250m ext4 系统运行必要库、命令等, system
14 system_b 250m ext4 系统运行必要库、命令等, system
15 hbre_a 200m ext4 HOBOT 工具分区,挂载到 /usr/hobot
16 hbre_b 200m ext4 HOBOT 工具分区,挂载到 /usr/hobot
17 app_a 700m ext4 测试用例和 sample
18 app_b 700m ext4 测试用例和 sample
19 private 256k ext4 存放不可更改设备数据等,如 MAC 地址、 SN 号
20 userdata - ext4 存放用户数据,在 Uboot 会扩展到 eMMC 最后位置

根据实际需求增减分区调整分区大小

默认情况下,系统采用分区配置;若需使用 AB 形式分区,将需要相应修改配置文件。以下示例以 EVB 配置修改为例进行说明。

ab_config

目前,系统列出 NAND 的具体分区表。为了确保兼容性,我们默认提供一个最小分区表,一份为 OTA 升级设计的 AB 类型 NAND 分区表,文件名为 “x5-soc-debug-nand-ab-ota-gpt.json”。客户使用时须自行进行修改,以适应特定需求。在切换分区类型时,请务必同时调整 NAND_SIZE 参数,具体切换方式参考下图示意。

nand_ab_config

功能详细介绍

OTA 无缝更新

AB 分区升级无缝更新

OTA 升级支持 Android 的 AB 无缝更新机制,旨在实现对系统 AB 分区高效升级。

AB 分区通常包含一套完整镜像,分别称为 a 和 b 两个槽( slot)。在系统启动时,系统始终一个选定的 _a 或_b 分区进行启动。 AB 分区升级过程采用交替升级策略。当系统当前运行在 a 槽时, OTA 更新对 b 槽进行更新,确保下次系统启动从 b 槽启动,反之亦然——当系统运行在 b 槽时, OTA 更新针对 a 槽进行。 这种交替更新机制发生升级失败时,能够自动触发版本回退,以确保系统能够恢复升级稳定状态。在设备上电启动后,会引导加载程序第二阶段( bl2 )运行一个名为 ab select 的程序,以选择合适槽( a 或 b),从而保证后续系统能够该 slot 顺利启动。

bl2_ab

每个 slot 的信息结构如下所示:

struct slot_metadata {
	// Slot priority with 15 meaning highest priority, 1 lowest
	// priority and 0 the slot is unbootable.
	uint8_t priority : 4;
	// Number of times left attempting to boot this slot.
	uint8_t tries_remaining : 3;
	// 1 if this slot has booted successfully, 0 otherwise.
	uint8_t successful_boot : 1;
	// 1 if this slot is corrupted from a dm-verity corruption, 0 otherwise.
	uint8_t verity_corrupted : 1;
	// Reserved for further use.
	uint8_t reserved : 7;
} __attribute__((packed));

操作系统启动管理中, bootloader 需判定每个系统分区( slot)的启动状态。为此, bootloader 为每个分区定义一系列重要属性,具体说明如下:

  • active (活动分区): 该标识排他性,指明当前操作启动分区。 bootloader 始终优先选择这个分区进行系统引导。

  • bootable(可启动): 表示分区存在一套引导系统文件,换言之,该分区具备启动必要条件。

  • successful(启动成功):表示该 slot 的系统正常启动。

  • unbootable(不可启动):表示分区损坏存在其他故障,无法完成启动过程。在系统升级过程中,该状态通常标记,且标记效果相当于所有上述标记清除;值得注意是,只有当前分区的 active 标记设置时, unbootable 的标记清除。

系统分区管理中,通常两个主要分区: slot_a 和 slot_b。根据设计,只有一个分区可以标记为 active,而两个分区可以同时具备 bootable 和 successful 属性。

下图展示升级过程四种状态,各个阶段两个分区状态变化(以 b 升 a 为例):

ab

  • 状态1: 默认状态, ab slot 都可以启动。 b 的优先级高于 a,默认从 b 启动。

slot_a = {
    .priority = 14,         //14 或更低
    .tries_remaining = 1,
    .successful_boot = 1,
    .verity_corrupted = 0,
    .reserved = 0
};

slot_b = {
    .priority = 15,         //15
    .tries_remaining = 1,
    .successful_boot = 1,
    .verity_corrupted = 0,
    .reserved = 0
};
  • 状态2 : 升级状态(烧写状态), slot a 不可启动且 boot success 为 0 。

slot_a = {
    .priority = 14,         //14 或更低
    .tries_remaining = 0,   //tries_remaining 为 0
    .successful_boot = 0,   //successful_boot 设置为 0
    .verity_corrupted = 0,
    .reserved = 0
};

slot_b = {
    .priority = 15,         //15
    .tries_remaining = 1,
    .successful_boot = 1,
    .verity_corrupted = 0,
    .reserved = 0
};

异常处理: 下次启动 从 slot b 启动

  • 状态3 : 烧写成功,还重启时,这个时候将要升级的 slot a 设置为 active 状态,并当前的 b slot 设置非 active 状态(调整优先级)

slot_a = {
    .priority = 15,         //15
    .tries_remaining = 1,   //tries_remaining 为 1
    .successful_boot = 0,   //successful_boot 为 0
    .verity_corrupted = 0,
    .reserved = 0
};

slot_b = {
    .priority = 14,         //14 或更低
    .tries_remaining = 1,
    .successful_boot = 1,
    .verity_corrupted = 0,
    .reserved = 0
};

异常处理: slot a 重启验证失败,未能到 successful_boot,则 tries_remaining 会在 bl2 的 ab select 阶段减 1 ,导致变成 slot a 变成 unbootable(相当于跳转状态 2 )。然后下次启动从 slot b 启动

  • 状态4 : 重启验证成功,将 slot a 设置为 success boot 状态

slot_a = {
    .priority = 15,         //15
    .tries_remaining = 1,   //tries_remaining 为 1
    .successful_boot = 1,   //successful_boot 为 1
    .verity_corrupted = 0,
    .reserved = 0
};

slot_b = {
    .priority = 14,         //14 或更低
    .tries_remaining = 1,
    .successful_boot = 1,
    .verity_corrupted = 0,
    .reserved = 0
};

详细原理实现参考官方文档:OTA 更新

BAK 升级

设备架构中, miniboot 分区定义为 BAK 类型分区。 BAK 分区主要功能启动过程优先尝试从主分区加载系统,若主分区启动失败,系统依次切换备份分区进行启动。因此,采用 BAK 方式进行系统升级显得尤为重要。

BAK 分区升级流程如下:

bak_upgrade

  • 升级操作针对分区进行,确保分区内容安全完整。

  • 分区升级成功完成验证后,系统内容同步备份分区,以保持数据一致性恢复性。

  • 随后,设备重启重新验证分区启动状态。

  • 验证成功,主分区内容再次同步到 BAK 分区,并进行 md5 值校验确保数据完整性。若验证失败,则系统自动切换至 BAK 分区保证设备可用性。

以下流程详细分析:

  1. 开始(Start)

    • 流程开始节点启动。

  2. 升级分区(Upgrade main partition)

    • 首先对主分区进行升级操作,这一步固件软件入主分区。升级操作针对分区进行,确保分区内容安全完整。

  3. 分区升级成功?(Main partition upgrade successful?)

    • 检查分区升级是否成功。

    • 如果升级失败(no),流程结束。

    • 如果升级成功(yes),则继续一步。

  4. 同步备份分区(Sync to backup partition)

    • 升级分区数据同步备份分区。这一步为了确保备份分区最新数据,以便需要可以恢复。

  5. 重启设备验证启动状态(Restart the device and verify its startup status)

    • 重启设备应用升级。

    • 验证设备是否能够正常启动。

  6. 分区启动验证成功?(Main partition startup verification successful?)

    • 检查设备启动后主分区是否正常工作。

    • 如果验证失败(no),则自动切换备份分区,以确保系统稳定运行。

    • 如果验证成功(yes),则继续一步。

  7. 同步到 BAK 分区执行 MD5验证(Sync to BAK partition and perform MD5 verification)

    • 数据同步到 BAK 分区,并进行 MD5校验,以确保数据完整性一致性。

  8. MD5验证成功?(MD5 verification successful?)

    • 检查 MD5校验是否通过。

    • 如果校验失败(no),则自动切换备份分区,以确保系统稳定运行。

    • 如果校验成功(yes),则流程继续。

  9. 升级完成(Upgrade completed)

    • 所有步骤成功完成后,升级流程结束,系统处于最新状态。

GOLDEN 升级

  • 支持版本: NAND, eMMC;

  • 配置步骤(以 eMMC 版为例)

    1. 板级配置文件HR_RECOVERY_MODE 变量赋值为 “yes”,以便使系统支持 recovery 模式;

      recovery_config

    2. 选用支持 GOLDEN 升级分区表配置文件;

      recovery_partition

  • 升级流程

    1. 重启设备,进入 recovery 模式;

      reboot -m recovery -f
      
    2. 从 Server 下载升级包到 /tmp(内存)中;

    3. OTA 升级应用(执行方式正常模式一致);

  • 注意事项

    1. GOLDEN 分区升级必须在 recovery 模式进行;

    2. recovery 模式使用是 initramfs,升级对 /tmp 下可用空间大小要求(/tmp 可用空间一般是 Kernel 下可用内存一半);

      • NAND 版:/tmp 下可用空间最低要求为:升级包镜像大小 + 程序运行内存(约 50M);

      • eMMC 版:/tmp 下可用空间最低要求为:升级包镜像大小 + 程序运行内存(约 60M);

分区烧写方式

系统采用分区单位进行升级,每个升级分区具有独立镜像文件。在升级过程中,主要对应分区镜像文件写入外部存储器中。当前对eMMC版支持镜像升级差分升级,NAND版本支持全量镜像升级

镜像升级

  • 全量镜像提供目标分区完整镜像文件。在执行升级时,该镜像直接写入外部存储器对应分区中。全镜像升级确保分区完整性一致性,能够有效恢复分区预期状态,适用需要全面更新恢复场景。通过这种方式,用户可以简化升级流程,减少潜在兼容性问题,提高系统稳定性可靠性。

差分镜像升级(仅支持eMMC)

  • 差分升级一种基于差异升级方式,通过传输目标分区新旧版本之间差异部分(即差异包),而不是完整镜像文件。在执行升级时,设备端会接收差异包,并应用当前分区数据上,从而生成版本目标分区。差分升级核心优势在于显著减少传输数据量,节省带宽升级时间,特别适合网络条件有限需要频繁升级场景。但是,差分升级不会节省内存。由于差异需要设备进行解压应用,这一过程通常需要额外内存资源存储临时数据执行复杂算法操作。因此,差分升级设备内存资源要求高,尤其处理较大差异包时,可能占用内存空间。

  • 注意事项

    • 差分升级分区必须mount成只读模式(ro)。

OTA 安全包含措施

镜像防回保护

OTA 升级支持进行镜像防回保护,目的防止OTA 升级低于当前设备antirollback 的版本。

  • OTA 升级包的antirollback 版本号:通过解析镜像头部的antirollback 版本号获取。

  • 升级设备的antirollback 版本号:存储于eFuse中。

镜像防回限制:

  • 目前只有eMMC介质的AB形式分区支持镜像防回保护;

  • antirollback版本支持通过OTA升级方式更新;

  • sec和nosec的antirollback版本分别支持65个版本[0-64]的迭代

  • 其他限制实现细节参考secure部分软件版本防回说明

开启 antirollback

当前默认关闭镜像antirollback校验,若需要使能antirollback版本校验,则需要修改以下配置文件。

  1. 检查分区表配置文件board_cfg/soc/x5-soc-debug-ab-gpt.jsonboard_cfg/soc/sub_config/miniboot.jsonhave_anti_ver属性,此属性含义分区镜像包中是否含有antirollback的版本信息,需确保miniboot、uboot、vbmeta分区have_anti_ver属性为true,其他分区省略属性为false。目前x5-soc-debug-ab-gpt.json默认开启属性,若客户使用自定义分区表,需确保属性开启。

    {
        "Note": "Miniboot partition configuration information, please do not modify it.",
        "miniboot": {
            "bl2": {
                "size": "256k"
            },
            "bl3x": {
                "size": "2m"
            },
            "size": "2304k",
            "part_type": "BAK",
            "ota_is_update": true,
            "ota_update_mode": "image",
            "have_anti_ver": true
        }
    }
    
    "uboot": {
            "part_type": "AB",
            "size": "2m",
            "ota_is_update": true,
            "ota_update_mode": "image",
            "have_anti_ver": true
        },
    "vbmeta": {
            "part_type": "AB",
            "size": "16k",
            "ota_is_update": true,
            "ota_update_mode": "image",
            "have_anti_ver": true
        }
    
  2. 修改device/horizon/x5/board_x5_evb_debug_config.mk配置文件:

    • 如果客户签名BL2的芯片(X5 Customer root rsa key hash烧录使用),会同时开启secure和non-secure antirollback域防回检查;如果地瓜key签名的BL2芯片,开启secure boot后,只支持non-secure antirollback域防回检查。对于客户key签名BL2的芯片,需要设置以下变量:

          export HR_ENABLE_CUSTOMER_KEY="yes"
      
    • 修改HR_PART_CONF_FILENAME变量,由于只有eMMC介质的AB形式分区支持镜像防回保护,所以选用x5-soc-debug-ab-gpt.json配置文件,若客户自定义分区配置文件,需确保为AB分区;

          export HR_PART_CONF_FILENAME=${HR_BOARD_CONF_DIR}/x5-soc-debug-ab-gpt.json
      
    • 修改ANTIROLLBACK_SEC_UPDATEANTIROLLBACK_NOSEC_UPDATE值(true/false),选择是否更新相应eFuse中的antirollback版本;

    • 修改ANTIROLLBACK_SEC_VER值,配置miniboot.img的版本号;

    • 修改ANTIROLLBACK_NOSEC_VER值,同时配置boot.img和vbmeta.img的版本号;

          # antirollback version
          export ANTIROLLBACK_SEC_UPDATE="true"
          export ANTIROLLBACK_SEC_VER=0
          export ANTIROLLBACK_NOSEC_UPDATE="true"
          export ANTIROLLBACK_NOSEC_VER=0
      
  3. 修改uboot/configs/hobot_x5_auto_defconfig配置文件,添加CONFIG_X5_SUPPORT_CHECK_ROLLBACK=y,开启antirollback检查:

        CONFIG_X5_SUPPORT_CHECK_ROLLBACK=y
    
版本号获取
  1. eFuse中antirollback版本获取方法

    • 端侧输入ota_tool -v命令,获取当前的antirollback版本(antirollback版本更新重启才能生效,否则查询还是版本号)。

          root@buildroot:~# ota_tool -v
          OTA Library version is 1.0.1
          system version is V1.1.0_20250617-1317
          Nosecure antirollback version is 35
          Secure antirollback version is 35
      

OTA 版本校验

OTA 系统支持升级升级包版本进行校验,以确保升级包版本低于当前系统版本。如果检测升级包版本低于系统版本,系统自动停止此次升级操作。

  • 升级包版本信息存储在 OTA 配置文件 data.json 中的 sys_version 字中。

  • 当前设备版本号通过解析位于 /etc/version 文件获取。

当前版本号格式如下:

root@buildroot:~# cat /etc/version
LNX6.1.83_PL5.1_V1.0.16_20250107-1708

系统定位版本号第一次出现 “_V” 的位置,然后版本号 “ V1.0.16” 进行大小比较,同时忽略版本号后面时间信息。

注:若用户具备 root 权限,则可以通过修改 /etc/version 文件,以临时解锁降级能力。

分区校验

OTA 系统同样支持镜像的 GPT 分区文件进行校验,这一步通过镜像的 GPT 分区表当前系统的 GPT 分区表进行对比,以验证是否分区调整。如果发现 GPT 分区调整,系统立即停止升级操作。

OTA 升级包含有编译系统生成分区文件 gpt.conf,其内容格式如下:

mbr:20480:24575:2
miniboot:24576:2383871:2
miniboot_bak1:2383872:4743167:2
misc:4743168:4747263:2
uboot:4747264:6844415:2
ubootenv:6844416:7106559:2
vbmeta:7106560:7122943:2
boot:7122944:19705855:2
system:19705856:281849855:2
hbre:281849856:491565055:2
app:491565056:1225568255:2
private:1225568256:1225830399:2
userdata:1225830400:1278259199:2

gpt.conf 文件格式为:分区名称:起始位置:结束位置:分区ID,该文件内容解析如下:

  1. mbr

    • 起始位置:20480

    • 结束位置:24575

    • 大小:24575 - 20480 + 1 = 4096 字节

  2. miniboot

    • 起始位置:24576

    • 结束位置:2383871

    • 大小:2383871 - 24576 + 1 = 2359296 字节

  3. miniboot_bak1

    • 起始位置:2383872

    • 结束位置:4743167

    • 大小:4743167 - 2383872 + 1 = 2359296 字节

  4. misc

    • 起始位置:4743168

    • 结束位置:4747263

    • 大小:4747263 - 4743168 + 1 = 4096 字节

  5. uboot

    • 起始位置:4747264

    • 结束位置:6844415

    • 大小:6844415 - 4747264 + 1 = 2097152 字节

  6. ubootenv

    • 起始位置:6844416

    • 结束位置:7106559

    • 大小:7106559 - 6844416 + 1 = 262144 字节

  7. vbmeta

    • 起始位置:7106560

    • 结束位置:7122943

    • 大小:7122943 - 7106560 + 1 = 16384 字节

  8. boot

    • 起始位置:7122944

    • 结束位置:19705855

    • 大小:19705855 - 7122944 + 1 = 12582912 字节

  9. system

    • 起始位置:19705856

    • 结束位置:281849855

    • 大小:281849855 - 19705856 + 1 = 262943000 字节

  10. hbre

    • 起始位置:281849856

    • 结束位置:491565055

    • 大小:491565055 - 281849856 + 1 = 209715200 字节

  11. app

    • 起始位置:491565056

    • 结束位置:1225568255

    • 大小:1225568255 - 491565056 + 1 = 734002200 字节

  12. private

    • 起始位置:1225568256

    • 结束位置:1225830399

    • 大小:1225830399 - 1225568256 + 1 = 262144 字节

  13. userdata

    • 起始位置:1225830400

    • 结束位置:1278259199

    • 大小:1278259199 - 1225830400 + 1 = 52428800 字节

汇总如下:

分区名称 起始位置 结束位置 大小
mbr 20480 24575 4 KB
miniboot 24576 2383871 2304 KB
miniboot_bak1 2383872 4743167 2304 KB
misc 4743168 4747263 4 KB
uboot 4747264 6844415 2048 KB
ubootenv 6844416 7106559 256 KB
vbmeta 7106560 7122943 16 KB
boot 7122944 19705855 12 MB
system 19705856 281849855 250 MB
hbre 281849856 491565055 200 MB
app 491565056 1225568255 700 MB
private 1225568256 1225830399 256 KB
userdata 1225830400 1278259199 50 MB

分区表系统分区表章节分区形式分区表进行对比,符合系统分区大小限制。

由于系统支持最后一个分区自动扩展,因此结束地址动态改变。因此进行 GPT 分区校验时,只校验到 userdata 分区,因为最后一个分区通常包含镜像内容,不会影响正常使用。

OTA 升级流程

  1. OTA 服务云端下载升级包进行校验,如果存在 OTA 分区,则下载至 /ota;否则下载至 /userdata。此时还会调用 otaInitLib 进行动态初始化。

  2. 通过调用 otaRequestStart 发起升级请求。该 API 将解压升级包的 ota_process 程序,并创建一个进程执行程序进行实际镜像烧写。同时,该 API 还会创建文件锁以防止同时进行多次升级,并创建管道文件( pipe)以便与 ota_process 进行通信。此外,还会启动一个线程定期读取管道( pipe),以获取实时升级进程、结果分区信息。

  3. 在子进程升级阶段, OTA Service 可以通过调用 otaGetResult 获取升级结果、调用 otaGetProgress 获取升级进度、调用 otaGetUpdatingImageName 获取正在升级镜像。

  4. 当 otaGetResult 返回 OTA_UPGRADE_SUCCESS 时,表示镜像烧写成功,系统进入校验阶段。

  5. 调用 otaSetPartition 将下次启动分区设置分区,并开始重启流程。

  6. 重启后, OTA Service 通过调用 otaGetOwnerFlag 获取升级的 owner,如果 owner 为 OTA Service,则 OTA Service 将负责此次升级校验,进入校验流程。

  7. 调用 otaCheckUpdate 获取升级结果。该 API 主要检查镜像是否完整写入、是否预期的 AB slot、 BAK slot 启动。

  8. 调用 otaMarkOTASuccessful 标记当前分区启动成功。该 API 操作 AB 状态机,将当前 slot 标记为 boot_successful,确保后续该 slot 启动。如果步骤之前发生任何重启,下次旧版本镜像所在的 slot 启动,代表升级失败。

  9. 调用 otaPartitionSync 进行 BAK 分区同步,确保 BAK 分区内容分区内容一致。

  10. 最后,通过调用 otaClearFlags 清除升级标记,完成此次升级过程。

升级流程图如下:

otaservice

OTA 打包介绍

打包镜像

打包工具使用方法

通过执行下列命令查询打包工具帮助信息

./bd.sh otapackage help

以下为ota打包工具详细使用方法

==================================================================================
   \  //   Welcome to the OTA Package Build System!
    \//    Working directory: /home/zxs/x5
    //\
   //  \
==================================================================================
Available commands for bd.sh:
./bd.sh otapackage [image | image_diff | all | help] [path]

Support functions:
        help              Displays this help message.
        image|all         Generate an all_in_one.zip package.
        image_diff [path] Generate a differential OTA package using the specified path.

Usage example:
        ./bd.sh otapackage image
        ./bd.sh otapackage all
        ./bd.sh otapackage image_diff /path/to/diff
        ./bd.sh otapackage help
==================================================================================
Note:
  1. If no command is provided, the default action will be 'all'.
  2. The [path] argument must be a valid file path for differential OTA.
==================================================================================

镜像打包方法

完成升级镜像制作后,可以通过执行以下命令打包 OTA 升级包( all_in_one.zip):

./bd.sh otapackage

生成的 OTA 升级包输出以下路径: out/product/ota_packages。在目录下,您看到两个文件:

ls out/product/ota_packages
all_in_one.signature all_in_one.zip

其中, all_in_one.zip 是 OTA 升级包, all_in_one.signature 是升级包签名文件。签名算法采用 RSA 4096 SHA-256 。

差分镜像打包方法

差分镜像升级需要依赖已经烧录镜像包,因此,在计划使用差分升级时,务必妥善保存镜像避免丢失损坏

  • 首次差分升级准备

    • 生成镜像包:在第一次进行差分升级时,需要生成镜像前,通过镜像打包方法生成镜像包,为了便于区分手动生成的all_in_one.zip重命名为all_in_one_old.zip。

      ./bd.sh otapackage
      
    • 生成镜像:旧镜像妥善保存编译生成镜像。

    • 生成差分镜像包:通过一下命令生成差分镜像包,同时生成全量镜像包,可作为下次差分升级镜像包。

      ./bd.sh otapackage image_diff ./out/product/ota_packages/all_in_one_old.zip
      

      生成的 OTA 包将输出以下路径: out/product/ota_packages。在目录下,您看到四个生成文件:

      ls out/product/ota_packages
      all_in_one.signature  all_in_one.zip  all_in_one_inc.signature  all_in_one_inc.zip
      

      其中,all_in_one.zip是OTA全量镜像包,可重命名为all_in_one_old.zip作为一次差分升级镜像包。all_in_one_inc.zip是差分镜像包,.signature是相应镜像签名文件,签名算法采用 RSA 4096 SHA-256。

  • 后续差分升级

    • 第二次差分升级开始,可以使用上次差分打包生成全量镜像作为镜像包,无需重复生成镜像操作。

签名密钥

签名使用密钥位于以下路径:build/tools/ota_tools/keys,包含两个文件:

private_key.pem  public_key.pem

其中, private_key.pem 是签名私钥, public_key.pem 是验签公钥。在编译 hbre/otaupdate 模块时, public_key.pem 会嵌入到 hbre 镜像中。在设备端,该公钥路径为 /usr/hobot/share/ota/public_key.pem。

如果需要替换自己密钥,步骤如下:

  • 私钥生成:

    openssl genrsa -out private_key.pem 4096
    
  • 公钥生成:

    openssl rsa -RSAPublicKey_out -in private_key.pem  -out public_key.pem
    
  • 替换路径 build/tools/ota_tools/keys 下的 private_key.pem 和 public_key.pem

  • 执行下列命令重新编译

    ./bd.sh
    ./bd.sh otapackage
    

注意: 打包生成的 OTA 包默认命名为 all_in_one.zip。升级程序包名进行校验,包名必须包含 “all_in_one” 关键字,且后缀必须为 .zip。此外,包名不得包含以下关键字:”app”、”APP”、”middleware”、”param”。

OTA 升级包介绍

升级包结构

Archive:  all_in_one.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
      497  2025-01-09 14:30   gpt.conf
     3030  2025-01-09 14:30   data.json
  1310720  2025-01-09 17:39   miniboot.img
  1210176  2025-01-09 21:04   uboot.img
    16384  2025-01-09 17:40   vbmeta.img
 33554432  2025-01-09 17:40   boot.img
262144000  2025-01-09 17:40   system.img
209190912  2025-01-09 21:09   hbre.img
734003200  2025-01-09 17:41   app.img
   237520  2025-01-09 14:29   ota_process
---------                     -------
1241670871                     10 files

上述内容展示当前针对 eMMC 介质的 OTA 升级包文件结构,主要包括以下四类文件。镜像文件数量根据实际配置有所增减。

文件 描述
gpt.conf 分区表文件
data.json OTA 配置文件
*.img 分区镜像
ota_process OTA 烧写程序

注意:OTA 包中应包含所有支持 OTA 升级分区镜像。当前支持单个部分分区进行单独的 OTA 升级。

OTA 配置文件

OTA 升级包包含一个名为 data.json 的配置文件。该文件编译生成,其中包含升级包分区信息镜像信息。

通用配置

配置 类型 功能
sys_version str 系统软件版本
update_partition arr[str] 升级分区
partition_info arr[obj] 分区配置

分区配置( partition_info)

配置 类型 功能
md5sum arr[obj] 镜像 MD5 值
md5_scope arr[obj] 镜像 MD5 校验长度
medium str 所在外存介质 (nor/eMMC/nand)
part_type str 分区类型 (AB/BAK/GOLDEN)
upgrade_method str 升级方式 (image)
imgname str 镜像名,仅支持.img/.bin/.ubifs后缀文件

以下一个 data.json 文件示例:

{
    "sys_version": "LNX6.1.83_PL5.1_V1.0.16_20250111-1657",
    "update_partition": [
        "miniboot",
        "uboot",
        "vbmeta",
        "boot",
        "system",
        "hbre",
        "app"
    ],
    "partition_info": {
        "miniboot": {
            "md5sum": {
                "miniboot.img": "892e12d67ce2f6d8cf4fa77644fb329f"
            },
            "md5_scope": {
                "miniboot.img": 2359296
            },
            "medium": "emmc",
            "part_type": "BAK",
            "upgrade_method": "image",
            "imgname": "miniboot.img"
        },
        "uboot": {
            "md5sum": {
                "uboot.img": "2859118f26e787993551baa1eaf05b79"
            },
            "md5_scope": {
                "uboot.img": 2097152
            },
            "medium": "emmc",
            "part_type": "GOLDEN",
            "upgrade_method": "image",
            "imgname": "uboot.img"
        },
        "vbmeta": {
            "md5sum": {
                "vbmeta.img": "a81d6d6d7c3bd900ab1c6d172fc160c0"
            },
            "md5_scope": {
                "vbmeta.img": 16384
            },
            "medium": "emmc",
            "part_type": "GOLDEN",
            "upgrade_method": "image",
            "imgname": "vbmeta.img"
        },
        "boot": {
            "md5sum": {
                "boot.img": "a0e1065fd364f8d18198f099309b975c"
            },
            "md5_scope": {
                "boot.img": 12582912
            },
            "medium": "emmc",
            "part_type": "GOLDEN",
            "upgrade_method": "image",
            "imgname": "boot.img"
        },
        "system": {
            "md5sum": {
                "system.img": "92d66eb15695e0f29de1368703cf74b2"
            },
            "md5_scope": {
                "system.img": 262144000
            },
            "medium": "emmc",
            "part_type": "GOLDEN",
            "upgrade_method": "image",
            "imgname": "system.img"
        },
        "hbre": {
            "md5sum": {
                "hbre.img": "c7b4a3467667f2b61940aa8b5135b811"
            },
            "md5_scope": {
                "hbre.img": 209715200
            },
            "medium": "emmc",
            "part_type": "GOLDEN",
            "upgrade_method": "image",
            "imgname": "hbre.img"
        },
        "app": {
            "md5sum": {
                "app.img": "4303af014b1f269aa97eb0d91be8cb9f"
            },
            "md5_scope": {
                "app.img": 734003200
            },
            "medium": "emmc",
            "part_type": "GOLDEN",
            "upgrade_method": "image",
            "imgname": "app.img"
        }
    }
}

通过以上配置, OTA 升级包能够确保每个分区镜像升级过程正确校验更新。

OTA 升级介绍

系统提供 libupdate.so 库,其中包含进行 OTA 升级的 API。有关 API 的详细定义,请参考相应OTA API 文档

ota_tool 使用

ota_tool 是使用 libupdate.so API 实现示例工具,旨在通过板端手动发起 OTA 升级。该工具可以作为 OTA 升级服务开发参考,相关代码位于 hbre/otaupdate/src/ota_tool 目录。

ota_tool Usage:
   -v, --version                      get this library's version,
                                          system version,
                                          antirollback version
   -b, --boot                         check ota update status when boot.
   -s, --setpartition [partition]     set A/B slot partition, 0--A; 1--B.
   -g, --getpartition                 get A/B slot partition, 0--A; 1--B.
   -p, --package [package_path]       specify the path of package, the package paths can be relative or absolute, it's length must be smaller than 64 bytes.
   -n, --noreboot                     request ota without reboot.
   -c, --checksign                    signature check.
   -i, --signature                    signature information file.
   -h, --help                         Display this help screen.

使用 ota_tool 进行升级前,需要将 OTA 升级包上传至板端。

参数介绍:

  • -h 用于获取帮助信息。

  • -v 用于获取 libupdate.so 版本、当前系统软件版本。

  • -b 启动检查升级结果(系统启动时会自动进行检查,用户无需干预)。

  • -s 设置下次启动 A/B slot, 0 表示 A, 1 表示 B。

  • -g 获取当前 A/B slot。

  • -p 指定升级包。

  • -n 升级成功进行自动重启。

  • -c 启用完整性验证。

  • -i 指定签名文件(必须在 -p 参数后面)。

举例:

# 全量升级,不验证包完整性
ota_tool -p all_in_one.zip

# 全量升级,验证包完整性
ota_tool -c -p all_in_one.zip -i all_in_one.signature

# 差分升级,不验证包完整性
ota_tool -p all_in_one_inc.zip

# 差分升级,验证包完整性
ota_tool -c -p all_in_one_inc.zip -i all_in_one_inc.signature

ota_tool 实现

ota_tool 使用 C 语言实现,源码文件主要hbre/otaupdate/src/ota_tool/otainterface.c。其实现获取系统软件版本、设置 / 获取 ab slot、 OTA 包签名校验、 OTA 升级、升级进度显示、重启检测功能。

函数前半部负责解析输入参数,例如传入 -c 参数,则使用相应签名文件升级包进行签名校验。

最后,调用 ota_update_all_img 函数启动升级过程。

ota_update_all_img 实现

static int32_t ota_update_all_img(const char *zip_path)
{
	int32_t progress = 0;

	uint8_t		    slot = 0;
	uint8_t		    next_slot = 0;
	int32_t		    ret = 0;
	ota_update_result_e result = 0;
	char		    part_name[ARRAY_32] = { 0 };

	ret = otaGetPartition(&slot);
	if (ret < 0) {
		return ret;
	}

	if (slot == 2) {
		next_slot = 0;
		printf("The slot [%d] to be burned\n", next_slot);
	} else {
		next_slot = 1 - slot;
		printf("The slot [%d] to be burned\n", next_slot);
	}

	ret = otaInitLib();
	if (ret < 0) {
		printf("error:init failed!\n");
		return ret;
	}

	ret = otaRequestStart(zip_path, OTA_TOOL);
	if (ret < 0) {
		printf("error: start ota update failed!\n");
		ret = -1;
		goto err;
	}
	while (otaGetResult() != OTA_UPGRADE_SUCCESS && otaGetResult() != OTA_UPGRADE_FAILED) {
		progress = otaGetProgress();
		result = otaGetResult();
		otaGetUpdatingImageName(part_name, sizeof(part_name));

		if (result == UPGRADE_FAILED) {
			printf("error: ota update failed!\n");
			ret = -1;
			break;
		}
		OTA_show_Process_Bar(part_name, progress,
				     "OTA is upgrading ...");
		usleep(100 * 1000);
	}

err:
	if (otaGetResult() == OTA_UPGRADE_SUCCESS) {
		ret = otaSetPartition(next_slot);
		if (ret < 0) {
			printf("error: set partition failed!\n");
			return ret;
		}
		if (g_is_reboot == true) {
			printf("reboot system!\n");
			ota_system_exe("reboot");
		} else {
			printf("ota update success and waiting for reboot!\n");
		}
	}
	otaDeinitLib();

	return ret;
}
  1. 调用 otaGetPartition 获取当前所在 ab slot。

  2. 调用 otaInitLib 初始化 libupdate.so 库。

  3. 调用 otaRequestStart 函数传入升级包路径所有者( OTA_TOOL),开始执行升级。

  4. 等待 otaGetResult 返回结果OTA_UPGRADE_SUCCESSOTA_UPGRADE_FAILED 。在过程中,调用 otaGetProgressotaGetResultotaGetUpdatingImageName 获取升级进度、结果正在升级镜像,并通过 OTA_show_Process_Bar控制台打印进度。

  5. 升级结果 otaGetResultOTA_UPGRADE_SUCCESS ,则认为升级成功,调用 otaSetPartition 设置 ab slot 到向 slot ,并根据配置重启系统。

otatool-ota_update_all_img

ota_boot_check 实现

系统启动后,将启动一个 S99ota_update_check 服务,该服务调用 ota_tool -b 进行升级校验,进入 ota_boot_check 流程。

int32_t ota_boot_check(void)
{
	int32_t		      ret = 0;
	enum ota_update_owner owner = 0;

	if ((ret = otaInitLib()) != 0) {
		printf("error: init failed!\n");
		return ret;
	}

	if ((ret = otaGetOwnerFlag(&owner)) != 0) {
		printf("error: Get owner flag failed!\n");
		goto exit;
	}

	if (owner == NORMAL_BOOT) {
		printf("Normal boot\n");
		if ((ret = otaMarkOTASuccessful()) != 0) {
			printf("error: mark boot success failed\n");
		}
		return ret;
	}

	if (owner != OTA_TOOL) {
		printf("ota_tool is not owner, owner is [%d]\n", owner);
		return otaDeinitLib();
	}

	if ((ret = otaCheckUpdate()) != 0) {
		printf("error: boot check failed\n");
		goto exit;
	}

	if ((ret = otaMarkOTASuccessful()) != 0) {
		printf("error: mark boot success failed\n");
		goto exit;
	}

	if ((ret = otaPartitionSync()) != 0) {
		printf("error: partition sync failed\n");
		goto exit;
	}

exit:
	otaClearFlags();
	otaDeinitLib();
	return ret;
}
  1. 调用 otaInitLib 初始化库。

  2. 调用 otaGetOwnerFlag 获取当前升级 owner。

  3. 若 owner 为 NORMAL_BOOT ,则调用 otaMarkOTASuccessful 标记启动成功,随后退出。

  4. 若 owner 不OTA_TOOL ,正常退出,等待其他所有者进行重启检测。

  5. 调用 otaCheckUpdate 获取升级结果。如果结果异常,调用 otaClearFlags 清除 OTA 标记,终止 OTA 流程。

  6. 调用 otaMarkOTASuccessful 标记启动成功。

  7. 调用 otaPartitionSync 同步 AB 分区和 BAK 分区。如果调用返回非零值,则表示 AB、 BAK 分区同步失败,对应分区处于不可状态,需要重新进行 OTA 升级修复对应的 AB 和 BAK 分区。

ota_boot_check 流程图如下:

otatool-ota_boot_check