Someone else with more knowledge of the VQ mapping code should also review.
On Fri, Feb 2, 2024 at 4:55 PM David Hildenbrand <da...@redhat.com> wrote: > > Currently, we try to remap all rings whenever we add a single new memory > region. That doesn't quite make sense, because we already map rings when > setting the ring address, and panic if that goes wrong. Likely, that > handling was simply copied from set_mem_table code, where we actually > have to remap all rings. > > Remapping all rings might require us to walk quite a lot of memory > regions to perform the address translations. Ideally, we'd simply remove > that remapping. > > However, let's be a bit careful. There might be some weird corner cases > where we might temporarily remove a single memory region (e.g., resize > it), that would have worked for now. Further, a ring might be located on > hotplugged memory, and as the VM reboots, we might unplug that memory, to > hotplug memory before resetting the ring addresses. > > So let's unmap affected rings as we remove a memory region, and try > dynamically mapping the ring again when required. > > Signed-off-by: David Hildenbrand <da...@redhat.com> Acked-by: Raphael Norwitz <raph...@enfabrica.net> > --- > subprojects/libvhost-user/libvhost-user.c | 107 ++++++++++++++++------ > 1 file changed, 78 insertions(+), 29 deletions(-) > > diff --git a/subprojects/libvhost-user/libvhost-user.c > b/subprojects/libvhost-user/libvhost-user.c > index febeb2eb89..738e84ab63 100644 > --- a/subprojects/libvhost-user/libvhost-user.c > +++ b/subprojects/libvhost-user/libvhost-user.c > @@ -283,10 +283,75 @@ vu_remove_all_mem_regs(VuDev *dev) > dev->nregions = 0; > } > > +static bool > +map_ring(VuDev *dev, VuVirtq *vq) > +{ > + vq->vring.desc = qva_to_va(dev, vq->vra.desc_user_addr); > + vq->vring.used = qva_to_va(dev, vq->vra.used_user_addr); > + vq->vring.avail = qva_to_va(dev, vq->vra.avail_user_addr); > + > + DPRINT("Setting virtq addresses:\n"); > + DPRINT(" vring_desc at %p\n", vq->vring.desc); > + DPRINT(" vring_used at %p\n", vq->vring.used); > + DPRINT(" vring_avail at %p\n", vq->vring.avail); > + > + return !(vq->vring.desc && vq->vring.used && vq->vring.avail); > +} > + > static bool Consider changing the function name to indicate that it may actually map a vq? Maybe vu_maybe_map_vq()? > vu_is_vq_usable(VuDev *dev, VuVirtq *vq) > { > - return likely(!dev->broken) && likely(vq->vring.avail); > + if (unlikely(dev->broken)) { > + return false; > + } > + > + if (likely(vq->vring.avail)) { > + return true; > + } > + > + /* > + * In corner cases, we might temporarily remove a memory region that > + * mapped a ring. When removing a memory region we make sure to > + * unmap any rings that would be impacted. Let's try to remap if we > + * already succeeded mapping this ring once. > + */ > + if (!vq->vra.desc_user_addr || !vq->vra.used_user_addr || > + !vq->vra.avail_user_addr) { > + return false; > + } > + if (map_ring(dev, vq)) { > + vu_panic(dev, "remapping queue on access"); > + return false; > + } > + return true; > +} > + > +static void > +unmap_rings(VuDev *dev, VuDevRegion *r) > +{ > + int i; > + > + for (i = 0; i < dev->max_queues; i++) { > + VuVirtq *vq = &dev->vq[i]; > + const uintptr_t desc = (uintptr_t)vq->vring.desc; > + const uintptr_t used = (uintptr_t)vq->vring.used; > + const uintptr_t avail = (uintptr_t)vq->vring.avail; > + > + if (desc < r->mmap_addr || desc >= r->mmap_addr + r->size) { > + continue; > + } > + if (used < r->mmap_addr || used >= r->mmap_addr + r->size) { > + continue; > + } > + if (avail < r->mmap_addr || avail >= r->mmap_addr + r->size) { > + continue; > + } > + > + DPRINT("Unmapping rings of queue %d\n", i); > + vq->vring.desc = NULL; > + vq->vring.used = NULL; > + vq->vring.avail = NULL; > + } > } > > static size_t > @@ -784,21 +849,6 @@ vu_reset_device_exec(VuDev *dev, VhostUserMsg *vmsg) > return false; > } > > -static bool > -map_ring(VuDev *dev, VuVirtq *vq) > -{ > - vq->vring.desc = qva_to_va(dev, vq->vra.desc_user_addr); > - vq->vring.used = qva_to_va(dev, vq->vra.used_user_addr); > - vq->vring.avail = qva_to_va(dev, vq->vra.avail_user_addr); > - > - DPRINT("Setting virtq addresses:\n"); > - DPRINT(" vring_desc at %p\n", vq->vring.desc); > - DPRINT(" vring_used at %p\n", vq->vring.used); > - DPRINT(" vring_avail at %p\n", vq->vring.avail); > - > - return !(vq->vring.desc && vq->vring.used && vq->vring.avail); > -} > - > static bool > generate_faults(VuDev *dev) { > unsigned int i; > @@ -882,7 +932,6 @@ generate_faults(VuDev *dev) { > > static bool > vu_add_mem_reg(VuDev *dev, VhostUserMsg *vmsg) { > - int i; > VhostUserMemoryRegion m = vmsg->payload.memreg.region, *msg_region = &m; > > if (vmsg->fd_num != 1) { > @@ -928,19 +977,9 @@ vu_add_mem_reg(VuDev *dev, VhostUserMsg *vmsg) { > vmsg->fd_num = 0; > DPRINT("Successfully added new region in postcopy\n"); > return true; > - } else { > - for (i = 0; i < dev->max_queues; i++) { > - if (dev->vq[i].vring.desc) { > - if (map_ring(dev, &dev->vq[i])) { > - vu_panic(dev, "remapping queue %d for new memory region", > - i); > - } > - } > - } > - > - DPRINT("Successfully added new region\n"); > - return false; > } > + DPRINT("Successfully added new region\n"); > + return false; > } > > static inline bool reg_equal(VuDevRegion *vudev_reg, > @@ -993,6 +1032,16 @@ vu_rem_mem_reg(VuDev *dev, VhostUserMsg *vmsg) { > return false; > } > > + /* > + * There might be valid cases where we temporarily remove memory regions > + * to readd them again, or remove memory regions and don't use the rings > + * anymore before we set the ring addresses and restart the device. > + * > + * Unmap all affected rings, remapping them on demand later. This should > + * be a corner case. > + */ > + unmap_rings(dev, r); > + > munmap((void *)(uintptr_t)r->mmap_addr, r->size + r->mmap_offset); > > idx = r - dev->regions; > -- > 2.43.0 > >