Boot: add non-swapping functionality to MCUBoot
This patch introduces the non-swapping functionality:
- Default behavior is still the swapping
- MCUBOOT_NO_SWAP compiler switch is used to adding support
to run image directly from either flash partition
- MCUBoot decides based on version number in image header
which image to start
- Firmware must be built in two instance to have an XIP
image for either flash partition
- New firmware image must be loaded to the current
inactive flash partition
Change-Id: Icd2bc419490302cf550aabdf5a263ed0d0958f16
Signed-off-by: Tamas Ban <tamas.ban@arm.com>
diff --git a/bl2/ext/mcuboot/bootutil/src/loader.c b/bl2/ext/mcuboot/bootutil/src/loader.c
index 30b2eae..edab6bb 100644
--- a/bl2/ext/mcuboot/bootutil/src/loader.c
+++ b/bl2/ext/mcuboot/bootutil/src/loader.c
@@ -18,9 +18,9 @@
*/
/*
- Original code taken from mcuboot project at:
- https://github.com/runtimeco/mcuboot
- Modifications are Copyright (c) 2018 Arm Limited.
+ * Original code taken from mcuboot project at:
+ * https://github.com/runtimeco/mcuboot
+ * Modifications are Copyright (c) 2018 Arm Limited.
*/
/**
@@ -44,6 +44,7 @@
static struct boot_loader_state boot_data;
+#ifndef MCUBOOT_NO_SWAP
struct boot_status_table {
/**
* For each field, a value of 0 means "any".
@@ -128,14 +129,239 @@
(sizeof(boot_status_tables) / sizeof(boot_status_tables[0]))
#define BOOT_LOG_SWAP_STATE(area, state) \
- BOOT_LOG_INF("%s: magic=%s, copy_done=0x%x, image_ok=0x%x", \
+ BOOT_LOG_INF("%s: magic=%5s, copy_done=0x%x, image_ok=0x%x", \
(area), \
((state)->magic == BOOT_MAGIC_GOOD ? "good" : \
(state)->magic == BOOT_MAGIC_UNSET ? "unset" : \
"bad"), \
(state)->copy_done, \
(state)->image_ok)
+#endif /* !MCUBOOT_NO_SWAP */
+
+static int
+boot_read_image_header(int slot, struct image_header *out_hdr)
+{
+ const struct flash_area *fap = NULL;
+ 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, 0, out_hdr, sizeof(*out_hdr));
+ if (rc != 0) {
+ rc = BOOT_EFLASH;
+ goto done;
+ }
+
+ rc = 0;
+
+done:
+ flash_area_close(fap);
+ return rc;
+}
+
+static int
+boot_read_image_headers(void)
+{
+ int rc;
+ int i;
+
+ for (i = 0; i < BOOT_NUM_SLOTS; i++) {
+ rc = boot_read_image_header(i, boot_img_hdr(&boot_data, i));
+ if (rc != 0) {
+ /* If at least the first slot's header was read successfully, then
+ * the boot loader can attempt a boot. Failure to read any headers
+ * is a fatal error.
+ */
+ if (i > 0) {
+ return 0;
+ } else {
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static uint8_t
+boot_write_sz(void)
+{
+ const struct flash_area *fap;
+ uint8_t elem_sz;
+ uint8_t align;
+ int rc;
+
+ /* Figure out what size to write update status update as. The size depends
+ * on what the minimum write size is for scratch area, active image slot.
+ * We need to use the bigger of those 2 values.
+ */
+ rc = flash_area_open(FLASH_AREA_IMAGE_0, &fap);
+ assert(rc == 0);
+ elem_sz = flash_area_align(fap);
+ flash_area_close(fap);
+
+ rc = flash_area_open(FLASH_AREA_IMAGE_SCRATCH, &fap);
+ assert(rc == 0);
+ align = flash_area_align(fap);
+ flash_area_close(fap);
+
+ if (align > elem_sz) {
+ elem_sz = align;
+ }
+
+ return elem_sz;
+}
+
+/**
+ * Determines the sector layout of both image slots and the scratch area.
+ * This information is necessary for calculating the number of bytes to erase
+ * and copy during an image swap. The information collected during this
+ * function is used to populate the boot_data global.
+ */
+static int
+boot_read_sectors(void)
+{
+ int rc;
+
+ rc = boot_initialize_area(&boot_data, FLASH_AREA_IMAGE_0);
+ if (rc != 0) {
+ return BOOT_EFLASH;
+ }
+
+ rc = boot_initialize_area(&boot_data, FLASH_AREA_IMAGE_1);
+ if (rc != 0) {
+ return BOOT_EFLASH;
+ }
+
+ BOOT_WRITE_SZ(&boot_data) = boot_write_sz();
+
+ return 0;
+}
+
+/*
+ * Validate image hash/signature in a slot.
+ */
+static int
+boot_image_check(struct image_header *hdr, const struct flash_area *fap)
+{
+ static uint8_t tmpbuf[BOOT_TMPBUF_SZ];
+
+ if (bootutil_img_validate(hdr, fap, tmpbuf, BOOT_TMPBUF_SZ,
+ NULL, 0, NULL)) {
+ return BOOT_EBADIMAGE;
+ }
+ return 0;
+}
+
+static int
+boot_validate_slot(int slot)
+{
+ const struct flash_area *fap;
+ struct image_header *hdr;
+ int rc;
+
+ hdr = boot_img_hdr(&boot_data, slot);
+ if (hdr->ih_magic == 0xffffffff || hdr->ih_flags & IMAGE_F_NON_BOOTABLE) {
+ /* No bootable image in slot; continue booting from slot 0. */
+ return -1;
+ }
+
+ rc = flash_area_open(flash_area_id_from_image_slot(slot), &fap);
+ if (rc != 0) {
+ return BOOT_EFLASH;
+ }
+
+ if ((hdr->ih_magic != IMAGE_MAGIC || boot_image_check(hdr, fap) != 0)) {
+ if (slot != 0) {
+ flash_area_erase(fap, 0, fap->fa_size);
+ /* Image in slot 1 is invalid. Erase the image and
+ * continue booting from slot 0.
+ */
+ }
+ BOOT_LOG_ERR("Authentication failed! Image in slot %d is not valid.",
+ slot);
+ return -1;
+ }
+
+ flash_area_close(fap);
+
+ /* Image in slot 1 is valid. */
+ return 0;
+}
+
+/**
+ * Erases a region of flash.
+ *
+ * @param flash_area_idx The ID of the flash area containing the region
+ * to erase.
+ * @param off The offset within the flash area to start the
+ * erase.
+ * @param sz The number of bytes to erase.
+ *
+ * @return 0 on success; nonzero on failure.
+ */
+static int
+boot_erase_sector(int flash_area_id, uint32_t off, uint32_t sz)
+{
+ const struct flash_area *fap = NULL;
+ int rc;
+
+ rc = flash_area_open(flash_area_id, &fap);
+ if (rc != 0) {
+ rc = BOOT_EFLASH;
+ goto done;
+ }
+
+ rc = flash_area_erase(fap, off, sz);
+ if (rc != 0) {
+ rc = BOOT_EFLASH;
+ goto done;
+ }
+
+ rc = 0;
+
+done:
+ flash_area_close(fap);
+ return rc;
+}
+
+#ifndef MCUBOOT_OVERWRITE_ONLY
+static int
+boot_erase_last_sector_by_id(int flash_area_id)
+{
+ uint8_t slot;
+ uint32_t last_sector;
+ int rc;
+
+ switch (flash_area_id) {
+ case FLASH_AREA_IMAGE_0:
+ slot = 0;
+ break;
+ case FLASH_AREA_IMAGE_1:
+ slot = 1;
+ break;
+ default:
+ return BOOT_EFLASH;
+ }
+
+ last_sector = boot_img_num_sectors(&boot_data, slot) - 1;
+ rc = boot_erase_sector(flash_area_id,
+ boot_img_sector_off(&boot_data, slot, last_sector),
+ boot_img_sector_size(&boot_data, slot, last_sector));
+ assert(rc == 0);
+
+ return rc;
+}
+#endif /* !MCUBOOT_OVERWRITE_ONLY */
+
+#ifndef MCUBOOT_NO_SWAP
/**
* 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
@@ -248,86 +474,6 @@
#endif /* !MCUBOOT_OVERWRITE_ONLY */
static int
-boot_read_image_header(int slot, struct image_header *out_hdr)
-{
- const struct flash_area *fap = NULL;
- 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, 0, out_hdr, sizeof(*out_hdr));
- if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
- }
-
- rc = 0;
-
-done:
- flash_area_close(fap);
- return rc;
-}
-
-static int
-boot_read_image_headers(void)
-{
- int rc;
- int i;
-
- for (i = 0; i < BOOT_NUM_SLOTS; i++) {
- rc = boot_read_image_header(i, boot_img_hdr(&boot_data, i));
- if (rc != 0) {
- /* If at least the first slot's header was read successfully, then
- * the boot loader can attempt a boot. Failure to read any headers
- * is a fatal error.
- */
- if (i > 0) {
- return 0;
- } else {
- return rc;
- }
- }
- }
-
- return 0;
-}
-
-static uint8_t
-boot_write_sz(void)
-{
- const struct flash_area *fap;
- uint8_t elem_sz;
- uint8_t align;
- int rc;
-
- /* Figure out what size to write update status update as. The size depends
- * on what the minimum write size is for scratch area, active image slot.
- * We need to use the bigger of those 2 values.
- */
- rc = flash_area_open(FLASH_AREA_IMAGE_0, &fap);
- assert(rc == 0);
- elem_sz = flash_area_align(fap);
- flash_area_close(fap);
-
- rc = flash_area_open(FLASH_AREA_IMAGE_SCRATCH, &fap);
- assert(rc == 0);
- align = flash_area_align(fap);
- flash_area_close(fap);
-
- if (align > elem_sz) {
- elem_sz = align;
- }
-
- return elem_sz;
-}
-
-static int
boot_slots_compatible(void)
{
size_t num_sectors_0 = boot_img_num_sectors(&boot_data, 0);
@@ -350,31 +496,6 @@
return 1;
}
-/**
- * Determines the sector layout of both image slots and the scratch area.
- * This information is necessary for calculating the number of bytes to erase
- * and copy during an image swap. The information collected during this
- * function is used to populate the boot_data global.
- */
-static int
-boot_read_sectors(void)
-{
- int rc;
-
- rc = boot_initialize_area(&boot_data, FLASH_AREA_IMAGE_0);
- if (rc != 0) {
- return BOOT_EFLASH;
- }
-
- rc = boot_initialize_area(&boot_data, FLASH_AREA_IMAGE_1);
- if (rc != 0) {
- return BOOT_EFLASH;
- }
-
- BOOT_WRITE_SZ(&boot_data) = boot_write_sz();
-
- return 0;
-}
static uint32_t
boot_status_internal_off(int idx, int state, int elem_sz)
@@ -533,56 +654,6 @@
return rc;
}
-/*
- * Validate image hash/signature in a slot.
- */
-static int
-boot_image_check(struct image_header *hdr, const struct flash_area *fap)
-{
- static uint8_t tmpbuf[BOOT_TMPBUF_SZ];
-
- if (bootutil_img_validate(hdr, fap, tmpbuf, BOOT_TMPBUF_SZ,
- NULL, 0, NULL)) {
- return BOOT_EBADIMAGE;
- }
- return 0;
-}
-
-static int
-boot_validate_slot(int slot)
-{
- const struct flash_area *fap;
- struct image_header *hdr;
- int rc;
-
- hdr = boot_img_hdr(&boot_data, slot);
- if (hdr->ih_magic == 0xffffffff || hdr->ih_flags & IMAGE_F_NON_BOOTABLE) {
- /* No bootable image in slot; continue booting from slot 0. */
- return -1;
- }
-
- rc = flash_area_open(flash_area_id_from_image_slot(slot), &fap);
- if (rc != 0) {
- return BOOT_EFLASH;
- }
-
- if ((hdr->ih_magic != IMAGE_MAGIC || boot_image_check(hdr, fap) != 0)) {
- if (slot != 0) {
- flash_area_erase(fap, 0, fap->fa_size);
- /* Image in slot 1 is invalid. Erase the image and
- * continue booting from slot 0.
- */
- }
- BOOT_LOG_ERR("Image in slot %d is not valid!", slot);
- return -1;
- }
-
- flash_area_close(fap);
-
- /* Image in slot 1 is valid. */
- return 0;
-}
-
/**
* Determines which swap operation to perform, if any. If it is determined
* that a swap operation is required, the image in the second slot is checked
@@ -652,42 +723,6 @@
#endif /* !MCUBOOT_OVERWRITE_ONLY */
/**
- * Erases a region of flash.
- *
- * @param flash_area_idx The ID of the flash area containing the region
- * to erase.
- * @param off The offset within the flash area to start the
- * erase.
- * @param sz The number of bytes to erase.
- *
- * @return 0 on success; nonzero on failure.
- */
-static int
-boot_erase_sector(int flash_area_id, uint32_t off, uint32_t sz)
-{
- const struct flash_area *fap = NULL;
- int rc;
-
- rc = flash_area_open(flash_area_id, &fap);
- if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
- }
-
- rc = flash_area_erase(fap, off, sz);
- if (rc != 0) {
- rc = BOOT_EFLASH;
- goto done;
- }
-
- rc = 0;
-
-done:
- flash_area_close(fap);
- return rc;
-}
-
-/**
* Copies the contents of one flash region to another. You must erase the
* destination region prior to calling this function.
*
@@ -794,35 +829,6 @@
}
#endif
-#ifndef MCUBOOT_OVERWRITE_ONLY
-static int
-boot_erase_last_sector_by_id(int flash_area_id)
-{
- uint8_t slot;
- uint32_t last_sector;
- int rc;
-
- switch (flash_area_id) {
- case FLASH_AREA_IMAGE_0:
- slot = 0;
- break;
- case FLASH_AREA_IMAGE_1:
- slot = 1;
- break;
- default:
- return BOOT_EFLASH;
- }
-
- last_sector = boot_img_num_sectors(&boot_data, slot) - 1;
- rc = boot_erase_sector(flash_area_id,
- boot_img_sector_off(&boot_data, slot, last_sector),
- boot_img_sector_size(&boot_data, slot, last_sector));
- assert(rc == 0);
-
- return rc;
-}
-#endif /* !MCUBOOT_OVERWRITE_ONLY */
-
/**
* Swaps the contents of two flash regions within the two image slots.
*
@@ -1243,6 +1249,7 @@
rc = flash_area_open(fa_id, &BOOT_IMG_AREA(&boot_data, slot));
assert(rc == 0);
}
+
rc = flash_area_open(FLASH_AREA_IMAGE_SCRATCH,
&BOOT_SCRATCH_AREA(&boot_data));
assert(rc == 0);
@@ -1346,9 +1353,9 @@
rc = BOOT_EBADIMAGE;
goto out;
}
-#else
+#else /* MCUBOOT_VALIDATE_SLOT0 */
(void)reload_headers;
-#endif
+#endif /* MCUBOOT_VALIDATE_SLOT0 */
/* Always boot from the primary slot. */
rsp->br_flash_dev_id = boot_img_fa_device_id(&boot_data, 0);
@@ -1362,3 +1369,213 @@
}
return rc;
}
+
+#else /* MCUBOOT_NO_SWAP */
+
+#define BOOT_LOG_IMAGE_INFO(area, hdr, state) \
+ BOOT_LOG_INF("Image %"PRIu32": version=%"PRIu8".%"PRIu8".%"PRIu16"" \
+ ".%"PRIu32", magic=%5s, image_ok=0x%x", \
+ (area), \
+ (hdr)->ih_ver.iv_major, \
+ (hdr)->ih_ver.iv_minor, \
+ (hdr)->ih_ver.iv_revision, \
+ (hdr)->ih_ver.iv_build_num, \
+ ((state)->magic == BOOT_MAGIC_GOOD ? "good" : \
+ (state)->magic == BOOT_MAGIC_UNSET ? "unset" : \
+ "bad"), \
+ (state)->image_ok)
+
+struct image_slot_version {
+ uint64_t version;
+ uint32_t slot_number;
+};
+
+/**
+ * Extract the version number from the image header. This function must be
+ * ported if version number format has changed in the image header.
+ *
+ * @param hdr Pointer to an image header structure
+ *
+ * @return Version number casted to unit64_t
+ */
+static uint64_t
+boot_get_version_number(struct image_header *hdr)
+{
+ return *((uint64_t *)(&hdr->ih_ver));
+}
+
+/**
+ * Comparator function for `qsort` to compare version numbers. This function
+ * must be ported if version number format has changed in the image header.
+ *
+ * @param ver1 Pointer to an array element which holds the version number
+ * @param ver2 Pointer to another array element which holds the version
+ * number
+ *
+ * @return if version1 > version2 -1
+ * if version1 == version2 0
+ * if version1 < version2 1
+ */
+static int
+boot_compare_version_numbers(const void *ver1, const void *ver2)
+{
+ if (((struct image_slot_version *)ver1)->version <
+ ((struct image_slot_version *)ver2)->version) {
+ return 1;
+ }
+
+ if (((struct image_slot_version *)ver1)->version ==
+ ((struct image_slot_version *)ver2)->version) {
+ return 0;
+ }
+
+ return -1;
+}
+
+/**
+ * Sort the available images based on the version number and puts them in
+ * a list.
+ *
+ * @param boot_sequence A pointer to an array, whose aim is to carry
+ * the boot order of candidate images.
+ * @param slot_cnt The number of flash areas, which can contains firmware
+ * images.
+ *
+ * @return The number of valid images.
+ */
+uint32_t
+boot_get_boot_sequence(uint32_t *boot_sequence, uint32_t slot_cnt)
+{
+ struct boot_swap_state slot_state;
+ struct image_header *hdr;
+ struct image_slot_version image_versions[BOOT_NUM_SLOTS] = {{0}};
+ uint32_t image_cnt = 0;
+ uint32_t slot;
+ int32_t rc;
+ int32_t fa_id;
+
+ for (slot = 0; slot < slot_cnt; slot++) {
+ hdr = boot_img_hdr(&boot_data, slot);
+ fa_id = flash_area_id_from_image_slot(slot);
+ rc = boot_read_swap_state_by_id(fa_id, &slot_state);
+ if (rc != 0) {
+ BOOT_LOG_ERR("Error during reading image trailer from slot:"
+ " %"PRIu32"", slot);
+ continue;
+ }
+
+ if (hdr->ih_magic == IMAGE_MAGIC) {
+ if (slot_state.magic == BOOT_MAGIC_GOOD ||
+ slot_state.image_ok == 0x01) {
+ /* Valid cases:
+ * - Test mode: magic is OK in image trailer
+ * - Permanent mode: image_ok flag has previously set
+ */
+ image_versions[slot].slot_number = slot;
+ image_versions[slot].version = boot_get_version_number(hdr);
+ image_cnt++;
+ }
+
+ if (slot_state.magic == BOOT_MAGIC_GOOD &&
+ slot_state.image_ok == 0xFF) {
+ /* Delete trailer in test mode in order to avoid booting it
+ * again without confirmation by runtime in case of subsequent
+ * boot.
+ */
+ boot_erase_last_sector_by_id(fa_id);
+ }
+ BOOT_LOG_IMAGE_INFO(slot, hdr, &slot_state);
+ } else {
+ BOOT_LOG_INF("Image %"PRIu32": No valid image", slot);
+ }
+ }
+
+ /* Sort the images based on version number */
+ qsort(&image_versions[0],
+ slot_cnt,
+ sizeof(struct image_slot_version),
+ boot_compare_version_numbers);
+
+ /* Copy the calculated boot sequence to boot_sequence array */
+ for (slot = 0; slot < slot_cnt; slot++) {
+ boot_sequence[slot] = image_versions[slot].slot_number;
+ }
+
+ return image_cnt;
+}
+
+/**
+ * Prepares the booting process. This function choose the newer image in flash
+ * as appropriate, and returns the address to boot from.
+ *
+ * @param rsp On success, indicates how booting should occur.
+ *
+ * @return 0 on success; nonzero on failure.
+ */
+int
+boot_go(struct boot_rsp *rsp)
+{
+ size_t slot = 0;
+ int32_t i;
+ int rc;
+ int fa_id;
+ uint32_t boot_sequence[BOOT_NUM_SLOTS];
+ uint32_t img_cnt;
+
+ static boot_sector_t slot0_sectors[BOOT_MAX_IMG_SECTORS];
+ static boot_sector_t slot1_sectors[BOOT_MAX_IMG_SECTORS];
+
+ boot_data.imgs[0].sectors = &slot0_sectors[0];
+ boot_data.imgs[1].sectors = &slot1_sectors[0];
+
+ /* Open boot_data image areas for the duration of this call. */
+ for (i = 0; i < BOOT_NUM_SLOTS; i++) {
+ fa_id = flash_area_id_from_image_slot(i);
+ rc = flash_area_open(fa_id, &BOOT_IMG_AREA(&boot_data, i));
+ assert(rc == 0);
+ }
+
+ /* Determine the sector layout of the image slots. */
+ rc = boot_read_sectors();
+ if (rc != 0) {
+ goto out;
+ }
+
+ /* Attempt to read an image header from each slot. */
+ rc = boot_read_image_headers();
+ if (rc != 0) {
+ goto out;
+ }
+
+ img_cnt = boot_get_boot_sequence(boot_sequence, BOOT_NUM_SLOTS);
+ if (img_cnt) {
+ /* Authenticate images */
+ for (i = 0; i < img_cnt; i++) {
+ rc = boot_validate_slot(boot_sequence[i]);
+ if (rc == 0) {
+ slot = boot_sequence[i];
+ break;
+ }
+ }
+ if (rc) {
+ /* If there was no valid image at all */
+ rc = BOOT_EBADIMAGE;
+ goto out;
+ }
+
+ BOOT_LOG_INF("Booting image from slot %d", slot);
+ rsp->br_flash_dev_id = boot_img_fa_device_id(&boot_data, slot);
+ rsp->br_image_off = boot_img_slot_off(&boot_data, slot);
+ rsp->br_hdr = boot_img_hdr(&boot_data, slot);
+ } else {
+ /* No candidate image available */
+ rc = BOOT_EBADIMAGE;
+ }
+
+out:
+ for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) {
+ flash_area_close(BOOT_IMG_AREA(&boot_data, BOOT_NUM_SLOTS - 1 - slot));
+ }
+ return rc;
+}
+#endif /* MCUBOOT_NO_SWAP */