Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@
/samples/dect/dect_phy/dect_shell/ @nrfconnect/ncs-modem-tre
/samples/dect/dect_phy/hello_dect/ @nrfconnect/ncs-modem
/samples/dfu/ab/ @nrfconnect/ncs-eris
/samples/dfu/ab_split/ @nrfconnect/ncs-eris
/samples/dfu/dfu_target/ @nrfconnect/ncs-eris
/samples/dfu/dfu_multi_image/ @nrfconnect/ncs-eris
/samples/dfu/fw_loader/ @nrfconnect/ncs-eris
Expand Down
4 changes: 4 additions & 0 deletions cmake/sysbuild/image_signing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
88 changes: 88 additions & 0 deletions cmake/sysbuild/mcuboot_manifest.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#
# 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")

# There is no need to generate a manifest if there is only a single (merged) image.
if(NOT SB_CONFIG_MCUBOOT_SIGN_MERGED_BINARY)
sysbuild_get(manifest_img IMAGE mcuboot VAR CONFIG_MCUBOOT_MANIFEST_IMAGE_INDEX 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}")
25 changes: 25 additions & 0 deletions samples/dfu/ab_split/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#
# 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
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
)
14 changes: 14 additions & 0 deletions samples/dfu/ab_split/Kconfig
Original file line number Diff line number Diff line change
@@ -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"
203 changes: 203 additions & 0 deletions samples/dfu/ab_split/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
.. _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 <ab_sample>`, where the application and radio images are not merged, but reside in separate MCUboot slots.
This split increases the number of memory areas that must be individually protected from accidental writes.
It also requires additional care when preparing updates to ensure that only a compatible set of slots is 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.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 a firmware update using the A/B method.
This method allows you to store two copies of the application in non-volatile memory (NVM).
You can switch between these copies without performing a swap.
This solution significantly reduces the time during the update in which the device is unavailable.
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 you use the A/B configuration with separated slots, the device provides two slots for each set of application and radio firmware: slot A and slot B.
The slots are equivalent, and the device can boot from either of them.
With MCUboot, this is achieved by using the Direct XIP feature.
By design, slot A of the application image boots slot A of the radio image.
This design implies that verifying the image pairs correctly requires a manifest-based dependency.
There can be only one image that includes the manifest TLV.
Its index is configured using :kconfig:option:`SB_CONFIG_MCUBOOT_MANIFEST_IMAGE_INDEX`.
By default, the application image index (``0``) is selected.

In this document, the following conventions are followed:

* The application image index (``0``) is referred to as the *manifest image*.
* The following names refer to the same images and are used interchangeably throughout the documentation:

* *Slot 0*, *primary slot*, and *slot A*
* *Slot 1*, *secondary slot*, and *slot B*

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 is considered *active* and is booted on the next reboot:

1. If one of the slots of the manifest image contains a valid image, it is marked as valid only if all other images, described by the manifest are present and placed in the same slot as the manifest.
#. If one of the slots of the manifest image is not valid, the other slot is selected as active.
#. If both slots of the manifest image are valid, the slot marked as "preferred" is selected as active.
#. If both slots of the manifest image 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.
#. For all other images, the same slot is selected.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a kconfig option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is something in between Kconfig and a compile-time define - it does not have a definition inside Kconfig* files, but it is defined in both .config as well as autoconf.h files.

You mask ask Jamie for more details/design decisions regarding this symbol.

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 supported only on the nRF54H20 SoC), you can identify the currently running slot by comparing the address referenced by ``zephyr,code-partition`` with the specific node addresses defined in the devicetree.
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, you must send the following files to the device when performing an update:


* :file:`build/mcuboot_secondary_app/zephyr/zephyr.signed.bin` - Contains the slot B of the application image.
Upload this file 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.
Upload this file 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.
Upload this file 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.
Upload this file 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.
This means that it turns on once the application is booted and turns off in short intervals to blinks.
The number of short blinks is configurable using the :kconfig:option:`CONFIG_N_BLINKS` Kconfig option.
It remains off when 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.
This means that it turns on once the application is booted and turns off at short intervals to blinks.
The number of short blinks is configurable using the :kconfig:option:`CONFIG_N_BLINKS` Kconfig option.
It remains off when the application is running from slot A.

Button 0:
By pressing this button, you select the non-active slot as the preferred slot for 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 options for the sample:

.. _CONFIG_N_BLINKS_ABSPLIT:

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_AB_SPLIT:

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` Kconfig option for 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 <mcuboot_index_ncs>`
13 changes: 13 additions & 0 deletions samples/dfu/ab_split/boards/nrf54h20dk_nrf54h20_cpuapp.overlay
Original file line number Diff line number Diff line change
@@ -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;
};
};
Loading
Loading