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>
diff --git a/boot/zephyr/CMakeLists.txt b/boot/zephyr/CMakeLists.txt
index 3ce4235..1356f4e 100644
--- a/boot/zephyr/CMakeLists.txt
+++ b/boot/zephyr/CMakeLists.txt
@@ -131,6 +131,11 @@
${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
diff --git a/boot/zephyr/Kconfig b/boot/zephyr/Kconfig
index 183bb50..a67126a 100644
--- a/boot/zephyr/Kconfig
+++ b/boot/zephyr/Kconfig
@@ -256,6 +256,18 @@
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 @@
rsource "Kconfig.serial_recovery"
+rsource "Kconfig.firmware_loader"
+
config BOOT_INTR_VEC_RELOC
bool "Relocate the interrupt vector to the application"
default n
diff --git a/boot/zephyr/Kconfig.firmware_loader b/boot/zephyr/Kconfig.firmware_loader
new file mode 100644
index 0000000..1ba2239
--- /dev/null
+++ b/boot/zephyr/Kconfig.firmware_loader
@@ -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
diff --git a/boot/zephyr/Kconfig.serial_recovery b/boot/zephyr/Kconfig.serial_recovery
index c73badd..74bced7 100644
--- a/boot/zephyr/Kconfig.serial_recovery
+++ b/boot/zephyr/Kconfig.serial_recovery
@@ -13,6 +13,7 @@
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.
diff --git a/boot/zephyr/firmware_loader.c b/boot/zephyr/firmware_loader.c
new file mode 100644
index 0000000..38b121c
--- /dev/null
+++ b/boot/zephyr/firmware_loader.c
@@ -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);
+}
diff --git a/boot/zephyr/include/mcuboot_config/mcuboot_config.h b/boot/zephyr/include/mcuboot_config/mcuboot_config.h
index 04e4c59..a9c52bd 100644
--- a/boot/zephyr/include/mcuboot_config/mcuboot_config.h
+++ b/boot/zephyr/include/mcuboot_config/mcuboot_config.h
@@ -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
diff --git a/boot/zephyr/io.c b/boot/zephyr/io.c
index 6d3b01e..fc1966d 100644
--- a/boot/zephyr/io.c
+++ b/boot/zephyr/io.c
@@ -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 @@
}
#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 @@
}
#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 @@
}
#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;