Blob Blame History Raw
From 960075802f1c30e656c118916c7ea32de0fa8563 Mon Sep 17 00:00:00 2001
From: Laszlo Ersek <lersek@redhat.com>
Date: Thu, 17 Apr 2014 11:13:59 +0200
Subject: qemu_loadvm_state(): shadow SeaBIOS for VM incoming from RHEL-6 host

RH-Author: Laszlo Ersek <lersek@redhat.com>
Message-id: <1397733239-8835-1-git-send-email-lersek@redhat.com>
Patchwork-id: 58498
O-Subject: [RHEL-7.0 0day qemu-kvm PATCH v2] qemu_loadvm_state(): shadow SeaBIOS for VM incoming from RHEL-6 host
Bugzilla: 1103579
RH-Acked-by: Dr. David Alan Gilbert (git) <dgilbert@redhat.com>
RH-Acked-by: Marcel Apfelbaum <marcel.a@redhat.com>
RH-Acked-by: Eduardo Habkost <ehabkost@redhat.com>

Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=1027565
Brew:     https://brewweb.devel.redhat.com/taskinfo?taskID=7352678

RHEL-only patch.

SeaBIOS's shadowing logic has no effect on "pc.ram" (only on "pc.bios" and
"pc.rom") when it runs on the RHEL-6 emulator. When such a guest is
migrated to the RHEL-7 emulator, where the PAM registers actually work,
these two UMBs under 1MB simply disappear from the guest's view, breaking
reboot and S3 resume.

Tested extensively by QE.

Signed-off-by: Laszlo Ersek <lersek@redhat.com>

diff --git a/hw/i386/pc_piix.c b/hw/i386/pc_piix.c
index c4e1af4..6f8420d 100644
--- a/hw/i386/pc_piix.c
+++ b/hw/i386/pc_piix.c
@@ -1155,6 +1155,7 @@ static void pc_compat_rhel650(MachineState *machine)
     rom_file_has_mr = false;
     has_acpi_build = false;
     gigabyte_align = false;
+    shadow_bios_after_incoming = true;
 }
 
 static void pc_init_rhel650(MachineState *machine)
diff --git a/include/sysemu/sysemu.h b/include/sysemu/sysemu.h
index 4faa8af..a369c3b 100644
--- a/include/sysemu/sysemu.h
+++ b/include/sysemu/sysemu.h
@@ -89,6 +89,7 @@ void qemu_savevm_state_complete(QEMUFile *f);
 void qemu_savevm_state_cancel(void);
 uint64_t qemu_savevm_state_pending(QEMUFile *f, uint64_t max_size);
 int qemu_loadvm_state(QEMUFile *f);
+extern bool shadow_bios_after_incoming;
 
 /* SLIRP */
 void do_info_slirp(Monitor *mon);
diff --git a/savevm.c b/savevm.c
index e19ae0a..41495bc 100644
--- a/savevm.c
+++ b/savevm.c
@@ -51,6 +51,8 @@
 #define ARP_PTYPE_IP 0x0800
 #define ARP_OP_REQUEST_REV 0x3
 
+bool shadow_bios_after_incoming;
+
 static int announce_self_create(uint8_t *buf,
                                 uint8_t *mac_addr)
 {
@@ -878,6 +880,63 @@ typedef struct LoadStateEntry {
     int version_id;
 } LoadStateEntry;
 
+static void shadow_bios(void)
+{
+    RAMBlock *block, *ram, *oprom, *bios;
+    size_t one_meg, oprom_size, bios_size;
+    uint8_t *cd_seg_host, *ef_seg_host;
+
+    ram = NULL;
+    oprom = NULL;
+    bios = NULL;
+    QTAILQ_FOREACH(block, &ram_list.blocks, next) {
+        if (strcmp("pc.ram", block->idstr) == 0) {
+            assert(ram == NULL);
+            ram = block;
+        } else if (strcmp("pc.rom", block->idstr) == 0) {
+            assert(oprom == NULL);
+            oprom = block;
+        } else if (strcmp("pc.bios", block->idstr) == 0) {
+            assert(bios == NULL);
+            bios = block;
+        }
+    }
+    assert(ram != NULL);
+    assert(oprom != NULL);
+    assert(bios != NULL);
+    assert(memory_region_is_ram(ram->mr));
+    assert(memory_region_is_ram(oprom->mr));
+    assert(memory_region_is_ram(bios->mr));
+    assert(int128_eq(ram->mr->size, int128_make64(ram->length)));
+    assert(int128_eq(oprom->mr->size, int128_make64(oprom->length)));
+    assert(int128_eq(bios->mr->size, int128_make64(bios->length)));
+
+    one_meg = 1024 * 1024;
+    oprom_size = 128 * 1024;
+    bios_size = 128 * 1024;
+    assert(ram->length >= one_meg);
+    assert(oprom->length == oprom_size);
+    assert(bios->length == bios_size);
+
+    ef_seg_host = memory_region_get_ram_ptr(ram->mr) + (one_meg - bios_size);
+    cd_seg_host = ef_seg_host - oprom_size;
+
+    /* This is a crude hack, but we must distinguish a rhel6.x.0 machtype guest
+     * coming in from a RHEL-6 emulator (where shadowing has had no effect on
+     * "pc.ram") from a similar guest coming in from a RHEL-7 emulator (where
+     * shadowing has worked). In the latter case we must not trample the live
+     * SeaBIOS variables in "pc.ram".
+     */
+    if (buffer_is_zero(ef_seg_host, bios_size)) {
+        fprintf(stderr, "copying E and F segments from pc.bios to pc.ram\n");
+        memcpy(ef_seg_host, memory_region_get_ram_ptr(bios->mr), bios_size);
+    }
+    if (buffer_is_zero(cd_seg_host, oprom_size)) {
+        fprintf(stderr, "copying C and D segments from pc.rom to pc.ram\n");
+        memcpy(cd_seg_host, memory_region_get_ram_ptr(oprom->mr), oprom_size);
+    }
+}
+
 int qemu_loadvm_state(QEMUFile *f)
 {
     QLIST_HEAD(, LoadStateEntry) loadvm_handlers =
@@ -982,6 +1041,13 @@ int qemu_loadvm_state(QEMUFile *f)
         }
     }
 
+    /* Supplement SeaBIOS's shadowing now, because it was useless when the
+     * incoming VM started on the RHEL-6 emulator.
+     */
+    if (shadow_bios_after_incoming) {
+        shadow_bios();
+    }
+
     cpu_synchronize_all_post_init();
 
     ret = 0;