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 <viresh.kumar@linaro.org>
---
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 <viresh.kumar@linaro.org>
+ *
+ * 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 <viresh.kumar@linaro.org>");
+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 <viresh.kumar@linaro.org>
+ */
+
+#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
On 30/07/2025 11:29, Viresh Kumar wrote: > 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. No, because I can name regions how I wish. You cannot add undocumented ABI and claim in commit msg, that the commit msg is the ABI. Either you have ABI documented or you cannot rely on implicit ABI, thus remove above restriction. This is really poor choice to add such hidden ABI and it already broke several users in the past. Best regards, Krzysztof
© 2016 - 2025 Red Hat, Inc.