feat(app_update): add API for checking the spi mode compatibility

New API to check the SPI flash mode from the incoming firmware image
during OTA updates could prevent bootloader/app incompatibility of
DIO vs QIO flash modes.

More information:
 - https://github.com/espressif/esp-hosted-mcu/issues/143#issuecomment-3741753788
 - https://github.com/espressif/esp-idf/issues/9674#issuecomment-1232533757
 - https://github.com/espressif/esp-idf/issues/9542#issuecomment-1211317354
This commit is contained in:
Mahavir Jain
2026-01-18 19:01:20 +05:30
parent 1837390102
commit 21f8ca5e6f
11 changed files with 131 additions and 26 deletions

View File

@@ -1012,6 +1012,52 @@ bool esp_ota_check_rollback_is_possible(void)
return false;
}
esp_err_t esp_ota_check_image_validity(esp_partition_type_t part_type,
const esp_image_header_t *img_hdr,
const esp_app_desc_t *app_desc)
{
if (img_hdr == NULL) {
return ESP_ERR_INVALID_ARG;
}
// Map partition type to image type for bootloader_common API
esp_image_type img_type;
if (part_type == ESP_PARTITION_TYPE_APP) {
img_type = ESP_IMAGE_APPLICATION;
} else if (part_type == ESP_PARTITION_TYPE_BOOTLOADER) {
img_type = ESP_IMAGE_BOOTLOADER;
} else {
return ESP_ERR_INVALID_ARG;
}
// Check chip ID and chip revision validity
esp_err_t err = bootloader_common_check_chip_validity(img_hdr, img_type);
if (err != ESP_OK) {
return ESP_ERR_INVALID_VERSION;
}
// Check SPI flash mode if app descriptor is provided
if (part_type == ESP_PARTITION_TYPE_APP && app_desc != NULL) {
// Get the running app's descriptor
const esp_app_desc_t *running_app_desc = esp_app_get_description();
if (running_app_desc->spi_flash_mode == 0 || app_desc->spi_flash_mode == 0) {
// Older image format, CONFIG_ESPTOOLPY_FLASHMODE_VAL not stored in the app descriptor
ESP_LOGD(TAG, "Older image format and hence no SPI flash mode info is available");
return ESP_OK;
}
// Compare SPI flash modes as stored in app descriptor (CONFIG_ESPTOOLPY_FLASHMODE_VAL)
if (app_desc->spi_flash_mode != running_app_desc->spi_flash_mode) {
ESP_LOGE(TAG, "SPI flash mode mismatch: running app has mode %d, new app has mode %d",
running_app_desc->spi_flash_mode, app_desc->spi_flash_mode);
return ESP_ERR_OTA_SPI_MODE_MISMATCH;
}
}
return ESP_OK;
}
// if valid == false - will done rollback with reboot. After reboot will boot previous OTA[x] or Factory partition.
// if valid == true - it confirm that current OTA[x] is workable. Reboot will not happen.
static esp_err_t esp_ota_current_ota_is_workable(bool valid)

View File

