OFF CHIP OTA UPDATE using external flash

I am working on this project off chip ota update on nRF5_SDK  using ecample/thread/dfu

by now, i have done changes in these functions on_data_obj_create_request() and on_data_obj_write_request(). initially these functions were erasing and writing data in internal flash which I changed to qspi functions like nrf_drv_qspi_erase() and nrf_drv_qspi_write(). and it works perfectly.

Later i changed functions for hash verification which is also working but i am not sure about what should be the next step so that bootloader changes the earlier application with the new firmware image.

  • Hi,

    Unfortunately our experts on this matter are out-of-office today. We expect to get back to you beginning of next week. If you have any progress in the mean time then please update this ticket with your findings.

    Regards,
    Terje

  • Hi Terje,

    Thank you for the update. I understand that your experts are currently out-of-office. I will continue to work on the matter and gather any progress or findings in the meantime.

    If I make any significant discoveries or need further assistance, I'll be sure to update this ticket accordingly.

    Looking forward to reconnecting at the beginning of next week.

    Best regards,

    Priyesh

  • I have made three changes in code 

    1: in flash erase i have changed it to erase external flash using QSPI.

    static void on_data_obj_create_request(nrf_dfu_request_t * p_req, nrf_dfu_response_t * p_res)
    {
        NRF_LOG_DEBUG("Handle NRF_DFU_OP_OBJECT_CREATE (data)");
    
        if (!nrf_dfu_validation_init_cmd_present())
        {
            /* Can't accept data because DFU isn't initialized by init command. */
            NRF_LOG_ERROR("Cannot create data object without valid init command");
            p_res->result = NRF_DFU_RES_CODE_OPERATION_NOT_PERMITTED;
            return;
        }
    
        if (p_req->create.object_size == 0)
        {
            NRF_LOG_ERROR("Object size cannot be 0.")
            p_res->result = NRF_DFU_RES_CODE_INVALID_PARAMETER;
            return;
        }
    
        if (  ((p_req->create.object_size & (CODE_PAGE_SIZE - 1)) != 0)
            && (s_dfu_settings.progress.firmware_image_offset_last + p_req->create.object_size != m_firmware_size_req))
        {
            NRF_LOG_ERROR("Object size must be page aligned");
            ota_error_code = 1;
            p_res->result = NRF_DFU_RES_CODE_INVALID_PARAMETER;
            return;
        }
    
        if (p_req->create.object_size > DATA_OBJECT_MAX_SIZE)
        {
            /* It is impossible to handle the command because the size is too large */
            NRF_LOG_ERROR("Invalid size for object (too large)");
            p_res->result = NRF_DFU_RES_CODE_INSUFFICIENT_RESOURCES;
            return;
        }
    
        if ((s_dfu_settings.progress.firmware_image_offset_last + p_req->create.object_size) >
            m_firmware_size_req)
        {
            NRF_LOG_ERROR("Creating the object with size 0x%08x would overflow firmware size. "
                          "Offset is 0x%08x and firmware size is 0x%08x.",
                          p_req->create.object_size,
                          s_dfu_settings.progress.firmware_image_offset_last,
                          m_firmware_size_req);
    
            p_res->result = NRF_DFU_RES_CODE_OPERATION_NOT_PERMITTED;
            ota_error_code = 2;
            return;
        }
    
        s_dfu_settings.progress.data_object_size      = p_req->create.object_size;
        s_dfu_settings.progress.firmware_image_crc    = s_dfu_settings.progress.firmware_image_crc_last;
        s_dfu_settings.progress.firmware_image_offset = s_dfu_settings.progress.firmware_image_offset_last;
        s_dfu_settings.write_offset                   = s_dfu_settings.progress.firmware_image_offset_last;
    
        /* Erase the page we're at. */
        if (nrf_dfu_flash_erase((m_firmware_start_addr + s_dfu_settings.progress.firmware_image_offset),
                                CEIL_DIV(p_req->create.object_size, CODE_PAGE_SIZE), NULL) != NRF_SUCCESS)
        {
            NRF_LOG_ERROR("Erase operation failed");
            p_res->result = NRF_DFU_RES_CODE_INVALID_OBJECT;
            return;
        }
    
        NRF_LOG_DEBUG("Creating object with size: %d. Offset: 0x%08x, CRC: 0x%08x",
                     s_dfu_settings.progress.data_object_size,
                     s_dfu_settings.progress.firmware_image_offset,
                     s_dfu_settings.progress.firmware_image_crc);
        
       NRF_LOG_INFO("Creating object with size: %d.  Offset: 0x%08x, CRC: 0x%08x\r\n",
                     s_dfu_settings.progress.data_object_size,
                     s_dfu_settings.progress.firmware_image_offset,
                     s_dfu_settings.progress.firmware_image_crc);
    
    
          /* check if flash is enabled */
            if(is_flash_enable() == false)
            {
              NRF_LOG_INFO("Flash in sleep state. Reinintialising\r\n");
    
              Enable_Flash();
            }
    
            if(nrf_drv_qspi_erase(NRF_QSPI_ERASE_LEN_4KB, FIRMWARE_FLASH_ADDR + (s_dfu_settings.progress.firmware_image_offset)) != NRFX_SUCCESS)
            {
              NRF_LOG_INFO("Failed to rrase data blocks in Ext Flash.\r\n");
            }
            else
            {
              NRF_LOG_INFO("Succsessfully Erased Ext Flash block at: 0x%X\r\n\r\n",FIRMWARE_FLASH_ADDR + (s_dfu_settings.progress.firmware_image_offset));
              WAIT_FOR_PERIPH();
              nrf_delay_ms(500);
            }
    
    
    }
     

    2: in flash store I have changed to write the code in external flash using QSPI.

    static void on_data_obj_write_request(nrf_dfu_request_t * p_req, nrf_dfu_response_t * p_res)
    {
        NRF_LOG_DEBUG("Handle NRF_DFU_OP_OBJECT_WRITE (data)");
    
        if (!nrf_dfu_validation_init_cmd_present())
        {
            /* Can't accept data because DFU isn't initialized by init command. */
            p_res->result = NRF_DFU_RES_CODE_OPERATION_NOT_PERMITTED;
            return;
        }
    
        uint32_t const data_object_offset = s_dfu_settings.progress.firmware_image_offset -
                                            s_dfu_settings.progress.firmware_image_offset_last;
    
        if ((p_req->write.len + data_object_offset) > s_dfu_settings.progress.data_object_size)
        {
            /* Can't accept data because too much data has been received. */
            NRF_LOG_ERROR("Write request too long");
            p_res->result = NRF_DFU_RES_CODE_INVALID_PARAMETER;
            return;
        }
    
    
    
        uint32_t const write_addr = m_firmware_start_addr + s_dfu_settings.write_offset;
        /* CRC must be calculated before handing off the data to fstorage because the data is
         * freed on write completion.
         */
        uint32_t const next_crc =
            crc32_compute(p_req->write.p_data, p_req->write.len, &s_dfu_settings.progress.firmware_image_crc);
    
        ASSERT(p_req->callback.write);
    
        ret_code_t ret = NRF_ERROR_BASE_NUM;
          //ret_code_t ret =
          //  nrf_dfu_flash_store(write_addr, p_req->write.p_data, p_req->write.len, p_req->callback.write);
    
            
            /* check if flash is enabled */
            if(is_flash_enable() == false)
            {
              NRF_LOG_INFO("Flash in sleep state. Reinintialising\r\n");
    
              Enable_Flash();
            }
            
            
            if(nrf_drv_qspi_write(p_req->write.p_data, p_req->write.len,FIRMWARE_FLASH_ADDR + s_dfu_settings.write_offset) != NRFX_SUCCESS)
            {
              NRF_LOG_INFO("\r\nFailed storing data blocks in Ext Flash.\r\n");
            }
            else
            {
              NRF_LOG_INFO("Succsessfully storing data block at: 0x%X\r\n\r\n",FIRMWARE_FLASH_ADDR + s_dfu_settings.write_offset);
              WAIT_FOR_PERIPH();
              p_req->callback.write((void*)p_req->write.p_data);
              ret = NRF_SUCCESS;
            }
    
        if (ret != NRF_SUCCESS)
        {
            /* When nrf_dfu_flash_store() fails because there is no space in the queue,
             * stop processing the request so that the peer can detect a CRC error
             * and retransmit this object. Remember to manually free the buffer !
             */
            p_req->callback.write((void*)p_req->write.p_data);
            return;
        }
    
        /* Update the CRC of the firmware image. */
        s_dfu_settings.write_offset                   += p_req->write.len;
        s_dfu_settings.progress.firmware_image_offset += p_req->write.len;
        s_dfu_settings.progress.firmware_image_crc     = next_crc;
    
        /* This is only used when the PRN is triggered and the 'write' message
         * is answered with a CRC message and these field are copied into the response.
         */
        p_res->write.crc    = s_dfu_settings.progress.firmware_image_crc;
        p_res->write.offset = s_dfu_settings.progress.firmware_image_offset;
    }

    3: in calculate hash i have changed it to copy data from QSPI and update the hash accordingly.

    ret_code_t nrf_crypto_hash_calculate(nrf_crypto_hash_context_t    * const p_context,
                                         nrf_crypto_hash_info_t       const * p_info,
                                         uint8_t                      const * p_data,
                                         size_t                               data_size,
                                         uint8_t                            * p_digest,
                                         size_t                       * const p_digest_size)
    {
        ret_code_t                      ret_val;
        nrf_crypto_hash_context_t     * p_ctx  = (nrf_crypto_hash_context_t *)p_context;
        void                          * p_allocated_context = NULL;
    
    // Internal allocation of context not available for CC310_BL in order to save code size.
    #if defined(NRF_CRYPTO_BACKEND_CC310_BL_HASH_SHA256_ENABLED) && (NRF_CRYPTO_BACKEND_CC310_BL_HASH_SHA256_ENABLED == 1)
        
        // Do nothing
        
    #elif defined(NRF_CRYPTO_BACKEND_CC310_BL_HASH_SHA256_ENABLED) && (NRF_CRYPTO_BACKEND_CC310_BL_HASH_SHA256_ENABLED == 0)
        
        // Validate input. Only validate input parameters that are used locally, others are validated
        // in the init, update and/or finalize functions.
        VERIFY_TRUE(p_info != NULL, NRF_ERROR_CRYPTO_INPUT_NULL);
    
        // Allocate context if needed (not provided by the user).
        if (p_context == NULL)
        {
            p_allocated_context = NRF_CRYPTO_ALLOC(p_info->context_size);
            if (p_allocated_context == NULL)
            {
                return NRF_ERROR_CRYPTO_ALLOC_FAILED;
            }
            p_ctx = (nrf_crypto_hash_context_t *)p_allocated_context;
        }
        
    #else
    
        #warning NRF_CRYPTO_BACKEND_CC310_BL_HASH_SHA256_ENABLED define not found in sdk_config.h (Is the sdk_config.h valid?).
        
    #endif // NRF_CRYPTO_BACKEND_CC310_BL_HASH_SHA256_ENABLED
    
        ret_val = nrf_crypto_hash_init(p_ctx, p_info);
        NRF_CRYPTO_VERIFY_SUCCESS_DEALLOCATE(ret_val, p_allocated_context);
    
    
        if(validation_state == 0)
        {
            ret_val = nrf_crypto_hash_update(p_ctx, p_data, data_size);
            NRF_CRYPTO_VERIFY_SUCCESS_DEALLOCATE(ret_val, p_allocated_context);
            validation_state = 1;
        }
        else
        {
            uint16_t firmware_blocks = data_size / FIRMWARE_BLOCK_SIZE;
            uint8_t rest_block = data_size % FIRMWARE_BLOCK_SIZE;
    
    
            printf("data_size: %d", data_size);
            printf("firmware_blocks: %d", firmware_blocks);
            printf("rest_block: %d", rest_block);
    
    
            for(uint16_t i=0; i <= firmware_blocks; i++)
            {
              /* check if flash is enabled */
                if(is_flash_enable() == false)
                {
                    printf("Flash in sleep state. Reinintialising\r\n");
    
                    Enable_Flash();
                }
            
                uint8_t m_buffer_rx[FIRMWARE_BLOCK_SIZE];
    
                if(i == firmware_blocks)
                {
                    if(nrf_drv_qspi_read(m_buffer_rx, rest_block,FIRMWARE_FLASH_ADDR + (FIRMWARE_BLOCK_SIZE * i)) != NRFX_SUCCESS)
                    {
                       printf("\r\nFailed read data blocks in Ext Flash.\r\n");
                    }
                    else
                    {
                       printf("Succsessfully read rest_block data block from: 0x%X\r\n\r\n",FIRMWARE_FLASH_ADDR + (FIRMWARE_BLOCK_SIZE * i));
                       WAIT_FOR_PERIPH();
                       ret_val = nrf_crypto_hash_update(p_ctx, m_buffer_rx, rest_block);
                       NRF_CRYPTO_VERIFY_SUCCESS_DEALLOCATE(ret_val, p_allocated_context);
                    }
                }
                else
                {
                    if(nrf_drv_qspi_read(m_buffer_rx, FIRMWARE_BLOCK_SIZE,FIRMWARE_FLASH_ADDR + (FIRMWARE_BLOCK_SIZE * i)) != NRFX_SUCCESS)
                    {
                        printf("\r\nFailed read data blocks in Ext Flash.\r\n");
                    }
                    else
                    {
                        printf("Succsessfully read data block %d from: 0x%X\r\n\r\n", i ,FIRMWARE_FLASH_ADDR + (FIRMWARE_BLOCK_SIZE * i));
                        WAIT_FOR_PERIPH();
                        ret_val = nrf_crypto_hash_update(p_ctx, m_buffer_rx, FIRMWARE_BLOCK_SIZE);
                        NRF_CRYPTO_VERIFY_SUCCESS_DEALLOCATE(ret_val, p_allocated_context);
                    }
                }            
            }
            validation_state = 0;
        }
    
        ret_val = nrf_crypto_hash_finalize(p_ctx, p_digest, p_digest_size);
        NRF_CRYPTO_VERIFY_SUCCESS_DEALLOCATE(ret_val, p_allocated_context);
    
    #if !NRF_MODULE_ENABLED(NRF_CRYPTO_BACKEND_CC310_BL_HASH_SHA256)
        // Free context if allocated internally
        if (p_allocated_context != NULL)
        {
            NRF_CRYPTO_FREE(p_allocated_context);
        }
    #endif // !NRF_MODULE_ENABLED(NRF_CRYPTO_BACKEND_CC310_BL_HASH_SHA256)
    
        return NRF_SUCCESS;
    }
    
    #endif // NRF_MODULE_ENABLED(NRF_CRYPTO_HASH)
    
    #endif // NRF_MODULE_ENABLED(NRF_CRYPTO)

    and after that few more changes to get notified that hash has been verified and also to find out flow of the code 

    1

    #if NRF_DFU_IN_APP
            res.result = nrf_dfu_validation_post_data_execute(m_firmware_start_addr, m_firmware_size_req);
            printf("Post Validation Error Code: %d\r\n",res.result );
    
            #else
            res.result = nrf_dfu_validation_activation_prepare(m_firmware_start_addr, m_firmware_size_req);
            #endif

    2 and in this this change i didn't find the output after this  if (!is_trusted). may be this piece of code is not executing or something

    nrf_dfu_result_t postvalidate(uint32_t data_addr, uint32_t data_len, bool is_trusted)
    {
        nrf_dfu_result_t           ret_val = NRF_DFU_RES_CODE_SUCCESS;
        dfu_init_command_t const * p_init  = mp_init;
    
        if (!fw_hash_ok(p_init, data_addr, data_len))
        {
            ret_val = EXT_ERR(NRF_DFU_EXT_ERROR_VERIFICATION_FAILED);
        }
        else
        {
            printf("postvalidate Hash ok\r\n");
          
            if (p_init->type == DFU_FW_TYPE_APPLICATION)
            {
                if (!postvalidate_app(p_init, data_addr, data_len, is_trusted))
                {
                    ret_val = NRF_DFU_RES_CODE_INVALID_OBJECT;
                    printf("postvalidate fun error: postvalidate_app %d\r\n",ret_val);
                }
                else
                {
                  printf("postvalidate fun success\r\n");
                }
            }
    #if NRF_DFU_SUPPORTS_EXTERNAL_APP
            else if (p_init->type == DFU_FW_TYPE_EXTERNAL_APPLICATION)
            {
                if (!is_trusted)
                {
                    // This function must be implemented externally
                    ret_val = nrf_dfu_validation_post_external_app_execute(p_init, is_trusted);
                }
                else
                {
                    s_dfu_settings.bank_1.bank_code = NRF_DFU_BANK_VALID_EXT_APP;
                }
            }
    #endif // NRF_DFU_SUPPORTS_EXTERNAL_APP
            else
            {
                bool with_sd = p_init->type & DFU_FW_TYPE_SOFTDEVICE;
                bool with_bl = p_init->type & DFU_FW_TYPE_BOOTLOADER;
    
                if (!postvalidate_sd_bl(p_init, with_sd, with_bl, data_addr, data_len, is_trusted))
                {
                    ret_val = NRF_DFU_RES_CODE_INVALID_OBJECT;
                    if (is_trusted && with_sd && !DFU_REQUIRES_SOFTDEVICE &&
                        (data_addr == nrf_dfu_softdevice_start_address()))
                    {
                        nrf_dfu_softdevice_invalidate();
                    }
                }
            }
        }
    
        if (!is_trusted)
        {
            if (ret_val == NRF_DFU_RES_CODE_SUCCESS)
            {
                    printf("\r\nSet DFU setting current bank.\r\n");
    
                s_dfu_settings.bank_current = NRF_DFU_CURRENT_BANK_1;
            }
            else
            {
                printf("nrf_dfu_settings_progress_reset if !is_trusted\r\n");
    
                nrf_dfu_settings_progress_reset();
            }
        }
        else
        {
            if (ret_val == NRF_DFU_RES_CODE_SUCCESS)
            {
            printf("Mark the update as complete and valid.\r\n");
                // Mark the update as complete and valid.
                s_dfu_settings.bank_1.image_crc  = crc32_compute((uint8_t *)data_addr, data_len, NULL);
                s_dfu_settings.bank_1.image_size = data_len;
            }
            else
            {
                nrf_dfu_bank_invalidate(&s_dfu_settings.bank_1);
                        printf("Mark the update as Invalid. : %d\r\n",ret_val);
    
            }
    
            printf("nrf_dfu_settings_progress_reset if is_trusted\r\n");
    
            nrf_dfu_settings_progress_reset();
            s_dfu_settings.progress.update_start_address = data_addr;
        }
    
        return ret_val;
    }
    

    3

    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);
    
       NRF_LOG_INFO("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);
            result = false;
        }
        else if (memcmp(m_fw_hash, p_hash, NRF_CRYPTO_HASH_SIZE_SHA256) != 0)
        {
            NRF_LOG_WARNING("Hash verification failed.");
            NRF_LOG_INFO("Hash verification failed.");
            NRF_LOG_DEBUG("Expected FW hash:")
            NRF_LOG_INFO("Expected FW hash:")
            NRF_LOG_HEXDUMP_DEBUG(p_hash, NRF_CRYPTO_HASH_SIZE_SHA256);
            NRF_LOG_DEBUG("Actual FW hash:")
            NRF_LOG_INFO("Actual FW hash:")
            NRF_LOG_HEXDUMP_DEBUG(m_fw_hash, NRF_CRYPTO_HASH_SIZE_SHA256);
            NRF_LOG_FLUSH();
    
            result = false;
        }
    
        return result;
    }

  • Hi Priyesh,

    It seems that you've made good progress on this already. The next step may be to modify the image copy functions in nrf_bootloader_fw_activation.c to enable the bootloader to copy images from bank 1 in the QSPI flash.

    Best regards,

    Vidar

  • Hi Vidar,

    Thank you for your guidance.

    I am also reading previous questions on devzone about the same subject and I have a few more questions regarding the process:

    1. External Flash Detection: How does the bootloader identify that there is a new firmware in the external flash? Initially, it appears to search for new firmware in the internal flash. Could you please provide more insights into this process?

    2. Logging: I'm also working on enabling logging in the bootloader. Could you please guide me on how to modify the flash address settings to ensure the logs are written correctly and do not conflict with other memory regions?

      MEMORY
      {
        FLASH (rx) : ORIGIN = 0xf8000, LENGTH = 0x6000
        RAM (rwx) :  ORIGIN = 0x20000008, LENGTH = 0x3fff8
        uicr_bootloader_start_address (r) : ORIGIN = 0x10001014, LENGTH = 0x4
        mbr_params_page (r) : ORIGIN = 0x000FE000, LENGTH = 0x1000
        bootloader_settings_page (r) : ORIGIN = 0x000FF000, LENGTH = 0x1000
        uicr_mbr_params_page (r) : ORIGIN = 0x10001018, LENGTH = 0x4
      }

Related