Skip to content

Commit

Permalink
Merge branch 'feature/add_ota_resumption_feature_in_advanced_ota_exam…
Browse files Browse the repository at this point in the history
…ple' into 'master'

feat(app_utils): add ota resumption feature in advanced_ota_example

Closes IDF-2455, IDF-10421, and IDFGH-12065

See merge request espressif/esp-idf!32927
  • Loading branch information
mahavirj committed Jan 24, 2025
2 parents 5950be0 + 50adc8b commit 23ce1cc
Show file tree
Hide file tree
Showing 14 changed files with 588 additions and 33 deletions.
85 changes: 75 additions & 10 deletions components/app_update/esp_ota_ops.c
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -47,6 +47,7 @@ typedef struct ota_ops_entry_ {
bool need_erase;
uint32_t wrote_size;
uint8_t partial_bytes;
bool ota_resumption;
WORD_ALIGNED_ATTR uint8_t partial_data[16];
LIST_ENTRY(ota_ops_entry_) entries;
} ota_ops_entry_t;
Expand Down Expand Up @@ -119,6 +120,24 @@ static esp_ota_img_states_t set_new_state_otadata(void)
#endif
}

static ota_ops_entry_t* esp_ota_init_entry(const esp_partition_t *partition)
{
ota_ops_entry_t *new_entry = (ota_ops_entry_t *) calloc(1, sizeof(ota_ops_entry_t));
if (new_entry == NULL) {
return NULL;
}

LIST_INSERT_HEAD(&s_ota_ops_entries_head, new_entry, entries);

new_entry->partition.staging = partition;
new_entry->partition.final = partition;
new_entry->partition.finalize_with_copy = false;
new_entry->handle = ++s_ota_ops_last_handle;

return new_entry;
}


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;
Expand Down Expand Up @@ -153,17 +172,10 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp
#endif
}

new_entry = (ota_ops_entry_t *) calloc(1, sizeof(ota_ops_entry_t));
new_entry = esp_ota_init_entry(partition);
if (new_entry == NULL) {
return ESP_ERR_NO_MEM;
}

LIST_INSERT_HEAD(&s_ota_ops_entries_head, new_entry, entries);

new_entry->partition.staging = partition;
new_entry->partition.final = partition;
new_entry->partition.finalize_with_copy = false;
new_entry->handle = ++s_ota_ops_last_handle;
new_entry->need_erase = (image_size == OTA_WITH_SEQUENTIAL_WRITES);
*out_handle = new_entry->handle;

Expand Down Expand Up @@ -197,6 +209,54 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp
return ESP_OK;
}

esp_err_t esp_ota_resume(const esp_partition_t *partition, const size_t erase_size, const size_t image_offset, esp_ota_handle_t *out_handle)
{
ota_ops_entry_t *new_entry;

if ((partition == NULL) || (out_handle == NULL)) {
return ESP_ERR_INVALID_ARG;
}

if (image_offset > partition->size) {
return ESP_ERR_INVALID_ARG;
}

partition = esp_partition_verify(partition);
if (partition == NULL) {
return ESP_ERR_NOT_FOUND;
}

if (partition->type == ESP_PARTITION_TYPE_APP) {
// The staging partition cannot be of type Factory, but the final partition can be.
if (!is_ota_partition(partition)) {
return ESP_ERR_INVALID_ARG;
}
}

const esp_partition_t* running_partition = esp_ota_get_running_partition();
if (partition == running_partition) {
return ESP_ERR_OTA_PARTITION_CONFLICT;
}

new_entry = esp_ota_init_entry(partition);
if (new_entry == NULL) {
return ESP_ERR_NO_MEM;
}

if (partition->type == ESP_PARTITION_TYPE_BOOTLOADER) {
esp_image_bootloader_offset_set(partition->address);
}
if (partition->type == ESP_PARTITION_TYPE_BOOTLOADER || partition->type == ESP_PARTITION_TYPE_PARTITION_TABLE) {
esp_flash_set_dangerous_write_protection(esp_flash_default_chip, false);
}

new_entry->ota_resumption = true;
new_entry->wrote_size = image_offset;
new_entry->need_erase = (erase_size == OTA_WITH_SEQUENTIAL_WRITES);
*out_handle = new_entry->handle;
return ESP_OK;
}

