Skip to content

Commit 996a9ca

Browse files
nileshkale123mahavirj
authored andcommitted
fix(app_update): Do not change ota_seq when partition is the same
Revised logic to always update non-running otadata at the time of setting ota boot partition Closes #14688
1 parent 081de6d commit 996a9ca

File tree

3 files changed

+159
-37
lines changed

3 files changed

+159
-37
lines changed

components/app_update/esp_ota_ops.c

Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#include "esp_flash.h"
3434
#include "esp_flash_internal.h"
3535

36-
#define SUB_TYPE_ID(i) (i & 0x0F)
36+
#define OTA_SLOT(i) (i & 0x0F)
3737
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
3838

3939
/* Partial_data is word aligned so no reallocation is necessary for encrypted flash write */
@@ -539,6 +539,69 @@ static esp_err_t rewrite_ota_seq(esp_ota_select_entry_t *two_otadata, uint32_t s
539539
}
540540
}
541541

542+
/**
543+
* @brief Calculate the next OTA sequence number that will boot the given OTA slot.
544+
*
545+
* Based on the ESP-IDF OTA boot scheme, the system selects the OTA slot to boot by:
546+
* boot_slot = (seq - 1) % ota_app_count
547+
*
548+
* This function determines the required seq value that would cause the given ota_slot_idx
549+
* to be selected on next boot.
550+
*
551+
* @param current_seq Current active OTA sequence number
552+
* @param ota_slot_idx Target OTA slot index (0-based)
553+
* @param ota_app_count Total number of OTA slots
554+
*
555+
* @return New sequence number that will result in booting ota_slot_idx
556+
*/
557+
static uint32_t compute_ota_seq_for_target_slot(uint32_t current_seq, uint32_t ota_slot_idx, uint8_t ota_app_count)
558+
{
559+
if (ota_app_count == 0) {
560+
return 0;
561+
}
562+
/* ESP-IDF stores OTA boot information in the OTA data partition, which consists of two sectors.
563+
* Each sector holds an esp_ota_select_entry_t structure: otadata[0] and otadata[1].
564+
* These structures record the OTA sequence number (ota_seq) used to determine the current boot partition.
565+
*
566+
* Boot selection logic:
567+
* - If both otadata[0].ota_seq and otadata[1].ota_seq are 0xFFFFFFFF (invalid), it is the initial state:
568+
* → Boot the factory app, if it exists.
569+
* → Otherwise, fall back to booting ota[0].
570+
*
571+
* - If both otadata entries have valid sequence numbers and CRCs:
572+
* → Choose the higher sequence number (max_seq).
573+
* → Determine the OTA partition for boot (or running partition) using:
574+
* running_ota_slot = (max_seq - 1) % ota_app_count
575+
* where ota_app_count is the total number of OTA app partitions.
576+
*
577+
* Example:
578+
* otadata[0].ota_seq = 4
579+
* otadata[1].ota_seq = 5
580+
* ota_app_count = 8 (available OTA slots: ota_0 to ota_7)
581+
* → max_seq = 5
582+
* → running slot = (5 - 1) % 8 = 4
583+
* → So ota_4 is currently running
584+
*
585+
* If you want to switch to boot a different OTA slot (e.g., ota_7):
586+
* → You need to compute a new sequence number such that:
587+
* (new_seq - 1) % ota_app_count == 7
588+
* while ensuring new_seq > current_seq.
589+
*
590+
* General formula:
591+
* x = current OTA slot ID
592+
* ota_slot_idx = desired OTA slot ID
593+
* seq = current ota_seq
594+
*
595+
* To find the next ota_seq that will boot ota_y, use:
596+
* new_seq = ((ota_slot_idx + 1) % ota_app_count) + ota_app_count * i;
597+
* // where i is the smallest non-negative integer such that new_seq > seq
598+
*/
599+
uint32_t i = 0;
600+
uint32_t base = (ota_slot_idx + 1) % ota_app_count;
601+
while (current_seq > (base + i * ota_app_count)) { i++; };
602+
return base + i * ota_app_count;
603+
}
604+
542605
uint8_t esp_ota_get_app_partition_count(void)
543606
{
544607
uint16_t ota_app_count = 0;
@@ -549,6 +612,30 @@ uint8_t esp_ota_get_app_partition_count(void)
549612
return ota_app_count;
550613
}
551614

