Boot: Load image to SRAM for execution

Check the newest image's header for an SRAM load address, and if it
is present then copy the image to that address. This allows for faster
execution as well as the potential for larger images.

Signed-off-by: Oliver Swede <oli.swede@arm.com>
Change-Id: Ifbe868cb35d217086918ebeb5bb41690065b9f46
diff --git a/bl2/ext/mcuboot/CMakeLists.txt b/bl2/ext/mcuboot/CMakeLists.txt
index dfe60a8..0d8258b 100644
--- a/bl2/ext/mcuboot/CMakeLists.txt
+++ b/bl2/ext/mcuboot/CMakeLists.txt
@@ -134,6 +134,10 @@
 	target_compile_definitions(${PROJECT_NAME} PRIVATE MCUBOOT_NO_SWAP)
 endif()
 
+if (MCUBOOT_RAM_LOADING)
+	target_compile_definitions(${PROJECT_NAME} PRIVATE MCUBOOT_RAM_LOADING)
+endif()
+
 #Set install location. Keep original value to avoid overriding command line settings.
 if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
 	set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE PATH "Default install location for MCUBoot." FORCE)
diff --git a/bl2/ext/mcuboot/bl2_main.c b/bl2/ext/mcuboot/bl2_main.c
index 9adc072..c556816 100644
--- a/bl2/ext/mcuboot/bl2_main.c
+++ b/bl2/ext/mcuboot/bl2_main.c
@@ -64,9 +64,19 @@
     rc = flash_device_base(rsp->br_flash_dev_id, &flash_base);
     assert(rc == 0);
 
-    vt = (struct arm_vector_table *)(flash_base +
-                                     rsp->br_image_off +
-                                     rsp->br_hdr->ih_hdr_size);
+    if (rsp->br_hdr->ih_flags & IMAGE_F_RAM_LOAD) {
+       /* The image has been copied to SRAM, find the vector table
+        * at the load address instead of image's address in flash
+        */
+        vt = (struct arm_vector_table *)(rsp->br_hdr->ih_load_addr +
+                                         rsp->br_hdr->ih_hdr_size);
+    } else {
+        /* Using the flash address as not executing in SRAM */
+        vt = (struct arm_vector_table *)(flash_base +
+                                         rsp->br_image_off +
+                                         rsp->br_hdr->ih_hdr_size);
+    }
+
     __disable_irq();
     __set_MSP(vt->msp);
     __DSB();
diff --git a/bl2/ext/mcuboot/bootutil/include/bootutil/image.h b/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
index b6a472d..306ab31 100644
--- a/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
+++ b/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
@@ -17,6 +17,12 @@
  * under the License.
  */
 
+/*
+ * Original code taken from mcuboot project at:
+ * https://github.com/runtimeco/mcuboot
+ * Modifications are Copyright (c) 2018 Arm Limited.
+ */
+
 #ifndef H_IMAGE_
 #define H_IMAGE_
 
@@ -61,6 +67,11 @@
 #define IMAGE_TLV_SHA256            0x10   /* SHA256 of image hdr and body */
 #define IMAGE_TLV_RSA2048_PSS       0x20   /* RSA2048 of hash output */
 
+#define IMAGE_VER_MAJOR_LENGTH      8
+#define IMAGE_VER_MINOR_LENGTH      8
+#define IMAGE_VER_REVISION_LENGTH   16
+#define IMAGE_VER_BUILD_NUM_LENGTH  32
+
 struct image_version {
     uint8_t iv_major;
     uint8_t iv_minor;
@@ -101,6 +112,10 @@
                           uint8_t *tmp_buf, uint32_t tmp_buf_sz,
                           uint8_t *seed, int seed_len, uint8_t *out_hash);
 
