Virtio-mem dynamic memslots are marked unmergeable so listeners can
track their lifetimes independently. A vring part crossing the boundary
between two such slots consequently cannot be contained in a single
vhost memory region.

Coalesce adjacent unmergeable sections only when a descriptor table,
available ring, or used ring spans their boundary and the sections
preserve a coherent GPA-to-HVA translation. Keep unrelated slots
separate so activating them does not reshape the region containing the
vring.

Fixes: 533f5d667909 ("memory,vhost: Allow for marking memory device memory 
regions unmergeable")

Buglink: https://redhat.atlassian.net/browse/RHEL-146583

Signed-off-by: Alex Fishman <[email protected]>
---
 hw/virtio/vhost.c | 72 +++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 67 insertions(+), 5 deletions(-)

diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c
index 371dca17dd..76910e2628 100644
--- a/hw/virtio/vhost.c
+++ b/hw/virtio/vhost.c
@@ -796,6 +796,70 @@ out:
     g_free(old_sections);
 }
 
+static bool vhost_vring_part_crosses_boundary(uint64_t ring_gpa,
+                                              uint64_t ring_size,
+                                              uint64_t boundary)
+{
+    return ring_size && ring_gpa < boundary &&
+           range_get_last(ring_gpa, ring_size) >= boundary;
+}
+
+static bool vhost_vring_crosses_boundary(struct vhost_dev *dev,
+                                         uint64_t boundary)
+{
+    int i;
+
+    if (vhost_dev_has_iommu(dev)) {
+        return false;
+    }
+
+    for (i = 0; i < dev->nvqs; i++) {
+        struct vhost_virtqueue *vq = &dev->vqs[i];
+
+        if (vhost_vring_part_crosses_boundary(vq->desc_phys, vq->desc_size,
+                                              boundary) ||
+            vhost_vring_part_crosses_boundary(vq->avail_phys, vq->avail_size,
+                                              boundary) ||
+            vhost_vring_part_crosses_boundary(vq->used_phys, vq->used_size,
+                                              boundary)) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+static bool vhost_sections_can_merge(struct vhost_dev *dev,
+                                     const MemoryRegionSection *prev_sec,
+                                     const MemoryRegionSection *section,
+                                     uint64_t section_gpa,
+                                     uintptr_t section_host)
+{
+    uint64_t prev_gpa_start = prev_sec->offset_within_address_space;
+    uintptr_t prev_host_start =
+        (uintptr_t)memory_region_get_ram_ptr(prev_sec->mr) +
+        prev_sec->offset_within_region;
+    uint64_t offset;
+
+    if (section->mr != prev_sec->mr || section_gpa < prev_gpa_start) {
+        return false;
+    }
+
+    offset = section_gpa - prev_gpa_start;
+
+    if (prev_host_start + offset != section_host) {
+        return false;
+    }
+
+    if (!prev_sec->unmergeable && !section->unmergeable) {
+        return true;
+    }
+
+    /* Only override an unmergeable boundary when a ring part spans it. */
+    return vhost_vring_crosses_boundary(
+        dev, section->offset_within_address_space);
+}
+
 /* Adds the section data to the tmp_section structure.
  * It relies on the listener calling us in memory address order
  * and for each region (via the _add and _nop methods) to
@@ -833,7 +897,7 @@ static void vhost_region_add_section(struct vhost_dev *dev,
                                                mrs_size, mrs_host);
     }
 
-    if (dev->n_tmp_sections && !section->unmergeable) {
+    if (dev->n_tmp_sections) {
         /* Since we already have at least one section, lets see if
          * this extends it; since we're scanning in order, we only
          * have to look at the last one, and the FlatView that calls
@@ -862,11 +926,9 @@ static void vhost_region_add_section(struct vhost_dev *dev,
                 /* A way to cleanly fail here would be better */
                 return;
             }
-            /* Offset from the start of the previous GPA to this GPA */
-            size_t offset = mrs_gpa - prev_gpa_start;
 
-            if (prev_host_start + offset == mrs_host &&
-                section->mr == prev_sec->mr && !prev_sec->unmergeable) {
+            if (vhost_sections_can_merge(dev, prev_sec, section,
+                                         mrs_gpa, mrs_host)) {
                 uint64_t max_end = MAX(prev_host_end, mrs_host + mrs_size);
                 need_add = false;
                 prev_sec->offset_within_address_space =
-- 
2.52.0


Reply via email to