diff mbox

[v8,4/8] ACPI: Add Virtual Machine Generation ID support

Message ID 88232638f9ff3b17b54987624468678ea14a3037.1487286467.git.ben@skyportsystems.com (mailing list archive)
State New, archived
Headers show

Commit Message

ben@skyportsystems.com Feb. 16, 2017, 11:15 p.m. UTC
From: Ben Warren <ben@skyportsystems.com>

This implements the VM Generation ID feature by passing a 128-bit
GUID to the guest via a fw_cfg blob.
Any time the GUID changes, an ACPI notify event is sent to the guest

The user interface is a simple device with one parameter:
 - guid (string, must be "auto" or in UUID format
   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)

Signed-off-by: Ben Warren <ben@skyportsystems.com>
Reviewed-by: Igor Mammedov <imammedo@redhat.com>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
Tested-by: Laszlo Ersek <lersek@redhat.com>
---
 default-configs/i386-softmmu.mak     |   1 +
 default-configs/x86_64-softmmu.mak   |   1 +
 hw/acpi/Makefile.objs                |   1 +
 hw/acpi/vmgenid.c                    | 242 +++++++++++++++++++++++++++++++++++
 hw/i386/acpi-build.c                 |  16 +++
 include/hw/acpi/acpi_dev_interface.h |   1 +
 include/hw/acpi/vmgenid.h            |  35 +++++
 7 files changed, 297 insertions(+)
 create mode 100644 hw/acpi/vmgenid.c
 create mode 100644 include/hw/acpi/vmgenid.h

Comments

Igor Mammedov Feb. 17, 2017, 10:43 a.m. UTC | #1
On Thu, 16 Feb 2017 15:15:36 -0800
ben@skyportsystems.com wrote:

