diff --git a/boot/bootutil/src/bootutil_misc.c b/boot/bootutil/src/bootutil_misc.c index f125623295..21fef45809 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; @@ -475,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; 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 95f8f1732e..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); @@ -365,19 +366,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/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 42ba7a6679..d475513ac9 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,28 +469,62 @@ 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); 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"); 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; } @@ -379,13 +533,14 @@ 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, - struct boot_status *bs, const struct flash_area *fap_pri, - const struct flash_area *fap_sec) +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) { 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,27 +551,41 @@ 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 - * erase requirement this will also prepare traier for write. - */ - rc = swap_scramble_trailer_sectors(state, fap_sec); + if (!sector_erased_with_trailer) { + rc = boot_erase_region(fap_pri, new_off, sz); 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); + rc = boot_copy_region(state, fap_pri, fap_pri, old_off, new_off, copy_sz); assert(rc == 0); rc = boot_write_status(state, bs); @@ -426,7 +595,7 @@ boot_move_sector_up(int 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) { @@ -440,77 +609,79 @@ boot_swap_sectors(int idx, uint32_t sz, struct boot_loader_state *state, 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); + uint32_t copy_sz = sz; + size_t fallback_trailer_sector = get_fallback_trailer_sector(state); - rc = boot_copy_region(state, fap_sec, fap_pri, sec_off, pri_off, sz); + rc = boot_erase_region(fap_pri, pri_off, sz); assert(rc == 0); - rc = boot_write_status(state, bs); - bs->state = BOOT_STATUS_STATE_1; - BOOT_STATUS_ASSERT(rc == 0); - } + /* 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); - if (bs->state == BOOT_STATUS_STATE_1) { - rc = boot_erase_region(fap_sec, sec_off, sz); - assert(rc == 0); + /* 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_pri, fap_sec, pri_up_off, sec_off, 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); - bs->idx++; - bs->state = BOOT_STATUS_STATE_0; + bs->state = BOOT_STATUS_STATE_1; 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) -{ - struct boot_swap_state swap_state; - int rc; -#if (BOOT_IMAGE_NUMBER == 1) - (void)state; -#endif + if (bs->state == BOOT_STATUS_STATE_1) { + bool sector_erased_with_trailer = false; + uint32_t copy_sz = sz; - /* No fixup required */ - if (bs->swap_type != BOOT_SWAP_TYPE_REVERT || - bs->op != BOOT_STATUS_OP_MOVE || - bs->idx != BOOT_STATUS_IDX_0) { - return; - } + if (idx == last_idx) { + rc = swap_scramble_trailer_sectors(state, fap_sec); + assert(rc == 0); - rc = boot_read_swap_state(fap_sec, &swap_state); - 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); - BOOT_LOG_SWAP_STATE("Secondary image", &swap_state); + 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 (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); + 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; + } + } - rc = boot_write_image_ok(fap_sec); - assert(rc == 0); + if (!sector_erased_with_trailer) { + rc = boot_erase_region(fap_sec, sec_off, sz); + assert(rc == 0); + } - rc = boot_write_swap_size(fap_sec, bs->swap_size); + rc = boot_copy_region(state, fap_pri, fap_sec, pri_up_off, sec_off, copy_sz); assert(rc == 0); - rc = boot_write_magic(fap_sec); - assert(rc == 0); + rc = boot_write_status(state, bs); + bs->idx++; + bs->state = BOOT_STATUS_STATE_0; + BOOT_STATUS_ASSERT(rc == 0); } } @@ -518,12 +689,10 @@ 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; const struct flash_area *fap_sec; @@ -532,45 +701,17 @@ 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); 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) { 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--; } @@ -582,10 +723,22 @@ 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++; } + + /* 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) 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); 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 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. diff --git a/sim/src/image.rs b/sim/src/image.rs index dcea8c5772..09214c501f 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, @@ -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 { @@ -1819,19 +1821,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 +1907,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 +2022,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 +2047,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 +2422,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