空中升级 (OTA)

[English]

OTA 流程概览

OTA 升级机制可以让设备在固件正常运行时根据接收数据更新(如通过 Wi-Fi 或蓝牙)。

要运行 OTA 机制,需配置设备的 :doc:` 分区表 <../../api-guides/partition-tables>`,该分区表至少包括两个 OTA 应用程序分区(即 ota_0ota_1)和一个 OTA 数据分区。

OTA 功能启动后,向当前未用于启动的 OTA 应用分区写入新的应用固件镜像。镜像验证后,OTA 数据分区更新,指定在下一次启动时使用该镜像。

OTA 数据分区

所有使用 OTA 功能项目,其 :doc:` 分区表 <../../api-guides/partition-tables>`必须包含一个 OTA 数据分区 (类型为 data,子类型为 ota)。

工厂启动设置下,OTA 数据分区中应没有数据(所有字节擦写成 0xFF)。如果分区表中有工厂应用程序,ESP-IDF 软件启动加载器会启动工厂应用程序。如果分区表中没有工厂应用程序,则启动第一个可用的 OTA 分区(通常是 ota_0)。

第一次 OTA 升级后,OTA 数据分区更新,指定下一次启动哪个 OTA 应用程序分区。

OTA 数据分区是两个 0x2000 字节大小的 flash 扇区,防止写入时电源故障引发问题。两个扇区单独擦除、写入匹配数据,若存在不一致,则用计数器字段判定哪个扇区为最新数据。

应用程序回滚

应用程序回滚的主要目的是确保设备更新后正常运转。该功能可使设备在更新新版本后出现严重错误时,回滚到之前正常运行的应用版本。回滚使能,OTA 升级,应用更新至新版本,之后可能有以下三种情况:

注解:应用程序的状态不是写到程序的二进制镜像,而是写到 otadata 分区。该分区有一个 ota_seq 计数器,该计数器是 OTA 应用分区的指针,指向下次启动时选取应用所在的分区 (ota_0, ota_1, …)。

应用程序 OTA 状态

状态控制了选取启动应用程序的过程:

状态

启动加载器选取启动应用程序的限制

ESP_OTA_IMG_VALID

没有限制,可以选取。

ESP_OTA_IMG_UNDEFINED

没有限制,可以选取。

ESP_OTA_IMG_INVALID

不会选取。

ESP_OTA_IMG_ABORTED

不会选取。

ESP_OTA_IMG_NEW

如使能 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE, 则仅会选取一次。在启动加载器中,状态立即变为 ESP_OTA_IMG_PENDING_VERIFY

ESP_OTA_IMG_PENDING_VERIFY

如使能 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE, 则不会选取,状态变为``ESP_OTA_IMG_ABORTED``。

如果 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 没有使能(默认情况),则 esp_ota_mark_app_valid_cancel_rollback()esp_ota_mark_app_invalid_rollback_and_reboot() 为可选功能,ESP_OTA_IMG_NEWESP_OTA_IMG_PENDING_VERIFY 不会使用。

Kconfig 中的 CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 可以帮助用户追踪新版应用程序的第一次启动。应用程序需调用 esp_ota_mark_app_valid_cancel_rollback() 函数确认可以运行,否则将会在重启时回滚至旧版本。该功能可让用户在启动阶段控制应用程序的可操作性。新版应用程序仅有一次机会尝试是否能成功启动。

回滚过程

CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 使能时,回滚过程如下:

  • 新版应用程序下载成功,esp_ota_set_boot_partition() 函数将分区设为可启动,状态设为 ESP_OTA_IMG_NEW。该状态表示应用程序为新版本,第一次启动需要监测。

  • 重新启动 esp_restart()

  • 启动加载器检查新版应用程序,若状态设置为 ESP_OTA_IMG_PENDING_VERIFY,则写入 ESP_OTA_IMG_ABORTED

  • 启动加载器选取新版应用程序启动,应用程序状态不设置为 ESP_OTA_IMG_INVALIDESP_OTA_IMG_ABORTED

  • 启动加载器检查所选取的新版应用程序,若状态设置为 ESP_OTA_IMG_NEW,则写入 ESP_OTA_IMG_PENDING_VERIFY。该状态表示,需确认应用程序的可操作性,如不确认,发生重启,则状态会重写为 ESP_OTA_IMG_ABORTED (见上文),该应用程序不可再启动,将回滚至上一版本。

  • 新版应用程序启动,应进行自测。

  • 若通过自测,则必须调用函数 esp_ota_mark_app_valid_cancel_rollback(),因为新版应用程序在等待确认其可操作性 (ESP_OTA_IMG_PENDING_VERIFY 状态)。

  • 若未通过自测,则调用函数 esp_ota_mark_app_invalid_rollback_and_reboot(),回滚至之前的版本,同时无效的新版本设置为 ESP_OTA_IMG_INVALID

  • 如果新版应用程序可操作性没有确认,则状态一直为 ESP_OTA_IMG_PENDING_VERIFY。下一次启动时,状态变更为 ESP_OTA_IMG_ABORTED,阻止其再次启动,之后回滚到之前的版本。

意外复位

如果在新版应用第一次启动时发生断电或意外崩溃,则会回滚至之前正常运行的版本。

建议:尽快完成自测,防止因断电回滚。

只有 OTA 分区可以回滚。工厂分区不会回滚。

启动无效/中止的应用程序

用户可以启动此前设置为 ESP_OTA_IMG_INVALIDESP_OTA_IMG_ABORTED 的应用程序:

要确定是否在应用程序启动时进行自测,可以调用 esp_ota_get_state_partition() 函数。如果结果为 ESP_OTA_IMG_PENDING_VERIFY,则需要自测,后续确认应用程序的可操作性。

如何设置状态

下文简单描述了如何设置应用程序状态:

防回滚

防回滚机制可以防止回滚到安全版本号低于芯片 eFuse 中烧录程序的应用程序版本。

设置 CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK,启动防回滚机制。在启动加载器中选取可启动的应用程序,会额外检查芯片和应用程序镜像的安全版本号。可启动固件中的应用安全版本号必须等于或高于芯片中的应用安全版本号。

CONFIG_BOOTLOADER_APP_ANTI_ROLLBACKCONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE 一起使用。此时,只有安全版本号等于或高于芯片中的应用安全版本号时才会回滚。

典型的防回滚机制

  • 新发布的固件解决了此前版本的安全问题。

  • 开发者在确保固件可以运行之后,增加安全版本号,发布固件。

  • 下载新版应用程序。

  • 运行函数 esp_ota_set_boot_partition(),将新版应用程序设为可启动。如果新版应用程序的安全版本号低于芯片中的应用安全版本号,新版应用程序会被擦除,无法更新到新固件。

  • 重新启动。

  • 在启动加载器中选取安全版本号等于或高于芯片中应用安全版本号的应用程序。如果 otadata 处于初始阶段,通过串行通道加载了安全版本号高于芯片中应用安全版本号的固件,则启动加载器中 eFuse 的安全版本号会立即更新。

  • 新版应用程序启动,之后进行可操作性检测,如果通过检测,则调用函数 esp_ota_mark_app_valid_cancel_rollback(),将应用程序标记为 ESP_OTA_IMG_VALID,更新芯片中应用程序的安全版本号。注意,如果调用函数 esp_ota_mark_app_invalid_rollback_and_reboot(),可能会因为设备中没有可启动的应用程序而回滚失败,返回 ESP_ERR_OTA_ROLLBACK_FAILED 错误,应用程序状态一直为 ESP_OTA_IMG_PENDING_VERIFY

  • 如果运行的应用程序处于 ESP_OTA_IMG_VALID 状态,则可再次更新。

建议:

