[RFC PATCH] hw/i386/pc: split ram-below-4g at 1MB and prevent cross-boundary merging

Zhiyu Hu posted 1 patch 5 days, 14 hours ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260406025828.22909-1-huzy4@chinatelecom.cn
Maintainers: "Michael S. Tsirkin" <mst@redhat.com>, Marcel Apfelbaum <marcel.apfelbaum@gmail.com>, Paolo Bonzini <pbonzini@redhat.com>, Richard Henderson <richard.henderson@linaro.org>, Peter Xu <peterx@redhat.com>, "Philippe Mathieu-Daudé" <philmd@linaro.org>
hw/i386/pc.c    | 30 +++++++++++++++++++++++++++---
system/memory.c | 13 +++++++++++++
2 files changed, 40 insertions(+), 3 deletions(-)
[RFC PATCH] hw/i386/pc: split ram-below-4g at 1MB and prevent cross-boundary merging
Posted by Zhiyu Hu 5 days, 14 hours ago
Split ram-below-4g into ram-below-1m and ram-1m-to-below-4g at the
1MB boundary in pc_memory_init().

Also prevent flatview_simplify() from merging FlatRanges across the
1MB boundary by adding a boundary check in can_merge(). Without this,
PAM attribute changes below 1MB cause the below-1MB range to re-merge
with the above-1MB range, triggering VFIO to unmap/remap the entire
merged region and creating a transient IOMMU mapping hole for active
device DMA (e.g. virtio-blk virtqueues) above 1MB.

Signed-off-by: Zhiyu Hu <huzy4@chinatelecom.cn>
---
 hw/i386/pc.c    | 30 +++++++++++++++++++++++++++---
 system/memory.c | 13 +++++++++++++
 2 files changed, 40 insertions(+), 3 deletions(-)

diff --git a/hw/i386/pc.c b/hw/i386/pc.c
index 4b53b5be4a..4e9823e46f 100644
--- a/hw/i386/pc.c
+++ b/hw/i386/pc.c
@@ -790,11 +790,35 @@ void pc_memory_init(PCMachineState *pcms,
     /*
      * Split single memory region and use aliases to address portions of it,
      * done for backwards compatibility with older qemus.
+     *
+     * Further split the below-4g region at the 1MB boundary so that PAM
+     * register updates (which only affect 0xC0000-0xFFFFF) do not cause
+     * the VFIO DMA listener to unmap/remap the entire below-4g range.
+     * This avoids a transient IOMMU mapping hole for virtqueue memory
+     * (above 1MB) when the BIOS reconfigures PAM during early boot.
      */
     ram_below_4g = g_malloc(sizeof(*ram_below_4g));
-    memory_region_init_alias(ram_below_4g, NULL, "ram-below-4g", machine->ram,
-                             0, x86ms->below_4g_mem_size);
-    memory_region_add_subregion(system_memory, 0, ram_below_4g);
+    if (x86ms->below_4g_mem_size <= 0x100000) {
+        memory_region_init_alias(ram_below_4g, NULL, "ram-below-4g",
+                                 machine->ram,
+                                 0, x86ms->below_4g_mem_size);
+        memory_region_add_subregion(system_memory, 0, ram_below_4g);
+    } else {
+        MemoryRegion *ram_above_1m = g_malloc(sizeof(*ram_above_1m));
+
+        /* 0 ~ 1MB: affected by PAM/SMRAM overlays */
+        memory_region_init_alias(ram_below_4g, NULL, "ram-below-1m",
+                                 machine->ram,
+                                 0, 0x100000);
+        memory_region_add_subregion(system_memory, 0, ram_below_4g);
+
+        /* 1MB ~ below_4g_mem_size: not affected by PAM updates */
+        memory_region_init_alias(ram_above_1m, NULL, "ram-1m-to-below-4g",
+                                 machine->ram,
+                                 0x100000,
+                                 x86ms->below_4g_mem_size - 0x100000);
+        memory_region_add_subregion(system_memory, 0x100000, ram_above_1m);
+    }
     e820_add_entry(0, x86ms->below_4g_mem_size, E820_RAM);
     if (x86ms->above_4g_mem_size > 0) {
         ram_above_4g = g_malloc(sizeof(*ram_above_4g));
diff --git a/system/memory.c b/system/memory.c
index 56f3225b21..28fc12e992 100644
--- a/system/memory.c
+++ b/system/memory.c
@@ -321,6 +321,19 @@ void flatview_unref(FlatView *view)
 
 static bool can_merge(FlatRange *r1, FlatRange *r2)
 {
+    /*
+     * Do not merge ranges across the 1MB (0x100000) boundary.
+     * PAM/SMRAM overlays frequently change attributes below 1MB,
+     * and merging with the large RAM region above 1MB would cause
+     * VFIO to unmap/remap the entire merged range on every PAM
+     * update, creating a transient IOMMU mapping hole for device
+     * DMA (e.g. virtio-blk virtqueues) in the above-1MB region.
+     */
+    if (int128_lt(r1->addr.start, int128_make64(0x100000))
+        && int128_ge(r2->addr.start, int128_make64(0x100000))) {
+        return false;
+    }
+
     return int128_eq(addrrange_end(r1->addr), r2->addr.start)
         && r1->mr == r2->mr
         && int128_eq(int128_add(int128_make64(r1->offset_in_region),
-- 
2.43.0