diff --git a/boot/bootutil/zephyr/CMakeLists.txt b/boot/bootutil/zephyr/CMakeLists.txt index 2823900e0..2a3cd0014 100644 --- a/boot/bootutil/zephyr/CMakeLists.txt +++ b/boot/bootutil/zephyr/CMakeLists.txt @@ -17,9 +17,15 @@ zephyr_library_sources( ../src/bootutil_public.c ) if(CONFIG_NRF_MCUBOOT_BOOT_REQUEST) + zephyr_library_sources( + src/boot_request.c + ) zephyr_library_sources_ifdef(CONFIG_NRF_MCUBOOT_BOOT_REQUEST_IMPL_RETENTION src/boot_request_retention.c ) + zephyr_library_sources_ifdef(CONFIG_NRF_MCUBOOT_BOOT_REQUEST_IMPL_FLASH + src/boot_request_flash.c + ) endif() zephyr_library_sources_ifdef(CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES ../src/mcuboot_manifest.c diff --git a/boot/bootutil/zephyr/src/boot_request.c b/boot/bootutil/zephyr/src/boot_request.c new file mode 100644 index 000000000..d283e4771 --- /dev/null +++ b/boot/bootutil/zephyr/src/boot_request.c @@ -0,0 +1,378 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2025 Nordic Semiconductor ASA + */ +#include + +#include "bootutil/bootutil_log.h" +#include "boot_request_mem.h" + +/** Special value of image number, indicating a request to the bootloader. */ +#define BOOT_REQUEST_IMG_BOOTLOADER 0xFF + +/** Additional memory used by the retention subsystem (2B - prefix, 4B - CRC).*/ +#define BOOT_REQUEST_ENTRY_METADATA_SIZE (2 + 4) + +/** Number of images supported by the bootloader requests. */ +#define BOOT_REQUEST_IMG_NUM 2 + +BOOT_LOG_MODULE_REGISTER(bootloader_request); + +enum boot_request_type { + /** Invalid request. */ + BOOT_REQUEST_INVALID = 0, + + /** Request a change in the bootloader boot mode. + * + * @details Use @p boot_request_mode as argument. + * @p BOOT_REQUEST_IMG_BOOTLOADER as image number. + * + * @note Used to trigger recovery through i.e. retention sybsystem. + */ + BOOT_REQUEST_BOOT_MODE = 1, + + /** Select the preferred image to be selected during boot or update. + * + * @details Use @p boot_request_slot_t as argument. + * + * @note Used in the Direct XIP mode. + */ + BOOT_REQUEST_IMG_PREFERENCE = 2, + + /** Request a confirmation of an image. + * + * @details Use @p boot_request_slot_t as argument. + * + * @note Used if the code cannot modify the image trailer directly. + */ + BOOT_REQUEST_IMG_CONFIRM = 3, +}; + +/* Entries inside the boot request shared memory. */ +enum boot_request_entry { + BOOT_REQUEST_ENTRY_BOOT_MODE = 0, + BOOT_REQUEST_ENTRY_IMAGE_0_PREFERENCE = 1, + BOOT_REQUEST_ENTRY_IMAGE_0_CONFIRM = 2, + BOOT_REQUEST_ENTRY_IMAGE_1_PREFERENCE = 3, + BOOT_REQUEST_ENTRY_IMAGE_1_CONFIRM = 4, + BOOT_REQUEST_ENTRY_MAX = 5, +}; + +/* Assert that all requests will fit within the retention area. */ +BUILD_ASSERT((BOOT_REQUEST_ENTRY_METADATA_SIZE + BOOT_REQUEST_ENTRY_MAX * sizeof(uint8_t)) < + DT_REG_SIZE_BY_IDX(DT_CHOSEN(nrf_bootloader_request), 0), + "nrf,bootloader-request area is too small for bootloader request struct"); + +enum boot_request_slot { + /** Unsupported value. */ + BOOT_REQUEST_SLOT_INVALID = 0, + /** Primary slot. */ + BOOT_REQUEST_SLOT_PRIMARY = 1, + /** Secondary slot. */ + BOOT_REQUEST_SLOT_SECONDARY = 2, +}; + +/** Alias type for the image and number. */ +typedef uint8_t boot_request_slot_t; + +enum boot_request_mode { + /** Execute a regular boot logic. */ + BOOT_REQUEST_MODE_REGULAR = 0, + /** Execute the recovery boot logic. */ + BOOT_REQUEST_MODE_RECOVERY = 1, + /** Execute the firmware loader logic. */ + BOOT_REQUEST_MODE_FIRMWARE_LOADER = 2, + /** Unsupported value. */ + BOOT_REQUEST_MODE_INVALID = 0xFF, +}; + +/** Alias type for the image number. */ +typedef uint8_t boot_request_img_t; + +/** + * @brief Find an entry for a given request. + * + * @param[in] type Type of request. + * @param[in] image Image number. Use @p BOOT_REQUEST_IMG_BOOTLOADER for generic requests. + * @param[out] entry Entry to use. + * + * @return 0 on success; nonzero on failure. + */ +static int boot_request_entry_find(enum boot_request_type type, boot_request_img_t image, + size_t *entry) +{ + if (entry == NULL) { + return -EINVAL; + } + + switch (type) { + case BOOT_REQUEST_BOOT_MODE: + *entry = BOOT_REQUEST_ENTRY_BOOT_MODE; + break; + case BOOT_REQUEST_IMG_PREFERENCE: + switch (image) { + case 0: + *entry = BOOT_REQUEST_ENTRY_IMAGE_0_PREFERENCE; + break; + case 1: + *entry = BOOT_REQUEST_ENTRY_IMAGE_1_PREFERENCE; + break; + default: + return -EINVAL; + } + break; + case BOOT_REQUEST_IMG_CONFIRM: + switch (image) { + case 0: + *entry = BOOT_REQUEST_ENTRY_IMAGE_0_CONFIRM; + break; + case 1: + *entry = BOOT_REQUEST_ENTRY_IMAGE_1_CONFIRM; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +int boot_request_init(void) +{ + return boot_request_mem_init(); +} + +int boot_request_clear(void) +{ +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + size_t nv_indexes[BOOT_REQUEST_IMG_NUM]; + uint8_t image; + + for (image = 0; image < BOOT_REQUEST_IMG_NUM; image++) { + if (boot_request_entry_find(BOOT_REQUEST_IMG_PREFERENCE, image, + &nv_indexes[image]) != 0) { + return -EINVAL; + } + } + + return boot_request_mem_selective_erase(nv_indexes, BOOT_REQUEST_IMG_NUM); +#else + return boot_request_mem_selective_erase(NULL, 0); +#endif +} + +int boot_request_confirm_slot(uint8_t image, enum boot_slot slot) +{ + uint8_t value = BOOT_REQUEST_SLOT_INVALID; + size_t req_entry; + int ret; + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + if (!boot_request_mem_updateable()) { + BOOT_LOG_ERR("Unable to confirm slot - area not updateable."); + /* Cannot update area if it is corrupted. */ + return -EIO; + } +#endif + + ret = boot_request_entry_find(BOOT_REQUEST_IMG_CONFIRM, image, &req_entry); + if (ret != 0) { + return ret; + } + + switch (slot) { + case BOOT_SLOT_PRIMARY: + value = BOOT_REQUEST_SLOT_PRIMARY; + break; + case BOOT_SLOT_SECONDARY: + value = BOOT_REQUEST_SLOT_SECONDARY; + break; + default: + return -EINVAL; + } + + return boot_request_mem_write(req_entry * sizeof(value), &value); +} + +bool boot_request_check_confirmed_slot(uint8_t image, enum boot_slot slot) +{ + uint8_t value = BOOT_REQUEST_SLOT_INVALID; + size_t req_entry; + int ret; + + ret = boot_request_entry_find(BOOT_REQUEST_IMG_CONFIRM, image, &req_entry); + if (ret != 0) { + return false; + } + + ret = boot_request_mem_read(req_entry * sizeof(uint8_t), &value); + if (ret != 0) { + return false; + } + + switch (value) { + case BOOT_REQUEST_SLOT_PRIMARY: + return (slot == BOOT_SLOT_PRIMARY); + case BOOT_REQUEST_SLOT_SECONDARY: + return (slot == BOOT_SLOT_SECONDARY); + default: + break; + } + + return false; +} + +int boot_request_set_preferred_slot(uint8_t image, enum boot_slot slot) +{ + uint8_t value = BOOT_REQUEST_SLOT_INVALID; + size_t req_entry; + int ret; + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + if (!boot_request_mem_updateable()) { + BOOT_LOG_ERR("Unable to select slot - area not updateable."); + /* Cannot update area if it is corrupted. */ + return -EIO; + } +#endif + + ret = boot_request_entry_find(BOOT_REQUEST_IMG_PREFERENCE, image, &req_entry); + if (ret != 0) { + return ret; + } + + switch (slot) { + case BOOT_SLOT_PRIMARY: + value = BOOT_REQUEST_SLOT_PRIMARY; + break; + case BOOT_SLOT_SECONDARY: + value = BOOT_REQUEST_SLOT_SECONDARY; + break; + default: + return -EINVAL; + } + + return boot_request_mem_write(req_entry * sizeof(value), &value); +} + +enum boot_slot boot_request_get_preferred_slot(uint8_t image) +{ + uint8_t value = BOOT_REQUEST_SLOT_INVALID; + size_t req_entry; + int ret; + + ret = boot_request_entry_find(BOOT_REQUEST_IMG_PREFERENCE, image, &req_entry); + if (ret != 0) { + return BOOT_SLOT_NONE; + } + + ret = boot_request_mem_read(req_entry * sizeof(uint8_t), &value); + if (ret != 0) { + return BOOT_SLOT_NONE; + } + + switch (value) { + case BOOT_REQUEST_SLOT_PRIMARY: + return BOOT_SLOT_PRIMARY; + case BOOT_REQUEST_SLOT_SECONDARY: + return BOOT_SLOT_SECONDARY; + default: + break; + } + + return BOOT_SLOT_NONE; +} + +int boot_request_enter_recovery(void) +{ + uint8_t value = BOOT_REQUEST_MODE_RECOVERY; + size_t req_entry; + int ret; + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + if (!boot_request_mem_updateable()) { + BOOT_LOG_ERR("Unable to enter recovery - area not updateable."); + /* Cannot update area if it is corrupted. */ + return -EIO; + } +#endif + + ret = boot_request_entry_find(BOOT_REQUEST_BOOT_MODE, BOOT_REQUEST_IMG_BOOTLOADER, + &req_entry); + if (ret != 0) { + return ret; + } + + return boot_request_mem_write(req_entry * sizeof(value), &value); +} + +#ifdef CONFIG_NRF_BOOT_SERIAL_BOOT_REQ +bool boot_request_detect_recovery(void) +{ + uint8_t value = BOOT_REQUEST_MODE_INVALID; + size_t req_entry; + int ret; + + ret = boot_request_entry_find(BOOT_REQUEST_BOOT_MODE, BOOT_REQUEST_IMG_BOOTLOADER, + &req_entry); + if (ret != 0) { + return false; + } + + ret = boot_request_mem_read(req_entry * sizeof(uint8_t), &value); + if ((ret == 0) && (value == BOOT_REQUEST_MODE_RECOVERY)) { + return true; + } + + return false; +} +#endif /* CONFIG_NRF_BOOT_SERIAL_BOOT_REQ */ + +int boot_request_enter_firmware_loader(void) +{ + uint8_t value = BOOT_REQUEST_MODE_FIRMWARE_LOADER; + size_t req_entry; + int ret; + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + if (!boot_request_mem_updateable()) { + BOOT_LOG_ERR("Unable to enter FW loader - area not updateable."); + /* Cannot update area if it is corrupted. */ + return -EIO; + } +#endif + + ret = boot_request_entry_find(BOOT_REQUEST_BOOT_MODE, BOOT_REQUEST_IMG_BOOTLOADER, + &req_entry); + if (ret != 0) { + return ret; + } + + return boot_request_mem_write(req_entry * sizeof(value), &value); +} + +#ifdef CONFIG_NRF_BOOT_FIRMWARE_LOADER_BOOT_REQ +bool boot_request_detect_firmware_loader(void) +{ + uint8_t value = BOOT_REQUEST_MODE_INVALID; + size_t req_entry; + int ret; + + ret = boot_request_entry_find(BOOT_REQUEST_BOOT_MODE, BOOT_REQUEST_IMG_BOOTLOADER, + &req_entry); + if (ret != 0) { + return false; + } + + ret = boot_request_mem_read(req_entry * sizeof(uint8_t), &value); + if ((ret == 0) && (value == BOOT_REQUEST_MODE_FIRMWARE_LOADER)) { + return true; + } + + return false; +} +#endif /* CONFIG_NRF_BOOT_FIRMWARE_LOADER_BOOT_REQ */ diff --git a/boot/bootutil/zephyr/src/boot_request_flash.c b/boot/bootutil/zephyr/src/boot_request_flash.c new file mode 100644 index 000000000..6353d4baf --- /dev/null +++ b/boot/bootutil/zephyr/src/boot_request_flash.c @@ -0,0 +1,396 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2025 Nordic Semiconductor ASA + */ + +#include "boot_request_mem.h" +#include +#include +#include + +#include "bootutil/bootutil_log.h" + +#define MAIN_FLASH_DEV FIXED_PARTITION_NODE_DEVICE(DT_CHOSEN(nrf_bootloader_request)) +#define MAIN_OFFSET FIXED_PARTITION_NODE_OFFSET(DT_CHOSEN(nrf_bootloader_request)) +#define MAIN_SIZE FIXED_PARTITION_NODE_SIZE(DT_CHOSEN(nrf_bootloader_request)) + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP +#define BACKUP_FLASH_DEV FIXED_PARTITION_NODE_DEVICE(DT_CHOSEN(nrf_bootloader_request_backup)) +#define BACKUP_OFFSET FIXED_PARTITION_NODE_OFFSET(DT_CHOSEN(nrf_bootloader_request_backup)) +#define BACKUP_SIZE FIXED_PARTITION_NODE_SIZE(DT_CHOSEN(nrf_bootloader_request_backup)) +#endif /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ + +#define BOOT_REQUEST_CHECKSUM_SIZE sizeof(uint32_t) +#define BOOT_REQUEST_PREFIX ((uint16_t)0x0B01) +#define BOOT_REQUEST_PREREFIX_SIZE sizeof(uint16_t) +#define BOOT_REQUEST_ENTRIES_SIZE (MAIN_SIZE - BOOT_REQUEST_CHECKSUM_SIZE - BOOT_REQUEST_PREREFIX_SIZE) + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP +BUILD_ASSERT((MAIN_SIZE == BACKUP_SIZE), + "nrf,bootloader-request and nrf,bootloader-request-backup areas must be of equal size"); +#endif /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ +BUILD_ASSERT((BOOT_REQUEST_ENTRIES_SIZE > 0), + "nrf,bootloader-request area is too small"); + +BOOT_LOG_MODULE_DECLARE(bootloader_request); + +/** Structure representing the boot request area in NVM. */ +typedef struct { + /* Prefix to identify the boot request area. Must be equal to BOOT_REQUEST_PREFIX. */ + uint16_t prefix; + /* Modifiable entries in the boot request area. */ + uint8_t entries[BOOT_REQUEST_ENTRIES_SIZE]; + /* Checksum of the boot request entries area. */ + uint32_t checksum; +} boot_request_area_t; + +/** + * @brief Check if the boot request area contains a valid checksum. + * + * @param[in] fdev Flash device. + * @param[in] offset Offset of the boot request area in flash. + * + * @return true if the area is valid; false otherwise. + */ +static bool boot_request_area_valid(const struct device *fdev, size_t offset) +{ + boot_request_area_t area_copy; + uint32_t exp_checksum; + + if (flash_read(fdev, offset, (uint8_t *)&area_copy, sizeof(boot_request_area_t)) != 0) { + return false; + }; + + if (area_copy.prefix != BOOT_REQUEST_PREFIX) { + return false; + } + + exp_checksum = crc32_ieee_update(0, area_copy.entries, BOOT_REQUEST_ENTRIES_SIZE); + if (exp_checksum != area_copy.checksum) { + return false; + } + + return true; +} + +/** + * @brief Commit the boot request area by updating its checksum. + * + * @param[in] fdev Flash device. + * @param[in] offset Offset of the boot request area in flash. + * + * @retval 0 if area is successfully committed. + * @retval -EIO if unable to read from the area. + * @retval -EROFS if unable to write to the area. + */ +static int boot_request_commit(const struct device *fdev, size_t offset) +{ + boot_request_area_t area_copy; + + if (flash_read(fdev, offset, (uint8_t *)&area_copy, sizeof(boot_request_area_t)) != 0) { + return -EIO; + }; + + area_copy.prefix = BOOT_REQUEST_PREFIX; + area_copy.checksum = crc32_ieee_update(0, area_copy.entries, BOOT_REQUEST_ENTRIES_SIZE); + + if (flash_write(fdev, offset, (uint8_t *)&area_copy, sizeof(boot_request_area_t)) != 0) { + return -EROFS; + } + + return 0; +} + +/** + * @brief Clear the boot request area by erasing it and committing the changes. + * + * @param[in] fdev Flash device. + * @param[in] offset Offset of the boot request area in flash. + * + * @retval 0 if area is successfully cleared. + * @retval -EIO if unable to erase or read from the area. + * @retval -EROFS if unable to commit the changes. + */ +static int boot_request_clear(const struct device *fdev, size_t offset) +{ + if (flash_erase(fdev, offset, sizeof(boot_request_area_t)) != 0) { + return -EIO; + } + + return boot_request_commit(fdev, offset); +} + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP +/** + * @brief Check if two boot request areas are equal. + * + * @param[in] fdev1 Flash device of the first area. + * @param[in] offset1 Offset of the first area in flash. + * @param[in] fdev2 Flash device of the second area. + * @param[in] offset2 Offset of the second area in flash. + * + * @return true if areas are equal; false otherwise. + */ +static bool boot_request_equal(const struct device *fdev1, size_t offset1, + const struct device *fdev2, size_t offset2) +{ + boot_request_area_t area1_copy; + boot_request_area_t area2_copy; + + if (flash_read(fdev1, offset1, (uint8_t *)&area1_copy, sizeof(boot_request_area_t)) != 0) { + return -EIO; + } + + if (flash_read(fdev2, offset2, (uint8_t *)&area2_copy, sizeof(boot_request_area_t)) != 0) { + return -EIO; + } + + return (memcmp(&area1_copy, &area2_copy, sizeof(boot_request_area_t)) == 0); +} + +/** + * @brief Copy the boot request area from one location to another. + * + * @param[in] fdev1 Flash device of the destination area. + * @param[in] offset1 Offset of the destination area in flash. + * @param[in] fdev2 Flash device of the source area. + * @param[in] offset2 Offset of the source area in flash. + * + * @retval 0 if area is successfully copied. + * @retval -ENOENT if source area is not valid. + * @retval -EIO if unable to read from source area. + * @retval -EROFS if unable to write to destination area. + */ +static int boot_request_copy(const struct device *fdev1, size_t offset1, + const struct device *fdev2, size_t offset2) +{ + boot_request_area_t area_copy; + + if (!boot_request_area_valid(fdev2, offset2)) { + return -ENOENT; + } + + BOOT_LOG_INF("Copying boot request area (0x%x to 0x%x).", offset2, offset1); + + if (flash_read(fdev2, offset2, (uint8_t *)&area_copy, sizeof(boot_request_area_t)) != 0) { + return -EIO; + } + + if (flash_write(fdev1, offset1, (uint8_t *)&area_copy, sizeof(boot_request_area_t)) != 0) { + return -EROFS; + } + + return 0; +} +#endif /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ + +int boot_request_mem_init(void) +{ + if (!device_is_ready(MAIN_FLASH_DEV)) { + return -EIO; + } + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + if (!device_is_ready(BACKUP_FLASH_DEV)) { + return -EIO; + } + + if (boot_request_area_valid(MAIN_FLASH_DEV, MAIN_OFFSET)) { + if (boot_request_area_valid(BACKUP_FLASH_DEV, BACKUP_OFFSET)) { + if (!boot_request_equal(MAIN_FLASH_DEV, MAIN_OFFSET, + BACKUP_FLASH_DEV, BACKUP_OFFSET)) { + BOOT_LOG_INF("New values found. Update backup area."); + /* Primary is valid, backup is outdated. */ + (void)boot_request_copy(BACKUP_FLASH_DEV, BACKUP_OFFSET, + MAIN_FLASH_DEV, MAIN_OFFSET); + } else { + /* Both are valid and equal, nothing to do. */ + } + } else { + BOOT_LOG_INF("Backup area is invalid. Update backup area."); + /* Backup is invalid, copy primary to backup. */ + (void)boot_request_copy(BACKUP_FLASH_DEV, BACKUP_OFFSET, + MAIN_FLASH_DEV, MAIN_OFFSET); + } + } else { + if (boot_request_area_valid(BACKUP_FLASH_DEV, BACKUP_OFFSET)) { + BOOT_LOG_INF("Primary area is invalid. Restore from backup."); + /* Primary is invalid, restore from backup. */ + return boot_request_copy(MAIN_FLASH_DEV, MAIN_OFFSET, + BACKUP_FLASH_DEV, BACKUP_OFFSET); + } else { + BOOT_LOG_INF("Both areas are invalid. Clear both areas."); + /* Both are invalid, clear both. */ + (void)boot_request_clear(BACKUP_FLASH_DEV, BACKUP_OFFSET); + return boot_request_clear(MAIN_FLASH_DEV, MAIN_OFFSET); + } + } +#else /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ + if (!boot_request_area_valid(MAIN_FLASH_DEV, MAIN_OFFSET)) { + BOOT_LOG_INF("Retention area is invalid. Clear area."); + return boot_request_clear(MAIN_FLASH_DEV, MAIN_OFFSET); + } +#endif /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ + return 0; +} + +bool boot_request_mem_updateable(void) +{ + if (!boot_request_area_valid(MAIN_FLASH_DEV, MAIN_OFFSET)) { +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + if (boot_request_area_valid(BACKUP_FLASH_DEV, BACKUP_OFFSET)) { + BOOT_LOG_INF("Broken main area. Restore from backup."); + /* Recover main area from a valid backup. */ + if (boot_request_copy(MAIN_FLASH_DEV, MAIN_OFFSET, BACKUP_FLASH_DEV, + BACKUP_OFFSET) != 0) { + return false; + } + } else if (boot_request_clear(MAIN_FLASH_DEV, MAIN_OFFSET) != 0) { + /* Both areas are not valid - clear the main area. */ + return false; + } +#else + /* Main area is not valid and no backup area is available. */ + if (boot_request_clear(MAIN_FLASH_DEV, MAIN_OFFSET) != 0) { + return false; + } +#endif /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ + } + + return true; +} + +int boot_request_mem_read(size_t entry, uint8_t *value) +{ + if ((value == NULL) || (entry >= BOOT_REQUEST_ENTRIES_SIZE)) { + return -EINVAL; + } + + if (boot_request_area_valid(MAIN_FLASH_DEV, MAIN_OFFSET)) { + /* Read from the main area. */ + return flash_read(MAIN_FLASH_DEV, MAIN_OFFSET + + offsetof(boot_request_area_t, entries) + entry, + value, sizeof(uint8_t)); +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + } else if (boot_request_area_valid(BACKUP_FLASH_DEV, + BACKUP_OFFSET)) { + /* Read from backup area. */ + return flash_read(BACKUP_FLASH_DEV, BACKUP_OFFSET + + offsetof(boot_request_area_t, entries) + entry, + value, sizeof(uint8_t)); +#endif /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ + } + + /* Both areas are invalid. */ + return -ENOENT; +} + +int boot_request_mem_write(size_t entry, uint8_t *value) +{ + boot_request_area_t area_copy; + + if ((value == NULL) || (entry >= BOOT_REQUEST_ENTRIES_SIZE)) { + return -EINVAL; + } + + /* Update only the main region. It will be backed up after a reboot. */ + if (flash_read(MAIN_FLASH_DEV, MAIN_OFFSET, (uint8_t *)&area_copy, + sizeof(boot_request_area_t)) != 0) { + return -EIO; + } + + if (area_copy.entries[entry] == *value) { + /* No need to update the entry. */ + return 0; + } + + area_copy.entries[entry] = *value; + + if (flash_write(MAIN_FLASH_DEV, MAIN_OFFSET, (uint8_t *)&area_copy, + sizeof(boot_request_area_t)) != 0) { + return -EROFS; + }; + + /* Update checksum. */ + return boot_request_commit(MAIN_FLASH_DEV, MAIN_OFFSET); +} + +int boot_request_mem_selective_erase(size_t *nv_indexes, size_t nv_count) +{ +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + boot_request_area_t new_area; + boot_request_area_t old_area; + bool update_needed = false; + size_t i; +#endif /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ + + if ((nv_indexes == NULL) && (nv_count != 0)) { + return -EINVAL; + } + +#ifdef CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP + if (boot_request_area_valid(BACKUP_FLASH_DEV, BACKUP_OFFSET)) { + /* Prepare a new area with all entries (including prefix and CRC) set to 0xFF. */ + memset(&new_area, 0xFF, sizeof(boot_request_area_t)); + + /* Copy non-volatile entries. */ + for (i = 0; i < nv_count; i++) { + if (flash_read(BACKUP_FLASH_DEV, BACKUP_OFFSET + + offsetof(boot_request_area_t, entries) + nv_indexes[i], + &new_area.entries[nv_indexes[i]], sizeof(uint8_t)) != 0) { + return -EBADF; + }; + } + + /* Read the current value of the main area. */ + if (flash_read(MAIN_FLASH_DEV, MAIN_OFFSET, (uint8_t *)&old_area, + sizeof(boot_request_area_t)) != 0) { + return -EIO; + } + + /* Check if there is a need to update entries. */ + for (i = 0; i < ARRAY_SIZE(new_area.entries); i++) { + if (new_area.entries[i] != old_area.entries[i]) { + update_needed = true; + break; + } + } + + if (!update_needed) { + /* No need to update the area. */ + return 0; + } + + /* Erase the main area. */ + if (flash_erase(MAIN_FLASH_DEV, MAIN_OFFSET, sizeof(boot_request_area_t)) != 0) { + return -EIO; + } + + /* Initialize the main area with prepared values. */ + if (flash_write(MAIN_FLASH_DEV, MAIN_OFFSET, (uint8_t *)&new_area, + sizeof(boot_request_area_t)) != 0) { + return -EROFS; + }; + + /* Validate the main area by updating checksum. */ + if (boot_request_commit(MAIN_FLASH_DEV, MAIN_OFFSET) != 0) { + return -EIO; + } + + /* Sync backup with the main area. */ + return boot_request_copy(BACKUP_FLASH_DEV, BACKUP_OFFSET, MAIN_FLASH_DEV, + MAIN_OFFSET); + } + + /* Backup is invalid, do not clear the memory to keep at least one valid copy. */ + return 0; +#else /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ + /* Selective erase is not supported if there is no backup area. */ + if (nv_count != 0) { + return -ENOTSUP; + } + + return boot_request_clear(MAIN_FLASH_DEV, MAIN_OFFSET); +#endif /* CONFIG_NRF_MCUBOOT_BOOT_REQUEST_PREFERENCE_KEEP */ +} diff --git a/boot/bootutil/zephyr/src/boot_request_mem.h b/boot/bootutil/zephyr/src/boot_request_mem.h new file mode 100644 index 000000000..42af7fcff --- /dev/null +++ b/boot/bootutil/zephyr/src/boot_request_mem.h @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * Copyright (c) 2025 Nordic Semiconductor ASA + */ + +#ifndef __BOOT_REQUEST_MEM_H__ +#define __BOOT_REQUEST_MEM_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/** + * @brief Initialize the boot request memory areas. + * + * @retval 0 if initialization is successful. + * @retval -EIO if unable to access the flash devices. + * @retval -EROFS if unable to initialize memory. + */ +int boot_request_mem_init(void); + +/** + * @brief Check if the boot request area can be updated. + * + * @return true if the area is updateable; false otherwise. + */ +bool boot_request_mem_updateable(void); + +/** + * @brief Read a single boot request entry from a memory. + * + * @param[in] entry Index of the entry to read. + * @param[out] value Pointer to store the read value. + * + * @retval 0 if entry is successfully read. + * @retval -EINVAL if input parameters are invalid. + * @retval -ENOENT if both retention areas are invalid. + */ +int boot_request_mem_read(size_t entry, uint8_t *value); + +/** + * @brief Write a single boot request entry to a memory. + * + * @param[in] entry Index of the entry to write. + * @param[in] value Pointer to the value to write. + * + * @retval 0 if entry is successfully written. + * @retval -EINVAL if input parameters are invalid. + * @retval -EIO if unable to read from the area. + * @retval -EROFS if unable to write to the area. + */ +int boot_request_mem_write(size_t entry, uint8_t *value); + +/** + * @brief Selectively erase boot request entries, keeping specified indexes intact. + * + * @param[in] nv_indexes Array of indexes of entries to keep. + * @param[in] nv_count Number of entries in the array. + * + * @retval 0 if selective erase is successful. + * @retval -EINVAL if one of the input arguments is not correct (i.e. NULL). + * @retval -ENOTSUP if selective erase is not supported. + * @retval -EIO if unable to erase or read from the area. + * @retval -EBADF if unable to read a entry at a given index. + * @retval -EROFS if unable to write a entry at a given index. + */ +int boot_request_mem_selective_erase(size_t *nv_indexes, size_t nv_count); + +#ifdef __cplusplus +} +#endif + +#endif /* __BOOT_REQUEST_MEMH__ */ diff --git a/boot/bootutil/zephyr/src/boot_request_retention.c b/boot/bootutil/zephyr/src/boot_request_retention.c index 9cde6da04..2587ab765 100644 --- a/boot/bootutil/zephyr/src/boot_request_retention.c +++ b/boot/bootutil/zephyr/src/boot_request_retention.c @@ -3,342 +3,57 @@ * * Copyright (c) 2025 Nordic Semiconductor ASA */ -#include -#include "bootutil/bootutil_log.h" +#include "boot_request_mem.h" #include -/** Special value of image number, indicating a request to the bootloader. */ -#define BOOT_REQUEST_IMG_BOOTLOADER 0xFF - -/** Additional memory used by the retention subsystem (2B - prefix, 4B - CRC).*/ -#define BOOT_REQUEST_ENTRY_METADATA_SIZE (2 + 4) - -BOOT_LOG_MODULE_REGISTER(bootloader_request); - static const struct device *bootloader_request_dev = DEVICE_DT_GET(DT_CHOSEN(nrf_bootloader_request)); -enum boot_request_type { - /** Invalid request. */ - BOOT_REQUEST_INVALID = 0, - - /** Request a change in the bootloader boot mode. - * - * @details Use @p boot_request_mode as argument. - * @p BOOT_REQUEST_IMG_BOOTLOADER as image number. - * - * @note Used to trigger recovery through i.e. retention sybsystem. - */ - BOOT_REQUEST_BOOT_MODE = 1, - - /** Select the preferred image to be selected during boot or update. - * - * @details Use @p boot_request_slot_t as argument. - * - * @note Used in the Direct XIP mode. - */ - BOOT_REQUEST_IMG_PREFERENCE = 2, - - /** Request a confirmation of an image. - * - * @details Use @p boot_request_slot_t as argument. - * - * @note Used if the code cannot modify the image trailer directly. - */ - BOOT_REQUEST_IMG_CONFIRM = 3, -}; - -/* Entries inside the boot request shared memory. */ -enum boot_request_entry { - BOOT_REQUEST_ENTRY_BOOT_MODE = 0, - BOOT_REQUEST_ENTRY_IMAGE_0_PREFERENCE = 1, - BOOT_REQUEST_ENTRY_IMAGE_0_CONFIRM = 2, - BOOT_REQUEST_ENTRY_IMAGE_1_PREFERENCE = 3, - BOOT_REQUEST_ENTRY_IMAGE_1_CONFIRM = 4, - BOOT_REQUEST_ENTRY_MAX = 5, -}; - -/* Assert that all requests will fit within the retention area. */ -BUILD_ASSERT((BOOT_REQUEST_ENTRY_METADATA_SIZE + BOOT_REQUEST_ENTRY_MAX * sizeof(uint8_t)) < - DT_REG_SIZE_BY_IDX(DT_CHOSEN(nrf_bootloader_request), 0), - "nrf,bootloader-request area is too small for bootloader request struct"); - -enum boot_request_slot { - /** Unsupported value. */ - BOOT_REQUEST_SLOT_INVALID = 0, - /** Primary slot. */ - BOOT_REQUEST_SLOT_PRIMARY = 1, - /** Secondary slot. */ - BOOT_REQUEST_SLOT_SECONDARY = 2, -}; - -/** Alias type for the image and number. */ -typedef uint8_t boot_request_slot_t; - -enum boot_request_mode { - /** Execute a regular boot logic. */ - BOOT_REQUEST_MODE_REGULAR = 0, - /** Execute the recovery boot logic. */ - BOOT_REQUEST_MODE_RECOVERY = 1, - /** Execute the firmware loader logic. */ - BOOT_REQUEST_MODE_FIRMWARE_LOADER = 2, - /** Unsupported value. */ - BOOT_REQUEST_MODE_INVALID = 0xFF, -}; - -/** Alias type for the image number. */ -typedef uint8_t boot_request_img_t; - -/** - * @brief Find an entry for a given request. - * - * @param[in] type Type of request. - * @param[in] image Image number. Use @p BOOT_REQUEST_IMG_BOOTLOADER for generic requests. - * @param[out] entry Entry to use. - * - * @return 0 on success; nonzero on failure. - */ -static int boot_request_entry_find(enum boot_request_type type, boot_request_img_t image, - size_t *entry) -{ - if (entry == NULL) { - return -EINVAL; - } - - switch (type) { - case BOOT_REQUEST_BOOT_MODE: - *entry = BOOT_REQUEST_ENTRY_BOOT_MODE; - break; - case BOOT_REQUEST_IMG_PREFERENCE: - switch (image) { - case 0: - *entry = BOOT_REQUEST_ENTRY_IMAGE_0_PREFERENCE; - break; - case 1: - *entry = BOOT_REQUEST_ENTRY_IMAGE_1_PREFERENCE; - break; - default: - return -EINVAL; - } - break; - case BOOT_REQUEST_IMG_CONFIRM: - switch (image) { - case 0: - *entry = BOOT_REQUEST_ENTRY_IMAGE_0_CONFIRM; - break; - case 1: - *entry = BOOT_REQUEST_ENTRY_IMAGE_1_CONFIRM; - break; - default: - return -EINVAL; - } - break; - default: - return -EINVAL; - } - - return 0; -} - -int boot_request_init(void) +int boot_request_mem_init(void) { if (!device_is_ready(bootloader_request_dev)) { return -EIO; } - + return 0; } -int boot_request_clear(void) +bool boot_request_mem_updateable(void) { - return retention_clear(bootloader_request_dev); + return true; } -int boot_request_confirm_slot(uint8_t image, enum boot_slot slot) +int boot_request_mem_read(size_t entry, uint8_t *value) { - uint8_t value = BOOT_REQUEST_SLOT_INVALID; - size_t req_entry; - int ret; - - ret = boot_request_entry_find(BOOT_REQUEST_IMG_CONFIRM, image, &req_entry); - if (ret != 0) { - return ret; - } - - switch (slot) { - case BOOT_SLOT_PRIMARY: - value = BOOT_REQUEST_SLOT_PRIMARY; - break; - case BOOT_SLOT_SECONDARY: - value = BOOT_REQUEST_SLOT_SECONDARY; - break; - default: + if ((value == NULL) || (entry >= retention_size(bootloader_request_dev) / sizeof(*value))) { return -EINVAL; } - return retention_write(bootloader_request_dev, req_entry * sizeof(value), (void *)&value, - sizeof(value)); + return retention_read(bootloader_request_dev, entry * sizeof(*value), (void *)value, + sizeof(*value)); } -bool boot_request_check_confirmed_slot(uint8_t image, enum boot_slot slot) +int boot_request_mem_write(size_t entry, uint8_t *value) { - uint8_t value = BOOT_REQUEST_SLOT_INVALID; - size_t req_entry; - int ret; - - ret = boot_request_entry_find(BOOT_REQUEST_IMG_CONFIRM, image, &req_entry); - if (ret != 0) { - return false; - } - - ret = retention_read(bootloader_request_dev, req_entry * sizeof(value), (void *)&value, - sizeof(value)); - if (ret != 0) { - return false; - } - - switch (value) { - case BOOT_REQUEST_SLOT_PRIMARY: - return (slot == BOOT_SLOT_PRIMARY); - case BOOT_REQUEST_SLOT_SECONDARY: - return (slot == BOOT_SLOT_SECONDARY); - default: - break; - } - - return false; -} - -int boot_request_set_preferred_slot(uint8_t image, enum boot_slot slot) -{ - uint8_t value = BOOT_REQUEST_SLOT_INVALID; - size_t req_entry; - int ret; - - ret = boot_request_entry_find(BOOT_REQUEST_IMG_PREFERENCE, image, &req_entry); - if (ret != 0) { - return ret; - } - - switch (slot) { - case BOOT_SLOT_PRIMARY: - value = BOOT_REQUEST_SLOT_PRIMARY; - break; - case BOOT_SLOT_SECONDARY: - value = BOOT_REQUEST_SLOT_SECONDARY; - break; - default: + if ((value == NULL) || (entry >= retention_size(bootloader_request_dev) / sizeof(*value))) { return -EINVAL; } - return retention_write(bootloader_request_dev, req_entry * sizeof(value), (void *)&value, - sizeof(value)); -} - -enum boot_slot boot_request_get_preferred_slot(uint8_t image) -{ - uint8_t value = BOOT_REQUEST_SLOT_INVALID; - size_t req_entry; - int ret; - - ret = boot_request_entry_find(BOOT_REQUEST_IMG_PREFERENCE, image, &req_entry); - if (ret != 0) { - return BOOT_SLOT_NONE; - } - - ret = retention_read(bootloader_request_dev, req_entry * sizeof(value), (void *)&value, - sizeof(value)); - if (ret != 0) { - return BOOT_SLOT_NONE; - } - - switch (value) { - case BOOT_REQUEST_SLOT_PRIMARY: - return BOOT_SLOT_PRIMARY; - case BOOT_REQUEST_SLOT_SECONDARY: - return BOOT_SLOT_SECONDARY; - default: - break; - } - - return BOOT_SLOT_NONE; -} - -int boot_request_enter_recovery(void) -{ - uint8_t value = BOOT_REQUEST_MODE_RECOVERY; - size_t req_entry; - int ret; - - ret = boot_request_entry_find(BOOT_REQUEST_BOOT_MODE, BOOT_REQUEST_IMG_BOOTLOADER, - &req_entry); - if (ret != 0) { - return ret; - } - - return retention_write(bootloader_request_dev, req_entry * sizeof(value), (void *)&value, - sizeof(value)); -} - -#ifdef CONFIG_NRF_BOOT_SERIAL_BOOT_REQ -bool boot_request_detect_recovery(void) -{ - uint8_t value = BOOT_REQUEST_MODE_INVALID; - size_t req_entry; - int ret; - - ret = boot_request_entry_find(BOOT_REQUEST_BOOT_MODE, BOOT_REQUEST_IMG_BOOTLOADER, - &req_entry); - if (ret != 0) { - return false; - } - - ret = retention_read(bootloader_request_dev, req_entry * sizeof(value), (void *)&value, - sizeof(value)); - if ((ret == 0) && (value == BOOT_REQUEST_MODE_RECOVERY)) { - return true; - } - - return false; + return retention_write(bootloader_request_dev, entry * sizeof(*value), (void *)value, + sizeof(*value)); } -#endif /* CONFIG_NRF_BOOT_SERIAL_BOOT_REQ */ -int boot_request_enter_firmware_loader(void) +int boot_request_mem_selective_erase(size_t *nv_indexes, size_t nv_count) { - uint8_t value = BOOT_REQUEST_MODE_FIRMWARE_LOADER; - size_t req_entry; - int ret; - - ret = boot_request_entry_find(BOOT_REQUEST_BOOT_MODE, BOOT_REQUEST_IMG_BOOTLOADER, - &req_entry); - if (ret != 0) { - return ret; - } - - return retention_write(bootloader_request_dev, req_entry * sizeof(value), (void *)&value, - sizeof(value)); -} - -#ifdef CONFIG_NRF_BOOT_FIRMWARE_LOADER_BOOT_REQ -bool boot_request_detect_firmware_loader(void) -{ - uint8_t value = BOOT_REQUEST_MODE_INVALID; - size_t req_entry; - int ret; - - ret = boot_request_entry_find(BOOT_REQUEST_BOOT_MODE, BOOT_REQUEST_IMG_BOOTLOADER, - &req_entry); - if (ret != 0) { - return false; + if ((nv_indexes == NULL) && (nv_count != 0)) { + return -EINVAL; } - ret = retention_read(bootloader_request_dev, req_entry * sizeof(value), (void *)&value, - sizeof(value)); - if ((ret == 0) && (value == BOOT_REQUEST_MODE_FIRMWARE_LOADER)) { - return true; + /* Selective erase is not supported if there is no backup area. */ + if (nv_count != 0) { + return -ENOTSUP; } - return false; + return retention_clear(bootloader_request_dev); } -#endif /* CONFIG_NRF_BOOT_FIRMWARE_LOADER_BOOT_REQ */