如果想避免因服务器应用程序的安全版本号低于运行的应用程序,造成不必要的下载和擦除,必须从镜像的第一个包中获取 new_app_info.secure_version,和 eFuse 的安全版本号比较。如果 esp_efuse_check_secure_version(new_app_info.secure_version) 函数为真,则下载继续,反之则中断。

....
bool image_header_was_checked = false;
while (1) {
    int data_read = esp_http_client_read(client, ota_write_data, BUFFSIZE);
    ...
    if (data_read > 0) {
        if (image_header_was_checked == false) {
            esp_app_desc_t new_app_info;
            if (data_read > sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
                // check current version with downloading
                if (esp_efuse_check_secure_version(new_app_info.secure_version) == false) {
                    ESP_LOGE(TAG, "This a new app can not be downloaded due to a secure version is lower than stored in efuse.");
                    http_cleanup(client);
                    task_fatal_error();
                }

                image_header_was_checked = true;

                esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle);
            }
        }
        esp_ota_write( update_handle, (const void *)ota_write_data, data_read);
    }
}
...

限制:

  • secure_version 字段最多有 32 位。也就是说,防回滚最多可以做 32 次。用户可以使用 CONFIG_BOOTLOADER_APP_SEC_VER_SIZE_EFUSE_FIELD 减少该 eFuse 字段的长度。

  • 防回滚仅在 eFuse 编码机制设置为 NONE 时生效。

  • 分区表不应有工厂分区,应仅有两个应用程序分区。

security_version:

  • ESP32 中版本号存储在 eFuse 的 EFUSE_BLK3_RDATA4_REG 里(若 eFuse 的位烧写为 1,则永远无法恢复为 0)。寄存器设置了多少位,应用程序的安全版本号就为多少。

没有安全启动的安全 OTA 升级

即便硬件安全启动没有使能,也可验证已签名的 OTA 升级,具体可参考 Signed App Verification Without Hardware Secure Boot

OTA 工具 (otatool.py)

app_update 组件中有 otatool.py 工具,用于在目标设备上完成下列 OTA 分区相关操作:

  • 读取 otadata 分区 (read_otadata)

  • 擦除 otadata 分区,将设备复位至工厂应用程序 (erase_otadata)

  • 切换 OTA 分区 (switch_ota_partition)

  • 擦除 OTA 分区 (erase_ota_partition)

  • 写入 OTA 分区 (write_ota_partition)

  • 读取 OTA 分区 (read_ota_partition)

用户若想通过编程方式完成相关操作,可从另一个 Python 脚本导入并使用分区工具,或者从 Shell 脚本调用分区工具。前者可使用工具的 Python API,后者可使用命令行界面。

Python API

首先,确保已导入 otatool 模块。

import sys
import os

idf_path = os.environ["IDF_PATH"]  # 从环境中获取 IDF_PATH 的值
otatool_dir = os.path.join(idf_path, "components", "app_update")  # otatool.py 位于 $IDF_PATH/components/app_update 下

sys.path.append(otatool_dir)  # 使能 Python 寻找 otatool 模块
from otatool import *  # 导入 otatool 模块内的所有名称

要使用 OTA 工具的 Python API,第一步是创建 OtatoolTarget

# 创建 partool.py 的目标设备,并将目标设备连接到串行端口 /dev/ttyUSB1
target = OtatoolTarget("/dev/ttyUSB1")

现在,可使用创建的 OtatoolTarget 在目标设备上完成操作:

# 擦除 otadata,将设备复位至工厂应用程序
target.erase_otadata()

# 擦除 OTA 应用程序分区 0
target.erase_ota_partition(0)

# 将启动分区切换至 OTA 应用程序分区 1
target.switch_ota_partition(1)

# 读取 OTA 分区 'ota_3',将内容保存至文件 'ota_3.bin'
target.read_ota_partition("ota_3", "ota_3.bin")

要操作的 OTA 分区通过应用程序分区序号或分区名称指定。

更多关于 Python API 的信息,请查看 OTA 工具的代码注释。

命令行界面

otatool.py 的命令行界面具有如下结构:

otatool.py [command-args] [subcommand] [subcommand-args]

- command-args - 执行主命令 (otatool.py) 所需的实际参数,多与目标设备有关
- subcommand - 要执行的操作
- subcommand-args - 所选操作的实际参数
# 擦除 otadata,将设备复位至工厂应用程序
otatool.py --port "/dev/ttyUSB1" erase_otadata

# 擦除 OTA 应用程序分区 0
otatool.py --port "/dev/ttyUSB1" erase_ota_partition --slot 0

# 将启动分区切换至 OTA 应用程序分区 1
otatool.py --port "/dev/ttyUSB1" switch_ota_partition --slot 1

# 读取 OTA 分区 'ota_3',将内容保存至文件 'ota_3.bin'
otatool.py --port "/dev/ttyUSB1" read_ota_partition --name=ota_3

更多信息可用 –help 指令查看:

# 显示可用的子命令和主命令描述
otatool.py --help

# 显示子命令的描述
otatool.py [subcommand] --help

应用程序示例

端对端的 OTA 固件升级示例请参考 system/ota

API 参考

Functions

const esp_app_desc_t *esp_ota_get_app_description(void)

Return esp_app_desc structure. This structure includes app version.

Return description for running app.

Return

Pointer to esp_app_desc structure.

int esp_ota_get_app_elf_sha256(char *dst, size_t size)

Fill the provided buffer with SHA256 of the ELF file, formatted as hexadecimal, null-terminated. If the buffer size is not sufficient to fit the entire SHA256 in hex plus a null terminator, the largest possible number of bytes will be written followed by a null.

Return

Number of bytes written to dst (including null terminator)

Parameters
  • dst: Destination buffer

  • size: Size of the buffer

esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle)

Commence an OTA update writing to the specified partition.

The specified partition is erased to the specified image size.

If image size is not yet known, pass OTA_SIZE_UNKNOWN which will cause the entire partition to be erased.

On success, this function allocates memory that remains in use until esp_ota_end() is called with the returned handle.

Note: If the rollback option is enabled and the running application has the ESP_OTA_IMG_PENDING_VERIFY state then it will lead to the ESP_ERR_OTA_ROLLBACK_INVALID_STATE error. Confirm the running app before to run download a new app, use esp_ota_mark_app_valid_cancel_rollback() function for it (this should be done as early as possible when you first download a new application).

Return

  • ESP_OK: OTA operation commenced successfully.

  • ESP_ERR_INVALID_ARG: partition or out_handle arguments were NULL, or partition doesn’t point to an OTA app partition.

  • ESP_ERR_NO_MEM: Cannot allocate memory for OTA operation.

  • ESP_ERR_OTA_PARTITION_CONFLICT: Partition holds the currently running firmware, cannot update in place.

  • ESP_ERR_NOT_FOUND: Partition argument not found in partition table.

  • ESP_ERR_OTA_SELECT_INFO_INVALID: The OTA data partition contains invalid data.

  • ESP_ERR_INVALID_SIZE: Partition doesn’t fit in configured flash size.

  • ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed.

  • ESP_ERR_OTA_ROLLBACK_INVALID_STATE: If the running app has not confirmed state. Before performing an update, the application must be valid.

Parameters
  • partition: Pointer to info for partition which will receive the OTA update. Required.

  • image_size: Size of new OTA app image. Partition will be erased in order to receive this size of image. If 0 or OTA_SIZE_UNKNOWN, the entire partition is erased.

  • out_handle: On success, returns a handle which should be used for subsequent esp_ota_write() and esp_ota_end() calls.

esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)

Write OTA update data to partition.

This function can be called multiple times as data is received during the OTA operation. Data is written sequentially to the partition.

Return

  • ESP_OK: Data was written to flash successfully.

  • ESP_ERR_INVALID_ARG: handle is invalid.

  • ESP_ERR_OTA_VALIDATE_FAILED: First byte of image contains invalid app image magic byte.

  • ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed.

  • ESP_ERR_OTA_SELECT_INFO_INVALID: OTA data partition has invalid contents