@@ -13,6 +13,7 @@
#include "esp_err.h"
#include "esp_partition.h"
#include "esp_app_desc.h"
#include "esp_app_format.h"
#include "esp_bootloader_desc.h"
#include "esp_flash_partitions.h"
#include "soc/soc_caps.h"
@@ -32,7 +33,8 @@ extern "C"
#define ESP_ERR_OTA_SMALL_SEC_VER (ESP_ERR_OTA_BASE + 0x04) /*!< Error if the firmware has a secure version less than the running firmware. */
#define ESP_ERR_OTA_ROLLBACK_FAILED (ESP_ERR_OTA_BASE + 0x05) /*!< Error if flash does not have valid firmware in passive partition and hence rollback is not possible */
#define ESP_ERR_OTA_ROLLBACK_INVALID_STATE (ESP_ERR_OTA_BASE + 0x06) /*!< 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 */
#define ESP_ERR_OTA_ALREADY_IN_PROGRESS (ESP_ERR_OTA_BASE + 0x07) /*!< Error if another OTA operation is already in progress on the same partition */
#define ESP_ERR_OTA_ALREADY_IN_PROGRESS (ESP_ERR_OTA_BASE + 0x07) /*!< Error if another OTA operation is already in progress on the same partition */
#define ESP_ERR_OTA_SPI_MODE_MISMATCH (ESP_ERR_OTA_BASE + 0x08) /*!< Error if the firmware's SPI flash mode doesn't match the running firmware */
/**
@@ -420,6 +422,31 @@ esp_err_t esp_ota_erase_last_boot_app_partition(void);
*/
bool esp_ota_check_rollback_is_possible(void);
/**
* @brief Check image validity including chip ID, chip revision, and optionally SPI flash mode.
*
* This function performs comprehensive validation of an OTA image:
* - Verifies the chip ID matches the current chip
* - Verifies the chip revision meets the image requirements
* - Optionally verifies the SPI flash mode matches (if app_desc is provided)
*
* For bootloader partitions (ESP_PARTITION_TYPE_BOOTLOADER), the maximum chip revision check is skipped.
* For application partitions (ESP_PARTITION_TYPE_APP), both minimum and maximum chip revision are checked.
*
* @param[in] part_type Partition type (ESP_PARTITION_TYPE_APP or ESP_PARTITION_TYPE_BOOTLOADER)
* @param[in] img_hdr Pointer to the image header for chip ID and revision checks
* @param[in] app_desc Pointer to the app descriptor for SPI mode check (can be NULL to skip SPI mode verification)
*
* @return
* - ESP_OK: Image is valid for this chip
* - ESP_ERR_INVALID_ARG: img_hdr is NULL or part_type is not APP or BOOTLOADER
* - ESP_ERR_INVALID_VERSION: Chip ID or chip revision mismatch
* - ESP_ERR_OTA_SPI_MODE_MISMATCH: SPI flash mode does not match (only when app_desc is provided)
*/
esp_err_t esp_ota_check_image_validity(esp_partition_type_t part_type,
const esp_image_header_t *img_hdr,
const esp_app_desc_t *app_desc);
#if SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS > 1 && (CONFIG_SECURE_BOOT_V2_ENABLED || __DOXYGEN__)
/**

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/

View File

@@ -60,6 +60,9 @@ const __attribute__((weak)) __attribute__((section(".rodata_desc"))) esp_app_de
.min_efuse_blk_rev_full = CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL,
.max_efuse_blk_rev_full = CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL,
.mmu_page_size = 31 - __builtin_clz(CONFIG_MMU_PAGE_SIZE),
#if !(CONFIG_IDF_TARGET_LINUX || CONFIG_APP_BUILD_TYPE_PURE_RAM_APP)
.spi_flash_mode = CONFIG_ESPTOOLPY_FLASHMODE_VAL,
#endif
};
#ifndef CONFIG_APP_EXCLUDE_PROJECT_VER_VAR

View File

@@ -36,7 +36,8 @@ typedef struct {
uint16_t min_efuse_blk_rev_full; /*!< Minimal eFuse block revision supported by image, in format: major * 100 + minor */
uint16_t max_efuse_blk_rev_full; /*!< Maximal eFuse block revision supported by image, in format: major * 100 + minor */
uint8_t mmu_page_size; /*!< MMU page size in log base 2 format */
uint8_t reserv3[3]; /*!< reserv3 */
uint8_t spi_flash_mode; /*!< SPI flash mode as per CONFIG_ESPTOOLPY_FLASHMODE_VAL for compatibility check during OTA */
uint8_t reserv3[2]; /*!< reserv3 */
uint32_t reserv2[18]; /*!< reserv2 */
} esp_app_desc_t;

View File

