]> granicus.if.org Git - esp-idf/commitdiff
bootloader: Add support flags for rollback app
authorKonstantin Kondrashov <konstantin@espressif.com>
Tue, 23 Oct 2018 12:27:32 +0000 (20:27 +0800)
committerKonstantin Kondrashov <konstantin@espressif.com>
Tue, 11 Dec 2018 03:54:21 +0000 (11:54 +0800)
Added
* Set actual ota_seq if both ota are init or incorrect.
* Description of rollback
* UT tests

Closes TW15459

components/app_update/esp_ota_ops.c
components/app_update/include/esp_ota_ops.h
components/app_update/test/test_switch_ota.c
components/bootloader/Kconfig.projbuild
components/bootloader_support/src/bootloader_common.c
components/bootloader_support/src/bootloader_utility.c
components/esp32/include/esp_flash_data_types.h
docs/en/api-reference/system/ota.rst

index 5d415ffecae82dc732e058ec40a1f25520dd542e..b11f35699962307d42b771d3340de4a0a73f6726 100644 (file)
@@ -39,6 +39,7 @@
 #include "esp_flash_data_types.h"
 #include "bootloader_common.h"
 #include "sys/param.h"
+#include "esp_system.h"
 
 #define SUB_TYPE_ID(i) (i & 0x0F) 
 
@@ -115,6 +116,16 @@ static esp_err_t image_validate(const esp_partition_t *partition, esp_image_load
     return ESP_OK;
 }
 
+static esp_ota_img_states_t set_new_state_otadata(void)
+{
+#ifdef CONFIG_APP_ROLLBACK_ENABLE
+    ESP_LOGD(TAG, "Monitoring the first boot of the app is enabled.");
+    return ESP_OTA_IMG_NEW;
+#else
+    return ESP_OTA_IMG_UNDEFINED;
+#endif
+}
+
 esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp_ota_handle_t *out_handle)
 {
     ota_ops_entry_t *new_entry;
@@ -348,10 +359,12 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
             i++;
         }
         int next_otadata = (~active_otadata)&1; // if 0 -> will be next 1. and if 1 -> will be next 0.
+        otadata[next_otadata].ota_state = set_new_state_otadata();
         return rewrite_ota_seq(otadata, (SUB_TYPE_ID(subtype) + 1) % ota_app_count + i * ota_app_count, next_otadata, otadata_partition);
     } else {
         /* Both OTA slots are invalid, probably because unformatted... */
         int next_otadata = 0;
+        otadata[next_otadata].ota_state = set_new_state_otadata();
         return rewrite_ota_seq(otadata, SUB_TYPE_ID(subtype) + 1, next_otadata, otadata_partition);
     }
 }
@@ -547,3 +560,185 @@ esp_err_t esp_ota_get_partition_description(const esp_partition_t *partition, es
 
     return ESP_OK;
 }
