# OTA 功能与介绍

## 概述与介绍

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

![ota_intro](_static/_images/ota_intro.png)

- 在 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        | 嵌入式非易失性存储器       |

<a id="System_partition_table"></a>

## 系统分区表

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

| 分区类型 | 属性 | 升级方式 | 举例 |
|---------|------|---------|------|
| 持久化分区 | 参数分区：一般存储系统运行时需要加载的一些配置文件和参数等数据。如分区表中的 ubootenv 等 <br><br> 用户分区：指与系统启动无关的分区。一般在系统启动后才会被挂载，如 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](_static/_images/ab_config.png)

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

![nand_ab_config](_static/_images/nand_ab_config.png)

## 功能详细介绍

### 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](_static/_images/bl2_ab.png)

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

```C
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](_static/_images/ab.png)

- **状态1：** 默认状态， ab slot 都可以启动。 b 的优先级高于 a，默认从 b 启动。

```C
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 。

```C
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 状态（调整优先级）

```C
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 状态

```C
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 更新](https://source.android.google.cn/docs/core/ota?hl=zh-cn)

#### BAK 升级

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

**BAK 分区升级流程如下：**

![bak_upgrade](_static/_images/bak_upgrade.png)

- 升级操作仅针对主分区进行，确保主分区内容的安全和完整。
- 主分区升级成功并完成验证后，系统会将内容同步至备份分区，以保持数据的一致性和可恢复性。
- 随后，设备将重启并重新验证主分区的启动状态。
- 若验证成功，主分区的内容将再次同步到 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](_static/_images/recovery_config.png)
  2. 选用支持 GOLDEN 升级的分区表配置文件；

        ![recovery_partition](_static/_images/recovery_partition.png)
- 升级流程
  1. 重启设备，进入 recovery 模式；

     ```bash
     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部分[软件版本防回滚](../security_development/secure_boot/secure_boot.html#antirollback-main)说明

##### <span id="enable_antirollback"/> 开启 antirollback
当前默认关闭镜像antirollback校验，若需要使能antirollback版本校验，则需要修改以下配置文件。
1. 检查分区表配置文件`board_cfg/soc/x5-soc-debug-ab-gpt.json`和`board_cfg/soc/sub_config/miniboot.json`中的`have_anti_ver`属性，此属性的含义是此分区的镜像包中是否含有antirollback的版本信息，需确保miniboot、uboot、vbmeta分区中的`have_anti_ver`属性为true，其他分区省略该属性或为false。目前`x5-soc-debug-ab-gpt.json`默认已开启此属性，若客户使用自定义分区表，需确保属性已开启。
    ```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
        }
    }
    ```
    ```json
    "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烧录及使用](../security_development/efuse/cus_key_hash_update.html))，会同时开启secure和non-secure antirollback域的防回滚检查；如果是地瓜key签名的BL2芯片，开启secure boot后，只支持non-secure antirollback域的防回滚检查。对于由客户key签名BL2的芯片，需要设置以下变量：

        ```bash
            export HR_ENABLE_CUSTOMER_KEY="yes"
        ```

   - 修改`HR_PART_CONF_FILENAME`变量，由于只有eMMC介质下的AB形式的分区支持镜像防回滚保护，所以选用`x5-soc-debug-ab-gpt.json`配置文件，若客户自定义分区配置文件，需确保为AB分区；

        ```bash
            export HR_PART_CONF_FILENAME=${HR_BOARD_CONF_DIR}/x5-soc-debug-ab-gpt.json
        ```

   - 修改`ANTIROLLBACK_SEC_UPDATE`和`ANTIROLLBACK_NOSEC_UPDATE`的值（true/false），选择是否更新相应eFuse中的antirollback版本；
   - 修改`ANTIROLLBACK_SEC_VER`的值，配置miniboot.img的版本号；
   - 修改`ANTIROLLBACK_NOSEC_VER`的值，同时配置boot.img和vbmeta.img的版本号；
        ```bash
            # 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检查：

    ```bash
        CONFIG_X5_SUPPORT_CHECK_ROLLBACK=y
    ```

##### 版本号获取

1. eFuse中antirollback版本获取方法
   - 在端侧输入ota_tool -v命令，获取当前的antirollback版本(antirollback版本更新后需重启才能生效，否则查询到的还是旧版本号)。

        ```bash
            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 文件来获取。

当前版本号的格式如下：

```SHELL
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`，其内容格式如下：

```bash
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       |

该分区表可[系统分区表](#System_partition_table)章节中的单分区形式分区表进行对比，符合系统分区大小的限制。

由于系统支持最后一个分区的自动扩展，因此其结束地址会动态改变。因此在进行 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](_static/_images/otaservice.png)

## OTA 打包端介绍

### 打包镜像

#### 打包工具使用方法

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

```bash
./bd.sh otapackage help
```

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

```bash
==================================================================================
   \  //   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）：

```BASH
./bd.sh otapackage
```

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

```BASH
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。

    ```bash
    ./bd.sh otapackage
    ```

  - 生成新镜像：旧镜像包妥善保存后编译生成新的镜像。
  - 生成差分镜像包：通过一下命令可生成差分镜像包，同时会生成全量镜像包，可作为下次差分升级的旧镜像包。

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

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

    ```BASH
    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`，包含两个文件：

```bash
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。

如果需要替换为自己的密钥，步骤如下：

- 私钥生成：

  ```bash
  openssl genrsa -out private_key.pem 4096
  ```

- 公钥生成：

  ```bash
  openssl rsa -RSAPublicKey_out -in private_key.pem  -out public_key.pem
  ```

- 替换路径 build/tools/ota_tools/keys 下的 private_key.pem 和 public_key.pem
- 执行下列命令重新编译

    ```bash
    ./bd.sh
    ./bd.sh otapackage

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

### OTA 升级包介绍

#### 升级包结构

```bash
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 文件的示例：

```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 文档](./2-ota-api.html)。

### ota_tool 使用

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

```c
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 参数后面）。

举例：

```BASH
# 全量升级，不验证包完整性
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 实现

```C
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_SUCCESS` 或 `OTA_UPGRADE_FAILED` 。在此过程中，调用  `otaGetProgress` 、 `otaGetResult` 、 `otaGetUpdatingImageName` 获取升级进度、结果和正在升级的镜像，并通过 `OTA_show_Process_Bar` 在控制台打印进度。
5. 若升级结果 `otaGetResult` 为 `OTA_UPGRADE_SUCCESS` ，则认为升级成功，调用 `otaSetPartition` 设置 ab slot 到对向 slot ，并根据配置重启系统。

![otatool-ota_update_all_img](_static/_images/otatool-ota_update_all_img.png)

#### ota_boot_check 实现

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

```C
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](_static/_images/otatool-ota_boot_check.png)
