zephyr: Add firmware loader MCUboot operation style

Adds a new operation style in which the secondary slot has an
image which is used to update the primary image only.

Signed-off-by: Jamie McCrae <jamie.mccrae@nordicsemi.no>
This commit is contained in:
Jamie McCrae 2023-08-16 07:37:18 +01:00 committed by Jamie
parent 433b8480f7
commit 215345f76a
11 changed files with 294 additions and 10 deletions

View File

@ -122,7 +122,8 @@ enum mcuboot_mode {
MCUBOOT_MODE_SWAP_USING_MOVE,
MCUBOOT_MODE_DIRECT_XIP,
MCUBOOT_MODE_DIRECT_XIP_WITH_REVERT,
MCUBOOT_MODE_RAM_LOAD
MCUBOOT_MODE_RAM_LOAD,
MCUBOOT_MODE_FIRMWARE_LOADER
};
enum mcuboot_signature_type {

View File

@ -250,6 +250,8 @@ int boot_save_shared_data(const struct image_header *hdr, const struct flash_are
#endif
#elif defined(MCUBOOT_RAM_LOAD)
uint8_t mode = MCUBOOT_MODE_RAM_LOAD;
#elif defined(MCUBOOT_FIRMWARE_LOADER)
uint8_t mode = MCUBOOT_MODE_FIRMWARE_LOADER;
#else
#error "Unknown mcuboot operating mode"
#endif

View File

@ -333,7 +333,8 @@ boot_write_enc_key(const struct flash_area *fap, uint8_t slot,
uint32_t bootutil_max_image_size(const struct flash_area *fap)
{
#if defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SINGLE_APPLICATION_SLOT)
#if defined(MCUBOOT_SWAP_USING_SCRATCH) || defined(MCUBOOT_SINGLE_APPLICATION_SLOT) || \
defined(MCUBOOT_FIRMWARE_LOADER)
return boot_status_off(fap);
#elif defined(MCUBOOT_SWAP_USING_MOVE)
struct flash_sector sector;

View File

@ -57,15 +57,17 @@ struct flash_area;
#if (defined(MCUBOOT_OVERWRITE_ONLY) + \
defined(MCUBOOT_SWAP_USING_MOVE) + \
defined(MCUBOOT_DIRECT_XIP) + \
defined(MCUBOOT_RAM_LOAD)) > 1
#error "Please enable only one of MCUBOOT_OVERWRITE_ONLY, MCUBOOT_SWAP_USING_MOVE, MCUBOOT_DIRECT_XIP or MCUBOOT_RAM_LOAD"
defined(MCUBOOT_RAM_LOAD) + \
defined(MCUBOOT_FIRMWARE_LOADER)) > 1
#error "Please enable only one of MCUBOOT_OVERWRITE_ONLY, MCUBOOT_SWAP_USING_MOVE, MCUBOOT_DIRECT_XIP, MCUBOOT_RAM_LOAD or MCUBOOT_FIRMWARE_LOADER"
#endif
#if !defined(MCUBOOT_OVERWRITE_ONLY) && \
!defined(MCUBOOT_SWAP_USING_MOVE) && \
!defined(MCUBOOT_DIRECT_XIP) && \
!defined(MCUBOOT_RAM_LOAD) && \
!defined(MCUBOOT_SINGLE_APPLICATION_SLOT)
!defined(MCUBOOT_SINGLE_APPLICATION_SLOT) && \
!defined(MCUBOOT_FIRMWARE_LOADER)
#define MCUBOOT_SWAP_USING_SCRATCH 1
#endif

View File

@ -131,6 +131,11 @@ zephyr_library_sources(
${BOOT_DIR}/zephyr/single_loader.c
)
zephyr_library_include_directories(${BOOT_DIR}/bootutil/src)
elseif(CONFIG_BOOT_FIRMWARE_LOADER)
zephyr_library_sources(
${BOOT_DIR}/zephyr/firmware_loader.c
)
zephyr_library_include_directories(${BOOT_DIR}/bootutil/src)
else()
zephyr_library_sources(
${BOOT_DIR}/bootutil/src/loader.c

View File

@ -256,6 +256,18 @@ config BOOT_RAM_LOAD
The address that the image is copied to is specified using the load-addr
argument to the imgtool.py script which writes it to the image header.
config BOOT_FIRMWARE_LOADER
bool "Firmware loader"
help
If y, mcuboot will have a single application slot, and the secondary
slot will be for a non-upgradeable firmware loaded image (e.g. for
loading firmware via Bluetooth). The main application will boot by
default unless there is an error with it or the boot mode has been
forced to the firmware loader.
Note: The firmware loader image must be signed with the same signing
key as the primary image.
endchoice
# Workaround for not being able to have commas in macro arguments
@ -582,6 +594,8 @@ config MCUBOOT_INDICATION_LED
rsource "Kconfig.serial_recovery"
rsource "Kconfig.firmware_loader"
config BOOT_INTR_VEC_RELOC
bool "Relocate the interrupt vector to the application"
default n

View File

@ -0,0 +1,47 @@
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
if BOOT_FIRMWARE_LOADER
menu "Firmware loader entrance methods"
menuconfig BOOT_FIRMWARE_LOADER_ENTRANCE_GPIO
bool "GPIO"
depends on GPIO
help
Use a GPIO to enter firmware loader mode.
config BOOT_FIRMWARE_LOADER_DETECT_DELAY
int "Serial detect pin detection delay time [ms]"
default 0
depends on BOOT_FIRMWARE_LOADER_ENTRANCE_GPIO
help
Used to prevent the bootloader from loading on button press.
Useful for powering on when using the same button as
the one used to place the device in bootloader mode.
config BOOT_FIRMWARE_LOADER_BOOT_MODE
bool "Check boot mode via retention subsystem"
depends on RETENTION_BOOT_MODE
help
Allows for entering firmware loader mode by using Zephyr's boot mode
retention system (i.e. an application must set the boot mode to stay
in firmware loader mode and reboot the module).
config BOOT_FIRMWARE_LOADER_NO_APPLICATION
bool "Stay in bootloader if no application"
help
Allows for entering firmware loader mode if there is no bootable
application that the bootloader can jump to.
config BOOT_FIRMWARE_LOADER_PIN_RESET
bool "Check for device reset by pin"
select HWINFO
help
Checks if the module reset was caused by the reset pin and will
remain in bootloader firmware loader mode if it was.
endmenu
endif

View File

@ -13,6 +13,7 @@ menuconfig MCUBOOT_SERIAL
select BASE64
select CRC
select ZCBOR
depends on !BOOT_FIRMWARE_LOADER
help
If y, enables a serial-port based update mode. This allows
MCUboot itself to load update images into flash over a UART.

View File

@ -0,0 +1,194 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright (c) 2020 Arm Limited
* Copyright (c) 2020-2023 Nordic Semiconductor ASA
*/
#include <assert.h>
#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include "bootutil/image.h"
#include "bootutil_priv.h"
#include "bootutil/bootutil_log.h"
#include "bootutil/bootutil_public.h"
#include "bootutil/fault_injection_hardening.h"
#include "io/io.h"
#include "mcuboot_config/mcuboot_config.h"
BOOT_LOG_MODULE_DECLARE(mcuboot);
/* Variables passed outside of unit via poiters. */
static const struct flash_area *_fa_p;
static struct image_header _hdr = { 0 };
#if defined(MCUBOOT_VALIDATE_PRIMARY_SLOT) || defined(MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE)
/**
* Validate hash of a primary boot image.
*
* @param[in] fa_p flash area pointer
* @param[in] hdr boot image header pointer
*
* @return FIH_SUCCESS on success, error code otherwise
*/
fih_ret
boot_image_validate(const struct flash_area *fa_p,
struct image_header *hdr)
{
static uint8_t tmpbuf[BOOT_TMPBUF_SZ];
FIH_DECLARE(fih_rc, FIH_FAILURE);
/* NOTE: The first argument to boot_image_validate, for enc_state pointer,
* is allowed to be NULL only because the single image loader compiles
* with BOOT_IMAGE_NUMBER == 1, which excludes the code that uses
* the pointer from compilation.
*/
/* Validate hash */
if (IS_ENCRYPTED(hdr))
{
/* Clear the encrypted flag we didn't supply a key
* This flag could be set if there was a decryption in place
* was performed. We will try to validate the image, and if still
* encrypted the validation will fail, and go in panic mode
*/
hdr->ih_flags &= ~(ENCRYPTIONFLAGS);
}
FIH_CALL(bootutil_img_validate, fih_rc, NULL, 0, hdr, fa_p, tmpbuf,
BOOT_TMPBUF_SZ, NULL, 0, NULL);
FIH_RET(fih_rc);
}
#endif /* MCUBOOT_VALIDATE_PRIMARY_SLOT || MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE*/
inline static fih_ret
boot_image_validate_once(const struct flash_area *fa_p,
struct image_header *hdr)
{
static struct boot_swap_state state;
int rc;
FIH_DECLARE(fih_rc, FIH_FAILURE);
memset(&state, 0, sizeof(struct boot_swap_state));
rc = boot_read_swap_state(fa_p, &state);
if (rc != 0)
FIH_RET(FIH_FAILURE);
if (state.magic != BOOT_MAGIC_GOOD
|| state.image_ok != BOOT_FLAG_SET) {
/* At least validate the image once */
FIH_CALL(boot_image_validate, fih_rc, fa_p, hdr);
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
FIH_RET(FIH_FAILURE);
}
if (state.magic != BOOT_MAGIC_GOOD) {
rc = boot_write_magic(fa_p);
if (rc != 0)
FIH_RET(FIH_FAILURE);
}
rc = boot_write_image_ok(fa_p);
if (rc != 0)
FIH_RET(FIH_FAILURE);
}
FIH_RET(FIH_SUCCESS);
}
/**
* Validates that an image in a slot is OK to boot.
*
* @param[in] slot Slot number to check
* @param[out] rsp Parameters for booting image, on success
*
* @return FIH_SUCCESS on success; non-zero on failure.
*/
static fih_ret validate_image_slot(int slot, struct boot_rsp *rsp)
{
int rc = -1;
FIH_DECLARE(fih_rc, FIH_FAILURE);
rc = flash_area_open(slot, &_fa_p);
assert(rc == 0);
rc = boot_image_load_header(_fa_p, &_hdr);
if (rc != 0) {
goto other;
}
#ifdef MCUBOOT_VALIDATE_PRIMARY_SLOT
FIH_CALL(boot_image_validate, fih_rc, _fa_p, &_hdr);
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
goto other;
}
#elif defined(MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE)
FIH_CALL(boot_image_validate_once, fih_rc, _fa_p, &_hdr);
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
goto other;
}
#else
fih_rc = FIH_SUCCESS;
#endif /* MCUBOOT_VALIDATE_PRIMARY_SLOT */
rsp->br_flash_dev_id = flash_area_get_device_id(_fa_p);
rsp->br_image_off = flash_area_get_off(_fa_p);
rsp->br_hdr = &_hdr;
other:
flash_area_close(_fa_p);
FIH_RET(fih_rc);
}
/**
* Gather information on image and prepare for booting. Will boot from main
* image if none of the enabled entrance modes for the firmware loader are set,
* otherwise will boot the firmware loader. Note: firmware loader must be a
* valid signed image with the same signing key as the application image.
*
* @param[out] rsp Parameters for booting image, on success
*
* @return FIH_SUCCESS on success; non-zero on failure.
*/
fih_ret
boot_go(struct boot_rsp *rsp)
{
bool boot_firmware_loader = false;
FIH_DECLARE(fih_rc, FIH_FAILURE);
#ifdef CONFIG_BOOT_FIRMWARE_LOADER_ENTRANCE_GPIO
if (io_detect_pin() &&
!io_boot_skip_serial_recovery()) {
boot_firmware_loader = true;
}
#endif
#ifdef CONFIG_BOOT_FIRMWARE_LOADER_PIN_RESET
if (io_detect_pin_reset()) {
boot_firmware_loader = true;
}
#endif
#ifdef CONFIG_BOOT_FIRMWARE_LOADER_BOOT_MODE
if (io_detect_boot_mode()) {
boot_firmware_loader = true;
}
#endif
/* Check if firmware loader button is pressed. TODO: check all entrance methods */
if (boot_firmware_loader == true) {
FIH_CALL(validate_image_slot, fih_rc, FLASH_AREA_IMAGE_SECONDARY(0), rsp);
if (FIH_EQ(fih_rc, FIH_SUCCESS)) {
FIH_RET(fih_rc);
}
}
FIH_CALL(validate_image_slot, fih_rc, FLASH_AREA_IMAGE_PRIMARY(0), rsp);
#ifdef CONFIG_BOOT_FIRMWARE_LOADER_NO_APPLICATION
if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
FIH_CALL(validate_image_slot, fih_rc, FLASH_AREA_IMAGE_SECONDARY(0), rsp);
}
#endif
FIH_RET(fih_rc);
}

