From: Laszlo Ersek <lersek@redhat.com>
Date: Tue, 6 Aug 2013 12:37:10 +0200
Subject: dump: populate guest_phys_blocks

While the machine is paused, in guest_phys_blocks_append() we register a
one-shot MemoryListener, solely for the initial collection of the valid
guest-physical memory ranges that happens at listener registration time.

For each range that is reported to guest_phys_blocks_region_add(), we
attempt to merge the range with the preceding one.

Ranges can only be joined if they are contiguous in both guest-physical
address space, and contiguous in host virtual address space.

The "maximal" ranges that remain in the end constitute the guest-physical
memory map that the dump will be based on.

Related RHBZ: https://bugzilla.redhat.com/show_bug.cgi?id=981582

Signed-off-by: Laszlo Ersek <lersek@redhat.com>
Signed-off-by: Luiz Capitulino <lcapitulino@redhat.com>
(cherry picked from commit c5d7f60f0614250bd925071e25220ce5958f75d0)
[BR: BSC#1049785]
Signed-off-by: Bruce Rogers <brogers@suse.com>
---
 dump.c                          |  2 +-
 include/sysemu/memory_mapping.h |  1 +
 memory_mapping.c                | 84 +++++++++++++++++++++++++++++++++
 3 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/dump.c b/dump.c
index bfdb2dbe21c7c129296ab394b14f..9e0c3229d314c76e948f990f8c7c 100644
--- a/dump.c
+++ b/dump.c
@@ -741,7 +741,7 @@ static int dump_init(DumpState *s, int fd, bool paging, bool has_filter,
     s->length = length;
 
     guest_phys_blocks_init(&s->guest_phys_blocks);
-    /* FILL LIST */
+    guest_phys_blocks_append(&s->guest_phys_blocks);
 
     s->start = get_start_block(s);
     if (s->start == -1) {
diff --git a/include/sysemu/memory_mapping.h b/include/sysemu/memory_mapping.h
index 9b5f8f297b3e694986dbdf5ba307..2c527b5c6cb2eb071b87ce25f69c 100644
--- a/include/sysemu/memory_mapping.h
+++ b/include/sysemu/memory_mapping.h
@@ -66,6 +66,7 @@ void memory_mapping_list_init(MemoryMappingList *list);
 
 void guest_phys_blocks_free(GuestPhysBlockList *list);
 void guest_phys_blocks_init(GuestPhysBlockList *list);
+void guest_phys_blocks_append(GuestPhysBlockList *list);
 
 /*
  * Return value:
diff --git a/memory_mapping.c b/memory_mapping.c
index 48eaf4da7ce7e3ae6410f49b0e54..2002cc80047c853af4c2c426cfc9 100644
--- a/memory_mapping.c
+++ b/memory_mapping.c
@@ -11,9 +11,15 @@
  *
  */
 
+#include <glib.h>
+
 #include "cpu.h"
 #include "exec/cpu-all.h"
 #include "sysemu/memory_mapping.h"
+#include "exec/memory.h"
+#include "exec/address-spaces.h"
+
+//#define DEBUG_GUEST_PHYS_REGION_ADD
 
 static void memory_mapping_list_add_mapping_sorted(MemoryMappingList *list,
                                                    MemoryMapping *mapping)
@@ -182,6 +188,84 @@ void guest_phys_blocks_init(GuestPhysBlockList *list)
     QTAILQ_INIT(&list->head);
 }
 
+typedef struct GuestPhysListener {
+    GuestPhysBlockList *list;
+    MemoryListener listener;
+} GuestPhysListener;
+
+static void guest_phys_blocks_region_add(MemoryListener *listener,
+                                         MemoryRegionSection *section)
+{
+    GuestPhysListener *g;
+    uint64_t section_size;
+    hwaddr target_start, target_end;
+    uint8_t *host_addr;
+    GuestPhysBlock *predecessor;
+
+    /* we only care about RAM */
+    if (!memory_region_is_ram(section->mr)) {
+        return;
+    }
+
+    g            = container_of(listener, GuestPhysListener, listener);
+    section_size = section->size;
+    target_start = section->offset_within_address_space;
+    target_end   = target_start + section_size;
+    host_addr    = memory_region_get_ram_ptr(section->mr) +
+                   section->offset_within_region;
+    predecessor  = NULL;
+
+    /* find continuity in guest physical address space */
+    if (!QTAILQ_EMPTY(&g->list->head)) {
+        hwaddr predecessor_size;
+
+        predecessor = QTAILQ_LAST(&g->list->head, GuestPhysBlockHead);
+        predecessor_size = predecessor->target_end - predecessor->target_start;
+
+        /* the memory API guarantees monotonically increasing traversal */
+        g_assert(predecessor->target_end <= target_start);
+
+        /* we want continuity in both guest-physical and host-virtual memory */
+        if (predecessor->target_end < target_start ||
+            predecessor->host_addr + predecessor_size != host_addr) {
+            predecessor = NULL;
+        }
+    }
+
+    if (predecessor == NULL) {
+        /* isolated mapping, allocate it and add it to the list */
+        GuestPhysBlock *block = g_malloc0(sizeof *block);
+
+        block->target_start = target_start;
+        block->target_end   = target_end;
+        block->host_addr    = host_addr;
+
+        QTAILQ_INSERT_TAIL(&g->list->head, block, next);
+        ++g->list->num;
+    } else {
+        /* expand predecessor until @target_end; predecessor's start doesn't
+         * change
+         */
+        predecessor->target_end = target_end;
+    }
+
+#ifdef DEBUG_GUEST_PHYS_REGION_ADD
+    fprintf(stderr, "%s: target_start=" TARGET_FMT_plx " target_end="
+            TARGET_FMT_plx ": %s (count: %u)\n", __FUNCTION__, target_start,
+            target_end, predecessor ? "joined" : "added", g->list->num);
+#endif
+}
+
+void guest_phys_blocks_append(GuestPhysBlockList *list)
+{
+    GuestPhysListener g = { 0 };
+
+    g.list = list;
+    g.listener.region_add = &guest_phys_blocks_region_add;
+    memory_listener_register(&g.listener, &address_space_memory);
+    memory_listener_unregister(&g.listener);
+}
+
 static CPUArchState *find_paging_enabled_cpu(CPUArchState *start_cpu)
 {
     CPUArchState *env;
