All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
@ 2026-06-17 20:31 Rishi Chhibber
  2026-06-18 10:26 ` Greg KH
                   ` (2 more replies)
  0 siblings, 3 replies; 9+ messages in thread
From: Rishi Chhibber @ 2026-06-17 20:31 UTC (permalink / raw)
  To: arnd, gregkh, linux-kernel
  Cc: ajay.kaher, alexey.makhalov, vamsi-krishna.brahmajosyula,
	yin.ding, tapas.kundu, Rishi Chhibber

This driver implements a character device (/dev/vmw_zc) that allows
guest userspace applications to share pinned memory buffers with a
VMware hypervisor-side peer using the VMCI datagram interface.

The driver pins user pages via get_user_pages_fast(), transmits their
physical page frame numbers to the hypervisor peer over VMCI, and
avoids an intermediate copy between the guest workload VM and the
hypervisor.

Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
---
 MAINTAINERS                                   |   8 +
 drivers/misc/Kconfig                          |   1 +
 drivers/misc/Makefile                         |   1 +
 drivers/misc/vmw_zerocopy/Kconfig             |  16 +
 drivers/misc/vmw_zerocopy/Makefile            |  17 +
 .../misc/vmw_zerocopy/vmw_zerocopy_driver.c   | 490 ++++++++++++++++++
 .../misc/vmw_zerocopy/vmw_zerocopy_driver.h   |  51 ++
 .../uapi/linux/vmw_zerocopy_ioctl_common.h    |  66 +++
 8 files changed, 650 insertions(+)
 create mode 100644 drivers/misc/vmw_zerocopy/Kconfig
 create mode 100644 drivers/misc/vmw_zerocopy/Makefile
 create mode 100644 drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
 create mode 100644 drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.h
 create mode 100644 include/uapi/linux/vmw_zerocopy_ioctl_common.h

diff --git a/MAINTAINERS b/MAINTAINERS
index efd1fa7d66f0..59ee66158486 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24790,6 +24790,14 @@ L:	linux-kernel@vger.kernel.org
 S:	Supported
 F:	net/vmw_vsock/vmci_transport*
 
+VMWARE ZEROCOPY DRIVER
+M:	Rishi Chhibber <rishi.chhibber@broadcom.com>
+R:	Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com>
+L:	linux-kernel@vger.kernel.org
+S:	Supported
+F:	drivers/misc/vmw_zerocopy/
+F:	include/uapi/linux/vmw_zerocopy*
+
 VOCORE VOCORE2 BOARD
 M:	Harvey Hunt <harveyhuntnexus@gmail.com>
 L:	linux-mips@vger.kernel.org
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index dcfe77b30bd5..6445a878ffdb 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -631,4 +631,5 @@ source "drivers/misc/keba/Kconfig"
 source "drivers/misc/vmw_extcfg/Kconfig"
 source "drivers/misc/vmw_sbx/Kconfig"
 source "drivers/misc/vmw_sbx_dtls/Kconfig"
+source "drivers/misc/vmw_zerocopy/Kconfig"
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e4f21eccc994..c2b14169d9ed 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_TPS6594_PFSM)	+= tps6594-pfsm.o
 obj-$(CONFIG_VMWARE_EXTCFG)	+= vmw_extcfg/
 obj-$(CONFIG_SBX)		+= vmw_sbx/
 obj-$(CONFIG_SBX_DTLS)		+= vmw_sbx_dtls/
+obj-$(CONFIG_VMW_ZC)		+= vmw_zerocopy/
 obj-$(CONFIG_NSM)		+= nsm.o
 obj-$(CONFIG_MARVELL_CN10K_DPI)	+= mrvl_cn10k_dpi.o
 obj-y				+= keba/
