mirror of
https://github.com/espressif/esp-idf.git
synced 2025-07-30 18:57:19 +02:00
Merge branch 'feature/bootloader_ota' into 'master'
feat(bootloader): Supports recovery bootloader Closes IDF-7780 and IDF-7779 See merge request espressif/esp-idf!31893
This commit is contained in:
@ -7,6 +7,7 @@ components/app_update/test_apps:
|
|||||||
- if: CONFIG_NAME == "xip_psram" and SOC_SPIRAM_XIP_SUPPORTED == 1
|
- if: CONFIG_NAME == "xip_psram" and SOC_SPIRAM_XIP_SUPPORTED == 1
|
||||||
# S2 doesn't have ROM for flash
|
# S2 doesn't have ROM for flash
|
||||||
- if: CONFIG_NAME == "xip_psram_with_rom_impl" and (SOC_SPIRAM_XIP_SUPPORTED == 1 and IDF_TARGET != "esp32s2")
|
- if: CONFIG_NAME == "xip_psram_with_rom_impl" and (SOC_SPIRAM_XIP_SUPPORTED == 1 and IDF_TARGET != "esp32s2")
|
||||||
|
- if: CONFIG_NAME == "recovery_bootloader" and SOC_RECOVERY_BOOTLOADER_SUPPORTED == 1
|
||||||
disable:
|
disable:
|
||||||
- if: IDF_TARGET in ["esp32h21", "esp32h4"]
|
- if: IDF_TARGET in ["esp32h21", "esp32h4"]
|
||||||
temporary: true
|
temporary: true
|
||||||
@ -14,3 +15,7 @@ components/app_update/test_apps:
|
|||||||
- if: IDF_TARGET == "esp32c61" and CONFIG_NAME == "xip_psram_with_rom_impl"
|
- if: IDF_TARGET == "esp32c61" and CONFIG_NAME == "xip_psram_with_rom_impl"
|
||||||
temporary: true
|
temporary: true
|
||||||
reason: not supported yet # TODO: [ESP32C61] IDF-12784
|
reason: not supported yet # TODO: [ESP32C61] IDF-12784
|
||||||
|
disable_test:
|
||||||
|
- if: CONFIG_NAME == "recovery_bootloader" and SOC_RECOVERY_BOOTLOADER_SUPPORTED == 1 and IDF_TARGET == "esp32c61"
|
||||||
|
temporary: true
|
||||||
|
reason: lack of runners # TODO: [ESP32C61] IDF-13165
|
||||||
|
@ -1,4 +1,15 @@
|
|||||||
idf_component_register(SRC_DIRS "."
|
idf_component_register(
|
||||||
PRIV_INCLUDE_DIRS "."
|
SRC_DIRS "."
|
||||||
PRIV_REQUIRES cmock test_utils app_update bootloader_support nvs_flash driver spi_flash esp_psram
|
PRIV_INCLUDE_DIRS "."
|
||||||
WHOLE_ARCHIVE)
|
PRIV_REQUIRES
|
||||||
|
cmock
|
||||||
|
test_utils
|
||||||
|
app_update
|
||||||
|
bootloader_support
|
||||||
|
nvs_flash
|
||||||
|
driver
|
||||||
|
spi_flash
|
||||||
|
esp_psram
|
||||||
|
efuse
|
||||||
|
WHOLE_ARCHIVE
|
||||||
|
)
|
||||||
|
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Tests bootloader update.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "unity.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "esp_efuse.h"
|
||||||
|
#include "esp_flash_internal.h"
|
||||||
|
#include "esp_rom_sys.h"
|
||||||
|
#include "utils_update.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#define BOOT_COUNT_NAMESPACE "boot_count"
|
||||||
|
|
||||||
|
static __attribute__((unused)) const char *TAG = "btldr_update";
|
||||||
|
|
||||||
|
#if CONFIG_BOOTLOADER_RECOVERY_ENABLE
|
||||||
|
|
||||||
|
/* @brief Checks and prepares the partition so that the factory app is launched after that.
|
||||||
|
*/
|
||||||
|
static void start_test(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "boot count 1 - reset");
|
||||||
|
set_boot_count_in_nvs(1);
|
||||||
|
erase_ota_data();
|
||||||
|
ESP_LOGI(TAG, "ota_data erased");
|
||||||
|
ESP_LOGI(TAG, "Bootloader offset: 0x%x", esp_rom_get_bootloader_offset());
|
||||||
|
reboot_as_deep_sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_flow1(void)
|
||||||
|
{
|
||||||
|
uint8_t boot_count = get_boot_count_from_nvs();
|
||||||
|
boot_count++;
|
||||||
|
set_boot_count_in_nvs(boot_count);
|
||||||
|
ESP_LOGI(TAG, "boot count %d", boot_count);
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Bootloader offset: 0x%x", esp_rom_get_bootloader_offset());
|
||||||
|
|
||||||
|
const esp_partition_t *primary_bootloader;
|
||||||
|
TEST_ESP_OK(esp_partition_register_external(NULL, ESP_PRIMARY_BOOTLOADER_OFFSET, ESP_BOOTLOADER_SIZE, "PrimaryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_PRIMARY, &primary_bootloader));
|
||||||
|
const esp_partition_t *recovery_bootloader;
|
||||||
|
TEST_ESP_OK(esp_partition_register_external(NULL, CONFIG_BOOTLOADER_RECOVERY_OFFSET, ESP_BOOTLOADER_SIZE, "RecoveryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_RECOVERY, &recovery_bootloader));
|
||||||
|
ESP_LOGI(TAG, "Bootloaders are registered");
|
||||||
|
|
||||||
|
// Remove write protection for the bootloader
|
||||||
|
esp_flash_set_dangerous_write_protection(esp_flash_default_chip, false);
|
||||||
|
switch (boot_count) {
|
||||||
|
case 2:
|
||||||
|
TEST_ASSERT_EQUAL_HEX32(ESP_PRIMARY_BOOTLOADER_OFFSET, esp_rom_get_bootloader_offset());
|
||||||
|
|
||||||
|
TEST_ESP_OK(esp_partition_erase_range(recovery_bootloader, 0, recovery_bootloader->size));
|
||||||
|
ESP_LOGI(TAG, "Erase recovery bootloader");
|
||||||
|
|
||||||
|
TEST_ESP_OK(esp_efuse_set_recovery_bootloader_offset(CONFIG_BOOTLOADER_RECOVERY_OFFSET));
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Backup, copy <%s> -> <%s>", primary_bootloader->label, recovery_bootloader->label);
|
||||||
|
TEST_ESP_OK(esp_partition_copy(recovery_bootloader, 0, primary_bootloader, 0, primary_bootloader->size));
|
||||||
|
|
||||||
|
TEST_ESP_OK(esp_partition_erase_range(primary_bootloader, 0, primary_bootloader->size));
|
||||||
|
ESP_LOGI(TAG, "Erase primary bootloader");
|
||||||
|
reboot_as_deep_sleep();
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
TEST_ASSERT_EQUAL_HEX32(CONFIG_BOOTLOADER_RECOVERY_OFFSET, esp_rom_get_bootloader_offset());
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Return to primary bootloader...");
|
||||||
|
ESP_LOGI(TAG, "Copy <%s> -> <%s>", recovery_bootloader->label, primary_bootloader->label);
|
||||||
|
TEST_ESP_OK(esp_partition_copy(primary_bootloader, 0, recovery_bootloader, 0, primary_bootloader->size));
|
||||||
|
|
||||||
|
TEST_ESP_OK(esp_partition_erase_range(recovery_bootloader, 0, recovery_bootloader->size));
|
||||||
|
ESP_LOGI(TAG, "Erase recovery bootloader");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
TEST_FAIL_MESSAGE("Unexpected stage");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE_MULTIPLE_STAGES("Recovery bootloader feature", "[recovery_bootloader][timeout=90][reset=DEEPSLEEP_RESET, DEEPSLEEP_RESET]", start_test, test_flow1, test_flow1);
|
||||||
|
|
||||||
|
#endif // CONFIG_BOOTLOADER_RECOVERY_ENABLE
|
@ -7,312 +7,16 @@
|
|||||||
* Tests for switching between partitions: factory, OTAx, test.
|
* Tests for switching between partitions: factory, OTAx, test.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <esp_types.h>
|
#include "esp_system.h"
|
||||||
#include <stdio.h>
|
|
||||||
#include "string.h"
|
|
||||||
#include <inttypes.h>
|
|
||||||
#include "sdkconfig.h"
|
|
||||||
|
|
||||||
#include "esp_rom_spiflash.h"
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/task.h"
|
|
||||||
#include "freertos/semphr.h"
|
|
||||||
#include "freertos/queue.h"
|
|
||||||
#include "unity.h"
|
|
||||||
|
|
||||||
#include "bootloader_common.h"
|
#include "bootloader_common.h"
|
||||||
#include "../bootloader_flash/include/bootloader_flash_priv.h"
|
#include "../bootloader_flash/include/bootloader_flash_priv.h"
|
||||||
|
|
||||||
#include "esp_err.h"
|
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_ota_ops.h"
|
#include "unity.h"
|
||||||
#include "esp_partition.h"
|
#include "utils_update.h"
|
||||||
#include "esp_flash_partitions.h"
|
#include "sdkconfig.h"
|
||||||
#include "esp_image_format.h"
|
|
||||||
#include "nvs_flash.h"
|
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "esp_sleep.h"
|
|
||||||
#include "test_utils.h"
|
|
||||||
|
|
||||||
#define BOOT_COUNT_NAMESPACE "boot_count"
|
|
||||||
|
|
||||||
static const char *TAG = "ota_test";
|
static const char *TAG = "ota_test";
|
||||||
|
|
||||||
static void set_boot_count_in_nvs(uint8_t boot_count)
|
|
||||||
{
|
|
||||||
nvs_handle_t boot_count_handle;
|
|
||||||
esp_err_t err = nvs_open(BOOT_COUNT_NAMESPACE, NVS_READWRITE, &boot_count_handle);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
TEST_ESP_OK(nvs_flash_erase());
|
|
||||||
TEST_ESP_OK(nvs_flash_init());
|
|
||||||
TEST_ESP_OK(nvs_open(BOOT_COUNT_NAMESPACE, NVS_READWRITE, &boot_count_handle));
|
|
||||||
}
|
|
||||||
TEST_ESP_OK(nvs_set_u8(boot_count_handle, "boot_count", boot_count));
|
|
||||||
TEST_ESP_OK(nvs_commit(boot_count_handle));
|
|
||||||
nvs_close(boot_count_handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint8_t get_boot_count_from_nvs(void)
|
|
||||||
{
|
|
||||||
nvs_handle_t boot_count_handle;
|
|
||||||
esp_err_t err = nvs_open(BOOT_COUNT_NAMESPACE, NVS_READONLY, &boot_count_handle);
|
|
||||||
if (err == ESP_ERR_NVS_NOT_FOUND) {
|
|
||||||
set_boot_count_in_nvs(0);
|
|
||||||
}
|
|
||||||
uint8_t boot_count;
|
|
||||||
TEST_ESP_OK(nvs_get_u8(boot_count_handle, "boot_count", &boot_count));
|
|
||||||
nvs_close(boot_count_handle);
|
|
||||||
return boot_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Copies a current app to next partition using handle.
|
|
||||||
*
|
|
||||||
* @param[in] update_handle - Handle of API ota.
|
|
||||||
* @param[in] cur_app - Current app.
|
|
||||||
*/
|
|
||||||
static void copy_app_partition(esp_ota_handle_t update_handle, const esp_partition_t *curr_app)
|
|
||||||
{
|
|
||||||
const void *partition_bin = NULL;
|
|
||||||
esp_partition_mmap_handle_t data_map;
|
|
||||||
ESP_LOGI(TAG, "start the copy process");
|
|
||||||
TEST_ESP_OK(esp_partition_mmap(curr_app, 0, curr_app->size, ESP_PARTITION_MMAP_DATA, &partition_bin, &data_map));
|
|
||||||
TEST_ESP_OK(esp_ota_write(update_handle, (const void *)partition_bin, curr_app->size));
|
|
||||||
esp_partition_munmap(data_map);
|
|
||||||
ESP_LOGI(TAG, "finish the copy process");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Copies a current app to next partition using handle.
|
|
||||||
*
|
|
||||||
* @param[in] update_handle - Handle of API ota.
|
|
||||||
* @param[in] cur_app - Current app.
|
|
||||||
*/
|
|
||||||
static void copy_app_partition_with_offset(esp_ota_handle_t update_handle, const esp_partition_t *curr_app)
|
|
||||||
{
|
|
||||||
const void *partition_bin = NULL;
|
|
||||||
esp_partition_mmap_handle_t data_map;
|
|
||||||
ESP_LOGI(TAG, "start the copy process");
|
|
||||||
uint32_t offset = 0, bytes_to_write = curr_app->size;
|
|
||||||
uint32_t write_bytes;
|
|
||||||
while (bytes_to_write > 0) {
|
|
||||||
write_bytes = (bytes_to_write > (4 * 1024)) ? (4 * 1024) : bytes_to_write;
|
|
||||||
TEST_ESP_OK(esp_partition_mmap(curr_app, offset, write_bytes, ESP_PARTITION_MMAP_DATA, &partition_bin, &data_map));
|
|
||||||
TEST_ESP_OK(esp_ota_write_with_offset(update_handle, (const void *)partition_bin, write_bytes, offset));
|
|
||||||
esp_partition_munmap(data_map);
|
|
||||||
bytes_to_write -= write_bytes;
|
|
||||||
offset += write_bytes;
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "finish the copy process");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Get the next partition of OTA for the update.
|
|
||||||
*
|
|
||||||
* @return The next partition of OTA(OTA0-15).
|
|
||||||
*/
|
|
||||||
static const esp_partition_t * get_next_update_partition(void)
|
|
||||||
{
|
|
||||||
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, update_partition);
|
|
||||||
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32, update_partition->subtype, update_partition->address);
|
|
||||||
return update_partition;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Copies a current app to next partition (OTA0-15) and then configure OTA data for a new boot partition.
|
|
||||||
*
|
|
||||||
* @param[in] cur_app_partition - Current app.
|
|
||||||
* @param[in] next_app_partition - Next app for boot.
|
|
||||||
*/
|
|
||||||
static void copy_current_app_to_next_part(const esp_partition_t *cur_app_partition, const esp_partition_t *next_app_partition)
|
|
||||||
{
|
|
||||||
esp_ota_get_next_update_partition(NULL);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, next_app_partition);
|
|
||||||
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32, next_app_partition->subtype, next_app_partition->address);
|
|
||||||
|
|
||||||
esp_ota_handle_t update_handle = 0;
|
|
||||||
TEST_ESP_OK(esp_ota_begin(next_app_partition, OTA_SIZE_UNKNOWN, &update_handle));
|
|
||||||
|
|
||||||
copy_app_partition(update_handle, cur_app_partition);
|
|
||||||
|
|
||||||
TEST_ESP_OK(esp_ota_end(update_handle));
|
|
||||||
TEST_ESP_OK(esp_ota_set_boot_partition(next_app_partition));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Copies a current app to next partition (OTA0-15) and then configure OTA data for a new boot partition.
|
|
||||||
*
|
|
||||||
* @param[in] cur_app_partition - Current app.
|
|
||||||
* @param[in] next_app_partition - Next app for boot.
|
|
||||||
*/
|
|
||||||
static void copy_current_app_to_next_part_with_offset(const esp_partition_t *cur_app_partition, const esp_partition_t *next_app_partition)
|
|
||||||
{
|
|
||||||
esp_ota_get_next_update_partition(NULL);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, next_app_partition);
|
|
||||||
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32, next_app_partition->subtype, next_app_partition->address);
|
|
||||||
|
|
||||||
esp_ota_handle_t update_handle = 0;
|
|
||||||
TEST_ESP_OK(esp_ota_begin(next_app_partition, OTA_SIZE_UNKNOWN, &update_handle));
|
|
||||||
|
|
||||||
copy_app_partition_with_offset(update_handle, cur_app_partition);
|
|
||||||
|
|
||||||
TEST_ESP_OK(esp_ota_end(update_handle));
|
|
||||||
TEST_ESP_OK(esp_ota_set_boot_partition(next_app_partition));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Erase otadata partition
|
|
||||||
*/
|
|
||||||
static void erase_ota_data(void)
|
|
||||||
{
|
|
||||||
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, data_partition);
|
|
||||||
TEST_ESP_OK(esp_partition_erase_range(data_partition, 0, 2 * data_partition->erase_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Reboots ESP using mode deep sleep. This mode guaranty that RTC_DATA_ATTR variables is not reset.
|
|
||||||
*/
|
|
||||||
static void reboot_as_deep_sleep(void)
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG, "reboot as deep sleep");
|
|
||||||
esp_deep_sleep(20000);
|
|
||||||
TEST_FAIL_MESSAGE("Should never be reachable except when sleep is rejected, abort");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Copies a current app to next partition (OTA0-15), after that ESP is rebooting and run this (the next) OTAx.
|
|
||||||
*/
|
|
||||||
static void copy_current_app_to_next_part_and_reboot(void)
|
|
||||||
{
|
|
||||||
const esp_partition_t *cur_app = esp_ota_get_running_partition();
|
|
||||||
ESP_LOGI(TAG, "copy current app to next part");
|
|
||||||
copy_current_app_to_next_part(cur_app, get_next_update_partition());
|
|
||||||
reboot_as_deep_sleep();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Copies a current app to next partition (OTA0-15) using esp_ota_write_with_offest(), after that ESP is rebooting and run this (the next) OTAx.
|
|
||||||
*/
|
|
||||||
static void copy_current_app_to_next_part_with_offset_and_reboot(void)
|
|
||||||
{
|
|
||||||
const esp_partition_t *cur_app = esp_ota_get_running_partition();
|
|
||||||
ESP_LOGI(TAG, "copy current app to next part");
|
|
||||||
copy_current_app_to_next_part_with_offset(cur_app, get_next_update_partition());
|
|
||||||
reboot_as_deep_sleep();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Get running app.
|
|
||||||
*
|
|
||||||
* @return The next partition of OTA(OTA0-15).
|
|
||||||
*/
|
|
||||||
static const esp_partition_t* get_running_firmware(void)
|
|
||||||
{
|
|
||||||
const esp_partition_t *configured = esp_ota_get_boot_partition();
|
|
||||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
|
||||||
// If a reboot hasn't occurred after app_update(), the configured and running partitions may differ
|
|
||||||
ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08"PRIx32")",
|
|
||||||
running->type, running->subtype, running->address);
|
|
||||||
ESP_LOGI(TAG, "Configured partition type %d subtype %d (offset 0x%08"PRIx32")",
|
|
||||||
configured->type, configured->subtype, configured->address);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, configured);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, running);
|
|
||||||
return running;
|
|
||||||
}
|
|
||||||
|
|
||||||
// type of a corrupt ota_data
|
|
||||||
typedef enum {
|
|
||||||
CORR_CRC_1_SECTOR_OTA_DATA = (1 << 0), /*!< Corrupt CRC only 1 sector of ota_data */
|
|
||||||
CORR_CRC_2_SECTOR_OTA_DATA = (1 << 1), /*!< Corrupt CRC only 2 sector of ota_data */
|
|
||||||
} corrupt_ota_data_t;
|
|
||||||
|
|
||||||
/* @brief Get two copies ota_data from otadata partition.
|
|
||||||
*
|
|
||||||
* @param[in] otadata_partition - otadata partition.
|
|
||||||
* @param[out] ota_data_0 - First copy from otadata_partition.
|
|
||||||
* @param[out] ota_data_1 - Second copy from otadata_partition.
|
|
||||||
*/
|
|
||||||
static void get_ota_data(const esp_partition_t *otadata_partition, esp_ota_select_entry_t *ota_data_0, esp_ota_select_entry_t *ota_data_1)
|
|
||||||
{
|
|
||||||
uint32_t offset = otadata_partition->address;
|
|
||||||
uint32_t size = otadata_partition->size;
|
|
||||||
if (offset != 0) {
|
|
||||||
const esp_ota_select_entry_t *ota_select_map;
|
|
||||||
ota_select_map = bootloader_mmap(offset, size);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, ota_select_map);
|
|
||||||
|
|
||||||
memcpy(ota_data_0, ota_select_map, sizeof(esp_ota_select_entry_t));
|
|
||||||
memcpy(ota_data_1, (uint8_t *)ota_select_map + otadata_partition->erase_size, sizeof(esp_ota_select_entry_t));
|
|
||||||
bootloader_munmap(ota_select_map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Writes a ota_data into required sector of otadata_partition.
|
|
||||||
*
|
|
||||||
* @param[in] otadata_partition - Partition information otadata.
|
|
||||||
* @param[in] ota_data - otadata structure.
|
|
||||||
* @param[in] sec_id - Sector number 0 or 1.
|
|
||||||
*/
|
|
||||||
static void write_ota_data(const esp_partition_t *otadata_partition, esp_ota_select_entry_t *ota_data, int sec_id)
|
|
||||||
{
|
|
||||||
esp_partition_write(otadata_partition, otadata_partition->erase_size * sec_id, &ota_data[sec_id], sizeof(esp_ota_select_entry_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Makes a corrupt of ota_data.
|
|
||||||
* @param[in] err - type error
|
|
||||||
*/
|
|
||||||
static void corrupt_ota_data(corrupt_ota_data_t err)
|
|
||||||
{
|
|
||||||
esp_ota_select_entry_t ota_data[2];
|
|
||||||
|
|
||||||
const esp_partition_t *otadata_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
|
|
||||||
TEST_ASSERT_NOT_EQUAL(NULL, otadata_partition);
|
|
||||||
get_ota_data(otadata_partition, &ota_data[0], &ota_data[1]);
|
|
||||||
|
|
||||||
if (err & CORR_CRC_1_SECTOR_OTA_DATA) {
|
|
||||||
ota_data[0].crc = 0;
|
|
||||||
}
|
|
||||||
if (err & CORR_CRC_2_SECTOR_OTA_DATA) {
|
|
||||||
ota_data[1].crc = 0;
|
|
||||||
}
|
|
||||||
TEST_ESP_OK(esp_partition_erase_range(otadata_partition, 0, otadata_partition->size));
|
|
||||||
write_ota_data(otadata_partition, &ota_data[0], 0);
|
|
||||||
write_ota_data(otadata_partition, &ota_data[1], 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(CONFIG_BOOTLOADER_FACTORY_RESET) || defined(CONFIG_BOOTLOADER_APP_TEST)
|
|
||||||
/* @brief Sets the pin number to output and sets output level as low. After reboot (deep sleep) this pin keep the same level.
|
|
||||||
*
|
|
||||||
* The output level of the pad will be force locked and can not be changed.
|
|
||||||
* Power down or call gpio_hold_dis will disable this function.
|
|
||||||
*
|
|
||||||
* @param[in] num_pin - Pin number
|
|
||||||
*/
|
|
||||||
static void set_output_pin(uint32_t num_pin)
|
|
||||||
{
|
|
||||||
TEST_ESP_OK(gpio_hold_dis(num_pin));
|
|
||||||
|
|
||||||
gpio_config_t io_conf;
|
|
||||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
|
||||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
|
||||||
io_conf.pin_bit_mask = (1ULL << num_pin);
|
|
||||||
io_conf.pull_down_en = 0;
|
|
||||||
io_conf.pull_up_en = 0;
|
|
||||||
TEST_ESP_OK(gpio_config(&io_conf));
|
|
||||||
|
|
||||||
TEST_ESP_OK(gpio_set_level(num_pin, 0));
|
|
||||||
TEST_ESP_OK(gpio_hold_en(num_pin));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Unset the pin number hold function.
|
|
||||||
*/
|
|
||||||
static void reset_output_pin(uint32_t num_pin)
|
|
||||||
{
|
|
||||||
TEST_ESP_OK(gpio_hold_dis(num_pin));
|
|
||||||
TEST_ESP_OK(gpio_reset_pin(num_pin));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void mark_app_valid(void)
|
|
||||||
{
|
|
||||||
#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
|
|
||||||
TEST_ESP_OK(esp_ota_mark_app_valid_cancel_rollback());
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/* @brief Checks and prepares the partition so that the factory app is launched after that.
|
/* @brief Checks and prepares the partition so that the factory app is launched after that.
|
||||||
*/
|
*/
|
||||||
@ -544,20 +248,6 @@ static void test_flow5(void)
|
|||||||
TEST_CASE_MULTIPLE_STAGES("Switching between factory, test, factory", "[app_update][timeout=90][reset=SW_CPU_RESET, SW_CPU_RESET, DEEPSLEEP_RESET]", start_test, test_flow5, test_flow5, test_flow5);
|
TEST_CASE_MULTIPLE_STAGES("Switching between factory, test, factory", "[app_update][timeout=90][reset=SW_CPU_RESET, SW_CPU_RESET, DEEPSLEEP_RESET]", start_test, test_flow5, test_flow5, test_flow5);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const esp_partition_t* app_update(void)
|
|
||||||
{
|
|
||||||
const esp_partition_t *cur_app = get_running_firmware();
|
|
||||||
const esp_partition_t* update_partition = esp_ota_get_next_update_partition(NULL);
|
|
||||||
TEST_ASSERT_NOT_NULL(update_partition);
|
|
||||||
esp_ota_handle_t update_handle = 0;
|
|
||||||
TEST_ESP_OK(esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle));
|
|
||||||
copy_app_partition(update_handle, cur_app);
|
|
||||||
TEST_ESP_OK(esp_ota_end(update_handle));
|
|
||||||
TEST_ESP_OK(esp_ota_set_boot_partition(update_partition));
|
|
||||||
return update_partition;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void test_rollback1(void)
|
static void test_rollback1(void)
|
||||||
{
|
{
|
||||||
uint8_t boot_count = get_boot_count_from_nvs();
|
uint8_t boot_count = get_boot_count_from_nvs();
|
||||||
|
@ -0,0 +1,307 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "esp_rom_spiflash.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_flash_partitions.h"
|
||||||
|
#include "esp_image_format.h"
|
||||||
|
#include "../bootloader_flash/include/bootloader_flash_priv.h"
|
||||||
|
#include "esp_sleep.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "test_utils.h"
|
||||||
|
#include "utils_update.h"
|
||||||
|
#include "unity.h"
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#define BOOT_COUNT_NAMESPACE "boot_count"
|
||||||
|
|
||||||
|
static const char *TAG = "ota_test";
|
||||||
|
|
||||||
|
|
||||||
|
void set_boot_count_in_nvs(uint8_t boot_count)
|
||||||
|
{
|
||||||
|
nvs_handle_t boot_count_handle;
|
||||||
|
esp_err_t err = nvs_open(BOOT_COUNT_NAMESPACE, NVS_READWRITE, &boot_count_handle);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
TEST_ESP_OK(nvs_flash_erase());
|
||||||
|
TEST_ESP_OK(nvs_flash_init());
|
||||||
|
TEST_ESP_OK(nvs_open(BOOT_COUNT_NAMESPACE, NVS_READWRITE, &boot_count_handle));
|
||||||
|
}
|
||||||
|
TEST_ESP_OK(nvs_set_u8(boot_count_handle, "boot_count", boot_count));
|
||||||
|
TEST_ESP_OK(nvs_commit(boot_count_handle));
|
||||||
|
nvs_close(boot_count_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t get_boot_count_from_nvs(void)
|
||||||
|
{
|
||||||
|
nvs_handle_t boot_count_handle;
|
||||||
|
esp_err_t err = nvs_open(BOOT_COUNT_NAMESPACE, NVS_READONLY, &boot_count_handle);
|
||||||
|
if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||||||
|
set_boot_count_in_nvs(0);
|
||||||
|
}
|
||||||
|
uint8_t boot_count;
|
||||||
|
TEST_ESP_OK(nvs_get_u8(boot_count_handle, "boot_count", &boot_count));
|
||||||
|
nvs_close(boot_count_handle);
|
||||||
|
return boot_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Copies a current app to next partition using handle.
|
||||||
|
*
|
||||||
|
* @param[in] update_handle - Handle of API ota.
|
||||||
|
* @param[in] cur_app - Current app.
|
||||||
|
*/
|
||||||
|
void copy_app_partition(esp_ota_handle_t update_handle, const esp_partition_t *curr_app)
|
||||||
|
{
|
||||||
|
const void *partition_bin = NULL;
|
||||||
|
esp_partition_mmap_handle_t data_map;
|
||||||
|
ESP_LOGI(TAG, "start the copy process");
|
||||||
|
TEST_ESP_OK(esp_partition_mmap(curr_app, 0, curr_app->size, ESP_PARTITION_MMAP_DATA, &partition_bin, &data_map));
|
||||||
|
TEST_ESP_OK(esp_ota_write(update_handle, (const void *)partition_bin, curr_app->size));
|
||||||
|
esp_partition_munmap(data_map);
|
||||||
|
ESP_LOGI(TAG, "finish the copy process");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Copies a current app to next partition using handle.
|
||||||
|
*
|
||||||
|
* @param[in] update_handle - Handle of API ota.
|
||||||
|
* @param[in] cur_app - Current app.
|
||||||
|
*/
|
||||||
|
void copy_app_partition_with_offset(esp_ota_handle_t update_handle, const esp_partition_t *curr_app)
|
||||||
|
{
|
||||||
|
const void *partition_bin = NULL;
|
||||||
|
esp_partition_mmap_handle_t data_map;
|
||||||
|
ESP_LOGI(TAG, "start the copy process");
|
||||||
|
uint32_t offset = 0, bytes_to_write = curr_app->size;
|
||||||
|
uint32_t write_bytes;
|
||||||
|
while (bytes_to_write > 0) {
|
||||||
|
write_bytes = (bytes_to_write > (4 * 1024)) ? (4 * 1024) : bytes_to_write;
|
||||||
|
TEST_ESP_OK(esp_partition_mmap(curr_app, offset, write_bytes, ESP_PARTITION_MMAP_DATA, &partition_bin, &data_map));
|
||||||
|
TEST_ESP_OK(esp_ota_write_with_offset(update_handle, (const void *)partition_bin, write_bytes, offset));
|
||||||
|
esp_partition_munmap(data_map);
|
||||||
|
bytes_to_write -= write_bytes;
|
||||||
|
offset += write_bytes;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "finish the copy process");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Get the next partition of OTA for the update.
|
||||||
|
*
|
||||||
|
* @return The next partition of OTA(OTA0-15).
|
||||||
|
*/
|
||||||
|
const esp_partition_t * get_next_update_partition(void)
|
||||||
|
{
|
||||||
|
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(NULL, update_partition);
|
||||||
|
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32, update_partition->subtype, update_partition->address);
|
||||||
|
return update_partition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Copies a current app to next partition (OTA0-15) and then configure OTA data for a new boot partition.
|
||||||
|
*
|
||||||
|
* @param[in] cur_app_partition - Current app.
|
||||||
|
* @param[in] next_app_partition - Next app for boot.
|
||||||
|
*/
|
||||||
|
void copy_current_app_to_next_part(const esp_partition_t *cur_app_partition, const esp_partition_t *next_app_partition)
|
||||||
|
{
|
||||||
|
esp_ota_get_next_update_partition(NULL);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(NULL, next_app_partition);
|
||||||
|
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32, next_app_partition->subtype, next_app_partition->address);
|
||||||
|
|
||||||
|
esp_ota_handle_t update_handle = 0;
|
||||||
|
TEST_ESP_OK(esp_ota_begin(next_app_partition, OTA_SIZE_UNKNOWN, &update_handle));
|
||||||
|
|
||||||
|
copy_app_partition(update_handle, cur_app_partition);
|
||||||
|
|
||||||
|
TEST_ESP_OK(esp_ota_end(update_handle));
|
||||||
|
TEST_ESP_OK(esp_ota_set_boot_partition(next_app_partition));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Copies a current app to next partition (OTA0-15) and then configure OTA data for a new boot partition.
|
||||||
|
*
|
||||||
|
* @param[in] cur_app_partition - Current app.
|
||||||
|
* @param[in] next_app_partition - Next app for boot.
|
||||||
|
*/
|
||||||
|
void copy_current_app_to_next_part_with_offset(const esp_partition_t *cur_app_partition, const esp_partition_t *next_app_partition)
|
||||||
|
{
|
||||||
|
esp_ota_get_next_update_partition(NULL);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(NULL, next_app_partition);
|
||||||
|
ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%"PRIx32, next_app_partition->subtype, next_app_partition->address);
|
||||||
|
|
||||||
|
esp_ota_handle_t update_handle = 0;
|
||||||
|
TEST_ESP_OK(esp_ota_begin(next_app_partition, OTA_SIZE_UNKNOWN, &update_handle));
|
||||||
|
|
||||||
|
copy_app_partition_with_offset(update_handle, cur_app_partition);
|
||||||
|
|
||||||
|
TEST_ESP_OK(esp_ota_end(update_handle));
|
||||||
|
TEST_ESP_OK(esp_ota_set_boot_partition(next_app_partition));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Erase otadata partition
|
||||||
|
*/
|
||||||
|
void erase_ota_data(void)
|
||||||
|
{
|
||||||
|
const esp_partition_t *data_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(NULL, data_partition);
|
||||||
|
TEST_ESP_OK(esp_partition_erase_range(data_partition, 0, 2 * data_partition->erase_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Reboots ESP using mode deep sleep. This mode guaranty that RTC_DATA_ATTR variables is not reset.
|
||||||
|
*/
|
||||||
|
void reboot_as_deep_sleep(void)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "reboot as deep sleep");
|
||||||
|
esp_deep_sleep(20000);
|
||||||
|
TEST_FAIL_MESSAGE("Should never be reachable except when sleep is rejected, abort");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Copies a current app to next partition (OTA0-15), after that ESP is rebooting and run this (the next) OTAx.
|
||||||
|
*/
|
||||||
|
void copy_current_app_to_next_part_and_reboot(void)
|
||||||
|
{
|
||||||
|
const esp_partition_t *cur_app = esp_ota_get_running_partition();
|
||||||
|
ESP_LOGI(TAG, "copy current app to next part");
|
||||||
|
copy_current_app_to_next_part(cur_app, get_next_update_partition());
|
||||||
|
reboot_as_deep_sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Copies a current app to next partition (OTA0-15) using esp_ota_write_with_offest(), after that ESP is rebooting and run this (the next) OTAx.
|
||||||
|
*/
|
||||||
|
void copy_current_app_to_next_part_with_offset_and_reboot(void)
|
||||||
|
{
|
||||||
|
const esp_partition_t *cur_app = esp_ota_get_running_partition();
|
||||||
|
ESP_LOGI(TAG, "copy current app to next part");
|
||||||
|
copy_current_app_to_next_part_with_offset(cur_app, get_next_update_partition());
|
||||||
|
reboot_as_deep_sleep();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Get running app.
|
||||||
|
*
|
||||||
|
* @return The next partition of OTA(OTA0-15).
|
||||||
|
*/
|
||||||
|
const esp_partition_t* get_running_firmware(void)
|
||||||
|
{
|
||||||
|
const esp_partition_t *configured = esp_ota_get_boot_partition();
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
// If a reboot hasn't occurred after app_update(), the configured and running partitions may differ
|
||||||
|
ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08"PRIx32")",
|
||||||
|
running->type, running->subtype, running->address);
|
||||||
|
ESP_LOGI(TAG, "Configured partition type %d subtype %d (offset 0x%08"PRIx32")",
|
||||||
|
configured->type, configured->subtype, configured->address);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(NULL, configured);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(NULL, running);
|
||||||
|
return running;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Get two copies ota_data from otadata partition.
|
||||||
|
*
|
||||||
|
* @param[in] otadata_partition - otadata partition.
|
||||||
|
* @param[out] ota_data_0 - First copy from otadata_partition.
|
||||||
|
* @param[out] ota_data_1 - Second copy from otadata_partition.
|
||||||
|
*/
|
||||||
|
void get_ota_data(const esp_partition_t *otadata_partition, esp_ota_select_entry_t *ota_data_0, esp_ota_select_entry_t *ota_data_1)
|
||||||
|
{
|
||||||
|
uint32_t offset = otadata_partition->address;
|
||||||
|
uint32_t size = otadata_partition->size;
|
||||||
|
if (offset != 0) {
|
||||||
|
const esp_ota_select_entry_t *ota_select_map;
|
||||||
|
ota_select_map = bootloader_mmap(offset, size);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(NULL, ota_select_map);
|
||||||
|
|
||||||
|
memcpy(ota_data_0, ota_select_map, sizeof(esp_ota_select_entry_t));
|
||||||
|
memcpy(ota_data_1, (uint8_t *)ota_select_map + otadata_partition->erase_size, sizeof(esp_ota_select_entry_t));
|
||||||
|
bootloader_munmap(ota_select_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Writes a ota_data into required sector of otadata_partition.
|
||||||
|
*
|
||||||
|
* @param[in] otadata_partition - Partition information otadata.
|
||||||
|
* @param[in] ota_data - otadata structure.
|
||||||
|
* @param[in] sec_id - Sector number 0 or 1.
|
||||||
|
*/
|
||||||
|
void write_ota_data(const esp_partition_t *otadata_partition, esp_ota_select_entry_t *ota_data, int sec_id)
|
||||||
|
{
|
||||||
|
esp_partition_write(otadata_partition, otadata_partition->erase_size * sec_id, &ota_data[sec_id], sizeof(esp_ota_select_entry_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Makes a corrupt of ota_data.
|
||||||
|
* @param[in] err - type error
|
||||||
|
*/
|
||||||
|
void corrupt_ota_data(corrupt_ota_data_t err)
|
||||||
|
{
|
||||||
|
esp_ota_select_entry_t ota_data[2];
|
||||||
|
|
||||||
|
const esp_partition_t *otadata_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
|
||||||
|
TEST_ASSERT_NOT_EQUAL(NULL, otadata_partition);
|
||||||
|
get_ota_data(otadata_partition, &ota_data[0], &ota_data[1]);
|
||||||
|
|
||||||
|
if (err & CORR_CRC_1_SECTOR_OTA_DATA) {
|
||||||
|
ota_data[0].crc = 0;
|
||||||
|
}
|
||||||
|
if (err & CORR_CRC_2_SECTOR_OTA_DATA) {
|
||||||
|
ota_data[1].crc = 0;
|
||||||
|
}
|
||||||
|
TEST_ESP_OK(esp_partition_erase_range(otadata_partition, 0, otadata_partition->size));
|
||||||
|
write_ota_data(otadata_partition, &ota_data[0], 0);
|
||||||
|
write_ota_data(otadata_partition, &ota_data[1], 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(CONFIG_BOOTLOADER_FACTORY_RESET) || defined(CONFIG_BOOTLOADER_APP_TEST)
|
||||||
|
/* @brief Sets the pin number to output and sets output level as low. After reboot (deep sleep) this pin keep the same level.
|
||||||
|
*
|
||||||
|
* The output level of the pad will be force locked and can not be changed.
|
||||||
|
* Power down or call gpio_hold_dis will disable this function.
|
||||||
|
*
|
||||||
|
* @param[in] num_pin - Pin number
|
||||||
|
*/
|
||||||
|
void set_output_pin(uint32_t num_pin)
|
||||||
|
{
|
||||||
|
TEST_ESP_OK(gpio_hold_dis(num_pin));
|
||||||
|
|
||||||
|
gpio_config_t io_conf;
|
||||||
|
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||||
|
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||||
|
io_conf.pin_bit_mask = (1ULL << num_pin);
|
||||||
|
io_conf.pull_down_en = 0;
|
||||||
|
io_conf.pull_up_en = 0;
|
||||||
|
TEST_ESP_OK(gpio_config(&io_conf));
|
||||||
|
|
||||||
|
TEST_ESP_OK(gpio_set_level(num_pin, 0));
|
||||||
|
TEST_ESP_OK(gpio_hold_en(num_pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* @brief Unset the pin number hold function.
|
||||||
|
*/
|
||||||
|
void reset_output_pin(uint32_t num_pin)
|
||||||
|
{
|
||||||
|
TEST_ESP_OK(gpio_hold_dis(num_pin));
|
||||||
|
TEST_ESP_OK(gpio_reset_pin(num_pin));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void mark_app_valid(void)
|
||||||
|
{
|
||||||
|
#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
|
||||||
|
TEST_ESP_OK(esp_ota_mark_app_valid_cancel_rollback());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const esp_partition_t* app_update(void)
|
||||||
|
{
|
||||||
|
const esp_partition_t *cur_app = get_running_firmware();
|
||||||
|
const esp_partition_t* update_partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
TEST_ASSERT_NOT_NULL(update_partition);
|
||||||
|
esp_ota_handle_t update_handle = 0;
|
||||||
|
TEST_ESP_OK(esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle));
|
||||||
|
copy_app_partition(update_handle, cur_app);
|
||||||
|
TEST_ESP_OK(esp_ota_end(update_handle));
|
||||||
|
TEST_ESP_OK(esp_ota_set_boot_partition(update_partition));
|
||||||
|
return update_partition;
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_partition.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enumeration for specifying which OTA data sectors' CRCs to corrupt.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
CORR_CRC_1_SECTOR_OTA_DATA = (1 << 0), /*!< Corrupt CRC only 1 sector of ota_data */
|
||||||
|
CORR_CRC_2_SECTOR_OTA_DATA = (1 << 1), /*!< Corrupt CRC only 2 sector of ota_data */
|
||||||
|
} corrupt_ota_data_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set boot count value in NVS.
|
||||||
|
* @param boot_count Value to set.
|
||||||
|
*/
|
||||||
|
void set_boot_count_in_nvs(uint8_t boot_count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get boot count value from NVS.
|
||||||
|
* @return Boot count value.
|
||||||
|
*/
|
||||||
|
uint8_t get_boot_count_from_nvs(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy current app to next partition using OTA handle.
|
||||||
|
* @param update_handle OTA update handle.
|
||||||
|
* @param curr_app Current app partition.
|
||||||
|
*/
|
||||||
|
void copy_app_partition(esp_ota_handle_t update_handle, const esp_partition_t *curr_app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy current app to next partition using OTA handle with offset.
|
||||||
|
* @param update_handle OTA update handle.
|
||||||
|
* @param curr_app Current app partition.
|
||||||
|
*/
|
||||||
|
void copy_app_partition_with_offset(esp_ota_handle_t update_handle, const esp_partition_t *curr_app);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the next OTA update partition.
|
||||||
|
* @return Pointer to next OTA partition.
|
||||||
|
*/
|
||||||
|
const esp_partition_t * get_next_update_partition(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy current app to next partition and set boot partition.
|
||||||
|
* @param cur_app_partition Current app partition.
|
||||||
|
* @param next_app_partition Next app partition.
|
||||||
|
*/
|
||||||
|
void copy_current_app_to_next_part(const esp_partition_t *cur_app_partition, const esp_partition_t *next_app_partition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy current app to next partition with offset and set boot partition.
|
||||||
|
* @param cur_app_partition Current app partition.
|
||||||
|
* @param next_app_partition Next app partition.
|
||||||
|
*/
|
||||||
|
void copy_current_app_to_next_part_with_offset(const esp_partition_t *cur_app_partition, const esp_partition_t *next_app_partition);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Erase OTA data partition.
|
||||||
|
*/
|
||||||
|
void erase_ota_data(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reboot ESP using deep sleep mode.
|
||||||
|
*/
|
||||||
|
void reboot_as_deep_sleep(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy current app to next partition and reboot.
|
||||||
|
*/
|
||||||
|
void copy_current_app_to_next_part_and_reboot(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Copy current app to next partition with offset and reboot.
|
||||||
|
*/
|
||||||
|
void copy_current_app_to_next_part_with_offset_and_reboot(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get running firmware partition.
|
||||||
|
* @return Pointer to running firmware partition.
|
||||||
|
*/
|
||||||
|
const esp_partition_t* get_running_firmware(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get two OTA data copies from OTA data partition.
|
||||||
|
* @param otadata_partition OTA data partition.
|
||||||
|
* @param ota_data_0 First OTA data copy.
|
||||||
|
* @param ota_data_1 Second OTA data copy.
|
||||||
|
*/
|
||||||
|
void get_ota_data(const esp_partition_t *otadata_partition, esp_ota_select_entry_t *ota_data_0, esp_ota_select_entry_t *ota_data_1);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write OTA data into required sector of OTA data partition.
|
||||||
|
* @param otadata_partition OTA data partition.
|
||||||
|
* @param ota_data OTA data structure.
|
||||||
|
* @param sec_id Sector number (0 or 1).
|
||||||
|
*/
|
||||||
|
void write_ota_data(const esp_partition_t *otadata_partition, esp_ota_select_entry_t *ota_data, int sec_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Corrupt OTA data for testing.
|
||||||
|
* @param err Type of corruption.
|
||||||
|
*/
|
||||||
|
void corrupt_ota_data(corrupt_ota_data_t err);
|
||||||
|
|
||||||
|
#if defined(CONFIG_BOOTLOADER_FACTORY_RESET) || defined(CONFIG_BOOTLOADER_APP_TEST)
|
||||||
|
/**
|
||||||
|
* @brief Set output pin to low and hold state.
|
||||||
|
* @param num_pin Pin number.
|
||||||
|
*/
|
||||||
|
void set_output_pin(uint32_t num_pin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reset output pin hold function.
|
||||||
|
* @param num_pin Pin number.
|
||||||
|
*/
|
||||||
|
void reset_output_pin(uint32_t num_pin);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Mark app as valid and cancel rollback.
|
||||||
|
*/
|
||||||
|
void mark_app_valid(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Perform app update and set new boot partition.
|
||||||
|
* @return Pointer to updated partition.
|
||||||
|
*/
|
||||||
|
const esp_partition_t* app_update(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
@ -60,3 +60,18 @@ def test_app_update_xip_psram_rom_impl(dut: Dut) -> None:
|
|||||||
@idf_parametrize('target', ['esp32', 'esp32c3', 'esp32s3', 'esp32p4'], indirect=['target'])
|
@idf_parametrize('target', ['esp32', 'esp32c3', 'esp32s3', 'esp32p4'], indirect=['target'])
|
||||||
def test_app_update_with_rollback(dut: Dut) -> None:
|
def test_app_update_with_rollback(dut: Dut) -> None:
|
||||||
dut.run_all_single_board_cases(timeout=180)
|
dut.run_all_single_board_cases(timeout=180)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.recovery_bootloader
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
'config',
|
||||||
|
['recovery_bootloader'],
|
||||||
|
indirect=True,
|
||||||
|
)
|
||||||
|
@idf_parametrize('target', ['esp32c5'], indirect=['target'])
|
||||||
|
def test_recovery_bootloader_update(dut: Dut) -> None:
|
||||||
|
try:
|
||||||
|
dut.run_all_single_board_cases(group='recovery_bootloader', timeout=90)
|
||||||
|
finally:
|
||||||
|
# Erase recovery bootloader after test because it may interfere with other tests using this runner
|
||||||
|
dut.serial.erase_flash()
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
CONFIG_BOOTLOADER_RECOVERY_ENABLE=y
|
||||||
|
CONFIG_BOOTLOADER_RECOVERY_OFFSET=0x3F0000
|
||||||
|
CONFIG_PARTITION_TABLE_OFFSET=0x9000
|
@ -0,0 +1,2 @@
|
|||||||
|
# ESP32C5 supports the Recovery bootloader feature in ROM starting from v1.0 (ECO2)
|
||||||
|
CONFIG_IDF_TARGET="esp32c5"
|
@ -0,0 +1,2 @@
|
|||||||
|
# ESP32C61 supports the Recovery bootloader feature in ROM starting from v1.0 (ECO3)
|
||||||
|
CONFIG_IDF_TARGET="esp32c61"
|
@ -1,8 +1,37 @@
|
|||||||
menu "Bootloader Rollback"
|
menu "Recovery Bootloader and Rollback"
|
||||||
|
|
||||||
|
config BOOTLOADER_RECOVERY_ENABLE
|
||||||
|
bool "Enable Recovery Bootloader"
|
||||||
|
depends on SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
default n
|
||||||
|
help
|
||||||
|
The recovery bootloader feature is implemented in the ROM bootloader. It is required for safe OTA
|
||||||
|
updates of the bootloader. The feature is activated when the eFuse field
|
||||||
|
(ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR) is set, which defines the flash address of the
|
||||||
|
recovery bootloader. If activated and the primary bootloader fails to load, the ROM bootloader
|
||||||
|
will attempt to load the recovery bootloader from the address specified in eFuse.
|
||||||
|
|
||||||
|
config BOOTLOADER_RECOVERY_OFFSET
|
||||||
|
hex "Recovery Bootloader Flash Offset"
|
||||||
|
depends on BOOTLOADER_RECOVERY_ENABLE
|
||||||
|
default 0x3F0000
|
||||||
|
range 0x0 0xFFE000
|
||||||
|
help
|
||||||
|
Flash address where the recovery bootloader is stored.
|
||||||
|
This value must be written to the eFuse field (ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR)
|
||||||
|
to activate the recovery bootloader in the ROM bootloader. The eFuse can be programmed
|
||||||
|
using espefuse.py or in the user application with the API esp_efuse_set_recovery_bootloader_offset().
|
||||||
|
Setting this value in the config allows parttool.py to verify that it does not overlap with existing
|
||||||
|
partitions in the partition table.
|
||||||
|
|
||||||
|
The address must be a multiple of the flash sector size (0x1000 bytes).
|
||||||
|
The eFuse field stores the offset in sectors.
|
||||||
|
If the feature is no longer needed or unused, you can burn the 0xFFF value to disable this feature in
|
||||||
|
the ROM bootloader.
|
||||||
|
|
||||||
config BOOTLOADER_ANTI_ROLLBACK_ENABLE
|
config BOOTLOADER_ANTI_ROLLBACK_ENABLE
|
||||||
bool "Enable bootloader rollback support"
|
bool "Enable bootloader rollback support"
|
||||||
depends on SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
depends on BOOTLOADER_RECOVERY_ENABLE
|
||||||
default n
|
default n
|
||||||
help
|
help
|
||||||
This option prevents rollback to previous bootloader image with lower security version.
|
This option prevents rollback to previous bootloader image with lower security version.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -19,6 +19,7 @@
|
|||||||
#include "hal/wdt_hal.h"
|
#include "hal/wdt_hal.h"
|
||||||
#include "hal/efuse_hal.h"
|
#include "hal/efuse_hal.h"
|
||||||
#include "esp_bootloader_desc.h"
|
#include "esp_bootloader_desc.h"
|
||||||
|
#include "esp_rom_sys.h"
|
||||||
|
|
||||||
static const char *TAG = "boot";
|
static const char *TAG = "boot";
|
||||||
|
|
||||||
@ -34,7 +35,12 @@ void bootloader_clear_bss_section(void)
|
|||||||
esp_err_t bootloader_read_bootloader_header(void)
|
esp_err_t bootloader_read_bootloader_header(void)
|
||||||
{
|
{
|
||||||
/* load bootloader image header */
|
/* load bootloader image header */
|
||||||
if (bootloader_flash_read(ESP_BOOTLOADER_OFFSET, &bootloader_image_hdr, sizeof(esp_image_header_t), true) != ESP_OK) {
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
const uint32_t bootloader_flash_offset = esp_rom_get_bootloader_offset();
|
||||||
|
#else
|
||||||
|
const uint32_t bootloader_flash_offset = ESP_PRIMARY_BOOTLOADER_OFFSET;
|
||||||
|
#endif
|
||||||
|
if (bootloader_flash_read(bootloader_flash_offset, &bootloader_image_hdr, sizeof(esp_image_header_t), true) != ESP_OK) {
|
||||||
ESP_EARLY_LOGE(TAG, "failed to load bootloader image header!");
|
ESP_EARLY_LOGE(TAG, "failed to load bootloader image header!");
|
||||||
return ESP_FAIL;
|
return ESP_FAIL;
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include "soc/soc_caps.h"
|
#include "soc/soc_caps.h"
|
||||||
#include "hal/cache_ll.h"
|
#include "hal/cache_ll.h"
|
||||||
#include "spi_flash_mmap.h"
|
#include "spi_flash_mmap.h"
|
||||||
|
#include "hal/efuse_hal.h"
|
||||||
|
|
||||||
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
|
||||||
|
|
||||||
@ -119,11 +120,26 @@ void esp_image_bootloader_offset_set(const uint32_t offset)
|
|||||||
{
|
{
|
||||||
s_bootloader_partition_offset = offset;
|
s_bootloader_partition_offset = offset;
|
||||||
ESP_LOGI(TAG, "Bootloader offsets for PRIMARY: 0x%x, Secondary: 0x%" PRIx32, ESP_PRIMARY_BOOTLOADER_OFFSET, s_bootloader_partition_offset);
|
ESP_LOGI(TAG, "Bootloader offsets for PRIMARY: 0x%x, Secondary: 0x%" PRIx32, ESP_PRIMARY_BOOTLOADER_OFFSET, s_bootloader_partition_offset);
|
||||||
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
uint32_t recovery_offset = efuse_hal_get_recovery_bootloader_address();
|
||||||
|
if (efuse_hal_recovery_bootloader_enabled()) {
|
||||||
|
ESP_LOGI(TAG, "Bootloader offset for RECOVERY: 0x%" PRIx32, recovery_offset);
|
||||||
|
} else if (recovery_offset == 0) {
|
||||||
|
ESP_LOGI(TAG, "Bootloader offset for RECOVERY: has not been set yet");
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "Bootloader offset for RECOVERY: is disabled");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool is_bootloader(uint32_t offset)
|
static bool is_bootloader(uint32_t offset)
|
||||||
{
|
{
|
||||||
return ((offset == ESP_PRIMARY_BOOTLOADER_OFFSET) || (offset == s_bootloader_partition_offset));
|
return ((offset == ESP_PRIMARY_BOOTLOADER_OFFSET)
|
||||||
|
|| (offset == s_bootloader_partition_offset)
|
||||||
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
|| (efuse_hal_recovery_bootloader_enabled() ? offset == efuse_hal_get_recovery_bootloader_address() : false)
|
||||||
|
#endif
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data)
|
static esp_err_t image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data)
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include "esp_efuse_table.h"
|
#include "esp_efuse_table.h"
|
||||||
|
|
||||||
// md5_digest_table 0c453d200f282e320677c1ac46786658
|
// md5_digest_table a89a21bde56c3936f31af16ba1de1fe3
|
||||||
// This file was generated from the file esp_efuse_table.csv. DO NOT CHANGE THIS FILE MANUALLY.
|
// This file was generated from the file esp_efuse_table.csv. DO NOT CHANGE THIS FILE MANUALLY.
|
||||||
// If you want to change some fields, you need to change esp_efuse_table.csv file
|
// If you want to change some fields, you need to change esp_efuse_table.csv file
|
||||||
// then run `efuse_common_table` or `efuse_custom_table` command it will generate this file.
|
// then run `efuse_common_table` or `efuse_custom_table` command it will generate this file.
|
||||||
@ -599,10 +599,6 @@ static const esp_efuse_desc_t SEC_DPA_LEVEL[] = {
|
|||||||
{EFUSE_BLK0, 116, 2}, // [] Represents the security level of anti-DPA attack. The level is adjusted by configuring the clock random frequency division mode.0: Security level is SEC\_DPA\_OFF1: Security level is SEC\_DPA\_LOW2: Security level is SEC\_DPA\_MIDDLE3: Security level is SEC\_DPA\_HIGHFor more information; please refer to Chapter \ref{mod:sysreg} \textit{\nameref{mod:sysreg}} > Section \ref{sec:sysreg-anti-dpa-attack-security-control} \textit{\nameref{sec:sysreg-anti-dpa-attack-security-control}}.,
|
{EFUSE_BLK0, 116, 2}, // [] Represents the security level of anti-DPA attack. The level is adjusted by configuring the clock random frequency division mode.0: Security level is SEC\_DPA\_OFF1: Security level is SEC\_DPA\_LOW2: Security level is SEC\_DPA\_MIDDLE3: Security level is SEC\_DPA\_HIGHFor more information; please refer to Chapter \ref{mod:sysreg} \textit{\nameref{mod:sysreg}} > Section \ref{sec:sysreg-anti-dpa-attack-security-control} \textit{\nameref{sec:sysreg-anti-dpa-attack-security-control}}.,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const esp_efuse_desc_t RECOVERY_BOOTLOADER_FLASH_SECTOR_HI[] = {
|
|
||||||
{EFUSE_BLK0, 118, 3}, // [] Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled. (The high part of the field),
|
|
||||||
};
|
|
||||||
|
|
||||||
static const esp_efuse_desc_t SECURE_BOOT_EN[] = {
|
static const esp_efuse_desc_t SECURE_BOOT_EN[] = {
|
||||||
{EFUSE_BLK0, 121, 1}, // [] Represents whether Secure Boot is enabled.1: Enabled0: Disabled,
|
{EFUSE_BLK0, 121, 1}, // [] Represents whether Secure Boot is enabled.1: Enabled0: Disabled,
|
||||||
};
|
};
|
||||||
@ -691,8 +687,9 @@ static const esp_efuse_desc_t ECC_FORCE_CONST_TIME[] = {
|
|||||||
{EFUSE_BLK0, 173, 1}, // [] Represents whether to force ECC to use constant-time mode for point multiplication calculation. 0: Not force1: Force,
|
{EFUSE_BLK0, 173, 1}, // [] Represents whether to force ECC to use constant-time mode for point multiplication calculation. 0: Not force1: Force,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const esp_efuse_desc_t RECOVERY_BOOTLOADER_FLASH_SECTOR_LO[] = {
|
static const esp_efuse_desc_t RECOVERY_BOOTLOADER_FLASH_SECTOR[] = {
|
||||||
{EFUSE_BLK0, 174, 9}, // [] Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled. (The low part of the field),
|
{EFUSE_BLK0, 174, 9}, // [] Low 9 bits. Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled.,
|
||||||
|
{EFUSE_BLK0, 118, 3}, // [] High 3 bits. Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled.,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const esp_efuse_desc_t MAC[] = {
|
static const esp_efuse_desc_t MAC[] = {
|
||||||
@ -1642,11 +1639,6 @@ const esp_efuse_desc_t* ESP_EFUSE_SEC_DPA_LEVEL[] = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
const esp_efuse_desc_t* ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR_HI[] = {
|
|
||||||
&RECOVERY_BOOTLOADER_FLASH_SECTOR_HI[0], // [] Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled. (The high part of the field)
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
const esp_efuse_desc_t* ESP_EFUSE_SECURE_BOOT_EN[] = {
|
const esp_efuse_desc_t* ESP_EFUSE_SECURE_BOOT_EN[] = {
|
||||||
&SECURE_BOOT_EN[0], // [] Represents whether Secure Boot is enabled.1: Enabled0: Disabled
|
&SECURE_BOOT_EN[0], // [] Represents whether Secure Boot is enabled.1: Enabled0: Disabled
|
||||||
NULL
|
NULL
|
||||||
@ -1757,8 +1749,9 @@ const esp_efuse_desc_t* ESP_EFUSE_ECC_FORCE_CONST_TIME[] = {
|
|||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
const esp_efuse_desc_t* ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR_LO[] = {
|
const esp_efuse_desc_t* ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR[] = {
|
||||||
&RECOVERY_BOOTLOADER_FLASH_SECTOR_LO[0], // [] Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled. (The low part of the field)
|
&RECOVERY_BOOTLOADER_FLASH_SECTOR[0], // [] Low 9 bits. Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled.
|
||||||
|
&RECOVERY_BOOTLOADER_FLASH_SECTOR[1], // [] High 3 bits. Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled.
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -157,7 +157,6 @@ KEY_PURPOSE_3, EFUSE_BLK0, 101, 5, [KEY3_PUR
|
|||||||
KEY_PURPOSE_4, EFUSE_BLK0, 106, 5, [KEY4_PURPOSE] Represents the purpose of Key4. See Table \ref{tab:efuse-key-purpose}
|
KEY_PURPOSE_4, EFUSE_BLK0, 106, 5, [KEY4_PURPOSE] Represents the purpose of Key4. See Table \ref{tab:efuse-key-purpose}
|
||||||
KEY_PURPOSE_5, EFUSE_BLK0, 111, 5, [KEY5_PURPOSE] Represents the purpose of Key5. See Table \ref{tab:efuse-key-purpose}
|
KEY_PURPOSE_5, EFUSE_BLK0, 111, 5, [KEY5_PURPOSE] Represents the purpose of Key5. See Table \ref{tab:efuse-key-purpose}
|
||||||
SEC_DPA_LEVEL, EFUSE_BLK0, 116, 2, [] Represents the security level of anti-DPA attack. The level is adjusted by configuring the clock random frequency division mode.0: Security level is SEC\_DPA\_OFF1: Security level is SEC\_DPA\_LOW2: Security level is SEC\_DPA\_MIDDLE3: Security level is SEC\_DPA\_HIGHFor more information; please refer to Chapter \ref{mod:sysreg} \textit{\nameref{mod:sysreg}} > Section \ref{sec:sysreg-anti-dpa-attack-security-control} \textit{\nameref{sec:sysreg-anti-dpa-attack-security-control}}.
|
SEC_DPA_LEVEL, EFUSE_BLK0, 116, 2, [] Represents the security level of anti-DPA attack. The level is adjusted by configuring the clock random frequency division mode.0: Security level is SEC\_DPA\_OFF1: Security level is SEC\_DPA\_LOW2: Security level is SEC\_DPA\_MIDDLE3: Security level is SEC\_DPA\_HIGHFor more information; please refer to Chapter \ref{mod:sysreg} \textit{\nameref{mod:sysreg}} > Section \ref{sec:sysreg-anti-dpa-attack-security-control} \textit{\nameref{sec:sysreg-anti-dpa-attack-security-control}}.
|
||||||
RECOVERY_BOOTLOADER_FLASH_SECTOR_HI, EFUSE_BLK0, 118, 3, [] Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled. (The high part of the field)
|
|
||||||
SECURE_BOOT_EN, EFUSE_BLK0, 121, 1, [] Represents whether Secure Boot is enabled.1: Enabled0: Disabled
|
SECURE_BOOT_EN, EFUSE_BLK0, 121, 1, [] Represents whether Secure Boot is enabled.1: Enabled0: Disabled
|
||||||
SECURE_BOOT_AGGRESSIVE_REVOKE, EFUSE_BLK0, 122, 1, [] Represents whether aggressive revocation of Secure Boot is enabled.1: Enabled0: Disabled
|
SECURE_BOOT_AGGRESSIVE_REVOKE, EFUSE_BLK0, 122, 1, [] Represents whether aggressive revocation of Secure Boot is enabled.1: Enabled0: Disabled
|
||||||
KM_XTS_KEY_LENGTH_256, EFUSE_BLK0, 123, 1, [] Represents which key flash encryption uses.0: XTS-AES-256 key1: XTS-AES-128 key
|
KM_XTS_KEY_LENGTH_256, EFUSE_BLK0, 123, 1, [] Represents which key flash encryption uses.0: XTS-AES-256 key1: XTS-AES-128 key
|
||||||
@ -180,7 +179,8 @@ HUK_GEN_STATE, EFUSE_BLK0, 160, 9, [] Repres
|
|||||||
XTAL_48M_SEL, EFUSE_BLK0, 169, 3, [] Represents whether XTAL frequency is 48MHz or not. If not; 40MHz XTAL will be used. If this field contains Odd number bit 1: Enable 48MHz XTAL\ Even number bit 1: Enable 40MHz XTAL
|
XTAL_48M_SEL, EFUSE_BLK0, 169, 3, [] Represents whether XTAL frequency is 48MHz or not. If not; 40MHz XTAL will be used. If this field contains Odd number bit 1: Enable 48MHz XTAL\ Even number bit 1: Enable 40MHz XTAL
|
||||||
XTAL_48M_SEL_MODE, EFUSE_BLK0, 172, 1, [] Represents what determines the XTAL frequency in \textbf{Joint Download Boot} mode. For more information; please refer to Chapter \ref{mod:bootctrl} \textit{\nameref{mod:bootctrl}}.0: Strapping PAD state1: \hyperref[fielddesc:EFUSEXTAL48MSEL]{EFUSE\_XTAL\_48M\_SEL} in eFuse
|
XTAL_48M_SEL_MODE, EFUSE_BLK0, 172, 1, [] Represents what determines the XTAL frequency in \textbf{Joint Download Boot} mode. For more information; please refer to Chapter \ref{mod:bootctrl} \textit{\nameref{mod:bootctrl}}.0: Strapping PAD state1: \hyperref[fielddesc:EFUSEXTAL48MSEL]{EFUSE\_XTAL\_48M\_SEL} in eFuse
|
||||||
ECC_FORCE_CONST_TIME, EFUSE_BLK0, 173, 1, [] Represents whether to force ECC to use constant-time mode for point multiplication calculation. 0: Not force1: Force
|
ECC_FORCE_CONST_TIME, EFUSE_BLK0, 173, 1, [] Represents whether to force ECC to use constant-time mode for point multiplication calculation. 0: Not force1: Force
|
||||||
RECOVERY_BOOTLOADER_FLASH_SECTOR_LO, EFUSE_BLK0, 174, 9, [] Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled. (The low part of the field)
|
RECOVERY_BOOTLOADER_FLASH_SECTOR, EFUSE_BLK0, 174, 9, [] Low 9 bits. Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled.
|
||||||
|
, EFUSE_BLK0, 118, 3, [] High 3 bits. Represents the starting flash sector (flash sector size is 0x1000) of the recovery bootloader used by the ROM bootloader If the primary bootloader fails. 0 and 0xFFF - this feature is disabled.
|
||||||
MAC, EFUSE_BLK1, 40, 8, [MAC_FACTORY] MAC address
|
MAC, EFUSE_BLK1, 40, 8, [MAC_FACTORY] MAC address
|
||||||
, EFUSE_BLK1, 32, 8, [MAC_FACTORY] MAC address
|
, EFUSE_BLK1, 32, 8, [MAC_FACTORY] MAC address
|
||||||
, EFUSE_BLK1, 24, 8, [MAC_FACTORY] MAC address
|
, EFUSE_BLK1, 24, 8, [MAC_FACTORY] MAC address
|
||||||
|
Can't render this file because it contains an unexpected character in line 8 and column 53.
|
@ -10,7 +10,7 @@ extern "C" {
|
|||||||
|
|
||||||
#include "esp_efuse.h"
|
#include "esp_efuse.h"
|
||||||
|
|
||||||
// md5_digest_table 0c453d200f282e320677c1ac46786658
|
// md5_digest_table a89a21bde56c3936f31af16ba1de1fe3
|
||||||
// This file was generated from the file esp_efuse_table.csv. DO NOT CHANGE THIS FILE MANUALLY.
|
// This file was generated from the file esp_efuse_table.csv. DO NOT CHANGE THIS FILE MANUALLY.
|
||||||
// If you want to change some fields, you need to change esp_efuse_table.csv file
|
// If you want to change some fields, you need to change esp_efuse_table.csv file
|
||||||
// then run `efuse_common_table` or `efuse_custom_table` command it will generate this file.
|
// then run `efuse_common_table` or `efuse_custom_table` command it will generate this file.
|
||||||
@ -193,7 +193,6 @@ extern const esp_efuse_desc_t* ESP_EFUSE_KEY_PURPOSE_4[];
|
|||||||
extern const esp_efuse_desc_t* ESP_EFUSE_KEY_PURPOSE_5[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_KEY_PURPOSE_5[];
|
||||||
#define ESP_EFUSE_KEY5_PURPOSE ESP_EFUSE_KEY_PURPOSE_5
|
#define ESP_EFUSE_KEY5_PURPOSE ESP_EFUSE_KEY_PURPOSE_5
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_SEC_DPA_LEVEL[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_SEC_DPA_LEVEL[];
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR_HI[];
|
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_SECURE_BOOT_EN[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_SECURE_BOOT_EN[];
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_SECURE_BOOT_AGGRESSIVE_REVOKE[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_SECURE_BOOT_AGGRESSIVE_REVOKE[];
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_KM_XTS_KEY_LENGTH_256[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_KM_XTS_KEY_LENGTH_256[];
|
||||||
@ -216,7 +215,7 @@ extern const esp_efuse_desc_t* ESP_EFUSE_HUK_GEN_STATE[];
|
|||||||
extern const esp_efuse_desc_t* ESP_EFUSE_XTAL_48M_SEL[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_XTAL_48M_SEL[];
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_XTAL_48M_SEL_MODE[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_XTAL_48M_SEL_MODE[];
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_ECC_FORCE_CONST_TIME[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_ECC_FORCE_CONST_TIME[];
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR_LO[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR[];
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_MAC[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_MAC[];
|
||||||
#define ESP_EFUSE_MAC_FACTORY ESP_EFUSE_MAC
|
#define ESP_EFUSE_MAC_FACTORY ESP_EFUSE_MAC
|
||||||
extern const esp_efuse_desc_t* ESP_EFUSE_MAC_EXT[];
|
extern const esp_efuse_desc_t* ESP_EFUSE_MAC_EXT[];
|
||||||
|
@ -282,6 +282,22 @@ esp_err_t esp_efuse_write_block(esp_efuse_block_t blk, const void* src_key, size
|
|||||||
*/
|
*/
|
||||||
uint32_t esp_efuse_get_pkg_ver(void);
|
uint32_t esp_efuse_get_pkg_ver(void);
|
||||||
|
|
||||||
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED || __DOXYGEN__
|
||||||
|
/**
|
||||||
|
* @brief Sets the recovery bootloader flash offset in eFuse.
|
||||||
|
*
|
||||||
|
* This function is used to set the flash offset in eFuse for the recovery bootloader.
|
||||||
|
* If an offset is already set in eFuse, it will be validated against the provided offset.
|
||||||
|
*
|
||||||
|
* @param offset Flash offset where the recovery bootloader is located.
|
||||||
|
* @return
|
||||||
|
* - ESP_OK: Successfully set or given offset is already set.
|
||||||
|
* - ESP_ERR_NOT_ALLOWED: Recovery bootloader feature is disabled in eFuse.
|
||||||
|
* - ESP_FAIL: Failed to update the recovery bootloader flash offset.
|
||||||
|
* - Error code from eFuse read/write operations if an error occurs.
|
||||||
|
*/
|
||||||
|
esp_err_t esp_efuse_set_recovery_bootloader_offset(const uint32_t offset);
|
||||||
|
#endif // SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Reset efuse write registers
|
* @brief Reset efuse write registers
|
||||||
|
@ -12,8 +12,11 @@
|
|||||||
#include "esp_types.h"
|
#include "esp_types.h"
|
||||||
#include "assert.h"
|
#include "assert.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
|
#include "esp_check.h"
|
||||||
#include "esp_fault.h"
|
#include "esp_fault.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "hal/efuse_ll.h"
|
||||||
|
#include "hal/efuse_hal.h"
|
||||||
#include "soc/efuse_periph.h"
|
#include "soc/efuse_periph.h"
|
||||||
#include "sys/param.h"
|
#include "sys/param.h"
|
||||||
#include "soc/soc_caps.h"
|
#include "soc/soc_caps.h"
|
||||||
@ -151,3 +154,34 @@ esp_err_t esp_efuse_enable_ecdsa_p192_curve_mode(void)
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
#endif /* SOC_ECDSA_P192_CURVE_DEFAULT_DISABLED */
|
#endif /* SOC_ECDSA_P192_CURVE_DEFAULT_DISABLED */
|
||||||
|
|
||||||
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
esp_err_t esp_efuse_set_recovery_bootloader_offset(const uint32_t offset)
|
||||||
|
{
|
||||||
|
// The eFuse field stores the sector number instead of the full address to conserve eFuse bits.
|
||||||
|
if (efuse_ll_get_recovery_bootloader_sector() == 0) {
|
||||||
|
ESP_LOGI(TAG, "Recovery bootloader offset has not been set yet.");
|
||||||
|
uint32_t recovery_flash_sector = efuse_hal_convert_recovery_bootloader_address_to_flash_sectors(offset);
|
||||||
|
ESP_RETURN_ON_FALSE((recovery_flash_sector & ((1U << EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR_LEN) - 1)) == recovery_flash_sector, ESP_ERR_INVALID_ARG, TAG,
|
||||||
|
"Given address exceeds the allowed range of the efuse field");
|
||||||
|
|
||||||
|
size_t recovery_flash_sector_len = esp_efuse_get_field_size(ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR);
|
||||||
|
assert(recovery_flash_sector_len == EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR_LEN);
|
||||||
|
ESP_RETURN_ON_ERROR(esp_efuse_write_field_blob(ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR, &recovery_flash_sector, recovery_flash_sector_len), TAG,
|
||||||
|
"Failed to burn recovery bootloader offset to eFuse");
|
||||||
|
|
||||||
|
} else if (!efuse_hal_recovery_bootloader_enabled()) {
|
||||||
|
ESP_LOGE(TAG, "Recovery bootloader offset is disabled");
|
||||||
|
return ESP_ERR_NOT_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t programmed_offset = efuse_hal_get_recovery_bootloader_address();
|
||||||
|
if (programmed_offset != offset) {
|
||||||
|
ESP_LOGE(TAG, "Verification failed. eFuse recovery bootloader offset=0x%" PRIx32 ", expected=0x%" PRIx32, programmed_offset, offset);
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Recovery bootloader offset in eFuse = 0x%" PRIx32, programmed_offset);
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
#endif // SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -421,6 +421,30 @@ typedef enum {
|
|||||||
} STATUS __attribute__((deprecated("Use ETS_STATUS instead")));
|
} STATUS __attribute__((deprecated("Use ETS_STATUS instead")));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the offset from which the bootloader image is used to load.
|
||||||
|
*
|
||||||
|
* The offset can point to either the PRIMARY or RECOVERY bootloader.
|
||||||
|
*
|
||||||
|
* @note The bootloader offset variable in ROM is stored in a memory that will be reclaimed by heap component.
|
||||||
|
* Read it before the heap is initialized, otherwise it may return an invalid value.
|
||||||
|
*
|
||||||
|
* @return The offset of the active bootloader.
|
||||||
|
*/
|
||||||
|
uint32_t ets_get_bootloader_offset(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the offset from which the bootloader image is used to load.
|
||||||
|
*
|
||||||
|
* The offset can point to either the PRIMARY or RECOVERY bootloader.
|
||||||
|
*
|
||||||
|
* @note The bootloader offset variable in ROM is stored in a memory that will be reclaimed by heap component.
|
||||||
|
* Setting it after the heap is initialized, may corrupt the heap memory.
|
||||||
|
*
|
||||||
|
* @param offset The offset value to set for the active bootloader.
|
||||||
|
*/
|
||||||
|
void ets_set_bootloader_offset(uint32_t offset);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -421,6 +421,30 @@ typedef enum {
|
|||||||
} STATUS __attribute__((deprecated("Use ETS_STATUS instead")));
|
} STATUS __attribute__((deprecated("Use ETS_STATUS instead")));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the offset from which the bootloader image is used to load.
|
||||||
|
*
|
||||||
|
* The offset can point to either the PRIMARY or RECOVERY bootloader.
|
||||||
|
*
|
||||||
|
* @note The bootloader offset variable in ROM is stored in a memory that will be reclaimed by heap component.
|
||||||
|
* Read it before the heap is initialized, otherwise it may return an invalid value.
|
||||||
|
*
|
||||||
|
* @return The offset of the active bootloader.
|
||||||
|
*/
|
||||||
|
uint32_t ets_get_bootloader_offset(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the offset from which the bootloader image is used to load.
|
||||||
|
*
|
||||||
|
* The offset can point to either the PRIMARY or RECOVERY bootloader.
|
||||||
|
*
|
||||||
|
* @note The bootloader offset variable in ROM is stored in a memory that will be reclaimed by heap component.
|
||||||
|
* Setting it after the heap is initialized, may corrupt the heap memory.
|
||||||
|
*
|
||||||
|
* @param offset The offset value to set for the active bootloader.
|
||||||
|
*/
|
||||||
|
void ets_set_bootloader_offset(uint32_t offset);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @}
|
* @}
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -7,9 +7,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "sdkconfig.h"
|
#include "sdkconfig.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include "soc/reset_reasons.h"
|
#include "soc/reset_reasons.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@ -151,6 +153,17 @@ uint32_t esp_rom_get_cpu_ticks_per_us(void);
|
|||||||
*/
|
*/
|
||||||
void esp_rom_set_cpu_ticks_per_us(uint32_t ticks_per_us);
|
void esp_rom_set_cpu_ticks_per_us(uint32_t ticks_per_us);
|
||||||
|
|
||||||
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED || __DOXYGEN__
|
||||||
|
/**
|
||||||
|
* @brief Returns the offset from which the bootloader image is used to load.
|
||||||
|
*
|
||||||
|
* The offset can point to either the PRIMARY or RECOVERY bootloader.
|
||||||
|
*
|
||||||
|
* @return The offset of the active bootloader.
|
||||||
|
*/
|
||||||
|
uint32_t esp_rom_get_bootloader_offset(void);
|
||||||
|
#endif // SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -103,3 +103,14 @@ void esp_rom_set_cpu_ticks_per_us(uint32_t ticks_per_us)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif // CONFIG_IDF_TARGET_ESP32
|
#endif // CONFIG_IDF_TARGET_ESP32
|
||||||
|
|
||||||
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
uint32_t esp_rom_get_bootloader_offset(void)
|
||||||
|
{
|
||||||
|
static uint32_t offset_of_active_bootloader = UINT32_MAX;
|
||||||
|
if (offset_of_active_bootloader == UINT32_MAX) {
|
||||||
|
offset_of_active_bootloader = ets_get_bootloader_offset();
|
||||||
|
}
|
||||||
|
return offset_of_active_bootloader;
|
||||||
|
}
|
||||||
|
#endif // SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
@ -163,6 +163,17 @@ ESP_SYSTEM_INIT_FN(init_coexist, SECONDARY, BIT(0), 204)
|
|||||||
}
|
}
|
||||||
#endif // CONFIG_SW_COEXIST_ENABLE || CONFIG_EXTERNAL_COEX_ENABLE
|
#endif // CONFIG_SW_COEXIST_ENABLE || CONFIG_EXTERNAL_COEX_ENABLE
|
||||||
|
|
||||||
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
ESP_SYSTEM_INIT_FN(init_bootloader_offset, SECONDARY, BIT(0), 205)
|
||||||
|
{
|
||||||
|
// The bootloader offset variable in ROM is stored in a memory that will be reclaimed by heap component.
|
||||||
|
// Reading it before the heap is initialized helps to preserve the value.
|
||||||
|
volatile int bootloader_offset = esp_rom_get_bootloader_offset();
|
||||||
|
(void)bootloader_offset;
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
#endif // SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
|
||||||
#ifndef CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE
|
#ifndef CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE
|
||||||
ESP_SYSTEM_INIT_FN(init_disable_rtc_wdt, SECONDARY, BIT(0), 999)
|
ESP_SYSTEM_INIT_FN(init_disable_rtc_wdt, SECONDARY, BIT(0), 999)
|
||||||
{
|
{
|
||||||
|
@ -109,6 +109,7 @@ SECONDARY: 151: nvs_sec_provider_register_hmac_scheme in components/nvs_sec_prov
|
|||||||
SECONDARY: 201: init_pm in components/esp_system/startup_funcs.c on BIT(0)
|
SECONDARY: 201: init_pm in components/esp_system/startup_funcs.c on BIT(0)
|
||||||
SECONDARY: 203: init_apb_dma in components/esp_system/startup_funcs.c on BIT(0)
|
SECONDARY: 203: init_apb_dma in components/esp_system/startup_funcs.c on BIT(0)
|
||||||
SECONDARY: 204: init_coexist in components/esp_system/startup_funcs.c on BIT(0)
|
SECONDARY: 204: init_coexist in components/esp_system/startup_funcs.c on BIT(0)
|
||||||
|
SECONDARY: 205: init_bootloader_offset in components/esp_system/startup_funcs.c on BIT(0)
|
||||||
|
|
||||||
# usb_console needs to create an esp_timer at startup.
|
# usb_console needs to create an esp_timer at startup.
|
||||||
# This can be done only after esp_timer initialization (esp_timer_init_os).
|
# This can be done only after esp_timer initialization (esp_timer_init_os).
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -145,6 +145,11 @@ __attribute__((always_inline)) static inline void efuse_ll_set_ecdsa_key_blk(ecd
|
|||||||
EFUSE0.conf.cfg_ecdsa_blk = efuse_blk;
|
EFUSE0.conf.cfg_ecdsa_blk = efuse_blk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__attribute__((always_inline)) static inline uint32_t efuse_ll_get_recovery_bootloader_sector(void)
|
||||||
|
{
|
||||||
|
return EFUSE0.rd_repeat_data3.recovery_bootloader_flash_sector;
|
||||||
|
}
|
||||||
|
|
||||||
/******************* eFuse control functions *************************/
|
/******************* eFuse control functions *************************/
|
||||||
|
|
||||||
__attribute__((always_inline)) static inline bool efuse_ll_get_read_cmd(void)
|
__attribute__((always_inline)) static inline bool efuse_ll_get_read_cmd(void)
|
||||||
|
@ -1457,11 +1457,11 @@ config SOC_PSRAM_ENCRYPTION_XTS_AES_256
|
|||||||
|
|
||||||
config SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
config SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
bool
|
bool
|
||||||
default n
|
default y
|
||||||
|
|
||||||
config SOC_BOOTLOADER_ANTI_ROLLBACK_SUPPORTED
|
config SOC_BOOTLOADER_ANTI_ROLLBACK_SUPPORTED
|
||||||
bool
|
bool
|
||||||
default n
|
default y
|
||||||
|
|
||||||
config SOC_APM_CTRL_FILTER_SUPPORTED
|
config SOC_APM_CTRL_FILTER_SUPPORTED
|
||||||
bool
|
bool
|
||||||
|
@ -564,9 +564,9 @@
|
|||||||
|
|
||||||
/*------------------------Bootloader CAPS---------------------------------*/
|
/*------------------------Bootloader CAPS---------------------------------*/
|
||||||
/* Support Recovery Bootloader */
|
/* Support Recovery Bootloader */
|
||||||
#define SOC_RECOVERY_BOOTLOADER_SUPPORTED (0)
|
#define SOC_RECOVERY_BOOTLOADER_SUPPORTED (1)
|
||||||
/* Support Anti-rollback */
|
/* Support Anti-rollback */
|
||||||
#define SOC_BOOTLOADER_ANTI_ROLLBACK_SUPPORTED (0)
|
#define SOC_BOOTLOADER_ANTI_ROLLBACK_SUPPORTED (1)
|
||||||
|
|
||||||
/*-------------------------- APM CAPS-----------------------------------------*/
|
/*-------------------------- APM CAPS-----------------------------------------*/
|
||||||
#define SOC_APM_CTRL_FILTER_SUPPORTED 1 /*!< Support for APM control filter */
|
#define SOC_APM_CTRL_FILTER_SUPPORTED 1 /*!< Support for APM control filter */
|
||||||
|
@ -967,6 +967,14 @@ config SOC_FLASH_ENCRYPTION_XTS_AES_SUPPORT_PSEUDO_ROUND
|
|||||||
bool
|
bool
|
||||||
default y
|
default y
|
||||||
|
|
||||||
|
config SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
bool
|
||||||
|
default y
|
||||||
|
|
||||||
|
config SOC_BOOTLOADER_ANTI_ROLLBACK_SUPPORTED
|
||||||
|
bool
|
||||||
|
default y
|
||||||
|
|
||||||
config SOC_APM_CTRL_FILTER_SUPPORTED
|
config SOC_APM_CTRL_FILTER_SUPPORTED
|
||||||
bool
|
bool
|
||||||
default y
|
default y
|
||||||
|
@ -401,6 +401,12 @@
|
|||||||
#define SOC_FLASH_ENCRYPTION_XTS_AES_128 1
|
#define SOC_FLASH_ENCRYPTION_XTS_AES_128 1
|
||||||
#define SOC_FLASH_ENCRYPTION_XTS_AES_SUPPORT_PSEUDO_ROUND 1
|
#define SOC_FLASH_ENCRYPTION_XTS_AES_SUPPORT_PSEUDO_ROUND 1
|
||||||
|
|
||||||
|
/*------------------------Bootloader CAPS---------------------------------*/
|
||||||
|
/* Support Recovery Bootloader */
|
||||||
|
#define SOC_RECOVERY_BOOTLOADER_SUPPORTED (1)
|
||||||
|
/* Support Anti-rollback */
|
||||||
|
#define SOC_BOOTLOADER_ANTI_ROLLBACK_SUPPORTED (1)
|
||||||
|
|
||||||
/*-------------------------- APM CAPS ----------------------------------------*/
|
/*-------------------------- APM CAPS ----------------------------------------*/
|
||||||
#define SOC_APM_CTRL_FILTER_SUPPORTED 1 /*!< Support for APM control filter */
|
#define SOC_APM_CTRL_FILTER_SUPPORTED 1 /*!< Support for APM control filter */
|
||||||
#define SOC_APM_SUPPORT_CTRL_CFG_LOCK 1 /*!< Support for APM controller configuration lock */
|
#define SOC_APM_SUPPORT_CTRL_CFG_LOCK 1 /*!< Support for APM controller configuration lock */
|
||||||
|
@ -189,3 +189,67 @@ In the bootloader space, you cannot use the drivers and functions from other com
|
|||||||
* :example:`storage/nvs/nvs_bootloader`
|
* :example:`storage/nvs/nvs_bootloader`
|
||||||
|
|
||||||
If the bootloader grows too large then it can collide with the partition table, which is flashed at offset 0x8000 by default. Increase the :ref:`partition table offset <CONFIG_PARTITION_TABLE_OFFSET>` value to place the partition table later in the flash. This increases the space available for the bootloader.
|
If the bootloader grows too large then it can collide with the partition table, which is flashed at offset 0x8000 by default. Increase the :ref:`partition table offset <CONFIG_PARTITION_TABLE_OFFSET>` value to place the partition table later in the flash. This increases the space available for the bootloader.
|
||||||
|
|
||||||
|
.. only:: SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
|
||||||
|
Recovery Bootloader
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
The {IDF_TARGET_NAME} introduces Recovery Bootloader and Anti-rollback Bootloader features, implemented in the ROM bootloader to enhance device security and reliability during OTA updates.
|
||||||
|
|
||||||
|
The recovery bootloader feature enables safe OTA updates of the bootloader itself. When the eFuse field ``ESP_EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR`` is set, it specifies the flash address (in sectors) of the recovery bootloader. If the primary bootloader at {IDF_TARGET_CONFIG_BOOTLOADER_OFFSET_IN_FLASH} fails to load, the ROM bootloader attempts to load the recovery bootloader from this address.
|
||||||
|
|
||||||
|
- The eFuse can be programmed using ``espefuse.py`` or via the user application using :cpp:func:`esp_efuse_set_recovery_bootloader_offset()`.
|
||||||
|
- The address can be set via the ``CONFIG_BOOTLOADER_RECOVERY_OFFSET``, it must be a multiple of the flash sector size (0x1000 bytes). This Kconfig option helps ensure the recovery bootloader does not overlap with existing partitions.
|
||||||
|
- Note that the eFuse field stores the offset in sectors. Setting it to the maximum value ``0xFFF`` disables the feature.
|
||||||
|
- The recovery bootloader image at the ``CONFIG_BOOTLOADER_RECOVERY_OFFSET`` is not flashed by default. It can be written as part of the OTA update process.
|
||||||
|
|
||||||
|
The example below shows the bootloader log when the primary bootloader fails to load and the recovery bootloader is loaded instead.
|
||||||
|
|
||||||
|
.. code-block:: none
|
||||||
|
|
||||||
|
ESP-ROM:esp32c5-eco2-20250121
|
||||||
|
Build:Jan 21 2025
|
||||||
|
rst:0x1 (POWERON),boot:0x18 (SPI_FAST_FLASH_BOOT)
|
||||||
|
invalid header: 0xffffffff
|
||||||
|
invalid header: 0xffffffff
|
||||||
|
invalid header: 0xffffffff
|
||||||
|
PRIMARY - FAIL
|
||||||
|
Loading RECOVERY Bootloader...
|
||||||
|
SPI mode:DIO, clock div:1
|
||||||
|
load:0x408556b0,len:0x17cc
|
||||||
|
load:0x4084bba0,len:0xdac
|
||||||
|
load:0x4084e5a0,len:0x3140
|
||||||
|
entry 0x4084bbaa
|
||||||
|
|
||||||
|
I (46) boot: ESP-IDF v6.0-dev-172-g12c5d730097-dirty 2nd stage bootloader
|
||||||
|
I (46) boot: compile time May 22 2025 12:41:59
|
||||||
|
I (47) boot: chip revision: v1.0
|
||||||
|
I (48) boot: efuse block revision: v0.1
|
||||||
|
I (52) boot.esp32c5: SPI Speed : 80MHz
|
||||||
|
I (55) boot.esp32c5: SPI Mode : DIO
|
||||||
|
I (59) boot.esp32c5: SPI Flash Size : 4MB
|
||||||
|
I (63) boot: Enabling RNG early entropy source...
|
||||||
|
I (67) boot: Partition Table:
|
||||||
|
...
|
||||||
|
|
||||||
|
Anti-Rollback Feature
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The anti-rollback feature prevents downgrading to an older, potentially vulnerable bootloader version. The bootloader header includes a security version, defined by ``CONFIG_BOOTLOADER_SECURE_VERSION``. When ``EFUSE_BOOTLOADER_ANTI_ROLLBACK_EN`` is set, the ROM bootloader checks this version against the value stored in ``EFUSE_BOOTLOADER_ANTI_ROLLBACK_SECURE_VERSION``. Only bootloaders with a version greater than or equal to the eFuse value are allowed to boot.
|
||||||
|
|
||||||
|
- The ROM bootloader can update the secure version in eFuse if ``EFUSE_BOOTLOADER_ANTI_ROLLBACK_SECURE_VERSION_UPDATE_IN_ROM`` is set.
|
||||||
|
- The secure version value is incremented as new bootloader versions are deployed, and cannot be decreased.
|
||||||
|
- If the secure version in eFuse is not updated in the ROM bootloader, then the application can update it using the :cpp:func:`esp_efuse_write_field_blob` function.
|
||||||
|
|
||||||
|
Relevant eFuses
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- ``EFUSE_RECOVERY_BOOTLOADER_FLASH_SECTOR`` (12 bits): Flash sector address for the recovery bootloader. Default value is 0 (disabled), set any other value to enable, 0xFFF to permanently disable.
|
||||||
|
- ``EFUSE_BOOTLOADER_ANTI_ROLLBACK_EN`` (1 bit): Enables anti-rollback check in the ROM bootloader.
|
||||||
|
- ``EFUSE_BOOTLOADER_ANTI_ROLLBACK_SECURE_VERSION`` (4 bits): Secure version for anti-rollback protection. The value increases as bits are set - 0x0, 0x1, 0x3, 0x7, 0xF.
|
||||||
|
- ``EFUSE_BOOTLOADER_ANTI_ROLLBACK_SECURE_VERSION_UPDATE_IN_ROM`` (1 bit): Allows the ROM bootloader to update the secure version in eFuse.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Use these features to improve device security and reliability during OTA updates. Carefully plan eFuse programming, as these settings are permanent and may affect future update strategies.
|
||||||
|
@ -191,10 +191,16 @@ examples/system/ota/partitions_ota:
|
|||||||
- if: IDF_TARGET in ["esp32h2", "esp32h21", "esp32h4"]
|
- if: IDF_TARGET in ["esp32h2", "esp32h21", "esp32h4"]
|
||||||
temporary: true
|
temporary: true
|
||||||
reason: not supported yet
|
reason: not supported yet
|
||||||
|
- if: CONFIG_NAME == "recovery_bootloader" and SOC_RECOVERY_BOOTLOADER_SUPPORTED != 1
|
||||||
|
temporary: true
|
||||||
|
reason: disabled for targets that do not support recovery bootloader
|
||||||
disable_test:
|
disable_test:
|
||||||
- if: IDF_TARGET not in ["esp32", "esp32c3", "esp32s3"]
|
- if: IDF_TARGET not in ["esp32", "esp32c3", "esp32s3"]
|
||||||
temporary: true
|
temporary: true
|
||||||
reason: lack of runners
|
reason: lack of runners
|
||||||
|
- if: CONFIG_NAME == "recovery_bootloader" and SOC_RECOVERY_BOOTLOADER_SUPPORTED == 1
|
||||||
|
temporary: true
|
||||||
|
reason: lack of runners with recovery_bootloader lablel
|
||||||
depends_components:
|
depends_components:
|
||||||
- app_update
|
- app_update
|
||||||
- esp_https_ota
|
- esp_https_ota
|
||||||
|
@ -22,7 +22,42 @@ Application OTA updates use one active and one passive partition. The new image
|
|||||||
|
|
||||||
### Bootloader
|
### Bootloader
|
||||||
|
|
||||||
Bootloader OTA updates are not inherently safe because the ROM bootloader does not support fallback to a recovery bootloader partition. Only the primary bootloader partition can be loaded by the ROM bootloader. Updating the bootloader is rarely necessary, and it is generally not recommended. However, if required, it can be done using the following approaches:
|
Updating the bootloader is rarely necessary, and it is generally not recommended. However, if required, it can be done using the following approaches, depending on chip support:
|
||||||
|
|
||||||
|
- **Safe OTA Update (with Recovery Bootloader):** On chips that support the recovery bootloader feature in the ROM, a backup of the original bootloader is created in a dedicated recovery partition before updating. If the update fails or power is lost, the device can boot from this recovery bootloader, reducing the risk of bricking. The recovery bootloader partition and its offset must be configured in the partition table and eFuse.
|
||||||
|
|
||||||
|
- Enable `CONFIG_BOOTLOADER_RECOVERY_ENABLE` and set the reocevery bootloader offset in `CONFIG_BOOTLOADER_RECOVERY_OFFSET`.
|
||||||
|
- Ensure that both the primary and recovery bootloader partitions are defined in the partition table (see `test/partitions_efuse_emul_2.csv`). If these entries are missing, the application can register them automatically, so manual addition is optional.
|
||||||
|
- Backup the primary bootloader to the recovery bootloader.
|
||||||
|
- Download directly into the primary bootloader partition or into a passive app partition.
|
||||||
|
|
||||||
|
#### Example: ROM Bootloader Fallback to Recovery Bootloader
|
||||||
|
|
||||||
|
Below is a sample log output demonstrating the ROM bootloader behavior when the primary bootloader is corrupted or fails to load, and the device successfully falls back to the recovery bootloader partition. This mechanism helps prevent device bricking in the event of a failed bootloader update.
|
||||||
|
|
||||||
|
```
|
||||||
|
ESP-ROM:esp32c5-eco2-20250121
|
||||||
|
Build:Jan 21 2025
|
||||||
|
rst:0x1 (POWERON),boot:0x18 (SPI_FAST_FLASH_BOOT)
|
||||||
|
invalid header: 0xffffffff
|
||||||
|
invalid header: 0xffffffff
|
||||||
|
invalid header: 0xffffffff
|
||||||
|
PRIMARY - FAIL
|
||||||
|
Loading RECOVERY Bootloader...
|
||||||
|
SPI mode:DIO, clock div:1
|
||||||
|
load:0x408556b0,len:0x17cc
|
||||||
|
load:0x4084bba0,len:0xdac
|
||||||
|
load:0x4084e5a0,len:0x3140
|
||||||
|
entry 0x4084bbaa
|
||||||
|
I (46) boot: ESP-IDF v6.0-dev-172-g12c5d730097-dirty 2nd stage bootloader
|
||||||
|
I (46) boot: compile time May 22 2025 12:41:59
|
||||||
|
I (47) boot: chip revision: v1.0
|
||||||
|
I (48) boot: efuse block revision: v0.1
|
||||||
|
I (52) boot.esp32c5: SPI Speed : 80MHz
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Unsafe OTA Update (ROM bootloader does not support the Recovery Bootloader):** Bootloader OTA updates are not inherently safe because the ROM bootloader does not support fallback to a recovery bootloader partition. Only the primary bootloader partition can be loaded by the ROM bootloader. On chips without recovery bootloader support, the new bootloader is downloaded to a staging partition and then copied to the primary bootloader partition. If power is lost during the final copy, the device may become unbootable.
|
||||||
|
|
||||||
- Register the primary bootloader partition in the partition table, if not already present (see `test/partitions_efuse_emul_2.csv`).
|
- Register the primary bootloader partition in the partition table, if not already present (see `test/partitions_efuse_emul_2.csv`).
|
||||||
- Decide where to download the new bootloader image:
|
- Decide where to download the new bootloader image:
|
||||||
@ -32,9 +67,11 @@ Bootloader OTA updates are not inherently safe because the ROM bootloader does n
|
|||||||
|
|
||||||
After verification, if `finalize_with_copy` is set to `true`, the tool will automatically copy the new image to the primary bootloader partition. Set `finalize_with_copy` to `false` if you wish to control the final copy step manually.
|
After verification, if `finalize_with_copy` is set to `true`, the tool will automatically copy the new image to the primary bootloader partition. Set `finalize_with_copy` to `false` if you wish to control the final copy step manually.
|
||||||
|
|
||||||
Limitations for Bootloader OTA updates:
|
**Limitations:**
|
||||||
- Secure Boot V1-enabled devices do not support bootloader updates.
|
- Secure Boot V1-enabled devices do not support bootloader updates.
|
||||||
- There is always a risk of device bricking when updating the bootloader.
|
- There is always a risk of device bricking when updating the bootloader, especially if power is lost during the final copy step to the primary bootloader.
|
||||||
|
|
||||||
|
On chips that support a recovery bootloader feature, a backup of the original bootloader can be created in a dedicated recovery partition before updating. If the update fails or power is lost, the device is able to boot from this recovery bootloader, reducing the risk of bricking. The recovery bootloader partition and its offset must be configured in the partition table and eFuse.
|
||||||
|
|
||||||
### Partition Table
|
### Partition Table
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||||
*/
|
*/
|
||||||
@ -14,6 +14,7 @@
|
|||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
|
#include "esp_check.h"
|
||||||
#include "esp_event.h"
|
#include "esp_event.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_ota_ops.h"
|
#include "esp_ota_ops.h"
|
||||||
@ -27,7 +28,10 @@
|
|||||||
#include "esp_flash.h"
|
#include "esp_flash.h"
|
||||||
#include "esp_flash_partitions.h"
|
#include "esp_flash_partitions.h"
|
||||||
#include "esp_partition.h"
|
#include "esp_partition.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED
|
||||||
|
#include "esp_efuse.h"
|
||||||
|
#endif
|
||||||
#include "nvs.h"
|
#include "nvs.h"
|
||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
#include "protocol_examples_common.h"
|
#include "protocol_examples_common.h"
|
||||||
@ -102,6 +106,67 @@ static esp_err_t register_partition(size_t offset, size_t size, const char *labe
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if CONFIG_BOOTLOADER_RECOVERY_ENABLE
|
||||||
|
static esp_err_t safe_bootloader_ota_update(esp_https_ota_config_t *ota_config)
|
||||||
|
{
|
||||||
|
const esp_partition_t *primary_bootloader;
|
||||||
|
const esp_partition_t *recovery_bootloader;
|
||||||
|
ESP_ERROR_CHECK(register_partition(ESP_PRIMARY_BOOTLOADER_OFFSET, ESP_BOOTLOADER_SIZE, "PrimaryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_PRIMARY, &primary_bootloader));
|
||||||
|
ESP_ERROR_CHECK(register_partition(CONFIG_BOOTLOADER_RECOVERY_OFFSET, ESP_BOOTLOADER_SIZE, "RecoveryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_RECOVERY, &recovery_bootloader));
|
||||||
|
ESP_RETURN_ON_FALSE(recovery_bootloader->address == CONFIG_BOOTLOADER_RECOVERY_OFFSET, ESP_FAIL, TAG,
|
||||||
|
"The partition table contains <%s> (0x%08" PRIx32 "), which does not match the efuse recovery address (0x%08" PRIx32 ")", recovery_bootloader->label, recovery_bootloader->address, CONFIG_BOOTLOADER_RECOVERY_OFFSET);
|
||||||
|
// Since the recovery boot partition is registered successfully, we are sure that the flash memory size is enough to store it.
|
||||||
|
ESP_ERROR_CHECK(esp_efuse_set_recovery_bootloader_offset(CONFIG_BOOTLOADER_RECOVERY_OFFSET));
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "Backup, copy <%s> -> <%s>", primary_bootloader->label, recovery_bootloader->label);
|
||||||
|
ESP_ERROR_CHECK(esp_partition_copy(recovery_bootloader, 0, primary_bootloader, 0, primary_bootloader->size));
|
||||||
|
|
||||||
|
ota_config->partition.staging = primary_bootloader;
|
||||||
|
esp_err_t ret = esp_https_ota(ota_config);
|
||||||
|
if (ret == ESP_OK) {
|
||||||
|
// If the recovery_bootloader or primary_bootloader already exists in the partition table on flash, it will not be deregistered, and the function will return an error.
|
||||||
|
esp_partition_deregister_external(recovery_bootloader);
|
||||||
|
esp_partition_deregister_external(primary_bootloader);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#else // !CONFIG_BOOTLOADER_RECOVERY_ENABLE
|
||||||
|
|
||||||
|
static esp_err_t unsafe_bootloader_ota_update(esp_https_ota_config_t *ota_config)
|
||||||
|
{
|
||||||
|
const esp_partition_t *primary_bootloader;
|
||||||
|
ESP_ERROR_CHECK(register_partition(ESP_PRIMARY_BOOTLOADER_OFFSET, ESP_BOOTLOADER_SIZE, "PrimaryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_PRIMARY, &primary_bootloader));
|
||||||
|
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL); // free app ota partition will be used for downloading a new image
|
||||||
|
#if CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
|
||||||
|
// Check if the passive OTA app partition is not needed for rollback before using it for other partitions.
|
||||||
|
// The same can be done for partition table and storage updates.
|
||||||
|
esp_ota_img_states_t ota_state;
|
||||||
|
ESP_ERROR_CHECK(esp_ota_get_state_partition(ota_partition, &ota_state));
|
||||||
|
if (ota_state == ESP_OTA_IMG_VALID) {
|
||||||
|
ESP_LOGW(TAG, "Passive OTA app partition <%s> contains a valid app image eligible for rollback.", ota_partition->label);
|
||||||
|
uint32_t ota_bootloader_offset;
|
||||||
|
ESP_ERROR_CHECK(partition_utils_find_unallocated(NULL, ESP_BOOTLOADER_SIZE, ESP_PARTITION_TABLE_OFFSET + ESP_PARTITION_TABLE_SIZE, &ota_bootloader_offset, NULL));
|
||||||
|
ESP_ERROR_CHECK(register_partition(ota_bootloader_offset, ESP_BOOTLOADER_SIZE, "OtaBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_OTA, &ota_partition));
|
||||||
|
ESP_LOGW(TAG, "To avoid overwriting the passive app partition, using the unallocated space on the flash to create a temporary OTA bootloader partition <%s>", ota_partition->label);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ota_config->partition.staging = ota_partition;
|
||||||
|
ota_config->partition.final = primary_bootloader;
|
||||||
|
esp_err_t ret = esp_https_ota(ota_config);
|
||||||
|
if (ret == ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Ensure stable power supply! Loss of power at this stage leads to a chip bricking");
|
||||||
|
ESP_LOGI(TAG, "Copy from <%s> staging partition to <%s>...", ota_partition->label, primary_bootloader->label);
|
||||||
|
ret = esp_partition_copy(primary_bootloader, 0, ota_partition, 0, primary_bootloader->size);
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to copy partition to Primary bootloader (err=0x%x). Bootloader likely corrupted. Device will not be able to boot again!", ret);
|
||||||
|
}
|
||||||
|
// If the primary_bootloader already exists in the partition table on flash, it will not be deregistered, and the function will return an error.
|
||||||
|
esp_partition_deregister_external(primary_bootloader);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif // !CONFIG_BOOTLOADER_RECOVERY_ENABLE
|
||||||
|
|
||||||
static esp_err_t ota_update_partitions(esp_https_ota_config_t *ota_config)
|
static esp_err_t ota_update_partitions(esp_https_ota_config_t *ota_config)
|
||||||
{
|
{
|
||||||
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
|
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
|
||||||
@ -109,35 +174,16 @@ static esp_err_t ota_update_partitions(esp_https_ota_config_t *ota_config)
|
|||||||
ret = esp_https_ota(ota_config);
|
ret = esp_https_ota(ota_config);
|
||||||
|
|
||||||
} else if (strstr(ota_config->http_config->url, "bootloader.bin") != NULL) {
|
} else if (strstr(ota_config->http_config->url, "bootloader.bin") != NULL) {
|
||||||
const esp_partition_t *primary_bootloader;
|
#if CONFIG_BOOTLOADER_RECOVERY_ENABLE
|
||||||
ESP_ERROR_CHECK(register_partition(ESP_PRIMARY_BOOTLOADER_OFFSET, ESP_BOOTLOADER_SIZE, "PrimaryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_PRIMARY, &primary_bootloader));
|
ESP_LOGI(TAG, "Safe OTA bootloader update: This chip version supports the recovery bootloader feature.");
|
||||||
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL); // free app ota partition will be used for downloading a new image
|
ESP_LOGI(TAG, "If a failure or power loss occurs during the update, the recovery bootloader will be used.");
|
||||||
#if CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
|
ESP_LOGI(TAG, "The recovery bootloader contains a backup of the original bootloader created before the OTA update.");
|
||||||
// Check if the passive OTA app partition is not needed for rollback before using it for other partitions.
|
ret = safe_bootloader_ota_update(ota_config);
|
||||||
// The same can be done for partition table and storage updates.
|
#else
|
||||||
esp_ota_img_states_t ota_state;
|
ESP_LOGW(TAG, "Unsafe OTA bootloader update: This chip version does not support the recovery bootloader feature.");
|
||||||
ESP_ERROR_CHECK(esp_ota_get_state_partition(ota_partition, &ota_state));
|
ESP_LOGW(TAG, "If a failure or power loss occurs during the final copy, the chip may become unbootable.");
|
||||||
if (ota_state == ESP_OTA_IMG_VALID) {
|
ret = unsafe_bootloader_ota_update(ota_config);
|
||||||
ESP_LOGW(TAG, "Passive OTA app partition <%s> contains a valid app image eligible for rollback.", ota_partition->label);
|
|
||||||
uint32_t ota_bootloader_offset;
|
|
||||||
ESP_ERROR_CHECK(partition_utils_find_unallocated(NULL, ESP_BOOTLOADER_SIZE, ESP_PARTITION_TABLE_OFFSET + ESP_PARTITION_TABLE_SIZE, &ota_bootloader_offset, NULL));
|
|
||||||
ESP_ERROR_CHECK(register_partition(ota_bootloader_offset, ESP_BOOTLOADER_SIZE, "OtaBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_OTA, &ota_partition));
|
|
||||||
ESP_LOGW(TAG, "To avoid overwriting the passive app partition, using the unallocated space on the flash to create a temporary OTA bootloader partition <%s>", ota_partition->label);
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
ota_config->partition.staging = ota_partition;
|
|
||||||
ota_config->partition.final = primary_bootloader;
|
|
||||||
ret = esp_https_ota(ota_config);
|
|
||||||
if (ret == ESP_OK) {
|
|
||||||
ESP_LOGW(TAG, "Ensure stable power supply! Loss of power at this stage leads to a chip bricking");
|
|
||||||
ESP_LOGI(TAG, "Copy from <%s> staging partition to <%s>...", ota_partition->label, primary_bootloader->label);
|
|
||||||
ret = esp_partition_copy(primary_bootloader, 0, ota_partition, 0, primary_bootloader->size);
|
|
||||||
if (ret != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "Failed to copy partition to Primary bootloader (err=0x%x). Bootloader likely corrupted. Device will not be able to boot again!", ret);
|
|
||||||
}
|
|
||||||
// If the primary_bootloader already exists in the partition table on flash, it will not be deregistered, and the function will return an error.
|
|
||||||
esp_partition_deregister_external(primary_bootloader);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (strstr(ota_config->http_config->url, "partition-table.bin") != NULL) {
|
} else if (strstr(ota_config->http_config->url, "partition-table.bin") != NULL) {
|
||||||
const esp_partition_t *primary_partition_table;
|
const esp_partition_t *primary_partition_table;
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
CONFIG_BOOTLOADER_RECOVERY_ENABLE=y
|
||||||
|
CONFIG_BOOTLOADER_RECOVERY_OFFSET=0x3F0000
|
||||||
|
|
||||||
|
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||||
|
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="test/partitions_efuse_emul_3.csv"
|
@ -0,0 +1,2 @@
|
|||||||
|
# ESP32C5 supports the Recovery bootloader feature in ROM starting from v1.0 (ECO2)
|
||||||
|
CONFIG_IDF_TARGET="esp32c5"
|
@ -0,0 +1,12 @@
|
|||||||
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
|
PrimaryBTLDR, bootloader, primary, N/A, N/A,
|
||||||
|
PrimaryPrtTable, partition_table, primary, N/A, N/A,
|
||||||
|
nvs, data, nvs, , 0x6000,
|
||||||
|
nvs_key, data, nvs_keys, , 4K,
|
||||||
|
storage, data, , , 0x1000, encrypted
|
||||||
|
otadata, data, ota, , 0x2000,
|
||||||
|
phy_init, data, phy, , 0x1000,
|
||||||
|
emul_efuse, data, efuse, , 0x2000,
|
||||||
|
ota_0, app, ota_0, , 0x1B0000,
|
||||||
|
ota_1, app, ota_1, , 0x1B0000,
|
||||||
|
RecoveryBTLDR, bootloader, recovery, N/A, N/A,
|
|
@ -147,6 +147,7 @@ ENV_MARKERS = {
|
|||||||
'ram_app': 'ram_app runners',
|
'ram_app': 'ram_app runners',
|
||||||
'esp32c3eco7': 'esp32c3 major version(v1.1) chips',
|
'esp32c3eco7': 'esp32c3 major version(v1.1) chips',
|
||||||
'esp32c2eco4': 'esp32c2 major version(v2.0) chips',
|
'esp32c2eco4': 'esp32c2 major version(v2.0) chips',
|
||||||
|
'recovery_bootloader': 'Runner with recovery bootloader offset set in eFuse',
|
||||||
}
|
}
|
||||||
|
|
||||||
# by default the timeout is 1h, for some special cases we need to extend it
|
# by default the timeout is 1h, for some special cases we need to extend it
|
||||||
|
Reference in New Issue
Block a user