boot: Introduce direct execute-in-place (XIP) mode
This patch introduces the direct execute-in-place (XIP) mode in addition
to the other upgrade modes (swap strategies, overwrite-only). When
direct-XIP is enabled with the MCUBOOT_DIRECT_XIP option, mcuboot
selects the newest valid image based on the image version numbers from
the image header, thereafter the selected image runs directly from its
flash partition (slot) instead of moving it. Therefore the images must
be linked to be executed from the given image slot. It means that in
direct-XIP mode either of the primary and the secondary slots can hold
the active image.
This patch is based on the NO_SWAP upgrade strategy which was first
introduced in the Trusted Firmware-M project.
Source TF-M version: TF-Mv1.0.
Change-Id: If584cf01ae5aa7208845f6a6fa206f0595e0e61e
Signed-off-by: David Vincze <david.vincze@linaro.org>
diff --git a/boot/bootutil/src/bootutil_priv.h b/boot/bootutil/src/bootutil_priv.h
index cd33f76..171b409 100644
--- a/boot/bootutil/src/bootutil_priv.h
+++ b/boot/bootutil/src/bootutil_priv.h
@@ -3,7 +3,7 @@
*
* Copyright (c) 2017-2020 Linaro LTD
* Copyright (c) 2017-2019 JUUL Labs
- * Copyright (c) 2019 Arm Limited
+ * Copyright (c) 2019-2020 Arm Limited
*
* Original license:
*
@@ -66,11 +66,15 @@
/** Number of image slots in flash; currently limited to two. */
#define BOOT_NUM_SLOTS 2
-#if defined(MCUBOOT_OVERWRITE_ONLY) && defined(MCUBOOT_SWAP_USING_MOVE)
-#error "Please enable only one of MCUBOOT_OVERWRITE_ONLY or MCUBOOT_SWAP_USING_MOVE"
+#if (defined(MCUBOOT_OVERWRITE_ONLY) + \
+ defined(MCUBOOT_SWAP_USING_MOVE) + \
+ defined(MCUBOOT_DIRECT_XIP)) > 1
+#error "Please enable only one of MCUBOOT_OVERWRITE_ONLY, MCUBOOT_SWAP_USING_MOVE or MCUBOOT_DIRECT_XIP"
#endif
-#if !defined(MCUBOOT_OVERWRITE_ONLY) && !defined(MCUBOOT_SWAP_USING_MOVE)
+#if !defined(MCUBOOT_OVERWRITE_ONLY) && \
+ !defined(MCUBOOT_SWAP_USING_MOVE) && \
+ !defined(MCUBOOT_DIRECT_XIP)
#define MCUBOOT_SWAP_USING_SCRATCH 1
#endif
@@ -166,6 +170,19 @@
_Static_assert(BOOT_IMAGE_NUMBER > 0, "Invalid value for BOOT_IMAGE_NUMBER");
+#if !defined(MCUBOOT_DIRECT_XIP)
+#define IS_IN_XIP_MODE() 0
+#else
+#define IS_IN_XIP_MODE() 1
+
+#if (BOOT_IMAGE_NUMBER != 1)
+#error "The MCUBOOT_DIRECT_XIP mode only supports single-image boot (MCUBOOT_IMAGE_NUMBER=1)."
+#endif
+#ifdef MCUBOOT_ENC_IMAGES
+#error "Image encryption (MCUBOOT_ENC_IMAGES) is not supported when MCUBOOT_DIRECT_XIP mode is selected."
+#endif
+#endif /* MCUBOOT_DIRECT_XIP */
+
#define BOOT_MAX_IMG_SECTORS MCUBOOT_MAX_IMG_SECTORS
/*
@@ -183,6 +200,14 @@
| (type); \
}
+#define BOOT_LOG_IMAGE_INFO(slot, hdr) \
+ BOOT_LOG_INF("%-9s slot: version=%u.%u.%u+%u", \
+ ((slot) == BOOT_PRIMARY_SLOT) ? "Primary" : "Secondary", \
+ (hdr)->ih_ver.iv_major, \
+ (hdr)->ih_ver.iv_minor, \
+ (hdr)->ih_ver.iv_revision, \
+ (hdr)->ih_ver.iv_build_num)
+
/*
* The current flashmap API does not check the amount of space allocated when
* loading sector data from the flash device, allowing for smaller counts here
diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c
index 60815df..c98b841 100644
--- a/boot/bootutil/src/loader.c
+++ b/boot/bootutil/src/loader.c
@@ -74,6 +74,34 @@
#define TARGET_STATIC
#endif
+static int
+boot_read_image_headers(struct boot_loader_state *state, bool require_all,
+ struct boot_status *bs)
+{
+ int rc;
+ int i;
+
+ for (i = 0; i < BOOT_NUM_SLOTS; i++) {
+ rc = boot_read_image_header(state, i, boot_img_hdr(state, i), bs);
+ if (rc != 0) {
+ /* If `require_all` is set, fail on any single fail, otherwise
+ * 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 && !require_all) {
+ return 0;
+ } else {
+ return rc;
+ }
+ }
+ }
+
+ return 0;
+}
+
+#ifndef MCUBOOT_DIRECT_XIP
/*
* Compute the total size of the given image. Includes the size of
* the TLVs.
@@ -137,33 +165,6 @@
}
#endif /* !MCUBOOT_OVERWRITE_ONLY */
-static int
-boot_read_image_headers(struct boot_loader_state *state, bool require_all,
- struct boot_status *bs)
-{
- int rc;
- int i;
-
- for (i = 0; i < BOOT_NUM_SLOTS; i++) {
- rc = boot_read_image_header(state, i, boot_img_hdr(state, i), bs);
- if (rc != 0) {
- /* If `require_all` is set, fail on any single fail, otherwise
- * 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 && !require_all) {
- return 0;
- } else {
- return rc;
- }
- }
- }
-
- return 0;
-}
-
static uint32_t
boot_write_sz(struct boot_loader_state *state)
{
@@ -378,6 +379,7 @@
flash_area_close(fap);
return rc;
}
+#endif /* !MCUBOOT_DIRECT_XIP */
/*
* Validate image hash/signature and optionally the security counter in a slot.
@@ -419,6 +421,7 @@
return 0;
}
+#ifndef MCUBOOT_DIRECT_XIP
static int
split_image_check(struct image_header *app_hdr,
const struct flash_area *app_fap,
@@ -447,6 +450,7 @@
return 0;
}
+#endif /* !MCUBOOT_DIRECT_XIP */
/*
* Check that this is a valid header. Valid means that the magic is
@@ -517,6 +521,7 @@
}
#if (BOOT_IMAGE_NUMBER > 1) || \
+ defined(MCUBOOT_DIRECT_XIP) || \
(defined(MCUBOOT_OVERWRITE_ONLY) && defined(MCUBOOT_DOWNGRADE_PREVENTION))
/**
* Compare image version numbers not including the build number
@@ -625,10 +630,10 @@
#endif
if (!boot_is_header_valid(hdr, fap) || boot_image_check(state, hdr, fap, bs)) {
- if (slot != BOOT_PRIMARY_SLOT) {
+ if ((slot != BOOT_PRIMARY_SLOT) || IS_IN_XIP_MODE()) {
flash_area_erase(fap, 0, fap->fa_size);
- /* Image in the secondary slot is invalid. Erase the image and
- * continue booting from the primary slot.
+ /* Image is invalid, erase it to prevent further unnecessary
+ * attempts to validate and boot it.
*/
}
#if !defined(__BOOTSIM__)
@@ -647,37 +652,6 @@
return rc;
}
-/**
- * Determines which swap operation to perform, if any. If it is determined
- * that a swap operation is required, the image in the secondary slot is checked
- * for validity. If the image in the secondary slot is invalid, it is erased,
- * and a swap type of "none" is indicated.
- *
- * @return The type of swap to perform (BOOT_SWAP_TYPE...)
- */
-static int
-boot_validated_swap_type(struct boot_loader_state *state,
- struct boot_status *bs)
-{
- int swap_type;
- int rc;
-
- swap_type = boot_swap_type_multi(BOOT_CURR_IMG(state));
- if (BOOT_IS_UPGRADE(swap_type)) {
- /* Boot loader wants to switch to the secondary slot.
- * Ensure image is valid.
- */
- rc = boot_validate_slot(state, BOOT_SECONDARY_SLOT, bs);
- if (rc == 1) {
- swap_type = BOOT_SWAP_TYPE_NONE;
- } else if (rc != 0) {
- swap_type = BOOT_SWAP_TYPE_FAIL;
- }
- }
-
- return swap_type;
-}
-
#ifdef MCUBOOT_HW_ROLLBACK_PROT
/**
* Updates the stored security counter value with the image's security counter
@@ -723,6 +697,38 @@
}
#endif /* MCUBOOT_HW_ROLLBACK_PROT */
+#ifndef MCUBOOT_DIRECT_XIP
+/**
+ * Determines which swap operation to perform, if any. If it is determined
+ * that a swap operation is required, the image in the secondary slot is checked
+ * for validity. If the image in the secondary slot is invalid, it is erased,
+ * and a swap type of "none" is indicated.
+ *
+ * @return The type of swap to perform (BOOT_SWAP_TYPE...)
+ */
+static int
+boot_validated_swap_type(struct boot_loader_state *state,
+ struct boot_status *bs)
+{
+ int swap_type;
+ int rc;
+
+ swap_type = boot_swap_type_multi(BOOT_CURR_IMG(state));
+ if (BOOT_IS_UPGRADE(swap_type)) {
+ /* Boot loader wants to switch to the secondary slot.
+ * Ensure image is valid.
+ */
+ rc = boot_validate_slot(state, BOOT_SECONDARY_SLOT, bs);
+ if (rc == 1) {
+ swap_type = BOOT_SWAP_TYPE_NONE;
+ } else if (rc != 0) {
+ swap_type = BOOT_SWAP_TYPE_FAIL;
+ }
+ }
+
+ return swap_type;
+}
+
/**
* Erases a region of flash.
*
@@ -1864,20 +1870,6 @@
return rc;
}
-/**
- * Prepares the booting process. This function moves images around in flash as
- * appropriate, and tells you what 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)
-{
- return context_boot_go(&boot_data, rsp);
-}
-
int
split_go(int loader_slot, int split_slot, void **entry)
{
@@ -1939,3 +1931,193 @@
free(sectors);
return rc;
}
+
+#else /* MCUBOOT_DIRECT_XIP */
+
+/**
+ * Iterates over all slots and determines which contain a firmware image.
+ *
+ * @param state Boot loader status information.
+ * @param slot_usage Pointer to an array, which aim is to carry information
+ * about slots that contain an image. After return the
+ * corresponding array elements are set to a non-zero
+ * value if the given slots are in use (contain a firmware
+ * image), otherwise they are set to zero.
+ * @param slot_cnt The number of slots, which can contain firmware images.
+ * (Equal to or smaller than the size of the
+ * slot_usage array.)
+ *
+ * @return The number of found images.
+ */
+static uint32_t
+boot_get_slot_usage(struct boot_loader_state *state, uint8_t slot_usage[],
+ uint32_t slot_cnt)
+{
+ struct image_header *hdr = NULL;
+ uint32_t image_cnt = 0;
+ uint32_t slot;
+
+ memset(slot_usage, 0, slot_cnt);
+
+ for (slot = 0; slot < slot_cnt; slot++) {
+ hdr = boot_img_hdr(state, slot);
+
+ if (boot_is_header_valid(hdr, BOOT_IMG_AREA(state, slot))) {
+ slot_usage[slot] = 1;
+ image_cnt++;
+ BOOT_LOG_IMAGE_INFO(slot, hdr);
+ } else {
+ BOOT_LOG_INF("%s slot: Image not found", (slot == BOOT_PRIMARY_SLOT)
+ ? "Primary" : "Secondary");
+ }
+ }
+
+ return image_cnt;
+}
+
+int
+context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)
+{
+ struct image_header *hdr = NULL;
+ struct image_header *selected_image_header = NULL;
+ uint8_t slot_usage[BOOT_NUM_SLOTS];
+ uint32_t selected_slot;
+ uint32_t slot;
+ uint32_t img_cnt;
+ uint32_t i;
+ int fa_id;
+ int rc;
+
+ memset(state, 0, sizeof(struct boot_loader_state));
+
+ /* Open primary and secondary image areas for the duration
+ * of this call.
+ */
+ for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) {
+ fa_id = flash_area_id_from_image_slot(slot);
+ rc = flash_area_open(fa_id, &BOOT_IMG_AREA(state, slot));
+ assert(rc == 0);
+ }
+
+ /* Attempt to read an image header from each slot. */
+ rc = boot_read_image_headers(state, false, NULL);
+ if (rc != 0) {
+ BOOT_LOG_WRN("Failed reading image headers.");
+ goto out;
+ }
+
+ img_cnt = boot_get_slot_usage(state, slot_usage,
+ sizeof(slot_usage)/sizeof(slot_usage[0]));
+
+ if (img_cnt) {
+ /* Select the newest and valid image. */
+ for (i = 0; i < img_cnt; i++) {
+ selected_slot = 0;
+ selected_image_header = NULL;
+
+ /* Iterate over all the slots that are in use (contain an image)
+ * and select the one that holds the newest image.
+ */
+ for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) {
+ if (slot_usage[slot]) {
+ hdr = boot_img_hdr(state, slot);
+ if (selected_image_header != NULL) {
+ rc = boot_version_cmp(&hdr->ih_ver,
+ &selected_image_header->ih_ver);
+ if (rc < 1) {
+ /* The version of the image being examined wasn't
+ * greater than the currently selected image's
+ * version.
+ */
+ continue;
+ }
+ }
+ selected_slot = slot;
+ selected_image_header = hdr;
+ }
+ }
+
+ rc = boot_validate_slot(state, selected_slot, NULL);
+ if (rc == 0) {
+ /* If a valid image is found then there is no reason to check
+ * the rest of the images, as each of them has a smaller version
+ * number.
+ */
+ break;
+ }
+ /* The selected image is invalid, mark its slot as "unused"
+ * and start over.
+ */
+ slot_usage[selected_slot] = 0;
+ }
+
+ if (rc || (selected_image_header == NULL)) {
+ /* If there was no valid image at all */
+ rc = BOOT_EBADIMAGE;
+ goto out;
+ }
+
+#ifdef MCUBOOT_HW_ROLLBACK_PROT
+ /* Update the stored security counter with the newer (active) image's
+ * security counter value.
+ */
+ rc = boot_update_security_counter(0, selected_slot,
+ selected_image_header);
+ if (rc != 0) {
+ BOOT_LOG_ERR("Security counter update failed after image "
+ "validation.");
+ goto out;
+ }
+#endif /* MCUBOOT_HW_ROLLBACK_PROT */
+
+#ifdef MCUBOOT_MEASURED_BOOT
+ rc = boot_save_boot_status(0, selected_image_header,
+ BOOT_IMG_AREA(state, selected_slot));
+ if (rc != 0) {
+ BOOT_LOG_ERR("Failed to add image data to shared area");
+ }
+#endif /* MCUBOOT_MEASURED_BOOT */
+
+#ifdef MCUBOOT_DATA_SHARING
+ rc = boot_save_shared_data(selected_image_header,
+ BOOT_IMG_AREA(state, selected_slot));
+ if (rc != 0) {
+ BOOT_LOG_ERR("Failed to add data to shared memory area.");
+ }
+#endif /* MCUBOOT_DATA_SHARING */
+
+ BOOT_LOG_INF("Booting image from the %s slot",
+ (selected_slot == BOOT_PRIMARY_SLOT) ?
+ "primary" : "secondary");
+
+ rsp->br_flash_dev_id =
+ BOOT_IMG_AREA(state, selected_slot)->fa_device_id;
+ rsp->br_image_off = boot_img_slot_off(state, selected_slot);
+ rsp->br_hdr = selected_image_header;
+ } else {
+ /* No candidate image available */
+ rc = BOOT_EBADIMAGE;
+ goto out;
+ }
+
+out:
+ for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) {
+ flash_area_close(BOOT_IMG_AREA(state, BOOT_NUM_SLOTS - 1 - slot));
+ }
+ return rc;
+}
+#endif /* MCUBOOT_DIRECT_XIP */
+
+/**
+ * Prepares the booting process. This function moves images around in flash as
+ * appropriate, and tells you what 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)
+{
+ return context_boot_go(&boot_data, rsp);
+}
diff --git a/boot/bootutil/src/swap_scratch.c b/boot/bootutil/src/swap_scratch.c
index 3266dc4..e60d93d 100644
--- a/boot/bootutil/src/swap_scratch.c
+++ b/boot/bootutil/src/swap_scratch.c
@@ -82,6 +82,7 @@
return rc;
}
+#if !defined(MCUBOOT_DIRECT_XIP)
/**
* Reads the status of a partially-completed swap, if any. This is necessary
* to recover in case the boot lodaer was reset in the middle of a swap
@@ -541,7 +542,7 @@
/* Write a trailer to the scratch area, even if we don't need the
* scratch area for status. We need a temporary place to store the
* `swap-type` while we erase the primary trailer.
- */
+ */
rc = swap_status_init(state, fap_scratch, bs);
assert(rc == 0);
@@ -721,6 +722,8 @@
}
}
-#endif
+#endif /* !MCUBOOT_OVERWRITE_ONLY */
-#endif
+#endif /* !MCUBOOT_DIRECT_XIP */
+
+#endif /* !MCUBOOT_SWAP_USING_MOVE */
diff --git a/docs/design.md b/docs/design.md
index 9e3550c..697243a 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -1,7 +1,7 @@
<!--
- SPDX-License-Identifier: Apache-2.0
- - Copyright (c) 2017 Linaro LTD
+ - Copyright (c) 2017-2020 Linaro LTD
- Copyright (c) 2017-2019 JUUL Labs
- Copyright (c) 2019-2020 Arm Limited
@@ -166,10 +166,11 @@
A portion of the flash memory can be partitioned into multiple image areas, each
contains two image slots: a primary slot and a secondary slot.
-The boot loader will only run an image from the primary slot, so images must be
-built such that they can run from that fixed location in flash. If the boot
-loader needs to run the image resident in the secondary slot, it must copy its
-contents into the primary slot before doing so, either by swapping the two
+Normally, the boot loader will only run an image from the primary slot, so
+images must be built such that they can run from that fixed location in flash
+(the exception to this is the [direct-xip](#direct-xip) upgrade mode). If the
+boot loader needs to run the image resident in the secondary slot, it must copy
+its contents into the primary slot before doing so, either by swapping the two
images or by overwriting the contents of the primary slot. The bootloader
supports either swap- or overwrite-based image upgrades, but must be configured
at build time to choose one of these two strategies.
@@ -215,11 +216,33 @@
manufacturer's specified number of erase cycles. In general, using a ratio that
allows hundreds to thousands of field upgrades in production is recommended.
-The overwrite upgrade strategy is substantially simpler to implement than the
-image swapping strategy, especially since the bootloader must work properly
-even when it is reset during the middle of an image swap. For this reason, the
-rest of the document describes its behavior when configured to swap images
-during an upgrade.
+### [Equal slots (direct-xip)](#direct-xip)
+
+When the direct-xip mode is enabled the active image flag is "moved" between the
+slots during image upgrade and in contrast to the above, the bootloader can
+run an image directly from either the primary or the secondary slot (without
+having to move/copy it into the primary slot). Therefore the image update
+client, which downloads the new images must be aware, which slot contains the
+active image and which acts as a staging area and it is responsible for loading
+the proper images into the proper slot. All this requires that the images be
+built to be executed from the corresponding slot. At boot time the bootloader
+first looks for images in the slots and then inspects the version numbers in the
+image headers. It selects the newest image (with the highest version number) and
+then checks its validity (integrity check, signature verification etc.). If the
+image is invalid MCUboot erases its memory slot and starts to validate the other
+image. After a successful validation of the selected image the bootloader
+chain-loads it.
+Handling the primary and secondary slots as equals has its drawbacks. Since the
+images are not moved between the slots, the on-the-fly image
+encryption/decryption can't be supported (it only applies to storing the image
+in an external flash on the device, the transport of encrypted image data is
+still feasible).
+
+The overwrite and the direct-xip upgrade strategies are substantially simpler to
+implement than the image swapping strategy, especially since the bootloader must
+work properly even when it is reset during the middle of an image swap. For this
+reason, the rest of the document describes its behavior when configured to swap
+images during an upgrade.
## [Boot Swap Types](#boot-swap-types)
@@ -890,6 +913,9 @@
If you want to enable and use encrypted images, see:
[encrypted_images](encrypted_images.md).
+Note: Image encryption is not supported when the direct-xip upgrade strategy
+is selected.
+
### [Using Hardware Keys for Verification](#hw-key-support)
By default, the whole public key is embedded in the bootloader code and its
diff --git a/samples/mcuboot_config/mcuboot_config.template.h b/samples/mcuboot_config/mcuboot_config.template.h
index 3038a93..72e33eb 100644
--- a/samples/mcuboot_config/mcuboot_config.template.h
+++ b/samples/mcuboot_config/mcuboot_config.template.h
@@ -37,9 +37,13 @@
/*
* Upgrade mode
*
- * The default is to support A/B image swapping with rollback. A
- * simpler code path, which only supports overwriting the
- * existing image with the update image, is also available.
+ * The default is to support A/B image swapping with rollback. Other modes
+ * with simpler code path, which only supports overwriting the existing image
+ * with the update image or running the newest image directly from its flash
+ * partition, are also available.
+ *
+ * You can enable only one mode at a time from the list below to override
+ * the default upgrade mode.
*/
/* Uncomment to enable the overwrite-only code path. */
@@ -51,6 +55,9 @@
/* #define MCUBOOT_OVERWRITE_ONLY_FAST */
#endif
+/* Uncomment to enable the direct-xip code path. */
+/* #define MCUBOOT_DIRECT_XIP */
+
/*
* Cryptographic settings
*
diff --git a/scripts/imgtool/main.py b/scripts/imgtool/main.py
index ff7c79b..ca5cc52 100755
--- a/scripts/imgtool/main.py
+++ b/scripts/imgtool/main.py
@@ -243,7 +243,8 @@
'keys. Enable when BOOT_SWAP_SAVE_ENCTLV config option '
'was set.')
@click.option('-E', '--encrypt', metavar='filename',
- help='Encrypt image using the provided public key')
+ help='Encrypt image using the provided public key. '
+ '(Not supported in direct-xip mode.)')
@click.option('-e', '--endian', type=click.Choice(['little', 'big']),
default='little', help="Select little or big endian")
@click.option('--overwrite-only', default=False, is_flag=True,