* [PATCH v3 0/4] misc: vmw_zerocopy: VMware zero-copy buffer sharing driver
@ 2026-09-14 20:22 Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 1/4] misc: vmw_vmci: Reserve a datagram resource id for zero-copy buffer sharing Rishi Chhibber
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Rishi Chhibber @ 2026-09-14 20:22 UTC (permalink / raw)
To: gregkh, arnd
Cc: bryan-bt.tan, vishnu.dasa, corbet, skhan, shuah, rdunlap,
bcm-kernel-feedback-list, linux-doc, linux-kselftest,
linux-kernel, ajay.kaher, alexey.makhalov,
vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu,
Rishi Chhibber
Summary of changes:
- 1/4 reserves a VMCI datagram resource id for the hypervisor-side
zero-copy service
- 2/4 adds the vmw_zerocopy driver: /dev/vmw_zc, a single ioctl, page
pinning and the VMCI datagram transport
- 3/4 documents the userspace interface
- 4/4 adds a kselftest covering the ioctl input-validation paths
This series adds a misc character device, /dev/vmw_zc, that lets guest
userspace hand a buffer to a VMware hypervisor-side peer without copying
it. The driver pins the user pages, collects their guest physical frame
numbers and sends that list over a VMCI datagram; the hypervisor then
reads the buffer directly out of guest memory. An optional metadata
buffer is pinned writable so the peer can return a result through the
same mechanism.
Why not vsock or virtio: this is not paravirtual I/O. The guest is not
sending data to a virtual device; it hands page frame numbers to the
hypervisor and reads the results back inline in those same pages, with
no copy and no ring buffer. Two properties have no mapping onto an
existing virtio device type:
- PFN-level zero copy without ownership transfer. virtio-vsock copies
through the virtqueue ring. virtio-mem and virtio-balloon transfer
page ownership to the host; here the guest keeps its pages and unpins
them when the ioctl returns.
- Per-ioctl synchronous feedback. The metadata pages are pinned
writable and the peer stores status directly into them, so userspace
reads the result on ioctl return. storvsc and hv_balloon return
status in a separate ring packet instead; Xen gntdev's writable
grants and UNMAP_NOTIFY_CLEAR_BYTE serve persistent ring lifecycles,
not per-call results.
The established upstream shape for this is a thin guest driver over the
vendor's native transport: Hyper-V builds a PFN list for the host with
vmbus_establish_gpadl(), Xen shares frames through the grant table and
exposes that to userspace in gntdev, and VMware already has vmxnet3,
vmw_pvscsi and vmw_balloon on its own protocols. This driver is the
VMCI equivalent. Discussed in full in the v2 thread (linked below);
2/4 carries the short form.
v2 was posted as a single patch and is split here into a reviewable
series along file boundaries: every file belongs entirely to one patch,
so each commit builds on its own. 1/4 comes first because 2/4 sends to
the resource id it reserves, and because it has a different maintainer
set. vmw_zerocopy_core.c and vmw_zerocopy_vmci.c stay in one patch as
they form a single module and reference each other.
Changes since v2:
- Split the single patch into this four-patch series
- The destination is now owned by the kernel: the userspace-supplied
peer_id is gone, and that word is a reserved field that must be zero
- Dropped VMW_ZC_MSG_INIT and struct vmw_zc_ioctl_config; the device
needs no configuration step
- Release metadata pages with unpin_user_pages_dirty_lock(..., true);
the peer stores through the physical frame, so nothing else marks
those pages dirty
- Split the VMCI transport out behind a small transport ops struct
- Dropped the out-of-tree KBUILD_EXTMOD build logic from
drivers/misc/Makefile (Greg Kroah-Hartman)
- Removed __user from struct vmw_zc_guest_raw_buffer; it has no place
in an ioctl structure (Greg Kroah-Hartman)
- Registered ioctl magic 0xDC in ioctl-number.rst
- Added driver documentation (3/4) and a selftest (4/4)
- Rebased onto v7.3-rc3
Userspace built against the v2 header must be rebuilt: the peer_id field
and the configuration ioctl it used no longer exist. Nothing is merged
upstream, so no stable ABI is affected.
Testing:
- Every commit builds on x86_64 with CONFIG_VMW_ZC=m, and
make headers_install succeeds at each one
- checkpatch.pl --strict reports nothing on 1/4 and 2/4; on 3/4 and 4/4
only the MAINTAINERS advisory for newly added files
- The selftest builds, and skips cleanly when /dev/vmw_zc is absent
Link to v2:
https://lore.kernel.org/lkml/20260619182710.2498154-1-rishi.chhibber@broadcom.com/
Transport choice, discussed in the v2 thread:
https://lore.kernel.org/lkml/CABJwUKKCwGxYB0is-U84EgAm6Co9_bfxbkZi56jZNLkzihDRyw@mail.gmail.com/
Rishi Chhibber (4):
misc: vmw_vmci: Reserve a datagram resource id for zero-copy buffer
sharing
misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
Documentation: misc: Add vmw_zerocopy driver documentation
selftests: misc: Add vmw_zerocopy selftest
Documentation/misc-devices/index.rst | 1 +
Documentation/misc-devices/vmw_zerocopy.rst | 278 ++++++++++++++++++
.../userspace-api/ioctl/ioctl-number.rst | 1 +
MAINTAINERS | 10 +
drivers/misc/Kconfig | 23 ++
drivers/misc/Makefile | 2 +
drivers/misc/vmw_zerocopy_core.c | 267 +++++++++++++++++
drivers/misc/vmw_zerocopy_priv.h | 66 +++++
drivers/misc/vmw_zerocopy_vmci.c | 132 +++++++++
include/linux/vmw_vmci_defs.h | 3 +-
include/uapi/linux/vmw_zerocopy.h | 67 +++++
tools/testing/selftests/Makefile | 1 +
.../drivers/misc/vmw_zerocopy/.gitignore | 1 +
.../drivers/misc/vmw_zerocopy/Makefile | 20 ++
.../drivers/misc/vmw_zerocopy/config | 2 +
.../misc/vmw_zerocopy/test_vmw_zerocopy.c | 276 +++++++++++++++++
16 files changed, 1149 insertions(+), 1 deletion(-)
create mode 100644 Documentation/misc-devices/vmw_zerocopy.rst
create mode 100644 drivers/misc/vmw_zerocopy_core.c
create mode 100644 drivers/misc/vmw_zerocopy_priv.h
create mode 100644 drivers/misc/vmw_zerocopy_vmci.c
create mode 100644 include/uapi/linux/vmw_zerocopy.h
create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore
create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile
create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/config
create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c
base-commit: fd73f4a6659897191fa0d40695fe370925dd3780
--
2.52.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 1/4] misc: vmw_vmci: Reserve a datagram resource id for zero-copy buffer sharing
2026-09-14 20:22 [PATCH v3 0/4] misc: vmw_zerocopy: VMware zero-copy buffer sharing driver Rishi Chhibber
@ 2026-09-14 20:22 ` Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 2/4] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver Rishi Chhibber
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Rishi Chhibber @ 2026-09-14 20:22 UTC (permalink / raw)
To: gregkh, arnd
Cc: bryan-bt.tan, vishnu.dasa, corbet, skhan, shuah, rdunlap,
bcm-kernel-feedback-list, linux-doc, linux-kselftest,
linux-kernel, ajay.kaher, alexey.makhalov,
vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu,
Rishi Chhibber
Summary of changes:
- Add VMCI_VMWZC_DST_RID to the reserved hypervisor datagram resource id
enum and bump VMCI_RESOURCE_MAX accordingly
The hypervisor-side zero-copy buffer sharing service occupies its own
resource id in the hypervisor datagram namespace. Reserve a name for it
alongside the existing entries rather than leaving callers to open-code the
number, following the procedure described in that enum's own comment: the
set of resource ids available in the hypervisor is fixed and statically
known, and a new caller extends it by incrementing the maximum.
VMCI_RESOURCE_MAX has no in-tree users, so the bump does not affect
existing code. The new entry likewise has no in-tree user until the
vmw_zerocopy driver, added later in this series, references it.
Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
Reviewed-by: Alexey Makhalov <alexey.makhalov@broadcom.com>
Reviewed-by: Vishnu Dasa <vishnu.dasa@broadcom.com>
---
include/linux/vmw_vmci_defs.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/linux/vmw_vmci_defs.h b/include/linux/vmw_vmci_defs.h
index 60c9eacd2cf3..4435d2ff1241 100644
--- a/include/linux/vmw_vmci_defs.h
+++ b/include/linux/vmw_vmci_defs.h
@@ -179,7 +179,8 @@ enum {
VMCI_UNITY_PBRPC_REGISTER = 14,
VMCI_RPC_PRIVILEGED = 15,
VMCI_RPC_UNPRIVILEGED = 16,
- VMCI_RESOURCE_MAX = 17,
+ VMCI_VMWZC_DST_RID = 17,
+ VMCI_RESOURCE_MAX = 18,
};
/*
--
2.52.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 2/4] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
2026-09-14 20:22 [PATCH v3 0/4] misc: vmw_zerocopy: VMware zero-copy buffer sharing driver Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 1/4] misc: vmw_vmci: Reserve a datagram resource id for zero-copy buffer sharing Rishi Chhibber
@ 2026-09-14 20:22 ` Rishi Chhibber
2026-09-15 7:13 ` Greg KH
2026-09-14 20:22 ` [PATCH v3 3/4] Documentation: misc: Add vmw_zerocopy driver documentation Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 4/4] selftests: misc: Add vmw_zerocopy selftest Rishi Chhibber
3 siblings, 1 reply; 6+ messages in thread
From: Rishi Chhibber @ 2026-09-14 20:22 UTC (permalink / raw)
To: gregkh, arnd
Cc: bryan-bt.tan, vishnu.dasa, corbet, skhan, shuah, rdunlap,
bcm-kernel-feedback-list, linux-doc, linux-kselftest,
linux-kernel, ajay.kaher, alexey.makhalov,
vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu,
Rishi Chhibber
Summary of changes:
- Add drivers/misc/vmw_zerocopy_core.c: the misc character device
/dev/vmw_zc, its single ioctl, page pinning and PFN collection
- Add drivers/misc/vmw_zerocopy_vmci.c: the VMCI datagram transport backend
- Add drivers/misc/vmw_zerocopy_priv.h: the wire-format message structs and
the struct vmw_zc_transport_ops interface that joins the two
- Add include/uapi/linux/vmw_zerocopy.h: struct vmw_zc_guest_message and
VMW_ZC_IOCTL_MSG
- Register ioctl magic 0xDC in
Documentation/userspace-api/ioctl/ioctl-number.rst
- Add the VMW_ZC Kconfig symbol and a MAINTAINERS entry
This driver implements a misc character device (/dev/vmw_zc) that pins
guest userspace pages and transmits their physical frame numbers (PFNs) to
a VMware hypervisor-side peer.
The payload itself is never transferred. What crosses to the hypervisor is
a list of guest physical frame numbers for pages pinned in place, so the
hypervisor reads the buffer directly out of guest memory. vsock and virtio
are copying transports: both move the bytes through a ring or a socket
buffer, which is precisely the copy this interface exists to avoid. The
VMCI datagram carries only the PFN descriptor and never the data, which is
why an existing standard transport cannot serve this purpose.
The destination is deliberately not part of the UAPI. It is a VMCI resource
id in the hypervisor context, and that namespace belongs to the platform:
the low ids are the hypervisor's own control entry points, and
vmci_datagram_send() dispatches from an in-kernel sender, so the check that
stops userspace from addressing the hypervisor does not apply here. A
device node that unprivileged userspace can open must therefore not let
userspace name a destination, or it becomes an unfiltered writer onto the
hypervisor's control namespace. Fixing the destination in the driver
removes that reachability by construction rather than by validation.
Delivery is abstracted behind a small transport interface so that VMCI, the
only backend today, is not depended on directly by the ioctl path.
The metadata buffer is released with unpin_user_pages_dirty_lock(...,
true). The hypervisor stores its result through the physical frame, so
neither the guest page table nor the folio records the write and the result
can be lost to writeback or reclaim; this is the same fix as commit
779055842da5 ("xen/gntdev.c: Mark pages as dirty") and the pattern used by
drivers/xen/privcmd.c. The read-only data buffer keeps the plain path.
Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
Reviewed-by: Alexey Makhalov <alexey.makhalov@broadcom.com>
Reviewed-by: Vishnu Dasa <vishnu.dasa@broadcom.com>
---
.../userspace-api/ioctl/ioctl-number.rst | 1 +
MAINTAINERS | 10 +
drivers/misc/Kconfig | 23 ++
drivers/misc/Makefile | 2 +
drivers/misc/vmw_zerocopy_core.c | 267 ++++++++++++++++++
drivers/misc/vmw_zerocopy_priv.h | 66 +++++
drivers/misc/vmw_zerocopy_vmci.c | 132 +++++++++
include/uapi/linux/vmw_zerocopy.h | 67 +++++
8 files changed, 568 insertions(+)
create mode 100644 drivers/misc/vmw_zerocopy_core.c
create mode 100644 drivers/misc/vmw_zerocopy_priv.h
create mode 100644 drivers/misc/vmw_zerocopy_vmci.c
create mode 100644 include/uapi/linux/vmw_zerocopy.h
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 2fc53093752d..f55998d5e270 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -396,6 +396,7 @@ Code Seq# Include File Comments
0xCD 01 linux/reiserfs_fs.h Dead since 6.13
0xCE 01-02 uapi/linux/cxl_mem.h Compute Express Link Memory Devices
0xCF 02 fs/smb/client/cifs_ioctl.h
+0xDC 01 uapi/linux/vmw_zerocopy.h VMware zero-copy buffer sharing
0xDD 00-3F ZFCP device driver see drivers/s390/scsi/
<mailto:aherrman@de.ibm.com>
0xE5 00-3F linux/fuse.h
diff --git a/MAINTAINERS b/MAINTAINERS
index c2414447892c..d683336b30d8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29228,6 +29228,16 @@ 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: Documentation/misc-devices/vmw_zerocopy.rst
+F: drivers/misc/vmw_zerocopy*
+F: include/uapi/linux/vmw_zerocopy.h
+F: tools/testing/selftests/drivers/misc/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 7364931dad3a..cf48c29db35e 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -359,6 +359,29 @@ config VMWARE_BALLOON
To compile this driver as a module, choose M here: the
module will be called vmw_balloon.
+config VMW_ZC
+ tristate "VMware zero-copy buffer sharing device"
+ depends on VMWARE_VMCI && HYPERVISOR_GUEST && !X86_MEM_ENCRYPT
+ 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.
+
+ This driver is not compatible with guests that use memory
+ encryption (AMD SEV-SNP or Intel TDX), as the hypervisor
+ cannot read encrypted guest physical memory.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called vmw_zerocopy.
+
config PCH_PHUB
tristate "Intel EG20T PCH/LAPIS Semicon IOH(ML7213/ML7223/ML7831) PHUB"
select GENERIC_NET_UTILS
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index e8d8d5d88c0d..eda927fd68dd 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -62,6 +62,8 @@ obj-$(CONFIG_TMR_MANAGER) += xilinx_tmr_manager.o
obj-$(CONFIG_TMR_INJECT) += xilinx_tmr_inject.o
obj-$(CONFIG_TPS6594_ESM) += tps6594-esm.o
obj-$(CONFIG_TPS6594_PFSM) += tps6594-pfsm.o
+obj-$(CONFIG_VMW_ZC) += vmw_zerocopy.o
+vmw_zerocopy-y := vmw_zerocopy_core.o vmw_zerocopy_vmci.o
obj-$(CONFIG_NSM) += nsm.o
obj-$(CONFIG_MARVELL_CN10K_DPI) += mrvl_cn10k_dpi.o
lan966x-pci-objs := lan966x_pci.o
diff --git a/drivers/misc/vmw_zerocopy_core.c b/drivers/misc/vmw_zerocopy_core.c
new file mode 100644
index 000000000000..bf5ab20ff1a0
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy_core.c
@@ -0,0 +1,267 @@
+// 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+
+#include "vmw_zerocopy_priv.h"
+
+/* Compile-time transport selection; a second backend swaps this pointer. */
+static const struct vmw_zc_transport_ops *vmw_zc_transport = &vmw_zc_vmci_transport;
+
+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,
+ .compat_ioctl = compat_ptr_ioctl,
+};
+
+/*
+ * 0644: any local user may open this device, but that only lets them
+ * pin their own memory and reach VMCI_VMWZC_DST_RID, a destination
+ * fixed by the driver.
+ */
+static struct miscdevice vmw_zc_misc = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = VMW_ZC_DEVICE_NAME,
+ .fops = &vmw_zc_fops,
+ .mode = 0644,
+};
+
+/*
+ * @make_dirty must be true for any buffer the peer wrote into: it stores
+ * through the physical frame, so nothing marks the page dirty and the result
+ * would be lost to writeback or reclaim.
+ */
+static void vmw_zc_unpin_user_pages(struct page **pages, int num_pages,
+ bool make_dirty)
+{
+ if (num_pages <= 0)
+ return;
+
+ unpin_user_pages_dirty_lock(pages, num_pages, make_dirty);
+}
+
+/* @pages must have room for VMW_ZC_MAX_PAGES entries. */
+static int vmw_zc_pin_user_pages(void __user *user_buf, size_t size,
+ struct page **pages, int *num_pages,
+ bool writable)
+{
+ unsigned long start_addr = (unsigned long)user_buf;
+ unsigned int gup_flags = writable ? FOLL_WRITE : 0;
+ int nr_pages;
+ int ret;
+
+ if (!access_ok(user_buf, size)) {
+ dev_dbg(vmw_zc_misc.this_device,
+ "buffer not in user address space: 0x%lx + %zu\n",
+ start_addr, size);
+ return -EFAULT;
+ }
+
+ nr_pages = DIV_ROUND_UP(offset_in_page(user_buf) + size, PAGE_SIZE);
+
+ if ((unsigned long)nr_pages > VMW_ZC_MAX_PAGES) {
+ dev_dbg(vmw_zc_misc.this_device,
+ "buffer spans too many pages: %d > %lu\n",
+ nr_pages, VMW_ZC_MAX_PAGES);
+ return -EINVAL;
+ }
+
+ ret = pin_user_pages_fast(start_addr & PAGE_MASK, nr_pages, gup_flags,
+ pages);
+ if (ret < 0)
+ return ret;
+
+ if (ret != nr_pages) {
+ /* Nothing was sent to the peer yet, so nothing was written. */
+ vmw_zc_unpin_user_pages(pages, ret, false);
+ return -EFAULT;
+ }
+
+ *num_pages = nr_pages;
+ return 0;
+}
+
+/*
+ * Pin @buffer and optional @metadata and send PFN layout to the peer.
+ * Either buffer or metadata must exist.
+ */
+static int vmw_zc_send_user_buffer_msg(void __user *buffer, u32 buffer_len,
+ void __user *metadata, u32 metadata_len)
+{
+ int ret;
+ int i;
+ struct vmw_zc_host_message msg = { };
+ struct page *buffer_pages[VMW_ZC_MAX_PAGES];
+ struct page *metadata_pages[VMW_ZC_MAX_PAGES];
+ int num_buffer_pages = 0;
+ int num_metadata_pages = 0;
+
+ if (!buffer && !metadata) {
+ dev_dbg(vmw_zc_misc.this_device,
+ "neither buffer nor metadata provided\n");
+ return -EINVAL;
+ }
+
+ if (buffer) {
+ if (buffer_len == 0 || buffer_len > VMW_ZC_MAX_BUFFER_SIZE) {
+ dev_dbg(vmw_zc_misc.this_device,
+ "invalid buffer size: %u (expected between 1-%u)\n",
+ buffer_len, VMW_ZC_MAX_BUFFER_SIZE);
+ return -EINVAL;
+ }
+ ret = vmw_zc_pin_user_pages(buffer, buffer_len, buffer_pages,
+ &num_buffer_pages, false);
+ if (ret)
+ return ret;
+ }
+
+ if (metadata) {
+ if (metadata_len == 0 ||
+ metadata_len > VMW_ZC_MAX_BUFFER_SIZE) {
+ dev_dbg(vmw_zc_misc.this_device,
+ "invalid metadata size: %u (expected between 1-%u)\n",
+ metadata_len, VMW_ZC_MAX_BUFFER_SIZE);
+ ret = -EINVAL;
+ goto out_unpin;
+ }
+ ret = vmw_zc_pin_user_pages(metadata, metadata_len,
+ metadata_pages,
+ &num_metadata_pages, true);
+ if (ret)
+ goto out_unpin;
+ }
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+
+ if (buffer) {
+ msg.body.user_buf.data.offset = offset_in_page(buffer);
+ msg.body.user_buf.data.length = buffer_len;
+ msg.body.user_buf.data.num_pages = num_buffer_pages;
+ for (i = 0; i < num_buffer_pages; i++) {
+ msg.body.user_buf.data.page_pfns[i] =
+ page_to_pfn(buffer_pages[i]);
+ }
+ }
+
+ if (metadata) {
+ msg.body.user_buf.metadata.offset = offset_in_page(metadata);
+ msg.body.user_buf.metadata.length = metadata_len;
+ msg.body.user_buf.metadata.num_pages = num_metadata_pages;
+ for (i = 0; i < num_metadata_pages; i++) {
+ msg.body.user_buf.metadata.page_pfns[i] =
+ page_to_pfn(metadata_pages[i]);
+ }
+ }
+
+ ret = vmw_zc_transport->send(&msg);
+
+out_unpin:
+ /* Metadata is written by the peer; the data buffer is read-only. */
+ vmw_zc_unpin_user_pages(metadata_pages, num_metadata_pages, true);
+ vmw_zc_unpin_user_pages(buffer_pages, num_buffer_pages, false);
+ return ret;
+}
+
+static int vmw_zc_send_raw_msg(const u8 *raw, u32 raw_len)
+{
+ struct vmw_zc_host_message msg = { };
+
+ if (raw_len == 0 || raw_len > VMW_ZC_MAX_RAW_BUFFER_LEN) {
+ dev_dbg(vmw_zc_misc.this_device,
+ "invalid raw size: %u (max %u)\n",
+ raw_len, VMW_ZC_MAX_RAW_BUFFER_LEN);
+ return -EINVAL;
+ }
+
+ msg.message_type = VMW_ZC_MSG_RAW;
+ memcpy(msg.body.raw.raw, raw, raw_len);
+
+ return vmw_zc_transport->send(&msg);
+}
+
+static long vmw_zc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ int ret = 0;
+ void __user *buf, *meta;
+ u32 buf_len, meta_len;
+ 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) {
+ dev_dbg(vmw_zc_misc.this_device,
+ "reserved field must be zero: %u\n",
+ guest_msg.reserved);
+ return -EINVAL;
+ }
+
+ switch (guest_msg.message_type) {
+ case VMW_ZC_MSG_USER_BUFFER:
+ buf = u64_to_user_ptr(guest_msg.u.data.buffer);
+ buf_len = guest_msg.u.data.buffer_length;
+ meta = u64_to_user_ptr(guest_msg.u.data.metadata);
+ meta_len = guest_msg.u.data.metadata_length;
+ ret = vmw_zc_send_user_buffer_msg(buf, buf_len, meta, meta_len);
+ break;
+
+ case VMW_ZC_MSG_RAW:
+ ret = vmw_zc_send_raw_msg(guest_msg.u.raw_buffer.raw_buffer,
+ guest_msg.u.raw_buffer.raw_len);
+ break;
+
+ default:
+ dev_dbg(vmw_zc_misc.this_device,
+ "unknown message type: %u\n",
+ guest_msg.message_type);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int __init vmw_zc_init(void)
+{
+ int ret;
+
+ ret = vmw_zc_transport->init();
+ if (ret)
+ return ret;
+
+ ret = misc_register(&vmw_zc_misc);
+ if (ret) {
+ pr_err("failed to register misc device: %d\n", ret);
+ vmw_zc_transport->exit();
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit vmw_zc_exit(void)
+{
+ misc_deregister(&vmw_zc_misc);
+ vmw_zc_transport->exit();
+}
+
+module_init(vmw_zc_init);
+module_exit(vmw_zc_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Broadcom Corporation");
+MODULE_DESCRIPTION("Broadcom VMware zero-copy sync buffer sharing");
diff --git a/drivers/misc/vmw_zerocopy_priv.h b/drivers/misc/vmw_zerocopy_priv.h
new file mode 100644
index 000000000000..f2de3ce9391a
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy_priv.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_ZEROCOPY_PRIV_H_
+#define _VMW_ZEROCOPY_PRIV_H_
+
+#include <linux/math.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+#include <linux/vmw_zerocopy.h>
+#include <asm/page.h>
+
+#define VMW_ZC_MAX_BUFFER_SIZE SZ_64K
+
+/*
+ * Worst-case pages spanned by a max-size buffer: the +1 covers a buffer whose
+ * start is not page aligned and therefore straddles one additional page.
+ */
+#define VMW_ZC_MAX_PAGES (DIV_ROUND_UP(VMW_ZC_MAX_BUFFER_SIZE, PAGE_SIZE) + 1)
+
+/* Wire-format messages sent to the peer over the transport (driver private). */
+struct vmw_zc_msg_unit {
+ u32 offset;
+ u32 length;
+ u32 num_pages;
+ u32 pad;
+ u64 page_pfns[VMW_ZC_MAX_PAGES];
+};
+
+struct vmw_zc_user_buffer_pair {
+ struct vmw_zc_msg_unit data;
+ struct vmw_zc_msg_unit metadata;
+};
+
+struct vmw_zc_host_raw {
+ u8 raw[VMW_ZC_MAX_RAW_BUFFER_LEN];
+};
+
+union vmw_zc_host_body {
+ struct vmw_zc_user_buffer_pair user_buf;
+ struct vmw_zc_host_raw raw;
+};
+
+struct vmw_zc_host_message {
+ u32 message_type;
+ u32 pad;
+ union vmw_zc_host_body body;
+};
+
+/*
+ * A transport delivers a filled-in message to its peer. The destination is
+ * owned entirely by the transport backend: userspace supplies no address and
+ * cannot influence where a message is sent.
+ */
+struct vmw_zc_transport_ops {
+ int (*init)(void);
+ void (*exit)(void);
+ int (*send)(const struct vmw_zc_host_message *msg);
+};
+
+extern const struct vmw_zc_transport_ops vmw_zc_vmci_transport;
+
+#endif /* _VMW_ZEROCOPY_PRIV_H_ */
diff --git a/drivers/misc/vmw_zerocopy_vmci.c b/drivers/misc/vmw_zerocopy_vmci.c
new file mode 100644
index 000000000000..39aadc1afaad
--- /dev/null
+++ b/drivers/misc/vmw_zerocopy_vmci.c
@@ -0,0 +1,132 @@
+// 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/build_bug.h>
+#include <linux/stddef.h>
+#include <linux/string.h>
+#include <linux/vmw_vmci_api.h>
+
+#include "vmw_zerocopy_priv.h"
+
+/*
+ * On-wire layout: a VMCI datagram header immediately followed by our payload.
+ * Built on the stack per send, so the send path holds no shared state and
+ * needs no lock. Must not be const: vmci_route() may rewrite src.context.
+ */
+struct vmw_zc_vmci_wire {
+ struct vmci_datagram hdr;
+ struct vmw_zc_host_message payload;
+};
+
+static struct vmci_handle vmw_zc_vmci_src_hdl;
+
+/* Driver is send-only; responses from the hypervisor are not expected. */
+static int vmw_zc_vmci_dgram_cb(void *cookie __always_unused,
+ struct vmci_datagram *dg __always_unused)
+{
+ return 0;
+}
+
+/*
+ * Map a VMCI send failure to an errno, modelled on
+ * vmci_transport_error_to_vsock_error(). Distinguishing these matters most for
+ * the unreachable cases: they are what a guest sees when the hypervisor has no
+ * listener on VMCI_VMWZC_DST_RID, which must not look like a transient failure.
+ */
+static int vmw_zc_vmci_errno(int vmci_error)
+{
+ switch (vmci_error) {
+ case VMCI_ERROR_INVALID_RESOURCE:
+ case VMCI_ERROR_NO_HANDLE:
+ case VMCI_ERROR_DST_UNREACHABLE:
+ return -EHOSTUNREACH;
+ case VMCI_ERROR_NO_MEM:
+ return -ENOMEM;
+ case VMCI_ERROR_NO_RESOURCES:
+ return -ENOBUFS;
+ case VMCI_ERROR_PAYLOAD_TOO_LARGE:
+ return -EMSGSIZE;
+ case VMCI_ERROR_NO_ACCESS:
+ case VMCI_ERROR_INVALID_PRIV:
+ return -EPERM;
+ case VMCI_ERROR_INVALID_ARGS:
+ return -EINVAL;
+ default:
+ return -EIO;
+ }
+}
+
+static int vmw_zc_vmci_send(const struct vmw_zc_host_message *msg)
+{
+ struct vmw_zc_vmci_wire wire = { };
+ int ret;
+
+ BUILD_BUG_ON(offsetof(struct vmw_zc_vmci_wire, payload) !=
+ VMCI_DG_HEADERSIZE);
+ BUILD_BUG_ON(sizeof(wire) > VMCI_MAX_DG_SIZE);
+
+ /*
+ * The destination is fixed by the host ABI (VMCI_VMWZC_DST_RID in
+ * VMCI's reserved hypervisor datagram range); userspace never selects
+ * it. @message_type in the payload distinguishes operations.
+ */
+ wire.hdr.dst.context = VMCI_HYPERVISOR_CONTEXT_ID;
+ wire.hdr.dst.resource = VMCI_VMWZC_DST_RID;
+ wire.hdr.src = vmw_zc_vmci_src_hdl;
+ wire.hdr.payload_size = sizeof(wire.payload);
+
+ /*
+ * memcpy, not struct assignment: the payload's union leaves its
+ * inactive tail bytes unspecified, and kernel stacks are recycled.
+ */
+ memcpy(&wire.payload, msg, sizeof(wire.payload));
+
+ ret = vmci_datagram_send(&wire.hdr);
+ if (ret < 0) {
+ pr_err_ratelimited("failed to send VMCI datagram: %d\n", ret);
+ return vmw_zc_vmci_errno(ret);
+ }
+ return 0;
+}
+
+static int vmw_zc_vmci_init(void)
+{
+ int ret;
+
+ vmw_zc_vmci_src_hdl = VMCI_INVALID_HANDLE;
+
+ ret = vmci_datagram_create_handle(VMCI_INVALID_ID, 0,
+ vmw_zc_vmci_dgram_cb, NULL,
+ &vmw_zc_vmci_src_hdl);
+ if (ret != VMCI_SUCCESS) {
+ pr_err("failed to create VMCI datagram handle: %d\n", ret);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void vmw_zc_vmci_exit(void)
+{
+ int ret;
+
+ if (!vmci_handle_is_invalid(vmw_zc_vmci_src_hdl)) {
+ ret = vmci_datagram_destroy_handle(vmw_zc_vmci_src_hdl);
+ if (ret != VMCI_SUCCESS) {
+ pr_err("failed to destroy VMCI datagram handle: %d\n",
+ ret);
+ }
+ }
+ vmw_zc_vmci_src_hdl = VMCI_INVALID_HANDLE;
+}
+
+const struct vmw_zc_transport_ops vmw_zc_vmci_transport = {
+ .init = vmw_zc_vmci_init,
+ .exit = vmw_zc_vmci_exit,
+ .send = vmw_zc_vmci_send,
+};
diff --git a/include/uapi/linux/vmw_zerocopy.h b/include/uapi/linux/vmw_zerocopy.h
new file mode 100644
index 000000000000..0fa263870df1
--- /dev/null
+++ b/include/uapi/linux/vmw_zerocopy.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */
+/*
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ */
+
+#ifndef _UAPI_LINUX_VMW_ZEROCOPY_H_
+#define _UAPI_LINUX_VMW_ZEROCOPY_H_
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+#define VMW_ZC_DEVICE_NAME "vmw_zc"
+#define VMW_ZC_IOCTL_MAGIC 0xDC
+/*
+ * Largest inline payload that keeps sizeof(struct vmw_zc_guest_message)
+ * at 48 bytes
+ */
+#define VMW_ZC_MAX_RAW_BUFFER_LEN 36
+
+/*
+ * Used for sending user buffer.
+ *
+ * Pointers are __u64 so the struct layout is identical on 32-bit and 64-bit
+ * userspace. The ioctl argument pointer itself is converted by
+ * compat_ptr_ioctl(); the driver uses u64_to_user_ptr() on these fields.
+ *
+ * Set buffer or metadata to 0 to indicate absence.
+ * At least one must be non-zero.
+ */
+struct vmw_zc_guest_data {
+ __u64 buffer;
+ __u64 metadata;
+ __u32 buffer_length;
+ __u32 metadata_length;
+};
+
+struct vmw_zc_guest_raw_buffer {
+ __u8 raw_buffer[VMW_ZC_MAX_RAW_BUFFER_LEN];
+ __u32 raw_len;
+};
+
+/*
+ * @message_type: numeric values (see below). User-buffer transfer uses
+ * @u.data; small inline payload uses @u.raw_buffer.
+ * @reserved: must be zero. The destination is fixed by the driver and cannot
+ * be selected by userspace.
+ */
+#define VMW_ZC_MSG_USER_BUFFER 1
+#define VMW_ZC_MSG_RAW 2
+
+struct vmw_zc_guest_message {
+ __u32 message_type;
+ __u32 reserved;
+ union {
+ struct vmw_zc_guest_data data;
+ struct vmw_zc_guest_raw_buffer raw_buffer;
+ } u;
+};
+
+#define VMW_ZC_IOCTL_NR_MSG 0x01
+
+#define VMW_ZC_IOCTL_MSG \
+ _IOW(VMW_ZC_IOCTL_MAGIC, VMW_ZC_IOCTL_NR_MSG, struct vmw_zc_guest_message)
+
+#endif /* _UAPI_LINUX_VMW_ZEROCOPY_H_ */
--
2.52.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 3/4] Documentation: misc: Add vmw_zerocopy driver documentation
2026-09-14 20:22 [PATCH v3 0/4] misc: vmw_zerocopy: VMware zero-copy buffer sharing driver Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 1/4] misc: vmw_vmci: Reserve a datagram resource id for zero-copy buffer sharing Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 2/4] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver Rishi Chhibber
@ 2026-09-14 20:22 ` Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 4/4] selftests: misc: Add vmw_zerocopy selftest Rishi Chhibber
3 siblings, 0 replies; 6+ messages in thread
From: Rishi Chhibber @ 2026-09-14 20:22 UTC (permalink / raw)
To: gregkh, arnd
Cc: bryan-bt.tan, vishnu.dasa, corbet, skhan, shuah, rdunlap,
bcm-kernel-feedback-list, linux-doc, linux-kselftest,
linux-kernel, ajay.kaher, alexey.makhalov,
vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu,
Rishi Chhibber
Summary of changes:
- Add Documentation/misc-devices/vmw_zerocopy.rst describing the device
node, the ioctl interface, the message types and the buffer limits
- Link it from Documentation/misc-devices/index.rst
Document the userspace interface of the vmw_zerocopy driver added earlier
in this series: the /dev/vmw_zc device node, the VMW_ZC_IOCTL_MSG ioctl and
its message types, the data and metadata size limits, and an example call
sequence.
The prose also covers the parts of the design that are not apparent from
the interface alone: how a PFN list replaces a data copy, the metadata
feedback path and why those pages need different release handling from the
data pages, the atomic submission semantics of placing both PFN lists in a
single datagram, the transport abstraction, and why the destination
resource id is owned by the kernel rather than selectable by userspace.
Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
Reviewed-by: Alexey Makhalov <alexey.makhalov@broadcom.com>
Reviewed-by: Vishnu Dasa <vishnu.dasa@broadcom.com>
---
Documentation/misc-devices/index.rst | 1 +
Documentation/misc-devices/vmw_zerocopy.rst | 278 ++++++++++++++++++++
2 files changed, 279 insertions(+)
create mode 100644 Documentation/misc-devices/vmw_zerocopy.rst
diff --git a/Documentation/misc-devices/index.rst b/Documentation/misc-devices/index.rst
index f911edaecbfa..8d1687a2d566 100644
--- a/Documentation/misc-devices/index.rst
+++ b/Documentation/misc-devices/index.rst
@@ -27,4 +27,5 @@ fit into other categories.
spear-pcie-gadget
tps6594-pfsm
uacce
+ vmw_zerocopy
xilinx_sdfec
diff --git a/Documentation/misc-devices/vmw_zerocopy.rst b/Documentation/misc-devices/vmw_zerocopy.rst
new file mode 100644
index 000000000000..80a6277acf28
--- /dev/null
+++ b/Documentation/misc-devices/vmw_zerocopy.rst
@@ -0,0 +1,278 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+======================================
+VMware Zero-Copy Buffer Sharing Driver
+======================================
+
+Overview
+========
+
+The VMware zero-copy driver (``vmw_zerocopy``) provides a misc character
+device at ``/dev/vmw_zc`` that allows guest userspace to share memory buffers
+with a VMware hypervisor-side peer without copying the payload through a
+kernel bounce buffer.
+
+The driver's responsibilities are strictly bounded:
+
+- Pin the caller's user pages with ``pin_user_pages_fast()``.
+- Collect the physical page frame numbers (PFNs) of those pages.
+- Transmit the PFN list to the hypervisor peer through the driver's
+ transport backend (currently VMCI; see `Transport Abstraction`_).
+- Unpin the pages when the ioctl returns.
+
+The hypervisor reads the payload directly from the guest's physical frames.
+No data is copied into or out of kernel memory.
+
+
+How Zero Copy Works
+===================
+
+A conventional driver copies application data into a kernel buffer and then
+sends that buffer to the device. This driver does not.
+
+When userspace calls ``VMW_ZC_IOCTL_MSG`` with a ``VMW_ZC_MSG_USER_BUFFER``
+message, the kernel:
+
+1. Calls ``pin_user_pages_fast()`` to pin the data and metadata pages in
+ place, preventing them from being swapped or moved for the duration of
+ the ioctl.
+2. Iterates over the pinned pages and records each page's PFN using
+ ``page_to_pfn()``.
+3. Packs the PFN list into a ``struct vmw_zc_host_message`` and hands it to
+ the transport backend for delivery to the hypervisor peer (currently
+ VMCI's ``vmci_datagram_send()``).
+4. Calls ``unpin_user_pages()`` before returning.
+
+The hypervisor receives the datagram, maps the listed guest-physical frames
+directly, and accesses the buffer contents without any copy traversing the
+guest kernel. The guest kernel never touches the payload data itself.
+
+
+Metadata Feedback Path
+======================
+
+Each ``VMW_ZC_MSG_USER_BUFFER`` call may supply two separate buffers:
+
+- **data buffer** - the payload the hypervisor will read. Pages are pinned
+ with ``gup_flags = 0`` (read-only from the caller's perspective).
+- **metadata buffer** - a control region (up to 64 KiB) the hypervisor
+ writes into. Pages are pinned with ``FOLL_WRITE`` so the hypervisor can
+ store per-operation results directly in the caller's own memory.
+
+Both PFN lists travel in a single VMCI datagram. The hypervisor, while those
+pages are still pinned, writes per-operation information - such as completion
+status, byte counts, or application-level result codes - directly into the
+metadata pages.
+
+Because the metadata pages are the same physical pages that userspace mapped,
+userspace can read the hypervisor's response immediately after the ioctl
+returns by reading its own buffer. No additional system call is required to
+retrieve the result.
+
+Key properties of this feedback path:
+
+- **Per-call**: a fresh metadata region is provided with each ioctl, so
+ results from one call do not interfere with another.
+- **Synchronous**: the result is present in the metadata buffer when the
+ ioctl returns; no polling or waiting is required.
+- **In-place**: the hypervisor writes into the caller's own pinned pages;
+ the result does not pass through any intermediate kernel buffer.
+- **Rich**: up to 64 KiB of arbitrary per-operation result data per call.
+
+
+Atomic Submission Semantics
+============================
+
+Both the data PFN list and the metadata PFN list are placed in a single
+``struct vmw_zc_host_message`` and delivered in one transport ``send()``
+call (currently backed by a single ``vmci_datagram_send()``). The
+hypervisor receives the complete bundle atomically. There is no state held
+between ioctl calls: if the send fails, nothing has been committed on the
+hypervisor side. This makes error recovery straightforward for the caller.
+
+
+Transport Abstraction
+=====================
+
+The driver is split along a small internal interface so that VMCI is a
+swappable backend rather than something the ioctl path depends on directly:
+
+- ``vmw_zerocopy_core.c`` owns the misc device, the ioctl, and user page
+ pinning. It builds a ``struct vmw_zc_host_message`` on the stack and
+ hands it to a transport for delivery. It contains no VMCI-specific code.
+- ``vmw_zerocopy_vmci.c`` implements delivery over VMCI: creating the
+ driver's VMCI datagram handle, maintaining the per-peer datagram buffers
+ described under `Constraints`_, and calling ``vmci_datagram_send()``.
+
+The two are joined by ``struct vmw_zc_transport_ops``, defined in the
+driver-private ``vmw_zerocopy_priv.h``::
+
+ struct vmw_zc_transport_ops {
+ int (*init)(void);
+ void (*exit)(void);
+ int (*send)(const struct vmw_zc_host_message *msg);
+ };
+
+VMCI is currently the only transport, selected at compile time with
+``vmw_zc_transport = &vmw_zc_vmci_transport`` in ``vmw_zerocopy_core.c``.
+The destination is owned entirely by the transport backend, so ``send()`` takes
+no address argument. Adding a second transport means implementing this
+interface in a new source file and pointing ``vmw_zc_transport`` at it - the
+ioctl, pinning, and message-building code do not change.
+
+
+Use Cases
+=========
+
+This driver is suited to workloads where:
+
+- A guest application needs to hand large buffers to a VMware hypervisor
+ service without incurring a kernel-to-kernel copy of the payload.
+- Per-operation completion feedback (status, byte count, result code) is
+ required synchronously with the send, without a separate receive call.
+- The metadata region is bounded (<= 64 KiB) and the response latency must be
+ bounded by the ioctl round-trip rather than by a polling interval.
+
+Examples include guest-side offload of data transformation, checksum, or
+inspection workloads where the hypervisor peer processes each buffer and
+reports the outcome directly into the caller's metadata page.
+
+
+Userspace API
+=============
+
+Device
+------
+
+The driver registers a misc character device::
+
+ /dev/vmw_zc
+
+The device is created with mode ``0644`` so that unprivileged guest
+userspace can open it without additional privilege.
+
+ioctl
+-----
+
+All operations use a single ioctl::
+
+ ioctl(fd, VMW_ZC_IOCTL_MSG, &msg)
+
+where ``msg`` is a ``struct vmw_zc_guest_message`` defined in
+``include/uapi/linux/vmw_zerocopy.h``.
+
+Addressing the peer
+-------------------
+
+Userspace does not address the peer. The destination is a compile-time
+constant owned by the transport backend, and there is no initialisation call
+and no per-message address field.
+
+This is deliberate. The destination is a VMCI resource ID in the hypervisor
+context, and that namespace belongs to the platform: low IDs are the
+hypervisor's own control entry points. A device node that unprivileged
+userspace can open must therefore not let userspace name a destination, or it
+becomes an unfiltered writer onto the hypervisor's control namespace.
+``msg.reserved`` exists only to keep the structure layout fixed and must be
+zero; a non-zero value returns ``-EINVAL``.
+
+Message types
+-------------
+
+``VMW_ZC_MSG_USER_BUFFER``
+ Transfer a buffer, with optional metadata feedback. Fill
+ ``msg.u.data`` as follows:
+
+ ===================== =================================================
+ Field Meaning
+ ===================== =================================================
+ ``buffer`` ``__u64`` userspace address of the data buffer;
+ 0 if no data buffer.
+ ``buffer_length`` Length in bytes; 1-65536 (64 KiB maximum).
+ ``metadata`` ``__u64`` userspace address of the metadata buffer;
+ 0 if no metadata buffer.
+ ``metadata_length`` Length in bytes; 1-65536 (64 KiB maximum).
+ ===================== =================================================
+
+ At least one of ``buffer`` or ``metadata`` must be non-zero.
+ The metadata buffer must remain mapped for the duration of the ioctl; the
+ hypervisor may write into it at any point while the call is in progress.
+
+``VMW_ZC_MSG_RAW``
+ Send a small inline payload (<= 36 bytes) to the peer without pinning any
+ user pages. Fill ``msg.u.raw_buffer.raw_buffer`` with the payload and
+ set ``msg.u.raw_buffer.raw_len`` to the payload length. Useful for
+ lightweight control or signaling messages.
+
+Pointer fields (``buffer``, ``metadata``) are declared as ``__u64`` so that
+``struct vmw_zc_guest_message`` has the same layout on 32-bit and 64-bit
+userspace. The argument pointer itself still needs conversion for 32-bit
+callers, so ``.compat_ioctl`` is set to ``compat_ptr_ioctl``.
+
+Example call sequence
+---------------------
+
+The following pseudo-code illustrates the typical usage pattern::
+
+ /* Step 1: open the device */
+ fd = open("/dev/vmw_zc", O_RDWR);
+
+ /* Step 2: allocate data and metadata buffers */
+ void *data = mmap(NULL, DATA_SIZE, PROT_READ, ...);
+ void *metadata = mmap(NULL, METADATA_SIZE, PROT_READ | PROT_WRITE,...);
+ /* fill data buffer with payload */
+
+ /* Step 3: transfer the buffer and wait for metadata feedback.
+ * There is no destination to supply; the driver owns it.
+ */
+ struct vmw_zc_guest_message xfer_msg = {
+ .message_type = VMW_ZC_MSG_USER_BUFFER,
+ .u.data = {
+ .buffer = (uint64_t)(uintptr_t)data,
+ .buffer_length = DATA_SIZE,
+ .metadata = (uint64_t)(uintptr_t)metadata,
+ .metadata_length = METADATA_SIZE,
+ },
+ };
+ ioctl(fd, VMW_ZC_IOCTL_MSG, &xfer_msg);
+
+ /* Step 4: read the result - no extra syscall needed */
+ struct my_result *res = (struct my_result *)metadata;
+ if (res->status == 0)
+ /* success */;
+
+
+Constraints
+===========
+
+**One destination, fixed by the driver.**
+The peer is not selectable by userspace; see "Addressing the peer" above.
+The send path builds its datagram on the stack per call, so it performs no
+allocation, holds no driver lock, and has no shared mutable state. Concurrent
+senders are serialised only where the VMCI transport itself serialises them.
+
+**Send-only.**
+The driver transmits PFN descriptors to the hypervisor; it does not receive
+datagrams. Hypervisor responses are delivered through the metadata writeback
+path described above, not through a kernel receive queue.
+
+**Buffer size limits.**
+Data and metadata buffers are both capped at 64 KiB (``SZ_64K``). These limits
+bound the worst-case VMCI datagram size and the number of pages that must
+remain pinned during the ioctl.
+
+**Inline raw payload limit.**
+The ``VMW_ZC_MSG_RAW`` message carries at most 36 bytes
+(``VMW_ZC_MAX_RAW_BUFFER_LEN``).
+
+**Incompatible with encrypted guest memory.**
+The driver's correctness depends on the hypervisor being able to read and
+write guest physical pages. Hardware memory encryption (AMD SEV-SNP, Intel
+TDX) intentionally prevents this. The Kconfig dependency
+``depends on !X86_MEM_ENCRYPT`` enforces this constraint at build time and
+prevents the module from being enabled in encrypted-memory guests.
+
+**x86 only.**
+The driver depends on ``CONFIG_X86`` and ``CONFIG_HYPERVISOR_GUEST``. Its
+current transport backend uses ``VMWARE_VMCI`` (see `Transport
+Abstraction`_), which is an x86 VMware platform interface.
--
2.52.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v3 4/4] selftests: misc: Add vmw_zerocopy selftest
2026-09-14 20:22 [PATCH v3 0/4] misc: vmw_zerocopy: VMware zero-copy buffer sharing driver Rishi Chhibber
` (2 preceding siblings ...)
2026-09-14 20:22 ` [PATCH v3 3/4] Documentation: misc: Add vmw_zerocopy driver documentation Rishi Chhibber
@ 2026-09-14 20:22 ` Rishi Chhibber
3 siblings, 0 replies; 6+ messages in thread
From: Rishi Chhibber @ 2026-09-14 20:22 UTC (permalink / raw)
To: gregkh, arnd
Cc: bryan-bt.tan, vishnu.dasa, corbet, skhan, shuah, rdunlap,
bcm-kernel-feedback-list, linux-doc, linux-kselftest,
linux-kernel, ajay.kaher, alexey.makhalov,
vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu,
Rishi Chhibber
Summary of changes:
- Add tools/testing/selftests/drivers/misc/vmw_zerocopy: a
kselftest_harness test covering the ioctl input-validation paths, plus
its Makefile and config fragment
- Add the target to tools/testing/selftests/Makefile
Cover the input-validation paths of the vmw_zerocopy ioctl added earlier in
this series: an unknown ioctl number, a bad message pointer, an unknown
message type, a non-zero reserved word, neither buffer supplied,
zero-length and oversized data and metadata buffers, a wrapping
address+length, a wrapping PAGE_ALIGN() of the end address, an unmapped
buffer, and zero-length and oversized raw payloads.
These are all rejected before any datagram is sent, so they need no
hypervisor peer. Two further tests submit a well-formed message and assert
only that any failure is not a validation error, which holds whether or not
a peer is listening; they also check that repeated sends do not depend on
per-call setup surviving. The whole test therefore runs on any guest where
the module loads, and skips cleanly when /dev/vmw_zc is absent so it does
not fail on kernels built without CONFIG_VMW_ZC.
Signed-off-by: Rishi Chhibber <rishi.chhibber@broadcom.com>
Reviewed-by: Alexey Makhalov <alexey.makhalov@broadcom.com>
Reviewed-by: Vishnu Dasa <vishnu.dasa@broadcom.com>
---
tools/testing/selftests/Makefile | 1 +
.../drivers/misc/vmw_zerocopy/.gitignore | 1 +
.../drivers/misc/vmw_zerocopy/Makefile | 20 ++
.../drivers/misc/vmw_zerocopy/config | 2 +
.../misc/vmw_zerocopy/test_vmw_zerocopy.c | 276 ++++++++++++++++++
5 files changed, 300 insertions(+)
create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore
create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile
create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/config
create mode 100644 tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
index 2d960626750e..325723e92bf3 100644
--- a/tools/testing/selftests/Makefile
+++ b/tools/testing/selftests/Makefile
@@ -20,6 +20,7 @@ TARGETS += devices/error_logs
TARGETS += devices/probe
TARGETS += dmabuf-heaps
TARGETS += drivers/dma-buf
+TARGETS += drivers/misc/vmw_zerocopy
TARGETS += drivers/ntsync
TARGETS += drivers/s390x/uvdevice
TARGETS += drivers/net
diff --git a/tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore b/tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore
new file mode 100644
index 000000000000..68941a8c0553
--- /dev/null
+++ b/tools/testing/selftests/drivers/misc/vmw_zerocopy/.gitignore
@@ -0,0 +1 @@
+test_vmw_zerocopy
diff --git a/tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile b/tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile
new file mode 100644
index 000000000000..195826f72f9c
--- /dev/null
+++ b/tools/testing/selftests/drivers/misc/vmw_zerocopy/Makefile
@@ -0,0 +1,20 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+include ../../../../../build/Build.include
+
+UNAME_M := $(shell uname -m)
+
+ifeq ($(filter $(UNAME_M),x86_64 i686 i386),)
+nothing:
+.PHONY: all clean run_tests install
+.SILENT:
+else
+
+TEST_GEN_PROGS := test_vmw_zerocopy
+
+top_srcdir ?= ../../../../../..
+
+CFLAGS += -Wall -Werror $(KHDR_INCLUDES)
+
+include ../../../lib.mk
+
+endif
diff --git a/tools/testing/selftests/drivers/misc/vmw_zerocopy/config b/tools/testing/selftests/drivers/misc/vmw_zerocopy/config
new file mode 100644
index 000000000000..03e4a430c5ff
--- /dev/null
+++ b/tools/testing/selftests/drivers/misc/vmw_zerocopy/config
@@ -0,0 +1,2 @@
+CONFIG_VMWARE_VMCI=m
+CONFIG_VMW_ZC=m
diff --git a/tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c b/tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c
new file mode 100644
index 000000000000..af8035c4fa0f
--- /dev/null
+++ b/tools/testing/selftests/drivers/misc/vmw_zerocopy/test_vmw_zerocopy.c
@@ -0,0 +1,276 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * selftest for the VMware zero-copy buffer sharing UAPI
+ *
+ * Copyright (c) 2026 Broadcom. All Rights Reserved. The term
+ * "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * Every input check in the driver runs before the message reaches the
+ * transport, so all of the tests below are meaningful without a hypervisor
+ * peer listening: they assert that bad input is rejected with the documented
+ * errno. Tests that would require a live peer only assert "not one of the
+ * input-validation errnos", since the transport result depends on the host.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <linux/vmw_zerocopy.h>
+
+#include "../../../kselftest_harness.h"
+
+#define VMW_ZC_PATH "/dev/" VMW_ZC_DEVICE_NAME
+
+/* Driver-private, but fixed by the UAPI's documented buffer limits. */
+#define VMW_ZC_TEST_MAX_BUFFER 65536
+
+FIXTURE(vmw_zc) {
+ int fd;
+ void *data;
+ void *metadata;
+ size_t page_size;
+};
+
+FIXTURE_SETUP(vmw_zc)
+{
+ self->page_size = (size_t)getpagesize();
+
+ self->fd = open(VMW_ZC_PATH, O_RDWR);
+ if (self->fd < 0)
+ SKIP(return, "cannot open %s: %s", VMW_ZC_PATH,
+ strerror(errno));
+
+ self->data = mmap(NULL, self->page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(MAP_FAILED, self->data);
+
+ self->metadata = mmap(NULL, self->page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(MAP_FAILED, self->metadata);
+
+ memset(self->data, 0xa5, self->page_size);
+}
+
+FIXTURE_TEARDOWN(vmw_zc)
+{
+ if (self->data)
+ munmap(self->data, self->page_size);
+ if (self->metadata)
+ munmap(self->metadata, self->page_size);
+ if (self->fd >= 0)
+ close(self->fd);
+}
+
+/* An unknown ioctl number must be rejected, not silently accepted. */
+TEST_F(vmw_zc, unknown_ioctl)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ EXPECT_EQ(-1, ioctl(self->fd, _IOW(VMW_ZC_IOCTL_MAGIC, 0x7f,
+ struct vmw_zc_guest_message), &msg));
+ EXPECT_EQ(ENOTTY, errno);
+}
+
+TEST_F(vmw_zc, bad_message_pointer)
+{
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, NULL));
+ EXPECT_EQ(EFAULT, errno);
+}
+
+TEST_F(vmw_zc, unknown_message_type)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = 0xffff;
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * The destination is owned by the kernel: @reserved is not an address and a
+ * caller that treats it as one (e.g. a binary built against the older header
+ * where this word was peer_id) must fail loudly rather than be misrouted.
+ */
+TEST_F(vmw_zc, reserved_must_be_zero)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+ msg.reserved = 1;
+ msg.u.data.buffer = (__u64)(uintptr_t)self->data;
+ msg.u.data.buffer_length = 64;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(vmw_zc, neither_buffer_nor_metadata)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(vmw_zc, zero_length_buffer)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+ msg.u.data.buffer = (__u64)(uintptr_t)self->data;
+ msg.u.data.buffer_length = 0;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(vmw_zc, oversized_buffer)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+ msg.u.data.buffer = (__u64)(uintptr_t)self->data;
+ msg.u.data.buffer_length = VMW_ZC_TEST_MAX_BUFFER + 1;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(vmw_zc, oversized_metadata)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+ msg.u.data.metadata = (__u64)(uintptr_t)self->metadata;
+ msg.u.data.metadata_length = VMW_ZC_TEST_MAX_BUFFER + 1;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * A near-top address must be rejected by the range check before any page-count
+ * arithmetic runs, whether it wraps on start + length or only on the
+ * subsequent page alignment.
+ */
+TEST_F(vmw_zc, address_length_overflow)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+ msg.u.data.buffer = ~(__u64)0 - 15;
+ msg.u.data.buffer_length = 4096;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EFAULT, errno);
+}
+
+/*
+ * The page-alignment-only wrap: start + length does not overflow, but rounding
+ * the end up to a page boundary would, which previously yielded nr_pages == 1.
+ */
+TEST_F(vmw_zc, address_page_align_overflow)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+ msg.u.data.buffer = ~(__u64)0 - 0x7ff;
+ msg.u.data.buffer_length = 0x800;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EFAULT, errno);
+}
+
+TEST_F(vmw_zc, unmapped_buffer)
+{
+ struct vmw_zc_guest_message msg = { };
+ void *gap;
+
+ gap = mmap(NULL, self->page_size, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ ASSERT_NE(MAP_FAILED, gap);
+ ASSERT_EQ(0, munmap(gap, self->page_size));
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+ msg.u.data.buffer = (__u64)(uintptr_t)gap;
+ msg.u.data.buffer_length = 64;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EFAULT, errno);
+}
+
+TEST_F(vmw_zc, zero_length_raw)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_RAW;
+ msg.u.raw_buffer.raw_len = 0;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+TEST_F(vmw_zc, oversized_raw)
+{
+ struct vmw_zc_guest_message msg = { };
+
+ msg.message_type = VMW_ZC_MSG_RAW;
+ msg.u.raw_buffer.raw_len = VMW_ZC_MAX_RAW_BUFFER_LEN + 1;
+
+ EXPECT_EQ(-1, ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg));
+ EXPECT_EQ(EINVAL, errno);
+}
+
+/*
+ * A well-formed send passes every input check, so it must not fail with one
+ * of the validation errnos. Whether it ultimately succeeds depends on a peer
+ * being present, which this test cannot assume.
+ */
+TEST_F(vmw_zc, well_formed_send_passes_validation)
+{
+ struct vmw_zc_guest_message msg = { };
+ int rc;
+
+ msg.message_type = VMW_ZC_MSG_USER_BUFFER;
+ msg.u.data.buffer = (__u64)(uintptr_t)self->data;
+ msg.u.data.buffer_length = 64;
+ msg.u.data.metadata = (__u64)(uintptr_t)self->metadata;
+ msg.u.data.metadata_length = 64;
+
+ rc = ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg);
+ if (rc < 0) {
+ EXPECT_NE(EINVAL, errno);
+ EXPECT_NE(EFAULT, errno);
+ EXPECT_NE(ENOTTY, errno);
+ TH_LOG("send returned %s; a hypervisor peer may not be present",
+ strerror(errno));
+ }
+}
+
+/* Repeated sends must not depend on any per-call setup surviving. */
+TEST_F(vmw_zc, repeated_raw_sends)
+{
+ struct vmw_zc_guest_message msg = { };
+ int i;
+
+ msg.message_type = VMW_ZC_MSG_RAW;
+ msg.u.raw_buffer.raw_len = VMW_ZC_MAX_RAW_BUFFER_LEN;
+ memset(msg.u.raw_buffer.raw_buffer, 0x5a,
+ sizeof(msg.u.raw_buffer.raw_buffer));
+
+ for (i = 0; i < 16; i++) {
+ if (ioctl(self->fd, VMW_ZC_IOCTL_MSG, &msg) < 0) {
+ EXPECT_NE(EINVAL, errno);
+ EXPECT_NE(ENOTTY, errno);
+ }
+ }
+}
+
+TEST_HARNESS_MAIN
--
2.52.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v3 2/4] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
2026-09-14 20:22 ` [PATCH v3 2/4] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver Rishi Chhibber
@ 2026-09-15 7:13 ` Greg KH
0 siblings, 0 replies; 6+ messages in thread
From: Greg KH @ 2026-09-15 7:13 UTC (permalink / raw)
To: Rishi Chhibber
Cc: arnd, bryan-bt.tan, vishnu.dasa, corbet, skhan, shuah, rdunlap,
bcm-kernel-feedback-list, linux-doc, linux-kselftest,
linux-kernel, ajay.kaher, alexey.makhalov,
vamsi-krishna.brahmajosyula, yin.ding, tapas.kundu
On Mon, Sep 14, 2026 at 01:22:07PM -0700, Rishi Chhibber wrote:
> Summary of changes:
> - Add drivers/misc/vmw_zerocopy_core.c: the misc character device
> /dev/vmw_zc, its single ioctl, page pinning and PFN collection
> - Add drivers/misc/vmw_zerocopy_vmci.c: the VMCI datagram transport backend
> - Add drivers/misc/vmw_zerocopy_priv.h: the wire-format message structs and
> the struct vmw_zc_transport_ops interface that joins the two
> - Add include/uapi/linux/vmw_zerocopy.h: struct vmw_zc_guest_message and
> VMW_ZC_IOCTL_MSG
> - Register ioctl magic 0xDC in
> Documentation/userspace-api/ioctl/ioctl-number.rst
> - Add the VMW_ZC Kconfig symbol and a MAINTAINERS entry
This all goes below the --- line, right? Otherwise it would show up in
the changelog which doesn't make sense :(
The kernel documentation should explain all of this, how did this pass
internal review?
thanks,
greg k-h
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-15 7:15 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-14 20:22 [PATCH v3 0/4] misc: vmw_zerocopy: VMware zero-copy buffer sharing driver Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 1/4] misc: vmw_vmci: Reserve a datagram resource id for zero-copy buffer sharing Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 2/4] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver Rishi Chhibber
2026-09-15 7:13 ` Greg KH
2026-09-14 20:22 ` [PATCH v3 3/4] Documentation: misc: Add vmw_zerocopy driver documentation Rishi Chhibber
2026-09-14 20:22 ` [PATCH v3 4/4] selftests: misc: Add vmw_zerocopy selftest Rishi Chhibber
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®