+#ifdef MCUBOOT_RAM_LOADING
+int bootutil_check_hash_after_loading(struct image_header *hdr);
+#endif
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/bl2/ext/mcuboot/bootutil/src/image_validate.c b/bl2/ext/mcuboot/bootutil/src/image_validate.c
index d7fb7cd..f6ae619 100644
--- a/bl2/ext/mcuboot/bootutil/src/image_validate.c
+++ b/bl2/ext/mcuboot/bootutil/src/image_validate.c
@@ -17,6 +17,12 @@
  * under the License.
  */
 
+/*
+ * Original code taken from mcuboot project at:
+ * https://github.com/runtimeco/mcuboot
+ * Modifications are Copyright (c) 2018 Arm Limited.
+ */
+
 #include <assert.h>
 #include <stddef.h>
 #include <inttypes.h>
@@ -47,7 +53,6 @@
     uint32_t blk_sz;
     uint32_t size;
     uint32_t off;
-    int rc;
 
     bootutil_sha256_init(&sha256_ctx);
 
@@ -63,16 +68,24 @@
      * Hash is computed over image header and image itself. No TLV is
      * included ATM.
      */
-    size = hdr->ih_img_size + hdr->ih_hdr_size;
     for (off = 0; off < size; off += blk_sz) {
         blk_sz = size - off;
         if (blk_sz > tmp_buf_sz) {
             blk_sz = tmp_buf_sz;
         }
-        rc = flash_area_read(fap, off, tmp_buf, blk_sz);
-        if (rc) {
-            return rc;
+
+#ifdef MCUBOOT_RAM_LOADING
+        if (fap == NULL) { /* The image is in SRAM */
+            memcpy(tmp_buf, (uint32_t *)(hdr->ih_load_addr + off), blk_sz);
+        } else { /* The image is in flash */
+#endif
+            if(flash_area_read(fap, off, tmp_buf, blk_sz)) {
+                return -1;
+            }
+#ifdef MCUBOOT_RAM_LOADING
         }
+#endif
+
         bootutil_sha256_update(&sha256_ctx, tmp_buf, blk_sz);
     }
     bootutil_sha256_finish(&sha256_ctx, hash_result);
@@ -115,6 +128,74 @@
 }
 #endif
 
+#ifdef MCUBOOT_RAM_LOADING
+/* Check the hash of an image after it has been copied to SRAM */
+int
+bootutil_check_hash_after_loading(struct image_header *hdr)
+{
+    uint32_t off;
+    uint32_t end;
+    int sha256_valid = 0;
+    struct image_tlv_info info;
+    struct image_tlv tlv;
+    uint8_t tmp_buf[BOOT_TMPBUF_SZ];
+    uint8_t hash[32] = {0};
+    int rc;
+    uint32_t load_address;
+    uint32_t tlv_sz;
+
+    rc = bootutil_img_hash(hdr, NULL, tmp_buf, BOOT_TMPBUF_SZ, hash, NULL, 0);
+
+    if (rc) {
+        return rc;
+    }
+
+    load_address = (uint32_t) hdr->ih_load_addr;
+
+    /* The TLVs come after the image. */
+    off = hdr->ih_img_size + hdr->ih_hdr_size;
+
+    info = *((struct image_tlv_info *)(load_address + off));
+
+    if (info.it_magic != IMAGE_TLV_INFO_MAGIC) {
+        return -1;
+    }
+    end = off + info.it_tlv_tot;
+    off += sizeof(info);
+
+    /*
+     * Traverse through all of the TLVs, performing any checks we know
+     * and are able to do.
+     */
+    for (; off < end; off += sizeof(tlv) + tlv.it_len) {
+        tlv = *((struct image_tlv *)(load_address + off));
+        tlv_sz = sizeof(tlv);
+
+        if (tlv.it_type == IMAGE_TLV_SHA256) {
+            /*
+             * Verify the SHA256 image hash. This must always be present.
+             */
+            if (tlv.it_len != sizeof(hash)) {
+                return -1;
+            }
+
+            if (memcmp(hash, (uint32_t *)(load_address + off + tlv_sz),
+                       sizeof(hash))) {
+                return -1;
+            }
+
+            sha256_valid = 1;
+        }
+    }
+
+    if (!sha256_valid) {
+        return -1;
+    }
+
+    return 0;
+}
+#endif /* MCUBOOT_RAM_LOADING */
+
 /*
  * Verify the integrity of the image.
  * Return non-zero if image could not be validated/does not validate.
diff --git a/bl2/ext/mcuboot/bootutil/src/loader.c b/bl2/ext/mcuboot/bootutil/src/loader.c
index edab6bb..154cd7e 100644
--- a/bl2/ext/mcuboot/bootutil/src/loader.c
+++ b/bl2/ext/mcuboot/bootutil/src/loader.c
@@ -44,7 +44,7 @@
 
 static struct boot_loader_state boot_data;
 
-#ifndef MCUBOOT_NO_SWAP
+#if !defined(MCUBOOT_NO_SWAP) && !defined(MCUBOOT_RAM_LOADING)
 struct boot_status_table {
     /**
      * For each field, a value of 0 means "any".
@@ -136,7 +136,7 @@
                   "bad"),                                           \
                  (state)->copy_done,                                \
                  (state)->image_ok)
-#endif /* !MCUBOOT_NO_SWAP */
+#endif /* !MCUBOOT_NO_SWAP && !MCUBOOT_RAM_LOADING */
 
 
 static int
