Add a loopback bus implementation for the virtio-msg transport.

This bus simulates a backend that echoes messages to itself, allowing
testing and development of virtio-msg without requiring an actual remote
backend or transport hardware.

The loopback bus requires a reserved memory region for its operation.
All DMA-coherent and streaming DMA allocations are restricted to this
region, enabling safe mapping into user space and helping validate the
memory-sharing model.

The reserved-memory region must be named "vmsglb" in the device tree.
Example:

  reserved-memory {
    #address-cells = <2>;
    #size-cells   = <2>;
    ranges;

    vmsglb@100000000 {
      compatible = "restricted-dma-pool";
      reg = <0x00000001 0x00000000  0x0 0x00400000>; /* 4 MiB */
    };
  };

This bus is primarily intended for functional testing, development, and
validation of the virtio-msg transport and its userspace interface.

Signed-off-by: Viresh Kumar <[email protected]>
---
 drivers/virtio/Kconfig               |   9 +
 drivers/virtio/Makefile              |   1 +
 drivers/virtio/virtio_msg_loopback.c | 323 +++++++++++++++++++++++++++
 include/uapi/linux/virtio_msg_lb.h   |  22 ++
 4 files changed, 355 insertions(+)
 create mode 100644 drivers/virtio/virtio_msg_loopback.c
 create mode 100644 include/uapi/linux/virtio_msg_lb.h

diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
index 683152477e3f..934e8ccb3a01 100644
--- a/drivers/virtio/Kconfig
+++ b/drivers/virtio/Kconfig
@@ -196,6 +196,15 @@ config VIRTIO_MSG_FFA
 
         If unsure, say N.
 
+config VIRTIO_MSG_LOOPBACK
+       tristate "Loopback bus driver for virtio message transport"
+       select VIRTIO_MSG
+       select VIRTIO_MSG_USER
+       help
+        This implements the Loopback bus for Virtio msg transport.
+
+        If unsure, say N.
+
 config VIRTIO_DMA_SHARED_BUFFER
        tristate
        depends on DMA_SHARED_BUFFER
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
index 96ec0a9c4a7a..90a2f1d86937 100644
--- a/drivers/virtio/Makefile
+++ b/drivers/virtio/Makefile
@@ -7,6 +7,7 @@ obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.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_MSG_LOOPBACK) += virtio_msg_loopback.o
 obj-$(CONFIG_VIRTIO_MSG_FFA) += virtio_msg_ffa.o
 obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o
 virtio_pci-y := virtio_pci_modern.o virtio_pci_common.o
