Files
u-boot/drivers/virtio/fs.c
Simon Glass 753438e516 virtio: Add a little more debugging
Add some debugging to virtiofs in the directory-handling area.

Drop a stray, blank line while here.

Signed-off-by: Simon Glass <sjg@chromium.org>
2025-07-12 14:29:06 +02:00

432 lines
11 KiB
C

// 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("fh %llx offset %llx\n", in.fh, in.offset);
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);
if (IS_ENABLED(CONFIG_SANDBOX))
return log_msg_ret("vfs", -ENOENT);
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,
.lookup_dir = virtio_fs_setup_dir,
};
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,
};