mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-13 08:33:49 +01:00
pkg/xipfs: add XIPFS as vfs module
This commit is contained in:
parent
416db07ac5
commit
8dc500703b
@ -83,6 +83,12 @@ ifneq (,$(filter lwext%_vfs,$(USEMODULE)))
|
||||
USEMODULE += vfs
|
||||
endif
|
||||
|
||||
ifneq (,$(filter xipfs,$(USEMODULE)))
|
||||
USEPKG += xipfs
|
||||
USEMODULE += vfs
|
||||
USEMODULE += xipfs_fs
|
||||
endif
|
||||
|
||||
ifneq (,$(filter nimble_%,$(USEMODULE)))
|
||||
USEPKG += nimble
|
||||
endif
|
||||
|
||||
@ -141,6 +141,7 @@ Here is a quick overview of the examples available in the RIOT:
|
||||
| [twr_aloha](./advanced/twr_aloha/README.md) | This example allows testing different two-way ranging algorithms between two boards supporting a dw1000 device. This makes use of the uwb-core pkg. |
|
||||
| [senml_saul](./advanced/senml_saul/README.md) | This example demonstrates the usage of the SAUL (Sensor Actuator Uber Layer) module with the SenML (Sensor Measurement Lists) format. |
|
||||
| [opendsme](./advanced/opendsme/README.md) | This example demonstrates the usage of the OpenDSME module in RIOT. |
|
||||
| [xipfs](./advanced/xipfs/README.md) | This example demonstrates the usage of XIPFS for creating and executing an executable file. |
|
||||
|
||||
## Examples from Guides
|
||||
|
||||
|
||||
32
examples/advanced/xipfs/Makefile
Normal file
32
examples/advanced/xipfs/Makefile
Normal file
@ -0,0 +1,32 @@
|
||||
# name of your application
|
||||
APPLICATION = xipfs
|
||||
|
||||
# If no BOARD is found in the environment, use this default:
|
||||
BOARD ?= dwm1001
|
||||
|
||||
# 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:
|
||||
DEVELHELP ?= 1
|
||||
|
||||
# Change this to 0 show compiler invocation lines by default:
|
||||
QUIET ?= 1
|
||||
|
||||
# XIPFS is currently not compatible with the llvm toolchain
|
||||
TOOLCHAINS_BLACKLIST += llvm
|
||||
|
||||
BLOBS += hello-world.fae
|
||||
|
||||
# Modules to include:
|
||||
USEMODULE += shell
|
||||
USEMODULE += shell_cmds_default
|
||||
USEMODULE += ps
|
||||
USEMODULE += saul_default
|
||||
|
||||
# Use xipfs file system
|
||||
USEMODULE += xipfs
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
24
examples/advanced/xipfs/Makefile.ci
Normal file
24
examples/advanced/xipfs/Makefile.ci
Normal file
@ -0,0 +1,24 @@
|
||||
BOARD_INSUFFICIENT_MEMORY := \
|
||||
blackpill-stm32f103c8 \
|
||||
bluepill-stm32f030c8 \
|
||||
bluepill-stm32f103c8 \
|
||||
i-nucleo-lrwan1 \
|
||||
nucleo-c031c6 \
|
||||
nucleo-f030r8 \
|
||||
nucleo-f031k6 \
|
||||
nucleo-f042k6 \
|
||||
nucleo-f302r8 \
|
||||
nucleo-f303k8 \
|
||||
nucleo-f334r8 \
|
||||
nucleo-l011k4 \
|
||||
nucleo-l031k6 \
|
||||
nucleo-l053r8 \
|
||||
samd10-xmini \
|
||||
slstk3400a \
|
||||
stk3200 \
|
||||
stm32f030f4-demo \
|
||||
stm32f0discovery \
|
||||
stm32g0316-disco \
|
||||
stm32l0538-disco \
|
||||
weact-g030f6 \
|
||||
#
|
||||
61
examples/advanced/xipfs/README.md
Normal file
61
examples/advanced/xipfs/README.md
Normal file
@ -0,0 +1,61 @@
|
||||
# The eXecute In-Place File System
|
||||
|
||||
## Description
|
||||
|
||||
`xipfs` is a file system designed to streamline post-issuance software
|
||||
deployment. `xipfs` allows direct execution of programs from flash
|
||||
memory, eliminating the need for prior copying to RAM. This approach
|
||||
conserves memory space and accelerates boot times, as the
|
||||
microcontroller can run code directly from storage memory without
|
||||
preloading into RAM.
|
||||
|
||||
The `xipfs` structure is based on a linked list, where each file
|
||||
occupies at least one flash memory page. To prevent fragmentation, when
|
||||
a file is deleted, subsequent files are shifted to fill the vacant
|
||||
space.
|
||||
|
||||
`xipfs` is compatible with all microcontrollers featuring addressable
|
||||
flash memory and most operating systems, provided they implement the
|
||||
necessary functions to interact with the flash controller.
|
||||
|
||||
## Limitations
|
||||
|
||||
`xipfs` has the following limitations:
|
||||
|
||||
- No journaling: `xipfs` doesn't provide journaling. Without journaling,
|
||||
the file system cannot keep track of changes in a way that allows for
|
||||
recovery in the event of a crash or power failure. This can lead to
|
||||
data corruption and loss.
|
||||
|
||||
- No checksums: `xipfs` doesn't provide checksums. The lack of checksums
|
||||
means that the file system cannot verify the integrity of files. This
|
||||
increases the risk of undetected data corruption, as there is no
|
||||
mechanism to ensure that files have not been altered or damaged.
|
||||
|
||||
- Per mountpoint file system lock: `xipfs` needs a file system lock per
|
||||
mountpoint. Such a mechanism can lead to performance bottlenecks,
|
||||
as it prevents multiple threads from accessing the file system montpoint
|
||||
simultaneously.
|
||||
|
||||
- Fixed file size: `xipfs` provide fixed file size. By default, a file
|
||||
created using `vfs_open(2)` has a fixed space reserved in flash that
|
||||
is the size of a flash page. This size cannot be extended later. To
|
||||
create a file larger than the fixed size of one flash page, the
|
||||
`mk(1)` command or the `xipfs_new_file(3)` function must be used.
|
||||
|
||||
- Limited character set: `xipfs` supports only a subset of 7-bit ASCII
|
||||
characters, specifically `[0-9A-Za-z\/\.\-_]`.
|
||||
|
||||
- Limited path length: `xipfs` maximum path length is 64 characters.
|
||||
|
||||
## Tested cards
|
||||
|
||||
`xipfs` is expected to be compatible with all boards that feature
|
||||
addressable NVM. However, only the `DWM1001` board has been tested and
|
||||
is confirmed to function correctly.
|
||||
|
||||
## Funding
|
||||
|
||||
The `xipfs` project is part of the TinyPART project funded by the
|
||||
MESRI-BMBF German-French cybersecurity program under grant agreements
|
||||
n°ANR-20-CYAL-0005 and 16KIS1395K.
|
||||
BIN
examples/advanced/xipfs/hello-world.fae
Normal file
BIN
examples/advanced/xipfs/hello-world.fae
Normal file
Binary file not shown.
227
examples/advanced/xipfs/main.c
Normal file
227
examples/advanced/xipfs/main.c
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Université de Lille
|
||||
*
|
||||
* 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 An application demonstrating xipfs
|
||||
*
|
||||
* @author Damien Amara <damien.amara@univ-lille.fr>
|
||||
* @author Gregory Guche <gregory.guche@univ-lille.fr>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "fs/xipfs_fs.h"
|
||||
#include "periph/flashpage.h"
|
||||
#include "shell.h"
|
||||
#include "vfs.h"
|
||||
|
||||
/**
|
||||
* @def PANIC
|
||||
*
|
||||
* @brief This macro handles fatal errors
|
||||
*/
|
||||
#define PANIC() for (;;) {}
|
||||
|
||||
/**
|
||||
* @def NVME0P0_PAGE_NUM
|
||||
*
|
||||
* @brief The number of flash page for the nvme0p0 file system
|
||||
*/
|
||||
#define NVME0P0_PAGE_NUM 10
|
||||
|
||||
/**
|
||||
* @def NVME0P1_PAGE_NUM
|
||||
*
|
||||
* @brief The number of flash page for the nvme0p1 file system
|
||||
*/
|
||||
#define NVME0P1_PAGE_NUM 15
|
||||
|
||||
/*
|
||||
* Allocate a new contiguous space for the nvme0p0 file system
|
||||
*/
|
||||
XIPFS_NEW_PARTITION(nvme0p0, "/dev/nvme0p0", NVME0P0_PAGE_NUM);
|
||||
|
||||
/*
|
||||
* Allocate a new contiguous space for the nvme0p1 file system
|
||||
*/
|
||||
XIPFS_NEW_PARTITION(nvme0p1, "/dev/nvme0p1", NVME0P1_PAGE_NUM);
|
||||
|
||||
#ifdef BOARD_DWM1001
|
||||
|
||||
/**
|
||||
* @brief hello-world.fae data blob.
|
||||
*
|
||||
* To create a *.fae file, you will need to clone the master branch of
|
||||
* xipfs_format, that can be found at https://github.com/2xs/XiPFS_Format .
|
||||
*
|
||||
* Then modify the Makefile to suit your needs/sources and call make.
|
||||
* You should end up with a *.fae file ready to be uploaded.
|
||||
*/
|
||||
#include "blob/hello-world.fae.h"
|
||||
|
||||
#define FILENAME_OF_HELLO_WORLD_FAE "/dev/nvme0p0/hello-world.fae"
|
||||
#define SIZEOF_HELLO_WORLD_FAE (sizeof(hello_world_fae) / sizeof(hello_world_fae[0]))
|
||||
|
||||
/**
|
||||
* @brief Execution in-place demonstrator.
|
||||
*
|
||||
* This shell command handler will create a file hello-world.fae on /dev/nvme0p0,
|
||||
* if none exists yet from the data blob above.
|
||||
* Then, it will execute this file.
|
||||
*
|
||||
* Once the file has been created, execute command can be used to rerun
|
||||
* the executable file as many times as wanted.
|
||||
*
|
||||
* ```bash
|
||||
* 2025-01-14 09:48:36,303 # main(): This is RIOT! (Version: 2024.10)
|
||||
* 2025-01-14 09:48:36,307 # vfs_mount: "/dev/nvme0p0": OK
|
||||
* 2025-01-14 09:48:36,313 # vfs_mount: "/dev/nvme0p1": OK
|
||||
* > help
|
||||
* 2025-01-14 09:48:42,300 # help
|
||||
* 2025-01-14 09:48:42,302 # Command Description
|
||||
* 2025-01-14 09:48:42,305 # ---------------------------------------
|
||||
* 2025-01-14 09:48:42,309 # exec Execute Hello World
|
||||
* 2025-01-14 09:48:42,314 # create_executable Create an XIPFS executable file
|
||||
* 2025-01-14 09:48:42,317 # execute Execute an XIPFS file
|
||||
* 2025-01-14 09:48:42,320 # ls list files
|
||||
* 2025-01-14 09:48:42,325 # pm interact with layered PM subsystem
|
||||
* 2025-01-14 09:48:42,331 # ps Prints information about running threads.
|
||||
* 2025-01-14 09:48:42,334 # reboot Reboot the node
|
||||
* 2025-01-14 09:48:42,338 # version Prints current RIOT_VERSION
|
||||
* 2025-01-14 09:48:42,343 # vfs virtual file system operations
|
||||
* > exec
|
||||
* 2025-01-14 09:48:49,572 # exec
|
||||
* 2025-01-14 09:48:49,573 # Hello World!
|
||||
* > ls /dev/nvme0p0
|
||||
* 2025-01-14 09:48:59,997 # ls /dev/nvme0p0
|
||||
* 2025-01-14 09:48:59,999 # hello-world.fae 896 B
|
||||
* 2025-01-14 09:49:00,000 # total 1 files
|
||||
* > vfs df
|
||||
* 2025-01-14 09:49:04,957 # vfs df
|
||||
* 2025-01-14 09:49:04,962 # Mountpoint Total Used Available Use%
|
||||
* 2025-01-14 09:49:04,968 # /dev/nvme0p0 40 KiB 4 KiB 36 KiB 10%
|
||||
* 2025-01-14 09:49:04,974 # /dev/nvme0p1 60 KiB 0 B 60 KiB 0%
|
||||
* execute /dev/nvme0p0/hello-world.fae ipsum dolores it
|
||||
* 2025-01-14 09:49:14,223 # execute /dev/nvme0p0/hello-world.fae Lorem ipsum dolor sit amet
|
||||
* 2025-01-14 09:49:14,225 # Hello World!
|
||||
* 2025-01-14 09:49:14,225 # Lorem
|
||||
* 2025-01-14 09:49:14,226 # ipsum
|
||||
* 2025-01-14 09:49:14,226 # dolor
|
||||
* 2025-01-14 09:49:14,227 # sit
|
||||
* 2025-01-14 09:49:14,227 # amet
|
||||
* ```
|
||||
*/
|
||||
int execution_handler(int argc, char **argv) {
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
int file_handle = vfs_open(FILENAME_OF_HELLO_WORLD_FAE, O_RDONLY, 0);
|
||||
if (file_handle < 0) {
|
||||
|
||||
/** There's no executable file yet, let's drop one */
|
||||
int ret = xipfs_extended_driver_new_file(
|
||||
FILENAME_OF_HELLO_WORLD_FAE, SIZEOF_HELLO_WORLD_FAE, 1
|
||||
);
|
||||
if (ret < 0) {
|
||||
printf("xipfs_extended_driver_new_file : failed to create '%s' : error=%d\n",
|
||||
FILENAME_OF_HELLO_WORLD_FAE, ret);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill it with blob data
|
||||
* Take care : vfs does not support O_APPEND with vfs_write, only O_WRONLY or O_RDWR
|
||||
*/
|
||||
file_handle = vfs_open(FILENAME_OF_HELLO_WORLD_FAE, O_WRONLY, 0);
|
||||
if (file_handle < 0) {
|
||||
printf("vfs_open : failed to open '%s' : error =%d\n",
|
||||
FILENAME_OF_HELLO_WORLD_FAE, file_handle);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
ssize_t write_ret = vfs_write(file_handle, hello_world_fae, SIZEOF_HELLO_WORLD_FAE);
|
||||
if (write_ret < 0) {
|
||||
printf("vfs_write : failed to fill '%s' : error=%d\n",
|
||||
FILENAME_OF_HELLO_WORLD_FAE, write_ret);
|
||||
vfs_close(file_handle);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
vfs_close(file_handle);
|
||||
|
||||
char *exec_argv[] = {
|
||||
FILENAME_OF_HELLO_WORLD_FAE,
|
||||
NULL
|
||||
};
|
||||
|
||||
int ret = xipfs_extended_driver_execv(FILENAME_OF_HELLO_WORLD_FAE, exec_argv);
|
||||
if (ret < 0) {
|
||||
printf("Failed to execute '%s' : error=%d\n", FILENAME_OF_HELLO_WORLD_FAE, ret);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
static shell_command_t shell_commands[] = {
|
||||
{"exec", "Execute Hello World", execution_handler},
|
||||
{NULL, NULL, NULL},
|
||||
};
|
||||
|
||||
#else /* BOARD_DWM1001 */
|
||||
|
||||
static shell_command_t shell_commands[] = { {NULL, NULL, NULL} };
|
||||
|
||||
#endif /* BOARD_DWM1001 */
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @brief Mount a partition, or if it is corrupted, format and
|
||||
* remount it
|
||||
*
|
||||
* @param xipfs_mp A pointer to a memory region containing an
|
||||
* xipfs mount point structure
|
||||
*/
|
||||
static void mount_or_format(vfs_xipfs_mount_t *xipfs_mp)
|
||||
{
|
||||
if (vfs_mount(&xipfs_mp->vfs_mp) < 0) {
|
||||
printf("vfs_mount: \"%s\": file system has not been "
|
||||
"initialized or is corrupted\n", xipfs_mp->vfs_mp.mount_point);
|
||||
printf("vfs_format: \"%s\": try initializing it\n",
|
||||
xipfs_mp->vfs_mp.mount_point);
|
||||
vfs_format(&xipfs_mp->vfs_mp);
|
||||
printf("vfs_format: \"%s\": OK\n", xipfs_mp->vfs_mp.mount_point);
|
||||
if (vfs_mount(&xipfs_mp->vfs_mp) < 0) {
|
||||
printf("vfs_mount: \"%s\": file system is corrupted!\n",
|
||||
xipfs_mp->vfs_mp.mount_point);
|
||||
PANIC();
|
||||
}
|
||||
}
|
||||
printf("vfs_mount: \"%s\": OK\n", xipfs_mp->vfs_mp.mount_point);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
char line_buf[SHELL_DEFAULT_BUFSIZE];
|
||||
|
||||
mount_or_format(&nvme0p0);
|
||||
mount_or_format(&nvme0p1);
|
||||
|
||||
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -443,6 +443,7 @@ PSEUDOMODULES += shell_cmd_sntp
|
||||
PSEUDOMODULES += shell_cmd_suit
|
||||
PSEUDOMODULES += shell_cmd_sys
|
||||
PSEUDOMODULES += shell_cmd_udptty
|
||||
PSEUDOMODULES += shell_cmd_xipfs
|
||||
PSEUDOMODULES += shell_cmd_vfs
|
||||
PSEUDOMODULES += shell_cmds_default
|
||||
PSEUDOMODULES += shell_hooks
|
||||
|
||||
17
pkg/xipfs/Makefile
Normal file
17
pkg/xipfs/Makefile
Normal file
@ -0,0 +1,17 @@
|
||||
PKG_NAME=xipfs
|
||||
PKG_URL=https://github.com/2xs/xipfs.git
|
||||
PKG_VERSION=962c5edc55abb6363ff2988dc3b2c3c293362e96
|
||||
PKG_LICENSE=CeCILL-2.1
|
||||
|
||||
include $(RIOTBASE)/pkg/pkg.mk
|
||||
|
||||
export RIOT_INCLUDES=$(INCLUDES)
|
||||
export RIOT_CFLAGS=$(CFLAGS)
|
||||
|
||||
all: $(BINDIR)/xipfs.a
|
||||
|
||||
$(BINDIR)/xipfs.a:
|
||||
$(QQ)"$(MAKE)" -C $(PKG_SOURCE_DIR)
|
||||
cp $(PKG_SOURCE_DIR)/$(BOARD)/xipfs.a $@
|
||||
|
||||
.PHONY: $(BINDIR)/xipfs.a
|
||||
13
pkg/xipfs/Makefile.dep
Normal file
13
pkg/xipfs/Makefile.dep
Normal file
@ -0,0 +1,13 @@
|
||||
DEFAULT_MODULE += xipfs_fs
|
||||
|
||||
# require xipfs_fs dependencies if this module isn't disabled
|
||||
ifeq (,$(filter xipfs_fs,$(DISABLE_MODULE)))
|
||||
# xipfs only makes sense if the flash memory is addressable
|
||||
FEATURES_REQUIRED += periph_flashpage_in_address_space
|
||||
FEATURES_REQUIRED += arch_32bit arch_arm
|
||||
FEATURES_OPTIONAL += periph_flashpage_aux
|
||||
USEMODULE += periph_flashpage
|
||||
USEMODULE += mtd_flashpage
|
||||
USEMODULE += vfs
|
||||
USEMODULE += shell_cmd_xipfs
|
||||
endif
|
||||
8
pkg/xipfs/Makefile.include
Normal file
8
pkg/xipfs/Makefile.include
Normal file
@ -0,0 +1,8 @@
|
||||
INCLUDES += -I$(PKGDIRBASE)/xipfs
|
||||
|
||||
ifneq (,$(filter xipfs_fs,$(USEMODULE)))
|
||||
DIRS += $(RIOTBASE)/pkg/xipfs/fs
|
||||
endif
|
||||
|
||||
# include archive
|
||||
ARCHIVES += $(BINDIR)/xipfs.a
|
||||
32
pkg/xipfs/doc.txt
Normal file
32
pkg/xipfs/doc.txt
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* @defgroup pkg_xipfs xipfs file system
|
||||
* @ingroup pkg
|
||||
* @ingroup sys_fs
|
||||
* @brief The eXecute In-Place File System
|
||||
*
|
||||
*`xipfs` is a file system designed to streamline post-issuance software
|
||||
* deployment.
|
||||
*
|
||||
* `xipfs` allows direct execution of programs from flash memory, eliminating
|
||||
* the need for prior copying to RAM.
|
||||
*
|
||||
* This approach conserves memory space and accelerates boot times, as the
|
||||
* microcontroller can run code directly from storage memory without preloading into RAM.
|
||||
*
|
||||
* The `xipfs` structure is based on a linked list, where each file occupies at least one flash memory page.
|
||||
*
|
||||
* To prevent fragmentation, when a file is deleted, subsequent files are shifted to fill the vacant space.
|
||||
*
|
||||
*`xipfs` is compatible with all microcontrollers featuring addressable
|
||||
* flash memory and most operating systems, provided they implement the
|
||||
* necessary functions to interact with the flash controller.
|
||||
*
|
||||
* **To have a viable executable file within XiPFS**, please follow these steps :
|
||||
* - create a file with executable flag.
|
||||
* - Fill it with actual compiled code in an appropriate file format, here XiPFS-Format.
|
||||
* *This is where the actual startup sequence and relocation happen*.
|
||||
* - Call execute on it with appropriate args *at least the executable filename should be passed along*.
|
||||
*
|
||||
* @see https://github.com/2xs/xipfs
|
||||
* @see https://github.com/2xs/XiPFS_Format
|
||||
*/
|
||||
5
pkg/xipfs/fs/Makefile
Normal file
5
pkg/xipfs/fs/Makefile
Normal file
@ -0,0 +1,5 @@
|
||||
MODULE := xipfs_fs
|
||||
|
||||
CFLAGS+=-DRIOT_OS
|
||||
|
||||
include $(RIOTBASE)/Makefile.base
|
||||
41
pkg/xipfs/fs/xipfs_flashpage.c
Normal file
41
pkg/xipfs/fs/xipfs_flashpage.c
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Université de Lille
|
||||
*
|
||||
* 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_fs_xipfs
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief xipfs integration with flashpage
|
||||
*
|
||||
* @author Damien Amara <damien.amara@univ-lille.fr>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include "periph/flashpage.h"
|
||||
|
||||
void *xipfs_nvm_addr(unsigned page)
|
||||
{
|
||||
return flashpage_addr(page);
|
||||
}
|
||||
|
||||
void xipfs_nvm_erase(unsigned page)
|
||||
{
|
||||
flashpage_erase(page);
|
||||
}
|
||||
|
||||
unsigned xipfs_nvm_page(const void *addr)
|
||||
{
|
||||
return flashpage_page(addr);
|
||||
}
|
||||
|
||||
void xipfs_nvm_write(void *target_addr, const void *data, size_t len)
|
||||
{
|
||||
flashpage_write(target_addr, data, len);
|
||||
}
|
||||
790
pkg/xipfs/fs/xipfs_fs.c
Normal file
790
pkg/xipfs/fs/xipfs_fs.c
Normal file
@ -0,0 +1,790 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Université de Lille
|
||||
*
|
||||
* 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_fs_xipfs
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief xipfs integration with vfs
|
||||
*
|
||||
* @author Damien Amara <damien.amara@univ-lille.fr>
|
||||
* @author Gregory Guche <gregory.guche@univ-lille.fr>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
/*
|
||||
* The following define is required in order to use strnlen(3)
|
||||
* since glibc 2.10. Refer to the SYNOPSIS section of the
|
||||
* strnlen(3) manual and the feature_test_macros(7) manual for
|
||||
* more information
|
||||
*/
|
||||
#define _POSIX_C_SOURCE 200809L
|
||||
|
||||
/*
|
||||
* libc includes
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <mutex.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/*
|
||||
* RIOT includes
|
||||
*/
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
#include "fs/xipfs_fs.h"
|
||||
#include "periph/flashpage.h"
|
||||
|
||||
#include "saul_reg.h"
|
||||
|
||||
/*
|
||||
* xipfs includes
|
||||
*/
|
||||
#include "include/buffer.h"
|
||||
#include "include/errno.h"
|
||||
#include "include/file.h"
|
||||
#include "include/flash.h"
|
||||
#include "include/fs.h"
|
||||
#include "include/path.h"
|
||||
#ifndef RIOT_OS
|
||||
#include "include/xipfs.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* The eXecute In Place File System only makes sense if the
|
||||
* non-volatile memory of the target MCU is addressable
|
||||
*/
|
||||
#ifndef MODULE_PERIPH_FLASHPAGE_IN_ADDRESS_SPACE
|
||||
#error "sys/fs/xipfs: the target MCU has no addressable NVM"
|
||||
#endif /* !MODULE_PERIPH_FLASHPAGE_IN_ADDRESS_SPACE */
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @pre dir must be a pointer that references an accessible
|
||||
* memory region
|
||||
*
|
||||
* @pre path must be a pointer that references a path which is
|
||||
* accessible, null-terminated, starts with a slash, normalized,
|
||||
* and shorter than XIPFS_PATH_MAX
|
||||
*
|
||||
* @brief Copy the directory name component of path, including
|
||||
* the final slash, into the memory region pointed to by dir
|
||||
*
|
||||
* @param dir A pointer to a memory region that respects the
|
||||
* preconditions for storing the directory name component
|
||||
*
|
||||
* @param path A pointer to a path that respects the
|
||||
* preconditions
|
||||
*/
|
||||
static void dirname(char *dir, const char *path)
|
||||
{
|
||||
const char *end;
|
||||
size_t len, i;
|
||||
|
||||
assert(dir != NULL);
|
||||
assert(path != NULL);
|
||||
assert(path[0] == '/');
|
||||
|
||||
if (path[1] == '\0') {
|
||||
dir[0] = '/';
|
||||
dir[1] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
len = strnlen(path, XIPFS_PATH_MAX);
|
||||
assert(len < XIPFS_PATH_MAX);
|
||||
end = path + len - 1;
|
||||
|
||||
if (*end == '/') {
|
||||
/* skip the trailing slash if the
|
||||
* path ends with one */
|
||||
end--;
|
||||
}
|
||||
|
||||
while (end > path && *end != '/') {
|
||||
/* skip all characters that are not
|
||||
* slashes */
|
||||
end--;
|
||||
}
|
||||
|
||||
if (end != path) {
|
||||
for (i = 0; path + i <= end; i++) {
|
||||
/* copy the characters of the directory
|
||||
* name component until end */
|
||||
dir[i] = path[i];
|
||||
}
|
||||
dir[i] = '\0';
|
||||
} else {
|
||||
/* no slashes found, except for the root */
|
||||
dir[0] = '/';
|
||||
dir[1] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @warning This function provides a workaround for xipfs-
|
||||
* specific functions that need to retrieve the xipfs mount
|
||||
* point structure directly, bypassing the VFS layer, as these
|
||||
* functions are not available in the VFS abstraction
|
||||
*
|
||||
* @brief Retrieves the xipfs mount point structure from a
|
||||
* specified path within the mount point
|
||||
*
|
||||
* @param path A path within the mount point for retrieving the
|
||||
* corresponding xipfs mount point structure
|
||||
*
|
||||
* @param mp A pointer to an accessible memory region for
|
||||
* storing the xipfs mount point structure
|
||||
*
|
||||
* @return Returns zero if the function succeeds or a negative
|
||||
* value otherwise
|
||||
*/
|
||||
static int get_xipfs_mp(const char *path, xipfs_mount_t *xipfs_mp)
|
||||
{
|
||||
char dir[XIPFS_PATH_MAX];
|
||||
size_t count, len;
|
||||
int fd, ret;
|
||||
|
||||
if (path[0] != '/') {
|
||||
return -EINVAL;
|
||||
}
|
||||
len = strnlen(path, XIPFS_PATH_MAX);
|
||||
if (len == XIPFS_PATH_MAX) {
|
||||
return -ENAMETOOLONG;
|
||||
}
|
||||
dirname(dir, path);
|
||||
|
||||
if (len + strlen(".xipfs_infos") + 1 > XIPFS_PATH_MAX) {
|
||||
return -ENAMETOOLONG;
|
||||
}
|
||||
(void)strcat(dir, ".xipfs_infos");
|
||||
|
||||
if ((ret = vfs_open(dir, O_RDONLY, 0)) < 0) {
|
||||
/* not a xipfs mount point */
|
||||
return ret;
|
||||
}
|
||||
fd = ret;
|
||||
|
||||
count = sizeof(*xipfs_mp);
|
||||
while (count > 0) {
|
||||
ret = vfs_read(fd, xipfs_mp, count);
|
||||
if (ret < 0) {
|
||||
/* error */
|
||||
return ret;
|
||||
}
|
||||
|
||||
count -= ret;
|
||||
xipfs_mp += ret;
|
||||
if (ret == 0) {
|
||||
/* EOF */
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(count == 0);
|
||||
|
||||
(void)vfs_close(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @pre full_path must be a pointer that references a path which
|
||||
* is accessible, null-terminated, starts with a slash,
|
||||
* normalized, and shorter than XIPFS_PATH_MAX
|
||||
*
|
||||
* @brief Returns a pointer to the first character of the
|
||||
* relative path derived from the absolute path retrieved from
|
||||
* the vfs_mp mount point structure
|
||||
*
|
||||
* @param vfs_mp A pointer to a memory region containing an
|
||||
* accessible VFS mount point structure
|
||||
*
|
||||
* @param full_path A pointer to a path that respects the
|
||||
* preconditions
|
||||
*/
|
||||
static const char *get_rel_path(xipfs_mount_t *mp,
|
||||
const char *full_path)
|
||||
{
|
||||
const char *p1, *p2;
|
||||
|
||||
assert(mp != NULL);
|
||||
assert(full_path != NULL);
|
||||
|
||||
p1 = mp->mount_path;
|
||||
p2 = full_path;
|
||||
|
||||
while (*p1 != '\0') {
|
||||
if (*p1++ != *p2++) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return p2;
|
||||
}
|
||||
|
||||
static inline xipfs_mount_t *_get_xipfs_mount_t(vfs_mount_t *vfs_mp)
|
||||
{
|
||||
vfs_xipfs_mount_t *vfs_xipfs_mp;
|
||||
xipfs_mount_t *xipfs_mp;
|
||||
|
||||
vfs_xipfs_mp = (vfs_xipfs_mount_t *)(uintptr_t)vfs_mp;
|
||||
xipfs_mp = (xipfs_mount_t *)(uintptr_t)&vfs_xipfs_mp->magic;
|
||||
|
||||
return xipfs_mp;
|
||||
}
|
||||
|
||||
/*
|
||||
* Operations on open files
|
||||
*/
|
||||
|
||||
static int _close(vfs_file_t *filp)
|
||||
{
|
||||
xipfs_file_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(filp->mp);
|
||||
descp = (xipfs_file_desc_t *)(uintptr_t)&filp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_close(mp, descp);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _fstat(vfs_file_t *filp, struct stat *buf)
|
||||
{
|
||||
xipfs_file_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(filp->mp);
|
||||
descp = (xipfs_file_desc_t *)(uintptr_t)&filp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_fstat(mp, descp, buf);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static off_t _lseek(vfs_file_t *filp, off_t off, int whence)
|
||||
{
|
||||
xipfs_file_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(filp->mp);
|
||||
descp = (xipfs_file_desc_t *)(uintptr_t)&filp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_lseek(mp, descp, off, whence);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _open(vfs_file_t *filp, const char *name, int flags,
|
||||
mode_t mode)
|
||||
{
|
||||
xipfs_file_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(filp->mp);
|
||||
descp = (xipfs_file_desc_t *)(uintptr_t)&filp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_open(mp, descp, name, flags, mode);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t _read(vfs_file_t *filp, void *dest, size_t nbytes)
|
||||
{
|
||||
xipfs_file_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(filp->mp);
|
||||
descp = (xipfs_file_desc_t *)(uintptr_t)&filp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_read(mp, descp, dest, nbytes);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t _write(vfs_file_t *filp, const void *src, size_t nbytes)
|
||||
{
|
||||
xipfs_file_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(filp->mp);
|
||||
descp = (xipfs_file_desc_t *)(uintptr_t)&filp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_write(mp, descp, src, nbytes);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _fsync(vfs_file_t *filp)
|
||||
{
|
||||
xipfs_file_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(filp->mp);
|
||||
descp = (xipfs_file_desc_t *)(uintptr_t)&filp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_fsync(mp, descp, filp->pos);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Operations on open directories
|
||||
*/
|
||||
|
||||
static int _opendir(vfs_DIR *dirp, const char *dirname)
|
||||
{
|
||||
xipfs_dir_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(dirp->mp);
|
||||
descp = (xipfs_dir_desc_t *)(uintptr_t)&dirp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_opendir(mp, descp, dirname);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _readdir(vfs_DIR *dirp, vfs_dirent_t *entry)
|
||||
{
|
||||
xipfs_dir_desc_t *descp;
|
||||
xipfs_dirent_t *direntp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(dirp->mp);
|
||||
descp = (xipfs_dir_desc_t *)(uintptr_t)&dirp->private_data.ptr;
|
||||
direntp = (xipfs_dirent_t *)(uintptr_t)entry->d_name;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_readdir(mp, descp, direntp);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _closedir(vfs_DIR *dirp)
|
||||
{
|
||||
xipfs_dir_desc_t *descp;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(dirp->mp);
|
||||
descp = (xipfs_dir_desc_t *)(uintptr_t)&dirp->private_data.ptr;
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_closedir(mp, descp);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Operations on mounted file systems
|
||||
*/
|
||||
|
||||
static int _format(vfs_mount_t *vfs_mp)
|
||||
{
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(vfs_mp);
|
||||
|
||||
mutex_lock(mp->execution_mutex);
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_format(mp);
|
||||
mutex_lock(mp->mutex);
|
||||
mutex_lock(mp->execution_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _mount(vfs_mount_t *vfs_mp)
|
||||
{
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(vfs_mp);
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_mount(mp);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _umount(vfs_mount_t *vfs_mp)
|
||||
{
|
||||
(void)vfs_mp;
|
||||
|
||||
/* nothing to do */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int _unlink(vfs_mount_t *vfs_mp, const char *name)
|
||||
{
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(vfs_mp);
|
||||
|
||||
mutex_lock(mp->execution_mutex);
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_unlink(mp, name);
|
||||
mutex_unlock(mp->mutex);
|
||||
mutex_lock(mp->execution_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _mkdir(vfs_mount_t *vfs_mp, const char *name, mode_t mode)
|
||||
{
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(vfs_mp);
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_mkdir(mp, name, mode);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _rmdir(vfs_mount_t *vfs_mp, const char *name)
|
||||
{
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(vfs_mp);
|
||||
|
||||
mutex_lock(mp->execution_mutex);
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_rmdir(mp, name);
|
||||
mutex_unlock(mp->mutex);
|
||||
mutex_unlock(mp->execution_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _rename(vfs_mount_t *vfs_mp, const char *from_path,
|
||||
const char *to_path)
|
||||
{
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(vfs_mp);
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_rename(mp, from_path, to_path);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _stat(vfs_mount_t *vfs_mp, const char *restrict path,
|
||||
struct stat *restrict buf)
|
||||
{
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(vfs_mp);
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_stat(mp, path, buf);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int _statvfs(vfs_mount_t *vfs_mp, const char *restrict path,
|
||||
struct statvfs *restrict buf)
|
||||
{
|
||||
struct xipfs_statvfs xipfs_buf;
|
||||
xipfs_mount_t *mp;
|
||||
int ret;
|
||||
|
||||
mp = _get_xipfs_mount_t(vfs_mp);
|
||||
|
||||
mutex_lock(mp->mutex);
|
||||
ret = xipfs_statvfs(mp, path, &xipfs_buf);
|
||||
mutex_unlock(mp->mutex);
|
||||
|
||||
(void)memset(buf, 0, sizeof(*buf));
|
||||
buf->f_bsize = xipfs_buf.f_bsize;
|
||||
buf->f_frsize = xipfs_buf.f_frsize;
|
||||
buf->f_blocks = xipfs_buf.f_blocks;
|
||||
buf->f_bfree = xipfs_buf.f_bfree;
|
||||
buf->f_bavail = xipfs_buf.f_bavail;
|
||||
buf->f_flag = xipfs_buf.f_flag;
|
||||
buf->f_namemax = xipfs_buf.f_namemax;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* xipfs-specific functions
|
||||
*/
|
||||
|
||||
int xipfs_extended_driver_new_file(const char *full_path, uint32_t size, uint32_t exec)
|
||||
{
|
||||
xipfs_mount_t mp;
|
||||
const char *path;
|
||||
int ret;
|
||||
|
||||
if (full_path == NULL) {
|
||||
return -EFAULT;
|
||||
}
|
||||
if ((ret = get_xipfs_mp(full_path, &mp)) < 0) {
|
||||
return ret;
|
||||
}
|
||||
if ((path = get_rel_path(&mp, full_path)) == NULL) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mutex_lock(mp.mutex);
|
||||
ret = xipfs_new_file(&mp, path, size, exec);
|
||||
mutex_unlock(mp.mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int get_temperature(void) {
|
||||
phydat_t physical_data;
|
||||
saul_reg_t *device = saul_reg_find_type(SAUL_SENSE_TEMP);
|
||||
int res = saul_reg_read(device, &physical_data);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return physical_data.val[0];
|
||||
}
|
||||
|
||||
static int get_led(int pos) {
|
||||
phydat_t physical_data;
|
||||
saul_reg_t *device = saul_reg_find_nth(pos);
|
||||
int res = saul_reg_read(device, &physical_data);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return physical_data.val[0];
|
||||
}
|
||||
|
||||
static int set_led(int pos, int val) {
|
||||
phydat_t physical_data;
|
||||
saul_reg_t *device = saul_reg_find_nth(pos);
|
||||
physical_data.val[0] = val;
|
||||
int res = saul_reg_write(device, &physical_data);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int get_file_size(const char *full_path, size_t *size) {
|
||||
xipfs_mount_t mp;
|
||||
const char *path;
|
||||
int ret;
|
||||
struct stat buf;
|
||||
|
||||
if ((full_path == NULL) || (size == NULL)) {
|
||||
return -EFAULT;
|
||||
}
|
||||
if ((ret = get_xipfs_mp(full_path, &mp)) < 0) {
|
||||
return ret;
|
||||
}
|
||||
if ((path = get_rel_path(&mp, full_path)) == NULL) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
*size = 0;
|
||||
ret = xipfs_stat(&mp, path, &buf);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
*size = buf.st_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t copy_file(const char *full_path, void *buf, size_t nbyte) {
|
||||
xipfs_mount_t mp;
|
||||
const char *path;
|
||||
xipfs_file_desc_t desc;
|
||||
int ret;
|
||||
size_t file_size;
|
||||
|
||||
ret = get_file_size(full_path, &file_size);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
if (nbyte > file_size) {
|
||||
nbyte = file_size;
|
||||
}
|
||||
if (full_path == NULL) {
|
||||
return -EFAULT;
|
||||
}
|
||||
if ((ret = get_xipfs_mp(full_path, &mp)) < 0) {
|
||||
return ret;
|
||||
}
|
||||
if ((path = get_rel_path(&mp, full_path)) == NULL) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ret = xipfs_open(&mp, &desc, path, O_RDONLY, 0);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = xipfs_read(&mp, &desc, buf, nbyte);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
ret = xipfs_close(&mp, &desc);
|
||||
|
||||
return nbyte;
|
||||
}
|
||||
|
||||
static const void *xipfs_user_syscalls_table[XIPFS_USER_SYSCALL_MAX] = {
|
||||
[ XIPFS_USER_SYSCALL_PRINTF] = vprintf,
|
||||
[ XIPFS_USER_SYSCALL_GET_TEMP] = get_temperature,
|
||||
[ XIPFS_USER_SYSCALL_ISPRINT] = isprint,
|
||||
[ XIPFS_USER_SYSCALL_STRTOL] = strtol,
|
||||
[ XIPFS_USER_SYSCALL_GET_LED] = get_led,
|
||||
[ XIPFS_USER_SYSCALL_SET_LED] = set_led,
|
||||
[ XIPFS_USER_SYSCALL_COPY_FILE] = copy_file,
|
||||
[XIPFS_USER_SYSCALL_GET_FILE_SIZE] = get_file_size,
|
||||
[ XIPFS_USER_SYSCALL_MEMSET] = memset
|
||||
};
|
||||
|
||||
int xipfs_extended_driver_execv(const char *full_path, char *const argv[])
|
||||
{
|
||||
xipfs_mount_t mp;
|
||||
const char *path;
|
||||
int ret;
|
||||
|
||||
if (full_path == NULL) {
|
||||
return -EFAULT;
|
||||
}
|
||||
if ((ret = get_xipfs_mp(full_path, &mp)) < 0) {
|
||||
return ret;
|
||||
}
|
||||
if ((path = get_rel_path(&mp, full_path)) == NULL) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
mutex_lock(mp.execution_mutex);
|
||||
ret = xipfs_execv(&mp, path, argv, xipfs_user_syscalls_table);
|
||||
mutex_unlock(mp.execution_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* File system driver structures
|
||||
*/
|
||||
|
||||
static const vfs_file_ops_t xipfs_file_ops = {
|
||||
.close = _close,
|
||||
.fstat = _fstat,
|
||||
.lseek = _lseek,
|
||||
.open = _open,
|
||||
.read = _read,
|
||||
.write = _write,
|
||||
.fsync = _fsync,
|
||||
};
|
||||
|
||||
static const vfs_dir_ops_t xipfs_dir_ops = {
|
||||
.opendir = _opendir,
|
||||
.readdir = _readdir,
|
||||
.closedir = _closedir,
|
||||
};
|
||||
|
||||
static const vfs_file_system_ops_t xipfs_fs_ops = {
|
||||
.format = _format,
|
||||
.mount = _mount,
|
||||
.umount = _umount,
|
||||
.unlink = _unlink,
|
||||
.mkdir = _mkdir,
|
||||
.rmdir = _rmdir,
|
||||
.rename = _rename,
|
||||
.stat = _stat,
|
||||
.statvfs = _statvfs,
|
||||
};
|
||||
|
||||
const vfs_file_system_t xipfs_file_system = {
|
||||
.fs_op = &xipfs_fs_ops,
|
||||
.f_op = &xipfs_file_ops,
|
||||
.d_op = &xipfs_dir_ops,
|
||||
};
|
||||
|
||||
int xipfs_construct_from_flashpage(mtd_flashpage_t *flashpage, const char *path,
|
||||
mutex_t *execution_mutex, mutex_t *mutex,
|
||||
vfs_xipfs_mount_t *vfs_xipfs_mount) {
|
||||
|
||||
if ( (flashpage == NULL) || (path == NULL) || (path[0] == '\0')
|
||||
|| (execution_mutex == NULL) || (mutex == NULL) ) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
vfs_xipfs_mount->vfs_mp.fs = &xipfs_file_system;
|
||||
vfs_xipfs_mount->vfs_mp.mount_point = path;
|
||||
|
||||
vfs_xipfs_mount->magic = XIPFS_MAGIC;
|
||||
vfs_xipfs_mount->mount_path = path;
|
||||
vfs_xipfs_mount->page_num = flashpage->base.sector_count;
|
||||
vfs_xipfs_mount->page_addr = (void *)(
|
||||
(unsigned char *)XIPFS_NVM_BASE
|
||||
+ (flashpage->offset * XIPFS_NVM_PAGE_SIZE)
|
||||
);
|
||||
vfs_xipfs_mount->execution_mutex = execution_mutex;
|
||||
mutex_init(vfs_xipfs_mount->execution_mutex);
|
||||
vfs_xipfs_mount->mutex = mutex;
|
||||
mutex_init(vfs_xipfs_mount->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
146
sys/include/fs/xipfs_fs.h
Normal file
146
sys/include/fs/xipfs_fs.h
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Université de Lille
|
||||
*
|
||||
* 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_xipfs xipfs integration
|
||||
* @ingroup pkg_xipfs
|
||||
* @brief RIOT integration of xipfs
|
||||
*
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief xipfs integration with vfs
|
||||
*
|
||||
* @author Damien Amara <damien.amara@univ-lille.fr>
|
||||
* @author Gregory Guche <gregory.guche@univ-lille.fr>
|
||||
*/
|
||||
|
||||
#ifndef FS_XIPFS_FS_H
|
||||
#define FS_XIPFS_FS_H
|
||||
|
||||
#include "vfs.h"
|
||||
#include "mtd_flashpage.h"
|
||||
#include "mutex.h"
|
||||
#include "include/xipfs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def XIPFS_NEW_PARTITION
|
||||
*
|
||||
* @brief Allocate a new contiguous space aligned to a page in
|
||||
* the non-volatile addressable memory of the MCU to serve as a
|
||||
* partition for an xipfs file system
|
||||
*
|
||||
* @param id Identifier name for the mount point used by
|
||||
* functions that manipulate xipfs file systems
|
||||
*
|
||||
* @param path The mount point of the file system in the VFS tree
|
||||
*
|
||||
* @param num The total number of pages allocated for the
|
||||
* partition
|
||||
*/
|
||||
#define XIPFS_NEW_PARTITION(id, path, num) \
|
||||
FLASH_WRITABLE_INIT(xipfs_part_##id, num); \
|
||||
static mutex_t execution_mutex_##id = MUTEX_INIT; \
|
||||
static mutex_t mutex_##id = MUTEX_INIT; \
|
||||
static vfs_xipfs_mount_t id = { \
|
||||
.vfs_mp = { \
|
||||
.fs = &xipfs_file_system, \
|
||||
.mount_point = path, \
|
||||
}, \
|
||||
.magic = XIPFS_MAGIC, \
|
||||
.mount_path = path, \
|
||||
.page_num = num, \
|
||||
.page_addr = (void *)xipfs_part_##id, \
|
||||
.execution_mutex = &execution_mutex_##id, \
|
||||
.mutex = &mutex_##id \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief xipfs descriptor for vfs integration
|
||||
*
|
||||
* execution_mutex is taken by xipfs_extended_driver_execv and by deletion operations.
|
||||
* mutex is taken by all operations except from xipfs_extended_driver_execv
|
||||
*
|
||||
* This two mutexes scheme :
|
||||
* - allows to call xipfs operation from within a code run by xipfs_extended_driver_execv,
|
||||
* - prevents from deleting files or directories when operations are performed on them.
|
||||
*/
|
||||
typedef struct vfs_xipfs_mount_s {
|
||||
vfs_mount_t vfs_mp; /**< VFS mount point */
|
||||
unsigned magic; /**< xipfs magic number */
|
||||
const char *mount_path; /**< mount point path */
|
||||
size_t page_num; /**< number of flash page */
|
||||
void *page_addr; /**< first flash page address */
|
||||
mutex_t *execution_mutex;/**< For execution and deletion operations */
|
||||
mutex_t *mutex; /**< For regular and deletion operations */
|
||||
} vfs_xipfs_mount_t;
|
||||
|
||||
/** The xipfs vfs driver */
|
||||
extern const vfs_file_system_t xipfs_file_system;
|
||||
|
||||
/**
|
||||
* @brief vfs_xipfs_mount_t constructor from a mtd_flashpage_t.
|
||||
*
|
||||
* This function constructs an xipfs mount point from a mtd_flashpage_t such as mtd_flash_aux_slot.
|
||||
* It allows to use XIPFS with the RIOT AUX slot mechanism.
|
||||
*
|
||||
* @param flashpage A valid mtd_flashpage_t pointer such as &mtd_flash_aux_slot.
|
||||
* @param path A valid mounting point path.
|
||||
* @param execution_mutex A valid mutex pointer used for execution and deletions operations.
|
||||
* @param mutex A valid mutex pointer used by all operations except from execution.
|
||||
* @param vfs_xipfs_mount A valid vfs xipfs mount point pointer.
|
||||
*
|
||||
* @retval -EINVAL when one of the parameters is NULL or when path is an empty string.
|
||||
* @retval 0 on success.
|
||||
*
|
||||
* @note Both mutexes will be initialized by this function.
|
||||
*/
|
||||
int xipfs_construct_from_flashpage(mtd_flashpage_t *flashpage, const char *path,
|
||||
mutex_t *execution_mutex, mutex_t *mutex,
|
||||
vfs_xipfs_mount_t *vfs_xipfs_mount);
|
||||
|
||||
/* Extended driver handling executables */
|
||||
|
||||
/**
|
||||
* @brief Executable or regular new file.
|
||||
*
|
||||
* Allows to create a regular or executable new file within XiPFS.
|
||||
*
|
||||
* @param full_path A full path such as `/dev/nvme0p0/my_new_file`
|
||||
*
|
||||
* @param size The file size in bytes.
|
||||
*
|
||||
* @param exec 0 for regular files, 1 for executable files.
|
||||
*
|
||||
* @retval Less than 0 on errors.
|
||||
* @retval 0 on success.
|
||||
*/
|
||||
int xipfs_extended_driver_new_file(const char *full_path, uint32_t size, uint32_t exec);
|
||||
|
||||
/**
|
||||
* @brief Executes an executable file with arguments.
|
||||
*
|
||||
* @param full_path A full path such as `/dev/nvme0p0/my_executable_file`
|
||||
*
|
||||
* @param argv Executable arguments. Cannot be NULL, argv[0] contains the executable filename.
|
||||
*
|
||||
* @retval Less than 0 on errors.
|
||||
* @retval 0 on success.
|
||||
*/
|
||||
int xipfs_extended_driver_execv(const char *full_path, char *const argv[]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* FS_XIPFS_FS_H */
|
||||
/** @} */
|
||||
@ -78,10 +78,10 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief MAX6 Function to get the largest of 6 values
|
||||
* @brief MAX7 Function to get the largest of 7 values
|
||||
*/
|
||||
#ifndef MAX6
|
||||
#define MAX6(a, b, c, d, e, f) MAX(MAX(MAX(MAX((a), (b)), MAX((c), (d))), (e)), (f))
|
||||
#ifndef MAX7
|
||||
#define MAX7(a, b, c, d, e, f, g) MAX(MAX(MAX(MAX(MAX((a), (b)), MAX((c), (d))), (e)), (f)), (g))
|
||||
#endif
|
||||
|
||||
/**
|
||||
@ -217,6 +217,19 @@ extern "C" {
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief VFS parameters for xipfs
|
||||
* @{
|
||||
*/
|
||||
#if defined(MODULE_XIPFS) || DOXYGEN
|
||||
# define XIPFS_VFS_DIR_BUFFER_SIZE (68) /**< sizeof(xipfs_dir_desc_t) */
|
||||
# define XIPFS_VFS_FILE_BUFFER_SIZE (12) /**< sizeof(xipfs_file_desc_t) */
|
||||
#else
|
||||
# define XIPFS_VFS_DIR_BUFFER_SIZE (1)
|
||||
# define XIPFS_VFS_FILE_BUFFER_SIZE (1)
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
#ifndef VFS_MAX_OPEN_FILES
|
||||
/**
|
||||
* @brief Maximum number of simultaneous open files
|
||||
@ -252,12 +265,13 @@ extern "C" {
|
||||
* @attention Put the check in the public header file (.h), do not put the check in the
|
||||
* implementation (.c) file.
|
||||
*/
|
||||
#define VFS_DIR_BUFFER_SIZE MAX6(FATFS_VFS_DIR_BUFFER_SIZE, \
|
||||
LITTLEFS_VFS_DIR_BUFFER_SIZE, \
|
||||
LITTLEFS2_VFS_DIR_BUFFER_SIZE, \
|
||||
SPIFFS_VFS_DIR_BUFFER_SIZE, \
|
||||
LWEXT4_VFS_DIR_BUFFER_SIZE, \
|
||||
NANOCOAP_FS_VFS_DIR_BUFFER_SIZE \
|
||||
#define VFS_DIR_BUFFER_SIZE MAX7(FATFS_VFS_DIR_BUFFER_SIZE, \
|
||||
LITTLEFS_VFS_DIR_BUFFER_SIZE, \
|
||||
LITTLEFS2_VFS_DIR_BUFFER_SIZE, \
|
||||
SPIFFS_VFS_DIR_BUFFER_SIZE, \
|
||||
LWEXT4_VFS_DIR_BUFFER_SIZE, \
|
||||
NANOCOAP_FS_VFS_DIR_BUFFER_SIZE, \
|
||||
XIPFS_VFS_DIR_BUFFER_SIZE \
|
||||
)
|
||||
#endif
|
||||
|
||||
@ -281,12 +295,13 @@ extern "C" {
|
||||
* @attention Put the check in the public header file (.h), do not put the check in the
|
||||
* implementation (.c) file.
|
||||
*/
|
||||
#define VFS_FILE_BUFFER_SIZE MAX6(FATFS_VFS_FILE_BUFFER_SIZE, \
|
||||
LITTLEFS_VFS_FILE_BUFFER_SIZE, \
|
||||
LITTLEFS2_VFS_FILE_BUFFER_SIZE, \
|
||||
SPIFFS_VFS_FILE_BUFFER_SIZE, \
|
||||
LWEXT4_VFS_FILE_BUFFER_SIZE, \
|
||||
NANOCOAP_FS_VFS_FILE_BUFFER_SIZE \
|
||||
#define VFS_FILE_BUFFER_SIZE MAX7(FATFS_VFS_FILE_BUFFER_SIZE, \
|
||||
LITTLEFS_VFS_FILE_BUFFER_SIZE, \
|
||||
LITTLEFS2_VFS_FILE_BUFFER_SIZE, \
|
||||
SPIFFS_VFS_FILE_BUFFER_SIZE, \
|
||||
LWEXT4_VFS_FILE_BUFFER_SIZE, \
|
||||
NANOCOAP_FS_VFS_FILE_BUFFER_SIZE, \
|
||||
XIPFS_VFS_FILE_BUFFER_SIZE \
|
||||
)
|
||||
#endif
|
||||
|
||||
|
||||
@ -43,6 +43,9 @@
|
||||
#if IS_USED(MODULE_LWEXT4)
|
||||
#include "fs/lwext4_fs.h"
|
||||
#endif
|
||||
#if IS_USED(MODULE_XIPFS)
|
||||
#include "fs/xipfs_fs.h"
|
||||
#endif
|
||||
#if IS_USED(MODULE_FS_NATIVE)
|
||||
#include "fs/native_fs.h"
|
||||
#endif
|
||||
|
||||
@ -288,6 +288,10 @@ ifneq (,$(filter shell_cmd_vfs,$(USEMODULE)))
|
||||
USEMODULE += tiny_strerror
|
||||
endif
|
||||
|
||||
ifneq (,$(filter shell_cmd_xipfs,$(USEMODULE)))
|
||||
USEMODULE += xipfs
|
||||
endif
|
||||
|
||||
ifneq (,$(filter shell_democommands,$(USEMODULE)))
|
||||
USEMODULE += rust_riotmodules
|
||||
USEMODULE += shell
|
||||
|
||||
93
sys/shell/cmds/xipfs.c
Normal file
93
sys/shell/cmds/xipfs.c
Normal file
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Université de Lille
|
||||
*
|
||||
* 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_shell_commands
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Shell commands for XIPFS
|
||||
*
|
||||
* @author Gregory Guche <gregory.guche@univ-lille.fr>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#if defined(MODULE_XIPFS_FS) || defined(MODULE_XIPFS)
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "fs/xipfs_fs.h"
|
||||
#include "include/xipfs.h"
|
||||
#include "shell.h"
|
||||
|
||||
static char *execute_file_handler_args[XIPFS_EXEC_ARGC_MAX];
|
||||
|
||||
static int _execute_file_handler(int argc, char **argv) {
|
||||
if ( (argc == 1) || (argc > XIPFS_EXEC_ARGC_MAX) ) {
|
||||
printf("Usage %s xipfs_executable_filename [arg0] [arg1] ... [arg%d]\n",
|
||||
argv[0], (XIPFS_EXEC_ARGC_MAX - 1));
|
||||
printf("\t- xipfs_executable_filename : filename of the desired XIPFS file to execute\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
memset(execute_file_handler_args, 0, sizeof(execute_file_handler_args));
|
||||
for (int i = 1; i <argc; ++i) {
|
||||
execute_file_handler_args[i-1] = argv[i];
|
||||
}
|
||||
|
||||
int ret = xipfs_extended_driver_execv(argv[1], execute_file_handler_args);
|
||||
if (ret != 0) {
|
||||
printf("Failed to execute '%s', error=%d\n", argv[1], ret);
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SHELL_COMMAND(execute, "Execute an XIPFS file", _execute_file_handler);
|
||||
|
||||
static void print_create_executable_file_usage(int argc, char **argv) {
|
||||
(void)argc;
|
||||
printf("Usage %s xipfs_executable_filename xipfs_executable_file_bytesize\n", argv[0]);
|
||||
printf("\t- xipfs_executable_filename : filename of the desired XIPFS file\n");
|
||||
printf("\t- xipfs_executable_file_bytesize : bytesize of the desired XIPFS file\n");
|
||||
}
|
||||
|
||||
static int _create_executable_file(int argc, char **argv) {
|
||||
if (argc != 3) {
|
||||
print_create_executable_file_usage(argc, argv);
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned long file_size = 0;
|
||||
if (argv[2][0] == '-') {
|
||||
printf("Error : xipfs_executable_file_bytesize must be positive.\n");
|
||||
print_create_executable_file_usage(argc, argv);
|
||||
return 2;
|
||||
}
|
||||
file_size = strtoul(argv[2], NULL, 10);
|
||||
if (file_size >= (unsigned long)UINT32_MAX) {
|
||||
printf("Error : xipfs_executable_file_bytesize must be less than %lu.\n", UINT32_MAX);
|
||||
print_create_executable_file_usage(argc, argv);
|
||||
return 3;
|
||||
}
|
||||
|
||||
int ret = xipfs_extended_driver_new_file(argv[1], (uint32_t)file_size, 1);
|
||||
if (ret != 0) {
|
||||
printf("Failed to create '%s' as an XIPFS executable file.", argv[1]);
|
||||
return 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SHELL_COMMAND(create_executable, "Create an XIPFS executable file", _create_executable_file);
|
||||
|
||||
#endif /* MODULE_XIPFS_FS */
|
||||
13
tests/pkg/xipfs/Makefile
Normal file
13
tests/pkg/xipfs/Makefile
Normal file
@ -0,0 +1,13 @@
|
||||
include ../Makefile.pkg_common
|
||||
|
||||
BOARD ?= dwm1001
|
||||
|
||||
SLOT_AUX_LEN := 0x8000
|
||||
|
||||
USEPKG += xipfs
|
||||
USEMODULE += xipfs
|
||||
USEMODULE += saul_default
|
||||
|
||||
TOOLCHAINS_BLACKLIST += llvm
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
26
tests/pkg/xipfs/Makefile.ci
Normal file
26
tests/pkg/xipfs/Makefile.ci
Normal file
@ -0,0 +1,26 @@
|
||||
BOARD_INSUFFICIENT_MEMORY := \
|
||||
blackpill-stm32f103c8 \
|
||||
bluepill-stm32f030c8 \
|
||||
bluepill-stm32f103c8 \
|
||||
i-nucleo-lrwan1 \
|
||||
nucleo-c031c6 \
|
||||
nucleo-f030r8 \
|
||||
nucleo-f031k6 \
|
||||
nucleo-f042k6 \
|
||||
nucleo-f302r8 \
|
||||
nucleo-f303k8 \
|
||||
nucleo-f334r8 \
|
||||
nucleo-l011k4 \
|
||||
nucleo-l031k6 \
|
||||
nucleo-l053r8 \
|
||||
samd10-xmini \
|
||||
saml10-xpro \
|
||||
saml11-xpro \
|
||||
slstk3400a \
|
||||
stk3200 \
|
||||
stm32f030f4-demo \
|
||||
stm32f0discovery \
|
||||
stm32g0316-disco \
|
||||
stm32l0538-disco \
|
||||
weact-g030f6 \
|
||||
#
|
||||
20
tests/pkg/xipfs/README.md
Normal file
20
tests/pkg/xipfs/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
# The eXecute In-Place File System tests
|
||||
|
||||
## Description
|
||||
|
||||
This application is a best effort to test the return codes of system
|
||||
calls provided by `xipfs`. It accomplishes this by executing these calls
|
||||
in various environments, ensuring that each system call returns the
|
||||
expected error codes. However, further in-depth tests need to be done.
|
||||
|
||||
## Tested cards
|
||||
|
||||
`xipfs-tests` is expected to be compatible with all boards that feature
|
||||
addressable NVM. However, only the `DWM1001` board has been tested and
|
||||
is confirmed to function correctly.
|
||||
|
||||
## Funding
|
||||
|
||||
The `xipfs-tests` project is part of the TinyPART project funded by the
|
||||
MESRI-BMBF German-French cybersecurity program under grant agreements
|
||||
n°ANR-20-CYAL-0005 and 16KIS1395K.
|
||||
2507
tests/pkg/xipfs/main.c
Normal file
2507
tests/pkg/xipfs/main.c
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user