Parameters
  • handle: Handle obtained from esp_ota_begin

  • data: Data buffer to write

  • size: Size of data buffer in bytes.

esp_err_t esp_ota_end(esp_ota_handle_t handle)

Finish OTA update and validate newly written app image.

Note

After calling esp_ota_end(), the handle is no longer valid and any memory associated with it is freed (regardless of result).

Return

  • ESP_OK: Newly written OTA app image is valid.

  • ESP_ERR_NOT_FOUND: OTA handle was not found.

  • ESP_ERR_INVALID_ARG: Handle was never written to.

  • ESP_ERR_OTA_VALIDATE_FAILED: OTA image is invalid (either not a valid app image, or - if secure boot is enabled - signature failed to verify.)

  • ESP_ERR_INVALID_STATE: If flash encryption is enabled, this result indicates an internal error writing the final encrypted bytes to flash.

Parameters
  • handle: Handle obtained from esp_ota_begin().

esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)

Configure OTA data for a new boot partition.

Note

If this function returns ESP_OK, calling esp_restart() will boot the newly configured app partition.

Return

  • ESP_OK: OTA data updated, next reboot will use specified partition.

  • ESP_ERR_INVALID_ARG: partition argument was NULL or didn’t point to a valid OTA partition of type “app”.

  • ESP_ERR_OTA_VALIDATE_FAILED: Partition contained invalid app image. Also returned if secure boot is enabled and signature validation failed.

  • ESP_ERR_NOT_FOUND: OTA data partition not found.

  • ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash erase or write failed.

Parameters
  • partition: Pointer to info for partition containing app image to boot.

const esp_partition_t *esp_ota_get_boot_partition(void)

Get partition info of currently configured boot app.

If esp_ota_set_boot_partition() has been called, the partition which was set by that function will be returned.

If esp_ota_set_boot_partition() has not been called, the result is usually the same as esp_ota_get_running_partition(). The two results are not equal if the configured boot partition does not contain a valid app (meaning that the running partition will be an app that the bootloader chose via fallback).

If the OTA data partition is not present or not valid then the result is the first app partition found in the partition table. In priority order, this means: the factory app, the first OTA app slot, or the test app partition.

Note that there is no guarantee the returned partition is a valid app. Use esp_image_verify(ESP_IMAGE_VERIFY, …) to verify if the returned partition contains a bootable image.

Return

Pointer to info for partition structure, or NULL if partition table is invalid or a flash read operation failed. Any returned pointer is valid for the lifetime of the application.

const esp_partition_t *esp_ota_get_running_partition(void)

Get partition info of currently running app.

This function is different to esp_ota_get_boot_partition() in that it ignores any change of selected boot partition caused by esp_ota_set_boot_partition(). Only the app whose code is currently running will have its partition information returned.

The partition returned by this function may also differ from esp_ota_get_boot_partition() if the configured boot partition is somehow invalid, and the bootloader fell back to a different app partition at boot.

Return

Pointer to info for partition structure, or NULL if no partition is found or flash read operation failed. Returned pointer is valid for the lifetime of the application.

const esp_partition_t *esp_ota_get_next_update_partition(const esp_partition_t *start_from)

Return the next OTA app partition which should be written with a new firmware.

Call this function to find an OTA app partition which can be passed to esp_ota_begin().

Finds next partition round-robin, starting from the current running partition.

Return

Pointer to info for partition which should be updated next. NULL result indicates invalid OTA data partition, or that no eligible OTA app slot partition was found.

Parameters
  • start_from: If set, treat this partition info as describing the current running partition. Can be NULL, in which case esp_ota_get_running_partition() is used to find the currently running partition. The result of this function is never the same as this argument.

esp_err_t esp_ota_get_partition_description(const esp_partition_t *partition, esp_app_desc_t *app_desc)

