Trouble updating bootloader

Our company has an existing product (snodar) which uses an nRF52840. When we created the snodar, it used a bootloader based on the `secure_bootloader` project. On top of that, we are using the s140 SD and have an APP which allows DFU over BLE. We have been able to update the snodar over BLE using the nordic DFU tools or our own custom Android/iOS apps.

We also have another product (gateway) which uses an nRF52840 + nRF9160. I've been working on mechanism to update the snodar by transferring the firmware over ESB using the gateway then doing an in-place DFU on the snodar. This works only if I can replace the bootloader on the snodar. To get the in-place DFU to work, I had to make a couple of changes to the bootloader project.

I needed to revise the bootloader to:
- revise sdk_config.h to add "#define NRF_BL_DFU_ALLOW_UPDATE_FROM_APP 1"
- revise the bootloader .ld file to increase RAM for my changes to the sdk
- revise the nrf_dfu_validation.c to add a function to copy cached flash data into ram
"In the lab" I can easily make a new merged hex and load onto a system over jtag using nrfprog. However, for field units, we update with DFU over BLE. I'm not clear on how I can update the bootloader for these systems.
The typical .zip just contains the SD + APP. It would be nice to have a one-off .zip which is BL + SD + ZIP. Reading through the nrfutil manual it indicates this is possible on page 9 but doesn't provide an example. Do I just add the --bootloader and --bootloader_version to the call which is making the zip now? Ideally, I'd almost like to just add the bootloader + softdevice and have the nrf5 flash erased elsewise so I can start fresh again.
Our bootloader was based on the 'nRF5_SDK_17.0.2\examples\dfu\secure_bootloader' project. We added our own dfu_public_key.c and then made some small customizations to the sdk_config.h so we couldn't do things like downgrade firmware. This bootloader was loaded on the units during assembly long before we had the idea to do this in-place DFU.
The only recent change to the bootloader's sdk_config.h was to add the line: "#define NRF_BL_DFU_ALLOW_UPDATE_FROM_APP 1". However, due the change necessary to that nrf_dfu_validation.c file, I had to increase the RAM some. The bootloader's .ld looks like:
MEMORY
{
  FLASH (rx) : ORIGIN = 0xf7000, LENGTH = 0x7000
  RAM (rwx) :  ORIGIN = 0x20005978, LENGTH = 0x3a688
  uicr_bootloader_start_address (r) : ORIGIN = 0x10001014, LENGTH = 0x4
  bootloader_settings_page (r) : ORIGIN = 0x000FF000, LENGTH = 0x1000
  uicr_mbr_params_page (r) : ORIGIN = 0x10001018, LENGTH = 0x4
  mbr_params_page (r) : ORIGIN = 0x000FE000, LENGTH = 0x1000
}
The FLASH was from 0xf8000 with len 0x6000 before the mod.
On the snodar side, the .ld had the following at the top:
MEMORY
{
  FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xc9000
  RAM (rwx) :  ORIGIN = 0x20006368, LENGTH = 0x39c98
  uicr_bootloader_start_address (r) : ORIGIN = 0x10001014, LENGTH = 0x4
}

SECTIONS
{
  . = ALIGN(4);
  .uicr_bootloader_start_address :
  {
    PROVIDE(__start_uicr_bootloader_start_address = .);
    KEEP(*(SORT(.uicr_bootloader_start_address*)))
    PROVIDE(__stop_uicr_bootloader_start_address = .);
  } > uicr_bootloader_start_address
}
If I attempted an in-place DFU with that setup, I would see:
00> dat len/crc = 142, b100ec1e
00> bin len/crc = 284644, 97038ff8
00> *** Trigger DFU ***
00> ~~ RTC ptimer disabled
00> CRC ok?
00> dat: b100ec1e - b100ec1e -> 1
00> bin: 97038ff8 - 97038ff8 -> 1
00> Starting DFU update: bin_addr = 3f81000, remaining = 284644
00> <info> app: NRF_DFU_EVT_DFU_STARTED
00>
00> <info> nrf_dfu_validation: Signature required. Checking signature.
00>
00> <info> nrf_dfu_validation: Calculating hash (len: 65)
00>
00> <info> nrf_dfu_validation: Verify signature
00>
00> <info> nrf_dfu_validation: Image verified
00>
00> <warning> nrf_dfu_settings: Settings write aborted since it tries writing to forbidden settings.
00>
00> <warning> nrf_dfu_serial: DFU request completed with result: 0xA
00>
00> <info> app: NRF_DFU_EVT_DFU_FAILED
So I modified the snodar's .ld to resemble:
MEMORY
{
  FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xc9000
  RAM (rwx) :  ORIGIN = 0x20006368, LENGTH = 0x39c98
  uicr_bootloader_start_address (r) : ORIGIN = 0x10001014, LENGTH = 0x4
  bootloader_settings_page (r) : ORIGIN = 0x000FF000, LENGTH = 0x1000
  uicr_mbr_params_page (r) : ORIGIN = 0x10001018, LENGTH = 0x4
  mbr_params_page (r) : ORIGIN = 0x000FE000, LENGTH = 0x1000
}

