virtio: Add top-level functions for virtio-fs

Provide access to the virtual queue and some functions for sending
various FUSE messages.

Add myself as a maintainer of virtio.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass
2025-06-26 07:08:18 -06:00
parent eb8eeed91b
commit f4b820f8ec
8 changed files with 675 additions and 0 deletions

View File

@@ -85,4 +85,17 @@ config VIRTIO_RNG
help
This is the virtual random number generator driver. It can be used
with QEMU based targets.
config VIRTIO_FS
bool "virtio filesystem driver"
depends on VIRTIO && FS
default y
help
Provides support for virtio-fs which provides access to host files
within the guest OS. This needs a user-space helper (virtiofsd) when
running QEMU - see https://virtio-fs.gitlab.io/ for details.
A specification for the protocol is available at
https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html
endmenu

View File

@@ -12,3 +12,4 @@ obj-$(CONFIG_VIRTIO_SANDBOX_EMUL) += sandbox_emul.o emul_blk.o
obj-$(CONFIG_VIRTIO_NET) += virtio_net.o
obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
obj-$(CONFIG_VIRTIO_FS) += fs.o

427
drivers/virtio/fs.c Normal file
View File

@@ -0,0 +1,427 @@
// SPDX-License-Identifier: GPL-2.0
/*
* U-Boot Virtio-FS Driver
*
* Copyright 2025 Simon Glass <sjg@chromium.org>
*
* This driver provides U-Boot with the ability to access a virtio-fs
* device, allowing the bootloader to read files from a shared directory
* on the host. This is particularly useful for loading kernels, device
* tree blobs, and other boot-time resources.
*
* The driver is implemented using the U-Boot driver model and the virtio
* uclass. It communicates with the host using the FUSE protocol over
* virtqueues.
*/
#define LOG_CATEGORY UCLASS_VIRTIO
#include <dir.h>
#include <dm.h>
#include <fs.h>
#include <log.h>
#include <virtio.h>
#include <virtio_fs.h>
#include <virtio_ring.h>
#include <linux/fuse.h>
#include "fs_internal.h"
#define MAX_FNAME_LEN 256 /* including \0 terminator */
#define VIRTIO_FS_TAG_SIZE 36
/**
* struct virtio_fs_config - Configuration info for virtio-fs
*
* Modelled on Linux v6.15 include/uapi/linux/type.h
*
* @tag: filesystem name padded with NUL
*/
struct __packed virtio_fs_config {
u8 tag[VIRTIO_FS_TAG_SIZE];
__le32 unused1;
__le32 unused2;
};
/*
* Driver-private data
*
* @tag: Tag for the filesystem
* @root: inode of the root node, or 0 if not known
* @vdev: virtio device to which this FS is attached
* @vq: Virtual queue to use
* @config: Configuration read from QEMU
* @root_inode: Inode of the root directory exported into QEMU
* @next_id: ID to use for the next FUSE request
*/
struct virtio_fs_priv {
char tag[VIRTIO_FS_TAG_SIZE + 1];
struct virtio_device *vdev;
struct virtqueue *vq;
struct virtio_fs_config config;
u64 root_inode;
int next_id;
};
u64 virtio_fs_get_root(struct udevice *dev)
{
struct virtio_fs_priv *priv = dev_get_priv(dev);
return priv->root_inode;
}
/**
* virtio_fs_xfer() - Perform a transfer of a FUSE request
*
* @dev: virtio-fs device to use (UCLASS_FS)
* @inhdr: FUSE header to send
* @in: Extra data to send (must be present)
* @insize: Size of data to send
* @outhdr: Returns the output header received via FUSE
* @out: Place to put extra output data (NULL if none)
* @outsize: Size of output buffer at @out (must be 0 if @out is NULL)
* Return: 0 if OK, -ve on error
*/
static int virtio_fs_xfer(struct udevice *dev, struct fuse_in_header *inhdr,
const void *in, int insize,
struct fuse_out_header *outhdr, void *out,
int outsize)
{
struct virtio_fs_priv *priv = dev_get_priv(dev);
struct virtqueue *vq = priv->vq;
struct virtio_sg sg[4];
struct virtio_sg *sgs[4];
uint dummy_len;
int i, ret;
inhdr->unique = priv->next_id++;
sg[0].addr = inhdr;
sg[0].length = sizeof(*inhdr);
sg[1].addr = (void *)in;
sg[1].length = insize;
sg[2].addr = outhdr;
sg[2].length = sizeof(*outhdr);
sg[3].addr = out;
sg[3].length = outsize;
for (i = 0; i < 4; i++)
sgs[i] = &sg[i];
inhdr->len = sizeof(*inhdr) + insize;
outhdr->len = sizeof(*outhdr) + outsize;
log_debug("sg[1].addr %p sg[3].length %zx\n", sg[1].addr, sg[3].length);
ret = virtqueue_add(vq, sgs, 2, out ? 2 : 1);
if (ret) {
log_err("Failed to add buffers to virtqueue\n");
return ret;
}
virtqueue_kick(vq);
log_debug("wait...");
while (!virtqueue_get_buf(vq, &dummy_len))
;
log_debug("done\n");
if (outhdr->error)
return log_msg_ret("vix", outhdr->error);
return 0;
}
int virtio_fs_lookup_(struct udevice *dev, u64 nodeid, const char *name,
struct fuse_entry_out *out)
{
struct fuse_in_header inhdr = {};
struct fuse_out_header outhdr;
int name_len = strlen(name) + 1;
int ret;
inhdr.opcode = FUSE_LOOKUP;
inhdr.nodeid = nodeid;
ret = virtio_fs_xfer(dev, &inhdr, name, name_len, &outhdr, out,
sizeof(struct fuse_entry_out));
log_debug("len %x error %x unique %llx\n", outhdr.len, outhdr.error,
outhdr.unique);
if (ret)
return log_msg_ret("vfl", ret);
return 0;
}
int virtio_fs_lookup(struct udevice *dev, const char *name, u64 *entryp)
{
struct virtio_fs_priv *priv = dev_get_priv(dev);
struct fuse_entry_out out;
int ret;
log_debug("dev '%s': lookup in inode %llx name '%s'\n", dev->name,
priv->root_inode, name);
ret = virtio_fs_lookup_(dev, priv->root_inode, name, &out);
if (ret)
return log_msg_ret("vfl", ret);
*entryp = out.nodeid;
return 0;
}
int virtio_fs_forget(struct udevice *dev, u64 nodeid)
{
struct fuse_in_header inhdr = {};
struct fuse_forget_in in;
struct fuse_out_header outhdr;
int ret;
inhdr.opcode = FUSE_FORGET;
inhdr.nodeid = nodeid;
in.nlookup = 1;
ret = virtio_fs_xfer(dev, &inhdr, &in, sizeof(in), &outhdr, NULL, 0);
log_debug("len %x error %x unique %llx\n", outhdr.len, outhdr.error,
outhdr.unique);
if (ret)
return log_msg_ret("vfl", ret);
return 0;
}
int virtio_fs_opendir(struct udevice *dev, u64 nodeid, u64 *fhp)
{
struct fuse_in_header inhdr = {};
struct fuse_open_in in = {};
struct fuse_out_header outhdr;
struct fuse_open_out out;
int ret;
inhdr.opcode = FUSE_OPENDIR;
inhdr.nodeid = nodeid;
ret = virtio_fs_xfer(dev, &inhdr, &in, sizeof(in), &outhdr, &out,
sizeof(struct fuse_open_out));
log_debug("fh %llx open_flags %x backing_id %x\n", out.fh,
out.open_flags, out.backing_id);
log_debug("len %x error %x unique %llx\n", outhdr.len, outhdr.error,
outhdr.unique);
if (ret)
return log_msg_ret("vfo", ret);
*fhp = out.fh;
return 0;
}
int virtio_fs_readdir(struct udevice *dev, u64 nodeid, u64 fh, u64 offset,
void *buf, int size, int *out_sizep)
{
struct fuse_in_header inhdr = {};
struct fuse_read_in in = {};
struct fuse_out_header outhdr;
uint len;
int ret;
inhdr.opcode = FUSE_READDIRPLUS;
inhdr.nodeid = nodeid;
in.fh = fh;
in.size = size;
in.offset = offset;
ret = virtio_fs_xfer(dev, &inhdr, &in, sizeof(in), &outhdr, buf, size);
log_debug("len %x error %x unique %llx\n", outhdr.len, outhdr.error,
outhdr.unique);
if (ret)
return log_msg_ret("vfr", ret);
len = outhdr.len - sizeof(outhdr);
/* sanity check */
if (len > size) {
log_debug("virtio: internal size error outhdr.len %x size %x\n",
outhdr.len, size);
return log_msg_ret("vle", -EFAULT);
}
*out_sizep = len;
return 0;
}
int virtio_fs_releasedir(struct udevice *dev, u64 nodeid, u64 fh)
{
struct fuse_in_header inhdr = {};
struct fuse_release_in in = {};
struct fuse_out_header outhdr;
int ret;
inhdr.opcode = FUSE_RELEASEDIR;
inhdr.nodeid = nodeid;
in.fh = fh;
ret = virtio_fs_xfer(dev, &inhdr, &in, sizeof(in), &outhdr, NULL, 0);
if (ret)
return log_msg_ret("vfe", ret);
log_debug("len %x error %x unique %llx\n", outhdr.len, outhdr.error,
outhdr.unique);
return 0;
}
int virtio_fs_open_file(struct udevice *dev, u64 nodeid,
enum dir_open_flags_t flags, u64 *fhp, uint *flagsp)
{
struct fuse_in_header inhdr = {};
char buf[MAX_FNAME_LEN + sizeof(struct fuse_open_in)];
struct fuse_open_in *in = (struct fuse_open_in *)buf;
struct fuse_out_header outhdr;
struct fuse_open_out out;
int ret;
inhdr.opcode = FUSE_OPEN;
inhdr.nodeid = nodeid;
in->flags = 0;
in->open_flags = flags;
ret = virtio_fs_xfer(dev, &inhdr, buf, sizeof(*in), &outhdr,
&out, sizeof(struct fuse_open_out));
if (ret)
return log_msg_ret("vfr", ret);
*fhp = out.fh;
*flagsp = out.open_flags;
return 0;
}
long virtio_fs_read(struct udevice *dev, u64 nodeid, u64 fh, u64 offset,
void *buf, uint size)
{
struct fuse_in_header inhdr = {};
struct fuse_read_in in = {};
struct fuse_out_header outhdr;
int ret;
log_debug("dev '%s' nodeid %lld fh %lld offset %llx size %x buf %p\n",
dev->name, nodeid, fh, offset, size, buf);
inhdr.opcode = FUSE_READ;
inhdr.nodeid = nodeid;
in.fh = fh;
in.offset = offset;
in.size = size;
ret = virtio_fs_xfer(dev, &inhdr, &in, sizeof(in), &outhdr, buf, size);
if (ret)
return log_msg_ret("vfr", ret);
log_debug("read len %x\n", outhdr.len);
return outhdr.len;
}
static int virtio_fs_init(struct udevice *dev)
{
struct fuse_in_header inhdr = {};
struct fuse_init_in in = {};
struct fuse_out_header outhdr;
struct fuse_init_out out;
int ret;
inhdr.opcode = FUSE_INIT;
in.major = FUSE_KERNEL_VERSION;
in.minor = FUSE_KERNEL_MINOR_VERSION;
ret = virtio_fs_xfer(dev, &inhdr, &in, sizeof(in), &outhdr, &out,
sizeof(out));
if (ret)
return log_msg_ret("vfx", ret);
log_debug("major %x minor %x max_readahead %x flags %x ", out.major,
out.minor, out.max_readahead, out.flags);
log_debug("max_background %x congestion_threshold %x max_write %x\n",
out.max_background, out.congestion_threshold, out.max_write);
log_debug("time_gran %x max_pages %x, map_alignment %x flags2 %x ",
out.time_gran, out.max_pages, out.map_alignment, out.flags2);
log_debug("max_stack_depth %x request_timeout %x\n",
out.max_stack_depth, out.request_timeout);
return 0;
}
static int _virtio_fs_probe(struct udevice *dev)
{
struct virtio_dev_priv *virtio_priv = dev_get_uclass_priv(dev->parent);
struct virtio_fs_priv *priv = dev_get_priv(dev);
int ret;
/* Indicate what driver features we support */
virtio_driver_features_init(virtio_priv, NULL, 0, NULL, 0);
virtio_cread_bytes(dev, 0, &priv->tag, VIRTIO_FS_TAG_SIZE);
priv->tag[VIRTIO_FS_TAG_SIZE] = '\0';
log_debug("tag %s\n", priv->tag);
ret = virtio_find_vqs(dev, 1, &priv->vq);
if (ret)
return log_msg_ret("vff", ret);
return 0;
}
static int virtio_fs_mount(struct udevice *dev)
{
struct fs_priv *uc_priv = dev_get_uclass_priv(dev);
struct virtio_fs_priv *priv = dev_get_priv(dev);
struct fuse_entry_out out;
int ret;
if (uc_priv->mounted)
return log_msg_ret("vfi", -EISCONN);
ret = virtio_fs_init(dev);
if (ret)
return log_msg_ret("vfi", ret);
ret = virtio_fs_lookup_(dev, FUSE_ROOT_ID, ".", &out);
if (ret) {
log_err("Failed to lookup root directory: %d\n", ret);
return ret;
}
priv->root_inode = out.nodeid;
log_debug("directory found, ino=%lld\n", priv->root_inode);
uc_priv->mounted = true;
return 0;
}
static int virtio_fs_unmount(struct udevice *dev)
{
struct fs_priv *uc_priv = dev_get_uclass_priv(dev);
struct virtio_fs_priv *priv = dev_get_priv(dev);
int ret;
if (!uc_priv->mounted)
return log_msg_ret("vfu", -ENOTCONN);
ret = virtio_fs_forget(dev, priv->root_inode);
if (ret)
return log_msg_ret("vff", ret);
return 0;
}
static const struct fs_ops virtio_fs_ops = {
.mount = virtio_fs_mount,
.unmount = virtio_fs_unmount,
};
static const struct udevice_id virtio_fs_ids[] = {
{ .compatible = "virtio,fs" },
{ }
};
U_BOOT_DRIVER(virtio_fs) = {
.name = VIRTIO_FS_DRV_NAME,
.id = UCLASS_FS,
.of_match = virtio_fs_ids,
.ops = &virtio_fs_ops,
.probe = _virtio_fs_probe,
.priv_auto = sizeof(struct virtio_fs_priv),
.flags = DM_FLAG_ACTIVE_DMA,
};

