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>
432 lines
11 KiB
C
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,
|
|
};
|