From 94610b8519ced286b7e26f1b40945d1100ad69ed Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 01:28:30 +0100 Subject: [PATCH 01/10] bootutil: Make boot_get_first_trailer_sector available for swap-move This routine will now also be useful for the swap-move strategy. The trailer size is removed from the list of parameters as it can be easily obtained using the 'state' parameter and it will be more convenient, for the swap-move strategy, not to have to compute it manually before calling 'boot_get_first_trailer_sector'. Signed-off-by: Thomas Altenbach --- boot/bootutil/src/bootutil_misc.c | 16 +++++++++------- boot/bootutil/src/bootutil_priv.h | 7 +++---- boot/bootutil/src/swap_scratch.c | 5 ++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/boot/bootutil/src/bootutil_misc.c b/boot/bootutil/src/bootutil_misc.c index f125623295..49670bfa02 100644 --- a/boot/bootutil/src/bootutil_misc.c +++ b/boot/bootutil/src/bootutil_misc.c @@ -397,12 +397,13 @@ boot_write_enc_key(const struct flash_area *fap, uint8_t slot, } #endif -#ifdef MCUBOOT_SWAP_USING_SCRATCH +#if defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SWAP_USING_MOVE) size_t -boot_get_first_trailer_sector(struct boot_loader_state *state, size_t slot, size_t trailer_sz) +boot_get_first_trailer_sector(struct boot_loader_state *state, size_t slot) { size_t first_trailer_sector = boot_img_num_sectors(state, slot) - 1; size_t sector_sz = boot_img_sector_size(state, slot, first_trailer_sector); + size_t trailer_sz = boot_trailer_sz(BOOT_WRITE_SZ(state)); size_t trailer_sector_sz = sector_sz; while (trailer_sector_sz < trailer_sz) { @@ -415,20 +416,21 @@ boot_get_first_trailer_sector(struct boot_loader_state *state, size_t slot, size return first_trailer_sector; } +#endif /* MCUBOOT_SWAP_USING_SCRATCH || MCUBOOT_SWAP_USING_MOVE */ +#ifdef MCUBOOT_SWAP_USING_SCRATCH /** * Returns the offset to the end of the first sector of a given slot that holds image trailer data. * * @param state Current bootloader's state. * @param slot The index of the slot to consider. - * @param trailer_sz The size of the trailer, in bytes. * * @return The offset to the end of the first sector of the slot that holds image trailer data. */ static uint32_t -get_first_trailer_sector_end_off(struct boot_loader_state *state, size_t slot, size_t trailer_sz) +get_first_trailer_sector_end_off(struct boot_loader_state *state, size_t slot) { - size_t first_trailer_sector = boot_get_first_trailer_sector(state, slot, trailer_sz); + size_t first_trailer_sector = boot_get_first_trailer_sector(state, slot); return boot_img_sector_off(state, slot, first_trailer_sector) + boot_img_sector_size(state, slot, first_trailer_sector); @@ -455,9 +457,9 @@ uint32_t bootutil_max_image_size(struct boot_loader_state *state, const struct f * trailer containing part of the trailer in the primary and secondary slot. */ size_t trailer_sector_primary_end_off = - get_first_trailer_sector_end_off(state, BOOT_PRIMARY_SLOT, slot_trailer_sz); + get_first_trailer_sector_end_off(state, BOOT_PRIMARY_SLOT); size_t trailer_sector_secondary_end_off = - get_first_trailer_sector_end_off(state, BOOT_SECONDARY_SLOT, slot_trailer_sz); + get_first_trailer_sector_end_off(state, BOOT_SECONDARY_SLOT); size_t trailer_sz_in_first_sector; diff --git a/boot/bootutil/src/bootutil_priv.h b/boot/bootutil/src/bootutil_priv.h index 95f8f1732e..fe6905b7c2 100644 --- a/boot/bootutil/src/bootutil_priv.h +++ b/boot/bootutil/src/bootutil_priv.h @@ -365,19 +365,18 @@ int boot_read_enc_key(const struct flash_area *fap, uint8_t slot, struct boot_status *bs); #endif -#ifdef MCUBOOT_SWAP_USING_SCRATCH +#if defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SWAP_USING_MOVE) /** * Finds the first sector of a given slot that holds image trailer data. * * @param state Current bootloader's state. * @param slot The index of the slot to consider. - * @param trailer_sz The size of the trailer, in bytes. * * @return The index of the first sector of the slot that holds image trailer data. */ size_t -boot_get_first_trailer_sector(struct boot_loader_state *state, size_t slot, size_t trailer_sz); -#endif +boot_get_first_trailer_sector(struct boot_loader_state *state, size_t slot); +#endif /* MCUBOOT_SWAP_USING_SCRATCH || MCUBOOT_SWAP_USING_MOVE */ /** * Checks that a buffer is erased according to what the erase value for the diff --git a/boot/bootutil/src/swap_scratch.c b/boot/bootutil/src/swap_scratch.c index af4be88a68..d0058a551a 100644 --- a/boot/bootutil/src/swap_scratch.c +++ b/boot/bootutil/src/swap_scratch.c @@ -592,8 +592,7 @@ boot_swap_sectors(int idx, uint32_t sz, struct boot_loader_state *state, * NOTE: `use_scratch` is a temporary flag (never written to flash) which * controls if special handling is needed (swapping the first trailer sector). */ - first_trailer_sector_primary = - boot_get_first_trailer_sector(state, BOOT_PRIMARY_SLOT, trailer_sz); + first_trailer_sector_primary = boot_get_first_trailer_sector(state, BOOT_PRIMARY_SLOT); /* Check if the currently swapped sector(s) contain the trailer or part of it */ if ((img_off + sz) > @@ -673,7 +672,7 @@ boot_swap_sectors(int idx, uint32_t sz, struct boot_loader_state *state, * sector(s) containing the beginning of the trailer won't be erased again. */ size_t trailer_sector_secondary = - boot_get_first_trailer_sector(state, BOOT_SECONDARY_SLOT, trailer_sz); + boot_get_first_trailer_sector(state, BOOT_SECONDARY_SLOT); uint32_t trailer_sector_offset = boot_img_sector_off(state, BOOT_SECONDARY_SLOT, trailer_sector_secondary); From aba5f614dcd2a8d15fc4f210764e9a1d86bbcefd Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 01:49:14 +0100 Subject: [PATCH 02/10] bootutil: swap-move: Allow unaligned trailer sector during move The swap-move strategy is currently assuming the trailer is stored in dedicated sector, meaning the size of the area allocated to the trailer must be a multiple of the sector size. This commit relaxes this assumption when moving sectors up in the primary slot by allowing the last sector containing firmware data to also hold part of the trailer (or the whole trailer if it is small enough). Signed-off-by: Thomas Altenbach --- boot/bootutil/src/swap_move.c | 42 ++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/boot/bootutil/src/swap_move.c b/boot/bootutil/src/swap_move.c index 42ba7a6679..0788073609 100644 --- a/boot/bootutil/src/swap_move.c +++ b/boot/bootutil/src/swap_move.c @@ -379,13 +379,15 @@ swap_status_source(struct boot_loader_state *state) * "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, +boot_move_sector_up(size_t 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; + uint32_t copy_sz; int rc; + bool sector_erased_with_trailer; /* * FIXME: assuming sectors of size == sz, a single off variable @@ -396,14 +398,32 @@ boot_move_sector_up(int idx, uint32_t sz, struct boot_loader_state *state, new_off = boot_img_sector_off(state, BOOT_PRIMARY_SLOT, idx); old_off = boot_img_sector_off(state, BOOT_PRIMARY_SLOT, idx - 1); + copy_sz = sz; + sector_erased_with_trailer = false; + if (bs->idx == BOOT_STATUS_IDX_0) { - if (bs->source != BOOT_STATUS_SOURCE_PRIMARY_SLOT) { - /* Remove data and prepare for write on devices requiring erase */ - rc = swap_scramble_trailer_sectors(state, fap_pri); - assert(rc == 0); + rc = swap_scramble_trailer_sectors(state, fap_pri); + assert(rc == 0); + + rc = swap_status_init(state, fap_pri, bs); + assert(rc == 0); - rc = swap_status_init(state, fap_pri, bs); - assert(rc == 0); + /* The first sector to be moved is the last sector containing part of the firmware image. If + * the trailer size is not a multiple of the sector size, the destination sector will + * contain both firmware and trailer data. In that case: + * - Only the firmware data must be copied to the destination sector to avoid overwriting + * the trailer data. + * - The destination sector has already been erased with the trailer. + */ + size_t first_trailer_idx = boot_get_first_trailer_sector(state, BOOT_PRIMARY_SLOT); + + if (idx == first_trailer_idx) { + /* Swap-move => constant sector size, so 'sz' is the size of a sector and 'swap_size % + * sz' gives the number of bytes used by the largest firmware image in the last sector + * to be moved. + */ + copy_sz = bs->swap_size % sz; + sector_erased_with_trailer = true; } /* Remove status from secondary slot trailer, in case of device with @@ -413,10 +433,12 @@ boot_move_sector_up(int idx, uint32_t sz, struct boot_loader_state *state, assert(rc == 0); } - rc = boot_erase_region(fap_pri, new_off, sz); - assert(rc == 0); + if (!sector_erased_with_trailer) { + 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); + rc = boot_copy_region(state, fap_pri, fap_pri, old_off, new_off, copy_sz); assert(rc == 0); rc = boot_write_status(state, bs); From b428292a1600bf2d7945fe0dd1d528ab17c8122f Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 18:36:34 +0200 Subject: [PATCH 03/10] bootutil: swap-move: Erase secondary trailer during swap When using the swap-move strategy, the secondary trailer was erased just after the primary trailer, when moving the sectors up in the primary slot. This was working because it was assumed the trailer is stored in dedicated sectors, so no sector could contain both trailer and firmware data. However, if this assumption is relaxed, it means it is no more possible to erase the secondary trailer before the last sector holding firmware data has been copied to the primary slot, since that sector might also contain trailer data. So the erasure of the secondary trailer has to occur at the time the last sector containing firmware data is swapped. Signed-off-by: Thomas Altenbach --- boot/bootutil/src/swap_move.c | 65 ++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/boot/bootutil/src/swap_move.c b/boot/bootutil/src/swap_move.c index 0788073609..e54cc0793e 100644 --- a/boot/bootutil/src/swap_move.c +++ b/boot/bootutil/src/swap_move.c @@ -362,9 +362,14 @@ swap_status_source(struct boot_loader_state *state) BOOT_LOG_SWAP_STATE("Secondary image", &state_secondary_slot); if (state_primary_slot.magic == BOOT_MAGIC_GOOD && - state_primary_slot.copy_done == BOOT_FLAG_UNSET && - state_secondary_slot.magic != BOOT_MAGIC_GOOD) { - + state_primary_slot.copy_done == BOOT_FLAG_UNSET) { + /* In this case, either: + * - A swap operation was interrupted and can be resumed from the status stored in the + * primary slot's trailer. + * - No swap was ever made and the initial firmware image has been written with a MCUboot + * trailer. In this case, the status in the primary slot's trailer is empty and there is + * no harm in loading it. + */ source = BOOT_STATUS_SOURCE_PRIMARY_SLOT; BOOT_LOG_INF("Boot source: primary slot"); @@ -380,8 +385,7 @@ swap_status_source(struct boot_loader_state *state) */ static void boot_move_sector_up(size_t 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) + struct boot_status *bs, const struct flash_area *fap_pri) { uint32_t new_off; uint32_t old_off; @@ -425,12 +429,6 @@ boot_move_sector_up(size_t idx, uint32_t sz, struct boot_loader_state *state, copy_sz = bs->swap_size % sz; sector_erased_with_trailer = true; } - - /* Remove status from secondary slot trailer, in case of device with - * erase requirement this will also prepare traier for write. - */ - rc = swap_scramble_trailer_sectors(state, fap_sec); - assert(rc == 0); } if (!sector_erased_with_trailer) { @@ -448,7 +446,7 @@ boot_move_sector_up(size_t idx, uint32_t sz, struct boot_loader_state *state, } static void -boot_swap_sectors(int idx, uint32_t sz, struct boot_loader_state *state, +boot_swap_sectors(size_t idx, size_t last_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) { @@ -474,10 +472,43 @@ boot_swap_sectors(int idx, uint32_t sz, struct boot_loader_state *state, } if (bs->state == BOOT_STATUS_STATE_1) { - rc = boot_erase_region(fap_sec, sec_off, sz); - assert(rc == 0); + bool sector_erased_with_trailer = false; + uint32_t copy_sz = sz; + + if (idx == last_idx) { + rc = swap_scramble_trailer_sectors(state, fap_sec); + assert(rc == 0); + + size_t first_trailer_sector_pri = + boot_get_first_trailer_sector(state, BOOT_PRIMARY_SLOT); + size_t first_trailer_sector_sec = + boot_get_first_trailer_sector(state, BOOT_SECONDARY_SLOT); + + if (first_trailer_sector_sec == idx - 1) { + /* The destination sector was containing part of the trailer and has therefore + * already been erased. + */ + sector_erased_with_trailer = true; + } + + if (first_trailer_sector_pri == idx) { + /* The source sector contains both firmware and trailer data, so only the firmware + * data must be copied to the destination sector. + * + * Swap-move => constant sector size, so 'sz' is the size of a sector and 'swap_size + * % sz' gives the number of bytes used by the largest firmware image in the last + * sector to be moved. + */ + copy_sz = bs->swap_size % sz; + } + } + + if (!sector_erased_with_trailer) { + 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); + rc = boot_copy_region(state, fap_pri, fap_sec, pri_up_off, sec_off, copy_sz); assert(rc == 0); rc = boot_write_status(state, bs); @@ -592,7 +623,7 @@ swap_run(struct boot_loader_state *state, struct boot_status *bs, idx = last_idx; while (idx > 0) { if (idx <= (last_idx - bs->idx + 1)) { - boot_move_sector_up(idx, sector_sz, state, bs, fap_pri, fap_sec); + boot_move_sector_up(idx, sector_sz, state, bs, fap_pri); } idx--; } @@ -604,7 +635,7 @@ swap_run(struct boot_loader_state *state, struct boot_status *bs, idx = 1; while (idx <= last_idx) { if (idx >= bs->idx) { - boot_swap_sectors(idx, sector_sz, state, bs, fap_pri, fap_sec); + boot_swap_sectors(idx, last_idx, sector_sz, state, bs, fap_pri, fap_sec); } idx++; } From 88e80a20127b8f06fe81ec80faa820e78467837d Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 19:21:33 +0200 Subject: [PATCH 04/10] bootutil: swap-move: Avoid rewriting the secondary trailer on revert When using the swap-move strategy, at the very beginning of the revert process, the secondary trailer was rewritten to make the revert look like a permanent upgrade in case an unfortunate reset occurs when rewriting the primary trailer. This was possible because the assumption was that no sector contained both part of the firmware and part of the trailer. To relax this assumption, it is necessary to avoid having to rewrite the secondary trailer at the start of the revert process, since that could also erase firmware data. The solution chosen is to write, during the upgrade process, a "fallback trailer" at the end of the last sector that is not containing part of the slot's trailer. The fallback trailer only contains the magic field. Depending on the size of the trailer, the maximum firmware image size has to be reduced a bit (by at most 8 bytes) to ensure the fallback trailer can be written. During a revert, if a reboot occurs while the primary trailer is rewritten, the presence of the fallback trailer will make possible to determine that a revert was in progress and has to be resumed. Signed-off-by: Thomas Altenbach --- boot/bootutil/src/bootutil_misc.h | 2 +- boot/bootutil/src/bootutil_priv.h | 1 + boot/bootutil/src/bootutil_public.c | 13 +- boot/bootutil/src/swap_move.c | 241 +++++++++++++++++++++------- 4 files changed, 198 insertions(+), 59 deletions(-) diff --git a/boot/bootutil/src/bootutil_misc.h b/boot/bootutil/src/bootutil_misc.h index 1fcaabdfc1..0202877d89 100644 --- a/boot/bootutil/src/bootutil_misc.h +++ b/boot/bootutil/src/bootutil_misc.h @@ -16,7 +16,7 @@ #include "bootutil/enc_key.h" #endif -static int +static inline int boot_magic_decode(const uint8_t *magic) { if (memcmp(magic, BOOT_IMG_MAGIC, BOOT_MAGIC_SZ) == 0) { diff --git a/boot/bootutil/src/bootutil_priv.h b/boot/bootutil/src/bootutil_priv.h index fe6905b7c2..ca4e5bb36d 100644 --- a/boot/bootutil/src/bootutil_priv.h +++ b/boot/bootutil/src/bootutil_priv.h @@ -317,6 +317,7 @@ uint32_t boot_status_off(const struct flash_area *fap); int boot_read_swap_state(const struct flash_area *fap, struct boot_swap_state *state); int boot_write_magic(const struct flash_area *fap); +int boot_write_magic_at_off(const struct flash_area *fap, uint32_t off); int boot_write_status(const struct boot_loader_state *state, struct boot_status *bs); int boot_write_copy_done(const struct flash_area *fap); int boot_write_image_ok(const struct flash_area *fap); diff --git a/boot/bootutil/src/bootutil_public.c b/boot/bootutil/src/bootutil_public.c index 1021188644..cdade81e54 100644 --- a/boot/bootutil/src/bootutil_public.c +++ b/boot/bootutil/src/bootutil_public.c @@ -302,14 +302,23 @@ boot_read_swap_state_by_id(int flash_area_id, struct boot_swap_state *state) int boot_write_magic(const struct flash_area *fap) { + int rc = 0; uint32_t off; + + off = boot_magic_off(fap); + rc = boot_write_magic_at_off(fap, off); + + return rc; +} + +int +boot_write_magic_at_off(const struct flash_area *fap, uint32_t off) +{ uint32_t pad_off; int rc; uint8_t magic[BOOT_MAGIC_ALIGN_SIZE]; uint8_t erased_val; - off = boot_magic_off(fap); - /* image_trailer structure was modified with additional padding such that * the pad+magic ends up in a flash minimum write region. The address * returned by boot_magic_off() is the start of magic which is not the diff --git a/boot/bootutil/src/swap_move.c b/boot/bootutil/src/swap_move.c index e54cc0793e..b49d627428 100644 --- a/boot/bootutil/src/swap_move.c +++ b/boot/bootutil/src/swap_move.c @@ -23,6 +23,7 @@ #include #include "bootutil/bootutil.h" #include "bootutil_priv.h" +#include "bootutil_misc.h" #include "swap_priv.h" #include "bootutil/bootutil_log.h" @@ -68,6 +69,123 @@ find_last_idx(struct boot_loader_state *state, uint32_t swap_size) return last_idx; } +/** + * Returns the index of the sector containing the fallback trailer in the primary slot. + * + * The fallback trailer is needed during a revert process, in case a reboot occurs between the time + * the primary slot's trailer is erased and the time it is fully rewritten. When this happens, the + * bootloader will be able to detect that a revert process was in progress by the looking at the + * fallback trailer. The fallback trailer is located in the last sector of the primary slot that + * doesn't contain part of the slot's trailer, so in other words, in the sector right before the + * first sector holding part of the slot's trailer. It is written at the end of the upgrade process. + * + * Example: + * PRIMARY + * | ... | + * +-----------------------+ + * | Firmware (3968 bytes) | Sector N-2 + * | Padding (128 bytes) | <---- Fallback trailer stored here + * +-----------------------+ + * | Padding (3968 bytes) | Sector N-1 + * | Trailer (128 bytes) | + * +-----------------------+ + * | Trailer (4096 bytes) | Sector N + * | . | + * +-----------------------+ + * + * @param state Current bootloader's state. + * + * @return The index of the sector containg the fallback trailer in the primary slot. + */ +static size_t +get_fallback_trailer_sector(struct boot_loader_state *state) +{ + size_t first_trailer_sector = boot_get_first_trailer_sector(state, BOOT_PRIMARY_SLOT); + + return first_trailer_sector - 1; +} + +/** + * Returns the offset of the fallback trailer in the primary slot. + * + * The fallback trailer is composed only of the magic field. When computing the maximum image size, + * it is ensured at least BOOT_MAGIC_ALIGN_SIZE bytes are available at the end of the firmware image + * to write the fallback trailer. + * + * @param state Current bootloader's state. + * + * @return The offset of the fallback trailer in the primary slot. + */ +static uint32_t +get_fallback_trailer_off(struct boot_loader_state *state) +{ + size_t fallback_trailer_sector = get_fallback_trailer_sector(state); + size_t end_off = boot_img_sector_off(state, BOOT_PRIMARY_SLOT, fallback_trailer_sector + 1); + + return end_off - BOOT_MAGIC_ALIGN_SIZE; +} + +/** + * Writes the fallback trailer in the primary slot. + * + * @param state Current bootloader's state. + * @param need_erase True if the sector has to be erased before writing, false otherwise. + */ +static void +write_fallback_trailer(struct boot_loader_state *state, bool need_erase) +{ + int rc; + uint32_t fallback_trailer_off; + const struct flash_area *fap_pri; + + fap_pri = BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT); + assert(fap_pri != NULL); + + if (need_erase) { + size_t fallback_trailer_sector = get_fallback_trailer_sector(state); + uint32_t sector_off = boot_img_sector_off(state, BOOT_PRIMARY_SLOT, fallback_trailer_sector); + uint32_t sector_sz = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, 0); + + rc = boot_erase_region(fap_pri, sector_off, sector_sz); + assert(rc == 0); + } + + fallback_trailer_off = get_fallback_trailer_off(state); + + rc = boot_write_magic_at_off(fap_pri, fallback_trailer_off); + assert(rc == 0); +} + +/** + * Reads the magic field of the fallback trailer. + * + * @param state Current bootloader's state. + * + * @return BOOT_MAGIC_GOOD if the magic is valid, BOOT_MAGIC_BAD otherwise. + */ +static int +read_fallback_trailer_magic(struct boot_loader_state *state) +{ + int rc; + const struct flash_area *fap_pri; + uint32_t fallback_trailer_off; + uint32_t magic_off; + uint8_t magic[BOOT_MAGIC_SZ]; + + fap_pri = BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT); + assert(fap_pri != NULL); + + fallback_trailer_off = get_fallback_trailer_off(state); + + /* The magic has to be read without including the padding */ + magic_off = fallback_trailer_off + BOOT_MAGIC_ALIGN_SIZE - BOOT_MAGIC_SZ; + + rc = flash_area_read(fap_pri, magic_off, magic, BOOT_MAGIC_SZ); + assert(rc == 0); + + return boot_magic_decode(magic); +} + int boot_read_image_header(struct boot_loader_state *state, int slot, struct image_header *out_hdr, struct boot_status *bs) @@ -337,6 +455,8 @@ boot_slots_compatible(struct boot_loader_state *state) int swap_status_source(struct boot_loader_state *state) { + const struct flash_area *fap_pri; + const struct flash_area *fap_sec; struct boot_swap_state state_primary_slot; struct boot_swap_state state_secondary_slot; int rc; @@ -349,14 +469,18 @@ swap_status_source(struct boot_loader_state *state) image_index = BOOT_CURR_IMG(state); - rc = boot_read_swap_state(state->imgs[image_index][BOOT_PRIMARY_SLOT].area, - &state_primary_slot); + fap_pri = BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT); + assert(fap_pri != NULL); + + fap_sec = BOOT_IMG_AREA(state, BOOT_SECONDARY_SLOT); + assert(fap_sec != NULL); + + rc = boot_read_swap_state(fap_pri, &state_primary_slot); assert(rc == 0); BOOT_LOG_SWAP_STATE("Primary image", &state_primary_slot); - rc = boot_read_swap_state(state->imgs[image_index][BOOT_SECONDARY_SLOT].area, - &state_secondary_slot); + rc = boot_read_swap_state(fap_sec, &state_secondary_slot); assert(rc == 0); BOOT_LOG_SWAP_STATE("Secondary image", &state_secondary_slot); @@ -376,6 +500,31 @@ swap_status_source(struct boot_loader_state *state) return source; } + /* If both primary and secondary trailers are absent and the fallback trailer is present, it + * means a revert process was interrupted in the middle of rewriting of the primary trailer. + * This can only happen at the very beggining of the revert process. + */ + if (state_primary_slot.magic != BOOT_MAGIC_GOOD && + state_secondary_slot.magic != BOOT_MAGIC_GOOD && + read_fallback_trailer_magic(state) == BOOT_MAGIC_GOOD) { + /* In that case, the primary trailer is rewritten with just enough data to allow to the + * revert process to be resumed. Only the swap-type and the magic are needed since we are at + * the beginning of the revert process so the process has to be restarted from scratch, + * which will rewrite the primary trailer with all required data (swap size, encryption + * keys, ...). + */ + rc = swap_scramble_trailer_sectors(state, fap_pri); + assert(rc == 0); + + rc = boot_write_swap_info(fap_pri, BOOT_SWAP_TYPE_REVERT, image_index); + assert(rc == 0); + + rc = boot_write_magic(fap_pri); + assert(rc == 0); + + return BOOT_STATUS_SOURCE_PRIMARY_SLOT; + } + BOOT_LOG_INF("Boot source: none"); return BOOT_STATUS_SOURCE_NONE; } @@ -460,10 +609,28 @@ boot_swap_sectors(size_t idx, size_t last_idx, uint32_t sz, struct boot_loader_s sec_off = boot_img_sector_off(state, BOOT_SECONDARY_SLOT, idx - 1); if (bs->state == BOOT_STATUS_STATE_0) { + uint32_t copy_sz = sz; + size_t fallback_trailer_sector = get_fallback_trailer_sector(state); + 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); + /* The last sector containing part of the firmware image is about to be written to the + * primary slot. If we are not reverting, a fallback trailer must be written in case a + * revert is performed later and is interrupted. The fallback trailer is located in the last + * sector not containing part of the slot's trailer. This sector can be the one being + * written at this step, so in that case write the fallback trailer now. Otherwise, it will + * be written at the very end of the swap. + */ + if (fallback_trailer_sector == idx - 1 && bs->swap_type != BOOT_SWAP_TYPE_REVERT) { + /* The sector has already been erased, no need to erase twice */ + write_fallback_trailer(state, false); + + /* Adjust the copy size to ensure the fallback trailer won't be rewritten. */ + copy_sz = bs->swap_size % sz; + } + + rc = boot_copy_region(state, fap_sec, fap_pri, sec_off, pri_off, copy_sz); assert(rc == 0); rc = boot_write_status(state, bs); @@ -518,55 +685,6 @@ boot_swap_sectors(size_t idx, size_t last_idx, uint32_t sz, struct boot_loader_s } } -/* - * 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) -{ - 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(fap_sec, &swap_state); - assert(rc == 0); - - BOOT_LOG_SWAP_STATE("Secondary image", &swap_state); - - if (swap_state.magic == BOOT_MAGIC_UNSET) { - /* Remove trailer and prepare area for write on devices requiring erase */ - rc = swap_scramble_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); - - 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) @@ -577,6 +695,7 @@ swap_run(struct boot_loader_state *state, struct boot_status *bs, uint32_t trailer_sz; uint32_t first_trailer_idx; uint32_t last_idx; + size_t fallback_trailer_sector; const struct flash_area *fap_pri; const struct flash_area *fap_sec; @@ -617,8 +736,6 @@ swap_run(struct boot_loader_state *state, struct boot_status *bs, fap_sec = BOOT_IMG_AREA(state, BOOT_SECONDARY_SLOT); assert(fap_sec != NULL); - fixup_revert(state, bs, fap_sec); - if (bs->op == BOOT_STATUS_OP_MOVE) { idx = last_idx; while (idx > 0) { @@ -639,6 +756,18 @@ swap_run(struct boot_loader_state *state, struct boot_status *bs, } idx++; } + + /* The swap is done, if we are upgrading and the fallback trailer has not been written during + * the swap, write it now. This is necessary if the fallback trailer sector is not part of the + * sectors that have been swapped, either because the images are not large enough to include + * that sector, or because the trailer is sector-aligned and the fallback trailer is therefore + * stored at the end of the padding sector. + */ + fallback_trailer_sector = get_fallback_trailer_sector(state); + + if (last_idx <= fallback_trailer_sector && bs->swap_type != BOOT_SWAP_TYPE_REVERT) { + write_fallback_trailer(state, true); + } } int app_max_size(struct boot_loader_state *state) From 5703b6ef85aa2becf4b42ef71e6281cfa141b29b Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 18:50:31 +0200 Subject: [PATCH 05/10] bootutil: swap-move: Remove check of the image size in swap_run This check is not useful because it is already done during the validation of the image and was also making sure there was no firmware data in the first trailer sector, which is no more needed. Signed-off-by: Thomas Altenbach --- boot/bootutil/src/swap_move.c | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/boot/bootutil/src/swap_move.c b/boot/bootutil/src/swap_move.c index b49d627428..d475513ac9 100644 --- a/boot/bootutil/src/swap_move.c +++ b/boot/bootutil/src/swap_move.c @@ -689,11 +689,8 @@ void swap_run(struct boot_loader_state *state, struct boot_status *bs, uint32_t copy_size) { - uint32_t sz; uint32_t sector_sz; uint32_t idx; - uint32_t trailer_sz; - uint32_t first_trailer_idx; uint32_t last_idx; size_t fallback_trailer_sector; const struct flash_area *fap_pri; @@ -704,32 +701,6 @@ swap_run(struct boot_loader_state *state, struct boot_status *bs, last_idx = find_last_idx(state, copy_size); sector_sz = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, 0); - /* - * When starting a new swap upgrade, check that there is enough space. - */ - if (boot_status_is_reset(bs)) { - sz = 0; - trailer_sz = boot_trailer_sz(BOOT_WRITE_SZ(state)); - first_trailer_idx = boot_img_num_sectors(state, BOOT_PRIMARY_SLOT) - 1; - - while (1) { - sz += sector_sz; - if (sz >= trailer_sz) { - break; - } - first_trailer_idx--; - } - - if (last_idx >= first_trailer_idx) { - BOOT_LOG_WRN("Not enough free space to run swap upgrade"); - BOOT_LOG_WRN("required %d bytes but only %d are available", - (last_idx + 1) * sector_sz, - first_trailer_idx * sector_sz); - bs->swap_type = BOOT_SWAP_TYPE_NONE; - return; - } - } - fap_pri = BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT); assert(fap_pri != NULL); From 0e76aeb240e2da1d5d97baed8af1dc2857019520 Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 18:59:58 +0200 Subject: [PATCH 06/10] bootutil: swap-move: Update maximum size to allow unaligned trailers This commit updates the computation of the maximum firmware image size for the swap-move strategy to allow the first sector containing part of the trailer to also hold firmware data. This means it is no more necessary to allocate a sector-aligned area for the trailer when using swap-move, the area to allocate is now simply equal to the maximum trailer size. The space required for writing the fallback trailer is also taken into account. Signed-off-by: Thomas Altenbach --- boot/bootutil/src/bootutil_misc.c | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/boot/bootutil/src/bootutil_misc.c b/boot/bootutil/src/bootutil_misc.c index 49670bfa02..21fef45809 100644 --- a/boot/bootutil/src/bootutil_misc.c +++ b/boot/bootutil/src/bootutil_misc.c @@ -477,7 +477,31 @@ uint32_t bootutil_max_image_size(struct boot_loader_state *state, const struct f } return slot_trailer_off - trailer_padding; -#elif defined(MCUBOOT_SWAP_USING_MOVE) || defined(MCUBOOT_SWAP_USING_OFFSET) +#elif defined(MCUBOOT_SWAP_USING_MOVE) + (void) fap; + + const struct flash_area *fap_pri = BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT); + assert(fap_pri != NULL); + + /* Swap-move needs to reserve in the primary slot the size of one full sector plus some space at + * the end of the slot to write the trailer. Also, it must be ensured at least + * BOOT_MAGIC_ALIGN_SIZE bytes are available in the last sector that is not containing part of + * the slot's trailer to be able to write the fallback trailer. That is always the case unless + * the trailer is only a few bytes larger than the size of a sector, i.e. unless: + * 0 < trailer_sz % sector_sz < BOOT_MAGIC_ALIGN_SIZE + */ + size_t trailer_sz = boot_trailer_sz(BOOT_WRITE_SZ(state)); + size_t sector_sz = boot_img_sector_size(state, BOOT_PRIMARY_SLOT, 0); + size_t padding = sector_sz; + + size_t trailer_sz_in_padding_sector = trailer_sz % sector_sz; + + if (trailer_sz_in_padding_sector > 0 && trailer_sz_in_padding_sector < BOOT_MAGIC_ALIGN_SIZE) { + padding += BOOT_MAGIC_ALIGN_SIZE - trailer_sz_in_padding_sector; + } + + return flash_area_get_size(fap_pri) - trailer_sz - padding; +#elif defined(MCUBOOT_SWAP_USING_OFFSET) (void) state; struct flash_sector sector; From 7309c0f02cc8ea349b41d5b5cb619e3280703dbe Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 19:43:56 +0200 Subject: [PATCH 07/10] sim: Fix largest image computation for swap-move and swap-offset For swap-move and swap-offset strategies, the size of the images used for running the tests was hardcoded and was smaller than the maximum possible size. This was done this way because the logic used to compute the maximum image size was not taking into account the padding that is needed when using those strategies. This commit fixes the issue, making now possible to run the tests with images having the maximum possible size. Signed-off-by: Thomas Altenbach --- sim/src/image.rs | 140 +++++++++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 59 deletions(-) diff --git a/sim/src/image.rs b/sim/src/image.rs index dcea8c5772..39056ebf46 100644 --- a/sim/src/image.rs +++ b/sim/src/image.rs @@ -234,21 +234,21 @@ impl ImagesBuilder { let (primaries,upgrades) = if img_manipulation == ImageManipulation::CorruptHigherVersionImage && !higher_version_corrupted { higher_version_corrupted = true; - let prim = install_image(&mut flash, &self.areadesc, &slots[0], - maximal(42784), &ram, &*dep, ImageManipulation::None, Some(0), false); + let prim = install_image(&mut flash, &self.areadesc, &slots, 0, + maximal(42784), &ram, &*dep, ImageManipulation::None, Some(0)); let upgr = match deps.depends[image_num] { DepType::NoUpgrade => install_no_image(), - _ => install_image(&mut flash, &self.areadesc, &slots[1], - maximal(46928), &ram, &*dep, ImageManipulation::BadSignature, Some(0), true) + _ => install_image(&mut flash, &self.areadesc, &slots, 1, + maximal(46928), &ram, &*dep, ImageManipulation::BadSignature, Some(0)) }; (prim, upgr) } else { - let prim = install_image(&mut flash, &self.areadesc, &slots[0], - maximal(42784), &ram, &*dep, img_manipulation, Some(0), false); + let prim = install_image(&mut flash, &self.areadesc, &slots, 0, + maximal(42784), &ram, &*dep, img_manipulation, Some(0)); let upgr = match deps.depends[image_num] { DepType::NoUpgrade => install_no_image(), - _ => install_image(&mut flash, &self.areadesc, &slots[1], - maximal(46928), &ram, &*dep, img_manipulation, Some(0), true) + _ => install_image(&mut flash, &self.areadesc, &slots, 1, + maximal(46928), &ram, &*dep, img_manipulation, Some(0)) }; (prim, upgr) }; @@ -298,10 +298,10 @@ impl ImagesBuilder { let ram = self.ram.clone(); // TODO: Avoid this clone. let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| { let dep = BoringDep::new(image_num, &NO_DEPS); - let primaries = install_image(&mut bad_flash, &self.areadesc, &slots[0], - maximal(32784), &ram, &dep, ImageManipulation::None, Some(0), false); - let upgrades = install_image(&mut bad_flash, &self.areadesc, &slots[1], - maximal(41928), &ram, &dep, ImageManipulation::BadSignature, Some(0), true); + let primaries = install_image(&mut bad_flash, &self.areadesc, &slots, 0, + maximal(32784), &ram, &dep, ImageManipulation::None, Some(0)); + let upgrades = install_image(&mut bad_flash, &self.areadesc, &slots, 1, + maximal(41928), &ram, &dep, ImageManipulation::BadSignature, Some(0)); OneImage { slots, primaries, @@ -321,10 +321,10 @@ impl ImagesBuilder { let ram = self.ram.clone(); // TODO: Avoid this clone. let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| { let dep = BoringDep::new(image_num, &NO_DEPS); - let primaries = install_image(&mut bad_flash, &self.areadesc, &slots[0], - maximal(32784), &ram, &dep, ImageManipulation::None, Some(0), false); - let upgrades = install_image(&mut bad_flash, &self.areadesc, &slots[1], - ImageSize::Oversized, &ram, &dep, ImageManipulation::None, Some(0), true); + let primaries = install_image(&mut bad_flash, &self.areadesc, &slots, 0, + maximal(32784), &ram, &dep, ImageManipulation::None, Some(0)); + let upgrades = install_image(&mut bad_flash, &self.areadesc, &slots, 1, + ImageSize::Oversized, &ram, &dep, ImageManipulation::None, Some(0)); OneImage { slots, primaries, @@ -344,8 +344,8 @@ impl ImagesBuilder { let ram = self.ram.clone(); // TODO: Avoid this clone. let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| { let dep = BoringDep::new(image_num, &NO_DEPS); - let primaries = install_image(&mut flash, &self.areadesc, &slots[0], - maximal(32784), &ram, &dep,ImageManipulation::None, Some(0), false); + let primaries = install_image(&mut flash, &self.areadesc, &slots, 0, + maximal(32784), &ram, &dep,ImageManipulation::None, Some(0)); let upgrades = install_no_image(); OneImage { slots, @@ -367,8 +367,8 @@ impl ImagesBuilder { let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| { let dep = BoringDep::new(image_num, &NO_DEPS); let primaries = install_no_image(); - let upgrades = install_image(&mut flash, &self.areadesc, &slots[1], - maximal(32784), &ram, &dep, ImageManipulation::None, Some(0), true); + let upgrades = install_image(&mut flash, &self.areadesc, &slots, 1, + maximal(32784), &ram, &dep, ImageManipulation::None, Some(0)); OneImage { slots, primaries, @@ -389,8 +389,8 @@ impl ImagesBuilder { let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| { let dep = BoringDep::new(image_num, &NO_DEPS); let primaries = install_no_image(); - let upgrades = install_image(&mut flash, &self.areadesc, &slots[1], - ImageSize::Oversized, &ram, &dep, ImageManipulation::None, Some(0), true); + let upgrades = install_image(&mut flash, &self.areadesc, &slots, 1, + ImageSize::Oversized, &ram, &dep, ImageManipulation::None, Some(0)); OneImage { slots, primaries, @@ -411,10 +411,10 @@ impl ImagesBuilder { let ram = self.ram.clone(); // TODO: Avoid this clone. let images = self.slots.into_iter().enumerate().map(|(image_num, slots)| { let dep = BoringDep::new(image_num, &NO_DEPS); - let primaries = install_image(&mut flash, &self.areadesc, &slots[0], - maximal(32784), &ram, &dep, ImageManipulation::None, security_cnt, false); - let upgrades = install_image(&mut flash, &self.areadesc, &slots[1], - maximal(41928), &ram, &dep, ImageManipulation::None, security_cnt.map(|v| v + 1), true); + let primaries = install_image(&mut flash, &self.areadesc, &slots, 0, + maximal(32784), &ram, &dep, ImageManipulation::None, security_cnt); + let upgrades = install_image(&mut flash, &self.areadesc, &slots, 1, + maximal(41928), &ram, &dep, ImageManipulation::None, security_cnt.map(|v| v + 1)); OneImage { slots, primaries, @@ -1819,19 +1819,61 @@ fn image_largest_trailer(dev: &dyn Flash, areadesc: &AreaDesc, slot: &SlotInfo) trailer } +// Computes the padding required in the primary or secondary slot to be able to perform an upgrade. +// This is needed only for the swap-move and swap-offset upgrade strategies. +fn required_slot_padding(dev: &dyn Flash, trailer_size: usize) -> usize { + let mut required_padding = 0; + + if Caps::SwapUsingMove.present() || Caps::SwapUsingOffset.present() { + // Assumes equally-sized sectors + let sector_size = dev.sector_iter().next().unwrap().size; + + required_padding = sector_size; + + // For swap-move, ensure enough space is available for the fallback trailer + if Caps::SwapUsingMove.present() { + let magic_size = c::boot_magic_sz(); + + if trailer_size % sector_size > 0 && trailer_size % sector_size < magic_size { + required_padding += magic_size - trailer_size % sector_size; + } + } + }; + + required_padding +} + +// Computes the largest possible firmware image size, not including the header and TLV area. +fn compute_largest_image_size(dev: &dyn Flash, areadesc: &AreaDesc, slots: &[SlotInfo], + slot_ind: usize, hdr_size: usize, tlv: &dyn ManifestGen) -> usize { + let slot_len = if Caps::SwapUsingOffset.present() { + slots[1].len + } else { + slots[0].len + }; + + let trailer = image_largest_trailer(dev, areadesc, &slots[slot_ind]); + let padding = required_slot_padding(dev, trailer); + let tlv_len = tlv.estimate_size(); + info!("slot: 0x{:x}, HDR: 0x{:x}, trailer: 0x{:x}, tlv_len: 0x{:x}, padding: 0x{:x}", + slot_len, hdr_size, trailer, tlv_len, padding); + + slot_len - hdr_size - trailer - tlv_len - padding +} + /// Install a "program" into the given image. This fakes the image header, or at least all of the /// fields used by the given code. Returns a copy of the image that was written. -fn install_image(flash: &mut SimMultiFlash, areadesc: &AreaDesc, slot: &SlotInfo, len: ImageSize, - ram: &RamData, - deps: &dyn Depender, img_manipulation: ImageManipulation, security_counter:Option, secondary_slot:bool) -> ImageData { +fn install_image(flash: &mut SimMultiFlash, areadesc: &AreaDesc, slots: &[SlotInfo], + slot_ind: usize, len: ImageSize, ram: &RamData, + deps: &dyn Depender, img_manipulation: ImageManipulation, security_counter:Option) -> ImageData { + let slot = &slots[slot_ind]; let mut offset = slot.base_off; - let slot_len = slot.len; let dev_id = slot.dev_id; let dev = flash.get_mut(&dev_id).unwrap(); let mut tlv: Box = Box::new(make_tlv()); - if Caps::SwapUsingOffset.present() && secondary_slot { + if Caps::SwapUsingOffset.present() && slot_ind == 1 { let sector_size = dev.sector_iter().next().unwrap().size as usize; offset += sector_size; } @@ -1863,30 +1905,13 @@ fn install_image(flash: &mut SimMultiFlash, areadesc: &AreaDesc, slot: &SlotInfo let len = match len { ImageSize::Given(size) => size, - ImageSize::Largest => { - let trailer = image_largest_trailer(dev, &areadesc, &slot); - let tlv_len = tlv.estimate_size(); - info!("slot: 0x{:x}, HDR: 0x{:x}, trailer: 0x{:x}", - slot_len, HDR_SIZE, trailer); - slot_len - HDR_SIZE - trailer - tlv_len - }, + ImageSize::Largest => compute_largest_image_size(dev, areadesc, slots, slot_ind, + HDR_SIZE, tlv.as_ref()), ImageSize::Oversized => { - let trailer = image_largest_trailer(dev, &areadesc, &slot); - let tlv_len = tlv.estimate_size(); - let mut sector_offset = 0; - - if Caps::SwapUsingOffset.present() && secondary_slot { - // This accounts for when both slots have the same size, it will not work where - // the second slot is one sector larger than the primary - sector_offset = dev.sector_iter().next().unwrap().size as usize; - } - - info!("slot: 0x{:x}, HDR: 0x{:x}, trailer: 0x{:x}", - slot_len, HDR_SIZE, trailer); - - slot_len - HDR_SIZE - trailer - tlv_len - sector_offset + dev.align() + let largest_img_sz = compute_largest_image_size(dev, areadesc, slots, slot_ind, + HDR_SIZE, tlv.as_ref()); + largest_img_sz + dev.align() } - }; // Generate a boot header. Note that the size doesn't include the header. @@ -1995,7 +2020,7 @@ fn install_image(flash: &mut SimMultiFlash, areadesc: &AreaDesc, slot: &SlotInfo enc_copy = Some(enc); - dev.erase(offset, slot_len).unwrap(); + dev.erase(offset, slot.len).unwrap(); } else { enc_copy = None; } @@ -2020,7 +2045,7 @@ fn install_image(flash: &mut SimMultiFlash, areadesc: &AreaDesc, slot: &SlotInfo let enc_copy: Option>; if is_encrypted { - dev.erase(offset, slot_len).unwrap(); + dev.erase(offset, slot.len).unwrap(); dev.write(offset, &encbuf).unwrap(); @@ -2395,10 +2420,7 @@ trait AsRaw : Sized { /// Determine whether it makes sense to test this configuration with a maximally-sized image. /// Returns an ImageSize representing the best size to test, possibly just with the given size. fn maximal(size: usize) -> ImageSize { - if Caps::OverwriteUpgrade.present() || - Caps::SwapUsingOffset.present() || - Caps::SwapUsingMove.present() - { + if Caps::OverwriteUpgrade.present() { ImageSize::Given(size) } else { ImageSize::Largest From 5b1f9a9f04569ae5d5678ba23803e4f60d162152 Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 19:50:04 +0200 Subject: [PATCH 08/10] sim: swap-move: Allow firmware data in the first trailer sector When using the swap-move strategy, the simulator now doesn't compute anymore the maximum image size so the area allocated to the trailer has a size that is a multiple of the sector size. Indeed, the swap-move strategy now supports having part of the firmware in the first sector holding trailer data, allowing larger images. Signed-off-by: Thomas Altenbach --- sim/src/image.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sim/src/image.rs b/sim/src/image.rs index 39056ebf46..09214c501f 100644 --- a/sim/src/image.rs +++ b/sim/src/image.rs @@ -1807,9 +1807,11 @@ fn image_largest_trailer(dev: &dyn Flash, areadesc: &AreaDesc, slot: &SlotInfo) let trailer = if Caps::OverwriteUpgrade.present() { // magic + image-ok + copy-done + swap-info c::boot_magic_sz() + 3 * c::boot_max_align() - } else if Caps::SwapUsingOffset.present() || Caps::SwapUsingMove.present() { + } else if Caps::SwapUsingOffset.present() { let sector_size = dev.sector_iter().next().unwrap().size as u32; align_up(c::boot_trailer_sz(dev.align() as u32), sector_size) as usize + } else if Caps::SwapUsingMove.present() { + c::boot_trailer_sz(dev.align() as u32) as usize } else if Caps::SwapUsingScratch.present() { estimate_swap_scratch_trailer_size(dev, areadesc, slot) } else { From 5218c82573fb386acba5791f559f19e63915474b Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 23:05:25 +0200 Subject: [PATCH 09/10] docs: design: Update maximum image size when using swap-move When using the swap-move strategy, the area allocated to the trailer no more need to have a size that is a multiple of the sector size, since the area not allocated to the trailer in the first sector holding trailer data can now be used to store firmware data. Signed-off-by: Thomas Altenbach --- docs/design.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/design.md b/docs/design.md index 66fcd45f96..c06dee8626 100755 --- a/docs/design.md +++ b/docs/design.md @@ -296,7 +296,6 @@ This algorithm is designed so that the higher sector of the primary slot is used only for allowing sectors to move up. Therefore the most memory-size-effective slot layout is when the primary slot is larger than the secondary slot by exactly one sector plus the size of the swap status area, -rounded up to the total size of the sectors it occupies, although same-sized slots are allowed as well. The algorithm is limited to support sectors of the same sector layout. All slot's sectors should be of the same size. @@ -304,15 +303,16 @@ sector layout. All slot's sectors should be of the same size. When using this algorithm the maximum image size available for the application will be: ``` -maximum-image-size = (N-1) * slot-sector-size - image-trailer-sectors-size +maximum-image-size = (N-1) * slot-sector-size - image-trailer-size - fallback-trailer-padding ``` Where: `N` is the number of sectors in the primary slot. - `image-trailer-sectors-size` is the size of the image trailer rounded up to - the total size of sectors its occupied. For instance if the image-trailer-size - is equal to 1056 B and the sector size is equal to 1024 B, then - `image-trailer-sectors-size` will be equal to 2048 B. + `image-trailer-size` is the size of the image trailer. + `fallback-trailer-padding` is the padding required for the fallback trailer and is equal to + `magic-aligned-size - (image-trailer-size mod slot-sector-size)` if that value is strictly greater + than zero, to zero otherwise. + `magic-aligned-size` is the size of the trailer magic field, rounded up to `BOOT_MAX_ALIGN`. This does imply, if there is any doubt, that the primary slot will be exactly one sector larger than the secondary slot due to the swap sector alone. It is From 2bc56dc98c10ce0ed4ae5f7f4b066d764687cabd Mon Sep 17 00:00:00 2001 From: Thomas Altenbach Date: Sun, 30 Mar 2025 23:20:16 +0200 Subject: [PATCH 10/10] docs: release-notes: Add note on unaligned swap-move trailers The swap-move strategy now support having firmware data in the first sector holding trailer data. This commit adds a release note snippet regarding this change. Signed-off-by: Thomas Altenbach --- docs/release-notes.d/swap-move-unaligned-trailers.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/release-notes.d/swap-move-unaligned-trailers.md diff --git a/docs/release-notes.d/swap-move-unaligned-trailers.md b/docs/release-notes.d/swap-move-unaligned-trailers.md new file mode 100644 index 0000000000..881b87d68c --- /dev/null +++ b/docs/release-notes.d/swap-move-unaligned-trailers.md @@ -0,0 +1,3 @@ +- The swap-move strategy no more requires using dedicated sectors for the + trailer. Any byte not allocated to the trailer in the first sector holding + trailer data can now be used by the firmware image.