diff --git a/Makefile.include b/Makefile.include index 93a9958cd8..d85ce551da 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 0000000000..b8398cc24a --- /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 0000000000..90af49a944 --- /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 0000000000..ce6914296c --- /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 0000000000..ab0c6e63fa --- /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)))