[PATCH v2] misc: vmw_zerocopy: Add VMware zero-copy buffer sharing driver
From: Rishi Chhibber
Date: Thu Jun 18 2026 - 14:13:25 EST
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@xxxxxxxxxxxx>
---
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@xxxxxxxxxxxxxxx
S: Supported
F: net/vmw_vsock/vmci_transport*
+VMWARE ZEROCOPY DRIVER
+M: Rishi Chhibber <rishi.chhibber@xxxxxxxxxxxx>
+L: linux-kernel@xxxxxxxxxxxxxxx
+S: Supported
+F: drivers/misc/vmw_zerocopy/
+F: include/uapi/linux/vmw_zerocopy*
+
VOCORE VOCORE2 BOARD
M: Harvey Hunt <harveyhuntnexus@xxxxxxxxx>
L: linux-mips@xxxxxxxxxxxxxxx
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@xxxxxxxxxxxx>");
+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