Hello,
We are currently helping one of our client develop a product using the nRF5340.
DK versions:
- nRF5340-DK
- PCA10095
- 2.0.1
Host OS:
- Ubuntu 22.04.2
- Linux 6.5.0-41-generic
nRF SDK and toolchains version:
- nRF SDK 2.6.1
- toolchains 2.6.1
We need to implement a custom upgrade process, where the application, or the bootloader if possible, would check if an upgrade file exists on the SD-Card, and flash if existing and valid. Both bootloader and application must be upgradable.
We currently have implemented a dual bootloader setup, with an application which reads the update file from the UART (no SD-card at the moment, so purely evaluation purpose) and uses DFUTarget to flash the memory chunk by chunk. This application can upgrade itself without any issue using the build/zephyr/app_update.bin file as the upgrade file sent via UART. I attach you the application and the prj.conf.
Now I'm trying to understand how MCUBoot is upgraded. From what I read, it uses both S0 and S1 slots, and only boots the latest valid MCUBoot flashed in one of those.
I can successfully upgrade MCUBoot by alternating with the s0 and s1 update file that I send to my test application implementing DFUTarget. I need clarification about the usage of those two images and how they are flashed:
- Why is it needed to switch between build/zephyr/signed_by_mcuboot_and_b0_s0_image_update.bin and build/zephyr/signed_by_mcuboot_and_b0_s1_image_update.bin?
- How will it then be possible to manage multiple devices when deployed on field?
- Should we always send both and do a check of which on is currently in use to select the correct one to flash?
- Why nothing happens when sending s0 image when the device currently uses s0 as the bootloader slot?
Thanks a lot in advance for you help, and please correct me if I'm wrong on any topic I covered asking my questions!
Best regards
David TRUAN
#include <stdio.h>
#include <string.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/crc.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/reboot.h>
#include <dfu/dfu_target.h>
#include <dfu/dfu_target_mcuboot.h>
#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/drivers/flash.h>
#include <zephyr/storage/flash_map.h>
#include <logging.h>
#define PRG_BUFSIZ 32000
#define VERSION 1
#define CHUNK_SZ 1024
#define MCUBOOT_BUF_SZ 512
static const struct device *uart_dev;
/* The buffer to receive */
static char mcuboot_buf[MCUBOOT_BUF_SZ] __aligned(4);
/**
* @brief Helper to read the update size using UART
*
* @param uart_dev the UART device to use to read
* @return int32_t >= 0 on success, -1 otherwise
*/
int32_t read_update_size_uart(const struct device *uart_dev) {
int err;
uint32_t prg_size = 0;
uint32_t bytes_read = 0;
uint8_t byte;
do {
err = uart_poll_in(uart_dev, &byte);
if (err == 0) {
prg_size |= (int)byte << (bytes_read * 8);
bytes_read++;
} else if (err == -1) {
continue;
} else {
log_error("Error detected %d", err);
return -1;
}
} while (bytes_read != sizeof(uint32_t));
return prg_size;
}
/**
* @brief Callback handler for DFUTarget events
*
* @param evt the event which called the callback
*/
static void dfu_target_callback_handler(enum dfu_target_evt_id evt) {
switch (evt) {
case DFU_TARGET_EVT_TIMEOUT:
printf("DFU_TARGET_EVT_TIMEOUT");
break;
case DFU_TARGET_EVT_ERASE_DONE:
printf("DFU_TARGET_EVT_ERASE_DONE");
break;
case DFU_TARGET_EVT_ERASE_PENDING:
printf("DFU_TARGET_EVT_ERASE_PENDING");
break;
}
}
int main(void) {
static char prg_data_buf[CHUNK_SZ];
char *prg_data;
int update_size_full;
int err;
int total_bytes_read;
uart_dev = DEVICE_DT_GET(DT_NODELABEL(uart0));
prg_data = prg_data_buf;
total_bytes_read = 0;
update_size_full = 0;
k_msleep(1000);
log_set_level(LOG_LEVEL_DEBUG);
log_info("current version: %d board: %s", VERSION, CONFIG_BOARD);
log_info("Waiting for playload...");
/* Retrieve the update size */
update_size_full = read_update_size_uart(uart_dev);
if (update_size_full == -1) {
log_error("Error while getting update size!");
return 1;
}
log_info("Update size: %d", update_size_full);
/* Set the buffer used by DFUTarget in MCUBoot mode */
if ((err = dfu_target_mcuboot_set_buf(mcuboot_buf, MCUBOOT_BUF_SZ)) != 0) {
log_error("dfu_target_mcuboot_set_buf error: %d", err);
return -1;
}
/* Initialize the DFUTarget library with our image info */
if ((err = dfu_target_init(DFU_TARGET_IMAGE_TYPE_ANY_APPLICATION, 0, update_size_full, dfu_target_callback_handler)) != 0) {
log_error("dfu_target_init error: %d", err);
return -1;
}
while (1) {
err = uart_poll_in(uart_dev, prg_data);
if (err == 0) {
prg_data++;
total_bytes_read++;
if (total_bytes_read == update_size_full) {
log_info("Received the full file!");
dfu_target_write(prg_data_buf, total_bytes_read % CHUNK_SZ);
break;
}
if (total_bytes_read % CHUNK_SZ == 0) {
dfu_target_write(prg_data_buf, CHUNK_SZ);
prg_data = prg_data_buf;
}
} else if (err == -ENOSYS) {
log_error("Error RX");
return 1;
} else if (err == -EBUSY) {
log_error("Error UART busy!");
return 1;
}
}
/* Ask DFU target to clean its data */
if ((err = dfu_target_done(true)) != 0) {
log_error("dfu_target_done failed: %d", err);
return 1;
} else {
log_info("DFU target download done!");
}
/* Indicate to DFUTarget that the update is ready
and reboot to boot into the updated app if successful */
if ((err = dfu_target_schedule_update(-1)) != 0) {
log_error("dfu_target_schedule_update failed: %d", err);
return 1;
} else {
log_info("DFU target successfully scheduled the update!");
sys_reboot(SYS_REBOOT_WARM);
}
return 0;
}