+
+// if valid == false - will done rollback with reboot. After reboot will boot previous OTA[x] or Factory partition.
+// if valid == true  - it confirm that current OTA[x] is workable. Reboot will not happen.
+static esp_err_t esp_ota_current_ota_is_workable(bool valid)
+{
+    esp_ota_select_entry_t otadata[2];
+    const esp_partition_t *otadata_partition = read_otadata(otadata);
+    if (otadata_partition == NULL) {
+        return ESP_ERR_NOT_FOUND;
+    }
+
+    int active_otadata = bootloader_common_get_active_otadata(otadata);
+    if (active_otadata != -1 && get_ota_partition_count() != 0) {
+        if (valid == true && otadata[active_otadata].ota_state != ESP_OTA_IMG_VALID) {
+            otadata[active_otadata].ota_state = ESP_OTA_IMG_VALID;
+            ESP_LOGD(TAG, "OTA[current] partition is marked as VALID");
+            return rewrite_ota_seq(otadata, otadata[active_otadata].ota_seq, active_otadata, otadata_partition);
+        } else if (valid == false) {
+            ESP_LOGD(TAG, "OTA[current] partition is marked as INVALID");
+            otadata[active_otadata].ota_state = ESP_OTA_IMG_INVALID;
+            esp_err_t err = rewrite_ota_seq(otadata, otadata[active_otadata].ota_seq, active_otadata, otadata_partition);
+            if (err != ESP_OK) {
+                return err;
+            }
+            ESP_LOGI(TAG, "Rollback to previously worked partition. Restart.");
+            esp_restart();
+        }
+    } else {
+        ESP_LOGE(TAG, "Running firmware is factory");
+        return ESP_FAIL;
+    }
+    return ESP_OK;
+}
+
+esp_err_t esp_ota_mark_app_valid_cancel_rollback()
+{
+    return esp_ota_current_ota_is_workable(true);
+}
+
+esp_err_t esp_ota_mark_app_invalid_rollback_and_reboot()
+{
+    return esp_ota_current_ota_is_workable(false);
+}
+
+static bool check_invalid_otadata (const esp_ota_select_entry_t *s) {
+    return s->ota_seq != UINT32_MAX &&
+           s->crc == bootloader_common_ota_select_crc(s) &&
+           (s->ota_state == ESP_OTA_IMG_INVALID ||
+            s->ota_state == ESP_OTA_IMG_ABORTED);
+}
+
+static int get_last_invalid_otadata(const esp_ota_select_entry_t *two_otadata)
+{
+    int num_invalid_otadata = -1;
+
+    bool invalid_otadata[2];
+    invalid_otadata[0] = check_invalid_otadata(&two_otadata[0]);
+    invalid_otadata[1] = check_invalid_otadata(&two_otadata[1]);
+    if (invalid_otadata[0] == true && invalid_otadata[1] == true) {
+        if (MIN(two_otadata[0].ota_seq, two_otadata[1].ota_seq) == two_otadata[0].ota_seq) {
+            num_invalid_otadata = 0;
+        } else {
+            num_invalid_otadata = 1;
+        }
+    } else {
+        for (int i = 0; i < 2; ++i) {
+            if(invalid_otadata[i]) {
+                num_invalid_otadata = i;
+                break;
+            }
+        }
+    }
+
+    ESP_LOGD(TAG, "Invalid otadata[%d]", num_invalid_otadata);
+    return num_invalid_otadata;
+}
+
+const esp_partition_t* esp_ota_get_last_invalid_partition()
+{
+    esp_ota_select_entry_t otadata[2];
+    if (read_otadata(otadata) == NULL) {
+        return NULL;
+    }
+
+    int invalid_otadata = get_last_invalid_otadata(otadata);
+
+    int ota_app_count = get_ota_partition_count();
+    if (invalid_otadata != -1 && ota_app_count != 0) {
+        int ota_slot = (otadata[invalid_otadata].ota_seq - 1) % ota_app_count;
+        ESP_LOGD(TAG, "Find invalid ota_%d app", ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ota_slot);
+
+        const esp_partition_t* invalid_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ota_slot, NULL);
+        if (invalid_partition != NULL) {
+            if (image_validate(invalid_partition, ESP_IMAGE_VERIFY_SILENT) != ESP_OK) {
+                ESP_LOGD(TAG, "Last invalid partition has corrupted app");
+                return NULL;
+            }
+        }
+        return invalid_partition;
+    }
+    return NULL;
+}
+
+esp_err_t esp_ota_get_state_partition(const esp_partition_t *partition, esp_ota_img_states_t *ota_state)
+{
+    if (partition == NULL || ota_state == NULL) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!is_ota_partition(partition)) {
+        return ESP_ERR_NOT_SUPPORTED;
+    }
+
+    esp_ota_select_entry_t otadata[2];
+    int ota_app_count = get_ota_partition_count();
+    if (read_otadata(otadata) == NULL || ota_app_count == 0) {
+        return ESP_ERR_NOT_FOUND;
+    }
+
+    int req_ota_slot = partition->subtype - ESP_PARTITION_SUBTYPE_APP_OTA_MIN;
+    bool not_found = true;
+    for (int i = 0; i < 2; ++i) {
+        int ota_slot = (otadata[i].ota_seq - 1) % ota_app_count;
+        if (ota_slot == req_ota_slot && otadata[i].crc == bootloader_common_ota_select_crc(&otadata[i])) {
+            *ota_state = otadata[i].ota_state;
+            not_found = false;
+            break;
+        }
+    }
+
+    if (not_found) {
+        return ESP_ERR_NOT_FOUND;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t esp_ota_erase_last_boot_app_partition(void)
+{
+    esp_ota_select_entry_t otadata[2];
+    const esp_partition_t* ota_data_partition = read_otadata(otadata);
+    if (ota_data_partition == NULL) {
+        return ESP_FAIL;
+    }
+
+    int active_otadata = bootloader_common_get_active_otadata(otadata);
+    int ota_app_count = get_ota_partition_count();
+    if (active_otadata == -1 || ota_app_count == 0) {
+        return ESP_FAIL;
+    }
+
+    int inactive_otadata = (~active_otadata)&1;
+    if (otadata[inactive_otadata].ota_seq == UINT32_MAX || otadata[inactive_otadata].crc != bootloader_common_ota_select_crc(&otadata[inactive_otadata])) {
+        return ESP_FAIL;
+    }
+
+    int ota_slot = (otadata[inactive_otadata].ota_seq - 1) % ota_app_count; // Actual OTA partition selection
+    ESP_LOGD(TAG, "finding last_boot_app_partition ota_%d app...", ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ota_slot);
+
+    const esp_partition_t* last_boot_app_partition_from_otadata = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_MIN + ota_slot, NULL);
+    if (last_boot_app_partition_from_otadata == NULL) {
+        return ESP_FAIL;
+    }
+
+    const esp_partition_t* running_partition = esp_ota_get_running_partition();
+    if (running_partition == NULL || last_boot_app_partition_from_otadata == running_partition) {
+        return ESP_FAIL;
+    }
+
+    esp_err_t err = esp_partition_erase_range(last_boot_app_partition_from_otadata, 0, last_boot_app_partition_from_otadata->size);
+    if (err != ESP_OK) {
+        return err;
+    }
+
+    int sec_id = inactive_otadata;
+    err = esp_partition_erase_range(ota_data_partition, sec_id * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE);
+    if (err != ESP_OK) {
+        return err;
+    }
+
+    return ESP_OK;
+}
index 3a2355d9e469ae0332ec755d2b66b02c530f7832..ff07cec5a0bae3836b3cc85bf16a43506ceabfdc 100644 (file)
@@ -21,6 +21,7 @@
 #include "esp_err.h"
 #include "esp_partition.h"
 #include "esp_image_format.h"
+#include "esp_flash_data_types.h"
 
 #ifdef __cplusplus
 extern "C"
