:p
atchew
Login
This series lets QEMU, when running as a Xen device model, drive vhost-user backends that map guest memory through the Xen foreign mapping interface, implementing the front-end side of VHOST_USER_PROTOCOL_F_XEN_MMAP. The protocol extension itself is already documented in docs/interop/vhost-user.rst (feature bit 17, extended memory region description) and implemented by rust-vmm's vhost / vm-memory crates and the vhost-device backends built on them. The problem this solves: under Xen the guest's RAM is not allocated by QEMU and is not backed by a file descriptor. memory_region_get_fd() returns -1, so vhost_section() filters out every RAM section, the vhost memory listener registers no regions, and starting any vhost-user device fails with "Failed initializing vhost-user memory map". With F_XEN_MMAP the backend maps guest memory itself. The protocol requires one file descriptor per region in SET_MEM_TABLE. Guest RAM under Xen has no backing fd, so the front-end opens /dev/xen/privcmd per region purely to satisfy that requirement; the backend derives the mapping from guest_phys_addr + domid and never reads the fd. Each fd is closed once the message has been sent. This patchset was rebased onto the new vhost_phys_vring_addr infrastructure and extends vhost_user_gpa_addresses() so that negotiated F_XEN_MMAP (bit 17), not F_GPA_ADDRESSES (bit 21, which the backend doesn't advertise), drives GPA addressing for both rings and userspace_addr. The two patches: 1/2 accept the Xen RAM section in vhost_section() 2/2 negotiate F_XEN_MMAP and build SET_MEM_TABLE from the extended region layout. Testing: Tested on Xen/ARM64 with a DomU using virtio-mmio transports created by the xenpvh machine, running vhost-device-sound (rust-vmm, built with the "xen" feature) as the backend in dom0. The device negotiates, receives the memory table and ring addresses, and the guest's virtio-snd driver probes and operates. Non-Xen / x86 KVM: vhost-user-snd backed by vhost-device-sound (null backend) on a q35/KVM guest. The device negotiates, the guest virtio-snd driver probes and runs the control and PCM paths, and the SET_MEM_TABLE and vring-address traffic is identical to a build without this series confirming the non-Xen path is unchanged. The control message exchange between the frontend and backend was tracked using sockdump as was described in: Making VirtIO sing - implementing virtio-sound in rust-vmm project |-> at FOSDEM 2024 Setup: The main part of the xl config this enables: virtio = [ 'backend=0,type=virtio,device,transport=mmio,grant_usage=false' ] device_model_args = [ ... '-chardev', 'socket,id=snd_chardev,path=/tmp/snd.sock', '-device', 'vhost-user-snd,chardev=snd_chardev,id=snd,iommu_platform=true', ... ] Xen 4.22-unstable was used with: -enable-IOREQ_SERVER -enable-EXPERT An extra patch was added to xen-tools. Namely, xen tools will request a pv device drive type for ARM64 but qemu expects pvh. This is a known issue: github.com/Xilinx/xen/commit/5f669949c9ffdb1947cb47038956b5fb8eeb072a Qemu master was used configured with the following flags: --target-list=aarch64-softmmu \ --cross-prefix=aarch64-linux-gnu- \ --enable-xen \ --enable-vhost-user \ --extra-cflags="-I$XEN-TOOLS/usr/local/include" \ --extra-ldflags="-L$XEN-TOOLS/usr/local/lib -Wl, -rpath-link,$XEN-TOOLS/usr/local/lib" \ Likewise for x86: --target-list=aarch64-softmmu \ --enable-slirp \ --enable-xen \ --enable-vhost-user \ --enable-virtfs \ Linux version 6.11.7 was used with extra configuration flags: * For enabling Xen Dom0/DomU support * For enabling virtio (mmio, snd, etc.) * For enabling sockdump features (BPF, IKHEADERS, KPROBE, etc.) * Extra debug flags (DEBUG_FS, etc.) vhost-device commit-id: c3bb658ef4fe20a2f264dbbbbc6fa19f1c08c0c5 Was used built with: --features alsa-backend,xen Importantly in vhost-device-scmi/src/vhu_scmi.rs: // QUEUE_SIZE must be apparently at least 1024 for MMIO. // There is probably a maximum size per descriptor defined in the kernel. const QUEUE_SIZE: usize = 1024; A similar change was made to make mmio work in vhost-user-sound device, bumping QUEUE_SIZE to 1024. Without this frontend and backend will fail to negotiate queue size. Scope and known limitations: * Foreign mappings only. Grant mappings are not supported: vhost's section tracking derives a host pointer for each region, which is invalid for the grant pseudo-region, and per-access grant mapping needs a different region description (GRANT | no-advance-map). Patch 1 rejects the xen.grants region explicitly. Setting grant_usage=true does not change the qemu<->backend vhost-user exchange. * VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS is suppressed under Xen: the ADD/REM_MEM_REG path has not been converted to the extended region format, and Xen guests currently expose a single RAM region, so SET_MEM_TABLE is sufficient. Multiple RAM regions are not yet exercised. Postcopy is refused. * Spec vs reference implementation: docs/interop/vhost-user.rst describes the "can not be mapped in advance" xen-mmap flag as Bit 8 (value 0x100), whereas rust-vmm's vm-memory uses 0x8 (bit 3, MmapXenFlags::NO_ADVANCE_MAP). This series uses neither, but the discrepancy probably wants resolving in the spec. Viresh, which is intended -- bit position 8 or value 0x8? * userspace_addr is carried unchanged in the region descriptor; under Xen it does not correspond to a mapping and backends do not interpret it. An alternative would be to define it (e.g. mirror guest_phys_addr). Open questions: - userspace_addr semantics under Xen: leave it unchanged, or define it? - Multi-region support: convert ADD/REM_MEM_REG to the extended layout rather than suppressing CONFIGURE_MEM_SLOTS? - Grant-mapping support: worth pursuing, and what region-description shape do backends expect? - Updating vhost-device-sound to reflect the mmio support. References: - vhost-user spec, F_XEN_MMAP / extended memory region / xen mmap flags: docs/interop/vhost-user.rst - rust-vmm vm-memory MmapXenFlags (FOREIGN=0x1, GRANT=0x2, NO_ADVANCE_MAP=0x8): src/mmap/xen.rs - Making VirtIO sing - implementing virtio-sound in rust-vmm project |-> at FOSDEM 2024 Signed-off-by: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> Signed-off-by: Nikola Jelic <Nikola.Jelic@rt-rk.com> --- Changes in v2: - Rebased onto current master - Cover letter: removed a rust-vmm hunk which made the git am on Patchview fail. The reference is now mentioned in a sentance. - Link to v1: https://lore.kernel.org/qemu-devel/20260618-vhost-xen-foreign-mapping-v1-0-7f60a6241971@rt-rk.com --- Dusan Stojkovic (2): vhost: accept Xen guest RAM sections for vhost-user vhost-user: implement VHOST_USER_PROTOCOL_F_XEN_MMAP hw/virtio/trace-events | 2 + hw/virtio/vhost-user.c | 120 +++++++++++++++++++++++++++++++++++++++-- hw/virtio/vhost.c | 18 +++++++ hw/xen/xen_stubs.c | 5 ++ include/hw/virtio/vhost-user.h | 2 +- 5 files changed, 143 insertions(+), 4 deletions(-) --- base-commit: 20553466cc47af6a8c95f665b601fce3c852e503 change-id: 20260618-vhost-xen-foreign-mapping-d023c85bb706 Best regards, -- Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com>
From: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> When QEMU runs as a Xen device model, the guest's RAM is not allocated by QEMU and is not backed by a file descriptor that could be shared with a vhost-user backend: accesses from QEMU go through the Xen mapcache and memory_region_get_fd() returns -1. vhost_section() therefore filters out every RAM section, the vhost memory listener registers no regions, and starting any vhost-user device fails with "Failed initializing vhost-user memory map". With VHOST_USER_PROTOCOL_F_XEN_MMAP the backend does not need an fd or a process-local mapping it maps guest memory itself through the Xen foreign mapping interface, using the guest physical address and domain id. Accept the Xen RAM region in vhost_section() so that it reaches the backend's memory table. The Xen grant region (xen.grants) must never be accepted: grant references can only be mapped individually on demand via address_space_map(), and deriving a host pointer for the whole region, as vhost_region_add_section() does, aborts in the Xen mapcache. Note that xen_mr_is_memory() returns true for both the RAM and the grants region, so the grants region is excluded explicitly. Because of the necessity to exlude xen.grants, the missing stub for xen_mr_is_grants is added so that it can be called from common code. Signed-off-by: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> Signed-off-by: Nikola Jelic <Nikola.Jelic@rt-rk.com> --- hw/virtio/vhost.c | 18 ++++++++++++++++++ hw/xen/xen_stubs.c | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/vhost.c +++ b/hw/virtio/vhost.c @@ -XXX,XX +XXX,XX @@ #include "system/dma.h" #include "system/memory.h" #include "system/ramblock.h" +#include "system/xen.h" #include "trace.h" /* enabled until disconnected backend stabilizes */ @@ -XXX,XX +XXX,XX @@ static bool vhost_section(struct vhost_dev *dev, MemoryRegionSection *section) return false; } + /* + * Under Xen, the guest's RAM is not backed by an fd that + * be passed to a vhost-user backend. The backend instead + * guest memory through the Xen foreign mapping interface, + * by guest physical address and domain id (see + * VHOST_USER_PROTOCOL_F_XEN_MMAP), so accept the Xen RAM + * region even though it has no fd. + */ + if (xen_enabled()) { + if (xen_mr_is_memory(mr) && !xen_mr_is_grants(mr)) { + trace_vhost_section(mr->name); + return true; + } + trace_vhost_reject_section(mr->name, 4); + return false; + } + /* * Some backends (like vhost-user) can only handle memory regions * that have an fd (can be mapped into a different process). Filter diff --git a/hw/xen/xen_stubs.c b/hw/xen/xen_stubs.c index XXXXXXX..XXXXXXX 100644 --- a/hw/xen/xen_stubs.c +++ b/hw/xen/xen_stubs.c @@ -XXX,XX +XXX,XX @@ bool xen_mr_is_memory(const MemoryRegion *mr) g_assert_not_reached(); } +bool xen_mr_is_grants(const MemoryRegion *mr) +{ + g_assert_not_reached(); +} + bool xen_map_cache_enabled(void) { return false; -- 2.43.0
From: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> The vhost-user specification reserves protocol feature bit 17 and documents an extended memory region description for backends that map guest memory through Xen rather than mapping a file descriptor each region carries two extra fields, "xen mmap flags" and "domid" (see docs/interop/vhost-user.rst, "Memory region description"). The layout is implemented by rust-vmm's vhost and vm-memory crates and used by Xen vhost-user device backends. Implement the front-end side for foreign mappings: - negotiate VHOST_USER_PROTOCOL_F_XEN_MMAP - when negotiated, build SET_MEM_TABLE payloads from the extended region layout, with xen_mmap_flags = FOREIGN and xen_mmap_data set to the guest's domain id. - under Xen, do not call vhost_user_get_mr_data(): guest RAM has no fd and its userspace_addr does not correspond to a valid mapping in the address space. Backends map regions through privcmd using the guest physical address and domid; the fd accompanying each region only satisfies the protocol's one-fd-per-region requirement. Pass a /dev/xen/privcmd fd and close it once the message has been sent. Tracepoints for opening and closing xen fds are added as well. - suppress VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS under Xen: Postcopy is likewise refused. The userspace_addr field is carried unchanged; Xen backends derive mappings from guest_phys_addr and domid and do not interpret it. Signed-off-by: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> Signed-off-by: Nikola Jelic <Nikola.Jelic@rt-rk.com> --- hw/virtio/trace-events | 2 + hw/virtio/vhost-user.c | 120 +++++++++++++++++++++++++++++++++++++++-- include/hw/virtio/vhost-user.h | 2 +- 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/hw/virtio/trace-events b/hw/virtio/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/trace-events +++ b/hw/virtio/trace-events @@ -XXX,XX +XXX,XX @@ vhost_user_postcopy_fault_handler_found(int i, uint64_t region_offset, uint64_t vhost_user_postcopy_listen(void) "" vhost_user_set_mem_table_postcopy(uint64_t client_addr, uint64_t qhva, int reply_i, int region_i) "client:0x%"PRIx64" for hva: 0x%"PRIx64" reply %d region %d" vhost_user_set_mem_table_withfd(int index, const char *name, uint64_t memory_size, uint64_t guest_phys_addr, uint64_t userspace_addr, uint64_t offset) "%d:%s: size:0x%"PRIx64" GPA:0x%"PRIx64" QVA/userspace:0x%"PRIx64" RB offset:0x%"PRIx64 +vhost_user_open_region_fd(int index, int fd) "region:%d fd:%d" +vhost_user_put_region_fds(int index, int fd) "region:%d fd:%d" vhost_user_postcopy_waker(const char *rb, uint64_t rb_offset) "%s + 0x%"PRIx64 vhost_user_postcopy_waker_found(uint64_t client_addr) "0x%"PRIx64 vhost_user_postcopy_waker_nomatch(const char *rb, uint64_t rb_offset) "%s + 0x%"PRIx64 diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/vhost-user.c +++ b/hw/virtio/vhost-user.c @@ -XXX,XX +XXX,XX @@ #include "migration/postcopy-ram.h" #include "trace.h" #include "system/ramblock.h" +#include "system/xen.h" +#include "hw/xen/xen.h" #include <sys/ioctl.h> #include <sys/socket.h> @@ -XXX,XX +XXX,XX @@ typedef struct VhostUserMemoryRegion { uint64_t mmap_offset; } VhostUserMemoryRegion; +/* + * Memory region flags for VHOST_USER_PROTOCOL_F_XEN_MMAP, matching the + * values used by rust-vmm's vm-memory (MmapXenFlags). + */ +#define VHOST_USER_XEN_MMAP_FLAG_FOREIGN 0x1 +#define VHOST_USER_XEN_MMAP_FLAG_GRANT 0x2 + +/* + * Extended memory region description, used when + * VHOST_USER_PROTOCOL_F_XEN_MMAP has been negotiated. + */ +typedef struct VhostUserMemoryRegionXen { + VhostUserMemoryRegion region; + uint32_t xen_mmap_flags; + uint32_t xen_mmap_data; /* domain id for FOREIGN/GRANT mappings */ +} VhostUserMemoryRegionXen; + + typedef struct VhostUserMemory { uint32_t nregions; uint32_t padding; VhostUserMemoryRegion regions[VHOST_MEMORY_BASELINE_NREGIONS]; } VhostUserMemory; +typedef struct VhostUserMemoryXen { + uint32_t nregions; + uint32_t padding; + VhostUserMemoryRegionXen regions[VHOST_MEMORY_BASELINE_NREGIONS]; +} VhostUserMemoryXen; + typedef struct VhostUserMemRegMsg { uint64_t padding; VhostUserMemoryRegion region; @@ -XXX,XX +XXX,XX @@ typedef union { struct vhost_vring_state state; struct vhost_vring_addr addr; VhostUserMemory memory; + VhostUserMemoryXen memory_xen; VhostUserMemRegMsg mem_reg; VhostUserLog log; struct vhost_iotlb_msg iotlb; @@ -XXX,XX +XXX,XX @@ static MemoryRegion *vhost_user_get_mr_data(uint64_t addr, ram_addr_t *offset, static bool vhost_user_gpa_addresses(struct vhost_dev *dev) { return vhost_user_has_protocol_feature( + dev, VHOST_USER_PROTOCOL_F_XEN_MMAP) || + vhost_user_has_protocol_feature( dev, VHOST_USER_PROTOCOL_F_GPA_ADDRESSES); } @@ -XXX,XX +XXX,XX @@ static void vhost_user_fill_msg_region(struct vhost_dev *dev, dst->mmap_offset = mmap_offset; } +/* + * With VHOST_USER_PROTOCOL_F_XEN_MMAP the region fds are opened by us + * rather than owned by the RAMBlocks, so they must be closed once the + * message carrying them has been sent (or on error). + */ +static void vhost_user_put_region_fds(struct vhost_dev *dev, int *fds, + size_t fd_num) +{ + if (!vhost_user_has_protocol_feature(dev, VHOST_USER_PROTOCOL_F_XEN_MMAP)) { + return; + } + for (size_t i = 0; i < fd_num; i++) { + trace_vhost_user_put_region_fds(i, fds[i]); + close(fds[i]); + } +} + static int vhost_user_fill_set_mem_table_msg(struct vhost_user *u, struct vhost_dev *dev, VhostUserMsg *msg, @@ -XXX,XX +XXX,XX @@ static int vhost_user_fill_set_mem_table_msg(struct vhost_user *u, MemoryRegion *mr; struct vhost_memory_region *reg; VhostUserMemoryRegion region_buffer; + bool xen_mmap = vhost_user_has_protocol_feature(dev, + VHOST_USER_PROTOCOL_F_XEN_MMAP); + + if (track_ramblocks && xen_mmap) { + error_report("vhost-user: postcopy is not supported under Xen"); + return -ENOTSUP; + } msg->hdr.request = VHOST_USER_SET_MEM_TABLE; for (i = 0; i < dev->mem->nregions; ++i) { reg = dev->mem->regions + i; - mr = vhost_user_get_mr_data(reg->userspace_addr, &offset, &fd); + if (xen_mmap) { + /* + * Under Xen the guest RAM is not mapped into our address + * space; the backend maps it through the Xen foreign + * mapping interface using the guest physical address and + * domain id carried in the region descriptor. The file + * descriptor only satisfies the one-fd-per-region + * requirement of the protocol: pass /dev/xen/privcmd and + * close it once the message has been sent. + */ + mr = NULL; + offset = 0; + fd = open("/dev/xen/privcmd", O_RDWR | O_CLOEXEC); + if (fd < 0) { + error_report("vhost-user: failed to open /dev/xen/privcmd:" + " %s", strerror(errno)); + return -errno; + } + trace_vhost_user_open_region_fd(i, fd); + } else { + mr = vhost_user_get_mr_data(reg->userspace_addr, &offset, &fd); + } if (fd > 0) { if (track_ramblocks) { assert(*fd_num < VHOST_MEMORY_BASELINE_NREGIONS); @@ -XXX,XX +XXX,XX @@ static int vhost_user_fill_set_mem_table_msg(struct vhost_user *u, u->region_rb[i] = mr->ram_block; } else if (*fd_num == VHOST_MEMORY_BASELINE_NREGIONS) { error_report("Failed preparing vhost-user memory table msg"); + if (xen_mmap) { + close(fd); + } return -ENOBUFS; } vhost_user_fill_msg_region(dev, ®ion_buffer, reg, offset); - msg->payload.memory.regions[*fd_num] = region_buffer; + if (xen_mmap) { + msg->payload.memory_xen.regions[*fd_num].region = region_buffer; + msg->payload.memory_xen.regions[*fd_num].xen_mmap_flags = + VHOST_USER_XEN_MMAP_FLAG_FOREIGN; + msg->payload.memory_xen.regions[*fd_num].xen_mmap_data = + xen_domid; + } else { + msg->payload.memory.regions[*fd_num] = region_buffer; + } fds[(*fd_num)++] = fd; } else if (track_ramblocks) { u->region_rb_offset[i] = 0; @@ -XXX,XX +XXX,XX @@ static int vhost_user_fill_set_mem_table_msg(struct vhost_user *u, msg->hdr.size = sizeof(msg->payload.memory.nregions); msg->hdr.size += sizeof(msg->payload.memory.padding); - msg->hdr.size += *fd_num * sizeof(VhostUserMemoryRegion); + if (xen_mmap) { + msg->hdr.size += *fd_num * sizeof(VhostUserMemoryRegionXen); + } else { + msg->hdr.size += *fd_num * sizeof(VhostUserMemoryRegion); + } return 0; } @@ -XXX,XX +XXX,XX @@ static int vhost_user_set_mem_table(struct vhost_dev *dev, ret = vhost_user_fill_set_mem_table_msg(u, dev, &msg, fds, &fd_num, false); if (ret < 0) { + vhost_user_put_region_fds(dev, fds, fd_num); return ret; } ret = vhost_user_write(dev, &msg, fds, fd_num); + vhost_user_put_region_fds(dev, fds, fd_num); if (ret < 0) { return ret; } @@ -XXX,XX +XXX,XX @@ static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque, VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT); } + if (!xen_enabled()) { + /* + * Xen memory mappings only make sense when QEMU itself runs + * as a Xen device model. + */ + protocol_features &= ~(1ULL << VHOST_USER_PROTOCOL_F_XEN_MMAP); + } else { + if (!virtio_has_feature(protocol_features, + VHOST_USER_PROTOCOL_F_XEN_MMAP)) { + error_setg(errp, "vhost-user backend does not support " + "VHOST_USER_PROTOCOL_F_XEN_MMAP, which is " + "required when running under Xen"); + return -EPROTO; + } + /* + * The ADD/REM_MEM_REG message path has not been adapted to + * the Xen region format. Xen guests expose a single RAM + * region, so fall back to SET_MEM_TABLE. + */ + protocol_features &= + ~(1ULL << VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS); + } + /* final set of protocol features */ u->protocol_features = protocol_features; err = vhost_user_set_protocol_features(dev, u->protocol_features); diff --git a/include/hw/virtio/vhost-user.h b/include/hw/virtio/vhost-user.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/virtio/vhost-user.h +++ b/include/hw/virtio/vhost-user.h @@ -XXX,XX +XXX,XX @@ enum VhostUserProtocolFeature { VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS = 14, VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS = 15, VHOST_USER_PROTOCOL_F_STATUS = 16, - /* Feature 17 reserved for VHOST_USER_PROTOCOL_F_XEN_MMAP. */ + VHOST_USER_PROTOCOL_F_XEN_MMAP = 17, VHOST_USER_PROTOCOL_F_SHARED_OBJECT = 18, VHOST_USER_PROTOCOL_F_DEVICE_STATE = 19, VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT = 20, -- 2.43.0
This series lets QEMU, when running as a Xen device model, drive vhost-user backends that map guest memory through the Xen foreign mapping interface, implementing the front-end side of VHOST_USER_PROTOCOL_F_XEN_MMAP. The protocol extension itself is already documented in docs/interop/vhost-user.rst (feature bit 17, extended memory region description) and implemented by rust-vmm's vhost / vm-memory crates and the vhost-device backends built on them. The problem this solves: under Xen the guest's RAM is not allocated by QEMU and is not backed by a file descriptor. memory_region_get_fd() returns -1, so vhost_section() filters out every RAM section, the vhost memory listener registers no regions, and starting any vhost-user device fails with "Failed initializing vhost-user memory map". With F_XEN_MMAP the backend maps guest memory itself. The protocol requires one file descriptor per region in SET_MEM_TABLE. Guest RAM under Xen has no backing fd, so the front-end opens /dev/xen/privcmd per region purely to satisfy that requirement; the backend derives the mapping from guest_phys_addr + domid and never reads the fd. Each fd is closed once the message has been sent. This patchset was rebased onto the new vhost_phys_vring_addr infrastructure and extends vhost_user_gpa_addresses() so that negotiated F_XEN_MMAP (bit 17), not F_GPA_ADDRESSES (bit 21, which the backend doesn't advertise), drives GPA addressing for both rings and userspace_addr. The two patches: 1/2 accept the Xen RAM section in vhost_section() 2/2 negotiate F_XEN_MMAP and build SET_MEM_TABLE from the extended region layout. Testing: Tested on Xen/ARM64 with a DomU using virtio-mmio transports created by the xenpvh machine, running vhost-device-sound (rust-vmm, built with the "xen" feature) as the backend in dom0. The device negotiates, receives the memory table and ring addresses, and the guest's virtio-snd driver probes and operates. Non-Xen / x86 KVM: vhost-user-snd backed by vhost-device-sound (null backend) on a q35/KVM guest. The device negotiates, the guest virtio-snd driver probes and runs the control and PCM paths, and the SET_MEM_TABLE and vring-address traffic is identical to a build without this series confirming the non-Xen path is unchanged. The control message exchange between the frontend and backend was tracked using sockdump as was described in: Making VirtIO sing - implementing virtio-sound in rust-vmm project |-> at FOSDEM 2024 Setup: The main part of the xl config this enables: virtio = [ 'backend=0,type=virtio,device,transport=mmio,grant_usage=false' ] device_model_args = [ ... '-chardev', 'socket,id=snd_chardev,path=/tmp/snd.sock', '-device', 'vhost-user-snd,chardev=snd_chardev,id=snd,iommu_platform=true', ... ] Xen 4.22-unstable was used with: -enable-IOREQ_SERVER -enable-EXPERT An extra patch was added to xen-tools. Namely, xen tools will request a pv device drive type for ARM64 but qemu expects pvh. This is a known issue: github.com/Xilinx/xen/commit/5f669949c9ffdb1947cb47038956b5fb8eeb072a Qemu master was used configured with the following flags: --target-list=aarch64-softmmu \ --cross-prefix=aarch64-linux-gnu- \ --enable-xen \ --enable-vhost-user \ --extra-cflags="-I$XEN-TOOLS/usr/local/include" \ --extra-ldflags="-L$XEN-TOOLS/usr/local/lib -Wl, -rpath-link,$XEN-TOOLS/usr/local/lib" \ Likewise for x86: --target-list=aarch64-softmmu \ --enable-slirp \ --enable-xen \ --enable-vhost-user \ --enable-virtfs \ Linux version 6.11.7 was used with extra configuration flags: * For enabling Xen Dom0/DomU support * For enabling virtio (mmio, snd, etc.) * For enabling sockdump features (BPF, IKHEADERS, KPROBE, etc.) * Extra debug flags (DEBUG_FS, etc.) vhost-device commit-id: c3bb658ef4fe20a2f264dbbbbc6fa19f1c08c0c5 Was used built with: --features alsa-backend,xen Importantly in vhost-device-scmi/src/vhu_scmi.rs: // QUEUE_SIZE must be apparently at least 1024 for MMIO. // There is probably a maximum size per descriptor defined in the kernel. const QUEUE_SIZE: usize = 1024; A similar change was made to make mmio work in vhost-user-sound device, bumping QUEUE_SIZE to 1024. Without this frontend and backend will fail to negotiate queue size. Scope and known limitations: * Foreign mappings only. Grant mappings are not supported: vhost's section tracking derives a host pointer for each region, which is invalid for the grant pseudo-region, and per-access grant mapping needs a different region description (GRANT | no-advance-map). Patch 1 rejects the xen.grants region explicitly. Setting grant_usage=true does not change the qemu<->backend vhost-user exchange. * VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS is suppressed under Xen: the ADD/REM_MEM_REG path has not been converted to the extended region format, and Xen guests currently expose a single RAM region, so SET_MEM_TABLE is sufficient. Multiple RAM regions are not yet exercised. Postcopy is refused. * Spec vs reference implementation: docs/interop/vhost-user.rst describes the "can not be mapped in advance" xen-mmap flag as Bit 8 (value 0x100), whereas rust-vmm's vm-memory uses 0x8 (bit 3, MmapXenFlags::NO_ADVANCE_MAP). This series uses neither, but the discrepancy probably wants resolving in the spec. Viresh, which is intended -- bit position 8 or value 0x8? * userspace_addr is carried unchanged in the region descriptor; under Xen it does not correspond to a mapping and backends do not interpret it. An alternative would be to define it (e.g. mirror guest_phys_addr). Open questions: - userspace_addr semantics under Xen: leave it unchanged, or define it? - Multi-region support: convert ADD/REM_MEM_REG to the extended layout rather than suppressing CONFIGURE_MEM_SLOTS? - Grant-mapping support: worth pursuing, and what region-description shape do backends expect? - Updating vhost-device-sound to reflect the mmio support. References: - vhost-user spec, F_XEN_MMAP / extended memory region / xen mmap flags: docs/interop/vhost-user.rst - rust-vmm vm-memory MmapXenFlags (FOREIGN=0x1, GRANT=0x2, NO_ADVANCE_MAP=0x8): src/mmap/xen.rs - Making VirtIO sing - implementing virtio-sound in rust-vmm project |-> at FOSDEM 2024 Signed-off-by: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> Signed-off-by: Nikola Jelic <Nikola.Jelic@rt-rk.com> --- Changes in v3: - Rebased onto current master - Fixed semantic error in vhost_section comment - Link to v2: https://lore.kernel.org/qemu-devel/20260629-vhost-xen-foreign-mapping-v2-0-19e4685e7575@rt-rk.com Changes in v2: - Rebased onto current master - Cover letter: removed a rust-vmm hunk which made the git am on Patchview fail. The reference is now mentioned in a sentance. - Link to v1: https://lore.kernel.org/qemu-devel/20260618-vhost-xen-foreign-mapping-v1-0-7f60a6241971@rt-rk.com --- Dusan Stojkovic (2): vhost: accept Xen guest RAM sections for vhost-user vhost-user: implement VHOST_USER_PROTOCOL_F_XEN_MMAP hw/virtio/trace-events | 2 + hw/virtio/vhost-user.c | 120 +++++++++++++++++++++++++++++++++++++++-- hw/virtio/vhost.c | 18 +++++++ hw/xen/xen_stubs.c | 5 ++ include/hw/virtio/vhost-user.h | 2 +- 5 files changed, 143 insertions(+), 4 deletions(-) --- base-commit: 30e8a06b64aa58a3990ba39cb5d09531e7d265e0 change-id: 20260618-vhost-xen-foreign-mapping-d023c85bb706 Best regards, -- Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com>
From: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> When QEMU runs as a Xen device model, the guest's RAM is not allocated by QEMU and is not backed by a file descriptor that could be shared with a vhost-user backend: accesses from QEMU go through the Xen mapcache and memory_region_get_fd() returns -1. vhost_section() therefore filters out every RAM section, the vhost memory listener registers no regions, and starting any vhost-user device fails with "Failed initializing vhost-user memory map". With VHOST_USER_PROTOCOL_F_XEN_MMAP the backend does not need an fd or a process-local mapping it maps guest memory itself through the Xen foreign mapping interface, using the guest physical address and domain id. Accept the Xen RAM region in vhost_section() so that it reaches the backend's memory table. The Xen grant region (xen.grants) must never be accepted: grant references can only be mapped individually on demand via address_space_map(), and deriving a host pointer for the whole region, as vhost_region_add_section() does, aborts in the Xen mapcache. Note that xen_mr_is_memory() returns true for both the RAM and the grants region, so the grants region is excluded explicitly. Because of the necessity to exlude xen.grants, the missing stub for xen_mr_is_grants is added so that it can be called from common code. Signed-off-by: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> Signed-off-by: Nikola Jelic <Nikola.Jelic@rt-rk.com> --- hw/virtio/vhost.c | 18 ++++++++++++++++++ hw/xen/xen_stubs.c | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/vhost.c +++ b/hw/virtio/vhost.c @@ -XXX,XX +XXX,XX @@ #include "system/dma.h" #include "system/memory.h" #include "system/ramblock.h" +#include "system/xen.h" #include "trace.h" /* enabled until disconnected backend stabilizes */ @@ -XXX,XX +XXX,XX @@ static bool vhost_section(struct vhost_dev *dev, MemoryRegionSection *section) return false; } + /* + * Under Xen, the guest's RAM is not backed by an fd that + * can be passed to a vhost-user backend. The backend + * instead maps guest memory through the Xen foreign mapping + * interface, by guest physical address and domain id (see + * VHOST_USER_PROTOCOL_F_XEN_MMAP), so accept the Xen RAM + * region even though it has no fd. + */ + if (xen_enabled()) { + if (xen_mr_is_memory(mr) && !xen_mr_is_grants(mr)) { + trace_vhost_section(mr->name); + return true; + } + trace_vhost_reject_section(mr->name, 4); + return false; + } + /* * Some backends (like vhost-user) can only handle memory regions * that have an fd (can be mapped into a different process). Filter diff --git a/hw/xen/xen_stubs.c b/hw/xen/xen_stubs.c index XXXXXXX..XXXXXXX 100644 --- a/hw/xen/xen_stubs.c +++ b/hw/xen/xen_stubs.c @@ -XXX,XX +XXX,XX @@ bool xen_mr_is_memory(const MemoryRegion *mr) g_assert_not_reached(); } +bool xen_mr_is_grants(const MemoryRegion *mr) +{ + g_assert_not_reached(); +} + bool xen_map_cache_enabled(void) { return false; -- 2.43.0
From: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> The vhost-user specification reserves protocol feature bit 17 and documents an extended memory region description for backends that map guest memory through Xen rather than mapping a file descriptor each region carries two extra fields, "xen mmap flags" and "domid" (see docs/interop/vhost-user.rst, "Memory region description"). The layout is implemented by rust-vmm's vhost and vm-memory crates and used by Xen vhost-user device backends. Implement the front-end side for foreign mappings: - negotiate VHOST_USER_PROTOCOL_F_XEN_MMAP - when negotiated, build SET_MEM_TABLE payloads from the extended region layout, with xen_mmap_flags = FOREIGN and xen_mmap_data set to the guest's domain id. - under Xen, do not call vhost_user_get_mr_data(): guest RAM has no fd and its userspace_addr does not correspond to a valid mapping in the address space. Backends map regions through privcmd using the guest physical address and domid; the fd accompanying each region only satisfies the protocol's one-fd-per-region requirement. Pass a /dev/xen/privcmd fd and close it once the message has been sent. Tracepoints for opening and closing xen fds are added as well. - suppress VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS under Xen: Postcopy is likewise refused. The userspace_addr field is carried unchanged; Xen backends derive mappings from guest_phys_addr and domid and do not interpret it. Signed-off-by: Dusan Stojkovic <Dusan.Stojkovic@rt-rk.com> Signed-off-by: Nikola Jelic <Nikola.Jelic@rt-rk.com> --- hw/virtio/trace-events | 2 + hw/virtio/vhost-user.c | 120 +++++++++++++++++++++++++++++++++++++++-- include/hw/virtio/vhost-user.h | 2 +- 3 files changed, 120 insertions(+), 4 deletions(-) diff --git a/hw/virtio/trace-events b/hw/virtio/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/trace-events +++ b/hw/virtio/trace-events @@ -XXX,XX +XXX,XX @@ vhost_user_postcopy_fault_handler_found(int i, uint64_t region_offset, uint64_t vhost_user_postcopy_listen(void) "" vhost_user_set_mem_table_postcopy(uint64_t client_addr, uint64_t qhva, int reply_i, int region_i) "client:0x%"PRIx64" for hva: 0x%"PRIx64" reply %d region %d" vhost_user_set_mem_table_withfd(int index, const char *name, uint64_t memory_size, uint64_t guest_phys_addr, uint64_t userspace_addr, uint64_t offset) "%d:%s: size:0x%"PRIx64" GPA:0x%"PRIx64" QVA/userspace:0x%"PRIx64" RB offset:0x%"PRIx64 +vhost_user_open_region_fd(int index, int fd) "region:%d fd:%d" +vhost_user_put_region_fds(int index, int fd) "region:%d fd:%d" vhost_user_postcopy_waker(const char *rb, uint64_t rb_offset) "%s + 0x%"PRIx64 vhost_user_postcopy_waker_found(uint64_t client_addr) "0x%"PRIx64 vhost_user_postcopy_waker_nomatch(const char *rb, uint64_t rb_offset) "%s + 0x%"PRIx64 diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c index XXXXXXX..XXXXXXX 100644 --- a/hw/virtio/vhost-user.c +++ b/hw/virtio/vhost-user.c @@ -XXX,XX +XXX,XX @@ #include "migration/postcopy-ram.h" #include "trace.h" #include "system/ramblock.h" +#include "system/xen.h" +#include "hw/xen/xen.h" #include <sys/ioctl.h> #include <sys/socket.h> @@ -XXX,XX +XXX,XX @@ typedef struct VhostUserMemoryRegion { uint64_t mmap_offset; } VhostUserMemoryRegion; +/* + * Memory region flags for VHOST_USER_PROTOCOL_F_XEN_MMAP, matching the + * values used by rust-vmm's vm-memory (MmapXenFlags). + */ +#define VHOST_USER_XEN_MMAP_FLAG_FOREIGN 0x1 +#define VHOST_USER_XEN_MMAP_FLAG_GRANT 0x2 + +/* + * Extended memory region description, used when + * VHOST_USER_PROTOCOL_F_XEN_MMAP has been negotiated. + */ +typedef struct VhostUserMemoryRegionXen { + VhostUserMemoryRegion region; + uint32_t xen_mmap_flags; + uint32_t xen_mmap_data; /* domain id for FOREIGN/GRANT mappings */ +} VhostUserMemoryRegionXen; + + typedef struct VhostUserMemory { uint32_t nregions; uint32_t padding; VhostUserMemoryRegion regions[VHOST_MEMORY_BASELINE_NREGIONS]; } VhostUserMemory; +typedef struct VhostUserMemoryXen { + uint32_t nregions; + uint32_t padding; + VhostUserMemoryRegionXen regions[VHOST_MEMORY_BASELINE_NREGIONS]; +} VhostUserMemoryXen; + typedef struct VhostUserMemRegMsg { uint64_t padding; VhostUserMemoryRegion region; @@ -XXX,XX +XXX,XX @@ typedef union { struct vhost_vring_state state; struct vhost_vring_addr addr; VhostUserMemory memory; + VhostUserMemoryXen memory_xen; VhostUserMemRegMsg mem_reg; VhostUserLog log; struct vhost_iotlb_msg iotlb; @@ -XXX,XX +XXX,XX @@ static MemoryRegion *vhost_user_get_mr_data(uint64_t addr, ram_addr_t *offset, static bool vhost_user_gpa_addresses(struct vhost_dev *dev) { return vhost_user_has_protocol_feature( + dev, VHOST_USER_PROTOCOL_F_XEN_MMAP) || + vhost_user_has_protocol_feature( dev, VHOST_USER_PROTOCOL_F_GPA_ADDRESSES); } @@ -XXX,XX +XXX,XX @@ static void vhost_user_fill_msg_region(struct vhost_dev *dev, dst->mmap_offset = mmap_offset; } +/* + * With VHOST_USER_PROTOCOL_F_XEN_MMAP the region fds are opened by us + * rather than owned by the RAMBlocks, so they must be closed once the + * message carrying them has been sent (or on error). + */ +static void vhost_user_put_region_fds(struct vhost_dev *dev, int *fds, + size_t fd_num) +{ + if (!vhost_user_has_protocol_feature(dev, VHOST_USER_PROTOCOL_F_XEN_MMAP)) { + return; + } + for (size_t i = 0; i < fd_num; i++) { + trace_vhost_user_put_region_fds(i, fds[i]); + close(fds[i]); + } +} + static int vhost_user_fill_set_mem_table_msg(struct vhost_user *u, struct vhost_dev *dev, VhostUserMsg *msg, @@ -XXX,XX +XXX,XX @@ static int vhost_user_fill_set_mem_table_msg(struct vhost_user *u, MemoryRegion *mr; struct vhost_memory_region *reg; VhostUserMemoryRegion region_buffer; + bool xen_mmap = vhost_user_has_protocol_feature(dev, + VHOST_USER_PROTOCOL_F_XEN_MMAP); + + if (track_ramblocks && xen_mmap) { + error_report("vhost-user: postcopy is not supported under Xen"); + return -ENOTSUP; + } msg->hdr.request = VHOST_USER_SET_MEM_TABLE; for (i = 0; i < dev->mem->nregions; ++i) { reg = dev->mem->regions + i; - mr = vhost_user_get_mr_data(reg->userspace_addr, &offset, &fd); + if (xen_mmap) { + /* + * Under Xen the guest RAM is not mapped into our address + * space; the backend maps it through the Xen foreign + * mapping interface using the guest physical address and + * domain id carried in the region descriptor. The file + * descriptor only satisfies the one-fd-per-region + * requirement of the protocol: pass /dev/xen/privcmd and + * close it once the message has been sent. + */ + mr = NULL; + offset = 0; + fd = open("/dev/xen/privcmd", O_RDWR | O_CLOEXEC); + if (fd < 0) { + error_report("vhost-user: failed to open /dev/xen/privcmd:" + " %s", strerror(errno)); + return -errno; + } + trace_vhost_user_open_region_fd(i, fd); + } else { + mr = vhost_user_get_mr_data(reg->userspace_addr, &offset, &fd); + } if (fd > 0) { if (track_ramblocks) { assert(*fd_num < VHOST_MEMORY_BASELINE_NREGIONS); @@ -XXX,XX +XXX,XX @@ static int vhost_user_fill_set_mem_table_msg(struct vhost_user *u, u->region_rb[i] = mr->ram_block; } else if (*fd_num == VHOST_MEMORY_BASELINE_NREGIONS) { error_report("Failed preparing vhost-user memory table msg"); + if (xen_mmap) { + close(fd); + } return -ENOBUFS; } vhost_user_fill_msg_region(dev, ®ion_buffer, reg, offset); - msg->payload.memory.regions[*fd_num] = region_buffer; + if (xen_mmap) { + msg->payload.memory_xen.regions[*fd_num].region = region_buffer; + msg->payload.memory_xen.regions[*fd_num].xen_mmap_flags = + VHOST_USER_XEN_MMAP_FLAG_FOREIGN; + msg->payload.memory_xen.regions[*fd_num].xen_mmap_data = + xen_domid; + } else { + msg->payload.memory.regions[*fd_num] = region_buffer; + } fds[(*fd_num)++] = fd; } else if (track_ramblocks) { u->region_rb_offset[i] = 0; @@ -XXX,XX +XXX,XX @@ static int vhost_user_fill_set_mem_table_msg(struct vhost_user *u, msg->hdr.size = sizeof(msg->payload.memory.nregions); msg->hdr.size += sizeof(msg->payload.memory.padding); - msg->hdr.size += *fd_num * sizeof(VhostUserMemoryRegion); + if (xen_mmap) { + msg->hdr.size += *fd_num * sizeof(VhostUserMemoryRegionXen); + } else { + msg->hdr.size += *fd_num * sizeof(VhostUserMemoryRegion); + } return 0; } @@ -XXX,XX +XXX,XX @@ static int vhost_user_set_mem_table(struct vhost_dev *dev, ret = vhost_user_fill_set_mem_table_msg(u, dev, &msg, fds, &fd_num, false); if (ret < 0) { + vhost_user_put_region_fds(dev, fds, fd_num); return ret; } ret = vhost_user_write(dev, &msg, fds, fd_num); + vhost_user_put_region_fds(dev, fds, fd_num); if (ret < 0) { return ret; } @@ -XXX,XX +XXX,XX @@ static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque, VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT); } + if (!xen_enabled()) { + /* + * Xen memory mappings only make sense when QEMU itself runs + * as a Xen device model. + */ + protocol_features &= ~(1ULL << VHOST_USER_PROTOCOL_F_XEN_MMAP); + } else { + if (!virtio_has_feature(protocol_features, + VHOST_USER_PROTOCOL_F_XEN_MMAP)) { + error_setg(errp, "vhost-user backend does not support " + "VHOST_USER_PROTOCOL_F_XEN_MMAP, which is " + "required when running under Xen"); + return -EPROTO; + } + /* + * The ADD/REM_MEM_REG message path has not been adapted to + * the Xen region format. Xen guests expose a single RAM + * region, so fall back to SET_MEM_TABLE. + */ + protocol_features &= + ~(1ULL << VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS); + } + /* final set of protocol features */ u->protocol_features = protocol_features; err = vhost_user_set_protocol_features(dev, u->protocol_features); diff --git a/include/hw/virtio/vhost-user.h b/include/hw/virtio/vhost-user.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/virtio/vhost-user.h +++ b/include/hw/virtio/vhost-user.h @@ -XXX,XX +XXX,XX @@ enum VhostUserProtocolFeature { VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS = 14, VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS = 15, VHOST_USER_PROTOCOL_F_STATUS = 16, - /* Feature 17 reserved for VHOST_USER_PROTOCOL_F_XEN_MMAP. */ + VHOST_USER_PROTOCOL_F_XEN_MMAP = 17, VHOST_USER_PROTOCOL_F_SHARED_OBJECT = 18, VHOST_USER_PROTOCOL_F_DEVICE_STATE = 19, VHOST_USER_PROTOCOL_F_GET_VRING_BASE_INFLIGHT = 20, -- 2.43.0