Commit 0f2c5855 authored by Andrzej Puzdrowski's avatar Andrzej Puzdrowski
Browse files
parents 813d29ca 5a6e1814
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
+292 −104
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,35 +521,42 @@ 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))
/**
 * Check if the version of the image is not older than required.
 * Compare image version numbers not including the build number
 *
 * @param req         Required minimal image version.
 * @param ver         Version of the image to be checked.
 * @param ver1           Pointer to the first image version to compare.
 * @param ver2           Pointer to the second image version to compare.
 *
 * @return            0 if the version is sufficient, nonzero otherwise.
 * @retval -1           If ver1 is strictly less than ver2.
 * @retval 0            If the image version numbers are equal,
 *                      (not including the build number).
 * @retval 1            If ver1 is strictly greater than ver2.
 */
static int
boot_is_version_sufficient(struct image_version *req,
                           struct image_version *ver)
boot_version_cmp(const struct image_version *ver1,
                 const struct image_version *ver2)
{
    if (ver->iv_major > req->iv_major) {
        return 0;
    if (ver1->iv_major > ver2->iv_major) {
        return 1;
    }
    if (ver1->iv_major < ver2->iv_major) {
        return -1;
    }
    if (ver->iv_major < req->iv_major) {
        return BOOT_EBADVERSION;
    /* The major version numbers are equal, continue comparison. */
    if (ver1->iv_minor > ver2->iv_minor) {
        return 1;
    }
    /* The major version numbers are equal. */
    if (ver->iv_minor > req->iv_minor) {
        return 0;
    if (ver1->iv_minor < ver2->iv_minor) {
        return -1;
    }
    if (ver->iv_minor < req->iv_minor) {
        return BOOT_EBADVERSION;
    /* The minor version numbers are equal, continue comparison. */
    if (ver1->iv_revision > ver2->iv_revision) {
        return 1;
    }
    /* The minor version numbers are equal. */
    if (ver->iv_revision < req->iv_revision) {
        return BOOT_EBADVERSION;
    if (ver1->iv_revision < ver2->iv_revision) {
        return -1;
    }

    return 0;
@@ -603,10 +614,10 @@ boot_validate_slot(struct boot_loader_state *state, int slot,
#if defined(MCUBOOT_OVERWRITE_ONLY) && defined(MCUBOOT_DOWNGRADE_PREVENTION)
    if (slot != BOOT_PRIMARY_SLOT) {
        /* Check if version of secondary slot is sufficient */
        rc = boot_is_version_sufficient(
                &boot_img_hdr(state, BOOT_PRIMARY_SLOT)->ih_ver,
                &boot_img_hdr(state, BOOT_SECONDARY_SLOT)->ih_ver);
        if (rc != 0 && boot_check_header_erased(state, BOOT_PRIMARY_SLOT)) {
        rc = boot_version_cmp(
                &boot_img_hdr(state, BOOT_SECONDARY_SLOT)->ih_ver,
                &boot_img_hdr(state, BOOT_PRIMARY_SLOT)->ih_ver);
        if (rc < 0 && boot_check_header_erased(state, BOOT_PRIMARY_SLOT)) {
            BOOT_LOG_ERR("insufficient version in secondary slot");
            flash_area_erase(fap, 0, fap->fa_size);
            /* Image in the secondary slot does not satisfy version requirement.
@@ -619,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__)
@@ -641,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
@@ -717,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.
 *
@@ -1126,8 +1138,8 @@ boot_verify_slot_dependency(struct boot_loader_state *state,
                                          : BOOT_PRIMARY_SLOT;
    dep_version = &state->imgs[dep->image_id][dep_slot].hdr.ih_ver;

    rc = boot_is_version_sufficient(&dep->image_min_version, dep_version);
    if (rc != 0) {
    rc = boot_version_cmp(dep_version, &dep->image_min_version);
    if (rc < 0) {
        /* Dependency not satisfied.
         * Modify the swap type to decrease the version number of the image
         * (which will be located in the primary slot after the boot process),
@@ -1145,6 +1157,9 @@ boot_verify_slot_dependency(struct boot_loader_state *state,
        default:
            break;
        }
    } else {
        /* Dependency satisfied. */
        rc = 0;
    }

    return rc;
@@ -1243,7 +1258,7 @@ boot_verify_dependencies(struct boot_loader_state *state)
        if (rc == 0) {
            /* All dependencies've been satisfied, continue with next image. */
            BOOT_CURR_IMG(state)++;
        } else if (rc == BOOT_EBADVERSION) {
        } else {
            /* Cannot upgrade due to non-met dependencies, so disable all
             * image upgrades.
             */
@@ -1252,9 +1267,6 @@ boot_verify_dependencies(struct boot_loader_state *state)
                BOOT_SWAP_TYPE(state) = BOOT_SWAP_TYPE_NONE;
            }
            break;
        } else {
            /* Other error happened, images are inconsistent */
            return rc;
        }
    }
    return rc;
@@ -1675,7 +1687,7 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)
         * are all satisfied and update swap type if necessary.
         */
        rc = boot_verify_dependencies(state);
        if (rc == BOOT_EBADVERSION) {
        if (rc != 0) {
            /*
             * It was impossible to upgrade because the expected dependency version
             * was not available. Here we already changed the swap_type so that
@@ -1858,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)
{
@@ -1933,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 */
+5 −0
Original line number Diff line number Diff line
@@ -231,9 +231,14 @@ if(CONFIG_MCUBOOT_SERIAL)
    )
endif()

# CONF_FILE points to the KConfig configuration file of the bootloader
get_filename_component(CONF_DIR ${CONF_FILE} DIRECTORY)

if(NOT CONFIG_BOOT_SIGNATURE_KEY_FILE STREQUAL "")
  if(IS_ABSOLUTE ${CONFIG_BOOT_SIGNATURE_KEY_FILE})
    set(KEY_FILE ${CONFIG_BOOT_SIGNATURE_KEY_FILE})
  elseif(EXISTS ${CONF_DIR}/${CONFIG_BOOT_SIGNATURE_KEY_FILE})
    set(KEY_FILE ${CONF_DIR}/${CONFIG_BOOT_SIGNATURE_KEY_FILE})
  else()
    set(KEY_FILE ${MCUBOOT_DIR}/${CONFIG_BOOT_SIGNATURE_KEY_FILE})
  endif()
+27 −3
Original line number Diff line number Diff line
@@ -128,6 +128,11 @@ config BOOT_SIGNATURE_KEY_FILE
	string "PEM key file"
	default ""
	help
	  You can use either absolute or relative path.
	  In case relative path is used, the build system assumes that it starts
	  from the directory where the MCUBoot KConfig configuration file is
	  located. If the key file is not there, the build system uses relative
	  path that starts from the MCUBoot repository root directory.
	  The key file will be parsed by imgtool's getpub command and a .c source
	  with the public key information will be written in a format expected by
	  MCUboot.
@@ -159,9 +164,19 @@ config BOOT_VALIDATE_SLOT0
	  able to modify the flash image itself.

if !SINGLE_IMAGE_DFU
choice
	prompt "Image upgrade modes"
	default BOOT_SWAP_USING_MOVE if SOC_FAMILY_NRF
	default BOOT_SWAP_USING_SCRATCH

config BOOT_SWAP_USING_SCRATCH
	bool "Swap mode that run with the scratch partition"
	help
	  This is the most conservative swap mode but it can work even on
	  devices with heterogeneous flash page layout.

config BOOT_UPGRADE_ONLY
	bool "Overwrite image updates instead of swapping"
	default n
	help
	  If y, overwrite the primary slot with the upgrade image instead
	  of swapping them. This prevents the fallback recovery, but
@@ -169,8 +184,6 @@ config BOOT_UPGRADE_ONLY

config BOOT_SWAP_USING_MOVE
	bool "Swap mode that can run without a scratch partition"
	default y if SOC_FAMILY_NRF
	default n
	help
	  If y, the swap upgrade is done in two steps, where first every
	  sector of the primary slot is moved up one sector, then for
@@ -180,6 +193,17 @@ config BOOT_SWAP_USING_MOVE
	  This allows a swap upgrade without using a scratch partition,
	  but is currently limited to all sectors in both slots being of
	  the same size.

config BOOT_DIRECT_XIP
	bool "Run the latest image directly from its slot"
	help
	  If y, mcuboot selects the newest valid image based on the image version
	  numbers, thereafter the selected image can run directly from its slot
	  without having to move/copy it into the primary slot. For this reason the
	  images must be linked to be executed from the given image slot. Using this
	  mode results in a simpler code path and smaller code size.

endchoice
endif

if !SINGLE_IMAGE_DFU
Loading