diff --git a/drivers/virtio/virtio_msg_loopback.c 
b/drivers/virtio/virtio_msg_loopback.c
new file mode 100644
index 000000000000..d1d454fadc6f
--- /dev/null
+++ b/drivers/virtio/virtio_msg_loopback.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Loopback bus implementation for Virtio message transport.
+ *
+ * Copyright (C) 2025 Google LLC and Linaro.
+ * Viresh Kumar <[email protected]>
+ *
+ * This implements the Loopback bus for Virtio msg transport.
+ */
+
+#define pr_fmt(fmt) "virtio-msg-loopback: " fmt
+
+#include <linux/err.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/virtio.h>
+#include <uapi/linux/virtio_msg.h>
+#include <uapi/linux/virtio_msg_lb.h>
+
+#include "virtio_msg_internal.h"
+
+struct vmlb_device {
+       struct virtio_msg_device vmdev;
+       struct list_head list;
+};
+
+struct virtio_msg_lb {
+       /* Serializes transfers and protects list */
+       struct mutex lock;
+       struct list_head list;
+       struct miscdevice misc;
+       struct virtio_msg_user_device vmudev;
+       struct virtio_msg *response;
+       struct reserved_mem *rmem;
+       struct device *dev;
+};
+
+static struct virtio_msg_lb *vmlb;
+
+#define to_vmlbdev(_vmdev)     ((struct vmlb_device *)(_vmdev)->bus_data)
+
+static struct vmlb_device *find_vmlbdev(u16 dev_id)
+{
+       struct vmlb_device *vmlbdev;
+
+       list_for_each_entry(vmlbdev, &vmlb->list, list) {
+               if (vmlbdev->vmdev.dev_id == dev_id)
+                       return vmlbdev;
+       }
+
+       return NULL;
+}
+
+static const char *virtio_msg_lb_bus_info(struct virtio_msg_device *vmdev,
+                                         u16 *msg_size, u32 *rev)
+{
+       *msg_size = VIRTIO_MSG_MIN_SIZE;
+       *rev = VIRTIO_MSG_REVISION_1;
+
+       return dev_name(vmlb->dev);
+}
+
+static int virtio_msg_lb_transfer(struct virtio_msg_device *vmdev,
+                                 struct virtio_msg *request,
+                                 struct virtio_msg *response)
+{
+       struct virtio_msg_user_device *vmudev = &vmlb->vmudev;
+       int ret;
+
+       /*
+        * Allow only one transaction to progress at once.
+        */
+       guard(mutex)(&vmlb->lock);
+
+       /*
+        * Set `vmsg` to `request` and finish the completion to wake up the
+        * `read()` thread.
+        */
+       vmudev->vmsg = request;
+       vmlb->response = response;
+       complete(&vmudev->r_completion);
+
+       /*
+        * Wait here for the `write()` thread to finish and not return before
+        * the operation is finished to avoid any potential races.
+        */
+       ret = wait_for_completion_interruptible_timeout(&vmudev->w_completion, 
1000);
+       if (ret < 0) {
+               dev_err(vmlb->dev, "Interrupted while waiting for response: 
%d\n", ret);
+       } else if (!ret) {
+               dev_err(vmlb->dev, "Timed out waiting for response\n");
+               ret = -ETIMEDOUT;
+       } else {
+               ret = 0;
+       }
+
+       /* Clear the pointers, just to be safe */
+       vmudev->vmsg = NULL;
+       vmlb->response = NULL;
+
+       return ret;
+}
+
+static struct virtio_msg_ops virtio_msg_lb_ops = {
+       .transfer = virtio_msg_lb_transfer,
+       .bus_info = virtio_msg_lb_bus_info,
+};
+
+static int virtio_msg_lb_user_handle(struct virtio_msg_user_device *vmudev,
+                                    struct virtio_msg *vmsg)
+{
+       struct vmlb_device *vmlbdev;
+
+       /* Response message */
+       if (vmsg->type & VIRTIO_MSG_TYPE_RESPONSE) {
+               if (vmlb->response)
+                       memcpy(vmlb->response, vmsg, VIRTIO_MSG_MIN_SIZE);
+
+               return 0;
+       }
+
+       /* Only support EVENT_USED virtio request messages */
+       if (vmsg->type & VIRTIO_MSG_TYPE_BUS || vmsg->msg_id != 
VIRTIO_MSG_EVENT_USED) {
+               dev_err(vmlb->dev, "Unsupported message received\n");
+               return 0;
+       }
+
+       vmlbdev = find_vmlbdev(le16_to_cpu(vmsg->dev_id));
+       if (!vmlbdev)
+               return 0;
+
+       virtio_msg_event(&vmlbdev->vmdev, vmsg);
+       return 0;
+}
+
+static struct virtio_msg_user_ops vmlb_user_ops = {
+       .handle = virtio_msg_lb_user_handle,
+};
+
+static int vmlbdev_add(struct file *file, struct vmsg_lb_dev_info *info)
+{
+       struct vmlb_device *vmlbdev;
+       int ret;
+
+       scoped_guard(mutex, &vmlb->lock) {
+               if (find_vmlbdev(info->dev_id))
+                       return -EEXIST;
+
+               vmlbdev = kzalloc(sizeof(*vmlbdev), GFP_KERNEL);
+               if (!vmlbdev)
+                       return -ENOMEM;
+
+               vmlbdev->vmdev.dev_id = info->dev_id;
+               vmlbdev->vmdev.ops = &virtio_msg_lb_ops;
+               vmlbdev->vmdev.vdev.dev.parent = vmlb->dev;
+               vmlbdev->vmdev.bus_data = vmlbdev;
+
+               list_add(&vmlbdev->list, &vmlb->list);
+       }
+
+       ret = virtio_msg_register(&vmlbdev->vmdev);
+       if (ret) {
+               dev_err(vmlb->dev, "Failed to register virtio msg lb device 
(%d)\n", ret);
+               goto out;
+       }
+
+       return 0;
+
+out:
+       scoped_guard(mutex, &vmlb->lock)
+               list_del(&vmlbdev->list);
+
+       kfree(vmlbdev);
+       return ret;
+}
+
+static int vmlbdev_remove(struct file *file, struct vmsg_lb_dev_info *info)
+{
+       struct vmlb_device *vmlbdev;
+
+       scoped_guard(mutex, &vmlb->lock) {
+               vmlbdev = find_vmlbdev(info->dev_id);
+               if (vmlbdev) {
+                       list_del(&vmlbdev->list);
+                       virtio_msg_unregister(&vmlbdev->vmdev);
+                       return 0;
+               }
+       }
+
+       dev_err(vmlb->dev, "Failed to find virtio msg lb device.\n");
+       return -ENODEV;
+}
+
+static void vmlbdev_remove_all(void)
+{
+       struct vmlb_device *vmlbdev, *tvmlbdev;
+
+       guard(mutex)(&vmlb->lock);
+
+       list_for_each_entry_safe(vmlbdev, tvmlbdev, &vmlb->list, list) {
+               virtio_msg_unregister(&vmlbdev->vmdev);
+               list_del(&vmlbdev->list);
+       }
+}
+
+static long vmlb_ioctl(struct file *file, unsigned int cmd, unsigned long data)
+{
+       struct vmsg_lb_dev_info info;
+
+       if (copy_from_user(&info, (void __user *)data, sizeof(info)))
+               return -EFAULT;
+
+       switch (cmd) {
+       case IOCTL_VMSG_LB_ADD:
+               return vmlbdev_add(file, &info);
+
+       case IOCTL_VMSG_LB_REMOVE:
+               return vmlbdev_remove(file, &info);
+
+       default:
+               return -ENOTTY;
+       }
+}
+
+static int vmlb_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       unsigned long size = vma->vm_end - vma->vm_start;
+       unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+       if (offset > vmlb->rmem->size - size)
+               return -EINVAL;
+
+       return remap_pfn_range(vma, vma->vm_start,
+                       (vmlb->rmem->base + offset) >> PAGE_SHIFT,
+                       size,
+                       vma->vm_page_prot);
+}
+
+static loff_t vmlb_llseek(struct file *file, loff_t offset, int whence)
+{
+       return fixed_size_llseek(file, offset, whence, vmlb->rmem->size);
+}
+
+static const struct file_operations vmlb_miscdev_fops = {
+       .owner = THIS_MODULE,
+       .unlocked_ioctl = vmlb_ioctl,
+       .mmap = vmlb_mmap,
+       .llseek = vmlb_llseek,
+};
+
+static int virtio_msg_lb_init(void)
+{
+       int ret;
+
+       vmlb = kzalloc(sizeof(*vmlb), GFP_KERNEL);
+       if (!vmlb)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&vmlb->list);
+       mutex_init(&vmlb->lock);
+       vmlb->vmudev.ops = &vmlb_user_ops;
+
+       vmlb->misc.name = "virtio-msg-lb";
+       vmlb->misc.minor = MISC_DYNAMIC_MINOR;
+       vmlb->misc.fops = &vmlb_miscdev_fops;
+
+       ret = misc_register(&vmlb->misc);
+       if (ret)
+               goto vmlb_free;
+
+       vmlb->dev = vmlb->misc.this_device;
+       vmlb->vmudev.parent = vmlb->dev;
+
+       vmlb->rmem = of_reserved_mem_lookup_by_name("vmsglb");
+       if (IS_ERR(vmlb->rmem)) {
+               ret = PTR_ERR(vmlb->rmem);
+               goto unregister;
+       }
+       ret = reserved_mem_device_init(vmlb->dev, vmlb->rmem);
+       if (ret)
+               goto mem_release;
+
+       /* Register with virtio-msg UAPI */
+       ret = virtio_msg_user_register(&vmlb->vmudev);
+       if (ret) {
+               dev_err(vmlb->dev, "Could not register virtio-msg user API\n");
+               goto mem_release;
+       }
+
+       ret = dma_coerce_mask_and_coherent(vmlb->dev, DMA_BIT_MASK(64));
+       if (ret)
+               dev_warn(vmlb->dev, "Failed to enable 64-bit or 32-bit DMA\n");
+
+       return 0;
+
+mem_release:
+       of_reserved_mem_device_release(vmlb->dev);
+unregister:
+       misc_deregister(&vmlb->misc);
+vmlb_free:
+       kfree(vmlb);
+       return ret;
+}
+module_init(virtio_msg_lb_init);
+
+static void virtio_msg_lb_exit(void)
+{
+       virtio_msg_user_unregister(&vmlb->vmudev);
+       of_reserved_mem_device_release(vmlb->dev);
+       vmlbdev_remove_all();
+       misc_deregister(&vmlb->misc);
+       kfree(vmlb);
+}
+module_exit(virtio_msg_lb_exit);
+
+MODULE_AUTHOR("Viresh Kumar <[email protected]>");
+MODULE_DESCRIPTION("Virtio message loopback bus driver");
+MODULE_LICENSE("GPL");
diff --git a/include/uapi/linux/virtio_msg_lb.h 
b/include/uapi/linux/virtio_msg_lb.h
new file mode 100644
index 000000000000..fe0ce6269a0a
--- /dev/null
+++ b/include/uapi/linux/virtio_msg_lb.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR 
BSD-3-Clause) */
+/*
+ * Virtio message Loopback bus header.
+ *
+ * Copyright (C) 2025 Google LLC and Linaro.
+ * Viresh Kumar <[email protected]>
+ */
+
+#ifndef _LINUX_VIRTIO_MSG_LB_H
+#define _LINUX_VIRTIO_MSG_LB_H
+
+struct vmsg_lb_dev_info {
+       unsigned int dev_id;
+};
+
+#define IOCTL_VMSG_LB_ADD                                      \
+       _IOC(_IOC_NONE, 'P', 0, sizeof(struct vmsg_lb_dev_info))
+
+#define IOCTL_VMSG_LB_REMOVE                                   \
+       _IOC(_IOC_NONE, 'P', 1, sizeof(struct vmsg_lb_dev_info))
+
+#endif /* _LINUX_VIRTIO_MSG_LB_H */
-- 
2.31.1.272.g89b43f80a514


Reply via email to