|  | /* | 
|  | * 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 | 
|  |  | 
|  | /* | 
|  | * Currently we are using only one request queue, so we don't have to initialize queues | 
|  | * after that one | 
|  | */ | 
|  | #define QUEUE_COUNT (REQUEST_QUEUE + 1) | 
|  |  | 
|  |  | 
|  | 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, QUEUE_COUNT, 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 | 
|  | */ | 
|  | } |