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.ku...@linaro.org>
---
 drivers/virtio/Kconfig               |   8 ++
 drivers/virtio/Makefile              |   4 +-
 drivers/virtio/virtio_msg_internal.h |  32 ++++++
 drivers/virtio/virtio_msg_user.c     | 140 +++++++++++++++++++++++++++
 4 files changed, 183 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_internal.h 
b/drivers/virtio/virtio_msg_internal.h
index b7c2cb44b67b..0d13d73507eb 100644
--- a/drivers/virtio/virtio_msg_internal.h
+++ b/drivers/virtio/virtio_msg_internal.h
@@ -9,6 +9,8 @@
 #ifndef _DRIVERS_VIRTIO_MSG_INTERNAL_H
 #define _DRIVERS_VIRTIO_MSG_INTERNAL_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_MSG_INTERNAL_H */
diff --git a/drivers/virtio/virtio_msg_user.c b/drivers/virtio/virtio_msg_user.c
new file mode 100644
index 000000000000..cf7286b3a311
--- /dev/null
+++ b/drivers/virtio/virtio_msg_user.c
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Virtio message transport user API.
+ *
+ * Copyright (C) 2025 Google LLC and Linaro.
+ * Viresh Kumar <viresh.ku...@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_internal.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,
+};
+
+/**
+ * virtio_msg_user_register - Register a user-space accessible virtio message 
device
+ * @vmudev: Pointer to the virtio message user device
+ *
+ * Initializes and registers a user-accessible virtio message device as a 
`misc`
+ * character device. Upon successful registration, the device appears in
+ * userspace as `/dev/virtio-msg-N` where `N` is a unique identifier assigned 
at
+ * runtime.
+ *
+ * The resulting device node allows user-space interaction with the virtio
+ * message transport.
+ *
+ * Return: 0 on success, or a negative error code on failure.
+ */
+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);
+
+/**
+ * virtio_msg_user_unregister - Unregister a user-space virtio message device
+ * @vmudev: Pointer to the virtio message user device
+ *
+ * Unregisters a previously registered virtio message device from the misc
+ * subsystem. This removes its user-space interface (e.g., /dev/virtio-msg-N).
+ */
+void virtio_msg_user_unregister(struct virtio_msg_user_device *vmudev)
+{
+       misc_deregister(&vmudev->misc);
+}
+EXPORT_SYMBOL_GPL(virtio_msg_user_unregister);
-- 
2.31.1.272.g89b43f80a514


Reply via email to