Re: [PATCH RFC 14/15] hw/virtio/vhost-user: handle data movement with shadow vqs
Akihiko Odaki <[email protected]> Sat, 25 Jul 2026 00:20:20 +0900
| Newsgroups | dev.linux.lists.virtio-fs,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
On 2026/07/24 7:30, Connor Kite wrote: > - Add start logic for shadow virtqueues, which sets vring addresses. > - Update logic for sending vring addresses to backend to point > to the shadow vrings when isolation mode is active. > - Implement handlers for intercepted avail and used descriptors. These > handlers copy buffer contents between bounce buffers in the isolation > region and the buffers made available by the guest > - Implement logic to stop svqs > > Signed-off-by: Connor Kite <[email protected]> > --- > hw/virtio/vhost-user.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 166 insertions(+), 1 deletion(-) > > diff --git a/hw/virtio/vhost-user.c b/hw/virtio/vhost-user.c > index 75858289a2..e65f877f9a 100644 > --- a/hw/virtio/vhost-user.c > +++ b/hw/virtio/vhost-user.c > @@ -1299,6 +1299,97 @@ static int init_isolation_regions(struct vhost_dev *dev, > return 0; > } > > +static int vhost_user_memory_lookup(struct vhost_dev *dev, hwaddr gpa, > + hwaddr *hva) > +{ > + int i; > + hwaddr offset; > + > + for (i = 0; i < dev->mem->nregions; i++) { > + struct vhost_memory_region *reg = dev->mem->regions + i; > + > + if (gpa >= reg->guest_phys_addr && > + reg->guest_phys_addr + reg->memory_size > gpa) { > + offset = gpa - reg->guest_phys_addr; > + *hva = reg->userspace_addr + offset; > + return 0; > + } > + } > + > + return -EFAULT; > +} > + > +static int vhost_user_svq_handle_used(VhostShadowVirtqueue *svq, > + VirtQueueElement *elem, > + void *opaque) > +{ > + hwaddr hva; > + int r; > + struct vhost_dev *dev = opaque; > + > + for (int i = 0; i < elem->in_num; i++) { > + r = vhost_user_memory_lookup(dev, elem->in_addr[i], &hva); > + if (r < 0) { > + return r; > + } > + > + memcpy((void *) hva, elem->in_sg[i].iov_base, elem->in_sg[i].iov_len); > + elem->in_sg[i].iov_base = (void *) hva; > + } > + > + for (int i = 0; i < elem->out_num; i++) { > + r = vhost_user_memory_lookup(dev, elem->out_addr[i], &hva); > + if (r < 0) { > + return r; > + } > + > + memcpy((void *) hva, elem->out_sg[i].iov_base, elem->out_sg[i].iov_len); > + elem->out_sg[i].iov_base = (void *) hva; > + } This copies data in the reversed direction. > + > + return 0; > +} > + > +static int vhost_user_svq_handle_avail(VhostShadowVirtqueue *svq, > + VirtQueueElement *elem, > + void *opaque) > +{ > + hwaddr offset; > + const DMAMap *map; > + DMAMap needle; > + hwaddr *iova_base; Please make it void *. > + > + for (int i = 0; i < elem->out_num; i++) { > + needle.translated_addr = elem->out_addr[i]; > + needle.size = elem->out_sg[i].iov_len - 1; > + map = vhost_iova_tree_find_gpa(svq->iova_tree, &needle); An error check is missing here. This translation does not seem to support IOMMU. > + offset = needle.translated_addr - map->translated_addr; > + iova_base = (void *)(map->iova + offset); > + > + elem->out_sg[i].iov_base = iova_base; elem->out_sg shouldn't be overwritten since virtqueue_unpop() will use it to unmap the element. > + memcpy(iova_base, elem->out_sg[i].iov_base, needle.size + 1); This memcpy() is no-op. elem->out_sg[i].iov_base is already set to iova_base, so it's copying from iova_base to iova_base. > + } > + > + for (int i = 0; i < elem->in_num; i++) { > + needle.translated_addr = elem->in_addr[i]; > + needle.size = elem->in_sg[i].iov_len - 1; > + map = vhost_iova_tree_find_gpa(svq->iova_tree, &needle); > + offset = needle.translated_addr - map->translated_addr; > + iova_base = (void *)(map->iova + offset); > + > + elem->in_sg[i].iov_base = iova_base; > + memcpy(iova_base, elem->in_sg[i].iov_base, needle.size + 1); > + } > + > + vhost_svq_add(svq, elem->out_sg, elem->out_num, elem->out_addr, > + elem->in_sg, elem->in_num, elem->in_addr, elem); An error is ignored here too. > + > + return 0; > +} > + > + > + > + > static int vhost_user_set_mem_table(struct vhost_dev *dev, > struct vhost_memory *mem) > { > @@ -1772,6 +1863,8 @@ static int vhost_user_set_vring_err(struct vhost_dev *dev, > static int vhost_user_set_vring_addr(struct vhost_dev *dev, > struct vhost_vring_addr *addr) > { > + struct vhost_user *u = dev->opaque; > + > VhostUserMsg msg = { > .hdr.request = VHOST_USER_SET_VRING_ADDR, > .hdr.flags = VHOST_USER_VERSION, > @@ -1779,6 +1872,25 @@ static int vhost_user_set_vring_addr(struct vhost_dev *dev, > .hdr.size = sizeof(msg.payload.addr), > }; > > + if (u->user->memory_isolation) { > + if (!u->svqs_allocated) { > + return 0; > + } > + > + int svq_idx = addr->index - dev->vq_index; > + VhostShadowVirtqueue *svq = g_ptr_array_index(u->shadow_vqs, > + svq_idx); > + > + struct vhost_vring_addr svq_addr = { > + .avail_user_addr = (uint64_t)(uintptr_t)svq->vring.avail, > + .desc_user_addr = (uint64_t)(uintptr_t)svq->vring.desc, > + .used_user_addr = (uint64_t)(uintptr_t)svq->vring.used, > + .index = addr->index, > + }; > + > + msg.payload.addr = svq_addr; > + } > + > /* > * wait for a reply if logging is enabled to make sure > * backend is actually logging changes > @@ -2754,13 +2866,18 @@ static int vhost_user_postcopy_notifier(NotifierWithReturn *notifier, > return 0; > } > > +static const VhostShadowVirtqueueOps vhost_user_svq_ops = { > + .avail_handler = vhost_user_svq_handle_avail, > + .used_handler = vhost_user_svq_handle_used > +}; > + > static void vhost_user_init_svq(struct vhost_dev *dev, struct vhost_user *u) > { > /*Modified from vhost-vdpa*/ > u->shadow_vqs = g_ptr_array_new_full(dev->nvqs, vhost_svq_free); > for (int i = 0; i < dev->nvqs; i++) { > VhostShadowVirtqueue *svq; > - svq = vhost_svq_new(NULL, NULL); > + svq = vhost_svq_new(&vhost_user_svq_ops, dev); > g_ptr_array_add(u->shadow_vqs, svq); > } > } > @@ -3466,8 +3583,56 @@ void vhost_user_async_close(DeviceState *d, > } > } > > +static bool vhost_user_svqs_start(struct vhost_dev *dev) > +{ > + struct vhost_user *u = dev->opaque; > + uint64_t vring_base = u->iso_memory.vring_base_addr; > + u->svqs_allocated = true; > + > + for (int i = 0; i < u->shadow_vqs->len; i++) { > + VirtQueue *vq = virtio_get_queue(dev->vdev, dev->vq_index + i); > + VhostShadowVirtqueue *svq = g_ptr_array_index(u->shadow_vqs, i); > + svq->base_addr = (hwaddr *) vring_base; > + vhost_svq_start(svq, dev->vdev, vq, u->iso_iova_tree); Calling vhost_svq_start() here overwrites the shadow vring index configured in vhost_virtqueue_start(). > + > + struct vhost_vring_addr addr = { > + .index = dev->vq_index + i, > + .desc_user_addr = vring_base, > + .avail_user_addr = vring_base + sizeof(vring_desc_t) * > + svq->vring.num, This assumes the ring is split, but VIRTIO_F_RING_PACKED is not rejected. > + .used_user_addr = vring_base + vhost_svq_driver_area_size(svq) > + }; > + > + vhost_user_set_vring_addr(dev, &addr); The initial ring startup is ordered incorrectly. Generic startup suppresses SET_VRING_ADDR, installs SET_VRING_KICK, and injects a kick; only the later backend-start hook creates the shadow ring and sends its address. SET_VRING_KICK starts the ring, so the backend can process before receiving valid addresses. The late SET_VRING_ADDR result is also ignored. Regards, Akihiko Odaki > + > + vring_base += vhost_svq_device_area_size(svq) + > + vhost_svq_driver_area_size(svq); > + } > + > + return false; > +} > + > +static void vhost_user_svqs_stop(struct vhost_dev *dev) > +{ > + struct vhost_user *u = dev->opaque; > + > + for (int i = 0; i < u->shadow_vqs->len; i++) { > + vhost_svq_stop(g_ptr_array_index(u->shadow_vqs, i)); > + } > +} > + > + > static int vhost_user_dev_start(struct vhost_dev *dev, bool started) > { > + struct vhost_user *u = dev->opaque; > + if (u->user->memory_isolation) { > + if (started) { > + vhost_user_svqs_start(dev); > + } else { > + vhost_user_svqs_stop(dev); > + } > + } > + > if (!vhost_user_has_protocol_feature(dev, VHOST_USER_PROTOCOL_F_STATUS)) { > return 0; > } >