@@ -361,7 +361,46 @@
 }
 #endif /* !MCUBOOT_OVERWRITE_ONLY */
 
-#ifndef MCUBOOT_NO_SWAP
+#if !defined(MCUBOOT_NO_SWAP) && !defined(MCUBOOT_OVERWRITE_ONLY)
+/*
+ * Compute the total size of the given image.  Includes the size of
+ * the TLVs.
+ */
+static int
+boot_read_image_size(int slot, struct image_header *hdr, uint32_t *size)
+{
+    const struct flash_area *fap = NULL;
+    struct image_tlv_info info;
+    int area_id;
+    int rc;
+
+    area_id = flash_area_id_from_image_slot(slot);
+    rc = flash_area_open(area_id, &fap);
+    if (rc != 0) {
+        rc = BOOT_EFLASH;
+        goto done;
+    }
+
+    rc = flash_area_read(fap, hdr->ih_hdr_size + hdr->ih_img_size,
+                         &info, sizeof(info));
+    if (rc != 0) {
+        rc = BOOT_EFLASH;
+        goto done;
+    }
+    if (info.it_magic != IMAGE_TLV_INFO_MAGIC) {
+        rc = BOOT_EBADIMAGE;
+        goto done;
+    }
+    *size = hdr->ih_hdr_size + hdr->ih_img_size + info.it_tlv_tot;
+    rc = 0;
+
+done:
+    flash_area_close(fap);
+    return rc;
+}
+#endif /* !MCUBOOT_NO_SWAP && !MCUBOOT_OVERWRITE_ONLY */
+
+#if !defined(MCUBOOT_NO_SWAP) && !defined(MCUBOOT_RAM_LOADING)
 /**
  * Determines where in flash the most recent boot status is stored. The boot
  * status is necessary for completing a swap that was interrupted by a boot
@@ -434,45 +473,6 @@
     return BOOT_SWAP_TYPE_FAIL;
 }
 
-/*
- * Compute the total size of the given image.  Includes the size of
- * the TLVs.
- */
-#ifndef MCUBOOT_OVERWRITE_ONLY
-static int
-boot_read_image_size(int slot, struct image_header *hdr, uint32_t *size)
-{
-    const struct flash_area *fap = NULL;
-    struct image_tlv_info info;
-    int area_id;
-    int rc;
-
-    area_id = flash_area_id_from_image_slot(slot);
-    rc = flash_area_open(area_id, &fap);
-    if (rc != 0) {
-        rc = BOOT_EFLASH;
-        goto done;
-    }
-
-    rc = flash_area_read(fap, hdr->ih_hdr_size + hdr->ih_img_size,
-                         &info, sizeof(info));
-    if (rc != 0) {
-        rc = BOOT_EFLASH;
-        goto done;
-    }
-    if (info.it_magic != IMAGE_TLV_INFO_MAGIC) {
-        rc = BOOT_EBADIMAGE;
-        goto done;
-    }
-    *size = hdr->ih_hdr_size + hdr->ih_img_size + info.it_tlv_tot;
-    rc = 0;
-
-done:
-    flash_area_close(fap);
-    return rc;
-}
-#endif /* !MCUBOOT_OVERWRITE_ONLY */
-
 static int
 boot_slots_compatible(void)
 {
@@ -1370,7 +1370,7 @@
     return rc;
 }
 
