nRF9151 (SDK 2.9.0) – Using External NAND Flash (MT29F4G01ABAFDWB) for Single-Image OTA Update Without Secondary Partition

Hi,

I am using nRF9151 with nRF Connect SDK 2.9.0.

In our project we are using an external NAND flash (MT29F4G01ABAFDWB) over SPI, and it is working successfully.
We can perform page write/read operations and also run our filesystem on it without issues.

We also have an external Wi-Fi module (SiWG917Y) connected over UART.
Using this Wi-Fi module, we download an OTA file using HTTP and store it into our external NAND flash.
We also verify the CRC of the OTA file, and the CRC matches correctly.
After writing the OTA file, we trigger a reboot.

Current Setup

  1. We only use Primary Application Partition on internal flash.
    No secondary slot partition in internal flash.

  2. OTA file is fully stored in the external NAND flash.

  3. CRC check of downloaded OTA firmware is done and successful.

  4. System reboots after download.


Goal

We want to implement Single-Image OTA update, where the bootloader will take the firmware image stored in external NAND flash and update the internal flash of nRF9151.


Question

Since we do not have a secondary partition in internal flash, and the new image is stored externally, what steps should we take to modify or configure the bootloader so that it:

  1. Reads the OTA image from external NAND flash after reboot.

  2. Validates the image (hash/CRC).

  3. Writes the new firmware into the primary app region.

  4. Boots into the updated firmware.

What is the recommended approach in NCS to support this kind of external-flash single-image update?

Is there an official reference for staging an image from external Nand flash storage instead of using the built-in secondary slot?

Or any other  guidance for ota using external nand flash

Best regards,
Milan

