Generalize QEMU's VFIO core layer to support mapping sparse memory subregions from distinct file descriptors within a single device region.
Replace list of region_fds in VFIODevice with a collection of struct VFIORegionFDs that internally holds a collection of FDs to mmap per region. Add setup_sparse_mmaps hook to VFIODeviceIOOps to allow transport backends (such as vfio-user) to handle custom sparse mmap capabilities, falling back to default kernel VFIO sparse parsing. Store per-subregion fd_offset in struct VFIOMmap to properly address the mmap call for a subregion. Signed-off-by: Naman Gulati <[email protected]> --- hw/vfio-user/device.c | 15 +++++++++-- hw/vfio/device.c | 49 +++++++++++++++++++++-------------- hw/vfio/region.c | 41 +++++++++++++++++++++++------ hw/vfio/vfio-region.h | 8 ++++++ include/hw/vfio/vfio-device.h | 32 ++++++++++++++++++++--- 5 files changed, 112 insertions(+), 33 deletions(-) diff --git a/hw/vfio-user/device.c b/hw/vfio-user/device.c index b8d2b7c1a8..03a7026778 100644 --- a/hw/vfio-user/device.c +++ b/hw/vfio-user/device.c @@ -170,11 +170,15 @@ static int vfio_user_get_region_info(VFIOUserProxy *proxy, static int vfio_user_device_io_get_region_info(VFIODevice *vbasedev, struct vfio_region_info *info, - int *fd) + struct VFIORegionFDs *region_fds) { - VFIOUserFDs fds = { 0, 1, fd}; + int fd = -1; + VFIOUserFDs fds = { 0, 1, &fd }; int ret; + region_fds->fds = NULL; + region_fds->nr_fds = 0; + if (info->index > vbasedev->num_initial_regions) { return -EINVAL; } @@ -190,6 +194,13 @@ static int vfio_user_device_io_get_region_info(VFIODevice *vbasedev, return -EINVAL; } + if (fds.recv_fds > 0) { + region_fds->fds = g_new0(int, 1); + region_fds->nr_fds = 1; + region_fds->fds[0] = fd; + } + + return 0; } diff --git a/hw/vfio/device.c b/hw/vfio/device.c index 1a7f8088aa..5dc459dbf7 100644 --- a/hw/vfio/device.c +++ b/hw/vfio/device.c @@ -201,8 +201,8 @@ int vfio_device_get_irq_info(VFIODevice *vbasedev, int index, int vfio_device_get_region_info(VFIODevice *vbasedev, int index, struct vfio_region_info **info) { + struct VFIORegionFDs region_fds = {.fds = NULL, .nr_fds = 0}; size_t argsz = sizeof(struct vfio_region_info); - int fd = -1; int ret; /* @@ -226,7 +226,7 @@ int vfio_device_get_region_info(VFIODevice *vbasedev, int index, retry: (*info)->argsz = argsz; - ret = vbasedev->io_ops->get_region_info(vbasedev, *info, &fd); + ret = vbasedev->io_ops->get_region_info(vbasedev, *info, ®ion_fds); if (ret != 0) { g_free(*info); *info = NULL; @@ -237,10 +237,14 @@ retry: argsz = (*info)->argsz; *info = g_realloc(*info, argsz); - if (fd != -1) { - close(fd); - fd = -1; + if (region_fds.nr_fds) { + for (int j = 0; j < region_fds.nr_fds; j++) { + close(region_fds.fds[j]); + } + g_free(region_fds.fds); + region_fds.nr_fds = 0; } + region_fds.fds = NULL; goto retry; } @@ -249,18 +253,24 @@ retry: /* fill cache */ vbasedev->reginfo[index] = *info; if (vbasedev->region_fds != NULL) { - vbasedev->region_fds[index] = fd; + vbasedev->region_fds[index] = region_fds; } } return 0; } -int vfio_device_get_region_fd(VFIODevice *vbasedev, int index) +int vfio_device_get_region_fd(VFIODevice *vbasedev, int index, + uint32_t fd_index) { - return vbasedev->region_fds ? - vbasedev->region_fds[index] : - vbasedev->fd; + if (!vbasedev->region_fds) { + return vbasedev->fd; + } + if (index < 0 || index >= vbasedev->num_initial_regions || + fd_index >= vbasedev->region_fds[index].nr_fds) { + return -1; + } + return vbasedev->region_fds[index].fds[fd_index]; } int vfio_device_get_region_info_type(VFIODevice *vbasedev, uint32_t type, @@ -480,7 +490,6 @@ void vfio_device_detach(VFIODevice *vbasedev) void vfio_device_prepare(VFIODevice *vbasedev, VFIOContainer *bcontainer, struct vfio_device_info *info) { - int i; vbasedev->num_irqs = info->num_irqs; vbasedev->num_initial_regions = info->num_regions; @@ -495,10 +504,8 @@ void vfio_device_prepare(VFIODevice *vbasedev, VFIOContainer *bcontainer, vbasedev->reginfo = g_new0(struct vfio_region_info *, vbasedev->num_initial_regions); if (vbasedev->use_region_fds) { - vbasedev->region_fds = g_new0(int, vbasedev->num_initial_regions); - for (i = 0; i < vbasedev->num_initial_regions; i++) { - vbasedev->region_fds[i] = -1; - } + vbasedev->region_fds = g_new0(VFIORegionFDs, + vbasedev->num_initial_regions); } } @@ -508,8 +515,11 @@ void vfio_device_unprepare(VFIODevice *vbasedev) for (i = 0; i < vbasedev->num_initial_regions; i++) { g_free(vbasedev->reginfo[i]); - if (vbasedev->region_fds != NULL && vbasedev->region_fds[i] != -1) { - close(vbasedev->region_fds[i]); + if (vbasedev->region_fds != NULL) { + for (int j = 0; j < vbasedev->region_fds[i].nr_fds; j++) { + close(vbasedev->region_fds[i].fds[j]); + } + g_free(vbasedev->region_fds[i].fds); } } @@ -593,11 +603,12 @@ static int vfio_device_io_device_feature(VFIODevice *vbasedev, static int vfio_device_io_get_region_info(VFIODevice *vbasedev, struct vfio_region_info *info, - int *fd) + struct VFIORegionFDs *fds) { int ret; - *fd = -1; + fds->nr_fds = 0; + fds->fds = NULL; ret = ioctl(vbasedev->fd, VFIO_DEVICE_GET_REGION_INFO, info); diff --git a/hw/vfio/region.c b/hw/vfio/region.c index dbde339180..3a555e574c 100644 --- a/hw/vfio/region.c +++ b/hw/vfio/region.c @@ -162,9 +162,9 @@ static int vfio_mmap_compare_offset(const void *a, const void *b) return 0; } -static int vfio_setup_region_sparse_mmaps(VFIORegion *region, - struct vfio_region_info *info, - Error **errp) +int vfio_default_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 *sparse; @@ -188,6 +188,8 @@ static int vfio_setup_region_sparse_mmaps(VFIORegion *region, sparse->areas[i].offset + sparse->areas[i].size - 1); region->mmaps[j].offset = sparse->areas[i].offset; + region->mmaps[j].fd_offset = region->fd_offset + + sparse->areas[i].offset; region->mmaps[j].size = sparse->areas[i].size; j++; } @@ -195,6 +197,25 @@ static int vfio_setup_region_sparse_mmaps(VFIORegion *region, region->nr_mmaps = j; region->mmaps = g_realloc(region->mmaps, j * sizeof(VFIOMmap)); + return 0; +} + +static int vfio_setup_region_sparse_mmaps(VFIORegion *region, + struct vfio_region_info *info, + Error **errp) +{ + int ret, i; + + if (region->vbasedev->io_ops && + region->vbasedev->io_ops->setup_sparse_mmaps) { + ret = region->vbasedev->io_ops->setup_sparse_mmaps(region, info, errp); + } else { + ret = vfio_default_setup_sparse_mmaps(region, info, errp); + } + + if (ret) { + return ret; + } /* * Sort sparse mmaps by offset to ensure proper handling of gaps @@ -261,6 +282,7 @@ int vfio_region_setup(Object *obj, VFIODevice *vbasedev, VFIORegion *region, region->nr_mmaps = 1; region->mmaps = g_new0(VFIOMmap, region->nr_mmaps); region->mmaps[0].offset = 0; + region->mmaps[0].fd_offset = region->fd_offset; region->mmaps[0].size = region->size; } else if (ret) { return ret; @@ -355,7 +377,6 @@ int vfio_region_mmap(VFIORegion *region) off_t map_offset = 0; size_t align; char *name; - int fd; if (!region->mem || !region->nr_mmaps) { return 0; @@ -392,8 +413,6 @@ int vfio_region_mmap(VFIORegion *region) return ret; } - fd = vfio_device_get_region_fd(region->vbasedev, region->nr); - map_align = (void *)ROUND_UP((uintptr_t)map_base, (uintptr_t)align); munmap(map_base, map_align - map_base); munmap(map_align + region->size, @@ -405,12 +424,18 @@ int vfio_region_mmap(VFIORegion *region) * offsets being in ascending order. */ for (i = 0; i < region->nr_mmaps; i++) { + int fd = vfio_device_get_region_fd(region->vbasedev, region->nr, + region->mmaps[i].fd_index); + if (fd < 0) { + ret = -EINVAL; + goto no_mmap; + } + munmap(map_align + map_offset, region->mmaps[i].offset - map_offset); region->mmaps[i].mmap = mmap(map_align + region->mmaps[i].offset, region->mmaps[i].size, prot, MAP_SHARED | MAP_FIXED, fd, - region->fd_offset + - region->mmaps[i].offset); + region->mmaps[i].fd_offset); if (region->mmaps[i].mmap == MAP_FAILED) { ret = -errno; /* diff --git a/hw/vfio/vfio-region.h b/hw/vfio/vfio-region.h index 58b236f113..0bb082e9a6 100644 --- a/hw/vfio/vfio-region.h +++ b/hw/vfio/vfio-region.h @@ -10,12 +10,17 @@ #define HW_VFIO_REGION_H #include "system/memory.h" +#ifdef CONFIG_LINUX +#include <linux/vfio.h> +#endif typedef struct VFIOMmap { MemoryRegion mem; void *mmap; off_t offset; size_t size; + uint32_t fd_index; + uint64_t fd_offset; } VFIOMmap; typedef struct VFIODevice VFIODevice; @@ -43,5 +48,8 @@ int vfio_region_mmap(VFIORegion *region); void vfio_region_mmaps_set_enabled(VFIORegion *region, bool enabled); void vfio_region_exit(VFIORegion *region); void vfio_region_finalize(VFIORegion *region); +int vfio_default_setup_sparse_mmaps(VFIORegion *region, + struct vfio_region_info *info, + Error **errp); #endif /* HW_VFIO_REGION_H */ diff --git a/include/hw/vfio/vfio-device.h b/include/hw/vfio/vfio-device.h index 8472420d3f..c63a867188 100644 --- a/include/hw/vfio/vfio-device.h +++ b/include/hw/vfio/vfio-device.h @@ -43,12 +43,18 @@ enum { typedef struct VFIODeviceOps VFIODeviceOps; typedef struct VFIODeviceIOOps VFIODeviceIOOps; +typedef struct VFIORegion VFIORegion; typedef struct VFIOMigration VFIOMigration; typedef struct IOMMUFDBackend IOMMUFDBackend; typedef struct VFIOIOASHwpt VFIOIOASHwpt; typedef struct VFIOUserProxy VFIOUserProxy; +typedef struct VFIORegionFDs { + int *fds; + int nr_fds; +} VFIORegionFDs; + typedef struct VFIODevice { QLIST_ENTRY(VFIODevice) next; QLIST_ENTRY(VFIODevice) container_next; @@ -89,7 +95,7 @@ typedef struct VFIODevice { VFIOIOASHwpt *hwpt; QLIST_ENTRY(VFIODevice) hwpt_next; struct vfio_region_info **reginfo; - int *region_fds; + struct VFIORegionFDs *region_fds; VFIODeviceCPR cpr; VFIOUserProxy *proxy; } VFIODevice; @@ -211,12 +217,29 @@ struct VFIODeviceIOOps { * @vdev: #VFIODevice to use * @info: set @info->index to the region index to look up; the rest of the * struct will be filled in on success - * @fd: pointer to the fd for the region; will be -1 if not found + * @fds: pointer to the collection of fds for the region, will have nr_fds = + * 0 if not found * * Returns 0 on success or -errno. */ int (*get_region_info)(VFIODevice *vdev, - struct vfio_region_info *info, int *fd); + struct vfio_region_info *info, + struct VFIORegionFDs *fds); + + /** + * @setup_sparse_mmaps + * + * Parse sparse mmap capabilities for a region and initialize region->mmaps. + * + * @region: #VFIORegion to set up + * @info: region info struct containing capabilities + * @errp: pointer to Error*, to store an error if it happens + * + * Returns 0 on success, -ENODEV if not sparse, or negative error code. + */ + int (*setup_sparse_mmaps)(VFIORegion *region, + struct vfio_region_info *info, + Error **errp); /** * @get_irq_info @@ -317,7 +340,8 @@ int vfio_device_get_region_info_type(VFIODevice *vbasedev, uint32_t type, * * Returns the fd. */ -int vfio_device_get_region_fd(VFIODevice *vbasedev, int index); +int vfio_device_get_region_fd(VFIODevice *vbasedev, int index, + uint32_t fd_index); bool vfio_device_has_region_cap(VFIODevice *vbasedev, int region, uint16_t cap_type); -- 2.55.0.860.g4b6b3295ed-goog
