nRF54L15 FOTA Failure:
MCUboot reports magic=bad on Slot 0 and magic=unset on Slot 1 using NSIB + MCUboot + TF-M with Pure DTS Partitioning
Description
I am implementing a secure boot chain on the nRF54L15 using nRF Connect SDK v3.4.0 with the following boot architecture:
- NSIB (First-stage immutable bootloader)
- MCUboot (Second-stage upgradable bootloader, configured for
SWAP_USING_MOVE) - TF-M (Secure Processing Environment)
- Non-Secure Application
- Pure Devicetree (DTS) partitioning (Partition Manager disabled)
During FOTA using the nRF Connect Mobile App with a dfu_application.zip package in Confirm Only mode, the firmware upload completes successfully. However, after reboot, MCUboot does not perform the image swap.
Instead, MCUboot reports:
- Primary slot:
magic=bad - Secondary slot:
magic=unset
As a result, MCUboot skips the upgrade and boots the existing application.
Environment
| Component | Version |
|---|---|
| SoC | nRF54L15 (Application Core) |
| SDK | nRF Connect SDK v3.4.0 |
| Zephyr | v4.4.0 |
| Board | Custom nRF54L15 board |
| Boot Chain | NSIB → MCUboot → TF-M → NS Application |
| FOTA Method | MCUmgr BLE OTA (nRF Connect Mobile App) |
| DFU Mode | Confirm Only |
| Partitioning | Pure Devicetree (Partition Manager disabled) |
Custom Board Files
I am using a pure DTS-based flash layout instead of the legacy Partition Manager.
One reason for using an absolute-address layout is to work around the secure read boundary detection inside the RRAM flash driver (soc_flash_nrf_rram.c), where slot0_ns_partition must resolve to an absolute flash address.
<custom_board>_cpuapp_ns_partition.dtsi&cpuapp_rram {
partitions {
/*
* ranges replaces compatible = "fixed-partitions"
* This enables zephyr,mapped-partition to correctly
* map all child partition addresses to the CPU
* address space automatically.
*/
ranges;
#address-cells = <1>;
#size-cells = <1>;
b0_partition: partition@0 {
compatible = "zephyr,mapped-partition";
label = "b0";
reg = <0x0 0xC000>;
};
provision_partition: partition@C000 {
compatible = "zephyr,mapped-partition";
label = "provision";
reg = <0xC000 0x4000>;
};
tfm_its_partition: partition@10000 {
compatible = "zephyr,mapped-partition";
label = "tfm-its";
reg = <0x10000 0x4000>;
};
tfm_otp_partition: partition@14000 {
compatible = "zephyr,mapped-partition";
label = "tfm-otp";
reg = <0x14000 0x2000>;
};
tfm_ps_partition: partition@16000 {
compatible = "zephyr,mapped-partition";
label = "tfm-ps";
reg = <0x16000 0x4000>;
};
s0_partition: boot_partition: partition@1A000 {
compatible = "zephyr,mapped-partition";
label = "mcuboot";
reg = <0x1A000 0x10000>;
};
s1_partition: partition@2A000 {
compatible = "zephyr,mapped-partition";
label = "s1";
reg = <0x2A000 0x10000>;
};
slot0_partition: partition@3A000 {
compatible = "zephyr,mapped-partition";
label = "image-0";
reg = <0x3A000 0x80000>;
ranges = <0x0 0x3A000 0x80000>;
#address-cells = <1>;
#size-cells = <1>;
slot0_s_partition: partition@0 {
compatible = "zephyr,mapped-partition";
label = "image-0-secure";
reg = <0x0 0x30000>;
};
};
slot0_ns_partition: partition@6A000 {
compatible = "zephyr,mapped-partition";
label = "image-0-nonsecure";
reg = <0x6A000 0x50000>;
};
slot1_partition: partition@BA000 {
compatible = "zephyr,mapped-partition";
label = "image-1";
reg = <0xBA000 0x80000>;
};
storage_partition: partition@13A000 {
compatible = "zephyr,mapped-partition";
label = "storage";
reg = <0x13A000 0x10000>;
};
};
};
&cpuapp_sram {
#address-cells = <1>;
#size-cells = <1>;
sram0_s: image_s@0 {
reg = <0x0 DT_SIZE_K(128)>;
};
sram0_ns: image_ns@20000 {
#address-cells = <1>;
#size-cells = <1>;
reg = <0x20000 DT_SIZE_K(128)>;
};
};
<custom_board>_cpuapp_ns.dts
/*
* Copyright (c) 2025 <vendor> Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/dts-v1/;
#define USE_NON_SECURE_ADDRESS_MAP 1
#include <arm/nordic/nrf54l15_cpuapp_ns.dtsi>
#include "<custom_board>_cpuapp_common.dtsi"
/ {
model = "nRF54L15 Application Non-Secure";
compatible = "<vendor>,<custom_board>-nrf54l15-cpuapp-ns";
chosen {
zephyr,code-partition = &slot0_ns_partition;
zephyr,sram = &sram0_ns;
zephyr,entropy = &psa_rng;
};
/delete-node/ rng;
psa_rng: psa-rng {
status = "okay";
};
};
&uart30 {
/* Disable so that TF-M can use this UART */
status = "disabled";
current-speed = <115200>;
pinctrl-0 = <&uart30_default>;
pinctrl-1 = <&uart30_sleep>;
pinctrl-names = "default", "sleep";
};
#include "<custom_board>_cpuapp_ns_partition.dtsi"
board.yml
board:
name: <custom_board>_nrf54l15
full_name: <custom_board>_nrf54l15
vendor: <vendor>
socs:
- name: nrf54l15
variants:
- name: xip
cpucluster: cpuflpr
- name: ns
cpucluster: cpuapp
runners:
run_once:
'--recover':
- runners:
- nrfjprog
- nrfutil
run: first
groups:
- boards:
- <custom_board>_nrf54l15/nrf54l15/cpuapp
- <custom_board>_nrf54l15/nrf54l15/cpuapp/ns
- <custom_board>_nrf54l15/nrf54l15/cpuflpr
- <custom_board>_rf54l15/nrf54l15/cpuflpr/xip
'--erase':
- runners:
- nrfjprog
- jlink
- nrfutil
run: first
groups:
- boards:
- <custom_board>_nrf54l15/nrf54l15/cpuapp
- <custom_board>_nrf54l15/nrf54l15/cpuapp/ns
- <custom_board>_nrf54l15/nrf54l15/cpuflpr
- <custom_board>_nrf54l15/nrf54l15/cpuflpr/xip
'--reset':
- runners:
- nrfjprog
- jlink
- nrfutil
run: last
groups:
- boards:
- <custom_board>_nrf54l15/nrf54l15/cpuapp
- <custom_board>_nrf54l15/nrf54l15/cpuapp/ns
- <custom_board>_nrf54l15/nrf54l15/cpuflpr
- <custom_board>_nrf54l15/nrf54l15/cpuflpr/xip
<custom_board>_nrf54l15_cpuapp_ns_defconfig
# Copyright (c) 2025 <vendor> Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0
CONFIG_ARM_MPU=y
CONFIG_NULL_POINTER_EXCEPTION_DETECTION_NONE=y
CONFIG_ARM_TRUSTZONE_M=y
# This Board implies building Non-Secure firmware
CONFIG_TRUSTED_EXECUTION_NONSECURE=y
# Don't enable the cache in the non-secure image as it is a
# secure-only peripheral on 54l
CONFIG_CACHE_MANAGEMENT=n
CONFIG_EXTERNAL_CACHE=n
CONFIG_UART_CONSOLE=y
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_GPIO=y
# Start SYSCOUNTER on driver init
CONFIG_NRF_GRTC_START_SYSCOUNTER=y
# Disable TFM BL2 since it is not supported
CONFIG_TFM_BL2=n
# Support for silence logging is not supported at the moment
# Tracked by: NCSDK-31930
CONFIG_TFM_LOG_LEVEL_SILENCE=n
# The oscillators are configured as secure and cannot be configured
# from the non secure application directly. This needs to be set
# otherwise nrfx will try to configure them, resulting in a bus
# fault.
CONFIG_NRF_SKIP_CLOCK_CONFIG=y
Appliaction Configuration Files
SYSBUILD Configuration (sysbuild.conf)
# Enable two-stage boot chain
SB_CONFIG_SECURE_BOOT_APPCORE=y
SB_CONFIG_BOOTLOADER_MCUBOOT=y
# ED25519 keys for nRF54L15
SB_CONFIG_SECURE_BOOT_SIGNATURE_TYPE_ED25519=y
SB_CONFIG_BOOT_SIGNATURE_TYPE_ED25519=y
# Store MCUboot verification key in KMU
SB_CONFIG_MCUBOOT_SIGNATURE_USING_KMU=y
# Signing keys
SB_CONFIG_SECURE_BOOT_SIGNING_KEY_FILE="${APP_DIR}/keys/nsib_prv_key.pem"
SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${APP_DIR}/keys/mcub_prv_key.pem"
# KMU provisioning
SB_CONFIG_MCUBOOT_GENERATE_DEFAULT_KEY_FILE=y
SB_CONFIG_SECURE_BOOT_GENERATE_DEFAULT_KMU_KEYFILE=y
# Number of updateable images
SB_CONFIG_MCUBOOT_UPDATEABLE_IMAGES=1
# CRITICAL: Disable Partition Manager — use DTS partitions only
SB_CONFIG_PARTITION_MANAGER=n
Project Configuration (prj.conf)
#for logging CONFIG_LOG=y CONFIG_SERIAL=y CONFIG_UART_CONSOLE=y # Enable BLE SMP FOTA CONFIG_BT=y CONFIG_BT_PERIPHERAL=y CONFIG_NCS_SAMPLE_MCUMGR_BT_OTA_DFU=y CONFIG_BT_DEVICE_NAME="nRF54L15 fota test" #disable external flash CONFIG_SPI_NOR=n # MCUmgr dependencies CONFIG_MCUMGR=y CONFIG_MCUMGR_GRP_OS=y CONFIG_MCUMGR_GRP_IMG=y CONFIG_MCUMGR_TRANSPORT_BT=y CONFIG_NCS_SAMPLE_MCUMGR_BT_OTA_DFU=y CONFIG_MCUMGR_TRANSPORT_BT_PERM_RW_AUTHEN=n # Image manager - required for FOOTER_SIZE dependency CONFIG_IMG_MANAGER=y CONFIG_MCUBOOT_IMG_MANAGER=y CONFIG_STREAM_FLASH=y
Observations
NSPE APP LOGS Before FOTA
Fprotect disabled. No protection applied.
LCS-awareness disabled.
Attempting to boot slot 0.
Attempting to boot from address 0x1a800.
I: Trying to get Firmware version
I: Firmware signature verified.
Firmware version 1
I: Setting monotonic counter (version: 1, slot: 0)
*** Booting MCUboot v2.3.0-dev-c1d2d128a001 ***
*** Using nRF Connect SDK v3.4.0-99553055607b ***
*** Using Zephyr OS v4.4.0-bf801e4e3d19 ***
I: Starting bootloader
I: LCS-awareness disabled, skipping LCS check
I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: none
I: Image index: 0, Swap type: none
I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: none
I: Image index: 1, Swap type: none
I: Bootloader chainload address offset: 0x3a000
I: Image version: v1.1.0
I: Jumping to the first image slot
*** Booting My Application v1.1.0 - unknown commit ***
*** Using nRF Connect SDK v3.4.0-99553055607b ***
*** Using Zephyr OS v4.4.0-bf801e4e3d19 ***
[00:00:00.005,391] <inf> bt_sdc_hci_driver: SoftDevice Controller build revision:
c8 da 30 98 f9 f0 34 a4 4b 6e ba d3 08 19 cc 0c |..0...4. Kn......
ea 51 da 47 |.Q.G
[00:00:00.006,644] <inf> bt_hci_core: HW Platform: Nordic Semiconductor (0x0002)
[00:00:00.006,656] <inf> bt_hci_core: HW Variant: nRF54Lx (0x0005)
[00:00:00.006,668] <inf> bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 200.12506 Build 888207768
[00:00:00.007,063] <inf> bt_hci_core: HCI transport: SDC
[00:00:00.007,109] <inf> bt_hci_core: Identity: C6:1D:FC:E3:C6:49 (random)
[00:00:00.007,125] <inf> bt_hci_core: HCI: version 6.3 (0x11) revision 0x3039, manufacturer 0x0059
[00:00:00.007,140] <inf> bt_hci_core: LMP: version 6.3 (0x11) subver 0x3039
Bluetooth initialized
Advertising started
I am board with version 1.1.0
SPE_TF-M Logs Before FOTA
[INF] Writing random Hardware Unique Keys to the KMU.
[INF] Success
[INF] Pins have been configured as secure.
[INF] GPIO port: 0x00000000
[INF] Pin: 0x00000000
[INF] Pin: 0x00000001
[NOT] Booting TF-M v2.3.0**
[NOT] Built Mon 27 Jul 2026 09:19:45 UTC
Creating an empty ITS flash layout.
Creating an empty PS flash layout.
[INF] [PS] Encryption alg: 0x5500200
During BLE FOTA
[00:02:56.962,632] <inf> mcuboot_util: Image index: 0, Swap type: none [00:02:57.172,843] <inf> mcuboot_util: Image index: 0, Swap type: none [00:02:57.172,869] <inf> mcuboot_util: Image index: 0, Swap type: none
Logs After Automatic Reset
After the FOTA transfer completes and the device resets automatically:
Fprotect disabled. No protection applied.
LCS-awareness disabled.
Attempting to boot slot 0.
Attempting to boot from address 0x1a800.
I: Trying to get Firmware version
I: Firmware signature verified.
Firmware version 1
*** Booting MCUboot v2.3.0-dev-c1d2d128a001 ***
*** Using nRF Connect SDK v3.4.0-99553055607b ***
*** Using Zephyr OS v4.4.0-bf801e4e3d19 ***
I: Starting bootloader
I: LCS-awareness disabled, skipping LCS check
I: Primary image: magic=bad, swap_type=0x2, copy_done=0x2, image_ok=0x2
I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: none
I: Image index: 0, Swap type: none
I: Primary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Secondary image: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: none
I: Image index: 1, Swap type: none
I: Bootloader chainload address offset: 0x3a000
I: Image version: v1.1.0
I: Jumping to the first image slot
*** Booting My Application v1.1.0 - unknown commit ***
*** Using nRF Connect SDK v3.4.0-99553055607b ***
*** Using Zephyr OS v4.4.0-bf801e4e3d19 ***
[00:00:00.001,942] <inf> bt_sdc_hci_driver: SoftDevice Controller build revision:
c8 da 30 98 f9 f0 34 a4 4b 6e ba d3 08 19 cc 0c |..0...4. Kn......
ea 51 da 47 |.Q.G
[00:00:00.003,195] <inf> bt_hci_core: HW Platform: Nordic Semiconductor (0x0002)
[00:00:00.003,207] <inf> bt_hci_core: HW Variant: nRF54Lx (0x0005)
[00:00:00.003,218] <inf> bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 200.12506 Build 888207768
[00:00:00.003,613] <inf> bt_hci_core: HCI transport: SDC
[00:00:00.003,660] <inf> bt_hci_core: Identity: C6:1D:FC:E3:C6:49 (random)
[00:00:00.003,676] <inf> bt_hci_core: HCI: version 6.3 (0x11) revision 0x3039, manufacturer 0x0059
[00:00:00.003,691] <inf> bt_hci_core: LMP: version 6.3 (0x11) subver 0x3039
Bluetooth initialized
Advertising started
I am board with version 1.1.0
Because MCUboot detects:
magic=badon the primary slotmagic=unseton the secondary slot
it determines that no upgrade is pending and boots the existing image.
Questions
1. Secondary Slot Trailer
Although the firmware image is successfully written into slot1_partition, MCUboot reports:
Secondary image: magic=unset
This suggests that the image trailer (including the MCUboot magic value) is either not being written or not being detected.
- Is there any known issue with MCUmgr BLE DFU on nRF54L15 when using pure DTS partitioning instead of Partition Manager?
- Does MCUmgr require additional configuration to correctly write the image trailer in this configuration?
2. Primary Slot magic=bad
MCUboot reports:
Primary image: magic=bad
even though the existing application boots successfully.
- Is this expected behavior for the primary slot when using
CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_USING_MOVE=y? - Could this indicate an issue related to the split Secure/Non-Secure image layout (TF-M + NS Application)?
- Are there any known limitations or additional requirements for
SWAP_USING_MOVEon the nRF54L series?
3. Multiple Image Indices
My MCUboot configuration specifies:
CONFIG_UPDATEABLE_IMAGE_NUMBER=1
However, during boot MCUboot still scans:
Image index: 0
Image index: 1
Is this expected because the following option is enabled?
CONFIG_MCUBOOT_MCUBOOT_IMAGE_NUMBER=1
If so, is MCUboot treating the bootloader itself as the second image, even though only the application image is intended to be updated via FOTA?