@@ -195,6 +196,52 @@ const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *
  */
 esp_err_t esp_ota_get_partition_description(const esp_partition_t *partition, esp_app_desc_t *app_desc);
 
+/**
+ * @brief This function is called to indicate that the running app is working well.
+ *
+ * @return
+ *  - ESP_OK: if successful.
+ */
+esp_err_t esp_ota_mark_app_valid_cancel_rollback();
+
+/**
+ * @brief This function is called to roll back to the previously workable app with reboot.
+ *
+ * If rollback is successful then device will reset else API will return with error code.
+ * @return
+ *  - ESP_FAIL: if not successful.
+ */
+esp_err_t esp_ota_mark_app_invalid_rollback_and_reboot();
+
+/**
+ * @brief Returns last partition with invalid state (ESP_OTA_IMG_INVALID or ESP_OTA_IMG_ABORTED).
+ *
+ * @return partition.
+ */
+const esp_partition_t* esp_ota_get_last_invalid_partition();
+
+/**
+ * @brief Returns state for given partition.
+ *
+ * @param[in] partition  Pointer to partition.
+ * @param[out] ota_state state of partition (if this partition has a record in otadata).
+ * @return
+ *        - ESP_OK:                 Successful.
+ *        - ESP_ERR_INVALID_ARG:    partition or ota_state arguments were NULL.
+ *        - ESP_ERR_NOT_SUPPORTED:  partition is not ota.
+ *        - ESP_ERR_NOT_FOUND:      Partition table does not have otadata or state was not found for given partition.
+ */
+esp_err_t esp_ota_get_state_partition(const esp_partition_t *partition, esp_ota_img_states_t *ota_state);
+
+/**
+ * @brief Erase previous boot app partition and corresponding otadata select for this partition.
+ *
+ * When current app is marked to as valid then you can erase previous app partition.
+ * @return
+ *        - ESP_OK:   Successful, otherwise ESP_ERR.
+ */
+esp_err_t esp_ota_erase_last_boot_app_partition(void);
+
 #ifdef __cplusplus
 }
 #endif
index 28248ced085d68799bbc4b19556ada907203d4ff..a3cb113c6a71769e5deb1416a45612f2dc542ef1 100644 (file)
@@ -476,3 +476,244 @@ static void test_flow5(void)
 // 4 Stage: run factory    -> check it -> erase OTA_DATA for next tests             -> PASS
 TEST_CASE_MULTIPLE_STAGES("Switching between factory, test, factory", "[app_update][reset=DEEPSLEEP_RESET, DEEPSLEEP_RESET, DEEPSLEEP_RESET]", start_test, test_flow5, test_flow5, test_flow5);
 #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)
