diff --git a/boot/zephyr/CMakeLists.txt b/boot/zephyr/CMakeLists.txt index cc900e3a9..cb86da085 100644 --- a/boot/zephyr/CMakeLists.txt +++ b/boot/zephyr/CMakeLists.txt @@ -428,6 +428,12 @@ if(CONFIG_BOOT_MAX_IMG_SECTORS_AUTO) endif() endif() +if(BUILD_RUNTIME_SOURCE_SAMPLE) + zephyr_library_sources( + ${MCUBOOT_DIR}/samples/runtime-source/zephyr/flash_map_dispatcher.c + ) +endif() + if(SYSBUILD) if(CONFIG_SINGLE_APPLICATION_SLOT OR CONFIG_BOOT_FIRMWARE_LOADER OR CONFIG_BOOT_SWAP_USING_SCRATCH OR CONFIG_BOOT_SWAP_USING_MOVE OR CONFIG_BOOT_UPGRADE_ONLY OR CONFIG_BOOT_DIRECT_XIP OR CONFIG_BOOT_RAM_LOAD) # TODO: RAM LOAD support diff --git a/boot/zephyr/Kconfig b/boot/zephyr/Kconfig index 864a47991..f09047964 100644 --- a/boot/zephyr/Kconfig +++ b/boot/zephyr/Kconfig @@ -332,6 +332,17 @@ config FLASH_RUNTIME_SOURCES and `flash_map_id_get_next` functions to tell mcuboot where to find the images. +if FLASH_MAP_CUSTOM_SOURCES +config AARDVARK_I2C_FLASH + bool "Use aardvark to emulate an I2C device from where a firmware image is read" + default n + depends on I2C + help + If you have an I2C device that acts as a flash memory, you can enable this + option to read the firmware image from it. + +endif + config BOOT_ENCRYPTION_SUPPORT bool help diff --git a/boot/zephyr/flash_map_extended.c b/boot/zephyr/flash_map_extended.c index c13d6adf0..ef11f34cd 100644 --- a/boot/zephyr/flash_map_extended.c +++ b/boot/zephyr/flash_map_extended.c @@ -14,6 +14,10 @@ #include #include +#if defined(CONFIG_FLASH_RUNTIME_SOURCES) +#include +#endif + #include "bootutil/bootutil_log.h" BOOT_LOG_MODULE_DECLARE(mcuboot); diff --git a/samples/runtime-source/zephyr/README.md b/samples/runtime-source/zephyr/README.md new file mode 100644 index 000000000..825858160 --- /dev/null +++ b/samples/runtime-source/zephyr/README.md @@ -0,0 +1,56 @@ +# Runtime chosen image sample application + +This sample demonstrates how to use a non flash storage to retrieve the image +being booted. It was tested on a FRDM K64F. Both slots are used to store two +different images. The image to be booted is selected based on a button press. + +## Build + +Build mcuboot. First, ensure ZEPHYR_SDK_INSTALL_DIR is defined. From the +mcuboot directory, run the following commands: + +``` + source /zephyr-env.sh + + west build -p -b frdm_k64f boot/zephyr/ -- -DBUILD_RUNTIME_SOURCE_SAMPLE=1 \ + -DEXTRA_CONF_FILE="../../samples/runtime-source/zephyr/sample.conf" + -DEXTRA_DTC_OVERLAY_FILE=../../samples/runtime-source/zephyr/boards/frdm_k64f.overlay + + west build -t flash +``` + +Then, build the sample application to be loaded. We need to build it twice, one +for each slot. From the sample +app directory (mcuboot/samples/non-flash-source/zephyr/app), run: + +``` + west build -p -b frdm_k64f . + west flash +``` + +Then change the overlay file to use the second slot. For instance, open +`boards/frdm_k64f.overlay` and change the line: + +``` + zephyr,code-partition = &slot0_partition; + +``` + +to: + +``` + zephyr,code-partition = &slot1_partition; +``` + +And build and flash again: + +``` + west build -b frdm_k64f . + west flash +``` + +## Run + +Open a serial terminal to see the output and reset the board. It shall boot the +image on slot0 by default. By keeping the SW2 button pressed during reset, the +bootloader will randomly select the image to be booted. diff --git a/samples/runtime-source/zephyr/app/CMakeLists.txt b/samples/runtime-source/zephyr/app/CMakeLists.txt new file mode 100644 index 000000000..bb60128f3 --- /dev/null +++ b/samples/runtime-source/zephyr/app/CMakeLists.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(non_flash_backend_app) + +if (NOT DEFINED FROM_WHO) + set(FROM_WHO Zephyr) +endif() + +target_compile_definitions(app PRIVATE "-DMCUBOOT_HELLO_WORLD_FROM=\"${FROM_WHO}\"") + +target_sources(app PRIVATE src/main.c) diff --git a/samples/runtime-source/zephyr/app/boards/frdm_k64f.overlay b/samples/runtime-source/zephyr/app/boards/frdm_k64f.overlay new file mode 100644 index 000000000..8b642c6b6 --- /dev/null +++ b/samples/runtime-source/zephyr/app/boards/frdm_k64f.overlay @@ -0,0 +1,5 @@ +/ { + chosen { + zephyr,code-partition = &slot0_partition; + }; +}; diff --git a/samples/runtime-source/zephyr/app/prj.conf b/samples/runtime-source/zephyr/app/prj.conf new file mode 100644 index 000000000..bf0ea6a28 --- /dev/null +++ b/samples/runtime-source/zephyr/app/prj.conf @@ -0,0 +1,3 @@ +CONFIG_BOOTLOADER_MCUBOOT=y + +CONFIG_MCUBOOT_SIGNATURE_KEY_FILE="./bootloader/mcuboot/root-rsa-2048.pem" diff --git a/samples/runtime-source/zephyr/app/scripts/aardvark_i2c_image.py b/samples/runtime-source/zephyr/app/scripts/aardvark_i2c_image.py new file mode 100644 index 000000000..0c75f554c --- /dev/null +++ b/samples/runtime-source/zephyr/app/scripts/aardvark_i2c_image.py @@ -0,0 +1,161 @@ +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import signal +import sys +import time + +from aardvark_py import * + +done = False +contents = None + + +def parse_args(): + global args + parser = argparse.ArgumentParser(description='Aardvark I2C "Storage Device"') + parser.add_argument("-p", "--port", default=0, help="Aardvark port number") + parser.add_argument("device_addr", type=str, help="I2C device address") + parser.add_argument("file", type=str, help="File to provide") + parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") + args = parser.parse_args() + + +def signal_handler(sig, frame): + global done + global contents + + if sig == signal.SIGINT: + done = True + elif sig == signal.SIGQUIT: + print("Reloading file") + with open(args.file, "rb") as f: + contents = f.read() + + +def watch(handle): + print("Watching I2C data (Use ^\\ to reload file) ...") + + # Loop until aa_async_poll times out + while True: + result = aa_async_poll(handle, 100) + # Read the I2C message. + if (result & AA_ASYNC_I2C_READ) == AA_ASYNC_I2C_READ: + # Get data written by master + (num_bytes, addr, data_in) = aa_i2c_slave_read(handle, 6) + + if num_bytes < 0: + print("slave_read error: %s" % aa_status_string(num_bytes)) + return + + if num_bytes == 0: + continue + + if data_in[0] == 0x1: + if args.verbose: + print( + f"Got 0x1, asking size of file. Responding with {len(contents)}" + ) + ret = aa_i2c_slave_set_response( + handle, + array( + "B", + [ + (len(contents) >> 24) & 0xFF, + (len(contents) >> 16) & 0xFF, + (len(contents) >> 8) & 0xFF, + len(contents) & 0xFF, + ], + ), + ) + elif data_in[0] == 0x2: + addr = ( + data_in[1] << 24 | data_in[2] << 16 | data_in[3] << 8 | data_in[4] + ) + size = data_in[5] + + if args.verbose: + print(f"Got 0x2, asking for data, at {addr} with size {size}.") + + if addr < 0 or addr + size > len(contents): + print("Requested data is out of bounds, responding with 0x0") + ret = aa_i2c_slave_set_response(handle, array("B", [0x0])) + else: + ret = aa_i2c_slave_set_response( + handle, array("B", contents[addr : addr + size]) + ) + else: + print("Got unknown data, responding with 0x0") + ret = aa_i2c_slave_set_response(handle, array("B", [0x0])) + + elif (result & AA_ASYNC_I2C_WRITE) == AA_ASYNC_I2C_WRITE: + # Get number of bytes written to master + num_bytes = aa_i2c_slave_write_stats(handle) + + if num_bytes < 0: + print("slave_write_stats error: %s" % aa_status_string(num_bytes)) + return + + # Print status information to the screen + if args.verbose: + print(f"Number of bytes written to master: {num_bytes:04}\n") + + elif result == AA_ASYNC_NO_DATA: + if done: + break + else: + print("error: non-I2C asynchronous message is pending", result) + return + + +def main(): + global contents + + parse_args() + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGQUIT, signal_handler) + + port = args.port + addr = int(args.device_addr, 0) + + # Open the device + handle = aa_open(port) + if handle <= 0: + print("Unable to open Aardvark device on port %d" % port) + print("Error code = %d" % handle) + sys.exit() + + # Ensure that the I2C subsystem is enabled + aa_configure(handle, AA_CONFIG_SPI_I2C) + + # Disable the I2C bus pullup resistors (2.2k resistors). + # This command is only effective on v2.0 hardware or greater. + # The pullup resistors on the v1.02 hardware are enabled by default. + aa_i2c_pullup(handle, AA_I2C_PULLUP_NONE) + + # Power the EEPROM using the Aardvark adapter's power supply. + # This command is only effective on v2.0 hardware or greater. + # The power pins on the v1.02 hardware are not enabled by default. + aa_target_power(handle, AA_TARGET_POWER_BOTH) + + # Set default response + aa_i2c_slave_set_response(handle, array("B", [0])) + + # Enabled the device + aa_i2c_slave_enable(handle, addr, 64, 6) + + # Read the file + with open(args.file, "rb") as f: + contents = f.read() + + # Watch the I2C port + watch(handle) + + # Disable and close the device + aa_i2c_slave_disable(handle) + aa_close(handle) + + +if __name__ == "__main__": + main() diff --git a/samples/runtime-source/zephyr/app/src/main.c b/samples/runtime-source/zephyr/app/src/main.c new file mode 100644 index 000000000..a3ebbdca3 --- /dev/null +++ b/samples/runtime-source/zephyr/app/src/main.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2017 Linaro, Ltd. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +int main(void) +{ + printk("Hello World from %s on %s, slot %s!\n", + MCUBOOT_HELLO_WORLD_FROM, CONFIG_BOARD, + DT_PROP(DT_CHOSEN(zephyr_code_partition), label)); +} diff --git a/samples/runtime-source/zephyr/flash_map_dispatcher.c b/samples/runtime-source/zephyr/flash_map_dispatcher.c new file mode 100644 index 000000000..63e1feb88 --- /dev/null +++ b/samples/runtime-source/zephyr/flash_map_dispatcher.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define SW1_NODE DT_ALIAS(sw1) +#if DT_NODE_HAS_STATUS(SW1_NODE, okay) +static struct gpio_dt_spec sw1_spec = GPIO_DT_SPEC_GET(SW1_NODE, gpios); +#endif + +static int curr_idx = -1; + +static uint8_t known_ids[] = { + FIXED_PARTITION_ID(slot0_partition), + FIXED_PARTITION_ID(slot1_partition), +}; + +bool +flash_map_id_get_next(uint8_t *id, bool reset) +{ + if (reset) { + curr_idx = 0; +#if DT_NODE_HAS_STATUS(SW1_NODE, okay) + if (gpio_pin_configure_dt(&sw1_spec, GPIO_INPUT) == 0) { + if (gpio_pin_get_dt(&sw1_spec) == 1) { + curr_idx = sys_rand8_get() % ARRAY_SIZE(known_ids); + printk("Booting from curr_idx = %d\n", curr_idx); + } + } +#endif + } else { + curr_idx++; + } + + if (curr_idx >= ARRAY_SIZE(known_ids)) { + return false; + } + + *id = known_ids[curr_idx]; + + return true; +} + +bool +flash_map_id_get_current(uint8_t *id) +{ + if (curr_idx == -1 || curr_idx >= ARRAY_SIZE(known_ids)) { + return false; + } + + *id = known_ids[curr_idx]; + + return true; +} diff --git a/samples/runtime-source/zephyr/sample.conf b/samples/runtime-source/zephyr/sample.conf new file mode 100644 index 000000000..3a6b9819c --- /dev/null +++ b/samples/runtime-source/zephyr/sample.conf @@ -0,0 +1,4 @@ +CONFIG_FLASH_RUNTIME_SOURCES=y +CONFIG_BOOT_SIGNATURE_TYPE_RSA=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_ENTROPY_GENERATOR=y