From 1acec48d4b818a8c4036ff4af10dd10411742395 Mon Sep 17 00:00:00 2001 From: Michal Lenc Date: Mon, 19 Feb 2024 15:20:45 +0100 Subject: [PATCH 1/4] boot: generalize boot_write_image_ok() function to get flag as parameter This commit generalizes boot_write_image_ok() function to take flag as an input parameter. Function is also rename to boot_write_image_flag() so the name matches the usage better. This is useful to future implementation of different algorithms that might need other flags than BOOT_FLAG_SET to be written to image trailer. Signed-off-by: Michal Lenc --- boot/bootutil/src/bootutil_priv.h | 2 +- boot/bootutil/src/bootutil_public.c | 10 +++++----- boot/bootutil/src/swap_misc.c | 4 ++-- boot/bootutil/src/swap_move.c | 2 +- boot/bootutil/src/swap_scratch.c | 2 +- boot/zephyr/firmware_loader.c | 2 +- boot/zephyr/single_loader.c | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/boot/bootutil/src/bootutil_priv.h b/boot/bootutil/src/bootutil_priv.h index 32f996e78..49ff7af1e 100644 --- a/boot/bootutil/src/bootutil_priv.h +++ b/boot/bootutil/src/bootutil_priv.h @@ -277,7 +277,7 @@ int boot_read_swap_state_by_id(int flash_area_id, int boot_write_magic(const struct flash_area *fap); 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); +int boot_write_image_flag(const struct flash_area *fap, int flag); int boot_write_swap_info(const struct flash_area *fap, uint8_t swap_type, uint8_t image_num); int boot_write_swap_size(const struct flash_area *fap, uint32_t swap_size); diff --git a/boot/bootutil/src/bootutil_public.c b/boot/bootutil/src/bootutil_public.c index afa601cac..a40270fc6 100644 --- a/boot/bootutil/src/bootutil_public.c +++ b/boot/bootutil/src/bootutil_public.c @@ -354,7 +354,7 @@ boot_write_trailer_flag(const struct flash_area *fap, uint32_t off, } int -boot_write_image_ok(const struct flash_area *fap) +boot_write_image_flag(const struct flash_area *fap, int flag) { uint32_t off; @@ -362,7 +362,7 @@ boot_write_image_ok(const struct flash_area *fap) BOOT_LOG_DBG("writing image_ok; fa_id=%d off=0x%lx (0x%lx)", flash_area_get_id(fap), (unsigned long)off, (unsigned long)(flash_area_get_off(fap) + off)); - return boot_write_trailer_flag(fap, off, BOOT_FLAG_SET); + return boot_write_trailer_flag(fap, off, flag); } int @@ -515,7 +515,7 @@ boot_set_next(const struct flash_area *fa, bool active, bool confirm) * confirm a padded image which has been programmed using * a programming interface. */ - rc = boot_write_image_ok(fa); + rc = boot_write_image_flag(fa, BOOT_FLAG_SET); } break; @@ -525,7 +525,7 @@ boot_set_next(const struct flash_area *fa, bool active, bool confirm) rc = boot_write_magic(fa); if (rc == 0 && confirm) { - rc = boot_write_image_ok(fa); + rc = boot_write_image_flag(fa, BOOT_FLAG_SET); } if (rc == 0) { @@ -605,7 +605,7 @@ boot_set_next(const struct flash_area *fa, bool active, bool confirm) } if (slot_state.image_ok == BOOT_FLAG_UNSET) { - rc = boot_write_image_ok(fa); + rc = boot_write_image_flag(fa, BOOT_FLAG_SET); if (rc != 0) { break; } diff --git a/boot/bootutil/src/swap_misc.c b/boot/bootutil/src/swap_misc.c index 733a39744..0da9ba243 100644 --- a/boot/bootutil/src/swap_misc.c +++ b/boot/bootutil/src/swap_misc.c @@ -106,7 +106,7 @@ swap_status_init(const struct boot_loader_state *state, } if (swap_state.image_ok == BOOT_FLAG_SET) { - rc = boot_write_image_ok(fap); + rc = boot_write_image_flag(fap, BOOT_FLAG_SET); assert(rc == 0); } @@ -222,7 +222,7 @@ swap_set_image_ok(uint8_t image_index) } if (state.image_ok == BOOT_FLAG_UNSET) { - rc = boot_write_image_ok(fap); + rc = boot_write_image_flag(fap, BOOT_FLAG_SET); } out: diff --git a/boot/bootutil/src/swap_move.c b/boot/bootutil/src/swap_move.c index 111e82f05..3e90a9dc1 100644 --- a/boot/bootutil/src/swap_move.c +++ b/boot/bootutil/src/swap_move.c @@ -483,7 +483,7 @@ fixup_revert(const struct boot_loader_state *state, struct boot_status *bs, rc = swap_erase_trailer_sectors(state, fap_sec); assert(rc == 0); - rc = boot_write_image_ok(fap_sec); + rc = boot_write_image_flag(fap_sec, BOOT_FLAG_SET); assert(rc == 0); rc = boot_write_swap_size(fap_sec, bs->swap_size); diff --git a/boot/bootutil/src/swap_scratch.c b/boot/bootutil/src/swap_scratch.c index 66cbdce5f..524a2971a 100644 --- a/boot/bootutil/src/swap_scratch.c +++ b/boot/bootutil/src/swap_scratch.c @@ -635,7 +635,7 @@ boot_swap_sectors(int idx, uint32_t sz, struct boot_loader_state *state, assert(rc == 0); if (swap_state.image_ok == BOOT_FLAG_SET) { - rc = boot_write_image_ok(fap_primary_slot); + rc = boot_write_image_flag(fap_primary_slot, BOOT_FLAG_SET); assert(rc == 0); } diff --git a/boot/zephyr/firmware_loader.c b/boot/zephyr/firmware_loader.c index 11b461c41..888960f4e 100644 --- a/boot/zephyr/firmware_loader.c +++ b/boot/zephyr/firmware_loader.c @@ -87,7 +87,7 @@ boot_image_validate_once(const struct flash_area *fa_p, if (rc != 0) FIH_RET(FIH_FAILURE); } - rc = boot_write_image_ok(fa_p); + rc = boot_write_image_flag(fa_p, BOOT_FLAG_SET); if (rc != 0) FIH_RET(FIH_FAILURE); } diff --git a/boot/zephyr/single_loader.c b/boot/zephyr/single_loader.c index 75374d2db..e8943172b 100644 --- a/boot/zephyr/single_loader.c +++ b/boot/zephyr/single_loader.c @@ -83,7 +83,7 @@ boot_image_validate_once(const struct flash_area *fa_p, if (rc != 0) FIH_RET(FIH_FAILURE); } - rc = boot_write_image_ok(fa_p); + rc = boot_write_image_flag(fa_p, BOOT_FLAG_SET); if (rc != 0) FIH_RET(FIH_FAILURE); } From 8a3150a19323c6401ced2d69ab60aa113a38a4c1 Mon Sep 17 00:00:00 2001 From: Michal Lenc Date: Tue, 20 Feb 2024 10:46:54 +0100 Subject: [PATCH 2/4] boot: add new copy with revert algorithm This algorithm uses three flash partitions to copy images to primary slot without swap mechanism. This way much faster update process can be achieved but more space on flash has to be allocated. This is basically trade off between update speed and flash space taken for boot process. The algorithm always keeps recovery image in either secondary or tertiary slot and lets the user upload update image to the other one. Once image is updated and confirmed, update slot is marked as recovery (the image is already there uploaded by the user) and old recovery is marked as new update -> user will upload new image there. This means there are no writes to ota1 and ota2 partitions during boot process except and therefore there is no speed limitation if usually slower (compared to embedded flash) external NOR flash is used for these partitions. The only exception is first update process where bootloader has to create recovery image. Overall, this algorithm allows to achieve the speed of overwrite only algorithm while retaining the revert/recovery option. It is especially useful for devices with larger images and a lot of free space on external flash. Signed-off-by: Michal Lenc --- boot/bootutil/CMakeLists.txt | 1 + .../include/bootutil/bootutil_public.h | 35 +++ boot/bootutil/src/bootutil_misc.c | 2 + boot/bootutil/src/bootutil_priv.h | 16 +- boot/bootutil/src/bootutil_public.c | 235 +++++++++++++++- boot/bootutil/src/copy.c | 260 ++++++++++++++++++ boot/bootutil/src/copy_priv.h | 35 +++ boot/bootutil/src/loader.c | 218 ++++++++++++++- boot/bootutil/src/swap_scratch.c | 2 +- 9 files changed, 785 insertions(+), 19 deletions(-) create mode 100644 boot/bootutil/src/copy.c create mode 100644 boot/bootutil/src/copy_priv.h diff --git a/boot/bootutil/CMakeLists.txt b/boot/bootutil/CMakeLists.txt index 90977064f..39ac81029 100644 --- a/boot/bootutil/CMakeLists.txt +++ b/boot/bootutil/CMakeLists.txt @@ -20,6 +20,7 @@ target_sources(bootutil src/bootutil_misc.c src/bootutil_public.c src/caps.c + src/copy.c src/encrypted.c src/fault_injection_hardening.c src/fault_injection_hardening_delay_rng_mbedtls.c diff --git a/boot/bootutil/include/bootutil/bootutil_public.h b/boot/bootutil/include/bootutil/bootutil_public.h index b2d5a5de8..5a95a4d88 100644 --- a/boot/bootutil/include/bootutil/bootutil_public.h +++ b/boot/bootutil/include/bootutil/bootutil_public.h @@ -5,6 +5,7 @@ * Copyright (c) 2016-2019 JUUL Labs * Copyright (c) 2019-2021 Arm Limited * Copyright (c) 2020-2021 Nordic Semiconductor ASA + * Copyright (c) 2024 Elektroline Inc. * * Original license: * @@ -110,6 +111,8 @@ _Static_assert(MCUBOOT_BOOT_MAX_ALIGN >= 8 && MCUBOOT_BOOT_MAX_ALIGN <= 32, #define BOOT_FLAG_BAD 2 #define BOOT_FLAG_UNSET 3 #define BOOT_FLAG_ANY 4 /* NOTE: control only, not dependent on sector */ +#define BOOT_FLAG_UPDATED 5 /* NOTE: for copy with revert alg only */ +#define BOOT_FLAG_AVAIL 6 /* NOTE: for copy with revert alg only */ #define BOOT_EFLASH 1 #define BOOT_EFILE 2 @@ -153,6 +156,25 @@ struct boot_swap_state { uint8_t image_num; /* Boot status belongs to this image */ }; +struct boot_copy_state { + int update; /* Number of update slot */ + int recovery; /* Number of recovery slot */ + bool recovery_valid; /* True if recovery image is valid */ +}; + +/** + * @brief Determines the action, if any, that mcuboot will take on a image pair + * for copy with rever algorithm. + * + * @param image_index Image pair index. + * @param state Pointer to copy state structure. + * + * @return a BOOT_SWAP_TYPE_[...] constant on success, negative errno code on + * fail. + */ +int +boot_copy_type_multi(int image_index, struct boot_copy_state *state); + /** * @brief Determines the action, if any, that mcuboot will take on a image pair. * @@ -258,6 +280,19 @@ int boot_read_image_ok(const struct flash_area *fap, uint8_t *image_ok); int boot_read_swap_state_by_id(int flash_area_id, struct boot_swap_state *state); +/** + * @brief Read the image copy state + * + * @param image_index Image index pair + * @param state Pointer to structure for storing copy state. + * + * @return 0 on success; non-zero error code on failure. + */ +#ifdef MCUBOOT_COPY_WITH_REVERT +int +boot_read_copy_state(int image_index, struct boot_copy_state *state); +#endif + /** * @brief Read the image swap state * diff --git a/boot/bootutil/src/bootutil_misc.c b/boot/bootutil/src/bootutil_misc.c index 87b863507..e80db5702 100644 --- a/boot/bootutil/src/bootutil_misc.c +++ b/boot/bootutil/src/bootutil_misc.c @@ -347,6 +347,8 @@ uint32_t bootutil_max_image_size(const struct flash_area *fap) */ } return flash_sector_get_off(§or); +#elif defined(MCUBOOT_COPY_WITH_REVERT) + return boot_swap_info_off(fap); #elif defined(MCUBOOT_OVERWRITE_ONLY) return boot_swap_info_off(fap); #elif defined(MCUBOOT_DIRECT_XIP) diff --git a/boot/bootutil/src/bootutil_priv.h b/boot/bootutil/src/bootutil_priv.h index 49ff7af1e..3b25ed15e 100644 --- a/boot/bootutil/src/bootutil_priv.h +++ b/boot/bootutil/src/bootutil_priv.h @@ -51,8 +51,12 @@ struct flash_area; #define BOOT_TMPBUF_SZ 256 -/** Number of image slots in flash; currently limited to two. */ -#define BOOT_NUM_SLOTS 2 +/** Number of image slots in flash; 3 if MCUBOOT_COPY_WITH_REVERT, 2 otherwise */ +#ifdef MCUBOOT_COPY_WITH_REVERT +# define BOOT_NUM_SLOTS 3 +#else +# define BOOT_NUM_SLOTS 2 +#endif #if (defined(MCUBOOT_OVERWRITE_ONLY) + \ defined(MCUBOOT_SWAP_USING_MOVE) + \ @@ -67,7 +71,8 @@ struct flash_area; !defined(MCUBOOT_DIRECT_XIP) && \ !defined(MCUBOOT_RAM_LOAD) && \ !defined(MCUBOOT_SINGLE_APPLICATION_SLOT) && \ - !defined(MCUBOOT_FIRMWARE_LOADER) + !defined(MCUBOOT_FIRMWARE_LOADER) && \ + !defined(MCUBOOT_COPY_WITH_REVERT) #define MCUBOOT_SWAP_USING_SCRATCH 1 #endif @@ -197,6 +202,7 @@ _Static_assert(sizeof(boot_img_magic) == BOOT_MAGIC_SZ, "Invalid size for image #define BOOT_PRIMARY_SLOT 0 #define BOOT_SECONDARY_SLOT 1 +#define BOOT_TERTIARY_SLOT 2 #define BOOT_STATUS_SOURCE_NONE 0 #define BOOT_STATUS_SOURCE_SCRATCH 1 @@ -230,6 +236,10 @@ struct boot_loader_state { } scratch; #endif +#ifdef MCUBOOT_COPY_WITH_REVERT + struct boot_copy_state copy[BOOT_IMAGE_NUMBER]; +#endif + uint8_t swap_type[BOOT_IMAGE_NUMBER]; uint32_t write_sz; diff --git a/boot/bootutil/src/bootutil_public.c b/boot/bootutil/src/bootutil_public.c index a40270fc6..9c45ef626 100644 --- a/boot/bootutil/src/bootutil_public.c +++ b/boot/bootutil/src/bootutil_public.c @@ -5,6 +5,7 @@ * Copyright (c) 2016-2019 JUUL Labs * Copyright (c) 2019-2023 Arm Limited * Copyright (c) 2020-2023 Nordic Semiconductor ASA + * Copyright (c) 2024 Elektroline Inc. * * Original license: * @@ -132,10 +133,19 @@ static const struct boot_swap_table boot_swap_tables[] = { static int boot_flag_decode(uint8_t flag) { - if (flag != BOOT_FLAG_SET) { - return BOOT_FLAG_BAD; + switch (flag) { + case BOOT_FLAG_SET: + return BOOT_FLAG_SET; + + case BOOT_FLAG_UPDATED: + return BOOT_FLAG_UPDATED; + + case BOOT_FLAG_AVAIL: + return BOOT_FLAG_AVAIL; + + default: + return BOOT_FLAG_BAD; } - return BOOT_FLAG_SET; } uint32_t @@ -215,6 +225,61 @@ boot_read_copy_done(const struct flash_area *fap, uint8_t *copy_done) return boot_read_flag(fap, copy_done, boot_copy_done_off(fap)); } +#ifdef MCUBOOT_COPY_WITH_REVERT +int +boot_read_copy_state(int image_index, struct boot_copy_state *state) +{ + int rc; + struct boot_swap_state primary_state; + struct boot_swap_state secondary_state; + struct boot_swap_state tertiary_state; + + /* Read state from all slots */ + + rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_PRIMARY(image_index), + &primary_state); + if (rc != 0) { + return rc; + } + + rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_SECONDARY(image_index), + &secondary_state); + if (rc != 0) { + return rc; + } + + rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_TERTIARY(image_index), + &tertiary_state); + if (rc != 0) { + return rc; + } + + if (secondary_state.magic == BOOT_MAGIC_UNSET) { + /* Secondary is either empty or corrupted. In any case, we want + * this to mark as a partition for update. + */ + state->update = FLASH_AREA_IMAGE_SECONDARY(image_index); + state->recovery = FLASH_AREA_IMAGE_TERTIARY(image_index); + } else if (tertiary_state.magic == BOOT_MAGIC_UNSET) { + /* Tertiary is either empty or corrupted. In any case, we want + * this to mark as a partition for update. + */ + state->update = FLASH_AREA_IMAGE_TERTIARY(image_index); + state->recovery = FLASH_AREA_IMAGE_SECONDARY(image_index); + } else if ((tertiary_state.image_ok == BOOT_FLAG_AVAIL) || + (tertiary_state.image_ok == BOOT_FLAG_UPDATED)) { + /* Secondary image is marked as available, use it for update */ + state->update = FLASH_AREA_IMAGE_TERTIARY(image_index); + state->recovery = FLASH_AREA_IMAGE_SECONDARY(image_index); + } else { + /* Any other case. */ + state->update = FLASH_AREA_IMAGE_SECONDARY(image_index); + state->recovery = FLASH_AREA_IMAGE_TERTIARY(image_index); + } + + return 0; +} +#endif int boot_read_swap_state(const struct flash_area *fap, @@ -393,6 +458,112 @@ boot_write_swap_info(const struct flash_area *fap, uint8_t swap_type, return boot_write_trailer(fap, off, (const uint8_t *) &swap_info, 1); } +#ifdef MCUBOOT_COPY_WITH_REVERT +int +boot_copy_type_multi(int image_index, struct boot_copy_state *state) +{ + const struct boot_swap_table *table; + struct boot_swap_state primary_slot; + struct boot_swap_state update_slot; + struct boot_swap_state recovery_slot; + int update; + int recovery; + int rc; + size_t i; + + update = state->update; + recovery = state->recovery; + + /* Get state from all three slots. */ + + rc = BOOT_HOOK_CALL(boot_read_swap_state_primary_slot_hook, + BOOT_HOOK_REGULAR, image_index, &primary_slot); + if (rc == BOOT_HOOK_REGULAR) + { + rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_PRIMARY(image_index), + &primary_slot); + } + if (rc) { + return BOOT_SWAP_TYPE_PANIC; + } + + rc = boot_read_swap_state_by_id(update, &update_slot); + if (rc == BOOT_EFLASH) { + BOOT_LOG_INF("Secondary image of image pair" + "is unreachable. Treat it as empty"); + update_slot.magic = BOOT_MAGIC_UNSET; + update_slot.swap_type = BOOT_SWAP_TYPE_NONE; + update_slot.copy_done = BOOT_FLAG_UNSET; + update_slot.image_ok = BOOT_FLAG_UNSET; + update_slot.image_num = 0; + } else if (rc) { + return BOOT_SWAP_TYPE_PANIC; + } + + rc = boot_read_swap_state_by_id(recovery, &recovery_slot); + if (rc == BOOT_EFLASH) { + BOOT_LOG_INF("Tertiary image of image pair" + "is unreachable. Treat it as empty"); + update_slot.magic = BOOT_MAGIC_UNSET; + update_slot.swap_type = BOOT_SWAP_TYPE_NONE; + update_slot.copy_done = BOOT_FLAG_UNSET; + update_slot.image_ok = BOOT_FLAG_UNSET; + update_slot.image_num = 0; + } else if (rc) { + return BOOT_SWAP_TYPE_PANIC; + } + + /* First check revert type. Bootloader should do revert only if + * image in primary slot is not ok, image in recovery slot is ok + * and image in update slot is marked as updated. + * + * Regular update should be done if image in update slot is not + * marked as updated. + */ + + if ((primary_slot.image_ok == BOOT_FLAG_UNSET) && + (recovery_slot.image_ok == BOOT_FLAG_SET) && + (update_slot.image_ok == BOOT_FLAG_UPDATED)) { + BOOT_LOG_DBG("Image index: %d, Swap type: revert", image_index); + return BOOT_SWAP_TYPE_REVERT; + } + + /* Check whether bootloader should perform update. */ + + for (i = 0; i < BOOT_SWAP_TABLES_COUNT; i++) { + table = boot_swap_tables + i; + + if (boot_magic_compatible_check(table->magic_primary_slot, + primary_slot.magic) && + boot_magic_compatible_check(table->magic_secondary_slot, + update_slot.magic) && + (table->image_ok_primary_slot == BOOT_FLAG_ANY || + table->image_ok_primary_slot == primary_slot.image_ok) && + (table->image_ok_secondary_slot == BOOT_FLAG_ANY || + table->image_ok_secondary_slot == update_slot.image_ok) && + (table->copy_done_primary_slot == BOOT_FLAG_ANY || + table->copy_done_primary_slot == primary_slot.copy_done)) { + BOOT_LOG_DBG("Image index: %d, Swap type: %s", + image_index, + table->swap_type == BOOT_SWAP_TYPE_TEST ? "test" : + table->swap_type == BOOT_SWAP_TYPE_PERM ? "perm" : + "BUG; can't happen"); + if (table->swap_type == BOOT_SWAP_TYPE_REVERT) { + return BOOT_SWAP_TYPE_NONE; + } + if (table->swap_type != BOOT_SWAP_TYPE_TEST && + table->swap_type != BOOT_SWAP_TYPE_PERM) { + return BOOT_SWAP_TYPE_PANIC; + } + return table->swap_type; + } + } + + BOOT_LOG_INF("Image index: %d, Swap type: none", image_index); + return BOOT_SWAP_TYPE_NONE; +} +#endif + int boot_swap_type_multi(int image_index) { @@ -495,8 +666,18 @@ int boot_set_next(const struct flash_area *fa, bool active, bool confirm) { struct boot_swap_state slot_state; +#ifdef MCUBOOT_COPY_WITH_REVERT + struct boot_swap_state sec_state; + struct boot_swap_state ter_state; + const struct flash_area *fap_update; + const struct flash_area *fap_recover; + int update_slot = -1; + int recovery_slot; +#endif int rc; + BOOT_LOG_DBG("boot_set_next active %d confirm %d", active, confirm); + if (active) { confirm = true; } @@ -509,13 +690,59 @@ boot_set_next(const struct flash_area *fa, bool active, bool confirm) switch (slot_state.magic) { case BOOT_MAGIC_GOOD: /* If non-active then swap already scheduled, else confirm needed.*/ - if (active && slot_state.image_ok == BOOT_FLAG_UNSET) { /* Intentionally do not check copy_done flag to be able to * confirm a padded image which has been programmed using * a programming interface. */ rc = boot_write_image_flag(fa, BOOT_FLAG_SET); +#ifdef MCUBOOT_COPY_WITH_REVERT + if (rc != 0) { + return rc; + } + rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_SECONDARY(0), + &sec_state); + if (rc != 0) { + return rc; + } + rc = boot_read_swap_state_by_id(FLASH_AREA_IMAGE_TERTIARY(0), + &ter_state); + if (rc != 0) { + return rc; + } + + /* Check which slot is update and recovery. This can be done by + * BOOT_FLAG_UPDATED flag. If slot is marked as BOOT_FLAG_UPDATED + * then it is update slot and should be confirmed. Once confirmed, + * it becomes new recovery and old recovery is marked as + * BOOT_FLAG_AVAIL -> new slot where to upload update. + */ + if (sec_state.image_ok == BOOT_FLAG_UPDATED) { + update_slot = FLASH_AREA_IMAGE_SECONDARY(0); + recovery_slot = FLASH_AREA_IMAGE_TERTIARY(0); + } else if (ter_state.image_ok == BOOT_FLAG_UPDATED) { + update_slot = FLASH_AREA_IMAGE_TERTIARY(0); + recovery_slot = FLASH_AREA_IMAGE_SECONDARY(0); + } + + if (update_slot != -1) { + rc = flash_area_open(update_slot, &fap_update); + if (rc != 0) { + return rc; + } + rc = boot_write_image_flag(fap_update, BOOT_FLAG_SET); + flash_area_close(fap_update); + if (rc != 0) { + return rc; + } + rc = flash_area_open(recovery_slot, &fap_recover); + if (rc != 0) { + return rc; + } + rc = boot_write_image_flag(fap_recover, BOOT_FLAG_AVAIL); + flash_area_close(fap_recover); + } +#endif } break; diff --git a/boot/bootutil/src/copy.c b/boot/bootutil/src/copy.c new file mode 100644 index 000000000..5360b80ef --- /dev/null +++ b/boot/bootutil/src/copy.c @@ -0,0 +1,260 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2019 JUUL Labs + * Copyright (c) 2024 Elektroline Inc. + * + * 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 +#include +#include +#include +#include +#include "bootutil/bootutil.h" +#include "bootutil_priv.h" +#include "bootutil/bootutil_log.h" +#include "copy_priv.h" + +#include "mcuboot_config/mcuboot_config.h" + +BOOT_LOG_MODULE_DECLARE(mcuboot); + +#ifdef MCUBOOT_COPY_WITH_REVERT + +static inline bool +boot_data_is_set_to(uint8_t val, void *data, size_t len) +{ + uint8_t i; + uint8_t *p = (uint8_t *)data; + for (i = 0; i < len; i++) { + if (val != p[i]) { + return false; + } + } + return true; +} + +static int +boot_check_header_erased(struct boot_loader_state *state, int slot) +{ + const struct flash_area *fap; + struct image_header *hdr; + uint8_t erased_val; + int area_id; + int rc; + + area_id = flash_area_id_from_multi_image_slot(BOOT_CURR_IMG(state), slot); + rc = flash_area_open(area_id, &fap); + if (rc != 0) { + return -1; + } + + erased_val = flash_area_erased_val(fap); + flash_area_close(fap); + + hdr = boot_img_hdr(state, slot); + if (!boot_data_is_set_to(erased_val, &hdr->ih_magic, sizeof(hdr->ih_magic))) { + return -1; + } + + return 0; +} + +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; + int area_id; + int rc = 0; + + (void)bs; + +#if (BOOT_IMAGE_NUMBER == 1) + (void)state; +#endif + + 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 = flash_area_read(fap, 0, out_hdr, sizeof *out_hdr); + flash_area_close(fap); + } + + if (rc != 0) { + rc = BOOT_EFLASH; + } + + return rc; +} + +bool copy_compare_hash(uint8_t *hash1, uint8_t *hash2) +{ + for (int i = 0; i < 32; i++) { + if (hash1[i] != hash2[i]) { + return false; + } + } + + return true; +} + +int +copy_get_slot_type(struct boot_loader_state *state) +{ + uint8_t tmpbuf[BOOT_TMPBUF_SZ]; + struct image_header *hdr; + struct image_header *primary_header; + uint8_t image_index; + uint8_t primary_hash[32] = {0}; + uint8_t secondary_hash[32] = {0}; + uint8_t tertiary_hash[32] = {0}; + struct boot_swap_state secondary_slot; + struct boot_swap_state tertiary_slot; + struct boot_copy_state *copy_state; + int primary; + int secondary; + int tertiary; + int rc; + + image_index = BOOT_CURR_IMG(state); + copy_state = &state->copy[image_index]; + primary = FLASH_AREA_IMAGE_PRIMARY(image_index); + secondary = FLASH_AREA_IMAGE_SECONDARY(image_index); + tertiary = FLASH_AREA_IMAGE_TERTIARY(image_index); + + /* Set initial values. Update = secondary, recovery = tertiary */ + copy_state->update = secondary; + copy_state->recovery = tertiary; + copy_state->recovery_valid = false; + + primary_header = boot_img_hdr(state, primary); + rc = bootutil_img_validate(BOOT_CURR_ENC(state), image_index, + primary_header, BOOT_IMG_AREA(state, primary), + tmpbuf, BOOT_TMPBUF_SZ, NULL, 0, primary_hash); + + hdr = boot_img_hdr(state, secondary); + if (boot_check_header_erased(state, secondary) == 0 || + (hdr->ih_flags & IMAGE_F_NON_BOOTABLE)) { + } else { + bootutil_img_validate(BOOT_CURR_ENC(state), image_index, + hdr, BOOT_IMG_AREA(state, secondary), tmpbuf, + BOOT_TMPBUF_SZ, NULL, 0, secondary_hash); + } + + hdr = boot_img_hdr(state, tertiary); + if (boot_check_header_erased(state, tertiary) == 0 || + (hdr->ih_flags & IMAGE_F_NON_BOOTABLE)) { + } else { + rc = bootutil_img_validate(BOOT_CURR_ENC(state), image_index, + hdr, BOOT_IMG_AREA(state, tertiary), tmpbuf, + BOOT_TMPBUF_SZ, NULL, 0, tertiary_hash); + } + + rc = boot_read_swap_state_by_id(secondary, &secondary_slot); + if (rc == BOOT_EFLASH) { + BOOT_LOG_INF("Secondary image of image pair" + "is unreachable. Treat it as empty"); + secondary_slot.magic = BOOT_MAGIC_UNSET; + secondary_slot.swap_type = BOOT_SWAP_TYPE_NONE; + secondary_slot.copy_done = BOOT_FLAG_UNSET; + secondary_slot.image_ok = BOOT_FLAG_UNSET; + secondary_slot.image_num = 0; + } else if (rc) { + return rc; + } + + rc = boot_read_swap_state_by_id(tertiary, &tertiary_slot); + if (rc == BOOT_EFLASH) { + BOOT_LOG_INF("Secondary image of image pair" + "is unreachable. Treat it as empty"); + tertiary_slot.magic = BOOT_MAGIC_UNSET; + tertiary_slot.swap_type = BOOT_SWAP_TYPE_NONE; + tertiary_slot.copy_done = BOOT_FLAG_UNSET; + tertiary_slot.image_ok = BOOT_FLAG_UNSET; + tertiary_slot.image_num = 0; + } else if (rc) { + return rc; + } + + if (secondary_slot.image_ok == BOOT_FLAG_SET) { + copy_state->recovery = secondary; + copy_state->update = tertiary; + if (copy_compare_hash(primary_hash, secondary_hash)) { + copy_state->recovery_valid = true; + } + } else if (tertiary_slot.image_ok == BOOT_FLAG_SET) { + copy_state->recovery = tertiary; + copy_state->update = secondary; + if (copy_compare_hash(primary_hash, tertiary_hash)) { + copy_state->recovery_valid = true; + } + } + + return 0; +} + +uint32_t +boot_status_internal_off(const struct boot_status *bs, int elem_sz) +{ + int idx_sz; + + idx_sz = elem_sz * BOOT_STATUS_STATE_COUNT; + + return (bs->idx - BOOT_STATUS_IDX_0) * idx_sz + + (bs->state - BOOT_STATUS_STATE_0) * elem_sz; +} + +/* + * We basically do just overwrite with this algorithm therefor slots are + * compatible when they fit into each other. + */ +int +boot_slots_compatible(struct boot_loader_state *state) +{ + size_t num_sectors_primary; + size_t num_sectors_secondary; + size_t num_sectors_tertiary; + size_t primary_size; + size_t secondary_size; + size_t tertiary_size; + + /* Basic check whether sectors do not exceed maximum img sectors. */ + num_sectors_primary = boot_img_num_sectors(state, BOOT_PRIMARY_SLOT); + num_sectors_secondary = boot_img_num_sectors(state, BOOT_SECONDARY_SLOT); + num_sectors_tertiary = boot_img_num_sectors(state, BOOT_TERTIARY_SLOT); + if ((num_sectors_primary > BOOT_MAX_IMG_SECTORS) || + (num_sectors_secondary > BOOT_MAX_IMG_SECTORS) || + (num_sectors_tertiary > BOOT_MAX_IMG_SECTORS)) { + BOOT_LOG_WRN("Cannot upgrade: more sectors than allowed"); + return 0; + } + + /* Check whether slot sizes are equal. */ + primary_size = BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT)->fa_size; + secondary_size = BOOT_IMG_AREA(state, BOOT_SECONDARY_SLOT)->fa_size; + tertiary_size = BOOT_IMG_AREA(state, BOOT_TERTIARY_SLOT)->fa_size; + if ((primary_size != secondary_size) || + (primary_size != tertiary_size) || + (secondary_size != tertiary_size)) { + BOOT_LOG_WRN("Cannot upgrade: slots sizes are not equal"); + return 0; + } + + return 1; +} + +#endif /* MCUBOOT_COPY_WITH_REVERT */ diff --git a/boot/bootutil/src/copy_priv.h b/boot/bootutil/src/copy_priv.h new file mode 100644 index 000000000..78b116427 --- /dev/null +++ b/boot/bootutil/src/copy_priv.h @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2024 Elektroline Inc. + * + * 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. + */ + + +#ifndef H_COPY_PRIV_ +#define H_COPY_PRIV_ + +#include "mcuboot_config/mcuboot_config.h" + +#ifdef MCUBOOT_COPY_WITH_REVERT + +/** + * This function gets image type (update, recover) from its header. + * This is to let the bootloader know whether he should update from + * secondary or tertiary slot. + */ +int copy_get_slot_type(struct boot_loader_state *state); + +#endif /* MCUBOOT_COPY_WITH_REVERT */ +#endif /* H_COPY_PRIV_ */ diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c index bd3a7f09c..69b41069e 100644 --- a/boot/bootutil/src/loader.c +++ b/boot/bootutil/src/loader.c @@ -5,6 +5,7 @@ * Copyright (c) 2016-2019 JUUL Labs * Copyright (c) 2019-2023 Arm Limited * Copyright (c) 2024 Nordic Semiconductor ASA + * Copyright (c) 2024 Elektroline Inc. * * Original license: * @@ -49,6 +50,10 @@ #include "bootutil/boot_hooks.h" #include "bootutil/mcuboot_status.h" +#ifdef MCUBOOT_COPY_WITH_REVERT +#include "copy_priv.h" +#endif + #ifdef MCUBOOT_ENC_IMAGES #include "bootutil/enc_key.h" #endif @@ -522,7 +527,9 @@ boot_verify_slot_dependencies(struct boot_loader_state *state, uint32_t slot) * Compute the total size of the given image. Includes the size of * the TLVs. */ -#if !defined(MCUBOOT_OVERWRITE_ONLY) || defined(MCUBOOT_OVERWRITE_ONLY_FAST) +#if (!defined(MCUBOOT_OVERWRITE_ONLY) || \ + defined(MCUBOOT_OVERWRITE_ONLY_FAST)) && \ + !defined(MCUBOOT_COPY_WITH_REVERT) static int boot_read_image_size(struct boot_loader_state *state, int slot, uint32_t *size) { @@ -1140,11 +1147,24 @@ boot_validated_swap_type(struct boot_loader_state *state, int swap_type; FIH_DECLARE(fih_rc, FIH_FAILURE); +#ifdef MCUBOOT_COPY_WITH_REVERT + swap_type = boot_copy_type_multi(BOOT_CURR_IMG(state), + &state->copy[BOOT_CURR_IMG(state)]); +#else swap_type = boot_swap_type_multi(BOOT_CURR_IMG(state)); +#endif if (BOOT_IS_UPGRADE(swap_type)) { /* Boot loader wants to switch to the secondary slot. * Ensure image is valid. */ + +#ifdef MCUBOOT_COPY_WITH_REVERT + /* Slot validation was already done, just return swap_type */ + if (swap_type == BOOT_SWAP_TYPE_REVERT) { + return swap_type; + } +#endif + FIH_CALL(boot_validate_slot, fih_rc, state, BOOT_SECONDARY_SLOT, bs); if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) { if (FIH_EQ(fih_rc, FIH_NO_BOOTABLE_IMAGE)) { @@ -1481,7 +1501,167 @@ boot_copy_image(struct boot_loader_state *state, struct boot_status *bs) } #endif -#if !defined(MCUBOOT_OVERWRITE_ONLY) +/** + * Perform copy with revert algorithm. This is either update process where + * image is copied from secondary or tertiary slot to primary slot or + * revert process. + * + * @param state Current boot process state. + * @param bs The current boot status. This function reads + * this struct to determine if it is resuming + * an interrupted swap operation. This + * function writes the updated status to this + * function on return. + * + * @return 0 on success; nonzero on failure. + */ +#ifdef MCUBOOT_COPY_WITH_REVERT +static int +boot_copy_image_with_revert(struct boot_loader_state *state, + struct boot_status *bs) +{ + size_t sect_count; + size_t sect; + int rc; + size_t size; + size_t this_size; + const struct flash_area *fap_primary_slot; + const struct flash_area *fap_secondary_slot; + const struct flash_area *fap_tertiary_slot; + const struct flash_area *fap_update_slot; + const struct flash_area *fap_recovery_slot; + uint8_t image_index; + uint8_t image_ok; + struct boot_copy_state *copy_state; + struct image_header *hdr; + int primary; + int secondary; + int tertiary; + + (void)bs; + + image_index = BOOT_CURR_IMG(state); + primary = FLASH_AREA_IMAGE_PRIMARY(image_index); + secondary = FLASH_AREA_IMAGE_SECONDARY(image_index); + tertiary = FLASH_AREA_IMAGE_TERTIARY(image_index); + + rc = flash_area_open(FLASH_AREA_IMAGE_PRIMARY(image_index), + &fap_primary_slot); + assert (rc == 0); + + rc = flash_area_open(secondary, &fap_secondary_slot); + assert (rc == 0); + + rc = flash_area_open(tertiary, &fap_tertiary_slot); + assert (rc == 0); + + rc = copy_get_slot_type(state); + if (rc != 0) { + return rc; + } + + copy_state = &state->copy[image_index]; + if (copy_state->update == secondary) { + fap_update_slot = fap_secondary_slot; + fap_recovery_slot = fap_tertiary_slot; + } else { + fap_update_slot = fap_tertiary_slot; + fap_recovery_slot = fap_secondary_slot; + } + + if (BOOT_SWAP_TYPE(state) == BOOT_SWAP_TYPE_REVERT) { + /* Just do revert -> copy recovery image to primary image */ + BOOT_LOG_DBG("Recover image from %s slot", + secondary == copy_state->recovery ? "secondary" : + "tertiary"); + size = BOOT_IMG_AREA(state, copy_state->recovery)->fa_size; + rc = boot_copy_region(state, fap_recovery_slot, fap_primary_slot, + 0, 0, size); + } else { + /* Do we have recovery image saved in recovery flash? */ + if (!copy_state->recovery_valid) { + boot_read_image_ok(fap_primary_slot, &image_ok); + hdr = boot_img_hdr(state, primary); + rc = boot_image_check(state, hdr, fap_primary_slot, bs); + if ((rc == 0) && (image_ok == BOOT_FLAG_SET)) { + /* Save current image as recovery only if it is valid and + * confirmed. We have to check this in case of restart + * during update process. + * If board is restarted during update, primary slot contains + * non-valid image and we do not want to copy this image to + * recovery slot. + * There also might be a case where primary image is valid + * but not confirmed (timing of board reset right after + * update is uploaded to secondary). Still we do not want + * to upload this to recovery. + */ + size = BOOT_IMG_AREA(state, BOOT_PRIMARY_SLOT)->fa_size; + + BOOT_LOG_DBG("Saving recovery image to %s slot", + secondary == copy_state->update ? "tertiary" : + "secondary"); + + /* Just copy entire recovery to primary slot */ + rc = boot_copy_region(state, fap_primary_slot, + fap_recovery_slot, 0, 0, size); + } + } + + /* Now we can start update process. First erase primary slot. + * Recovery is already saved in recovery partition, so we have the + * backup available if needed. + */ + sect_count = boot_img_num_sectors(state, primary); + for (sect = 0, size = 0; sect < sect_count; sect++) { + this_size = boot_img_sector_size(state, primary, sect); + rc = boot_erase_region(fap_primary_slot, size, this_size); + assert(rc == 0); + + size += this_size; + } + + BOOT_LOG_DBG("Copying update image from %s slot to primary slot. %s " + "slot is used as recovery", + secondary == copy_state->update ? "secondary" : + "tertiary", + secondary == copy_state->recovery ? "Secondary" : + "Tertiary"); + + /* Just copy update slot to primary slot */ + rc = boot_copy_region(state, fap_update_slot, fap_primary_slot, 0, 0, + size); + if (rc != 0) { + return rc; + } + + rc = BOOT_HOOK_CALL(boot_copy_region_post_hook, 0, + BOOT_CURR_IMG(state), + BOOT_IMG_AREA(state, primary), size); + if (rc != 0) { + return rc; + } + + /* Mark update slot as already updated. This is to prevent + * the update to trigger itself after another reboot. We do not want + * to delete entire header and tail as in swap algorithm, because + * this image will be used as recovery once primary slot is + * confirmed. + */ + rc = boot_write_image_flag(fap_update_slot, BOOT_FLAG_UPDATED); + if (rc != 0) { + return rc; + } + } + + flash_area_close(fap_primary_slot); + flash_area_close(fap_secondary_slot); + flash_area_close(fap_tertiary_slot); + + return rc; +} +#endif + +#if !defined(MCUBOOT_OVERWRITE_ONLY) && !defined(MCUBOOT_COPY_WITH_REVERT) /** * Swaps the two images in flash. If a prior copy operation was interrupted * by a system reset, this function completes that operation. @@ -1630,13 +1810,15 @@ static int boot_perform_update(struct boot_loader_state *state, struct boot_status *bs) { int rc; -#ifndef MCUBOOT_OVERWRITE_ONLY +#if !defined(MCUBOOT_OVERWRITE_ONLY) && !defined(MCUBOOT_COPY_WITH_REVERT) uint8_t swap_type; #endif /* At this point there are no aborted swaps. */ #if defined(MCUBOOT_OVERWRITE_ONLY) rc = boot_copy_image(state, bs); +#elif defined(MCUBOOT_COPY_WITH_REVERT) + rc = boot_copy_image_with_revert(state, bs); #elif defined(MCUBOOT_BOOTSTRAP) /* Check if the image update was triggered by a bad image in the * primary slot (the validity of the image in the secondary slot had @@ -1655,7 +1837,7 @@ boot_perform_update(struct boot_loader_state *state, struct boot_status *bs) #endif assert(rc == 0); -#ifndef MCUBOOT_OVERWRITE_ONLY +#if !defined(MCUBOOT_OVERWRITE_ONLY) && !defined(MCUBOOT_COPY_WITH_REVERT) /* The following state needs image_ok be explicitly set after the * swap was finished to avoid a new revert. */ @@ -1709,7 +1891,7 @@ boot_perform_update(struct boot_loader_state *state, struct boot_status *bs) * * @return 0 on success; nonzero on failure. */ -#if !defined(MCUBOOT_OVERWRITE_ONLY) +#if !defined(MCUBOOT_OVERWRITE_ONLY) && !defined(MCUBOOT_COPY_WITH_REVERT) static int boot_complete_partial_swap(struct boot_loader_state *state, struct boot_status *bs) @@ -1861,7 +2043,7 @@ boot_prepare_image_for_update(struct boot_loader_state *state, if (boot_slots_compatible(state)) { boot_status_reset(bs); -#ifndef MCUBOOT_OVERWRITE_ONLY +#if !defined(MCUBOOT_OVERWRITE_ONLY) && !defined(MCUBOOT_COPY_WITH_REVERT) rc = swap_read_status(state, bs); if (rc != 0) { BOOT_LOG_WRN("Failed reading boot status; Image=%u", @@ -1903,7 +2085,7 @@ boot_prepare_image_for_update(struct boot_loader_state *state, boot_review_image_swap_types(state, true); #endif -#ifdef MCUBOOT_OVERWRITE_ONLY +#if defined(MCUBOOT_OVERWRITE_ONLY) || defined(MCUBOOT_COPY_WITH_REVERT) /* Should never arrive here, overwrite-only mode has * no swap state. */ @@ -1924,6 +2106,13 @@ boot_prepare_image_for_update(struct boot_loader_state *state, /* Swap has finished set to NONE */ BOOT_SWAP_TYPE(state) = BOOT_SWAP_TYPE_NONE; } else { +#ifdef MCUBOOT_COPY_WITH_REVERT + rc = copy_get_slot_type(state); + if (rc != 0) { + BOOT_LOG_WRN("Failed to get information about slots\n"); + return; + } +#endif /* There was no partial swap, determine swap type. */ if (bs->swap_type == BOOT_SWAP_TYPE_NONE) { BOOT_SWAP_TYPE(state) = boot_validated_swap_type(state, bs); @@ -2094,6 +2283,9 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp) */ TARGET_STATIC boot_sector_t primary_slot_sectors[BOOT_IMAGE_NUMBER][BOOT_MAX_IMG_SECTORS]; TARGET_STATIC boot_sector_t secondary_slot_sectors[BOOT_IMAGE_NUMBER][BOOT_MAX_IMG_SECTORS]; +#ifdef MCUBOOT_COPY_WITH_REVERT + TARGET_STATIC boot_sector_t tertiary_slot_sectors[BOOT_IMAGE_NUMBER][BOOT_MAX_IMG_SECTORS]; +#endif #if MCUBOOT_SWAP_USING_SCRATCH TARGET_STATIC boot_sector_t scratch_sectors[BOOT_MAX_IMG_SECTORS]; #endif @@ -2128,12 +2320,16 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp) primary_slot_sectors[image_index]; BOOT_IMG(state, BOOT_SECONDARY_SLOT).sectors = secondary_slot_sectors[image_index]; +#ifdef MCUBOOT_COPY_WITH_REVERT + BOOT_IMG(state, BOOT_TERTIARY_SLOT).sectors = + tertiary_slot_sectors[image_index]; +#endif #if MCUBOOT_SWAP_USING_SCRATCH state->scratch.sectors = scratch_sectors; #endif - /* Open primary and secondary image areas for the duration - * of this call. + /* Open primary secondary and tertiary (if used) image areas for the + * duration of this call. */ for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) { fa_id = flash_area_id_from_multi_image_slot(image_index, slot); @@ -2145,6 +2341,7 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp) "cannot continue", fa_id, image_index, (int8_t)slot, rc); FIH_PANIC; } + } #if MCUBOOT_SWAP_USING_SCRATCH rc = flash_area_open(FLASH_AREA_IMAGE_SCRATCH, @@ -2210,7 +2407,6 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp) /* Set the previously determined swap type */ bs.swap_type = BOOT_SWAP_TYPE(state); - switch (BOOT_SWAP_TYPE(state)) { case BOOT_SWAP_TYPE_NONE: break; @@ -2240,7 +2436,7 @@ context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp) * we don't try to boot into it again on the next reboot. Do this by * pretending we just reverted back to primary slot. */ -#ifndef MCUBOOT_OVERWRITE_ONLY +#if !defined(MCUBOOT_OVERWRITE_ONLY) && !defined(MCUBOOT_COPY_WITH_REVERT) /* image_ok needs to be explicitly set to avoid a new revert. */ rc = swap_set_image_ok(BOOT_CURR_IMG(state)); if (rc != 0) { diff --git a/boot/bootutil/src/swap_scratch.c b/boot/bootutil/src/swap_scratch.c index 524a2971a..523b3fb8a 100644 --- a/boot/bootutil/src/swap_scratch.c +++ b/boot/bootutil/src/swap_scratch.c @@ -30,7 +30,7 @@ BOOT_LOG_MODULE_DECLARE(mcuboot); -#if !defined(MCUBOOT_SWAP_USING_MOVE) +#if !defined(MCUBOOT_SWAP_USING_MOVE) && !defined(MCUBOOT_COPY_WITH_REVERT) #if defined(MCUBOOT_VALIDATE_PRIMARY_SLOT) /* From b40708062e6202f28f7012522323ac699ab400d8 Mon Sep 17 00:00:00 2001 From: Michal Lenc Date: Tue, 20 Feb 2024 11:03:19 +0100 Subject: [PATCH 3/4] docs: add entry for new copy with revert algorithm This commit adds documentation entry for new algorithm. Signed-off-by: Michal Lenc --- docs/design.md | 80 +++++++++++++++++++++++++++++++++++++++++++- docs/readme-nuttx.md | 1 + 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/docs/design.md b/docs/design.md index 8539ae0d7..73b444b83 100755 --- a/docs/design.md +++ b/docs/design.md @@ -4,6 +4,7 @@ - Copyright (c) 2017-2020 Linaro LTD - Copyright (c) 2017-2019 JUUL Labs - Copyright (c) 2019-2024 Arm Limited + - Copyright (c) 2024 Elektroline Inc. - Original license: @@ -165,6 +166,18 @@ images therefore the flash area IDs of primary and secondary areas are mapped based on the number of the active image (on which the bootloader is currently working). +Most of the algorithms use just primary and secondary ares, but algorithm +with tertiary area might also be used. This algorithm and its functionality +is described in subsequent sections. Definition of scratch area is not +required in this case, therefore flash area IDs would be defined as: + +```c +#define FLASH_AREA_BOOTLOADER 0 +#define FLASH_AREA_IMAGE_PRIMARY 1 +#define FLASH_AREA_IMAGE_SECONDARY 2 +#define FLASH_AREA_IMAGE_TERTIARY 3 +``` + ## [Image slots](#image-slots) A portion of the flash memory can be partitioned into multiple image areas, each @@ -175,7 +188,7 @@ images must be built such that they can run from that fixed location in flash [ram-load](#ram-load) upgrade mode). If the bootloader needs to run the image resident in the secondary slot, it must copy its contents into the primary slot before doing so, either by swapping the two images or by overwriting the -contents of the primary slot. The bootloader supports either swap- or +contents of the primary slot. The bootloader supports either swap-, copy- or overwrite-based image upgrades, but must be configured at build time to choose one of these two strategies. @@ -276,6 +289,71 @@ leveling the flash wear between the slots. The algorithm is enabled using the `MCUBOOT_SWAP_USING_MOVE` option. +### [Copy with three partitions and revert](#copy-with-revert) + +Swap based algorithms described above have to swap two images between flash +partitions. If secondary partition is on external flash (NOR for example), the +longer erase and write times may prolong the boot process. Also having the +secondary partition on external flash usually means slower +[swap-using-scratch](#image-swap-using-scratch) algorithm has to be used as +sector size of embedded and external flash is diffent. As a result, booting +larger image from external flash may take up to several minutes. + +However, there is a way to speed up the process for devices with spare space +on external flash. This algorithm requirest three flash partitions; primary, +secondary and tertiary. This algorithm do not swap images but rather just copy +them to primary slot from either secondary or tertiary slot while keeping the +possibility of revert if image is not confirmed. Therefore all writes and +erases during boot process are only to embedded flash and thus the process is +not prolonged by external flash latencies. The algorithm works as follows: + + 1. Check secondary and tertiary partition if update image and valid recovery + image are present. + 2. If valid recovery is not present, copy image from primary to either. + secondary or tertiary (based on where update image is not located) + 3. Erase primary slot. + 4. Copy update image to primary slot. + 5. During image confirm, mark both primary and secondary or tertiary (the + one with update image) as confirmed. Mark old recovery partition as + ready for update. New image will be uploaded to this partition while the + other one will be used as recovery. + +Image is copied from primary partition to tertiary or secondary partition only +if recovery is not present there. In reality, this is only for the first update +(or if user' application manually erases recovery slot), therefore all +subsequent update processes will be as fast as overwrite only algorithm. + +The bootloader has to check secondary and tertiary partition during each boot +and determine based on present images which partition is used as recovery and +update. Also the user application has to retrieve this information in order +to save new image to the correct partition. This can be done by following +function: + +```c +int +boot_read_copy_state(int image_index, struct boot_copy_state *state) +``` + +where state structure is defined as: + +```c +struct boot_copy_state { + int update; + int recovery; + bool recovery_valid; +}; +``` + +Recovery valid field is true if there is a valid recovery present on either +secondary or tertiary partition. + +This algorithm may speed up boot process by considerable amount of time, +depending on write/erase times difference between external flash and +embedded flash. Morover, the entire process of saving swap info to image +tail is ommited here as the algorithm is basically just copy of entire +partition to another slot. Update process starts from the begining if +it is interrupted by power reset. + ### [Equal slots (direct-xip)](#direct-xip) When the direct-xip mode is enabled the active image flag is "moved" between the diff --git a/docs/readme-nuttx.md b/docs/readme-nuttx.md index 1c069640c..ab9c357a0 100644 --- a/docs/readme-nuttx.md +++ b/docs/readme-nuttx.md @@ -5,6 +5,7 @@ The NuttX port of MCUboot secure boot library expects that the platform provides a Flash storage with the following partitions: - `CONFIG_MCUBOOT_PRIMARY_SLOT_PATH`: MTD partition for the application firmware image PRIMARY slot; - `CONFIG_MCUBOOT_SECONDARY_SLOT_PATH`: MTD partition for the application firmware image SECONDARY slot; +- `CONFIG_MCUBOOT_TERTIARY_SLOT_PATH`: MTD partition for the application firmware image TERTIARY slot if copy with revert algorithm is used; - `CONFIG_MCUBOOT_SCRATCH_PATH`: MTD partition for the Scratch area; Also, these are optional features that may be enabled: From 8110ce55fd437a57684b6472cb496f206eb1592f Mon Sep 17 00:00:00 2001 From: Michal Lenc Date: Tue, 20 Feb 2024 11:05:52 +0100 Subject: [PATCH 4/4] boot/nuttx: add configuration for tertiary slot This slot is used in newly introduced copy with revert algorithm. This commit allows NuttX to support this algorithm. Signed-off-by: Michal Lenc --- .../include/mcuboot_config/mcuboot_config.h | 10 +++++++ boot/nuttx/include/sysflash/sysflash.h | 6 +++- .../src/flash_map_backend/flash_map_backend.c | 28 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/boot/nuttx/include/mcuboot_config/mcuboot_config.h b/boot/nuttx/include/mcuboot_config/mcuboot_config.h index 41e7d6742..585bd0410 100644 --- a/boot/nuttx/include/mcuboot_config/mcuboot_config.h +++ b/boot/nuttx/include/mcuboot_config/mcuboot_config.h @@ -78,6 +78,16 @@ # define MCUBOOT_OVERWRITE_ONLY_FAST #endif +/* Enable copy with revert algorithm. This algorithm uses three slots and + * always keep the current image in both primary and secondary or tertiary + * partitions. This way update can be done without swapping while keeping + * the ability to revert. + */ + +#ifdef CONFIG_MCUBOOT_COPY_WITH_REVERT +# define MCUBOOT_COPY_WITH_REVERT +#endif + /* Enable the direct-xip code path. */ #ifdef CONFIG_MCUBOOT_DIRECT_XIP diff --git a/boot/nuttx/include/sysflash/sysflash.h b/boot/nuttx/include/sysflash/sysflash.h index 304ca0c1a..91ad07275 100644 --- a/boot/nuttx/include/sysflash/sysflash.h +++ b/boot/nuttx/include/sysflash/sysflash.h @@ -26,7 +26,8 @@ #define PRIMARY_ID 0 #define SECONDARY_ID 1 -#define SCRATCH_ID 2 +#define TERTIARY_ID 2 +#define SCRATCH_ID 3 #define FLASH_AREA_IMAGE_PRIMARY(x) (((x) == 0) ? \ PRIMARY_ID : \ @@ -34,6 +35,9 @@ #define FLASH_AREA_IMAGE_SECONDARY(x) (((x) == 0) ? \ SECONDARY_ID : \ SECONDARY_ID) +#define FLASH_AREA_IMAGE_TERTIARY(x) (((x) == 0) ? \ + TERTIARY_ID : \ + TERTIARY_ID) #define FLASH_AREA_IMAGE_SCRATCH SCRATCH_ID #endif /* __BOOT_NUTTX_INCLUDE_SYSFLASH_SYSFLASH_H */ diff --git a/boot/nuttx/src/flash_map_backend/flash_map_backend.c b/boot/nuttx/src/flash_map_backend/flash_map_backend.c index ae3ec6488..575d907d9 100644 --- a/boot/nuttx/src/flash_map_backend/flash_map_backend.c +++ b/boot/nuttx/src/flash_map_backend/flash_map_backend.c @@ -123,6 +123,31 @@ static struct flash_device_s g_secondary_priv = .erase_state = CONFIG_MCUBOOT_DEFAULT_FLASH_ERASE_STATE }; +static struct flash_area g_tertiary_img0 = +{ + .fa_id = FLASH_AREA_IMAGE_TERTIARY(0), + .fa_device_id = 0, + .fa_off = 0, + .fa_size = 0, + .fa_mtd_path = CONFIG_MCUBOOT_TERTIARY_SLOT_PATH +}; + +static struct flash_device_s g_tertiary_priv = +{ + .fa_cfg = &g_tertiary_img0, + .mtdgeo = + { + 0 + }, + .partinfo = + { + 0 + }, + .fd = -1, + .refs = 0, + .erase_state = CONFIG_MCUBOOT_DEFAULT_FLASH_ERASE_STATE +}; + static struct flash_area g_scratch_img0 = { .fa_id = FLASH_AREA_IMAGE_SCRATCH, @@ -152,6 +177,7 @@ static struct flash_device_s *g_flash_devices[] = { &g_primary_priv, &g_secondary_priv, + &g_tertiary_priv, &g_scratch_priv, }; @@ -713,6 +739,8 @@ int flash_area_id_from_multi_image_slot(int image_index, int slot) return FLASH_AREA_IMAGE_PRIMARY(image_index); case 1: return FLASH_AREA_IMAGE_SECONDARY(image_index); + case 2: + return FLASH_AREA_IMAGE_TERTIARY(image_index); } BOOT_LOG_ERR("Unexpected Request: image_index:%d, slot:%d",