ESPHome 2026.9.0-dev
Loading...
Searching...
No Matches
ota_bootloader_esp_idf.cpp
Go to the documentation of this file.
1#ifdef USE_ESP32
3
6
7#ifdef USE_OTA_PARTITIONS
8#include "esphome/core/log.h"
9
10#include <esp_image_format.h>
11#include <esp_ota_ops.h>
12
13namespace esphome::ota {
14
15static const char *const TAG = "ota";
16
18 // Register the bootloader partition
19 esp_err_t err = esp_partition_register_external(nullptr, ESP_PRIMARY_BOOTLOADER_OFFSET, ESP_BOOTLOADER_SIZE,
20 "PrimaryBTLDR", ESP_PARTITION_TYPE_BOOTLOADER,
21 ESP_PARTITION_SUBTYPE_BOOTLOADER_PRIMARY, &this->bootloader_part_);
22 if (err != ESP_OK) {
23 ESP_LOGE(TAG, "esp_partition_register_external failed (bootloader) (err=0x%X)", err);
25 }
26
27 // Verify existing bootloader to make sure ESP_PRIMARY_BOOTLOADER_OFFSET is correct
28 esp_image_metadata_t data = {};
29 const esp_partition_pos_t part_pos = {
30 .offset = this->bootloader_part_->address,
31 .size = this->bootloader_part_->size,
32 };
33 err = esp_image_verify(ESP_IMAGE_VERIFY, &part_pos, &data);
34 if (err != ESP_OK) {
35 ESP_LOGE(TAG, "esp_image_verify failed (existing bootloader) (err=0x%X)", err);
37 }
38 return OTA_RESPONSE_OK;
39}
40
41// Pre-esp_ota_begin: enforce size limit, register/verify the existing bootloader, and validate the
42// partition table to confirm the bootloader region is at the expected offset (and therefore the
43// expected size). The partition table registration is released here; abort() cleans up the
44// bootloader registration if any later step fails.
46 if (image_size > ESP_BOOTLOADER_SIZE) {
47 ESP_LOGE(TAG, "Length of received data exceeds the available bootloader size: expected <=%zu bytes, got %zu",
48 ESP_BOOTLOADER_SIZE, image_size);
50 }
52 if (result != OTA_RESPONSE_OK) {
53 return result;
54 }
56 if (result != OTA_RESPONSE_OK) {
58 }
59 esp_partition_deregister_external(this->partition_table_part_);
60 this->partition_table_part_ = nullptr;
61 return OTA_RESPONSE_OK;
62}
63
64// Post-esp_ota_begin: verify the staging app partition is large enough, erase it, and redirect the
65// final write target to the bootloader partition. esp_ota_set_final_partition is called with
66// `restore_old_data=false` because we erased the staging region in advance.
68 if (this->partition_->size < this->bootloader_part_->size) {
69 ESP_LOGE(TAG, "Staging partition too small");
71 }
72 // Erase full size of the bootloader partition in the staging partition
73 // to avoid copying old data to the bootloader partition later. Up to
74 // ESP_BOOTLOADER_SIZE of blocking erase; widen the WDT for its duration.
75 watchdog::WatchdogManager watchdog(15000);
76 esp_err_t err = esp_partition_erase_range(this->partition_, 0, this->bootloader_part_->size);
77 if (err != ESP_OK) {
78 ESP_LOGW(TAG, "esp_partition_erase_range failed (err=0x%X)", err);
79 // No critical error, don't return
80 }
81#ifdef USE_OTA_BLOCK_ERASE_AHEAD
82 if (err == ESP_OK) {
83 // Skip re-erasing the pre-erased staging region in erase_ahead_()
84 this->erased_end_ = this->bootloader_part_->size;
85 }
86#endif
87 err = esp_ota_set_final_partition(this->update_handle_, this->bootloader_part_, false);
88 if (err != ESP_OK) {
89 esp_ota_abort(this->update_handle_);
90 this->update_handle_ = 0;
91 ESP_LOGE(TAG, "esp_ota_set_final_partition failed (err=0x%X)", err);
93 }
94 return OTA_RESPONSE_OK;
95}
96
97// After esp_ota_end: copy the staged image into the bootloader partition. esp_partition_copy is
98// the only window in which a power loss can render the device unbootable; everything before this
99// point either preserves the existing bootloader or fails harmlessly. After a successful copy the
100// first sector of staging is wiped so the device can't accidentally boot from it, and the
101// bootloader partition is deregistered.
103 if (ota_end_err != ESP_OK) {
105 }
106#ifdef USE_OTA_SIGNED_VERIFICATION_MULTI_KEY
107 // The new bootloader is staged in partition_. IDF never signature-checks a
108 // bootloader image in this software-signed config -- esp_image_verify() skips
109 // it when is_bootloader() is true -- so without this a bootloader OTA would
110 // install unverified. Require a trusted signature, which means the bootloader
111 // must be externally signed and 4 KiB-padded, the same as the app.
112 if (!this->verify_signed_image_(this->partition_)) {
113 ESP_LOGE(TAG, "Bootloader image is not signed by a trusted key; a bootloader OTA requires an "
114 "externally-signed, 4 KiB-padded bootloader.bin");
116 }
117#endif
118 esp_bootloader_desc_t bootloader_desc;
119 esp_err_t desc_err = esp_ota_get_bootloader_description(this->partition_, &bootloader_desc);
120#ifdef USE_ESP32_SRAM1_AS_IRAM
121 if (desc_err != ESP_OK) {
122 ESP_LOGE(TAG, "New bootloader does not support SRAM1 as IRAM");
124 }
125#endif
126 ESP_LOGE(TAG, "Starting bootloader update.\n"
127 " DO NOT REMOVE POWER until the update completes successfully.\n"
128 " Loss of power during this operation may render the device\n"
129 " unable to boot until it is recovered via a serial flash.");
130 esp_err_t err = esp_partition_copy(this->bootloader_part_, 0, this->partition_, 0, this->bootloader_part_->size);
131 if (err != ESP_OK) {
132 ESP_LOGE(TAG, "esp_partition_copy failed (err=0x%X)", err);
133 // Only if esp_partition_copy failed there's a chance of the device being unbootable
135 }
136 ESP_LOGI(TAG,
137 "Successfully installed the new bootloader\n"
138 " ESP-IDF %s",
139 (desc_err == ESP_OK) ? bootloader_desc.idf_ver : "version unknown");
140 // Wipe first sector of staging partition to make sure the device can't boot from it
141 err = esp_partition_erase_range(this->partition_, 0, this->partition_->erase_size);
142 if (err != ESP_OK) {
143 ESP_LOGW(TAG, "esp_partition_erase_range failed (err=0x%X)", err);
144 // No critical error, don't return
145 }
146 esp_partition_deregister_external(this->bootloader_part_);
147 this->bootloader_part_ = nullptr;
148 return OTA_RESPONSE_OK;
149}
150
151} // namespace esphome::ota
152
153#endif // USE_OTA_PARTITIONS
154#endif // USE_ESP32
OTAResponseTypes finalize_bootloader_update_(esp_err_t ota_end_err)
OTAResponseTypes prepare_bootloader_update_(size_t image_size)
OTAResponseTypes register_and_validate_bootloader_part_()
OTAResponseTypes register_and_validate_partition_table_part_()
@ OTA_RESPONSE_ERROR_BOOTLOADER_UPDATE
Definition ota_backend.h:50
@ OTA_RESPONSE_ERROR_BOOTLOADER_VERIFY
Definition ota_backend.h:49