From fdc4f2e05c9783de8666f98cf35b3d6c7cbdf0e5 Mon Sep 17 00:00:00 2001 From: Tomasz Chyrowicz Date: Fri, 3 Oct 2025 12:02:19 +0200 Subject: [PATCH] samples: Add split slot A/B sample Add a variant of the A/B sample that presents how to perform A/B updates in a system, where application and radio images uses separate images and slots. Ref: NCSDK-35733 Signed-off-by: Tomasz Chyrowicz --- samples/dfu/ab_split/CMakeLists.txt | 24 ++ samples/dfu/ab_split/Kconfig | 14 + samples/dfu/ab_split/README.rst | 194 +++++++++++++ .../boards/nrf54h20dk_nrf54h20_cpuapp.overlay | 13 + samples/dfu/ab_split/prj.conf | 106 +++++++ samples/dfu/ab_split/sample.yaml | 17 ++ samples/dfu/ab_split/src/ab_utils.c | 261 ++++++++++++++++++ samples/dfu/ab_split/src/ab_utils.h | 7 + samples/dfu/ab_split/src/main.c | 41 +++ samples/dfu/ab_split/sysbuild.conf | 12 + samples/dfu/ab_split/sysbuild/ipc_radio.conf | 9 + .../boards/nrf54h20dk_nrf54h20_cpuapp.overlay | 13 + .../dfu/ab_split/sysbuild/mcuboot/prj.conf | 36 +++ .../nrf54h20dk_nrf54h20_memory_map.dtsi | 37 +++ 14 files changed, 784 insertions(+) create mode 100644 samples/dfu/ab_split/CMakeLists.txt create mode 100644 samples/dfu/ab_split/Kconfig create mode 100644 samples/dfu/ab_split/README.rst create mode 100644 samples/dfu/ab_split/boards/nrf54h20dk_nrf54h20_cpuapp.overlay create mode 100644 samples/dfu/ab_split/prj.conf create mode 100644 samples/dfu/ab_split/sample.yaml create mode 100644 samples/dfu/ab_split/src/ab_utils.c create mode 100644 samples/dfu/ab_split/src/ab_utils.h create mode 100644 samples/dfu/ab_split/src/main.c create mode 100644 samples/dfu/ab_split/sysbuild.conf create mode 100644 samples/dfu/ab_split/sysbuild/ipc_radio.conf create mode 100644 samples/dfu/ab_split/sysbuild/mcuboot/boards/nrf54h20dk_nrf54h20_cpuapp.overlay create mode 100644 samples/dfu/ab_split/sysbuild/mcuboot/prj.conf create mode 100644 samples/dfu/ab_split/sysbuild/nrf54h20dk_nrf54h20_memory_map.dtsi 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..902792b1d449 --- /dev/null +++ b/samples/dfu/ab_split/README.rst @@ -0,0 +1,194 @@ +.. _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 slot-based dependency management, the :kconfig:option:`CONFIG_BOOT_VERSION_CMP_USE_SLOT_NUMBER` 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 slot-based dependency required to correctly verify the correctness of the images. +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 `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. + +In addition, set the :kconfig:option:`CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS` library Kconfig option specific to the MCUboot library to specify the slot-specific image dependencies. +For example, to ensure that there is a valid radio image with version ``1.0.0`` or greater in the radio primary slot set: + +.. code-block:: console + + CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS="--dependencies \"(0,primary,1.0.0)\"" + +The slot-specific dependency is automatically converted to point to the secondary slot when building the second variant of each image. + +Building and running +******************** + +.. |sample path| replace:: :file:`samples/dfu/ab` + +.. 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_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..64744a54df7d --- /dev/null +++ b/samples/dfu/ab_split/prj.conf @@ -0,0 +1,106 @@ +# 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_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 +CONFIG_BT_CTLR_DATA_LENGTH_MAX=251 + +# 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 + +# Define dependencies +CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="1.0.0" +CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS="--dependencies \"(1,primary,1.0.0)\"" \ No newline at end of file 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..61f6dd3bebd3 --- /dev/null +++ b/samples/dfu/ab_split/src/ab_utils.c @@ -0,0 +1,261 @@ +/* + * 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_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) +#define IS_SLOT_B (CODE_PARTITION_OFFSET == SLOT_B_OFFSET) + +#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 toggle_slot_for_single_boot(void) +{ + int err = 0; + enum ab_boot_slot active_slot = active_boot_slot_get(); + enum boot_slot new_slot = BOOT_SLOT_NONE; + + if (active_slot == SLOT_A) { + LOG_INF("Temporarily switching slots (A -> B)"); + new_slot = BOOT_SLOT_SECONDARY; + } else if (active_slot == SLOT_B) { + LOG_INF("Temporarily switching slots (B -> A)"); + new_slot = BOOT_SLOT_PRIMARY; + } 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)) { + toggle_slot_for_single_boot(); + } +} + +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..be77e2a7d3a2 --- /dev/null +++ b/samples/dfu/ab_split/sysbuild.conf @@ -0,0 +1,12 @@ +# 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 diff --git a/samples/dfu/ab_split/sysbuild/ipc_radio.conf b/samples/dfu/ab_split/sysbuild/ipc_radio.conf new file mode 100644 index 000000000000..4ca827b42ca8 --- /dev/null +++ b/samples/dfu/ab_split/sysbuild/ipc_radio.conf @@ -0,0 +1,9 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Define dependencies +CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="1.0.0" +CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS="--dependencies \"(0,primary,1.0.0)\"" 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..63de1f5d2182 --- /dev/null +++ b/samples/dfu/ab_split/sysbuild/mcuboot/prj.conf @@ -0,0 +1,36 @@ +# 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" + +# Enable per-slot dependency management +CONFIG_BOOT_VERSION_CMP_USE_SLOT_NUMBER=y 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>; + }; + }; + }; + }; +};