Commit e574f2d6 authored by David Vincze's avatar David Vincze Committed by Dávid Vincze
Browse files

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: default avatarDavid Vincze <david.vincze@linaro.org>
parent 8b0b6375
Loading
Loading
Loading
Loading
+29 −4
Original line number Diff line number Diff line
@@ -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 @@ struct flash_area;
/** 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 @@ struct boot_swap_state {

_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 @@ _Static_assert(BOOT_IMAGE_NUMBER > 0, "Invalid value for BOOT_IMAGE_NUMBER");
                                                                | (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
+257 −75
Original line number Diff line number Diff line
@@ -74,6 +74,34 @@ static struct boot_loader_state boot_data;
#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 @@ done:
}
#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 @@ done:
    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 @@ boot_image_check(struct boot_loader_state *state, struct image_header *hdr,
    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 @@ split_image_check(struct image_header *app_hdr,

    return 0;
}
#endif /* !MCUBOOT_DIRECT_XIP */

/*
 * Check that this is a valid header.  Valid means that the magic is
@@ -517,6 +521,7 @@ boot_check_header_erased(struct boot_loader_state *state, int slot)
}

#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 @@ boot_validate_slot(struct boot_loader_state *state, int slot,
#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 @@ out:
    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 @@ done:
}
#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 @@ out:
    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 @@ done:
    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);
}
+6 −3
Original line number Diff line number Diff line
@@ -82,6 +82,7 @@ done:
    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
@@ -721,6 +722,8 @@ swap_run(struct boot_loader_state *state, struct boot_status *bs,
    }

}
#endif
#endif /* !MCUBOOT_OVERWRITE_ONLY */

#endif
#endif /* !MCUBOOT_DIRECT_XIP */

#endif /* !MCUBOOT_SWAP_USING_MOVE */
+36 −10
Original line number Diff line number Diff line
<!--
  - 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 @@ working).

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 @@ and during development, as well as any desired safety margin on the
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 @@ producing signed images, see: [signed_images](signed_images.md).
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
+10 −3
Original line number Diff line number Diff line
@@ -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
 *
Loading