@samitouri / QOSamiQemu / commits / b52e1896e7

vhost-user: Add VirtIO Shared Memory map request

Add SHMEM_MAP/UNMAP requests to vhost-user for dynamic management of VIRTIO Shared Memory mappings. This implementation introduces VirtioSharedMemoryMapping as a unified QOM object that manages both the mapping metadata and MemoryRegion lifecycle. This object provides reference-counted lifecycle management with automatic cleanup of file descriptors and memory regions through QOM finalization. This request allows backends to dynamically map file descriptors into a VIRTIO Shared Memory Region identified by their shmid. Maps are created using memory_region_init_ram_from_fd() with configurable read/write permissions, and the resulting MemoryRegions are added as subregions to the shmem container region. The mapped memory is then advertised to the guest VIRTIO drivers as a base address plus offset for reading and writting according to the requested mmap flags. The backend can unmap memory ranges within a given VIRTIO Shared Memory Region to free resources. Upon receiving this message, the frontend removes the MemoryRegion as a subregion and automatically unreferences the VirtioSharedMemoryMapping object, triggering cleanup if no other references exist. Error handling has been improved to ensure consistent behavior across handlers that manage their own vhost_user_send_resp() calls. Since these handlers clear the VHOST_USER_NEED_REPLY_MASK flag, explicit error checking ensures proper connection closure on failures, maintaining the expected error flow. Note the memory region commit for these operations needs to be delayed until after we reply to the backend to avoid deadlocks. Otherwise, the MemoryListener would send a VHOST_USER_SET_MEM_TABLE message before the reply. Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Reviewed-by: Stefano Garzarella <sgarzare@redhat.com> Signed-off-by: Albert Esteve <aesteve@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-Id: <20260304165223.2166175-2-aesteve@redhat.com>

