From 6a1b3221c44cfb90bc68778956f0ea4d538f213c Mon Sep 17 00:00:00 2001 From: Shubham Kulkarni Date: Wed, 22 Apr 2020 20:56:34 +0530 Subject: [PATCH] app_update: Add API to write data in a non contiguous manner --- components/app_update/esp_ota_ops.c | 37 +++++++++ components/app_update/include/esp_ota_ops.h | 23 ++++++ components/app_update/test/test_switch_ota.c | 82 ++++++++++++++++++++ 3 files changed, 142 insertions(+) diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index 802ed943ff..5b67453766 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -250,6 +250,43 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size) return ESP_ERR_INVALID_ARG; } +esp_err_t esp_ota_write_with_offset(esp_ota_handle_t handle, const void *data, size_t size, uint32_t offset) +{ + const uint8_t *data_bytes = (const uint8_t *)data; + esp_err_t ret; + ota_ops_entry_t *it; + + if (data == NULL) { + ESP_LOGE(TAG, "write data is invalid"); + return ESP_ERR_INVALID_ARG; + } + + // find ota handle in linked list + for (it = LIST_FIRST(&s_ota_ops_entries_head); it != NULL; it = LIST_NEXT(it, entries)) { + if (it->handle == handle) { + // must erase the partition before writing to it + assert(it->erased_size > 0 && "must erase the partition before writing to it"); + + /* esp_ota_write_with_offset is used to write data in non contiguous manner. + * Hence, unaligned data(less than 16 bytes) cannot be cached if flash encryption is enabled. + */ + if (esp_flash_encryption_enabled() && (size % 16)) { + ESP_LOGE(TAG, "Size should be 16byte aligned for flash encryption case"); + return ESP_ERR_INVALID_ARG; + } + ret = esp_partition_write(it->part, offset, data_bytes, size); + if (ret == ESP_OK) { + it->wrote_size += size; + } + return ret; + } + } + + // OTA handle is not found in linked list + ESP_LOGE(TAG,"OTA handle not found"); + return ESP_ERR_INVALID_ARG; +} + esp_err_t esp_ota_end(esp_ota_handle_t handle) { ota_ops_entry_t *it; diff --git a/components/app_update/include/esp_ota_ops.h b/components/app_update/include/esp_ota_ops.h index 4dc2b80fec..3bb063d3c0 100644 --- a/components/app_update/include/esp_ota_ops.h +++ b/components/app_update/include/esp_ota_ops.h @@ -117,6 +117,29 @@ esp_err_t esp_ota_begin(const esp_partition_t* partition, size_t image_size, esp */ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void* data, size_t size); +/** + * @brief Write OTA update data to partition + * + * This function can write data in non contiguous manner. + * If flash encryption is enabled, data should be 16 byte aligned. + * + * @param handle Handle obtained from esp_ota_begin + * @param data Data buffer to write + * @param size Size of data buffer in bytes + * @param offset Offset in flash partition + * + * @note While performing OTA, if the packets arrive out of order, esp_ota_write_with_offset() can be used to write data in non contiguous manner. + * Use of esp_ota_write_with_offset() in combination with esp_ota_write() is not recommended. + * + * @return + * - ESP_OK: Data was written to flash successfully. + * - ESP_ERR_INVALID_ARG: handle is invalid. + * - ESP_ERR_OTA_VALIDATE_FAILED: First byte of image contains invalid app image magic byte. + * - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed. + * - ESP_ERR_OTA_SELECT_INFO_INVALID: OTA data partition has invalid contents + */ +esp_err_t esp_ota_write_with_offset(esp_ota_handle_t handle, const void *data, size_t size, uint32_t offset); + /** * @brief Finish OTA update and validate newly written app image. * diff --git a/components/app_update/test/test_switch_ota.c b/components/app_update/test/test_switch_ota.c index 80d0594f99..93e75a1308 100644 --- a/components/app_update/test/test_switch_ota.c +++ b/components/app_update/test/test_switch_ota.c @@ -55,6 +55,29 @@ static void copy_app_partition(esp_ota_handle_t update_handle, const esp_partiti 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; + spi_flash_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, SPI_FLASH_MMAP_DATA, &partition_bin, &data_map)); + TEST_ESP_OK(esp_ota_write_with_offset(update_handle, (const void *)partition_bin, write_bytes, offset)); + spi_flash_munmap(data_map); + bytes_to_write -= write_bytes; + offset += write_bytes; + } + ESP_LOGI(TAG, "finish the copy process"); +} + #if defined(CONFIG_BOOTLOADER_FACTORY_RESET) || defined(CONFIG_BOOTLOADER_APP_TEST) /* @brief Copies partition from source partition to destination partition. * @@ -105,6 +128,26 @@ static void copy_current_app_to_next_part(const esp_partition_t *cur_app_partiti 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%x", 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) @@ -133,6 +176,16 @@ static void copy_current_app_to_next_part_and_reboot(void) 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). @@ -740,3 +793,32 @@ static void test_erase_last_app_rollback(void) // 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][timeout=90][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); + +static void test_flow6(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); + copy_current_app_to_next_part_with_offset_and_reboot(); + break; + case 3: + ESP_LOGI(TAG, "OTA0"); + TEST_ASSERT_EQUAL(ESP_PARTITION_SUBTYPE_APP_OTA_0, cur_app->subtype); + mark_app_valid(); + erase_ota_data(); + break; + default: + erase_ota_data(); + TEST_FAIL_MESSAGE("Unexpected stage"); + break; + } +} + +// 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 -> erase OTA_DATA for next tests -> PASS +TEST_CASE_MULTIPLE_STAGES("Switching between factory, OTA0 using esp_ota_write_with_offset", "[app_update][timeout=90][reset=DEEPSLEEP_RESET, DEEPSLEEP_RESET]", start_test, test_flow6, test_flow6);