diff --git a/Makefile.dep b/Makefile.dep index 45958925f611..bef39951dad3 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -613,6 +613,12 @@ USEPKG += nanocoap USEMODULE += gnrc_sock_udp endif +ifneq (,$(filter fw_slots,$(USEMODULE))) + FEATURES_REQUIRED += periph_flashpage + FEATURES_REQUIRED += flash_slots + USEMODULE += hashes +endif + # include package dependencies -include $(USEPKG:%=$(RIOTPKG)/%/Makefile.dep) diff --git a/Makefile.include b/Makefile.include index fbc3a77c482b..6905de58f40c 100644 --- a/Makefile.include +++ b/Makefile.include @@ -12,9 +12,14 @@ RIOTMAKE ?= $(RIOTBASE)/makefiles RIOTPKG ?= $(RIOTBASE)/pkg RIOTPROJECT ?= $(shell git rev-parse --show-toplevel 2>/dev/null || pwd) GITCACHE ?= $(RIOTBASE)/dist/tools/git/git-cache +FW_METADATA ?= $(RIOTBASE)/dist/tools/firmware_metadata APPDIR ?= $(CURDIR) BINDIRBASE ?= $(APPDIR)/bin +ifdef FW_SLOT +BINDIR ?= $(BINDIRBASE)/$(BOARD)/slot$(FW_SLOT) +else BINDIR ?= $(BINDIRBASE)/$(BOARD) +endif PKGDIRBASE ?= $(BINDIRBASE)/pkg/$(BOARD) __DIRECTORY_VARIABLES := \ @@ -41,6 +46,7 @@ override RIOTMAKE := $(abspath $(RIOTMAKE)) override RIOTPKG := $(abspath $(RIOTPKG)) override RIOTPROJECT := $(abspath $(RIOTPROJECT)) override GITCACHE := $(abspath $(GITCACHE)) +override FW_METADATA := $(abspath $(FW_METADATA)) override APPDIR := $(abspath $(APPDIR)) override BINDIRBASE := $(abspath $(BINDIRBASE)) override BINDIR := $(abspath $(BINDIR)) @@ -274,12 +280,16 @@ endif # the binaries to link BASELIBS += $(BINDIR)/${APPLICATION}.a BASELIBS += $(APPDEPS) +BASELIBS += $(SINGLE_SLOT) .PHONY: all clean flash term doc debug debug-server reset objdump help info-modules .PHONY: ..in-docker-container ELFFILE ?= $(BINDIR)/$(APPLICATION).elf HEXFILE ?= $(ELFFILE:.elf=.hex) +ifdef FW_SLOT +BINFILE ?= $(ELFFILE:.elf=.bin) +endif # variables used to compile and link c++ CPPMIX ?= $(if $(wildcard *.cpp),1,) @@ -289,6 +299,17 @@ LINKFLAGPREFIX ?= -Wl, DIRS += $(EXTERNAL_MODULE_DIRS) +# needed for bootloader... +ifdef FW_SLOT +TMPBIN = $(ELFFILE:.elf=.bin) +SLOTFILE = $(ELFFILE:.elf=slot$(FW_SLOT).o) +METADATA_FILE = $(BINDIR)/$(APPLICATION)-metadata-$(APPID)-$(VERSION).bin +FW_BINFILE = $(BINDIR)/$(APPLICATION)-slot$(FW_SLOT)-$(APPID)-$(VERSION).bin +FW_OBJFILE = $(BINDIR)/$(APPLICATION)-slot$(FW_SLOT)-$(APPID)-$(VERSION).o +BOOTLOADER_FILE = $(abspath $(BINDIR)/bootloader-$(APPLICATION).elf) +HEXFILE = $(BOOTLOADER_FILE:.elf=.hex) +endif #FW_SLOT + ifeq ($(BUILD_IN_DOCKER),1) all: ..in-docker-container else @@ -303,9 +324,38 @@ else endif $(Q)$(SIZE) $(ELFFILE) $(Q)$(OBJCOPY) $(OFLAGS) $(ELFFILE) $(HEXFILE) +ifdef FW_SLOTS + $(Q)$(OBJCOPY) -O binary $(ELFFILE) $(BINFILE) +endif endif endif # BUILD_IN_DOCKER + +ifdef FW_SLOT +firmware-slot: $(FW_OBJFILE) +$(FW_OBJFILE): all generate-metadata + $(Q)$(OBJCOPY) -O binary $(ELFFILE) $(TMPBIN) + $(Q)$(FW_METADATA)/bin/generate-metadata $(TMPBIN) $(VERSION) $(APPID) $(METADATA_FILE) + $(Q)srec_cat $(TMPBIN) -binary -offset $(FW_METADATA_SPACE) $(METADATA_FILE) -binary -o $(FW_BINFILE) -binary + $(Q)$(FW_METADATA)/bin/generate-metadata $(FW_BINFILE) $(VERSION) $(APPID) $(METADATA_FILE) --with-metadata + $(Q)srec_cat $(TMPBIN) -binary -offset $(FW_METADATA_SPACE) $(METADATA_FILE) -binary -o $(FW_BINFILE) -binary + $(Q)$(OBJCOPY) --output-target elf32-littlearm --binary-architecture=arm \ + --change-section-vma .data=$(OFFSET_SLOT_$(FW_SLOT))\ + --change-section-lma .data=$(OFFSET_SLOT_$(FW_SLOT))\ + --rename-section .data=.slot.$(FW_SLOT).text \ + --input-target binary $(FW_BINFILE) \ + $(FW_OBJFILE) + +bootloader: $(FW_OBJFILE) + $(Q)env -i ELFFILE=$(BOOTLOADER_FILE) PATH=$(PATH) \ + make BOARD=$(BOARD) SINGLE_SLOT=$(FW_OBJFILE) -C $(RIOTBASE)/bootloader all + +generate-metadata: $(FW_METADATA)/bin/generate-metadata + +$(FW_METADATA)/bin/generate-metadata: + $(Q)env -i FW_METADATA_SPACE=$(FW_METADATA_SPACE) make -C $(FW_METADATA) +endif # FW_SLOT + ..compiler-check: @command -v $(CC) >/dev/null 2>&1 || \ { $(COLOR_ECHO) \ @@ -314,6 +364,13 @@ endif # BUILD_IN_DOCKER ..build-message: @$(COLOR_ECHO) '${COLOR_GREEN}Building application "$(APPLICATION)" for "$(BOARD)" with MCU "$(MCU)".${COLOR_RESET}' + +ifdef FW_SLOT + @$(COLOR_ECHO) '${COLOR_PURPLE}This is a build for FW slot $(FW_SLOT).${COLOR_RESET}' +endif +ifeq ($(BOOTLOADER),1) + @$(COLOR_ECHO) '${COLOR_PURPLE}This is a Bootloader build.${COLOR_RESET}' +endif @$(COLOR_ECHO) # add extra include paths for packages in $(USEMODULE) diff --git a/bootloader/Makefile b/bootloader/Makefile new file mode 100644 index 000000000000..6f1dfa67a1dd --- /dev/null +++ b/bootloader/Makefile @@ -0,0 +1,41 @@ +# name of your application +APPLICATION = bootloader + +# If no BOARD is found in the environment, use this default: +BOARD ?= iotlab-m3 + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/.. + +# 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: +CFLAGS += -DDEVELHELP + +# Mark this example as the bootloader +BOOTLOADER = 1 + +# Activate interactive bootloader, if no specified +INTERACTIVE_BOOTLOADER ?= 1 +CFLAGS += -DINTERACTIVE_BOOTLOADER=$(INTERACTIVE_BOOTLOADER) + +# Import metadata space size, otherwise it will be defaulted to 0x100 by +# the code +CFLAGS += -DFW_METADATA_SPACE=$(FW_METADATA_SPACE) + +# Increase stack memory for flashpage requirements +CFLAGS += -DTHREAD_STACKSIZE_MAIN=\(3*THREAD_STACKSIZE_DEFAULT\) + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +# Use fw_slots module to manage images on ROM +USEMODULE += fw_slots + +# Add also the shell, some shell commands +ifeq ($(INTERACTIVE_BOOTLOADER),1) +USEMODULE += shell +USEMODULE += shell_commands +endif + +include $(RIOTBASE)/Makefile.include diff --git a/bootloader/README.md b/bootloader/README.md new file mode 100644 index 000000000000..30404ce4c546 --- /dev/null +++ b/bootloader/README.md @@ -0,0 +1 @@ +TODO \ No newline at end of file diff --git a/bootloader/main.c b/bootloader/main.c new file mode 100644 index 000000000000..342218fb0362 --- /dev/null +++ b/bootloader/main.c @@ -0,0 +1,245 @@ +/* + * Copyright (C)2016 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 examples + * @{ + * + * @file + * @brief Default bootloader application to manage FW slots + * + * @author Francisco Acosta + * + * @} + */ + +#include +#include +#include + +#if INTERACTIVE_BOOTLOADER +#include "thread.h" +#include "shell.h" +#include "shell_commands.h" +#endif +#include "fw_slots.h" +#include "cpu_conf.h" + +#if INTERACTIVE_BOOTLOADER +static int cmd_lsimg(int argc, char **argv) +{ + (void)argc; + (void)argv; + + firmware_metadata_t fw_metadata; + + for (uint8_t i = 1; i <= MAX_FW_SLOTS; i++) { + if (fw_slots_get_int_slot_metadata(i, &fw_metadata) == 0) { + printf("Metadata slot %d:\n", i); + fw_slots_print_metadata(&fw_metadata); + } + else { + printf("ERROR: Cannot retrieve metadata from slot %d.\n", i); + } + } + + return 0; +} + +static int cmd_get_metadata(int argc, char **argv) +{ + uint8_t slot; + firmware_metadata_t fw_metadata; + + if (argc < 2) { + printf("usage: %s \n", argv[0]); + return -1; + } + + slot = atoi(argv[1]); + + if (fw_slots_get_int_slot_metadata(slot, &fw_metadata) == 0) { + printf("Metadata slot %d\n", slot); + fw_slots_print_metadata(&fw_metadata); + return 0; + } + else { + printf("ERROR: Cannot retrieve metadata from slot %d.\n", slot); + return -1; + } + + return 0; +} + +static int cmd_verify(int argc, char **argv) +{ + uint8_t slot; + + if (argc < 2) { + printf("usage: %s \n", argv[0]); + return -1; + } + + slot = atoi(argv[1]); + + if (fw_slots_verify_int_slot(slot) == 0) { + printf("Slot %d successfully verified\n", slot); + return 0; + } + else { + printf("Slot %d not consistent\n", slot); + return -1; + } + + return 0; +} + +static int cmd_erase_slot(int argc, char**argv) +{ + uint8_t slot; + + if (argc < 2) { + printf("usage: %s \n", argv[0]); + return -1; + } + + slot = atoi(argv[1]); + + if (fw_slots_erase_int_image(slot) < 0) { + printf("ERROR: Cannot erase slot %d\n", slot); + return -1; + } + else { + printf("Slot %d successfully erased\n", slot); + } + + return 0; +} + +static int cmd_jump(int argc, char **argv) +{ + uint32_t address; + + uint8_t slot; + + if (argc < 2) { + printf("usage: %s \n", argv[0]); + return -1; + } + + slot = atoi(argv[1]); + + if (fw_slots_verify_int_slot(slot) == 0) { + printf("Slot %d verified!\n", slot); + } + else { + printf("Slot %u inconsistent!\n", slot); + return -1; + } + + address = fw_slots_get_slot_address(slot); + + printf("Booting slot %d at 0x%lx...\n", slot, address); + + fw_slots_jump_to_image(address); + + return 0; +} + +static const shell_command_t shell_commands[] = { + { "lsimg", "List the available firmwares on ROM", cmd_lsimg }, + { "get_metadata", "Get metadata from slot", cmd_get_metadata }, + { "verify", "Verify consistency (sha256) of slot", cmd_verify }, + { "erase", "Erase slot *WARNING use with caution*", cmd_erase_slot }, + { "jump", "Jump to specific FW slot (cause reset)", cmd_jump }, + { NULL, NULL, NULL } +}; + +#else +static int boot_img(void) +{ + uint8_t boot_slot = 0; + uint32_t appid_slot1 = 0, appid_slot2 = 0; + firmware_metadata_t metadata; + + puts("[bootlaoder] Cheking for slots metadata..."); + if (fw_slots_get_int_metadata(fw_slots_get_slot_page(1), &metadata) == 0) { + appid_slot1 = metadata.appid; + printf("[bootlaoder] Found slot 1 with APPID: 0x%lx \n", appid_slot1); + } + else { + puts("[bootloader] Slot 1 not valid"); + } + + if (fw_slots_get_int_metadata(fw_slots_get_slot_page(2), &metadata) == 0) { + appid_slot2 = metadata.appid; + printf("[bootlaoder] Found slot 2 with APPID: 0x%lx \n", appid_slot2); + } + else { + puts("[bootloader] Slot 2 not valid"); + } + + if ((appid_slot1 == 0) && (appid_slot2 == 0)) { + puts("[bootloader] No valid slot found!"); + return -1; + } + else { + if (appid_slot1 != appid_slot2) { + puts("[bootloader] Warning! application IDs are different!"); + puts("[bootloader] falling back to slot 1"); + if (fw_slots_verify_int_slot(1) == 0) { + uint32_t address; + address = fw_slots_get_slot_address(1); + fw_slots_jump_to_image(address); + } + else { + puts("[bootlaoder] Slot 1 inconsistent, no image to boot..."); + return -1; + } + } + else { + puts("[bootloader] Looking for the newest image on ROM, listing..."); + boot_slot = fw_slots_find_newest_int_image(); + + if (boot_slot > 0) { + if (fw_slots_verify_int_slot(boot_slot) == 0) { + uint32_t address; + printf("[bootloader] newest image on slot %d found and verified! Booting...\n", + boot_slot); + address = fw_slots_get_slot_address(boot_slot); + fw_slots_jump_to_image(address); + } + else { + printf("Slot %u inconsistent!\n", boot_slot); + } + } + else { + (void) puts("No bootable slot found!\n"); + return -1; + } + } + } + + /* Shouldn't happen */ + return 0; +} +#endif + +int main(void) +{ + (void) puts("Welcome to RIOT bootloader!"); +#if INTERACTIVE_BOOTLOADER + /* run the shell */ + char line_buf[SHELL_DEFAULT_BUFSIZE]; + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); +#else + boot_img(); +#endif + /* Should never happen */ + return 0; +} diff --git a/cpu/cortexm_common/include/cpu.h b/cpu/cortexm_common/include/cpu.h index b8e7e29e79b5..eddb255f06cf 100644 --- a/cpu/cortexm_common/include/cpu.h +++ b/cpu/cortexm_common/include/cpu.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2014-2015 Freie Universität Berlin + * 2017 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 @@ -23,6 +24,7 @@ * @author Stefan Pfeiffer * @author Hauke Petersen * @author Joakim Nohlgård + * @author Francisco Acosta * * @todo remove include irq.h once core was adjusted */ @@ -50,6 +52,29 @@ extern "C" { */ #define STACK_CANARY_WORD (0xE7FEE7FEu) +/** +* @brief Offset to reset handler on VTOR +*/ +#define VTOR_RESET_HANDLER (0x4) /** One pointer after the beginning */ + +#if defined(FLASHPAGE_SIZE) && defined(FLASHPAGE_NUMOF) && defined (FW_SLOT_PAGES) +/** + * @brief Defines for slot management + */ +#define BOOTLOADER_SPACE (0x4000) +#define FW_SLOT_SIZE FLASHPAGE_SIZE * FW_SLOT_PAGES +#define FW_SLOT_1 FLASH_BASE + BOOTLOADER_SPACE +#define FW_SLOT_1_END FW_SLOT_1 + FW_SLOT_SIZE +#define FW_SLOT_2 FW_SLOT_1_END +#define FW_SLOT_2_END FW_SLOT_2 + FW_SLOT_SIZE +#endif + +/** +* @brief _estack pointer needed to reset PSP position for +* cortexm_branch_address +*/ +extern uint32_t _estack; + /** * @brief Initialization of the CPU */ @@ -60,6 +85,54 @@ void cpu_init(void); */ void cortexm_init(void); +#if defined(FW_SLOT_1) && defined(FW_SLOT_2) +/** + * @brief Get FW internal address for a given slot + * + * @param[in] slot FW slot + * + * @return FW slot address + */ +static inline uint32_t get_slot_address(uint8_t slot) +{ + switch (slot) { + case 1: + return FW_SLOT_1; + break; + + case 2: + return FW_SLOT_2; + break; + } + + return 0; +} +#endif + +#if defined(FW_SLOT_1_PAGE) && defined(FW_SLOT_2_PAGE) +/** + * @brief Get internal page for a given slot + * + * @param[in] slot FW slot + * + * @return FW slot page + */ +static inline uint32_t get_slot_page(uint8_t slot) +{ + switch (slot) { + case 1: + return FW_SLOT_1_PAGE; + break; + + case 2: + return FW_SLOT_2_PAGE; + break; + } + + return 0; +} +#endif + /** * @brief Prints the current content of the link register (lr) */ @@ -114,6 +187,26 @@ static inline void cortexm_isr_end(void) } } +/** + * @brief Branch the execution to a specified address, usually used to + * jump to another RIOT instance. + */ +static inline void cortexm_branch_address(uint32_t destination_address) +{ + /* Disable IRQ */ + __disable_irq(); + + /* Move PSP to the end of the stack */ + __set_PSP((uint32_t)&_estack); + + /* Load the destination address */ + __asm("LDR R0, [%[dest]]"::[dest]"r"(destination_address)); + /* Make sure the Thumb State bit is set. */ + __asm("ORR R0, #1"); + /* Branch execution */ + __asm("BX R0"); +} + #ifdef __cplusplus } #endif diff --git a/cpu/stm32f1/Makefile.features b/cpu/stm32f1/Makefile.features index 7a418ea511cb..5d2e909132eb 100644 --- a/cpu/stm32f1/Makefile.features +++ b/cpu/stm32f1/Makefile.features @@ -1 +1,2 @@ FEATURES_PROVIDED += periph_pm +FEATURES_PROVIDED += flash_slots diff --git a/cpu/stm32f1/Makefile.include b/cpu/stm32f1/Makefile.include index abe4696648ee..8c61968d4e94 100644 --- a/cpu/stm32f1/Makefile.include +++ b/cpu/stm32f1/Makefile.include @@ -1,6 +1,13 @@ export CPU_ARCH = cortex-m3 export CPU_FAM = stm32f1 +# Add slot offsets for CPU stm32f103re +ifeq ($(call ifndef_any_of,FW_SLOT CPU_MODEL_STM32F103RE),) +export FW_METADATA_SPACE ?= 0x100 # 256 byte aligned metadata space +export OFFSET_SLOT_1 ?= 0x08004000 # Slot 1 starts at page 8 +export OFFSET_SLOT_2 ?= 0x08040000 # Slot 2 starts at page 128 +endif + USEMODULE += pm_layered include $(RIOTCPU)/stm32_common/Makefile.include diff --git a/cpu/stm32f1/include/cpu_conf.h b/cpu/stm32f1/include/cpu_conf.h index 311f01544084..1fa365dcf6b7 100644 --- a/cpu/stm32f1/include/cpu_conf.h +++ b/cpu/stm32f1/include/cpu_conf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 INRIA + * Copyright (C) 2013, 2016 Inria * Copyright (C) 2014 Freie Universität Berlin * * This file is subject to the terms and conditions of the GNU Lesser General @@ -18,6 +18,7 @@ * * @author Alaeddine Weslati * @author Hauke Petersen + * @author Francisco Acosta */ #ifndef CPU_CONF_H @@ -35,6 +36,38 @@ extern "C" { #endif +#if defined(CPU_MODEL_STM32F103RE) +/* + * @brief Flash partitioning for FW slots + * @{ + */ + +#ifndef FW_METADATA_SPACE +#define FW_METADATA_SPACE (0x100) +#endif + +#define MAX_FW_SLOTS (2) +#define FW_SLOT_PAGES (120) +#define FW_SLOT_1_PAGE (8) +#define FW_SLOT_2_PAGE (128) + +/** @} */ + +#endif /* defined(CPU_MODEL_STM32F103RE) */ +/** @} */ + +#if FW_SLOT == 1 +#define CURRENT_FIRMWARE_ADDR FW_SLOT_1 +#define CURRENT_FIRMWARE_PAGE FW_SLOT_1_PAGE +#define CURRENT_FIRMWARE_END FW_SLOT_1_END +#endif + +#if FW_SLOT == 2 +#define CURRENT_FIRMWARE_ADDR FW_SLOT_2 +#define CURRENT_FIRMWARE_PAGE FW_SLOT_2_PAGE +#define CURRENT_FIRMWARE_END FW_SLOT_2_END +#endif + /** * @brief ARM Cortex-M specific CPU configuration * @{ diff --git a/cpu/stm32f1/ldscripts/stm32f103re-bootloader.ld b/cpu/stm32f1/ldscripts/stm32f103re-bootloader.ld new file mode 100644 index 000000000000..e35d84ddcda0 --- /dev/null +++ b/cpu/stm32f1/ldscripts/stm32f103re-bootloader.ld @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2017 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. + */ + +/** + * @addtogroup cpu_stm32f1 + * @{ + * + * @file + * @brief Memory definitions for the STM32F103RE with bootloader + * + * @author Gaëtan Harter + * + * @} + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 16K + romslot1 (rx) : ORIGIN = 0x08004000, LENGTH = 0x3C000 + romslot2 (rx) : ORIGIN = 0x08040000, LENGTH = 0x3C000 + ram (xrw) : ORIGIN = 0x20000000, LENGTH = 64K + cpuid (r) : ORIGIN = 0x1ffff7e8, LENGTH = 12 +} + +_cpuid_address = ORIGIN(cpuid); + +INCLUDE cortexm_base.ld + +SECTIONS +{ + . = ALIGN(0x1000); + .slot.1 : + { + KEEP(*(.slot.1.*)) + } > romslot1 + + . = ALIGN(0x1000); + .slot.2 : + { + KEEP(*(.slot.2.*)) + } > romslot2 +} diff --git a/cpu/stm32f1/ldscripts/stm32f103re_slot1.ld b/cpu/stm32f1/ldscripts/stm32f103re_slot1.ld new file mode 100644 index 000000000000..91b381d1bcad --- /dev/null +++ b/cpu/stm32f1/ldscripts/stm32f103re_slot1.ld @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +/** + * @addtogroup cpu_stm32f1 + * @{ + * + * @file + * @brief Memory definitions for the STM32F103RE Slot 1 + * + * @author Francisco Acosta + * + * @} + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x08004100, LENGTH = 0x3BF00 + ram (xrw) : ORIGIN = 0x20000000, LENGTH = 64K + cpuid (r) : ORIGIN = 0x1ffff7e8, LENGTH = 12 +} + +_cpuid_address = ORIGIN(cpuid); + +INCLUDE cortexm_base.ld diff --git a/cpu/stm32f1/ldscripts/stm32f103re_slot2.ld b/cpu/stm32f1/ldscripts/stm32f103re_slot2.ld new file mode 100644 index 000000000000..181853d11ff8 --- /dev/null +++ b/cpu/stm32f1/ldscripts/stm32f103re_slot2.ld @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 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. + */ + +/** + * @addtogroup cpu_stm32f1 + * @{ + * + * @file + * @brief Memory definitions for the STM32F103RE Slot 2 + * + * @author Francisco Acosta + * + * @} + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x08040100, LENGTH = 0x3BF00 + ram (xrw) : ORIGIN = 0x20000000, LENGTH = 64K + cpuid (r) : ORIGIN = 0x1ffff7e8, LENGTH = 12 +} + +_cpuid_address = ORIGIN(cpuid); + +INCLUDE cortexm_base.ld diff --git a/dist/tools/firmware_metadata/Makefile b/dist/tools/firmware_metadata/Makefile new file mode 100644 index 000000000000..6d8d1428a1ba --- /dev/null +++ b/dist/tools/firmware_metadata/Makefile @@ -0,0 +1,20 @@ +RIOTBASE := ../../.. +RIOT_INCLUDE = $(RIOTBASE)/sys/include +SHA256_DIR := $(RIOTBASE)/sys/hashes +SHA256_INCLUDE := $(RIOT_INCLUDE)/hashes +METADATA_SRC := generate-metadata.c $(SHA256_DIR)/sha256.c +METADATA_HDR := $(RIOT_INCLUDE)/fw_slots.h $(RIOT_INCLUDE)/hashes/sha256.h + +CFLAGS += -DFW_METADATA_SPACE=$(FW_METADATA_SPACE) +CFLAGS += -g -O3 -Wall -Wextra -pedantic -std=c99 + +all: bin/generate-metadata + +bin/: + mkdir -p bin + +bin/generate-metadata: $(METADATA_HDR) $(METADATA_SRC) | bin/ + $(CC) $(CFLAGS) -I$(RIOT_INCLUDE) $(METADATA_SRC) -o $@ + +clean: + rm -rf bin/generate-metadata diff --git a/dist/tools/firmware_metadata/README.md b/dist/tools/firmware_metadata/README.md new file mode 100644 index 000000000000..a149d74031b6 --- /dev/null +++ b/dist/tools/firmware_metadata/README.md @@ -0,0 +1,51 @@ +# Metadata generator for firmware verification +This program will generate a binary file containing a metadata structure as +follows: + +```c +typedef struct firmware_metadata { + uint8_t hash[SHA256_DIGEST_LENGTH]; /**< SHA256 Hash of firmware image */ + uint8_t shash[SIGN_LEN]; /**< Signed SHA256 */ + uint16_t version; /**< Integer representing firmware version */ + uint32_t size; /**< Size of firmware image */ + uint32_t appid; /**< Integer representing the application ID */ +} firmware_metadata_t; +``` + +This structure will be filled with the data obtained from the firmware. + +## Requirements +In order to compile this program, you will need to provide the offset taken by +the metadata in itself. This can change depending on the alignment of the +architecture the firmware is compiled for. + +For instance: + +```makefile +CFLAGS+=-DFW_METADATA_SPACE=256 make clean all +``` + +will create the metadata binary in a space of 256 bytes. + +## Usage +To use, you should call `generate-metadata` with the following arguments: + +```console +./generate-metadata [output-path] [--with-metadata] +``` + +Where: + +_\\:_ The firmware in binary format + +_\\:_ The firmware version in 16-bit HEX + +_\_\: ID for the application in 32-bit HEX + +_\[output-path\]_\: The path for fimrware_metadata.bin + +_\[--with-metadata\]_\: This will take into account existing metadata in the +firmware + +The results will be printed if the operation is successful, and a binary +called "_\[output-path\]_\/firmware-metadata.bin" will be created. diff --git a/dist/tools/firmware_metadata/generate-metadata.c b/dist/tools/firmware_metadata/generate-metadata.c new file mode 100644 index 000000000000..beb2b2814a2f --- /dev/null +++ b/dist/tools/firmware_metadata/generate-metadata.c @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2016, Mark Solters . + * 2016, Inria + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @ingroup FW + * @file + * @brief Meta-data generation for FW images + * + * @author Mark Solters + * @author Francisco Acosta + */ + +#include +#include +#include +#include + +#include "hashes/sha256.h" +#include "fw_slots.h" + +/* Input firmware .bin file */ +FILE *firmware_bin; + +/* Output metadata .bin file */ +FILE *metadata_bin; + +int32_t firmware_size = 0; + +static void print_metadata(firmware_metadata_t *fw_metadata) +{ + printf("Firmware Size: %d\n", fw_metadata->size); + printf("Firmware Version: %#x\n", fw_metadata->version); + printf("Firmware APPID: %#x\n", fw_metadata->appid); + printf("Firmware HASH: "); + for (unsigned long i = 0; i < sizeof(fw_metadata->hash); i++) { + printf("%02x ", fw_metadata->hash[i]); + } + printf("\n"); + printf("Firmware signed HASH: "); + for (unsigned long i = 0; i < sizeof(fw_metadata->shash); i++) { + printf("%02x ", fw_metadata->shash[i]); + } + printf("\n"); +} + +int main(int argc, char *argv[]) +{ + firmware_metadata_t metadata; + sha256_context_t firmware_sha256; + uint8_t output_buffer[sizeof(firmware_metadata_t)]; + int bytes_read = 0; + uint8_t firmware_buffer[1024]; + char firmware_metadata_path[128]; + + if (argc < 4) { + puts("Usage: generate-metadata [output path]"); + puts("Options:"); + puts("--with-metadata The sha256 hash will also include previous metadata"); + return -1; + } + + if (argv[4]) { + strcpy(firmware_metadata_path, argv[4]); + } + else { + strcpy(firmware_metadata_path, "firmware-metadata.bin"); + } + + /* Open the firmware .bin file */ + if (!(firmware_bin = fopen(argv[1], "r+"))) { + printf("Error: No binary file found!\n"); + return -1; + } + + /* + * Firmware might need to be re-hashed to increase security (e.g. avoid + * non-matching version and/or signature), by also including the metadata. + */ + if (argv[5] != NULL && (strcmp(argv[5], "--with-metadata") == 0)) { + firmware_metadata_t current_metadata; + bytes_read = fread(¤t_metadata, 1, sizeof(current_metadata), firmware_bin); + rewind(firmware_bin); + if (bytes_read == sizeof(current_metadata)) { + /* + * Remove previous hashes and signatures, which will be added later. + */ + for (unsigned long i = 0; i < sizeof(current_metadata.shash); i++) { + current_metadata.shash[i] = 0xFF; + } + for (unsigned long i = 0; i < sizeof(current_metadata.hash); i++) { + current_metadata.hash[i] = 0xFF; + } + + /* + * Reduce the size of the firmware since metadata is taking + * FW_METADATA_SPACE more bytes. + */ + firmware_size -= FW_METADATA_SPACE; + + fwrite(¤t_metadata, sizeof(current_metadata), 1, firmware_bin); + rewind(firmware_bin); + puts("New firmware metadata:"); + } + } + + sha256_init(&firmware_sha256); + + while((bytes_read = fread(firmware_buffer, 1, sizeof(firmware_buffer), firmware_bin))) { + sha256_update(&firmware_sha256, firmware_buffer, bytes_read); + firmware_size += bytes_read; + } + sha256_final(&firmware_sha256, metadata.hash); + + /* Close the firmware .bin file. */ + fclose(firmware_bin); + + /* + * TODO Sign hash + */ + for (unsigned long i = 0; i < sizeof(metadata.shash); i++) { + metadata.shash[i] = 0; + } + + /* Generate FW image metadata */ + metadata.size = firmware_size; + sscanf(argv[2], "%xu", (unsigned int *)&(metadata.version)); + sscanf(argv[3], "%xu", &(metadata.appid)); + memcpy(output_buffer, (uint8_t*)&metadata, sizeof(firmware_metadata_t)); + print_metadata(&metadata); + + /* Open the output firmware .bin file */ + metadata_bin = fopen(firmware_metadata_path, "w"); + + /* Write the metadata */ + printf("Metadata size: %lu\n", sizeof(firmware_metadata_t)); + fwrite(output_buffer, sizeof(output_buffer), 1, metadata_bin); + + /* 0xff spacing until firmware binary starts */ + uint8_t blank_buffer[FW_METADATA_SPACE - sizeof(firmware_metadata_t)]; + + for (unsigned long b = 0; b < sizeof(blank_buffer); b++) { + blank_buffer[b] = 0xff; + } + + fwrite(blank_buffer, sizeof(blank_buffer), 1, metadata_bin); + + /* Close the metadata file */ + fclose(metadata_bin); + + return 0; +} diff --git a/makefiles/arch/cortexm.inc.mk b/makefiles/arch/cortexm.inc.mk index e46b85157638..cf3421b807d0 100644 --- a/makefiles/arch/cortexm.inc.mk +++ b/makefiles/arch/cortexm.inc.mk @@ -15,6 +15,19 @@ export CFLAGS_OPT ?= -Os export CFLAGS += $(CFLAGS_CPU) $(CFLAGS_LINK) $(CFLAGS_DBG) $(CFLAGS_OPT) export ASFLAGS += $(CFLAGS_CPU) $(CFLAGS_DBG) + +# If the FW_SLOT flag is set, we will use a specific linker script depending +# on the slot which we are compiling for. +ifdef FW_SLOT +export LINKER_SCRIPT = $(CPU_MODEL)_slot$(FW_SLOT).ld +export CFLAGS += -DFW_SLOT=$(FW_SLOT) +endif + +# If we compile a bootloader, set the correct linker script +ifeq (1,$(BOOTLOADER)) +export LINKER_SCRIPT = $(CPU_MODEL)-bootloader.ld +endif + export LINKFLAGS += -L$(RIOTCPU)/$(CPU)/ldscripts -L$(RIOTCPU)/cortexm_common/ldscripts export LINKER_SCRIPT ?= $(CPU_MODEL).ld export LINKFLAGS += -T$(LINKER_SCRIPT) -Wl,--fatal-warnings diff --git a/sys/Makefile b/sys/Makefile index 6e431596f7f7..31b6407d545c 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -122,6 +122,10 @@ ifneq (,$(filter l2filter,$(USEMODULE))) DIRS += net/link_layer/l2filter endif +ifneq (,$(filter fw_slots,$(USEMODULE))) + DIRS += fw_slots +endif + DIRS += $(dir $(wildcard $(addsuffix /Makefile, ${USEMODULE}))) include $(RIOTBASE)/Makefile.base diff --git a/sys/fw_slots/Makefile b/sys/fw_slots/Makefile new file mode 100644 index 000000000000..48422e909a47 --- /dev/null +++ b/sys/fw_slots/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/fw_slots/fw_slots.c b/sys/fw_slots/fw_slots.c new file mode 100644 index 000000000000..ee6852fcb34a --- /dev/null +++ b/sys/fw_slots/fw_slots.c @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2016, Mark Solters . + * 2016, Francisco Acosta + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @file + * @author Mark Solters + * @author Francisco Acosta + * + */ + +#include +#include + +#include "fw_slots.h" +#include "cpu_conf.h" +#include "cpu.h" +#include "periph/flashpage.h" +#include "hashes/sha256.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define HASH_BUF (1024) + +static uint8_t firmware_buffer[HASH_BUF]; + +/** + * @brief Read internal flash to a buffer at specific address. + * + * @param[in] address - Address to be read. + * @param[in] count - count in bytes. + * + * @param[out] data_buffer - The buffer filled with the read information. + * + */ +static void int_flash_read(uint8_t *data_buffer, uint32_t address, uint32_t count) +{ + uint8_t *read_addres = (uint8_t*)address; + + memcpy(data_buffer, read_addres, count); +} + +int fw_slots_validate_int_slot(uint8_t fw_slot) +{ + /* + * TODO + */ + + return 0; +} + +uint32_t fw_slots_get_slot_address(uint8_t fw_slot) +{ + return get_slot_address(fw_slot); +} + +uint32_t fw_slots_get_slot_page(uint8_t fw_slot) +{ + return get_slot_page(fw_slot); +} + +void fw_slots_print_metadata(firmware_metadata_t *metadata) +{ + printf("Firmware Size: %ld\n", metadata->size); + printf("Firmware Version: %#x\n", metadata->version); + printf("Firmware APPID: %#lx\n", metadata->appid); + printf("Firmware HASH: "); + for (unsigned long i = 0; i < sizeof(metadata->hash); i++) { + printf("%02x ", metadata->hash[i]); + } + printf("\n"); + printf("Firmware signed HASH: "); + for (unsigned long i = 0; i < sizeof(metadata->shash); i++) { + printf("%02x ", metadata->shash[i]); + } + printf("\n"); +} + +int fw_slots_get_int_metadata(uint8_t fw_slot_page, firmware_metadata_t *fw_metadata) +{ + uint32_t fw_address; + + fw_address = fw_slot_page * FLASHPAGE_SIZE + CPU_FLASH_BASE; + + DEBUG("[fw_slots] Getting internal metadata on page %d at address %#lx\n", + fw_slot_page, fw_address); + int_flash_read((uint8_t*)fw_metadata, fw_address, sizeof(firmware_metadata_t)); + + if (fw_slots_validate_metadata(fw_metadata) == 0) { + DEBUG("[fw_slots] ERROR empty metadata!\n"); + return -1; + } + + return 0; +} + +int fw_slots_get_slot_metadata(uint8_t fw_slot, firmware_metadata_t *fw_slot_metadata) +{ + /* + * TODO + */ + + return 0; +} + +int fw_slots_get_int_slot_metadata(uint8_t fw_slot, firmware_metadata_t *fw_slot_metadata) +{ + uint32_t page; + + DEBUG("[fw_slots] Getting internal FW slot %d metadata\n", fw_slot); + if (fw_slot > MAX_FW_SLOTS || fw_slot == 0) { + DEBUG("[fw_slots] FW slot not valid, should be <= %d and > 0\n", + MAX_FW_SLOTS); + return -1; + } + + page = fw_slots_get_slot_page(fw_slot); + + return fw_slots_get_int_metadata(page, fw_slot_metadata); +} + +int fw_slots_overwrite_int_slot_metadata(uint8_t fw_slot, firmware_metadata_t *fw_slot_metadata) +{ + /* + * TODO + */ + return 0; +} + + + +int fw_slots_overwrite_slot_metadata(uint8_t fw_slot, firmware_metadata_t *fw_slot_metadata) +{ + /* + * TODO + */ + return 0; +} + +int fw_slots_backup_golden_image(void) +{ + /* + * TODO + */ + return 0; +} + +int fw_slots_verify_int_slot(uint8_t fw_slot) +{ + firmware_metadata_t fw_metadata, cache_metadata; + uint32_t fw_image_address; + uint32_t address; + uint16_t rest; + sha256_context_t sha256_ctx; + uint8_t hash[SHA256_DIGEST_LENGTH]; + uint8_t page_cache[FLASHPAGE_SIZE]; + int parts = 0, i = 0; + + /* Determine the external flash address corresponding to the FW slot */ + if (fw_slot > MAX_FW_SLOTS || fw_slot == 0) { + DEBUG("[fw_slots] FW slot not valid, should be <= %d and > 0\n", + MAX_FW_SLOTS); + return -1; + } + + /* Read the metadata of the corresponding FW slot */ + fw_image_address = fw_slots_get_slot_address(fw_slot); + + if (fw_slots_get_int_slot_metadata(fw_slot, &fw_metadata) == 0) { + memcpy(&cache_metadata, &fw_metadata, sizeof(fw_metadata)); + fw_slots_print_metadata(&cache_metadata); + } else { + DEBUG("[fw_slots] ERROR cannot get slot metadata.\n"); + return -1; + } + + DEBUG("[fw_slots] Verifying slot %d at 0x%lx \n", fw_slot, fw_image_address); + + address = fw_image_address; + + /* + * Reset hash and shash to 0xFF + */ + for (unsigned long i = 0; i < sizeof(fw_metadata.shash); i++) { + fw_metadata.shash[i] = 0xFF; + } + for (unsigned long i = 0; i < sizeof(fw_metadata.hash); i++) { + fw_metadata.hash[i] = 0xFF; + } + + /* + * Write new metadata with overriden hashes and signatures + */ + int_flash_read(page_cache, address, sizeof(page_cache)); + memcpy(page_cache, &fw_metadata, sizeof(fw_metadata)); + DEBUG("[fw_slots] rewriting metadata at page %d on %p \n", + flashpage_page((void*)address), (void*)address); + if (flashpage_write_and_verify(flashpage_page((void*)address), page_cache) != FLASHPAGE_OK) { + DEBUG("[fw_slots] Failed to update metadata!\n"); + return -1; + } + + sha256_init(&sha256_ctx); + + uint32_t size = fw_metadata.size; + size += FW_METADATA_SPACE; + + parts = size / sizeof(firmware_buffer); + rest = size % sizeof(firmware_buffer); + + while (parts) { + int_flash_read(firmware_buffer, address, sizeof(firmware_buffer)); + sha256_update(&sha256_ctx, firmware_buffer, sizeof(firmware_buffer)); + address += sizeof(firmware_buffer); + parts--; + } + + int_flash_read(firmware_buffer, address, rest); + sha256_update(&sha256_ctx, firmware_buffer, rest); + sha256_final(&sha256_ctx, hash); + + for (i = 0; i < sizeof(hash); i++) { + if (hash[i] != cache_metadata.hash[i]) { + DEBUG("[fw_slots] hash verification failed!\n"); + return -1; + } + } + + /* + * Restore metadata + */ + address = fw_image_address; + int_flash_read(page_cache, address, sizeof(page_cache)); + memcpy(page_cache, &cache_metadata, sizeof(cache_metadata)); + DEBUG("[fw_slots] rewriting metadata at page %d on %p \n", + flashpage_page((void*)address), (void*)address); + if (flashpage_write_and_verify(flashpage_page((void*)address), page_cache) != FLASHPAGE_OK) { + DEBUG("[fw_slots] Failed to update metadata!\n"); + return -1; + } + + return 0; +} + +int fw_slots_validate_metadata(firmware_metadata_t *metadata) +{ + /* Is the FW slot erased? + * First, we check to see if every byte in the metadata is 0xFF. + * If this is the case, this metadata is "erased" and therefore we assume + * the FW slot to be empty. + */ + int erased = 1; + uint8_t *metadata_ptr = (uint8_t*)metadata; + int b = FW_METADATA_LENGTH; + + while (b--) { + if (*metadata_ptr++ != 0xff) { + /* We encountered a non-erased byte. + * There's some non-trivial data here. + */ + erased = 0; + break; + } + } + + /* If the FW slot is not erased, it's not valid! No more work to do here. */ + if (erased == 0) { + DEBUG("[fw_slots] Slot is not empty!\n"); + return -1; + } + + /* If we get this far, all metadata bytes were cleared (0xff) */ + return 0; +} + +int fw_slots_find_matching_int_slot(uint16_t version) +{ + int matching_slot = -1; /* Assume there is no matching FW slot. */ + + /* Iterate through each of the FW slots. */ + for (int slot = 1; slot <= MAX_FW_SLOTS; slot++) { + + /* Get the metadata of the current FW slot. */ + firmware_metadata_t fw_slot_metadata; + if(fw_slots_get_int_slot_metadata(slot, &fw_slot_metadata) == 0) { + fw_slots_print_metadata(&fw_slot_metadata); + } else { + DEBUG("[fw_slots] ERROR cannot get slot metadata.\n"); + } + + /* Is this slot empty? If yes, skip. */ + if (fw_slots_validate_metadata(&fw_slot_metadata) == 0) { + continue; + } + + /* Does this slot's FW version match our search parameter? */ + if (fw_slot_metadata.version == version) { + matching_slot = slot; + break; + } + } + + if (matching_slot == -1) { + DEBUG("[fw_slots] No FW slot matches Firmware v%i\n", version); + } else { + DEBUG("[fw_slots] FW slot #%i matches Firmware v%i\n", matching_slot, + version); + } + + return matching_slot; +} + +int fw_slots_find_empty_int_slot(void) +{ + /* Iterate through each of the MAX_FW_SLOTS internal slots. */ + for (int slot = 1; slot <= MAX_FW_SLOTS; slot++) { + + /* Get the metadata of the current FW slot. */ + firmware_metadata_t fw_slot_metadata; + + if(fw_slots_get_int_slot_metadata(slot, &fw_slot_metadata) == 0) { + fw_slots_print_metadata(&fw_slot_metadata); + } else { + DEBUG("[fw_slots] ERROR cannot get slot metadata.\n"); + } + + /* Is this slot invalid? If yes, let's treat it as empty. */ + if (fw_slots_validate_metadata(&fw_slot_metadata) == 0) { + return slot; + } + } + + DEBUG("[fw_slots] Could not find any empty FW slots!" + "\nSearching for oldest FW slot...\n"); + /* + * If execution goes this far, no empty slot was found. Now, we look for + * the oldest FW slot instead. + */ + return fw_slots_find_oldest_int_image(); +} + +int fw_slots_find_oldest_int_image(void) +{ + /* The oldest firmware should be the v0 */ + int oldest_fw_slot = 1; + uint16_t oldest_firmware_version = 0; + + /* Iterate through each of the MAX_FW_SLOTS internal slots. */ + for (int slot = 1; slot <= MAX_FW_SLOTS; slot++) { + /* Get the metadata of the current FW slot. */ + firmware_metadata_t fw_slot_metadata; + + if(fw_slots_get_int_slot_metadata(slot, &fw_slot_metadata) == 0) { + fw_slots_print_metadata(&fw_slot_metadata); + } else { + DEBUG("[fw_slots] ERROR cannot get slot metadata.\n"); + } + + /* Is this slot populated? If not, skip. */ + if (fw_slots_validate_metadata(&fw_slot_metadata) == 0) { + continue; + } + + /* Is this the oldest image we've found thus far? */ + if (oldest_firmware_version) { + if (fw_slot_metadata.version < oldest_firmware_version) { + oldest_fw_slot = slot; + oldest_firmware_version = fw_slot_metadata.version; + } + } else { + oldest_fw_slot = slot; + oldest_firmware_version = fw_slot_metadata.version; + } + } + + DEBUG("[fw_slots] Oldest FW slot: #%d; Firmware v%d\n", oldest_fw_slot, + oldest_firmware_version); + + return oldest_fw_slot; +} + +int fw_slots_find_newest_int_image(void) +{ + /* At first, we only assume knowledge of version v0 */ + int newest_fw_slot = 0; + uint16_t newest_firmware_version = 0; + + /* Iterate through each of the MAX_FW_SLOTS. */ + for (int slot = 1; slot <= MAX_FW_SLOTS ; slot++) { + /* Get the metadata of the current FW slot. */ + firmware_metadata_t fw_slot_metadata; + + if(fw_slots_get_int_slot_metadata(slot, &fw_slot_metadata) == 0) { + fw_slots_print_metadata(&fw_slot_metadata); + } else { + DEBUG("[fw_slots] ERROR cannot get slot metadata.\n"); + } + + /* Is this slot populated? If not, skip. */ + if (fw_slots_validate_metadata(&fw_slot_metadata) == 0) { + continue; + } + + /* Is this the newest non-Golden Image image we've found thus far? */ + if (fw_slot_metadata.version > newest_firmware_version) { + newest_fw_slot = slot; + newest_firmware_version = fw_slot_metadata.version; + } + } + + DEBUG("Newest FW slot: #%d; Firmware v%d\n", newest_fw_slot, + newest_firmware_version); + + return newest_fw_slot; +} + +int fw_slots_erase_int_image(uint8_t fw_slot) +{ +#if ENABLE_DEBUG + /* Get page address of the fw_slot in internal flash */ + uint32_t fw_image_base_address; + fw_image_base_address = fw_slots_get_slot_address(fw_slot); +#endif + /* Get the page where the fw_slot is located */ + uint8_t slot_page; + + if (fw_slot > MAX_FW_SLOTS || fw_slot == 0) { + DEBUG("[fw_slots] FW slot not valid, should be <= %d and > 0\n", + MAX_FW_SLOTS); + return -1; + } + + DEBUG("[fw_slots] Erasing FW slot %u [%#lx, %#lx]...\n", fw_slot, + fw_image_base_address, + fw_image_base_address + (FW_SLOT_PAGES * FLASHPAGE_SIZE) - 1); + + slot_page = fw_slots_get_slot_page(fw_slot); + + /* Erase each page in the FW internal slot! */ + for (int page = slot_page; page < slot_page + FW_SLOT_PAGES; page++) { + DEBUG("[fw_slots] Erasing page %d\n", page); + flashpage_write(page, NULL); + } + + DEBUG("[fw_slots] Erase successful\n"); + + return 0; +} + +int fw_slots_update_firmware(uint8_t fw_slot) +{ + /* + * TODO + */ + return 0; +} + +int fw_slots_store_fw_data( uint32_t ext_address, uint8_t *data, size_t data_length ) +{ + /* + * TODO + */ + return 0; +} + +void fw_slots_jump_to_image(uint32_t destination_address) +{ + if (destination_address) { + /* + * Only add the metadata length offset if destination_address is NOT 0! + * (Jumping to 0x0 is used to reboot the device) + */ + destination_address += FW_METADATA_SPACE; + } + + /* Move to the second pointer on VTOR (reset_handler_default) */ + destination_address += VTOR_RESET_HANDLER; + + /* + * Currently this approach will work only on Cortex-M3/M4 MCUs, which allow + * to move the VTOR to any 256 byte aligned address. + * + * TODO: Change for a more generic approach which will cover *if possible* + * all supported MCUs on RIOT. + */ + cortexm_branch_address(destination_address); +} diff --git a/sys/include/fw_slots.h b/sys/include/fw_slots.h new file mode 100644 index 000000000000..f03114ddf02b --- /dev/null +++ b/sys/include/fw_slots.h @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2016, Mark Solters + * 2016, Francisco Acosta + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +/** + * @defgroup sys_fw_slots Firmware slots management + * @ingroup sys + * @brief Slots management for several FW in ROM and ext Flash + * @{ + * + * @file + * @brief FW Image R/W and Verification + * + * @author Mark Solters + * @author Francisco Acosta + */ + +#ifndef FW_SLOTS_H +#define FW_SLOTS_H + +#include "hashes/sha256.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief FW_METADATA_LENGTH: + * This is just the size of the FW_metadata_t struct, which is 4-byte + * aligned. We use 76 bytes currently, so this struct will be 76 bytes. + */ +#define FW_METADATA_LENGTH sizeof(firmware_metadata_t) + +/** + * @brief SIGN_LEN: + * Provisional length for signed hash + */ +#define SIGN_LEN (SHA256_DIGEST_LENGTH) + +/** + * @brief Structure to store firmware metadata + * @{ + */ +typedef struct firmware_metadata { + uint8_t hash[SHA256_DIGEST_LENGTH]; /**< SHA256 Hash of firmware image */ + uint8_t shash[SIGN_LEN]; /**< Signed SHA256 */ + uint16_t version; /**< Integer representing firmware version */ + uint32_t size; /**< Size of firmware image */ + uint32_t appid; /**< Integer representing the application ID */ +} firmware_metadata_t; +/** @} */ + +/** + * @brief Print formatted FW image metadata to STDIO. + * + * @param[in] metadata Metadata struct to fill with firmware metadata + * + */ +void fw_slots_print_metadata(firmware_metadata_t *metadata); + +/** + * @brief Validate internal FW slot as a secure firmware + * + * @param[in] fw_slot The FW slot to be validated. + * + * @return 0 on success or error code + */ +int fw_slots_validate_int_slot(uint8_t fw_slot); + +/** + * @brief Get the internal metadata belonging to an FW slot in internal + * flash, using the flash page. + * + * @param[in] fw_slot_page The FW slot page to be read for metadata. + * + * @param[in] *fw_metadata Pointer to the FW_metadata_t struct where + * the metadata is to be written. + * + * @return 0 on success or error code + */ +int fw_slots_get_int_metadata(uint8_t fw_slot_page, + firmware_metadata_t *fw_metadata); + +/** + * @brief Get the metadata belonging to an FW slot in external flash. + * + * @param[in] fw_slot The FW slot to be read for metadata. + * + * @param[in] *fw_slot_metadata Pointer to the FW_metadata_t struct where + * the metadata is to be written. + * + * @return 0 on success or error code + */ +int fw_slots_get_slot_metadata(uint8_t fw_slot, firmware_metadata_t *fw_slot_metadata); + +/** + * @brief Get the metadata belonging to an FW slot in internal flash. + * + * @param[in] fw_slot The FW slot to be read for metadata. + * + * @param[in] *fw_slot_metadata Pointer to the FW_metadata_t struct where + * the metadata is to be written. + * + * @return 0 on success or error code + */ +int fw_slots_get_int_slot_metadata(uint8_t fw_slot, + firmware_metadata_t *fw_slot_metadata); + +/** + * @brief Get the address corresponding to a given slot + * + * @param[in] fw_slot The FW slot to get the address. + * + * + * @return 0 on success or error code + */ +uint32_t fw_slots_get_slot_address(uint8_t fw_slot); + +/** + * @brief Get the page corresponding to a given slot + * + * @param[in] fw_slot The FW slot to get the page. + * + * + * @return 0 on success or error code + */ +uint32_t fw_slots_get_slot_page(uint8_t fw_slot); + +/** + * @brief Write new metadata to a specific FW slot in internal flash. + * + * @param fw_slot The FW slot to be modified. + * + * @param *fw_slot_metadata Pointer to the new FW_metadata_t data. + * + * @return 0 on success or error code + */ +int fw_slots_overwrite_int_slot_metadata(uint8_t fw_slot, + firmware_metadata_t *fw_slot_metadata); + +/** + * @brief Write new metadata to a specific FW slot in external flash. + * + * @param fw_slot The FW slot to be modified. + * + * @param *fw_slot_metadata Pointer to the new FW_metadata_t data. + * + * @return 0 on success or error code + */ +int fw_slots_overwrite_slot_metadata(uint8_t fw_slot, + firmware_metadata_t *fw_slot_metadata); + +/** + * @brief Copy the current firmware into FW slot 0 as the "Golden Image" + * + * @return 0 for success or error code + */ +int fw_slots_backup_golden_image(void); + +/** + * @brief Given an FW slot, verify the firmware content against the metadata. + * + * @param[in] fw_slot FW slot index to verify. (1-N) + * + * @return 0 for success or error code + */ +int fw_slots_verify_int_slot(uint8_t fw_slot); + +/** + * @brief Returns true only if the metadata provided indicates the FW slot + * is populated and valid. + * + * @param[in] *metadata FW metadata to be validated + * + * @return 0 if the FW slot is populated and valid. Otherwise, -1. + */ +int fw_slots_validate_metadata(firmware_metadata_t *metadata); + +/** + * @brief Find a FW slot containing firmware matching the supplied + * firmware version number. Will only find the first matching + * slot. + * + * @param[in] version FW slot version. + * + * @return The FW slot index of the matching FW slot. Return -1 in the event + * of no match. + */ +int fw_slots_find_matching_int_slot(uint16_t version); + +/** + * @brief Find the first empty FW slot. Failing this, find the slot with the + * most out-of-date firmware version. + * + * @return The FW slot index of the empty/oldest FW slot. This will never be + * 0 because the Golden Image should never be erased. + */ +int fw_slots_find_empty_int_slot(void); + +/** + * @brief Find the FW slot containing the most out-of-date firmware version. + * FW slots are in internal flash. + * + * @return The FW slot index of the oldest firmware version. + */ +int fw_slots_find_oldest_int_image(void); + +/** + * @brief Find the FW slot containing the most recent firmware version. + * FW slots are in internal flash. + * + * @return The FW slot index of the newest firmware version. + */ +int fw_slots_find_newest_int_image(void); + +/** + * @brief Clear an FW slot in internal flash. + * + * @param[in] fw_slot The FW slot index of the firmware image to be erased. + * + * @return -1 or error code + */ +int fw_slots_erase_int_image(uint8_t fw_slot); + +/** + * @brief Overwrite firmware located in internal flash with the firmware + * stored in an external flash FW slot. + * + * @param[in] fw_slot The FW slot index of the firmware image to be copied. + * 0 = "Golden Image" backup, aka factory restore + * 1, 2, 3, n = FW Download slots + * + * @return -1 or error code + */ +int fw_slots_update_firmware(uint8_t fw_slot); + +/** + * @brief Store firmware data in external flash at the specified + * address. + * + * @param[in] ext_address External flash address to begin writing data. + * + * @param[in] data Pointer to the data buffer to be written. + * + * @param[in] data_length Length of the buffer + * + * @return -1 or error code + */ +int fw_slots_store_fw_data(uint32_t ext_address, uint8_t *data, size_t data_length); + +/** + * @brief Begin executing another firmware binary located in internal flash. + * + * @param[in] destination_address Internal flash address of the vector table + * for the firmware binary that is to be booted + * into. Since this FW lib prepends metadata + * to each binary, the true VTOR start address + * will be FW_METADATA_SPACE bytes past this + * address. + * + */ +void fw_slots_jump_to_image(uint32_t destination_address); + +#ifdef __cplusplus +} +#endif + +#endif /* FW_SLOTS_H */ +/** @} */