diff --git a/Makefile.dep b/Makefile.dep index 4b9636f5f51f..0e24c671f0fc 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -603,6 +603,13 @@ USEPKG += nanocoap USEMODULE += gnrc_sock_udp endif +ifneq (,$(filter fw_slots,$(USEMODULE))) + USEPKG += tweetnacl + USEMODULE += hashes + FEATURES_REQUIRED += periph_flashpage + FEATURES_REQUIRED += flash_slots +endif + # include package dependencies -include $(USEPKG:%=$(RIOTPKG)/%/Makefile.dep) diff --git a/Makefile.include b/Makefile.include index 902a30950aba..de51318c2b34 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,14 @@ 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) +BINFILE ?= $(ELFFILE:.elf=.bin) # variables used to compile and link c++ CPPMIX ?= $(if $(wildcard *.cpp),1,) @@ -289,6 +297,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 +322,38 @@ else endif $(Q)$(SIZE) $(ELFFILE) $(Q)$(OBJCOPY) $(OFLAGS) $(ELFFILE) $(HEXFILE) + $(Q)$(OBJCOPY) -O binary $(ELFFILE) $(BINFILE) endif endif # BUILD_IN_DOCKER + +ifdef FW_SLOT +SECKEY = $(FW_METADATA)/key_ed25519.sec + +firmware-slot: $(FW_OBJFILE) +$(FW_OBJFILE): all generate-metadata + $(Q)$(OBJCOPY) -O binary $(ELFFILE) $(TMPBIN) + $(Q)$(FW_METADATA)/bin/generate-metadata $(TMPBIN) $(VERSION) $(APPID) $(SECKEY) $(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) $(SECKEY) $(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 +362,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..7dc937d87b43 --- /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 + +# Enable the bootloader flag +BOOTLOADER = 1 + +# Activate interactive bootloader, if no specified +INTERACTIVE_BOOTLOADER ?= 0 +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=\(5*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..36cf04c46eea --- /dev/null +++ b/bootloader/main.c @@ -0,0 +1,235 @@ +/* + * 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_validate(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 verified!\n", slot); + if (fw_slots_validate_int_slot(slot) == 0) { + printf("Slot %d successfully validated\n", slot); + return 0; + } + else { + printf("Signature on slot %d is not valid!\n", slot); + return -1; + } + } + else { + printf("Slot %u inconsistent!\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]); + + 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 }, + { "validate", "Validate signature ed25519 of slot", cmd_validate}, + { "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 validate_and_boot(uint8_t slot) +{ + if (fw_slots_verify_int_slot(slot) == 0) { + uint32_t address; + printf("[bootloader] Image on slot %d verified! Validating...\n", + slot); + if (fw_slots_validate_int_slot(slot) == 0) { + printf("[bootlaoder] slot %d validated! Booting...\n", slot); + address = fw_slots_get_slot_address(slot); + fw_slots_jump_to_image(address); + } + else { + puts("[bootloader] no valid signature!"); + return -1; + } + } + else { + printf("Slot %u inconsistent!\n", slot); + return -1; + } + + /* Should not happen */ + return -1; +} +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 (validate_and_boot(1) == -1) { + puts("[bootloader] Booting failed!"); + } + 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) { + printf("[bootloader] newest image found on slot %d, checking...\n", + boot_slot); + if (validate_and_boot(boot_slot) == -1) { + puts("[bootloader] Booting failed!"); + } + } + 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 fed1ee08e916..9e797c5d89ff 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..f5cf8be33911 --- /dev/null +++ b/dist/tools/firmware_metadata/Makefile @@ -0,0 +1,37 @@ +RIOTBASE := ../../.. +RIOT_INCLUDE := $(RIOTBASE)/sys/include +SHA256_DIR := $(RIOTBASE)/sys/hashes +SHA256_INCLUDE := $(RIOT_INCLUDE)/hashes +TWEETNACL_INC := tweetnacl +TWEETNACL_DIR := tweetnacl +TWEETNACL_SRC := $(TWEETNACL_DIR)/tweetnacl.c randombytes.c +METADATA_SRC := $(TWEETNACL_SRC) generate-metadata.c $(SHA256_DIR)/sha256.c +METADATA_HDR := $(RIOT_INCLUDE)/fw_slots.h $(RIOT_INCLUDE)/hashes/sha256.h + +GITCACHE = ../git/git-cache +TWEETNACL_URL = https://github.com/RIOT-OS/tweetnacl.git +TWEETNACL_VERSION = 7ea05c7098a16c87fa66e9166ce301666f3f2623 + +CFLAGS += -DFW_METADATA_SPACE=$(FW_METADATA_SPACE) +CFLAGS += -g -O3 -Wall -Wextra -pedantic -std=c99 + +all: bin/generate-metadata + +bin/: + mkdir -p bin + +git-fetch-tweetnacl: + rm -Rf $(TWEETNACL_DIR) + mkdir -p $(TWEETNACL_DIR) + $(GITCACHE) clone "$(TWEETNACL_URL)" "$(TWEETNACL_VERSION)" "$(TWEETNACL_DIR)" + touch $@ + +bin/generate-metadata: git-fetch-tweetnacl $(METADATA_HDR) $(METADATA_SRC) | bin/ + $(CC) $(CFLAGS) -I$(RIOT_INCLUDE) -I$(TWEETNACL_INC) $(METADATA_SRC) -o $@ + +clean: + rm -rf bin/generate-metadata + +dist-clean: + rm -rf $(TWEETNACL_DIR) + rm -f git-fetch-tweetnacl 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..5c1e8a6646e1 --- /dev/null +++ b/dist/tools/firmware_metadata/generate-metadata.c @@ -0,0 +1,216 @@ +/* + * 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 + +#include "hashes/sha256.h" +#include "fw_slots.h" +#include "tweetnacl.h" + +/* Input firmware .bin file */ +FILE *firmware_bin; + +/* Output metadata .bin file */ +FILE *metadata_bin; + +int32_t firmware_size = 0; + +static void errorf(int code, char message[], char arg[]) +{ + fprintf(stderr, "Error: "); + fprintf(stderr, message, arg); + fprintf(stderr, "\n"); + exit(code); +} + +static void read_key(char filename[], unsigned char key[], size_t key_size) +{ + FILE* f = fopen(filename, "rb"); + + if (fread(key, 1, key_size, f) != key_size) { + errorf(1, "Could not read the key from <%s>", filename); + } + + fclose(f); +} + +static void print_metadata(firmware_metadata_t *fw_metadata) +{ + printf("Firmware Size: %u\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[256]; + unsigned char secret_key[crypto_sign_SECRETKEYBYTES]; + unsigned char signed_hash[SIGN_LEN]; + unsigned char hash[SHA256_DIGEST_LENGTH]; + unsigned long long mlen; + unsigned long long smlen; + + if (argc < 5) { + puts("Usage: generate-metadata [output path]"); + puts("Options:"); + puts("--with-metadata The sha256 hash will also include previous metadata"); + return -1; + } + + read_key(argv[4], secret_key, crypto_sign_SECRETKEYBYTES); + + if (argv[5]) { + strcpy(firmware_metadata_path, argv[5]); + } + 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[6] != NULL && (strcmp(argv[6], "--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); + + /* + * Initialise signed hash to zero, will be filled by the signature + */ + for (unsigned long i = 0; i < sizeof(metadata.shash); i++) { + metadata.shash[i] = 0; + } + + memcpy(hash, metadata.hash, SHA256_DIGEST_LENGTH); + memset(signed_hash, 0, SIGN_LEN); + mlen = SHA256_DIGEST_LENGTH; + smlen = SHA256_DIGEST_LENGTH + crypto_sign_BYTES; + + crypto_sign(signed_hash, &smlen, hash, mlen, secret_key); + + memcpy(metadata.shash, signed_hash, sizeof(metadata.shash)); + + /* Close the firmware .bin file. */ + fclose(firmware_bin); + + /* Generate FW image metadata */ + metadata.size = firmware_size; + sscanf(argv[2], "%xu", (unsigned int *)&(metadata.version)); + sscanf(argv[3], "%xu", &(metadata.appid)); + + /* 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)); + memcpy(output_buffer, (uint8_t*)&metadata, sizeof(firmware_metadata_t)); + fwrite(output_buffer, sizeof(output_buffer), 1, metadata_bin); + print_metadata(&metadata); + + /* 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/dist/tools/firmware_metadata/key_ed25519.sec b/dist/tools/firmware_metadata/key_ed25519.sec new file mode 100644 index 000000000000..d39fb197216c Binary files /dev/null and b/dist/tools/firmware_metadata/key_ed25519.sec differ diff --git a/dist/tools/firmware_metadata/randombytes.c b/dist/tools/firmware_metadata/randombytes.c new file mode 100644 index 000000000000..31ad3577af4f --- /dev/null +++ b/dist/tools/firmware_metadata/randombytes.c @@ -0,0 +1,40 @@ +/* +randombytes/devurandom.h version 20080713 +D. J. Bernstein +Public domain. +*/ + +#include +#include +#include +#include + +/* it's really stupid that there isn't a syscall for this */ + +static int fd = -1; + +void randombytes(unsigned char *x,unsigned long long xlen) +{ + int i; + + if (fd == -1) { + for (;;) { + fd = open("/dev/urandom",O_RDONLY); + if (fd != -1) break; + sleep(1); + } + } + + while (xlen > 0) { + if (xlen < 1048576) i = xlen; else i = 1048576; + + i = read(fd,x,i); + if (i < 1) { + sleep(1); + continue; + } + + x += i; + xlen -= i; + } +} diff --git a/examples/tftp_ota_programming/Makefile b/examples/tftp_ota_programming/Makefile new file mode 100644 index 000000000000..fc3defa931fe --- /dev/null +++ b/examples/tftp_ota_programming/Makefile @@ -0,0 +1,53 @@ +APPLICATION = tftp_ota_programming + +# If no BOARD is found in the environment, use this default: +BOARD ?= iotlab-m3 + +# Compile this example for FW_SLOT=1 +FW_SLOT ?= 1 + +# Use VERSION (> 0) and APPID (32bit hex) to build with bootloader +VERSION ?= 0x1 +APPID ?= 0xabcd1234 + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +BOARD_INSUFFICIENT_MEMORY := airfy-beacon calliope-mini chronos microbit msb-430 \ + msb-430h nrf51dongle nrf6310 nucleo32-f031 \ + nucleo32-f042 nucleo32-f303 nucleo32-l031 nucleo-f030 \ + nucleo-f070 nucleo-f072 nucleo-f103 nucleo-f302 nucleo-f334 \ + nucleo-l053 pca10000 pca10005 spark-core stm32f0discovery \ + telosb weio wsn430-v1_3b wsn430-v1_4 yunjia-nrf51822 z1 + +# Include packages that pull up and auto-init the link layer. +# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present +USEMODULE += gnrc_netdev_default +USEMODULE += auto_init_gnrc_netif +# Specify the mandatory networking modules for IPv6 and UDP +USEMODULE += gnrc_ipv6_router_default +USEMODULE += gnrc_udp +# Add a routing protocol +USEMODULE += gnrc_rpl +# This application dumps received packets to STDIO using the pktdump module +USEMODULE += gnrc_pktdump +# Additional networking modules that can be dropped if not needed +USEMODULE += gnrc_icmpv6_echo +# Use gnrc_tftp for TFTP +USEMODULE += gnrc_tftp +# Add also the shell, some shell commands +USEMODULE += shell +USEMODULE += shell_commands +USEMODULE += ps +# Use fw_slots module to manage images on ROM +USEMODULE += fw_slots + +# 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 + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +include $(RIOTBASE)/Makefile.include diff --git a/examples/tftp_ota_programming/README.md b/examples/tftp_ota_programming/README.md new file mode 100644 index 000000000000..ed23847f71a3 --- /dev/null +++ b/examples/tftp_ota_programming/README.md @@ -0,0 +1,108 @@ +# tftp_ota_programming example + +## Connecting RIOT board and the Linux host + +> **Note:** RIOT does not support IPv4, so you need to stick to IPv6 anytime. +To establish a connection between RIOT and the Linux host, you will need `tftp` +(with IPv6 support). On Ubuntu and Debian you would need the package `tftp-hpa` +for TFTP client and `tftpd-hpa` for TFTP server. +Be aware that many programs require you to add an option such as -6 to tell +them to use IPv6, otherwise they will fail. If you're using a _Raspberry Pi_, +run `sudo modprobe ipv6` before trying this example, because raspbian does not +load the IPv6 module automatically. +On some systems (openSUSE for example), the _firewall_ may interfere, and +prevent some packets to arrive at the application (they will however show up +in Wireshark, which can be confusing). So be sure to adjust your firewall +rules, or turn it off (who needs security anyway). + +First, you need to setup a border-router which will serve the binary image. +This can be done by your means or by using the RIOT border-router example. +If the latter is the case, by doing `ifconfig` for this example, you should +have: + +```cosole +INFO # ifconfig +INFO # Iface 7 HWaddr: 0f:36 Channel: 26 Page: 0 NID: 0x23 +INFO # Long HWaddr: 15:11:6b:10:65:f8:8f:36 +INFO # TX-Power: 0dBm State: IDLE max. Retrans.: 3 CSMA Retries: 4 +INFO # ACK_REQ CSMA MTU:1280 HL:64 6LO RTR RTR_ADV IPHC +INFO # Source address length: 8 +INFO # Link type: wireless +INFO # inet6 addr: ff02::1/128 scope: local [multicast] +INFO # inet6 addr: fe80::1711:6b10:65f8:8f36/64 scope: local +INFO # inet6 addr: ff02::1:fff8:8f36/128 scope: local [multicast] +INFO # inet6 addr: 2001:db8::1711:6b10:65f8:8f36/64 scope: global +INFO # inet6 addr: ff02::2/128 scope: local [multicast] +``` + +If you have an address starting by `2001:db8` it means your configuration is +successful, since the border-router just gave you a global address. + +On the Linux side, you should have `fd00:dead:beef::1/128` as a global address +in the lo interface: + +```console +lo Link encap:Local Loopback + inet addr:127.0.0.1 Mask:255.0.0.0 + inet6 addr: ::1/128 Scope:Host + inet6 addr: fd00:dead:beef::1/128 Scope:Global + UP LOOPBACK RUNNING MTU:65536 Metric:1 + RX packets:0 errors:0 dropped:0 overruns:0 frame:0 + TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 + collisions:0 txqueuelen:1 + RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) +``` + +To configure your tftp server on linux, you should first stop the tftpd-hpa +server: + +```console +$ sudo /etc/init.d/tftpd-hpa stop +``` + +Then, create a folder where you'll put the firmwares: + +```console +$ mkdir tftpserv +``` + +And copy to it the binaries you want to download. + +To start the server: + +```console +$ sudo in.tftpd -vvv -L -6 -c -s -u $USER ./tftpserv +``` + +## Download the image on RIOT +On the RIOT side, you can start the downloading by typing: + +```console +> tftpc get application-slot2-APPID-VERSION.bin octet 1 fd00:dead:beef::1 +``` + +This should trigger the downloading and writing into the internal flash. +It will download by pieces of 45 bytes, then accumulate `FLASHPAGE_SIZE` bytes +(2048 on stm32f103re mcu) in RAM which will be dumped to flash. + +## Booting the slot 2 image +When the image is successfully downloaded, the device can be rebooted. If the +interactive bootloader is chosen, the image can be verified by doing: + +```console +> verify 2 +``` + +Which will perfom a sha256 check on the image and compare it to the one in the +metadata. + +To boot the image: + +```console +> jump 2 +``` + +Which will verify and boot the image on slot 2. + +If the normal bootloader is selected, it will choose the newest image and boot +it, if the APPIDs are the same on both slot 1 and slot 2 images. diff --git a/examples/tftp_ota_programming/main.c b/examples/tftp_ota_programming/main.c new file mode 100644 index 000000000000..0d50711875f3 --- /dev/null +++ b/examples/tftp_ota_programming/main.c @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2015 Engineering-Spirit + * + * 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 Example application for demonstrating the RIOT TFTP stack + * + * @author Nick van IJzendoorn + * + * @} + */ + +#include +#include +#include +#include + +#include "fw_slots.h" +#include "shell.h" +#include "msg.h" + +extern int tftp_client_cmd(int argc, char **argv); +extern int tftp_server_cmd(int argc, char **argv); + +#define MAIN_QUEUE_SIZE (4) +static msg_t _main_msg_queue[MAIN_QUEUE_SIZE]; + +static const shell_command_t shell_commands[] = { + { "tftpc", "get/put data to a TFTP server", tftp_client_cmd }, + { "tftps", "start and stop the TFTP server", tftp_server_cmd }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + /* we need a message queue for the thread running the shell in order to + * receive potentially fast incoming networking packets */ + msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE); + puts("RIOT TFTP OTA example application"); + puts("Erasing slot 2..."); + fw_slots_erase_int_image(2); + + /* start shell */ + puts("All up, running the shell now"); + char line_buf[SHELL_DEFAULT_BUFSIZE]; + + shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE); + + /* should be never reached */ + return 0; +} diff --git a/examples/tftp_ota_programming/tftp_client.c b/examples/tftp_ota_programming/tftp_client.c new file mode 100644 index 000000000000..c1b8336fe253 --- /dev/null +++ b/examples/tftp_ota_programming/tftp_client.c @@ -0,0 +1,276 @@ +/* + * 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. + */ + +/** + * @ingroup examples + * @{ + * + * @file + * @brief Basic example showing how to download a new firmware to + * internal flash on slot 2 + * + * @author Francisco Acosta + * + * @} + */ + +#include +#include +#include + +#include "net/gnrc/tftp.h" +#include "periph/flashpage.h" + +static const char *_tftp_default_host = "::1"; +static uint8_t buf[FLASHPAGE_SIZE]; +static uint32_t buf_ptr = 0; +static uint32_t page2 = FW_SLOT_2_PAGE; + +static bool tftp_save_to_slot2(uint8_t *data, size_t data_len) +{ + if ((buf_ptr + data_len) <= FLASHPAGE_SIZE) { + memcpy(buf + buf_ptr, data, data_len); + buf_ptr += data_len; + } else { + uint32_t rest = (buf_ptr + data_len) - FLASHPAGE_SIZE; + size_t tmp_len = data_len - rest; + memcpy(buf + buf_ptr, data, tmp_len); + int err; + err = flashpage_write_and_verify(page2, buf); + if (err == FLASHPAGE_OK) { + printf("Successfully written page %lu at %p\n", + page2, flashpage_addr(page2)); + page2++; + memcpy(buf, data + tmp_len, rest); + buf_ptr = rest; + } else { + printf("Flash program failed with error %d\n", err); + return false; + } + } + + return true; +} + +static int write_last_page(uint32_t page, uint32_t slot_page) +{ + int err; + + if (page != slot_page) { + err = flashpage_write_and_verify(page, buf); + if (err == FLASHPAGE_OK) { + printf("Successfully written page %lu at %p\n", + page, flashpage_addr(page)); + page2 = slot_page; + buf_ptr = 0; + memset(buf, 0xFF, sizeof(buf)); + return true; + } else { + printf("Flash program failed with error %d\n", err); + return false; + } + } + + return false; +} + +/* default server text which can be received */ +static const char _tftp_client_hello[] = "Hello,\n" + "\n" + "Client text would also need to exist to be able to put data.\n" + "\n" + "Enjoy the RIOT-OS\n"; + +static tftp_action_t _tftp_action; + +/** + * @brief called at every transaction start + */ +static bool _tftp_client_start_cb(tftp_action_t action, tftp_mode_t mode, + const char *file_name, size_t *len) +{ + /* translate the mode */ + const char *str_mode = "ascii"; + + if (mode == TTM_OCTET) { + str_mode = "bin"; + } + else if (mode == TTM_MAIL) { + str_mode = "mail"; + } + + /* translate the action */ + const char *str_action = "read"; + if (action == TFTP_WRITE) { + str_action = "write"; + } + + /* display the action being performed */ + printf("tftp_client: %s %s %s:%lu\n", str_mode, str_action, file_name, (unsigned long)*len); + + /* return the length of the text, if this is an read action */ + if (action == TFTP_READ) { + *len = sizeof(_tftp_client_hello); + } + + /* remember the action of the current transfer */ + _tftp_action = action; + + /* we accept the transfer to take place so we return true */ + return true; +} + +/** + * @brief called to get or put data, depending on the mode received by `_tftp_start_cb(action, ...)` + */ +static int _tftp_client_data_cb(uint32_t offset, void *data, size_t data_len) +{ + char *c = (char *) data; + + /* if we are reading return the part of the data that is being requested */ + if (_tftp_action == TFTP_WRITE) { + /* calculate the length of the data block to transfer */ + if (offset + data_len > sizeof(_tftp_client_hello)) { + data_len -= (offset + data_len) - sizeof(_tftp_client_hello); + } + + /* copy the block to the output buffer */ + memcpy(data, _tftp_client_hello + offset, data_len); + } + else { + /* we received a data block which we save on slot 2 */ + tftp_save_to_slot2((uint8_t*)c, data_len); + } + + /* return the length of the data block */ + return data_len; +} + +/** + * @brief the transfer has stopped, see the event argument to determined if it was successful + * or not. + */ +static void _tftp_client_stop_cb(tftp_event_t event, const char *msg) +{ + /* decode the stop event received */ + const char *cause = "UNKOWN"; + + + if (event == TFTP_SUCCESS) { + cause = "SUCCESS"; + puts("tftp_client: writing last page"); + write_last_page(page2, FW_SLOT_2); + } + else if (event == TFTP_PEER_ERROR) { + cause = "ERROR From Client"; + } + else if (event == TFTP_INTERN_ERROR) { + cause = "ERROR Internal Server Error"; + } + + /* print the transfer result to the console */ + if (msg != NULL) { + printf("tftp_client: %s: %s\n", cause, msg); + } + else { + printf("tftp_client: %s\n", cause); + } +} + +static int _tftp_client_cmd(int argc, char * *argv) +{ + ipv6_addr_t ip; + const char *file_name = argv[2]; + tftp_mode_t mode = TTM_OCTET; + bool use_options = true; + + ipv6_addr_from_str(&ip, _tftp_default_host); + + if (argc >= 3 && argc <= 6) { + /* decode the action */ + if (strcmp(argv[1], "get") == 0) { + _tftp_action = TFTP_READ; + } + else if (strcmp(argv[1], "put") == 0) { + _tftp_action = TFTP_WRITE; + } + else { + return -1; + } + + /* get the transfer mode */ + if (argc >= 4) { + if (strcmp(argv[3], "octet") == 0) { + mode = TTM_OCTET; + } + else if (strcmp(argv[3], "ascii") == 0) { + mode = TTM_ASCII; + } + else if (strcmp(argv[3], "mail") == 0) { + mode = TTM_MAIL; + } + else { + puts("tftp: couldn't parse the TFTP transfer mode"); + return -1; + } + } + + /* decode if we must use the TFTP option extension or not */ + if (argc >= 5) { + if (strcmp(argv[4], "0") == 0) { + use_options = false; + } + else if (strcmp(argv[4], "1") == 0) { + use_options = true; + } + else { + puts("tftp: invalid options choose 0 or 1"); + return -1; + } + } + + /* decode the address */ + if (argc >= 6) { + if (!ipv6_addr_from_str(&ip, argv[5])) { + puts("tftp: invalid IP address"); + return -1; + } + } + } + else { + return -1; + } + + if (_tftp_action == TFTP_READ) { + puts("tftp: starting read request"); + + gnrc_tftp_client_read(&ip, file_name, mode, _tftp_client_data_cb, + _tftp_client_start_cb, _tftp_client_stop_cb, use_options); + } + else if (_tftp_action == TFTP_WRITE) { + puts("tftp: starting write request"); + + gnrc_tftp_client_write(&ip, file_name, mode, _tftp_client_data_cb, + sizeof(_tftp_client_hello), _tftp_client_stop_cb, use_options); + } + + return 0; +} + +/** + * @brief start the TFTP server by creating a thread + */ +int tftp_client_cmd(int argc, char * *argv) +{ + if (_tftp_client_cmd(argc, argv) < 0) { + printf("usage: %s \n" + "\t \n", argv[0]); + } + + return 0; +} diff --git a/examples/tftp_ota_programming/tftp_server.c b/examples/tftp_ota_programming/tftp_server.c new file mode 100644 index 000000000000..f29a47fbd0a7 --- /dev/null +++ b/examples/tftp_ota_programming/tftp_server.c @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2015 Engineering-Spirit + * + * 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 Demonstrating the sending and receiving of data via the TFTP server + * + * @author Nick van IJzendoorn + * + * @} + */ + +#include +#include +#include +#include + +#include "thread.h" +#include "net/gnrc/tftp.h" + +/* the message queues */ +#define TFTP_QUEUE_SIZE (4) +static msg_t _tftp_msg_queue[TFTP_QUEUE_SIZE]; + +/* allocate the stack */ +char _tftp_stack[THREAD_STACKSIZE_MAIN + THREAD_EXTRA_STACKSIZE_PRINTF]; + +/* default server text which can be received */ +static const char _tftp_server_hello[] = "Hello world,\n" + "\n" + "Welcome to the wonderful world of IoT and embedded systems.\n" + "\n" + "Enjoy the RIOT-OS\n"; + +static tftp_action_t _tftp_action; + +/** + * @brief called at every transcation start + */ +static bool _tftp_server_start_cb(tftp_action_t action, tftp_mode_t mode, + const char *file_name, size_t *len) +{ + /* translate the mode */ + const char *str_mode = "ascii"; + + if (mode == TTM_OCTET) { + str_mode = "bin"; + } + else if (mode == TTM_MAIL) { + str_mode = "mail"; + } + + /* translate the action */ + const char *str_action = "read"; + if (action == TFTP_WRITE) { + str_action = "write"; + } + + /* display the action being performed */ + printf("tftp_server: %s %s %s:%lu\n", str_mode, str_action, file_name, (unsigned long)*len); + + /* return the length of the text, if this is an read action */ + if (action == TFTP_READ) { + *len = sizeof(_tftp_server_hello); + } + + /* remember the action of the current transfer */ + _tftp_action = action; + + /* we accept the transfer to take place so we return true */ + return true; +} + +/** + * @brief called to get or put data, depending on the mode received by `_tftp_start_cb(action, ...)` + */ +static int _tftp_server_data_cb(uint32_t offset, void *data, size_t data_len) +{ + char *c = (char *) data; + + /* if we are reading return the part of the data that is being requested */ + if (_tftp_action == TFTP_READ) { + /* calculate the length of the data block to transfer */ + if (offset + data_len > sizeof(_tftp_server_hello)) { + data_len -= (offset + data_len) - sizeof(_tftp_server_hello); + } + + /* copy the block to the output buffer */ + memcpy(data, _tftp_server_hello + offset, data_len); + } + else { + /* we received a data block which we output to the console */ + printf("\n -- SERVER DATA --\n%.*s\n -- SERVER DATA --\n", (int)data_len, c); + } + + /* return the length of the data block */ + return data_len; +} + +/** + * @brief the transfer has stopped, see the event argument to determined if it was successful + * or not. + */ +static void _tftp_server_stop_cb(tftp_event_t event, const char *msg) +{ + /* decode the stop event received */ + const char *cause = "UNKOWN"; + + if (event == TFTP_SUCCESS) { + cause = "SUCCESS"; + } + else if (event == TFTP_PEER_ERROR) { + cause = "ERROR From Client"; + } + else if (event == TFTP_INTERN_ERROR) { + cause = "ERROR Internal Server Error"; + } + + /* print the transfer result to the console */ + printf("tftp_server: %s: %s\n", cause, (msg == NULL) ? "NULL" : msg); +} + +/** + * @brief the TFTP server thread + */ +void *tftp_server_wrapper(void *arg) +{ + (void)arg; + + /* A message queue is needed to register for incoming packets */ + msg_init_queue(_tftp_msg_queue, TFTP_QUEUE_SIZE); + + /* inform the user */ + puts("tftp_server: Starting TFTP service at port 69"); + + /* run the TFTP server */ + gnrc_tftp_server(_tftp_server_data_cb, _tftp_server_start_cb, _tftp_server_stop_cb, true); + + /* the TFTP server has been stopped */ + puts("tftp_server: Stopped TFTP service"); + + return NULL; +} + +/** + * @brief start the TFTP server by creating a thread + */ +void tftp_server_start(void) +{ + thread_create(_tftp_stack, sizeof(_tftp_stack), + 1, THREAD_CREATE_WOUT_YIELD | THREAD_CREATE_STACKTEST, + tftp_server_wrapper, NULL, "TFTP Server"); +} + +/** + * @brief stop the TFTP server by sending a message to the thread + */ +void tftp_server_stop(void) +{ + gnrc_tftp_server_stop(); +} + +int tftp_server_cmd(int argc, char * *argv) +{ + switch (argc) { + case 2: + if (strcmp(argv[1], "start") == 0) { + tftp_server_start(); + return 0; + } + else if (strcmp(argv[1], "stop") == 0) { + tftp_server_stop(); + return 0; + } + /* no break */ + + default: + printf("usage: %s [start|stop]\n", argv[0]); + return 0; + } + + 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 143b65e2b177..d4d9522ec5ad 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -129,6 +129,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..e2e402d6f09b --- /dev/null +++ b/sys/fw_slots/fw_slots.c @@ -0,0 +1,540 @@ +/* + * 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" +#include "crypto/keys/ed25519/key_ed25519_pub.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); +} + +static int check_slot(uint8_t 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; + } + else { + return 0; + } + + return 0; +} + +int fw_slots_validate_int_slot(uint8_t fw_slot) +{ + firmware_metadata_t fw_metadata; + uint8_t hash[SIGN_LEN]; + unsigned long long mlen; + int res, result; + + if (check_slot(fw_slot) == -1) { + return -1; + } + + res = fw_slots_get_int_metadata(fw_slots_get_slot_page(fw_slot), &fw_metadata); + + if (res == -1) { + DEBUG("[fw_slots] Cannot get metadata from slot %d\n", fw_slot); + return res; + } + + result = crypto_sign_open(hash, &mlen, fw_metadata.shash, SIGN_LEN, key_ed25519_pub); + + if (result == -1) { + DEBUG("[fw_slots] Signature check failed!\n"); + return result; + } + + for (int i = 0; i < mlen; i++) { + if (hash[i] != fw_metadata.hash[i]) { + DEBUG("[fw_slots] signature verification failed!\n"); + return -1; + } + } + + 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); +} + +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 (check_slot(fw_slot) == -1) { + 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, res = 0; + + if (check_slot(fw_slot) == -1) { + 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 overridden 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! %#x\n", hash[i]); + res = -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 res; +} + +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 (check_slot(fw_slot) == -1) { + 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/crypto/keys/ed25519/key_ed25519_pub.h b/sys/include/crypto/keys/ed25519/key_ed25519_pub.h new file mode 100644 index 000000000000..4d305ee597bf --- /dev/null +++ b/sys/include/crypto/keys/ed25519/key_ed25519_pub.h @@ -0,0 +1,6 @@ +unsigned char key_ed25519_pub[] = { + 0x2c, 0x01, 0x13, 0xbf, 0xbc, 0x55, 0xa7, 0x29, 0x51, 0x63, 0xd3, 0xb2, + 0x2f, 0xb8, 0x70, 0xe8, 0x22, 0x24, 0xb2, 0xc9, 0xe1, 0x93, 0x26, 0xb3, + 0xcc, 0xf1, 0xfd, 0xc5, 0x77, 0x4f, 0xe4, 0x20 +}; +unsigned int key_ed25519_pub_len = 32; diff --git a/sys/include/fw_slots.h b/sys/include/fw_slots.h new file mode 100644 index 000000000000..a00f3dcf862e --- /dev/null +++ b/sys/include/fw_slots.h @@ -0,0 +1,298 @@ +/* + * 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" +#include "tweetnacl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief FW_METADATA_LENGTH: + * This is just the size of the firmware_metadata_t struct, which is + * 4-byte aligned. We use 140 bytes currently, so this struct will be + * 140 bytes. + */ +#define FW_METADATA_LENGTH sizeof(firmware_metadata_t) + +/** + * @brief SIGN_LEN: + * Length for signed hash using ed25519 + */ +#define SIGN_LEN (SHA256_DIGEST_LENGTH + crypto_sign_BYTES) + +/** + * @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 */ +/** @} */