Hi nordic,
I have successfully completed the Bluetooth over-the-air upgrade using the secure bootloader example. However, we need to add an additional upgrade method. In the application, I have downloaded the new application file along with its corresponding settings file to external flash. How can I modify the secure bootloader so that after the device resets, the new application replaces the old one?
I tried simulating SPI in the bootloader to load the data from the SPI flash into the application region, but I encountered errors when writing the settings file.
SDK 17.1.0 nRF52833
Main code:
/**
* Copyright (c) 2016 - 2021, Nordic Semiconductor ASA
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form, except as embedded into a Nordic
* Semiconductor ASA integrated circuit in a product or a software update for
* such product, must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. Neither the name of Nordic Semiconductor ASA nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* 4. This software, with or without modification, must only be used with a
* Nordic Semiconductor ASA integrated circuit.
*
* 5. Any software provided in binary form under this license must not be reverse
* engineered, decompiled, modified and/or disassembled.
*
* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
/** @file
*
* @defgroup bootloader_secure_ble main.c
* @{
* @ingroup dfu_bootloader_api
* @brief Bootloader project main file for secure DFU.
*
*/
#include <stdint.h>
#include "boards.h"
#include "nrf_mbr.h"
#include "nrf_bootloader.h"
#include "nrf_bootloader_app_start.h"
#include "nrf_bootloader_dfu_timers.h"
#include "nrf_dfu.h"
#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"
#include "app_error.h"
#include "app_error_weak.h"
#include "nrf_bootloader_info.h"
#include "nrf_delay.h"
#include "m_fstorage.h"
#include "w25qxx.h"
static void on_error(void)
{
NRF_LOG_FINAL_FLUSH();
#if NRF_MODULE_ENABLED(NRF_LOG_BACKEND_RTT)
// To allow the buffer to be flushed by the host.
nrf_delay_ms(100);
#endif
#ifdef NRF_DFU_DEBUG_VERSION
NRF_BREAKPOINT_COND;
#endif
NVIC_SystemReset();
}
void app_error_handler(uint32_t error_code, uint32_t line_num, const uint8_t * p_file_name)
{
NRF_LOG_ERROR("%s:%d", p_file_name, line_num);
on_error();
}
void app_error_fault_handler(uint32_t id, uint32_t pc, uint32_t info)
{
NRF_LOG_ERROR("Received a fault! id: 0x%08x, pc: 0x%08x, info: 0x%08x", id, pc, info);
on_error();
}
void app_error_handler_bare(uint32_t error_code)
{
NRF_LOG_ERROR("Received an error: 0x%08x!", error_code);
on_error();
}
/**
* @brief Function notifies certain events in DFU process.
*/
static void dfu_observer(nrf_dfu_evt_type_t evt_type)
{
switch (evt_type)
{
case NRF_DFU_EVT_DFU_FAILED:
case NRF_DFU_EVT_DFU_ABORTED:
case NRF_DFU_EVT_DFU_INITIALIZED:
// bsp_board_init(BSP_INIT_LEDS);
// bsp_board_led_on(BSP_BOARD_LED_0);
// bsp_board_led_on(BSP_BOARD_LED_1);
// bsp_board_led_off(BSP_BOARD_LED_2);
break;
case NRF_DFU_EVT_TRANSPORT_ACTIVATED:
// bsp_board_led_off(BSP_BOARD_LED_1);
// bsp_board_led_on(BSP_BOARD_LED_2);
break;
case NRF_DFU_EVT_DFU_STARTED:
break;
default:
break;
}
}
/**
* @brief Function update application data.
*/
//uint8_t fs_user_data[FS_BUFF_SIZE] = {0};
static void application_update(void)
{
uint32_t FileLengh;
uint8_t FileConfig[36];
uint16_t FileLenHigh;
uint16_t FileLenLow;
char CloudMd5[33];
uint8_t fs_user_data[4];
W25QXX_Init();
if((W25QXX_Check() == true) || (W25QXX_valid_check() == true))
{
W25QXX_WAKEUP();
W25QXX_Read(fs_user_data,FLASH_FS_VALID_ADDR,36);
FileLengh = (fs_user_data[0] << 24) + (fs_user_data[1] << 16) + (fs_user_data[2] << 8) + fs_user_data[3];
for(int i=0; i<32; i++) CloudMd5[i] = fs_user_data[4+i];
FileLenHigh = FileLengh/4;
FileLenLow = FileLengh%4;
m_fs_init();
m_fs_erase_user_app();
for(int DownloadCnt = 0; DownloadCnt < FileLenHigh; DownloadCnt++)
{
W25QXX_Read(fs_user_data,FLASH_FS_APP_ADDR+FS_BUFF_SIZE*DownloadCnt,FS_BUFF_SIZE);
if(m_fs_write_check(FS_APP_ADDR+FS_BUFF_SIZE*DownloadCnt,fs_user_data,FS_BUFF_SIZE) == true)
{
;
}else
{
// W25QXX_PowerDown();
// m_fs_uninit();
// __disable_irq();
NVIC_SystemReset();
}
}
W25QXX_Read(fs_user_data, FLASH_FS_APP_ADDR+FS_BUFF_SIZE*FileLenHigh, FileLenLow);
if(m_fs_write_check(FS_APP_ADDR+FS_BUFF_SIZE*FileLenHigh,fs_user_data,FileLenLow) == true)
{
;
}else
{
// W25QXX_PowerDown();
// m_fs_uninit();
// __disable_irq();
NVIC_SystemReset();
}
m_fs_erase_user_setting();
for(uint16_t j=0; j<8092/FS_BUFF_SIZE; j++)
{
W25QXX_Read(fs_user_data,FLASH_FS_SETTING_ADDR+FS_BUFF_SIZE*j,FS_BUFF_SIZE);
if(m_fs_write_check(FS_SETTING_ADDR+FS_BUFF_SIZE*j,fs_user_data,FS_BUFF_SIZE) == true)
{
;
}else
{
// W25QXX_PowerDown();
// m_fs_uninit();
// __disable_irq();
NVIC_SystemReset();
}
}
W25QXX_Erase_Sector(FLASH_FS_VALID_SECTOR);
W25QXX_PowerDown();
// m_fs_uninit();
__disable_irq();
NVIC_SystemReset();
}
}
/**@brief Function for application main entry. */
int main(void)
{
uint32_t ret_val;
application_update();
// Must happen before flash protection is applied, since it edits a protected page.
nrf_bootloader_mbr_addrs_populate();
// Protect MBR and bootloader code from being overwritten.
ret_val = nrf_bootloader_flash_protect(0, MBR_SIZE);
APP_ERROR_CHECK(ret_val);
ret_val = nrf_bootloader_flash_protect(BOOTLOADER_START_ADDR, BOOTLOADER_SIZE);
// ret_val = nrf_bootloader_flash_protect(BOOTLOADER_START_ADDR, 0x5000);
APP_ERROR_CHECK(ret_val);
(void) NRF_LOG_INIT(nrf_bootloader_dfu_timer_counter_get);
NRF_LOG_DEFAULT_BACKENDS_INIT();
NRF_LOG_INFO("Inside main");
ret_val = nrf_bootloader_init(dfu_observer);
APP_ERROR_CHECK(ret_val);
NRF_LOG_FLUSH();
NRF_LOG_ERROR("After main, should never be reached.");
NRF_LOG_FLUSH();
APP_ERROR_CHECK_BOOL(false);
}
/**
* @}
*/
flash storage code:
#include "m_fstorage.h"
#include "nrf_soc.h"
#include "nrf_sdh.h"
#include "nrf_sdh_ble.h"
#include "nrf_fstorage.h"
#include "nrf_fstorage_sd.h"
#include "nrf_fstorage_nvmc.h"
#include "app_error.h"
#include "nrf_log.h"
static void fstorage_evt_handler(nrf_fstorage_evt_t * p_evt);
NRF_FSTORAGE_DEF(nrf_fstorage_t fstorage) =
{
/* Set a handler for fstorage events. */
.evt_handler = fstorage_evt_handler,
/* These below are the boundaries of the flash space assigned to this instance of fstorage.
* You must set these manually, even at runtime, before nrf_fstorage_init() is called.
* The function nrf5_flash_end_addr_get() can be used to retrieve the last address on the
* last page of flash available to write data. */
.start_addr = FS_START_ADDR,
.end_addr = FS_END_ADDR,
};
/* Dummy data to write to flash. */
/**@brief Helper function to obtain the last address on the last page of the on-chip flash that
* can be used to write user data.
*/
static uint32_t nrf5_flash_end_addr_get()
{
uint32_t const bootloader_addr = BOOTLOADER_ADDRESS;
uint32_t const page_sz = NRF_FICR->CODEPAGESIZE;
uint32_t const code_sz = NRF_FICR->CODESIZE;
return (bootloader_addr != 0xFFFFFFFF ?
bootloader_addr : (code_sz * page_sz));
}
static void fstorage_evt_handler(nrf_fstorage_evt_t * p_evt)
{
if (p_evt->result != NRF_SUCCESS)
{
NRF_LOG_INFO("--> Event received: ERROR while executing an fstorage operation.");
return;
}
switch (p_evt->id)
{
case NRF_FSTORAGE_EVT_WRITE_RESULT:
{
NRF_LOG_INFO("--> Event received: wrote %d bytes at address 0x%x.",
p_evt->len, p_evt->addr);
} break;
case NRF_FSTORAGE_EVT_ERASE_RESULT:
{
NRF_LOG_INFO("--> Event received: erased %d page from address 0x%x.",
p_evt->len, p_evt->addr);
} break;
default:
break;
}
}
static void print_flash_info(nrf_fstorage_t * p_fstorage)
{
NRF_LOG_INFO("========| flash info |========");
NRF_LOG_INFO("erase unit: \t%d bytes", p_fstorage->p_flash_info->erase_unit);
NRF_LOG_INFO("program unit: \t%d bytes", p_fstorage->p_flash_info->program_unit);
NRF_LOG_INFO("==============================");
}
void wait_for_flash_ready(nrf_fstorage_t const * p_fstorage)
{
/* While fstorage is busy, sleep and wait for an event. */
while (nrf_fstorage_is_busy(p_fstorage))
{
#ifndef SOFTDEVICE_PRESENT
(void) sd_app_evt_wait();
#else
__WFE();
#endif
}
}
/**@brief flash storage init.
*
*/
void m_fs_init(void)
{
ret_code_t rc;
nrf_fstorage_api_t * p_fs_api;
#ifndef SOFTDEVICE_PRESENT
NRF_LOG_INFO("SoftDevice is present.");
NRF_LOG_INFO("Initializing nrf_fstorage_sd implementation...");
/* Initialize an fstorage instance using the nrf_fstorage_sd backend.
* nrf_fstorage_sd uses the SoftDevice to write to flash. This implementation can safely be
* used whenever there is a SoftDevice, regardless of its status (enabled/disabled). */
p_fs_api = &nrf_fstorage_sd;
#else
NRF_LOG_INFO("SoftDevice not present.");
NRF_LOG_INFO("Initializing nrf_fstorage_nvmc implementation...");
/* Initialize an fstorage instance using the nrf_fstorage_nvmc backend.
* nrf_fstorage_nvmc uses the NVMC peripheral. This implementation can be used when the
* SoftDevice is disabled or not present.
*
* Using this implementation when the SoftDevice is enabled results in a hardfault. */
p_fs_api = &nrf_fstorage_nvmc;
#endif
rc = nrf_fstorage_init(&fstorage, p_fs_api, NULL);
APP_ERROR_CHECK(rc);
print_flash_info(&fstorage);
/* It is possible to set the start and end addresses of an fstorage instance at runtime.
* They can be set multiple times, should it be needed. The helper function below can
* be used to determine the last address on the last page of flash memory available to
* store data. */
(void) nrf5_flash_end_addr_get();
}
/**@brief flash storage uninit.
*
*/
void m_fs_uninit(void)
{
ret_code_t rc;
rc = nrf_fstorage_uninit(&fstorage, NULL);
APP_ERROR_CHECK(rc);
}
/**@brief flash storage write data no check.
*
*/
void m_fs_write_nocheck(uint32_t w_addr, uint8_t *w_data, uint32_t w_len)
{
ret_code_t rc;
rc = nrf_fstorage_write(&fstorage, w_addr, w_data, w_len, NULL);
APP_ERROR_CHECK(rc);
wait_for_flash_ready(&fstorage);
NRF_LOG_INFO("Write Done.");
}
/**@brief flash storage write data with check.
* @detail w_len max size is FS_BUFF_SIZE
*/
bool m_fs_write_check(uint32_t w_addr, uint8_t *w_data, uint32_t w_len)
{
ret_code_t rc;
uint8_t r_data[FS_BUFF_SIZE]={0};
if(w_len != 0)
{
/* Write data page*/
rc = nrf_fstorage_write(&fstorage, w_addr, w_data, w_len, NULL);
APP_ERROR_CHECK(rc);
wait_for_flash_ready(&fstorage);
NRF_LOG_INFO("Write Done.");
/* Read data page*/
rc = nrf_fstorage_read(&fstorage,w_addr,r_data,w_len);
APP_ERROR_CHECK(rc);
wait_for_flash_ready(&fstorage);
if(memcmp(w_data,r_data,w_len)==0)
{
return true;
}else
{
return false;
}
}else
{
return false;
}
}
/**@brief flash storage read data.
*
*/
void m_fs_read(uint32_t r_addr, uint8_t *r_data, uint32_t r_len)
{
ret_code_t rc;
/* Read data page*/
rc = nrf_fstorage_read(&fstorage,r_addr,r_data,r_len);
APP_ERROR_CHECK(rc);
wait_for_flash_ready(&fstorage);
}
/**@brief flash storage erase data.
*
*/
//void m_fs_erase_backup_valid(void)
//{
// ret_code_t rc;
// /* Erase data page*/
// rc = nrf_fstorage_erase(&fstorage,FS_BACKUP_VALID_ADDR,FS_VALID_SECTOR_LEN,NULL);
// APP_ERROR_CHECK(rc);
// wait_for_flash_ready(&fstorage);
// NRF_LOG_INFO("Erase Done.");
//}
//void m_fs_erase_backup_app(void)
//{
// ret_code_t rc;
// /* Erase data page*/
// rc = nrf_fstorage_erase(&fstorage,FS_BACKUP_APP_ADDR,FS_APP_SECTOR_LEN,NULL);
// APP_ERROR_CHECK(rc);
// wait_for_flash_ready(&fstorage);
// NRF_LOG_INFO("Erase Done.");
//}
//void m_fs_erase_backup_setting(void)
//{
// ret_code_t rc;
// /* Erase data page*/
// rc = nrf_fstorage_erase(&fstorage,FS_SETTING_ADDR,FS_SETTING_SECTOR_LEN,NULL);
// APP_ERROR_CHECK(rc);
// wait_for_flash_ready(&fstorage);
// NRF_LOG_INFO("Erase Done.");
//}
void m_fs_erase_user_app(void)
{
ret_code_t rc;
/* Erase data page*/
rc = nrf_fstorage_erase(&fstorage,FS_APP_ADDR,FS_APP_SECTOR_LEN,NULL);
APP_ERROR_CHECK(rc);
wait_for_flash_ready(&fstorage);
NRF_LOG_INFO("Erase Done.");
}
void m_fs_erase_user_setting(void)
{
ret_code_t rc;
/* Erase data page*/
rc = nrf_fstorage_erase(&fstorage,FS_SETTING_ADDR,FS_SETTING_SECTOR_LEN,NULL);
APP_ERROR_CHECK(rc);
wait_for_flash_ready(&fstorage);
NRF_LOG_INFO("Erase Done.");
}