+{
+    boot_count++;
+    ESP_LOGI(TAG, "boot count %d", boot_count);
+    const esp_partition_t *cur_app = get_running_firmware();
+    esp_ota_img_states_t ota_state = 0x5555AAAA;
+    const esp_partition_t* update_partition = NULL;
+    switch (boot_count) {
+        case 2:
+            ESP_LOGI(TAG, "Factory");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_FACTORY, cur_app->subtype);
+            TEST_ASSERT_NULL(esp_ota_get_last_invalid_partition());
+            TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, esp_ota_get_state_partition(cur_app, &ota_state));
+            update_partition = app_update();
+            TEST_ESP_OK(esp_ota_get_state_partition(update_partition, &ota_state));
+#ifndef CONFIG_APP_ROLLBACK_ENABLE
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_UNDEFINED, ota_state);
+#else
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_NEW, ota_state);
+#endif
+            reboot_as_deep_sleep();
+            break;
+        case 3:
+            ESP_LOGI(TAG, "OTA0");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype);
+            TEST_ASSERT_NULL(esp_ota_get_last_invalid_partition());
+            TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state));
+#ifndef CONFIG_APP_ROLLBACK_ENABLE
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_UNDEFINED, ota_state);
+#else
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_PENDING_VERIFY, ota_state);
+#endif
+            esp_ota_mark_app_valid_cancel_rollback();
+            TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state));
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_VALID, ota_state);
+            reboot_as_deep_sleep();
+            break;
+        case 4:
+            ESP_LOGI(TAG, "OTA0");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype);
+            TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state));
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_VALID, ota_state);
+            TEST_ESP_OK(esp_ota_mark_app_invalid_rollback_and_reboot());
+            break;
+        default:
+            erase_ota_data();
+            TEST_FAIL_MESSAGE("Unexpected stage");
+            break;
+    }
+}
+
+static void test_rollback1_1(void)
+{
+    boot_count = 5;
+    esp_ota_img_states_t ota_state = 0x5555AAAA;
+    ESP_LOGI(TAG, "boot count %d", boot_count);
+    const esp_partition_t *cur_app = get_running_firmware();
+    ESP_LOGI(TAG, "Factory");
+    TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_FACTORY, cur_app->subtype);
+
+    const esp_partition_t *invalid_partition = esp_ota_get_last_invalid_partition();
+    const esp_partition_t* next_update_partition = esp_ota_get_next_update_partition(NULL);
+    TEST_ASSERT_NOT_NULL(invalid_partition);
+    TEST_ASSERT_NOT_NULL(next_update_partition);
+    TEST_ASSERT_EQUAL_PTR(invalid_partition, next_update_partition);
+    TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, esp_ota_get_state_partition(cur_app, &ota_state));
+    TEST_ESP_OK(esp_ota_get_state_partition(invalid_partition, &ota_state));
+    TEST_ASSERT_EQUAL(ESP_OTA_IMG_INVALID, ota_state);
+
+    erase_ota_data();
+}
+
+// 1 Stage: After POWER_RESET erase OTA_DATA for this test                          -> reboot through deep sleep.
+// 2 Stage: run factory    -> check it -> copy factory to next app slot             -> reboot  --//--
+// 3 Stage: run OTA0       -> check it -> esp_ota_mark_app_valid_cancel_rollback()                 -> reboot  --//--
+// 4 Stage: run OTA0       -> check it -> esp_ota_mark_app_invalid_rollback_and_reboot()         -> reboot
+// 5 Stage: run factory    -> check it -> erase OTA_DATA for next tests             -> PASS
+TEST_CASE_MULTIPLE_STAGES("Test rollback. factory, OTA0, OTA0, rollback -> factory", "[app_update][reset=DEEPSLEEP_RESET, DEEPSLEEP_RESET, DEEPSLEEP_RESET, SW_CPU_RESET]", start_test, test_rollback1, test_rollback1, test_rollback1, test_rollback1_1);
+
+static void test_rollback2(void)
+{
+    boot_count++;
+    ESP_LOGI(TAG, "boot count %d", boot_count);
+    const esp_partition_t *cur_app = get_running_firmware();
+    esp_ota_img_states_t ota_state = 0x5555AAAA;
+    const esp_partition_t* update_partition = NULL;
+    switch (boot_count) {
+        case 2:
+            ESP_LOGI(TAG, "Factory");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_FACTORY, cur_app->subtype);
+            TEST_ASSERT_NULL(esp_ota_get_last_invalid_partition());
+            TEST_ESP_ERR(ESP_ERR_NOT_SUPPORTED, esp_ota_get_state_partition(cur_app, &ota_state));
+            update_partition = app_update();
+            TEST_ESP_OK(esp_ota_get_state_partition(update_partition, &ota_state));
+#ifndef CONFIG_APP_ROLLBACK_ENABLE
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_UNDEFINED, ota_state);
+#else
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_NEW, ota_state);
+#endif
+            reboot_as_deep_sleep();
+            break;
+        case 3:
+            ESP_LOGI(TAG, "OTA0");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype);
+            TEST_ASSERT_NULL(esp_ota_get_last_invalid_partition());
+            TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state));
+#ifndef CONFIG_APP_ROLLBACK_ENABLE
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_UNDEFINED, ota_state);
+#else
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_PENDING_VERIFY, ota_state);
+#endif
+            esp_ota_mark_app_valid_cancel_rollback();
+            TEST_ASSERT_NULL(esp_ota_get_last_invalid_partition());
+            TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state));
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_VALID, ota_state);
+            update_partition = app_update();
+            TEST_ESP_OK(esp_ota_get_state_partition(update_partition, &ota_state));
+#ifndef CONFIG_APP_ROLLBACK_ENABLE
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_UNDEFINED, ota_state);
+#else
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_NEW, ota_state);
+#endif
+            reboot_as_deep_sleep();
+            break;
+        case 4:
+            ESP_LOGI(TAG, "OTA1");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_1, cur_app->subtype);
+            TEST_ASSERT_NULL(esp_ota_get_last_invalid_partition());
+            TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state));
+#ifndef CONFIG_APP_ROLLBACK_ENABLE
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_UNDEFINED, ota_state);
+            TEST_ESP_OK(esp_ota_mark_app_invalid_rollback_and_reboot());
+#else
+            TEST_ASSERT_EQUAL(ESP_OTA_IMG_PENDING_VERIFY, ota_state);
+            reboot_as_deep_sleep();
+#endif
+            break;
+        default:
+            erase_ota_data();
+            TEST_FAIL_MESSAGE("Unexpected stage");
+            break;
+    }
+}
+
+static void test_rollback2_1(void)
+{
+    boot_count = 5;
+    esp_ota_img_states_t ota_state = 0x5555AAAA;
+    ESP_LOGI(TAG, "boot count %d", boot_count);
+    const esp_partition_t *cur_app = get_running_firmware();
+    ESP_LOGI(TAG, "OTA0");
+    TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype);
+
+    const esp_partition_t *invalid_partition = esp_ota_get_last_invalid_partition();
+    TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_1, invalid_partition->subtype);
+    const esp_partition_t* next_update_partition = esp_ota_get_next_update_partition(NULL);
+    TEST_ASSERT_NOT_NULL(invalid_partition);
+    TEST_ASSERT_NOT_NULL(next_update_partition);
+    TEST_ASSERT_EQUAL_PTR(invalid_partition, next_update_partition);
+    TEST_ESP_OK(esp_ota_get_state_partition(cur_app, &ota_state));
+    TEST_ASSERT_EQUAL(ESP_OTA_IMG_VALID, ota_state);
+    TEST_ESP_OK(esp_ota_get_state_partition(invalid_partition, &ota_state));
+#ifndef CONFIG_APP_ROLLBACK_ENABLE
+    TEST_ASSERT_EQUAL(ESP_OTA_IMG_INVALID, ota_state);
+#else
+    TEST_ASSERT_EQUAL(ESP_OTA_IMG_ABORTED, ota_state);
+#endif
+    erase_ota_data();
+}
+
+// 1 Stage: After POWER_RESET erase OTA_DATA for this test                                     -> reboot through deep sleep.
+// 2 Stage: run factory        -> check it -> copy factory to next app slot                    -> reboot  --//--
+// 3 Stage: run OTA0           -> check it -> esp_ota_mark_app_valid_cancel_rollback(), copy to next app slot -> reboot  --//--
+// 4 Stage: run OTA1           -> check it -> PENDING_VERIFY/esp_ota_mark_app_invalid_rollback_and_reboot() -> reboot
+// 5 Stage: run OTA0(rollback) -> check it -> erase OTA_DATA for next tests                    -> PASS
+TEST_CASE_MULTIPLE_STAGES("Test rollback. factory, OTA0, OTA1, rollback -> OTA0", "[app_update][reset=DEEPSLEEP_RESET, DEEPSLEEP_RESET, DEEPSLEEP_RESET, SW_CPU_RESET]", start_test, test_rollback2, test_rollback2, test_rollback2, test_rollback2_1);
+
+static void test_erase_last_app_flow(void)
+{
+    boot_count++;
+    ESP_LOGI(TAG, "boot count %d", boot_count);
+    const esp_partition_t *cur_app = get_running_firmware();
+    switch (boot_count) {
+        case 2:
+            ESP_LOGI(TAG, "Factory");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_FACTORY, cur_app->subtype);
+            app_update();
+            reboot_as_deep_sleep();
+            break;
+        case 3:
+            ESP_LOGI(TAG, "OTA0");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype);
+            app_update();
+            reboot_as_deep_sleep();
+            break;
+        case 4:
+            ESP_LOGI(TAG, "OTA1");
+            TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_1, cur_app->subtype);
+            TEST_ESP_OK(esp_ota_erase_last_boot_app_partition());
+            TEST_ESP_OK(esp_ota_mark_app_invalid_rollback_and_reboot());
+            reboot_as_deep_sleep();
+            break;
+        default:
+            erase_ota_data();
+            TEST_FAIL_MESSAGE("Unexpected stage");
+            break;
+    }
+}
+
+static void test_erase_last_app_rollback(void)
+{
+    boot_count = 5;
+    ESP_LOGI(TAG, "boot count %d", boot_count);
+    const esp_partition_t *cur_app = get_running_firmware();
+    ESP_LOGI(TAG, "erase_last_app");
+    TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_FACTORY, cur_app->subtype);
+    TEST_ESP_ERR(ESP_FAIL, esp_ota_erase_last_boot_app_partition());
+    erase_ota_data();
+}
+
+// 1 Stage: After POWER_RESET erase OTA_DATA for this test                                     -> reboot through deep sleep.
+// 2 Stage: run factory        -> check it -> copy factory to OTA0                             -> reboot  --//--
+// 3 Stage: run OTA0           -> check it -> copy factory to OTA1                             -> reboot  --//--
+// 4 Stage: run OTA1           -> check it -> erase OTA0 and rollback                          -> reboot
+// 5 Stage: run factory        -> check it -> erase OTA_DATA for next tests                    -> PASS
+TEST_CASE_MULTIPLE_STAGES("Test erase_last_boot_app_partition. factory, OTA1, OTA0, factory", "[app_update][reset=DEEPSLEEP_RESET, DEEPSLEEP_RESET, DEEPSLEEP_RESET, SW_CPU_RESET]", start_test, test_erase_last_app_flow, test_erase_last_app_flow, test_erase_last_app_flow, test_erase_last_app_rollback);
index 3ecbed93b987eede62d7f7fb9e9096fff79bc058..a982461e23d6dd1a5e64fa0ac6a7b361d21f34b8 100644 (file)
@@ -159,6 +159,18 @@ config BOOTLOADER_WDT_TIME_MS
         - these options can increase the execution time. 
         Note: RTC_WDT will reset while encryption operations will be performed.
 
