Files
esp-idf/examples/system/ota/partitions_ota/main/app_main.c

318 lines
15 KiB
C
Raw Normal View History

/*
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* OTA partitions example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_check.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"
#include "protocol_examples_common.h"
#include "string.h"
#ifdef CONFIG_EXAMPLE_USE_CERT_BUNDLE
#include "esp_crt_bundle.h"
#endif
#include "esp_flash.h"
#include "esp_flash_partitions.h"
#include "esp_partition.h"
#include "soc/soc_caps.h"
#if SOC_RECOVERY_BOOTLOADER_SUPPORTED
#include "esp_efuse.h"
#endif
#include "nvs.h"
#include "nvs_flash.h"
#include "protocol_examples_common.h"
#include <sys/socket.h>
#if CONFIG_EXAMPLE_CONNECT_WIFI
#include "esp_wifi.h"
#endif
#include "partition_utils.h"
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF
/* The interface name value can refer to if_desc in esp_netif_defaults.h */
#if CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF_ETH
static const char *bind_interface_name = EXAMPLE_NETIF_DESC_ETH;
#elif CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF_STA
static const char *bind_interface_name = EXAMPLE_NETIF_DESC_STA;
#endif
#endif
static const char *TAG = "ota_example";
extern const uint8_t server_cert_pem_start[] asm("_binary_ca_cert_pem_start");
extern const uint8_t server_cert_pem_end[] asm("_binary_ca_cert_pem_end");
#define OTA_URL_SIZE 256
esp_err_t _http_event_handler(esp_http_client_event_t *evt)
{
switch (evt->event_id) {
case HTTP_EVENT_ERROR:
ESP_LOGD(TAG, "HTTP_EVENT_ERROR");
break;
case HTTP_EVENT_ON_CONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED");
break;
case HTTP_EVENT_HEADER_SENT:
ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT");
break;
case HTTP_EVENT_ON_HEADER:
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", evt->header_key, evt->header_value);
break;
case HTTP_EVENT_ON_HEADERS_COMPLETE:
ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADERS_COMPLETE");
break;
case HTTP_EVENT_ON_DATA:
ESP_LOGD(TAG, "HTTP_EVENT_ON_DATA, len=%d", evt->data_len);
break;
case HTTP_EVENT_ON_FINISH:
ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH");
break;
case HTTP_EVENT_DISCONNECTED:
ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED");
break;
case HTTP_EVENT_REDIRECT:
ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT");
break;
}
return ESP_OK;
}
static esp_err_t register_partition(size_t offset, size_t size, const char *label, esp_partition_type_t type, esp_partition_subtype_t subtype, const esp_partition_t **p_partition)
{
// If the partition table contains this partition, then use it, otherwise register it.
*p_partition = esp_partition_find_first(type, subtype, NULL);
if ((*p_partition) == NULL) {
esp_err_t error = esp_partition_register_external(NULL, offset, size, label, type, subtype, p_partition);
if (error != ESP_OK) {
ESP_LOGE(TAG, "Failed to register %s partition (err=0x%x)", "PrimaryBTLDR", error);
return error;
}
}
ESP_LOGI(TAG, "Use <%s> partition (0x%08" PRIx32 ")", (*p_partition)->label, (*p_partition)->address);
return ESP_OK;
}
#if CONFIG_BOOTLOADER_RECOVERY_ENABLE
static esp_err_t safe_bootloader_ota_update(esp_https_ota_config_t *ota_config)
{
const esp_partition_t *primary_bootloader;
const esp_partition_t *recovery_bootloader;
ESP_ERROR_CHECK(register_partition(ESP_PRIMARY_BOOTLOADER_OFFSET, ESP_BOOTLOADER_SIZE, "PrimaryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_PRIMARY, &primary_bootloader));
ESP_ERROR_CHECK(register_partition(CONFIG_BOOTLOADER_RECOVERY_OFFSET, ESP_BOOTLOADER_SIZE, "RecoveryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_RECOVERY, &recovery_bootloader));
ESP_RETURN_ON_FALSE(recovery_bootloader->address == CONFIG_BOOTLOADER_RECOVERY_OFFSET, ESP_FAIL, TAG,
"The partition table contains <%s> (0x%08" PRIx32 "), which does not match the efuse recovery address (0x%08" PRIx32 ")", recovery_bootloader->label, recovery_bootloader->address, CONFIG_BOOTLOADER_RECOVERY_OFFSET);
// Since the recovery boot partition is registered successfully, we are sure that the flash memory size is enough to store it.
ESP_ERROR_CHECK(esp_efuse_set_recovery_bootloader_offset(CONFIG_BOOTLOADER_RECOVERY_OFFSET));
ESP_LOGI(TAG, "Backup, copy <%s> -> <%s>", primary_bootloader->label, recovery_bootloader->label);
ESP_ERROR_CHECK(esp_partition_copy(recovery_bootloader, 0, primary_bootloader, 0, primary_bootloader->size));
ota_config->partition.staging = primary_bootloader;
esp_err_t ret = esp_https_ota(ota_config);
if (ret == ESP_OK) {
// If the recovery_bootloader or primary_bootloader already exists in the partition table on flash, it will not be deregistered, and the function will return an error.
esp_partition_deregister_external(recovery_bootloader);
esp_partition_deregister_external(primary_bootloader);
}
return ret;
}
#else // !CONFIG_BOOTLOADER_RECOVERY_ENABLE
static esp_err_t unsafe_bootloader_ota_update(esp_https_ota_config_t *ota_config)
{
const esp_partition_t *primary_bootloader;
ESP_ERROR_CHECK(register_partition(ESP_PRIMARY_BOOTLOADER_OFFSET, ESP_BOOTLOADER_SIZE, "PrimaryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_PRIMARY, &primary_bootloader));
const esp_partition_t *ota_partition = esp_ota_get_next_update_partition(NULL); // free app ota partition will be used for downloading a new image
#if CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE
// Check if the passive OTA app partition is not needed for rollback before using it for other partitions.
// The same can be done for partition table and storage updates.
esp_ota_img_states_t ota_state;
ESP_ERROR_CHECK(esp_ota_get_state_partition(ota_partition, &ota_state));
if (ota_state == ESP_OTA_IMG_VALID) {
ESP_LOGW(TAG, "Passive OTA app partition <%s> contains a valid app image eligible for rollback.", ota_partition->label);
uint32_t ota_bootloader_offset;
ESP_ERROR_CHECK(partition_utils_find_unallocated(NULL, ESP_BOOTLOADER_SIZE, ESP_PARTITION_TABLE_OFFSET + ESP_PARTITION_TABLE_SIZE, &ota_bootloader_offset, NULL));
ESP_ERROR_CHECK(register_partition(ota_bootloader_offset, ESP_BOOTLOADER_SIZE, "OtaBTLDR", ESP_PARTITION_TYPE_BOOTLOADER, ESP_PARTITION_SUBTYPE_BOOTLOADER_OTA, &ota_partition));
ESP_LOGW(TAG, "To avoid overwriting the passive app partition, using the unallocated space on the flash to create a temporary OTA bootloader partition <%s>", ota_partition->label);
}
#endif
ota_config->partition.staging = ota_partition;
ota_config->partition.final = primary_bootloader;
esp_err_t ret = esp_https_ota(ota_config);
if (ret == ESP_OK) {
ESP_LOGW(TAG, "Ensure stable power supply! Loss of power at this stage leads to a chip bricking");
ESP_LOGI(TAG, "Copy from <%s> staging partition to <%s>...", ota_partition->label, primary_bootloader->label);
ret = esp_partition_copy(primary_bootloader, 0, ota_partition, 0, primary_bootloader->size);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to copy partition to Primary bootloader (err=0x%x). Bootloader likely corrupted. Device will not be able to boot again!", ret);
}
// If the primary_bootloader already exists in the partition table on flash, it will not be deregistered, and the function will return an error.
esp_partition_deregister_external(primary_bootloader);
}
return ret;
}
#endif // !CONFIG_BOOTLOADER_RECOVERY_ENABLE
static esp_err_t ota_update_partitions(esp_https_ota_config_t *ota_config)
{
esp_err_t ret = ESP_ERR_NOT_SUPPORTED;
if (strstr(ota_config->http_config->url, "partitions_ota.bin") != NULL) {
ret = esp_https_ota(ota_config);
} else if (strstr(ota_config->http_config->url, "bootloader.bin") != NULL) {
#if CONFIG_BOOTLOADER_RECOVERY_ENABLE
ESP_LOGI(TAG, "Safe OTA bootloader update: This chip version supports the recovery bootloader feature.");
ESP_LOGI(TAG, "If a failure or power loss occurs during the update, the recovery bootloader will be used.");
ESP_LOGI(TAG, "The recovery bootloader contains a backup of the original bootloader created before the OTA update.");
ret = safe_bootloader_ota_update(ota_config);
#else
ESP_LOGW(TAG, "Unsafe OTA bootloader update: This chip version does not support the recovery bootloader feature.");
ESP_LOGW(TAG, "If a failure or power loss occurs during the final copy, the chip may become unbootable.");
ret = unsafe_bootloader_ota_update(ota_config);
#endif
} else if (strstr(ota_config->http_config->url, "partition-table.bin") != NULL) {
const esp_partition_t *primary_partition_table;
ESP_ERROR_CHECK(register_partition(ESP_PRIMARY_PARTITION_TABLE_OFFSET, ESP_PARTITION_TABLE_SIZE, "PrimaryPrtTable", ESP_PARTITION_TYPE_PARTITION_TABLE, ESP_PARTITION_SUBTYPE_PARTITION_TABLE_PRIMARY, &primary_partition_table));
const esp_partition_t *free_app_ota_partition = esp_ota_get_next_update_partition(NULL); // free app ota partition will be used for downloading a new image
ota_config->partition.staging = free_app_ota_partition;
ota_config->partition.final = primary_partition_table;
ota_config->partition.finalize_with_copy = false; // After the download is complete, do not copy the received image to the final partition automatically (it is false by default)
ret = esp_https_ota(ota_config);
if (ret == ESP_OK) {
ESP_LOGW(TAG, "Ensure stable power supply! Loss of power at this stage leads to a chip bricking.");
ESP_LOGI(TAG, "Copy from <%s> staging partition to <%s>...", free_app_ota_partition->label, primary_partition_table->label);
ret = esp_partition_copy(primary_partition_table, 0, free_app_ota_partition, 0, primary_partition_table->size);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to copy partition to Primary partition table (err=0x%x). Partition table likely corrupted. Device will not be able to boot again!", ret);
}
// If the primary_partition_table already exists in the partition table on flash, it will not be deregistered, and the function will return an error.
esp_partition_deregister_external(primary_partition_table);
}
} else if (strstr(ota_config->http_config->url, "storage.bin") != NULL) {
ota_config->partition.staging = NULL; // free app ota partition will be selected and used for downloading a new image
ota_config->partition.final = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
assert(ota_config->partition.final != NULL);
ota_config->partition.finalize_with_copy = true; // After the download is complete, copy the received image to the final partition automatically
ret = esp_https_ota(ota_config);
char text[16];
ESP_ERROR_CHECK(esp_partition_read(ota_config->partition.final, 0, text, sizeof(text)));
ESP_LOG_BUFFER_CHAR(TAG, text, sizeof(text));
assert(memcmp("7296406769363431", text, sizeof(text)) == 0);
} else {
ESP_LOGE(TAG, "Unable to load this file (%s). The final partition is unknown.", ota_config->http_config->url);
}
return ret;
}
void ota_example_task(void *pvParameter)
{
ESP_LOGI(TAG, "Starting OTA example task");
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF
esp_netif_t *netif = get_example_netif_from_desc(bind_interface_name);
if (netif == NULL) {
ESP_LOGE(TAG, "Can't find netif from interface description");
abort();
}
struct ifreq ifr;
esp_netif_get_netif_impl_name(netif, ifr.ifr_name);
ESP_LOGI(TAG, "Bind interface name is %s", ifr.ifr_name);
#endif
esp_http_client_config_t config = {
.url = CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL,
#ifdef CONFIG_EXAMPLE_USE_CERT_BUNDLE
.crt_bundle_attach = esp_crt_bundle_attach,
#else
.cert_pem = (char *)server_cert_pem_start,
#endif /* CONFIG_EXAMPLE_USE_CERT_BUNDLE */
.event_handler = _http_event_handler,
.keep_alive_enable = true,
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_BIND_IF
.if_name = &ifr,
#endif
};
#ifdef CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL_FROM_STDIN
char url_buf[OTA_URL_SIZE];
if (strcmp(config.url, "FROM_STDIN") == 0) {
example_configure_stdin_stdout();
fgets(url_buf, OTA_URL_SIZE, stdin);
int len = strlen(url_buf);
url_buf[len - 1] = '\0';
config.url = url_buf;
} else {
ESP_LOGE(TAG, "Configuration mismatch: wrong firmware upgrade image url");
abort();
}
#endif
#ifdef CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK
config.skip_cert_common_name_check = true;
#endif
esp_https_ota_config_t ota_config = {
.http_config = &config,
};
ESP_LOGI(TAG, "Attempting to download update from %s", config.url);
esp_err_t ret = ota_update_partitions(&ota_config);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "OTA Succeed, Rebooting...");
esp_restart();
} else {
ESP_LOGE(TAG, "Firmware upgrade failed");
}
while (1) {
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
void app_main(void)
{
ESP_LOGI(TAG, "OTA example app_main start");
// Initialize NVS.
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
// 1.OTA app partition table has a smaller NVS partition size than the non-OTA
// partition table. This size mismatch may cause NVS initialization to fail.
// 2.NVS partition contains data in new format and cannot be recognized by this version of code.
// If this happens, we erase NVS partition and initialize NVS again.
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
* Read "Establishing Wi-Fi or Ethernet Connection" section in
* examples/protocols/README.md for more information about this function.
*/
ESP_ERROR_CHECK(example_connect());
#if CONFIG_EXAMPLE_CONNECT_WIFI
/* Ensure to disable any WiFi power save mode, this allows best throughput
* and hence timings for overall OTA operation.
*/
esp_wifi_set_ps(WIFI_PS_NONE);
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
xTaskCreate(&ota_example_task, "ota_example_task", 8192, NULL, 5, NULL);
}