SECTIONS
{
  . = ALIGN(4);
  .uicr_bootloader_start_address :
  {
    PROVIDE(__start_uicr_bootloader_start_address = .);
    KEEP(*(SORT(.uicr_bootloader_start_address*)))
    PROVIDE(__stop_uicr_bootloader_start_address = .);
  } > uicr_bootloader_start_address
  . = ALIGN(4);
  .bootloader_settings_page(NOLOAD) :
  {
    PROVIDE(__start_bootloader_settings_page = .);
    KEEP(*(SORT(.bootloader_settings_page*)))
    PROVIDE(__stop_bootloader_settings_page = .);
  } > bootloader_settings_page
  . = ALIGN(4);
  .uicr_mbr_params_page :
  {
    PROVIDE(__start_uicr_mbr_params_page = .);
    KEEP(*(SORT(.uicr_mbr_params_page*)))
    PROVIDE(__stop_uicr_mbr_params_page = .);
  } > uicr_mbr_params_page
  . = ALIGN(4);
  .mbr_params_page(NOLOAD) :
  {
    PROVIDE(__start_mbr_params_page = .);
    KEEP(*(SORT(.mbr_params_page*)))
    PROVIDE(__stop_mbr_params_page = .);
  } > mbr_params_page
}
The top four lines in the memory part match the same in the bootloader. If I do this then the in-place dfu works.
The in-place dfu is weird and I don't fully understand it. During a normal ble dfu, when I start a dfu, the snodar switches to the bootloader and then does the update. I know this since the LEDs indicate what stage I am in. For this in-place dfu, we stay in the snodar fw and it is able to update itself -- it doesn't switch to the bootloader.
If I just use jtag and the hex file, both the bootloader and snodar work fine. I can update from one snodar fw to the next as long as the fw version is bigger. I also know that the in-place dfu will not work at all unless I use the newer bootloader. If I use the old bootloader, the validation fails, and it just reverts back to the current snodar fw.
When we use nrfutil we are doing a BL + SD + APP setup:
# "nrf52840_xxaa_s140.hex" is the generated bootloader
nrfutil settings generate --family NRF52840 --application "${APP_NAME}.hex" --application-version 0 --bootloader-version 1 --bl-settings-version 2 settings.hex
mergehex -m "${APP_NAME}.hex" *_softdevice.hex "nrf52840_xxaa_s140.hex" -o tmp.hex
mergehex -m tmp.hex settings.hex -o "${PROD_FW}_v${FW_VER}.hex"
rm tmp.hex
This hex works over jtag, but our field units are sealed up, so we need to use BLE DFU to update them. My understanding is that we need to make two files to update both. We are never changing the SD part. It is always the s140.
So my test bootloader generation was:
nrfutil pkg generate --hw-version 2 --bootloader nrf52840_xxaa_s140.hex --bootloader-version 1 --sd-req 0x0100 --key-file ../../../../../keys/private.key secure_bl_v1.zip
And the generated app was:
nrfutil pkg generate --hw-version "${HW_REV_VER}" --application-version-string "${FW_VER}" --application "${APP_NAME}.hex" --sd-req 0x0100 --key-file ../../../../../../keys/private.key "${SECURE_FW_NAME}.zip"
If I attempt to ble dfu of this, what I witness is that the snodar switches to some old version of the bootloader. The advertising name I see is "SNOdar BootloaderD" -- this is different from the new one. At this stage, I am able to then update the app firmware fine, but If I try to update this bootloader zip, it just gives me a bunch of "x" in the dfu app and really no feedback.
I had also tried adding the soft device to the bootloader generation:
nrfutil pkg generate --hw-version 2 --bootloader nrf52840_xxaa_s140.hex --bootloader-version 1 --softdevice "${SOFT_DEV}" --sd-req 0x0100 --key-file ../../../../../keys/private.key secure_bl_v1.zip
This has a similar result as before, but this time, the dfu app will show a "insufficient resources" at the bottom if I try to use this bootloader zip. The app update works fine.
Sorry for the data dump, but I just wanted to collect all my notes of things I've tried so far.
Is there any way to just wipe the flash and make a zip that just contains a BL + SD? over a BLE DFU? Do you have any other suggestions to update the bootloader?
  • Hi,

    Do I just add the --bootloader and --bootloader_version to the call which is making the zip now?

    Yes.

    For this in-place dfu, we stay in the snodar fw and it is able to update itself -- it doesn't switch to the bootloader.

    This is commonly referred to as "background DFU" because the app continues to run while receiving the FW update unlike normal DFU where the entire process takes place in the bootloader. So, the application is responsible for receiving the FW update image and storing it to flash (bank_1 area). But you still need to reboot the device afterwards to let the bootloader activate the update by copying the image from Bank 1 to bank 0.

    The FLASH was from 0xf8000 with len 0x6000 before the mod.

    It is not possible to change the Bootloader's start address through DFU as the start address is stored in a protected area of flash. What is the actual size of your bootloader after your modifications? I am basically wondering if it would be possible to keep the original start address with some small code optimizations.

    I get just below the 24K limit  here when I build the bootloader (nRF5_SDK_17.1.0_ddde560/examples/dfu/secure_bootloader/pca10056_s140_ble/armgcc) with NRF_BL_DFU_ALLOW_UPDATE_FROM_APP==1:

    Linking target: _build/nrf52840_xxaa_s140.out
       text	   data	    bss	    dec	    hex	filename
      23968	    184	  21976	  46128	   b430	_build/nrf52840_xxaa_s140.out
    

    If I attempt to ble dfu of this, what I witness is that the snodar switches to some old version of the bootloader. The advertising name I see is "SNOdar BootloaderD" -- this is different from the new one. At this stage, I am able to then update the app firmware fine, but If I try to update this bootloader zip, it just gives me a bunch of "x" in the dfu app and really no feedback.

    This is strange. The DFU app should give a clear error message if an update is rejected. Could you try DFU with the nRF connect app on Android/iOS and see if it gives the same result?   

    Is there any way to just wipe the flash and make a zip that just contains a BL + SD? over a BLE DFU? Do you have any other suggestions to update the bootloader?

    It should be possible to update just the bootloader as long as you bump the version number and make sure the old and new bootloader have the same start address in FLASH.

    Best regards,

    Vidar

  • If I restore the previous .ld and build the project, I see:

    Linking target: _build/nrf52840_xxaa_s140.out
    c:/program files (x86)/gnu arm embedded toolchain/10 2020-q4-major/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: region FLASH overflowed with .data and user data
    collect2.exe: error: ld returned 1 exit status
    make: *** [_build/nrf52840_xxaa_s140.out] Error 1

    I added my secure_bootloader project but changed the public key to just have random data for sharing. Interestingly, the snodar_2p0 build fails, but the other one doesn't. Not sure if I really need the serial_dfu and slip since the in-place dfu in the other project is doing that.

    I did make a mod to the nRF5_sdk/components/bootloader/dfu/nrf_dfu_validation.c. This is what caused the issue in the first place?

    static bool flash_ram_validation_hash_ok(uint8_t const * p_hash, uint32_t src_addr, uint32_t data_len)
    {
        ret_code_t err_code;
    	
    	const uint32_t fw_chunk_size = 4096;
        static uint8_t fw_chunk[4096];
    	
    	nrf_crypto_hash_sha256_digest_t digest;
    	size_t digest_len = NRF_CRYPTO_HASH_SIZE_SHA256;
    	
    	nrf_crypto_hash_context_t hash_context = {0};
    	
    	// Initialize the hash context
    	err_code = nrf_crypto_hash_init(&hash_context, &g_nrf_crypto_hash_sha256_info);
    	APP_ERROR_CHECK(err_code);
    
    	uint32_t chunk_addr = src_addr;
    	uint32_t remaining = data_len;
    	while (remaining) {
    		uint32_t copy_len = remaining > fw_chunk_size ? fw_chunk_size : remaining;
    		//NRF_LOG_DEBUG("$$$ start_addr = %lx, size = %ld $$$", chunk_addr, copy_len);
    		//NRF_LOG_FLUSH();
    		
    		memcpy(fw_chunk, (uint8_t *)chunk_addr, copy_len);
    		
    		err_code = nrf_crypto_hash_update(&hash_context, fw_chunk, copy_len);
    		APP_ERROR_CHECK(err_code);
    		
    		chunk_addr += copy_len;
    		remaining -= copy_len;
    	}
    	
    	// Run the finalize when all data has been fed to the update function which gives the result
    	err_code = nrf_crypto_hash_finalize(&hash_context, digest, &digest_len);
    	APP_ERROR_CHECK(err_code);
    
    	// Compare memory of our calculated hash vs the expected hash
    	bool hash_valid = memcmp(digest, p_hash, NRF_CRYPTO_HASH_SIZE_SHA256) == 0;
    	NRF_LOG_DEBUG("flash_ram_validation_hash_ok() hash OK == %d", hash_valid);
    	NRF_LOG_FLUSH();
    	
    	if (!hash_valid) {
    		NRF_LOG_DEBUG("Expected FW hash:")
    		NRF_LOG_FLUSH();
            NRF_LOG_HEXDUMP_DEBUG(p_hash, NRF_CRYPTO_HASH_SIZE_SHA256);
    		NRF_LOG_FLUSH();
            NRF_LOG_DEBUG("Actual FW hash:")
    		NRF_LOG_FLUSH();
            NRF_LOG_HEXDUMP_DEBUG(digest, NRF_CRYPTO_HASH_SIZE_SHA256);
    		NRF_LOG_FLUSH();
    	}
    	
    	return hash_valid;
    }
    
    // Function to check the hash received in the init command against the received firmware.
    // little_endian specifies the endianness of @p p_hash.
    static bool nrf_dfu_validation_hash_ok(uint8_t const * p_hash, uint32_t src_addr, uint32_t data_len, bool little_endian)
    {
        ret_code_t err_code;
        bool       result   = true;
        uint8_t    hash_be[NRF_CRYPTO_HASH_SIZE_SHA256];
        size_t     hash_len = NRF_CRYPTO_HASH_SIZE_SHA256;
    
        nrf_crypto_hash_context_t hash_context = {0};
    
        crypto_init();
    
        if (little_endian)
        {
            // Convert to hash to big-endian format for use in nrf_crypto.
            nrf_crypto_internal_swap_endian(hash_be,
                                            p_hash,
                                            NRF_CRYPTO_HASH_SIZE_SHA256);
            p_hash = hash_be;
        }
    
        NRF_LOG_DEBUG("Hash verification. start address: 0x%x, size: 0x%x",
                      src_addr,
                      data_len);
    
        err_code = nrf_crypto_hash_calculate(&hash_context,
                                             &g_nrf_crypto_hash_sha256_info,
                                             (uint8_t*)src_addr,
                                             data_len,
                                             m_fw_hash,
                                             &hash_len);
    
        if (err_code != NRF_SUCCESS)
        {
            NRF_LOG_ERROR("Could not run hash verification (err_code 0x%x).", err_code);
    		NRF_LOG_FLUSH();
    	
    		// This will trigger if the crypto can't do a hash calculation as it has no access
    		// to the memory being checked. This happens for the in-place DFU since the data is
    		// in flash memory (not RAM). We will retry using custom method...
    		result = flash_ram_validation_hash_ok(p_hash, src_addr, data_len);
        }
        else if (memcmp(m_fw_hash, p_hash, NRF_CRYPTO_HASH_SIZE_SHA256) != 0)
        {
            NRF_LOG_WARNING("Hash verification failed.");
            NRF_LOG_DEBUG("Expected FW hash:")
            NRF_LOG_HEXDUMP_DEBUG(p_hash, NRF_CRYPTO_HASH_SIZE_SHA256);
            NRF_LOG_DEBUG("Actual FW hash:")
            NRF_LOG_HEXDUMP_DEBUG(m_fw_hash, NRF_CRYPTO_HASH_SIZE_SHA256);
            NRF_LOG_FLUSH();
    
            result = false;
        }
    
        return result;
    }

    1425.secure_bootloader.zip

  • Did you base your bootloader on the 'pca10056_s140_ble' configuration? It does not make sense to include the slip.c and nrf_dfu_serial.c sources if the bootloader only uses the BLE DFU transport. If you remove these source files from your Makefile, you will also be able to enable the NRF_DFU_PROTOCOL_REDUCED setting in sdk_config.h again, which will reduce the flash usage. Hopefully enough to make it fit into the original bootloader area again (0xF8000 - 0xFE000) 

  • Looks like that might have done it! Had to simplify the makefile and set that reduced dfu flag to get it to build successfully.

    I was able to eventually get the bootloader to finally build. It looks I can update the original bootloader to the new one. It doesn't let me update that bootloader anymore, but this is because same version updates are disallowed.

    I know it works since the in-place DFU only works properly if the bootloader has been updated, which is the case.

    Thanks for all your help!

Related