View File

@ -88,6 +88,10 @@
#define IMAGE_EXECUTABLE_RAM_SIZE CONFIG_BOOT_IMAGE_EXECUTABLE_RAM_SIZE
#endif
#ifdef CONFIG_BOOT_FIRMWARE_LOADER
#define MCUBOOT_FIRMWARE_LOADER
#endif
#ifdef CONFIG_UPDATEABLE_IMAGE_NUMBER
#define MCUBOOT_IMAGE_NUMBER CONFIG_UPDATEABLE_IMAGE_NUMBER
#else

View File

@ -29,11 +29,11 @@
#include "target.h"
#if defined(CONFIG_BOOT_SERIAL_PIN_RESET)
#if defined(CONFIG_BOOT_SERIAL_PIN_RESET) || defined(CONFIG_BOOT_FIRMWARE_LOADER_PIN_RESET)
#include <zephyr/drivers/hwinfo.h>
#endif
#if defined(CONFIG_BOOT_SERIAL_BOOT_MODE)
#if defined(CONFIG_BOOT_SERIAL_BOOT_MODE) || defined(CONFIG_BOOT_FIRMWARE_LOADER_BOOT_MODE)
#include <zephyr/retention/bootmode.h>
#endif
@ -48,6 +48,16 @@
#endif
#endif
/* Validate firmware loader configuration */
#ifdef CONFIG_BOOT_FIRMWARE_LOADER
#if !defined(CONFIG_BOOT_FIRMWARE_LOADER_ENTRANCE_GPIO) && \
!defined(CONFIG_BOOT_FIRMWARE_LOADER_BOOT_MODE) && \
!defined(CONFIG_BOOT_FIRMWARE_LOADER_NO_APPLICATION) && \
!defined(CONFIG_BOOT_FIRMWARE_LOADER_PIN_RESET)
#error "Firmware loader selected without an entrance mode set"
#endif
#endif
#ifdef CONFIG_MCUBOOT_INDICATION_LED
/*
@ -80,10 +90,13 @@ void io_led_init(void)
}
#endif /* CONFIG_MCUBOOT_INDICATION_LED */
#if defined(CONFIG_BOOT_SERIAL_ENTRANCE_GPIO) || defined(CONFIG_BOOT_USB_DFU_GPIO)
#if defined(CONFIG_BOOT_SERIAL_ENTRANCE_GPIO) || defined(CONFIG_BOOT_USB_DFU_GPIO) || \
defined(CONFIG_BOOT_FIRMWARE_LOADER_ENTRANCE_GPIO)
#if defined(CONFIG_MCUBOOT_SERIAL)
#define BUTTON_0_DETECT_DELAY CONFIG_BOOT_SERIAL_DETECT_DELAY
#elif defined(CONFIG_BOOT_FIRMWARE_LOADER)
#define BUTTON_0_DETECT_DELAY CONFIG_BOOT_FIRMWARE_LOADER_DETECT_DELAY
#else
#define BUTTON_0_DETECT_DELAY CONFIG_BOOT_USB_DFU_DETECT_DELAY
#endif
@ -152,7 +165,7 @@ bool io_detect_pin(void)
}
#endif
#if defined(CONFIG_BOOT_SERIAL_PIN_RESET)
#if defined(CONFIG_BOOT_SERIAL_PIN_RESET) || defined(CONFIG_BOOT_FIRMWARE_LOADER_PIN_RESET)
bool io_detect_pin_reset(void)
{
uint32_t reset_cause;
@ -169,7 +182,7 @@ bool io_detect_pin_reset(void)
}
#endif
#if defined(CONFIG_BOOT_SERIAL_BOOT_MODE)
#if defined(CONFIG_BOOT_SERIAL_BOOT_MODE) || defined(CONFIG_BOOT_FIRMWARE_LOADER_BOOT_MODE)
bool io_detect_boot_mode(void)
{
int32_t boot_mode;