+config APP_ROLLBACK_ENABLE
+    bool "Enable app rollback support"
+    default n
+       help
+        After updating the app, the bootloader runs a new app with the "ESP_OTA_IMG_PENDING_VERIFY" state set. 
+        This state prevents the re-run of this app. 
+        After the first boot of the new app in the user code, the function should be called to confirm 
+        the operability of the app or vice versa about its non-operability. If the app is working, 
+        then it is marked as valid. Otherwise, it is marked as not valid and rolls back to the previous working app. 
+        A reboot is performed, and the app is booted before the software update.
+        Note: If during the first boot a new app the power goes out or the WDT works, then roll back will happen.
+
 endmenu  # Bootloader
 
 
index 75b4a31b9b0dd1f356eefc85f4f11fa9e64722a8..7486164164b5f393c27c6527675d925bcbcb59cc 100644 (file)
@@ -42,7 +42,7 @@ uint32_t bootloader_common_ota_select_crc(const esp_ota_select_entry_t *s)
 
 bool bootloader_common_ota_select_invalid(const esp_ota_select_entry_t *s)
 {
-    return s->ota_seq == UINT32_MAX;
+    return s->ota_seq == UINT32_MAX || s->ota_state == ESP_OTA_IMG_INVALID || s->ota_state == ESP_OTA_IMG_ABORTED;
 }
 
 bool bootloader_common_ota_select_valid(const esp_ota_select_entry_t *s)
