diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index efd697d07d..ca46765c79 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -33,6 +33,7 @@ #include "esp_ota_ops.h" #include "rom/queue.h" #include "rom/crc.h" +#include "soc/dport_reg.h" #include "esp_log.h" @@ -75,6 +76,9 @@ esp_err_t esp_ota_begin(const esp_partition_t *partition, size_t image_size, esp if ((partition == NULL) || (out_handle == NULL)) { return ESP_ERR_INVALID_ARG; } + if (partition == esp_ota_get_running_partition()) { + return ESP_ERR_OTA_PARTITION_CONFLICT; + } ota_ops_entry_t *new_entry = (ota_ops_entry_t *) calloc(sizeof(ota_ops_entry_t), 1); @@ -446,3 +450,73 @@ const esp_partition_t *esp_ota_get_boot_partition(void) return esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL); } } + + +const esp_partition_t* esp_ota_get_running_partition(void) +{ + /* Find the flash address of this exact function. By definition that is part + of the currently running firmware. Then find the enclosing partition. */ + + size_t phys_offs = spi_flash_cache2phys(esp_ota_get_running_partition); + + assert (phys_offs != SPI_FLASH_CACHE2PHYS_FAIL); /* indicates cache2phys lookup is buggy */ + + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, + ESP_PARTITION_SUBTYPE_ANY, + NULL); + assert(it != NULL); /* has to be at least one app partition */ + + while (it != NULL) { + const esp_partition_t *p = esp_partition_get(it); + if (p->address <= phys_offs && p->address + p->size > phys_offs) { + esp_partition_iterator_release(it); + return p; + } + it = esp_partition_next(it); + } + esp_partition_iterator_release(it); + return NULL; +} + + +const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *start_from) +{ + const esp_partition_t *result = NULL; + bool next_is_result = false; + if (start_from == NULL) { + start_from = esp_ota_get_running_partition(); + } + assert (start_from != NULL); + + /* Two possibilities: either we want the OTA partition immediately after the + current running OTA partition, or we want the first OTA partition we see (for + the case when the last OTA partition is the running partition, or if the current + running partition is not OTA.) + */ + + esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, + ESP_PARTITION_SUBTYPE_ANY, + NULL); + while (it != NULL) { + const esp_partition_t *p = esp_partition_get(it); + if(p->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_0 + && p->subtype < ESP_PARTITION_SUBTYPE_APP_OTA_MAX) { + /* is OTA partition */ + if (p == start_from || p->address == start_from->address) { + next_is_result = true; /* next OTA partition is the one */ + } + else if (next_is_result) { + result = p; /* this is it! */ + break; + } + else if (result == NULL) { + result = p; /* first OTA partition is the fallback */ + } + } + it = esp_partition_next(it); + } + + esp_partition_iterator_release(it); + return result; + +} diff --git a/components/app_update/include/esp_ota_ops.h b/components/app_update/include/esp_ota_ops.h index dbe53b9f4f..8f99b23300 100755 --- a/components/app_update/include/esp_ota_ops.h +++ b/components/app_update/include/esp_ota_ops.h @@ -59,9 +59,10 @@ typedef uint32_t esp_ota_handle_t; * @return * - ESP_OK: OTA operation commenced successfully. - * - ESP_ERR_INVALID_ARG: partition or out_handle arguments were NULL. + * - ESP_ERR_INVALID_ARG: partition or out_handle arguments were NULL, or partition doesn't point to a non OTA app partition. * - ESP_ERR_NO_MEM: Cannot allocate memory for OTA operation. - * - ESP_ERR_OTA_PARTITION_CONFLICT: Partition is currently in use, cannot update. + * - 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. @@ -121,12 +122,45 @@ esp_err_t esp_ota_end(esp_ota_handle_t handle); esp_err_t esp_ota_set_boot_partition(const esp_partition_t* partition); /** - * @brief Get partition info of currently running app + * @brief Get partition info of currently configured boot app + * + * If esp_ota_set_boot_partition() has been called, the partition which was set by that function will be returned. + * + * If esp_ota_set_boot_partition() has not been called, the result is + * equivalent to esp_ota_get_running_partition(). * * @return Pointer to info for partition structure, or NULL if no partition is found or flash read operation failed. Returned pointer is valid for the lifetime of the application. */ const esp_partition_t* esp_ota_get_boot_partition(void); + +/** + * @brief Get partition info of currently running app + * + * This function is different to esp_ota_get_boot_partition() in that + * it ignores any change of selected boot partition caused by + * esp_ota_set_boot_partition(). Only the app whose code is currently + * running will have its partition information returned. + * + * @return Pointer to info for partition structure, or NULL if no partition is found or flash read operation failed. Returned pointer is valid for the lifetime of the application. + */ +const esp_partition_t* esp_ota_get_running_partition(void); + + +/** + * @brief Return the next OTA app partition which should be written with a new firmware. + * + * Call this function to find an OTA app partition which can be passed to esp_ota_begin(). + * + * Finds next partition round-robin, starting from the current running partition. + * + * @param start_from If set, treat this partition info as describing the current running partition. Can be NULL, in which case esp_ota_get_running_partition() is used to find the currently running partition. The result of this function is never the same as this argument. + * + * @return Pointer to info for partition which should be updated next. NULL result indicates invalid OTA data partition, or that no eligible OTA app slot partition was found. + * + */ +const esp_partition_t* esp_ota_get_next_update_partition(const esp_partition_t *start_from); + #ifdef __cplusplus } #endif diff --git a/components/app_update/test/component.mk b/components/app_update/test/component.mk new file mode 100644 index 0000000000..5dd172bdb7 --- /dev/null +++ b/components/app_update/test/component.mk @@ -0,0 +1,5 @@ +# +#Component Makefile +# + +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/components/app_update/test/test_ota_ops.c b/components/app_update/test/test_ota_ops.c new file mode 100644 index 0000000000..c5bfc2ebef --- /dev/null +++ b/components/app_update/test/test_ota_ops.c @@ -0,0 +1,40 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + + +/* These OTA tests currently don't assume an OTA partition exists + on the device, so they're a bit limited +*/ + +TEST_CASE("esp_ota_begin() verifies arguments", "[ota]") +{ + const esp_partition_t *running = esp_ota_get_running_partition(); + esp_partition_t partition; + static esp_ota_handle_t handle = 0; + + if (handle != 0) { /* clean up from any previous test */ + esp_ota_end(handle); + handle = 0; + } + + /* running partition & configured boot partition are same */ + TEST_ASSERT_NOT_NULL(running); + + /* trying to 'begin' on running partition fails */ + TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_ota_begin(running, OTA_SIZE_UNKNOWN, &handle)); + TEST_ASSERT_EQUAL(0, handle); + + memcpy(&partition, running, sizeof(esp_partition_t)); + partition.address--; + + /* non existent partition fails */ + TEST_ASSERT_EQUAL_HEX(ESP_ERR_NOT_FOUND, esp_ota_begin(&partition, OTA_SIZE_UNKNOWN, &handle)); + TEST_ASSERT_EQUAL(0, handle); +} diff --git a/components/bootloader_support/src/esp_image_format.c b/components/bootloader_support/src/esp_image_format.c index 8b156680c6..942ffd4bf8 100644 --- a/components/bootloader_support/src/esp_image_format.c +++ b/components/bootloader_support/src/esp_image_format.c @@ -108,6 +108,16 @@ esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *p *p_length = 0; } + if (src_addr % SPI_FLASH_MMU_PAGE_SIZE != 0) { + /* Image must start on a 64KB boundary + + (This is not a technical limitation, only the flash mapped regions need to be 64KB aligned. But the most + consistent way to do this is to have all the offsets internal to the image correctly 64KB aligned, and then + start the image on a 64KB boundary also.) + */ + return ESP_ERR_INVALID_ARG; + } + err = esp_image_load_header(src_addr, log_errors, &image_header); if (err != ESP_OK) { return err; diff --git a/components/spi_flash/include/esp_partition.h b/components/spi_flash/include/esp_partition.h index 28f8551dcb..ba1327ab74 100644 --- a/components/spi_flash/include/esp_partition.h +++ b/components/spi_flash/include/esp_partition.h @@ -63,7 +63,7 @@ typedef enum { ESP_PARTITION_SUBTYPE_APP_OTA_13 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 13,//!< OTA partition 13 ESP_PARTITION_SUBTYPE_APP_OTA_14 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 14,//!< OTA partition 14 ESP_PARTITION_SUBTYPE_APP_OTA_15 = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 15,//!< OTA partition 15 - ESP_PARTITION_SUBTYPE_APP_OTA_MAX = 15, //!< Max subtype of OTA partition + ESP_PARTITION_SUBTYPE_APP_OTA_MAX = ESP_PARTITION_SUBTYPE_APP_OTA_MIN + 16,//!< Max subtype of OTA partition ESP_PARTITION_SUBTYPE_APP_TEST = 0x20, //!< Test application partition ESP_PARTITION_SUBTYPE_DATA_OTA = 0x00, //!< OTA selection partition diff --git a/docs/api/system/ota.rst b/docs/api/system/ota.rst index 3fd0dd25c9..eaabeae3a8 100644 --- a/docs/api/system/ota.rst +++ b/docs/api/system/ota.rst @@ -71,5 +71,7 @@ Functions .. doxygenfunction:: esp_ota_begin .. doxygenfunction:: esp_ota_write .. doxygenfunction:: esp_ota_end +.. doxygenfunction:: esp_ota_get_running_partition .. doxygenfunction:: esp_ota_set_boot_partition .. doxygenfunction:: esp_ota_get_boot_partition +.. doxygenfunction:: esp_ota_get_next_update_partition diff --git a/examples/system/ota/main/ota.c b/examples/system/ota/main/ota_example.c similarity index 78% rename from examples/system/ota/main/ota.c rename to examples/system/ota/main/ota_example.c index 2dfda1c828..06ed81e638 100644 --- a/examples/system/ota/main/ota.c +++ b/examples/system/ota/main/ota_example.c @@ -33,17 +33,14 @@ static const char *TAG = "ota"; /*an ota data write buffer ready to write to the flash*/ -char ota_write_data[BUFFSIZE + 1] = { 0 }; +static char ota_write_data[BUFFSIZE + 1] = { 0 }; /*an packet receive buffer*/ -char text[BUFFSIZE + 1] = { 0 }; +static char text[BUFFSIZE + 1] = { 0 }; /* an image total length*/ -int binary_file_length = 0; +static int binary_file_length = 0; /*socket id*/ -int socket_id = -1; -char http_request[64] = {0}; -/* operate handle : uninitialized value is zero ,every ota begin would exponential growth*/ -esp_ota_handle_t out_handle = 0; -esp_partition_t operate_partition; +static int socket_id = -1; +static char http_request[64] = {0}; /* FreeRTOS event group to signal when we are connected & ready to make a request */ static EventGroupHandle_t wifi_event_group; @@ -109,7 +106,7 @@ static int read_until(char *buffer, char delim, int len) * return true if packet including \r\n\r\n that means http packet header finished,start to receive packet body * otherwise return false * */ -static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t out_handle) +static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t update_handle) { /* i means current position */ int i = 0, i_read_len = 0; @@ -122,7 +119,7 @@ static bool read_past_http_header(char text[], int total_len, esp_ota_handle_t o /*copy first http packet body to write buffer*/ memcpy(ota_write_data, &(text[i + 2]), i_write_len); - esp_err_t err = esp_ota_write( out_handle, (const void *)ota_write_data, i_write_len); + esp_err_t err = esp_ota_write( update_handle, (const void *)ota_write_data, i_write_len); if (err != ESP_OK) { ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err); return false; @@ -170,48 +167,6 @@ bool connect_to_http_server() return false; } -bool ota_init() -{ - esp_err_t err; - const esp_partition_t *esp_current_partition = esp_ota_get_boot_partition(); - if (esp_current_partition->type != ESP_PARTITION_TYPE_APP) { - ESP_LOGE(TAG, "Error: esp_current_partition->type != ESP_PARTITION_TYPE_APP"); - return false; - } - - esp_partition_t find_partition; - memset(&operate_partition, 0, sizeof(esp_partition_t)); - /*choose which OTA image should we write to*/ - switch (esp_current_partition->subtype) { - case ESP_PARTITION_SUBTYPE_APP_FACTORY: - find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0; - break; - case ESP_PARTITION_SUBTYPE_APP_OTA_0: - find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_1; - break; - case ESP_PARTITION_SUBTYPE_APP_OTA_1: - find_partition.subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0; - break; - default: - break; - } - find_partition.type = ESP_PARTITION_TYPE_APP; - - const esp_partition_t *partition = esp_partition_find_first(find_partition.type, find_partition.subtype, NULL); - assert(partition != NULL); - memset(&operate_partition, 0, sizeof(esp_partition_t)); - err = esp_ota_begin( partition, OTA_SIZE_UNKNOWN, &out_handle); - if (err != ESP_OK) { - ESP_LOGE(TAG, "esp_ota_begin failed err=0x%x!", err); - return false; - } else { - memcpy(&operate_partition, partition, sizeof(esp_partition_t)); - ESP_LOGI(TAG, "esp_ota_begin init OK"); - return true; - } - return false; -} - void __attribute__((noreturn)) task_fatal_error() { ESP_LOGE(TAG, "Exiting task due to fatal error..."); @@ -226,7 +181,19 @@ void __attribute__((noreturn)) task_fatal_error() void main_task(void *pvParameter) { esp_err_t err; + /* update handle : set by esp_ota_begin(), must be freed via esp_ota_end() */ + esp_ota_handle_t update_handle = 0 ; + const esp_partition_t *update_partition = NULL; + ESP_LOGI(TAG, "Starting OTA example..."); + + const esp_partition_t *configured = esp_ota_get_boot_partition(); + const esp_partition_t *running = esp_ota_get_running_partition(); + + assert(configured == running); /* fresh from reset, should be running from configured boot partition */ + ESP_LOGI(TAG, "Running partition type %d subtype %d (offset 0x%08x)", + configured->type, configured->subtype, configured->address); + /* Wait for the callback to set the CONNECTED_BIT in the event group. */ @@ -252,12 +219,17 @@ void main_task(void *pvParameter) ESP_LOGI(TAG, "Send GET request to server succeeded"); } - if ( ota_init() ) { - ESP_LOGI(TAG, "OTA Init succeeded"); - } else { - ESP_LOGE(TAG, "OTA Init failed"); + update_partition = esp_ota_get_next_update_partition(NULL); + ESP_LOGI(TAG, "Writing to partition subtype %d at offset 0x%x", + update_partition->subtype, update_partition->address); + assert(update_partition != NULL); + + err = esp_ota_begin(update_partition, OTA_SIZE_UNKNOWN, &update_handle); + if (err != ESP_OK) { + ESP_LOGE(TAG, "esp_ota_begin failed, error=%d", err); task_fatal_error(); } + ESP_LOGI(TAG, "esp_ota_begin succeeded"); bool resp_body_start = false, flag = true; /*deal with all receive packet*/ @@ -270,10 +242,10 @@ void main_task(void *pvParameter) task_fatal_error(); } else if (buff_len > 0 && !resp_body_start) { /*deal with response header*/ memcpy(ota_write_data, text, buff_len); - resp_body_start = read_past_http_header(text, buff_len, out_handle); + resp_body_start = read_past_http_header(text, buff_len, update_handle); } else if (buff_len > 0 && resp_body_start) { /*deal with response body*/ memcpy(ota_write_data, text, buff_len); - err = esp_ota_write( out_handle, (const void *)ota_write_data, buff_len); + err = esp_ota_write( update_handle, (const void *)ota_write_data, buff_len); if (err != ESP_OK) { ESP_LOGE(TAG, "Error: esp_ota_write failed! err=0x%x", err); task_fatal_error(); @@ -291,11 +263,11 @@ void main_task(void *pvParameter) ESP_LOGI(TAG, "Total Write binary data length : %d", binary_file_length); - if (esp_ota_end(out_handle) != ESP_OK) { + if (esp_ota_end(update_handle) != ESP_OK) { ESP_LOGE(TAG, "esp_ota_end failed!"); task_fatal_error(); } - err = esp_ota_set_boot_partition(&operate_partition); + err = esp_ota_set_boot_partition(update_partition); if (err != ESP_OK) { ESP_LOGE(TAG, "esp_ota_set_boot_partition failed! err=0x%x", err); task_fatal_error();