@@ -294,6 +294,10 @@ static const esp_err_msg_t esp_err_msg_table[] = {
ERR_TBL_IT(ESP_ERR_OTA_ALREADY_IN_PROGRESS), /* 5383 0x1507 Error if another OTA operation is
already in progress on the same
partition */
# endif
# ifdef ESP_ERR_OTA_SPI_MODE_MISMATCH
ERR_TBL_IT(ESP_ERR_OTA_SPI_MODE_MISMATCH), /* 5384 0x1508 Error if the firmware's SPI flash mode
doesn't match the running firmware */
# endif
// components/efuse/include/esp_efuse.h
# ifdef ESP_ERR_EFUSE

View File

@@ -33,4 +33,13 @@ menu "ESP HTTPS OTA"
This enables use of range header in esp_https_ota component.
The firmware image will be downloaded over multiple HTTP requests.
config ESP_HTTPS_OTA_VERIFY_SPI_MODE
bool "Verify SPI flash mode compatibility for application during OTA"
default y
help
When enabled, the OTA process will verify that the SPI flash mode of the new
application image is compatible with the currently running firmware. This helps
prevent flashing firmware with incompatible SPI mode settings which could
cause flash write failures.
endmenu

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2017-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -681,27 +681,24 @@ esp_err_t esp_https_ota_get_bootloader_img_desc(esp_https_ota_handle_t https_ota
return get_description_from_image(https_ota_handle, new_img_info);
}
static esp_err_t esp_ota_verify_chip_id(const void *arg)
static const esp_app_desc_t *esp_https_ota_get_app_desc(const void *data_buf)
{
esp_image_header_t *data = (esp_image_header_t *)(arg);
esp_https_ota_dispatch_event(ESP_HTTPS_OTA_VERIFY_CHIP_ID, (void *)(&data->chip_id), sizeof(esp_chip_id_t));
if (data->chip_id != CONFIG_IDF_FIRMWARE_CHIP_ID) {
ESP_LOGE(TAG, "Mismatch chip id, expected %d, found %d", CONFIG_IDF_FIRMWARE_CHIP_ID, data->chip_id);
return ESP_ERR_INVALID_VERSION;
}
return ESP_OK;
return (const esp_app_desc_t *)((const uint8_t *)data_buf +
sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t));
}
static esp_err_t esp_ota_verify_chip_revision(const void *arg)
static esp_err_t esp_https_ota_verify_image(const void *data_buf, esp_partition_type_t part_type, bool verify_spi_mode)
{
esp_image_header_t *data = (esp_image_header_t *)(arg);
esp_https_ota_dispatch_event(ESP_HTTPS_OTA_VERIFY_CHIP_REVISION, (void *)(&data->min_chip_rev_full), sizeof(uint16_t));
const esp_image_header_t *img_hdr = (const esp_image_header_t *) data_buf;
if (!bootloader_common_check_chip_revision_validity(data, true)) {
return ESP_ERR_INVALID_VERSION;
}
return ESP_OK;
// Dispatch verification events
esp_https_ota_dispatch_event(ESP_HTTPS_OTA_VERIFY_CHIP_ID, (void *)(&img_hdr->chip_id), sizeof(esp_chip_id_t));
esp_https_ota_dispatch_event(ESP_HTTPS_OTA_VERIFY_CHIP_REVISION, (void *)(&img_hdr->min_chip_rev_full), sizeof(uint16_t));
// Get app descriptor only if SPI mode verification is needed
const esp_app_desc_t *app_desc = verify_spi_mode ? esp_https_ota_get_app_desc(data_buf) : NULL;
return esp_ota_check_image_validity(part_type, img_hdr, app_desc);
}
esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle)
@@ -761,12 +758,11 @@ esp_err_t esp_https_ota_perform(esp_https_ota_handle_t https_ota_handle)
}
#endif // CONFIG_ESP_HTTPS_OTA_DECRYPT_CB
if (handle->partition.final->type == ESP_PARTITION_TYPE_APP || handle->partition.final->type == ESP_PARTITION_TYPE_BOOTLOADER) {
err = esp_ota_verify_chip_id(data_buf);
if (err != ESP_OK) {
return err;
}
err = esp_ota_verify_chip_revision(data_buf);
bool verify_spi_mode = false;
#if CONFIG_ESP_HTTPS_OTA_VERIFY_SPI_MODE
verify_spi_mode = (handle->partition.final->type == ESP_PARTITION_TYPE_APP);
#endif
err = esp_https_ota_verify_image(data_buf, handle->partition.final->type, verify_spi_mode);
if (err != ESP_OK) {
return err;
}

View File

@@ -88,6 +88,17 @@ menu "Serial flasher config"
# information get from efuse, so don't care this dout choice.
default "dout" if ESPTOOLPY_FLASHMODE_OPI
# Hidden config providing the numeric value of flash mode
# This value will be stored in the esp_app_desc_t structure in the binary
# Note: legacy images will have this field set to 0 and hence using 1 to start the count
config ESPTOOLPY_FLASHMODE_VAL
int
default 1 if ESPTOOLPY_FLASHMODE_QIO
default 2 if ESPTOOLPY_FLASHMODE_QOUT
default 3 if ESPTOOLPY_FLASHMODE_DIO
default 4 if ESPTOOLPY_FLASHMODE_DOUT
default 5 if ESPTOOLPY_FLASHMODE_OPI
orsource "../spi_flash/$IDF_TARGET/Kconfig.flash_freq"
config ESPTOOLPY_FLASHFREQ

View File

@@ -125,6 +125,10 @@ The ``DROM`` segment of the application binary starts with the :cpp:type:`esp_ap
* ``time`` and ``date``: compile time and date
* ``idf_ver``: version of ESP-IDF [#f1]_
* ``app_elf_sha256``: contains SHA256 hash for the application ELF file
* ``min_efuse_blk_rev_full``: minimal eFuse block revision supported by the image, in format: major * 100 + minor
* ``max_efuse_blk_rev_full``: maximal eFuse block revision supported by the image, in format: major * 100 + minor
* ``mmu_page_size``: MMU page size in log base 2 format
* ``spi_flash_mode``: SPI flash mode as per ``CONFIG_ESPTOOLPY_FLASHMODE_VAL`` for compatibility check during OTA
.. [#f1] The maximum length is 32 characters, including null-termination character. For example, if the length of ``PROJECT_NAME`` exceeds 31 characters, the excess characters will be disregarded.

View File

@@ -125,6 +125,10 @@
* ``time````date``:编译时间和日期
* ``idf_ver``ESP-IDF 的版本 [#f1]_
* ``app_elf_sha256``:包含应用程序 ELF 文件的 sha256 哈希
* ``min_efuse_blk_rev_full``:镜像支持的最小 eFuse 块版本格式为major * 100 + minor
* ``max_efuse_blk_rev_full``:镜像支持的最大 eFuse 块版本格式为major * 100 + minor
* ``mmu_page_size``MMU 页大小,以 log2 格式表示
* ``spi_flash_mode``SPI flash 模式,取自 ``CONFIG_ESPTOOLPY_FLASHMODE_VAL``,用于 OTA 过程中的兼容性检查
.. [#f1] 最大长度为 32 个字符,其中包括 null 终止符。也就是说,如果 ``PROJECT_NAME`` 的长度超过 31 个字符,超出的字符将被忽略。