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
  • Hi,

    Glad to hear that it was helpful!

    believe that the answer to your follow up question is answered by the DFU/FOTA lesson on our academy pages https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-8-bootloaders-and-dfu-fota/, but I don't remember the contents from cover to cover, so I'll chime in with some additional comments as well:

    Dax-id said:
    • Have the upgrade code residing in the Application code, which would run only once per boot when the app is booted.
    • Have the upgrade code residing in the second stage bootloader (MCUBoot), also ran only once per boot. Is it even possible to "add" some processing to MCUBoot without modifying the SDK to much? I read about the MCUBoot hooks and will have to dig a bit here, but is it planned to do such operations here?

    My assumption is that here you refer to the "upgrade code" as either the code to initiate DFU and perform it or "the new update image". I'm going to assume it is the first one, so correct me if I'm wrong and I'll explain for the other case as well

    This is explained in https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-8-bootloaders-and-dfu-fota/topic/device-firmware-update-dfu-essentials/ where it is stated that you can choose either to have the upgrade code initiating the update in the bootloader or in the application. In either case you will have to have some sort of way to enter the bootloader mode, for instance with a button press, power cycle where you hold a button for certain seconds when you power on or through a BLE SMP server initiating the DFU mode.

    I think for now the course I linked is the best entry point since it has explanations, graphics crosslinks to our official documentation as well as hands on exercises for you to have a look at that are more minimal than the samples in the SDK which supports DFU

    Why nothing happens when sending s0 image when the device currently uses s0 as the bootloader slot?

    I also noticed this question in your description. This is implied in my previous description but I'll state it explicetly as well: An new image will not be "accepted" as a new image by the bootloader to run from/swap into, both for the case of application image and second stage bootloader image, unless there has been made changes to the firmware (it can be as little as a log/print statement modification or simply changing the fw version number) due to a checksum comparison. 

    Let me know if this answers your questions

    Kind regards,
    Andreas

Reply
  • Hi,

    Glad to hear that it was helpful!

    believe that the answer to your follow up question is answered by the DFU/FOTA lesson on our academy pages https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-8-bootloaders-and-dfu-fota/, but I don't remember the contents from cover to cover, so I'll chime in with some additional comments as well:

    Dax-id said:
    • Have the upgrade code residing in the Application code, which would run only once per boot when the app is booted.
    • Have the upgrade code residing in the second stage bootloader (MCUBoot), also ran only once per boot. Is it even possible to "add" some processing to MCUBoot without modifying the SDK to much? I read about the MCUBoot hooks and will have to dig a bit here, but is it planned to do such operations here?

    My assumption is that here you refer to the "upgrade code" as either the code to initiate DFU and perform it or "the new update image". I'm going to assume it is the first one, so correct me if I'm wrong and I'll explain for the other case as well

    This is explained in https://academy.nordicsemi.com/courses/nrf-connect-sdk-intermediate/lessons/lesson-8-bootloaders-and-dfu-fota/topic/device-firmware-update-dfu-essentials/ where it is stated that you can choose either to have the upgrade code initiating the update in the bootloader or in the application. In either case you will have to have some sort of way to enter the bootloader mode, for instance with a button press, power cycle where you hold a button for certain seconds when you power on or through a BLE SMP server initiating the DFU mode.

    I think for now the course I linked is the best entry point since it has explanations, graphics crosslinks to our official documentation as well as hands on exercises for you to have a look at that are more minimal than the samples in the SDK which supports DFU

    Why nothing happens when sending s0 image when the device currently uses s0 as the bootloader slot?

    I also noticed this question in your description. This is implied in my previous description but I'll state it explicetly as well: An new image will not be "accepted" as a new image by the bootloader to run from/swap into, both for the case of application image and second stage bootloader image, unless there has been made changes to the firmware (it can be as little as a log/print statement modification or simply changing the fw version number) due to a checksum comparison. 

    Let me know if this answers your questions

    Kind regards,
    Andreas

Children
No Data
Related