Albert Esteve committed Mar 4, 2026 at 17:52 UTC b52e1896e76437ef7f4c73c834902a829af92f28
7 files changed +748 -1
hw/virtio/vhost-user.c
+274
@@ -100,6 +100,7 @@ typedef enum VhostUserRequest {
100 VHOST_USER_GET_SHARED_OBJECT = 41,
101 VHOST_USER_SET_DEVICE_STATE_FD = 42,
102 VHOST_USER_CHECK_DEVICE_STATE = 43,
103 + VHOST_USER_GET_SHMEM_CONFIG = 44,
104 VHOST_USER_MAX
105 } VhostUserRequest;
106
@@ -111,6 +112,8 @@ typedef enum VhostUserBackendRequest {
112 VHOST_USER_BACKEND_SHARED_OBJECT_ADD = 6,
113 VHOST_USER_BACKEND_SHARED_OBJECT_REMOVE = 7,
114 VHOST_USER_BACKEND_SHARED_OBJECT_LOOKUP = 8,
115 + VHOST_USER_BACKEND_SHMEM_MAP = 9,
116 + VHOST_USER_BACKEND_SHMEM_UNMAP = 10,
117 VHOST_USER_BACKEND_MAX
118 } VhostUserBackendRequest;
119
@@ -189,6 +192,12 @@ typedef struct VhostUserMemRegMsg {
192 VhostUserMemoryRegion region;
193 } VhostUserMemRegMsg;
194
195 +typedef struct VhostUserShMemConfig {
196 + uint32_t nregions;
197 + uint32_t padding;
198 + uint64_t memory_sizes[VIRTIO_MAX_SHMEM_REGIONS];
199 +} VhostUserShMemConfig;
200 +
201 typedef struct VhostUserLog {
202 uint64_t mmap_size;
203 uint64_t mmap_offset;
@@ -245,6 +254,23 @@ typedef struct VhostUserShared {
254 unsigned char uuid[16];
255 } VhostUserShared;
256
257 +/* For the flags field of VhostUserMMap */
258 +#define VHOST_USER_FLAG_MAP_RW (1u << 0)
259 +
260 +typedef struct {
261 + /* VIRTIO Shared Memory Region ID */
262 + uint8_t shmid;
263 + uint8_t padding[7];
264 + /* File offset */
265 + uint64_t fd_offset;
266 + /* Offset within the VIRTIO Shared Memory Region */
267 + uint64_t shm_offset;
268 + /* Size of the mapping */
269 + uint64_t len;
270 + /* Flags for the mmap operation, from VHOST_USER_FLAG_MAP_* */
271 + uint64_t flags;
272 +} VhostUserMMap;
273 +
274 typedef struct {
275 VhostUserRequest request;
276
@@ -277,6 +303,8 @@ typedef union {
303 VhostUserInflight inflight;
304 VhostUserShared object;
305 VhostUserTransferDeviceState transfer_state;
306 + VhostUserMMap mmap;
307 + VhostUserShMemConfig shmem;
308 } VhostUserPayload;
309
310 typedef struct VhostUserMsg {
@@ -1860,6 +1888,196 @@ vhost_user_backend_handle_shared_object_lookup(struct vhost_user *u,
1888 return 0;
1889 }
1890
1891 +/**
1892 + * vhost_user_backend_handle_shmem_map() - Handle SHMEM_MAP backend request
1893 + * @dev: vhost device
1894 + * @ioc: QIOChannel for communication
1895 + * @hdr: vhost-user message header
1896 + * @payload: message payload containing mapping details
1897 + * @fd: file descriptor for the shared memory region
1898 + *
1899 + * Handles VHOST_USER_BACKEND_SHMEM_MAP requests from the backend. Creates
1900 + * a VhostUserShmemObject to manage the shared memory mapping and adds it
1901 + * to the appropriate VirtIO shared memory region. The VhostUserShmemObject
1902 + * serves as an intermediate parent for the MemoryRegion, ensuring proper
1903 + * lifecycle management with reference counting.
1904 + *
1905 + * Returns: 0 on success, negative errno on failure
1906 + */
1907 +static int
1908 +vhost_user_backend_handle_shmem_map(struct vhost_dev *dev,
1909 + QIOChannel *ioc,
1910 + VhostUserHeader *hdr,
1911 + VhostUserPayload *payload,
1912 + int fd)
1913 +{
1914 + VirtioSharedMemory *shmem;
1915 + VhostUserMMap *vu_mmap = &payload->mmap;
1916 + VirtioSharedMemoryMapping *existing;
1917 + Error *local_err = NULL;
1918 + int ret = 0;
1919 +
1920 + if (fd < 0) {
1921 + error_report("Bad fd for map");
1922 + ret = -EBADF;
1923 + goto send_reply;
1924 + }
1925 +
1926 + if (QSIMPLEQ_EMPTY(&dev->vdev->shmem_list)) {
1927 + error_report("Device has no VIRTIO Shared Memory Regions. "
1928 + "Requested ID: %d", vu_mmap->shmid);
1929 + ret = -EFAULT;
1930 + goto send_reply;
1931 + }
1932 +
1933 + shmem = virtio_find_shmem_region(dev->vdev, vu_mmap->shmid);
1934 + if (!shmem) {
1935 + error_report("VIRTIO Shared Memory Region at "
1936 + "ID %d not found or uninitialized", vu_mmap->shmid);
1937 + ret = -EFAULT;
1938 + goto send_reply;
1939 + }
1940 +
1941 + if ((vu_mmap->shm_offset + vu_mmap->len) < vu_mmap->len ||
1942 + (vu_mmap->shm_offset + vu_mmap->len) > memory_region_size(&shmem->mr)) {
1943 + error_report("Bad offset/len for mmap %" PRIx64 "+%" PRIx64,
1944 + vu_mmap->shm_offset, vu_mmap->len);
1945 + ret = -EFAULT;
1946 + goto send_reply;
1947 + }
1948 +
1949 + QTAILQ_FOREACH(existing, &shmem->mmaps, link) {
1950 + if (ranges_overlap(existing->offset, existing->len,
1951 + vu_mmap->shm_offset, vu_mmap->len)) {
1952 + error_report("VIRTIO Shared Memory mapping overlap");
1953 + ret = -EFAULT;
1954 + goto send_reply;
1955 + }
1956 + }
1957 +
1958 + memory_region_transaction_begin();
1959 +
1960 + /* Create VirtioSharedMemoryMapping object */
1961 + VirtioSharedMemoryMapping *mapping = virtio_shared_memory_mapping_new(
1962 + vu_mmap->shmid, fd, vu_mmap->fd_offset, vu_mmap->shm_offset,
1963 + vu_mmap->len, vu_mmap->flags & VHOST_USER_FLAG_MAP_RW);
1964 +
1965 + if (!mapping) {
1966 + ret = -EFAULT;
1967 + goto send_reply_commit;
1968 + }
1969 +
1970 + /* Add the mapping to the shared memory region */
1971 + if (virtio_add_shmem_map(shmem, mapping) != 0) {
1972 + error_report("Failed to add shared memory mapping");
1973 + object_unref(OBJECT(mapping));
1974 + ret = -EFAULT;
1975 + goto send_reply_commit;
1976 + }
1977 +
1978 +send_reply_commit:
1979 + /* Send reply and commit after transaction started */
1980 + if (hdr->flags & VHOST_USER_NEED_REPLY_MASK) {
1981 + payload->u64 = !!ret;
1982 + hdr->size = sizeof(payload->u64);
1983 + if (!vhost_user_send_resp(ioc, hdr, payload, &local_err)) {
1984 + error_report_err(local_err);
1985 + memory_region_transaction_commit();
1986 + return -EFAULT;
1987 + }
1988 + }
1989 + memory_region_transaction_commit();
1990 + return 0;
1991 +
1992 +send_reply:
1993 + if (hdr->flags & VHOST_USER_NEED_REPLY_MASK) {
1994 + payload->u64 = !!ret;
1995 + hdr->size = sizeof(payload->u64);
1996 + if (!vhost_user_send_resp(ioc, hdr, payload, &local_err)) {
1997 + error_report_err(local_err);
1998 + return -EFAULT;
1999 + }
2000 + }
2001 + return 0;
2002 +}
2003 +
2004 +/**
2005 + * vhost_user_backend_handle_shmem_unmap() - Handle SHMEM_UNMAP backend request
2006 + * @dev: vhost device
2007 + * @ioc: QIOChannel for communication
2008 + * @hdr: vhost-user message header
2009 + * @payload: message payload containing unmapping details
2010 + *
2011 + * Handles VHOST_USER_BACKEND_SHMEM_UNMAP requests from the backend. Removes
2012 + * the specified memory mapping from the VirtIO shared memory region. This
2013 + * automatically unreferences the associated VhostUserShmemObject, which may
2014 + * trigger its finalization and cleanup (munmap, close fd) if no other
2015 + * references exist.
2016 + *
2017 + * Returns: 0 on success, negative errno on failure
2018 + */
2019 +static int
2020 +vhost_user_backend_handle_shmem_unmap(struct vhost_dev *dev,
2021 + QIOChannel *ioc,
2022 + VhostUserHeader *hdr,
2023 + VhostUserPayload *payload)
2024 +{
2025 + VirtioSharedMemory *shmem = NULL;
2026 + VirtioSharedMemoryMapping *mmap = NULL;
2027 + VhostUserMMap *vu_mmap = &payload->mmap;
2028 + Error *local_err = NULL;
2029 + int ret = 0;
2030 +
2031 + if (QSIMPLEQ_EMPTY(&dev->vdev->shmem_list)) {
2032 + error_report("Device has no VIRTIO Shared Memory Regions. "
2033 + "Requested ID: %d", vu_mmap->shmid);
2034 + ret = -EFAULT;
2035 + goto send_reply;
2036 + }
2037 +
2038 + shmem = virtio_find_shmem_region(dev->vdev, vu_mmap->shmid);
2039 + if (!shmem) {
2040 + error_report("VIRTIO Shared Memory Region at "
2041 + "ID %d not found or uninitialized", vu_mmap->shmid);
2042 + ret = -EFAULT;
2043 + goto send_reply;
2044 + }
2045 +
2046 + if ((vu_mmap->shm_offset + vu_mmap->len) < vu_mmap->len ||
2047 + (vu_mmap->shm_offset + vu_mmap->len) > memory_region_size(&shmem->mr)) {
2048 + error_report("Bad offset/len for unmmap %" PRIx64 "+%" PRIx64,
2049 + vu_mmap->shm_offset, vu_mmap->len);
2050 + ret = -EFAULT;
2051 + goto send_reply;
2052 + }
2053 +
2054 + mmap = virtio_find_shmem_map(shmem, vu_mmap->shm_offset, vu_mmap->len);
2055 + if (!mmap) {
2056 + error_report("Shared memory mapping not found at offset %" PRIx64
2057 + " with length %" PRIx64,
2058 + vu_mmap->shm_offset, vu_mmap->len);
2059 + ret = -EFAULT;
2060 + goto send_reply;
2061 + }
2062 +
2063 +send_reply:
2064 + if (hdr->flags & VHOST_USER_NEED_REPLY_MASK) {
2065 + payload->u64 = !!ret;
2066 + hdr->size = sizeof(payload->u64);
2067 + if (!vhost_user_send_resp(ioc, hdr, payload, &local_err)) {
2068 + error_report_err(local_err);
2069 + return -EFAULT;
2070 + }
2071 + }
2072 +
2073 + if (!ret && shmem && mmap) {
2074 + /* Free the MemoryRegion only after reply */
2075 + virtio_del_shmem_map(shmem, vu_mmap->shm_offset, vu_mmap->len);
2076 + }
2077 +
2078 + return 0;
2079 +}
2080 +
2081 static void close_backend_channel(struct vhost_user *u)
2082 {
2083 g_source_destroy(u->backend_src);
@@ -1933,6 +2151,21 @@ static gboolean backend_read(QIOChannel *ioc, GIOCondition condition,
2151 ret = vhost_user_backend_handle_shared_object_lookup(dev->opaque,
2152 &payload.object);
2153 break;
2154 + case VHOST_USER_BACKEND_SHMEM_MAP:
2155 + /* Handler manages its own response, check error and close connection */
2156 + reply_ack = false;
2157 + if (vhost_user_backend_handle_shmem_map(dev, ioc, &hdr, &payload,
2158 + fd ? fd[0] : -1) < 0) {
2159 + goto err;
2160 + }
2161 + break;
2162 + case VHOST_USER_BACKEND_SHMEM_UNMAP:
2163 + /* Handler manages its own response, check error and close connection */
2164 + reply_ack = false;
2165 + if (vhost_user_backend_handle_shmem_unmap(dev, ioc, &hdr, &payload) < 0) {
2166 + goto err;
2167 + }
2168 + break;
2169 default:
2170 error_report("Received unexpected msg type: %d.", hdr.request);
2171 ret = -EINVAL;
@@ -3123,6 +3356,46 @@ void vhost_user_qmp_status(struct vhost_dev *dev, VirtioStatus *status)
3356 qmp_decode_protocols(u->protocol_features);
3357 }
3358
3359 +static int vhost_user_get_shmem_config(struct vhost_dev *dev,
3360 + int *nregions,
3361 + uint64_t *memory_sizes,
3362 + Error **errp)
3363 +{
3364 + int ret;
3365 + VhostUserMsg msg = {
3366 + .hdr.request = VHOST_USER_GET_SHMEM_CONFIG,
3367 + .hdr.flags = VHOST_USER_VERSION,
3368 + };
3369 +
3370 + if (!vhost_user_has_protocol_feature(dev,
3371 + VHOST_USER_PROTOCOL_F_SHMEM)) {
3372 + *nregions = 0;
3373 + return 0;
3374 + }
3375 +
3376 + ret = vhost_user_write(dev, &msg, NULL, 0);
3377 + if (ret < 0) {
3378 + return ret;
3379 + }
3380 +
3381 + ret = vhost_user_read(dev, &msg);
3382 + if (ret < 0) {
3383 + return ret;
3384 + }
3385 +
3386 + if (msg.payload.shmem.nregions > VIRTIO_MAX_SHMEM_REGIONS) {
3387 + error_setg(errp, "Received too many shared memory regions: %d",
3388 + msg.payload.shmem.nregions);
3389 + return -EINVAL;
3390 + }
3391 +
3392 + *nregions = msg.payload.shmem.nregions;
3393 + memcpy(memory_sizes,
3394 + &msg.payload.shmem.memory_sizes,
3395 + sizeof(uint64_t) * VIRTIO_MAX_SHMEM_REGIONS);
3396 + return 0;
3397 +}
3398 +
3399 const VhostOps user_ops = {
3400 .backend_type = VHOST_BACKEND_TYPE_USER,
3401 .vhost_init = vhost_user_backend_init,
@@ -3163,4 +3436,5 @@ const VhostOps user_ops = {
3436 .vhost_check_device_state = vhost_user_check_device_state,
3437 .vhost_phys_vring_addr = vhost_user_gpa_addresses,
3438 .vhost_phys_iotlb_msg = vhost_user_gpa_addresses,
3439 + .vhost_get_shmem_config = vhost_user_get_shmem_config,
3440 };
hw/virtio/virtio.c
+201
@@ -3119,6 +3119,173 @@ int virtio_save(VirtIODevice *vdev, QEMUFile *f)
3119 return ret;
3120 }
3121
3122 +VirtioSharedMemory *virtio_new_shmem_region(VirtIODevice *vdev, uint8_t shmid, uint64_t size)
3123 +{
3124 + VirtioSharedMemory *elem;
3125 + g_autofree char *name = NULL;
3126 +
3127 + elem = g_new0(VirtioSharedMemory, 1);
3128 + elem->shmid = shmid;
3129 +
3130 + /* Initialize embedded MemoryRegion as container for shmem mappings */
3131 + name = g_strdup_printf("virtio-shmem-%d", shmid);
3132 + memory_region_init(&elem->mr, OBJECT(vdev), name, size);
3133 + QTAILQ_INIT(&elem->mmaps);
3134 + QSIMPLEQ_INSERT_TAIL(&vdev->shmem_list, elem, entry);
3135 + return elem;
3136 +}
3137 +
3138 +VirtioSharedMemory *virtio_find_shmem_region(VirtIODevice *vdev, uint8_t shmid)
3139 +{
3140 + VirtioSharedMemory *shmem, *next;
3141 + QSIMPLEQ_FOREACH_SAFE(shmem, &vdev->shmem_list, entry, next) {
3142 + if (shmem->shmid == shmid) {
3143 + return shmem;
3144 + }
3145 + }
3146 + return NULL;
3147 +}
3148 +
3149 +static void virtio_shared_memory_mapping_instance_init(Object *obj)
3150 +{
3151 + VirtioSharedMemoryMapping *mapping = VIRTIO_SHARED_MEMORY_MAPPING(obj);
3152 +
3153 + mapping->shmid = 0;
3154 + mapping->offset = 0;
3155 + mapping->len = 0;
3156 + mapping->mr = NULL;
3157 +}
3158 +
3159 +static void virtio_shared_memory_mapping_instance_finalize(Object *obj)
3160 +{
3161 + VirtioSharedMemoryMapping *mapping = VIRTIO_SHARED_MEMORY_MAPPING(obj);
3162 +
3163 + /* Clean up MemoryRegion if it exists */
3164 + if (mapping->mr) {
3165 + /* Unparent the MemoryRegion to trigger cleanup */
3166 + object_unparent(OBJECT(mapping->mr));
3167 + mapping->mr = NULL;
3168 + }
3169 +}
3170 +
3171 +VirtioSharedMemoryMapping *virtio_shared_memory_mapping_new(uint8_t shmid,
3172 + int fd,
3173 + uint64_t fd_offset,
3174 + uint64_t shm_offset,
3175 + uint64_t len,
3176 + bool allow_write)
3177 +{
3178 + VirtioSharedMemoryMapping *mapping;
3179 + MemoryRegion *mr;
3180 + g_autoptr(GString) mr_name = g_string_new(NULL);
3181 + uint32_t ram_flags;
3182 + Error *local_err = NULL;
3183 +
3184 + if (len == 0) {
3185 + error_report("Shared memory mapping size cannot be zero");
3186 + return NULL;
3187 + }
3188 +
3189 + fd = dup(fd);
3190 + if (fd < 0) {
3191 + error_report("Failed to duplicate fd: %s", strerror(errno));
3192 + return NULL;
3193 + }
3194 +
3195 + /* Determine RAM flags */
3196 + ram_flags = RAM_SHARED;
3197 + if (!allow_write) {
3198 + ram_flags |= RAM_READONLY_FD;
3199 + }
3200 +
3201 + /* Create the VirtioSharedMemoryMapping */
3202 + mapping = VIRTIO_SHARED_MEMORY_MAPPING(
3203 + object_new(TYPE_VIRTIO_SHARED_MEMORY_MAPPING));
3204 +
3205 + /* Set up object properties */
3206 + mapping->shmid = shmid;
3207 + mapping->offset = shm_offset;
3208 + mapping->len = len;
3209 +
3210 + /* Create MemoryRegion as a child of this object */
3211 + mr = g_new0(MemoryRegion, 1);
3212 + g_string_printf(mr_name, "virtio-shmem-%d-%" PRIx64, shmid, shm_offset);
3213 +
3214 + /* Initialize MemoryRegion with file descriptor */
3215 + if (!memory_region_init_ram_from_fd(mr, OBJECT(mapping), mr_name->str,
3216 + len, ram_flags, fd, fd_offset,
3217 + &local_err)) {
3218 + error_report_err(local_err);
3219 + g_free(mr);
3220 + close(fd);
3221 + object_unref(OBJECT(mapping));
3222 + return NULL;
3223 + }
3224 +
3225 + mapping->mr = mr;
3226 + return mapping;
3227 +}
3228 +
3229 +int virtio_add_shmem_map(VirtioSharedMemory *shmem,
3230 + VirtioSharedMemoryMapping *mapping)
3231 +{
3232 + if (!mapping) {
3233 + error_report("VirtioSharedMemoryMapping cannot be NULL");
3234 + return -1;
3235 + }
3236 + if (!mapping->mr) {
3237 + error_report("VirtioSharedMemoryMapping has no MemoryRegion");
3238 + return -1;
3239 + }
3240 +
3241 + /* Validate boundaries against the VIRTIO shared memory region */
3242 + if (mapping->offset + mapping->len > memory_region_size(&shmem->mr)) {
3243 + error_report("Memory exceeds the shared memory boundaries");
3244 + return -1;
3245 + }
3246 +
3247 + /* Add as subregion to the VIRTIO shared memory */
3248 + memory_region_add_subregion(&shmem->mr, mapping->offset, mapping->mr);
3249 +
3250 + /* Add to the mapped regions list */
3251 + QTAILQ_INSERT_TAIL(&shmem->mmaps, mapping, link);
3252 +
3253 + return 0;
3254 +}
3255 +
3256 +VirtioSharedMemoryMapping *virtio_find_shmem_map(VirtioSharedMemory *shmem,
3257 + hwaddr offset, uint64_t size)
3258 +{
3259 + VirtioSharedMemoryMapping *mapping;
3260 + QTAILQ_FOREACH(mapping, &shmem->mmaps, link) {
3261 + if (mapping->offset == offset && mapping->len == size) {
3262 + return mapping;
3263 + }
3264 + }
3265 + return NULL;
3266 +}
3267 +
3268 +void virtio_del_shmem_map(VirtioSharedMemory *shmem, hwaddr offset,
3269 + uint64_t size)
3270 +{
3271 + VirtioSharedMemoryMapping *mapping = virtio_find_shmem_map(shmem, offset, size);
3272 + if (mapping == NULL) {
3273 + return;
3274 + }
3275 +
3276 + /*
3277 + * Remove from memory region first
3278 + */
3279 + memory_region_del_subregion(&shmem->mr, mapping->mr);
3280 +
3281 + /*
3282 + * Remove from list and unref the mapping which will trigger automatic cleanup
3283 + * when the reference count reaches zero.
3284 + */
3285 + QTAILQ_REMOVE(&shmem->mmaps, mapping, link);
3286 + object_unref(OBJECT(mapping));
3287 +}
3288 +
3289 /* A wrapper for use as a VMState .put function */
3290 static int virtio_device_put(QEMUFile *f, void *opaque, size_t size,
3291 const VMStateField *field, JSONWriter *vmdesc)
@@ -3244,6 +3411,7 @@ int virtio_set_features_ex(VirtIODevice *vdev, const uint64_t *features)
3411 void virtio_reset(VirtIODevice *vdev)
3412 {
3413 VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
3414 + VirtioSharedMemory *shmem;
3415 uint64_t features[VIRTIO_FEATURES_NU64S];
3416 int i;
3417
@@ -3283,6 +3451,15 @@ void virtio_reset(VirtIODevice *vdev)
3451 for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
3452 __virtio_queue_reset(vdev, i);
3453 }
3454 +
3455 + /* Mappings are removed to prevent stale fds from remaining open. */
3456 + QSIMPLEQ_FOREACH(shmem, &vdev->shmem_list, entry) {
3457 + while (!QTAILQ_EMPTY(&shmem->mmaps)) {
3458 + VirtioSharedMemoryMapping *mapping = QTAILQ_FIRST(&shmem->mmaps);
3459 + virtio_del_shmem_map(shmem, mapping->offset,
3460 + memory_region_size(mapping->mr));
3461 + }
3462 + }
3463 }
3464
3465 static void virtio_device_check_notification_compatibility(VirtIODevice *vdev,
@@ -3606,6 +3783,7 @@ void virtio_init(VirtIODevice *vdev, uint16_t device_id, size_t config_size)
3783 NULL, virtio_vmstate_change, vdev);
3784 vdev->device_endian = virtio_default_endian();
3785 vdev->use_guest_notifier_mask = true;
3786 + QSIMPLEQ_INIT(&vdev->shmem_list);
3787 }
3788
3789 /*
@@ -4117,11 +4295,25 @@ static void virtio_device_free_virtqueues(VirtIODevice *vdev)
4295 static void virtio_device_instance_finalize(Object *obj)
4296 {
4297 VirtIODevice *vdev = VIRTIO_DEVICE(obj);
4298 + VirtioSharedMemory *shmem;
4299
4300 virtio_device_free_virtqueues(vdev);
4301
4302 g_free(vdev->config);
4303 g_free(vdev->vector_queues);
4304 + while (!QSIMPLEQ_EMPTY(&vdev->shmem_list)) {
4305 + shmem = QSIMPLEQ_FIRST(&vdev->shmem_list);
4306 + while (!QTAILQ_EMPTY(&shmem->mmaps)) {
4307 + VirtioSharedMemoryMapping *mapping = QTAILQ_FIRST(&shmem->mmaps);
4308 + virtio_del_shmem_map(shmem, mapping->offset,
4309 + memory_region_size(mapping->mr));
4310 + }
4311 +
4312 + /* Clean up the embedded MemoryRegion */
4313 + object_unparent(OBJECT(&shmem->mr));
4314 + QSIMPLEQ_REMOVE_HEAD(&vdev->shmem_list, entry);
4315 + g_free(shmem);
4316 + }
4317 }
4318
4319 static const Property virtio_properties[] = {
@@ -4487,9 +4679,18 @@ static const TypeInfo virtio_device_info = {
4679 .class_size = sizeof(VirtioDeviceClass),
4680 };
4681
4682 +static const TypeInfo virtio_shared_memory_mapping_info = {
4683 + .name = TYPE_VIRTIO_SHARED_MEMORY_MAPPING,
4684 + .parent = TYPE_OBJECT,
4685 + .instance_size = sizeof(VirtioSharedMemoryMapping),
4686 + .instance_init = virtio_shared_memory_mapping_instance_init,
4687 + .instance_finalize = virtio_shared_memory_mapping_instance_finalize,
4688 +};
4689 +
4690 static void virtio_register_types(void)
4691 {
4692 type_register_static(&virtio_device_info);
4693 + type_register_static(&virtio_shared_memory_mapping_info);
4694 }
4695
4696 type_init(virtio_register_types)
include/hw/virtio/virtio.h
+134
@@ -99,6 +99,44 @@ enum virtio_device_endian {
99 VIRTIO_DEVICE_ENDIAN_BIG,
100 };
101
102 +#define TYPE_VIRTIO_SHARED_MEMORY_MAPPING "virtio-shared-memory-mapping"
103 +OBJECT_DECLARE_SIMPLE_TYPE(VirtioSharedMemoryMapping, VIRTIO_SHARED_MEMORY_MAPPING)
104 +
105 +/**
106 + * VirtioSharedMemoryMapping:
107 + * @parent: Parent QOM object
108 + * @shmid: VIRTIO Shared Memory Region ID
109 + * @offset: Offset within the VIRTIO Shared Memory Region
110 + * @len: Size of the mapping
111 + * @mr: MemoryRegion associated with this shared memory mapping
112 + * @link: List entry for the shared memory region's mapping list
113 + *
114 + * A QOM object that represents an individual file descriptor-based shared
115 + * memory mapping within a VIRTIO Shared Memory Region. It manages the
116 + * MemoryRegion lifecycle and file descriptor cleanup through QOM reference
117 + * counting. When the object is unreferenced and its reference count drops
118 + * to zero, it automatically cleans up the MemoryRegion and closes the file
119 + * descriptor.
120 + */
121 +struct VirtioSharedMemoryMapping {
122 + Object parent;
123 +
124 + uint8_t shmid;
125 + hwaddr offset;
126 + uint64_t len;
127 + MemoryRegion *mr;
128 + QTAILQ_ENTRY(VirtioSharedMemoryMapping) link;
129 +};
130 +
131 +struct VirtioSharedMemory {
132 + uint8_t shmid;
133 + MemoryRegion mr;
134 + QTAILQ_HEAD(, VirtioSharedMemoryMapping) mmaps;
135 + QSIMPLEQ_ENTRY(VirtioSharedMemory) entry;
136 +};
137 +
138 +typedef struct VirtioSharedMemory VirtioSharedMemory;
139 +
140 /**
141 * struct VirtIODevice - common VirtIO structure
142 * @name: name of the device
@@ -168,6 +206,8 @@ struct VirtIODevice
206 */
207 EventNotifier config_notifier;
208 bool device_iotlb_enabled;
209 + /* Shared memory region for mappings. */
210 + QSIMPLEQ_HEAD(, VirtioSharedMemory) shmem_list;
211 };
212
213 struct VirtioDeviceClass {
@@ -298,6 +338,100 @@ void virtio_notify(VirtIODevice *vdev, VirtQueue *vq);
338
339 int virtio_save(VirtIODevice *vdev, QEMUFile *f);
340
341 +/**
342 + * virtio_new_shmem_region() - Create a new shared memory region
343 + * @vdev: VirtIODevice
344 + * @shmid: Shared memory ID
345 + * @size: Size of the shared memory region
346 + *
347 + * Creates a new VirtioSharedMemory region for the given device and ID.
348 + * The returned VirtioSharedMemory is owned by the VirtIODevice and will
349 + * be automatically freed when the device is destroyed. The caller
350 + * should not free the returned pointer.
351 + *
352 + * Returns: Pointer to the new VirtioSharedMemory region, or NULL on failure
353 + */
354 +VirtioSharedMemory *virtio_new_shmem_region(VirtIODevice *vdev, uint8_t shmid, uint64_t size);
355 +
356 +/**
357 + * virtio_find_shmem_region() - Find an existing shared memory region
358 + * @vdev: VirtIODevice
359 + * @shmid: Shared memory ID to find
360 + *
361 + * Finds an existing VirtioSharedMemory region by ID. The returned pointer
362 + * is owned by the VirtIODevice and should not be freed by the caller.
363 + *
364 + * Returns: Pointer to the VirtioSharedMemory region, or NULL if not found
365 + */
366 +VirtioSharedMemory *virtio_find_shmem_region(VirtIODevice *vdev, uint8_t shmid);
367 +
368 +/**
369 + * virtio_shared_memory_mapping_new() - Create a new VirtioSharedMemoryMapping
370 + * @shmid: VIRTIO Shared Memory Region ID
371 + * @fd: File descriptor for the shared memory
372 + * @fd_offset: Offset within the file descriptor
373 + * @shm_offset: Offset within the VIRTIO Shared Memory Region
374 + * @len: Size of the mapping
375 + * @allow_write: Whether to allow write access to the mapping
376 + *
377 + * Creates a new VirtioSharedMemoryMapping that manages a shared memory mapping.
378 + * The object will create a MemoryRegion using memory_region_init_ram_from_fd()
379 + * as a child object. When the object is finalized, it will automatically
380 + * clean up the MemoryRegion and close the file descriptor.
381 + *
382 + * Return: A new VirtioSharedMemoryMapping on success, NULL on error.
383 + */
384 +VirtioSharedMemoryMapping *virtio_shared_memory_mapping_new(uint8_t shmid,
385 + int fd,
386 + uint64_t fd_offset,
387 + uint64_t shm_offset,
388 + uint64_t len,
389 + bool allow_write);
390 +
391 +/**
392 + * virtio_add_shmem_map() - Add a memory mapping to a shared region
393 + * @shmem: VirtioSharedMemory region
394 + * @mapping: VirtioSharedMemoryMapping to add (transfers ownership)
395 + *
396 + * Adds a memory mapping to the shared memory region. The VirtioSharedMemoryMapping
397 + * ownership is transferred to the shared memory region and will be automatically
398 + * cleaned up through QOM reference counting when virtio_del_shmem_map() is
399 + * called or when the shared memory region is destroyed.
400 + *
401 + * Returns: 0 on success, negative errno on failure
402 + */
403 +int virtio_add_shmem_map(VirtioSharedMemory *shmem,
404 + VirtioSharedMemoryMapping *mapping);
405 +
406 +/**
407 + * virtio_find_shmem_map() - Find a memory mapping in a shared region
408 + * @shmem: VirtioSharedMemory region
409 + * @offset: Offset within the shared memory region
410 + * @size: Size of the mapping to find
411 + *
412 + * Finds an existing memory mapping that covers the specified range.
413 + * The returned VirtioSharedMemoryMapping is owned by the VirtioSharedMemory
414 + * region and should not be freed by the caller.
415 + *
416 + * Returns: Pointer to the VirtioSharedMemoryMapping, or NULL if not found
417 + */
418 +VirtioSharedMemoryMapping *virtio_find_shmem_map(VirtioSharedMemory *shmem,
419 + hwaddr offset, uint64_t size);
420 +
421 +/**
422 + * virtio_del_shmem_map() - Remove a memory mapping from a shared region
423 + * @shmem: VirtioSharedMemory region
424 + * @offset: Offset of the mapping to remove
425 + * @size: Size of the mapping to remove
426 + *
427 + * Removes a memory mapping from the shared memory region. This will
428 + * automatically unref the associated VhostUserShmemObject, which may
429 + * trigger its finalization and cleanup if no other references exist.
430 + * The mapping's MemoryRegion will be properly unmapped and cleaned up.
431 + */
432 +void virtio_del_shmem_map(VirtioSharedMemory *shmem, hwaddr offset,
433 + uint64_t size);
434 +
435 extern const VMStateInfo virtio_vmstate_info;
436
437 #define VMSTATE_VIRTIO_DEVICE \
include/system/memory.h
+1 -1
@@ -1456,6 +1456,7 @@ bool memory_region_init_ram_from_file(MemoryRegion *mr,
1456 const char *path,
1457 ram_addr_t offset,
1458 Error **errp);
1459 +#endif
1460
1461 /**
1462 * memory_region_init_ram_from_fd: Initialize RAM memory region with a
@@ -1485,7 +1486,6 @@ bool memory_region_init_ram_from_fd(MemoryRegion *mr,
1486 int fd,
1487 ram_addr_t offset,
1488 Error **errp);
1488 -#endif
1489
1490 /**
1491 * memory_region_init_ram_ptr: Initialize RAM memory region from a
subprojects/libvhost-user/libvhost-user.c
+70
@@ -1592,6 +1592,76 @@ vu_rm_shared_object(VuDev *dev, unsigned char uuid[UUID_LEN])
1592 return vu_send_message(dev, &msg);
1593 }
1594
1595 +bool
1596 +vu_shmem_map(VuDev *dev, uint8_t shmid, uint64_t fd_offset,
1597 + uint64_t shm_offset, uint64_t len, uint64_t flags, int fd)
1598 +{
1599 + VhostUserMsg vmsg = {
1600 + .request = VHOST_USER_BACKEND_SHMEM_MAP,
1601 + .size = sizeof(vmsg.payload.mmap),
1602 + .flags = VHOST_USER_VERSION,
1603 + .payload.mmap = {
1604 + .shmid = shmid,
1605 + .fd_offset = fd_offset,
1606 + .shm_offset = shm_offset,
1607 + .len = len,
1608 + .flags = flags,
1609 + },
1610 + .fd_num = 1,
1611 + .fds[0] = fd,
1612 + };
1613 +
1614 + if (!vu_has_protocol_feature(dev, VHOST_USER_PROTOCOL_F_SHMEM)) {
1615 + return false;
1616 + }
1617 +
1618 + if (vu_has_protocol_feature(dev, VHOST_USER_PROTOCOL_F_REPLY_ACK)) {
1619 + vmsg.flags |= VHOST_USER_NEED_REPLY_MASK;
1620 + }
1621 +
1622 + pthread_mutex_lock(&dev->backend_mutex);
1623 + if (!vu_message_write(dev, dev->backend_fd, &vmsg)) {
1624 + pthread_mutex_unlock(&dev->backend_mutex);
1625 + return false;
1626 + }
1627 +
1628 + /* Also unlocks the backend_mutex */
1629 + return vu_process_message_reply(dev, &vmsg);
1630 +}
1631 +
1632 +bool
1633 +vu_shmem_unmap(VuDev *dev, uint8_t shmid, uint64_t shm_offset, uint64_t len)
1634 +{
1635 + VhostUserMsg vmsg = {
1636 + .request = VHOST_USER_BACKEND_SHMEM_UNMAP,
1637 + .size = sizeof(vmsg.payload.mmap),
1638 + .flags = VHOST_USER_VERSION,
1639 + .payload.mmap = {
1640 + .shmid = shmid,
1641 + .fd_offset = 0,
1642 + .shm_offset = shm_offset,
1643 + .len = len,
1644 + },
1645 + };
1646 +
1647 + if (!vu_has_protocol_feature(dev, VHOST_USER_PROTOCOL_F_SHMEM)) {
1648 + return false;
1649 + }
1650 +
1651 + if (vu_has_protocol_feature(dev, VHOST_USER_PROTOCOL_F_REPLY_ACK)) {
1652 + vmsg.flags |= VHOST_USER_NEED_REPLY_MASK;
1653 + }
1654 +
1655 + pthread_mutex_lock(&dev->backend_mutex);
1656 + if (!vu_message_write(dev, dev->backend_fd, &vmsg)) {
1657 + pthread_mutex_unlock(&dev->backend_mutex);
1658 + return false;
1659 + }
1660 +
1661 + /* Also unlocks the backend_mutex */
1662 + return vu_process_message_reply(dev, &vmsg);
1663 +}
1664 +
1665 static bool
1666 vu_set_vring_call_exec(VuDev *dev, VhostUserMsg *vmsg)
1667 {
subprojects/libvhost-user/libvhost-user.h
+54
@@ -69,6 +69,8 @@ enum VhostUserProtocolFeature {
69 /* Feature 16 is reserved for VHOST_USER_PROTOCOL_F_STATUS. */
70 /* Feature 17 reserved for VHOST_USER_PROTOCOL_F_XEN_MMAP. */
71 VHOST_USER_PROTOCOL_F_SHARED_OBJECT = 18,
72 + /* Feature 19 is reserved for VHOST_USER_PROTOCOL_F_DEVICE_STATE */
73 + VHOST_USER_PROTOCOL_F_SHMEM = 20,
74 VHOST_USER_PROTOCOL_F_MAX
75 };
76
@@ -127,6 +129,8 @@ typedef enum VhostUserBackendRequest {
129 VHOST_USER_BACKEND_SHARED_OBJECT_ADD = 6,
130 VHOST_USER_BACKEND_SHARED_OBJECT_REMOVE = 7,
131 VHOST_USER_BACKEND_SHARED_OBJECT_LOOKUP = 8,
132 + VHOST_USER_BACKEND_SHMEM_MAP = 9,
133 + VHOST_USER_BACKEND_SHMEM_UNMAP = 10,
134 VHOST_USER_BACKEND_MAX
135 } VhostUserBackendRequest;
136
@@ -186,6 +190,23 @@ typedef struct VhostUserShared {
190 unsigned char uuid[UUID_LEN];
191 } VhostUserShared;
192
193 +/* For the flags field of VhostUserMMap */
194 +#define VHOST_USER_FLAG_MAP_RW (1u << 0)
195 +
196 +typedef struct {
197 + /* VIRTIO Shared Memory Region ID */
198 + uint8_t shmid;
199 + uint8_t padding[7];
200 + /* File offset */
201 + uint64_t fd_offset;
202 + /* Offset within the VIRTIO Shared Memory Region */
203 + uint64_t shm_offset;
204 + /* Size of the mapping */
205 + uint64_t len;
206 + /* Flags for the mmap operation, from VHOST_USER_FLAG_MAP_* */
207 + uint64_t flags;
208 +} VhostUserMMap;
209 +
210 #define VU_PACKED __attribute__((packed))
211
212 typedef struct VhostUserMsg {
@@ -210,6 +231,7 @@ typedef struct VhostUserMsg {
231 VhostUserVringArea area;
232 VhostUserInflight inflight;
233 VhostUserShared object;
234 + VhostUserMMap mmap;
235 } payload;
236
237 int fds[VHOST_MEMORY_BASELINE_NREGIONS];
@@ -593,6 +615,38 @@ bool vu_add_shared_object(VuDev *dev, unsigned char uuid[UUID_LEN]);
615 */
616 bool vu_rm_shared_object(VuDev *dev, unsigned char uuid[UUID_LEN]);
617
618 +/**
619 + * vu_shmem_map:
620 + * @dev: a VuDev context
621 + * @shmid: VIRTIO Shared Memory Region ID
622 + * @fd_offset: File offset
623 + * @shm_offset: Offset within the VIRTIO Shared Memory Region
624 + * @len: Size of the mapping
625 + * @flags: Flags for the mmap operation
626 + * @fd: A file descriptor
627 + *
628 + * Advertises a new mapping to be made in a given VIRTIO Shared Memory Region.
629 + *
630 + * Returns: TRUE on success, FALSE on failure.
631 + */
632 +bool vu_shmem_map(VuDev *dev, uint8_t shmid, uint64_t fd_offset,
633 + uint64_t shm_offset, uint64_t len, uint64_t flags, int fd);
634 +
635 +/**
636 + * vu_shmem_unmap:
637 + * @dev: a VuDev context
638 + * @shmid: VIRTIO Shared Memory Region ID
639 + * @fd_offset: File offset
640 + * @len: Size of the mapping
641 + *
642 + * The front-end un-mmaps a given range in the VIRTIO Shared Memory Region
643 + * with the requested `shmid`.
644 + *
645 + * Returns: TRUE on success, FALSE on failure.
646 + */
647 +bool vu_shmem_unmap(VuDev *dev, uint8_t shmid, uint64_t shm_offset,
648 + uint64_t len);
649 +
650 /**
651 * vu_queue_set_notification:
652 * @dev: a VuDev context
system/memory.c
+14
@@ -1652,6 +1652,20 @@ bool memory_region_init_ram_from_fd(MemoryRegion *mr, Object *owner,
1652 false, errp);
1653 return memory_region_set_ram_block(mr, rb);
1654 }
1655 +#else
1656 +bool memory_region_init_ram_from_fd(MemoryRegion *mr,
1657 + Object *owner,
1658 + const char *name,
1659 + uint64_t size,
1660 + uint32_t ram_flags,
1661 + int fd,
1662 + ram_addr_t offset,
1663 + Error **errp)
1664 +{
1665 + error_setg(errp,
1666 + "memory_region_init_ram_from_fd is not supported on this platform");
1667 + return false;
1668 +}
1669 #endif
1670
1671 static void memory_region_set_ram_ptr(MemoryRegion *mr, uint64_t size,