View File

@@ -0,0 +1,149 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* U-Boot Virtio-FS directories
*
* Copyright 2025 Simon Glass <sjg@chromium.org>
*
* Supports access to directories in virtio-fs
*/
#ifndef _FS_INTERNAL_H
#define _FS_INTERNAL_H
#include <dir.h>
struct fuse_entry_out;
struct udevice;
/**
* struct virtio_fs_dir_priv - Information about a directory
*
* @inode: Associated inode for the directory
* @path: Path of this directory, e.g. '/fred/mary', or NULL for the root
* directory
*/
struct virtio_fs_dir_priv {
u64 inode;
char *path;
};
/**
* virtio_fs_lookup() - Look up an entry in a directory
*
* @dev: Filesystem device which holds the directory (UCLASS_FS)
* @nodeid: Node ID of the directory containing the entry
* @name: Name of the entry
* @out: Returns lookup info
* Return: 0 on success, -ENOENT if not found, other -ve on other error
*/
int virtio_fs_lookup_(struct udevice *dev, u64 nodeid, const char *name,
struct fuse_entry_out *out);
/**
* virtio_fs_lookup() - Look up an entry in a directory
*
* This is a simplified wrapper around virtio_fs_lookup_() that performs a
* lookup within the filesystem's root directory.
*
* @dev: Filesystem device which holds the directory (UCLASS_FS)
* @name: Name of the entry
* @entryp: Returns the node ID of the entry, on success
* Return: 0 on success, -ENOENT if not found, other -ve on other error
*/
int virtio_fs_lookup(struct udevice *dev, const char *name, u64 *entryp);
/**
* virtio_fs_forget() - Forget a nodeid
*
* Tells FUSE that this nodeid is no-longer needed
*
* @dev: Filesystem device which holds the directory (UCLASS_FS)
* @nodeid: Node ID to forget
* Return: 0 on success, -ve on error
*/
int virtio_fs_forget(struct udevice *dev, u64 nodeid);
/**
* virtio_fs_opendir() - Open a directory for reading
*
* @dev: Filesystem device which holds the directory (UCLASS_FS)
* @nodeid: Node ID of the directory
* @fhp: Returns unique filehandle for the directory
* Return: 0 if OK, -ve on error
*/
int virtio_fs_opendir(struct udevice *dev, u64 nodeid, u64 *fhp);
/**
* virtio_fs_readdir() - Read a chunk of entries from a directory
*
* Fills in @buf with directory records, using an internal FUSE format. The
* format is one struct fuse_direntplus (plus a name string) for each record.
* See include/linux/fuse.h for the struct details (unfortunately not
* commented). Use FUSE_DIRENTPLUS_SIZE() to calculate the size of each entry.
*
* It would be possible to convert the format into struct fs_dirent but the
* API which uses it only allows returning a single record at a time, so it
* seems best to tackle that later.
*
* @dev: Filesystem device which holds the directory (UCLASS_FS)
* @nodeid: Node ID of the directory
* @fh: Unique filehandle for the directory
* @offset: Start offset for reading; use the value returned from the previous
* call, or 0 to start at the beginning
* @buf: Buffer to receive records, must be large enough to hold at least one
* struct fuse_direntplus
* @size: Size of buffer
* @out_sizep: Returns amount of data used in the buffer
*/
int virtio_fs_readdir(struct udevice *dev, u64 nodeid, u64 fh, u64 offset,
void *buf, int size, int *out_sizep);
/**
* virtio_fs_releasedir() - Close a directory
*
* Use this on a directory opened with virtio_fs_opendir() when you have
* finished reading entries with virtio_fs_readdir()
*
* @dev: Filesystem device which holds the directory (UCLASS_FS)
* @nodeid: Node ID of the directory
* @fh: Unique filehandle for the directory
*/
int virtio_fs_releasedir(struct udevice *dev, u64 nodeid, u64 fh);
/**
* virtio_fs_open_file() - Open a file
*
* @dev: Filesystem device which holds the file (UCLASS_FS)
* @nodeid: Node ID of the directory containing the file
* @flags: Open-mode flags to use
* @fhp: Return unique filehandle for the directory
* @flagsp: Updated open-mode flags
*/
int virtio_fs_open_file(struct udevice *dev, u64 nodeid,
enum dir_open_flags_t flags, u64 *fhp, uint *flagsp);
/**
* virtio_fs_get_root() - Get the nodeid of the root directory of the virtio-fs
*
* Return: node ID of root node
*/
u64 virtio_fs_get_root(struct udevice *dev);
/**
* virtio_fs_read() - Read data from an open file
*
* Read a block of data from a file previously opened with
* virtio_fs_open_file()
*
* @dev: Filesystem device (UCLASS_FS)
* @nodeid: Node ID of the file being read (for context)
* @fh: File handle of the open file
* @offset: Offset within the file to start reading from
* @buf: Buffer to store the read data
* @size: Maximum number of bytes to read
* Return: Number of bytes read on success, -ve on error
*/
long virtio_fs_read(struct udevice *dev, u64 nodeid, u64 fh, u64 offset,
void *buf, uint size);
#endif

View File

@@ -30,6 +30,7 @@ static const char *const virtio_drv_name[VIRTIO_ID_MAX_NUM] = {
[VIRTIO_ID_NET] = VIRTIO_NET_DRV_NAME,
[VIRTIO_ID_BLOCK] = VIRTIO_BLK_DRV_NAME,
[VIRTIO_ID_RNG] = VIRTIO_RNG_DRV_NAME,
[VIRTIO_ID_FS] = VIRTIO_FS_DRV_NAME,
};
int virtio_get_config(struct udevice *vdev, unsigned int offset,