442 lines
15 KiB
C
442 lines
15 KiB
C
|
/**
|
||
|
* Copyright (c) 2016 - 2020, 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "nrf_bootloader_fw_activation.h"
|
||
|
#include "nrf_dfu_settings.h"
|
||
|
#include "nrf_dfu_mbr.h"
|
||
|
#include "nrf_bootloader_info.h"
|
||
|
#include "crc32.h"
|
||
|
#include "nrf_log.h"
|
||
|
#include "nrf_log_ctrl.h"
|
||
|
#include "nrf_dfu_utils.h"
|
||
|
#include "nrf_bootloader_wdt.h"
|
||
|
|
||
|
|
||
|
static volatile bool m_flash_write_done;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* @brief Function for copying image. Image is copied in chunks. Frequency of storing progress
|
||
|
* in flash is configured by input parameter.
|
||
|
*
|
||
|
* @param[in] dst_addr Destination address. Must be page aligned.
|
||
|
* @param[in] src_addr Source address. Must be higher value than dst_addr.
|
||
|
* @param[in] size Image size.
|
||
|
* @param[in] progress_update_step Number of copied pages that triggers saving progress to non-volatile memory.
|
||
|
* Note that step can be decreased if there is a risk of corruption caused by source
|
||
|
* and destination overlapping.
|
||
|
*
|
||
|
* @return NRF_SUCCESS or error code in case of failure.
|
||
|
*/
|
||
|
static uint32_t image_copy(uint32_t dst_addr,
|
||
|
uint32_t src_addr,
|
||
|
uint32_t size,
|
||
|
uint32_t progress_update_step)
|
||
|
{
|
||
|
if (src_addr == dst_addr)
|
||
|
{
|
||
|
NRF_LOG_DEBUG("No copy needed");
|
||
|
return NRF_SUCCESS;
|
||
|
}
|
||
|
|
||
|
ASSERT(src_addr >= dst_addr);
|
||
|
ASSERT(progress_update_step > 0);
|
||
|
if (size != 0)
|
||
|
{
|
||
|
ASSERT((dst_addr % CODE_PAGE_SIZE) == 0);
|
||
|
}
|
||
|
|
||
|
uint32_t max_safe_progress_upd_step = (src_addr - dst_addr)/CODE_PAGE_SIZE;
|
||
|
ASSERT(max_safe_progress_upd_step > 0);
|
||
|
|
||
|
uint32_t ret_val = NRF_SUCCESS;
|
||
|
uint32_t pages_left = CEIL_DIV(size, CODE_PAGE_SIZE);
|
||
|
|
||
|
//Firmware copying is time consuming operation thus watchdog handling is started
|
||
|
nrf_bootloader_wdt_init();
|
||
|
|
||
|
progress_update_step = MIN(progress_update_step, max_safe_progress_upd_step);
|
||
|
|
||
|
while (size > 0)
|
||
|
{
|
||
|
uint32_t pages;
|
||
|
uint32_t bytes;
|
||
|
if (pages_left <= progress_update_step)
|
||
|
{
|
||
|
pages = pages_left;
|
||
|
bytes = size;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pages = progress_update_step;
|
||
|
bytes = progress_update_step * CODE_PAGE_SIZE;
|
||
|
}
|
||
|
// Erase the target pages
|
||
|
ret_val = nrf_dfu_flash_erase(dst_addr, pages, NULL);
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
// Flash one page
|
||
|
NRF_LOG_DEBUG("Copying 0x%x to 0x%x, size: 0x%x", src_addr, dst_addr, bytes);
|
||
|
ret_val = nrf_dfu_flash_store(dst_addr,
|
||
|
(uint32_t *)src_addr,
|
||
|
ALIGN_NUM(sizeof(uint32_t), bytes),
|
||
|
NULL);
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
pages_left -= pages;
|
||
|
size -= bytes;
|
||
|
dst_addr += bytes;
|
||
|
src_addr += bytes;
|
||
|
s_dfu_settings.write_offset += bytes;
|
||
|
|
||
|
//store progress in flash on every successful chunk write
|
||
|
ret_val = nrf_dfu_settings_write_and_backup(NULL);
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
NRF_LOG_ERROR("Failed to write image copying progress to settings page.");
|
||
|
return ret_val;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
/** @brief Function to continue application update.
|
||
|
*
|
||
|
* @details This function will be called after reset if there is a valid application in Bank1
|
||
|
* required to be copied down to Bank 0.
|
||
|
*
|
||
|
* @return NRF_SUCCESS if continuation was successful, NRF_ERROR_INTERNAL if new firmware does not
|
||
|
* contain softdevice or other error coming from modules used by this function.
|
||
|
*/
|
||
|
static uint32_t app_activate(void)
|
||
|
{
|
||
|
// This function is only in use when new app is present in Bank 1
|
||
|
uint32_t const image_size = s_dfu_settings.bank_1.image_size;
|
||
|
|
||
|
uint32_t src_addr = s_dfu_settings.progress.update_start_address;
|
||
|
uint32_t ret_val = NRF_SUCCESS;
|
||
|
uint32_t target_addr = nrf_dfu_bank0_start_addr() + s_dfu_settings.write_offset;
|
||
|
uint32_t length_left = (image_size - s_dfu_settings.write_offset);
|
||
|
uint32_t crc;
|
||
|
|
||
|
NRF_LOG_DEBUG("Enter nrf_dfu_app_continue");
|
||
|
|
||
|
src_addr += s_dfu_settings.write_offset;
|
||
|
|
||
|
if (src_addr == target_addr)
|
||
|
{
|
||
|
length_left = 0;
|
||
|
}
|
||
|
|
||
|
ret_val = image_copy(target_addr, src_addr, length_left, NRF_BL_FW_COPY_PROGRESS_STORE_STEP);
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
NRF_LOG_ERROR("Failed to copy firmware.");
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
// Check the CRC of the copied data. Enable if so.
|
||
|
crc = crc32_compute((uint8_t*)nrf_dfu_bank0_start_addr(), image_size, NULL);
|
||
|
|
||
|
if (crc == s_dfu_settings.bank_1.image_crc)
|
||
|
{
|
||
|
NRF_LOG_DEBUG("Setting app as valid");
|
||
|
s_dfu_settings.bank_0.bank_code = NRF_DFU_BANK_VALID_APP;
|
||
|
s_dfu_settings.bank_0.image_crc = crc;
|
||
|
s_dfu_settings.bank_0.image_size = image_size;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NRF_LOG_ERROR("CRC computation failed for copied app: "
|
||
|
"src crc: 0x%08x, res crc: 0x%08x",
|
||
|
s_dfu_settings.bank_1.image_crc,
|
||
|
crc);
|
||
|
}
|
||
|
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** @brief Function to execute the continuation of a SoftDevice update.
|
||
|
*
|
||
|
* @return NRF_SUCCESS if continuation was successful, NRF_ERROR_INTERNAL if new firmware does not
|
||
|
* contain softdevice or other error coming from modules used by this function.
|
||
|
*/
|
||
|
static uint32_t sd_activate(void)
|
||
|
{
|
||
|
uint32_t ret_val = NRF_SUCCESS;
|
||
|
uint32_t target_addr = nrf_dfu_softdevice_start_address() + s_dfu_settings.write_offset;
|
||
|
uint32_t src_addr = s_dfu_settings.progress.update_start_address;
|
||
|
uint32_t sd_size = s_dfu_settings.sd_size;
|
||
|
uint32_t length_left = ALIGN_TO_PAGE(sd_size - s_dfu_settings.write_offset);
|
||
|
|
||
|
NRF_LOG_DEBUG("Enter nrf_bootloader_dfu_sd_continue");
|
||
|
|
||
|
if (SD_MAGIC_NUMBER_GET(src_addr) != SD_MAGIC_NUMBER)
|
||
|
{
|
||
|
NRF_LOG_ERROR("Source address does not contain a valid SoftDevice.")
|
||
|
return NRF_ERROR_INTERNAL;
|
||
|
}
|
||
|
|
||
|
// This can be a continuation due to a power failure
|
||
|
src_addr += s_dfu_settings.write_offset;
|
||
|
|
||
|
if (s_dfu_settings.write_offset == sd_size)
|
||
|
{
|
||
|
NRF_LOG_DEBUG("SD already copied");
|
||
|
return NRF_SUCCESS;
|
||
|
}
|
||
|
|
||
|
if (s_dfu_settings.write_offset == 0)
|
||
|
{
|
||
|
NRF_LOG_DEBUG("Updating SD. Old SD ver: %d, New ver: %d",
|
||
|
SD_VERSION_GET(MBR_SIZE) / 1000000, SD_VERSION_GET(src_addr) / 1000000);
|
||
|
}
|
||
|
|
||
|
ret_val = image_copy(target_addr, src_addr, length_left, NRF_BL_FW_COPY_PROGRESS_STORE_STEP);
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
NRF_LOG_ERROR("Failed to copy firmware.");
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
ret_val = nrf_dfu_settings_write_and_backup(NULL);
|
||
|
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** @brief Function to continue bootloader update.
|
||
|
*
|
||
|
* @details This function will be called after reset if there is a valid bootloader in Bank 0 or Bank 1
|
||
|
* required to be relocated and activated through MBR commands.
|
||
|
*
|
||
|
* @return This function will not return if the bootloader is copied successfully.
|
||
|
* After the copy is verified, the device will reset and start the new bootloader.
|
||
|
*
|
||
|
* @retval NRF_SUCCESS Continuation was successful.
|
||
|
* @retval NRF_ERROR_INVALID_LENGTH Invalid length of flash operation.
|
||
|
* @retval NRF_ERROR_NO_MEM If no parameter page is provided (see sds for more info).
|
||
|
* @retval NRF_ERROR_INVALID_PARAM If an invalid command is given.
|
||
|
* @retval NRF_ERROR_INTERNAL Internal error that should not happen.
|
||
|
* @retval NRF_ERROR_FORBIDDEN If NRF_UICR->BOOTADDR is not set.
|
||
|
*/
|
||
|
static uint32_t bl_activate(void)
|
||
|
{
|
||
|
uint32_t ret_val = NRF_ERROR_INVALID_DATA;
|
||
|
nrf_dfu_bank_t * p_bank = &s_dfu_settings.bank_1;
|
||
|
uint32_t len = p_bank->image_size;
|
||
|
uint32_t src_addr = s_dfu_settings.progress.update_start_address;
|
||
|
|
||
|
if (p_bank->bank_code == NRF_DFU_BANK_VALID_SD_BL)
|
||
|
{
|
||
|
src_addr += s_dfu_settings.sd_size;
|
||
|
len -= s_dfu_settings.sd_size;
|
||
|
}
|
||
|
else if (src_addr == 0)
|
||
|
{
|
||
|
src_addr = nrf_dfu_bank1_start_addr();
|
||
|
}
|
||
|
|
||
|
NRF_LOG_DEBUG("Verifying BL: Addr: 0x%08x, Src: 0x%08x, Len: 0x%08x", BOOTLOADER_START_ADDR, src_addr, len);
|
||
|
|
||
|
// This code is a configurable workaround for updating SD+BL from SDK 12.x.y - 14.1.0
|
||
|
// SoftDevice size increase would lead to unaligned source address when comparing new BL in SD+BL updates.
|
||
|
// This workaround is not required once BL is successfully installed with a version that is compiled SDK 14.1.0
|
||
|
#if defined(NRF52832_XXAA) && defined(BLE_STACK_SUPPORT_REQD)
|
||
|
if ((p_bank->bank_code == NRF_DFU_BANK_VALID_SD_BL) &&
|
||
|
(memcmp((void *)BOOTLOADER_START_ADDR, (void *)(src_addr - 0x4000), len) == 0))
|
||
|
{
|
||
|
ret_val = NRF_SUCCESS;
|
||
|
}
|
||
|
#endif // defined(NRF52832_XXAA)
|
||
|
|
||
|
// Check if the BL has already been copied.
|
||
|
if ((ret_val != NRF_SUCCESS) &&
|
||
|
(memcmp((void *)BOOTLOADER_START_ADDR, (void *)src_addr, len) == 0))
|
||
|
{
|
||
|
ret_val = NRF_SUCCESS;
|
||
|
}
|
||
|
|
||
|
// If the bootloader is the same as the banked version, the copy is finished
|
||
|
if (ret_val == NRF_SUCCESS)
|
||
|
{
|
||
|
NRF_LOG_DEBUG("No bootloader copy needed, bootloader update complete.");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NRF_LOG_DEBUG("Copying bootloader: Src: 0x%08x, Len: 0x%08x", src_addr, len);
|
||
|
NRF_LOG_FLUSH();
|
||
|
|
||
|
nrf_bootloader_wdt_feed();
|
||
|
|
||
|
// Bootloader is different than the banked version. Continue copy
|
||
|
// Note that if the SD and BL was combined, then the split point between them is in s_dfu_settings.sd_size
|
||
|
// On success this function won't return.
|
||
|
ret_val = nrf_dfu_mbr_copy_bl((uint32_t*)src_addr, len);
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
NRF_LOG_ERROR("Request to copy BL failed");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
|
||
|
/** @brief Function to continue combined bootloader and SoftDevice update.
|
||
|
*
|
||
|
* @details This function will be called after reset if there is a valid bootloader and SoftDevice in Bank 0 or Bank 1
|
||
|
* required to be relocated and activated through MBR commands.
|
||
|
*
|
||
|
* @retval NRF_SUCCESS Continuation was successful.
|
||
|
* @retval NRF_ERROR_INVALID_LENGTH Invalid length.
|
||
|
* @retval NRF_ERROR_NO_MEM If UICR.NRFFW[1] is not set (i.e. is 0xFFFFFFFF).
|
||
|
* @retval NRF_ERROR_INVALID_PARAM If an invalid command is given.
|
||
|
* @retval NRF_ERROR_INTERNAL Indicates that the contents of the memory blocks where not verified correctly after copying.
|
||
|
* @retval NRF_ERROR_NULL If the content of the memory blocks differs after copying.
|
||
|
* @retval NRF_ERROR_FORBIDDEN If NRF_UICR->BOOTADDR is not set.
|
||
|
*/
|
||
|
static uint32_t sd_bl_activate()
|
||
|
{
|
||
|
uint32_t ret_val = NRF_SUCCESS;
|
||
|
|
||
|
NRF_LOG_DEBUG("Enter nrf_dfu_sd_bl_continue");
|
||
|
|
||
|
ret_val = sd_activate();
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
NRF_LOG_ERROR("SD+BL: SD copy failed");
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
ret_val = bl_activate();
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
NRF_LOG_ERROR("SD+BL: BL copy failed");
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
return ret_val;
|
||
|
}
|
||
|
|
||
|
|
||
|
static void flash_write_callback(void * p_context)
|
||
|
{
|
||
|
UNUSED_PARAMETER(p_context);
|
||
|
m_flash_write_done = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
nrf_bootloader_fw_activation_result_t nrf_bootloader_fw_activate(void)
|
||
|
{
|
||
|
nrf_bootloader_fw_activation_result_t result;
|
||
|
uint32_t ret_val = NRF_SUCCESS;
|
||
|
nrf_dfu_bank_t * p_bank = &s_dfu_settings.bank_1;
|
||
|
bool sd_update = false;
|
||
|
|
||
|
NRF_LOG_DEBUG("Enter nrf_bootloader_fw_activate");
|
||
|
|
||
|
switch (p_bank->bank_code)
|
||
|
{
|
||
|
case NRF_DFU_BANK_VALID_APP:
|
||
|
NRF_LOG_DEBUG("Valid App");
|
||
|
ret_val = app_activate();
|
||
|
break;
|
||
|
case NRF_DFU_BANK_VALID_SD:
|
||
|
NRF_LOG_DEBUG("Valid SD");
|
||
|
ret_val = sd_activate();
|
||
|
sd_update = true;
|
||
|
break;
|
||
|
case NRF_DFU_BANK_VALID_BL:
|
||
|
NRF_LOG_DEBUG("Valid BL");
|
||
|
ret_val = bl_activate();
|
||
|
break;
|
||
|
case NRF_DFU_BANK_VALID_SD_BL:
|
||
|
NRF_LOG_DEBUG("Valid SD + BL");
|
||
|
ret_val = sd_bl_activate();
|
||
|
sd_update = true;
|
||
|
break;
|
||
|
case NRF_DFU_BANK_INVALID:
|
||
|
default:
|
||
|
NRF_LOG_INFO("No firmware to activate.");
|
||
|
return ACTIVATION_NONE;
|
||
|
}
|
||
|
|
||
|
if (ret_val != NRF_SUCCESS)
|
||
|
{
|
||
|
NRF_LOG_ERROR("Activation failed with error %d (bank code: 0x%x)", ret_val, p_bank->bank_code);
|
||
|
result = ACTIVATION_ERROR;
|
||
|
}
|
||
|
|
||
|
// Invalidate bank, marking completion.
|
||
|
nrf_dfu_bank_invalidate(p_bank);
|
||
|
|
||
|
m_flash_write_done = false;
|
||
|
ret_val = nrf_dfu_settings_write_and_backup(flash_write_callback);
|
||
|
ASSERT(m_flash_write_done); /* At this point flash module is performing blocking operation. It is expected that operation is already performed. */
|
||
|
|
||
|
if (ret_val == NRF_SUCCESS)
|
||
|
{
|
||
|
result = ACTIVATION_SUCCESS;
|
||
|
if (sd_update && (s_dfu_settings.bank_0.bank_code == NRF_DFU_BANK_VALID_APP))
|
||
|
{
|
||
|
//If SD was updated and application is valid we want to stay in DFU to receive application.
|
||
|
NRF_LOG_DEBUG("A SoftDevice has just been activated. It's likely that an application will come immediately");
|
||
|
result = ACTIVATION_SUCCESS_EXPECT_ADDITIONAL_UPDATE;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NRF_LOG_ERROR("Could not write settings.");
|
||
|
result = ACTIVATION_ERROR;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|