From 0e8cc3e52df98d37169225769f923fd3eb75dd56 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Mon, 13 Jan 2025 10:52:57 +0000 Subject: [PATCH] dfu: Add support for new MCUboot swap using offset mode Allows using this newly introduced MCUboot algorithm Signed-off-by: Jamie McCrae --- include/zephyr/dfu/mcuboot.h | 16 +++ modules/Kconfig.mcuboot | 9 ++ subsys/dfu/boot/mcuboot.c | 89 +++++++++++++-- subsys/dfu/img_util/flash_img.c | 102 +++++++++++++++++- .../mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c | 9 +- .../mcumgr/grp/img_mgmt/src/zephyr_img_mgmt.c | 19 ++-- subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c | 2 + 7 files changed, 224 insertions(+), 22 deletions(-) diff --git a/include/zephyr/dfu/mcuboot.h b/include/zephyr/dfu/mcuboot.h index 8788ec4657e..efd8c83b26b 100644 --- a/include/zephyr/dfu/mcuboot.h +++ b/include/zephyr/dfu/mcuboot.h @@ -81,6 +81,8 @@ extern "C" { #define BOOT_IMG_VER_STRLEN_MAX 25 /* 255.255.65535.4294967295\0 */ +/** Sector at which firmware update should be placed by application in swap using offset mode */ +#define SWAP_USING_OFFSET_SECTOR_UPDATE_BEGIN 1 /** * @brief MCUboot image header representation for image version @@ -287,6 +289,20 @@ ssize_t boot_get_area_trailer_status_offset(uint8_t area_id); */ ssize_t boot_get_trailer_status_offset(size_t area_size); +#if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET) || defined(__DOXYGEN__) +/** + * @brief Get the offset of the image header, this should be used in swap using offset mode to + * account for the secondary slot data starting in the first or second sector, depending + * upon the current state + * + * @param area_id flash_area ID of image bank to get the status offset + * @return offset of the image header + */ +size_t boot_get_image_start_offset(uint8_t area_id); +#else +#define boot_get_image_start_offset(...) 0 +#endif + #ifdef __cplusplus } #endif diff --git a/modules/Kconfig.mcuboot b/modules/Kconfig.mcuboot index 865f574ce71..73a6346f89b 100644 --- a/modules/Kconfig.mcuboot +++ b/modules/Kconfig.mcuboot @@ -149,6 +149,15 @@ config MCUBOOT_BOOTLOADER_MODE_SINGLE_APP to DFU its own update to secondary slot and all updates need to be performed using MCUboot serial recovery. +config MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET + bool "MCUboot has been configured for swap using offset operation" + select MCUBOOT_BOOTLOADER_MODE_HAS_NO_DOWNGRADE + help + MCUboot expects slot0_partition and slot1_partition to be present + in DT and application will boot from slot0_partition. + MCUBOOT_BOOTLOADER_NO_DOWNGRADE should also be selected + if MCUboot has been built with MCUBOOT_DOWNGRADE_PREVENTION. + config MCUBOOT_BOOTLOADER_MODE_SWAP_USING_MOVE bool "MCUboot has been configured for swap using move operation" select MCUBOOT_BOOTLOADER_MODE_HAS_NO_DOWNGRADE diff --git a/subsys/dfu/boot/mcuboot.c b/subsys/dfu/boot/mcuboot.c index a23c82dce75..5683d23a87a 100644 --- a/subsys/dfu/boot/mcuboot.c +++ b/subsys/dfu/boot/mcuboot.c @@ -42,6 +42,13 @@ LOG_MODULE_REGISTER(mcuboot_dfu, LOG_LEVEL_DBG); #define BOOT_HEADER_MAGIC_V1 0x96f3b83d #define BOOT_HEADER_SIZE_V1 32 +enum IMAGE_INDEXES { + IMAGE_INDEX_INVALID = -1, + IMAGE_INDEX_0, + IMAGE_INDEX_1, + IMAGE_INDEX_2 +}; + #if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD) /* For RAM LOAD mode, the active image must be fetched from the bootloader */ #define ACTIVE_SLOT_FLASH_AREA_ID boot_fetch_active_slot() @@ -99,11 +106,69 @@ uint8_t boot_fetch_active_slot(void) } #endif /* CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD */ +#if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET) +size_t boot_get_image_start_offset(uint8_t area_id) +{ + size_t off = 0; + int image = IMAGE_INDEX_INVALID; + + if (area_id == FIXED_PARTITION_ID(slot1_partition)) { + image = IMAGE_INDEX_0; +#if FIXED_PARTITION_EXISTS(slot3_partition) + } else if (area_id == FIXED_PARTITION_ID(slot3_partition)) { + image = IMAGE_INDEX_1; +#endif +#if FIXED_PARTITION_EXISTS(slot5_partition) + } else if (area_id == FIXED_PARTITION_ID(slot5_partition)) { + image = IMAGE_INDEX_2; +#endif + } + + if (image != IMAGE_INDEX_INVALID) { + /* Need to check status from primary slot to get correct offset for secondary + * slot image header + */ + const struct flash_area *fa; + uint32_t num_sectors = SWAP_USING_OFFSET_SECTOR_UPDATE_BEGIN; + struct flash_sector sector_data; + int rc; + + rc = flash_area_open(area_id, &fa); + if (rc) { + LOG_ERR("Flash open area %u failed: %d", area_id, rc); + goto done; + } + + if (mcuboot_swap_type_multi(image) != BOOT_SWAP_TYPE_REVERT) { + /* For swap using offset mode, the image starts in the second sector of + * the upgrade slot, so apply the offset when this is needed, do this by + * getting information on first sector only, this is expected to return an + * error as there are more slots, so allow the not enough memory error + */ + rc = flash_area_get_sectors(area_id, &num_sectors, §or_data); + if ((rc != 0 && rc != -ENOMEM) || + num_sectors != SWAP_USING_OFFSET_SECTOR_UPDATE_BEGIN) { + LOG_ERR("Failed to get sector details: %d", rc); + } else { + off = sector_data.fs_size; + } + } + + flash_area_close(fa); + } + +done: + LOG_DBG("Start offset for area %u: 0x%x", area_id, off); + return off; +} +#endif /* CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET */ + static int boot_read_v1_header(uint8_t area_id, struct mcuboot_v1_raw_header *v1_raw) { const struct flash_area *fa; int rc; + size_t off = boot_get_image_start_offset(area_id); rc = flash_area_open(area_id, &fa); if (rc) { @@ -111,27 +176,19 @@ static int boot_read_v1_header(uint8_t area_id, } /* - * Read and sanity-check the raw header. + * Read and validty-check the raw header. */ - rc = flash_area_read(fa, 0, v1_raw, sizeof(*v1_raw)); + rc = flash_area_read(fa, off, v1_raw, sizeof(*v1_raw)); flash_area_close(fa); if (rc) { return rc; } v1_raw->header_magic = sys_le32_to_cpu(v1_raw->header_magic); - v1_raw->image_load_address = - sys_le32_to_cpu(v1_raw->image_load_address); v1_raw->header_size = sys_le16_to_cpu(v1_raw->header_size); - v1_raw->image_size = sys_le32_to_cpu(v1_raw->image_size); - v1_raw->image_flags = sys_le32_to_cpu(v1_raw->image_flags); - v1_raw->version.revision = - sys_le16_to_cpu(v1_raw->version.revision); - v1_raw->version.build_num = - sys_le32_to_cpu(v1_raw->version.build_num); /* - * Sanity checks. + * Validity checks. * * Larger values in header_size than BOOT_HEADER_SIZE_V1 are * possible, e.g. if Zephyr was linked with @@ -142,6 +199,16 @@ static int boot_read_v1_header(uint8_t area_id, return -EIO; } + v1_raw->image_load_address = + sys_le32_to_cpu(v1_raw->image_load_address); + v1_raw->header_size = sys_le16_to_cpu(v1_raw->header_size); + v1_raw->image_size = sys_le32_to_cpu(v1_raw->image_size); + v1_raw->image_flags = sys_le32_to_cpu(v1_raw->image_flags); + v1_raw->version.revision = + sys_le16_to_cpu(v1_raw->version.revision); + v1_raw->version.build_num = + sys_le32_to_cpu(v1_raw->version.build_num); + return 0; } diff --git a/subsys/dfu/img_util/flash_img.c b/subsys/dfu/img_util/flash_img.c index 31e65c4e30c..8f287404da3 100644 --- a/subsys/dfu/img_util/flash_img.c +++ b/subsys/dfu/img_util/flash_img.c @@ -1,19 +1,24 @@ /* - * Copyright (c) 2017, 2020 Nordic Semiconductor ASA + * Copyright (c) 2017-2025 Nordic Semiconductor ASA * Copyright (c) 2017 Linaro Limited * Copyright (c) 2020 Gerson Fernando Budke * * SPDX-License-Identifier: Apache-2.0 */ #include +#include #include #include #include +#include +#include #include #include #include #include +LOG_MODULE_REGISTER(flash_img, CONFIG_IMG_MANAGER_LOG_LEVEL); + #ifdef CONFIG_IMG_ERASE_PROGRESSIVELY #include #endif @@ -48,6 +53,9 @@ BUILD_ASSERT((CONFIG_IMG_BLOCK_BUF_SIZE % FLASH_WRITE_BLOCK_SIZE == 0), "FLASH_WRITE_BLOCK_SIZE"); #endif +#define FLASH_CHECK_ERASED_BUFFER_SIZE 16 +#define ERASED_VAL_32(x) (((x) << 24) | ((x) << 16) | ((x) << 8) | (x)) + static int scramble_mcuboot_trailer(struct flash_img_context *ctx) { int rc = 0; @@ -132,10 +140,64 @@ size_t flash_img_bytes_written(struct flash_img_context *ctx) return stream_flash_bytes_written(&ctx->stream); } +#if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET) +/** + * Determines if the specified area of flash is completely unwritten. + * + * @param fa pointer to flash area to scan + * + * @return 0 when not empty, 1 when empty, negative errno code on error. + */ +static int flash_check_erased(const struct flash_area *fa) +{ + uint32_t data[FLASH_CHECK_ERASED_BUFFER_SIZE]; + off_t addr; + off_t end; + int bytes_to_read; + int rc; + int i; + uint8_t erased_val; + uint32_t erased_val_32; + + assert(fa->fa_size % sizeof(erased_val_32) == 0); + + erased_val = flash_area_erased_val(fa); + erased_val_32 = ERASED_VAL_32(erased_val); + + end = fa->fa_size; + for (addr = 0; addr < end; addr += sizeof(data)) { + if (end - addr < sizeof(data)) { + bytes_to_read = end - addr; + } else { + bytes_to_read = sizeof(data); + } + + rc = flash_area_read(fa, addr, data, bytes_to_read); + + if (rc < 0) { + LOG_ERR("Failed to read data from flash area: %d", rc); + return rc; + } + + for (i = 0; i < bytes_to_read / sizeof(erased_val_32); i++) { + if (data[i] != erased_val_32) { + return 0; + } + } + } + + return 1; +} +#endif + int flash_img_init_id(struct flash_img_context *ctx, uint8_t area_id) { int rc; const struct device *flash_dev; +#if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET) + uint32_t sector_count = SWAP_USING_OFFSET_SECTOR_UPDATE_BEGIN; + struct flash_sector sector_data; +#endif rc = flash_area_open(area_id, (const struct flash_area **)&(ctx->flash_area)); @@ -145,9 +207,45 @@ int flash_img_init_id(struct flash_img_context *ctx, uint8_t area_id) flash_dev = flash_area_get_device(ctx->flash_area); +#if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET) + /* Query size of first sector in flash for upgrade slot, so it can be erased, and begin + * upload started at the second sector + */ + rc = flash_area_sectors((const struct flash_area *)ctx->flash_area, §or_count, + §or_data); + + if (rc && rc != -ENOMEM) { + flash_area_close(ctx->flash_area); + ctx->flash_area = NULL; + return rc; + } else if (sector_count != SWAP_USING_OFFSET_SECTOR_UPDATE_BEGIN) { + flash_area_close(ctx->flash_area); + ctx->flash_area = NULL; + return -ENOENT; + } + + if (!flash_check_erased((const struct flash_area *)ctx->flash_area)) { + /* Flash is not empty, therefore flatten/erase the area to prevent issues when + * the firmware update process begins + */ + rc = flash_area_flatten((const struct flash_area *)ctx->flash_area, 0, + sector_data.fs_size); + + if (rc) { + flash_area_close(ctx->flash_area); + ctx->flash_area = NULL; + return rc; + } + } + + return stream_flash_init(&ctx->stream, flash_dev, ctx->buf, CONFIG_IMG_BLOCK_BUF_SIZE, + (ctx->flash_area->fa_off + sector_data.fs_size), + (ctx->flash_area->fa_size - sector_data.fs_size), NULL); +#else return stream_flash_init(&ctx->stream, flash_dev, ctx->buf, CONFIG_IMG_BLOCK_BUF_SIZE, ctx->flash_area->fa_off, ctx->flash_area->fa_size, NULL); +#endif } #ifdef CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD @@ -194,7 +292,7 @@ int flash_img_check(struct flash_img_context *ctx, fac.match = fic->match; fac.clen = fic->clen; - fac.off = 0; + fac.off = boot_get_image_start_offset(area_id); fac.rbuf = ctx->buf; fac.rblen = sizeof(ctx->buf); diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c index 3c8995cd9e3..a5a06e3d9f5 100644 --- a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -262,7 +263,10 @@ int img_mgmt_read_info(int image_slot, struct image_version *ver, uint8_t *hash, return IMG_MGMT_ERR_FLASH_CONFIG_QUERY_FAIL; } - rc = img_mgmt_read(image_slot, 0, &hdr, sizeof(hdr)); + rc = img_mgmt_read(image_slot, + boot_get_image_start_offset(img_mgmt_flash_area_id(image_slot)), + &hdr, sizeof(hdr)); + if (rc != 0) { return rc; } @@ -290,7 +294,8 @@ int img_mgmt_read_info(int image_slot, struct image_version *ver, uint8_t *hash, * TLV. All images are required to have a hash TLV. If the hash is missing, the image * is considered invalid. */ - data_off = hdr.ih_hdr_size + hdr.ih_img_size; + data_off = hdr.ih_hdr_size + hdr.ih_img_size + + boot_get_image_start_offset(img_mgmt_flash_area_id(image_slot)); rc = img_mgmt_find_tlvs(image_slot, &data_off, &data_end, IMAGE_TLV_PROT_INFO_MAGIC); if (!rc) { diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/src/zephyr_img_mgmt.c b/subsys/mgmt/mcumgr/grp/img_mgmt/src/zephyr_img_mgmt.c index 1b19fc101e0..63900f4250c 100644 --- a/subsys/mgmt/mcumgr/grp/img_mgmt/src/zephyr_img_mgmt.c +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/src/zephyr_img_mgmt.c @@ -441,6 +441,10 @@ int img_mgmt_erase_image_data(unsigned int off, unsigned int num_bytes) { const struct flash_area *fa; int rc; + const struct device *dev; + struct flash_pages_info page; + off_t page_offset; + size_t erase_size; if (off != 0) { rc = IMG_MGMT_ERR_INVALID_OFFSET; @@ -454,16 +458,15 @@ int img_mgmt_erase_image_data(unsigned int off, unsigned int num_bytes) goto end; } - /* align requested erase size to the erase-block-size */ - const struct device *dev = flash_area_get_device(fa); + /* Align requested erase size to the erase-block-size */ + dev = flash_area_get_device(fa); if (dev == NULL) { rc = IMG_MGMT_ERR_FLASH_AREA_DEVICE_NULL; goto end_fa; } - struct flash_pages_info page; - off_t page_offset = fa->fa_off + num_bytes - 1; + page_offset = fa->fa_off + num_bytes - 1; rc = flash_get_page_info_by_offs(dev, page_offset, &page); if (rc != 0) { LOG_ERR("bad offset (0x%lx)", (long)page_offset); @@ -471,9 +474,9 @@ int img_mgmt_erase_image_data(unsigned int off, unsigned int num_bytes) goto end_fa; } - size_t erase_size = page.start_offset + page.size - fa->fa_off; - - rc = flash_area_flatten(fa, 0, erase_size); + erase_size = page.start_offset + page.size - fa->fa_off; + rc = flash_area_flatten(fa, boot_get_image_start_offset(g_img_mgmt_state.area_id), + erase_size); if (rc != 0) { LOG_ERR("image slot erase of 0x%zx bytes failed (err %d)", erase_size, @@ -568,6 +571,7 @@ int img_mgmt_upload_inspect(const struct img_mgmt_upload_req *req, const struct flash_area *fa; #if defined(CONFIG_MCUMGR_GRP_IMG_TOO_LARGE_SYSBUILD) && \ (defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_WITHOUT_SCRATCH) || \ + defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET) || \ defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_MOVE) || \ defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_SCRATCH) || \ defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_OVERWRITE_ONLY) || \ @@ -645,6 +649,7 @@ int img_mgmt_upload_inspect(const struct img_mgmt_upload_req *req, #if defined(CONFIG_MCUMGR_GRP_IMG_TOO_LARGE_SYSBUILD) && \ (defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_WITHOUT_SCRATCH) || \ + defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET) || \ defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_MOVE) || \ defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_SCRATCH) || \ defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_OVERWRITE_ONLY) || \ diff --git a/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c b/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c index c26a5b18d55..3fc4d42cfe1 100644 --- a/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c +++ b/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c @@ -430,6 +430,8 @@ os_mgmt_mcumgr_params(struct smp_streamer *ctxt) #define BOOTLOADER_MODE MCUBOOT_MODE_SWAP_USING_SCRATCH #elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_OVERWRITE_ONLY) #define BOOTLOADER_MODE MCUBOOT_MODE_UPGRADE_ONLY +#elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_OFFSET) +#define BOOTLOADER_MODE MCUBOOT_MODE_SWAP_USING_OFFSET #elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_MOVE) || \ defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_WITHOUT_SCRATCH) #define BOOTLOADER_MODE MCUBOOT_MODE_SWAP_USING_MOVE