bootutil: add swap without scratch strategy
This implements a swap upgrade that does not use a scratch area. It
works by first moving all sectors in the primary slot up one position,
and then looping on moving sector of index X of the secondary slot to
index X of the primary slot, followed by moving sector X+1 of the
primary slot to X on the secondary slot, for each sector X.
The idea behind this implementation was initially suggested by Jehudi
Maes (@Laczen) and implemented on his own bootloader (ZEPboot).
Signed-off-by: Fabio Utzig <utzig@apache.org>
diff --git a/boot/bootutil/src/bootutil_priv.h b/boot/bootutil/src/bootutil_priv.h
index 70a1b0d..6b298f4 100644
--- a/boot/bootutil/src/bootutil_priv.h
+++ b/boot/bootutil/src/bootutil_priv.h
@@ -62,16 +62,24 @@
/** Number of image slots in flash; currently limited to two. */
#define BOOT_NUM_SLOTS 2
-#if !defined(MCUBOOT_OVERWRITE_ONLY)
+#if defined(MCUBOOT_OVERWRITE_ONLY) && defined(MCUBOOT_SWAP_USING_MOVE)
+#error "Please enable only one of MCUBOOT_OVERWRITE_ONLY or MCUBOOT_SWAP_USING_MOVE"
+#endif
+
+#if !defined(MCUBOOT_OVERWRITE_ONLY) && !defined(MCUBOOT_SWAP_USING_MOVE)
#define MCUBOOT_SWAP_USING_SCRATCH 1
#endif
+#define BOOT_STATUS_OP_MOVE 1
+#define BOOT_STATUS_OP_SWAP 2
+
/*
* Maintain state of copy progress.
*/
struct boot_status {
uint32_t idx; /* Which area we're operating on */
uint8_t state; /* Which part of the swapping process are we at */
+ uint8_t op; /* What operation are we performing? */
uint8_t use_scratch; /* Are status bytes ever written to scratch? */
uint8_t swap_type; /* The type of swap in effect */
uint32_t swap_size; /* Total size of swapped image */
@@ -178,8 +186,15 @@
#error "Too few sectors, please increase BOOT_MAX_IMG_SECTORS to at least 32"
#endif
-/** Maximum number of image sectors supported by the bootloader. */
+#if MCUBOOT_SWAP_USING_MOVE
+#define BOOT_STATUS_MOVE_STATE_COUNT 1
+#define BOOT_STATUS_SWAP_STATE_COUNT 2
+#define BOOT_STATUS_STATE_COUNT (BOOT_STATUS_MOVE_STATE_COUNT + BOOT_STATUS_SWAP_STATE_COUNT)
+#else
#define BOOT_STATUS_STATE_COUNT 3
+#endif
+
+/** Maximum number of image sectors supported by the bootloader. */
#define BOOT_STATUS_MAX_ENTRIES BOOT_MAX_IMG_SECTORS
#define BOOT_PRIMARY_SLOT 0
diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c
index 1635efc..36ea754 100644
--- a/boot/bootutil/src/loader.c
+++ b/boot/bootutil/src/loader.c
@@ -286,6 +286,7 @@
boot_status_reset(struct boot_status *bs)
{
memset(bs, 0, sizeof *bs);
+ bs->op = BOOT_STATUS_OP_MOVE;
bs->idx = BOOT_STATUS_IDX_0;
bs->state = BOOT_STATUS_STATE_0;
bs->swap_type = BOOT_SWAP_TYPE_NONE;
@@ -294,7 +295,9 @@
bool
boot_status_is_reset(const struct boot_status *bs)
{
- return (bs->idx == BOOT_STATUS_IDX_0 && bs->state == BOOT_STATUS_STATE_0);
+ return (bs->op == BOOT_STATUS_OP_MOVE &&
+ bs->idx == BOOT_STATUS_IDX_0 &&
+ bs->state == BOOT_STATUS_STATE_0);
}
/**
@@ -650,16 +653,25 @@
#ifdef MCUBOOT_ENC_IMAGES
image_index = BOOT_CURR_IMG(state);
- if (fap_src->fa_id == FLASH_AREA_IMAGE_SECONDARY(image_index) ||
- fap_dst->fa_id == FLASH_AREA_IMAGE_SECONDARY(image_index)) {
+ if ((fap_src->fa_id == FLASH_AREA_IMAGE_SECONDARY(image_index) ||
+ fap_dst->fa_id == FLASH_AREA_IMAGE_SECONDARY(image_index)) &&
+ !(fap_src->fa_id == FLASH_AREA_IMAGE_SECONDARY(image_index) &&
+ fap_dst->fa_id == FLASH_AREA_IMAGE_SECONDARY(image_index))) {
/* assume the secondary slot as src, needs decryption */
hdr = boot_img_hdr(state, BOOT_SECONDARY_SLOT);
+#if !defined(MCUBOOT_SWAP_USING_MOVE)
off = off_src;
if (fap_dst->fa_id == FLASH_AREA_IMAGE_SECONDARY(image_index)) {
/* might need encryption (metadata from the primary slot) */
hdr = boot_img_hdr(state, BOOT_PRIMARY_SLOT);
off = off_dst;
}
+#else
+ off = off_dst;
+ if (fap_dst->fa_id == FLASH_AREA_IMAGE_SECONDARY(image_index)) {
+ hdr = boot_img_hdr(state, BOOT_PRIMARY_SLOT);
+ }
+#endif
if (IS_ENCRYPTED(hdr)) {
blk_sz = chunk_sz;
idx = 0;
@@ -1357,6 +1369,21 @@
}
#endif
+#ifdef MCUBOOT_SWAP_USING_MOVE
+ /*
+ * Must re-read image headers because the boot status might
+ * have been updated in the previous function call.
+ */
+ rc = boot_read_image_headers(state, !boot_status_is_reset(bs), bs);
+ if (rc != 0) {
+ /* Continue with next image if there is one. */
+ BOOT_LOG_WRN("Failed reading image headers; Image=%u",
+ BOOT_CURR_IMG(state));
+ BOOT_SWAP_TYPE(state) = BOOT_SWAP_TYPE_NONE;
+ return;
+ }
+#endif
+
/* Determine if we rebooted in the middle of an image swap
* operation. If a partial swap was detected, complete it.
*/
diff --git a/boot/bootutil/src/swap_move.c b/boot/bootutil/src/swap_move.c
new file mode 100644
index 0000000..6955474
--- /dev/null
+++ b/boot/bootutil/src/swap_move.c
@@ -0,0 +1,483 @@
+/*
+ * Copyright (c) 2019 JUUL Labs
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include "bootutil/bootutil.h"
+#include "bootutil_priv.h"
+#include "swap_priv.h"
+#include "bootutil/bootutil_log.h"
+
+#include "mcuboot_config/mcuboot_config.h"
+
+MCUBOOT_LOG_MODULE_DECLARE(mcuboot);
+
+#ifdef MCUBOOT_SWAP_USING_MOVE
+
+#if defined(MCUBOOT_VALIDATE_PRIMARY_SLOT)
+/*
+ * FIXME: this might have to be updated for threaded sim
+ */
+int boot_status_fails = 0;
+#define BOOT_STATUS_ASSERT(x) \
+ do { \
+ if (!(x)) { \
+ boot_status_fails++; \
+ } \
+ } while (0)
+#else
+#define BOOT_STATUS_ASSERT(x) ASSERT(x)
+#endif
+
+static uint32_t g_last_idx = UINT32_MAX;
+
+int
+boot_read_image_header(struct boot_loader_state *state, int slot,
+ struct image_header *out_hdr, struct boot_status *bs)
+{
+ const struct flash_area *fap;
+ uint32_t off;
+ uint32_t sz;
+ int area_id;
+ int rc;
+
+#if (BOOT_IMAGE_NUMBER == 1)
+ (void)state;
+#endif
+
+ off = 0;
+ if (bs) {
+ sz = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, 0);
+ if (bs->op == BOOT_STATUS_OP_MOVE) {
+ if (slot == 0 && bs->idx > g_last_idx) {
+ /* second sector */
+ off = sz;
+ }
+ } else if (bs->op == BOOT_STATUS_OP_SWAP) {
+ if (bs->idx > 1 && bs->idx <= g_last_idx) {
+ if (slot == 0) {
+ slot = 1;
+ } else {
+ slot = 0;
+ }
+ } else if (bs->idx == 1) {
+ if (slot == 0) {
+ off = sz;
+ }
+ if (slot == 1 && bs->state == 2) {
+ slot = 0;
+ }
+ }
+ }
+ }
+
+ area_id = flash_area_id_from_multi_image_slot(BOOT_CURR_IMG(state), slot);
+ rc = flash_area_open(area_id, &fap);
+ if (rc != 0) {
+ rc = BOOT_EFLASH;
+ goto done;
+ }
+
+ rc = flash_area_read(fap, off, out_hdr, sizeof *out_hdr);
+ if (rc != 0) {
+ rc = BOOT_EFLASH;
+ goto done;
+ }
+
+ /* We only know where the headers are located when bs is valid */
+ if (bs != NULL && out_hdr->ih_magic != IMAGE_MAGIC) {
+ rc = -1;
+ goto done;
+ }
+
+ rc = 0;
+
+done:
+ flash_area_close(fap);
+ return rc;
+}
+
+int
+swap_read_status_bytes(const struct flash_area *fap,
+ struct boot_loader_state *state, struct boot_status *bs)
+{
+ uint32_t off;
+ uint8_t status;
+ int max_entries;
+ int found_idx;
+ uint8_t write_sz;
+ int move_entries;
+ int rc;
+ int last_rc;
+ int erased_sections;
+ int i;
+
+ max_entries = boot_status_entries(BOOT_CURR_IMG(state), fap);
+ if (max_entries < 0) {
+ return BOOT_EBADARGS;
+ }
+
+ erased_sections = 0;
+ found_idx = -1;
+ /* skip erased sectors at the end */
+ last_rc = 1;
+ write_sz = BOOT_WRITE_SZ(state);
+ off = boot_status_off(fap);
+ for (i = max_entries; i > 0; i--) {
+ rc = flash_area_read_is_empty(fap, off + (i - 1) * write_sz, &status, 1);
+ if (rc < 0) {
+ return BOOT_EFLASH;
+ }
+
+ if (rc == 1) {
+ if (rc != last_rc) {
+ erased_sections++;
+ }
+ } else {
+ if (found_idx == -1) {
+ found_idx = i;
+ }
+ }
+ last_rc = rc;
+ }
+
+ if (erased_sections > 1) {
+ /* This means there was an error writing status on the last
+ * swap. Tell user and move on to validation!
+ */
+ BOOT_LOG_ERR("Detected inconsistent status!");
+
+#if !defined(MCUBOOT_VALIDATE_PRIMARY_SLOT)
+ /* With validation of the primary slot disabled, there is no way
+ * to be sure the swapped primary slot is OK, so abort!
+ */
+ assert(0);
+#endif
+ }
+
+ move_entries = BOOT_MAX_IMG_SECTORS * BOOT_STATUS_MOVE_STATE_COUNT;
+ if (found_idx == -1) {
+ /* no swap status found; nothing to do */
+ } else if (found_idx < move_entries) {
+ bs->op = BOOT_STATUS_OP_MOVE;
+ bs->idx = (found_idx / BOOT_STATUS_MOVE_STATE_COUNT) + BOOT_STATUS_IDX_0;
+ bs->state = (found_idx % BOOT_STATUS_MOVE_STATE_COUNT) + BOOT_STATUS_STATE_0;;
+ } else {
+ bs->op = BOOT_STATUS_OP_SWAP;
+ bs->idx = ((found_idx - move_entries) / BOOT_STATUS_SWAP_STATE_COUNT) + BOOT_STATUS_IDX_0;
+ bs->state = ((found_idx - move_entries) % BOOT_STATUS_SWAP_STATE_COUNT) + BOOT_STATUS_STATE_0;
+ }
+
+ return 0;
+}
+
+uint32_t
+boot_status_internal_off(const struct boot_status *bs, int elem_sz)
+{
+ uint32_t off;
+ int idx_sz;
+
+ idx_sz = elem_sz * ((bs->op == BOOT_STATUS_OP_MOVE) ?
+ BOOT_STATUS_MOVE_STATE_COUNT : BOOT_STATUS_SWAP_STATE_COUNT);
+
+ off = ((bs->op == BOOT_STATUS_OP_MOVE) ?
+ 0 : (BOOT_MAX_IMG_SECTORS * BOOT_STATUS_MOVE_STATE_COUNT * elem_sz)) +
+ (bs->idx - BOOT_STATUS_IDX_0) * idx_sz +
+ (bs->state - BOOT_STATUS_STATE_0) * elem_sz;
+
+ return off;
+}
+
+int
+boot_slots_compatible(struct boot_loader_state *state)
+{
+ size_t num_sectors;
+ size_t i;
+
+ num_sectors = boot_img_num_sectors(state, BOOT_PRIMARY_SLOT);
+ if (num_sectors != boot_img_num_sectors(state, BOOT_SECONDARY_SLOT)) {
+ BOOT_LOG_WRN("Cannot upgrade: slots don't have same amount of sectors");
+ return 0;
+ }
+
+ if (num_sectors > BOOT_MAX_IMG_SECTORS) {
+ BOOT_LOG_WRN("Cannot upgrade: more sectors than allowed");
+ return 0;
+ }
+
+ for (i = 0; i < num_sectors; i++) {
+ if (boot_img_sector_size(state, BOOT_PRIMARY_SLOT, i) !=
+ boot_img_sector_size(state, BOOT_SECONDARY_SLOT, i)) {
+ BOOT_LOG_WRN("Cannot upgrade: not same sector layout");
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+#define BOOT_LOG_SWAP_STATE(area, state) \
+ BOOT_LOG_INF("%s: magic=%s, swap_type=0x%x, copy_done=0x%x, " \
+ "image_ok=0x%x", \
+ (area), \
+ ((state)->magic == BOOT_MAGIC_GOOD ? "good" : \
+ (state)->magic == BOOT_MAGIC_UNSET ? "unset" : \
+ "bad"), \
+ (state)->swap_type, \
+ (state)->copy_done, \
+ (state)->image_ok)
+
+int
+swap_status_source(struct boot_loader_state *state)
+{
+ struct boot_swap_state state_primary_slot;
+ int rc;
+ uint8_t source;
+ uint8_t image_index;
+
+#if (BOOT_IMAGE_NUMBER == 1)
+ (void)state;
+#endif
+
+ image_index = BOOT_CURR_IMG(state);
+
+ rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_PRIMARY(image_index),
+ &state_primary_slot);
+ assert(rc == 0);
+
+ BOOT_LOG_SWAP_STATE("Primary image", &state_primary_slot);
+
+ if (state_primary_slot.magic == BOOT_MAGIC_GOOD &&
+ state_primary_slot.copy_done == BOOT_FLAG_UNSET) {
+
+ source = BOOT_STATUS_SOURCE_PRIMARY_SLOT;
+
+ BOOT_LOG_INF("Boot source: primary slot");
+ return source;
+ }
+
+ BOOT_LOG_INF("Boot source: none");
+ return BOOT_STATUS_SOURCE_NONE;
+}
+
+/*
+ * "Moves" the sector located at idx - 1 to idx.
+ */
+static void
+boot_move_sector_up(int idx, uint32_t sz, struct boot_loader_state *state,
+ struct boot_status *bs, const struct flash_area *fap_pri,
+ const struct flash_area *fap_sec)
+{
+ uint32_t new_off;
+ uint32_t old_off;
+ int rc;
+
+ /*
+ * FIXME: assuming sectors of size == sz, a single off variable
+ * would be enough
+ */
+
+ /* Calculate offset from start of image area. */
+ new_off = boot_img_sector_off(state, BOOT_PRIMARY_SLOT, idx);
+ old_off = boot_img_sector_off(state, BOOT_PRIMARY_SLOT, idx - 1);
+
+ if (bs->idx == BOOT_STATUS_IDX_0) {
+ rc = swap_erase_trailer_sectors(state, fap_pri);
+ assert(rc == 0);
+
+ rc = swap_status_init(state, fap_pri, bs);
+ assert(rc == 0);
+
+ rc = swap_erase_trailer_sectors(state, fap_sec);
+ assert(rc == 0);
+ }
+
+ rc = boot_erase_region(fap_pri, new_off, sz);
+ assert(rc == 0);
+
+ rc = boot_copy_region(state, fap_pri, fap_pri, old_off, new_off, sz);
+ assert(rc == 0);
+
+ rc = boot_write_status(state, bs);
+
+ bs->idx++;
+ BOOT_STATUS_ASSERT(rc == 0);
+}
+
+static void
+boot_swap_sectors(int idx, uint32_t sz, struct boot_loader_state *state,
+ struct boot_status *bs, const struct flash_area *fap_pri,
+ const struct flash_area *fap_sec)
+{
+ uint32_t pri_off;
+ uint32_t pri_up_off;
+ uint32_t sec_off;
+ int rc;
+
+ pri_up_off = boot_img_sector_off(state, BOOT_PRIMARY_SLOT, idx);
+ pri_off = boot_img_sector_off(state, BOOT_PRIMARY_SLOT, idx - 1);
+ sec_off = boot_img_sector_off(state, BOOT_SECONDARY_SLOT, idx - 1);
+
+ if (bs->state == BOOT_STATUS_STATE_0) {
+ rc = boot_erase_region(fap_pri, pri_off, sz);
+ assert(rc == 0);
+
+ rc = boot_copy_region(state, fap_sec, fap_pri, sec_off, pri_off, sz);
+ assert(rc == 0);
+
+ rc = boot_write_status(state, bs);
+ bs->state = BOOT_STATUS_STATE_1;
+ BOOT_STATUS_ASSERT(rc == 0);
+ }
+
+ if (bs->state == BOOT_STATUS_STATE_1) {
+ rc = boot_erase_region(fap_sec, sec_off, sz);
+ assert(rc == 0);
+
+ rc = boot_copy_region(state, fap_pri, fap_sec, pri_up_off, sec_off, sz);
+ assert(rc == 0);
+
+ rc = boot_write_status(state, bs);
+ bs->idx++;
+ bs->state = BOOT_STATUS_STATE_0;
+ BOOT_STATUS_ASSERT(rc == 0);
+ }
+}
+
+/*
+ * When starting a revert the swap status exists in the primary slot, and
+ * the status in the secondary slot is erased. To start the swap, the status
+ * area in the primary slot must be re-initialized; if during the small
+ * window of time between re-initializing it and writing the first metadata
+ * a reset happens, the swap process is broken and cannot be resumed.
+ *
+ * This function handles the issue by making the revert look like a permanent
+ * upgrade (by initializing the secondary slot).
+ */
+void
+fixup_revert(const struct boot_loader_state *state, struct boot_status *bs,
+ const struct flash_area *fap_sec, uint8_t sec_id)
+{
+ struct boot_swap_state swap_state;
+ int rc;
+
+#if (BOOT_IMAGE_NUMBER == 1)
+ (void)state;
+#endif
+
+ /* No fixup required */
+ if (bs->swap_type != BOOT_SWAP_TYPE_REVERT ||
+ bs->op != BOOT_STATUS_OP_MOVE ||
+ bs->idx != BOOT_STATUS_IDX_0) {
+ return;
+ }
+
+ rc = boot_read_swap_state_by_id(sec_id, &swap_state);
+ assert(rc == 0);
+
+ BOOT_LOG_SWAP_STATE("Secondary image", &swap_state);
+
+ if (swap_state.magic == BOOT_MAGIC_UNSET) {
+ rc = swap_erase_trailer_sectors(state, fap_sec);
+ assert(rc == 0);
+
+ rc = boot_write_image_ok(fap_sec);
+ assert(rc == 0);
+
+ rc = boot_write_swap_size(fap_sec, bs->swap_size);
+ assert(rc == 0);
+
+#ifdef MCUBOOT_ENC_IMAGES
+ rc = boot_write_enc_key(fap_sec, 0, bs->enckey[0]);
+ assert(rc == 0);
+
+ rc = boot_write_enc_key(fap_sec, 1, bs->enckey[1]);
+ assert(rc == 0);
+#endif
+
+ rc = boot_write_magic(fap_sec);
+ assert(rc == 0);
+ }
+}
+
+void
+swap_run(struct boot_loader_state *state, struct boot_status *bs,
+ uint32_t copy_size)
+{
+ uint32_t sz;
+ uint32_t idx;
+ uint32_t slot_size;
+ uint8_t image_index;
+ const struct flash_area *fap_pri;
+ const struct flash_area *fap_sec;
+ int rc;
+
+ slot_size = 0;
+ g_last_idx = 0;
+
+ //FIXME: assume all sectors of same size and just do math here...
+ sz = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, 0);
+ while (1) {
+ slot_size += sz;
+ /* Skip to next sector because all sectors will be moved up. */
+ g_last_idx++;
+ if (slot_size >= copy_size) {
+ break;
+ }
+ }
+
+ image_index = BOOT_CURR_IMG(state);
+
+ rc = flash_area_open(FLASH_AREA_IMAGE_PRIMARY(image_index), &fap_pri);
+ assert (rc == 0);
+
+ rc = flash_area_open(FLASH_AREA_IMAGE_SECONDARY(image_index), &fap_sec);
+ assert (rc == 0);
+
+ fixup_revert(state, bs, fap_sec, FLASH_AREA_IMAGE_SECONDARY(image_index));
+
+ /* FIXME: assure last sector does not overrun trailer */
+
+ if (bs->op == BOOT_STATUS_OP_MOVE) {
+ idx = g_last_idx;
+ while (idx > 0) {
+ if (idx <= (g_last_idx - bs->idx + 1)) {
+ boot_move_sector_up(idx, sz, state, bs, fap_pri, fap_sec);
+ }
+ idx--;
+ }
+ bs->idx = BOOT_STATUS_IDX_0;
+ }
+
+ bs->op = BOOT_STATUS_OP_SWAP;
+
+ idx = 1;
+ while (idx <= g_last_idx) {
+ if (idx >= bs->idx) {
+ boot_swap_sectors(idx, sz, state, bs, fap_pri, fap_sec);
+ }
+ idx++;
+ }
+
+ flash_area_close(fap_pri);
+ flash_area_close(fap_sec);
+}
+
+#endif
diff --git a/boot/bootutil/src/swap_scratch.c b/boot/bootutil/src/swap_scratch.c
index bf638bf..588bd28 100644
--- a/boot/bootutil/src/swap_scratch.c
+++ b/boot/bootutil/src/swap_scratch.c
@@ -29,7 +29,7 @@
MCUBOOT_LOG_MODULE_DECLARE(mcuboot);
-#if MCUBOOT_SWAP_USING_SCRATCH
+#if !defined(MCUBOOT_SWAP_USING_MOVE)
#if defined(MCUBOOT_VALIDATE_PRIMARY_SLOT)
/*
@@ -175,7 +175,9 @@
size_t num_sectors_secondary;
size_t sz0, sz1;
size_t primary_slot_sz, secondary_slot_sz;
+#ifndef MCUBOOT_OVERWRITE_ONLY
size_t scratch_sz;
+#endif
size_t i, j;
int8_t smaller;
@@ -187,7 +189,9 @@
return 0;
}
+#ifndef MCUBOOT_OVERWRITE_ONLY
scratch_sz = boot_scratch_area_size(state);
+#endif
/*
* The following loop scans all sectors in a linear fashion, assuring that
@@ -228,6 +232,7 @@
smaller = 2;
j++;
}
+#ifndef MCUBOOT_OVERWRITE_ONLY
if (sz0 == sz1) {
primary_slot_sz += sz0;
secondary_slot_sz += sz1;
@@ -240,6 +245,7 @@
}
smaller = sz0 = sz1 = 0;
}
+#endif
}
if ((i != num_sectors_primary) ||
@@ -412,6 +418,7 @@
return BOOT_STATUS_SOURCE_NONE;
}
+#ifndef MCUBOOT_OVERWRITE_ONLY
/**
* Calculates the number of sectors the scratch area can contain. A "last"
* source sector is specified because images are copied backwards in flash
@@ -710,5 +717,6 @@
}
}
+#endif
#endif