Understanding the S0 vs S1 MCUBoot images and slots

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;
}
5488.prj.conf

Parents
  • Oh and by the way, I just noticed this was a private case. FYI in the future this is a typical case that would've benefited from being a public case since it's a question that many other developers are asking as well :) 

    Nothing wrong with creating a private case, specially as your first one, but private cases are typically meant for cases where you share sensitive information with us w.r.t debugging and development

    Kind regards,
    Andreas

Reply
  • Oh and by the way, I just noticed this was a private case. FYI in the future this is a typical case that would've benefited from being a public case since it's a question that many other developers are asking as well :) 

    Nothing wrong with creating a private case, specially as your first one, but private cases are typically meant for cases where you share sensitive information with us w.r.t debugging and development

    Kind regards,
    Andreas

Children
No Data
Related