diff --git a/Makefile.dep b/Makefile.dep index 4a811c91a31f18a12ad8e4b1a0ebde71a7b62156..d01275961d6914a2efc784570db2302b98a2aca8 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/Makefile.include b/Makefile.include index 93a9958cd8e28357631a5dcdad6d67e5c1baacd3..d85ce551da7dd4038390db2e735b17daa4d55e4f 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/boards/common/iotlab/Makefile.features b/boards/common/iotlab/Makefile.features index 5b0e6f92e99b3243798b97478dffa1e7c4ff924b..c4cd705c6d051df1bee5a8c8a4bf221f041d5af3 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 ad81f0d97e9836a2c33651808a5720d32c40fdce..459e65383d2679fc8330f26d2b083de84dfad78e 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 edd3b96f393a0804d8023242b18a1ebef8905256..651f526750ea53f582df0bd9edd5f65fa0c8ba95 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 432747244252b7600268f96949635bae4a759b51..02f222cb2a25dabd0a16ab7860eb19e03885c0fa 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 diff --git a/bootloaders/riotboot/Makefile b/bootloaders/riotboot/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b8398cc24a4194f502f36719153a4e66e9459fc2 --- /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 0000000000000000000000000000000000000000..90af49a94464f373d749bc069c4ebcb9ac48d406 --- /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 0000000000000000000000000000000000000000..ce6914296c7f93d19fdc4d1d33daa54be2d656d9 --- /dev/null +++ b/bootloaders/riotboot/main.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * 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 <kaspar@schleiser.de> + * @author Francisco Acosta <francisco.acosta@inria.fr> + * + * @} + */ + +#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/cpu/cortexm_common/Makefile.include b/cpu/cortexm_common/Makefile.include index 60e5938bc439bdc1679bc34e6cf016ff3abbe387..f99c5b6cda97e73711905b2ebfb6cd8edfc9bf5c 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) diff --git a/cpu/cortexm_common/include/cpu.h b/cpu/cortexm_common/include/cpu.h index 50ba3b3e12e5a6ceabab5a321edb056b5408f721..aa1364c4922014fdd7a9d053776dd22b1bc2642b 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 diff --git a/dist/tools/riotboot_gen_hdr/Makefile b/dist/tools/riotboot_gen_hdr/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..dd57e45f27107856e5c16abc913baacd2e98fe62 --- /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 0000000000000000000000000000000000000000..c3cb771b6a15c183369e5d91a5c5bc14c06b11f4 --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/common.c @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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 <kaspar@schleiser.de> + */ + +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +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 0000000000000000000000000000000000000000..f3380d8ffb4b3d777cb9048e8f58f9636d573a0a --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/common.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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 <stddef.h> + +#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 0000000000000000000000000000000000000000..f8559d589fddc002ff07c0a1ad94af9b6666ae46 --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/genhdr.c @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2016 Inria + * 2017 Kaspar Schleiser <kaspar@schleiser.de> + * + * 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 <francisco.acosta@inria.fr> + * @author Kaspar Schleiser <kaspar@schleiser.de> + */ + +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> + +#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[] = "<IMG_BIN> <APP_VER> <START_ADDR> <HDR_LEN> <outfile|->"; + + /* 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 0000000000000000000000000000000000000000..6288fa29edda6b155c743e25f77e57aa8cceb8d1 --- /dev/null +++ b/dist/tools/riotboot_gen_hdr/main.c @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * 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 <francisco.acosta@inria.fr> + * @author Kaspar Schleiser <kaspar@schleiser.de> + */ + +#include <stdio.h> +#include <string.h> + +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; +} diff --git a/makefiles/boot/riotboot.mk b/makefiles/boot/riotboot.mk new file mode 100644 index 0000000000000000000000000000000000000000..ab0c6e63fa3b23402ffa49bbc2731ac341a550dc --- /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))) diff --git a/sys/include/riotboot/slot.h b/sys/include/riotboot/slot.h new file mode 100644 index 0000000000000000000000000000000000000000..24d65e32da174ce45b1232cf117bebe0c4745777 --- /dev/null +++ b/sys/include/riotboot/slot.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 Kaspar Schleiser <kaspar@schleiser.de> + * 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 <kaspar@schleiser.de> + * @author Francisco Acosta <francisco.acosta@inria.fr> + * + * @} + */ + +#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 cd1af2456e0554b9f34f996712ad3fa356b0d770..344672d2e216db7b2213058ff46de27039e661d0 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 0000000000000000000000000000000000000000..09cac62a789bf28990d9029874546539dc65bee8 --- /dev/null +++ b/sys/riotboot/slot.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de> + * 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 <kaspar@schleiser.de> + * @author Francisco Acosta <francisco.acosta@inria.fr> + * + * @} + */ +#include <string.h> + +#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]; +} diff --git a/tests/riotboot/Makefile b/tests/riotboot/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..c4b90fa4e37902fbb9797527daa8baf38c1d5798 --- /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 0000000000000000000000000000000000000000..c85ed4d5ae8486562f84cdf669221739c9b78e16 --- /dev/null +++ b/tests/riotboot/README.md @@ -0,0 +1,21 @@ +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 `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/main.c b/tests/riotboot/main.c new file mode 100644 index 0000000000000000000000000000000000000000..8064b41d2bb57f03b7eaabcadfae22c2dca81f98 --- /dev/null +++ b/tests/riotboot/main.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 Inria + * Federico Pellegrin <fede@evolware.org> + * + * 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 <francisco.acosta@inria.fr> + * @author Federico Pellegrin <fede@evolware.org> + * + * @} + */ + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> + +#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; +} diff --git a/tests/riotboot/tests/01-run.py b/tests/riotboot/tests/01-run.py new file mode 100755 index 0000000000000000000000000000000000000000..07ececb93f0320eb88f75b274ecd2712efea8095 --- /dev/null +++ b/tests/riotboot/tests/01-run.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2018 Federico Pellegrin <fede@evolware.org> +# +# 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))