Regarding FOTA failure in nRF54L15 with NSIB, MCUBoot and TF-M enabled

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=bad on the primary slot
  • magic=unset on 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_MOVE on 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?

Related