mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2026-01-01 01:41:18 +01:00
Merge pull request #17943 from fjmolinas/pr_suit_vfs_storage
sys/suit/storage/vfs: initial import
This commit is contained in:
commit
6019925c75
@ -59,6 +59,7 @@ DEFAULT_MODULE += test_utils_interactive_sync
|
||||
|
||||
ifeq ($(BOARD),native)
|
||||
USE_ETHOS ?= 0
|
||||
IFACE ?= tapbr0
|
||||
# Configure two RAM regions with 2K each
|
||||
CFLAGS += -DCONFIG_SUIT_STORAGE_RAM_REGIONS=2 -DCONFIG_SUIT_STORAGE_RAM_SIZE=2048
|
||||
endif
|
||||
@ -82,9 +83,9 @@ ifeq (1,$(USE_ETHOS))
|
||||
# $ cd dist/tools/ethos; sudo ./setup_network.sh riot0 2001:db8::0/64
|
||||
#
|
||||
#... in another shell and keep it running.
|
||||
export TAP ?= riot0
|
||||
IFACE ?= riot0
|
||||
TERMPROG = $(RIOTTOOLS)/ethos/ethos
|
||||
TERMFLAGS = $(TAP) $(PORT)
|
||||
TERMFLAGS = $(IFACE) $(PORT)
|
||||
endif
|
||||
|
||||
# Ensure both slot bin files are always generated and linked to avoid compiling
|
||||
@ -105,8 +106,16 @@ TESTRUNNER_RESET_AFTER_TERM ?= 1
|
||||
# with ed25519 support.
|
||||
TEST_ON_CI_BLACKLIST = all
|
||||
|
||||
# Add custom SUIT targets
|
||||
include $(CURDIR)/Makefile.suit.custom
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
|
||||
# export IFACE for test
|
||||
$(call target-export-variables,test-with-config test-with-config/check-config,IFACE)
|
||||
# export BOARD
|
||||
$(call target-export-variables,test-with-config test-with-config/check-config,BOARD)
|
||||
|
||||
# allow to use large blocks to utilize large MTUs (802.15.4g, Ethernet, WiFi)
|
||||
LARGE_BLOCKS ?= 0
|
||||
ifeq (1, $(LARGE_BLOCKS))
|
||||
|
||||
@ -1,6 +1,14 @@
|
||||
ifeq ($(BOARD),native)
|
||||
USEMODULE += suit_storage_ram
|
||||
USEMODULE += netdev_default
|
||||
# Use VFS storage for native
|
||||
USEMODULE += suit_storage_vfs
|
||||
## Use VFS
|
||||
USEMODULE += vfs
|
||||
## Use default storage
|
||||
USEMODULE += vfs_default
|
||||
## Auto-format on mount
|
||||
USEMODULE += vfs_auto_format
|
||||
else
|
||||
USEMODULE += suit_storage_flashwrite
|
||||
endif
|
||||
|
||||
31
examples/suit_update/Makefile.suit.custom
Normal file
31
examples/suit_update/Makefile.suit.custom
Normal file
@ -0,0 +1,31 @@
|
||||
# Required variables defined in riotboot.inc.mk or Makefile.include
|
||||
BINDIR_APP = $(CURDIR)/bin/$(BOARD)/$(APPLICATION)
|
||||
$(BINDIR_APP): $(CLEAN)
|
||||
$(Q)mkdir -p $(BINDIR_APP)
|
||||
|
||||
# Include to be able to use memoized
|
||||
include $(RIOTBASE)/makefiles/utils/variables.mk
|
||||
EPOCH = $(call memoized,EPOCH,$(shell date +%s))
|
||||
APP_VER ?= $(EPOCH)
|
||||
|
||||
# Default addressing if following README.native.md
|
||||
ifeq ($(BOARD),native)
|
||||
SUIT_CLIENT ?= [2001:db8::2]
|
||||
SUIT_COAP_SERVER ?= [2001:db8::1]
|
||||
$(call target-export-variables,test-with-config,SUIT_COAP_SERVER)
|
||||
endif
|
||||
|
||||
ifeq ($(BOARD),native)
|
||||
# Set settings for publishing fake fw payloads to native
|
||||
SUIT_NATIVE_PAYLOAD ?= "AABBCCDD"
|
||||
SUIT_NATIVE_PAYLOAD_BIN ?= $(BINDIR_APP)/fw.$(APP_VER).bin
|
||||
# Make sure it is built
|
||||
BUILD_FILES += $(SUIT_NATIVE_PAYLOAD_BIN)
|
||||
|
||||
$(SUIT_NATIVE_PAYLOAD_BIN): $(BINDIR_APP)
|
||||
$(Q)echo $(SUIT_NATIVE_PAYLOAD) > $@
|
||||
|
||||
SUIT_FW_STORAGE ?= /nvm0/SLOT0.TXT
|
||||
SUIT_MANIFEST_PAYLOADS ?= $(SUIT_NATIVE_PAYLOAD_BIN)
|
||||
SUIT_MANIFEST_SLOTFILES ?= $(SUIT_NATIVE_PAYLOAD_BIN):0:$(SUIT_FW_STORAGE)
|
||||
endif
|
||||
@ -48,7 +48,7 @@ $ dist/tools/suit/suit-manifest-generator/bin/suit-tool sign -k keys/default.pem
|
||||
|
||||
5. Pull the manifest from the native instance:
|
||||
```
|
||||
> suit coap://[2001:db8::1]/suit_manifest.signed
|
||||
> suit fetch coap://[2001:db8::1]/suit_manifest.signed
|
||||
```
|
||||
|
||||
6. Verify the content of the storage location
|
||||
@ -172,14 +172,18 @@ the payloads.
|
||||
lsstorage
|
||||
RAM slot 0: ".ram.0"
|
||||
RAM slot 1: ".ram.1"
|
||||
VFS 0: "/nvm0/SLOT0.txt"
|
||||
VFS 1: "/nvm0/SLOT1.txt"
|
||||
```
|
||||
|
||||
As shown above, two storage locations are available, `.ram.0` and `.ram.1`.
|
||||
While two slots are available, in this example only the content of the `.ram.0`
|
||||
slot will be updated.
|
||||
As shown above, four storage locations are available, RAM based storage: `.ram.0` and `.ram.1`
|
||||
as well as VFS based storage: `/nvm0/SLOT0.TXT` and `/nvm0/SLOT1.TXT`.
|
||||
While multiple slots are available, in this example only the content of the `.ram.0`
|
||||
slot will be updated, but the procedure is the same for any other slot, just replace
|
||||
`.ram.0
|
||||
|
||||
- The `storage_content` command can be used to display a hex dump command of one
|
||||
of the storage locations. It requires a location string, an offset and a
|
||||
of the RAM storage locations. It requires a location string, an offset and a
|
||||
number of bytes to print:
|
||||
|
||||
```console
|
||||
@ -188,6 +192,13 @@ slot will be updated.
|
||||
```
|
||||
As the storage location is empty on boot, nothing is printed.
|
||||
|
||||
For VFS based storage the `vfs` command can be used instead:
|
||||
```console
|
||||
> vfs r /nvm0/SLOT0.txt
|
||||
Error opening file "/nvm0/SLOT0.txt": -ENOENT
|
||||
```
|
||||
But as the file does not initially exist, nothing is printed.
|
||||
|
||||
### Generating the payload and manifest
|
||||
[generating-the-payload-and-manifest]: #generating-the-payload-and-manifest
|
||||
|
||||
@ -208,6 +219,12 @@ acts as a template for the real SUIT manifest. Within RIOT, the script
|
||||
$ dist/tools/suit/gen_manifest.py --urlroot coap://[2001:db8::1]/ --seqnr 1 -o suit.tmp coaproot/payload.bin:0:ram:0
|
||||
```
|
||||
|
||||
or for vfs storage:
|
||||
|
||||
```console
|
||||
$ dist/tools/suit/gen_manifest.py --urlroot coap://[2001:db8::1]/ --seqnr 1 -o suit.tmp coaproot/payload.bin:0:/nvm0/SLOT0.txt
|
||||
```
|
||||
|
||||
This generates a suit manifest template with the sequence number set to `1`, a
|
||||
payload that should be stored at slot offset zero in slot `.ram.0`. The url for
|
||||
the payload starts with `coap://[fe80::4049:bfff:fe60:db09]/`. Make sure to
|
||||
@ -294,7 +311,7 @@ command sequences in the manifest and download the payload when instructed to.
|
||||
The URL for the manifest can be supplied to the instance via the command line.
|
||||
|
||||
```console
|
||||
> suit coap://[2001:db8::1]/suit_manifest.signed
|
||||
> suit fetch coap://[2001:db8::1]/suit_manifest.signed
|
||||
```
|
||||
|
||||
The payload is the full URL to the signed manifest. The native instance should
|
||||
@ -338,6 +355,14 @@ same payload as suggested above was used, it should look like this:
|
||||
41414242434344440A
|
||||
```
|
||||
|
||||
The process can be done multiple times with both slot `.ram.0` and `.ram.1` and
|
||||
Or for vfs storage:
|
||||
|
||||
```
|
||||
> vfs r /nvm0/SLOT0.txt
|
||||
vfs r /nvm0/SLOT0.txt
|
||||
00000000: 4141 4242 4343 4444 0a AABBCCDD.
|
||||
-- EOF --
|
||||
```
|
||||
The process can be done multiple times for any of the slots and
|
||||
different payloads. Keep in mind that the sequence number is a strict
|
||||
monotonically number and must be increased after every update.
|
||||
|
||||
@ -33,6 +33,11 @@
|
||||
|
||||
#include "suit/storage.h"
|
||||
#include "suit/storage/ram.h"
|
||||
#ifdef BOARD_NATIVE
|
||||
#include "suit/storage/vfs.h"
|
||||
#include "xfa.h"
|
||||
#include "vfs_default.h"
|
||||
#endif
|
||||
|
||||
#ifdef MODULE_PERIPH_GPIO
|
||||
#include "periph/gpio.h"
|
||||
@ -48,6 +53,15 @@ static msg_t _nanocoap_server_msg_queue[NANOCOAP_SERVER_QUEUE_SIZE];
|
||||
#define MAIN_QUEUE_SIZE (8)
|
||||
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];
|
||||
|
||||
/* add handled storages */
|
||||
#if IS_USED(MODULE_SUIT_STORAGE_VFS)
|
||||
XFA_USE(char*, suit_storage_files_reg);
|
||||
#ifdef BOARD_NATIVE
|
||||
XFA(suit_storage_files_reg, 0) char* _slot0 = VFS_DEFAULT_DATA "/SLOT0.txt";
|
||||
XFA(suit_storage_files_reg, 1) char* _slot1 = VFS_DEFAULT_DATA "/SLOT1.txt";
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static void *_nanocoap_server_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
@ -162,6 +176,12 @@ static int cmd_lsstorage(int argc, char **argv)
|
||||
if (IS_ACTIVE(MODULE_SUIT_STORAGE_FLASHWRITE)) {
|
||||
puts("Flashwrite slot 0: \"\"\n");
|
||||
}
|
||||
#if IS_USED(MODULE_SUIT_STORAGE_VFS)
|
||||
for (unsigned i = 0; i < XFA_LEN(char **, suit_storage_files_reg); i++) {
|
||||
const char *filepath = (const char *)suit_storage_files_reg[i];
|
||||
printf("VFS %u: \"%s\"\n", i, filepath);
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -189,7 +209,8 @@ int main(void)
|
||||
cmd_print_current_slot(0, NULL);
|
||||
cmd_print_riotboot_hdr(0, NULL);
|
||||
#endif
|
||||
|
||||
/* initialize suit storage */
|
||||
suit_storage_init_all();
|
||||
/* start suit coap updater thread */
|
||||
suit_coap_run();
|
||||
|
||||
|
||||
@ -11,21 +11,39 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import re
|
||||
import random
|
||||
from ipaddress import (
|
||||
IPv6Address,
|
||||
IPv6Network,
|
||||
)
|
||||
|
||||
from testrunner import run
|
||||
from testrunner import utils
|
||||
|
||||
# Default test over loopback interface
|
||||
COAP_HOST = "[fd00:dead:beef::1]"
|
||||
COAP_HOST = os.getenv("SUIT_COAP_SERVER", "[fd00:dead:beef::1]")
|
||||
BOARD = os.getenv("BOARD", "samr21-xpro")
|
||||
|
||||
UPDATING_TIMEOUT = 10
|
||||
MANIFEST_TIMEOUT = 15
|
||||
|
||||
USE_ETHOS = int(os.getenv("USE_ETHOS", "1"))
|
||||
TAP = os.getenv("TAP", "riot0")
|
||||
IFACE = os.getenv("IFACE", "tapbr0")
|
||||
TMPDIR = tempfile.TemporaryDirectory()
|
||||
|
||||
|
||||
def get_iface_addr(iface):
|
||||
out = subprocess.check_output(["ip", "a", "s", "dev", iface]).decode()
|
||||
p = re.compile(
|
||||
r"inet6\s+(?P<global>[0-9a-fA-F:]+:[A-Fa-f:0-9]+/\d+)\s+" r"scope\s+global"
|
||||
)
|
||||
for line in out.splitlines():
|
||||
m = p.search(line)
|
||||
if m is not None:
|
||||
return m.group("global")
|
||||
return None
|
||||
|
||||
|
||||
def start_aiocoap_fileserver():
|
||||
aiocoap_process = subprocess.Popen(
|
||||
"exec aiocoap-fileserver %s" % TMPDIR.name, shell=True
|
||||
@ -51,7 +69,7 @@ def notify(coap_server, client_url, version=None):
|
||||
assert not subprocess.call(cmd)
|
||||
|
||||
|
||||
def publish(server_dir, server_url, app_ver, keys='default', latest_name=None):
|
||||
def publish(server_dir, server_url, app_ver, keys="default", latest_name=None):
|
||||
cmd = [
|
||||
"make",
|
||||
"suit/publish",
|
||||
@ -68,14 +86,15 @@ def publish(server_dir, server_url, app_ver, keys='default', latest_name=None):
|
||||
|
||||
|
||||
def wait_for_update(child):
|
||||
return child.expect([r"Fetching firmware \|[█ ]+\|\s+\d+\%",
|
||||
"Finalizing payload store"],
|
||||
timeout=UPDATING_TIMEOUT)
|
||||
return child.expect(
|
||||
[r"Fetching firmware \|[█ ]+\|\s+\d+\%", "Finalizing payload store"],
|
||||
timeout=UPDATING_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
def get_ipv6_addr(child):
|
||||
child.expect_exact('>')
|
||||
child.sendline('ifconfig')
|
||||
child.expect_exact(">")
|
||||
child.sendline("ifconfig")
|
||||
if USE_ETHOS == 0:
|
||||
# Get device global address
|
||||
child.expect(
|
||||
@ -87,10 +106,9 @@ def get_ipv6_addr(child):
|
||||
# Get device local address
|
||||
child.expect_exact("Link type: wired")
|
||||
child.expect(
|
||||
r"inet6 addr: (?P<lladdr>[0-9a-fA-F:]+:[A-Fa-f:0-9]+)"
|
||||
" scope: link VAL"
|
||||
r"inet6 addr: (?P<lladdr>[0-9a-fA-F:]+:[A-Fa-f:0-9]+)" " scope: link VAL"
|
||||
)
|
||||
addr = "{}%{}".format(child.match.group("lladdr").lower(), TAP)
|
||||
addr = "{}%{}".format(child.match.group("lladdr").lower(), IFACE)
|
||||
return addr
|
||||
|
||||
|
||||
@ -117,7 +135,20 @@ def get_reachable_addr(child):
|
||||
# Give some time for the network interface to be configured
|
||||
time.sleep(1)
|
||||
# Get address
|
||||
client_addr = get_ipv6_addr(child)
|
||||
if BOARD == "native":
|
||||
iface_addr = get_iface_addr(IFACE)
|
||||
network = IPv6Network(iface_addr, strict=False)
|
||||
client_addr = iface_addr
|
||||
while iface_addr == client_addr:
|
||||
client_addr = IPv6Address(
|
||||
random.randrange(
|
||||
int(network.network_address) + 1, int(network.broadcast_address) - 1
|
||||
)
|
||||
)
|
||||
child.sendline(f"ifconfig 5 add {client_addr}/{format(network.prefixlen)}")
|
||||
client_addr = format(client_addr)
|
||||
else:
|
||||
client_addr = get_ipv6_addr(child)
|
||||
# Verify address is reachable
|
||||
ping6(client_addr)
|
||||
return "[{}]".format(client_addr)
|
||||
@ -127,7 +158,7 @@ def seq_no(child):
|
||||
utils.test_utils_interactive_sync_shell(child, 5, 1)
|
||||
# get version of currently running image
|
||||
# "seq_no: 0x00000000"
|
||||
child.sendline('suit seq_no')
|
||||
child.sendline("suit seq_no")
|
||||
child.expect(r"seq_no: (?P<seq_no>0x[0-9a-fA-F:]+)\r\n")
|
||||
app_ver = int(child.match.group("seq_no"), 16)
|
||||
return app_ver
|
||||
@ -136,7 +167,7 @@ def seq_no(child):
|
||||
def running_slot(child):
|
||||
utils.test_utils_interactive_sync_shell(child, 5, 1)
|
||||
|
||||
child.sendline('current-slot')
|
||||
child.sendline("current-slot")
|
||||
child.expect(r"Running from slot (\d+)\r\n")
|
||||
slot = int(child.match.group(1))
|
||||
return slot
|
||||
@ -151,7 +182,7 @@ def _test_invalid_version(child, client, app_ver):
|
||||
|
||||
|
||||
def _test_invalid_signature(child, client, app_ver):
|
||||
publish(TMPDIR.name, COAP_HOST, app_ver + 1, 'invalid_keys')
|
||||
publish(TMPDIR.name, COAP_HOST, app_ver + 1, "invalid_keys")
|
||||
notify(COAP_HOST, client, app_ver + 1)
|
||||
child.expect_exact("suit_coap: trigger received")
|
||||
child.expect_exact("suit: verifying manifest signature")
|
||||
@ -166,25 +197,26 @@ def _test_successful_update(child, client, app_ver):
|
||||
child.expect_exact("suit_coap: trigger received")
|
||||
child.expect_exact("suit: verifying manifest signature")
|
||||
child.expect(
|
||||
r"riotboot_flashwrite: initializing update to target slot (\d+)\r\n",
|
||||
r"SUIT policy check OK.\r\n",
|
||||
timeout=MANIFEST_TIMEOUT,
|
||||
)
|
||||
target_slot = int(child.match.group(1))
|
||||
# Wait for update to complete
|
||||
while wait_for_update(child) == 0:
|
||||
pass
|
||||
# Check successful install
|
||||
child.expect_exact("Install correct payload")
|
||||
child.expect_exact("Install correct payload")
|
||||
|
||||
# Wait for reboot
|
||||
child.expect_exact("suit_coap: rebooting...")
|
||||
# Verify running slot
|
||||
current_slot = running_slot(child)
|
||||
assert target_slot == current_slot, "BOOTED FROM SAME SLOT"
|
||||
# Verify client is reachable and get address
|
||||
client = get_reachable_addr(child)
|
||||
# Wait for reboot on non-native BOARDs
|
||||
if BOARD != "native":
|
||||
child.expect_exact("suit_coap: rebooting...")
|
||||
# Verify client is reachable and get address
|
||||
client = get_reachable_addr(child)
|
||||
assert seq_no(child) == version
|
||||
|
||||
|
||||
def _test_suit_command_is_there(child):
|
||||
child.sendline('suit')
|
||||
child.sendline("suit")
|
||||
child.expect_exact("Usage: suit fetch <manifest url>")
|
||||
|
||||
|
||||
|
||||
36
examples/suit_update/tests-with-config/check-config.sh
Executable file
36
examples/suit_update/tests-with-config/check-config.sh
Executable file
@ -0,0 +1,36 @@
|
||||
#! /bin/sh
|
||||
#
|
||||
# check_config.sh
|
||||
# Copyright (C) 2021 Martine Lenders <mail@martine-lenders.eu>
|
||||
# Copyright (C) 2022 Inria
|
||||
#
|
||||
# Distributed under terms of the MIT license.
|
||||
#
|
||||
|
||||
ip link show dev "${IFACE}" > /dev/null
|
||||
RESULT=$?
|
||||
|
||||
if [ $RESULT -eq 0 ]; then
|
||||
if [ "${BOARD}" = "native" ]; then
|
||||
IFACE_IPV6_ADDR=$(ip -6 addr show dev "${IFACE}"| grep inet6 | \
|
||||
awk -F '[ \t]+|/' '{print $3}' | grep -v ^::1 | \
|
||||
grep -v ^fe80)
|
||||
if [ -n "${IFACE_IPV6_ADDR}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${BOARD}" = "native" ]; then
|
||||
echo "You may be able to create \"${IFACE}\" by using e.g." \
|
||||
"\`${RIOTTOOLS#${RIOTBASE}/}/tapsetup/tapsetup\`."
|
||||
echo "You can add a routable IPV6 address by using e.g." \
|
||||
"sudo ip address add 2001:db8::1/64 dev ${IFACE}"
|
||||
else
|
||||
echo "You may setup \"${IFACE}\" by using e.g." \
|
||||
"\`${RIOTTOOLS#${RIOTBASE}/}/ethos/setup_network.sh ${IFACE} 2001:db8::/64\`"
|
||||
fi
|
||||
|
||||
exit 1
|
||||
@ -870,6 +870,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
|
||||
|
||||
72
sys/include/suit/storage/vfs.h
Normal file
72
sys/include/suit/storage/vfs.h
Normal file
@ -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 <koen@bergzand.net>
|
||||
*/
|
||||
|
||||
#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 */
|
||||
/** @} */
|
||||
@ -22,6 +22,7 @@
|
||||
|
||||
#include "kernel_defines.h"
|
||||
#include "log.h"
|
||||
#include "xfa.h"
|
||||
|
||||
#include "suit.h"
|
||||
#include "suit/storage.h"
|
||||
@ -29,6 +30,8 @@
|
||||
#include "riotboot/flashwrite.h"
|
||||
#include "riotboot/slot.h"
|
||||
|
||||
XFA_USE(suit_storage_t, suit_storage_reg);
|
||||
|
||||
static inline suit_storage_flashwrite_t *_get_fw(suit_storage_t *storage)
|
||||
{
|
||||
return container_of(storage, suit_storage_flashwrite_t, storage);
|
||||
@ -234,8 +237,10 @@ static const suit_storage_driver_t suit_storage_flashwrite_driver = {
|
||||
.separator = '\0',
|
||||
};
|
||||
|
||||
suit_storage_flashwrite_t suit_storage_flashwrite = {
|
||||
static suit_storage_flashwrite_t suit_storage_flashwrite = {
|
||||
.storage = {
|
||||
.driver = &suit_storage_flashwrite_driver,
|
||||
},
|
||||
};
|
||||
|
||||
XFA(suit_storage_reg, 0) suit_storage_t* suit_storage_flashwrite_ptr = &suit_storage_flashwrite.storage;
|
||||
|
||||
@ -24,11 +24,14 @@
|
||||
#include "fmt.h"
|
||||
#include "kernel_defines.h"
|
||||
#include "log.h"
|
||||
#include "xfa.h"
|
||||
|
||||
#include "suit.h"
|
||||
#include "suit/storage.h"
|
||||
#include "suit/storage/ram.h"
|
||||
|
||||
XFA_USE(suit_storage_t, suit_storage_reg);
|
||||
|
||||
static inline suit_storage_ram_t *_get_ram(suit_storage_t *storage)
|
||||
{
|
||||
return container_of(storage, suit_storage_ram_t, storage);
|
||||
@ -227,3 +230,5 @@ suit_storage_ram_t suit_storage_ram = {
|
||||
.driver = &suit_storage_ram_driver,
|
||||
},
|
||||
};
|
||||
|
||||
XFA(suit_storage_reg, 0) suit_storage_t* suit_storage_ram_ptr = &suit_storage_ram.storage;
|
||||
|
||||
@ -25,32 +25,15 @@
|
||||
#include "suit.h"
|
||||
#include "suit/storage.h"
|
||||
|
||||
#ifdef MODULE_SUIT_STORAGE_FLASHWRITE
|
||||
#include "suit/storage/flashwrite.h"
|
||||
extern suit_storage_flashwrite_t suit_storage_flashwrite;
|
||||
#endif
|
||||
#include "xfa.h"
|
||||
|
||||
#ifdef MODULE_SUIT_STORAGE_RAM
|
||||
#include "suit/storage/ram.h"
|
||||
extern suit_storage_ram_t suit_storage_ram;
|
||||
#endif
|
||||
|
||||
static suit_storage_t *reg[] = {
|
||||
#ifdef MODULE_SUIT_STORAGE_FLASHWRITE
|
||||
&suit_storage_flashwrite.storage,
|
||||
#endif
|
||||
#ifdef MODULE_SUIT_STORAGE_RAM
|
||||
&suit_storage_ram.storage,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const size_t reg_size = ARRAY_SIZE(reg);
|
||||
XFA_INIT(suit_storage_t*, suit_storage_reg);
|
||||
|
||||
suit_storage_t *suit_storage_find_by_id(const char *id)
|
||||
{
|
||||
for (size_t i = 0; i < reg_size; i++) {
|
||||
if (suit_storage_has_location(reg[i], id)) {
|
||||
return reg[i];
|
||||
for (size_t i = 0; i < XFA_LEN(suit_storage_t*, suit_storage_reg); i++) {
|
||||
if (suit_storage_has_location(suit_storage_reg[i], id)) {
|
||||
return suit_storage_reg[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
@ -58,22 +41,22 @@ suit_storage_t *suit_storage_find_by_id(const char *id)
|
||||
|
||||
void suit_storage_init_all(void)
|
||||
{
|
||||
for (size_t i = 0; i < reg_size; i++) {
|
||||
suit_storage_init(reg[i]);
|
||||
for (size_t i = 0; i < XFA_LEN(suit_storage_t*, suit_storage_reg); i++) {
|
||||
suit_storage_init(suit_storage_reg[i]);
|
||||
}
|
||||
}
|
||||
|
||||
suit_storage_t *suit_storage_find_by_component(const suit_manifest_t *manifest,
|
||||
const suit_component_t *component)
|
||||
{
|
||||
for (size_t i = 0; i < reg_size; i++) {
|
||||
for (size_t i = 0; i < XFA_LEN(suit_storage_t*, suit_storage_reg); i++) {
|
||||
char name[CONFIG_SUIT_COMPONENT_MAX_NAME_LEN];
|
||||
if (suit_component_name_to_string(manifest, component,
|
||||
reg[i]->driver->separator,
|
||||
suit_storage_reg[i]->driver->separator,
|
||||
name, sizeof(name)) == SUIT_OK) {
|
||||
|
||||
if (suit_storage_has_location(reg[i], name)) {
|
||||
return reg[i];
|
||||
if (suit_storage_has_location(suit_storage_reg[i], name)) {
|
||||
return suit_storage_reg[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -85,9 +68,9 @@ int suit_storage_get_highest_seq_no(uint32_t *seq_no)
|
||||
uint32_t max_seq = 0;
|
||||
int res = SUIT_ERR_STORAGE;
|
||||
|
||||
for (size_t i = 0; i < reg_size; i++) {
|
||||
for (size_t i = 0; i < XFA_LEN(suit_storage_t*, suit_storage_reg); i++) {
|
||||
uint32_t seq_no = 0;
|
||||
if (suit_storage_get_seq_no(reg[i], &seq_no) == SUIT_OK) {
|
||||
if (suit_storage_get_seq_no(suit_storage_reg[i], &seq_no) == SUIT_OK) {
|
||||
res = SUIT_OK;
|
||||
if (seq_no > max_seq) {
|
||||
max_seq = seq_no;
|
||||
@ -100,8 +83,8 @@ int suit_storage_get_highest_seq_no(uint32_t *seq_no)
|
||||
|
||||
int suit_storage_set_seq_no_all(uint32_t seq_no)
|
||||
{
|
||||
for (size_t i = 0; i < reg_size; i++) {
|
||||
suit_storage_set_seq_no(reg[i], seq_no);
|
||||
for (size_t i = 0; i < XFA_LEN(suit_storage_t*, suit_storage_reg); i++) {
|
||||
suit_storage_set_seq_no(suit_storage_reg[i], seq_no);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
274
sys/suit/storage/vfs.c
Normal file
274
sys/suit/storage/vfs.c
Normal file
@ -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 <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#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;
|
||||
Loading…
x
Reference in New Issue
Block a user