615+
/**
616+
* @brief Update the OTA data partition to set the given OTA app subtype as the next boot target.
617+
*
618+
* ESP-IDF uses the OTA data partition to track which OTA app should boot.
619+
* This partition contains two entries (otadata[0] and otadata[1]), each storing an esp_ota_select_entry_t struct,
620+
* which includes the OTA sequence number (ota_seq).
621+
*
622+
* On boot, the chip determines the current running OTA slot using:
623+
* current_slot = (max(ota_seq) - 1) % ota_app_count
624+
*
625+
* This function updates the OTA data to switch the next boot to the partition with the given subtype.
626+
*
627+
* Behavior:
628+
* - If the currently selected OTA slot already matches the requested subtype,
629+
* only the state field is updated (e.g., to mark the app as newly downloaded).
630+
* - Otherwise, it calculates the next valid ota_seq that will cause the bootloader to select
631+
* the requested OTA slot on reboot, and writes it to the inactive OTA data sector.
632+
*
633+
* @param subtype The OTA partition subtype (e.g., ESP_PARTITION_SUBTYPE_APP_OTA_0, ..._OTA_1, ...)
634+
* @return
635+
* - ESP_OK if update was successful
636+
* - ESP_ERR_NOT_FOUND if OTA data partition not found
637+
* - ESP_ERR_INVALID_ARG if subtype is out of range
638+
*/
552639
static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
553640
{
554641
esp_ota_select_entry_t otadata[2];
@@ -558,42 +645,31 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
558645
}
559646

560647
uint8_t ota_app_count = esp_ota_get_app_partition_count();
561-
if (SUB_TYPE_ID(subtype) >= ota_app_count) {
648+
if (OTA_SLOT(subtype) >= ota_app_count) {
562649
return ESP_ERR_INVALID_ARG;
563650
}
564-
565-
//esp32_idf use two sector for store information about which partition is running
566-
//it defined the two sector as ota data partition,two structure esp_ota_select_entry_t is saved in the two sector
567-
//named data in first sector as otadata[0], second sector data as otadata[1]
568-
//e.g.
569-
//if otadata[0].ota_seq == otadata[1].ota_seq == 0xFFFFFFFF,means ota info partition is in init status
570-
//so it will boot factory application(if there is),if there's no factory application,it will boot ota[0] application
571-
//if otadata[0].ota_seq != 0 and otadata[1].ota_seq != 0,it will choose a max seq ,and get value of max_seq%max_ota_app_number
572-
//and boot a subtype (mask 0x0F) value is (max_seq - 1)%max_ota_app_number,so if want switch to run ota[x],can use next formulas.
573-
//for example, if otadata[0].ota_seq = 4, otadata[1].ota_seq = 5, and there are 8 ota application,
574-
//current running is (5-1)%8 = 4,running ota[4],so if we want to switch to run ota[7],
575-
//we should add otadata[0].ota_seq (is 4) to 4 ,(8-1)%8=7,then it will boot ota[7]
576-
//if A=(B - C)%D
577-
//then B=(A + C)%D + D*n ,n= (0,1,2...)
578-
//so current ota app sub type id is x , dest bin subtype is y,total ota app count is n
579-
//seq will add (x + n*1 + 1 - seq)%n
580-
581651
int active_otadata = bootloader_common_get_active_otadata(otadata);
652+
int next_otadata;
653+
uint32_t new_seq;
582654
if (active_otadata != -1) {
583-
uint32_t seq = otadata[active_otadata].ota_seq;
584-
uint32_t i = 0;
585-
while (seq > (SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count) {
586-
i++;
655+
uint32_t ota_slot = (otadata[active_otadata].ota_seq - 1) % ota_app_count;
656+
if (ota_slot == OTA_SLOT(subtype)) {
657+
// ota_data is already valid and points to the correct OTA slot.
658+
// So after reboot the requested partition will be selected for boot.
659+
// Only update the ota_state of the requested partition.
660+
next_otadata = active_otadata;
661+
new_seq = otadata[active_otadata].ota_seq;
662+
} else {
663+
next_otadata = (~active_otadata) & 1; // if 0 -> will be next 1. and if 1 -> will be next 0.
664+
new_seq = compute_ota_seq_for_target_slot(otadata[active_otadata].ota_seq, OTA_SLOT(subtype), ota_app_count);
587665
}
588-
int next_otadata = (~active_otadata)&1; // if 0 -> will be next 1. and if 1 -> will be next 0.
589-
otadata[next_otadata].ota_state = set_new_state_otadata();
590-
return rewrite_ota_seq(otadata, (SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, next_otadata, otadata_partition);
591666
} else {
592667
/* Both OTA slots are invalid, probably because unformatted... */
593-
int next_otadata = 0;
594-
otadata[next_otadata].ota_state = set_new_state_otadata();
595-
return rewrite_ota_seq(otadata, SUB_TYPE_ID(subtype) + 1, next_otadata, otadata_partition);
668+
next_otadata = 0;
669+
new_seq = OTA_SLOT(subtype) + 1;
596670
}
671+
otadata[next_otadata].ota_state = set_new_state_otadata();
672+
return rewrite_ota_seq(otadata, new_seq, next_otadata, otadata_partition);
597673
}
598674

599675
esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)

components/app_update/test_apps/test_app_update/main/test_switch_ota.c

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
2+
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
33
*
44
* SPDX-License-Identifier: Apache-2.0
55
*/
@@ -204,15 +204,13 @@ static const esp_partition_t* get_running_firmware(void)
204204
{
205205
const esp_partition_t *configured = esp_ota_get_boot_partition();
206206
const esp_partition_t *running = esp_ota_get_running_partition();
207+
// If a reboot hasn't occurred after app_update(), the configured and running partitions may differ
207208
ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08"PRIx32")",
208209
running->type, running->subtype, running->address);
209210
ESP_LOGI(TAG, "Configured partition type %d subtype %d (offset 0x%08"PRIx32")",
210211
configured->type, configured->subtype, configured->address);
211212
TEST_ASSERT_NOT_EQUAL(NULL, configured);
212213
TEST_ASSERT_NOT_EQUAL(NULL, running);
213-
if (running->subtype != ESP_PARTITION_SUBTYPE_APP_TEST) {
214-
TEST_ASSERT_EQUAL_PTR(running, configured);
215-
}
216214
return running;
217215
}
218216

