Still needs updating to account for secure boot.
esp_err_t esp_ota_end(esp_ota_handle_t handle)
{
ota_ops_entry_t *it;
- size_t image_size;
esp_err_t ret = ESP_OK;
for (it = LIST_FIRST(&s_ota_ops_entries_head); it != NULL; it = LIST_NEXT(it, entries)) {
it->partial_bytes = 0;
}
- if (esp_image_basic_verify(it->part->address, true, &image_size) != ESP_OK) {
+ esp_image_metadata_t data;
+ const esp_partition_pos_t part_pos = {
+ .offset = it->part->address,
+ .size = it->part->size,
+ };
+
+ if (esp_image_load(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup;
}
#ifdef CONFIG_SECURE_BOOT_ENABLED
- ret = esp_secure_boot_verify_signature(it->part->address, image_size);
+ ret = esp_secure_boot_verify_signature(it->part->address, data.image_length);
if (ret != ESP_OK) {
ret = ESP_ERR_OTA_VALIDATE_FAILED;
goto cleanup;
esp_err_t esp_ota_set_boot_partition(const esp_partition_t *partition)
{
- size_t image_size;
const esp_partition_t *find_partition = NULL;
if (partition == NULL) {
return ESP_ERR_INVALID_ARG;
}
- if (esp_image_basic_verify(partition->address, true, &image_size) != ESP_OK) {
+ esp_image_metadata_t data;
+ const esp_partition_pos_t part_pos = {
+ .offset = partition->address,
+ .size = partition->size,
+ };
+ if (esp_image_load(ESP_IMAGE_VERIFY, &part_pos, &data) != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED;
}
#ifdef CONFIG_SECURE_BOOT_ENABLED
- esp_err_t ret = esp_secure_boot_verify_signature(partition->address, image_size);
+ esp_err_t ret = esp_secure_boot_verify_signature(partition->address, data.image_length);
if (ret != ESP_OK) {
return ESP_ERR_OTA_VALIDATE_FAILED;
}
extern int _bss_start;
extern int _bss_end;
+extern int _data_start;
+extern int _data_end;
static const char* TAG = "boot";
-/*
-We arrive here after the bootloader finished loading the program from flash. The hardware is mostly uninitialized,
-flash cache is down and the app CPU is in reset. We do have a stack, so we can do the initialization in C.
-*/
+/* Reduce literal size for some generic string literals */
+#define MAP_MSG "Mapping segment %d as %s"
+#define MAP_ERR_MSG "Image contains multiple %s segments. Only the last one will be mapped."
void bootloader_main();
static void unpack_load_app(const esp_partition_pos_t *app_node);
-void print_flash_info(const esp_image_header_t* pfhdr);
+static void print_flash_info(const esp_image_header_t* pfhdr);
static void set_cache_and_start_app(uint32_t drom_addr,
uint32_t drom_load_addr,
uint32_t drom_size,
static void uart_console_configure(void);
static void wdt_reset_check(void);
-void IRAM_ATTR call_start_cpu0()
+/*
+ * We arrive here after the ROM bootloader finished loading this second stage bootloader from flash.
+ * The hardware is mostly uninitialized, flash cache is down and the app CPU is in reset.
+ * We do have a stack, so we can do the initialization in C.
+ */
+void call_start_cpu0()
{
cpu_configure_region_protection();
+ /* Sanity check that static RAM is after the stack */
+ int *sp = get_sp();
+ assert(&_bss_start <= &_bss_end);
+ assert(&_data_start <= &_data_end);
+ assert(sp < &_bss_start);
+ assert(sp < &_data_start);
+
//Clear bss
memset(&_bss_start, 0, (&_bss_end - &_bss_start) * sizeof(_bss_start));
memset(&bs, 0, sizeof(bs));
ESP_LOGI(TAG, "compile time " __TIME__ );
- ets_set_appcpu_boot_addr(0);
+ ets_set_appcpu_boot_addr(0);
/* disable watch dog here */
REG_CLR_BIT( RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN );
bootloader_enable_qio_mode();
#endif
- if(esp_image_load_header(0x1000, true, &fhdr) != ESP_OK) {
+ if (bootloader_flash_read(ESP_BOOTLOADER_OFFSET, &fhdr,
+ sizeof(esp_image_header_t), true) != ESP_OK) {
ESP_LOGE(TAG, "failed to load bootloader header!");
return;
}
static void unpack_load_app(const esp_partition_pos_t* partition)
{
esp_err_t err;
- esp_image_header_t image_header;
- uint32_t image_length;
+ esp_image_metadata_t data;
- /* TODO: verify the app image as part of OTA boot decision, so can have fallbacks */
- err = esp_image_basic_verify(partition->offset, true, &image_length);
+ /* TODO: load the app image as part of OTA boot decision, so can fallback if loading fails */
+ err = esp_image_load(ESP_IMAGE_LOAD, partition, &data);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to verify app image @ 0x%x (%d)", partition->offset, err);
return;
#ifdef CONFIG_SECURE_BOOT_ENABLED
if (esp_secure_boot_enabled()) {
- ESP_LOGI(TAG, "Verifying app signature @ 0x%x (length 0x%x)", partition->offset, image_length);
- err = esp_secure_boot_verify_signature(partition->offset, image_length);
+ ESP_LOGI(TAG, "Verifying app signature @ 0x%x (length 0x%x)", partition->offset, data.image_length);
+ err = esp_secure_boot_verify_signature(partition->offset, data.image_length);
if (err != ESP_OK) {
ESP_LOGE(TAG, "App image @ 0x%x failed signature verification (%d)", partition->offset, err);
return;
}
#endif
- if (esp_image_load_header(partition->offset, true, &image_header) != ESP_OK) {
- ESP_LOGE(TAG, "Failed to load app image header @ 0x%x", partition->offset);
- return;
- }
-
uint32_t drom_addr = 0;
uint32_t drom_load_addr = 0;
uint32_t drom_size = 0;
uint32_t irom_load_addr = 0;
uint32_t irom_size = 0;
- /* Reload the RTC memory segments whenever a non-deepsleep reset
- is occurring */
- bool load_rtc_memory = rtc_get_reset_reason(0) != DEEPSLEEP_RESET;
-
- ESP_LOGD(TAG, "bin_header: %u %u %u %u %08x", image_header.magic,
- image_header.segment_count,
- image_header.spi_mode,
- image_header.spi_size,
- (unsigned)image_header.entry_addr);
-
- /* Important: From here on this function cannot access any global data (bss/data segments),
- as loading the app image may overwrite these.
- */
- for (int segment = 0; segment < image_header.segment_count; segment++) {
- esp_image_segment_header_t segment_header;
- uint32_t data_offs;
- if(esp_image_load_segment_header(segment, partition->offset,
- &image_header, true,
- &segment_header, &data_offs) != ESP_OK) {
- ESP_LOGE(TAG, "failed to load segment header #%d", segment);
- return;
- }
-
- const uint32_t address = segment_header.load_addr;
- bool load = true;
- bool map = false;
- if (address == 0x00000000) { // padding, ignore block
- load = false;
- }
- if (address == 0x00000004) {
- load = false; // md5 checksum block
- // TODO: actually check md5
- }
-
- if (address >= SOC_DROM_LOW && address < SOC_DROM_HIGH) {
- ESP_LOGD(TAG, "found drom segment, map from %08x to %08x", data_offs,
- segment_header.load_addr);
- drom_addr = data_offs;
- drom_load_addr = segment_header.load_addr;
- drom_size = segment_header.data_len + sizeof(segment_header);
- load = false;
- map = true;
- }
-
- if (address >= SOC_IROM_LOW && address < SOC_IROM_HIGH) {
- ESP_LOGD(TAG, "found irom segment, map from %08x to %08x", data_offs,
- segment_header.load_addr);
- irom_addr = data_offs;
- irom_load_addr = segment_header.load_addr;
- irom_size = segment_header.data_len + sizeof(segment_header);
- load = false;
- map = true;
- }
-
- if (!load_rtc_memory && address >= SOC_RTC_IRAM_LOW && address < SOC_RTC_IRAM_HIGH) {
- ESP_LOGD(TAG, "Skipping RTC code segment at %08x\n", data_offs);
- load = false;
- }
-
- if (!load_rtc_memory && address >= SOC_RTC_DATA_LOW && address < SOC_RTC_DATA_HIGH) {
- ESP_LOGD(TAG, "Skipping RTC data segment at %08x\n", data_offs);
- load = false;
- }
-
- ESP_LOGI(TAG, "segment %d: paddr=0x%08x vaddr=0x%08x size=0x%05x (%6d) %s", segment, data_offs - sizeof(esp_image_segment_header_t),
- segment_header.load_addr, segment_header.data_len, segment_header.data_len, (load)?"load":(map)?"map":"");
-
- if (load) {
- intptr_t sp, start_addr, end_addr;
- ESP_LOGV(TAG, "bootloader_mmap data_offs=%08x data_len=%08x", data_offs, segment_header.data_len);
-
- start_addr = segment_header.load_addr;
- end_addr = start_addr + segment_header.data_len;
-
- /* Before loading segment, check it doesn't clobber
- bootloader RAM... */
-
- if (end_addr < 0x40000000) {
- if (end_addr > 0x3FFE0000) {
- /* Temporary workaround for an ugly crash, until we allow >192KB of static DRAM */
- ESP_LOGE(TAG, "DRAM segment %d (start 0x%08x end 0x%08x) too large for IDF to boot",
- segment, start_addr, end_addr);
- return;
- }
-
- sp = (intptr_t)get_sp();
- if (end_addr > sp) {
- ESP_LOGE(TAG, "Segment %d end address %08x overlaps bootloader stack %08x - can't load",
- segment, end_addr, sp);
- return;
- }
- if (end_addr > sp - 256) {
- /* We don't know for sure this is the stack high water mark, so warn if
- it seems like we may overflow.
- */
- ESP_LOGW(TAG, "Segment %d end address %08x close to stack pointer %08x",
- segment, end_addr, sp);
- }
+ // Find DROM & IROM addresses, to configure cache mappings
+ for (int i = 0; i < data.image.segment_count; i++) {
+ esp_image_segment_header_t *header = &data.segments[i];
+ if (header->load_addr >= SOC_IROM_LOW && header->load_addr < SOC_IROM_HIGH) {
+ if (drom_addr != 0) {
+ ESP_LOGE(TAG, MAP_ERR_MSG, "DROM");
+ } else {
+ ESP_LOGD(TAG, "Mapping segment %d as %s", i, "DROM");
}
-
- const void *data = bootloader_mmap(data_offs, segment_header.data_len);
- if(!data) {
- ESP_LOGE(TAG, "bootloader_mmap(0x%xc, 0x%x) failed",
- data_offs, segment_header.data_len);
- return;
+ drom_addr = data.segment_data[i];
+ drom_load_addr = header->load_addr;
+ drom_size = header->data_len;
+ }
+ if (header->load_addr >= SOC_DROM_LOW && header->load_addr < SOC_DROM_HIGH) {
+ if (irom_addr != 0) {
+ ESP_LOGE(TAG, MAP_ERR_MSG, "IROM");
+ } else {
+ ESP_LOGD(TAG, "Mapping segment %d as %s", i, "IROM");
}
- memcpy((void *)segment_header.load_addr, data, segment_header.data_len);
- bootloader_munmap(data);
+ irom_addr = data.segment_data[i];
+ irom_load_addr = header->load_addr;
+ irom_size = header->data_len;
}
}
+ ESP_LOGD(TAG, "calling set_cache_and_start_app");
set_cache_and_start_app(drom_addr,
drom_load_addr,
drom_size,
irom_addr,
irom_load_addr,
irom_size,
- image_header.entry_addr);
+ data.image.entry_addr);
}
static void set_cache_and_start_app(
Cache_Read_Enable( 0 );
}
-void print_flash_info(const esp_image_header_t* phdr)
+static void print_flash_info(const esp_image_header_t* phdr)
{
#if (BOOT_LOG_LEVEL >= BOOT_LOG_LEVEL_NOTICE)
}
wdt_reset_cpu0_info_enable();
}
+
+void __assert_func(const char *file, int line, const char *func, const char *expr)
+{
+ ESP_LOGE(TAG, "Assert failed in %s, %s:%d (%s)", func, file, line, expr);
+ while(1) {}
+}
/*
Linker file used to link the bootloader.
-
-*WARNING* For now this linker dumps everything into IRAM/DRAM. ToDo: move
-some/most stuff to DROM/IROM.
-
*/
-/* THESE ARE THE VIRTUAL RUNTIME ADDRESSES */
-/* The load addresses are defined later using the AT statements. */
+/* Simplified memory map for the bootloader
+
+ The main purpose is to make sure the bootloader can load into main memory
+ without overwriting itself.
+*/
MEMORY
{
- /* All these values assume the flash cache is on, and have the blocks this uses subtracted from the length
- of the various regions. The 'data access port' dram/drom regions map to the same iram/irom regions but
- are connected to the data port of the CPU and eg allow bytewise access. */
- dport0_seg (RW) : org = 0x3FF00000, len = 0x10 /* IO */
- iram_seg (RWX) : org = 0x40080000, len = 0x400 /* 1k of IRAM used by bootloader functions which need to flush/enable APP CPU cache */
- iram_pool_1_seg (RWX) : org = 0x40078000, len = 0x8000 /* IRAM POOL1, used for APP CPU cache. We can abuse it in bootloader because APP CPU is still held in reset, until we enable APP CPU cache */
- dram_seg (RW) : org = 0x3FFF0000, len = 0x10000 /* 64k at the end of DRAM, after ROM bootloader stack */
+ /* I/O */
+ dport0_seg (RW) : org = 0x3FF00000, len = 0x10
+ /* IRAM POOL1, used for APP CPU cache. We can abuse it in bootloader because APP CPU is still held in reset, the main app enables APP CPU cache */
+ iram_seg (RWX) : org = 0x40078000, len = 0x8000
+ /* 64k at the end of DRAM, after ROM bootloader stack */
+ dram_seg (RW) : org = 0x3FFF0000, len = 0x10000
}
/* Default entry point: */
{
.iram1.text :
{
- _init_start = ABSOLUTE(.);
- *(.UserEnter.literal);
- *(.UserEnter.text);
. = ALIGN (16);
*(.entry.text)
*(.init.literal)
*(.init)
- _init_end = ABSOLUTE(.);
-
- /* Code marked as runnning out of IRAM */
- _iram_text_start = ABSOLUTE(.);
- *(.iram1 .iram1.*)
- _iram_text_end = ABSOLUTE(.);
} > iram_seg
_bss_end = ABSOLUTE(.);
} >dram_seg
-
.dram0.data :
{
_data_start = ABSOLUTE(.);
_data_end = ABSOLUTE(.);
} >dram_seg
-
-
-
.dram0.rodata :
{
_rodata_start = ABSOLUTE(.);
*(.rodata)
*(.rodata.*)
- *(.irom1.text) /* catch stray ICACHE_RODATA_ATTR */
*(.gnu.linkonce.r.*)
*(.rodata1)
__XT_EXCEPTION_TABLE_ = ABSOLUTE(.);
_heap_start = ABSOLUTE(.);
} >dram_seg
- .iram_pool_1.text :
+ .iram.text :
{
_stext = .;
_text_start = ABSOLUTE(.);
*(.literal .text .literal.* .text.* .stub .gnu.warning .gnu.linkonce.literal.* .gnu.linkonce.t.*.literal .gnu.linkonce.t.*)
- *(.irom0.text) /* catch stray ICACHE_RODATA_ATTR */
+ *(.iram1 .iram1.*) /* catch stray IRAM_ATTR */
*(.fini.literal)
*(.fini)
*(.gnu.version)
_text_end = ABSOLUTE(.);
_etext = .;
- } >iram_pool_1_seg
+ } > iram_seg
}
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
-#ifndef __ESP32_IMAGE_FORMAT_H
-#define __ESP32_IMAGE_FORMAT_H
+#pragma once
#include <stdbool.h>
#include <esp_err.h>
+#include "esp_flash_partitions.h"
#define ESP_ERR_IMAGE_BASE 0x2000
#define ESP_ERR_IMAGE_FLASH_FAIL (ESP_ERR_IMAGE_BASE + 1)
uint32_t data_len;
} esp_image_segment_header_t;
+#define ESP_IMAGE_MAX_SEGMENTS 16
-/**
- * @brief Read an ESP image header from flash.
- *
- * If encryption is enabled, data will be transparently decrypted.
- *
- * @param src_addr Address in flash to load image header. Must be 4 byte aligned.
- * @param log_errors Log error output if image header appears invalid.
- * @param[out] image_header Pointer to an esp_image_header_t struture to be filled with data. If the function fails, contents are undefined.
- *
- * @return ESP_OK if image header was loaded, ESP_ERR_IMAGE_FLASH_FAIL
- * if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header
- * appears invalid.
- */
-esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header);
+/* Structure to hold on-flash image metadata */
+typedef struct {
+ uint32_t start_addr; /* Start address of image */
+ esp_image_header_t image; /* Header for entire image */
+ esp_image_segment_header_t segments[ESP_IMAGE_MAX_SEGMENTS]; /* Per-segment header data */
+ uint32_t segment_data[ESP_IMAGE_MAX_SEGMENTS]; /* Data offsets for each segment */
+ uint32_t image_length;
-/**
- * @brief Read the segment header and data offset of a segment in the image.
- *
- * If encryption is enabled, data will be transparently decrypted.
- *
- * @param index Index of the segment to load information for.
- * @param src_addr Base address in flash of the image.
- * @param[in] image_header Pointer to the flash image header, already loaded by @ref esp_image_load_header().
- * @param log_errors Log errors reading the segment header.
- * @param[out] segment_header Pointer to a segment header structure to be filled with data. If the function fails, contents are undefined.
- * @param[out] segment_data_offset Pointer to the data offset of the segment.
- *
- * @return ESP_OK if segment_header & segment_data_offset were loaded successfully, ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image header appears invalid, ESP_ERR_INVALID_ARG if the index is invalid.
- */
-esp_err_t esp_image_load_segment_header(uint8_t index, uint32_t src_addr, const esp_image_header_t *image_header, bool log_errors, esp_image_segment_header_t *segment_header, uint32_t *segment_data_offset);
+} esp_image_metadata_t;
+/* Mode selection for esp_image_load() */
+typedef enum {
+ ESP_IMAGE_VERIFY, /* Verify image contents, load metadata. Print errorsors. */
+ ESP_IMAGE_VERIFY_SILENT, /* Verify image contents, load metadata. Don't print errors. */
+#ifdef BOOTLOADER_BUILD
+ ESP_IMAGE_LOAD, /* Verify image contents, load to memory. Print errors. */
+#endif
+} esp_image_load_mode_t;
/**
- * @brief Non-cryptographically validate app image integrity. On success, length of image is provided to caller.
+ * @brief Verify and (optionally, in bootloader mode) load an app image.
*
- * If the image has a secure boot signature appended, the signature is not checked and this length is not included in the
- * output value.
+ * If encryption is enabled, data will be transparently decrypted.
+ *
+ * @param part Partition to load the app from.
+ * @param[out] data Pointer to the metadata structure to be filled in by this function. 'start_addr' member should be set (to the start address of the image.) Other fields will all be initialised by this function.
+ * @param log_errors Log errors reading the image.
*
* Image validation checks:
* - Magic byte
- * - No single segment longer than 16MB
- * - Total image no longer than 16MB
+ * - Partition smaller than 16MB
+ * - All segments & image fit in partition
* - 8 bit image checksum is valid
+ * - (Signature) if signature verification is enabled
*
- * If flash encryption is enabled, the image will be tranpsarently decrypted.
- *
- * @param src_addr Offset of the start of the image in flash. Must be 4 byte aligned.
- * @param allow_decrypt If true and flash encryption is enabled, the image will be transparently decrypted.
- * @param log_errors Log errors verifying the image.
- * @param[out] length Length of the image, set to a value if the image is valid. Can be null.
+ * @return ESP_OK if image metadata was loaded successfully, ESP_ERR_IMAGE_FLASH_FAIL if a SPI flash error occurs, ESP_ERR_IMAGE_INVALID if the image appears invalid, ESP_ERR_INVALID_ARG if the data pointer is invalid.
+ */
+esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data);
+
+/**
+ * @brief Verify the bootloader image.
*
- * @return ESP_OK if image is valid, ESP_FAIL or ESP_ERR_IMAGE_INVALID on errors.
+ * @param[out] If result is ESP_OK and this pointer is non-NULL, it
+ * will be set to the length of the bootloader image.
*
+ * @return As per esp_image_load_metadata().
*/
-esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *length);
+esp_err_t esp_image_verify_bootloader(uint32_t *length);
typedef struct {
uint32_t irom_load_addr;
uint32_t irom_size;
} esp_image_flash_mapping_t;
-
-#endif
static bool mapped;
+// Current bootloader mapping (ab)used for bootloader_read()
static uint32_t current_read_mapping = UINT32_MAX;
const void *bootloader_mmap(uint32_t src_addr, uint32_t size)
// limitations under the License.
#include <string.h>
+#include <rom/rtc.h>
+#include <soc/cpu.h>
#include <esp_image_format.h>
#include <esp_log.h>
#include <bootloader_flash.h>
#define SIXTEEN_MB 0x1000000
#define ESP_ROM_CHECKSUM_INITIAL 0xEF
-esp_err_t esp_image_load_header(uint32_t src_addr, bool log_errors, esp_image_header_t *image_header)
+/* Headroom to ensure between stack SP (at time of checking) and data loaded from flash */
+#define STACK_LOAD_HEADROOM 4096
+
+/* Return true if load_addr is an address the bootloader should load into */
+static bool should_load(uint32_t load_addr);
+/* Return true if load_addr is an address the bootloader should map via flash cache */
+static bool should_map(uint32_t load_addr);
+
+/* Load or verify a segment */
+static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, uint32_t *checksum);
+
+/* Verify the main image header */
+static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent);
+
+/* Verify a segment header */
+static esp_err_t verify_segment_header(int index, const esp_image_segment_header_t *segment, uint32_t segment_data_offs, bool silent);
+
+/* Log-and-fail macro for use in esp_image_load */
+#define FAIL_LOAD(...) do { \
+ if (!silent) { \
+ ESP_LOGE(TAG, __VA_ARGS__); \
+ } \
+ goto err; \
+ } \
+ while(0)
+
+esp_err_t esp_image_load(esp_image_load_mode_t mode, const esp_partition_pos_t *part, esp_image_metadata_t *data)
{
- esp_err_t err;
- ESP_LOGD(TAG, "reading image header @ 0x%x", src_addr);
+#ifdef BOOTLOADER_BUILD
+ bool do_load = (mode == ESP_IMAGE_LOAD);
+#else
+ bool do_load = false; // Can't load the image in app mode
+#endif
+ bool silent = (mode == ESP_IMAGE_VERIFY_SILENT);
+ esp_err_t err = ESP_OK;
+ // checksum the image a word at a time. This shaves 30-40ms per MB of image size
+ uint32_t checksum_word = ESP_ROM_CHECKSUM_INITIAL;
- err = bootloader_flash_read(src_addr, image_header, sizeof(esp_image_header_t), true);
+ if (data == NULL || part == NULL) {
+ return ESP_ERR_INVALID_ARG;
+ }
- if (err == ESP_OK) {
- if (image_header->magic != ESP_IMAGE_HEADER_MAGIC) {
- if (log_errors) {
- ESP_LOGE(TAG, "image at 0x%x has invalid magic byte", src_addr);
- }
- err = ESP_ERR_IMAGE_INVALID;
- }
- if (log_errors) {
- if (image_header->spi_mode > ESP_IMAGE_SPI_MODE_SLOW_READ) {
- ESP_LOGW(TAG, "image at 0x%x has invalid SPI mode %d", src_addr, image_header->spi_mode);
- }
- if (image_header->spi_speed > ESP_IMAGE_SPI_SPEED_80M) {
- ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image_header->spi_speed);
- }
- if (image_header->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) {
- ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image_header->spi_size);
- }
- }
+ if (part->size > SIXTEEN_MB) {
+ err = ESP_ERR_INVALID_ARG;
+ FAIL_LOAD("partition size %d invalid, larger than 16MB", part->size);
}
+ bzero(data, sizeof(esp_image_metadata_t));
+ data->start_addr = part->offset;
+
+ ESP_LOGD(TAG, "reading image header @ 0x%x", data->start_addr);
+ err = bootloader_flash_read(data->start_addr, &data->image, sizeof(esp_image_header_t), true);
if (err != ESP_OK) {
- bzero(image_header, sizeof(esp_image_header_t));
+ goto err;
}
- return err;
-}
-esp_err_t esp_image_load_segment_header(uint8_t index, uint32_t src_addr, const esp_image_header_t *image_header, bool log_errors, esp_image_segment_header_t *segment_header, uint32_t *segment_data_offset)
-{
- esp_err_t err = ESP_OK;
- uint32_t next_addr = src_addr + sizeof(esp_image_header_t);
+ ESP_LOGD(TAG, "image header: 0x%02x 0x%02x 0x%02x 0x%02x %08x",
+ data->image.magic,
+ data->image.segment_count,
+ data->image.spi_mode,
+ data->image.spi_size,
+ data->image.entry_addr);
- if(index >= image_header->segment_count) {
- if (log_errors) {
- ESP_LOGE(TAG, "index %d higher than segment count %d", index, image_header->segment_count);
- }
- return ESP_ERR_INVALID_ARG;
+ err = verify_image_header(data->start_addr, &data->image, silent);
+ if (err != ESP_OK) {
+goto err;
+ }
+
+ if (data->image.segment_count > ESP_IMAGE_MAX_SEGMENTS) {
+ FAIL_LOAD("image at 0x%x segment count %d exceeds max %d",
+ data->start_addr, data->image.segment_count, ESP_IMAGE_MAX_SEGMENTS);
}
- for(int i = 0; i <= index && err == ESP_OK; i++) {
+ uint32_t next_addr = data->start_addr + sizeof(esp_image_header_t);
+ for(int i = 0; i < data->image.segment_count && err == ESP_OK; i++) {
+ esp_image_segment_header_t *header = &data->segments[i];
ESP_LOGV(TAG, "loading segment header %d at offset 0x%x", i, next_addr);
- err = bootloader_flash_read(next_addr, segment_header, sizeof(esp_image_segment_header_t), true);
- if (err == ESP_OK) {
- if ((segment_header->data_len & 3) != 0
- || segment_header->data_len >= SIXTEEN_MB) {
- if (log_errors) {
- ESP_LOGE(TAG, "invalid segment length 0x%x", segment_header->data_len);
- }
- err = ESP_ERR_IMAGE_INVALID;
- }
- next_addr += sizeof(esp_image_segment_header_t);
- ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", segment_header->data_len, next_addr);
- *segment_data_offset = next_addr;
- next_addr += segment_header->data_len;
+ err = process_segment(i, next_addr, header, silent, do_load, &checksum_word);
+ if (err != ESP_OK) {
+ goto err;
}
+ next_addr += sizeof(esp_image_segment_header_t);
+ data->segment_data[i] = next_addr;
+ next_addr += header->data_len;
}
- if (err != ESP_OK) {
- *segment_data_offset = 0;
- bzero(segment_header, sizeof(esp_image_segment_header_t));
+ // Segments all loaded, verify length
+ uint32_t end_addr = next_addr;
+ if (end_addr < data->start_addr) {
+ FAIL_LOAD("image offset has wrapped");
}
+ uint32_t length = end_addr - data->start_addr;
+ length = length + 1; // Add a byte for the checksum
+ length = (length + 15) & ~15; // Pad to next full 16 byte block
+ if (length > part->size) {
+ FAIL_LOAD("Image length %d doesn't fit in partition length %d", length, part->size);
+ }
+
+ // Verify checksum
+ uint32_t buf[16/sizeof(uint32_t)];
+ err = bootloader_flash_read(data->start_addr + length - 16, buf, 16, true);
+ uint8_t calc = ((uint8_t *)buf)[15];
+ uint8_t checksum = (checksum_word >> 24)
+ ^ (checksum_word >> 16)
+ ^ (checksum_word >> 8)
+ ^ (checksum_word >> 0);
+ if (err != ESP_OK || checksum != calc) {
+ FAIL_LOAD("Checksum failed. Calculated 0x%x read 0x%x",
+ checksum, calc);
+ }
+
+ data->image_length = length;
+
+ // Success!
+ return ESP_OK;
+
+ err:
+ if (err == ESP_OK) {
+ err = ESP_ERR_IMAGE_INVALID;
+ }
+ // Prevent invalid/incomplete data leaking out
+ bzero(data, sizeof(esp_image_metadata_t));
return err;
}
-esp_err_t esp_image_basic_verify(uint32_t src_addr, bool log_errors, uint32_t *p_length)
+static esp_err_t verify_image_header(uint32_t src_addr, const esp_image_header_t *image, bool silent)
{
- esp_err_t err;
- uint8_t buf[128];
- uint8_t checksum = ESP_ROM_CHECKSUM_INITIAL;
- esp_image_header_t image_header;
- esp_image_segment_header_t segment_header = { 0 };
- uint32_t segment_data_offs = 0;
- uint32_t end_addr;
- uint32_t length;
+ esp_err_t err = ESP_OK;
- if (p_length != NULL) {
- *p_length = 0;
+ if (image->magic != ESP_IMAGE_HEADER_MAGIC) {
+ if (!silent) {
+ ESP_LOGE(TAG, "image at 0x%x has invalid magic byte", src_addr);
+ }
+ err = ESP_ERR_IMAGE_INVALID;
}
+ if (!silent) {
+ if (image->spi_mode > ESP_IMAGE_SPI_MODE_SLOW_READ) {
+ ESP_LOGW(TAG, "image at 0x%x has invalid SPI mode %d", src_addr, image->spi_mode);
+ }
+ if (image->spi_speed > ESP_IMAGE_SPI_SPEED_80M) {
+ ESP_LOGW(TAG, "image at 0x%x has invalid SPI speed %d", src_addr, image->spi_speed);
+ }
+ if (image->spi_size > ESP_IMAGE_FLASH_SIZE_MAX) {
+ ESP_LOGW(TAG, "image at 0x%x has invalid SPI size %d", src_addr, image->spi_size);
+ }
+ }
+ return err;
+}
+
+static esp_err_t process_segment(int index, uint32_t flash_addr, esp_image_segment_header_t *header, bool silent, bool do_load, uint32_t *checksum)
+{
+ esp_err_t err;
- err = esp_image_load_header(src_addr, log_errors, &image_header);
+ /* read segment header */
+ err = bootloader_flash_read(flash_addr, header, sizeof(esp_image_segment_header_t), true);
if (err != ESP_OK) {
return err;
}
- ESP_LOGD(TAG, "reading %d image segments", image_header.segment_count);
+ intptr_t load_addr = header->load_addr;
+ uint32_t data_len = header->data_len;
+ uint32_t data_addr = flash_addr + sizeof(esp_image_segment_header_t);
- /* Checksum each segment's data */
- for (int i = 0; i < image_header.segment_count; i++) {
- err = esp_image_load_segment_header(i, src_addr, &image_header, log_errors,
- &segment_header, &segment_data_offs);
- if (err != ESP_OK) {
- return err;
- }
+ ESP_LOGV(TAG, "segment data length 0x%x data starts 0x%x", data_len, data_addr);
- uint32_t load_addr = segment_header.load_addr;
- bool map_segment = (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH)
- || (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH);
+ err = verify_segment_header(index, header, data_addr, silent);
+ if (err != ESP_OK) {
+ return err;
+ }
+ if (data_len % 4 != 0) {
+ FAIL_LOAD("unaligned segment length 0x%x", data_len);
+ }
- /* Check that flash cache mapped segment aligns correctly from flash it's mapped address,
- relative to the 64KB page mapping size.
- */
- ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x",
- i, map_segment, segment_data_offs, load_addr);
- if (map_segment && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
- ESP_LOGE(TAG, "Segment %d has load address 0x%08x, conflict with segment data at 0x%08x",
- i, load_addr, segment_data_offs);
- }
+ bool is_mapping = should_map(load_addr);
+ do_load = do_load && should_load(load_addr);
- for (int i = 0; i < segment_header.data_len; i += sizeof(buf)) {
- err = bootloader_flash_read(segment_data_offs + i, buf, sizeof(buf), true);
- if (err != ESP_OK) {
- return err;
- }
- for (int j = 0; j < sizeof(buf) && i + j < segment_header.data_len; j++) {
- checksum ^= buf[j];
+ if (!silent) {
+ ESP_LOGI(TAG, "segment %d: paddr=0x%08x vaddr=0x%08x size=0x%05x (%6d) %s",
+ index, data_addr, load_addr,
+ data_len, data_len,
+ (do_load)?"load":(is_mapping)?"map":"");
+ }
+
+ if (do_load) {
+ /* Before loading segment, check it doesn't clobber bootloader RAM... */
+ uint32_t end_addr = load_addr + data_len;
+ if (end_addr < 0x40000000) {
+ intptr_t sp = (intptr_t)get_sp();
+ if (end_addr > sp - STACK_LOAD_HEADROOM) {
+ ESP_LOGE(TAG, "Segment %d end address 0x%08x too high (bootloader stack 0x%08x liimit 0x%08x)",
+ index, end_addr, sp, sp - STACK_LOAD_HEADROOM);
+ return ESP_ERR_IMAGE_INVALID;
}
}
}
- /* End of image, verify checksum */
- end_addr = segment_data_offs + segment_header.data_len;
+ const void *data = bootloader_mmap(data_addr, data_len);
+ if(!data) {
+ ESP_LOGE(TAG, "bootloader_mmap(0x%x, 0x%x) failed",
+ data_addr, data_len);
+ return ESP_FAIL;
+ }
+ const uint32_t *checksum_from;
+ if (do_load) {
+ memcpy((void *)load_addr, data, data_len);
+ checksum_from = (const uint32_t *)load_addr;
+ } else {
+ checksum_from = (const uint32_t *)data;
+ }
+ // Update checksum, either from RAM we just loaded or from flash
+ for (const uint32_t *c = checksum_from;
+ c < checksum_from + (data_len/sizeof(uint32_t));
+ c++) {
+ *checksum ^= *c;
+ }
+
+ bootloader_munmap(data);
+
+ return ESP_OK;
+
+ err:
+ if (err == ESP_OK) {
+ err = ESP_ERR_IMAGE_INVALID;
+ }
+ return err;
+}
- if (end_addr < src_addr) {
- if (log_errors) {
- ESP_LOGE(TAG, "image offset has wrapped");
+static esp_err_t verify_segment_header(int index, const esp_image_segment_header_t *segment, uint32_t segment_data_offs, bool silent)
+{
+ if ((segment->data_len & 3) != 0
+ || segment->data_len >= SIXTEEN_MB) {
+ if (!silent) {
+ ESP_LOGE(TAG, "invalid segment length 0x%x", segment->data_len);
}
return ESP_ERR_IMAGE_INVALID;
}
- length = end_addr - src_addr;
- if (length >= SIXTEEN_MB) {
- if (log_errors) {
- ESP_LOGE(TAG, "invalid total length 0x%x", length);
+ uint32_t load_addr = segment->load_addr;
+
+ /* Check that flash cache mapped segment aligns correctly from flash to its mapped address,
+ relative to the 64KB page mapping size.
+ */
+ ESP_LOGV(TAG, "segment %d map_segment %d segment_data_offs 0x%x load_addr 0x%x",
+ index, map_segment, segment_data_offs, load_addr);
+ if (should_map(load_addr)
+ && ((segment_data_offs % SPI_FLASH_MMU_PAGE_SIZE) != (load_addr % SPI_FLASH_MMU_PAGE_SIZE))) {
+ if (!silent) {
+ ESP_LOGE(TAG, "Segment %d has load address 0x%08x, doesn't match segment data at 0x%08x",
+ index, load_addr, segment_data_offs);
}
return ESP_ERR_IMAGE_INVALID;
}
- /* image padded to next full 16 byte block, with checksum byte at very end */
- ESP_LOGV(TAG, "unpadded image length 0x%x", length);
- length += 16; /* always pad by at least 1 byte */
- length = length - (length % 16);
- ESP_LOGV(TAG, "padded image length 0x%x", length);
- ESP_LOGD(TAG, "reading checksum block at 0x%x", src_addr + length - 16);
- bootloader_flash_read(src_addr + length - 16, buf, 16, true);
- if (checksum != buf[15]) {
- if (log_errors) {
- ESP_LOGE(TAG, "checksum failed. Calculated 0x%x read 0x%x",
- checksum, buf[15]);
+ return ESP_OK;
+}
+
+static bool should_map(uint32_t load_addr)
+{
+ return (load_addr >= SOC_IROM_LOW && load_addr < SOC_IROM_HIGH)
+ || (load_addr >= SOC_DROM_LOW && load_addr < SOC_DROM_HIGH);
+}
+
+static bool should_load(uint32_t load_addr)
+{
+ /* Reload the RTC memory segments whenever a non-deepsleep reset
+ is occurring */
+ bool load_rtc_memory = rtc_get_reset_reason(0) != DEEPSLEEP_RESET;
+
+ if (should_map(load_addr)) {
+ return false;
+ }
+
+ if (load_addr < 0x10000000) {
+ // Reserved for non-loaded addresses.
+ // Current reserved values are
+ // 0x0 (padding block)
+ // 0x4 (unused, but reserved for an MD5 block)
+ return false;
+ }
+
+ if (!load_rtc_memory) {
+ if (load_addr >= SOC_RTC_IRAM_LOW && load_addr < SOC_RTC_IRAM_HIGH) {
+ ESP_LOGD(TAG, "Skipping RTC code segment at 0x%08x\n", load_addr);
+ return false;
+ }
+ if (load_addr >= SOC_RTC_DATA_LOW && load_addr < SOC_RTC_DATA_HIGH) {
+ ESP_LOGD(TAG, "Skipping RTC data segment at 0x%08x\n", load_addr);
+ return false;
}
- return ESP_ERR_IMAGE_INVALID;
}
- if (p_length != NULL) {
- *p_length = length;
+ return true;
+}
+
+esp_err_t esp_image_verify_bootloader(uint32_t *length)
+{
+ esp_image_metadata_t data;
+ const esp_partition_pos_t bootloader_part = {
+ .offset = ESP_BOOTLOADER_OFFSET,
+ .size = ESP_PARTITION_TABLE_OFFSET - ESP_BOOTLOADER_OFFSET,
+ };
+ esp_err_t err = esp_image_load(ESP_IMAGE_VERIFY,
+ &bootloader_part,
+ &data);
+ if (length != NULL) {
+ *length = (err == ESP_OK) ? data.image_length : 0;
}
- return ESP_OK;
+ return err;
}
{
esp_err_t err;
uint32_t image_length;
- /* Check for plaintext bootloader */
- if (esp_image_basic_verify(ESP_BOOTLOADER_OFFSET, false, &image_length) == ESP_OK) {
+ /* Check for plaintext bootloader (verification will fail if it's already encrypted) */
+ if (esp_image_verify_bootloader(&image_length) == ESP_OK) {
ESP_LOGD(TAG, "bootloader is plaintext. Encrypting...");
err = esp_flash_encrypt_region(ESP_BOOTLOADER_OFFSET, image_length);
if (err != ESP_OK) {
static esp_err_t encrypt_partition(int index, const esp_partition_info_t *partition)
{
esp_err_t err;
- uint32_t image_len = partition->pos.size;
bool should_encrypt = (partition->flags & PART_FLAG_ENCRYPTED);
if (partition->type == PART_TYPE_APP) {
- /* check if the partition holds an unencrypted app */
- if (esp_image_basic_verify(partition->pos.offset, false, &image_len) == ESP_OK) {
- if(image_len > partition->pos.size) {
- ESP_LOGE(TAG, "partition entry %d has image longer than partition (%d vs %d)", index, image_len, partition->pos.size);
- should_encrypt = false;
- } else {
- should_encrypt = true;
- }
- } else {
- should_encrypt = false;
- }
+ /* check if the partition holds a valid unencrypted app */
+ esp_image_metadata_t data_ignored;
+ err = esp_image_load(ESP_IMAGE_VERIFY,
+ &partition->pos,
+ &data_ignored);
+ should_encrypt = (err == ESP_OK);
} else if (partition->type == PART_TYPE_DATA && partition->subtype == PART_SUBTYPE_DATA_OTA) {
/* check if we have ota data partition and the partition should be encrypted unconditionally */
should_encrypt = true;
}
/* generate digest from image contents */
- image = bootloader_mmap(0x1000, image_len);
+ image = bootloader_mmap(ESP_BOOTLOADER_OFFSET, image_len);
if (!image) {
ESP_LOGE(TAG, "bootloader_mmap(0x1000, 0x%x) failed", image_len);
return false;
return ESP_OK;
}
- err = esp_image_basic_verify(0x1000, true, &image_len);
+ err = esp_image_verify_bootloader(&image_len);
if (err != ESP_OK) {
ESP_LOGE(TAG, "bootloader image appears invalid! error %d", err);
return err;