Parents Reply Children
  • Hi Einar,

    Thank you for the clarification.

    We understand that the SDK officially supports external NOR flash for the secondary slot. However, in our project we are already using an external NAND flash (MT29F4G01ABAFDWB) and we have successfully implemented and verified the full driver layer (SPI interface, page read/write, bad-block management, and filesystem).

    Since our NAND flash integration is fully functional, we wanted to ask:

    Is there any possibility or recommended approach to modify or extend the SDK/MCUboot so that OTA can work using external NAND flash as the secondary image slot?
    If there are specific components (partition manager, storage backend, MCUboot bootloader, flash APIs, or flash_area drivers) that need to be adapted, we would appreciate guidance on what needs to be changed or where we should start.

    Any technical pointers, required hooks, or limitations that prevent NAND support would be extremely helpful for us.

    Thanks & Regards,
    Milan

  • Hi Milan,

    There is currently ongoing work in the Zephyr community to support NAND flash. You can refer to this pull request for details on it, but as you see it has been active for two years, so this is a major effort. Though far from trivial I assume it will be possible to add support for NAND flash devices in MCUboot, but we have not done any work on it and I am unfortunately not able to provide any pointers at this point.

  • Hi Einar,

    Thanks for the information.

    I understand NAND support is not available yet, but I would still like to try implementing this myself because our NAND driver and filesystem are already fully working in our project.

    To make progress, I need your help with understanding where MCUboot performs certain operations. If you can guide me to the correct source files / functions, then I can try to replace the external NOR secondary-slot logic with our NAND read implementation.

    Could you please help me with these specific technical questions?


    1) Where does the bootloader look for the secondary image after boot?

    Which MCUboot files/functions are responsible for:

    • Locating the secondary slot

    • Opening the flash area

    • Reading image headers / TLV / metadata

    I want to replace the external NOR flash backend with our NAND flash driver in these locations.


    2) Where in MCUboot is the CRC / hash / signature validation done?

    I need to know the exact function paths where:

    • The bootloader reads the image from secondary flash

    • Validates SHA/CRC/signature

    • Confirms the image is valid before swapping

    Which source files contain this logic?


    3) After validation, where does MCUboot perform the swap/overwrite of the primary slot?

    I need the functions that:

    • Erase primary slot

    • Copy data from secondary → primary

    • Handle sector-by-sector swapping

    • Write status to trailer area

    Please share the file names and function names where this happens.


    Once I know these entry points, I will try replacing the secondary-slot flash_area_read() calls with my own NAND-read logic and test whether the bootloader can update from external NAND.

    Any pointers—even high-level—will be extremely helpful.

    Thanks & Regards,
    Milan

  • Hi Milan,

    I have not been able to dig into all those details now. But MCUboot is an open source project, and I expect AI tools  can help in digging up where this these operations happens. (The AI assistant here on DevZone has parsed all the SDK documentation and source code, including from external open source project that are included, such as MCUboot).

    For reference, this is what I got:


    You are right that MCUBoot has no NAND support in NCS today; the DevZone answer confirms Nordic has no additional material beyond upstream MCUBoot itself.[Modify NAND]

    The Nordic docs you attached mostly describe behavior (swap types, high‑level flow) and the Zephyr MCUBoot API surface, not the internal file layout of MCUBoot’s source. That means:

    • I can describe where MCUBoot conceptually does each thing and which API layers you must plug into (Zephyr flash_area_* / multi‑image helpers, boot_request_upgrade, mcuboot_swap_type, etc.).[Zephyr MCUBoot API]
    • But the knowledge sources do not enumerate the exact MCUBoot C files or all function names implementing those behaviors (e.g. no direct references to bootutil_image_validate, swap_run, etc.), so I cannot list them reliably without guessing.

    Below I’ll stay at the level that is documented in the material you shared and point you to the integration hooks you can safely target.


    1) Where does MCUBoot “look” for the secondary image?

    Conceptually, MCUBoot boots as follows:

    1. On reset it:
      • Checks if a swap is being resumed (swap status area).[High‑level op]
      • If not, checks the image trailers in primary & secondary to see if a swap/upgrade is requested.[High‑level op]
    2. For multiple images, it loops over images, and for each image:
      • Inspects swap status.
      • Inspects image trailers in primary & secondary.
      • Validates the image requested for upgrade.
      • Later, performs the swap and updates trailers.[Multiple image]

    On the Zephyr side, MCUBoot uses the flash area abstraction and the multi‑image mapping helpers to reach the secondary slot:

    a) Locating primary / secondary slots

    MCUBoot uses “image index + slot” → “flash area id” mapping:

    int flash_area_id_from_multi_image_slot(int image_index, int slot) {
        switch (slot) {
          case 0:
            return FLASH_AREA_IMAGE_PRIMARY(image_index);
          case 1:
            return FLASH_AREA_IMAGE_SECONDARY(image_index);
        }
    
        MCUBOOT_LOG_ERR("Unexpected Request: image_index=%d, slot=%d", image_index, slot);
        return -1; /* flash_area_open will fail on that */
    }
    
    int flash_area_id_from_image_slot(int slot) {
      return flash_area_id_from_multi_image_slot(0, slot);
    }
    

    [flash_area_id_from_multi_image_slot]

    So the entry point you need to customize for NAND is:

    • flash_area_id_from_multi_image_slot() (and flash_area_id_from_image_slot() for single‑image builds).
      This tells MCUBoot which fa_id corresponds to primary(0)/secondary(1)/(scratch) for each image index.

    b) Opening the flash area

    Once MCUBoot has the fa_id, it calls the flash backend:

    • flash_area_open(uint8_t id, const struct flash_area **area_outp)
    • flash_area_close(const struct flash_area *fa)

    A typical port defines static flash_area structs and a small lookup helper:

    static const struct flash_area *s_flash_areas[] = {
      &bootloader,
      &primary_img0,
      &secondary_img0,
    };
    
    static const struct flash_area *prv_lookup_flash_area(uint8_t id) {
      for (size_t i = 0; i < ARRAY_SIZE(s_flash_areas); i++) {
        const struct flash_area *area = s_flash_areas[i];
        if (id == area->fa_id) {
          return area;
        }
      }
      return NULL;
    }
    
    int flash_area_open(uint8_t id, const struct flash_area **area_outp) {
      const struct flash_area *area = prv_lookup_flash_area(id);
      *area_outp = area;
      return area != NULL ? 0 : -1;
    }
    

    [Flash backend deps]

    For NAND, this is where you redirect MCUBoot from the default NOR device to your NAND device/driver:

    • Use fa_device_id or fa_id to distinguish “internal NOR” vs “external NAND”.
    • Implement flash_area_read, flash_area_write, flash_area_erase such that when fa_device_id == NAND_DEVICE_ID you route to your NAND driver instead of NOR.

    c) Reading image headers / TLVs / metadata

    On the application side, Zephyr exposes:

    • boot_read_bank_header(uint8_t area_id, struct mcuboot_img_header *header, size_t header_size) – read header from a given image bank (primary or secondary).[Zephyr MCUBoot API]

    Your own sample code already uses it:

    boot_read_bank_header(area_id, &header, sizeof(header));
    

    [DevZone example]

    Internally in MCUBoot, the same abstraction (flash_area_*) is used to scan:

    From your perspective, you don’t need to touch the TLV parsing – just ensure flash_area_read() returns valid bytes from NAND for the secondary slot.


    2) Where are CRC / hash / signature checks done?

    The docs describe the logic of integrity / authenticity checking, but not the exact filenames. The key steps are:

    • Before an image is swapped into primary, MCUBoot runs an integrity check on it.[Integrity check]
    • With encrypted images, it:
      • Decrypts the per‑image symmetric key TLV with a private key.
      • Decrypts blocks from secondary.
      • Validates the decrypted image (SHA/signature).
      • If valid, performs the upgrade (write to primary).[Encrypted upgrade]

    a) What is checked

    According to the integrity‑check spec:[Integrity check]

    • Magic (IMAGE_MAGIC) in the header.
    • Presence of image TLV info after hdr_size + img_size (possibly two TLV info blocks if protected TLVs are used).
    • Presence of SHA256 TLV; computed SHA256 must match.
    • Optional signature TLV + KEYHASH TLV; the matching key is used to verify the image contents.

    MCUBoot can also optionally validate the primary slot on boot if MCUBOOT_VALIDATE_PRIMARY_SLOT is set; and can cache the result once if MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE is enabled.[Integrity check]

    b) Where in the flow validation happens

    From the high‑level boot flow:[High‑level op; Multiple image]

    1. Inspect trailers: is a swap requested?
    2. If yes, is the requested image valid (integrity and security check)?
      • If yes → do swap / overwrite.
      • If no → erase invalid image, mark swap FAIL.

    So validation happens between:

    • Discovering a pending upgrade via trailer metadata.
    • Actually modifying the primary slot.

    For encrypted images, this is stated explicitly: the bootloader

    • Checks ENCRYPTED flag and key TLV in the secondary image.
    • Decrypts blocks and validates during the upgrade process.
    • If valid, reads from secondary, decrypts, and writes to primary.[Encrypted upgrade]

    The exact C function names that compute SHA/signature (e.g. in bootutil) are not listed in the docs you provided, so I cannot give you a precise symbol list without going beyond the allowed sources.

    For your NAND work, the important point is:

    • All validation I/O goes through flash_area_read() on the secondary’s flash_area.
      If you provide a correct NAND‑backed implementation of the flash area backend, the existing validation pipeline will work unchanged.

    3) Where does MCUBoot actually do swap / overwrite?

    The docs describe two different upgrade strategies and how the bootloader manipulates flash:

    a) Overwrite‑based upgrade

    With overwrite, MCUBoot:

    • Always executes firmware from the primary slot.
    • Treats the secondary as a staging area.
    • When a valid, marked image is found in secondary:
      • Overwrites primary with secondary contents.
      • Erases header & trailer in secondary after successful overwrite to avoid re‑triggering an upgrade.[TF‑M overwrite]

    The operation is described as fail‑safe and power‑cut resistant, but the low‑level C functions are not listed.

    b) Swap‑using‑scratch strategy

    With swapping, MCUBoot swaps the contents of primary and secondary, using a scratch area as a temporary region.[Image swapping; Image slots]

    Key behaviors:

    • Active image is in primary and always started by the bootloader.[TF‑M swapping]
    • On upgrade request:
      • Checks requested image validity.
      • Swaps regions from secondary <→ primary, using scratch as temporary storage.
      • Uses swap status and image trailers to survive resets and to track progress.[Image swapping]

    The swap algorithm is documented step‑by‑step: erase scratch, copy region from secondary → scratch, update status (i), erase secondary region, copy primary → secondary, erase primary, copy scratch → primary, update status (ii/iii), etc., and finally persist completion in the primary’s image trailer.[Image swapping]

    Again, all erase / copy operations go through:

    • flash_area_erase()
    • flash_area_read()
    • flash_area_write()

    on the flash_area corresponding to primary, secondary and scratch.

    c) Status / trailer handling

    The docs call out:

    • Swap status is kept either in primary trailer or, for the last region, temporarily in scratch.[Image swapping]
    • After an image upgrade is successful, you can mark it “OK” at runtime; otherwise, MCUBoot will revert on the next boot (test vs perm swap types).[Boot swap types; TF‑M swapping]

    On the application side, Zephyr exposes:

    • boot_is_img_confirmed() – check if current image is confirmed.[Zephyr MCUBoot API]
    • boot_write_img_confirmed() – mark running image OK (update trailer).[Zephyr MCUBoot API]
    • boot_request_upgrade(int permanent) – mark secondary as pending upgrade (sets trailer flags).[Zephyr MCUBoot API]
    • mcuboot_swap_type() – report what MCUBoot will do next reboot (NONE / TEST / PERM / REVERT / FAIL / PANIC).[Boot swap types; Zephyr MCUBoot API]

    Your own code already uses these correctly to test upgrades and confirm: [DevZone example]

    rc = boot_is_img_confirmed();
    if (!rc) {
        if (boot_write_img_confirmed()) {
          LOG_ERR("Failed to confirm image");
        } else {
          LOG_INF("Marked image as OK");
        }
    }
    ...
    rc = boot_request_upgrade(BOOT_UPGRADE_TEST);
    

    How to hook your NAND implementation

    Within the constraints of the docs you provided, here is the concrete set of “entry points” you should modify/implement to replace external NOR with NAND for the secondary slot (and optional scratch):

    1. Flash area definitions & mapping

      • Implement flash_area_id_from_multi_image_slot() such that:
      • Define struct flash_area entries for:
        • Primary (internal NOR, as today).
        • Secondary (external NAND).
        • Scratch (wherever you choose, if using swap).
    2. Flash backend functions

      • Implement (or extend) the MCUboot flash backend so that for the NAND‑backed fa_device_id:
        • flash_area_open() / flash_area_close() return your NAND‑backed area.[Flash backend deps]
        • flash_area_read(), flash_area_write(), flash_area_erase() call into your NAND driver instead of the default NOR driver.
    3. Image operations & validation

      • No changes required in MCUBoot’s validation code if your flash_area_* functions are correct:
      • If you use encrypted images with swap‑using‑scratch, consider the note:
        • Keys may be written in plaintext to scratch unless MCUBOOT_SWAP_SAVE_ENCTLV is enabled; if scratch is in external memory, you must enable it.[Encrypted upgrade]
    4. Swap / overwrite

      • The internal algorithm is driven entirely via flash_area_* and trailers:
        • Overwrite: primary erased and written from secondary; secondary’s headers/trailers erased.[TF‑M overwrite]
        • Swap: sector/region copying via scratch exactly as described.[Image swapping]
      • You don’t need to modify those algorithms; they become NAND‑aware automatically once the backend is.

    Because the provided docs don’t list the internal MCUBoot C files and symbols, I can’t give you a precise “file:function” map (e.g. bootutil/xxx.c:swap_run()). To get that level of detail you’ll need to browse the MCUBoot tree in your modules/bootloader/mcuboot (or upstream mcuboot/boot/bootutil) and search for:

    • flash_area_read, flash_area_write, flash_area_erase
    • flash_area_id_from_*image_slot
    • IMAGE_MAGIC, image_tlv_info
    • Swap‑related strings like “swap status” and “status area”

    But from the perspective of integrating a different secondary storage technology, the only layer you need to replace is the flash area backend plus the slot→area mapping, as outlined above. Everything else (trailer parsing, validation, swap logic, swap types) will work unchanged.

Related