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