diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 9b6e9c59a486..8da18ecd7dfd 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -865,6 +865,11 @@ ifneq (,$(filter suit_storage_flashwrite, $(USEMODULE))) USEMODULE += riotboot_flashwrite_verify_sha256 endif +ifneq (,$(filter suit_storage_vfs,$(USEMODULE))) + USEMODULE += vfs + USEMODULE += mtd +endif + ifneq (,$(filter suit_%,$(USEMODULE))) USEMODULE += suit endif diff --git a/sys/include/suit/storage/vfs.h b/sys/include/suit/storage/vfs.h new file mode 100644 index 000000000000..3d46bf0279ba --- /dev/null +++ b/sys/include/suit/storage/vfs.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 Inria + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ +/** + * @defgroup sys_suit_storage_vfs riotboot vfs storage backend + * @ingroup sys_suit_storage + * @brief SUIT riotboot firmware storage backend + * + * VFS storage can service different files mounted on the filesystem. Serviceable + * FILES must be registered: + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c} + * #include "suit/storage/vfs.h" + * #include "xfa.h" + * + * XFA_USE(char*, suit_storage_files_reg); + * XFA(suit_storage_files_reg, 0) char* _firmware_0 = VFS_DEFAULT_DATA "/FW0.TXT"; + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * Once registered its content may be securely updated via SUIT by specifying the + * "install-id" as the filepath. + * + * @{ + * + * @brief riotboot vfs storage backend functions for SUIT manifests + * @author Koen Zandberg + */ + +#ifndef SUIT_STORAGE_VFS_H +#define SUIT_STORAGE_VFS_H + +#include "suit.h" +#include "../../sys/include/vfs.h" +#include "vfs_default.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Storage location string separators + */ +#ifndef CONFIG_SUIT_STORAGE_MOUNT_POINT +#define CONFIG_SUIT_STORAGE_MOUNT_POINT VFS_DEFAULT_DATA +#endif + +/** + * @brief Storage location string separators + */ +#ifndef CONFIG_SUIT_STORAGE_SEQ_NO_LOCATION +#define CONFIG_SUIT_STORAGE_SEQ_NO_LOCATION (CONFIG_SUIT_STORAGE_MOUNT_POINT "/SEQNO.txt") +#endif + +/** + * @brief riotboot vfs SUIT storage context + */ +typedef struct { + suit_storage_t storage; /**< parent struct */ + const char **files; /**< storage file array */ + uint8_t active_region; /**< Active file idx to write to */ +} suit_storage_vfs_t; + +#ifdef __cplusplus +} +#endif + +#endif /* SUIT_STORAGE_VFS_H */ +/** @} */ diff --git a/sys/suit/storage/vfs.c b/sys/suit/storage/vfs.c new file mode 100644 index 000000000000..b2a540034e8b --- /dev/null +++ b/sys/suit/storage/vfs.c @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2022 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 sys_suit_storage + * @{ + * + * @file + * @brief SUIT vfs storage module implementation + * + * + * @} + */ +#include +#include +#include + +#include "board.h" + +#include "xfa.h" +#include "fmt.h" +#include "kernel_defines.h" +#include "log.h" + +#include "vfs.h" +#include "suit.h" +#include "suit/storage.h" +#include "suit/storage/vfs.h" + +XFA_USE(suit_storage_t, suit_storage_reg); +XFA_INIT(char *, suit_storage_files_reg); + +static inline suit_storage_vfs_t *_get_vfs(suit_storage_t *storage) +{ + return container_of(storage, suit_storage_vfs_t, storage); +} + +static inline const suit_storage_vfs_t *_get_vfs_const(const suit_storage_t *storage) +{ + return container_of(storage, suit_storage_vfs_t, storage); +} + +static inline const char *_get_active_file(suit_storage_vfs_t *vfs) +{ + return vfs->files[vfs->active_region]; +} + +static int _vfs_update_seq_no(uint32_t seq_no) +{ + char buf[16]; + uint32_t sequence_no = 0; + int res = SUIT_ERR_SEQUENCE_NUMBER; + + int fd = vfs_open(CONFIG_SUIT_STORAGE_SEQ_NO_LOCATION, O_RDWR | O_CREAT, 0); + if (fd < 0) { + LOG_INFO("ERROR: failed to open %s\n", CONFIG_SUIT_STORAGE_SEQ_NO_LOCATION); + return res; + } + + if (vfs_read(fd, buf, strlen("4294967295")) > 0) { + sequence_no = strtoul(buf, NULL, 0); + } + + if (sequence_no < seq_no) { + ssize_t len = fmt_u32_dec(buf, seq_no); + vfs_lseek(fd, 0, SEEK_SET); + if (vfs_write(fd, buf, len) == len) { + res = SUIT_OK; + LOG_DEBUG("Stored sequence number: %" PRIu32 "\n", seq_no); + } + else { + LOG_INFO("ERROR: failed to write seq_no %" PRIu32 " to %s\n", seq_no, + CONFIG_SUIT_STORAGE_SEQ_NO_LOCATION); + } + } + vfs_close(fd); + return res; +} + +static int _vfs_init(suit_storage_t *storage) +{ + suit_storage_vfs_t *vfs = _get_vfs(storage); + + vfs->files = (const char **)suit_storage_files_reg; + + return SUIT_OK; +} + +static int _vfs_start(suit_storage_t *storage, const suit_manifest_t *manifest, + size_t len) +{ + (void)manifest; + (void)len; + (void)storage; + + return SUIT_OK; +} + +static int _vfs_write(suit_storage_t *storage, const suit_manifest_t *manifest, + const uint8_t *buf, size_t offset, size_t len) +{ + (void)manifest; + suit_storage_vfs_t *vfs = _get_vfs(storage); + const char *filepath = _get_active_file(vfs); + + int fd = vfs_open(filepath, O_RDWR | O_CREAT, 0); + + /* seek to the given offset */ + int rc = vfs_lseek(fd, offset, SEEK_SET); + + if (rc < 0) { + return rc; + } + else if ((size_t)rc != offset) { + return SUIT_ERR_STORAGE_EXCEEDED; + } + + /* write all bytes to the file */ + rc = vfs_write(fd, buf, len); + if (rc < 0) { + return rc; + } + else if ((size_t)rc != len) { + return SUIT_ERR_STORAGE_EXCEEDED; + } + + vfs_close(fd); + return SUIT_OK; +} + +static int _vfs_finish(suit_storage_t *storage, const suit_manifest_t *manifest) +{ + (void)manifest; + (void)storage; + return SUIT_OK; +} + +static int _vfs_install(suit_storage_t *storage, const suit_manifest_t *manifest) +{ + (void)storage; + return _vfs_update_seq_no(manifest->seq_number); +} + +static int _vfs_erase(suit_storage_t *storage) +{ + suit_storage_vfs_t *vfs = _get_vfs(storage); + const char *filepath = _get_active_file(vfs); + + vfs_unlink(filepath); + return SUIT_OK; +} + +static int _vfs_read(suit_storage_t *storage, uint8_t *buf, size_t offset, + size_t len) +{ + suit_storage_vfs_t *vfs = _get_vfs(storage); + const char *filepath = _get_active_file(vfs); + + int fd = vfs_open(filepath, O_RDWR | O_CREAT, 0); + + /* seek to the given offset */ + int rc = vfs_lseek(fd, offset, SEEK_SET); + + if (rc < 0) { + return rc; + } + else if ((size_t)rc != offset) { + return SUIT_ERR_STORAGE; + } + + /* read from file into the buffer */ + rc = vfs_read(fd, buf, len); + if (rc < 0) { + return rc; + } + else if ((size_t)rc != len) { + return SUIT_ERR_STORAGE; + } + + vfs_close(fd); + + return SUIT_OK; +} + +static bool _get_region_by_string(const char *location, uint32_t *val) +{ + for (size_t i = 0; i < XFA_LEN(char **, suit_storage_files_reg); i++) { + const char *filepath = (const char *)suit_storage_files_reg[i]; + if (strncmp(filepath, location, strlen(filepath)) == 0) { + *val = i; + return true; + } + } + + return false; +} + +static int _vfs_set_active_location(suit_storage_t *storage, + const char *location) +{ + suit_storage_vfs_t *vfs = _get_vfs(storage); + uint32_t region = 0; + + if (!_get_region_by_string(location, ®ion)) { + return -1; + } + + vfs->active_region = region; + + return 0; +} + +static bool _vfs_has_location(const suit_storage_t *storage, const char *location) +{ + (void)storage; + uint32_t region = 0; + + return _get_region_by_string(location, ®ion); +} + +static int _vfs_get_seq_no(const suit_storage_t *storage, uint32_t *seq_no) +{ + (void)storage; + + char buf[16]; + + int fd = vfs_open(CONFIG_SUIT_STORAGE_SEQ_NO_LOCATION, O_RDWR | O_CREAT, 0); + if (fd < 0) { + LOG_INFO("ERROR: failed to open %s\n", CONFIG_SUIT_STORAGE_SEQ_NO_LOCATION); + return SUIT_ERR_SEQUENCE_NUMBER; + } + + if (vfs_read(fd, buf, strlen("4294967295")) > 0) { + *seq_no = strtoul(buf, NULL, 0); + } + LOG_INFO("Retrieved sequence number: %" PRIu32 "\n", *seq_no); + vfs_close(fd); + return SUIT_OK; +} + +static int _vfs_set_seq_no(suit_storage_t *storage, uint32_t seq_no) +{ + (void)storage; + + return _vfs_update_seq_no(seq_no); +} + +const suit_storage_driver_t suit_storage_vfs_driver = { + .init = _vfs_init, + .start = _vfs_start, + .write = _vfs_write, + .finish = _vfs_finish, + .read = _vfs_read, + .install = _vfs_install, + .erase = _vfs_erase, + .set_active_location = _vfs_set_active_location, + .has_location = _vfs_has_location, + .get_seq_no = _vfs_get_seq_no, + .set_seq_no = _vfs_set_seq_no, + .separator = '\0', +}; + +suit_storage_vfs_t suit_storage_vfs = { + .storage = { + .driver = &suit_storage_vfs_driver, + }, +}; + +XFA(suit_storage_reg, 0) suit_storage_t *suit_storage_vfs_ptr = &suit_storage_vfs.storage;