]> granicus.if.org Git - esp-idf/commitdiff
bootloader: Combine loading from flash & verifying to save boot time
authorAngus Gratton <gus@projectgus.com>
Fri, 16 Jun 2017 06:30:21 +0000 (16:30 +1000)
committerAngus Gratton <gus@projectgus.com>
Wed, 19 Jul 2017 08:25:17 +0000 (18:25 +1000)
Still needs updating to account for secure boot.

components/app_update/esp_ota_ops.c
components/bootloader/subproject/main/bootloader_start.c
components/bootloader/subproject/main/esp32.bootloader.ld
components/bootloader_support/include/esp_image_format.h
components/bootloader_support/src/bootloader_flash.c
components/bootloader_support/src/esp_image_format.c
components/bootloader_support/src/flash_encrypt.c
components/bootloader_support/src/secure_boot.c

index ab8342baa8ac4836e6c1abd7877a45df3948989a..a9247bf51ee4390b7089142bcec0933aaa4b0cd0 100644 (file)
@@ -198,7 +198,6 @@ esp_err_t esp_ota_write(esp_ota_handle_t handle, const void *data, size_t size)
 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)) {
@@ -230,13 +229,19 @@ esp_err_t esp_ota_end(esp_ota_handle_t handle)
         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;
@@ -365,18 +370,22 @@ static esp_err_t esp_rewrite_ota_data(esp_partition_subtype_t subtype)
 
 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;
     }
index 123f67d8f65c856876b825dbf23653f9ada7b8af..75f481ba27465c070df8e1cbde2730e12de23329 100644 (file)
 
 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,
@@ -76,10 +77,22 @@ static void clock_configure(void);
 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));
 
@@ -253,7 +266,7 @@ void bootloader_main()
     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 );
@@ -276,7 +289,8 @@ void bootloader_main()
     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;
     }
@@ -408,11 +422,10 @@ void bootloader_main()
 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;
@@ -420,8 +433,8 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
 
 #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;
@@ -430,11 +443,6 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
     }
 #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;
@@ -442,124 +450,39 @@ static void unpack_load_app(const esp_partition_pos_t* partition)
     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(
@@ -632,7 +555,7 @@ static void update_flash_config(const esp_image_header_t* pfhdr)
     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)
 
@@ -862,3 +785,9 @@ static void wdt_reset_check(void)
     }
     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) {}
+}
index 500478814c7de1a86239cf5e7e231190608ef7b4..3f1b9eb0b625b9fec805f6aaa0eeb503b9a36361 100644 (file)
@@ -1,23 +1,21 @@
 /*
 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:  */
@@ -28,19 +26,10 @@ SECTIONS
 {
   .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
 
 
@@ -66,7 +55,6 @@ SECTIONS
     _bss_end = ABSOLUTE(.);
   } >dram_seg
 
-
   .dram0.data :
   {
     _data_start = ABSOLUTE(.);
@@ -84,15 +72,11 @@ SECTIONS
     _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(.);
@@ -132,17 +116,17 @@ SECTIONS
     _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
 
 }
index e0759c3c0e4e688a9e3228bfb31e8e25fb7006e7..97afafc3f0781104e1c876916d3e4b4262f53d00 100644 (file)
 // 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)
@@ -73,62 +73,56 @@ typedef struct {
     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 {
@@ -139,5 +133,3 @@ typedef struct {
     uint32_t irom_load_addr;
     uint32_t irom_size;
 } esp_image_flash_mapping_t;
-
-#endif
index 931fa94126bc96ca669775de65ca63f3d66a7142..0432d755c2cd10b935472ffa220d3f4b2cad30c8 100644 (file)
@@ -88,6 +88,7 @@ static const char *TAG = "bootloader_flash";
 
 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)
index e6c5f899c16512c6141afaf87d74622268184999..a91983293d2ec32480b452513d4f5fcd7d5530e0 100644 (file)
@@ -13,6 +13,8 @@
 // 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>
@@ -22,168 +24,314 @@ static const char *TAG = "esp_image";
 #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;
 }
index 8ba068d03b7309cc080171407c3800bcfec67e0d..eff15b7ca42c899fb7af18a549ffbd77945a084b 100644 (file)
@@ -210,8 +210,8 @@ static esp_err_t encrypt_bootloader()
 {
     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) {
@@ -270,21 +270,15 @@ static esp_err_t encrypt_and_load_partition_table(esp_partition_info_t *partitio
 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;
index 0230e85ad5348b633e2e0d0b4e378d0eabcc2a9b..ef9744ffc8bf57419be78f12e71c75295c5b3312 100644 (file)
@@ -67,7 +67,7 @@ static bool secure_boot_generate(uint32_t image_len){
     }
 
     /* 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;
@@ -111,7 +111,7 @@ esp_err_t esp_secure_boot_permanently_enable(void) {
         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;