-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add split-slot A/B sample #25036
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add split-slot A/B sample #25036
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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}") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) |
| 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" |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,188 @@ | ||||||||||||||
| .. _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 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.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 firmware: slot A and slot B. | ||||||||||||||
| The slots are equivalent, and the device can boot from either of them. | ||||||||||||||
| In the case of MCUboot, this is achieved by using the Direct XIP feature. | ||||||||||||||
| 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 image pairs. | ||||||||||||||
| There can be only one image that includes manifest TLV. Its index is configured using :kconfig:option:`SB_CONFIG_MCUBOOT_MANIFEST_IMAGE_NUMBER`. | ||||||||||||||
| By default, the application image index (``0``) is selected. This image will be referred to as the manifest image. | ||||||||||||||
| 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 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. | ||||||||||||||
| 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. | ||||||||||||||
|
|
||||||||||||||
|
||||||||||||||
| .. note:: | |
| When using manifest-based updates (with :kconfig:option:`SB_CONFIG_MCUBOOT_MANIFEST_UPDATES=y`), both the application and radio image files for a given slot must be uploaded together as a set. | |
| Uploading only one file will not satisfy the manifest requirements and may result in an incomplete or failed update. | |
| Always ensure that both files for the target slot are uploaded together during the update process. |
tomchy marked this conversation as resolved.
Show resolved
Hide resolved
| 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; | ||
| }; | ||
| }; |
Uh oh!
There was an error while loading. Please reload this page.