From ae35860510915461544384ccde57d9455095f18d Mon Sep 17 00:00:00 2001 From: Kaspar Schleiser Date: Mon, 11 Mar 2019 14:28:17 +0100 Subject: [PATCH] sys/riotboot: add flashwrite submodule --- Makefile.dep | 14 ++- sys/include/riotboot/flashwrite.h | 174 ++++++++++++++++++++++++++++++ sys/riotboot/Makefile | 2 + sys/riotboot/flashwrite.c | 126 ++++++++++++++++++++++ 4 files changed, 311 insertions(+), 5 deletions(-) create mode 100644 sys/include/riotboot/flashwrite.h create mode 100644 sys/riotboot/flashwrite.c diff --git a/Makefile.dep b/Makefile.dep index ae9619f5d0..58879065a8 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -812,6 +812,15 @@ ifneq (,$(filter uuid,$(USEMODULE))) USEMODULE += fmt endif +ifneq (,$(filter riotboot_flashwrite, $(USEMODULE))) + USEMODULE += riotboot_slot + FEATURES_REQUIRED += periph_flashpage +endif + +ifneq (,$(filter riotboot_slot, $(USEMODULE))) + USEMODULE += riotboot_hdr +endif + ifneq (,$(filter riotboot_hdr, $(USEMODULE))) USEMODULE += checksum USEMODULE += riotboot @@ -821,11 +830,6 @@ endif ifneq (,$(filter periph_gpio_irq,$(USEMODULE))) FEATURES_REQUIRED += periph_gpio endif - -ifneq (,$(filter riotboot_slot, $(USEMODULE))) - USEMODULE += riotboot_hdr -endif - # always select gpio (until explicit dependencies are sorted out) FEATURES_OPTIONAL += periph_gpio diff --git a/sys/include/riotboot/flashwrite.h b/sys/include/riotboot/flashwrite.h new file mode 100644 index 0000000000..29c1c51a2e --- /dev/null +++ b/sys/include/riotboot/flashwrite.h @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2018 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_riotboot_flashwrite riotboot flash writer + * @ingroup sys + * @{ + * + * @file + * @brief riotboot flash writing module + * + * This module provides an API to reliably write a firmware image to flash. + * + * The API is similar to stream hashing functions: + * + * 1. initialize state structure using riotboot_flashwrite_init() + * 2. put some data using riotboot_flashwrite_putbytes() + * 3. repeat 2. until all data has been received + * 4. finish update using riotboot_flashwrite_finish() + * + * The module will *not* automatically reboot after an image has been + * successfully written. + * + * Under the hood, the module tries to abstract page sizes for writing the image + * to flash. Verification of the image is left to the caller. + * If the data is not correctly written, riotboot_put_bytes() will + * return -1. + * + * The module makes sure that at no point in time an invalid image is bootable. + * The algorithm for that makes use of the bootloader verifying checksum and + * works as follows: + * + * 1. erase first block (making its checksum invalid) + * 2. write image starting at second block + * 3. write first block + * + * @author Kaspar Schleiser + * @author Koen Zandberg + * + * @} + */ + +#ifndef RIOTBOOT_FLASHWRITE_H +#define RIOTBOOT_FLASHWRITE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "riotboot/slot.h" +#include "periph/flashpage.h" + +/** + * @brief firmware update state structure + */ +typedef struct { + int target_slot; /**< update targets this slot */ + size_t offset; /**< update is at this position */ + unsigned flashpage; /**< update is at this flashpage */ + uint8_t flashpage_buf[FLASHPAGE_SIZE]; /**< flash writing buffer */ +} riotboot_flashwrite_t; + +/** + * @brief Amount of bytes to skip at initial write of first page + */ +#define RIOTBOOT_FLASHWRITE_SKIPLEN sizeof(RIOTBOOT_MAGIC) + +/** + * @brief Initialize firmware update (raw version) + * + * Allows setting the initial offset to @p offset, which would mean that the + * first bytes passed in via @ref riotboot_flashwrite_putbytes() will be + * written to (slot_start + offset). + * + * @note offset *should* be <= FLASHPAGE_SIZE, otherwise the results are + * undefined. + * + * @param[in,out] state ptr to preallocated state structure + * @param[in] target_slot slot to write update into + * @param[in] offset Bytes offset to start write at + * + * @returns 0 on success, <0 otherwise + */ +int riotboot_flashwrite_init_raw(riotboot_flashwrite_t *state, int target_slot, + size_t offset); + +/** + * @brief Initialize firmware update (riotboot version) + * + * This function initializes a firmware update, skipping riotboot's magic + * number ("RIOT") by calling @ref riotboot_flashwrite_init_raw() with an + * offset of RIOTBOOT_FLASHWRITE_SKIPLEN (4). This ensures that riotboot will + * ignore the slot until the magic number has been restored, e.g., through @ref + * riotboot_flashwrite_finish(). + * + * @param[in,out] state ptr to preallocated state structure + * @param[in] target_slot slot to write update into + * + * @returns 0 on success, <0 otherwise + */ +static inline int riotboot_flashwrite_init(riotboot_flashwrite_t *state, + int target_slot) +{ + /* initialize state, but skip "RIOT" */ + return riotboot_flashwrite_init_raw(state, target_slot, + RIOTBOOT_FLASHWRITE_SKIPLEN); +} + +/** + * @brief Feed bytes into the firmware writer + * + * @note If the update has been initialized via @ref + * riotboot_flashwrite_init(), make sure to skip the first + * RIOTBOOT_FLASHWRITE_SKIPLEN bytes. + * + * @param[in,out] state ptr to previously used update state + * @param[in] bytes ptr to data + * @param[in] len len of data + * @param[in] more whether more data is comming + * + * @returns 0 on success, <0 otherwise + */ +int riotboot_flashwrite_putbytes(riotboot_flashwrite_t *state, + const uint8_t *bytes, size_t len, bool more); + +/** + * @brief Finish a firmware update (raw version) + * + * This function finishes a firmware update by re-writing the first header + * + * @param[in] state ptr to previously used state structure + * @param[in] bytes data to re-write in the first page + * @param[in] len size of data in bytes (must be <=FLASHPAGE_SIZE) + * + * @returns 0 on success, <0 otherwise + */ +int riotboot_flashwrite_finish_raw(riotboot_flashwrite_t *state, + const uint8_t *bytes, size_t len); + +/** + * @brief Finish a firmware update (riotboot version) + * + * This function finishes a firmware update by re-writing the first header so + * it includes riotboot's magic number ("RIOT"). + * + * @param[in] state ptr to previously used state structure + * + * @returns 0 on success, <0 otherwise + */ +static inline int riotboot_flashwrite_finish(riotboot_flashwrite_t *state) +{ + return riotboot_flashwrite_finish_raw(state, (const uint8_t *)"RIOT", + RIOTBOOT_FLASHWRITE_SKIPLEN); +} + +/** + * @brief Get a slot's size + * + * @param[in] state ptr to state struct + * + * @returns the size of the slot that @p state is configured to update to + */ +size_t riotboot_flashwrite_slotsize(const riotboot_flashwrite_t *state); + +#ifdef __cplusplus +} +#endif + +#endif /* RIOTBOOT_FLASHWRITE_H */ diff --git a/sys/riotboot/Makefile b/sys/riotboot/Makefile index 448312b317..b673cb4f82 100644 --- a/sys/riotboot/Makefile +++ b/sys/riotboot/Makefile @@ -1,7 +1,9 @@ SUBMODULES := 1 ifneq (,$(filter riotboot_slot,$(USEMODULE))) + CFLAGS += -DSLOT0_LEN=$(SLOT0_LEN) CFLAGS += -DSLOT0_OFFSET=$(SLOT0_OFFSET) + CFLAGS += -DSLOT1_LEN=$(SLOT1_LEN) CFLAGS += -DSLOT1_OFFSET=$(SLOT1_OFFSET) CFLAGS += -DNUM_SLOTS=$(NUM_SLOTS) endif diff --git a/sys/riotboot/flashwrite.c b/sys/riotboot/flashwrite.c new file mode 100644 index 0000000000..4e4b4a9153 --- /dev/null +++ b/sys/riotboot/flashwrite.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2019 Inria + * 2019 Kaspar Schleiser + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys_riotboot_flashwrite + * @{ + * + * @file + * @brief Firmware update helper functions + * + * @author Kaspar Schleiser + * + * @} + */ + +#include + +#include "riotboot/flashwrite.h" +#include "od.h" + +#define LOG_PREFIX "riotboot_flashwrite: " +#include "log.h" + +static inline size_t min(size_t a, size_t b) +{ + return a <= b ? a : b; +} + +size_t riotboot_flashwrite_slotsize(const riotboot_flashwrite_t *state) +{ + switch (state->target_slot) { + case 0: return SLOT0_LEN; +#if NUMOF_SLOTS==2 + case 1: return SLOT1_LEN; +#endif + default: return 0; + } +} + +int riotboot_flashwrite_init_raw(riotboot_flashwrite_t *state, int target_slot, + size_t offset) +{ + assert(offset <= FLASHPAGE_SIZE); + + LOG_INFO(LOG_PREFIX "initializing update to target slot %i\n", + target_slot); + + memset(state, 0, sizeof(riotboot_flashwrite_t)); + + state->offset = offset; + state->target_slot = target_slot; + state->flashpage = flashpage_page((void *)riotboot_slot_get_hdr(target_slot)); + + return 0; +} + +int riotboot_flashwrite_putbytes(riotboot_flashwrite_t *state, + const uint8_t *bytes, size_t len, bool more) +{ + LOG_INFO(LOG_PREFIX "processing bytes %u-%u\n", state->offset, state->offset + len - 1); + + while (len) { + size_t flashpage_pos = state->offset % FLASHPAGE_SIZE; + size_t flashpage_avail = FLASHPAGE_SIZE - flashpage_pos; + + size_t to_copy = min(flashpage_avail, len); + + memcpy(state->flashpage_buf + flashpage_pos, bytes, to_copy); + flashpage_avail -= to_copy; + + state->offset += to_copy; + flashpage_pos += to_copy; + bytes += to_copy; + len -= to_copy; + if ((!flashpage_avail) || (!more)) { + if (flashpage_write_and_verify(state->flashpage, state->flashpage_buf) != FLASHPAGE_OK) { + LOG_WARNING(LOG_PREFIX "error writing flashpage %u!\n", state->flashpage); + return -1; + } + state->flashpage++; + } + } + + return 0; +} + +int riotboot_flashwrite_finish_raw(riotboot_flashwrite_t *state, + const uint8_t *bytes, size_t len) +{ + assert(len <= FLASHPAGE_SIZE); + + int res = -1; + + uint8_t *slot_start = (uint8_t *)riotboot_slot_get_hdr(state->target_slot); + + uint8_t *firstpage; + + if (len < FLASHPAGE_SIZE) { + firstpage = state->flashpage_buf; + memcpy(firstpage, bytes, len); + memcpy(firstpage + len, + slot_start + len, + FLASHPAGE_SIZE - len); + } + else { + firstpage = (void *)bytes; + } + + int flashpage = flashpage_page((void *)slot_start); + if (flashpage_write_and_verify(flashpage, firstpage) != FLASHPAGE_OK) { + LOG_WARNING(LOG_PREFIX "re-flashing first block failed!\n"); + goto out; + } + + LOG_INFO(LOG_PREFIX "riotboot flashing completed successfully\n"); + res = 0; + +out: + return res; +}