QEMU fw_cfg: Write fw_cfg back on S3 resume

Any pointers to BIOS-allocated memory that were written back to QEMU
fw_cfg files are replayed when resuming from S3 sleep.

Signed-off-by: Ben Warren <ben@skyportsystems.com>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
Reviewed-by: Igor Mammedov <imammedo@redhat.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
(cherry picked from commit 8f598a4641f98cf503653f80c779793d91c95a84)
diff --git a/src/fw/romfile_loader.c b/src/fw/romfile_loader.c
index 30e7b58..18476e2 100644
--- a/src/fw/romfile_loader.c
+++ b/src/fw/romfile_loader.c
@@ -4,6 +4,7 @@
 #include "string.h" // strcmp
 #include "romfile.h" // struct romfile_s
 #include "malloc.h" // Zone*, _malloc
+#include "list.h" // struct hlist_node
 #include "output.h" // warn_*
 #include "paravirt.h" // qemu_cfg_write_file
 
@@ -16,6 +17,16 @@
     struct romfile_loader_file files[];
 };
 
+// Data structures for storing "write pointer" entries for possible replay
+struct romfile_wr_pointer_entry {
+    u64 pointer;
+    u32 offset;
+    u16 key;
+    u8 ptr_size;
+    struct hlist_node node;
+};
+static struct hlist_head romfile_pointer_list;
+
 static struct romfile_loader_file *
 romfile_loader_find(const char *name,
                     struct romfile_loader_files *files)
@@ -29,6 +40,19 @@
     return NULL;
 }
 
+// Replay "write pointer" entries back to QEMU
+void romfile_fw_cfg_resume(void)
+{
+    if (!CONFIG_QEMU)
+        return;
+
+    struct romfile_wr_pointer_entry *entry;
+    hlist_for_each_entry(entry, &romfile_pointer_list, node) {
+        qemu_cfg_write_file_simple(&entry->pointer, entry->key,
+                                   entry->offset, entry->ptr_size);
+    }
+}
+
 static void romfile_loader_allocate(struct romfile_loader_entry_s *entry,
                                     struct romfile_loader_files *files)
 {
@@ -163,6 +187,19 @@
                             entry->wr_pointer.size) != entry->wr_pointer.size) {
         goto err;
     }
+
+    /* Store the info so it can replayed later if necessary */
+    struct romfile_wr_pointer_entry *store = malloc_high(sizeof(*store));
+    if (!store) {
+        warn_noalloc();
+        return;
+    }
+    store->pointer = pointer;
+    store->key = qemu_get_romfile_key(dest_file);
+    store->offset = dst_offset;
+    store->ptr_size = entry->wr_pointer.size;
+    hlist_add_head(&store->node, &romfile_pointer_list);
+
     return;
  err:
     warn_internalerror();
diff --git a/src/fw/romfile_loader.h b/src/fw/romfile_loader.h
index 4dc50ab..fcd4ab2 100644
--- a/src/fw/romfile_loader.h
+++ b/src/fw/romfile_loader.h
@@ -86,4 +86,6 @@
 
 int romfile_loader_execute(const char *name);
 
+void romfile_fw_cfg_resume(void);
+
 #endif
diff --git a/src/resume.c b/src/resume.c
index e67cfce..99fa34f 100644
--- a/src/resume.c
+++ b/src/resume.c
@@ -17,6 +17,7 @@
 #include "string.h" // memset
 #include "util.h" // dma_setup
 #include "tcgbios.h" // tpm_s3_resume
+#include "fw/romfile_loader.h" // romfile_fw_cfg_resume
 
 // Handler for post calls that look like a resume.
 void VISIBLE16
@@ -105,6 +106,9 @@
     tpm_s3_resume();
     s3_resume_vga();
 
+    /* Replay any fw_cfg entries that go back to the host */
+    romfile_fw_cfg_resume();
+
     make_bios_readonly();
 
     // Invoke the resume vector.