@@ -930,3 +928,51 @@ static void test_rollback3_1(void)
930928
}
931929

932930
TEST_CASE_MULTIPLE_STAGES("Test rollback. Updated partition invalidated after esp_ota_begin", "[app_update][timeout=90][reset=DEEPSLEEP_RESET, DEEPSLEEP_RESET, DEEPSLEEP_RESET, SW_CPU_RESET]", start_test, test_rollback3, test_rollback3, test_rollback3, test_rollback3_1);
931+
932+
static void test_rollback4(void)
933+
{
934+
uint8_t boot_count = get_boot_count_from_nvs();
935+
boot_count++;
936+
set_boot_count_in_nvs(boot_count);
937+
ESP_LOGI(TAG, "boot count %d", boot_count);
938+
const esp_partition_t *cur_app = get_running_firmware();
939+
switch (boot_count) {
940+
case 2:
941+
ESP_LOGI(TAG, "Factory");
942+
TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_FACTORY, cur_app->subtype);
943+
app_update();
944+
reboot_as_deep_sleep();
945+
break;
946+
case 3:
947+
ESP_LOGI(TAG, "OTA0");
948+
TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype);
949+
TEST_ESP_OK(esp_ota_mark_app_valid_cancel_rollback());
950+
app_update();
951+
952+
// Do not reboot and call app_update again.
953+
// This will not change the running partition since we haven't rebooted.
954+
// The esp_rewrite_otadata() will update the otadata for the non-running partition only.
955+
app_update();
956+
#ifdef CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
957+
// The last call to esp_rewrite_otadata should have updated the otadata for the non-running partition only.
958+
// Therefore, calling esp_ota_get_state_partition on the running partition should succeed and not return ESP_ERR_NOT_FOUND
959+
const esp_partition_t* running_partition;
960+
running_partition = esp_ota_get_running_partition();
961+
esp_ota_img_states_t ota_state;
962+
TEST_ESP_OK(esp_ota_get_state_partition(running_partition, &ota_state));
963+
#endif
964+
reboot_as_deep_sleep();
965+
break;
966+
case 4:
967+
ESP_LOGI(TAG, "OTA1");
968+
TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_1, cur_app->subtype);
969+
TEST_ESP_OK(esp_ota_mark_app_valid_cancel_rollback());
970+
break;
971+
default:
972+
erase_ota_data();
973+
TEST_FAIL_MESSAGE("Unexpected stage");
974+
break;
975+
}
976+
}
977+
978+
TEST_CASE_MULTIPLE_STAGES("Test esp_rewrite_otadata. Updated sequence number for non-running partition always", "[app_update][timeout=90][reset=DEEPSLEEP_RESET, DEEPSLEEP_RESET, DEEPSLEEP_RESET, SW_CPU_RESET]", start_test, test_rollback4, test_rollback4, test_rollback4);

components/app_update/test_apps/test_app_update/pytest_app_update_ut.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
)
2121
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
2222
def test_app_update(dut: Dut) -> None:
23-
dut.run_all_single_board_cases(timeout=90)
23+
dut.run_all_single_board_cases(timeout=180)
2424

2525

2626
@pytest.mark.generic
@@ -33,7 +33,7 @@ def test_app_update(dut: Dut) -> None:
3333
)
3434
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
3535
def test_app_update_xip_psram(dut: Dut) -> None:
36-
dut.run_all_single_board_cases(timeout=90)
36+
dut.run_all_single_board_cases(timeout=180)
3737

3838

3939
@pytest.mark.generic
@@ -46,7 +46,7 @@ def test_app_update_xip_psram(dut: Dut) -> None:
4646
)
4747
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
4848
def test_app_update_xip_psram_rom_impl(dut: Dut) -> None:
49-
dut.run_all_single_board_cases(timeout=90)
49+
dut.run_all_single_board_cases(timeout=180)
5050

5151

5252
@pytest.mark.generic
@@ -59,4 +59,4 @@ def test_app_update_xip_psram_rom_impl(dut: Dut) -> None:
5959
)
6060
@idf_parametrize('target', ['esp32', 'esp32c3', 'esp32s3', 'esp32p4'], indirect=['target'])
6161
def test_app_update_with_rollback(dut: Dut) -> None:
62-
dut.run_all_single_board_cases(timeout=90)
62+
dut.run_all_single_board_cases(timeout=180)

0 commit comments

Comments
 (0)