Add support for an optional userspace interface to the virtio-msg transport via a per-bus miscdevice. When enabled by a bus implementation, this interface allows userspace to send and receive virtio messages through a character device node.
A separate device node is created for each bus that registers for userspace access, e.g., /dev/virtio-msg-N. This enables backend-side components or test tools to interact with the transport layer directly from userspace.
Bus implementations that do not require userspace interaction can omit this interface entirely.
Signed-off-by: Viresh Kumar viresh.kumar@linaro.org --- drivers/virtio/Kconfig | 8 +++ drivers/virtio/Makefile | 4 +- drivers/virtio/virtio_msg.h | 32 +++++++++ drivers/virtio/virtio_msg_user.c | 119 +++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 drivers/virtio/virtio_msg_user.c
diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index 690ac98850b6..a86025c9e008 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -178,6 +178,14 @@ config VIRTIO_MSG This enables support for Virtio message transport. This option is selected by any driver which implements the virtio message bus.
+config VIRTIO_MSG_USER + tristate "Userspace interface for virtio message transport" + depends on VIRTIO_MSG + help + This enables userspace interface for Virtio message transport. This + can be used to read / write messages over virtio-msg transport from + userspace. + config VIRTIO_DMA_SHARED_BUFFER tristate depends on DMA_SHARED_BUFFER diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile index 3eff8ca72446..5b664c5f5f25 100644 --- a/drivers/virtio/Makefile +++ b/drivers/virtio/Makefile @@ -4,7 +4,9 @@ obj-$(CONFIG_VIRTIO_ANCHOR) += virtio_anchor.o obj-$(CONFIG_VIRTIO_PCI_LIB) += virtio_pci_modern_dev.o obj-$(CONFIG_VIRTIO_PCI_LIB_LEGACY) += virtio_pci_legacy_dev.o obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o -obj-$(CONFIG_VIRTIO_MSG) += virtio_msg.o +virtio_msg_transport-y := virtio_msg.o +virtio_msg_transport-$(CONFIG_VIRTIO_MSG_USER) += virtio_msg_user.o +obj-$(CONFIG_VIRTIO_MSG) += virtio_msg_transport.o obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o virtio_pci-y := virtio_pci_modern.o virtio_pci_common.o virtio_pci-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o diff --git a/drivers/virtio/virtio_msg.h b/drivers/virtio/virtio_msg.h index 099bb2f0f679..b42c9d6f483a 100644 --- a/drivers/virtio/virtio_msg.h +++ b/drivers/virtio/virtio_msg.h @@ -9,6 +9,8 @@ #ifndef _DRIVERS_VIRTIO_VIRTIO_MSG_H #define _DRIVERS_VIRTIO_VIRTIO_MSG_H
+#include <linux/completion.h> +#include <linux/miscdevice.h> #include <linux/virtio.h> #include <uapi/linux/virtio_msg.h>
@@ -53,4 +55,34 @@ void virtio_msg_unregister(struct virtio_msg_device *vmdev); void virtio_msg_prepare(struct virtio_msg *vmsg, u8 msg_id, u16 payload_size); int virtio_msg_event(struct virtio_msg_device *vmdev, struct virtio_msg *vmsg);
+/* Virtio msg userspace interface */ +struct virtio_msg_user_device; + +struct virtio_msg_user_ops { + int (*handle)(struct virtio_msg_user_device *vmudev, struct virtio_msg *vmsg); +}; + +/* Host side device using virtio message */ +struct virtio_msg_user_device { + struct virtio_msg_user_ops *ops; + struct miscdevice misc; + struct completion r_completion; + struct completion w_completion; + struct virtio_msg *vmsg; + struct device *parent; + char name[15]; +}; + +#if IS_REACHABLE(CONFIG_VIRTIO_MSG_USER) +int virtio_msg_user_register(struct virtio_msg_user_device *vmudev); +void virtio_msg_user_unregister(struct virtio_msg_user_device *vmudev); +#else +static inline int virtio_msg_user_register(struct virtio_msg_user_device *vmudev) +{ + return -EOPNOTSUPP; +} + +static inline void virtio_msg_user_unregister(struct virtio_msg_user_device *vmudev) {} +#endif /* CONFIG_VIRTIO_MSG_USER */ + #endif /* _DRIVERS_VIRTIO_VIRTIO_MSG_H */ diff --git a/drivers/virtio/virtio_msg_user.c b/drivers/virtio/virtio_msg_user.c new file mode 100644 index 000000000000..479df68060a5 --- /dev/null +++ b/drivers/virtio/virtio_msg_user.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Virtio message transport user API. + * + * Copyright (C) 2025 Google LLC and Linaro. + * Viresh Kumar viresh.kumar@linaro.org + */ + +#define pr_fmt(fmt) "virtio-msg: " fmt + +#include <linux/err.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/uaccess.h> + +#include "virtio_msg.h" + +#define to_virtio_msg_user_device(_misc) \ + container_of(_misc, struct virtio_msg_user_device, misc) + +static ssize_t vmsg_miscdev_read(struct file *file, char __user *buf, + size_t count, loff_t *pos) +{ + struct miscdevice *misc = file->private_data; + struct virtio_msg_user_device *vmudev = to_virtio_msg_user_device(misc); + struct device *dev = vmudev->parent; + int ret; + + if (count < VIRTIO_MSG_MIN_SIZE) { + dev_err(dev, "Trying to read message of incorrect size: %zu\n", + count); + return 0; + } + + /* Wait for the message */ + ret = wait_for_completion_interruptible(&vmudev->r_completion); + if (ret < 0) { + dev_err(dev, "Interrupted while waiting for response: %d\n", ret); + return 0; + } + + WARN_ON(!vmudev->vmsg); + + /* The "vmsg" pointer is filled by the bus driver before waking up */ + if (copy_to_user(buf, vmudev->vmsg, count) != 0) + return 0; + + vmudev->vmsg = NULL; + + return count; +} + +static ssize_t vmsg_miscdev_write(struct file *file, const char __user *buf, + size_t count, loff_t *pos) +{ + struct miscdevice *misc = file->private_data; + struct virtio_msg_user_device *vmudev = to_virtio_msg_user_device(misc); + struct virtio_msg *vmsg __free(kfree) = NULL; + + if (count < VIRTIO_MSG_MIN_SIZE) { + dev_err(vmudev->parent, "Trying to write message of incorrect size: %zu\n", + count); + return 0; + } + + vmsg = kzalloc(count, GFP_KERNEL); + if (!vmsg) + return 0; + + if (copy_from_user(vmsg, buf, count) != 0) + return 0; + + vmudev->ops->handle(vmudev, vmsg); + + /* Wake up the handler only for responses */ + if (vmsg->type & VIRTIO_MSG_TYPE_RESPONSE) + complete(&vmudev->w_completion); + + return count; +} + +static const struct file_operations vmsg_miscdev_fops = { + .owner = THIS_MODULE, + .read = vmsg_miscdev_read, + .write = vmsg_miscdev_write, +}; + +int virtio_msg_user_register(struct virtio_msg_user_device *vmudev) +{ + static u8 vmsg_user_device_count; + int ret; + + if (!vmudev || !vmudev->ops) + return -EINVAL; + + init_completion(&vmudev->r_completion); + init_completion(&vmudev->w_completion); + + vmudev->misc.parent = vmudev->parent; + vmudev->misc.minor = MISC_DYNAMIC_MINOR; + vmudev->misc.fops = &vmsg_miscdev_fops; + vmudev->misc.name = vmudev->name; + sprintf(vmudev->name, "virtio-msg-%d", vmsg_user_device_count); + + ret = misc_register(&vmudev->misc); + if (ret) + return ret; + + vmsg_user_device_count++; + return 0; +} +EXPORT_SYMBOL_GPL(virtio_msg_user_register); + +void virtio_msg_user_unregister(struct virtio_msg_user_device *vmudev) +{ + misc_deregister(&vmudev->misc); +} +EXPORT_SYMBOL_GPL(virtio_msg_user_unregister);