2 * Virtual Machine Generation ID Device
4 * Copyright (C) 2017 Skyport Systems.
6 * Author: Ben Warren <ben@skyportsystems.com>
8 * This work is licensed under the terms of the GNU GPL, version 2 or later.
9 * See the COPYING file in the top-level directory.
13 #include "qemu/osdep.h"
14 #include "qmp-commands.h"
15 #include "hw/acpi/acpi.h"
16 #include "hw/acpi/aml-build.h"
17 #include "hw/acpi/vmgenid.h"
18 #include "hw/nvram/fw_cfg.h"
19 #include "sysemu/sysemu.h"
21 void vmgenid_build_acpi(VmGenIdState
*vms
, GArray
*table_data
, GArray
*guid
,
24 Aml
*ssdt
, *dev
, *scope
, *method
, *addr
, *if_ctx
;
28 /* Fill in the GUID values. These need to be converted to little-endian
29 * first, since that's what the guest expects
31 g_array_set_size(guid
, VMGENID_FW_CFG_SIZE
- ARRAY_SIZE(guid_le
.data
));
33 qemu_uuid_bswap(&guid_le
);
34 /* The GUID is written at a fixed offset into the fw_cfg file
35 * in order to implement the "OVMF SDT Header probe suppressor"
36 * see docs/specs/vmgenid.txt for more details
38 g_array_insert_vals(guid
, VMGENID_GUID_OFFSET
, guid_le
.data
,
39 ARRAY_SIZE(guid_le
.data
));
41 /* Put this in a separate SSDT table */
42 ssdt
= init_aml_allocator();
44 /* Reserve space for header */
45 acpi_data_push(ssdt
->buf
, sizeof(AcpiTableHeader
));
47 /* Storage for the GUID address */
48 vgia_offset
= table_data
->len
+
49 build_append_named_dword(ssdt
->buf
, "VGIA");
50 scope
= aml_scope("\\_SB");
51 dev
= aml_device("VGEN");
52 aml_append(dev
, aml_name_decl("_HID", aml_string("QEMUVGID")));
53 aml_append(dev
, aml_name_decl("_CID", aml_string("VM_Gen_Counter")));
54 aml_append(dev
, aml_name_decl("_DDN", aml_string("VM_Gen_Counter")));
56 /* Simple status method to check that address is linked and non-zero */
57 method
= aml_method("_STA", 0, AML_NOTSERIALIZED
);
59 aml_append(method
, aml_store(aml_int(0xf), addr
));
60 if_ctx
= aml_if(aml_equal(aml_name("VGIA"), aml_int(0)));
61 aml_append(if_ctx
, aml_store(aml_int(0), addr
));
62 aml_append(method
, if_ctx
);
63 aml_append(method
, aml_return(addr
));
64 aml_append(dev
, method
);
66 /* the ADDR method returns two 32-bit words representing the lower and
67 * upper halves * of the physical address of the fw_cfg blob
70 method
= aml_method("ADDR", 0, AML_NOTSERIALIZED
);
73 aml_append(method
, aml_store(aml_package(2), addr
));
75 aml_append(method
, aml_store(aml_add(aml_name("VGIA"),
76 aml_int(VMGENID_GUID_OFFSET
), NULL
),
77 aml_index(addr
, aml_int(0))));
78 aml_append(method
, aml_store(aml_int(0), aml_index(addr
, aml_int(1))));
79 aml_append(method
, aml_return(addr
));
81 aml_append(dev
, method
);
82 aml_append(scope
, dev
);
83 aml_append(ssdt
, scope
);
85 /* attach an ACPI notify */
86 method
= aml_method("\\_GPE._E05", 0, AML_NOTSERIALIZED
);
87 aml_append(method
, aml_notify(aml_name("\\_SB.VGEN"), aml_int(0x80)));
88 aml_append(ssdt
, method
);
90 g_array_append_vals(table_data
, ssdt
->buf
->data
, ssdt
->buf
->len
);
92 /* Allocate guest memory for the Data fw_cfg blob */
93 bios_linker_loader_alloc(linker
, VMGENID_GUID_FW_CFG_FILE
, guid
, 4096,
94 false /* page boundary, high memory */);
96 /* Patch address of GUID fw_cfg blob into the ADDR fw_cfg blob
97 * so QEMU can write the GUID there. The address is expected to be
98 * < 4GB, but write 64 bits anyway.
99 * The address that is patched in is offset in order to implement
100 * the "OVMF SDT Header probe suppressor"
101 * see docs/specs/vmgenid.txt for more details.
103 bios_linker_loader_write_pointer(linker
,
104 VMGENID_ADDR_FW_CFG_FILE
, 0, sizeof(uint64_t),
105 VMGENID_GUID_FW_CFG_FILE
, VMGENID_GUID_OFFSET
);
107 /* Patch address of GUID fw_cfg blob into the AML so OSPM can retrieve
108 * and read it. Note that while we provide storage for 64 bits, only
109 * the least-signficant 32 get patched into AML.
111 bios_linker_loader_add_pointer(linker
,
112 ACPI_BUILD_TABLE_FILE
, vgia_offset
, sizeof(uint32_t),
113 VMGENID_GUID_FW_CFG_FILE
, 0);
115 build_header(linker
, table_data
,
116 (void *)(table_data
->data
+ table_data
->len
- ssdt
->buf
->len
),
117 "SSDT", ssdt
->buf
->len
, 1, NULL
, "VMGENID");
118 free_aml_allocator();
121 void vmgenid_add_fw_cfg(VmGenIdState
*vms
, FWCfgState
*s
, GArray
*guid
)
123 /* Create a read-only fw_cfg file for GUID */
124 fw_cfg_add_file(s
, VMGENID_GUID_FW_CFG_FILE
, guid
->data
,
125 VMGENID_FW_CFG_SIZE
);
126 /* Create a read-write fw_cfg file for Address */
127 fw_cfg_add_file_callback(s
, VMGENID_ADDR_FW_CFG_FILE
, NULL
, NULL
,
128 vms
->vmgenid_addr_le
,
129 ARRAY_SIZE(vms
->vmgenid_addr_le
), false);
132 static void vmgenid_update_guest(VmGenIdState
*vms
)
134 Object
*obj
= object_resolve_path_type("", TYPE_ACPI_DEVICE_IF
, NULL
);
135 uint32_t vmgenid_addr
;
139 /* Write the GUID to guest memory */
140 memcpy(&vmgenid_addr
, vms
->vmgenid_addr_le
, sizeof(vmgenid_addr
));
141 vmgenid_addr
= le32_to_cpu(vmgenid_addr
);
142 /* A zero value in vmgenid_addr means that BIOS has not yet written
146 /* QemuUUID has the first three words as big-endian, and expect
147 * that any GUIDs passed in will always be BE. The guest,
148 * however, will expect the fields to be little-endian.
149 * Perform a byte swap immediately before writing.
152 qemu_uuid_bswap(&guid_le
);
153 /* The GUID is written at a fixed offset into the fw_cfg file
154 * in order to implement the "OVMF SDT Header probe suppressor"
155 * see docs/specs/vmgenid.txt for more details.
157 cpu_physical_memory_write(vmgenid_addr
, guid_le
.data
,
158 sizeof(guid_le
.data
));
159 /* Send _GPE.E05 event */
160 acpi_send_event(DEVICE(obj
), ACPI_VMGENID_CHANGE_STATUS
);
165 static void vmgenid_set_guid(Object
*obj
, const char *value
, Error
**errp
)
167 VmGenIdState
*vms
= VMGENID(obj
);
169 if (!strcmp(value
, "auto")) {
170 qemu_uuid_generate(&vms
->guid
);
171 } else if (qemu_uuid_parse(value
, &vms
->guid
) < 0) {
172 error_setg(errp
, "'%s. %s': Failed to parse GUID string: %s",
173 object_get_typename(OBJECT(vms
)), VMGENID_GUID
, value
);
177 vmgenid_update_guest(vms
);
180 /* After restoring an image, we need to update the guest memory and notify
181 * it of a potential change to VM Generation ID
183 static int vmgenid_post_load(void *opaque
, int version_id
)
185 VmGenIdState
*vms
= opaque
;
186 vmgenid_update_guest(vms
);
190 static const VMStateDescription vmstate_vmgenid
= {
193 .minimum_version_id
= 1,
194 .post_load
= vmgenid_post_load
,
195 .fields
= (VMStateField
[]) {
196 VMSTATE_UINT8_ARRAY(vmgenid_addr_le
, VmGenIdState
, sizeof(uint64_t)),
197 VMSTATE_END_OF_LIST()
201 static void vmgenid_handle_reset(void *opaque
)
203 VmGenIdState
*vms
= VMGENID(opaque
);
204 /* Clear the guest-allocated GUID address when the VM resets */
205 memset(vms
->vmgenid_addr_le
, 0, ARRAY_SIZE(vms
->vmgenid_addr_le
));
208 static void vmgenid_realize(DeviceState
*dev
, Error
**errp
)
210 VmGenIdState
*vms
= VMGENID(dev
);
212 if (!bios_linker_loader_can_write_pointer()) {
213 error_setg(errp
, "%s requires DMA write support in fw_cfg, "
214 "which this machine type does not provide", VMGENID_DEVICE
);
218 /* Given that this function is executing, there is at least one VMGENID
219 * device. Check if there are several.
221 if (!find_vmgenid_dev()) {
222 error_setg(errp
, "at most one %s device is permitted", VMGENID_DEVICE
);
226 qemu_register_reset(vmgenid_handle_reset
, vms
);
229 static void vmgenid_device_class_init(ObjectClass
*klass
, void *data
)
231 DeviceClass
*dc
= DEVICE_CLASS(klass
);
233 dc
->vmsd
= &vmstate_vmgenid
;
234 dc
->realize
= vmgenid_realize
;
235 dc
->hotpluggable
= false;
236 set_bit(DEVICE_CATEGORY_MISC
, dc
->categories
);
238 object_class_property_add_str(klass
, VMGENID_GUID
, NULL
,
239 vmgenid_set_guid
, NULL
);
240 object_class_property_set_description(klass
, VMGENID_GUID
,
241 "Set Global Unique Identifier "
242 "(big-endian) or auto for random value",
246 static const TypeInfo vmgenid_device_info
= {
247 .name
= VMGENID_DEVICE
,
248 .parent
= TYPE_DEVICE
,
249 .instance_size
= sizeof(VmGenIdState
),
250 .class_init
= vmgenid_device_class_init
,
253 static void vmgenid_register_types(void)
255 type_register_static(&vmgenid_device_info
);
258 type_init(vmgenid_register_types
)
260 GuidInfo
*qmp_query_vm_generation_id(Error
**errp
)
264 Object
*obj
= find_vmgenid_dev();
267 error_setg(errp
, "VM Generation ID device not found");
272 info
= g_malloc0(sizeof(*info
));
273 info
->guid
= qemu_uuid_unparse_strdup(&vms
->guid
);