diff --git a/cmake/sysbuild/image_signing.cmake b/cmake/sysbuild/image_signing.cmake index c3e0013c23ae..88298d5907ac 100644 --- a/cmake/sysbuild/image_signing.cmake +++ b/cmake/sysbuild/image_signing.cmake @@ -143,6 +143,10 @@ function(zephyr_mcuboot_tasks) set(imgtool_extra ${imgtool_extra} --cid "${CONFIG_MCUBOOT_IMGTOOL_UUID_CID_NAME}") endif() + if(CONFIG_NCS_MCUBOOT_IMGTOOL_APPEND_MANIFEST) + set(imgtool_extra ${imgtool_extra} --manifest "manifest.yaml") + endif() + set(imgtool_args ${imgtool_extra}) # Extensionless prefix of any output file. diff --git a/cmake/sysbuild/mcuboot_manifest.cmake b/cmake/sysbuild/mcuboot_manifest.cmake new file mode 100644 index 000000000000..c0559875ba2a --- /dev/null +++ b/cmake/sysbuild/mcuboot_manifest.cmake @@ -0,0 +1,89 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + +include(${ZEPHYR_NRF_MODULE_DIR}/cmake/sysbuild/bootloader_dts_utils.cmake) + +yaml_create(NAME mcuboot_manifest) +yaml_set(NAME mcuboot_manifest KEY format VALUE "1") +yaml_set(NAME mcuboot_manifest KEY images LIST) +set(manifest_path "manifest.yaml") +set(manifest_img_slot_0 "${DEFAULT_IMAGE}") + +yaml_create(NAME mcuboot_secondary_manifest) +yaml_set(NAME mcuboot_secondary_manifest KEY format VALUE "1") +yaml_set(NAME mcuboot_secondary_manifest KEY images LIST) +set(manifest_secondary_path "manifest_secondary.yaml") +set(manifest_img_slot_1 "mcuboot_secondary_app") + +# Since the default (merged) image is excluded from the manifest, because it contains the manifest +# itself, there is no need to construct the manifest when building a merged image. +if(NOT SB_CONFIG_MCUBOOT_SIGN_MERGED_BINARY) + sysbuild_get(manifest_img IMAGE mcuboot VAR CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER KCONFIG) + math(EXPR manifest_slot_0 "${manifest_img} * 2") + math(EXPR manifest_slot_1 "${manifest_img} * 2 + 1") + dt_partition_addr(slot0_addr LABEL "slot${manifest_slot_0}_partition" TARGET mcuboot ABSOLUTE REQUIRED) + dt_partition_addr(slot1_addr LABEL "slot${manifest_slot_1}_partition" TARGET mcuboot ABSOLUTE REQUIRED) + + UpdateableImage_Get(images GROUP "DEFAULT") + foreach(image ${images}) + sysbuild_get(BINARY_DIR IMAGE ${image} VAR APPLICATION_BINARY_DIR CACHE) + sysbuild_get(BINARY_BIN_FILE IMAGE ${image} VAR CONFIG_KERNEL_BIN_NAME KCONFIG) + dt_chosen(code_flash TARGET ${image} PROPERTY "zephyr,code-partition") + dt_partition_addr(code_addr PATH "${code_flash}" TARGET ${image} ABSOLUTE REQUIRED) + + if("${code_addr}" STREQUAL "${slot0_addr}") + cmake_path(APPEND BINARY_DIR "zephyr" "manifest.yaml" OUTPUT_VARIABLE manifest_path) + set(manifest_img_slot_0 "${image}") + continue() + endif() + + if(NOT "${SB_CONFIG_SIGNATURE_TYPE}" STREQUAL "NONE") + cmake_path(APPEND BINARY_DIR "zephyr" "${BINARY_BIN_FILE}.signed.bin" OUTPUT_VARIABLE image_path) + else() + cmake_path(APPEND BINARY_DIR "zephyr" "${BINARY_BIN_FILE}.bin" OUTPUT_VARIABLE image_path) + endif() + + yaml_set(NAME mcuboot_manifest KEY images APPEND LIST MAP "path: ${image_path}, name: ${image}") + endforeach() + + foreach(image ${images}) + if("${image}" STREQUAL "${manifest_img_slot_0}") + continue() + endif() + add_dependencies("${manifest_img_slot_0}" "${image}") + endforeach() + + UpdateableImage_Get(variants GROUP "VARIANT") + foreach(image ${variants}) + sysbuild_get(BINARY_DIR IMAGE ${image} VAR APPLICATION_BINARY_DIR CACHE) + sysbuild_get(BINARY_BIN_FILE IMAGE ${image} VAR CONFIG_KERNEL_BIN_NAME KCONFIG) + dt_chosen(code_flash TARGET ${image} PROPERTY "zephyr,code-partition") + dt_partition_addr(code_addr PATH "${code_flash}" TARGET ${image} ABSOLUTE REQUIRED) + + if("${code_addr}" STREQUAL "${slot1_addr}") + cmake_path(APPEND BINARY_DIR "zephyr" "manifest.yaml" OUTPUT_VARIABLE manifest_secondary_path) + set(manifest_img_slot_1 "${image}") + continue() + endif() + + if(NOT "${SB_CONFIG_SIGNATURE_TYPE}" STREQUAL "NONE") + cmake_path(APPEND BINARY_DIR "zephyr" "${BINARY_BIN_FILE}.signed.bin" OUTPUT_VARIABLE image_path) + else() + cmake_path(APPEND BINARY_DIR "zephyr" "${BINARY_BIN_FILE}.bin" OUTPUT_VARIABLE image_path) + endif() + + yaml_set(NAME mcuboot_secondary_manifest KEY images APPEND LIST MAP "path: ${image_path}, name: ${image}") + endforeach() + + foreach(image ${variants}) + if("${image}" STREQUAL "${manifest_img_slot_1}") + continue() + endif() + add_dependencies("${manifest_img_slot_1}" "${image}") + endforeach() +endif() + +yaml_save(NAME mcuboot_manifest FILE "${manifest_path}") +yaml_save(NAME mcuboot_secondary_manifest FILE "${manifest_secondary_path}") diff --git a/samples/dfu/ab_split/CMakeLists.txt b/samples/dfu/ab_split/CMakeLists.txt new file mode 100644 index 000000000000..8c773b7d17d1 --- /dev/null +++ b/samples/dfu/ab_split/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(ab_split) + +target_sources(app PRIVATE src/main.c) +target_sources(app PRIVATE src/ab_utils.c) + +target_include_directories( + app PRIVATE + ${ZEPHYR_MCUBOOT_MODULE_DIR}/boot/bootutil/include + ${ZEPHYR_MCUBOOT_MODULE_DIR}/boot/zephyr/include + ${ZEPHYR_BASE}/samples/subsys/mgmt/mcumgr/smp_svr/src + ) + +target_sources_ifdef(CONFIG_MCUMGR_TRANSPORT_BT app PRIVATE + ${ZEPHYR_BASE}/samples/subsys/mgmt/mcumgr/smp_svr/src/bluetooth.c) diff --git a/samples/dfu/ab_split/Kconfig b/samples/dfu/ab_split/Kconfig new file mode 100644 index 000000000000..98160fae0f97 --- /dev/null +++ b/samples/dfu/ab_split/Kconfig @@ -0,0 +1,14 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +config N_BLINKS + int "Number of fast blinks" + default 1 + +config EMULATE_APP_HEALTH_CHECK_FAILURE + bool "Blocks confirmation of being healthy after the update" + +source "Kconfig.zephyr" diff --git a/samples/dfu/ab_split/README.rst b/samples/dfu/ab_split/README.rst new file mode 100644 index 000000000000..37d6f3e6103b --- /dev/null +++ b/samples/dfu/ab_split/README.rst @@ -0,0 +1,185 @@ +.. _ab_split_sample: + +A/B with MCUboot and separated slots +#################################### + +.. contents:: + :local: + :depth: 2 + +The A/B with MCUboot and separated slots sample demonstrates how to configure the application for updates using the A/B method using MCUboot. +This sample is a variant of the :ref:`A/B sample `, where the application and radio images are not merged, but reside in separate MCUboot slots. +This split increases demand on the number of memory areas that should be individually locked from accidental writes as well as requires additional care when preparing updates, so only a compatible set of slots are booted. +The additional dependency check during the boot process increases the time to boot the system. + +It also includes an example to perform a device health check before confirming the image after the update. +You can update the sample using the Simple Management Protocol (SMP) with UART or Bluetooth® Low Energy. + +To prevent the build system from merging slots, the sysbuild :kconfig:option:`SB_CONFIG_MCUBOOT_SIGN_MERGED_BINARY` option is disabled. +To enable manifest-based dependency management, the :kconfig:option:`SB_CONFIG_MCUBOOT_MANIFEST_UPDATES=y` option is enabled in the :file:`sysbuild/mcuboot/prj.conf` file. + +Requirements +************ + +The sample supports the following development kits: + +.. table-from-sample-yaml:: + +You need the nRF Device Manager app for update over Bluetooth Low Energy: + +* `nRF Device Manager mobile app for Android`_ +* `nRF Device Manager mobile app for iOS`_ + + +Overview +******** + +This sample demonstrates firmware update using the A/B method. +This method allows two copies of the application in the NVM memory. +It is possible to switch between these copies without performing a swap, which significantly reduces time of device's unavailability during the update. +The switch between images can be triggered by the application or, for example, by a hardware button. + +This sample implements an SMP server. +SMP is a basic transfer encoding used with the MCUmgr management protocol. +For more information about MCUmgr and SMP, see :ref:`device_mgmt`. + +The sample supports the following MCUmgr transports by default: + +* Bluetooth +* Serial (UART) + +A/B functionality +================= + +When the A/B with separated slots functionality is used, the device has two slots for each application and radio firmwares: slot A and slot B. +The slots are equivalent, and the device can boot from either of them. +By design, the slot A of the application image boots the slot A of the radio image, so there is a manifest-based dependency required to correctly verify the correctness of the image pairs. +In the case of MCUboot, this is achieved by using the Direct XIP feature. +Thus, note that the terms slot 0, primary slot, slot A and slot 1, secondary slot, slot B are used interchangeably throughout the documentation. +This configuration allows a background update of the non-active slot while the application runs from the active slot. +After the update is complete, the device can quickly switch to the updated slot on the next reboot. + +The following conditions decide which slot will be booted (active) on the next reboot: + +1. If one of the slots contains a valid image, it is marked as valid only if the same slot of the other image is also valid. +#. If one of the slots is not valid, the other slot is selected as active. +#. If both slots are valid, the slot marked as "preferred" is selected as active. +#. If both slots are valid and none is marked as "preferred," the slot with the higher version number is selected as active. +#. If none of the above conditions is met, slot A is selected as active. + +You can set the preferred slot using the ``boot_request_set_preferred_slot`` function. +Currently, this only sets the boot preference for a single reboot. + +Identifying the active slot +--------------------------- + +If the project uses the Partition Manager, the currently running slot can be identified by checking if ``CONFIG_NCS_IS_VARIANT_IMAGE`` is defined. +If it is defined, the application is running from slot B. +Otherwise, it is running from slot A. + +If the project does not use the Partition Manager (a configuration currently only supported on the nRF54H20), the currently running slot can be identified by comparing the address pointed to by `zephyr,code-partition` to specific node addresses defined in the device tree. +The following node partitions are used by default: + +* ``cpuapp_slot0_partition`` - Application core, slot A +* ``cpuapp_slot1_partition`` - Application core, slot B +* ``cpurad_slot0_partition`` - Radio core, slot A +* ``cpurad_slot1_partition`` - Radio core, slot B + +For example, verifying that the application is running from slot A can be done by using the following macro: + +.. code-block:: c + + #define IS_RUNNING_FROM_SLOT_A \ + (FIXED_PARTITION_NODE_OFFSET(DT_CHOSEN(zephyr_code_partition)) == \ + FIXED_PARTITION_OFFSET(cpuapp_slot0_partition)) + +.. _ab_split_build_files: + +Build files +----------- + +This sample overrides the default build strategy, so application and radio images are built separately. +In this case, the following files should be sent to the device when performing an update: + +* :file:`build/mcuboot_secondary_app/zephyr/zephyr.signed.bin` - Contains the slot B of the application image. + This file should be uploaded to the secondary slot when the device is running from slot A. +* :file:`build/ipc_radio_secondary_app/zephyr/zephyr.signed.bin` - Contains the slot B of the radio image. + This file should be uploaded to the secondary slot when the device is running from slot A. +* :file:`build/ab/zephyr/zephyr.signed.bin` - Contains the slot A of the application image. + This file should be uploaded to the primary slot when the device is running from slot B. +* :file:`build/ipc_radio/zephyr/zephyr.signed.bin` - Contains the slot A of the radio image. + This file should be uploaded to the primary slot when the device is running from slot B. + +User interface +************** + +LED 0: + This LED indicates that the application is running from slot A. + It is controlled as active low, meaning it will turn on once the application is booted and blinks (turns off) in short intervals. + The number of short blinks is configurable using the :kconfig:option:`CONFIG_N_BLINKS` Kconfig option. + It will remain off if the application is running from slot B. + +LED 1: + This LED indicates that the application is running from slot B. + It is controlled as active low, meaning it will turn on once the application is booted and blinks (turns off) in short intervals. + The number of short blinks is configurable using the :kconfig:option:`CONFIG_N_BLINKS` Kconfig option. + It will remain off if the application is running from slot A. + +Button 0: + By pressing this button, the non-active slot will be selected as the preferred slot on the next reboot. + This preference applies only to the next boot and is cleared after the subsequent reset. + +Configuration +************* + +|config| + +Configuration options +===================== + +Check and configure the following configuration option for the sample: + +.. _CONFIG_N_BLINKS: + +CONFIG_N_BLINKS - The number of blinks. + This configuration option sets the number of times the LED corresponding to the currently active slot blinks (LED0 for slot A, LED1 for slot B). + The default value of the option is set to ``1``, causing a single blink to indicate *Version 1*. + You can increment this value to represent an update, such as set it to ``2`` to indicate *Version 2*. + +.. _CONFIG_EMULATE_APP_HEALTH_CHECK_FAILURE: + +CONFIG_EMULATE_APP_HEALTH_CHECK_FAILURE - Enables emulation of a broken application that fails the self-test. + This configuration option emulates a broken application that does not pass the self-test. + +Additional configuration +======================== + +Check and configure the :kconfig:option:`CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION` library Kconfig option specific to the MCUboot library. +This configuration option sets the version to pass to imgtool when signing. +To ensure the updated build is preferred after a DFU, set this option to a higher version than the version currently running on the device. + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/dfu/ab_split` + +.. include:: /includes/build_and_run.txt + +Testing +======= + +To perform DFU using the `nRF Connect Device Manager`_ mobile app, complete the following steps: + +.. include:: /app_dev/device_guides/nrf52/fota_update.rst + :start-after: fota_upgrades_over_ble_nrfcdm_common_dfu_steps_start + :end-before: fota_upgrades_over_ble_nrfcdm_common_dfu_steps_end + +Instead of using the :file:`dfu_application.zip` file, you can also send the appropriate binary file directly, as described in :ref:`ab_split_build_files`. +Make sure to select the correct file based on the currently running slot. + +Dependencies +************ + +This sample uses the following |NCS| library: + +* :ref:`MCUboot ` diff --git a/samples/dfu/ab_split/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/samples/dfu/ab_split/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 000000000000..af0e3a2720c9 --- /dev/null +++ b/samples/dfu/ab_split/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "../sysbuild/nrf54h20dk_nrf54h20_memory_map.dtsi" + +/ { + chosen { + zephyr,boot-mode = &boot_request; + }; +}; diff --git a/samples/dfu/ab_split/prj.conf b/samples/dfu/ab_split/prj.conf new file mode 100644 index 000000000000..7175e6985e7b --- /dev/null +++ b/samples/dfu/ab_split/prj.conf @@ -0,0 +1,102 @@ +# Enable MCUmgr and dependencies. +CONFIG_NET_BUF=y +CONFIG_ZCBOR=y +CONFIG_CRC=y +CONFIG_MCUMGR=y +CONFIG_STREAM_FLASH=y +CONFIG_FLASH_MAP=y + +# Some command handlers require a large stack. +CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2304 +CONFIG_MAIN_STACK_SIZE=2176 + +# Ensure an MCUboot-compatible binary is generated. +CONFIG_BOOTLOADER_MCUBOOT=y + +# Enable flash operations. +CONFIG_FLASH=y + +# Required by the `taskstat` command. +CONFIG_THREAD_MONITOR=y + +# Support for taskstat command +CONFIG_MCUMGR_GRP_OS_TASKSTAT=y + +# Enable statistics and statistic names. +CONFIG_STATS=y +CONFIG_STATS_NAMES=y + +# Enable most core commands. +CONFIG_FLASH=y +CONFIG_IMG_MANAGER=y +CONFIG_MCUMGR_GRP_IMG=y +CONFIG_MCUMGR_GRP_IMG_NRF=y +CONFIG_MCUMGR_GRP_OS=y +CONFIG_MCUMGR_GRP_STAT=y + +# Enable logging +CONFIG_LOG=y +CONFIG_MCUBOOT_UTIL_LOG_LEVEL_WRN=y + +# Disable debug logging +CONFIG_LOG_MAX_LEVEL=3 + +# Enable boot requests through retained memory. +CONFIG_RETAINED_MEM=y +CONFIG_RETENTION=y +CONFIG_NRF_MCUBOOT_BOOT_REQUEST=y + +CONFIG_RETENTION_BOOT_MODE=y +CONFIG_MCUMGR_GRP_OS_RESET_BOOT_MODE=y + +# Enable DK LED/button library +CONFIG_DK_LIBRARY=y + +# Configure bluetooth + +CONFIG_BT=y +CONFIG_BT_PERIPHERAL=y + +# Allow for large Bluetooth data packets. +CONFIG_BT_L2CAP_TX_MTU=498 +CONFIG_BT_BUF_ACL_RX_SIZE=502 +CONFIG_BT_BUF_ACL_TX_SIZE=502 + +# Enable the Bluetooth mcumgr transport (unauthenticated). +CONFIG_MCUMGR_TRANSPORT_BT=y +CONFIG_MCUMGR_TRANSPORT_BT_CONN_PARAM_CONTROL=y + +# Enable the Shell mcumgr transport. +CONFIG_BASE64=y +CONFIG_CRC=y +CONFIG_SHELL=y +CONFIG_SHELL_BACKEND_SERIAL=y +CONFIG_MCUMGR_TRANSPORT_SHELL=y + +# Enable the mcumgr Packet Reassembly feature over Bluetooth and its configuration dependencies. +# MCUmgr buffer size is optimized to fit one SMP packet divided into five Bluetooth Write Commands, +# transmitted with the maximum possible MTU value: 498 bytes. +CONFIG_MCUMGR_TRANSPORT_BT_REASSEMBLY=y +CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE=2475 +CONFIG_MCUMGR_GRP_OS_MCUMGR_PARAMS=y +CONFIG_MCUMGR_TRANSPORT_WORKQUEUE_STACK_SIZE=4608 + +# Enable the LittleFS file system. +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_LITTLEFS=y + +# Enable file system commands +CONFIG_MCUMGR_GRP_FS=y + +# Enable the storage erase command. +CONFIG_MCUMGR_GRP_ZBASIC=y +CONFIG_MCUMGR_GRP_ZBASIC_STORAGE_ERASE=y + +# Disable Bluetooth ping support +CONFIG_BT_CTLR_LE_PING=n + +# Disable shell commands that are not needed +CONFIG_CLOCK_CONTROL_NRF_SHELL=n +CONFIG_DEVICE_SHELL=n +CONFIG_DEVMEM_SHELL=n +CONFIG_FLASH_SHELL=n diff --git a/samples/dfu/ab_split/sample.yaml b/samples/dfu/ab_split/sample.yaml new file mode 100644 index 000000000000..51ffd4ebec15 --- /dev/null +++ b/samples/dfu/ab_split/sample.yaml @@ -0,0 +1,17 @@ +sample: + description: AB update sample with separated slots + name: ab_split +common: + sysbuild: true + build_only: true + tags: + - dfu_ab + - ci_samples_dfu + +tests: + sample.dfu.ab_split: + sysbuild: true + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + integration_platforms: + - nrf54h20dk/nrf54h20/cpuapp diff --git a/samples/dfu/ab_split/src/ab_utils.c b/samples/dfu/ab_split/src/ab_utils.c new file mode 100644 index 000000000000..f1011295336b --- /dev/null +++ b/samples/dfu/ab_split/src/ab_utils.c @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(ab_sample); + +#define ACTIVE_IMAGE 0 + +#define CODE_PARTITION DT_CHOSEN(zephyr_code_partition) +#define CODE_PARTITION_OFFSET FIXED_PARTITION_NODE_OFFSET(CODE_PARTITION) + +#define SLOT_A_PARTITION cpuapp_slot0_partition +#define SLOT_B_PARTITION cpuapp_slot1_partition +#define CPURAD_SLOT_A_PARTITION cpurad_slot0_partition +#define CPURAD_SLOT_B_PARTITION cpurad_slot1_partition + +#define SLOT_A_OFFSET FIXED_PARTITION_OFFSET(SLOT_A_PARTITION) +#define SLOT_B_OFFSET FIXED_PARTITION_OFFSET(SLOT_B_PARTITION) +#define SLOT_A_SIZE FIXED_PARTITION_SIZE(SLOT_A_PARTITION) +#define SLOT_B_SIZE FIXED_PARTITION_SIZE(SLOT_B_PARTITION) + +#define SLOT_A_FLASH_AREA_ID FIXED_PARTITION_ID(SLOT_A_PARTITION) +#define SLOT_B_FLASH_AREA_ID FIXED_PARTITION_ID(SLOT_B_PARTITION) +#define CPURAD_SLOT_A_FLASH_AREA_ID FIXED_PARTITION_ID(CPURAD_SLOT_A_PARTITION) +#define CPURAD_SLOT_B_FLASH_AREA_ID FIXED_PARTITION_ID(CPURAD_SLOT_B_PARTITION) + +#define IS_SLOT_A \ + (CODE_PARTITION_OFFSET >= SLOT_A_OFFSET && \ + CODE_PARTITION_OFFSET < SLOT_A_OFFSET + SLOT_A_SIZE) +#define IS_SLOT_B \ + (CODE_PARTITION_OFFSET >= SLOT_B_OFFSET && \ + CODE_PARTITION_OFFSET < SLOT_B_OFFSET + SLOT_B_SIZE) + +#define STATUS_LEDS_THREAD_STACK_SIZE 512 +#define STATUS_LEDS_THREAD_PRIORITY (CONFIG_NUM_PREEMPT_PRIORITIES - 1) +K_THREAD_STACK_DEFINE(status_leds_thread_stack_area, STATUS_LEDS_THREAD_STACK_SIZE); + +enum ab_boot_slot { + SLOT_A = 0, + SLOT_B = 1, + SLOT_INVALID, +}; + +/** @brief Radio firmware self test + * + * @details + * End-device specific self test should be implemented here. + */ +static bool radio_domain_healthy(void) +{ + return bt_is_ready(); +} + +/** @brief Application firmware self test + * + * @details + * End-device specific self test should be implemented here. Enabling + * CONFIG_EMULATE_APP_HEALTH_CHECK_FAILURE allows to emulate a faulty + * firmware, unable to confirm its health, and ultimately to test + * a rollback to previous firmware after the update. + */ +static bool app_domain_healthy(void) +{ + if (IS_ENABLED(CONFIG_EMULATE_APP_HEALTH_CHECK_FAILURE)) { + return false; + } + + return true; +} + +static enum ab_boot_slot active_boot_slot_get(void) +{ + enum ab_boot_slot active_slot = SLOT_INVALID; + + if (IS_SLOT_A) { + active_slot = SLOT_A; + } else if (IS_SLOT_B) { + active_slot = SLOT_B; + } else { + LOG_ERR("Cannot determine current slot"); + } + + return active_slot; +} + +static void device_healthcheck(void) +{ + int err; + char *img_set = NULL; + const struct flash_area *fa; + int area_id = -1; + int cpurad_area_id = -1; + enum ab_boot_slot active_slot = active_boot_slot_get(); + + if (active_slot == SLOT_INVALID) { + return; + } + + /* Confirming only in non-degraded boot states + */ + if (active_slot == SLOT_A) { + img_set = "A"; + area_id = SLOT_A_FLASH_AREA_ID; + cpurad_area_id = CPURAD_SLOT_A_FLASH_AREA_ID; + } else if (active_slot == SLOT_B) { + img_set = "B"; + area_id = SLOT_B_FLASH_AREA_ID; + cpurad_area_id = CPURAD_SLOT_B_FLASH_AREA_ID; + } + + LOG_INF("Testing image set %s...", img_set); + + bool healthy = true; + + if (!radio_domain_healthy()) { + LOG_ERR("Radio domain is NOT healthy"); + healthy = false; + } + + if (!app_domain_healthy()) { + LOG_ERR("App domain is NOT healthy"); + healthy = false; + } + + if (!healthy) { + LOG_ERR("Reboot the device to try to boot from previous firmware"); + return; + } + + LOG_INF("Confirming..."); + + if (flash_area_open(area_id, &fa) != 0) { + LOG_ERR("Cannot open flash area for application slot %s", img_set); + return; + } + + err = boot_set_next(fa, true, true); + + flash_area_close(fa); + if (err == 0) { + LOG_INF("Application confirmed\n"); + } else { + LOG_ERR("Failed to confirm application, err: %d", err); + } + + if (flash_area_open(cpurad_area_id, &fa) != 0) { + LOG_ERR("Cannot open flash area for radio slot %s", img_set); + return; + } + + err = boot_set_next(fa, true, true); + + flash_area_close(fa); + if (err == 0) { + LOG_INF("Radio confirmed\n"); + } else { + LOG_ERR("Failed to confirm radio, err: %d", err); + } +} + +static void select_slot_for_single_boot(enum ab_boot_slot slot) +{ + int err = 0; + char active_slot = (active_boot_slot_get() == SLOT_A) ? 'A' : 'B'; + enum boot_slot new_slot = BOOT_SLOT_NONE; + + if (slot == SLOT_A) { + LOG_INF("Temporarily switching slots (%c -> A)", active_slot); + new_slot = BOOT_SLOT_PRIMARY; + } else if (slot == SLOT_B) { + LOG_INF("Temporarily switching slots (%c -> B)", active_slot); + new_slot = BOOT_SLOT_SECONDARY; + } else { + LOG_ERR("Cannot determine active slot, cannot toggle"); + return; + } + + err = boot_request_set_preferred_slot(ACTIVE_IMAGE, new_slot); + + if (err == 0) { + LOG_INF("Slot toggled, restart the device to enforce"); + } else { + LOG_ERR("Failed to toggle slots, err: %d", err); + } +} + +static void boot_state_report(void) +{ + enum ab_boot_slot active_slot = active_boot_slot_get(); + + if (active_slot == SLOT_A) { + LOG_INF("Booted from slot A"); + } else if (active_slot == SLOT_B) { + LOG_INF("Booted from slot B"); + } else { + LOG_INF("Cannot determine active slot"); + } +} + +static void button_handler(uint32_t button_state, uint32_t has_changed) +{ + if ((has_changed & DK_BTN1_MSK) && (button_state & DK_BTN1_MSK)) { + select_slot_for_single_boot(SLOT_A); + } else if ((has_changed & DK_BTN2_MSK) && (button_state & DK_BTN2_MSK)) { + select_slot_for_single_boot(SLOT_B); + } +} + +struct k_thread status_leds_thread_data; + +static void status_leds_thread_entry_point(void *p1, void *p2, void *p3) +{ + int blinking_led = DK_LED1; + enum ab_boot_slot active_slot = active_boot_slot_get(); + + if (active_slot == SLOT_A) { + blinking_led = DK_LED1; + } else if (active_slot == SLOT_B) { + blinking_led = DK_LED2; + } else { + return; + } + + while (1) { + for (int i = 0; i < CONFIG_N_BLINKS; i++) { + dk_set_led_off(blinking_led); + k_msleep(250); + dk_set_led_on(blinking_led); + k_msleep(250); + } + + k_msleep(5000); + } +} + +void ab_actions_perform(void) +{ + int ret; + + boot_state_report(); + + ret = dk_leds_init(); + if (ret) { + LOG_ERR("Cannot init LEDs (err: %d)", ret); + } + + ret = dk_buttons_init(button_handler); + if (ret) { + LOG_ERR("Cannot init buttons (err: %d)", ret); + } + + k_thread_create(&status_leds_thread_data, status_leds_thread_stack_area, + K_THREAD_STACK_SIZEOF(status_leds_thread_stack_area), + status_leds_thread_entry_point, + NULL, NULL, NULL, + STATUS_LEDS_THREAD_PRIORITY, 0, K_NO_WAIT); + + device_healthcheck(); +} diff --git a/samples/dfu/ab_split/src/ab_utils.h b/samples/dfu/ab_split/src/ab_utils.h new file mode 100644 index 000000000000..abaabbda163a --- /dev/null +++ b/samples/dfu/ab_split/src/ab_utils.h @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +void ab_actions_perform(void); diff --git a/samples/dfu/ab_split/src/main.c b/samples/dfu/ab_split/src/main.c new file mode 100644 index 000000000000..1343bdbf530c --- /dev/null +++ b/samples/dfu/ab_split/src/main.c @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2012-2014 Wind River Systems, Inc. + * Copyright (c) 2020 Prevas A/S + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include "ab_utils.h" + +#define LOG_LEVEL LOG_LEVEL_DBG +#include +LOG_MODULE_REGISTER(ab_sample); + +int main(void) +{ +#ifdef CONFIG_MCUMGR_TRANSPORT_BT + start_smp_bluetooth_adverts(); +#endif + + /* Give BLE a moment to start up */ + k_sleep(K_MSEC(1000)); + + ab_actions_perform(); + + /* using __TIME__ ensure that a new binary will be built on every + * compile which is convenient when testing firmware upgrade. + */ + LOG_INF("build time: " __DATE__ " " __TIME__); + + /* The system work queue handles all incoming mcumgr requests. Let the + * main thread idle while the mcumgr server runs. + */ + while (1) { + k_sleep(K_MSEC(1000)); + } + + return 0; +} diff --git a/samples/dfu/ab_split/sysbuild.conf b/samples/dfu/ab_split/sysbuild.conf new file mode 100644 index 000000000000..6cb102f84375 --- /dev/null +++ b/samples/dfu/ab_split/sysbuild.conf @@ -0,0 +1,15 @@ +# Enable MCUboot bootloader support +SB_CONFIG_BOOTLOADER_MCUBOOT=y + +# Enable radiocore +SB_CONFIG_NETCORE_IPC_RADIO=y +SB_CONFIG_NETCORE_IPC_RADIO_BT_HCI_IPC=y + +# Enable direct XIP with revert support +SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP_WITH_REVERT=y + +# Disable merging of slots +SB_CONFIG_MCUBOOT_SIGN_MERGED_BINARY=n + +# Enable manifest-based updates +SB_CONFIG_MCUBOOT_MANIFEST_UPDATES=y diff --git a/samples/dfu/ab_split/sysbuild/mcuboot/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/samples/dfu/ab_split/sysbuild/mcuboot/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 000000000000..34ce53982244 --- /dev/null +++ b/samples/dfu/ab_split/sysbuild/mcuboot/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + + #include "../../nrf54h20dk_nrf54h20_memory_map.dtsi" + +/ { + chosen { + zephyr,code-partition = &boot_partition; + }; +}; diff --git a/samples/dfu/ab_split/sysbuild/mcuboot/prj.conf b/samples/dfu/ab_split/sysbuild/mcuboot/prj.conf new file mode 100644 index 000000000000..1d44edf17d6e --- /dev/null +++ b/samples/dfu/ab_split/sysbuild/mcuboot/prj.conf @@ -0,0 +1,33 @@ +# Enable boot requests through retained memory. +CONFIG_RETAINED_MEM=y +CONFIG_RETENTION=y +CONFIG_NRF_MCUBOOT_BOOT_REQUEST=y + +CONFIG_NRF_SECURITY=y +CONFIG_MULTITHREADING=y + + +# Configuration below is copied from mcuboot/boot/zephyr/prj.conf, as creating +# the sysbuild/mcuboot directory inside a sample removes the default configuration. + +CONFIG_PM=n + +CONFIG_MAIN_STACK_SIZE=10240 + +CONFIG_BOOT_SWAP_SAVE_ENCTLV=n +CONFIG_BOOT_ENCRYPT_IMAGE=n + +CONFIG_BOOT_UPGRADE_ONLY=n +CONFIG_BOOT_BOOTSTRAP=n + +CONFIG_FLASH=y + +CONFIG_LOG=y +CONFIG_LOG_MODE_MINIMAL=y +CONFIG_LOG_DEFAULT_LEVEL=0 +CONFIG_MCUBOOT_LOG_LEVEL_INF=y +CONFIG_CBPRINTF_NANO=y +CONFIG_PICOLIBC=y +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=0 + +CONFIG_NCS_APPLICATION_BOOT_BANNER_STRING="MCUboot" diff --git a/samples/dfu/ab_split/sysbuild/nrf54h20dk_nrf54h20_memory_map.dtsi b/samples/dfu/ab_split/sysbuild/nrf54h20dk_nrf54h20_memory_map.dtsi new file mode 100644 index 000000000000..b3b43b05fac1 --- /dev/null +++ b/samples/dfu/ab_split/sysbuild/nrf54h20dk_nrf54h20_memory_map.dtsi @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + nrf,bootloader-request = &boot_request; + }; +}; + +/ { + reserved-memory { + cpuapp_retained_mem: memory@e1ad000 { + compatible = "zephyr,memory-region"; + reg = <0xe1ad000 DT_SIZE_K(4)>; + zephyr,memory-region = "RetainedMem"; + status = "okay"; + + retainedmem { + compatible = "zephyr,retained-ram"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + boot_request: boot_request@0 { + compatible = "zephyr,retention"; + status = "okay"; + reg = <0x0 16>; + prefix = [0B 01]; + checksum = <4>; + }; + }; + }; + }; +}; diff --git a/scripts/ci/license_allow_list.yaml b/scripts/ci/license_allow_list.yaml index c56fd42497de..45c8fcc5e2ad 100644 --- a/scripts/ci/license_allow_list.yaml +++ b/scripts/ci/license_allow_list.yaml @@ -50,6 +50,7 @@ Apache-2.0: | ^nrf/tests/benchmarks/kernel_freq_change/src/kernel_context.c ^nrf/tests/subsys/suit/common/tls_config/user-tls-conf.h ^nrf/subsys/settings/ + ^nrf/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c ^nrf/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt_state.c ^nrf/tests/zephyr/subsys/settings/ ^nrf/samples/zephyr/ diff --git a/subsys/bootloader/Kconfig b/subsys/bootloader/Kconfig index 4b495344a396..14a146d59707 100644 --- a/subsys/bootloader/Kconfig +++ b/subsys/bootloader/Kconfig @@ -188,4 +188,27 @@ config NCS_MCUBOOT_BOOTLOADER_SIGN_MERGED_BINARY help This is a Kconfig which is informative only, the value should not be changed. +config NCS_MCUBOOT_MANIFEST_UPDATES + bool "Enable transactional updates" + help + Enables support for transactional updates using manifests. + This is a Kconfig which is informative only, the value should not be changed. + +if NCS_MCUBOOT_MANIFEST_UPDATES + +config NCS_MCUBOOT_IMGTOOL_APPEND_MANIFEST + bool "MCUboot append manifest" + help + Append common manifest while signing the image. + This is a Kconfig which is informative only, the value should not be changed. + +config NCS_MCUBOOT_MANIFEST_IMAGE_NUMBER + int "Number of image that must include manifest" + help + Specifies the index of the image that must include the manifest. + This is a Kconfig which is informative only, the value should not be changed. + +endif # NCS_MCUBOOT_MANIFEST_UPDATES + + endmenu diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/CMakeLists.txt b/subsys/mgmt/mcumgr/grp/img_mgmt/CMakeLists.txt index 4d4b9326951f..0b6af9076cbf 100644 --- a/subsys/mgmt/mcumgr/grp/img_mgmt/CMakeLists.txt +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/CMakeLists.txt @@ -7,9 +7,16 @@ if(CONFIG_MCUMGR_GRP_IMG_NRF) zephyr_library_amend() zephyr_library_sources( + src/img_mgmt.c src/img_mgmt_state.c ) + set_source_files_properties( + ${ZEPHYR_BASE}/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c + DIRECTORY ${ZEPHYR_BASE}/subsys/mgmt/mcumgr/grp/img_mgmt/ + PROPERTIES HEADER_FILE_ONLY ON + ) + set_source_files_properties( ${ZEPHYR_BASE}/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt_state.c DIRECTORY ${ZEPHYR_BASE}/subsys/mgmt/mcumgr/grp/img_mgmt/ diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/Kconfig b/subsys/mgmt/mcumgr/grp/img_mgmt/Kconfig index e7b68fd4add8..f5a7d34c46ae 100644 --- a/subsys/mgmt/mcumgr/grp/img_mgmt/Kconfig +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/Kconfig @@ -13,4 +13,15 @@ config MCUMGR_GRP_IMG_NRF Enables use of an extended version of the image management implementation that adds Nordic-specific functionalities. +if MCUMGR_GRP_IMG_NRF + +config MCUMGR_GRP_IMG_QSPI_XIP_SPLIT_IMAGE + bool "QSPI XIP Split image mode" + depends on MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP + help + This option should not be selected by users and should automatically be selected by + sysbuild if needed. This enables selecting the correct slot when running a QSPI XIP + split image application in DirectXIP mode. + +endif # MCUMGR_GRP_IMG_NRF endmenu diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c new file mode 100644 index 000000000000..60690a10a891 --- /dev/null +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c @@ -0,0 +1,1294 @@ +/* + * Copyright (c) 2018-2021 mcumgr authors + * Copyright (c) 2022-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#ifdef CONFIG_IMG_ENABLE_IMAGE_CHECK +#include +#endif + +#ifdef CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES +#include +#include +#endif + +#ifdef CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS +#include +#include +#endif + +#if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD) +#include +#include +#endif + +#if !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD) + +#if USE_PARTITION_MANAGER +#include + +#ifdef PM_MCUBOOT_SECONDARY_PAD_SIZE +BUILD_ASSERT(PM_MCUBOOT_PAD_SIZE == PM_MCUBOOT_SECONDARY_PAD_SIZE); +#endif + +#if CONFIG_BUILD_WITH_TFM + #define PM_ADDRESS_OFFSET (PM_MCUBOOT_PAD_SIZE + PM_TFM_SIZE) +#else + #define PM_ADDRESS_OFFSET (PM_MCUBOOT_PAD_SIZE) +#endif + +#define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ + (FIXED_PARTITION_OFFSET(label) == (PM_ADDRESS - PM_ADDRESS_OFFSET)) + +#else /* ! USE_PARTITION_MANAGER */ +#ifndef CONFIG_FLASH_LOAD_OFFSET +#error MCUmgr requires application to be built with CONFIG_FLASH_LOAD_OFFSET set \ + to be able to figure out application running slot. +#endif + +#define FIXED_PARTITION_IS_RUNNING_APP_PARTITION(label) \ + (FIXED_PARTITION_OFFSET(label) <= CONFIG_FLASH_LOAD_OFFSET && \ + FIXED_PARTITION_OFFSET(label) + FIXED_PARTITION_SIZE(label) > CONFIG_FLASH_LOAD_OFFSET) +#endif /* USE_PARTITION_MANAGER */ + +BUILD_ASSERT(sizeof(struct image_header) == IMAGE_HEADER_SIZE, + "struct image_header not required size"); + +#if CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER >= 2 +#if FIXED_PARTITION_EXISTS(slot0_ns_partition) && \ + FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot0_ns_partition) +#define ACTIVE_IMAGE_IS 0 +#elif FIXED_PARTITION_EXISTS(slot0_partition) && \ + FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot0_partition) +#define ACTIVE_IMAGE_IS 0 +#elif FIXED_PARTITION_EXISTS(slot1_partition) && \ + FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot1_partition) +#define ACTIVE_IMAGE_IS 0 +#elif FIXED_PARTITION_EXISTS(slot2_partition) && \ + FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot2_partition) +#define ACTIVE_IMAGE_IS 1 +#elif FIXED_PARTITION_EXISTS(slot3_partition) && \ + FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot3_partition) +#define ACTIVE_IMAGE_IS 1 +#elif FIXED_PARTITION_EXISTS(slot4_partition) && \ + FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot4_partition) +#define ACTIVE_IMAGE_IS 2 +#elif FIXED_PARTITION_EXISTS(slot5_partition) && \ + FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot5_partition) +#define ACTIVE_IMAGE_IS 2 +#else +#define ACTIVE_IMAGE_IS 0 +#endif +#else +#define ACTIVE_IMAGE_IS 0 +#endif + +#else +#define ACTIVE_IMAGE_IS 0 +#endif + +#if CONFIG_MCUBOOT_BOOTLOADER_MODE_FIRMWARE_UPDATER +#define SLOTS_PER_IMAGE 1 +#else +#define SLOTS_PER_IMAGE 2 +#endif + +LOG_MODULE_REGISTER(mcumgr_img_grp, CONFIG_MCUMGR_GRP_IMG_LOG_LEVEL); + +struct img_mgmt_state g_img_mgmt_state; + +#ifdef CONFIG_MCUMGR_GRP_IMG_MUTEX +static K_MUTEX_DEFINE(img_mgmt_mutex); +#endif + +#ifdef CONFIG_MCUMGR_GRP_IMG_VERBOSE_ERR +const char *img_mgmt_err_str_app_reject = "app reject"; +const char *img_mgmt_err_str_hdr_malformed = "header malformed"; +const char *img_mgmt_err_str_magic_mismatch = "magic mismatch"; +const char *img_mgmt_err_str_no_slot = "no slot"; +const char *img_mgmt_err_str_flash_open_failed = "fa open fail"; +const char *img_mgmt_err_str_flash_erase_failed = "fa erase fail"; +const char *img_mgmt_err_str_flash_write_failed = "fa write fail"; +const char *img_mgmt_err_str_downgrade = "downgrade"; +const char *img_mgmt_err_str_image_bad_flash_addr = "img addr mismatch"; +const char *img_mgmt_err_str_image_too_large = "img too large"; +const char *img_mgmt_err_str_data_overrun = "data overrun"; +#endif + +void img_mgmt_take_lock(void) +{ +#ifdef CONFIG_MCUMGR_GRP_IMG_MUTEX + k_mutex_lock(&img_mgmt_mutex, K_FOREVER); +#endif +} + +void img_mgmt_release_lock(void) +{ +#ifdef CONFIG_MCUMGR_GRP_IMG_MUTEX + k_mutex_unlock(&img_mgmt_mutex); +#endif +} + +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO_HOOKS) +static bool img_mgmt_reset_zse(struct smp_streamer *ctxt) +{ + zcbor_state_t *zse = ctxt->writer->zs; + + /* Because there is already data in the buffer, it must be cleared first */ + net_buf_reset(ctxt->writer->nb); + ctxt->writer->nb->len = sizeof(struct smp_hdr); + zcbor_new_encode_state(zse, ARRAY_SIZE(ctxt->writer->zs), + ctxt->writer->nb->data + sizeof(struct smp_hdr), + net_buf_tailroom(ctxt->writer->nb), 0); + + return zcbor_map_start_encode(zse, CONFIG_MCUMGR_SMP_CBOR_MAX_MAIN_MAP_ENTRIES); +} +#endif + +#if defined(CONFIG_MCUMGR_GRP_IMG_TOO_LARGE_SYSBUILD) +static bool img_mgmt_slot_max_size(size_t *area_sizes, zcbor_state_t *zse) +{ + bool ok = true; + + if (area_sizes[0] > 0 && area_sizes[1] > 0) { + /* Calculate maximum image size */ + size_t area_size_difference = (size_t)abs((ssize_t)area_sizes[1] - + (ssize_t)area_sizes[0]); + + if (CONFIG_MCUBOOT_UPDATE_FOOTER_SIZE >= area_size_difference) { + ok = zcbor_tstr_put_lit(zse, "max_image_size") && + zcbor_uint32_put(zse, (uint32_t)(area_sizes[0] - + CONFIG_MCUBOOT_UPDATE_FOOTER_SIZE)); } + } + + return ok; +} +#elif defined(CONFIG_MCUMGR_GRP_IMG_TOO_LARGE_BOOTLOADER_INFO) +static bool img_mgmt_slot_max_size(size_t *area_sizes, zcbor_state_t *zse) +{ + bool ok = true; + int rc; + int max_app_size; + + ARG_UNUSED(area_sizes); + + rc = blinfo_lookup(BLINFO_MAX_APPLICATION_SIZE, &max_app_size, sizeof(max_app_size)); + if (rc < 0) { + LOG_ERR("Failed to lookup max application size: %d", rc); + } else if (rc > 0) { + ok = zcbor_tstr_put_lit(zse, "max_image_size") && + zcbor_uint32_put(zse, (uint32_t)max_app_size); + } + + return ok; +} +#endif + +/** + * Finds the TLVs in the specified image slot, if any. + */ +static int img_mgmt_find_tlvs(int slot, size_t *start_off, size_t *end_off, uint16_t magic) +{ + struct image_tlv_info tlv_info; + int rc; + + rc = img_mgmt_read(slot, *start_off, &tlv_info, sizeof(tlv_info)); + if (rc != 0) { + /* Read error. */ + return rc; + } + + if (tlv_info.it_magic != magic) { + /* No TLVs. */ + return IMG_MGMT_ERR_NO_TLVS; + } + + *start_off += sizeof(tlv_info); + *end_off = *start_off + tlv_info.it_tlv_tot; + + return IMG_MGMT_ERR_OK; +} + +int img_mgmt_active_slot(int image) +{ + int slot = 0; + + /* Multi image does not support DirectXIP or RAM load currently */ +#if CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER > 1 + slot = (image << 1); + +#if defined(CONFIG_MCUMGR_GRP_IMG_QSPI_XIP_SPLIT_IMAGE) || \ + defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) || \ + defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) + if (FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot1_partition)) { + slot += 1; + } +#endif +#elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD) + /* RAM load requires querying bootloader */ + int rc; + uint8_t temp_slot; + + rc = blinfo_lookup(BLINFO_RUNNING_SLOT, &temp_slot, sizeof(temp_slot)); + + if (rc <= 0) { + LOG_ERR("Failed to fetch active slot: %d", rc); + + return 255; + } + + slot = (int)temp_slot; +#else + /* This covers single image, including DirectXiP */ + if (FIXED_PARTITION_IS_RUNNING_APP_PARTITION(slot1_partition)) { + slot = 1; + } +#endif + LOG_DBG("(%d) => %d", image, slot); + + return slot; +} + +int img_mgmt_active_image(void) +{ + return ACTIVE_IMAGE_IS; +} + +#ifdef CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES +/** + * Checks whether the manifest of the image in the specified slot can be used for booting. + * + * @param slot The slot to check the manifest for. + * + * @return true if the manifest can be used, false otherwise. + */ +static bool boot_check_manifest(enum boot_slot slot) +{ + struct image_header hdr; + struct image_tlv tlv; + size_t data_off; + size_t data_end; + bool manifest_found; + uint8_t erased_val; + uint32_t erased_val_32; + struct mcuboot_manifest tmp_manifest; + uint8_t hash[IMAGE_HASH_SIZE]; + int rc; + int image_slot = CONFIG_NCS_MCUBOOT_MANIFEST_IMAGE_NUMBER * BOOT_SLOT_COUNT + slot; + + rc = img_mgmt_erased_val(image_slot, &erased_val); + if (rc != 0) { + return false; + } + + 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 false; + } + + erased_val_32 = ERASED_VAL_32(erased_val); + if (hdr.ih_magic == IMAGE_MAGIC) { + /* Valid header */ + } else if (hdr.ih_magic == erased_val_32) { + return false; + } else { + return false; + } + + /* Read the image's TLVs. We try to find manifest only inside the protected TLVs. + * If the manifest is missing, the image is considered invalid. + */ + 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 != 0) { + return false; + } + + manifest_found = false; + while (data_off + sizeof(tlv) <= data_end) { + rc = img_mgmt_read(image_slot, data_off, &tlv, sizeof(tlv)); + if (rc != 0) { + return false; + } + if (tlv.it_type == 0xff && tlv.it_len == 0xffff) { + return false; + } + if ((tlv.it_type != IMAGE_TLV_MANIFEST) || (tlv.it_len != sizeof(struct mcuboot_manifest))) { + /* Non-manifest TLV. Skip it. */ + data_off += sizeof(tlv) + tlv.it_len; + continue; + } + + if (manifest_found) { + /* More than one manifest. */ + return false; + } + manifest_found = true; + + data_off += sizeof(tlv); + if (data_off + sizeof(struct mcuboot_manifest) > data_end) { + return false; + } + rc = img_mgmt_read(image_slot, data_off, &tmp_manifest, sizeof(struct mcuboot_manifest)); + if (rc != 0) { + return false; + } + } + + if (!manifest_found) { + return false; + } + + for (size_t i = 0; i < BOOT_IMAGE_NUMBER; i++) { + if (CONFIG_NCS_MCUBOOT_MANIFEST_IMAGE_NUMBER == i) { + continue; + } + + rc = img_mgmt_read_info(i * BOOT_SLOT_COUNT + slot, NULL, hash, NULL); + if ((rc == 0) && bootutil_verify_manifest_image_hash(&tmp_manifest, hash, i)) { + /* Hash matches */ + continue; + } + +#if !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) && \ + !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) && \ + !defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD) + /* In SWAP modes, the image may be placed in either primary or secondary slot. */ + rc = img_mgmt_read_info(i * BOOT_SLOT_COUNT + ((slot + 1) % BOOT_SLOT_COUNT), NULL, + hash, NULL); + if ((rc == 0) && bootutil_verify_manifest_image_hash(&tmp_manifest, hash, i)) { + /* Hash matches */ + continue; + } +#endif + + LOG_ERR("Manifest hash does not match image %d hash", i); + return false; + } + + return true; +} +#endif /* CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES */ + +/* + * Reads the version and build hash from the specified image slot. + */ +int img_mgmt_read_info(int image_slot, struct image_version *ver, uint8_t *hash, uint32_t *flags) +{ + struct image_header hdr; + struct image_tlv tlv; + size_t data_off; + size_t data_end; + bool hash_found; + uint8_t erased_val; + uint32_t erased_val_32; + int rc; + + rc = img_mgmt_erased_val(image_slot, &erased_val); + if (rc != 0) { + return IMG_MGMT_ERR_FLASH_CONFIG_QUERY_FAIL; + } + + 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; + } + + if (ver != NULL) { + memset(ver, erased_val, sizeof(*ver)); + } + erased_val_32 = ERASED_VAL_32(erased_val); + if (hdr.ih_magic == IMAGE_MAGIC) { + if (ver != NULL) { + memcpy(ver, &hdr.ih_ver, sizeof(*ver)); + } + } else if (hdr.ih_magic == erased_val_32) { + return IMG_MGMT_ERR_NO_IMAGE; + } else { + return IMG_MGMT_ERR_INVALID_IMAGE_HEADER_MAGIC; + } + + if (flags != NULL) { + *flags = hdr.ih_flags; +#ifdef CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES + /* Mark slot as unbootable if manifest is not satisfied. */ + if (!boot_check_manifest(image_slot % SLOTS_PER_IMAGE)) { + *flags |= IMAGE_F_NON_BOOTABLE; + } +#endif + } + + /* Read the image's TLVs. We first try to find the protected TLVs, if the protected + * TLV does not exist, we try to find non-protected TLV which also contains the 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 + + 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) { + /* The data offset should start after the header bytes after the end of + * the protected TLV, if one exists. + */ + data_off = data_end - sizeof(struct image_tlv_info); + } + + rc = img_mgmt_find_tlvs(image_slot, &data_off, &data_end, IMAGE_TLV_INFO_MAGIC); + if (rc != 0) { + return IMG_MGMT_ERR_NO_TLVS; + } + + hash_found = false; + while (data_off + sizeof(tlv) <= data_end) { + rc = img_mgmt_read(image_slot, data_off, &tlv, sizeof(tlv)); + if (rc != 0) { + return rc; + } + if (tlv.it_type == 0xff && tlv.it_len == 0xffff) { + return IMG_MGMT_ERR_INVALID_TLV; + } + if (tlv.it_type != IMAGE_TLV_SHA || tlv.it_len != IMAGE_SHA_LEN) { + /* Non-hash TLV. Skip it. */ + data_off += sizeof(tlv) + tlv.it_len; + continue; + } + + if (hash_found) { + /* More than one hash. */ + return IMG_MGMT_ERR_TLV_MULTIPLE_HASHES_FOUND; + } + hash_found = true; + + data_off += sizeof(tlv); + if (hash != NULL) { + if (data_off + IMAGE_SHA_LEN > data_end) { + return IMG_MGMT_ERR_TLV_INVALID_SIZE; + } + rc = img_mgmt_read(image_slot, data_off, hash, IMAGE_SHA_LEN); + if (rc != 0) { + return rc; + } + } + } + + if (!hash_found) { + return IMG_MGMT_ERR_HASH_NOT_FOUND; + } + + return 0; +} + +/* + * Finds image given version number. Returns the slot number image is in, + * or -1 if not found. + */ +int +img_mgmt_find_by_ver(struct image_version *find, uint8_t *hash) +{ + int i; + struct image_version ver; + + for (i = 0; i < SLOTS_PER_IMAGE * CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER; i++) { + if (img_mgmt_read_info(i, &ver, hash, NULL) != 0) { + continue; + } + if (!memcmp(find, &ver, sizeof(ver))) { + return i; + } + } + return -1; +} + +/* + * Finds image given hash of the image. Returns the slot number image is in, + * or -1 if not found. + */ +int +img_mgmt_find_by_hash(uint8_t *find, struct image_version *ver) +{ + int i; + uint8_t hash[IMAGE_SHA_LEN]; + + for (i = 0; i < SLOTS_PER_IMAGE * CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER; i++) { + if (img_mgmt_read_info(i, ver, hash, NULL) != 0) { + continue; + } + if (!memcmp(hash, find, IMAGE_SHA_LEN)) { + return i; + } + } + return -1; +} + +/* + * Resets upload status to defaults (no upload in progress) + */ +#ifdef CONFIG_MCUMGR_GRP_IMG_MUTEX +void img_mgmt_reset_upload(void) +#else +static void img_mgmt_reset_upload(void) +#endif +{ + img_mgmt_take_lock(); + memset(&g_img_mgmt_state, 0, sizeof(g_img_mgmt_state)); + g_img_mgmt_state.area_id = -1; + img_mgmt_release_lock(); +} + +/** + * Command handler: image erase + */ +static int +img_mgmt_erase(struct smp_streamer *ctxt) +{ + struct image_version ver; + int rc; + zcbor_state_t *zse = ctxt->writer->zs; + zcbor_state_t *zsd = ctxt->reader->zs; + bool ok; + uint32_t slot = img_mgmt_get_opposite_slot(img_mgmt_active_slot(img_mgmt_active_image())); + size_t decoded = 0; + + struct zcbor_map_decode_key_val image_erase_decode[] = { + ZCBOR_MAP_DECODE_KEY_DECODER("slot", zcbor_uint32_decode, &slot), + }; + + ok = zcbor_map_decode_bulk(zsd, image_erase_decode, + ARRAY_SIZE(image_erase_decode), &decoded) == 0; + + if (!ok) { + return MGMT_ERR_EINVAL; + } + + img_mgmt_take_lock(); + + /* + * First check if image info is valid. + * This check is done incase the flash area has a corrupted image. + */ + rc = img_mgmt_read_info(slot, &ver, NULL, NULL); + + if (rc == 0) { + /* Image info is valid. */ + if (img_mgmt_slot_in_use(slot)) { + /* No free slot. */ + ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, + IMG_MGMT_ERR_NO_FREE_SLOT); + goto end; + } + } + + rc = img_mgmt_erase_slot(slot); + img_mgmt_reset_upload(); + + if (rc != 0) { +#if defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) + int32_t err_rc; + uint16_t err_group; + + (void)mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED, NULL, 0, &err_rc, + &err_group); +#endif + ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, rc); + goto end; + } + + if (IS_ENABLED(CONFIG_MCUMGR_SMP_LEGACY_RC_BEHAVIOUR)) { + if (!zcbor_tstr_put_lit(zse, "rc") || !zcbor_int32_put(zse, 0)) { + img_mgmt_release_lock(); + return MGMT_ERR_EMSGSIZE; + } + } + +end: + img_mgmt_release_lock(); + + return MGMT_ERR_EOK; +} + +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO) +/** + * Command handler: image slot info + */ +static int img_mgmt_slot_info(struct smp_streamer *ctxt) +{ + int rc; + zcbor_state_t *zse = ctxt->writer->zs; + bool ok; + uint8_t i = 0; + size_t area_sizes[SLOTS_PER_IMAGE]; + +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO_HOOKS) + int32_t err_rc; + uint16_t err_group; + enum mgmt_cb_return status; +#endif + + img_mgmt_take_lock(); + + ok = zcbor_tstr_put_lit(zse, "images") && + zcbor_list_start_encode(zse, 10); + + while (i < CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER * SLOTS_PER_IMAGE) { + const struct flash_area *fa; + int area_id = img_mgmt_flash_area_id(i); + + if ((i % SLOTS_PER_IMAGE) == 0) { + memset(area_sizes, 0, sizeof(area_sizes)); + + ok = zcbor_map_start_encode(zse, 4) && + zcbor_tstr_put_lit(zse, "image") && + zcbor_uint32_put(zse, (uint32_t)(i / SLOTS_PER_IMAGE)) && + zcbor_tstr_put_lit(zse, "slots") && + zcbor_list_start_encode(zse, 4); + + if (!ok) { + goto finish; + } + } + + ok = zcbor_map_start_encode(zse, 4) && + zcbor_tstr_put_lit(zse, "slot") && + zcbor_uint32_put(zse, (uint32_t)(i % SLOTS_PER_IMAGE)); + + if (!ok) { + goto finish; + } + + rc = flash_area_open(area_id, &fa); + + if (rc) { + /* Failed opening slot, mark as error */ + ok = zcbor_tstr_put_lit(zse, "rc") && + zcbor_int32_put(zse, rc); + + LOG_ERR("Failed to open slot %d for information fetching: %d", area_id, rc); + } else { +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO_HOOKS) + struct img_mgmt_slot_info_slot slot_info_data = { + .image = (i / SLOTS_PER_IMAGE), + .slot = (i % SLOTS_PER_IMAGE), + .fa = fa, + .zse = zse, + }; +#endif + + if (sizeof(fa->fa_size) == sizeof(uint64_t)) { + ok = zcbor_tstr_put_lit(zse, "size") && + zcbor_uint64_put(zse, fa->fa_size); + } else { + ok = zcbor_tstr_put_lit(zse, "size") && + zcbor_uint32_put(zse, fa->fa_size); + } + + area_sizes[(i % SLOTS_PER_IMAGE)] = fa->fa_size; + + if (!ok) { + goto finish; + } + + /* + * Check if we support uploading to this slot and if so, return the + * image ID + */ +#if defined(CONFIG_MCUMGR_GRP_IMG_DIRECT_UPLOAD) + ok = zcbor_tstr_put_lit(zse, "upload_image_id") && + zcbor_uint32_put(zse, (i + 1)); +#else + if (img_mgmt_active_slot((i / SLOTS_PER_IMAGE)) != i) { + ok = zcbor_tstr_put_lit(zse, "upload_image_id") && + zcbor_uint32_put(zse, (i / SLOTS_PER_IMAGE)); + } +#endif + + if (!ok) { + goto finish; + } + +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO_HOOKS) + status = mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_SLOT_INFO_SLOT, + &slot_info_data, sizeof(slot_info_data), + &err_rc, &err_group); +#endif + + flash_area_close(fa); + +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO_HOOKS) + if (status != MGMT_CB_OK) { + if (status == MGMT_CB_ERROR_RC) { + img_mgmt_release_lock(); + return err_rc; + } + + ok = img_mgmt_reset_zse(ctxt) && + smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); + + goto finish; + } +#endif + } + + ok &= zcbor_map_end_encode(zse, 4); + + if (!ok) { + goto finish; + } + + if ((i % SLOTS_PER_IMAGE) == (SLOTS_PER_IMAGE - 1)) { +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO_HOOKS) + struct img_mgmt_slot_info_image image_info_data = { + .image = (i / SLOTS_PER_IMAGE), + .zse = zse, + }; +#endif + + ok = zcbor_list_end_encode(zse, 4); + + if (!ok) { + goto finish; + } + +#if defined(CONFIG_MCUMGR_GRP_IMG_TOO_LARGE_SYSBUILD) || \ + defined(MCUMGR_GRP_IMG_TOO_LARGE_BOOTLOADER_INFO) + ok = img_mgmt_slot_max_size(area_sizes, zse); + + if (!ok) { + goto finish; + } +#endif + +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO_HOOKS) + status = mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_SLOT_INFO_IMAGE, + &image_info_data, sizeof(image_info_data), + &err_rc, &err_group); + + if (status != MGMT_CB_OK) { + if (status == MGMT_CB_ERROR_RC) { + img_mgmt_release_lock(); + return err_rc; + } + + ok = img_mgmt_reset_zse(ctxt) && + smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); + + goto finish; + } +#endif + + ok = zcbor_map_end_encode(zse, 4); + + if (!ok) { + goto finish; + } + } + + ++i; + } + + ok = zcbor_list_end_encode(zse, 10); + +finish: + img_mgmt_release_lock(); + + return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; +} +#endif + +static int +img_mgmt_upload_good_rsp(struct smp_streamer *ctxt) +{ + zcbor_state_t *zse = ctxt->writer->zs; + bool ok = true; + + if (IS_ENABLED(CONFIG_MCUMGR_SMP_LEGACY_RC_BEHAVIOUR)) { + ok = zcbor_tstr_put_lit(zse, "rc") && + zcbor_int32_put(zse, MGMT_ERR_EOK); + } + + ok = ok && zcbor_tstr_put_lit(zse, "off") && + zcbor_size_put(zse, g_img_mgmt_state.off); + + return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; +} + +/** + * Logs an upload request if necessary. + * + * @param is_first Whether the request includes the first chunk of the image. + * @param is_last Whether the request includes the last chunk of the image. + * @param status The result of processing the upload request (MGMT_ERR code). + * + * @return 0 on success; nonzero on failure. + */ +static int +img_mgmt_upload_log(bool is_first, bool is_last, int status) +{ + uint8_t hash[IMAGE_SHA_LEN]; + const uint8_t *hashp; + int rc; + + if (is_last || status != 0) { + /* Log the image hash if we know it. */ + rc = img_mgmt_read_info(1, NULL, hash, NULL); + if (rc != 0) { + hashp = NULL; + } else { + hashp = hash; + } + } + + return 0; +} + +/** + * Command handler: image upload + */ +static int +img_mgmt_upload(struct smp_streamer *ctxt) +{ + zcbor_state_t *zse = ctxt->writer->zs; + zcbor_state_t *zsd = ctxt->reader->zs; + bool ok; + size_t decoded = 0; + struct img_mgmt_upload_req req = { + .off = SIZE_MAX, + .size = SIZE_MAX, + .img_data = { 0 }, + .data_sha = { 0 }, + .upgrade = false, + .image = 0, + }; + int rc; + struct img_mgmt_upload_action action; + bool last = false; + bool reset = false; + +#ifdef CONFIG_IMG_ENABLE_IMAGE_CHECK + bool data_match = false; +#endif + +#if defined(CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK) + enum mgmt_cb_return status; +#endif + +#if defined(CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK) || \ +defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) || \ +defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS) + int32_t err_rc; + uint16_t err_group; +#endif + + struct zcbor_map_decode_key_val image_upload_decode[] = { + ZCBOR_MAP_DECODE_KEY_DECODER("image", zcbor_uint32_decode, &req.image), + ZCBOR_MAP_DECODE_KEY_DECODER("data", zcbor_bstr_decode, &req.img_data), + ZCBOR_MAP_DECODE_KEY_DECODER("len", zcbor_size_decode, &req.size), + ZCBOR_MAP_DECODE_KEY_DECODER("off", zcbor_size_decode, &req.off), + ZCBOR_MAP_DECODE_KEY_DECODER("sha", zcbor_bstr_decode, &req.data_sha), + ZCBOR_MAP_DECODE_KEY_DECODER("upgrade", zcbor_bool_decode, &req.upgrade) + }; + +#if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS) + struct mgmt_evt_op_cmd_arg cmd_status_arg = { + .group = MGMT_GROUP_ID_IMAGE, + .id = IMG_MGMT_ID_UPLOAD, + }; +#endif + +#if defined(CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK) + struct img_mgmt_upload_check upload_check_data = { + .action = &action, + .req = &req, + }; +#endif + + ok = zcbor_map_decode_bulk(zsd, image_upload_decode, + ARRAY_SIZE(image_upload_decode), &decoded) == 0; + + IMG_MGMT_UPLOAD_ACTION_SET_RC_RSN(&action, NULL); + + if (!ok) { + return MGMT_ERR_EINVAL; + } + + img_mgmt_take_lock(); + + /* Determine what actions to take as a result of this request. */ + rc = img_mgmt_upload_inspect(&req, &action); + if (rc != 0) { +#if defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) + (void)mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED, NULL, 0, &err_rc, + &err_group); +#endif + + MGMT_CTXT_SET_RC_RSN(ctxt, IMG_MGMT_UPLOAD_ACTION_RC_RSN(&action)); + LOG_ERR("Image upload inspect failed: %d", rc); + ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, rc); + goto end; + } + + if (!action.proceed) { + /* Request specifies incorrect offset. Respond with a success code and + * the correct offset. + */ + rc = img_mgmt_upload_good_rsp(ctxt); + img_mgmt_release_lock(); + return rc; + } + +#if defined(CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK) + /* Request is valid. Give the application a chance to reject this upload + * request. + */ + status = mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK, &upload_check_data, + sizeof(upload_check_data), &err_rc, &err_group); + + if (status != MGMT_CB_OK) { + IMG_MGMT_UPLOAD_ACTION_SET_RC_RSN(&action, img_mgmt_err_str_app_reject); + + if (status == MGMT_CB_ERROR_RC) { + rc = err_rc; + ok = zcbor_tstr_put_lit(zse, "rc") && + zcbor_int32_put(zse, rc); + } else { + ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); + } + + goto end; + } +#endif + + /* Remember flash area ID and image size for subsequent upload requests. */ + g_img_mgmt_state.area_id = action.area_id; + g_img_mgmt_state.size = action.size; + + if (req.off == 0) { + /* + * New upload. + */ +#ifdef CONFIG_IMG_ENABLE_IMAGE_CHECK + struct flash_img_context ctx; + struct flash_img_check fic; +#endif + + g_img_mgmt_state.off = 0; + +#if defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) + (void)mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_STARTED, NULL, 0, &err_rc, + &err_group); +#endif + +#if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS) + cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_START; +#endif + + /* + * We accept SHA trimmed to any length by client since it's up to client + * to make sure provided data are good enough to avoid collisions when + * resuming upload. + */ + g_img_mgmt_state.data_sha_len = req.data_sha.len; + memcpy(g_img_mgmt_state.data_sha, req.data_sha.value, req.data_sha.len); + memset(&g_img_mgmt_state.data_sha[req.data_sha.len], 0, + IMG_MGMT_DATA_SHA_LEN - req.data_sha.len); + +#ifdef CONFIG_IMG_ENABLE_IMAGE_CHECK + /* Check if the existing image hash matches the hash of the underlying data, + * this check can only be performed if the provided hash is a full SHA256 hash + * of the file that is being uploaded, do not attempt the check if the length + * of the provided hash is less. + */ + if (g_img_mgmt_state.data_sha_len == IMG_MGMT_DATA_SHA_LEN) { + fic.match = g_img_mgmt_state.data_sha; + fic.clen = g_img_mgmt_state.size; + + if (flash_img_check(&ctx, &fic, g_img_mgmt_state.area_id) == 0) { + /* Underlying data already matches, no need to upload any more, + * set offset to image size so client knows upload has finished. + */ + g_img_mgmt_state.off = g_img_mgmt_state.size; + reset = true; + last = true; + data_match = true; + +#if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS) + cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_COMPLETE; +#endif + + goto end; + } + } +#endif + +#ifndef CONFIG_IMG_ERASE_PROGRESSIVELY + /* erase the entire req.size all at once */ + if (action.erase) { + rc = img_mgmt_erase_image_data(0, req.size); + if (rc != 0) { + IMG_MGMT_UPLOAD_ACTION_SET_RC_RSN(&action, + img_mgmt_err_str_flash_erase_failed); + ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, rc); + goto end; + } + } +#endif + } else { +#if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS) + cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_ONGOING; +#endif + } + + /* Write the image data to flash. */ + if (req.img_data.len != 0) { + /* If this is the last chunk */ + if (g_img_mgmt_state.off + req.img_data.len == g_img_mgmt_state.size) { + last = true; + } + + rc = img_mgmt_write_image_data(req.off, req.img_data.value, action.write_bytes, + last); + if (rc == 0) { + g_img_mgmt_state.off += action.write_bytes; + } else { + /* Write failed, currently not able to recover from this */ +#if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS) + cmd_status_arg.status = IMG_MGMT_ID_UPLOAD_STATUS_COMPLETE; +#endif + + IMG_MGMT_UPLOAD_ACTION_SET_RC_RSN(&action, + img_mgmt_err_str_flash_write_failed); + reset = true; + IMG_MGMT_UPLOAD_ACTION_SET_RC_RSN(&action, + img_mgmt_err_str_flash_write_failed); + + LOG_ERR("Irrecoverable error: flash write failed: %d", rc); + + ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_IMAGE, rc); + goto end; + } + + if (g_img_mgmt_state.off == g_img_mgmt_state.size) { + /* Done */ + reset = true; + +#ifdef CONFIG_IMG_ENABLE_IMAGE_CHECK + static struct flash_img_context ctx; + + if (flash_img_init_id(&ctx, g_img_mgmt_state.area_id) == 0) { + struct flash_img_check fic = { + .match = g_img_mgmt_state.data_sha, + .clen = g_img_mgmt_state.size, + }; + + if (flash_img_check(&ctx, &fic, g_img_mgmt_state.area_id) == 0) { + data_match = true; + } else { + LOG_ERR("Uploaded image sha256 hash verification failed"); + } + } else { + LOG_ERR("Uploaded image sha256 could not be checked"); + } +#endif + +#if defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) + (void)mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_PENDING, NULL, 0, + &err_rc, &err_group); + } else { + /* Notify that the write has completed */ + (void)mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK_WRITE_COMPLETE, + NULL, 0, &err_rc, &err_group); +#endif + } + } +end: + + img_mgmt_upload_log(req.off == 0, g_img_mgmt_state.off == g_img_mgmt_state.size, rc); + +#if defined(CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS) + (void)mgmt_callback_notify(MGMT_EVT_OP_CMD_STATUS, &cmd_status_arg, + sizeof(cmd_status_arg), &err_rc, &err_group); +#endif + + if (rc != 0) { +#if defined(CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS) + (void)mgmt_callback_notify(MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED, NULL, 0, &err_rc, + &err_group); +#endif + + img_mgmt_reset_upload(); + } else { + rc = img_mgmt_upload_good_rsp(ctxt); + +#ifdef CONFIG_IMG_ENABLE_IMAGE_CHECK + if (last && rc == MGMT_ERR_EOK) { + /* Append status to last packet */ + ok = zcbor_tstr_put_lit(zse, "match") && + zcbor_bool_put(zse, data_match); + } +#endif + + if (reset) { + /* Reset the upload state struct back to default */ + img_mgmt_reset_upload(); + } + } + + img_mgmt_release_lock(); + + if (!ok) { + return MGMT_ERR_EMSGSIZE; + } + + return MGMT_ERR_EOK; +} + +int img_mgmt_my_version(struct image_version *ver) +{ + return img_mgmt_read_info(img_mgmt_active_slot(img_mgmt_active_image()), + ver, NULL, NULL); +} + +#ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL +/* + * @brief Translate IMG mgmt group error code into MCUmgr error code + * + * @param ret #img_mgmt_err_code_t error code + * + * @return #mcumgr_err_t error code + */ +static int img_mgmt_translate_error_code(uint16_t err) +{ + int rc; + + switch (err) { + case IMG_MGMT_ERR_NO_IMAGE: + case IMG_MGMT_ERR_NO_TLVS: + rc = MGMT_ERR_ENOENT; + break; + + case IMG_MGMT_ERR_NO_FREE_SLOT: + case IMG_MGMT_ERR_CURRENT_VERSION_IS_NEWER: + case IMG_MGMT_ERR_IMAGE_ALREADY_PENDING: + rc = MGMT_ERR_EBADSTATE; + break; + + case IMG_MGMT_ERR_NO_FREE_MEMORY: + rc = MGMT_ERR_ENOMEM; + break; + + case IMG_MGMT_ERR_INVALID_SLOT: + case IMG_MGMT_ERR_INVALID_PAGE_OFFSET: + case IMG_MGMT_ERR_INVALID_OFFSET: + case IMG_MGMT_ERR_INVALID_LENGTH: + case IMG_MGMT_ERR_INVALID_IMAGE_HEADER: + case IMG_MGMT_ERR_INVALID_HASH: + case IMG_MGMT_ERR_INVALID_FLASH_ADDRESS: + rc = MGMT_ERR_EINVAL; + break; + + case IMG_MGMT_ERR_FLASH_CONFIG_QUERY_FAIL: + case IMG_MGMT_ERR_VERSION_GET_FAILED: + case IMG_MGMT_ERR_TLV_MULTIPLE_HASHES_FOUND: + case IMG_MGMT_ERR_TLV_INVALID_SIZE: + case IMG_MGMT_ERR_HASH_NOT_FOUND: + case IMG_MGMT_ERR_INVALID_TLV: + case IMG_MGMT_ERR_FLASH_OPEN_FAILED: + case IMG_MGMT_ERR_FLASH_READ_FAILED: + case IMG_MGMT_ERR_FLASH_WRITE_FAILED: + case IMG_MGMT_ERR_FLASH_ERASE_FAILED: + case IMG_MGMT_ERR_FLASH_CONTEXT_ALREADY_SET: + case IMG_MGMT_ERR_FLASH_CONTEXT_NOT_SET: + case IMG_MGMT_ERR_FLASH_AREA_DEVICE_NULL: + case IMG_MGMT_ERR_INVALID_IMAGE_HEADER_MAGIC: + case IMG_MGMT_ERR_INVALID_IMAGE_VECTOR_TABLE: + case IMG_MGMT_ERR_INVALID_IMAGE_TOO_LARGE: + case IMG_MGMT_ERR_INVALID_IMAGE_DATA_OVERRUN: + case IMG_MGMT_ERR_UNKNOWN: + default: + rc = MGMT_ERR_EUNKNOWN; + } + + return rc; +} +#endif + +static const struct mgmt_handler img_mgmt_handlers[] = { + [IMG_MGMT_ID_STATE] = { + .mh_read = img_mgmt_state_read, +#if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) || \ + defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD) || \ + defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_FIRMWARE_UPDATER) + .mh_write = NULL +#else + .mh_write = img_mgmt_state_write, +#endif + }, + [IMG_MGMT_ID_UPLOAD] = { + .mh_read = NULL, + .mh_write = img_mgmt_upload + }, + [IMG_MGMT_ID_ERASE] = { + .mh_read = NULL, + .mh_write = img_mgmt_erase + }, +#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO) + [IMG_MGMT_ID_SLOT_INFO] = { + .mh_read = img_mgmt_slot_info, + .mh_write = NULL + }, +#endif +}; + +static const struct mgmt_handler img_mgmt_handlers[]; + +#define IMG_MGMT_HANDLER_CNT ARRAY_SIZE(img_mgmt_handlers) + +static struct mgmt_group img_mgmt_group = { + .mg_handlers = (struct mgmt_handler *)img_mgmt_handlers, + .mg_handlers_count = IMG_MGMT_HANDLER_CNT, + .mg_group_id = MGMT_GROUP_ID_IMAGE, +#ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL + .mg_translate_error = img_mgmt_translate_error_code, +#endif +#ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_NAME + .mg_group_name = "img mgmt", +#endif +}; + +static void img_mgmt_register_group(void) +{ + mgmt_register_group(&img_mgmt_group); +} + +MCUMGR_HANDLER_DEFINE(img_mgmt, img_mgmt_register_group); diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt_state.c b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt_state.c index ae9e18685b86..c6bf7a763631 100644 --- a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt_state.c +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt_state.c @@ -131,6 +131,18 @@ img_mgmt_state_flags(int query_slot) int image = query_slot / 2; /* We support max 2 images for now */ int active_slot = img_mgmt_active_slot(image); +#ifdef CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES + /* If manifest-based updates are used, only the manifest image is considered. */ + image = CONFIG_NCS_MCUBOOT_MANIFEST_IMAGE_NUMBER; + if (query_slot == active_slot) { + active_slot = img_mgmt_active_slot(image); + query_slot = active_slot; + } else { + active_slot = img_mgmt_active_slot(image); + query_slot = img_mgmt_get_opposite_slot(active_slot); + } +#endif /* CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES */ + /* In case when MCUboot is configured for FW loader/updater mode, slots * can be either active or non-active. There is no concept of pending * or confirmed slots. @@ -258,18 +270,23 @@ int img_mgmt_get_next_boot_slot(int image, enum img_mgmt_next_boot_type *type) { struct image_version aver; struct image_version over; - int active_slot = img_mgmt_active_slot(image); - int other_slot = img_mgmt_get_opposite_slot(active_slot); #if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) int active_slot_state; int other_slot_state; #endif /* defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) */ enum img_mgmt_next_boot_type lt = NEXT_BOOT_TYPE_NORMAL; - int return_slot = active_slot; +#ifdef CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES + /* If manifest-based updates are used, only the manifest image is considered. */ + int query_image = image; + image = CONFIG_NCS_MCUBOOT_MANIFEST_IMAGE_NUMBER; +#endif /* CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES */ + int active_slot = img_mgmt_active_slot(image); + int other_slot = img_mgmt_get_opposite_slot(active_slot); int rcs = img_mgmt_read_info(other_slot, &over, NULL, NULL); int rca = img_mgmt_read_info(active_slot, &aver, NULL, NULL); + int return_slot = active_slot; #if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) active_slot_state = read_directxip_state(active_slot); @@ -364,6 +381,14 @@ int img_mgmt_get_next_boot_slot(int image, enum img_mgmt_next_boot_type *type) *type = lt; } +#ifdef CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES + if (return_slot != active_slot) { + return_slot = img_mgmt_get_opposite_slot(img_mgmt_active_slot(query_image)); + } else { + return_slot = img_mgmt_active_slot(query_image); + } +#endif /* CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES */ + return return_slot; } #endif /* defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) || \ diff --git a/sysbuild/CMakeLists.txt b/sysbuild/CMakeLists.txt index 6b23250a4c3c..2adf805e93db 100644 --- a/sysbuild/CMakeLists.txt +++ b/sysbuild/CMakeLists.txt @@ -172,6 +172,7 @@ function(${SYSBUILD_CURRENT_MODULE_NAME}_pre_cmake) set(dfu_slots_output_names Application;Network;Wifi\ patches;QSPI\ XIP;MCUboot\ b0\ update) set(dfu_slots_output_text "-- Sysbuild assigned MCUboot image IDs:\n") set(dfu_image_index -1) + set(manifest_image_target "none") list(LENGTH dfu_slots_sysbuild_kconfigs test_things_size) math(EXPR test_things_size "${test_things_size} - 1") @@ -193,6 +194,22 @@ function(${SYSBUILD_CURRENT_MODULE_NAME}_pre_cmake) set_config_int(${image} ${current_application_kconfig} ${value}) endforeach() + if(SB_CONFIG_MCUBOOT_MANIFEST_UPDATES) + if(${value} EQUAL ${SB_CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER}) + if("${current_application_kconfig}" STREQUAL "CONFIG_MCUBOOT_APPLICATION_IMAGE_NUMBER") + set(manifest_image_target ${DEFAULT_IMAGE}) + elseif("${current_application_kconfig}" STREQUAL "CONFIG_MCUBOOT_NETWORK_CORE_IMAGE_NUMBER") + set(manifest_image_target ${SB_CONFIG_NETCORE_IMAGE_NAME}) + else() + # Unsupported indexes: + # - CONFIG_MCUBOOT_WIFI_PATCHES_IMAGE_NUMBER + # - CONFIG_MCUBOOT_QSPI_XIP_IMAGE_NUMBER + # - CONFIG_MCUBOOT_MCUBOOT_IMAGE_NUMBER + message(FATAL_ERROR "MCUboot manifest image can only be assigned to application or network core images") + endif() + endif() + endif() + set(${current_cache_name} ${value} CACHE INTERNAL "" FORCE) endforeach() @@ -256,6 +273,25 @@ function(${SYSBUILD_CURRENT_MODULE_NAME}_pre_cmake) set_config_bool(mcuboot CONFIG_BOOT_IMG_HASH_ALG_SHA512 y) endif() + if(SB_CONFIG_MCUBOOT_MANIFEST_UPDATES) + set_config_bool(mcuboot CONFIG_MCUBOOT_MANIFEST_UPDATES y) + set_config_int(mcuboot CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER + ${SB_CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER}) + + if("${manifest_image_target}" STREQUAL "none") + message(FATAL_ERROR "No manifest image target, cannot append manifest to image") + endif() + set_config_bool(${manifest_image_target} CONFIG_NCS_MCUBOOT_IMGTOOL_APPEND_MANIFEST y) + if(SB_CONFIG_MCUBOOT_BUILD_DIRECT_XIP_VARIANT) + if("${manifest_image_target}" STREQUAL "${DEFAULT_IMAGE}") + set_config_bool(mcuboot_secondary_app CONFIG_NCS_MCUBOOT_IMGTOOL_APPEND_MANIFEST y) + else() + set_config_bool(${manifest_image_target}_secondary_app + CONFIG_NCS_MCUBOOT_IMGTOOL_APPEND_MANIFEST y) + endif() + endif() + endif() + # Apply configuration to application foreach(image ${updateable_images}) foreach(mode ${application_mcuboot_modes}) @@ -266,6 +302,11 @@ function(${SYSBUILD_CURRENT_MODULE_NAME}_pre_cmake) endif() endforeach() + if(SB_CONFIG_MCUBOOT_MANIFEST_UPDATES) + set_config_bool(${image} CONFIG_NCS_MCUBOOT_MANIFEST_UPDATES y) + set_config_int(${image} CONFIG_NCS_MCUBOOT_MANIFEST_IMAGE_NUMBER ${SB_CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER}) + endif() + if(SB_CONFIG_BOOT_SIGNATURE_TYPE_ED25519) set_config_bool(${image} CONFIG_MCUBOOT_BOOTLOADER_SIGNATURE_TYPE_ED25519 y) endif() @@ -381,7 +422,9 @@ function(${SYSBUILD_CURRENT_MODULE_NAME}_pre_cmake) set_config_bool(mcuboot CONFIG_BOOT_FIH_PROFILE_DEFAULT_LOW y) endif() - if(SB_CONFIG_PARTITION_MANAGER OR SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP OR SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP_WITH_REVERT OR SB_CONFIG_MCUBOOT_COMPRESSED_IMAGE_SUPPORT) + if(SB_CONFIG_PARTITION_MANAGER OR SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP + OR SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP_WITH_REVERT OR SB_CONFIG_MCUBOOT_COMPRESSED_IMAGE_SUPPORT + OR SB_CONFIG_MCUBOOT_MANIFEST_UPDATES) # Use NCS signing script with support for PM or direct XIP (NCS specific features) if(SB_CONFIG_QSPI_XIP_SPLIT_IMAGE) set(${DEFAULT_IMAGE}_SIGNING_SCRIPT "${ZEPHYR_NRF_MODULE_DIR}/cmake/sysbuild/image_signing_split.cmake" CACHE INTERNAL "MCUboot signing script" FORCE) @@ -845,6 +888,10 @@ function(${SYSBUILD_CURRENT_MODULE_NAME}_post_cmake) include(${ZEPHYR_NRF_MODULE_DIR}/cmake/sysbuild/mcuboot_nrf54h20.cmake) endif() + if(SB_CONFIG_BOOTLOADER_MCUBOOT AND SB_CONFIG_MCUBOOT_MANIFEST_UPDATES) + include(${ZEPHYR_NRF_MODULE_DIR}/cmake/sysbuild/mcuboot_manifest.cmake) + endif() + if(SB_CONFIG_DFU_ZIP) if(SB_CONFIG_BOOTLOADER_MCUBOOT) include(${ZEPHYR_NRF_MODULE_DIR}/cmake/sysbuild/zip.cmake) diff --git a/sysbuild/Kconfig.mcuboot b/sysbuild/Kconfig.mcuboot index a42f6b2a2591..408ab9f7cedf 100644 --- a/sysbuild/Kconfig.mcuboot +++ b/sysbuild/Kconfig.mcuboot @@ -23,6 +23,30 @@ config MCUBOOT_IMAGES_ROM_END_OFFSET_AUTO (e.g. "smp_svr;ipc_radio", "mcuboot_secondary_app"). If empty the default set of updateable images will be affected. +config MCUBOOT_MANIFEST_UPDATES + bool "Enable transactional updates" + help + If y, enables support for transactional updates using manifests. + This allows multiple images to be updated atomically. The manifest + is a separate TLV which contains a list of images to update and + their expected hash values. The manifest TLV is a part of an image + that is signed to prevent tampering. + The manifest must be transferred as part of the image with index 0. + It can be a dedicated image, or part of an existing image. + If the second option is selected, all updates must contain an update + for image 0. + +if MCUBOOT_MANIFEST_UPDATES + +config MCUBOOT_MANIFEST_IMAGE_NUMBER + int "Number of image that must include manifest" + default 0 + range 0 MCUBOOT_UPDATEABLE_IMAGES + help + Specifies the index of the image that must include the manifest. + +endif # MCUBOOT_MANIFEST_UPDATES + config MCUBOOT_BUILD_DIRECT_XIP_VARIANT bool "Build DirectXIP variant image" depends on MCUBOOT_MODE_DIRECT_XIP || MCUBOOT_MODE_DIRECT_XIP_WITH_REVERT diff --git a/west.yml b/west.yml index cbcee2d0afd4..90a1c6dae08a 100644 --- a/west.yml +++ b/west.yml @@ -65,7 +65,7 @@ manifest: # https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/guides/modules.html - name: zephyr repo-path: sdk-zephyr - revision: 9a54011af2dfd7b7e4b8b0b8a46f02961ad58511 + revision: pull/3460/head import: # In addition to the zephyr repository itself, NCS also # imports the contents of zephyr/west.yml at the above @@ -127,7 +127,7 @@ manifest: compare-by-default: true - name: mcuboot repo-path: sdk-mcuboot - revision: 459288d6a13cede9fa09e314b21a979a6cc6e3f2 + revision: pull/560/head path: bootloader/mcuboot - name: qcbor url: https://github.com/laurencelundblade/QCBOR