> From: Ben Warren <ben@skyportsystems.com>
> 
> This implements the VM Generation ID feature by passing a 128-bit
> GUID to the guest via a fw_cfg blob.
> Any time the GUID changes, an ACPI notify event is sent to the guest
> 
> The user interface is a simple device with one parameter:
>  - guid (string, must be "auto" or in UUID format
>    xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
I've given it some testing with WS2012R2 and v4 patches for Seabios,

Windows is able to read initial GUID allocation and writeback
seems to work somehow:

(qemu) info vm-generation-id 
c109c09b-0e8b-42d5-9b33-8409c9dcd16c

vmgenid client in Windows reads it as 2 following 64bit integers:
42d50e8bc109c09b:6cd1dcc90984339b

However update path/restore from snapshot doesn't
here is as I've tested it:

qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
(qemu) info vm-generation-id 
c109c09b-0e8b-42d5-9b33-8409c9dcd16c
(qemu) stop
(qemu) migrate "exec:gzip -c > STATEFILE.gz" 
(qemu) quit

qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
-incoming "exec: gzip -c -d STATEFILE.gz"
(qemu) info vm-generation-id 
28b587fa-991b-4267-80d7-9cf28b746fe9

guest
 1. doesn't get GPE notification that it must receive
 2. vmgenid client in Windows reads the same value
      42d50e8bc109c09b:6cd1dcc90984339b


> 
> Signed-off-by: Ben Warren <ben@skyportsystems.com>
> Reviewed-by: Igor Mammedov <imammedo@redhat.com>
> Reviewed-by: Laszlo Ersek <lersek@redhat.com>
> Tested-by: Laszlo Ersek <lersek@redhat.com>
> ---
>  default-configs/i386-softmmu.mak     |   1 +
>  default-configs/x86_64-softmmu.mak   |   1 +
>  hw/acpi/Makefile.objs                |   1 +
>  hw/acpi/vmgenid.c                    | 242 +++++++++++++++++++++++++++++++++++
>  hw/i386/acpi-build.c                 |  16 +++
>  include/hw/acpi/acpi_dev_interface.h |   1 +
>  include/hw/acpi/vmgenid.h            |  35 +++++
>  7 files changed, 297 insertions(+)
>  create mode 100644 hw/acpi/vmgenid.c
>  create mode 100644 include/hw/acpi/vmgenid.h
> 
> diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak
> index 48b07a4..029e952 100644
> --- a/default-configs/i386-softmmu.mak
> +++ b/default-configs/i386-softmmu.mak
> @@ -59,3 +59,4 @@ CONFIG_I82801B11=y
>  CONFIG_SMBIOS=y
>  CONFIG_HYPERV_TESTDEV=$(CONFIG_KVM)
>  CONFIG_PXB=y
> +CONFIG_ACPI_VMGENID=y
> diff --git a/default-configs/x86_64-softmmu.mak b/default-configs/x86_64-softmmu.mak
> index fd96345..d1d7432 100644
> --- a/default-configs/x86_64-softmmu.mak
> +++ b/default-configs/x86_64-softmmu.mak
> @@ -59,3 +59,4 @@ CONFIG_I82801B11=y
>  CONFIG_SMBIOS=y
>  CONFIG_HYPERV_TESTDEV=$(CONFIG_KVM)
>  CONFIG_PXB=y
> +CONFIG_ACPI_VMGENID=y
> diff --git a/hw/acpi/Makefile.objs b/hw/acpi/Makefile.objs
> index 6acf798..11c35bc 100644
> --- a/hw/acpi/Makefile.objs
> +++ b/hw/acpi/Makefile.objs
> @@ -5,6 +5,7 @@ common-obj-$(CONFIG_ACPI_CPU_HOTPLUG) += cpu_hotplug.o
>  common-obj-$(CONFIG_ACPI_MEMORY_HOTPLUG) += memory_hotplug.o
>  common-obj-$(CONFIG_ACPI_CPU_HOTPLUG) += cpu.o
>  common-obj-$(CONFIG_ACPI_NVDIMM) += nvdimm.o
> +common-obj-$(CONFIG_ACPI_VMGENID) += vmgenid.o
>  common-obj-$(call lnot,$(CONFIG_ACPI_X86)) += acpi-stub.o
>  
>  common-obj-y += acpi_interface.o
> diff --git a/hw/acpi/vmgenid.c b/hw/acpi/vmgenid.c
> new file mode 100644
> index 0000000..c8465df
> --- /dev/null
> +++ b/hw/acpi/vmgenid.c
> @@ -0,0 +1,242 @@
> +/*
> + *  Virtual Machine Generation ID Device
> + *
> + *  Copyright (C) 2017 Skyport Systems.
> + *
> + *  Author: Ben Warren <ben@skyportsystems.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qmp-commands.h"
> +#include "hw/acpi/acpi.h"
> +#include "hw/acpi/aml-build.h"
> +#include "hw/acpi/vmgenid.h"
> +#include "hw/nvram/fw_cfg.h"
> +#include "sysemu/sysemu.h"
> +
> +void vmgenid_build_acpi(VmGenIdState *vms, GArray *table_data, GArray *guid,
> +                        BIOSLinker *linker)
> +{
> +    Aml *ssdt, *dev, *scope, *method, *addr, *if_ctx;
> +    uint32_t vgia_offset;
> +    QemuUUID guid_le;
> +
> +    /* Fill in the GUID values.  These need to be converted to little-endian
> +     * first, since that's what the guest expects
> +     */
> +    g_array_set_size(guid, VMGENID_FW_CFG_SIZE - ARRAY_SIZE(guid_le.data));
> +    guid_le = vms->guid;
> +    qemu_uuid_bswap(&guid_le);
> +    /* The GUID is written at a fixed offset into the fw_cfg file
> +     * in order to implement the "OVMF SDT Header probe suppressor"
> +     * see docs/specs/vmgenid.txt for more details
> +     */
> +    g_array_insert_vals(guid, VMGENID_GUID_OFFSET, guid_le.data,
> +                        ARRAY_SIZE(guid_le.data));
> +
> +    /* Put this in a separate SSDT table */
> +    ssdt = init_aml_allocator();
> +
> +    /* Reserve space for header */
> +    acpi_data_push(ssdt->buf, sizeof(AcpiTableHeader));
> +
> +    /* Storage for the GUID address */
> +    vgia_offset = table_data->len +
> +        build_append_named_dword(ssdt->buf, "VGIA");
> +    scope = aml_scope("\\_SB");
> +    dev = aml_device("VGEN");
> +    aml_append(dev, aml_name_decl("_HID", aml_string("QEMUVGID")));
> +    aml_append(dev, aml_name_decl("_CID", aml_string("VM_Gen_Counter")));
> +    aml_append(dev, aml_name_decl("_DDN", aml_string("VM_Gen_Counter")));
> +
> +    /* Simple status method to check that address is linked and non-zero */
> +    method = aml_method("_STA", 0, AML_NOTSERIALIZED);
> +    addr = aml_local(0);
> +    aml_append(method, aml_store(aml_int(0xf), addr));
> +    if_ctx = aml_if(aml_equal(aml_name("VGIA"), aml_int(0)));
> +    aml_append(if_ctx, aml_store(aml_int(0), addr));
> +    aml_append(method, if_ctx);
> +    aml_append(method, aml_return(addr));
> +    aml_append(dev, method);
> +
> +    /* the ADDR method returns two 32-bit words representing the lower and
> +     * upper halves * of the physical address of the fw_cfg blob
> +     * (holding the GUID)
> +     */
> +    method = aml_method("ADDR", 0, AML_NOTSERIALIZED);
> +
> +    addr = aml_local(0);
> +    aml_append(method, aml_store(aml_package(2), addr));
> +
> +    aml_append(method, aml_store(aml_add(aml_name("VGIA"),
> +                                         aml_int(VMGENID_GUID_OFFSET), NULL),
> +                                 aml_index(addr, aml_int(0))));
> +    aml_append(method, aml_store(aml_int(0), aml_index(addr, aml_int(1))));
> +    aml_append(method, aml_return(addr));
> +
> +    aml_append(dev, method);
> +    aml_append(scope, dev);
> +    aml_append(ssdt, scope);
> +
> +    /* attach an ACPI notify */
> +    method = aml_method("\\_GPE._E05", 0, AML_NOTSERIALIZED);
> +    aml_append(method, aml_notify(aml_name("\\_SB.VGEN"), aml_int(0x80)));
> +    aml_append(ssdt, method);
> +
> +    g_array_append_vals(table_data, ssdt->buf->data, ssdt->buf->len);
> +
> +    /* Allocate guest memory for the Data fw_cfg blob */
> +    bios_linker_loader_alloc(linker, VMGENID_GUID_FW_CFG_FILE, guid, 4096,
> +                             false /* page boundary, high memory */);
> +
> +    /* Patch address of GUID fw_cfg blob into the ADDR fw_cfg blob
> +     * so QEMU can write the GUID there.  The address is expected to be
> +     * < 4GB, but write 64 bits anyway.
> +     * The address that is patched in is offset in order to implement
> +     * the "OVMF SDT Header probe suppressor"
> +     * see docs/specs/vmgenid.txt for more details.
> +     */
> +    bios_linker_loader_write_pointer(linker,
> +        VMGENID_ADDR_FW_CFG_FILE, 0, sizeof(uint64_t),
> +        VMGENID_GUID_FW_CFG_FILE, VMGENID_GUID_OFFSET);
> +
> +    /* Patch address of GUID fw_cfg blob into the AML so OSPM can retrieve
> +     * and read it.  Note that while we provide storage for 64 bits, only
> +     * the least-signficant 32 get patched into AML.
> +     */
> +    bios_linker_loader_add_pointer(linker,
> +        ACPI_BUILD_TABLE_FILE, vgia_offset, sizeof(uint32_t),
> +        VMGENID_GUID_FW_CFG_FILE, 0);
> +
> +    build_header(linker, table_data,
> +        (void *)(table_data->data + table_data->len - ssdt->buf->len),
> +        "SSDT", ssdt->buf->len, 1, NULL, "VMGENID");
> +    free_aml_allocator();
> +}
> +
> +void vmgenid_add_fw_cfg(VmGenIdState *vms, FWCfgState *s, GArray *guid)
> +{
> +    /* Create a read-only fw_cfg file for GUID */
> +    fw_cfg_add_file(s, VMGENID_GUID_FW_CFG_FILE, guid->data,
> +                    VMGENID_FW_CFG_SIZE);
> +    /* Create a read-write fw_cfg file for Address */
> +    fw_cfg_add_file_callback(s, VMGENID_ADDR_FW_CFG_FILE, NULL, NULL,
> +                             vms->vmgenid_addr_le,
> +                             ARRAY_SIZE(vms->vmgenid_addr_le), false);
> +}
> +
> +static void vmgenid_update_guest(VmGenIdState *vms)
> +{
> +    Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
> +    uint32_t vmgenid_addr;
> +    QemuUUID guid_le;
> +
> +    if (obj) {
> +        /* Write the GUID to guest memory */
> +        memcpy(&vmgenid_addr, vms->vmgenid_addr_le, sizeof(vmgenid_addr));
> +        vmgenid_addr = le32_to_cpu(vmgenid_addr);
> +        /* A zero value in vmgenid_addr means that BIOS has not yet written
> +         * the address
> +         */
> +        if (vmgenid_addr) {
> +            /* QemuUUID has the first three words as big-endian, and expect
> +             * that any GUIDs passed in will always be BE.  The guest,
> +             * however, will expect the fields to be little-endian.
> +             * Perform a byte swap immediately before writing.
> +             */
> +            guid_le = vms->guid;
> +            qemu_uuid_bswap(&guid_le);
> +            /* The GUID is written at a fixed offset into the fw_cfg file
> +             * in order to implement the "OVMF SDT Header probe suppressor"
> +             * see docs/specs/vmgenid.txt for more details.
> +             */
> +            cpu_physical_memory_write(vmgenid_addr, guid_le.data,
> +                                      sizeof(guid_le.data));
> +            /* Send _GPE.E05 event */
> +            acpi_send_event(DEVICE(obj), ACPI_VMGENID_CHANGE_STATUS);
> +        }
> +    }
> +}
> +
> +static void vmgenid_set_guid(Object *obj, const char *value, Error **errp)
> +{
> +    VmGenIdState *vms = VMGENID(obj);
> +
> +    if (!strcmp(value, "auto")) {
> +        qemu_uuid_generate(&vms->guid);
> +    } else if (qemu_uuid_parse(value, &vms->guid) < 0) {
> +        error_setg(errp, "'%s. %s': Failed to parse GUID string: %s",
> +                   object_get_typename(OBJECT(vms)), VMGENID_GUID, value);
> +        return;
> +    }
> +
> +    vmgenid_update_guest(vms);
> +}
> +
> +/* After restoring an image, we need to update the guest memory and notify
> + * it of a potential change to VM Generation ID
> + */
> +static int vmgenid_post_load(void *opaque, int version_id)
> +{
> +    VmGenIdState *vms = opaque;
> +    vmgenid_update_guest(vms);
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_vmgenid = {
> +    .name = "vmgenid",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .post_load = vmgenid_post_load,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT8_ARRAY(vmgenid_addr_le, VmGenIdState, sizeof(uint64_t)),
> +        VMSTATE_END_OF_LIST()
> +    },
> +};
> +
> +static void vmgenid_handle_reset(void *opaque)
> +{
> +    VmGenIdState *vms = VMGENID(opaque);
> +    /* Clear the guest-allocated GUID address when the VM resets */
> +    memset(vms->vmgenid_addr_le, 0, ARRAY_SIZE(vms->vmgenid_addr_le));
> +}
> +
> +static void vmgenid_realize(DeviceState *dev, Error **errp)
> +{
> +    VmGenIdState *vms = VMGENID(dev);
> +    qemu_register_reset(vmgenid_handle_reset, vms);
> +}
> +
> +static void vmgenid_device_class_init(ObjectClass *klass, void *data)
> +{
> +    DeviceClass *dc = DEVICE_CLASS(klass);
> +
> +    dc->vmsd = &vmstate_vmgenid;
> +    dc->realize = vmgenid_realize;
> +    dc->hotpluggable = false;
> +
> +    object_class_property_add_str(klass, VMGENID_GUID, NULL,
> +                                  vmgenid_set_guid, NULL);
> +    object_class_property_set_description(klass, VMGENID_GUID,
> +                                    "Set Global Unique Identifier "
> +                                    "(big-endian) or auto for random value",
> +                                    NULL);
> +}
> +
> +static const TypeInfo vmgenid_device_info = {
> +    .name          = VMGENID_DEVICE,
> +    .parent        = TYPE_DEVICE,
> +    .instance_size = sizeof(VmGenIdState),
> +    .class_init    = vmgenid_device_class_init,
> +};
> +
> +static void vmgenid_register_types(void)
> +{
> +    type_register_static(&vmgenid_device_info);
> +}
> +
> +type_init(vmgenid_register_types)
> diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
> index 1c928ab..db04cf5 100644
> --- a/hw/i386/acpi-build.c
> +++ b/hw/i386/acpi-build.c
> @@ -42,6 +42,7 @@
>  #include "hw/acpi/memory_hotplug.h"
>  #include "sysemu/tpm.h"
>  #include "hw/acpi/tpm.h"
> +#include "hw/acpi/vmgenid.h"
>  #include "sysemu/tpm_backend.h"
>  #include "hw/timer/mc146818rtc_regs.h"
>  #include "sysemu/numa.h"
> @@ -2610,6 +2611,7 @@ void acpi_build(AcpiBuildTables *tables, MachineState *machine)
>      size_t aml_len = 0;
>      GArray *tables_blob = tables->table_data;
>      AcpiSlicOem slic_oem = { .id = NULL, .table_id = NULL };
> +    Object *vmgenid_dev;
>  
>      acpi_get_pm_info(&pm);
>      acpi_get_misc_info(&misc);
> @@ -2653,6 +2655,13 @@ void acpi_build(AcpiBuildTables *tables, MachineState *machine)
>      acpi_add_table(table_offsets, tables_blob);
>      build_madt(tables_blob, tables->linker, pcms);
>  
> +    vmgenid_dev = find_vmgenid_dev();
> +    if (vmgenid_dev) {
> +        acpi_add_table(table_offsets, tables_blob);
> +        vmgenid_build_acpi(VMGENID(vmgenid_dev), tables_blob,
> +                           tables->vmgenid, tables->linker);
> +    }
> +
>      if (misc.has_hpet) {
>          acpi_add_table(table_offsets, tables_blob);
>          build_hpet(tables_blob, tables->linker);
> @@ -2823,6 +2832,7 @@ void acpi_setup(void)
>      PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms);
>      AcpiBuildTables tables;
>      AcpiBuildState *build_state;
> +    Object *vmgenid_dev;
>  
>      if (!pcms->fw_cfg) {
>          ACPI_BUILD_DPRINTF("No fw cfg. Bailing out.\n");
> @@ -2859,6 +2869,12 @@ void acpi_setup(void)
>      fw_cfg_add_file(pcms->fw_cfg, ACPI_BUILD_TPMLOG_FILE,
>                      tables.tcpalog->data, acpi_data_len(tables.tcpalog));
>  
> +    vmgenid_dev = find_vmgenid_dev();
> +    if (vmgenid_dev) {
> +        vmgenid_add_fw_cfg(VMGENID(vmgenid_dev), pcms->fw_cfg,
> +                           tables.vmgenid);
> +    }
> +
>      if (!pcmc->rsdp_in_ram) {
>          /*
>           * Keep for compatibility with old machine types.
> diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h
> index 71d3c48..3c2e4e9 100644
> --- a/include/hw/acpi/acpi_dev_interface.h
> +++ b/include/hw/acpi/acpi_dev_interface.h
> @@ -11,6 +11,7 @@ typedef enum {
>      ACPI_CPU_HOTPLUG_STATUS = 4,
>      ACPI_MEMORY_HOTPLUG_STATUS = 8,
>      ACPI_NVDIMM_HOTPLUG_STATUS = 16,
> +    ACPI_VMGENID_CHANGE_STATUS = 32,
>  } AcpiEventStatusBits;
>  
>  #define TYPE_ACPI_DEVICE_IF "acpi-device-interface"
> diff --git a/include/hw/acpi/vmgenid.h b/include/hw/acpi/vmgenid.h
> new file mode 100644
> index 0000000..db7fa0e
> --- /dev/null
> +++ b/include/hw/acpi/vmgenid.h
> @@ -0,0 +1,35 @@
> +#ifndef ACPI_VMGENID_H
> +#define ACPI_VMGENID_H
> +
> +#include "hw/acpi/bios-linker-loader.h"
> +#include "hw/qdev.h"
> +#include "qemu/uuid.h"
> +
> +#define VMGENID_DEVICE           "vmgenid"
> +#define VMGENID_GUID             "guid"
> +#define VMGENID_GUID_FW_CFG_FILE      "etc/vmgenid_guid"
> +#define VMGENID_ADDR_FW_CFG_FILE      "etc/vmgenid_addr"
> +
> +#define VMGENID_FW_CFG_SIZE      4096 /* Occupy a page of memory */
> +#define VMGENID_GUID_OFFSET      40   /* allow space for
> +                                       * OVMF SDT Header Probe Supressor
> +                                       */
> +
> +#define VMGENID(obj) OBJECT_CHECK(VmGenIdState, (obj), VMGENID_DEVICE)
> +
> +typedef struct VmGenIdState {
> +    DeviceClass parent_obj;
> +    QemuUUID guid;                /* The 128-bit GUID seen by the guest */
> +    uint8_t vmgenid_addr_le[8];   /* Address of the GUID (little-endian) */
> +} VmGenIdState;
> +
> +static inline Object *find_vmgenid_dev(void)
> +{
> +    return object_resolve_path_type("", VMGENID_DEVICE, NULL);
> +}
> +
> +void vmgenid_build_acpi(VmGenIdState *vms, GArray *table_data, GArray *guid,
> +                        BIOSLinker *linker);
> +void vmgenid_add_fw_cfg(VmGenIdState *vms, FWCfgState *s, GArray *guid);
> +
> +#endif
ben@skyportsystems.com Feb. 17, 2017, 3:33 p.m. UTC | #2
> On Feb 17, 2017, at 2:43 AM, Igor Mammedov <imammedo@redhat.com> wrote:
> 
> On Thu, 16 Feb 2017 15:15:36 -0800
> ben@skyportsystems.com wrote:
> 
>> From: Ben Warren <ben@skyportsystems.com>
>> 
>> This implements the VM Generation ID feature by passing a 128-bit
>> GUID to the guest via a fw_cfg blob.
>> Any time the GUID changes, an ACPI notify event is sent to the guest
>> 
>> The user interface is a simple device with one parameter:
>> - guid (string, must be "auto" or in UUID format
>>   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
> I've given it some testing with WS2012R2 and v4 patches for Seabios,
> 
> Windows is able to read initial GUID allocation and writeback
> seems to work somehow:
> 
> (qemu) info vm-generation-id 
> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
> 
> vmgenid client in Windows reads it as 2 following 64bit integers:
> 42d50e8bc109c09b:6cd1dcc90984339b
> 
> However update path/restore from snapshot doesn't
> here is as I've tested it:
> 
> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
> (qemu) info vm-generation-id 
> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
> (qemu) stop
> (qemu) migrate "exec:gzip -c > STATEFILE.gz" 
> (qemu) quit
> 
> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
> -incoming "exec: gzip -c -d STATEFILE.gz"
> (qemu) info vm-generation-id 
> 28b587fa-991b-4267-80d7-9cf28b746fe9
> 
> guest
> 1. doesn't get GPE notification that it must receive
> 2. vmgenid client in Windows reads the same value
>      42d50e8bc109c09b:6cd1dcc90984339b
> 
Strange, this was working for me, but with a slightly different test method:
I use virsh save/restore
While I do later testing with Windows, during development I use a Linux kernel module I wrote that keeps track of GUID and notifications.  I’m happy to share this with you if interested.

I’ll dig into this morning.

—Ben
<snip>
Laszlo Ersek Feb. 17, 2017, 4:03 p.m. UTC | #3
On 02/17/17 16:33, Ben Warren wrote:
> 
>> On Feb 17, 2017, at 2:43 AM, Igor Mammedov <imammedo@redhat.com
>> <mailto:imammedo@redhat.com>> wrote:
>>
>> On Thu, 16 Feb 2017 15:15:36 -0800
>> ben@skyportsystems.com <mailto:ben@skyportsystems.com> wrote:
>>
>>> From: Ben Warren <ben@skyportsystems.com <mailto:ben@skyportsystems.com>>
>>>
>>> This implements the VM Generation ID feature by passing a 128-bit
>>> GUID to the guest via a fw_cfg blob.
>>> Any time the GUID changes, an ACPI notify event is sent to the guest
>>>
>>> The user interface is a simple device with one parameter:
>>> - guid (string, must be "auto" or in UUID format
>>>   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
>> I've given it some testing with WS2012R2 and v4 patches for Seabios,
>>
>> Windows is able to read initial GUID allocation and writeback
>> seems to work somehow:
>>
>> (qemu) info vm-generation-id
>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>
>> vmgenid client in Windows reads it as 2 following 64bit integers:
>> 42d50e8bc109c09b:6cd1dcc90984339b
>>
>> However update path/restore from snapshot doesn't
>> here is as I've tested it:
>>
>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>> (qemu) info vm-generation-id
>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>> (qemu) stop
>> (qemu) migrate "exec:gzip -c > STATEFILE.gz"
>> (qemu) quit
>>
>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>> -incoming "exec: gzip -c -d STATEFILE.gz"
>> (qemu) info vm-generation-id
>> 28b587fa-991b-4267-80d7-9cf28b746fe9
>>
>> guest
>> 1. doesn't get GPE notification that it must receive
>> 2. vmgenid client in Windows reads the same value
>>      42d50e8bc109c09b:6cd1dcc90984339b
>>
> Strange, this was working for me, but with a slightly different test method:
> 
>   * I use virsh save/restore

Awesome, this actually what I should try. All my guests are managed by
libvirt (with the occasional <qemu:arg>, for development), and direct
QEMU monitor commands such as

  virsh qemu-monitor-command ovmf.rhel7 --hmp 'info vm-generation-id'

only work for me if they are reasonably non-intrusive.

>   * While I do later testing with Windows, during development I use a
>     Linux kernel module I wrote that keeps track of GUID and
>     notifications.  I’m happy to share this with you if interested.

Please do. If you have a public git repo somewhere, that would be
awesome. (Bonus points if the module builds out-of-tree, if the
kernel-devel package is installed.)

NB: while the set-id monitor command was part of the series, I did test
it to the extent that I checked the SCI ("ACPI interrupt") count in the
guest, in /proc/interrupts. I did see it increase, so minimally the SCI
injection was fine.

Thanks!
Laszlo

> I’ll dig into this morning.
> 
> —Ben
> <snip>
ben@skyportsystems.com Feb. 17, 2017, 6:34 p.m. UTC | #4
> On Feb 17, 2017, at 8:03 AM, Laszlo Ersek <lersek@redhat.com> wrote:
> 
> On 02/17/17 16:33, Ben Warren wrote:
>> 
>>> On Feb 17, 2017, at 2:43 AM, Igor Mammedov <imammedo@redhat.com <mailto:imammedo@redhat.com>
>>> <mailto:imammedo@redhat.com <mailto:imammedo@redhat.com>>> wrote:
>>> 
>>> On Thu, 16 Feb 2017 15:15:36 -0800
>>> ben@skyportsystems.com <mailto:ben@skyportsystems.com> <mailto:ben@skyportsystems.com <mailto:ben@skyportsystems.com>> wrote:
>>> 
>>>> From: Ben Warren <ben@skyportsystems.com <mailto:ben@skyportsystems.com> <mailto:ben@skyportsystems.com <mailto:ben@skyportsystems.com>>>
>>>> 
>>>> This implements the VM Generation ID feature by passing a 128-bit
>>>> GUID to the guest via a fw_cfg blob.
>>>> Any time the GUID changes, an ACPI notify event is sent to the guest
>>>> 
>>>> The user interface is a simple device with one parameter:
>>>> - guid (string, must be "auto" or in UUID format
>>>>  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
>>> I've given it some testing with WS2012R2 and v4 patches for Seabios,
>>> 
>>> Windows is able to read initial GUID allocation and writeback
>>> seems to work somehow:
>>> 
>>> (qemu) info vm-generation-id
>>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>> 
>>> vmgenid client in Windows reads it as 2 following 64bit integers:
>>> 42d50e8bc109c09b:6cd1dcc90984339b
>>> 
>>> However update path/restore from snapshot doesn't
>>> here is as I've tested it:
>>> 
>>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>>> (qemu) info vm-generation-id
>>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>> (qemu) stop
>>> (qemu) migrate "exec:gzip -c > STATEFILE.gz"
>>> (qemu) quit
>>> 
>>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>>> -incoming "exec: gzip -c -d STATEFILE.gz"
>>> (qemu) info vm-generation-id
>>> 28b587fa-991b-4267-80d7-9cf28b746fe9
>>> 
>>> guest
>>> 1. doesn't get GPE notification that it must receive
>>> 2. vmgenid client in Windows reads the same value
>>>     42d50e8bc109c09b:6cd1dcc90984339b
>>> 
>> Strange, this was working for me, but with a slightly different test method:
>> 
>>  * I use virsh save/restore
> 
> Awesome, this actually what I should try. All my guests are managed by
> libvirt (with the occasional <qemu:arg>, for development), and direct
> QEMU monitor commands such as
> 
>  virsh qemu-monitor-command ovmf.rhel7 --hmp 'info vm-generation-id'
> 
> only work for me if they are reasonably non-intrusive.
> 
>>  * While I do later testing with Windows, during development I use a
>>    Linux kernel module I wrote that keeps track of GUID and
>>    notifications.  I’m happy to share this with you if interested.
> 
> Please do. If you have a public git repo somewhere, that would be
> awesome. (Bonus points if the module builds out-of-tree, if the
> kernel-devel package is installed.)
> 
Here you go:
https://github.com/ben-skyportsystems/vmgenid-test <https://github.com/ben-skyportsystems/vmgenid-test>

I don’t know if something like this would ever be accepted into the Linux kernel, but it has been invaluable to me, and I’d like to see it somewhere better.

> NB: while the set-id monitor command was part of the series, I did test
> it to the extent that I checked the SCI ("ACPI interrupt") count in the
> guest, in /proc/interrupts. I did see it increase, so minimally the SCI
> injection was fine.
> 
> Thanks!
> Laszlo
> 
>> I’ll dig into this morning.
>> 
>> —Ben
>> <snip>
Michael S. Tsirkin Feb. 17, 2017, 7 p.m. UTC | #5
On Fri, Feb 17, 2017 at 10:34:29AM -0800, Ben Warren wrote:
> 
>     On Feb 17, 2017, at 8:03 AM, Laszlo Ersek <lersek@redhat.com> wrote:
> 
>     On 02/17/17 16:33, Ben Warren wrote:
> 
> 
> 
>             On Feb 17, 2017, at 2:43 AM, Igor Mammedov <imammedo@redhat.com
>             <mailto:imammedo@redhat.com>> wrote:
> 
>             On Thu, 16 Feb 2017 15:15:36 -0800
>             ben@skyportsystems.com <mailto:ben@skyportsystems.com> wrote:
> 
> 
>                 From: Ben Warren <ben@skyportsystems.com <
>                 mailto:ben@skyportsystems.com>>
> 
>                 This implements the VM Generation ID feature by passing a
>                 128-bit
>                 GUID to the guest via a fw_cfg blob.
>                 Any time the GUID changes, an ACPI notify event is sent to the
>                 guest
> 
>                 The user interface is a simple device with one parameter:
>                 - guid (string, must be "auto" or in UUID format
>                  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
> 
>             I've given it some testing with WS2012R2 and v4 patches for
>             Seabios,
> 
>             Windows is able to read initial GUID allocation and writeback
>             seems to work somehow:
> 
>             (qemu) info vm-generation-id
>             c109c09b-0e8b-42d5-9b33-8409c9dcd16c
> 
>             vmgenid client in Windows reads it as 2 following 64bit integers:
>             42d50e8bc109c09b:6cd1dcc90984339b
> 
>             However update path/restore from snapshot doesn't
>             here is as I've tested it:
> 
>             qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor
>             stdio
>             (qemu) info vm-generation-id
>             c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>             (qemu) stop
>             (qemu) migrate "exec:gzip -c > STATEFILE.gz"
>             (qemu) quit
> 
>             qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor
>             stdio
>             -incoming "exec: gzip -c -d STATEFILE.gz"
>             (qemu) info vm-generation-id
>             28b587fa-991b-4267-80d7-9cf28b746fe9
> 
>             guest
>             1. doesn't get GPE notification that it must receive
>             2. vmgenid client in Windows reads the same value
>                 42d50e8bc109c09b:6cd1dcc90984339b
> 
> 
>         Strange, this was working for me, but with a slightly different test
>         method:
> 
>          * I use virsh save/restore
> 
> 
>     Awesome, this actually what I should try. All my guests are managed by
>     libvirt (with the occasional <qemu:arg>, for development), and direct
>     QEMU monitor commands such as
> 
>      virsh qemu-monitor-command ovmf.rhel7 --hmp 'info vm-generation-id'
> 
>     only work for me if they are reasonably non-intrusive.
> 
> 
>          * While I do later testing with Windows, during development I use a
>            Linux kernel module I wrote that keeps track of GUID and
>            notifications.  I’m happy to share this with you if interested.
> 
> 
>     Please do. If you have a public git repo somewhere, that would be
>     awesome. (Bonus points if the module builds out-of-tree, if the
>     kernel-devel package is installed.)
> 
> 
> Here you go:
> https://github.com/ben-skyportsystems/vmgenid-test
> 
> I don’t know if something like this would ever be accepted into the Linux
> kernel, but it has been invaluable to me, and I’d like to see it somewhere
> better.

I think the main issue here is that there's no blocking
interface to wait for change events.



Also ioremap_nocache is definitely the wrong thing to do
since the spec says
	It must not be in the same 4-kilobyte page as any memory that is
	expected to be mapped by a page table entry with caching disabled.

Finally, I think it makes sense to add an mmap call to this driver.
Basically add some kind of interface telling guest that gen id does not
share a 4K page with any other structure. Maybe just a special HID
value?  Or a special method we can test for.  Then it's safe for guest
to map this page read-only into userspace memory. It should have an
interface to report the offset to userspace. Userspace can then get the
ID without a system call by doing

ptr = mmap(...)
offset = ioctl(... GET_OFFSET ...)
guid = *(ptr + offset)

Windows does not seem to have this ability but it might be
a significant performance enhancement IMHO.

> 
>     NB: while the set-id monitor command was part of the series, I did test
>     it to the extent that I checked the SCI ("ACPI interrupt") count in the
>     guest, in /proc/interrupts. I did see it increase, so minimally the SCI
>     injection was fine.
> 
>     Thanks!
>     Laszlo
> 
> 
>         I’ll dig into this morning.
> 
>         —Ben
>         <snip>
> 
>
Laszlo Ersek Feb. 17, 2017, 8:07 p.m. UTC | #6
On 02/17/17 17:03, Laszlo Ersek wrote:
> On 02/17/17 16:33, Ben Warren wrote:
>>
>>> On Feb 17, 2017, at 2:43 AM, Igor Mammedov <imammedo@redhat.com
>>> <mailto:imammedo@redhat.com>> wrote:
>>>
>>> On Thu, 16 Feb 2017 15:15:36 -0800
>>> ben@skyportsystems.com <mailto:ben@skyportsystems.com> wrote:
>>>
>>>> From: Ben Warren <ben@skyportsystems.com <mailto:ben@skyportsystems.com>>
>>>>
>>>> This implements the VM Generation ID feature by passing a 128-bit
>>>> GUID to the guest via a fw_cfg blob.
>>>> Any time the GUID changes, an ACPI notify event is sent to the guest
>>>>
>>>> The user interface is a simple device with one parameter:
>>>> - guid (string, must be "auto" or in UUID format
>>>>   xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
>>> I've given it some testing with WS2012R2 and v4 patches for Seabios,
>>>
>>> Windows is able to read initial GUID allocation and writeback
>>> seems to work somehow:
>>>
>>> (qemu) info vm-generation-id
>>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>>
>>> vmgenid client in Windows reads it as 2 following 64bit integers:
>>> 42d50e8bc109c09b:6cd1dcc90984339b
>>>
>>> However update path/restore from snapshot doesn't
>>> here is as I've tested it:
>>>
>>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>>> (qemu) info vm-generation-id
>>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>> (qemu) stop
>>> (qemu) migrate "exec:gzip -c > STATEFILE.gz"
>>> (qemu) quit
>>>
>>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>>> -incoming "exec: gzip -c -d STATEFILE.gz"
>>> (qemu) info vm-generation-id
>>> 28b587fa-991b-4267-80d7-9cf28b746fe9
>>>
>>> guest
>>> 1. doesn't get GPE notification that it must receive
>>> 2. vmgenid client in Windows reads the same value
>>>      42d50e8bc109c09b:6cd1dcc90984339b
>>>
>> Strange, this was working for me, but with a slightly different test method:
>>
>>   * I use virsh save/restore
> 
> Awesome, this actually what I should try. All my guests are managed by
> libvirt (with the occasional <qemu:arg>, for development), and direct
> QEMU monitor commands such as
> 
>   virsh qemu-monitor-command ovmf.rhel7 --hmp 'info vm-generation-id'
> 
> only work for me if they are reasonably non-intrusive.
> 
>>   * While I do later testing with Windows, during development I use a
>>     Linux kernel module I wrote that keeps track of GUID and
>>     notifications.  I’m happy to share this with you if interested.
> 
> Please do. If you have a public git repo somewhere, that would be
> awesome. (Bonus points if the module builds out-of-tree, if the
> kernel-devel package is installed.)
> 
> NB: while the set-id monitor command was part of the series, I did test
> it to the extent that I checked the SCI ("ACPI interrupt") count in the
> guest, in /proc/interrupts. I did see it increase, so minimally the SCI
> injection was fine.

So, I did some testing with a RHEL-7 guest. I passed '-device
vmgenid=auto' to QEMU using the <qemu:arg> element in the domain XML.

(1) I started the guest normally, and grepped /proc/interrupts for
"acpi". Zero interrupts on either VCPU.

(2) Dumped the guest RAM to a file with "virsh dump ... --memory-only",
opened it with crash, and listed the 16 GUID bytes at the offset that
the firmware (OVMF) reported at startup.

(3) cycled through "virsh managedsave" and "virsh start"

(4) grepped /proc/interrupts again for "acpi". One interrupt had been
delivered to one of the VCPUs, all others were zero.

(5) Repeated step (2). The bytes listed this time were different.

(6) Issued "virsh qemu-monitor-command ovmf.rhel7 --hmp 'info
vm-generation-id", and compared the output against the bytes dumped
(with crash) from guest memory, in step 5. They were a match.

So, to me it seems like the SCI is injected, and the memory contents are
changed.

---*---

Windows Server 2012 R2 test:

(7) booted the guest similarly with '-device vmgenid=auto' via
<qemu:arg> in the domain XML.

(8) Initial check from the host side:

$ virsh qemu-monitor-command ovmf.win2012r2.q35 \
    --hmp 'info vm-generation-id'
a3f7c334-7dc4-4694-8b8f-abf52abb072f

(9) Verifying the same from within, using Vadim's program (note: I
logged into the VM with ssh, using Cygwin's SSHD in the guest):

$ ./vmgenid.exe
VmCounterValue: 46947dc4a3f7c334:2f07bb2af5ab8f8b
0x34 0xc3 0xf7 0xa3 0xc4 0x7d 0x94 0x46 0x8b 0x8f 0xab 0xf5 0x2a 0xbb
0x07 0x2f

This is a match, so the initial setup works. (Look only at the raw byte
dump in the second line -- it matches the Little Endian UUID
representation as specified in the SMBIOS spec!)

(10) Logged out of the guest (with ssh), cycled through "virsh
managedsave" and "virsh start" for the domain, logged back in.

(11) in the guest:

$ ./vmgenid.exe
VmCounterValue: 4a12296b382162da:6c00d1a52699b7bd
0xda 0x62 0x21 0x38 0x6b 0x29 0x12 0x4a 0xbd 0xb7 0x99 0x26 0xa5 0xd1
0x00 0x6c

(12) on the host:

$ virsh qemu-monitor-command ovmf.win2012r2.q35 \
      --hmp 'info vm-generation-id'
382162da-296b-4a12-bdb7-9926a5d1006c

This is again a match. (Again, look only at the raw byte dump from
vmgenid.exe under (11), and consider the BE/LE conversion for the first
three segments!)

(13) Logged out of the guest with ssh, and started Vadim's other program
(vmgenid_wait.exe), this time from a normal CMD window on the GUI. The
program started, reproduced the above output (seen under (11)), and then
went to sleep (waiting).

(14) cycled through "virsh managedsave" and "virsh start" for the domain.

(15) The domain resumed, and Vadim's vmgenid_wait.exe woke up, printing
(manual transcript):

VmCounterValue changed to: 495ba7807ed37772:195d0cff681f7a7

Please refer to the following screenshot:

http://people.redhat.com/~lersek/vmgenid-dd1f68c5-89b0-4458-84fa-de9e3d23f4cb/Screenshot_ovmf.win2012r2.q35_2017-02-17_20:34:41.png

(16) on the host:

$ virsh qemu-monitor-command ovmf.win2012r2.q35 \
      --hmp 'info vm-generation-id'

7ed37772-a780-495b-a7f7-81f6cfd09501

This is again a match. It is not easy to see, because Vadim's
"vmgenid_wait.exe" does not print the raw byte dump after it wakes up;
the raw byte dump is only printed before it goes to sleep. After wakeup,
it only dumps the composed values. Somewhat more confusingly, the 64-bit
hex integers are not zero padded, we'll have to make up for that manually.

So here goes:

   [A]      [B]  [C]  [D]  [E]
   7ed37772-a780-495b-a7f7-81f6cfd09501 (from the host)

   [C]  [B]  [A]        [E']         [D']
   495b a780 7ed37772 : 0195d0cff681 f7a7 (from vmgenid_wait.exe)
                        ^
                        zero padding added manually

The parts marked with an apostrophe (') are reversed, byte-wise.

So, I'm going to have to declare this "working by design".

Confirming my earlier Tested-by (same patches as before):

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

What could be the difference between Igor's setup and mine? Perhaps the
BIOS? (Again, I used OVMF.)

The "managedsave" command of virsh boils down to (see
"src/qemu/qemu_driver.c" in the libvirt source):

qemuDomainManagedSave()
  qemuDomainSaveInternal()
    qemuProcessStopCPUs()
    qemuDomainSaveMemory()
      qemuDomainSaveHeader()
      qemuMigrationToFile()
        qemuMonitorMigrateToFd()
          ...
    qemuProcessStop()

I capture all traffic between libvirt and the QEMU monitor, and between
libvirt and the QEMU guest agent, in the libvirt log file, as a rule, so
I can paste the relevant lines:

Libvirt sending the file descriptor to QEMU:

2017-02-17 19:31:54.305+0000: 16586: debug :
qemuMonitorJSONCommandWithFd:296 : Send command
'{"execute":"getfd","arguments":{"fdname":"migrate"},"id":"libvirt-30"}'
for write with FD 26

Libvirt starting the migration:

2017-02-17 19:31:54.306+0000: 16586: debug :
qemuMonitorJSONCommandWithFd:296 : Send command
'{"execute":"migrate","arguments":{"detach":true,"blk":false,"inc":false,"uri":"fd:migrate"},"id":"libvirt-31"}'
for write with FD -1

Then loading it:

2017-02-17 19:32:02.083+0000: 16585: debug :
qemuMonitorJSONCommandWithFd:296 : Send command
'{"execute":"migrate-incoming","arguments":{"uri":"fd:25"},"id":"libvirt-17"}'
for write with FD -1

I don't have the slightest idea why it failed for Igor -- I can only
suspect the SeaBIOS patches.

Note that in the SeaBIOS discussion, Ben mentioned that he wasn't
actually seeing the fw_cfg writes in QEMU on the S3 resume path, despite
SeaBIOS attempting them. So, perhaps, is there a bug in the latest
SeaBIOS patches that prevent fw_cfg writes completely, even on the
normal boot path? That would be consistent with Igor's results: the
initial download works (hence the first GUID can be seen), but then the
update does not work (because QEMU has not received the address).

Thanks
Laszlo
Laszlo Ersek Feb. 17, 2017, 8:42 p.m. UTC | #7
On 02/17/17 19:34, Ben Warren wrote:
> 
>> On Feb 17, 2017, at 8:03 AM, Laszlo Ersek <lersek@redhat.com
>> <mailto:lersek@redhat.com>> wrote:
>>
>> On 02/17/17 16:33, Ben Warren wrote:
>>>
>>>> On Feb 17, 2017, at 2:43 AM, Igor Mammedov <imammedo@redhat.com
>>>> <mailto:imammedo@redhat.com>
>>>> <mailto:imammedo@redhat.com>> wrote:
>>>>
>>>> On Thu, 16 Feb 2017 15:15:36 -0800
>>>> ben@skyportsystems.com
>>>> <mailto:ben@skyportsystems.com> <mailto:ben@skyportsystems.com> wrote:
>>>>
>>>>> From: Ben Warren <ben@skyportsystems.com
>>>>> <mailto:ben@skyportsystems.com> <mailto:ben@skyportsystems.com>>
>>>>>
>>>>> This implements the VM Generation ID feature by passing a 128-bit
>>>>> GUID to the guest via a fw_cfg blob.
>>>>> Any time the GUID changes, an ACPI notify event is sent to the guest
>>>>>
>>>>> The user interface is a simple device with one parameter:
>>>>> - guid (string, must be "auto" or in UUID format
>>>>>  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
>>>> I've given it some testing with WS2012R2 and v4 patches for Seabios,
>>>>
>>>> Windows is able to read initial GUID allocation and writeback
>>>> seems to work somehow:
>>>>
>>>> (qemu) info vm-generation-id
>>>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>>>
>>>> vmgenid client in Windows reads it as 2 following 64bit integers:
>>>> 42d50e8bc109c09b:6cd1dcc90984339b
>>>>
>>>> However update path/restore from snapshot doesn't
>>>> here is as I've tested it:
>>>>
>>>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>>>> (qemu) info vm-generation-id
>>>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>>> (qemu) stop
>>>> (qemu) migrate "exec:gzip -c > STATEFILE.gz"
>>>> (qemu) quit
>>>>
>>>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>>>> -incoming "exec: gzip -c -d STATEFILE.gz"
>>>> (qemu) info vm-generation-id
>>>> 28b587fa-991b-4267-80d7-9cf28b746fe9
>>>>
>>>> guest
>>>> 1. doesn't get GPE notification that it must receive
>>>> 2. vmgenid client in Windows reads the same value
>>>>     42d50e8bc109c09b:6cd1dcc90984339b
>>>>
>>> Strange, this was working for me, but with a slightly different test
>>> method:
>>>
>>>  * I use virsh save/restore
>>
>> Awesome, this actually what I should try. All my guests are managed by
>> libvirt (with the occasional <qemu:arg>, for development), and direct
>> QEMU monitor commands such as
>>
>>  virsh qemu-monitor-command ovmf.rhel7 --hmp 'info vm-generation-id'
>>
>> only work for me if they are reasonably non-intrusive.
>>
>>>  * While I do later testing with Windows, during development I use a
>>>    Linux kernel module I wrote that keeps track of GUID and
>>>    notifications.  I’m happy to share this with you if interested.
>>
>> Please do. If you have a public git repo somewhere, that would be
>> awesome. (Bonus points if the module builds out-of-tree, if the
>> kernel-devel package is installed.)
>>
> Here you go:
> https://github.com/ben-skyportsystems/vmgenid-test

Ah, thanks -- I apologize, I stopped refreshing my incoming email while
I was testing and writing up the results. I'll stash this for later though.

Thanks!
Laszlo

> I don’t know if something like this would ever be accepted into the
> Linux kernel, but it has been invaluable to me, and I’d like to see it
> somewhere better.
> 
>> NB: while the set-id monitor command was part of the series, I did test
>> it to the extent that I checked the SCI ("ACPI interrupt") count in the
>> guest, in /proc/interrupts. I did see it increase, so minimally the SCI
>> injection was fine.
>>
>> Thanks!
>> Laszlo
>>
>>> I’ll dig into this morning.
>>>
>>> —Ben
>>> <snip>
>
ben@skyportsystems.com Feb. 18, 2017, 12:15 a.m. UTC | #8
> On Feb 17, 2017, at 12:07 PM, Laszlo Ersek <lersek@redhat.com> wrote:
> 
> On 02/17/17 17:03, Laszlo Ersek wrote:
>> On 02/17/17 16:33, Ben Warren wrote:
>>> 
>>>> On Feb 17, 2017, at 2:43 AM, Igor Mammedov <imammedo@redhat.com
>>>> <mailto:imammedo@redhat.com>> wrote:
>>>> 
>>>> On Thu, 16 Feb 2017 15:15:36 -0800
>>>> ben@skyportsystems.com <mailto:ben@skyportsystems.com> wrote:
>>>> 
>>>>> From: Ben Warren <ben@skyportsystems.com <mailto:ben@skyportsystems.com>>
>>>>> 
>>>>> This implements the VM Generation ID feature by passing a 128-bit
>>>>> GUID to the guest via a fw_cfg blob.
>>>>> Any time the GUID changes, an ACPI notify event is sent to the guest
>>>>> 
>>>>> The user interface is a simple device with one parameter:
>>>>> - guid (string, must be "auto" or in UUID format
>>>>>  xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
>>>> I've given it some testing with WS2012R2 and v4 patches for Seabios,
>>>> 
>>>> Windows is able to read initial GUID allocation and writeback
>>>> seems to work somehow:
>>>> 
>>>> (qemu) info vm-generation-id
>>>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>>> 
>>>> vmgenid client in Windows reads it as 2 following 64bit integers:
>>>> 42d50e8bc109c09b:6cd1dcc90984339b
>>>> 
>>>> However update path/restore from snapshot doesn't
>>>> here is as I've tested it:
>>>> 
>>>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>>>> (qemu) info vm-generation-id
>>>> c109c09b-0e8b-42d5-9b33-8409c9dcd16c
>>>> (qemu) stop
>>>> (qemu) migrate "exec:gzip -c > STATEFILE.gz"
>>>> (qemu) quit
>>>> 
>>>> qemu-system-x86_64 -device vmgenid,id=testvgid,guid=auto -monitor stdio
>>>> -incoming "exec: gzip -c -d STATEFILE.gz"
>>>> (qemu) info vm-generation-id
>>>> 28b587fa-991b-4267-80d7-9cf28b746fe9
>>>> 
>>>> guest
>>>> 1. doesn't get GPE notification that it must receive
>>>> 2. vmgenid client in Windows reads the same value
>>>>     42d50e8bc109c09b:6cd1dcc90984339b
>>>> 
>>> Strange, this was working for me, but with a slightly different test method:
>>> 
>>>  * I use virsh save/restore
>> 
>> Awesome, this actually what I should try. All my guests are managed by
>> libvirt (with the occasional <qemu:arg>, for development), and direct
>> QEMU monitor commands such as
>> 
>>  virsh qemu-monitor-command ovmf.rhel7 --hmp 'info vm-generation-id'
>> 
>> only work for me if they are reasonably non-intrusive.
>> 
>>>  * While I do later testing with Windows, during development I use a
>>>    Linux kernel module I wrote that keeps track of GUID and
>>>    notifications.  I’m happy to share this with you if interested.
>> 
>> Please do. If you have a public git repo somewhere, that would be
>> awesome. (Bonus points if the module builds out-of-tree, if the
>> kernel-devel package is installed.)
>> 
>> NB: while the set-id monitor command was part of the series, I did test
>> it to the extent that I checked the SCI ("ACPI interrupt") count in the
>> guest, in /proc/interrupts. I did see it increase, so minimally the SCI
>> injection was fine.
> 
> So, I did some testing with a RHEL-7 guest. I passed '-device
> vmgenid=auto' to QEMU using the <qemu:arg> element in the domain XML.
> 
> (1) I started the guest normally, and grepped /proc/interrupts for
> "acpi". Zero interrupts on either VCPU.
> 
> (2) Dumped the guest RAM to a file with "virsh dump ... --memory-only",
> opened it with crash, and listed the 16 GUID bytes at the offset that
> the firmware (OVMF) reported at startup.
> 
> (3) cycled through "virsh managedsave" and "virsh start"
> 
> (4) grepped /proc/interrupts again for "acpi". One interrupt had been
> delivered to one of the VCPUs, all others were zero.
> 
> (5) Repeated step (2). The bytes listed this time were different.
> 
> (6) Issued "virsh qemu-monitor-command ovmf.rhel7 --hmp 'info
> vm-generation-id", and compared the output against the bytes dumped
> (with crash) from guest memory, in step 5. They were a match.
> 
> So, to me it seems like the SCI is injected, and the memory contents are
> changed.
> 
> ---*---
> 
> Windows Server 2012 R2 test:
> 
> (7) booted the guest similarly with '-device vmgenid=auto' via
> <qemu:arg> in the domain XML.
> 
> (8) Initial check from the host side:
> 
> $ virsh qemu-monitor-command ovmf.win2012r2.q35 \
>    --hmp 'info vm-generation-id'
> a3f7c334-7dc4-4694-8b8f-abf52abb072f
> 
> (9) Verifying the same from within, using Vadim's program (note: I
> logged into the VM with ssh, using Cygwin's SSHD in the guest):
> 
> $ ./vmgenid.exe
> VmCounterValue: 46947dc4a3f7c334:2f07bb2af5ab8f8b
> 0x34 0xc3 0xf7 0xa3 0xc4 0x7d 0x94 0x46 0x8b 0x8f 0xab 0xf5 0x2a 0xbb
> 0x07 0x2f
> 
> This is a match, so the initial setup works. (Look only at the raw byte
> dump in the second line -- it matches the Little Endian UUID
> representation as specified in the SMBIOS spec!)
> 
> (10) Logged out of the guest (with ssh), cycled through "virsh
> managedsave" and "virsh start" for the domain, logged back in.
> 
> (11) in the guest:
> 
> $ ./vmgenid.exe
> VmCounterValue: 4a12296b382162da:6c00d1a52699b7bd
> 0xda 0x62 0x21 0x38 0x6b 0x29 0x12 0x4a 0xbd 0xb7 0x99 0x26 0xa5 0xd1
> 0x00 0x6c
> 
> (12) on the host:
> 
> $ virsh qemu-monitor-command ovmf.win2012r2.q35 \
>      --hmp 'info vm-generation-id'
> 382162da-296b-4a12-bdb7-9926a5d1006c
> 
> This is again a match. (Again, look only at the raw byte dump from
> vmgenid.exe under (11), and consider the BE/LE conversion for the first
> three segments!)
> 
> (13) Logged out of the guest with ssh, and started Vadim's other program
> (vmgenid_wait.exe), this time from a normal CMD window on the GUI. The
> program started, reproduced the above output (seen under (11)), and then
> went to sleep (waiting).
> 
> (14) cycled through "virsh managedsave" and "virsh start" for the domain.
> 
> (15) The domain resumed, and Vadim's vmgenid_wait.exe woke up, printing
> (manual transcript):
> 
> VmCounterValue changed to: 495ba7807ed37772:195d0cff681f7a7
> 
> Please refer to the following screenshot:
> 
> http://people.redhat.com/~lersek/vmgenid-dd1f68c5-89b0-4458-84fa-de9e3d23f4cb/Screenshot_ovmf.win2012r2.q35_2017-02-17_20:34:41.png
> 
> (16) on the host:
> 
> $ virsh qemu-monitor-command ovmf.win2012r2.q35 \
>      --hmp 'info vm-generation-id'
> 
> 7ed37772-a780-495b-a7f7-81f6cfd09501
> 
> This is again a match. It is not easy to see, because Vadim's
> "vmgenid_wait.exe" does not print the raw byte dump after it wakes up;
> the raw byte dump is only printed before it goes to sleep. After wakeup,
> it only dumps the composed values. Somewhat more confusingly, the 64-bit
> hex integers are not zero padded, we'll have to make up for that manually.
> 
> So here goes:
> 
>   [A]      [B]  [C]  [D]  [E]
>   7ed37772-a780-495b-a7f7-81f6cfd09501 (from the host)
> 
>   [C]  [B]  [A]        [E']         [D']
>   495b a780 7ed37772 : 0195d0cff681 f7a7 (from vmgenid_wait.exe)
>                        ^
>                        zero padding added manually
> 
> The parts marked with an apostrophe (') are reversed, byte-wise.
> 
> So, I'm going to have to declare this "working by design".
> 
> Confirming my earlier Tested-by (same patches as before):
> 
> Tested-by: Laszlo Ersek <lersek@redhat.com>
> 
> What could be the difference between Igor's setup and mine? Perhaps the
> BIOS? (Again, I used OVMF.)
> 
> The "managedsave" command of virsh boils down to (see
> "src/qemu/qemu_driver.c" in the libvirt source):
> 
> qemuDomainManagedSave()
>  qemuDomainSaveInternal()
>    qemuProcessStopCPUs()
>    qemuDomainSaveMemory()
>      qemuDomainSaveHeader()
>      qemuMigrationToFile()
>        qemuMonitorMigrateToFd()
>          ...
>    qemuProcessStop()
> 
> I capture all traffic between libvirt and the QEMU monitor, and between
> libvirt and the QEMU guest agent, in the libvirt log file, as a rule, so
> I can paste the relevant lines:
> 
> Libvirt sending the file descriptor to QEMU:
> 
> 2017-02-17 19:31:54.305+0000: 16586: debug :
> qemuMonitorJSONCommandWithFd:296 : Send command
> '{"execute":"getfd","arguments":{"fdname":"migrate"},"id":"libvirt-30"}'
> for write with FD 26
> 
> Libvirt starting the migration:
> 
> 2017-02-17 19:31:54.306+0000: 16586: debug :
> qemuMonitorJSONCommandWithFd:296 : Send command
> '{"execute":"migrate","arguments":{"detach":true,"blk":false,"inc":false,"uri":"fd:migrate"},"id":"libvirt-31"}'
> for write with FD -1
> 
> Then loading it:
> 
> 2017-02-17 19:32:02.083+0000: 16585: debug :
> qemuMonitorJSONCommandWithFd:296 : Send command
> '{"execute":"migrate-incoming","arguments":{"uri":"fd:25"},"id":"libvirt-17"}'
> for write with FD -1
> 
> I don't have the slightest idea why it failed for Igor -- I can only
> suspect the SeaBIOS patches.
> 
> Note that in the SeaBIOS discussion, Ben mentioned that he wasn't
> actually seeing the fw_cfg writes in QEMU on the S3 resume path, despite
> SeaBIOS attempting them. So, perhaps, is there a bug in the latest
> SeaBIOS patches that prevent fw_cfg writes completely, even on the
> normal boot path? That would be consistent with Igor's results: the
> initial download works (hence the first GUID can be seen), but then the
> update does not work (because QEMU has not received the address).
> 
> Thanks
> Laszlo

My functional testing was also successful.  I did make the one SeaBIOS change suggested by Laszlo, which I’ll post in a minute as v5.

In my case, I used a Windows 2012R2 VM that is a fully-joined Active Directory Domain Controller, so was able to query some VM Generation ID-derived data via power shell.  This use case is the reason why VM Generation ID exists in the first place.
We manage the VM using libvirt with this extra snippet in the domain XML:

  <qemu:commandline>
    <qemu:arg value='-device'/>
    <qemu:arg value='vmgenid,guid=auto'/>
  </qemu:commandline>

The commands I used within Windows are:

1.  repadmin /showrepl
	This shows the replication state of the Domain Controller, most importantly printing the “DSA invocation ID”.  This number is derived from VM Generation ID, and changes whenever Active Directory considers its local data dirty.  It’s an indicator the Windows detected a change in VM Generation ID.

2.  (Get-ADObject <x500 stuff> -server <domain controller> -property meds-generationid).’msds-generationid’
	This gets the least-significant 64 bits of the VM Generation ID GUID
	
	http://blogs.metcorpconsulting.com/tech/?p=1594


Initial State:
=========

virsh # qemu-monitor-command --hmp 20 info vm-generation-id
d43edc6c-9686-4bf3-8621-2a4be879eb22

PS C:\Users\bran> (Get-ADObject "cn=whiteharbor,ou=Domain Controllers,DC=thenorth,dc=ws" -server whiteharbor.thenorth.ws
 -property msds-generationid).'msds-generationid'
108
220
62
212
134
150
243
75

***
NOTE:  108,220,62,212,212,134,150,243,75 == 6c,dc,3e,d4,86,96,f3,4b  ==> The GUID shown in HMP in Little-endian format!
***

PS C:\Users\bran> repadmin /showrepl

Repadmin: running command /showrepl against full DC localhost
Default-First-Site-Name\WHITEHARBOR
DSA Options: IS_GC
Site Options: (none)
DSA object GUID: 3dcfed54-28d4-448e-9087-140dd251e1b4
DSA invocationID: 56ca3644-8bc3-4dd8-9860-df0b3f5ce06f

Reboot within Windows:
==================

Nothing changed


After virsh save, virsh restore:
=======================

virsh # qemu-monitor-command --hmp 22 info vm-generation-id
573a0dbc-6eca-49fd-a632-f4f1e016502a

PS C:\Users\bran> (Get-ADObject "cn=whiteharbor,ou=Domain Controllers,DC=thenorth,dc=ws" -server whiteharbor.thenorth.ws
 -property msds-generationid).'msds-generationid'
188
13
58
87
202
110
253
73
PS C:\Users\bran> repadmin /showrepl

Repadmin: running command /showrepl against full DC localhost
Default-First-Site-Name\WHITEHARBOR
DSA Options: IS_GC
Site Options: (none)
DSA object GUID: 3dcfed54-28d4-448e-9087-140dd251e1b4
DSA invocationID: 7b6dda08-d23b-4ca2-982d-774f9c214442


I’m satisfied that the current patches work and don’t really understand Igor’s failure, other than maybe the SeaBIOS difference.

regards,
Ben
diff mbox

Patch

diff --git a/default-configs/i386-softmmu.mak b/default-configs/i386-softmmu.mak
index 48b07a4..029e952 100644
--- a/default-configs/i386-softmmu.mak
+++ b/default-configs/i386-softmmu.mak
@@ -59,3 +59,4 @@  CONFIG_I82801B11=y
 CONFIG_SMBIOS=y
 CONFIG_HYPERV_TESTDEV=$(CONFIG_KVM)
 CONFIG_PXB=y
+CONFIG_ACPI_VMGENID=y
diff --git a/default-configs/x86_64-softmmu.mak b/default-configs/x86_64-softmmu.mak
index fd96345..d1d7432 100644
--- a/default-configs/x86_64-softmmu.mak
+++ b/default-configs/x86_64-softmmu.mak
@@ -59,3 +59,4 @@  CONFIG_I82801B11=y
 CONFIG_SMBIOS=y
 CONFIG_HYPERV_TESTDEV=$(CONFIG_KVM)
 CONFIG_PXB=y
+CONFIG_ACPI_VMGENID=y
diff --git a/hw/acpi/Makefile.objs b/hw/acpi/Makefile.objs
index 6acf798..11c35bc 100644
--- a/hw/acpi/Makefile.objs
+++ b/hw/acpi/Makefile.objs
@@ -5,6 +5,7 @@  common-obj-$(CONFIG_ACPI_CPU_HOTPLUG) += cpu_hotplug.o
 common-obj-$(CONFIG_ACPI_MEMORY_HOTPLUG) += memory_hotplug.o
 common-obj-$(CONFIG_ACPI_CPU_HOTPLUG) += cpu.o
 common-obj-$(CONFIG_ACPI_NVDIMM) += nvdimm.o
+common-obj-$(CONFIG_ACPI_VMGENID) += vmgenid.o
 common-obj-$(call lnot,$(CONFIG_ACPI_X86)) += acpi-stub.o
 
 common-obj-y += acpi_interface.o
diff --git a/hw/acpi/vmgenid.c b/hw/acpi/vmgenid.c
new file mode 100644
index 0000000..c8465df
--- /dev/null
+++ b/hw/acpi/vmgenid.c
@@ -0,0 +1,242 @@ 
+/*
+ *  Virtual Machine Generation ID Device
+ *
+ *  Copyright (C) 2017 Skyport Systems.
+ *
+ *  Author: Ben Warren <ben@skyportsystems.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qmp-commands.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/vmgenid.h"
+#include "hw/nvram/fw_cfg.h"
+#include "sysemu/sysemu.h"
+
+void vmgenid_build_acpi(VmGenIdState *vms, GArray *table_data, GArray *guid,
+                        BIOSLinker *linker)
+{
+    Aml *ssdt, *dev, *scope, *method, *addr, *if_ctx;
+    uint32_t vgia_offset;
+    QemuUUID guid_le;
+
+    /* Fill in the GUID values.  These need to be converted to little-endian
+     * first, since that's what the guest expects
+     */
+    g_array_set_size(guid, VMGENID_FW_CFG_SIZE - ARRAY_SIZE(guid_le.data));
+    guid_le = vms->guid;
+    qemu_uuid_bswap(&guid_le);
+    /* The GUID is written at a fixed offset into the fw_cfg file
+     * in order to implement the "OVMF SDT Header probe suppressor"
+     * see docs/specs/vmgenid.txt for more details
+     */
+    g_array_insert_vals(guid, VMGENID_GUID_OFFSET, guid_le.data,
+                        ARRAY_SIZE(guid_le.data));
+
+    /* Put this in a separate SSDT table */
+    ssdt = init_aml_allocator();
+
+    /* Reserve space for header */
+    acpi_data_push(ssdt->buf, sizeof(AcpiTableHeader));
+
+    /* Storage for the GUID address */
+    vgia_offset = table_data->len +
+        build_append_named_dword(ssdt->buf, "VGIA");
+    scope = aml_scope("\\_SB");
+    dev = aml_device("VGEN");
+    aml_append(dev, aml_name_decl("_HID", aml_string("QEMUVGID")));
+    aml_append(dev, aml_name_decl("_CID", aml_string("VM_Gen_Counter")));
+    aml_append(dev, aml_name_decl("_DDN", aml_string("VM_Gen_Counter")));
+
+    /* Simple status method to check that address is linked and non-zero */
+    method = aml_method("_STA", 0, AML_NOTSERIALIZED);
+    addr = aml_local(0);
+    aml_append(method, aml_store(aml_int(0xf), addr));
+    if_ctx = aml_if(aml_equal(aml_name("VGIA"), aml_int(0)));
+    aml_append(if_ctx, aml_store(aml_int(0), addr));
+    aml_append(method, if_ctx);
+    aml_append(method, aml_return(addr));
+    aml_append(dev, method);
+
+    /* the ADDR method returns two 32-bit words representing the lower and
+     * upper halves * of the physical address of the fw_cfg blob
+     * (holding the GUID)
+     */
+    method = aml_method("ADDR", 0, AML_NOTSERIALIZED);
+
+    addr = aml_local(0);
+    aml_append(method, aml_store(aml_package(2), addr));
+
+    aml_append(method, aml_store(aml_add(aml_name("VGIA"),
+                                         aml_int(VMGENID_GUID_OFFSET), NULL),
+                                 aml_index(addr, aml_int(0))));
+    aml_append(method, aml_store(aml_int(0), aml_index(addr, aml_int(1))));
+    aml_append(method, aml_return(addr));
+
+    aml_append(dev, method);
+    aml_append(scope, dev);
+    aml_append(ssdt, scope);
+
+    /* attach an ACPI notify */
+    method = aml_method("\\_GPE._E05", 0, AML_NOTSERIALIZED);
+    aml_append(method, aml_notify(aml_name("\\_SB.VGEN"), aml_int(0x80)));
+    aml_append(ssdt, method);
+
+    g_array_append_vals(table_data, ssdt->buf->data, ssdt->buf->len);
+
+    /* Allocate guest memory for the Data fw_cfg blob */
+    bios_linker_loader_alloc(linker, VMGENID_GUID_FW_CFG_FILE, guid, 4096,
+                             false /* page boundary, high memory */);
+
+    /* Patch address of GUID fw_cfg blob into the ADDR fw_cfg blob
+     * so QEMU can write the GUID there.  The address is expected to be
+     * < 4GB, but write 64 bits anyway.
+     * The address that is patched in is offset in order to implement
+     * the "OVMF SDT Header probe suppressor"
+     * see docs/specs/vmgenid.txt for more details.
+     */
+    bios_linker_loader_write_pointer(linker,
+        VMGENID_ADDR_FW_CFG_FILE, 0, sizeof(uint64_t),
+        VMGENID_GUID_FW_CFG_FILE, VMGENID_GUID_OFFSET);
+
+    /* Patch address of GUID fw_cfg blob into the AML so OSPM can retrieve
+     * and read it.  Note that while we provide storage for 64 bits, only
+     * the least-signficant 32 get patched into AML.
+     */
+    bios_linker_loader_add_pointer(linker,
+        ACPI_BUILD_TABLE_FILE, vgia_offset, sizeof(uint32_t),
+        VMGENID_GUID_FW_CFG_FILE, 0);
+
+    build_header(linker, table_data,
+        (void *)(table_data->data + table_data->len - ssdt->buf->len),
+        "SSDT", ssdt->buf->len, 1, NULL, "VMGENID");
+    free_aml_allocator();
+}
+
+void vmgenid_add_fw_cfg(VmGenIdState *vms, FWCfgState *s, GArray *guid)
+{
+    /* Create a read-only fw_cfg file for GUID */
+    fw_cfg_add_file(s, VMGENID_GUID_FW_CFG_FILE, guid->data,
+                    VMGENID_FW_CFG_SIZE);
+    /* Create a read-write fw_cfg file for Address */
+    fw_cfg_add_file_callback(s, VMGENID_ADDR_FW_CFG_FILE, NULL, NULL,
+                             vms->vmgenid_addr_le,
+                             ARRAY_SIZE(vms->vmgenid_addr_le), false);
+}
+
+static void vmgenid_update_guest(VmGenIdState *vms)
+{
+    Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
+    uint32_t vmgenid_addr;
+    QemuUUID guid_le;
+
+    if (obj) {
+        /* Write the GUID to guest memory */
+        memcpy(&vmgenid_addr, vms->vmgenid_addr_le, sizeof(vmgenid_addr));
+        vmgenid_addr = le32_to_cpu(vmgenid_addr);
+        /* A zero value in vmgenid_addr means that BIOS has not yet written
+         * the address
+         */
+        if (vmgenid_addr) {
+            /* QemuUUID has the first three words as big-endian, and expect
+             * that any GUIDs passed in will always be BE.  The guest,
+             * however, will expect the fields to be little-endian.
+             * Perform a byte swap immediately before writing.
+             */
+            guid_le = vms->guid;
+            qemu_uuid_bswap(&guid_le);
+            /* The GUID is written at a fixed offset into the fw_cfg file
+             * in order to implement the "OVMF SDT Header probe suppressor"
+             * see docs/specs/vmgenid.txt for more details.
+             */
+            cpu_physical_memory_write(vmgenid_addr, guid_le.data,
+                                      sizeof(guid_le.data));
+            /* Send _GPE.E05 event */
+            acpi_send_event(DEVICE(obj), ACPI_VMGENID_CHANGE_STATUS);
+        }
+    }
+}
+
+static void vmgenid_set_guid(Object *obj, const char *value, Error **errp)
+{
+    VmGenIdState *vms = VMGENID(obj);
+
+    if (!strcmp(value, "auto")) {
+        qemu_uuid_generate(&vms->guid);
+    } else if (qemu_uuid_parse(value, &vms->guid) < 0) {
+        error_setg(errp, "'%s. %s': Failed to parse GUID string: %s",
+                   object_get_typename(OBJECT(vms)), VMGENID_GUID, value);
+        return;
+    }
+
+    vmgenid_update_guest(vms);
+}
+
+/* After restoring an image, we need to update the guest memory and notify
+ * it of a potential change to VM Generation ID
+ */
+static int vmgenid_post_load(void *opaque, int version_id)
+{
+    VmGenIdState *vms = opaque;
+    vmgenid_update_guest(vms);
+    return 0;
+}
+
+static const VMStateDescription vmstate_vmgenid = {
+    .name = "vmgenid",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .post_load = vmgenid_post_load,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT8_ARRAY(vmgenid_addr_le, VmGenIdState, sizeof(uint64_t)),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void vmgenid_handle_reset(void *opaque)
+{
+    VmGenIdState *vms = VMGENID(opaque);
+    /* Clear the guest-allocated GUID address when the VM resets */
+    memset(vms->vmgenid_addr_le, 0, ARRAY_SIZE(vms->vmgenid_addr_le));
+}
+
+static void vmgenid_realize(DeviceState *dev, Error **errp)
+{
+    VmGenIdState *vms = VMGENID(dev);
+    qemu_register_reset(vmgenid_handle_reset, vms);
+}
+
+static void vmgenid_device_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd = &vmstate_vmgenid;
+    dc->realize = vmgenid_realize;
+    dc->hotpluggable = false;
+
+    object_class_property_add_str(klass, VMGENID_GUID, NULL,
+                                  vmgenid_set_guid, NULL);
+    object_class_property_set_description(klass, VMGENID_GUID,
+                                    "Set Global Unique Identifier "
+                                    "(big-endian) or auto for random value",
+                                    NULL);
+}
+
+static const TypeInfo vmgenid_device_info = {
+    .name          = VMGENID_DEVICE,
+    .parent        = TYPE_DEVICE,
+    .instance_size = sizeof(VmGenIdState),
+    .class_init    = vmgenid_device_class_init,
+};
+
+static void vmgenid_register_types(void)
+{
+    type_register_static(&vmgenid_device_info);
+}
+
+type_init(vmgenid_register_types)
diff --git a/hw/i386/acpi-build.c b/hw/i386/acpi-build.c
index 1c928ab..db04cf5 100644
--- a/hw/i386/acpi-build.c
+++ b/hw/i386/acpi-build.c
@@ -42,6 +42,7 @@ 
 #include "hw/acpi/memory_hotplug.h"
 #include "sysemu/tpm.h"
 #include "hw/acpi/tpm.h"
+#include "hw/acpi/vmgenid.h"
 #include "sysemu/tpm_backend.h"
 #include "hw/timer/mc146818rtc_regs.h"
 #include "sysemu/numa.h"
@@ -2610,6 +2611,7 @@  void acpi_build(AcpiBuildTables *tables, MachineState *machine)
     size_t aml_len = 0;
     GArray *tables_blob = tables->table_data;
     AcpiSlicOem slic_oem = { .id = NULL, .table_id = NULL };
+    Object *vmgenid_dev;
 
     acpi_get_pm_info(&pm);
     acpi_get_misc_info(&misc);
@@ -2653,6 +2655,13 @@  void acpi_build(AcpiBuildTables *tables, MachineState *machine)
     acpi_add_table(table_offsets, tables_blob);
     build_madt(tables_blob, tables->linker, pcms);
 
+    vmgenid_dev = find_vmgenid_dev();
+    if (vmgenid_dev) {
+        acpi_add_table(table_offsets, tables_blob);
+        vmgenid_build_acpi(VMGENID(vmgenid_dev), tables_blob,
+                           tables->vmgenid, tables->linker);
+    }
+
     if (misc.has_hpet) {
         acpi_add_table(table_offsets, tables_blob);
         build_hpet(tables_blob, tables->linker);
@@ -2823,6 +2832,7 @@  void acpi_setup(void)
     PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms);
     AcpiBuildTables tables;
     AcpiBuildState *build_state;
+    Object *vmgenid_dev;
 
     if (!pcms->fw_cfg) {
         ACPI_BUILD_DPRINTF("No fw cfg. Bailing out.\n");
@@ -2859,6 +2869,12 @@  void acpi_setup(void)
     fw_cfg_add_file(pcms->fw_cfg, ACPI_BUILD_TPMLOG_FILE,
                     tables.tcpalog->data, acpi_data_len(tables.tcpalog));
 
+    vmgenid_dev = find_vmgenid_dev();
+    if (vmgenid_dev) {
+        vmgenid_add_fw_cfg(VMGENID(vmgenid_dev), pcms->fw_cfg,
+                           tables.vmgenid);
+    }
+
     if (!pcmc->rsdp_in_ram) {
         /*
          * Keep for compatibility with old machine types.
diff --git a/include/hw/acpi/acpi_dev_interface.h b/include/hw/acpi/acpi_dev_interface.h
index 71d3c48..3c2e4e9 100644
--- a/include/hw/acpi/acpi_dev_interface.h
+++ b/include/hw/acpi/acpi_dev_interface.h
@@ -11,6 +11,7 @@  typedef enum {
     ACPI_CPU_HOTPLUG_STATUS = 4,
     ACPI_MEMORY_HOTPLUG_STATUS = 8,
     ACPI_NVDIMM_HOTPLUG_STATUS = 16,
+    ACPI_VMGENID_CHANGE_STATUS = 32,
 } AcpiEventStatusBits;
 
 #define TYPE_ACPI_DEVICE_IF "acpi-device-interface"
diff --git a/include/hw/acpi/vmgenid.h b/include/hw/acpi/vmgenid.h
new file mode 100644
index 0000000..db7fa0e
--- /dev/null
+++ b/include/hw/acpi/vmgenid.h
@@ -0,0 +1,35 @@ 
+#ifndef ACPI_VMGENID_H
+#define ACPI_VMGENID_H
+
+#include "hw/acpi/bios-linker-loader.h"
+#include "hw/qdev.h"
+#include "qemu/uuid.h"
+
+#define VMGENID_DEVICE           "vmgenid"
+#define VMGENID_GUID             "guid"
+#define VMGENID_GUID_FW_CFG_FILE      "etc/vmgenid_guid"
+#define VMGENID_ADDR_FW_CFG_FILE      "etc/vmgenid_addr"
+
+#define VMGENID_FW_CFG_SIZE      4096 /* Occupy a page of memory */
+#define VMGENID_GUID_OFFSET      40   /* allow space for
+                                       * OVMF SDT Header Probe Supressor
+                                       */
+
+#define VMGENID(obj) OBJECT_CHECK(VmGenIdState, (obj), VMGENID_DEVICE)
+
+typedef struct VmGenIdState {
+    DeviceClass parent_obj;
+    QemuUUID guid;                /* The 128-bit GUID seen by the guest */
+    uint8_t vmgenid_addr_le[8];   /* Address of the GUID (little-endian) */
+} VmGenIdState;
+
+static inline Object *find_vmgenid_dev(void)
+{
+    return object_resolve_path_type("", VMGENID_DEVICE, NULL);
+}
+
+void vmgenid_build_acpi(VmGenIdState *vms, GArray *table_data, GArray *guid,
+                        BIOSLinker *linker);
+void vmgenid_add_fw_cfg(VmGenIdState *vms, FWCfgState *s, GArray *guid);
+
+#endif