mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
sys/riotboot: add flashwrite submodule
This commit is contained in:
parent
cff2b5e23f
commit
ae35860510
14
Makefile.dep
14
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
|
||||
|
||||
|
174
sys/include/riotboot/flashwrite.h
Normal file
174
sys/include/riotboot/flashwrite.h
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Kaspar Schleiser <kaspar@schleiser.de>
|
||||
*
|
||||
* 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 <kaspar@schleiser.de>
|
||||
* @author Koen Zandberg <koen@bergzand.net>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#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 */
|
@ -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
|
||||
|
126
sys/riotboot/flashwrite.c
Normal file
126
sys/riotboot/flashwrite.c
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Inria
|
||||
* 2019 Kaspar Schleiser <kaspar@schleiser.de>
|
||||
*
|
||||
* 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 <kaspar@schleiser.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user