-#else /* MCUBOOT_NO_SWAP */
+#else /* MCUBOOT_NO_SWAP || MCUBOOT_RAM_LOADING */
 
 #define BOOT_LOG_IMAGE_INFO(area, hdr, state)                           \
     BOOT_LOG_INF("Image %"PRIu32": version=%"PRIu8".%"PRIu8".%"PRIu16"" \
@@ -1396,12 +1396,20 @@
  *
  * @param hdr  Pointer to an image header structure
  *
- * @return     Version number casted to unit64_t
+ * @return     Version number casted to uint64_t
  */
 static uint64_t
 boot_get_version_number(struct image_header *hdr)
 {
-    return *((uint64_t *)(&hdr->ih_ver));
+    uint64_t version = 0;
+    version |= (uint64_t)hdr->ih_ver.iv_major << (IMAGE_VER_MINOR_LENGTH
+                                                + IMAGE_VER_REVISION_LENGTH
+                                                + IMAGE_VER_BUILD_NUM_LENGTH);
+    version |= (uint64_t)hdr->ih_ver.iv_minor << (IMAGE_VER_REVISION_LENGTH
+                                                + IMAGE_VER_BUILD_NUM_LENGTH);
+    version |= (uint64_t)hdr->ih_ver.iv_revision << IMAGE_VER_BUILD_NUM_LENGTH;
+    version |= hdr->ih_ver.iv_build_num;
+    return version;
 }
 
 /**
@@ -1486,7 +1494,7 @@
             }
             BOOT_LOG_IMAGE_INFO(slot, hdr, &slot_state);
         } else {
-            BOOT_LOG_INF("Image  %"PRIu32": No valid image", slot);
+            BOOT_LOG_INF("Image %"PRIu32": No valid image", slot);
         }
     }
 
@@ -1504,6 +1512,73 @@
     return image_cnt;
 }
 
+#ifdef MCUBOOT_RAM_LOADING
+/**
+ * Copies an image from a slot in the flash to an SRAM address, where the load
+ * address has already been inserted into the image header by this point and is
+ * extracted from it within this method. The copying is done sector-by-sector.
+ *
+ * @param slot            The flash slot of the image to be copied to SRAM.
+ *
+ * @param hdr             Pointer to the image header structure of the image
+ *                        that needs to be copid to SRAM
+ *
+ * @return                0 on success; nonzero on failure.
+ */
+static int
+boot_copy_image_to_sram(int slot, struct image_header *hdr)
+{
+    int rc;
+    uint32_t sect_sz;
+    uint32_t sect = 0;
+    uint32_t bytes_copied = 0;
+    const struct flash_area *fap_src = NULL;
+    uint32_t dst = (uint32_t) hdr->ih_load_addr;
+    uint32_t img_sz;
+
+    if (dst % 4 != 0) {
+        BOOT_LOG_INF("Cannot copy the image to the SRAM address 0x%"PRIx32" "
+        "- the load address must be aligned with 4 bytes due to SRAM "
+        "restrictions", dst);
+        return BOOT_EBADARGS;
+    }
+
+    rc = flash_area_open(flash_area_id_from_image_slot(slot), &fap_src);
+    if (rc != 0) {
+        return BOOT_EFLASH;
+    }
+
+    rc = boot_read_image_size(slot, hdr, &img_sz);
+    if (rc != 0) {
+        return BOOT_EFLASH;
+    }
+
+    while (bytes_copied < img_sz) {
+        sect_sz = boot_img_sector_size(&boot_data, slot, sect);
+        /*
+         * Direct copy from where the image sector resides in flash to its new
+         * location in SRAM
+         */
+        rc = flash_area_read(fap_src,
+                             bytes_copied,
+                             (void *)(dst + bytes_copied),
+                             sect_sz);
+        if (rc != 0) {
+            BOOT_LOG_INF("Error whilst copying image from Flash to SRAM");
+            break;
+        } else {
+            bytes_copied += sect_sz;
+        }
+        sect++;
+    }
+
+    if (fap_src) {
+        flash_area_close(fap_src);
+    }
+    return rc;
+}
+#endif /* MCUBOOT_RAM_LOADING */
+
 /**
  * Prepares the booting process. This function choose the newer image in flash
  * as appropriate, and returns the address to boot from.
@@ -1521,6 +1596,7 @@
     int fa_id;
     uint32_t boot_sequence[BOOT_NUM_SLOTS];
     uint32_t img_cnt;
+    struct image_header *newest_image_header;
 
     static boot_sector_t slot0_sectors[BOOT_MAX_IMG_SECTORS];
     static boot_sector_t slot1_sectors[BOOT_MAX_IMG_SECTORS];
@@ -1563,10 +1639,48 @@
             goto out;
         }
 
-        BOOT_LOG_INF("Booting image from slot %d", slot);
-        rsp->br_flash_dev_id = boot_img_fa_device_id(&boot_data, slot);
+        /* The slot variable now refers to the newest image's slot in flash */
+        newest_image_header = boot_img_hdr(&boot_data, slot);
+
+        #ifdef MCUBOOT_RAM_LOADING
+        if (newest_image_header->ih_flags & IMAGE_F_RAM_LOAD) {
+            /* Copy image to the load address from where it
+             * currently resides in flash */
+            rc = boot_copy_image_to_sram(slot, newest_image_header);
+            if (rc != 0) {
+                rc = BOOT_EBADIMAGE;
+                BOOT_LOG_INF("Could not copy image from slot 0x%"PRIx32" in "
+                             "the Flash to load address 0x%"PRIx32" in SRAM, "
+                             "aborting..",
+                             slot,
+                             newest_image_header->ih_load_addr);
+                goto out;
+            } else {
+                BOOT_LOG_INF("Image has been copied from slot %d in flash to "
+                             "SRAM address 0x%"PRIx32"",
+                             slot,
+                             newest_image_header->ih_load_addr);
+            }
+
+            /* Validate the image hash in SRAM after the copy was successful */
+            rc = bootutil_check_hash_after_loading(newest_image_header);
+            if (rc != 0) {
+                rc = BOOT_EBADIMAGE;
+                BOOT_LOG_INF("Cannot validate the hash of the image that was "
+                             "copied to SRAM, aborting..");
+                goto out;
+            }
+
+            BOOT_LOG_INF("Booting image from SRAM at address 0x%"PRIx32"",
+                         newest_image_header->ih_load_addr);
+        } else {
+            BOOT_LOG_INF("Booting image from slot %d", slot);
+        }
+        #endif /* MCUBOOT_RAM_LOADING */
+
+        rsp->br_hdr = newest_image_header;
         rsp->br_image_off = boot_img_slot_off(&boot_data, slot);
-        rsp->br_hdr = boot_img_hdr(&boot_data, slot);
+        rsp->br_flash_dev_id = boot_img_fa_device_id(&boot_data, slot);
     } else {
         /* No candidate image available */
         rc = BOOT_EBADIMAGE;
@@ -1578,4 +1692,4 @@
    }
    return rc;
 }
-#endif /* MCUBOOT_NO_SWAP */
+#endif /* MCUBOOT_NO_SWAP || MCUBOOT_RAM_LOADING */