fs: add virtiofs operations
This commit adds virtiofs functions implementing fuse operations required to enable zephyr filesystem support Signed-off-by: Jakub Michalski <jmichalski@antmicro.com>
This commit is contained in:
parent
b28483a3fe
commit
02b18136e2
@ -18,13 +18,15 @@ if(CONFIG_FILE_SYSTEM_LIB_LINK)
|
||||
|
||||
add_subdirectory_ifdef(CONFIG_FILE_SYSTEM_EXT2 ext2)
|
||||
add_subdirectory_ifdef(CONFIG_FUSE_CLIENT fuse_client)
|
||||
add_subdirectory_ifdef(CONFIG_FILE_SYSTEM_VIRTIOFS virtiofs)
|
||||
|
||||
zephyr_library_link_libraries(FS)
|
||||
|
||||
target_link_libraries_ifdef(CONFIG_FAT_FILESYSTEM_ELM FS INTERFACE ELMFAT)
|
||||
target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_LITTLEFS FS INTERFACE LITTLEFS)
|
||||
target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_EXT2 FS INTERFACE EXT2)
|
||||
target_link_libraries_ifdef(CONFIG_FUSE_CLIENT FS INTERFACE FUSE_CLIENT)
|
||||
target_link_libraries_ifdef(CONFIG_FUSE_CLIENT FS INTERFACE FUSE_CLIENT)
|
||||
target_link_libraries_ifdef(CONFIG_FILE_SYSTEM_VIRTIOFS FS INTERFACE VIRTIOFS)
|
||||
endif()
|
||||
|
||||
add_subdirectory_ifdef(CONFIG_FCB ./fcb)
|
||||
|
||||
@ -120,6 +120,7 @@ rsource "Kconfig.fatfs"
|
||||
rsource "Kconfig.littlefs"
|
||||
rsource "ext2/Kconfig"
|
||||
rsource "fuse_client/Kconfig"
|
||||
rsource "virtiofs/Kconfig"
|
||||
|
||||
endif # FILE_SYSTEM_LIB_LINK
|
||||
|
||||
|
||||
13
subsys/fs/virtiofs/CMakeLists.txt
Normal file
13
subsys/fs/virtiofs/CMakeLists.txt
Normal file
@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2025 Antmicro <www.antmicro.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
add_library(VIRTIOFS INTERFACE)
|
||||
target_include_directories(VIRTIOFS INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
zephyr_library()
|
||||
zephyr_library_sources(
|
||||
virtiofs.c
|
||||
virtiofs_zfs.c
|
||||
)
|
||||
|
||||
zephyr_library_link_libraries(VIRTIOFS)
|
||||
52
subsys/fs/virtiofs/Kconfig
Normal file
52
subsys/fs/virtiofs/Kconfig
Normal file
@ -0,0 +1,52 @@
|
||||
# Copyright (c) 2025 Antmicro <www.antmicro.com>
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config FILE_SYSTEM_VIRTIOFS
|
||||
bool "Virtiofs file system support"
|
||||
depends on FILE_SYSTEM
|
||||
select FUSE_CLIENT
|
||||
help
|
||||
Enable virtiofs file system support.
|
||||
|
||||
config VIRTIOFS_DEBUG
|
||||
bool "Print virtiofs verbose debug information"
|
||||
help
|
||||
Enables printing of virtiofs verbose debug information
|
||||
|
||||
config VIRTIOFS_MAX_FILES
|
||||
int "Virtiofs max open files"
|
||||
default 1024
|
||||
help
|
||||
Virtiofs max simultaneously open files
|
||||
|
||||
config VIRTIOFS_MAX_VQUEUE_SIZE
|
||||
int "Virtiofs max virtqueue size"
|
||||
default 1024
|
||||
help
|
||||
Maximum size of virtqueue
|
||||
|
||||
config VIRTIOFS_NO_NOTIFICATION_QUEUE_SLOT
|
||||
bool "Omit notification queue (idx 1) and assume that idx 1 is the first request queue"
|
||||
default y
|
||||
help
|
||||
According to virtio specification v1.3 section 5.11.2 queue at idx 1 is notification queue and
|
||||
request queues start at idx 2, however on qemu+virtiofsd thats not true and idx 1 is
|
||||
the first request queue
|
||||
|
||||
config VIRTIOFS_VIRTIOFSD_UNLINK_QUIRK
|
||||
bool "Fix unlink() with some virtiofsd versions"
|
||||
default y
|
||||
help
|
||||
Some virtiofsd versions (at least Debian 1:7.2+dfsg-7+deb12u7)
|
||||
will fail with EIO on unlink if the file wasn't looked up before
|
||||
|
||||
config VIRTIOFS_CREATE_MODE_VALUE
|
||||
int "Virtiofs mode value used during file/directory creation"
|
||||
default 438 #0666
|
||||
help
|
||||
During creation of file or directory we have to set mode, this config allows
|
||||
configuring that value. This determines access permissions for created files and directories.
|
||||
|
||||
module = VIRTIOFS
|
||||
module-str = virtiofs
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
778
subsys/fs/virtiofs/virtiofs.c
Normal file
778
subsys/fs/virtiofs/virtiofs.c
Normal file
@ -0,0 +1,778 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Antmicro <www.antmicro.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/byteorder.h>
|
||||
#include <errno.h>
|
||||
#include "virtiofs.h"
|
||||
#include <zephyr/drivers/virtio.h>
|
||||
|
||||
LOG_MODULE_REGISTER(virtiofs, CONFIG_VIRTIOFS_LOG_LEVEL);
|
||||
|
||||
/*
|
||||
* According to 5.11.2 of virtio specification v1.3 the virtiofs queues are indexed as
|
||||
* follows:
|
||||
* - idx 0 - hiprio
|
||||
* - idx 1 - notification queue
|
||||
* - idx 2..n - request queues
|
||||
* notification queue is available only if VIRTIO_FS_F_NOTIFICATION is present and
|
||||
* there is no mention that in its absence the request queues will be shifted and start
|
||||
* at idx 1, so the request queues shall start at idx 2. However in case of qemu+virtiofsd
|
||||
* who don't support VIRTIO_FS_F_NOTIFICATION, the last available queue is at idx 1 and
|
||||
* virtio_fs_config.num_request_queues states that there is a single request queue present
|
||||
* which must be the one at idx 1
|
||||
*/
|
||||
#ifdef CONFIG_VIRTIOFS_NO_NOTIFICATION_QUEUE_SLOT
|
||||
#define REQUEST_QUEUE 1
|
||||
#else
|
||||
#define REQUEST_QUEUE 2
|
||||
#endif
|
||||
|
||||
|
||||
struct virtio_fs_config {
|
||||
char tag[36];
|
||||
uint32_t num_request_queues;
|
||||
};
|
||||
|
||||
static int virtiofs_validate_response(
|
||||
const struct fuse_out_header *header, uint32_t opcode, uint32_t used_len,
|
||||
uint32_t expected_len)
|
||||
{
|
||||
if (used_len < sizeof(*header)) {
|
||||
LOG_ERR("used length is smaller than size of fuse_out_header");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (header->error != 0) {
|
||||
LOG_ERR(
|
||||
"%s error %d (%s)",
|
||||
fuse_opcode_to_string(opcode),
|
||||
-header->error,
|
||||
strerror(-header->error)
|
||||
);
|
||||
return header->error;
|
||||
}
|
||||
|
||||
if (expected_len != -1 && header->len != expected_len) {
|
||||
LOG_ERR(
|
||||
"%s return message has invalid length (0x%x), expected 0x%x",
|
||||
fuse_opcode_to_string(opcode),
|
||||
header->len,
|
||||
expected_len
|
||||
);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct recv_cb_param {
|
||||
struct k_sem sem;
|
||||
uint32_t used_len;
|
||||
};
|
||||
|
||||
void virtiofs_recv_cb(void *opaque, uint32_t used_len)
|
||||
{
|
||||
struct recv_cb_param *arg = opaque;
|
||||
|
||||
arg->used_len = used_len;
|
||||
k_sem_give(&arg->sem);
|
||||
}
|
||||
|
||||
static uint32_t virtiofs_send_receive(
|
||||
const struct device *dev, uint16_t virtq, struct virtq_buf *bufs,
|
||||
uint16_t bufs_size, uint16_t device_readable)
|
||||
{
|
||||
struct virtq *virtqueue = virtio_get_virtqueue(dev, virtq);
|
||||
struct recv_cb_param cb_arg;
|
||||
|
||||
k_sem_init(&cb_arg.sem, 0, 1);
|
||||
|
||||
virtq_add_buffer_chain(
|
||||
virtqueue, bufs, bufs_size, device_readable, virtiofs_recv_cb, &cb_arg,
|
||||
K_FOREVER
|
||||
);
|
||||
virtio_notify_virtqueue(dev, virtq);
|
||||
|
||||
k_sem_take(&cb_arg.sem, K_FOREVER);
|
||||
|
||||
return cb_arg.used_len;
|
||||
}
|
||||
|
||||
static uint16_t virtiofs_queue_enum_cb(uint16_t queue_idx, uint16_t max_size, void *unused)
|
||||
{
|
||||
if (queue_idx == REQUEST_QUEUE) {
|
||||
return MIN(CONFIG_VIRTIOFS_MAX_VQUEUE_SIZE, max_size);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int virtiofs_init(const struct device *dev, struct fuse_init_out *response)
|
||||
{
|
||||
struct virtio_fs_config *fs_config = virtio_get_device_specific_config(dev);
|
||||
struct fuse_init_req req;
|
||||
int ret = 0;
|
||||
|
||||
if (!fs_config) {
|
||||
LOG_ERR("no virtio_fs_config present");
|
||||
return -ENXIO;
|
||||
}
|
||||
if (fs_config->num_request_queues < 1) {
|
||||
/* this shouldn't ever happen */
|
||||
LOG_ERR("no request queue present");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
ret = virtio_commit_feature_bits(dev);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = virtio_init_virtqueues(dev, REQUEST_QUEUE, virtiofs_queue_enum_cb, NULL);
|
||||
if (ret != 0) {
|
||||
LOG_ERR("failed to initialize fs virtqueues");
|
||||
return ret;
|
||||
}
|
||||
|
||||
virtio_finalize_init(dev);
|
||||
|
||||
fuse_create_init_req(&req);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.init_in) },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.init_out) }
|
||||
};
|
||||
|
||||
LOG_INF("sending FUSE_INIT, unique=%" PRIu64, req.in_header.unique);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1);
|
||||
|
||||
LOG_INF("received FUSE_INIT response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, FUSE_INIT, used_len, buf[1].len
|
||||
);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
if (req.init_out.major != FUSE_MAJOR_VERSION) {
|
||||
LOG_ERR(
|
||||
"FUSE_INIT major version mismatch (%d), version %d is supported",
|
||||
req.init_out.major,
|
||||
FUSE_MAJOR_VERSION
|
||||
);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (req.init_out.minor < FUSE_MINOR_VERSION) {
|
||||
LOG_ERR(
|
||||
"FUSE_INIT minor version is too low (%d), version %d is supported",
|
||||
req.init_out.minor,
|
||||
FUSE_MINOR_VERSION
|
||||
);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
*response = req.init_out;
|
||||
|
||||
#ifdef CONFIG_VIRTIOFS_DEBUG
|
||||
fuse_dump_init_req_out(&req.init_out);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief lookups object in the virtiofs filesystem
|
||||
*
|
||||
* @param dev virtio device its used on
|
||||
* @param inode inode to start from
|
||||
* @param path path to object we are looking for
|
||||
* @param response virtiofs response for object
|
||||
* @param parent_inode will be set to immediate parent inode of object that we are looking for.
|
||||
* If immediate parent doesn't exist it will be set to 0. If not 0 it has to be FUSE_FORGET by
|
||||
* caller. Can be NULL.
|
||||
* @return 0 or error code on failure
|
||||
*/
|
||||
int virtiofs_lookup(
|
||||
const struct device *dev, uint64_t inode, const char *path, struct fuse_entry_out *response,
|
||||
uint64_t *parent_inode)
|
||||
{
|
||||
uint32_t path_len = strlen(path) + 1;
|
||||
const char *curr = path;
|
||||
uint32_t curr_len = 0;
|
||||
uint64_t curr_inode = inode;
|
||||
struct fuse_lookup_req req;
|
||||
|
||||
/*
|
||||
* we have to split path and lookup it dir by dir, because FUSE_LOOKUP doesn't work with
|
||||
* full paths like abc/xyz/file. We have to lookup abc, then lookup xyz with abc's inode
|
||||
* as a base and then lookup file with xyz's inode as a base
|
||||
*/
|
||||
while (curr < path + path_len) {
|
||||
curr_len = 0;
|
||||
for (const char *c = curr; c < path + path_len - 1 && *c != '/'; c++) {
|
||||
curr_len++;
|
||||
}
|
||||
|
||||
fuse_create_lookup_req(&req, curr_inode, curr_len + 1);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(struct fuse_in_header) },
|
||||
{ .addr = (void *)curr, .len = curr_len },
|
||||
/*
|
||||
* despite length being part of in_header this still has to be null
|
||||
* terminated
|
||||
*/
|
||||
{ .addr = "", .len = 1},
|
||||
{ .addr = &req.out_header,
|
||||
.len = sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_LOOKUP for \"%s\", nodeid=%" PRIu64 ", unique=%" PRIu64,
|
||||
curr, curr_inode, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 4, 3);
|
||||
|
||||
LOG_INF("received FUSE_LOOKUP response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, FUSE_LOOKUP, used_len,
|
||||
sizeof(struct fuse_out_header) + sizeof(struct fuse_entry_out)
|
||||
);
|
||||
|
||||
if (parent_inode) {
|
||||
*parent_inode = curr_inode;
|
||||
}
|
||||
|
||||
*response = req.entry_out;
|
||||
if (valid_ret != 0) {
|
||||
if (parent_inode && (curr + curr_len + 1 != path + path_len)) {
|
||||
/* there is no immediate parent */
|
||||
if (*parent_inode != inode) {
|
||||
virtiofs_forget(dev, *parent_inode, 1);
|
||||
}
|
||||
*parent_inode = 0;
|
||||
}
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIRTIOFS_DEBUG
|
||||
fuse_dump_entry_out(&req.entry_out);
|
||||
#endif
|
||||
bool is_curr_parent = true;
|
||||
|
||||
for (const char *c = curr; c < path + path_len; c++) {
|
||||
if (*c == '/') {
|
||||
is_curr_parent = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* unless its inode param passed to this function or a parent of object we
|
||||
* are looking for, curr_inode won't be used anymore so we can forget it
|
||||
*/
|
||||
if (curr_inode != inode && (!parent_inode || !is_curr_parent)) {
|
||||
virtiofs_forget(dev, curr_inode, 1);
|
||||
}
|
||||
|
||||
curr_inode = req.entry_out.nodeid;
|
||||
curr += curr_len + 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtiofs_open(
|
||||
const struct device *dev, uint64_t inode, uint32_t flags, struct fuse_open_out *response,
|
||||
enum fuse_object_type type)
|
||||
{
|
||||
struct fuse_open_req req;
|
||||
|
||||
fuse_create_open_req(&req, inode, flags, type);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = req.in_header.len },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.open_out) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending %s, nodeid=%" PRIu64 ", flags=0%" PRIo32 ", unique=%" PRIu64,
|
||||
type == FUSE_DIR ? "FUSE_OPENDIR" : "FUSE_OPEN", inode, flags, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1);
|
||||
|
||||
LOG_INF(
|
||||
"received %s response, unique=%" PRIu64,
|
||||
type == FUSE_DIR ? "FUSE_OPENDIR" : "FUSE_OPEN", req.out_header.unique
|
||||
);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, type == FUSE_DIR ? FUSE_OPENDIR : FUSE_OPEN, used_len, buf[1].len
|
||||
);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
*response = req.open_out;
|
||||
|
||||
#ifdef CONFIG_VIRTIOFS_DEBUG
|
||||
fuse_dump_open_req_out(&req.open_out);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtiofs_read(
|
||||
const struct device *dev, uint64_t inode, uint64_t fh,
|
||||
uint64_t offset, uint32_t size, uint8_t *readbuf)
|
||||
{
|
||||
struct fuse_read_req req;
|
||||
|
||||
fuse_create_read_req(&req, inode, fh, offset, size, FUSE_FILE);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = req.in_header.len },
|
||||
{ .addr = &req.out_header, .len = sizeof(struct fuse_out_header) },
|
||||
{ .addr = readbuf, .len = size }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_READ, nodeid=%" PRIu64 ", fh=%" PRIu64 ", offset=%" PRIu64
|
||||
", size=%" PRIu32 ", unique=%" PRIu64,
|
||||
inode, fh, offset, size, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 1);
|
||||
|
||||
LOG_INF("received FUSE_READ response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(&req.out_header, FUSE_READ, used_len, -1);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
return req.out_header.len - sizeof(req.out_header);
|
||||
}
|
||||
|
||||
int virtiofs_release(const struct device *dev, uint64_t inode, uint64_t fh,
|
||||
enum fuse_object_type type)
|
||||
{
|
||||
struct fuse_release_req req;
|
||||
|
||||
fuse_create_release_req(&req, inode, fh, type);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = req.in_header.len },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending %s, inode=%" PRIu64 ", fh=%" PRIu64 ", unique=%" PRIu64,
|
||||
type == FUSE_DIR ? "FUSE_RELEASEDIR" : "FUSE_RELEASE", inode, fh,
|
||||
req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1);
|
||||
|
||||
LOG_INF(
|
||||
"received %s response, unique=%" PRIu64,
|
||||
type == FUSE_DIR ? "FUSE_RELEASEDIR" : "FUSE_RELEASE", req.out_header.unique
|
||||
);
|
||||
|
||||
return virtiofs_validate_response(
|
||||
&req.out_header, type == FUSE_DIR ? FUSE_RELEASEDIR : FUSE_RELEASE, used_len, -1
|
||||
);
|
||||
}
|
||||
|
||||
int virtiofs_destroy(const struct device *dev)
|
||||
{
|
||||
struct fuse_destroy_req req;
|
||||
|
||||
fuse_create_destroy_req(&req);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) }
|
||||
};
|
||||
|
||||
LOG_INF("sending FUSE_DESTROY, unique=%" PRIu64, req.in_header.unique);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1);
|
||||
|
||||
LOG_INF("received FUSE_DESTROY response, unique=%" PRIu64, req.in_header.unique);
|
||||
|
||||
return virtiofs_validate_response(&req.out_header, FUSE_DESTROY, used_len, -1);
|
||||
}
|
||||
|
||||
int virtiofs_create(
|
||||
const struct device *dev, uint64_t inode, const char *fname, uint32_t flags,
|
||||
uint32_t mode, struct fuse_create_out *response)
|
||||
{
|
||||
uint32_t fname_len = strlen(fname) + 1;
|
||||
struct fuse_create_req req;
|
||||
|
||||
fuse_create_create_req(&req, inode, fname_len, flags, mode);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.create_in) },
|
||||
{ .addr = (void *)fname, .len = fname_len },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.create_out) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_CREATE for \"%s\", nodeid=%" PRIu64 ", flags=0%" PRIo32
|
||||
", unique=%" PRIu64,
|
||||
fname, inode, flags, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 2);
|
||||
|
||||
LOG_INF("received FUSE_CREATE response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, FUSE_CREATE, used_len, buf[2].len
|
||||
);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
*response = req.create_out;
|
||||
|
||||
#ifdef CONFIG_VIRTIOFS_DEBUG
|
||||
fuse_dump_create_req_out(&req.create_out);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtiofs_write(
|
||||
const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset,
|
||||
uint32_t size, const uint8_t *write_buf)
|
||||
{
|
||||
struct fuse_write_req req;
|
||||
|
||||
fuse_create_write_req(&req, inode, fh, offset, size);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.write_in) },
|
||||
{ .addr = (void *)write_buf, .len = size },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.write_out) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_WRITE, nodeid=%" PRIu64", fh=%" PRIu64 ", offset=%" PRIu64
|
||||
", size=%" PRIu32 ", unique=%" PRIu64,
|
||||
inode, fh, offset, size, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 2);
|
||||
|
||||
LOG_INF("received FUSE_WRITE response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, FUSE_WRITE, used_len, buf[2].len
|
||||
);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIRTIOFS_DEBUG
|
||||
fuse_dump_write_out(&req.write_out);
|
||||
#endif
|
||||
|
||||
return req.write_out.size;
|
||||
}
|
||||
|
||||
int virtiofs_lseek(
|
||||
const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset,
|
||||
uint32_t whence, struct fuse_lseek_out *response)
|
||||
{
|
||||
struct fuse_lseek_req req;
|
||||
|
||||
fuse_create_lseek_req(&req, inode, fh, offset, whence);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = req.in_header.len },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.lseek_out) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_LSEEK, nodeid=%" PRIu64 ", fh=%" PRIu64 ", offset=%" PRIu64
|
||||
", whence=%" PRIu32 ", unique=%" PRIu64,
|
||||
inode, fh, offset, whence, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1);
|
||||
|
||||
LOG_INF("received FUSE_LSEEK response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, FUSE_LSEEK, used_len, buf[1].len
|
||||
);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
*response = req.lseek_out;
|
||||
|
||||
#ifdef CONFIG_VIRTIOFS_DEBUG
|
||||
fuse_dump_lseek_out(&req.lseek_out);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtiofs_setattr(
|
||||
const struct device *dev, uint64_t inode, struct fuse_setattr_in *in,
|
||||
struct fuse_attr_out *response)
|
||||
{
|
||||
struct fuse_setattr_req req;
|
||||
|
||||
fuse_create_setattr_req(&req, inode);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) },
|
||||
{ .addr = in, .len = sizeof(*in) },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) },
|
||||
{ .addr = response, .len = sizeof(*response) },
|
||||
};
|
||||
|
||||
LOG_INF("sending FUSE_SETATTR, unique=%" PRIu64, req.in_header.unique);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 4, 2);
|
||||
|
||||
LOG_INF("received FUSE_SETATTR response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, FUSE_SETATTR, used_len, sizeof(req.out_header) + sizeof(*response)
|
||||
);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIRTIOFS_DEBUG
|
||||
fuse_dump_attr_out(response);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtiofs_fsync(const struct device *dev, uint64_t inode, uint64_t fh)
|
||||
{
|
||||
struct fuse_fsync_req req;
|
||||
|
||||
fuse_create_fsync_req(&req, inode, fh);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header,
|
||||
.len = sizeof(req.in_header) + sizeof(req.fsync_in) },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_FSYNC, nodeid=%" PRIu64 ", fh=%" PRIu64 ", unique=%" PRIu64,
|
||||
inode, fh, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1);
|
||||
|
||||
LOG_INF("received FUSE_FSYNC response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
return virtiofs_validate_response(
|
||||
&req.out_header, FUSE_FSYNC, used_len, sizeof(req.out_header)
|
||||
);
|
||||
}
|
||||
|
||||
int virtiofs_mkdir(const struct device *dev, uint64_t inode, const char *dirname, uint32_t mode)
|
||||
{
|
||||
struct fuse_mkdir_req req;
|
||||
uint32_t dirname_len = strlen(dirname) + 1;
|
||||
|
||||
fuse_create_mkdir_req(&req, inode, dirname_len, mode);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.mkdir_in) },
|
||||
{ .addr = (void *)dirname, .len = dirname_len },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) + sizeof(req.entry_out) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_MKDIR %s, inode=%" PRIu64 ", unique=%" PRIu64,
|
||||
dirname, inode, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 2);
|
||||
|
||||
LOG_INF("received FUSE_MKDIR response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, FUSE_MKDIR, used_len, buf[2].len
|
||||
);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtiofs_unlink(const struct device *dev, const char *fname, enum fuse_object_type type)
|
||||
{
|
||||
struct fuse_unlink_req req;
|
||||
uint32_t fname_len = strlen(fname) + 1;
|
||||
|
||||
fuse_create_unlink_req(&req, fname_len, type);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) },
|
||||
{ .addr = (void *)fname, .len = fname_len },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending %s for %s, unique=%" PRIu64,
|
||||
type == FUSE_DIR ? "FUSE_RMDIR" : "FUSE_UNLINK", fname, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 3, 2);
|
||||
|
||||
LOG_INF(
|
||||
"received %s response, unique=%" PRIu64,
|
||||
type == FUSE_DIR ? "FUSE_RMDIR" : "FUSE_UNLINK", req.out_header.unique
|
||||
);
|
||||
|
||||
return virtiofs_validate_response(
|
||||
&req.out_header, type == FUSE_DIR ? FUSE_RMDIR : FUSE_UNLINK, used_len,
|
||||
sizeof(req.out_header)
|
||||
);
|
||||
}
|
||||
|
||||
int virtiofs_rename(
|
||||
const struct device *dev, uint64_t old_dir_inode, const char *old_name,
|
||||
uint64_t new_dir_inode, const char *new_name)
|
||||
{
|
||||
struct fuse_rename_req req;
|
||||
uint32_t old_len = strlen(old_name) + 1;
|
||||
uint32_t new_len = strlen(new_name) + 1;
|
||||
|
||||
fuse_create_rename_req(&req, old_dir_inode, old_len, new_dir_inode, new_len);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) + sizeof(req.rename_in) },
|
||||
{ .addr = (void *)old_name, .len = old_len },
|
||||
{ .addr = (void *)new_name, .len = new_len },
|
||||
{ .addr = &req.out_header, .len = sizeof(req.out_header) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_RENAME %s to %s, unique=%" PRIu64,
|
||||
old_name, new_name, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 4, 3);
|
||||
|
||||
LOG_INF("received FUSE_RENAME response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
return virtiofs_validate_response(
|
||||
&req.out_header, FUSE_RENAME, used_len, sizeof(req.out_header)
|
||||
);
|
||||
}
|
||||
|
||||
int virtiofs_statfs(const struct device *dev, struct fuse_kstatfs *response)
|
||||
{
|
||||
struct fuse_kstatfs_req req;
|
||||
|
||||
fuse_fill_header(&req.in_header, sizeof(req.in_header), FUSE_STATFS, FUSE_ROOT_INODE);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = sizeof(req.in_header) },
|
||||
{ .addr = &req.out_header,
|
||||
.len = sizeof(req.out_header) + sizeof(req.kstatfs_out) }
|
||||
};
|
||||
|
||||
LOG_INF("sending FUSE_STATFS, unique=%" PRIu64, req.in_header.unique);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 2, 1);
|
||||
|
||||
LOG_INF("received FUSE_STATFS response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(
|
||||
&req.out_header, FUSE_STATFS, used_len, buf[1].len
|
||||
);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VIRTIOFS_DEBUG
|
||||
fuse_dump_kstafs(&req.kstatfs_out);
|
||||
#endif
|
||||
|
||||
*response = req.kstatfs_out;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int virtiofs_readdir(
|
||||
const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset,
|
||||
uint8_t *dirent_buf, uint32_t dirent_size, uint8_t *name_buf, uint32_t name_size)
|
||||
{
|
||||
struct fuse_read_req req;
|
||||
|
||||
fuse_create_read_req(&req, inode, fh, offset, dirent_size + name_size, FUSE_DIR);
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req.in_header, .len = req.in_header.len },
|
||||
{ .addr = &req.out_header, .len = sizeof(struct fuse_out_header) },
|
||||
{ .addr = dirent_buf, .len = dirent_size },
|
||||
{ .addr = name_buf, .len = name_size }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_READDIR, nodeid=%" PRIu64 ", fh=%" PRIu64 ", offset=%" PRIu64
|
||||
", size=%" PRIu32 ", unique=%" PRIu64,
|
||||
inode, fh, offset, dirent_size + name_size, req.in_header.unique
|
||||
);
|
||||
uint32_t used_len = virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 4, 1);
|
||||
|
||||
LOG_INF("received FUSE_READDIR response, unique=%" PRIu64, req.out_header.unique);
|
||||
|
||||
int valid_ret = virtiofs_validate_response(&req.out_header, FUSE_READDIR, used_len, -1);
|
||||
|
||||
if (valid_ret != 0) {
|
||||
return valid_ret;
|
||||
}
|
||||
|
||||
return req.out_header.len - sizeof(req.out_header);
|
||||
}
|
||||
|
||||
void virtiofs_forget(const struct device *dev, uint64_t inode, uint64_t nlookup)
|
||||
{
|
||||
if (inode == FUSE_ROOT_INODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct fuse_forget_req req;
|
||||
|
||||
fuse_fill_header(&req.in_header, sizeof(req.in_header), FUSE_FORGET, inode);
|
||||
req.forget_in.nlookup = nlookup; /* refcount will be decreased by this value */
|
||||
|
||||
struct virtq_buf buf[] = {
|
||||
{ .addr = &req, .len = sizeof(req.in_header) + sizeof(req.forget_in) }
|
||||
};
|
||||
|
||||
LOG_INF(
|
||||
"sending FUSE_FORGET nodeid=%" PRIu64 ", nlookup=%" PRIu64 ", unique=%" PRIu64,
|
||||
inode, nlookup, req.in_header.unique
|
||||
);
|
||||
virtiofs_send_receive(dev, REQUEST_QUEUE, buf, 1, 1);
|
||||
LOG_INF("received FUSE_FORGET response, unique=%" PRIu64, req.in_header.unique);
|
||||
|
||||
/*
|
||||
* In comparison to other fuse operations this one doesn't return fuse_out_header,
|
||||
* despite virtio spec v1.3 5.11.6.1 saying that out header is common to all
|
||||
* types of fuse requests (comment in include/uapi/linux/fuse.h states otherwise that
|
||||
* FUSE_FORGET has no reply), so there is no error code to return
|
||||
*/
|
||||
}
|
||||
49
subsys/fs/virtiofs/virtiofs.h
Normal file
49
subsys/fs/virtiofs/virtiofs.h
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Antmicro <www.antmicro.com>
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_SUBSYS_FS_VIRTIOFS_VIRTIOFS_H_
|
||||
#define ZEPHYR_SUBSYS_FS_VIRTIOFS_VIRTIOFS_H_
|
||||
#include <zephyr/device.h>
|
||||
#include <../fuse_client/fuse_client.h>
|
||||
|
||||
int virtiofs_init(const struct device *dev, struct fuse_init_out *response);
|
||||
int virtiofs_lookup(
|
||||
const struct device *dev, uint64_t inode, const char *name, struct fuse_entry_out *response,
|
||||
uint64_t *parent_inode);
|
||||
int virtiofs_open(
|
||||
const struct device *dev, uint64_t inode, uint32_t flags, struct fuse_open_out *response,
|
||||
enum fuse_object_type type);
|
||||
int virtiofs_read(
|
||||
const struct device *dev, uint64_t inode, uint64_t fh,
|
||||
uint64_t offset, uint32_t size, uint8_t *buf);
|
||||
int virtiofs_release(const struct device *dev, uint64_t inode, uint64_t fh,
|
||||
enum fuse_object_type type);
|
||||
int virtiofs_destroy(const struct device *dev);
|
||||
int virtiofs_create(
|
||||
const struct device *dev, uint64_t inode, const char *fname, uint32_t flags,
|
||||
uint32_t mode, struct fuse_create_out *response);
|
||||
int virtiofs_write(
|
||||
const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset,
|
||||
uint32_t size, const uint8_t *write_buf);
|
||||
int virtiofs_lseek(
|
||||
const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset,
|
||||
uint32_t whence, struct fuse_lseek_out *response);
|
||||
int virtiofs_setattr(
|
||||
const struct device *dev, uint64_t inode, struct fuse_setattr_in *in,
|
||||
struct fuse_attr_out *response);
|
||||
int virtiofs_fsync(const struct device *dev, uint64_t inode, uint64_t fh);
|
||||
int virtiofs_mkdir(const struct device *dev, uint64_t inode, const char *dirname, uint32_t mode);
|
||||
int virtiofs_unlink(const struct device *dev, const char *fname, enum fuse_object_type type);
|
||||
int virtiofs_rename(
|
||||
const struct device *dev, uint64_t old_dir_inode, const char *old_name,
|
||||
uint64_t new_dir_inode, const char *new_name);
|
||||
int virtiofs_statfs(const struct device *dev, struct fuse_kstatfs *response);
|
||||
int virtiofs_readdir(
|
||||
const struct device *dev, uint64_t inode, uint64_t fh, uint64_t offset,
|
||||
uint8_t *dirent_buf, uint32_t dirent_size, uint8_t *name_buf, uint32_t name_size);
|
||||
void virtiofs_forget(const struct device *dev, uint64_t inode, uint64_t nlookup);
|
||||
|
||||
#endif /* ZEPHYR_SUBSYS_FS_VIRTIOFS_VIRTIOFS_H_ */
|
||||
Loading…
Reference in New Issue
Block a user