From: Naman Gulati <namangulati@google.com>
To: qemu-devel@nongnu.org
Cc: "John Levon" <john.levon@nutanix.com>,
"Thanos Makatos" <thanos.makatos@nutanix.com>,
"Cédric Le Goater" <clg@redhat.com>,
"Alex Williamson" <alex@shazbot.org>,
"Pierrick Bouvier" <pierrick.bouvier@oss.qualcomm.com>,
"Matt Evans" <matt@ozlabs.org>,
"Naman Gulati" <namangulati@google.com>
Subject: [PATCH v5 4/4] vfio-user: support multiple FDs for sparse mmap regions
Date: Wed, 14 Oct 2026 21:10:41 +0000 [thread overview]
Message-ID: <20261014211041.1087331-5-namangulati@google.com> (raw)
In-Reply-To: <20261014211041.1087331-1-namangulati@google.com>
Add client support for capability VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS
(capability ID 0x8001) in vfio-user, and advertise support via the
"sparse_mmap_fds" capability in VFIO_USER_VERSION.
Update vfio_user_device_io_get_region_info() to receive multiple file
descriptors passed in SCM_RIGHTS ancillary data for a region, store them
in VFIOUserProxy, and implement setup_sparse_mmaps in
vfio_user_device_io_ops_sock to validate and populate per-area file
descriptors and fd_offset values.
Signed-off-by: Naman Gulati <namangulati@google.com>
---
hw/vfio-user/protocol.h | 35 ++++++++++--
hw/vfio-user/proxy.h | 2 +
hw/vfio-user/device.c | 121 +++++++++++++++++++++++++++++++++++++---
hw/vfio-user/proxy.c | 52 +++++++++++++++--
4 files changed, 193 insertions(+), 17 deletions(-)
diff --git a/hw/vfio-user/protocol.h b/hw/vfio-user/protocol.h
index c478d1353f..b823689254 100644
--- a/hw/vfio-user/protocol.h
+++ b/hw/vfio-user/protocol.h
@@ -15,6 +15,8 @@
* SPDX-License-Identifier: GPL-2.0-or-later
*/
+#define VFIO_USER_MAX_REGIONS 100
+
typedef struct {
uint16_t id;
uint16_t command;
@@ -69,12 +71,13 @@ typedef struct {
#define VFIO_USER_CAP "capabilities"
/* "capabilities" members */
-#define VFIO_USER_CAP_MAX_FDS "max_msg_fds"
-#define VFIO_USER_CAP_MAX_XFER "max_data_xfer_size"
-#define VFIO_USER_CAP_PGSIZES "pgsizes"
-#define VFIO_USER_CAP_MAP_MAX "max_dma_maps"
-#define VFIO_USER_CAP_MIGR "migration"
-#define VFIO_USER_CAP_MULTI "write_multiple"
+#define VFIO_USER_CAP_MAX_FDS "max_msg_fds"
+#define VFIO_USER_CAP_MAX_XFER "max_data_xfer_size"
+#define VFIO_USER_CAP_PGSIZES "pgsizes"
+#define VFIO_USER_CAP_MAP_MAX "max_dma_maps"
+#define VFIO_USER_CAP_MIGR "migration"
+#define VFIO_USER_CAP_MULTI "write_multiple"
+#define VFIO_USER_CAP_SPARSE_MMAP_FDS "sparse_mmap_fds"
/* "migration" members */
#define VFIO_USER_CAP_PGSIZE "pgsize"
@@ -166,6 +169,26 @@ typedef struct {
uint64_t offset;
} VFIOUserRegionInfo;
+/*
+ * VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS
+ */
+#define VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS 0x8001
+
+struct vfio_region_sparse_mmap_fd_area {
+ uint64_t offset;
+ uint64_t fd_offset;
+ uint64_t size;
+ uint32_t fd_index;
+ uint32_t pad;
+};
+
+struct vfio_region_info_cap_sparse_mmap_fds {
+ struct vfio_info_cap_header header;
+ uint32_t nr_areas;
+ uint32_t reserved;
+ struct vfio_region_sparse_mmap_fd_area areas[];
+};
+
/*
* VFIO_USER_DEVICE_GET_IRQ_INFO
* imported from struct vfio_irq_info
diff --git a/hw/vfio-user/proxy.h b/hw/vfio-user/proxy.h
index 7b97460cc5..4b9834a436 100644
--- a/hw/vfio-user/proxy.h
+++ b/hw/vfio-user/proxy.h
@@ -72,6 +72,7 @@ typedef struct VFIOUserProxy {
AioContext *ctx;
QEMUBH *req_bh;
bool async_ops;
+ VFIOUserFDs *region_fds[VFIO_USER_MAX_REGIONS];
/*
* above only changed when BQL is held
@@ -111,6 +112,7 @@ bool vfio_user_validate_version(VFIOUserProxy *proxy, Error **errp);
VFIOUserFDs *vfio_user_getfds(int numfds);
void vfio_user_putfds(VFIOUserMsg *msg);
+void vfio_user_free_fds(VFIOUserFDs *fds);
void vfio_user_disable_posted_writes(VFIOUserProxy *proxy);
diff --git a/hw/vfio-user/device.c b/hw/vfio-user/device.c
index 1a01e748cd..4fbf536a04 100644
--- a/hw/vfio-user/device.c
+++ b/hw/vfio-user/device.c
@@ -14,12 +14,14 @@
#include "hw/vfio-user/device.h"
#include "hw/vfio-user/trace.h"
+#include "hw/vfio/vfio-region.h"
+#include "hw/vfio/vfio-helpers.h"
+#include "hw/vfio/trace.h"
/*
* These are to defend against a malign server trying
* to force us to run out of memory.
*/
-#define VFIO_USER_MAX_REGIONS 100
#define VFIO_USER_MAX_IRQS 50
bool vfio_user_get_device_info(VFIOUserProxy *proxy,
@@ -209,14 +211,17 @@ static int vfio_user_device_io_get_region_info(VFIODevice *vbasedev,
struct vfio_region_info *info,
int *fd)
{
- VFIOUserFDs fds = { 0, 1, fd};
- int ret;
+ int fds[VFIO_USER_MAX_MAX_FDS];
+ VFIOUserFDs user_fds = { 0, VFIO_USER_MAX_MAX_FDS, fds };
+ int i, ret;
+
+ *fd = -1;
if (info->index > vbasedev->num_initial_regions) {
return -EINVAL;
}
- ret = vfio_user_get_region_info(vbasedev->proxy, info, &fds);
+ ret = vfio_user_get_region_info(vbasedev->proxy, info, &user_fds);
if (ret) {
return ret;
}
@@ -225,10 +230,30 @@ static int vfio_user_device_io_get_region_info(VFIODevice *vbasedev,
if ((info->flags & VFIO_REGION_INFO_FLAG_CAPS) &&
(info->cap_offset < sizeof(*info)
|| info->cap_offset + sizeof(struct vfio_info_cap_header) > info->argsz)) {
- return -EINVAL;
+ ret = -EINVAL;
+ } else if (vfio_get_region_info_cap(info,
+ VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS)) {
+ if (user_fds.recv_fds > 0) {
+ VFIOUserFDs *saved = vfio_user_getfds(user_fds.recv_fds);
+
+ saved->recv_fds = user_fds.recv_fds;
+ memcpy(saved->fds, fds, user_fds.recv_fds * sizeof(int));
+ vfio_user_free_fds(vbasedev->proxy->region_fds[info->index]);
+ vbasedev->proxy->region_fds[info->index] = saved;
+ return 0;
+ }
+ ret = -EINVAL;
+ } else if (user_fds.recv_fds > 0) {
+ *fd = fds[0];
+ fds[0] = -1;
}
- return 0;
+ for (i = 0; i < user_fds.recv_fds; i++) {
+ if (fds[i] >= 0) {
+ close(fds[i]);
+ }
+ }
+ return ret;
}
static int vfio_user_device_io_get_irq_info(VFIODevice *vbasedev,
@@ -518,12 +543,94 @@ static int vfio_user_device_io_region_write(VFIODevice *vbasedev, uint8_t index,
/*
* Socket-based io_ops
*/
+static int vfio_user_device_io_setup_sparse_mmaps(VFIORegion *region,
+ struct vfio_region_info *info,
+ Error **errp)
+{
+ struct vfio_info_cap_header *hdr;
+ struct vfio_region_info_cap_sparse_mmap_fds *sparse_fds;
+ VFIOUserFDs *user_fds;
+ size_t cap_offset;
+ int i;
+
+ hdr = vfio_get_region_info_cap(info, VFIO_REGION_INFO_CAP_SPARSE_MMAP_FDS);
+ if (!hdr) {
+ return vfio_default_setup_sparse_mmaps(region, info, errp);
+ }
+
+ if (vfio_get_region_info_cap(info, VFIO_REGION_INFO_CAP_SPARSE_MMAP)) {
+ error_setg(errp, "%s: region %d cannot specify both "
+ "SPARSE_MMAP and SPARSE_MMAP_FDS", __func__, region->nr);
+ return -EINVAL;
+ }
+
+ sparse_fds = container_of(hdr, struct vfio_region_info_cap_sparse_mmap_fds,
+ header);
+
+ cap_offset = (uintptr_t)hdr - (uintptr_t)info;
+ if (cap_offset > info->argsz ||
+ info->argsz - cap_offset < sizeof(*sparse_fds) ||
+ sparse_fds->nr_areas >
+ (info->argsz - cap_offset - sizeof(*sparse_fds)) /
+ sizeof(sparse_fds->areas[0])) {
+ error_setg(errp, "%s: invalid nr_areas %u in region %d",
+ __func__, sparse_fds->nr_areas, region->nr);
+ return -EINVAL;
+ }
+
+ trace_vfio_region_sparse_mmap_header(region->vbasedev->name,
+ region->nr, sparse_fds->nr_areas);
+
+ user_fds = region->vbasedev->proxy->region_fds[region->nr];
+ if (!user_fds || user_fds->recv_fds > sparse_fds->nr_areas) {
+ error_setg(errp, "%s: invalid fd count %d for %u sparse mmap areas "
+ "in region %d", __func__,
+ user_fds ? user_fds->recv_fds : 0,
+ sparse_fds->nr_areas, region->nr);
+ return -EINVAL;
+ }
+
+ region->mmaps = g_try_new0(VFIOMmap, sparse_fds->nr_areas);
+ if (!region->mmaps) {
+ error_setg(errp, "%s: failed to allocate %u mmaps for region %d",
+ __func__, sparse_fds->nr_areas, region->nr);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < sparse_fds->nr_areas; i++) {
+ struct vfio_region_sparse_mmap_fd_area *area = &sparse_fds->areas[i];
+
+ if (area->size == 0 || area->offset > region->size ||
+ area->size > region->size - area->offset ||
+ area->fd_index >= user_fds->recv_fds) {
+ error_setg(errp, "%s: invalid sparse mmap area %d in region %d "
+ "(offset=0x%"PRIx64" size=0x%"PRIx64
+ " fd_offset=0x%"PRIx64" fd_index=%u)",
+ __func__, i, region->nr, area->offset, area->size,
+ area->fd_offset, area->fd_index);
+ g_free(region->mmaps);
+ region->mmaps = NULL;
+ return -EINVAL;
+ }
+
+ trace_vfio_region_sparse_mmap_entry(i, area->offset,
+ area->offset + area->size - 1);
+ region->mmaps[i].offset = area->offset;
+ region->mmaps[i].fd_offset = area->fd_offset;
+ region->mmaps[i].size = area->size;
+ region->mmaps[i].fd = user_fds->fds[area->fd_index];
+ }
+
+ region->nr_mmaps = sparse_fds->nr_areas;
+ return 0;
+}
+
VFIODeviceIOOps vfio_user_device_io_ops_sock = {
.device_feature = vfio_user_device_io_device_feature,
.get_region_info = vfio_user_device_io_get_region_info,
+ .setup_sparse_mmaps = vfio_user_device_io_setup_sparse_mmaps,
.get_irq_info = vfio_user_device_io_get_irq_info,
.set_irqs = vfio_user_device_io_set_irqs,
.region_read = vfio_user_device_io_region_read,
.region_write = vfio_user_device_io_region_write,
-
};
diff --git a/hw/vfio-user/proxy.c b/hw/vfio-user/proxy.c
index 197aee07bf..ba29085321 100644
--- a/hw/vfio-user/proxy.c
+++ b/hw/vfio-user/proxy.c
@@ -363,6 +363,13 @@ static int vfio_user_recv_one(VFIOUserProxy *proxy, Error **errp)
}
memcpy(msg->fds->fds, fdp, numfds * sizeof(int));
+ } else if (msg->fds != NULL) {
+ /*
+ * recv_fds holds the capacity of msg->fds->fds on entry; reset it
+ * to 0 when the reply carries no FDs so the caller knows none were
+ * received.
+ */
+ msg->fds->recv_fds = 0;
}
/*
@@ -740,6 +747,11 @@ bool vfio_user_send_wait(VFIOUserProxy *proxy, VFIOUserHdr *hdr,
qemu_mutex_unlock(&proxy->lock);
+ if (!ok && fds != NULL) {
+ /* No reply was received; report 0 received FDs to the caller. */
+ fds->recv_fds = 0;
+ }
+
return ok;
}
@@ -867,17 +879,30 @@ void vfio_user_send_error(VFIOUserProxy *proxy, VFIOUserHdr *hdr, int error)
}
/*
- * Close FDs erroneously received in an incoming request.
+ * Close and free received FDs.
*/
-void vfio_user_putfds(VFIOUserMsg *msg)
+void vfio_user_free_fds(VFIOUserFDs *fds)
{
- VFIOUserFDs *fds = msg->fds;
int i;
+ if (!fds) {
+ return;
+ }
+
for (i = 0; i < fds->recv_fds; i++) {
- close(fds->fds[i]);
+ if (fds->fds[i] >= 0) {
+ close(fds->fds[i]);
+ }
}
g_free(fds);
+}
+
+/*
+ * Close FDs erroneously received in an incoming request.
+ */
+void vfio_user_putfds(VFIOUserMsg *msg)
+{
+ vfio_user_free_fds(msg->fds);
msg->fds = NULL;
}
@@ -967,6 +992,7 @@ void vfio_user_set_handler(VFIODevice *vbasedev,
void vfio_user_disconnect(VFIOUserProxy *proxy)
{
VFIOUserMsg *r1, *r2;
+ int i;
qemu_mutex_lock(&proxy->lock);
@@ -1027,6 +1053,10 @@ void vfio_user_disconnect(VFIOUserProxy *proxy)
vfio_user_iothread = NULL;
}
+ for (i = 0; i < VFIO_USER_MAX_REGIONS; i++) {
+ vfio_user_free_fds(proxy->region_fds[i]);
+ }
+
g_free(proxy->sockname);
g_free(proxy);
}
@@ -1214,6 +1244,18 @@ static bool check_multi(VFIOUserProxy *proxy, QObject *qobj, Error **errp)
return true;
}
+static bool check_sparse_mmap_fds(VFIOUserProxy *proxy, QObject *qobj,
+ Error **errp)
+{
+ QBool *qb = qobject_to(QBool, qobj);
+
+ if (qb == NULL) {
+ error_setg(errp, "malformed %s", VFIO_USER_CAP_SPARSE_MMAP_FDS);
+ return false;
+ }
+ return true;
+}
+
static struct cap_entry caps_cap[] = {
{ VFIO_USER_CAP_MAX_FDS, check_max_fds },
{ VFIO_USER_CAP_MAX_XFER, check_max_xfer },
@@ -1221,6 +1263,7 @@ static struct cap_entry caps_cap[] = {
{ VFIO_USER_CAP_MAP_MAX, check_max_dma },
{ VFIO_USER_CAP_MIGR, check_migr },
{ VFIO_USER_CAP_MULTI, check_multi },
+ { VFIO_USER_CAP_SPARSE_MMAP_FDS, check_sparse_mmap_fds },
{ NULL }
};
@@ -1280,6 +1323,7 @@ static GString *caps_json(void)
qdict_put_int(capdict, VFIO_USER_CAP_PGSIZES, VFIO_USER_DEF_PGSIZE);
qdict_put_int(capdict, VFIO_USER_CAP_MAP_MAX, VFIO_USER_DEF_MAP_MAX);
qdict_put_bool(capdict, VFIO_USER_CAP_MULTI, true);
+ qdict_put_bool(capdict, VFIO_USER_CAP_SPARSE_MMAP_FDS, true);
qdict_put_obj(dict, VFIO_USER_CAP, QOBJECT(capdict));
--
2.56.0.385.gd3acb90ef8-goog
next prev parent reply other threads:[~2026-10-14 21:11 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-14 21:10 [PATCH v5 0/4] vfio-user: support multiple FDs backing region mmaps Naman Gulati
2026-10-14 21:10 ` [PATCH v5 1/4] vfio-user: add multi-fd region mmap capability Naman Gulati
2026-10-20 15:37 ` John Levon
2026-10-14 21:10 ` [PATCH v5 2/4] vfio: add 'fd' and 'fd_offset' to VFIOMmap Naman Gulati
2026-10-15 7:39 ` Cédric Le Goater
2026-10-20 15:37 ` John Levon
2026-10-14 21:10 ` [PATCH v5 3/4] vfio: add setup_sparse_mmaps hook to VFIODeviceIOOps Naman Gulati
2026-10-15 7:39 ` Cédric Le Goater
2026-10-15 7:41 ` Cédric Le Goater
2026-10-20 15:38 ` John Levon
2026-10-14 21:10 ` Naman Gulati [this message]
2026-10-15 7:45 ` [PATCH v5 4/4] vfio-user: support multiple FDs for sparse mmap regions Cédric Le Goater
2026-10-20 15:35 ` John Levon
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261014211041.1087331-5-namangulati@google.com \
--to=namangulati@google.com \
--cc=alex@shazbot.org \
--cc=clg@redhat.com \
--cc=john.levon@nutanix.com \
--cc=matt@ozlabs.org \
--cc=pierrick.bouvier@oss.qualcomm.com \
--cc=qemu-devel@nongnu.org \
--cc=thanos.makatos@nutanix.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.