diff --git a/drivers/misc/vmw_zerocopy/Kconfig b/drivers/misc/vmw_zerocopy/Kconfig
new file mode 100644
index 000000000000..e042d950cb6f
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+config VMW_ZC
+	tristate "VMware zero-copy buffer sharing device"
+	depends on VMWARE_VMCI
+	help
+	  This driver implements a character device (/dev/vmw_zc) that
+	  allows guest userspace applications to share pinned memory
+	  buffers with a VMware hypervisor-side peer using the VMCI
+	  datagram interface.
+
+	  Applications submit buffers via ioctl(). The driver pins the
+	  user pages and transmits their physical page frame numbers to
+	  the peer, enabling zero-copy data transfer between the guest
+	  and the hypervisor without an intermediate copy.
+
+	  If unsure, say N.
diff --git a/drivers/misc/vmw_zerocopy/Makefile b/drivers/misc/vmw_zerocopy/Makefile
new file mode 100644
index 000000000000..df9eef8988d3
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Makefile for the VMware Zero Copy Virtual Device driver
+#
+
+ccflags-y			+= -I$(src)
+
+# "make M=drivers/misc/vmw_zerocopy modules" sets KBUILD_EXTMOD and skips
+# syncconfig, so include/config/auto.conf may not list new CONFIG_* symbols
+# even when they are present in .config - then obj-$(CONFIG_...) adds nothing.
+ifneq ($(KBUILD_EXTMOD),)
+obj-m += vmw_zerocopy.o
+else
+obj-$(CONFIG_VMW_ZC) += vmw_zerocopy.o
+endif
+
+vmw_zerocopy-y := vmw_zerocopy_driver.o
diff --git a/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
new file mode 100644
index 000000000000..e5f95386652d
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
@@ -0,0 +1,490 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ */
+
+#include <linux/cdev.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/vmalloc.h>
+#include <linux/vmw_vmci_api.h>
+#include <linux/vmw_zerocopy_ioctl_common.h>
+
+#include "vmw_zerocopy_driver.h"
+
+#define VMW_ZC_CLASS_NAME	VMW_ZC_DEVICE_NAME
+#define LGPFX			"vmw_zc: "
+
+struct vmw_zc_context {
+	int major;
+	struct class *class;
+	struct cdev cdev;
+	struct vmci_handle dst_hdl;
+	struct vmci_handle src_hdl;
+	atomic_t peer_configured;
+};
+
+static struct vmw_zc_context vmw_zc_ctx;
+
+static int vmw_zc_open(struct inode *inode, struct file *file);
+static int vmw_zc_release(struct inode *inode, struct file *file);
+static long vmw_zc_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+
+static const struct file_operations vmw_zc_fops = {
+	.owner = THIS_MODULE,
+	.open = vmw_zc_open,
+	.release = vmw_zc_release,
+	.unlocked_ioctl = vmw_zc_ioctl,
+};
+
+/* Driver is send-only; responses from the hypervisor are not expected. */
+static int vmw_zc_dgram_cb(void *cookie, struct vmci_datagram *dg)
+{
+	pr_debug(LGPFX "datagram callback\n");
+	return 0;
+}
+
+static void vmw_zc_unpin_user_pages(struct page **pages, int num_pages)
+{
+	int i;
+
+	if (!pages)
+		return;
+
+	for (i = 0; i < num_pages; i++) {
+		if (pages[i])
+			put_page(pages[i]);
+	}
+	kfree(pages);
+}
+
+static int vmw_zc_pin_user_pages(void __user *user_buf, size_t size,
+				 struct page ***pages, int *num_pages)
+{
+	unsigned long start_addr = (unsigned long)user_buf;
+	unsigned long end_addr = start_addr + size;
+	int nr_pages;
+	int ret;
+
+	nr_pages = (PAGE_ALIGN(end_addr) - (start_addr & PAGE_MASK)) >> PAGE_SHIFT;
+
+	if (nr_pages > VMW_ZC_MAX_PAGES) {
+		pr_err(LGPFX "buffer spans too many pages: %d > %lu\n",
+		       nr_pages, VMW_ZC_MAX_PAGES);
+		return -EINVAL;
+	}
+
+	pr_debug(LGPFX "pinning %d pages for size %zu\n", nr_pages, size);
+
+	*pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
+	if (!*pages) {
+		pr_err(LGPFX "failed to allocate page pointer array\n");
+		return -ENOMEM;
+	}
+
+	ret = get_user_pages_fast(start_addr & PAGE_MASK, nr_pages, FOLL_WRITE,
+				  *pages);
+
+	if (ret < 0) {
+		pr_err(LGPFX "get_user_pages_fast failed: %d\n", ret);
+		kfree(*pages);
+		*pages = NULL;
+		return ret;
+	}
+
+	if (ret != nr_pages) {
+		pr_err(LGPFX "only pinned %d of %d pages\n", ret, nr_pages);
+		vmw_zc_unpin_user_pages(*pages, ret);
+		*pages = NULL;
+		return -EFAULT;
+	}
+
+	*num_pages = nr_pages;
+	return 0;
+}
+
+static int vmw_zc_send_datagram(struct vmw_zc_host_message *msg, size_t msg_size)
+{
+	int ret;
+	struct vmci_datagram *dgram;
+	size_t dgram_size;
+
+	if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+		pr_err(LGPFX "peer not configured; send init first\n");
+		return -ENODEV;
+	}
+	/* Pairs with smp_wmb() in vmw_zc_apply_peer_config() before atomic_cmpxchg(). */
+	smp_rmb();
+
+	dgram_size = VMCI_DG_HEADERSIZE + msg_size;
+	dgram = kzalloc(dgram_size, GFP_KERNEL);
+	if (!dgram) {
+		pr_err(LGPFX "failed to allocate datagram buffer\n");
+		return -ENOMEM;
+	}
+
+	dgram->dst = vmw_zc_ctx.dst_hdl;
+	dgram->src = vmw_zc_ctx.src_hdl;
+	dgram->payload_size = msg_size;
+	memcpy(VMCI_DG_PAYLOAD(dgram), msg, msg_size);
+
+	ret = vmci_datagram_send(dgram);
+	kfree(dgram);
+	return ret;
+}
+
+static void vmw_zc_clean_pinned(struct page **pages, int num_pages,
+				struct page **metadata_pages,
+				int num_metadata_pages,
+				struct vmw_zc_host_message *msg)
+{
+	if (pages)
+		vmw_zc_unpin_user_pages(pages, num_pages);
+	if (metadata_pages)
+		vmw_zc_unpin_user_pages(metadata_pages, num_metadata_pages);
+	kfree(msg);
+}
+
+/*
+ * Pin @buffer (and optional @metadata) and send PFN layout to the peer
+ * as @msg_type (expected VMW_ZC_MSG_USER_BUFFER).
+ */
+static int vmw_zc_send_user_buffer_msg(void __user *buffer, u32 buffer_len,
+				     void __user *metadata, u32 metadata_len,
+				     u8 msg_type)
+{
+	int ret;
+	int i;
+	struct vmw_zc_host_message *msg = NULL;
+	struct page **pages = NULL;
+	struct page **metadata_pages = NULL;
+	int num_pages = 0;
+	int num_metadata_pages = 0;
+	unsigned long buf_start = (unsigned long)buffer;
+	unsigned long buf_page_off = buf_start & ~PAGE_MASK;
+	const bool has_metadata = (metadata_len > 0);
+
+	if (msg_type != VMW_ZC_MSG_USER_BUFFER) {
+		pr_err(LGPFX "invalid message type for user buffer path: %u\n",
+		       msg_type);
+		return -EINVAL;
+	}
+
+	if (buffer_len == 0 || buffer_len > VMW_ZC_MAX_BUFFER_SIZE) {
+		pr_err(LGPFX "invalid buffer size: %u (max %lu)\n",
+		       buffer_len, (unsigned long)VMW_ZC_MAX_BUFFER_SIZE);
+		return -EINVAL;
+	}
+
+	if (has_metadata) {
+		if (!metadata ||
+		    metadata_len > VMW_ZC_MAX_METADATA_SIZE) {
+			pr_err(LGPFX "invalid metadata size: %u (max %u)\n",
+			       metadata_len, VMW_ZC_MAX_METADATA_SIZE);
+			return -EINVAL;
+		}
+	}
+
+	ret = vmw_zc_pin_user_pages(buffer, buffer_len, &pages, &num_pages);
+	if (ret) {
+		pr_err(LGPFX "failed to pin buffer pages: %d\n", ret);
+		return ret;
+	}
+
+	if (has_metadata) {
+		ret = vmw_zc_pin_user_pages(metadata, metadata_len, &metadata_pages,
+					    &num_metadata_pages);
+		if (ret) {
+			vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+					    num_metadata_pages, msg);
+			pr_err(LGPFX "failed to pin metadata pages: %d\n", ret);
+			return ret;
+		}
+	}
+
+	msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+	if (!msg) {
+		vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+				    num_metadata_pages, msg);
+		return -ENOMEM;
+	}
+
+	msg->message_type = msg_type;
+	msg->body.user_buf.data.offset = (__u32)buf_page_off;
+	msg->body.user_buf.data.length = buffer_len;
+	msg->body.user_buf.data.num_pages = (__u32)num_pages;
+	msg->body.user_buf.data.padding1 = 0;
+
+	if (has_metadata) {
+		unsigned long meta_start = (unsigned long)metadata;
+		unsigned long meta_page_off = meta_start & ~PAGE_MASK;
+
+		msg->body.user_buf.metadata.offset = (__u32)meta_page_off;
+		msg->body.user_buf.metadata.length = metadata_len;
+		msg->body.user_buf.metadata.num_pages = (__u32)num_metadata_pages;
+		msg->body.user_buf.metadata.padding1 = 0;
+	} else {
+		memset(&msg->body.user_buf.metadata, 0,
+		       sizeof(msg->body.user_buf.metadata));
+	}
+
+	for (i = 0; i < num_pages; i++)
+		msg->body.user_buf.data.page_pfns[i] =
+			(__u64)page_to_pfn(pages[i]);
+
+	if (has_metadata) {
+		for (i = 0; i < num_metadata_pages; i++)
+			msg->body.user_buf.metadata.page_pfns[i] =
+				(__u64)page_to_pfn(metadata_pages[i]);
+	}
+
+	ret = vmw_zc_send_datagram(msg, sizeof(*msg));
+	if (ret)
+		pr_err(LGPFX "failed to send user-buffer message: %d\n", ret);
+
+	vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+			    num_metadata_pages, msg);
+	return ret;
+}
+
+static int vmw_zc_send_raw_msg(const u8 *raw, u32 raw_len)
+{
+	int ret;
+	struct vmw_zc_host_message *host_msg;
+
+	if (raw_len == 0 || raw_len > MAX_RAW_BUFFER_LEN) {
+		pr_err(LGPFX "invalid raw size: %u (max %u)\n",
+		       raw_len, MAX_RAW_BUFFER_LEN);
+		return -EINVAL;
+	}
+
+	host_msg = kzalloc(sizeof(*host_msg), GFP_KERNEL);
+	if (!host_msg)
+		return -ENOMEM;
+
+	host_msg->message_type = VMW_ZC_MSG_RAW;
+	memcpy(host_msg->body.raw.raw, raw, raw_len);
+
+	ret = vmw_zc_send_datagram(host_msg, sizeof(*host_msg));
+	if (ret)
+		pr_err(LGPFX "failed to send raw message: %d\n", ret);
+
+	kfree(host_msg);
+	return ret;
+}
+
+static const char *vmw_zc_msg_type_str(u8 msg_type)
+{
+	switch (msg_type) {
+	case VMW_ZC_MSG_INIT:
+		return "INIT";
+	case VMW_ZC_MSG_USER_BUFFER:
+		return "USER_BUFFER";
+	case VMW_ZC_MSG_RAW:
+		return "RAW";
+	default:
+		return "INVALID";
+	}
+}
+
+static int vmw_zc_apply_peer_config(const struct vmw_zc_ioctl_config *cfg)
+{
+	if (cfg->vmci_resource_id == VMCI_INVALID_ID) {
+		pr_err(LGPFX "invalid resource id\n");
+		return -EINVAL;
+	}
+
+	vmw_zc_ctx.dst_hdl.context = VMCI_HYPERVISOR_CONTEXT_ID;
+	vmw_zc_ctx.dst_hdl.resource = cfg->vmci_resource_id;
+	/* Ensure dst_hdl stores complete before peer_configured=1 is observed.
+	 * Pairs with smp_rmb() in vmw_zc_send_datagram().
+	 */
+	smp_wmb();
+	if (atomic_cmpxchg(&vmw_zc_ctx.peer_configured, 0, 1) != 0) {
+		pr_err(LGPFX "peer already configured\n");
+		return -EBUSY;
+	}
+
+	pr_debug(LGPFX "peer resource id set to %u\n", cfg->vmci_resource_id);
+	return 0;
+}
+
+static long vmw_zc_ioctl(struct file *file, unsigned int cmd,
+			 unsigned long arg)
+{
+	int ret = 0;
+	struct vmw_zc_guest_message guest_msg;
+
+	if (cmd != VMW_ZC_IOCTL_MSG)
+		return -ENOTTY;
+
+	if (copy_from_user(&guest_msg, (void __user *)arg, sizeof(guest_msg)))
+		return -EFAULT;
+
+	if (guest_msg.reserved != 0)
+		return -EINVAL;
+
+	pr_debug(LGPFX "message type: %s\n",
+		 vmw_zc_msg_type_str(guest_msg.message_type));
+
+	switch (guest_msg.message_type) {
+	case VMW_ZC_MSG_INIT:
+		ret = vmw_zc_apply_peer_config(&guest_msg.u.config);
+		break;
+
+	case VMW_ZC_MSG_USER_BUFFER:
+		if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+			pr_err(LGPFX "user buffer before init\n");
+			return -ENODEV;
+		}
+		ret = vmw_zc_send_user_buffer_msg(
+			u64_to_user_ptr(guest_msg.u.data.buffer),
+			guest_msg.u.data.buffer_length,
+			u64_to_user_ptr(guest_msg.u.data.metadata),
+			guest_msg.u.data.metadata_length,
+			guest_msg.message_type);
+		break;
+
+	case VMW_ZC_MSG_RAW:
+		if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+			pr_err(LGPFX "raw message before init\n");
+			return -ENODEV;
+		}
+		ret = vmw_zc_send_raw_msg(
+			guest_msg.u.raw_buffer.raw_buffer,
+			guest_msg.u.raw_buffer.raw_len);
+		break;
+
+	default:
+		pr_err(LGPFX "unknown message type: %s\n",
+		       vmw_zc_msg_type_str(guest_msg.message_type));
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int vmw_zc_open(struct inode *inode, struct file *file)
+{
+	pr_debug(LGPFX "open\n");
+	return 0;
+}
+
+static int vmw_zc_release(struct inode *inode, struct file *file)
+{
+	pr_debug(LGPFX "release\n");
+	return 0;
+}
+
+static char *vmw_zc_devnode(const struct device *dev, umode_t *mode)
+{
+	if (mode)
+		*mode = 0644;
+	return NULL;
+}
+
+static int __init vmw_zc_init(void)
+{
+	dev_t dev;
+	int ret;
+	struct device *mydev;
+
+	pr_info(LGPFX "loading\n");
+
+	ret = alloc_chrdev_region(&dev, 0, 1, VMW_ZC_DEVICE_NAME);
+	if (ret) {
+		pr_err(LGPFX "alloc_chrdev_region failed: %d\n", ret);
+		return ret;
+	}
+
+	vmw_zc_ctx.major = MAJOR(dev);
+	vmw_zc_ctx.dst_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.dst_hdl.resource = VMCI_INVALID_ID;
+	atomic_set(&vmw_zc_ctx.peer_configured, 0);
+	vmw_zc_ctx.src_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.src_hdl.resource = VMCI_INVALID_ID;
+
+	cdev_init(&vmw_zc_ctx.cdev, &vmw_zc_fops);
+	vmw_zc_ctx.cdev.owner = THIS_MODULE;
+
+	ret = cdev_add(&vmw_zc_ctx.cdev, dev, 1);
+	if (ret) {
+		pr_err(LGPFX "cdev_add failed: %d\n", ret);
+		unregister_chrdev_region(dev, 1);
+		return ret;
+	}
+
+	vmw_zc_ctx.class = class_create(VMW_ZC_CLASS_NAME);
+	if (IS_ERR(vmw_zc_ctx.class)) {
+		ret = PTR_ERR(vmw_zc_ctx.class);
+		pr_err(LGPFX "class_create failed: %d\n", ret);
+		cdev_del(&vmw_zc_ctx.cdev);
+		unregister_chrdev_region(dev, 1);
+		return ret;
+	}
+
+	vmw_zc_ctx.class->devnode = vmw_zc_devnode;
+
+	mydev = device_create(vmw_zc_ctx.class, NULL, dev, NULL,
+			      VMW_ZC_DEVICE_NAME);
+	if (IS_ERR(mydev)) {
+		ret = PTR_ERR(mydev);
+		pr_err(LGPFX "device_create failed: %d\n", ret);
+		class_destroy(vmw_zc_ctx.class);
+		cdev_del(&vmw_zc_ctx.cdev);
+		unregister_chrdev_region(dev, 1);
+		return ret;
+	}
+
+	ret = vmci_datagram_create_handle(VMCI_INVALID_ID, 0, vmw_zc_dgram_cb,
+					  NULL, &vmw_zc_ctx.src_hdl);
+	if (ret != VMCI_SUCCESS) {
+		pr_err(LGPFX "vmci_datagram_create_handle failed: %d\n", ret);
+		device_destroy(vmw_zc_ctx.class, MKDEV(vmw_zc_ctx.major, 0));
+		class_destroy(vmw_zc_ctx.class);
+		cdev_del(&vmw_zc_ctx.cdev);
+		unregister_chrdev_region(dev, 1);
+		return ret;
+	}
+
+	pr_info(LGPFX "ready\n");
+	return 0;
+}
+
+static void __exit vmw_zc_exit(void)
+{
+	int result;
+
+	device_destroy(vmw_zc_ctx.class, MKDEV(vmw_zc_ctx.major, 0));
+	class_destroy(vmw_zc_ctx.class);
+	cdev_del(&vmw_zc_ctx.cdev);
+	unregister_chrdev_region(MKDEV(vmw_zc_ctx.major, 0), 1);
+
+	atomic_set(&vmw_zc_ctx.peer_configured, 0);
+	/* Ensure peer_configured=0 is visible before clearing dst_hdl. */
+	smp_mb();
+	vmw_zc_ctx.dst_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.dst_hdl.resource = VMCI_INVALID_ID;
+
+	if (vmw_zc_ctx.src_hdl.context != VMCI_INVALID_ID) {
+		result = vmci_datagram_destroy_handle(vmw_zc_ctx.src_hdl);
+		if (result != VMCI_SUCCESS)
+			pr_err(LGPFX "vmci_datagram_destroy_handle failed: %d\n",
+			       result);
+	}
+	vmw_zc_ctx.src_hdl.context = VMCI_INVALID_ID;
+
+	pr_info(LGPFX "unloaded\n");
+}
+
+module_init(vmw_zc_init);
+module_exit(vmw_zc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rishi Chhibber <rishi.chhibber@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom zero-copy buffer sharing (VMCI)");
diff --git a/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.h b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.h
new file mode 100644
index 000000000000..0df2c81df08d
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * Wire-format messages sent to the peer over VMCI (driver private).
+ * Limits and raw size must stay in sync with include/uapi/linux/vmw_zerocopy_ioctl_common.h
+ */
+
+#ifndef _VMW_ZC_HOST_H
+#define _VMW_ZC_HOST_H
+
+#include <linux/mm.h>
+#include <linux/types.h>
+
+#include <linux/vmw_zerocopy_ioctl_common.h>
+
+#define VMW_ZC_MAX_METADATA_SIZE	1024
+#define VMW_ZC_MAX_BUFFER_SIZE		(64 * 1024UL)
+#define VMW_ZC_MAX_PAGES \
+	(((VMW_ZC_MAX_BUFFER_SIZE) + (PAGE_SIZE - 1)) / PAGE_SIZE + 1)
+
+struct vmw_zc_msg_unit {
+	__u32 offset;
+	__u32 length;
+	__u32 num_pages;
+	__u32 padding1;
+	__u64 page_pfns[VMW_ZC_MAX_PAGES];
+} __packed;
+
+struct vmw_zc_user_buffer_pair {
+	struct vmw_zc_msg_unit data;
+	struct vmw_zc_msg_unit metadata;
+} __packed;
+
+struct vmw_zc_host_raw {
+	__u8 raw[MAX_RAW_BUFFER_LEN];
+} __packed;
+
+union vmw_zc_host_body {
+	struct vmw_zc_user_buffer_pair user_buf;
+	struct vmw_zc_host_raw raw;
+} __packed;
+
+struct vmw_zc_host_message {
+	__u32 message_type;
+	__u32 padding1;
+	union vmw_zc_host_body body;
+} __packed;
+
+#endif /* _VMW_ZC_HOST_H */
diff --git a/include/uapi/linux/vmw_zerocopy_ioctl_common.h b/include/uapi/linux/vmw_zerocopy_ioctl_common.h
new file mode 100644
index 000000000000..c126d4f2c967
--- /dev/null
+++ b/include/uapi/linux/vmw_zerocopy_ioctl_common.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ */
+
+#ifndef _VMW_ZC_IOCTL_COMMON_H_
+#define _VMW_ZC_IOCTL_COMMON_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define VMW_ZC_DEVICE_NAME		"vmw_zc"
+#define VMW_ZC_IOCTL_MAGIC		0xDC
+#define MAX_RAW_BUFFER_LEN		32
+
+/*
+ * Used for sending user buffer.
+ * Either is optional but not both.
+ *
+ * Pointers are __u64 so the struct layout is identical on 32-bit and 64-bit
+ * userspace, avoiding a compat_ioctl handler. Use u64_to_user_ptr() in the
+ * driver to convert back to a __user pointer.
+ */
+struct vmw_zc_guest_data {
+	__u64 buffer;
+	__u32 buffer_length;
+	__u64 metadata;
+	__u32 metadata_length;
+} __packed;
+
+struct vmw_zc_guest_raw_buffer {
+	__user __u8 raw_buffer[MAX_RAW_BUFFER_LEN];
+	__u32 raw_len;
+} __packed;
+
+/* VMCI datagram destination resource ID (hypervisor context). */
+struct vmw_zc_ioctl_config {
+	__u32 vmci_resource_id;
+} __packed;
+
+/*
+ * @message_type: numeric values (see below). INIT uses @u.config;
+ * user-buffer transfer uses @u.data; small inline payload uses @u.raw_buffer.
+ */
+#define VMW_ZC_MSG_INIT			1u
+#define VMW_ZC_MSG_USER_BUFFER		2u
+#define VMW_ZC_MSG_RAW			3u
+
+struct vmw_zc_guest_message {
+	__u8 message_type;
+	__u64 reserved;
+	union {
+		struct vmw_zc_guest_data data;
+		struct vmw_zc_guest_raw_buffer raw_buffer;
+		struct vmw_zc_ioctl_config config;
+	} u;
+} __packed;
+
+#define VMW_ZC_MESSAGE			0x01
+
+#define VMW_ZC_IOCTL_MSG \
+	_IOW(VMW_ZC_IOCTL_MAGIC, VMW_ZC_MESSAGE, struct vmw_zc_guest_message)
+
+#endif /* _VMW_ZC_IOCTL_COMMON_H_ */
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
  2026-06-17 20:31 [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver Rishi Chhibber
@ 2026-06-18 10:26 ` Greg KH
  2026-06-18 18:10 ` [PATCH v2] " Rishi Chhibber
  2026-06-19 18:27 ` [PATCH] " Rishi Chhibber
  2 siblings, 0 replies; 9+ messages in thread
From: Greg KH @ 2026-06-18 10:26 UTC (permalink / raw)
  To: Rishi Chhibber
  Cc: arnd, linux-kernel, ajay.kaher, alexey.makhalov,
	vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu

On Wed, Jun 17, 2026 at 01:31:25PM -0700, Rishi Chhibber wrote:
> This driver implements a character device (/dev/vmw_zc) that allows
> guest userspace applications to share pinned memory buffers with a
> VMware hypervisor-side peer using the VMCI datagram interface.

Why is this a new char device, don't we already have virtio apis for
this type of thing?

> The driver pins user pages via get_user_pages_fast(), transmits their
> physical page frame numbers to the hypervisor peer over VMCI, and
> avoids an intermediate copy between the guest workload VM and the
> hypervisor.
> 
> Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
> ---
>  MAINTAINERS                                   |   8 +
>  drivers/misc/Kconfig                          |   1 +
>  drivers/misc/Makefile                         |   1 +
>  drivers/misc/vmw_zerocopy/Kconfig             |  16 +
>  drivers/misc/vmw_zerocopy/Makefile            |  17 +
>  .../misc/vmw_zerocopy/vmw_zerocopy_driver.c   | 490 ++++++++++++++++++
>  .../misc/vmw_zerocopy/vmw_zerocopy_driver.h   |  51 ++
>  .../uapi/linux/vmw_zerocopy_ioctl_common.h    |  66 +++
>  8 files changed, 650 insertions(+)
>  create mode 100644 drivers/misc/vmw_zerocopy/Kconfig
>  create mode 100644 drivers/misc/vmw_zerocopy/Makefile
>  create mode 100644 drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
>  create mode 100644 drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.h
>  create mode 100644 include/uapi/linux/vmw_zerocopy_ioctl_common.h
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index efd1fa7d66f0..59ee66158486 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -24790,6 +24790,14 @@ L:	linux-kernel@vger.kernel.org
>  S:	Supported
>  F:	net/vmw_vsock/vmci_transport*
>  
> +VMWARE ZEROCOPY DRIVER
> +M:	Rishi Chhibber <rishi.chhibber@broadcom.com>
> +R:	Broadcom internal kernel review list <bcm-kernel-feedback-list@broadcom.com>

Please don't put closed lists in the MAINTAINERS file, that just causes
bounces.

> +static int vmw_zc_release(struct inode *inode, struct file *file)
> +{
> +	pr_debug(LGPFX "release\n");
> +	return 0;
> +}

If you do nothing in a function, no need to have it at all, right?

> +static int __init vmw_zc_init(void)
> +{
> +	dev_t dev;
> +	int ret;
> +	struct device *mydev;
> +
> +	pr_info(LGPFX "loading\n");

When drivers work properly, they are quiet.

> +
> +	ret = alloc_chrdev_region(&dev, 0, 1, VMW_ZC_DEVICE_NAME);

As you only want one char device, why not use miscdev instead?


> +	if (ret) {
> +		pr_err(LGPFX "alloc_chrdev_region failed: %d\n", ret);

No need for the LGPFX stuff everywhere, you all do know about pr_fmt(),
right?

> --- /dev/null
> +++ b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.h
> @@ -0,0 +1,51 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */

I have to ask, are you sure about "or later"?

> +/*
> + * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
> + * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
> + *
> + * Wire-format messages sent to the peer over VMCI (driver private).
> + * Limits and raw size must stay in sync with include/uapi/linux/vmw_zerocopy_ioctl_common.h

How is that going to happen?  Why not just keep everything in one file?



> + */
> +
> +#ifndef _VMW_ZC_HOST_H
> +#define _VMW_ZC_HOST_H
> +
> +#include <linux/mm.h>
> +#include <linux/types.h>
> +
> +#include <linux/vmw_zerocopy_ioctl_common.h>
> +
> +#define VMW_ZC_MAX_METADATA_SIZE	1024
> +#define VMW_ZC_MAX_BUFFER_SIZE		(64 * 1024UL)
> +#define VMW_ZC_MAX_PAGES \
> +	(((VMW_ZC_MAX_BUFFER_SIZE) + (PAGE_SIZE - 1)) / PAGE_SIZE + 1)
> +
> +struct vmw_zc_msg_unit {
> +	__u32 offset;
> +	__u32 length;
> +	__u32 num_pages;
> +	__u32 padding1;
> +	__u64 page_pfns[VMW_ZC_MAX_PAGES];

Why do you have an internal structure defined with __u32 and the like?
That's only a type that crosses the user/kernel boundry, and it would be
implied this would be defined in the uapi .h file, right?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
  2026-06-17 20:31 [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver Rishi Chhibber
  2026-06-18 10:26 ` Greg KH
@ 2026-06-18 18:10 ` Rishi Chhibber
  2026-06-19  5:08   ` Greg KH
  2026-06-19  5:10   ` Greg KH
  2026-06-19 18:27 ` [PATCH] " Rishi Chhibber
  2 siblings, 2 replies; 9+ messages in thread
From: Rishi Chhibber @ 2026-06-18 18:10 UTC (permalink / raw)
  To: arnd, gregkh, linux-kernel
  Cc: ajay.kaher, alexey.makhalov, vamsi-krishna.brahmajosyula,
	yin.ding, tapas.kundu, Rishi Chhibber

This driver implements a misc character device (/dev/vmw_zc) that allows
guest userspace applications to share pinned memory buffers with a
VMware hypervisor-side peer using the VMCI datagram interface.

The driver pins user pages via get_user_pages_fast(), transmits their
physical page frame numbers to the hypervisor peer over VMCI, and avoids
an intermediate copy between the guest workload VM and the hypervisor.

The hypervisor-side peer for this interface only speaks VMCI; there is no
virtio backend implemented on the VMware host for it. The closest
existing upstream transport, vsock (virtio-vsock), provides a socket
bytestream/datagram abstraction and does not expose a way to hand a set
of pinned guest page frame numbers to the host for true zero-copy
access; it would still require copying the payload through the socket.
This driver's purpose is specifically to pin guest pages and pass their
PFNs to the host so the payload is never copied. It also supports
bundling multiple buffers in a single request, which is required for the
all-or-none semantics of page-level zero-copy transfers.

Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
---
v2:
  - Convert to misc character device (misc_register) instead of manual
    cdev/class/device_create (Greg KH).
  - Use pr_fmt() and drop the per-call LGPFX log prefix (Greg KH).
  - Remove empty open()/release() and the load/ready/unload pr_info()
    noise (Greg KH).
  - Merge the private wire-format header into the .c file and use kernel
    u32/u64 types for the internal structs; uapi types remain only in
    the uapi header (Greg KH).
  - Drop the closed bcm-kernel-feedback-list R: entry from MAINTAINERS
    (Greg KH).
  - Switch SPDX to GPL-2.0-only (Greg KH).
  - Explain in the commit log why VMCI is required and why vsock/virtio
    cannot provide the zero-copy PFN-sharing semantics (Greg KH).
 MAINTAINERS                                   |   7 +
 drivers/misc/Kconfig                          |   1 +
 drivers/misc/Makefile                         |   1 +
 drivers/misc/vmw_zerocopy/Kconfig             |  16 +
 drivers/misc/vmw_zerocopy/Makefile            |  15 +
 .../misc/vmw_zerocopy/vmw_zerocopy_driver.c   | 420 ++++++++++++++++++
 .../uapi/linux/vmw_zerocopy_ioctl_common.h    |  66 +++
 7 files changed, 526 insertions(+)
 create mode 100644 drivers/misc/vmw_zerocopy/Kconfig
 create mode 100644 drivers/misc/vmw_zerocopy/Makefile
 create mode 100644 drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
 create mode 100644 include/uapi/linux/vmw_zerocopy_ioctl_common.h

diff --git a/MAINTAINERS b/MAINTAINERS
index efd1fa7d66f0..c92fda573a4f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24790,6 +24790,13 @@ L:	linux-kernel@vger.kernel.org
 S:	Supported
 F:	net/vmw_vsock/vmci_transport*
 
+VMWARE ZEROCOPY DRIVER
+M:	Rishi Chhibber <rishi.chhibber@broadcom.com>
+L:	linux-kernel@vger.kernel.org
+S:	Supported
+F:	drivers/misc/vmw_zerocopy/
+F:	include/uapi/linux/vmw_zerocopy*
+
 VOCORE VOCORE2 BOARD
 M:	Harvey Hunt <harveyhuntnexus@gmail.com>
 L:	linux-mips@vger.kernel.org
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index dcfe77b30bd5..6445a878ffdb 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -631,4 +631,5 @@ source "drivers/misc/keba/Kconfig"
 source "drivers/misc/vmw_extcfg/Kconfig"
 source "drivers/misc/vmw_sbx/Kconfig"
 source "drivers/misc/vmw_sbx_dtls/Kconfig"
+source "drivers/misc/vmw_zerocopy/Kconfig"
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e4f21eccc994..c2b14169d9ed 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_TPS6594_PFSM)	+= tps6594-pfsm.o
 obj-$(CONFIG_VMWARE_EXTCFG)	+= vmw_extcfg/
 obj-$(CONFIG_SBX)		+= vmw_sbx/
 obj-$(CONFIG_SBX_DTLS)		+= vmw_sbx_dtls/
+obj-$(CONFIG_VMW_ZC)		+= vmw_zerocopy/
 obj-$(CONFIG_NSM)		+= nsm.o
 obj-$(CONFIG_MARVELL_CN10K_DPI)	+= mrvl_cn10k_dpi.o
 obj-y				+= keba/
diff --git a/drivers/misc/vmw_zerocopy/Kconfig b/drivers/misc/vmw_zerocopy/Kconfig
new file mode 100644
index 000000000000..e578073cff44
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VMW_ZC
+	tristate "VMware zero-copy buffer sharing device"
+	depends on VMWARE_VMCI
+	help
+	  This driver implements a character device (/dev/vmw_zc) that
+	  allows guest userspace applications to share pinned memory
+	  buffers with a VMware hypervisor-side peer using the VMCI
+	  datagram interface.
+
+	  Applications submit buffers via ioctl(). The driver pins the
+	  user pages and transmits their physical page frame numbers to
+	  the peer, enabling zero-copy data transfer between the guest
+	  and the hypervisor without an intermediate copy.
+
+	  If unsure, say N.
diff --git a/drivers/misc/vmw_zerocopy/Makefile b/drivers/misc/vmw_zerocopy/Makefile
new file mode 100644
index 000000000000..5313e44ca142
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the VMware Zero Copy Virtual Device driver
+#
+
+# "make M=drivers/misc/vmw_zerocopy modules" sets KBUILD_EXTMOD and skips
+# syncconfig, so include/config/auto.conf may not list new CONFIG_* symbols
+# even when they are present in .config - then obj-$(CONFIG_...) adds nothing.
+ifneq ($(KBUILD_EXTMOD),)
+obj-m += vmw_zerocopy.o
+else
+obj-$(CONFIG_VMW_ZC) += vmw_zerocopy.o
+endif
+
+vmw_zerocopy-y := vmw_zerocopy_driver.o
diff --git a/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
new file mode 100644
index 000000000000..436f6297f531
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/vmw_vmci_api.h>
+#include <linux/vmw_zerocopy_ioctl_common.h>
+
+#define VMW_ZC_MAX_METADATA_SIZE	1024
+#define VMW_ZC_MAX_BUFFER_SIZE		(64 * 1024UL)
+#define VMW_ZC_MAX_PAGES \
+	(((VMW_ZC_MAX_BUFFER_SIZE) + (PAGE_SIZE - 1)) / PAGE_SIZE + 1)
+
+/* Wire-format messages sent to the peer over VMCI (driver private). */
+struct vmw_zc_msg_unit {
+	u32 offset;
+	u32 length;
+	u32 num_pages;
+	u32 padding1;
+	u64 page_pfns[VMW_ZC_MAX_PAGES];
+} __packed;
+
+struct vmw_zc_user_buffer_pair {
+	struct vmw_zc_msg_unit data;
+	struct vmw_zc_msg_unit metadata;
+} __packed;
+
+struct vmw_zc_host_raw {
+	u8 raw[MAX_RAW_BUFFER_LEN];
+} __packed;
+
+union vmw_zc_host_body {
+	struct vmw_zc_user_buffer_pair user_buf;
+	struct vmw_zc_host_raw raw;
+} __packed;
+
+struct vmw_zc_host_message {
+	u32 message_type;
+	u32 padding1;
+	union vmw_zc_host_body body;
+} __packed;
+
+struct vmw_zc_context {
+	struct vmci_handle dst_hdl;
+	struct vmci_handle src_hdl;
+	atomic_t peer_configured;
+};
+
+static struct vmw_zc_context vmw_zc_ctx;
+
+static long vmw_zc_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+
+static const struct file_operations vmw_zc_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = vmw_zc_ioctl,
+};
+
+static struct miscdevice vmw_zc_misc = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = VMW_ZC_DEVICE_NAME,
+	.fops = &vmw_zc_fops,
+	.mode = 0644,
+};
+
+/* Driver is send-only; responses from the hypervisor are not expected. */
+static int vmw_zc_dgram_cb(void *cookie, struct vmci_datagram *dg)
+{
+	return 0;
+}
+
+static void vmw_zc_unpin_user_pages(struct page **pages, int num_pages)
+{
+	int i;
+
+	if (!pages)
+		return;
+
+	for (i = 0; i < num_pages; i++) {
+		if (pages[i])
+			put_page(pages[i]);
+	}
+	kfree(pages);
+}
+
+static int vmw_zc_pin_user_pages(void __user *user_buf, size_t size,
+				 struct page ***pages, int *num_pages)
+{
+	unsigned long start_addr = (unsigned long)user_buf;
+	unsigned long end_addr = start_addr + size;
+	int nr_pages;
+	int ret;
+
+	nr_pages = (PAGE_ALIGN(end_addr) - (start_addr & PAGE_MASK)) >> PAGE_SHIFT;
+
+	if (nr_pages > VMW_ZC_MAX_PAGES) {
+		pr_err("buffer spans too many pages: %d > %lu\n",
+		       nr_pages, VMW_ZC_MAX_PAGES);
+		return -EINVAL;
+	}
+
+	*pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
+	if (!*pages)
+		return -ENOMEM;
+
+	ret = get_user_pages_fast(start_addr & PAGE_MASK, nr_pages, FOLL_WRITE,
+				  *pages);
+
+	if (ret < 0) {
+		kfree(*pages);
+		*pages = NULL;
+		return ret;
+	}
+
+	if (ret != nr_pages) {
+		vmw_zc_unpin_user_pages(*pages, ret);
+		*pages = NULL;
+		return -EFAULT;
+	}
+
+	*num_pages = nr_pages;
+	return 0;
+}
+
+static int vmw_zc_send_datagram(struct vmw_zc_host_message *msg, size_t msg_size)
+{
+	int ret;
+	struct vmci_datagram *dgram;
+	size_t dgram_size;
+
+	if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+		pr_err("peer not configured; send init first\n");
+		return -ENODEV;
+	}
+	/* Pairs with smp_wmb() in vmw_zc_apply_peer_config() before atomic_cmpxchg(). */
+	smp_rmb();
+
+	dgram_size = VMCI_DG_HEADERSIZE + msg_size;
+	dgram = kzalloc(dgram_size, GFP_KERNEL);
+	if (!dgram)
+		return -ENOMEM;
+
+	dgram->dst = vmw_zc_ctx.dst_hdl;
+	dgram->src = vmw_zc_ctx.src_hdl;
+	dgram->payload_size = msg_size;
+	memcpy(VMCI_DG_PAYLOAD(dgram), msg, msg_size);
+
+	ret = vmci_datagram_send(dgram);
+	kfree(dgram);
+	return ret;
+}
+
+static void vmw_zc_clean_pinned(struct page **pages, int num_pages,
+				struct page **metadata_pages,
+				int num_metadata_pages,
+				struct vmw_zc_host_message *msg)
+{
+	if (pages)
+		vmw_zc_unpin_user_pages(pages, num_pages);
+	if (metadata_pages)
+		vmw_zc_unpin_user_pages(metadata_pages, num_metadata_pages);
+	kfree(msg);
+}
+
+/*
+ * Pin @buffer (and optional @metadata) and send PFN layout to the peer
+ * as @msg_type (expected VMW_ZC_MSG_USER_BUFFER).
+ */
+static int vmw_zc_send_user_buffer_msg(void __user *buffer, u32 buffer_len,
+				     void __user *metadata, u32 metadata_len,
+				     u8 msg_type)
+{
+	int ret;
+	int i;
+	struct vmw_zc_host_message *msg = NULL;
+	struct page **pages = NULL;
+	struct page **metadata_pages = NULL;
+	int num_pages = 0;
+	int num_metadata_pages = 0;
+	unsigned long buf_start = (unsigned long)buffer;
+	unsigned long buf_page_off = buf_start & ~PAGE_MASK;
+	const bool has_metadata = (metadata_len > 0);
+
+	if (msg_type != VMW_ZC_MSG_USER_BUFFER) {
+		pr_err("invalid message type for user buffer path: %u\n",
+		       msg_type);
+		return -EINVAL;
+	}
+
+	if (buffer_len == 0 || buffer_len > VMW_ZC_MAX_BUFFER_SIZE) {
+		pr_err("invalid buffer size: %u (max %lu)\n",
+		       buffer_len, (unsigned long)VMW_ZC_MAX_BUFFER_SIZE);
+		return -EINVAL;
+	}
+
+	if (has_metadata) {
+		if (!metadata ||
+		    metadata_len > VMW_ZC_MAX_METADATA_SIZE) {
+			pr_err("invalid metadata size: %u (max %u)\n",
+			       metadata_len, VMW_ZC_MAX_METADATA_SIZE);
+			return -EINVAL;
+		}
+	}
+
+	ret = vmw_zc_pin_user_pages(buffer, buffer_len, &pages, &num_pages);
+	if (ret)
+		return ret;
+
+	if (has_metadata) {
+		ret = vmw_zc_pin_user_pages(metadata, metadata_len, &metadata_pages,
+					    &num_metadata_pages);
+		if (ret) {
+			vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+					    num_metadata_pages, msg);
+			return ret;
+		}
+	}
+
+	msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+	if (!msg) {
+		vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+				    num_metadata_pages, msg);
+		return -ENOMEM;
+	}
+
+	msg->message_type = msg_type;
+	msg->body.user_buf.data.offset = (u32)buf_page_off;
+	msg->body.user_buf.data.length = buffer_len;
+	msg->body.user_buf.data.num_pages = (u32)num_pages;
+	msg->body.user_buf.data.padding1 = 0;
+
+	if (has_metadata) {
+		unsigned long meta_start = (unsigned long)metadata;
+		unsigned long meta_page_off = meta_start & ~PAGE_MASK;
+
+		msg->body.user_buf.metadata.offset = (u32)meta_page_off;
+		msg->body.user_buf.metadata.length = metadata_len;
+		msg->body.user_buf.metadata.num_pages = (u32)num_metadata_pages;
+		msg->body.user_buf.metadata.padding1 = 0;
+	} else {
+		memset(&msg->body.user_buf.metadata, 0,
+		       sizeof(msg->body.user_buf.metadata));
+	}
+
+	for (i = 0; i < num_pages; i++)
+		msg->body.user_buf.data.page_pfns[i] =
+			(u64)page_to_pfn(pages[i]);
+
+	if (has_metadata) {
+		for (i = 0; i < num_metadata_pages; i++)
+			msg->body.user_buf.metadata.page_pfns[i] =
+				(u64)page_to_pfn(metadata_pages[i]);
+	}
+
+	ret = vmw_zc_send_datagram(msg, sizeof(*msg));
+	if (ret)
+		pr_err("failed to send user-buffer message: %d\n", ret);
+
+	vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+			    num_metadata_pages, msg);
+	return ret;
+}
+
+static int vmw_zc_send_raw_msg(const u8 *raw, u32 raw_len)
+{
+	int ret;
+	struct vmw_zc_host_message *host_msg;
+
+	if (raw_len == 0 || raw_len > MAX_RAW_BUFFER_LEN) {
+		pr_err("invalid raw size: %u (max %u)\n",
+		       raw_len, MAX_RAW_BUFFER_LEN);
+		return -EINVAL;
+	}
+
+	host_msg = kzalloc(sizeof(*host_msg), GFP_KERNEL);
+	if (!host_msg)
+		return -ENOMEM;
+
+	host_msg->message_type = VMW_ZC_MSG_RAW;
+	memcpy(host_msg->body.raw.raw, raw, raw_len);
+
+	ret = vmw_zc_send_datagram(host_msg, sizeof(*host_msg));
+	if (ret)
+		pr_err("failed to send raw message: %d\n", ret);
+
+	kfree(host_msg);
+	return ret;
+}
+
+static int vmw_zc_apply_peer_config(const struct vmw_zc_ioctl_config *cfg)
+{
+	if (cfg->vmci_resource_id == VMCI_INVALID_ID) {
+		pr_err("invalid resource id\n");
+		return -EINVAL;
+	}
+
+	vmw_zc_ctx.dst_hdl.context = VMCI_HYPERVISOR_CONTEXT_ID;
+	vmw_zc_ctx.dst_hdl.resource = cfg->vmci_resource_id;
+	/* Ensure dst_hdl stores complete before peer_configured=1 is observed.
+	 * Pairs with smp_rmb() in vmw_zc_send_datagram().
+	 */
+	smp_wmb();
+	if (atomic_cmpxchg(&vmw_zc_ctx.peer_configured, 0, 1) != 0) {
+		pr_err("peer already configured\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static long vmw_zc_ioctl(struct file *file, unsigned int cmd,
+			 unsigned long arg)
+{
+	int ret = 0;
+	struct vmw_zc_guest_message guest_msg;
+
+	if (cmd != VMW_ZC_IOCTL_MSG)
+		return -ENOTTY;
+
+	if (copy_from_user(&guest_msg, (void __user *)arg, sizeof(guest_msg)))
+		return -EFAULT;
+
+	if (guest_msg.reserved != 0)
+		return -EINVAL;
+
+	switch (guest_msg.message_type) {
+	case VMW_ZC_MSG_INIT:
+		ret = vmw_zc_apply_peer_config(&guest_msg.u.config);
+		break;
+
+	case VMW_ZC_MSG_USER_BUFFER:
+		if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+			pr_err("user buffer before init\n");
+			return -ENODEV;
+		}
+		ret = vmw_zc_send_user_buffer_msg(
+			u64_to_user_ptr(guest_msg.u.data.buffer),
+			guest_msg.u.data.buffer_length,
+			u64_to_user_ptr(guest_msg.u.data.metadata),
+			guest_msg.u.data.metadata_length,
+			guest_msg.message_type);
+		break;
+
+	case VMW_ZC_MSG_RAW:
+		if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+			pr_err("raw message before init\n");
+			return -ENODEV;
+		}
+		ret = vmw_zc_send_raw_msg(
+			guest_msg.u.raw_buffer.raw_buffer,
+			guest_msg.u.raw_buffer.raw_len);
+		break;
+
+	default:
+		pr_err("unknown message type: %u\n", guest_msg.message_type);
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int __init vmw_zc_init(void)
+{
+	int ret;
+
+	vmw_zc_ctx.dst_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.dst_hdl.resource = VMCI_INVALID_ID;
+	atomic_set(&vmw_zc_ctx.peer_configured, 0);
+	vmw_zc_ctx.src_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.src_hdl.resource = VMCI_INVALID_ID;
+
+	ret = vmci_datagram_create_handle(VMCI_INVALID_ID, 0, vmw_zc_dgram_cb,
+					  NULL, &vmw_zc_ctx.src_hdl);
+	if (ret != VMCI_SUCCESS)
+		return ret;
+
+	ret = misc_register(&vmw_zc_misc);
+	if (ret) {
+		vmci_datagram_destroy_handle(vmw_zc_ctx.src_hdl);
+		vmw_zc_ctx.src_hdl.context = VMCI_INVALID_ID;
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit vmw_zc_exit(void)
+{
+	int ret;
+
+	misc_deregister(&vmw_zc_misc);
+
+	atomic_set(&vmw_zc_ctx.peer_configured, 0);
+	/* Ensure peer_configured=0 is visible before clearing dst_hdl. */
+	smp_mb();
+	vmw_zc_ctx.dst_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.dst_hdl.resource = VMCI_INVALID_ID;
+
+	if (vmw_zc_ctx.src_hdl.context != VMCI_INVALID_ID) {
+		ret = vmci_datagram_destroy_handle(vmw_zc_ctx.src_hdl);
+		if (ret != VMCI_SUCCESS)
+			pr_err("vmci_datagram_destroy_handle failed: %d\n", ret);
+	}
+	vmw_zc_ctx.src_hdl.context = VMCI_INVALID_ID;
+}
+
+module_init(vmw_zc_init);
+module_exit(vmw_zc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rishi Chhibber <rishi.chhibber@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom zero-copy buffer sharing (VMCI)");
diff --git a/include/uapi/linux/vmw_zerocopy_ioctl_common.h b/include/uapi/linux/vmw_zerocopy_ioctl_common.h
new file mode 100644
index 000000000000..0d7b1d31c284
--- /dev/null
+++ b/include/uapi/linux/vmw_zerocopy_ioctl_common.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ */
+
+#ifndef _VMW_ZC_IOCTL_COMMON_H_
+#define _VMW_ZC_IOCTL_COMMON_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define VMW_ZC_DEVICE_NAME		"vmw_zc"
+#define VMW_ZC_IOCTL_MAGIC		0xDC
+#define MAX_RAW_BUFFER_LEN		32
+
+/*
+ * Used for sending user buffer.
+ * Either is optional but not both.
+ *
+ * Pointers are __u64 so the struct layout is identical on 32-bit and 64-bit
+ * userspace, avoiding a compat_ioctl handler. Use u64_to_user_ptr() in the
+ * driver to convert back to a __user pointer.
+ */
+struct vmw_zc_guest_data {
+	__u64 buffer;
+	__u32 buffer_length;
+	__u64 metadata;
+	__u32 metadata_length;
+} __packed;
+
+struct vmw_zc_guest_raw_buffer {
+	__user __u8 raw_buffer[MAX_RAW_BUFFER_LEN];
+	__u32 raw_len;
+} __packed;
+
+/* VMCI datagram destination resource ID (hypervisor context). */
+struct vmw_zc_ioctl_config {
+	__u32 vmci_resource_id;
+} __packed;
+
+/*
+ * @message_type: numeric values (see below). INIT uses @u.config;
+ * user-buffer transfer uses @u.data; small inline payload uses @u.raw_buffer.
+ */
+#define VMW_ZC_MSG_INIT			1u
+#define VMW_ZC_MSG_USER_BUFFER		2u
+#define VMW_ZC_MSG_RAW			3u
+
+struct vmw_zc_guest_message {
+	__u8 message_type;
+	__u64 reserved;
+	union {
+		struct vmw_zc_guest_data data;
+		struct vmw_zc_guest_raw_buffer raw_buffer;
+		struct vmw_zc_ioctl_config config;
+	} u;
+} __packed;
+
+#define VMW_ZC_MESSAGE			0x01
+
+#define VMW_ZC_IOCTL_MSG \
+	_IOW(VMW_ZC_IOCTL_MAGIC, VMW_ZC_MESSAGE, struct vmw_zc_guest_message)
+
+#endif /* _VMW_ZC_IOCTL_COMMON_H_ */
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
  2026-06-18 18:10 ` [PATCH v2] " Rishi Chhibber
@ 2026-06-19  5:08   ` Greg KH
  2026-06-19  5:10   ` Greg KH
  1 sibling, 0 replies; 9+ messages in thread
From: Greg KH @ 2026-06-19  5:08 UTC (permalink / raw)
  To: Rishi Chhibber
  Cc: arnd, linux-kernel, ajay.kaher, alexey.makhalov,
	vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu

On Thu, Jun 18, 2026 at 11:10:34AM -0700, Rishi Chhibber wrote:
> --- /dev/null
> +++ b/drivers/misc/vmw_zerocopy/Makefile
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +#
> +# Makefile for the VMware Zero Copy Virtual Device driver
> +#
> +
> +# "make M=drivers/misc/vmw_zerocopy modules" sets KBUILD_EXTMOD and skips
> +# syncconfig, so include/config/auto.conf may not list new CONFIG_* symbols
> +# even when they are present in .config - then obj-$(CONFIG_...) adds nothing.
> +ifneq ($(KBUILD_EXTMOD),)
> +obj-m += vmw_zerocopy.o
> +else
> +obj-$(CONFIG_VMW_ZC) += vmw_zerocopy.o
> +endif

I don't think you still need this "build out of tree" logic anymore,
right?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
  2026-06-18 18:10 ` [PATCH v2] " Rishi Chhibber
  2026-06-19  5:08   ` Greg KH
@ 2026-06-19  5:10   ` Greg KH
  2026-06-19 15:12     ` David Laight
  1 sibling, 1 reply; 9+ messages in thread
From: Greg KH @ 2026-06-19  5:10 UTC (permalink / raw)
  To: Rishi Chhibber
  Cc: arnd, linux-kernel, ajay.kaher, alexey.makhalov,
	vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu

On Thu, Jun 18, 2026 at 11:10:34AM -0700, Rishi Chhibber wrote:
> +/*
> + * Used for sending user buffer.
> + * Either is optional but not both.
> + *
> + * Pointers are __u64 so the struct layout is identical on 32-bit and 64-bit
> + * userspace, avoiding a compat_ioctl handler. Use u64_to_user_ptr() in the
> + * driver to convert back to a __user pointer.
> + */
> +struct vmw_zc_guest_data {
> +	__u64 buffer;
> +	__u32 buffer_length;
> +	__u64 metadata;
> +	__u32 metadata_length;
> +} __packed;

You have an unaligned metadata pointer here in the structure, are you
sure that's a good idea?  Or can you not change that anymore?

> +
> +struct vmw_zc_guest_raw_buffer {
> +	__user __u8 raw_buffer[MAX_RAW_BUFFER_LEN];

Why is __user in an ioctl structure?  That's an internal-to-the-kernel
marking.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
  2026-06-19  5:10   ` Greg KH
@ 2026-06-19 15:12     ` David Laight
  0 siblings, 0 replies; 9+ messages in thread
From: David Laight @ 2026-06-19 15:12 UTC (permalink / raw)
  To: Greg KH
  Cc: Rishi Chhibber, arnd, linux-kernel, ajay.kaher, alexey.makhalov,
	vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu

On Fri, 19 Jun 2026 07:10:33 +0200
Greg KH <gregkh@linuxfoundation.org> wrote:

> On Thu, Jun 18, 2026 at 11:10:34AM -0700, Rishi Chhibber wrote:
> > +/*
> > + * Used for sending user buffer.
> > + * Either is optional but not both.
> > + *
> > + * Pointers are __u64 so the struct layout is identical on 32-bit and 64-bit
> > + * userspace, avoiding a compat_ioctl handler. Use u64_to_user_ptr() in the
> > + * driver to convert back to a __user pointer.
> > + */
> > +struct vmw_zc_guest_data {
> > +	__u64 buffer;
> > +	__u32 buffer_length;
> > +	__u64 metadata;
> > +	__u32 metadata_length;
> > +} __packed;  
> 
> You have an unaligned metadata pointer here in the structure, are you
> sure that's a good idea?  Or can you not change that anymore?

The compiler is going to assume the whole thing can be misaligned.

Possibly:

> > +struct vmw_zc_guest_data {
> > +	__u64 buffer;
> > +	__u32 buffer_length;
> > +	__u64 metadata __packed;
> > +	__u32 metadata_length;
> > +};  

which just removes the pad before 'metadata' while leaving
the structure itself aligned.
The compiler will then only use two 32bit accesses for metadata
instead of byte accesses for all the structure members.

-- David

^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
  2026-06-17 20:31 [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver Rishi Chhibber
  2026-06-18 10:26 ` Greg KH
  2026-06-18 18:10 ` [PATCH v2] " Rishi Chhibber
@ 2026-06-19 18:27 ` Rishi Chhibber
  2026-06-20  5:04   ` Greg KH
  2 siblings, 1 reply; 9+ messages in thread
From: Rishi Chhibber @ 2026-06-19 18:27 UTC (permalink / raw)
  To: arnd, gregkh, linux-kernel
  Cc: ajay.kaher, alexey.makhalov, vamsi-krishna.brahmajosyula,
	yin.ding, tapas.kundu, Rishi Chhibber

This driver implements a misc character device (/dev/vmw_zc) that allows
guest userspace applications to share pinned memory buffers with a
VMware hypervisor-side peer using the VMCI datagram interface.

The driver pins user pages via get_user_pages_fast(), transmits their
physical page frame numbers to the hypervisor peer over VMCI, and avoids
an intermediate copy between the guest workload VM and the hypervisor.

The hypervisor-side peer for this interface only speaks VMCI; there is no
virtio backend implemented on the VMware host for it. The closest
existing upstream transport, vsock (virtio-vsock), provides a socket
bytestream/datagram abstraction and does not expose a way to hand a set
of pinned guest page frame numbers to the host for true zero-copy
access; it would still require copying the payload through the socket.
This driver's purpose is specifically to pin guest pages and pass their
PFNs to the host so the payload is never copied. It also supports
bundling multiple buffers in a single request, which is required for the
all-or-none semantics of page-level zero-copy transfers.

Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
---
Changes in v3:
- Reorder vmw_zc_guest_data fields so both __u64 members are first,
  eliminating the unaligned 'metadata' at offset 12 (Greg KH, David Laight)
- Remove KBUILD_EXTMOD out-of-tree build workaround from Makefile (Greg KH)
- Remove __user annotation from vmw_zc_guest_raw_buffer.raw_buffer (Greg KH)

 MAINTAINERS                                   |   7 +
 drivers/misc/Kconfig                          |   1 +
 drivers/misc/Makefile                         |   1 +
 drivers/misc/vmw_zerocopy/Kconfig             |  16 +
 drivers/misc/vmw_zerocopy/Makefile            |   7 +
 .../misc/vmw_zerocopy/vmw_zerocopy_driver.c   | 420 ++++++++++++++++++
 .../uapi/linux/vmw_zerocopy_ioctl_common.h    |  66 +++
 7 files changed, 518 insertions(+)
 create mode 100644 drivers/misc/vmw_zerocopy/Kconfig
 create mode 100644 drivers/misc/vmw_zerocopy/Makefile
 create mode 100644 drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
 create mode 100644 include/uapi/linux/vmw_zerocopy_ioctl_common.h

diff --git a/MAINTAINERS b/MAINTAINERS
index efd1fa7d66f0..c92fda573a4f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24790,6 +24790,13 @@ L:	linux-kernel@vger.kernel.org
 S:	Supported
 F:	net/vmw_vsock/vmci_transport*
 
+VMWARE ZEROCOPY DRIVER
+M:	Rishi Chhibber <rishi.chhibber@broadcom.com>
+L:	linux-kernel@vger.kernel.org
+S:	Supported
+F:	drivers/misc/vmw_zerocopy/
+F:	include/uapi/linux/vmw_zerocopy*
+
 VOCORE VOCORE2 BOARD
 M:	Harvey Hunt <harveyhuntnexus@gmail.com>
 L:	linux-mips@vger.kernel.org
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index dcfe77b30bd5..6445a878ffdb 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -631,4 +631,5 @@ source "drivers/misc/keba/Kconfig"
 source "drivers/misc/vmw_extcfg/Kconfig"
 source "drivers/misc/vmw_sbx/Kconfig"
 source "drivers/misc/vmw_sbx_dtls/Kconfig"
+source "drivers/misc/vmw_zerocopy/Kconfig"
 endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e4f21eccc994..c2b14169d9ed 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -72,6 +72,7 @@ obj-$(CONFIG_TPS6594_PFSM)	+= tps6594-pfsm.o
 obj-$(CONFIG_VMWARE_EXTCFG)	+= vmw_extcfg/
 obj-$(CONFIG_SBX)		+= vmw_sbx/
 obj-$(CONFIG_SBX_DTLS)		+= vmw_sbx_dtls/
+obj-$(CONFIG_VMW_ZC)		+= vmw_zerocopy/
 obj-$(CONFIG_NSM)		+= nsm.o
 obj-$(CONFIG_MARVELL_CN10K_DPI)	+= mrvl_cn10k_dpi.o
 obj-y				+= keba/
diff --git a/drivers/misc/vmw_zerocopy/Kconfig b/drivers/misc/vmw_zerocopy/Kconfig
new file mode 100644
index 000000000000..e578073cff44
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VMW_ZC
+	tristate "VMware zero-copy buffer sharing device"
+	depends on VMWARE_VMCI
+	help
+	  This driver implements a character device (/dev/vmw_zc) that
+	  allows guest userspace applications to share pinned memory
+	  buffers with a VMware hypervisor-side peer using the VMCI
+	  datagram interface.
+
+	  Applications submit buffers via ioctl(). The driver pins the
+	  user pages and transmits their physical page frame numbers to
+	  the peer, enabling zero-copy data transfer between the guest
+	  and the hypervisor without an intermediate copy.
+
+	  If unsure, say N.
diff --git a/drivers/misc/vmw_zerocopy/Makefile b/drivers/misc/vmw_zerocopy/Makefile
new file mode 100644
index 000000000000..429bc222bff9
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for the VMware Zero Copy Virtual Device driver
+#
+
+obj-$(CONFIG_VMW_ZC) += vmw_zerocopy.o
+vmw_zerocopy-y := vmw_zerocopy_driver.o
diff --git a/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
new file mode 100644
index 000000000000..436f6297f531
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy/vmw_zerocopy_driver.c
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/vmw_vmci_api.h>
+#include <linux/vmw_zerocopy_ioctl_common.h>
+
+#define VMW_ZC_MAX_METADATA_SIZE	1024
+#define VMW_ZC_MAX_BUFFER_SIZE		(64 * 1024UL)
+#define VMW_ZC_MAX_PAGES \
+	(((VMW_ZC_MAX_BUFFER_SIZE) + (PAGE_SIZE - 1)) / PAGE_SIZE + 1)
+
+/* Wire-format messages sent to the peer over VMCI (driver private). */
+struct vmw_zc_msg_unit {
+	u32 offset;
+	u32 length;
+	u32 num_pages;
+	u32 padding1;
+	u64 page_pfns[VMW_ZC_MAX_PAGES];
+} __packed;
+
+struct vmw_zc_user_buffer_pair {
+	struct vmw_zc_msg_unit data;
+	struct vmw_zc_msg_unit metadata;
+} __packed;
+
+struct vmw_zc_host_raw {
+	u8 raw[MAX_RAW_BUFFER_LEN];
+} __packed;
+
+union vmw_zc_host_body {
+	struct vmw_zc_user_buffer_pair user_buf;
+	struct vmw_zc_host_raw raw;
+} __packed;
+
+struct vmw_zc_host_message {
+	u32 message_type;
+	u32 padding1;
+	union vmw_zc_host_body body;
+} __packed;
+
+struct vmw_zc_context {
+	struct vmci_handle dst_hdl;
+	struct vmci_handle src_hdl;
+	atomic_t peer_configured;
+};
+
+static struct vmw_zc_context vmw_zc_ctx;
+
+static long vmw_zc_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
+
+static const struct file_operations vmw_zc_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = vmw_zc_ioctl,
+};
+
+static struct miscdevice vmw_zc_misc = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = VMW_ZC_DEVICE_NAME,
+	.fops = &vmw_zc_fops,
+	.mode = 0644,
+};
+
+/* Driver is send-only; responses from the hypervisor are not expected. */
+static int vmw_zc_dgram_cb(void *cookie, struct vmci_datagram *dg)
+{
+	return 0;
+}
+
+static void vmw_zc_unpin_user_pages(struct page **pages, int num_pages)
+{
+	int i;
+
+	if (!pages)
+		return;
+
+	for (i = 0; i < num_pages; i++) {
+		if (pages[i])
+			put_page(pages[i]);
+	}
+	kfree(pages);
+}
+
+static int vmw_zc_pin_user_pages(void __user *user_buf, size_t size,
+				 struct page ***pages, int *num_pages)
+{
+	unsigned long start_addr = (unsigned long)user_buf;
+	unsigned long end_addr = start_addr + size;
+	int nr_pages;
+	int ret;
+
+	nr_pages = (PAGE_ALIGN(end_addr) - (start_addr & PAGE_MASK)) >> PAGE_SHIFT;
+
+	if (nr_pages > VMW_ZC_MAX_PAGES) {
+		pr_err("buffer spans too many pages: %d > %lu\n",
+		       nr_pages, VMW_ZC_MAX_PAGES);
+		return -EINVAL;
+	}
+
+	*pages = kmalloc_array(nr_pages, sizeof(struct page *), GFP_KERNEL);
+	if (!*pages)
+		return -ENOMEM;
+
+	ret = get_user_pages_fast(start_addr & PAGE_MASK, nr_pages, FOLL_WRITE,
+				  *pages);
+
+	if (ret < 0) {
+		kfree(*pages);
+		*pages = NULL;
+		return ret;
+	}
+
+	if (ret != nr_pages) {
+		vmw_zc_unpin_user_pages(*pages, ret);
+		*pages = NULL;
+		return -EFAULT;
+	}
+
+	*num_pages = nr_pages;
+	return 0;
+}
+
+static int vmw_zc_send_datagram(struct vmw_zc_host_message *msg, size_t msg_size)
+{
+	int ret;
+	struct vmci_datagram *dgram;
+	size_t dgram_size;
+
+	if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+		pr_err("peer not configured; send init first\n");
+		return -ENODEV;
+	}
+	/* Pairs with smp_wmb() in vmw_zc_apply_peer_config() before atomic_cmpxchg(). */
+	smp_rmb();
+
+	dgram_size = VMCI_DG_HEADERSIZE + msg_size;
+	dgram = kzalloc(dgram_size, GFP_KERNEL);
+	if (!dgram)
+		return -ENOMEM;
+
+	dgram->dst = vmw_zc_ctx.dst_hdl;
+	dgram->src = vmw_zc_ctx.src_hdl;
+	dgram->payload_size = msg_size;
+	memcpy(VMCI_DG_PAYLOAD(dgram), msg, msg_size);
+
+	ret = vmci_datagram_send(dgram);
+	kfree(dgram);
+	return ret;
+}
+
+static void vmw_zc_clean_pinned(struct page **pages, int num_pages,
+				struct page **metadata_pages,
+				int num_metadata_pages,
+				struct vmw_zc_host_message *msg)
+{
+	if (pages)
+		vmw_zc_unpin_user_pages(pages, num_pages);
+	if (metadata_pages)
+		vmw_zc_unpin_user_pages(metadata_pages, num_metadata_pages);
+	kfree(msg);
+}
+
+/*
+ * Pin @buffer (and optional @metadata) and send PFN layout to the peer
+ * as @msg_type (expected VMW_ZC_MSG_USER_BUFFER).
+ */
+static int vmw_zc_send_user_buffer_msg(void __user *buffer, u32 buffer_len,
+				     void __user *metadata, u32 metadata_len,
+				     u8 msg_type)
+{
+	int ret;
+	int i;
+	struct vmw_zc_host_message *msg = NULL;
+	struct page **pages = NULL;
+	struct page **metadata_pages = NULL;
+	int num_pages = 0;
+	int num_metadata_pages = 0;
+	unsigned long buf_start = (unsigned long)buffer;
+	unsigned long buf_page_off = buf_start & ~PAGE_MASK;
+	const bool has_metadata = (metadata_len > 0);
+
+	if (msg_type != VMW_ZC_MSG_USER_BUFFER) {
+		pr_err("invalid message type for user buffer path: %u\n",
+		       msg_type);
+		return -EINVAL;
+	}
+
+	if (buffer_len == 0 || buffer_len > VMW_ZC_MAX_BUFFER_SIZE) {
+		pr_err("invalid buffer size: %u (max %lu)\n",
+		       buffer_len, (unsigned long)VMW_ZC_MAX_BUFFER_SIZE);
+		return -EINVAL;
+	}
+
+	if (has_metadata) {
+		if (!metadata ||
+		    metadata_len > VMW_ZC_MAX_METADATA_SIZE) {
+			pr_err("invalid metadata size: %u (max %u)\n",
+			       metadata_len, VMW_ZC_MAX_METADATA_SIZE);
+			return -EINVAL;
+		}
+	}
+
+	ret = vmw_zc_pin_user_pages(buffer, buffer_len, &pages, &num_pages);
+	if (ret)
+		return ret;
+
+	if (has_metadata) {
+		ret = vmw_zc_pin_user_pages(metadata, metadata_len, &metadata_pages,
+					    &num_metadata_pages);
+		if (ret) {
+			vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+					    num_metadata_pages, msg);
+			return ret;
+		}
+	}
+
+	msg = kzalloc(sizeof(*msg), GFP_KERNEL);
+	if (!msg) {
+		vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+				    num_metadata_pages, msg);
+		return -ENOMEM;
+	}
+
+	msg->message_type = msg_type;
+	msg->body.user_buf.data.offset = (u32)buf_page_off;
+	msg->body.user_buf.data.length = buffer_len;
+	msg->body.user_buf.data.num_pages = (u32)num_pages;
+	msg->body.user_buf.data.padding1 = 0;
+
+	if (has_metadata) {
+		unsigned long meta_start = (unsigned long)metadata;
+		unsigned long meta_page_off = meta_start & ~PAGE_MASK;
+
+		msg->body.user_buf.metadata.offset = (u32)meta_page_off;
+		msg->body.user_buf.metadata.length = metadata_len;
+		msg->body.user_buf.metadata.num_pages = (u32)num_metadata_pages;
+		msg->body.user_buf.metadata.padding1 = 0;
+	} else {
+		memset(&msg->body.user_buf.metadata, 0,
+		       sizeof(msg->body.user_buf.metadata));
+	}
+
+	for (i = 0; i < num_pages; i++)
+		msg->body.user_buf.data.page_pfns[i] =
+			(u64)page_to_pfn(pages[i]);
+
+	if (has_metadata) {
+		for (i = 0; i < num_metadata_pages; i++)
+			msg->body.user_buf.metadata.page_pfns[i] =
+				(u64)page_to_pfn(metadata_pages[i]);
+	}
+
+	ret = vmw_zc_send_datagram(msg, sizeof(*msg));
+	if (ret)
+		pr_err("failed to send user-buffer message: %d\n", ret);
+
+	vmw_zc_clean_pinned(pages, num_pages, metadata_pages,
+			    num_metadata_pages, msg);
+	return ret;
+}
+
+static int vmw_zc_send_raw_msg(const u8 *raw, u32 raw_len)
+{
+	int ret;
+	struct vmw_zc_host_message *host_msg;
+
+	if (raw_len == 0 || raw_len > MAX_RAW_BUFFER_LEN) {
+		pr_err("invalid raw size: %u (max %u)\n",
+		       raw_len, MAX_RAW_BUFFER_LEN);
+		return -EINVAL;
+	}
+
+	host_msg = kzalloc(sizeof(*host_msg), GFP_KERNEL);
+	if (!host_msg)
+		return -ENOMEM;
+
+	host_msg->message_type = VMW_ZC_MSG_RAW;
+	memcpy(host_msg->body.raw.raw, raw, raw_len);
+
+	ret = vmw_zc_send_datagram(host_msg, sizeof(*host_msg));
+	if (ret)
+		pr_err("failed to send raw message: %d\n", ret);
+
+	kfree(host_msg);
+	return ret;
+}
+
+static int vmw_zc_apply_peer_config(const struct vmw_zc_ioctl_config *cfg)
+{
+	if (cfg->vmci_resource_id == VMCI_INVALID_ID) {
+		pr_err("invalid resource id\n");
+		return -EINVAL;
+	}
+
+	vmw_zc_ctx.dst_hdl.context = VMCI_HYPERVISOR_CONTEXT_ID;
+	vmw_zc_ctx.dst_hdl.resource = cfg->vmci_resource_id;
+	/* Ensure dst_hdl stores complete before peer_configured=1 is observed.
+	 * Pairs with smp_rmb() in vmw_zc_send_datagram().
+	 */
+	smp_wmb();
+	if (atomic_cmpxchg(&vmw_zc_ctx.peer_configured, 0, 1) != 0) {
+		pr_err("peer already configured\n");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static long vmw_zc_ioctl(struct file *file, unsigned int cmd,
+			 unsigned long arg)
+{
+	int ret = 0;
+	struct vmw_zc_guest_message guest_msg;
+
+	if (cmd != VMW_ZC_IOCTL_MSG)
+		return -ENOTTY;
+
+	if (copy_from_user(&guest_msg, (void __user *)arg, sizeof(guest_msg)))
+		return -EFAULT;
+
+	if (guest_msg.reserved != 0)
+		return -EINVAL;
+
+	switch (guest_msg.message_type) {
+	case VMW_ZC_MSG_INIT:
+		ret = vmw_zc_apply_peer_config(&guest_msg.u.config);
+		break;
+
+	case VMW_ZC_MSG_USER_BUFFER:
+		if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+			pr_err("user buffer before init\n");
+			return -ENODEV;
+		}
+		ret = vmw_zc_send_user_buffer_msg(
+			u64_to_user_ptr(guest_msg.u.data.buffer),
+			guest_msg.u.data.buffer_length,
+			u64_to_user_ptr(guest_msg.u.data.metadata),
+			guest_msg.u.data.metadata_length,
+			guest_msg.message_type);
+		break;
+
+	case VMW_ZC_MSG_RAW:
+		if (!atomic_read(&vmw_zc_ctx.peer_configured)) {
+			pr_err("raw message before init\n");
+			return -ENODEV;
+		}
+		ret = vmw_zc_send_raw_msg(
+			guest_msg.u.raw_buffer.raw_buffer,
+			guest_msg.u.raw_buffer.raw_len);
+		break;
+
+	default:
+		pr_err("unknown message type: %u\n", guest_msg.message_type);
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int __init vmw_zc_init(void)
+{
+	int ret;
+
+	vmw_zc_ctx.dst_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.dst_hdl.resource = VMCI_INVALID_ID;
+	atomic_set(&vmw_zc_ctx.peer_configured, 0);
+	vmw_zc_ctx.src_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.src_hdl.resource = VMCI_INVALID_ID;
+
+	ret = vmci_datagram_create_handle(VMCI_INVALID_ID, 0, vmw_zc_dgram_cb,
+					  NULL, &vmw_zc_ctx.src_hdl);
+	if (ret != VMCI_SUCCESS)
+		return ret;
+
+	ret = misc_register(&vmw_zc_misc);
+	if (ret) {
+		vmci_datagram_destroy_handle(vmw_zc_ctx.src_hdl);
+		vmw_zc_ctx.src_hdl.context = VMCI_INVALID_ID;
+		return ret;
+	}
+
+	return 0;
+}
+
+static void __exit vmw_zc_exit(void)
+{
+	int ret;
+
+	misc_deregister(&vmw_zc_misc);
+
+	atomic_set(&vmw_zc_ctx.peer_configured, 0);
+	/* Ensure peer_configured=0 is visible before clearing dst_hdl. */
+	smp_mb();
+	vmw_zc_ctx.dst_hdl.context = VMCI_INVALID_ID;
+	vmw_zc_ctx.dst_hdl.resource = VMCI_INVALID_ID;
+
+	if (vmw_zc_ctx.src_hdl.context != VMCI_INVALID_ID) {
+		ret = vmci_datagram_destroy_handle(vmw_zc_ctx.src_hdl);
+		if (ret != VMCI_SUCCESS)
+			pr_err("vmci_datagram_destroy_handle failed: %d\n", ret);
+	}
+	vmw_zc_ctx.src_hdl.context = VMCI_INVALID_ID;
+}
+
+module_init(vmw_zc_init);
+module_exit(vmw_zc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Rishi Chhibber <rishi.chhibber@broadcom.com>");
+MODULE_DESCRIPTION("Broadcom zero-copy buffer sharing (VMCI)");
diff --git a/include/uapi/linux/vmw_zerocopy_ioctl_common.h b/include/uapi/linux/vmw_zerocopy_ioctl_common.h
new file mode 100644
index 000000000000..735aa8ced361
--- /dev/null
+++ b/include/uapi/linux/vmw_zerocopy_ioctl_common.h
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ */
+
+#ifndef _VMW_ZC_IOCTL_COMMON_H_
+#define _VMW_ZC_IOCTL_COMMON_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define VMW_ZC_DEVICE_NAME		"vmw_zc"
+#define VMW_ZC_IOCTL_MAGIC		0xDC
+#define MAX_RAW_BUFFER_LEN		32
+
+/*
+ * Used for sending user buffer.
+ * Buffer is required; metadata is optional.
+ *
+ * Pointers are __u64 so the struct layout is identical on 32-bit and 64-bit
+ * userspace, avoiding a compat_ioctl handler. Use u64_to_user_ptr() in the
+ * driver to convert back to a __user pointer.
+ */
+struct vmw_zc_guest_data {
+	__u64 buffer;
+	__u64 metadata;
+	__u32 buffer_length;
+	__u32 metadata_length;
+} __packed; /* defensive: prevents silent padding if fields are added later */
+
+struct vmw_zc_guest_raw_buffer {
+	__u8 raw_buffer[MAX_RAW_BUFFER_LEN];
+	__u32 raw_len;
+} __packed;
+
+/* VMCI datagram destination resource ID (hypervisor context). */
+struct vmw_zc_ioctl_config {
+	__u32 vmci_resource_id;
+} __packed;
+
+/*
+ * @message_type: numeric values (see below). INIT uses @u.config;
+ * user-buffer transfer uses @u.data; small inline payload uses @u.raw_buffer.
+ */
+#define VMW_ZC_MSG_INIT			1u
+#define VMW_ZC_MSG_USER_BUFFER		2u
+#define VMW_ZC_MSG_RAW			3u
+
+struct vmw_zc_guest_message {
+	__u8 message_type;
+	__u64 reserved;
+	union {
+		struct vmw_zc_guest_data data;
+		struct vmw_zc_guest_raw_buffer raw_buffer;
+		struct vmw_zc_ioctl_config config;
+	} u;
+} __packed;
+
+#define VMW_ZC_MESSAGE			0x01
+
+#define VMW_ZC_IOCTL_MSG \
+	_IOW(VMW_ZC_IOCTL_MAGIC, VMW_ZC_MESSAGE, struct vmw_zc_guest_message)
+
+#endif /* _VMW_ZC_IOCTL_COMMON_H_ */
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
  2026-06-19 18:27 ` [PATCH] " Rishi Chhibber
@ 2026-06-20  5:04   ` Greg KH
  2026-06-20  5:06     ` Greg KH
  0 siblings, 1 reply; 9+ messages in thread
From: Greg KH @ 2026-06-20  5:04 UTC (permalink / raw)
  To: Rishi Chhibber
  Cc: arnd, linux-kernel, ajay.kaher, alexey.makhalov,
	vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu

On Fri, Jun 19, 2026 at 11:27:10AM -0700, Rishi Chhibber wrote:
> This driver implements a misc character device (/dev/vmw_zc) that allows
> guest userspace applications to share pinned memory buffers with a
> VMware hypervisor-side peer using the VMCI datagram interface.
> 
> The driver pins user pages via get_user_pages_fast(), transmits their
> physical page frame numbers to the hypervisor peer over VMCI, and avoids
> an intermediate copy between the guest workload VM and the hypervisor.
> 
> The hypervisor-side peer for this interface only speaks VMCI; there is no
> virtio backend implemented on the VMware host for it. The closest
> existing upstream transport, vsock (virtio-vsock), provides a socket
> bytestream/datagram abstraction and does not expose a way to hand a set
> of pinned guest page frame numbers to the host for true zero-copy
> access; it would still require copying the payload through the socket.
> This driver's purpose is specifically to pin guest pages and pass their
> PFNs to the host so the payload is never copied. It also supports
> bundling multiple buffers in a single request, which is required for the
> all-or-none semantics of page-level zero-copy transfers.
> 
> Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
> ---
> Changes in v3:

There is no "v3" in the subject line :(

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
  2026-06-20  5:04   ` Greg KH
@ 2026-06-20  5:06     ` Greg KH
  0 siblings, 0 replies; 9+ messages in thread
From: Greg KH @ 2026-06-20  5:06 UTC (permalink / raw)
  To: Rishi Chhibber
  Cc: arnd, linux-kernel, ajay.kaher, alexey.makhalov,
	vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu

On Sat, Jun 20, 2026 at 07:04:55AM +0200, Greg KH wrote:
> On Fri, Jun 19, 2026 at 11:27:10AM -0700, Rishi Chhibber wrote:
> > This driver implements a misc character device (/dev/vmw_zc) that allows
> > guest userspace applications to share pinned memory buffers with a
> > VMware hypervisor-side peer using the VMCI datagram interface.
> > 
> > The driver pins user pages via get_user_pages_fast(), transmits their
> > physical page frame numbers to the hypervisor peer over VMCI, and avoids
> > an intermediate copy between the guest workload VM and the hypervisor.
> > 
> > The hypervisor-side peer for this interface only speaks VMCI; there is no
> > virtio backend implemented on the VMware host for it. The closest
> > existing upstream transport, vsock (virtio-vsock), provides a socket
> > bytestream/datagram abstraction and does not expose a way to hand a set
> > of pinned guest page frame numbers to the host for true zero-copy
> > access; it would still require copying the payload through the socket.
> > This driver's purpose is specifically to pin guest pages and pass their
> > PFNs to the host so the payload is never copied. It also supports
> > bundling multiple buffers in a single request, which is required for the
> > all-or-none semantics of page-level zero-copy transfers.
> > 
> > Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
> > ---
> > Changes in v3:
> 
> There is no "v3" in the subject line :(

And, because of this, I will strongly recommend and almost require now,
for you to get another internal person that has experience doing kernel
development, to review and sign off on the next version of your patch.

The community shouldn't be forced to do basic review like this, that's
what your coworkers are for.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-06-20  5:07 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-17 20:31 [PATCH] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver Rishi Chhibber
2026-06-18 10:26 ` Greg KH
2026-06-18 18:10 ` [PATCH v2] " Rishi Chhibber
2026-06-19  5:08   ` Greg KH
2026-06-19  5:10   ` Greg KH
2026-06-19 15:12     ` David Laight
2026-06-19 18:27 ` [PATCH] " Rishi Chhibber
2026-06-20  5:04   ` Greg KH
2026-06-20  5:06     ` Greg KH

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.