From 2df0abfd7006ea2595b1849d6a362a250ecce491 Mon Sep 17 00:00:00 2001 From: Francisco Acosta Date: Tue, 9 Oct 2018 15:35:18 +0200 Subject: [PATCH 1/8] sys/riotboot: add riotboot_slot module This module adds some helper function to manage slots created with a riotboot_hdr header. Co-authored-by: Kaspar Schleiser --- Makefile.dep | 4 ++ sys/include/riotboot/slot.h | 109 ++++++++++++++++++++++++++++++++++++ sys/riotboot/Makefile | 4 ++ sys/riotboot/slot.c | 86 ++++++++++++++++++++++++++++ 4 files changed, 203 insertions(+) create mode 100644 sys/include/riotboot/slot.h create mode 100644 sys/riotboot/slot.c diff --git a/Makefile.dep b/Makefile.dep index 4a811c91a31f..d01275961d69 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -808,6 +808,10 @@ ifneq (,$(filter periph_gpio_irq,$(USEMODULE))) FEATURES_REQUIRED += periph_gpio endif +ifneq (,$(filter riotboot_slot, $(USEMODULE))) + USEMODULE += riotboot_hdr +endif + # always select gpio (until explicit dependencies are sorted out) FEATURES_OPTIONAL += periph_gpio diff --git a/sys/include/riotboot/slot.h b/sys/include/riotboot/slot.h new file mode 100644 index 000000000000..24d65e32da17 --- /dev/null +++ b/sys/include/riotboot/slot.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 Kaspar Schleiser + * Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_riotboot_slot Helpers to manipulate partitions (slots) + * on internal flash + * @ingroup sys + * @{ + * + * @file + * @brief Slot management tools + * + * @author Kaspar Schleiser + * @author Francisco Acosta + * + * @} + */ + +#ifndef RIOTBOOT_SLOT_H +#define RIOTBOOT_SLOT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "riotboot/hdr.h" + +/** + * @brief Get currently running image slot + * + * @returns nr of currently active slot + */ +int riotboot_slot_current(void); + +/** + * @brief Get jump-to address of image slot + * + * @param[in] slot slot nr to work on + * + * @returns address of first byte of @p slot + */ +uint32_t riotboot_slot_get_image_startaddr(unsigned slot); + +/** + * @brief Boot into image in slot @p slot + * + * @param[in] slot slot nr to jump to + */ +void riotboot_slot_jump(unsigned slot); + +/** + * @brief Get header from a given flash slot + * + * @param[in] slot slot nr to work on + * + * @returns header of image slot nr @p slot + */ +const riotboot_hdr_t *riotboot_slot_get_hdr(unsigned slot); + +/** + * @brief Validate slot + * + * @param[in] slot slot nr to work on + * + * @returns 0 if ok. + */ +static inline int riotboot_slot_validate(unsigned slot) +{ + return riotboot_hdr_validate(riotboot_slot_get_hdr(slot)); +} + +/** + * @brief Print formatted slot header to STDIO + * + * @param[in] slot slot nr to work on + * + */ +static inline void riotboot_slot_print_hdr(unsigned slot) +{ + riotboot_hdr_print(riotboot_slot_get_hdr(slot)); +} + +/** + * @brief Dump the addresses of all configured slots + * + */ +void riotboot_slot_dump_addrs(void); + +/** + * @brief Number of configured firmware slots (incl. bootloader slot) + */ +extern const unsigned riotboot_slot_numof; + +/** + * @brief Storage for header pointers of the configured slots + */ +extern const riotboot_hdr_t * const riotboot_slots[]; + +#ifdef __cplusplus +} +#endif + +#endif /* RIOTBOOT_SLOT_H */ diff --git a/sys/riotboot/Makefile b/sys/riotboot/Makefile index cd1af2456e05..344672d2e216 100644 --- a/sys/riotboot/Makefile +++ b/sys/riotboot/Makefile @@ -1,3 +1,7 @@ SUBMODULES := 1 +ifneq (,$(filter riotboot_slot,$(USEMODULE))) + CFLAGS += -DSLOT0_OFFSET=$(SLOT0_OFFSET) +endif + include $(RIOTBASE)/Makefile.base diff --git a/sys/riotboot/slot.c b/sys/riotboot/slot.c new file mode 100644 index 000000000000..09cac62a789b --- /dev/null +++ b/sys/riotboot/slot.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_riotboot_slot + * @{ + * + * @file + * @brief Slot management functions + * + * @author Kaspar Schleiser + * @author Francisco Acosta + * + * @} + */ +#include + +#include "cpu.h" +#include "riotboot/slot.h" +#include "riotboot/hdr.h" + +/* + * Store the start addresses of each slot. + * Take into account that CPU_FLASH_BASE represents the starting + * address of the bootloader, thus the header is located after the + * space reserved to the bootloader. + */ +const riotboot_hdr_t * const riotboot_slots[] = { + (riotboot_hdr_t*)(CPU_FLASH_BASE + SLOT0_OFFSET), /* First slot address -> firmware image */ +}; + +/* Calculate the number of slots */ +const unsigned riotboot_slot_numof = sizeof(riotboot_slots) / sizeof(riotboot_hdr_t*); + +static void _riotboot_slot_jump_to_image(const riotboot_hdr_t *hdr) +{ + cpu_jump_to_image(hdr->start_addr); +} + +int riotboot_slot_current(void) +{ + uint32_t base_addr = cpu_get_image_baseaddr(); + + for (unsigned i = 0; i < riotboot_slot_numof; i++) { + const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(i); + if (base_addr == hdr->start_addr) { + return i; + } + } + + return -1; +} + +void riotboot_slot_jump(unsigned slot) +{ + _riotboot_slot_jump_to_image(riotboot_slot_get_hdr(slot)); +} + +uint32_t riotboot_slot_get_image_startaddr(unsigned slot) +{ + return riotboot_slot_get_hdr(slot)->start_addr; +} + +void riotboot_slot_dump_addrs(void) +{ + for (unsigned slot = 0; slot < riotboot_slot_numof; slot++) { + const riotboot_hdr_t *hdr = riotboot_slot_get_hdr(slot); + + printf("slot %u: metadata: %p image: 0x%08" PRIx32 "\n", slot, + hdr, + hdr->start_addr); + } +} + +const riotboot_hdr_t *riotboot_slot_get_hdr(unsigned slot) +{ + assert(slot < riotboot_slot_numof); + + return riotboot_slots[slot]; +} From a638f31bce114ea7a97fe97a569783458df92971 Mon Sep 17 00:00:00 2001 From: Francisco Acosta Date: Tue, 25 Sep 2018 01:59:12 +0200 Subject: [PATCH 2/8] dist/tools: add genhdr tool to generate RIOT image headers In order to use the RIOT bootloader (riotboot) a header needs to be created and placed before the firmware. This tool generates such a header with the expected information by the bootloader. Co-authored-by: Kaspar Schleiser --- dist/tools/riotboot_gen_hdr/Makefile | 39 +++++++++ dist/tools/riotboot_gen_hdr/common.c | 39 +++++++++ dist/tools/riotboot_gen_hdr/common.h | 31 ++++++++ dist/tools/riotboot_gen_hdr/genhdr.c | 114 +++++++++++++++++++++++++++ dist/tools/riotboot_gen_hdr/main.c | 36 +++++++++ 5 files changed, 259 insertions(+) create mode 100644 dist/tools/riotboot_gen_hdr/Makefile create mode 100644 dist/tools/riotboot_gen_hdr/common.c create mode 100644 dist/tools/riotboot_gen_hdr/common.h create mode 100644 dist/tools/riotboot_gen_hdr/genhdr.c create mode 100644 dist/tools/riotboot_gen_hdr/main.c diff --git a/dist/tools/riotboot_gen_hdr/Makefile b/dist/tools/riotboot_gen_hdr/Makefile new file mode 100644 index 000000000000..dd57e45f2710 --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/Makefile @@ -0,0 +1,39 @@ +RIOTBASE := ../../.. +RIOT_INCLUDE := $(RIOTBASE)/sys/include +RIOT_CORE_INC := $(RIOTBASE)/core/include +NATIVE_INCLUDE := $(RIOTBASE)/cpu/native/include +COMMON_SRC := common.c +COMMON_HDR := common.h + +RIOT_HDR_SRC := \ + $(RIOTBASE)/sys/checksum/fletcher32.c \ + $(RIOTBASE)/sys/riotboot/hdr.c + +RIOT_HDR_HDR := $(RIOT_INCLUDE)/riotboot/hdr.h \ + $(RIOT_INCLUDE)/checksum/fletcher32.h \ + $(RIOTBASE)/core/include/byteorder.h + +GENHDR_SRC := $(COMMON_SRC) $(RIOT_HDR_SRC) \ + main.c genhdr.c + +GENHDR_HDR := $(COMMON_HDR) $(RIOT_HDR_HDR) + +CFLAGS += -g -I. -O3 -Wall -Wextra -pedantic -std=c99 + +ifeq ($(QUIET),1) + Q=@ +else + Q= +endif + +all: bin/genhdr + +bin/: + $(Q)mkdir -p bin + +bin/genhdr: $(GENHDR_HDR) $(GENHDR_SRC) Makefile | bin/ + $(Q)$(CC) $(CFLAGS) -I$(RIOT_INCLUDE) -I$(RIOT_CORE_INC) \ + -I$(NATIVE_INCLUDE) $(GENHDR_SRC) -o $@ + +clean: + $(Q)rm -rf bin/ diff --git a/dist/tools/riotboot_gen_hdr/common.c b/dist/tools/riotboot_gen_hdr/common.c new file mode 100644 index 000000000000..c3cb771b6a15 --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/common.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +/** + * @file + * @brief Common tools for RIOT images header generation + * + * @author Kaspar Schleiser + */ + +#include +#include +#include +#include + +int to_file(const char *filename, void *buf, size_t len) +{ + int fd; + + if (strcmp("-", filename)) { + fd = open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + } + else { + fd = STDOUT_FILENO; + } + + if (fd > 0) { + ssize_t res = write(fd, buf, len); + close(fd); + return res == (ssize_t)len; + } + else { + return fd; + } +} diff --git a/dist/tools/riotboot_gen_hdr/common.h b/dist/tools/riotboot_gen_hdr/common.h new file mode 100644 index 000000000000..f3380d8ffb4b --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/common.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +#ifndef COMMON_H +#define COMMON_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/** + * @brief Write len bytes of a given buffer into a file + * + * @param[out] filename name of the file to be written + * @param[in] buf a pointer to the buffer which needs to be written + * @param[in] len the number of bytes from buf to be written + * + */ +int to_file(const char *filename, void *buf, size_t len); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* COMMON_H */ diff --git a/dist/tools/riotboot_gen_hdr/genhdr.c b/dist/tools/riotboot_gen_hdr/genhdr.c new file mode 100644 index 000000000000..f8559d589fdd --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/genhdr.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016 Inria + * 2017 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + * + */ + +/** + * @author Francisco Acosta + * @author Kaspar Schleiser + */ + +#include +#include +#include +#include +#include +#include + +#include "riotboot/hdr.h" +#include "common.h" + +/** + * @brief Alignment required to set VTOR in Cortex-M0+/3/4/7 + */ +#define HDR_ALIGN (256) + +static void populate_hdr(riotboot_hdr_t *hdr, uint32_t ver, uint32_t addr) +{ + /* ensure the buffer and header have 0's */ + memset(hdr, '\0', sizeof(riotboot_hdr_t)); + + /* Generate image header */ + hdr->magic_number = RIOTBOOT_MAGIC; + hdr->version = ver; + hdr->start_addr = addr; + + /* calculate header checksum */ + hdr->chksum = riotboot_hdr_checksum(hdr); +} + +int genhdr(int argc, char *argv[]) +{ + const char generate_usage[] = " "; + + /* riotboot_hdr buffer */ + uint8_t *hdr_buf; + + /* arguments storage variables */ + long app_ver_arg = 0; + long start_addr_arg = 0; + long hdr_len_arg = 0; + + /* header variables */ + size_t hdr_len = 0; + uint32_t app_ver = 0; + uint32_t start_addr = 0; + + /* helpers */ + errno = 0; + char *p; + + if (argc < 6) { + fprintf(stderr, "usage: genhdr generate %s\n", generate_usage); + return -1; + } + + app_ver_arg = strtol(argv[2], &p, 0); + if (errno != 0 || *p != '\0' || app_ver_arg > UINT32_MAX) { + fprintf(stderr, "Error: APP_VER not valid!\n"); + } + else { + app_ver = app_ver_arg; + } + + start_addr_arg = strtol(argv[3], &p, 0); + if (errno != 0 || *p != '\0' || start_addr_arg > UINT32_MAX) { + fprintf(stderr, "Error: START_ADDR not valid!\n"); + } + else { + start_addr = start_addr_arg; + } + + hdr_len_arg = strtol(argv[4], &p, 0); + if (errno != 0 || *p != '\0' || hdr_len_arg % HDR_ALIGN || hdr_len_arg > UINT32_MAX) { + fprintf(stderr, "Error: HDR_LEN not valid!\n"); + return -1; + } + else { + hdr_len = hdr_len_arg; + } + + /* prepare a 0 initialised buffer for riotboot_hdr_t */ + hdr_buf = calloc(1, hdr_len); + if (hdr_buf == NULL) { + fprintf(stderr, "Error: not enough memory!\n"); + return -1; + } + + populate_hdr((riotboot_hdr_t*)hdr_buf, app_ver, start_addr); + + /* Write the header */ + if (!to_file(argv[5], hdr_buf, hdr_len)) { + fprintf(stderr, "Error: cannot write output\n"); + free(hdr_buf); + return 1; + } + + free(hdr_buf); + + return 0; +} diff --git a/dist/tools/riotboot_gen_hdr/main.c b/dist/tools/riotboot_gen_hdr/main.c new file mode 100644 index 000000000000..6288fa29edda --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/main.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * 2018 Inria + * + * This file is subject to the terms and conditions of the GNU General Public + * License v2. See the file LICENSE for more details. + */ + +/** + * @file + * @brief Header generation tool for RIOT firmware images + * + * @author Francisco Acosta + * @author Kaspar Schleiser + */ + +#include +#include + +int genhdr(int argc, char *argv[]); + +int main(int argc, char *argv[]) +{ + char *usage = "genhdr generate [args]"; + + if (argc < 2) { + goto usage; + } + else if (!strcmp(argv[1], "generate")) { + return genhdr(argc - 1, &argv[1]); + } + +usage: + fprintf(stderr, "usage: %s\n", usage); + return 1; +} From 281d8084bffca8b2b7f01556be44880da1087e1c Mon Sep 17 00:00:00 2001 From: Francisco Acosta Date: Tue, 25 Sep 2018 01:15:04 +0200 Subject: [PATCH 3/8] riotboot: add riot-based minimal bootloader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit riotboot is introduced here and makes use of riotboot_hdr, which indentifies the images encapsulated as slots. The slot size and offset is configurable, which makes slots extendable if needed, e.g. 2 or more slots can be transparently added. Co-authored-by: Kaspar Schleiser Co-authored-by: Gaëtan Harter --- Makefile.include | 5 ++ bootloaders/riotboot/Makefile | 25 +++++++ bootloaders/riotboot/README.md | 75 ++++++++++++++++++++ bootloaders/riotboot/main.c | 46 +++++++++++++ makefiles/boot/riotboot.mk | 122 +++++++++++++++++++++++++++++++++ 5 files changed, 273 insertions(+) create mode 100644 bootloaders/riotboot/Makefile create mode 100644 bootloaders/riotboot/README.md create mode 100644 bootloaders/riotboot/main.c create mode 100644 makefiles/boot/riotboot.mk diff --git a/Makefile.include b/Makefile.include index 93a9958cd8e2..d85ce551da7d 100644 --- a/Makefile.include +++ b/Makefile.include @@ -434,6 +434,11 @@ else _LINK = $(if $(CPPMIX),$(LINKXX),$(LINK)) $(UNDEF) $(LINKFLAGPREFIX)--start-group $(BASELIBS) -lm $(LINKFLAGPREFIX)--end-group $(LINKFLAGS) $(LINKFLAGPREFIX)-Map=$(BINDIR)/$(APPLICATION).map endif # BUILDOSXNATIVE +# include bootloaders support. When trying to overwrite one variable +# like HEXFILE, the value will already have been evaluated when declaring +# the link target. Therefore, it must be placed before `link`. +include $(RIOTMAKE)/boot/riotboot.mk + ifeq ($(BUILD_IN_DOCKER),1) link: ..in-docker-container else diff --git a/bootloaders/riotboot/Makefile b/bootloaders/riotboot/Makefile new file mode 100644 index 000000000000..b8398cc24a41 --- /dev/null +++ b/bootloaders/riotboot/Makefile @@ -0,0 +1,25 @@ +# Default RIOT bootloader +APPLICATION = riotboot + +# Default testing board +BOARD ?= samr21-xpro + +# Select the boards with riotboot feature +FEATURES_REQUIRED += riotboot + +# Disable unused modules +CFLAGS += -DNDEBUG -DLOG_LEVEL=LOG_NONE +DISABLE_MODULE += auto_init + +# Include riotboot flash partition functionality +USEMODULE += riotboot_slot + +# RIOT codebase +RIOTBASE ?= $(CURDIR)/../../ + +include $(RIOTBASE)/Makefile.include + +# limit riotboot bootloader size +# TODO: Manage to set this variable for boards which already embed a +# bootloader, currently it will be overwritten +ROM_LEN := $(RIOTBOOT_LEN) diff --git a/bootloaders/riotboot/README.md b/bootloaders/riotboot/README.md new file mode 100644 index 000000000000..90af49a94464 --- /dev/null +++ b/bootloaders/riotboot/README.md @@ -0,0 +1,75 @@ +# Overview +This folder contains a simple bootloader called "riotboot". +A header with metadata of length `RIOTBOOT_HDR_LEN` precedes +the RIOT firmware. The header contains "RIOT" as a magic +number to recognise a RIOT firmware image, a checksum, and +the version of the RIOT firmware `APP_VER`. +This bootloader verifies the checksum of the header which is located +at an offset (`ROM_OFFSET`) with respect to the `ROM_START_ADDR` +defined by the CPU, just after the space allocated for riotboot. + +riotboot consists of: + + - This application which serves as minimal bootloader, + - the module "riotboot_hdr" used to recognise RIOT firmware which riotboot + can boot, + - the module "riotboot_slot" used to manage the partitions (slots) with a + RIOT header attached to them, + - a tool in dist/tools/riotboot_gen_hdr for header generation, + - several make targets to glue everything together. + +## Concept +`riotboot` expects the flash to be formatted in slots: at CPU_FLASH_BASE +address resides the bootloader, which is followed by a slot 0 with a +RIOT firmware. + +A RIOT firmware in a single slot is composed by: + +``` +|------------------------------- FLASH -------------------------------------| +|----- RIOTBOOT_LEN ----|----------- RIOTBOOT_SLOT_SIZE (slot 0) -----------| + |----- RIOTBOOT_HDR_LEN ------| + --------------------------------------------------------------------------- +| riotboot | riotboot_hdr_t + filler (0) | RIOT firmware | + --------------------------------------------------------------------------- +``` + +Please note that `RIOTBOOT_HDR_LEN` depends on the architecture of the +MCU, since it needs to be aligned to 256B. This is fixed regardless of +`sizeof(riotboot_hdr_t)` + +The bootloader will, on reset, verify the checksum of the first slot header, +then boot it. If the slot doesn't have a valid checksum, no image will be +booted and the bootloader will enter `while(1);` endless loop. + +# Requirements +A board capable to use riotboot must meet the following requirements: + + - Embed a Cortex-M0+/3/4/7 processor + - Provide the variables `ROM_START_ADDR` and `ROM_LEN` + - Use cpu/cortexm_common/ldscripts/cortexm.ld ld script + - Pass the cortexm_common_ldscript test in tests/ + - Being able to execute startup code at least twice (`board_init()`) + - Declare `FEATURES_PROVIDED += riotboot` to pull the rigth dependencies + - Being able to flash binary files, if integration with the build + system is required for flashing + +The above requirements are usually met if the board succeeds to execute +the riotboot test in tests/. + +# Usage +Just compile your application using the target `riotboot`. The header +is generated automatically according to your `APP_VER`, which can be +optionally set (0 by default) in your makefile. + +## Flashing +The image can be flashed using `riotboot/flash` which also flashes +the bootloader. + +e.g. `BOARD=samr21-xpro FEATURES_REQUIRED+=riotboot APP_VER=$(date +%s) make -C examples/hello-world riotboot/flash` + +The command compiles both the hello-world example and riotboot, +generates the header and attaches it at the beginning of the example +binary. + +A comprehensive test is available at tests/riotboot. diff --git a/bootloaders/riotboot/main.c b/bootloaders/riotboot/main.c new file mode 100644 index 000000000000..ce6914296c7f --- /dev/null +++ b/bootloaders/riotboot/main.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser + * Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup bootloaders RIOT compatible bootloaders + * @ingroup bootloaders + * @{ + * + * @file + * @brief Minimal riot-based bootloader + * + * @author Kaspar Schleiser + * @author Francisco Acosta + * + * @} + */ + +#include "cpu.h" +#include "panic.h" +#include "riotboot/slot.h" + +void kernel_init(void) +{ + /* bootloader boots only slot 0 if it is valid */ + unsigned slot = 0; + + if (riotboot_slot_validate(slot) == 0) { + riotboot_slot_jump(slot); + } + + /* serious trouble! */ + while (1) {} +} + +NORETURN void core_panic(core_panic_t crash_code, const char *message) +{ + (void)crash_code; + (void)message; + while (1) {} +} diff --git a/makefiles/boot/riotboot.mk b/makefiles/boot/riotboot.mk new file mode 100644 index 000000000000..ab0c6e63fa3b --- /dev/null +++ b/makefiles/boot/riotboot.mk @@ -0,0 +1,122 @@ +ifneq (,$(filter riotboot,$(FEATURES_USED))) + +.PHONY: riotboot/flash riotboot/flash-bootloader riotboot/flash-slot0 riotboot/bootloader/% + +RIOTBOOT_DIR = $(RIOTBASE)/bootloaders/riotboot +RIOTBOOT ?= $(RIOTBOOT_DIR)/bin/$(BOARD)/riotboot.elf +CFLAGS += -I$(BINDIR)/riotbuild + +HEADER_TOOL_DIR = $(RIOTBASE)/dist/tools/riotboot_gen_hdr +HEADER_TOOL ?= $(HEADER_TOOL_DIR)/bin/genhdr +BINDIR_APP = $(BINDIR)/$(APPLICATION) + +# Indicate the reserved space for a header, 256B by default +# Notice that it must be 256B aligned. This is restricted by +# the Cortex-M0+/3/4/7 architecture +RIOTBOOT_HDR_LEN ?= 0x100 + +# Export variables for 'riotboot_slot' +export SLOT0_LEN + +# By default, slot 0 is found just after RIOTBOOT_LEN. It might +# be overridden to add more offset as needed. +export SLOT0_OFFSET ?= $(RIOTBOOT_LEN) + +# Mandatory APP_VER, set to 0 by default +APP_VER ?= 0 + +# Final target for slot 0 with riot_hdr +SLOT0_RIOT_BIN = $(BINDIR_APP)-slot0.riot.bin + +# For slot generation only link is needed +$(BINDIR_APP)-%.elf: link + $(Q)$(_LINK) -o $@ + +# Slot 0 firmware offset, after header +SLOT0_IMAGE_OFFSET := $$(($(RIOTBOOT_LEN) + $(RIOTBOOT_HDR_LEN))) + +# Link slots ELF *after* riot_hdr and limit the ROM to the slots length +$(BINDIR_APP)-slot0.elf: FW_ROM_LEN=$$((SLOT0_LEN - $(RIOTBOOT_HDR_LEN))) +$(BINDIR_APP)-slot0.elf: ROM_OFFSET=$(SLOT0_IMAGE_OFFSET) + +# Create binary target with RIOT header +$(SLOT0_RIOT_BIN): %.riot.bin: %.hdr %.bin + @echo "creating $@..." + $(Q)cat $^ > $@ + +# Compile header tool if it doesn't exist, force its compilation in case +# some files changed +$(HEADER_TOOL): FORCE + @echo "compiling $@..." + $(Q)/usr/bin/env -i \ + QUIET=$(QUIET) \ + PATH=$(PATH) \ + $(MAKE) --no-print-directory -C $(HEADER_TOOL_DIR) all + +# Generate RIOT header and keep the original binary file +# It must be always regenerated in case of any changes, so FORCE +.PRECIOUS: %.bin +%.hdr: $(HEADER_TOOL) %.bin FORCE + $(Q)$(HEADER_TOOL) generate $< $(APP_VER) $$(($(ROM_START_ADDR)+$(OFFSET))) $(RIOTBOOT_HDR_LEN) - > $@ + +$(BINDIR_APP)-slot0.hdr: OFFSET=$(SLOT0_IMAGE_OFFSET) + +# Generic target to create a binary file from the image with header +riotboot: $(SLOT0_RIOT_BIN) + +# riotboot bootloader compile target +riotboot/flash-bootloader: riotboot/bootloader/flash +riotboot/bootloader/%: + $(Q)/usr/bin/env -i \ + QUIET=$(QUIET)\ + PATH=$(PATH) BOARD=$(BOARD) \ + $(MAKE) --no-print-directory -C $(RIOTBOOT_DIR) $* + +# Generate a binary file from the bootloader which fills all the +# allocated space. This is used afterwards to create a combined +# binary with both bootloader and RIOT firmware with header +BOOTLOADER_BIN = $(RIOTBOOT_DIR)/bin/$(BOARD) +$(BOOTLOADER_BIN)/riotboot.extended.bin: $(BOOTLOADER_BIN)/riotboot.bin + $(Q)cp $^ $@.tmp + $(Q)truncate -s $$(($(RIOTBOOT_LEN))) $@.tmp + $(Q)mv $@.tmp $@ + +# Only call sub make if not already in riotboot +ifneq ($(BOOTLOADER_BIN)/riotboot.bin,$(BINFILE)) + $(BOOTLOADER_BIN)/riotboot.bin: riotboot/bootloader/binfile +endif + +# Create combined binary booloader + RIOT firmware with header +RIOTBOOT_COMBINED_BIN = $(BINDIR_APP)-slot0-combined.bin +riotboot/combined-slot0: $(RIOTBOOT_COMBINED_BIN) +$(RIOTBOOT_COMBINED_BIN): $(BOOTLOADER_BIN)/riotboot.extended.bin $(SLOT0_RIOT_BIN) + $(Q)cat $^ > $@ + +# Flashing rule for edbg to flash combined binaries +riotboot/flash-combined-slot0: HEXFILE=$(RIOTBOOT_COMBINED_BIN) +# Flashing rule for openocd to flash combined binaries +riotboot/flash-combined-slot0: ELFFILE=$(RIOTBOOT_COMBINED_BIN) +riotboot/flash-combined-slot0: $(RIOTBOOT_COMBINED_BIN) $(FLASHDEPS) + $(FLASHER) $(FFLAGS) + +# Flashing rule for slot 0 +riotboot/flash-slot0: export IMAGE_OFFSET=$(SLOT0_OFFSET) +# Flashing rule for edbg to flash only slot 0 +riotboot/flash-slot0: HEXFILE=$(SLOT0_RIOT_BIN) +# openocd +riotboot/flash-slot0: ELFFILE=$(SLOT0_RIOT_BIN) +riotboot/flash-slot0: $(SLOT0_RIOT_BIN) $(FLASHDEPS) + $(FLASHER) $(FFLAGS) + +# Targets to generate only slot 0 binary +riotboot/slot0: $(SLOT0_RIOT_BIN) + +# Default flashing rule for bootloader + slot 0 +riotboot/flash: riotboot/flash-slot0 riotboot/flash-bootloader + +else +riotboot: + $(Q)echo "error: riotboot feature not selected! (try FEATURES_REQUIRED += riotboot)" + $(Q)false + +endif # (,$(filter riotboot,$(FEATURES_USED))) From 9cfdf6e379809690dde08f45a1e9b45e4a2c80c3 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Thu, 5 Oct 2017 17:13:11 +0200 Subject: [PATCH 4/8] cpu/cortexm_common: introduce cpu_jump_to_image() This new function allows to jump to another execution environment (VTOR) located at a certain (aligned) address. It's used to boot firmwares at another location than `CPU_FLASH_BASE`. The user needs to ensure that the CPU using this feature is able to be initialised at least twice while jumping to the RIOT `reset_handler_default` function, since it initialises the CPU again (calls cpu_init()). Co-authored-by: Kaspar Schleiser --- cpu/cortexm_common/include/cpu.h | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/cpu/cortexm_common/include/cpu.h b/cpu/cortexm_common/include/cpu.h index 50ba3b3e12e5..aa1364c49220 100644 --- a/cpu/cortexm_common/include/cpu.h +++ b/cpu/cortexm_common/include/cpu.h @@ -189,6 +189,44 @@ static inline void cortexm_isr_end(void) } } +/** + * @brief Jumps to another image in flash + * + * This function is supposed to be called by a bootloader application. + * + * @param[in] image_address address in flash of other image + */ +static inline void cpu_jump_to_image(uint32_t image_address) +{ + /* Disable IRQ */ + __disable_irq(); + + /* set MSP */ + __set_MSP(*(uint32_t*)image_address); + + /* skip stack pointer */ + image_address += 4; + + /* load the images reset_vector address */ + uint32_t destination_address = *(uint32_t*)image_address; + + /* Make sure the Thumb State bit is set. */ + destination_address |= 0x1; + + /* Branch execution */ + __asm("BX %0" :: "r" (destination_address)); +} + +/* The following register is only present for Cortex-M0+, -M3, -M4 and -M7 CPUs */ +#if defined(CPU_ARCH_CORTEX_M0PLUS) || defined(CPU_ARCH_CORTEX_M3) || \ + defined(CPU_ARCH_CORTEX_M4) || defined(CPU_ARCH_CORTEX_M4F) || \ + defined(CPU_ARCH_CORTEX_M7) +static inline uint32_t cpu_get_image_baseaddr(void) +{ + return SCB->VTOR; +} +#endif + #ifdef __cplusplus } #endif From 7a6849ca17bb9118c35cd5b110e8edc7722f5787 Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Thu, 15 Jun 2017 15:45:14 +0200 Subject: [PATCH 5/8] cpu/cortexm_common: add riotboot and slot support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RIOTBOOT_SLOT_LEN is calculated as an hexadecimal value and handles ROM_LEN defined as kilobytes like '512K' This enables support for all the cortex-m0+/3/4/7 arch, so most boards embedding these are potentially supported. One needs just to ensure that the CPU can be initialised at least twice. Co-authored-by: Gaëtan Harter --- cpu/cortexm_common/Makefile.include | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cpu/cortexm_common/Makefile.include b/cpu/cortexm_common/Makefile.include index 60e5938bc439..f99c5b6cda97 100644 --- a/cpu/cortexm_common/Makefile.include +++ b/cpu/cortexm_common/Makefile.include @@ -24,3 +24,11 @@ TOOLCHAINS_SUPPORTED = gnu llvm LINKFLAGS += $(if $(ROM_OFFSET),$(LINKFLAGPREFIX)--defsym=_rom_offset=$(ROM_OFFSET)) # FW_ROM_LEN: rom length to use for firmware linking. Allows linking only in a section of the rom. LINKFLAGS += $(if $(FW_ROM_LEN),$(LINKFLAGPREFIX)--defsym=_fw_rom_length=$(FW_ROM_LEN)) + + +# Configure riotboot bootloader and slot lengths +# 4KB are currently enough +RIOTBOOT_LEN ?= 0x1000 +# Take the whole flash minus RIOTBOOT_LEN +SLOT0_LEN ?= $(shell printf "0x%x" $$(($(ROM_LEN:%K=%*1024)-$(RIOTBOOT_LEN)))) +SLOT0_LEN := $(SLOT0_LEN) From 3cf33f7b5ce68b34285e061a9898f95ee8f1feee Mon Sep 17 00:00:00 2001 From: Francisco Acosta Date: Wed, 10 Oct 2018 15:20:56 +0200 Subject: [PATCH 6/8] boards: add riotboot feature to tested boards Currently only tested boards provide the feature riotboot. Potentially all boards embeding a cortex-m0+/3/4/7 are able to have riotboot as a feature, but other dependencies need to be met, e.g. usage of cortexm.ld linker script, double initialisation of cpu_init(), etc. See doc in bootloaders/riotboot. --- boards/common/iotlab/Makefile.features | 3 +++ boards/nucleo-l152re/Makefile.features | 3 +++ boards/saml21-xpro/Makefile.features | 3 +++ boards/samr21-xpro/Makefile.features | 3 +++ 4 files changed, 12 insertions(+) diff --git a/boards/common/iotlab/Makefile.features b/boards/common/iotlab/Makefile.features index 5b0e6f92e99b..c4cd705c6d05 100644 --- a/boards/common/iotlab/Makefile.features +++ b/boards/common/iotlab/Makefile.features @@ -6,5 +6,8 @@ FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +# Put other features for this board (in alphabetical order) +FEATURES_PROVIDED += riotboot + # The board MPU family (used for grouping by the CI system) FEATURES_MCU_GROUP = cortex_m3_1 diff --git a/boards/nucleo-l152re/Makefile.features b/boards/nucleo-l152re/Makefile.features index ad81f0d97e98..459e65383d26 100644 --- a/boards/nucleo-l152re/Makefile.features +++ b/boards/nucleo-l152re/Makefile.features @@ -9,6 +9,9 @@ FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +# Put other features for this board (in alphabetical order) +FEATURES_PROVIDED += riotboot + # load the common Makefile.features for Nucleo boards include $(RIOTBOARD)/common/nucleo64/Makefile.features diff --git a/boards/saml21-xpro/Makefile.features b/boards/saml21-xpro/Makefile.features index edd3b96f393a..651f526750ea 100644 --- a/boards/saml21-xpro/Makefile.features +++ b/boards/saml21-xpro/Makefile.features @@ -7,6 +7,9 @@ FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +# Put other features for this board (in alphabetical order) +FEATURES_PROVIDED += riotboot + # The board MPU family (used for grouping by the CI system) FEATURES_MCU_GROUP = cortex_m0_2 diff --git a/boards/samr21-xpro/Makefile.features b/boards/samr21-xpro/Makefile.features index 432747244252..02f222cb2a25 100644 --- a/boards/samr21-xpro/Makefile.features +++ b/boards/samr21-xpro/Makefile.features @@ -8,6 +8,9 @@ FEATURES_PROVIDED += periph_spi FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart +# Put other features for this board (in alphabetical order) +FEATURES_PROVIDED += riotboot + # The board MPU family (used for grouping by the CI system) FEATURES_MCU_GROUP = cortex_m0_2 From f1c57b21ec9a5977d5c0d964b8d78a73d6054af3 Mon Sep 17 00:00:00 2001 From: Francisco Acosta Date: Thu, 27 Sep 2018 02:00:36 +0200 Subject: [PATCH 7/8] tests: add riotboot bootloader test The tests overrides the target all to be tested by the CI. All the instructions how to use it are in README.md The test is successful if the image boots and displays information about the image and running slot. Co-authored-by: Federico Pellegrin --- tests/riotboot/Makefile | 34 ++++++++++++++ tests/riotboot/README.md | 18 ++++++++ tests/riotboot/main.c | 99 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) create mode 100644 tests/riotboot/Makefile create mode 100644 tests/riotboot/README.md create mode 100644 tests/riotboot/main.c diff --git a/tests/riotboot/Makefile b/tests/riotboot/Makefile new file mode 100644 index 000000000000..c4b90fa4e379 --- /dev/null +++ b/tests/riotboot/Makefile @@ -0,0 +1,34 @@ +# If no BOARD is found in the environment, use this default: +BOARD ?= samr21-xpro + +TEST_ON_CI_WHITELIST += all + +# Select the boards with riotboot feature +FEATURES_REQUIRED += riotboot + +# Include modules to test the bootloader +USEMODULE += riotboot_slot +USEMODULE += shell + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +all: riotboot + +include ../Makefile.tests_common +include $(RIOTBASE)/Makefile.include + +# Make 'flash' and 'flash-only' work without specific command. +# This is currently hacky as there is no way of specifiying a FLASHFILE +all: riotboot/combined-slot0 +# openocd +ELFFILE = $(BINDIR_APP)-slot0-combined.bin +# edbg +HEXFILE = $(BINDIR_APP)-slot0-combined.bin +# murdock uses ':=' to get the flashfile variable so should also be overwritten +FLASHFILE = $(BINDIR_APP)-slot0-combined.bin diff --git a/tests/riotboot/README.md b/tests/riotboot/README.md new file mode 100644 index 000000000000..09ae0e25f9fc --- /dev/null +++ b/tests/riotboot/README.md @@ -0,0 +1,18 @@ +RIOT bootloader test +==================== + +This is a basic example how to use RIOT bootloader in your embedded +application. + +This test should foremost give you an overview how to use riotboot: + + - `make all` build the test using the target riotboot, which generates + a binary file of the application with a header on top of it, used by + the bootloader to recognise a bootable image. + + - `make riotboot/flash` creates the binary files and flashes both + riotboot and the RIOT image with headers included. This should boot + as a normal application. + +In this test two modules `riot_hdr` and `slot_util` are used to showcase +the access to riotboot shared functions. diff --git a/tests/riotboot/main.c b/tests/riotboot/main.c new file mode 100644 index 000000000000..8064b41d2bb5 --- /dev/null +++ b/tests/riotboot/main.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 Inria + * Federico Pellegrin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * + * @file + * @brief riotboot bootloader test + * + * @author Francisco Acosta + * @author Federico Pellegrin + * + * @} + */ + +#include +#include +#include + +#include "riotboot/slot.h" +#include "shell.h" + +static int cmd_print_slot_nr(int argc, char **argv) +{ + (void)argc; + (void)argv; + + printf("Current slot=%d\n", riotboot_slot_current()); + return 0; +} + +static int cmd_print_slot_hdr(int argc, char **argv) +{ + (void)argc; + (void)argv; + + int current_slot = riotboot_slot_current(); + riotboot_slot_print_hdr(current_slot); + return 0; +} + +static int cmd_print_slot_addr(int argc, char **argv) +{ + (void)argc; + + int reqslot=atoi(argv[1]); + printf("Slot %d address=0x%08" PRIx32 "\n", + reqslot, riotboot_slot_get_image_startaddr(reqslot)); + return 0; +} + +static int cmd_dumpaddrs(int argc, char **argv) +{ + (void)argc; + (void)argv; + + riotboot_slot_dump_addrs(); + return 0; +} + +static const shell_command_t shell_commands[] = { + { "curslotnr", "Print current slot number", cmd_print_slot_nr }, + { "curslothdr", "Print current slot header", cmd_print_slot_hdr }, + { "getslotaddr", "Print address of requested slot", cmd_print_slot_addr }, + { "dumpaddrs", "Prints all slot data in header", cmd_dumpaddrs }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + int current_slot; + + puts("Hello riotboot!"); + + printf("You are running RIOT on a(n) %s board.\n", RIOT_BOARD); + printf("This board features a(n) %s MCU.\n", RIOT_MCU); + + /* print some information about the running image */ + current_slot = riotboot_slot_current(); + if (current_slot != -1) { + printf("riotboot_test: running from slot %d\n", current_slot); + riotboot_slot_print_hdr(current_slot); + } + else { + printf("[FAILED] You're not running riotboot\n"); + } + + /* run the shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + return 0; +} From 66911d6ce14f6f3f8e82d474ea510743b935333e Mon Sep 17 00:00:00 2001 From: Francisco Acosta Date: Wed, 5 Dec 2018 01:12:14 +0100 Subject: [PATCH 8/8] tests/riotboot: add basic automatic test in python Co-authored-by: Federico Pellegrin --- tests/riotboot/README.md | 5 ++++- tests/riotboot/tests/01-run.py | 41 ++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100755 tests/riotboot/tests/01-run.py diff --git a/tests/riotboot/README.md b/tests/riotboot/README.md index 09ae0e25f9fc..c85ed4d5ae84 100644 --- a/tests/riotboot/README.md +++ b/tests/riotboot/README.md @@ -14,5 +14,8 @@ This test should foremost give you an overview how to use riotboot: riotboot and the RIOT image with headers included. This should boot as a normal application. -In this test two modules `riot_hdr` and `slot_util` are used to showcase +In this test two modules `riotboot_hdr` and `riotboot_slot` are used to showcase the access to riotboot shared functions. + + - `make test` can be executed to run the automatic Python test that checks + basic functionalities of riotboot diff --git a/tests/riotboot/tests/01-run.py b/tests/riotboot/tests/01-run.py new file mode 100755 index 000000000000..07ececb93f03 --- /dev/null +++ b/tests/riotboot/tests/01-run.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2018 Federico Pellegrin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +from testrunner import run + + +def testfunc(child): + # Ask for current slot, should be 0 (riotboot slot) + child.sendline("curslotnr") + child.expect_exact("Current slot=0") + child.expect('>') + + # Ask for current slot header info and checks for basic output integrity + child.sendline("curslothdr") + # Magic number is "RIOT" (always in little endian) + child.expect_exact("Image magic_number: 0x544f4952") + # Other info is hardware/app dependant so we just check basic compliance + child.expect("Image Version: 0x[0-9a-fA-F]{8}") + child.expect("Image start address: 0x[0-9a-fA-F]{8}") + child.expect("Header chksum: 0x[0-9a-fA-F]{8}") + child.expect('>') + + # Ask for address of slot 0 + child.sendline("getslotaddr 0") + child.expect("Slot 0 address=0x[0-9a-fA-F]{8}") + child.expect('>') + + # Ask for data of all slots + child.sendline("dumpaddrs") + child.expect("slot 0: metadata: 0x[0-9a-fA-F]{1,8} image: 0x[0-9a-fA-F]{8}") + child.expect('>') + + +if __name__ == "__main__": + sys.exit(run(testfunc))