Returns esp_app_desc structure for app partition. This structure includes app version.

Returns a description for the requested app partition.

Return

  • ESP_OK Successful.

  • ESP_ERR_NOT_FOUND app_desc structure is not found. Magic word is incorrect.

  • ESP_ERR_NOT_SUPPORTED Partition is not application.

  • ESP_ERR_INVALID_ARG Arguments is NULL or if partition’s offset exceeds partition size.

  • ESP_ERR_INVALID_SIZE Read would go out of bounds of the partition.

  • or one of error codes from lower-level flash driver.

Parameters
  • [in] partition: Pointer to app partition. (only app partition)

  • [out] app_desc: Structure of info about app.

esp_err_t esp_ota_mark_app_valid_cancel_rollback(void)

This function is called to indicate that the running app is working well.

Return

  • ESP_OK: if successful.

esp_err_t esp_ota_mark_app_invalid_rollback_and_reboot(void)

This function is called to roll back to the previously workable app with reboot.

If rollback is successful then device will reset else API will return with error code. Checks applications on a flash drive that can be booted in case of rollback. If the flash does not have at least one app (except the running app) then rollback is not possible.

Return

  • ESP_FAIL: if not successful.

  • ESP_ERR_OTA_ROLLBACK_FAILED: The rollback is not possible due to flash does not have any apps.

const esp_partition_t *esp_ota_get_last_invalid_partition(void)

Returns last partition with invalid state (ESP_OTA_IMG_INVALID or ESP_OTA_IMG_ABORTED).

Return

partition.

esp_err_t esp_ota_get_state_partition(const esp_partition_t *partition, esp_ota_img_states_t *ota_state)

Returns state for given partition.

Return

  • ESP_OK: Successful.

  • ESP_ERR_INVALID_ARG: partition or ota_state arguments were NULL.

  • ESP_ERR_NOT_SUPPORTED: partition is not ota.

  • ESP_ERR_NOT_FOUND: Partition table does not have otadata or state was not found for given partition.

Parameters
  • [in] partition: Pointer to partition.

  • [out] ota_state: state of partition (if this partition has a record in otadata).

esp_err_t esp_ota_erase_last_boot_app_partition(void)

Erase previous boot app partition and corresponding otadata select for this partition.

When current app is marked to as valid then you can erase previous app partition.

Return

  • ESP_OK: Successful, otherwise ESP_ERR.

bool esp_ota_check_rollback_is_possible(void)

Checks applications on the slots which can be booted in case of rollback.

These applications should be valid (marked in otadata as not UNDEFINED, INVALID or ABORTED and crc is good) and be able booted, and secure_version of app >= secure_version of efuse (if anti-rollback is enabled).

Return

  • True: Returns true if the slots have at least one app (except the running app).

  • False: The rollback is not possible.

Macros

OTA_SIZE_UNKNOWN

Used for esp_ota_begin() if new image size is unknown

ESP_ERR_OTA_BASE

Base error code for ota_ops api

ESP_ERR_OTA_PARTITION_CONFLICT

Error if request was to write or erase the current running partition

ESP_ERR_OTA_SELECT_INFO_INVALID

Error if OTA data partition contains invalid content

ESP_ERR_OTA_VALIDATE_FAILED

Error if OTA app image is invalid

ESP_ERR_OTA_SMALL_SEC_VER

Error if the firmware has a secure version less than the running firmware.

ESP_ERR_OTA_ROLLBACK_FAILED

Error if flash does not have valid firmware in passive partition and hence rollback is not possible

ESP_ERR_OTA_ROLLBACK_INVALID_STATE

Error if current active firmware is still marked in pending validation state (ESP_OTA_IMG_PENDING_VERIFY), essentially first boot of firmware image post upgrade and hence firmware upgrade is not possible

Type Definitions

typedef uint32_t esp_ota_handle_t

Opaque handle for an application OTA update.

esp_ota_begin() returns a handle which is then used for subsequent calls to esp_ota_write() and esp_ota_end().