index 06b01f8a467850699fe70ccd90664b5dac609748..61099be41cdf905d4e4a2e4527af52196444e74a 100644 (file)
@@ -57,6 +57,8 @@ static const char* TAG = "boot";
 /* Reduce literal size for some generic string literals */
 #define MAP_ERR_MSG "Image contains multiple %s segments. Only the last one will be mapped."
 
+static bool ota_has_initial_contents;
+
 static void load_image(const esp_image_metadata_t* image_data);
 static void unpack_load_app(const esp_image_metadata_t *data);
 static void set_cache_and_start_app(uint32_t drom_addr,
@@ -220,6 +222,18 @@ static void log_invalid_app_partition(int index)
     }
 }
 
+static esp_err_t write_otadata(esp_ota_select_entry_t *otadata, uint32_t offset, bool write_encrypted)
+{
+    esp_err_t err = bootloader_flash_erase_sector(offset / FLASH_SECTOR_SIZE);
+    if (err == ESP_OK) {
+        err = bootloader_flash_write(offset, otadata, sizeof(esp_ota_select_entry_t), write_encrypted);
+    }
+    if (err != ESP_OK) {
+        ESP_LOGE(TAG, "Error in write_otadata operation. err = 0x%x", err);
+    }
+    return err;
+}
+
 int bootloader_utility_get_selected_boot_partition(const bootloader_state_t *bs)
 {
     esp_ota_select_entry_t otadata[2];
@@ -232,10 +246,20 @@ int bootloader_utility_get_selected_boot_partition(const bootloader_state_t *bs)
     if (read_otadata(&bs->ota_info, otadata) != ESP_OK) {
         return INVALID_INDEX;
     }
+    ota_has_initial_contents = false;
 
     ESP_LOGD(TAG, "otadata[0]: sequence values 0x%08x", otadata[0].ota_seq);
     ESP_LOGD(TAG, "otadata[1]: sequence values 0x%08x", otadata[1].ota_seq);
-
+#ifdef CONFIG_APP_ROLLBACK_ENABLE
+    bool write_encrypted = esp_flash_encryption_enabled();
+    for (int i = 0; i < 2; ++i) {
+        if (otadata[i].ota_state == ESP_OTA_IMG_PENDING_VERIFY) {
+            ESP_LOGD(TAG, "otadata[%d] is marking as ABORTED", i);
+            otadata[i].ota_state = ESP_OTA_IMG_ABORTED;
+            write_otadata(&otadata[i], bs->ota_info.offset + FLASH_SECTOR_SIZE * i, write_encrypted);
+        }
+    }
+#endif
     if ((bootloader_common_ota_select_invalid(&otadata[0]) &&
          bootloader_common_ota_select_invalid(&otadata[1])) ||
          bs->app_count == 0) {
@@ -246,11 +270,25 @@ int bootloader_utility_get_selected_boot_partition(const bootloader_state_t *bs)
         } else {
             ESP_LOGI(TAG, "No factory image, trying OTA 0");
             boot_index = 0;
+            // Try to boot from ota_0.
+            if ((otadata[0].ota_seq == UINT32_MAX || otadata[0].crc != bootloader_common_ota_select_crc(&otadata[0])) &&
+                (otadata[1].ota_seq == UINT32_MAX || otadata[1].crc != bootloader_common_ota_select_crc(&otadata[1]))) {
+                // Factory is not found and both otadata are initial(0xFFFFFFFF) or incorrect crc.
+                // will set correct ota_seq.
+                ota_has_initial_contents = true;
+            }
         }
     } else {
         int active_otadata = bootloader_common_get_active_otadata(otadata);
         if (active_otadata != -1) {
-            ESP_LOGD(TAG, "Active OTADATA copy is #%d", active_otadata);
+            ESP_LOGD(TAG, "Active otadata[%d]", active_otadata);
+#ifdef CONFIG_APP_ROLLBACK_ENABLE
+            if (otadata[active_otadata].ota_state == ESP_OTA_IMG_NEW) {
+                ESP_LOGD(TAG, "otadata[%d] is selected as new and marked PENDING_VERIFY state", active_otadata);
+                otadata[active_otadata].ota_state = ESP_OTA_IMG_PENDING_VERIFY;
+                write_otadata(&otadata[active_otadata], bs->ota_info.offset + FLASH_SECTOR_SIZE * active_otadata, write_encrypted);
+            }
+#endif
             uint32_t ota_seq = otadata[active_otadata].ota_seq - 1; // Raw OTA sequence number. May be more than # of OTA slots
             boot_index = ota_seq % bs->app_count; // Actual OTA partition selection
             ESP_LOGD(TAG, "Mapping seq %d -> OTA slot %d", ota_seq, boot_index);
@@ -284,6 +322,22 @@ static bool try_load_partition(const esp_partition_pos_t *partition, esp_image_m
     return false;
 }
 
+// ota_has_initial_contents flag is set if factory does not present in partition table and
+// otadata has initial content(0xFFFFFFFF), then set actual ota_seq.
+static void set_actual_ota_seq(const bootloader_state_t *bs, int index)
+{
+    if (index >= 0 && ota_has_initial_contents == true) {
+        esp_ota_select_entry_t otadata;
+        otadata.ota_seq = index + 1;
+        otadata.ota_state = ESP_OTA_IMG_VALID;
+        otadata.crc = bootloader_common_ota_select_crc(&otadata);
+
+        bool write_encrypted = esp_flash_encryption_enabled();
+        write_otadata(&otadata, bs->ota_info.offset + FLASH_SECTOR_SIZE * 0, write_encrypted);
+        ESP_LOGD(TAG, "Set actual ota_seq=%d in otadata[0]", otadata.ota_seq);
+    }
+}
+
 #define TRY_LOG_FORMAT "Trying partition index %d offs 0x%x size 0x%x"
 
 void bootloader_utility_load_boot_image(const bootloader_state_t *bs, int start_index)
@@ -309,6 +363,7 @@ void bootloader_utility_load_boot_image(const bootloader_state_t *bs, int start_
         }
         ESP_LOGD(TAG, TRY_LOG_FORMAT, index, part.offset, part.size);
         if (try_load_partition(&part, &image_data)) {
+            set_actual_ota_seq(bs, index);
             load_image(&image_data);
         }
         log_invalid_app_partition(index);
@@ -322,6 +377,7 @@ void bootloader_utility_load_boot_image(const bootloader_state_t *bs, int start_
         }
         ESP_LOGD(TAG, TRY_LOG_FORMAT, index, part.offset, part.size);
         if (try_load_partition(&part, &image_data)) {
+            set_actual_ota_seq(bs, index);
             load_image(&image_data);
         }
         log_invalid_app_partition(index);
index 9a26281b0a8d479042c80e21da14289056266199..d0f6ff32b819c60748a401de4557464359a58564 100644 (file)
@@ -24,11 +24,22 @@ extern "C"
 #define ESP_PARTITION_MAGIC 0x50AA
 #define ESP_PARTITION_MAGIC_MD5 0xEBEB
 
+/// OTA_DATA states for checking operability of the app.
+typedef enum {
+    ESP_OTA_IMG_NEW             = 0x0U,         /*!< Monitor the first boot. In bootloader this state is changed to ESP_OTA_IMG_PENDING_VERIFY. */
+    ESP_OTA_IMG_PENDING_VERIFY  = 0x1U,         /*!< First boot for this app was. If while the second boot this state is then it will be changed to ABORTED. */
+    ESP_OTA_IMG_VALID           = 0x2U,         /*!< App was confirmed as workable. App can boot and work without limits. */
+    ESP_OTA_IMG_INVALID         = 0x3U,         /*!< App was confirmed as non-workable. This app will not selected to boot at all. */
+    ESP_OTA_IMG_ABORTED         = 0x4U,         /*!< App could not confirm the workable or non-workable. In bootloader IMG_PENDING_VERIFY state will be changed to IMG_ABORTED. This app will not selected to boot at all. */
+    ESP_OTA_IMG_UNDEFINED       = 0xFFFFFFFFU,  /*!< Undefined. App can boot and work without limits. */
+} esp_ota_img_states_t;
+
 /* OTA selection structure (two copies in the OTA data partition.)
    Size of 32 bytes is friendly to flash encryption */
 typedef struct {
     uint32_t ota_seq;
-    uint8_t  seq_label[24];
+    uint8_t  seq_label[20];
+    uint32_t ota_state;
     uint32_t crc; /* CRC32 of ota_seq field only */
 } esp_ota_select_entry_t;
 
index db02807280547136f53e629bd275cab2881bac12..500b9512d254131a55fcb9587083e8f3ca9904f2 100644 (file)
@@ -32,6 +32,95 @@ The OTA data partition is two flash sectors (0x2000 bytes) in size, to prevent p
 while it is being written. Sectors are independently erased and written with matching data, and if they disagree a
 counter field is used to determine which sector was written more recently.
 
+.. _app_rollback:
+
+App rollback
+------------
+
+The main purpose of the application rollback is to keep the device working after the update. This feature allows you to roll back to the previous working application in case a new application has critical errors. When the rollback process is enabled and an OTA update provides a new version of the app, one of three things can happen:
+
+* The application works fine, :cpp:func:`esp_ota_mark_app_valid_cancel_rollback` marks the running application with the state ``ESP_OTA_IMG_VALID``. There are no restrictions on booting this application.
+* The application has critical errors and further work is not possible, a rollback to the previous application is required, :cpp:func:`esp_ota_mark_app_invalid_rollback_and_reboot` marks the running application with the state ``ESP_OTA_IMG_INVALID`` and reset. This application will not be selected by the bootloader for boot and will boot the previously working application.
+* If the :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is set, and occur a reset without calling either function then happend and is rolled back.
+
+Note: The state is not written to the binary image of the application it is written to the ``otadata`` partition. The partition contains a ``ota_seq`` counter  which is a pointer to the slot (ota_0, ota_1, ...) from which the application will be selected for boot.
+
+App OTA State
+^^^^^^^^^^^^^
+
+States control the process of selecting a boot app:
+
++-----------------------------+--------------------------------------------------------+
+|            States           | Restriction of selecting a boot app in bootloader      |
++=============================+========================================================+
+| ESP_OTA_IMG_VALID           | None restriction. Will be selected.                    |
++-----------------------------+--------------------------------------------------------+
+| ESP_OTA_IMG_UNDEFINED       | None restriction. Will be selected.                    |
++-----------------------------+--------------------------------------------------------+
+| ESP_OTA_IMG_INVALID         | Will not be selected.                                  |
++-----------------------------+--------------------------------------------------------+
+| ESP_OTA_IMG_ABORTED         | Will not be selected.                                  |
++-----------------------------+--------------------------------------------------------+
+| ESP_OTA_IMG_NEW             | If :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is set     |
+|                             | it will be selected only once. In bootloader the state |
+|                             | immediately changes to ``ESP_OTA_IMG_PENDING_VERIFY``. |
++-----------------------------+--------------------------------------------------------+
+| ESP_OTA_IMG_PENDING_VERIFY  | If :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is set     |                                                  
+|                             | it will not be selected and the state will change to   |
+|                             | ``ESP_OTA_IMG_ABORTED``.                               |
++-----------------------------+--------------------------------------------------------+
+
+If :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is not enabled (by default), then the use of the following functions :cpp:func:`esp_ota_mark_app_valid_cancel_rollback` and :cpp:func:`esp_ota_mark_app_invalid_rollback_and_reboot` are optional, and ``ESP_OTA_IMG_NEW`` and ``ESP_OTA_IMG_PENDING_VERIFY`` states are not used.
+
+An option in Kconfig :ref:`CONFIG_APP_ROLLBACK_ENABLE` allows you to track the first boot of a new application. In this case, the application must confirm its operability by calling :cpp:func:`esp_ota_mark_app_valid_cancel_rollback` function, otherwise the application will be rolled back upon reboot. It allows you to control the operability of the application during the boot phase. Thus, a new application has only one attempt to boot successfully.
+
+Rollback Process
+^^^^^^^^^^^^^^^^
+
+The description of the rollback process when :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is enabled:
+
+* The new application successfully downloaded and :cpp:func:`esp_ota_set_boot_partition` function makes this partition bootable and sets the state ``ESP_OTA_IMG_NEW``. This state means that the application is new and should be monitored for its first boot.
+* Reboot :cpp:func:`esp_restart`.
+* The bootloader checks for the ``ESP_OTA_IMG_PENDING_VERIFY`` state if it is set, then it will be written to ``ESP_OTA_IMG_ABORTED``.
+* The bootloader selects a new application to boot so that the state is not set as ``ESP_OTA_IMG_INVALID`` or ``ESP_OTA_IMG_ABORTED``.
+* The bootloader checks the selected application for ``ESP_OTA_IMG_NEW`` state if it is set, then it will be written to ``ESP_OTA_IMG_PENDING_VERIFY``. This state means that the application requires confirmation of its operability, if this does not happen and a reboot occurs, this state will be overwritten to ``ESP_OTA_IMG_ABORTED`` (see above) and this application will no longer be able to start, i.e. there will be a rollback to the previous work application.
+* A new application has started and should make a self-test.
+* If the self-test has completed successfully, then you must call the function :cpp:func:`esp_ota_mark_app_valid_cancel_rollback` because the application is awaiting confirmation of operability (``ESP_OTA_IMG_PENDING_VERIFY`` state).
+* If the self-test fails then call :cpp:func:`esp_ota_mark_app_invalid_rollback_and_reboot` function to roll back to the previous working application, while the invalid application is set ``ESP_OTA_IMG_INVALID`` state.
+* If the application has not been confirmed, the state remains ``ESP_OTA_IMG_PENDING_VERIFY``, and the next boot it will be changed to ``ESP_OTA_IMG_ABORTED``. That will prevent re-boot of this application. There will be a rollback to the previous working application.
+
+Unexpected Reset
+^^^^^^^^^^^^^^^^
+
+If a power loss or an unexpected crash occurs at the time of the first boot of a new application, it will roll back the application.
+
+Recommendation: Perform the self-test procedure as quickly as possible, to prevent rollback due to power loss.
+
+Only ``OTA`` partitions can be rolled back. Factory partition is not rolled back.
+
+Booting invalid/aborted apps
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Booting an application which was previously set to ``ESP_OTA_IMG_INVALID`` or ``ESP_OTA_IMG_ABORTED`` is possible:
+
+* Get the last invalid application partition :cpp:func:`esp_ota_get_last_invalid_partition`.
+* Pass the received partition to :cpp:func:`esp_ota_set_boot_partition`, this will update the ``otadata``.
+* Restart :cpp:func:`esp_restart`. The bootloader will boot the specified application.
+
+To determine if self-tests should be run during startup of an application, call the :cpp:func:`esp_ota_get_state_partition` function. If result is ``ESP_OTA_IMG_PENDING_VERIFY`` then self-testing and subsequent confirmation of operability is required.
+
+Where the states are set
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+A brief description of where the states are set:
+
+* ``ESP_OTA_IMG_VALID`` state is set by :cpp:func:`esp_ota_mark_app_valid_cancel_rollback` function.
+* ``ESP_OTA_IMG_UNDEFINED`` state is set by :cpp:func:`esp_ota_set_boot_partition` function if :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is not enabled.
+* ``ESP_OTA_IMG_NEW`` state is set by :cpp:func:`esp_ota_set_boot_partition` function if :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is enabled.
+* ``ESP_OTA_IMG_INVALID`` state is set by  :cpp:func:`esp_ota_mark_app_invalid_rollback_and_reboot` function.
+* ``ESP_OTA_IMG_ABORTED`` state is set if there was no confirmation of the application operability and occurs reboots (if :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is enabled).
+* ``ESP_OTA_IMG_PENDING_VERIFY`` state is set in a bootloader if :ref:`CONFIG_APP_ROLLBACK_ENABLE` option is enabled and selected app has ``ESP_OTA_IMG_NEW`` state.
+
 .. _secure-ota-updates:
 
 Secure OTA Updates Without Secure boot