esp_err_t esp_ota_set_final_partition(esp_ota_handle_t handle, const esp_partition_t *final, bool finalize_with_copy)
{
ota_ops_entry_t *it = get_ota_ops_entry(handle);
Expand All @@ -206,9 +266,14 @@ esp_err_t esp_ota_set_final_partition(esp_ota_handle_t handle, const esp_partiti
if (it == NULL) {
return ESP_ERR_NOT_FOUND;
}
if (it->wrote_size != 0) {

// If OTA resumption is enabled, it->wrote_size may already contain the size of previously written data.
// Ensure that wrote_size is zero only when OTA resumption is disabled, as any non-zero value in this case
// indicates an invalid state.
if (!it->ota_resumption && it->wrote_size != 0) {
return ESP_ERR_INVALID_STATE;
}

if (it->partition.staging != final) {
const esp_partition_t* final_partition = esp_partition_verify(final);
if (final_partition == NULL) {
Expand Down
26 changes: 26 additions & 0 deletions components/app_update/include/esp_ota_ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,32 @@ int esp_ota_get_app_elf_sha256(char* dst, size_t size) __attribute__((deprecated
*/
esp_err_t esp_ota_begin(const esp_partition_t* partition, size_t image_size, esp_ota_handle_t* out_handle);

/**
* @brief Resume an interrupted OTA update by continuing to write to the specified partition.
*
* This function is used when an OTA update was previously started and needs to be resumed after an interruption.
* It continues the OTA process from the specified offset within the partition.
*
* Unlike esp_ota_begin(), this function does not erase the partition which receives the OTA update, but rather expects that part of the image
* has already been written correctly, and it resumes writing from the given offset.
*
* @param partition Pointer to info for the partition which is receiving the OTA update. Required.
* @param erase_size Specifies how much flash memory to erase before resuming OTA, depending on whether a sequential write or a bulk erase is being used.
* @param image_offset Offset from where to resume the OTA process. Should be set to the number of bytes already written.
* @param out_handle On success, returns a handle that should be used for subsequent esp_ota_write() and esp_ota_end() calls.
*
* @return
* - ESP_OK: OTA operation resumed successfully.
* - ESP_ERR_INVALID_ARG: partition, out_handle were NULL or image_offset arguments is negative, or partition doesn't point to an OTA app partition.
* - ESP_ERR_NO_MEM: Cannot allocate memory for OTA operation.
* - ESP_ERR_OTA_PARTITION_CONFLICT: Partition holds the currently running firmware, cannot update in place.
* - ESP_ERR_NOT_FOUND: Partition argument not found in partition table.
* - ESP_ERR_OTA_SELECT_INFO_INVALID: The OTA data partition contains invalid data.
* - ESP_ERR_INVALID_SIZE: Partition doesn't fit in configured flash size.
* - ESP_ERR_FLASH_OP_TIMEOUT or ESP_ERR_FLASH_OP_FAIL: Flash write failed.
*/
esp_err_t esp_ota_resume(const esp_partition_t *partition, const size_t erase_size, const size_t image_offset, esp_ota_handle_t *out_handle);

/**
* @brief Set the final destination partition for OTA update
*
Expand Down
4 changes: 4 additions & 0 deletions components/esp_common/src/esp_err_to_name.c
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,10 @@ static const esp_err_msg_t esp_err_msg_table[] = {
# endif
# ifdef ESP_ERR_HTTP_NOT_MODIFIED
ERR_TBL_IT(ESP_ERR_HTTP_NOT_MODIFIED), /* 28681 0x7009 HTTP 304 Not Modified, no update available */
# endif
# ifdef ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE
ERR_TBL_IT(ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE), /* 28682 0x700a HTTP 416 Range Not Satisfiable,
requested range in header is incorrect */
# endif
// components/esp-tls/esp_tls_errors.h
# ifdef ESP_ERR_ESP_TLS_BASE
Expand Down
2 changes: 2 additions & 0 deletions components/esp_http_client/include/esp_http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ typedef enum {
HttpStatus_Unauthorized = 401,
HttpStatus_Forbidden = 403,
HttpStatus_NotFound = 404,
HttpStatus_RangeNotSatisfiable = 416,

/* 5xx - Server Error */
HttpStatus_InternalError = 500
Expand All @@ -242,6 +243,7 @@ typedef enum {
#define ESP_ERR_HTTP_EAGAIN (ESP_ERR_HTTP_BASE + 7) /*!< Mapping of errno EAGAIN to esp_err_t */
#define ESP_ERR_HTTP_CONNECTION_CLOSED (ESP_ERR_HTTP_BASE + 8) /*!< Read FIN from peer and the connection closed */
#define ESP_ERR_HTTP_NOT_MODIFIED (ESP_ERR_HTTP_BASE + 9) /*!< HTTP 304 Not Modified, no update available */
#define ESP_ERR_HTTP_RANGE_NOT_SATISFIABLE (ESP_ERR_HTTP_BASE + 10) /*!< HTTP 416 Range Not Satisfiable, requested range in header is incorrect */

/**
* @brief Start a HTTP session
Expand Down
2 changes: 2 additions & 0 deletions components/esp_https_ota/include/esp_https_ota.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ typedef struct {
bool partial_http_download; /*!< Enable Firmware image to be downloaded over multiple HTTP requests */
int max_http_request_size; /*!< Maximum request size for partial HTTP download */
uint32_t buffer_caps; /*!< The memory capability to use when allocating the buffer for OTA update. Default capability is MALLOC_CAP_DEFAULT */
bool ota_resumption; /*!< Enable resumption in downloading of OTA image between reboots */
size_t ota_image_bytes_written; /*!< Number of OTA image bytes written to flash so far, updated by the application when OTA data is written successfully in the target OTA partition. */
#if CONFIG_ESP_HTTPS_OTA_DECRYPT_CB || __DOXYGEN__
decrypt_cb_t decrypt_cb; /*!< Callback for external decryption layer */
void *decrypt_user_ctx; /*!< User context for external decryption layer */
Expand Down
Loading

0 comments on commit 23ce1cc

Please sign in to comment.