Guideline for nRF5340 network-core FOTA over MCUmgr (with app-core rollback)

Environment

  • nRF Connect SDK: v3.2.1
  • Zephyr: 4.2.99 (NCS fork)
  • CMake (bundled): 3.21.0
  • Board: nrf5340dk_nrf5340_cpuapp
  • OS: Linux

What already works: application-core FOTA (image 0)

This has worked reliably for a long time:

mcumgr ... image upload -e -n 0 app.signed.bin
mcumgr ... image list          # note new hash in slot 1
mcumgr ... image test <hash>
mcumgr ... reset
mcumgr ... image confirm <hash>

Swap + revert behave exactly as expected.

Goal

Add network-core (hci_ipc) FOTA through MCUmgr in the same way (image 1), without losing the application core's swap/revert.

Config we added

# sysbuild
SB_CONFIG_SECURE_BOOT_NETCORE=y
SB_CONFIG_NETCORE_APP_UPDATE=y
# application prj.conf
CONFIG_MCUMGR_GRP_IMG_UPDATABLE_IMAGE_NUMBER=2

Network-core FOTA steps we follow

mcumgr ... image upload -e -n 1 signed_by_mcuboot_and_b0_hci_ipc.bin
mcumgr ... image list          # image=1 slot=1 appears, secondary magic=good
mcumgr ... image confirm <hash>
mcumgr ... reset               # -> device bricked, only recoverable with: nrfjprog --qspieraseall

What we have tried

In every combination below, staging works fine (image upload -n 1image=1 slot=1, magic=good), but the result is always the same: after image test/confirm + reset, the device bricks (reboot loop, only recoverable with nrfjprog --qspieraseall).

  • A) SECURE_BOOT_NETCORE + NETCORE_APP_UPDATE only (app core keeps BOOT_SWAP_USING_MOVE). → Brick on reset. RTT shows MCUboot validating image 1 OK, then abort() right after boot_verify_slot_dependencies. The net image seems to go through the app-core swap path. We noticed the network-core swap-skip in boot_slots_compatible() (swap_move.c) is guarded by #ifdef PM_S1_ADDRESS, which is undefined in this config.

  • B) Added SECURE_BOOT_APPCORE too (to define PM_S1_ADDRESS). → Build assert PM_S0_SIZE == PM_S1_SIZE; fixed by removing our explicit CONFIG_PM_PARTITION_SIZE_MCUBOOT so PM sizes S0/S1 equally. The device still bricks on reset — now apparently later, possibly during the PCD copy to the network core.

  • We have not switched MCUboot to OVERWRITE_ONLY (used by the NCS ref_smp_svr_ext_flash sample for nRF53), because it removes the application core's revert capability, which we need to keep.

So we seem to be missing a config combination: every variant we tried either bricks immediately or bricks "later", always around the network-core update on reset.

Question

Is there a documented minimal configuration for nRF5340 network-core FOTA over MCUmgr (image 1) that coexists with application-core image swap + revert — i.e. without forcing MCUboot into OVERWRITE_ONLY? Does enabling network-core update with BOOT_SWAP_USING_MOVE on the app core require application-core secure boot (B0), or is there a supported combination we are missing?

A reference set of Kconfig options for this exact case would be ideal.

Parents Reply Children
  • I have tried this configuration.
    Then the APP CORE is updated properly and the SWAP/ROLLBACK works fine.

    BUT, when I try to perform the NET CORE FOTA, the device gets totally bricked until I perform a QSPI Flash erase:

    nrfjprog --qspieraseall --family NRF53

  • After iterating on Claude, the report and explanation for my problem suing the SDK 3.2.1:

    Why the default behavior bricks the device in swap mode (NCS v3.2.1)

    With MCUboot in BOOT_SWAP_USING_MOVE (the NCS default, and the mode that
    provides application-core rollback), a staged image-1 update takes the swap
    path instead. Trace (all references NCS v3.2.1):

    1. context_boot_go() determines an upgrade swap type (PERM/TEST) for
      image 1 and calls boot_perform_update()  boot_swap_image()
      (bootloader/mcuboot/boot/bootutil/src/loader.c). The stock
      boot_perform_update_hook is an empty stub, so nothing keeps the net core
      out of this generic swap path — this stub is exactly what the fix overrides
      (see The fix below).
    2. boot_swap_image() reads the primary header to size the swap. The
      hook hands it the fake header: ih_magic = IMAGE_MAGIC,
      ih_img_size = PM_CPUNET_APP_SIZE.
    3. Seeing a "valid" image, it calls
      boot_read_image_size(state, BOOT_SLOT_PRIMARY, ...), which reads the TLV
      info trailer at ih_hdr_size + ih_img_size  from the RAM flash
      simulator, which contains garbage
       (uninitialized RAM / zeros).
    4. The TLV magic check fails → boot_read_image_size() returns
      BOOT_EBADIMAGE  assert(rc == 0) in boot_swap_image() fires →
      abort().
    5. The device reboots. The staged image in external flash is still
      pending
      , so every boot repeats steps 1–4: a reboot loop only
      recoverable by erasing the QSPI (nrfjprog --qspieraseall).

    No Kconfig combination avoids this in v3.2.1 — the failing code path is the
    generic swap engine itself. In particular:

    • SB_CONFIG_SECURE_BOOT_APPCORE (defines PM_S1_ADDRESS, which gates the
      netcore exemption in boot_slots_compatible()) does not help: the
      slots happen to be "compatible" anyway (both 256 KB / 4 KB sectors), so the
      swap proceeds and crashes in step 3 regardless.
    • CONFIG_USE_NRF53_MULTI_IMAGE_WITHOUT_UPGRADE_ONLY (auto-set by sysbuild
      when multi-image update is enabled without overwrite-only) merely allows
      the build — it is flagged [dangerous] and does not make the update work
      (see Two separate failure modes below).
    • The official documentation ("Simultaneous multi-image DFU with nRF5340")
      requires CONFIG_BOOT_UPGRADE_ONLY for this feature, and every Nordic
      reference (including the Matter samples) forces
      MCUBOOT_MODE_OVERWRITE_ONLY when network-core update is enabled.

    So the supported configuration removes application-core rollback, and the
    permitted configuration bricks the device. Hence this module.

    Status in newer NCS releases

    Verified locally against ~/ncs/v3.3.1 (sdk-mcuboot + sdk-nrf):

    • The crash itself is fixed: boot_swap_image() now special-cases the
      network-core image (if (image_index == CONFIG_MCUBOOT_NETWORK_CORE_IMAGE_NUMBER))
      and reads the size from the secondary slot instead of the faked primary
      header, so the assert(rc == 0) no longer fires.
    • Everything else is unchanged: nrf53_hooks.c still leaves
      boot_perform_update_hook unimplemented (return BOOT_HOOK_REGULAR), the
      Kconfig still labels the combination [dangerous], and the official
      documentation still requires overwrite-only.
    • v3.3.1 does add a clean inline PCD path in boot_validated_swap_type(),
      but it is gated to !CONFIG_NRF53_MULTI_IMAGE_UPDATE (the single-image-pair
      / recovery mode). Our build uses CONFIG_NRF53_MULTI_IMAGE_UPDATE, so that
      path does not apply to us — Nordic still has no clean net-core path for
      the multi-image configuration.
    • On v3.3.1 without this module (multi-image mode), an image-1 update would
      run a full, pointless swap-move between QSPI and RAM (slow, wears the QSPI,
      writes RAM garbage into the secondary slot as the "old image") and the
      revert-bricks-the-netcore hazard remains live.
Related