mirror of
https://gitlab.com/qemu-project/qemu
synced 2024-11-05 20:35:44 +00:00
virtio, pc: fixes, features
virtio support for region caches broke a bunch of stuff - fixing most of it though it's not ideal. Still pondering the right way to fix it. New: VM gen ID and hotplug for PXB. Signed-off-by: Michael S. Tsirkin <mst@redhat.com> -----BEGIN PGP SIGNATURE----- iQEcBAABAgAGBQJYt7llAAoJECgfDbjSjVRp+r4H/1cmQ4F67H8oSOAT8xuAQFku OdHoVRJMWf7CRvZ7JqVke/a877d+h6ZpfW5dZQ7hp7O7rkPiuPHa5PVb0WGwDqrD scSOIvDPxJm19pnfZoF4zx+Ov45W5ahF+gwwm/sJU232ApLqOmAjs0FUxidkadQE f5Jrjs20WO2Vkkcd3U7Zl31myre0V7AbwIm7dB/8B+dpL6bJcxSvlM4krwLdBY6S lLs9V6ypRzjUxS3MDANL75KNrO/zys55J+Pa4sEh4+H0OX71v9Icl3s1zaM8J/EN VPjdqhDvJuEahc50FbJyRZQGIzOZ6PcGMsKUHKlxoVmDYZ6Pv5lOnpaLZRT6HMk= =ITdO -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/mst/tags/for_upstream' into staging virtio, pc: fixes, features virtio support for region caches broke a bunch of stuff - fixing most of it though it's not ideal. Still pondering the right way to fix it. New: VM gen ID and hotplug for PXB. Signed-off-by: Michael S. Tsirkin <mst@redhat.com> # gpg: Signature made Thu 02 Mar 2017 06:19:17 GMT # gpg: using RSA key 0x281F0DB8D28D5469 # gpg: Good signature from "Michael S. Tsirkin <mst@kernel.org>" # gpg: aka "Michael S. Tsirkin <mst@redhat.com>" # Primary key fingerprint: 0270 606B 6F3C DF3D 0B17 0970 C350 3912 AFBE 8E67 # Subkey fingerprint: 5D09 FD08 71C8 F85B 94CA 8A0D 281F 0DB8 D28D 5469 * remotes/mst/tags/for_upstream: hw/pxb-pcie: fix PCI Express hotplug support tests/acpi: update DSDT after last patch acpi: simplify _OSC virtio: unbreak virtio-pci with IOMMU after caching ring translations virtio: add missing region cache init in virtio_load() virtio: invalidate memory in vring_set_avail_event() virtio: guard vring access when setting notification virtio: check for vring setup in virtio_queue_empty MAINTAINERS: Add VM Generation ID entries tests: Move reusable ACPI code into a utility file qmp/hmp: add query-vm-generation-id and 'info vm-generation-id' commands ACPI: Add Virtual Machine Generation ID support ACPI: Add vmgenid blob storage to the build tables docs: VM Generation ID device description linker-loader: Add new 'write pointer' command Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
9a81b792cc
31 changed files with 902 additions and 134 deletions
11
MAINTAINERS
11
MAINTAINERS
|
@ -908,6 +908,8 @@ F: hw/acpi/*
|
|||
F: hw/smbios/*
|
||||
F: hw/i386/acpi-build.[hc]
|
||||
F: hw/arm/virt-acpi-build.c
|
||||
F: tests/bios-tables-test.c
|
||||
F: tests/acpi-utils.[hc]
|
||||
|
||||
ppc4xx
|
||||
M: Alexander Graf <agraf@suse.de>
|
||||
|
@ -1122,6 +1124,15 @@ F: hw/nvram/chrp_nvram.c
|
|||
F: include/hw/nvram/chrp_nvram.h
|
||||
F: tests/prom-env-test.c
|
||||
|
||||
VM Generation ID
|
||||
M: Ben Warren <ben@skyportsystems.com>
|
||||
S: Maintained
|
||||
F: hw/acpi/vmgenid.c
|
||||
F: include/hw/acpi/vmgenid.h
|
||||
F: docs/specs/vmgenid.txt
|
||||
F: tests/vmgenid-test.c
|
||||
F: stubs/vmgenid.c
|
||||
|
||||
Subsystems
|
||||
----------
|
||||
Audio
|
||||
|
|
|
@ -59,3 +59,4 @@ CONFIG_I82801B11=y
|
|||
CONFIG_SMBIOS=y
|
||||
CONFIG_HYPERV_TESTDEV=$(CONFIG_KVM)
|
||||
CONFIG_PXB=y
|
||||
CONFIG_ACPI_VMGENID=y
|
||||
|
|
|
@ -59,3 +59,4 @@ CONFIG_I82801B11=y
|
|||
CONFIG_SMBIOS=y
|
||||
CONFIG_HYPERV_TESTDEV=$(CONFIG_KVM)
|
||||
CONFIG_PXB=y
|
||||
CONFIG_ACPI_VMGENID=y
|
||||
|
|
245
docs/specs/vmgenid.txt
Normal file
245
docs/specs/vmgenid.txt
Normal file
|
@ -0,0 +1,245 @@
|
|||
VIRTUAL MACHINE GENERATION ID
|
||||
=============================
|
||||
|
||||
Copyright (C) 2016 Red Hat, Inc.
|
||||
Copyright (C) 2017 Skyport Systems, Inc.
|
||||
|
||||
This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
See the COPYING file in the top-level directory.
|
||||
|
||||
===
|
||||
|
||||
The VM generation ID (vmgenid) device is an emulated device which
|
||||
exposes a 128-bit, cryptographically random, integer value identifier,
|
||||
referred to as a Globally Unique Identifier, or GUID.
|
||||
|
||||
This allows management applications (e.g. libvirt) to notify the guest
|
||||
operating system when the virtual machine is executed with a different
|
||||
configuration (e.g. snapshot execution or creation from a template). The
|
||||
guest operating system notices the change, and is then able to react as
|
||||
appropriate by marking its copies of distributed databases as dirty,
|
||||
re-initializing its random number generator etc.
|
||||
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
These requirements are extracted from the "How to implement virtual machine
|
||||
generation ID support in a virtualization platform" section of the
|
||||
specification, dated August 1, 2012.
|
||||
|
||||
|
||||
The document may be found on the web at:
|
||||
http://go.microsoft.com/fwlink/?LinkId=260709
|
||||
|
||||
R1a. The generation ID shall live in an 8-byte aligned buffer.
|
||||
|
||||
R1b. The buffer holding the generation ID shall be in guest RAM, ROM, or device
|
||||
MMIO range.
|
||||
|
||||
R1c. The buffer holding the generation ID shall be kept separate from areas
|
||||
used by the operating system.
|
||||
|
||||
R1d. The buffer shall not be covered by an AddressRangeMemory or
|
||||
AddressRangeACPI entry in the E820 or UEFI memory map.
|
||||
|
||||
R1e. The generation ID shall not live in a page frame that could be mapped with
|
||||
caching disabled. (In other words, regardless of whether the generation ID
|
||||
lives in RAM, ROM or MMIO, it shall only be mapped as cacheable.)
|
||||
|
||||
R2 to R5. [These AML requirements are isolated well enough in the Microsoft
|
||||
specification for us to simply refer to them here.]
|
||||
|
||||
R6. The hypervisor shall expose a _HID (hardware identifier) object in the
|
||||
VMGenId device's scope that is unique to the hypervisor vendor.
|
||||
|
||||
|
||||
QEMU Implementation
|
||||
-------------------
|
||||
|
||||
The above-mentioned specification does not dictate which ACPI descriptor table
|
||||
will contain the VM Generation ID device. Other implementations (Hyper-V and
|
||||
Xen) put it in the main descriptor table (Differentiated System Description
|
||||
Table or DSDT). For ease of debugging and implementation, we have decided to
|
||||
put it in its own Secondary System Description Table, or SSDT.
|
||||
|
||||
The following is a dump of the contents from a running system:
|
||||
|
||||
# iasl -p ./SSDT -d /sys/firmware/acpi/tables/SSDT
|
||||
|
||||
Intel ACPI Component Architecture
|
||||
ASL+ Optimizing Compiler version 20150717-64
|
||||
Copyright (c) 2000 - 2015 Intel Corporation
|
||||
|
||||
Reading ACPI table from file /sys/firmware/acpi/tables/SSDT - Length
|
||||
00000198 (0x0000C6)
|
||||
ACPI: SSDT 0x0000000000000000 0000C6 (v01 BOCHS VMGENID 00000001 BXPC
|
||||
00000001)
|
||||
Acpi table [SSDT] successfully installed and loaded
|
||||
Pass 1 parse of [SSDT]
|
||||
Pass 2 parse of [SSDT]
|
||||
Parsing Deferred Opcodes (Methods/Buffers/Packages/Regions)
|
||||
|
||||
Parsing completed
|
||||
Disassembly completed
|
||||
ASL Output: ./SSDT.dsl - 1631 bytes
|
||||
# cat SSDT.dsl
|
||||
/*
|
||||
* Intel ACPI Component Architecture
|
||||
* AML/ASL+ Disassembler version 20150717-64
|
||||
* Copyright (c) 2000 - 2015 Intel Corporation
|
||||
*
|
||||
* Disassembling to symbolic ASL+ operators
|
||||
*
|
||||
* Disassembly of /sys/firmware/acpi/tables/SSDT, Sun Feb 5 00:19:37 2017
|
||||
*
|
||||
* Original Table Header:
|
||||
* Signature "SSDT"
|
||||
* Length 0x000000CA (202)
|
||||
* Revision 0x01
|
||||
* Checksum 0x4B
|
||||
* OEM ID "BOCHS "
|
||||
* OEM Table ID "VMGENID"
|
||||
* OEM Revision 0x00000001 (1)
|
||||
* Compiler ID "BXPC"
|
||||
* Compiler Version 0x00000001 (1)
|
||||
*/
|
||||
DefinitionBlock ("/sys/firmware/acpi/tables/SSDT.aml", "SSDT", 1, "BOCHS ",
|
||||
"VMGENID", 0x00000001)
|
||||
{
|
||||
Name (VGIA, 0x07FFF000)
|
||||
Scope (\_SB)
|
||||
{
|
||||
Device (VGEN)
|
||||
{
|
||||
Name (_HID, "QEMUVGID") // _HID: Hardware ID
|
||||
Name (_CID, "VM_Gen_Counter") // _CID: Compatible ID
|
||||
Name (_DDN, "VM_Gen_Counter") // _DDN: DOS Device Name
|
||||
Method (_STA, 0, NotSerialized) // _STA: Status
|
||||
{
|
||||
Local0 = 0x0F
|
||||
If ((VGIA == Zero))
|
||||
{
|
||||
Local0 = Zero
|
||||
}
|
||||
|
||||
Return (Local0)
|
||||
}
|
||||
|
||||
Method (ADDR, 0, NotSerialized)
|
||||
{
|
||||
Local0 = Package (0x02) {}
|
||||
Index (Local0, Zero) = (VGIA + 0x28)
|
||||
Index (Local0, One) = Zero
|
||||
Return (Local0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Method (\_GPE._E05, 0, NotSerialized) // _Exx: Edge-Triggered GPE
|
||||
{
|
||||
Notify (\_SB.VGEN, 0x80) // Status Change
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Design Details:
|
||||
---------------
|
||||
|
||||
Requirements R1a through R1e dictate that the memory holding the
|
||||
VM Generation ID must be allocated and owned by the guest firmware,
|
||||
in this case BIOS or UEFI. However, to be useful, QEMU must be able to
|
||||
change the contents of the memory at runtime, specifically when starting a
|
||||
backed-up or snapshotted image. In order to do this, QEMU must know the
|
||||
address that has been allocated.
|
||||
|
||||
The mechanism chosen for this memory sharing is writeable fw_cfg blobs.
|
||||
These are data object that are visible to both QEMU and guests, and are
|
||||
addressable as sequential files.
|
||||
|
||||
More information about fw_cfg can be found in "docs/specs/fw_cfg.txt"
|
||||
|
||||
Two fw_cfg blobs are used in this case:
|
||||
|
||||
/etc/vmgenid_guid - contains the actual VM Generation ID GUID
|
||||
- read-only to the guest
|
||||
/etc/vmgenid_addr - contains the address of the downloaded vmgenid blob
|
||||
- writeable by the guest
|
||||
|
||||
|
||||
QEMU sends the following commands to the guest at startup:
|
||||
|
||||
1. Allocate memory for vmgenid_guid fw_cfg blob.
|
||||
2. Write the address of vmgenid_guid into the SSDT (VGIA ACPI variable as
|
||||
shown above in the iasl dump). Note that this change is not propagated
|
||||
back to QEMU.
|
||||
3. Write the address of vmgenid_guid back to QEMU's copy of vmgenid_addr
|
||||
via the fw_cfg DMA interface.
|
||||
|
||||
After step 3, QEMU is able to update the contents of vmgenid_guid at will.
|
||||
|
||||
Since BIOS or UEFI does not necessarily run when we wish to change the GUID,
|
||||
the value of VGIA is persisted via the VMState mechanism.
|
||||
|
||||
As spelled out in the specification, any change to the GUID executes an
|
||||
ACPI notification. The exact handler to use is not specified, so the vmgenid
|
||||
device uses the first unused one: \_GPE._E05.
|
||||
|
||||
|
||||
Endian-ness Considerations:
|
||||
---------------------------
|
||||
|
||||
Although not specified in Microsoft's document, it is assumed that the
|
||||
device is expected to use little-endian format.
|
||||
|
||||
All GUID passed in via command line or monitor are treated as big-endian.
|
||||
GUID values displayed via monitor are shown in big-endian format.
|
||||
|
||||
|
||||
GUID Storage Format:
|
||||
--------------------
|
||||
|
||||
In order to implement an OVMF "SDT Header Probe Suppressor", the contents of
|
||||
the vmgenid_guid fw_cfg blob are not simply a 128-bit GUID. There is also
|
||||
significant padding in order to align and fill a memory page, as shown in the
|
||||
following diagram:
|
||||
|
||||
+----------------------------------+
|
||||
| SSDT with OEM Table ID = VMGENID |
|
||||
+----------------------------------+
|
||||
| ... | TOP OF PAGE
|
||||
| VGIA dword object ---------------|-----> +---------------------------+
|
||||
| ... | | fw-allocated array for |
|
||||
| _STA method referring to VGIA | | "etc/vmgenid_guid" |
|
||||
| ... | +---------------------------+
|
||||
| ADDR method referring to VGIA | | 0: OVMF SDT Header probe |
|
||||
| ... | | suppressor |
|
||||
+----------------------------------+ | 36: padding for 8-byte |
|
||||
| alignment |
|
||||
| 40: GUID |
|
||||
| 56: padding to page size |
|
||||
+---------------------------+
|
||||
END OF PAGE
|
||||
|
||||
|
||||
Device Usage:
|
||||
-------------
|
||||
|
||||
The device has one property, which may be only be set using the command line:
|
||||
|
||||
guid - sets the value of the GUID. A special value "auto" instructs
|
||||
QEMU to generate a new random GUID.
|
||||
|
||||
For example:
|
||||
|
||||
QEMU -device vmgenid,guid="324e6eaf-d1d1-4bf6-bf41-b9bb6c91fb87"
|
||||
QEMU -device vmgenid,guid=auto
|
||||
|
||||
The property may be queried via QMP/HMP:
|
||||
|
||||
(QEMU) query-vm-generation-id
|
||||
{"return": {"guid": "324e6eaf-d1d1-4bf6-bf41-b9bb6c91fb87"}}
|
||||
|
||||
Setting of this parameter is intentionally left out from the QMP/HMP
|
||||
interfaces. There are no known use cases for changing the GUID once QEMU is
|
||||
running, and adding this capability would greatly increase the complexity.
|
2
dtc
2
dtc
|
@ -1 +1 @@
|
|||
Subproject commit ec02b34c05be04f249ffaaca4b666f5246877dea
|
||||
Subproject commit 65cc4d2748a2c2e6f27f1cf39e07a5dbabd80ebf
|
|
@ -801,6 +801,20 @@ STEXI
|
|||
Show information about hotpluggable CPUs
|
||||
ETEXI
|
||||
|
||||
STEXI
|
||||
@item info vm-generation-id
|
||||
@findex vm-generation-id
|
||||
Show Virtual Machine Generation ID
|
||||
ETEXI
|
||||
|
||||
{
|
||||
.name = "vm-generation-id",
|
||||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "Show Virtual Machine Generation ID",
|
||||
.cmd = hmp_info_vm_generation_id,
|
||||
},
|
||||
|
||||
STEXI
|
||||
@end table
|
||||
ETEXI
|
||||
|
|
9
hmp.c
9
hmp.c
|
@ -2605,3 +2605,12 @@ void hmp_hotpluggable_cpus(Monitor *mon, const QDict *qdict)
|
|||
|
||||
qapi_free_HotpluggableCPUList(saved);
|
||||
}
|
||||
|
||||
void hmp_info_vm_generation_id(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
GuidInfo *info = qmp_query_vm_generation_id(NULL);
|
||||
if (info) {
|
||||
monitor_printf(mon, "%s\n", info->guid);
|
||||
}
|
||||
qapi_free_GuidInfo(info);
|
||||
}
|
||||
|
|
1
hmp.h
1
hmp.h
|
@ -137,5 +137,6 @@ void hmp_rocker_of_dpa_flows(Monitor *mon, const QDict *qdict);
|
|||
void hmp_rocker_of_dpa_groups(Monitor *mon, const QDict *qdict);
|
||||
void hmp_info_dump(Monitor *mon, const QDict *qdict);
|
||||
void hmp_hotpluggable_cpus(Monitor *mon, const QDict *qdict);
|
||||
void hmp_info_vm_generation_id(Monitor *mon, const QDict *qdict);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1559,6 +1559,7 @@ void acpi_build_tables_init(AcpiBuildTables *tables)
|
|||
tables->rsdp = g_array_new(false, true /* clear */, 1);
|
||||
tables->table_data = g_array_new(false, true /* clear */, 1);
|
||||
tables->tcpalog = g_array_new(false, true /* clear */, 1);
|
||||
tables->vmgenid = g_array_new(false, true /* clear */, 1);
|
||||
tables->linker = bios_linker_loader_init();
|
||||
}
|
||||
|
||||
|
@ -1568,6 +1569,7 @@ void acpi_build_tables_cleanup(AcpiBuildTables *tables, bool mfre)
|
|||
g_array_free(tables->rsdp, true);
|
||||
g_array_free(tables->table_data, true);
|
||||
g_array_free(tables->tcpalog, mfre);
|
||||
g_array_free(tables->vmgenid, mfre);
|
||||
}
|
||||
|
||||
/* Build rsdt table */
|
||||
|
|
|
@ -78,6 +78,21 @@ struct BiosLinkerLoaderEntry {
|
|||
uint32_t length;
|
||||
} cksum;
|
||||
|
||||
/*
|
||||
* COMMAND_WRITE_POINTER - write the fw_cfg file (originating from
|
||||
* @dest_file) at @wr_pointer.offset, by adding a pointer to
|
||||
* @src_offset within the table originating from @src_file.
|
||||
* 1,2,4 or 8 byte unsigned addition is used depending on
|
||||
* @wr_pointer.size.
|
||||
*/
|
||||
struct {
|
||||
char dest_file[BIOS_LINKER_LOADER_FILESZ];
|
||||
char src_file[BIOS_LINKER_LOADER_FILESZ];
|
||||
uint32_t dst_offset;
|
||||
uint32_t src_offset;
|
||||
uint8_t size;
|
||||
} wr_pointer;
|
||||
|
||||
/* padding */
|
||||
char pad[124];
|
||||
};
|
||||
|
@ -85,9 +100,10 @@ struct BiosLinkerLoaderEntry {
|
|||
typedef struct BiosLinkerLoaderEntry BiosLinkerLoaderEntry;
|
||||
|
||||
enum {
|
||||
BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1,
|
||||
BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2,
|
||||
BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3,
|
||||
BIOS_LINKER_LOADER_COMMAND_ALLOCATE = 0x1,
|
||||
BIOS_LINKER_LOADER_COMMAND_ADD_POINTER = 0x2,
|
||||
BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM = 0x3,
|
||||
BIOS_LINKER_LOADER_COMMAND_WRITE_POINTER = 0x4,
|
||||
};
|
||||
|
||||
enum {
|
||||
|
@ -278,3 +294,47 @@ void bios_linker_loader_add_pointer(BIOSLinker *linker,
|
|||
|
||||
g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
|
||||
}
|
||||
|
||||
/*
|
||||
* bios_linker_loader_write_pointer: ask guest to write a pointer to the
|
||||
* source file into the destination file, and write it back to QEMU via
|
||||
* fw_cfg DMA.
|
||||
*
|
||||
* @linker: linker object instance
|
||||
* @dest_file: destination file that must be written
|
||||
* @dst_patched_offset: location within destination file blob to be patched
|
||||
* with the pointer to @src_file, in bytes
|
||||
* @dst_patched_offset_size: size of the pointer to be patched
|
||||
* at @dst_patched_offset in @dest_file blob, in bytes
|
||||
* @src_file: source file who's address must be taken
|
||||
* @src_offset: location within source file blob to which
|
||||
* @dest_file+@dst_patched_offset will point to after
|
||||
* firmware's executed WRITE_POINTER command
|
||||
*/
|
||||
void bios_linker_loader_write_pointer(BIOSLinker *linker,
|
||||
const char *dest_file,
|
||||
uint32_t dst_patched_offset,
|
||||
uint8_t dst_patched_size,
|
||||
const char *src_file,
|
||||
uint32_t src_offset)
|
||||
{
|
||||
BiosLinkerLoaderEntry entry;
|
||||
const BiosLinkerFileEntry *source_file =
|
||||
bios_linker_find_file(linker, src_file);
|
||||
|
||||
assert(source_file);
|
||||
assert(src_offset < source_file->blob->len);
|
||||
memset(&entry, 0, sizeof entry);
|
||||
strncpy(entry.wr_pointer.dest_file, dest_file,
|
||||
sizeof entry.wr_pointer.dest_file - 1);
|
||||
strncpy(entry.wr_pointer.src_file, src_file,
|
||||
sizeof entry.wr_pointer.src_file - 1);
|
||||
entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_WRITE_POINTER);
|
||||
entry.wr_pointer.dst_offset = cpu_to_le32(dst_patched_offset);
|
||||
entry.wr_pointer.src_offset = cpu_to_le32(src_offset);
|
||||
entry.wr_pointer.size = dst_patched_size;
|
||||
assert(dst_patched_size == 1 || dst_patched_size == 2 ||
|
||||
dst_patched_size == 4 || dst_patched_size == 8);
|
||||
|
||||
g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
|
||||
}
|
||||
|
|
258
hw/acpi/vmgenid.c
Normal file
258
hw/acpi/vmgenid.c
Normal file
|
@ -0,0 +1,258 @@
|
|||
/*
|
||||
* 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)
|
||||
|
||||
GuidInfo *qmp_query_vm_generation_id(Error **errp)
|
||||
{
|
||||
GuidInfo *info;
|
||||
VmGenIdState *vms;
|
||||
Object *obj = find_vmgenid_dev();
|
||||
|
||||
if (!obj) {
|
||||
return NULL;
|
||||
}
|
||||
vms = VMGENID(obj);
|
||||
|
||||
info = g_malloc0(sizeof(*info));
|
||||
info->guid = qemu_uuid_unparse_strdup(&vms->guid);
|
||||
return info;
|
||||
}
|
|
@ -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"
|
||||
|
@ -1803,7 +1804,7 @@ static Aml *build_q35_osc_method(void)
|
|||
Aml *else_ctx;
|
||||
Aml *method;
|
||||
Aml *a_cwd1 = aml_name("CDW1");
|
||||
Aml *a_ctrl = aml_name("CTRL");
|
||||
Aml *a_ctrl = aml_local(0);
|
||||
|
||||
method = aml_method("_OSC", 4, AML_NOTSERIALIZED);
|
||||
aml_append(method, aml_create_dword_field(aml_arg(3), aml_int(0), "CDW1"));
|
||||
|
@ -1813,7 +1814,6 @@ static Aml *build_q35_osc_method(void)
|
|||
aml_append(if_ctx, aml_create_dword_field(aml_arg(3), aml_int(4), "CDW2"));
|
||||
aml_append(if_ctx, aml_create_dword_field(aml_arg(3), aml_int(8), "CDW3"));
|
||||
|
||||
aml_append(if_ctx, aml_store(aml_name("CDW2"), aml_name("SUPP")));
|
||||
aml_append(if_ctx, aml_store(aml_name("CDW3"), a_ctrl));
|
||||
|
||||
/*
|
||||
|
@ -1898,8 +1898,6 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
|
|||
aml_append(dev, aml_name_decl("_CID", aml_eisaid("PNP0A03")));
|
||||
aml_append(dev, aml_name_decl("_ADR", aml_int(0)));
|
||||
aml_append(dev, aml_name_decl("_UID", aml_int(1)));
|
||||
aml_append(dev, aml_name_decl("SUPP", aml_int(0)));
|
||||
aml_append(dev, aml_name_decl("CTRL", aml_int(0)));
|
||||
aml_append(dev, build_q35_osc_method());
|
||||
aml_append(sb_scope, dev);
|
||||
aml_append(dsdt, sb_scope);
|
||||
|
@ -1964,6 +1962,9 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
|
|||
aml_append(dev, aml_name_decl("_UID", aml_int(bus_num)));
|
||||
aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A03")));
|
||||
aml_append(dev, aml_name_decl("_BBN", aml_int(bus_num)));
|
||||
if (pci_bus_is_express(bus)) {
|
||||
aml_append(dev, build_q35_osc_method());
|
||||
}
|
||||
|
||||
if (numa_node != NUMA_NODE_UNASSIGNED) {
|
||||
aml_append(dev, aml_name_decl("_PXM", aml_int(numa_node)));
|
||||
|
@ -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.
|
||||
|
|
|
@ -1153,7 +1153,7 @@ static AddressSpace *virtio_pci_get_dma_as(DeviceState *d)
|
|||
VirtIOPCIProxy *proxy = VIRTIO_PCI(d);
|
||||
PCIDevice *dev = &proxy->pci_dev;
|
||||
|
||||
return pci_get_address_space(dev);
|
||||
return pci_device_iommu_address_space(dev);
|
||||
}
|
||||
|
||||
static int virtio_pci_add_mem_cap(VirtIOPCIProxy *proxy,
|
||||
|
|
|
@ -282,12 +282,17 @@ static inline void vring_set_avail_event(VirtQueue *vq, uint16_t val)
|
|||
caches = atomic_rcu_read(&vq->vring.caches);
|
||||
pa = offsetof(VRingUsed, ring[vq->vring.num]);
|
||||
virtio_stw_phys_cached(vq->vdev, &caches->used, pa, val);
|
||||
address_space_cache_invalidate(&caches->used, pa, sizeof(val));
|
||||
}
|
||||
|
||||
void virtio_queue_set_notification(VirtQueue *vq, int enable)
|
||||
{
|
||||
vq->notification = enable;
|
||||
|
||||
if (!vq->vring.desc) {
|
||||
return;
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_RING_F_EVENT_IDX)) {
|
||||
vring_set_avail_event(vq, vring_avail_idx(vq));
|
||||
|
@ -1852,7 +1857,10 @@ void virtio_save(VirtIODevice *vdev, QEMUFile *f)
|
|||
if (k->has_variable_vring_alignment) {
|
||||
qemu_put_be32(f, vdev->vq[i].vring.align);
|
||||
}
|
||||
/* XXX virtio-1 devices */
|
||||
/*
|
||||
* Save desc now, the rest of the ring addresses are saved in
|
||||
* subsections for VIRTIO-1 devices.
|
||||
*/
|
||||
qemu_put_be64(f, vdev->vq[i].vring.desc);
|
||||
qemu_put_be16s(f, &vdev->vq[i].last_avail_idx);
|
||||
if (k->save_queue) {
|
||||
|
@ -1993,14 +2001,11 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
|
|||
vdev->vq[i].signalled_used_valid = false;
|
||||
vdev->vq[i].notification = true;
|
||||
|
||||
if (vdev->vq[i].vring.desc) {
|
||||
/* XXX virtio-1 devices */
|
||||
virtio_queue_update_rings(vdev, i);
|
||||
} else if (vdev->vq[i].last_avail_idx) {
|
||||
if (!vdev->vq[i].vring.desc && vdev->vq[i].last_avail_idx) {
|
||||
error_report("VQ %d address 0x0 "
|
||||
"inconsistent with Host index 0x%x",
|
||||
i, vdev->vq[i].last_avail_idx);
|
||||
return -1;
|
||||
return -1;
|
||||
}
|
||||
if (k->load_queue) {
|
||||
ret = k->load_queue(qbus->parent, i, f);
|
||||
|
@ -2061,6 +2066,19 @@ int virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
|
|||
for (i = 0; i < num; i++) {
|
||||
if (vdev->vq[i].vring.desc) {
|
||||
uint16_t nheads;
|
||||
|
||||
/*
|
||||
* VIRTIO-1 devices migrate desc, used, and avail ring addresses so
|
||||
* only the region cache needs to be set up. Legacy devices need
|
||||
* to calculate used and avail ring addresses based on the desc
|
||||
* address.
|
||||
*/
|
||||
if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
||||
virtio_init_region_cache(vdev, i);
|
||||
} else {
|
||||
virtio_queue_update_rings(vdev, i);
|
||||
}
|
||||
|
||||
nheads = vring_avail_idx(&vdev->vq[i]) - vdev->vq[i].last_avail_idx;
|
||||
/* Check it isn't doing strange things with descriptor numbers. */
|
||||
if (nheads > vdev->vq[i].vring.num) {
|
||||
|
@ -2291,7 +2309,7 @@ static bool virtio_queue_host_notifier_aio_poll(void *opaque)
|
|||
VirtQueue *vq = container_of(n, VirtQueue, host_notifier);
|
||||
bool progress;
|
||||
|
||||
if (virtio_queue_empty(vq)) {
|
||||
if (!vq->vring.desc || virtio_queue_empty(vq)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -210,6 +210,7 @@ struct AcpiBuildTables {
|
|||
GArray *table_data;
|
||||
GArray *rsdp;
|
||||
GArray *tcpalog;
|
||||
GArray *vmgenid;
|
||||
BIOSLinker *linker;
|
||||
} AcpiBuildTables;
|
||||
|
||||
|
|
|
@ -26,5 +26,12 @@ void bios_linker_loader_add_pointer(BIOSLinker *linker,
|
|||
const char *src_file,
|
||||
uint32_t src_offset);
|
||||
|
||||
void bios_linker_loader_write_pointer(BIOSLinker *linker,
|
||||
const char *dest_file,
|
||||
uint32_t dst_patched_offset,
|
||||
uint8_t dst_patched_size,
|
||||
const char *src_file,
|
||||
uint32_t src_offset);
|
||||
|
||||
void bios_linker_loader_cleanup(BIOSLinker *linker);
|
||||
#endif
|
||||
|
|
35
include/hw/acpi/vmgenid.h
Normal file
35
include/hw/acpi/vmgenid.h
Normal file
|
@ -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
|
|
@ -6197,3 +6197,23 @@
|
|||
#
|
||||
##
|
||||
{ 'command': 'query-hotpluggable-cpus', 'returns': ['HotpluggableCPU'] }
|
||||
|
||||
##
|
||||
# @GuidInfo:
|
||||
#
|
||||
# GUID information.
|
||||
#
|
||||
# @guid: the globally unique identifier
|
||||
#
|
||||
# Since: 2.9
|
||||
##
|
||||
{ 'struct': 'GuidInfo', 'data': {'guid': 'str'} }
|
||||
|
||||
##
|
||||
# @query-vm-generation-id:
|
||||
#
|
||||
# Show Virtual Machine Generation ID
|
||||
#
|
||||
# Since 2.9
|
||||
##
|
||||
{ 'command': 'query-vm-generation-id', 'returns': 'GuidInfo' }
|
||||
|
|
|
@ -36,3 +36,4 @@ stub-obj-y += qmp_pc_dimm_device_list.o
|
|||
stub-obj-y += target-monitor-defs.o
|
||||
stub-obj-y += target-get-monitor-def.o
|
||||
stub-obj-y += pc_madt_cpu_entry.o
|
||||
stub-obj-y += vmgenid.o
|
||||
|
|
9
stubs/vmgenid.c
Normal file
9
stubs/vmgenid.c
Normal file
|
@ -0,0 +1,9 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include "qmp-commands.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
|
||||
GuidInfo *qmp_query_vm_generation_id(Error **errp)
|
||||
{
|
||||
error_setg(errp, QERR_UNSUPPORTED);
|
||||
return NULL;
|
||||
}
|
|
@ -669,7 +669,7 @@ tests/hd-geo-test$(EXESUF): tests/hd-geo-test.o
|
|||
tests/boot-order-test$(EXESUF): tests/boot-order-test.o $(libqos-obj-y)
|
||||
tests/boot-serial-test$(EXESUF): tests/boot-serial-test.o $(libqos-obj-y)
|
||||
tests/bios-tables-test$(EXESUF): tests/bios-tables-test.o \
|
||||
tests/boot-sector.o $(libqos-obj-y)
|
||||
tests/boot-sector.o tests/acpi-utils.o $(libqos-obj-y)
|
||||
tests/pxe-test$(EXESUF): tests/pxe-test.o tests/boot-sector.o $(libqos-obj-y)
|
||||
tests/tmp105-test$(EXESUF): tests/tmp105-test.o $(libqos-omap-obj-y)
|
||||
tests/ds1338-test$(EXESUF): tests/ds1338-test.o $(libqos-imx-obj-y)
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
65
tests/acpi-utils.c
Normal file
65
tests/acpi-utils.c
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* ACPI Utility Functions
|
||||
*
|
||||
* Copyright (c) 2013 Red Hat Inc.
|
||||
* Copyright (c) 2017 Skyport Systems
|
||||
*
|
||||
* Authors:
|
||||
* Michael S. Tsirkin <mst@redhat.com>,
|
||||
* 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 <glib/gstdio.h>
|
||||
#include "qemu-common.h"
|
||||
#include "hw/smbios/smbios.h"
|
||||
#include "qemu/bitmap.h"
|
||||
#include "acpi-utils.h"
|
||||
#include "boot-sector.h"
|
||||
|
||||
uint8_t acpi_calc_checksum(const uint8_t *data, int len)
|
||||
{
|
||||
int i;
|
||||
uint8_t sum = 0;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
sum += data[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
uint32_t acpi_find_rsdp_address(void)
|
||||
{
|
||||
uint32_t off;
|
||||
|
||||
/* RSDP location can vary across a narrow range */
|
||||
for (off = 0xf0000; off < 0x100000; off += 0x10) {
|
||||
uint8_t sig[] = "RSD PTR ";
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sizeof sig - 1; ++i) {
|
||||
sig[i] = readb(off + i);
|
||||
}
|
||||
|
||||
if (!memcmp(sig, "RSD PTR ", sizeof sig)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
void acpi_parse_rsdp_table(uint32_t addr, AcpiRsdpDescriptor *rsdp_table)
|
||||
{
|
||||
ACPI_READ_FIELD(rsdp_table->signature, addr);
|
||||
ACPI_ASSERT_CMP64(rsdp_table->signature, "RSD PTR ");
|
||||
|
||||
ACPI_READ_FIELD(rsdp_table->checksum, addr);
|
||||
ACPI_READ_ARRAY(rsdp_table->oem_id, addr);
|
||||
ACPI_READ_FIELD(rsdp_table->revision, addr);
|
||||
ACPI_READ_FIELD(rsdp_table->rsdt_physical_address, addr);
|
||||
ACPI_READ_FIELD(rsdp_table->length, addr);
|
||||
}
|
94
tests/acpi-utils.h
Normal file
94
tests/acpi-utils.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Utilities for working with ACPI tables
|
||||
*
|
||||
* Copyright (c) 2013 Red Hat Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Michael S. Tsirkin <mst@redhat.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.
|
||||
*/
|
||||
|
||||
#ifndef TEST_ACPI_UTILS_H
|
||||
#define TEST_ACPI_UTILS_H
|
||||
|
||||
#include "hw/acpi/acpi-defs.h"
|
||||
#include "libqtest.h"
|
||||
|
||||
/* DSDT and SSDTs format */
|
||||
typedef struct {
|
||||
AcpiTableHeader header;
|
||||
gchar *aml; /* aml bytecode from guest */
|
||||
gsize aml_len;
|
||||
gchar *aml_file;
|
||||
gchar *asl; /* asl code generated from aml */
|
||||
gsize asl_len;
|
||||
gchar *asl_file;
|
||||
bool tmp_files_retain; /* do not delete the temp asl/aml */
|
||||
} QEMU_PACKED AcpiSdtTable;
|
||||
|
||||
#define ACPI_READ_FIELD(field, addr) \
|
||||
do { \
|
||||
switch (sizeof(field)) { \
|
||||
case 1: \
|
||||
field = readb(addr); \
|
||||
break; \
|
||||
case 2: \
|
||||
field = readw(addr); \
|
||||
break; \
|
||||
case 4: \
|
||||
field = readl(addr); \
|
||||
break; \
|
||||
case 8: \
|
||||
field = readq(addr); \
|
||||
break; \
|
||||
default: \
|
||||
g_assert(false); \
|
||||
} \
|
||||
addr += sizeof(field); \
|
||||
} while (0);
|
||||
|
||||
#define ACPI_READ_ARRAY_PTR(arr, length, addr) \
|
||||
do { \
|
||||
int idx; \
|
||||
for (idx = 0; idx < length; ++idx) { \
|
||||
ACPI_READ_FIELD(arr[idx], addr); \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
#define ACPI_READ_ARRAY(arr, addr) \
|
||||
ACPI_READ_ARRAY_PTR(arr, sizeof(arr) / sizeof(arr[0]), addr)
|
||||
|
||||
#define ACPI_READ_TABLE_HEADER(table, addr) \
|
||||
do { \
|
||||
ACPI_READ_FIELD((table)->signature, addr); \
|
||||
ACPI_READ_FIELD((table)->length, addr); \
|
||||
ACPI_READ_FIELD((table)->revision, addr); \
|
||||
ACPI_READ_FIELD((table)->checksum, addr); \
|
||||
ACPI_READ_ARRAY((table)->oem_id, addr); \
|
||||
ACPI_READ_ARRAY((table)->oem_table_id, addr); \
|
||||
ACPI_READ_FIELD((table)->oem_revision, addr); \
|
||||
ACPI_READ_ARRAY((table)->asl_compiler_id, addr); \
|
||||
ACPI_READ_FIELD((table)->asl_compiler_revision, addr); \
|
||||
} while (0);
|
||||
|
||||
#define ACPI_ASSERT_CMP(actual, expected) do { \
|
||||
uint32_t ACPI_ASSERT_CMP_le = cpu_to_le32(actual); \
|
||||
char ACPI_ASSERT_CMP_str[5] = {}; \
|
||||
memcpy(ACPI_ASSERT_CMP_str, &ACPI_ASSERT_CMP_le, 4); \
|
||||
g_assert_cmpstr(ACPI_ASSERT_CMP_str, ==, expected); \
|
||||
} while (0)
|
||||
|
||||
#define ACPI_ASSERT_CMP64(actual, expected) do { \
|
||||
uint64_t ACPI_ASSERT_CMP_le = cpu_to_le64(actual); \
|
||||
char ACPI_ASSERT_CMP_str[9] = {}; \
|
||||
memcpy(ACPI_ASSERT_CMP_str, &ACPI_ASSERT_CMP_le, 8); \
|
||||
g_assert_cmpstr(ACPI_ASSERT_CMP_str, ==, expected); \
|
||||
} while (0)
|
||||
|
||||
uint8_t acpi_calc_checksum(const uint8_t *data, int len);
|
||||
uint32_t acpi_find_rsdp_address(void);
|
||||
void acpi_parse_rsdp_table(uint32_t addr, AcpiRsdpDescriptor *rsdp_table);
|
||||
|
||||
#endif /* TEST_ACPI_UTILS_H */
|
|
@ -13,10 +13,9 @@
|
|||
#include "qemu/osdep.h"
|
||||
#include <glib/gstdio.h>
|
||||
#include "qemu-common.h"
|
||||
#include "libqtest.h"
|
||||
#include "hw/acpi/acpi-defs.h"
|
||||
#include "hw/smbios/smbios.h"
|
||||
#include "qemu/bitmap.h"
|
||||
#include "acpi-utils.h"
|
||||
#include "boot-sector.h"
|
||||
|
||||
#define MACHINE_PC "pc"
|
||||
|
@ -24,18 +23,6 @@
|
|||
|
||||
#define ACPI_REBUILD_EXPECTED_AML "TEST_ACPI_REBUILD_AML"
|
||||
|
||||
/* DSDT and SSDTs format */
|
||||
typedef struct {
|
||||
AcpiTableHeader header;
|
||||
gchar *aml; /* aml bytecode from guest */
|
||||
gsize aml_len;
|
||||
gchar *aml_file;
|
||||
gchar *asl; /* asl code generated from aml */
|
||||
gsize asl_len;
|
||||
gchar *asl_file;
|
||||
bool tmp_files_retain; /* do not delete the temp asl/aml */
|
||||
} QEMU_PACKED AcpiSdtTable;
|
||||
|
||||
typedef struct {
|
||||
const char *machine;
|
||||
const char *variant;
|
||||
|
@ -53,65 +40,6 @@ typedef struct {
|
|||
int required_struct_types_len;
|
||||
} test_data;
|
||||
|
||||
#define ACPI_READ_FIELD(field, addr) \
|
||||
do { \
|
||||
switch (sizeof(field)) { \
|
||||
case 1: \
|
||||
field = readb(addr); \
|
||||
break; \
|
||||
case 2: \
|
||||
field = readw(addr); \
|
||||
break; \
|
||||
case 4: \
|
||||
field = readl(addr); \
|
||||
break; \
|
||||
case 8: \
|
||||
field = readq(addr); \
|
||||
break; \
|
||||
default: \
|
||||
g_assert(false); \
|
||||
} \
|
||||
addr += sizeof(field); \
|
||||
} while (0);
|
||||
|
||||
#define ACPI_READ_ARRAY_PTR(arr, length, addr) \
|
||||
do { \
|
||||
int idx; \
|
||||
for (idx = 0; idx < length; ++idx) { \
|
||||
ACPI_READ_FIELD(arr[idx], addr); \
|
||||
} \
|
||||
} while (0);
|
||||
|
||||
#define ACPI_READ_ARRAY(arr, addr) \
|
||||
ACPI_READ_ARRAY_PTR(arr, sizeof(arr)/sizeof(arr[0]), addr)
|
||||
|
||||
#define ACPI_READ_TABLE_HEADER(table, addr) \
|
||||
do { \
|
||||
ACPI_READ_FIELD((table)->signature, addr); \
|
||||
ACPI_READ_FIELD((table)->length, addr); \
|
||||
ACPI_READ_FIELD((table)->revision, addr); \
|
||||
ACPI_READ_FIELD((table)->checksum, addr); \
|
||||
ACPI_READ_ARRAY((table)->oem_id, addr); \
|
||||
ACPI_READ_ARRAY((table)->oem_table_id, addr); \
|
||||
ACPI_READ_FIELD((table)->oem_revision, addr); \
|
||||
ACPI_READ_ARRAY((table)->asl_compiler_id, addr); \
|
||||
ACPI_READ_FIELD((table)->asl_compiler_revision, addr); \
|
||||
} while (0);
|
||||
|
||||
#define ACPI_ASSERT_CMP(actual, expected) do { \
|
||||
uint32_t ACPI_ASSERT_CMP_le = cpu_to_le32(actual); \
|
||||
char ACPI_ASSERT_CMP_str[5] = {}; \
|
||||
memcpy(ACPI_ASSERT_CMP_str, &ACPI_ASSERT_CMP_le, 4); \
|
||||
g_assert_cmpstr(ACPI_ASSERT_CMP_str, ==, expected); \
|
||||
} while (0)
|
||||
|
||||
#define ACPI_ASSERT_CMP64(actual, expected) do { \
|
||||
uint64_t ACPI_ASSERT_CMP_le = cpu_to_le64(actual); \
|
||||
char ACPI_ASSERT_CMP_str[9] = {}; \
|
||||
memcpy(ACPI_ASSERT_CMP_str, &ACPI_ASSERT_CMP_le, 8); \
|
||||
g_assert_cmpstr(ACPI_ASSERT_CMP_str, ==, expected); \
|
||||
} while (0)
|
||||
|
||||
static char disk[] = "tests/acpi-test-disk-XXXXXX";
|
||||
static const char *data_dir = "tests/acpi-test-data";
|
||||
#ifdef CONFIG_IASL
|
||||
|
@ -147,36 +75,9 @@ static void free_test_data(test_data *data)
|
|||
g_array_free(data->tables, true);
|
||||
}
|
||||
|
||||
static uint8_t acpi_checksum(const uint8_t *data, int len)
|
||||
{
|
||||
int i;
|
||||
uint8_t sum = 0;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
sum += data[i];
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
static void test_acpi_rsdp_address(test_data *data)
|
||||
{
|
||||
uint32_t off;
|
||||
|
||||
/* OK, now find RSDP */
|
||||
for (off = 0xf0000; off < 0x100000; off += 0x10) {
|
||||
uint8_t sig[] = "RSD PTR ";
|
||||
int i;
|
||||
|
||||
for (i = 0; i < sizeof sig - 1; ++i) {
|
||||
sig[i] = readb(off + i);
|
||||
}
|
||||
|
||||
if (!memcmp(sig, "RSD PTR ", sizeof sig)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t off = acpi_find_rsdp_address();
|
||||
g_assert_cmphex(off, <, 0x100000);
|
||||
data->rsdp_addr = off;
|
||||
}
|
||||
|
@ -186,17 +87,10 @@ static void test_acpi_rsdp_table(test_data *data)
|
|||
AcpiRsdpDescriptor *rsdp_table = &data->rsdp_table;
|
||||
uint32_t addr = data->rsdp_addr;
|
||||
|
||||
ACPI_READ_FIELD(rsdp_table->signature, addr);
|
||||
ACPI_ASSERT_CMP64(rsdp_table->signature, "RSD PTR ");
|
||||
|
||||
ACPI_READ_FIELD(rsdp_table->checksum, addr);
|
||||
ACPI_READ_ARRAY(rsdp_table->oem_id, addr);
|
||||
ACPI_READ_FIELD(rsdp_table->revision, addr);
|
||||
ACPI_READ_FIELD(rsdp_table->rsdt_physical_address, addr);
|
||||
ACPI_READ_FIELD(rsdp_table->length, addr);
|
||||
acpi_parse_rsdp_table(addr, rsdp_table);
|
||||
|
||||
/* rsdp checksum is not for the whole table, but for the first 20 bytes */
|
||||
g_assert(!acpi_checksum((uint8_t *)rsdp_table, 20));
|
||||
g_assert(!acpi_calc_checksum((uint8_t *)rsdp_table, 20));
|
||||
}
|
||||
|
||||
static void test_acpi_rsdt_table(test_data *data)
|
||||
|
@ -220,8 +114,9 @@ static void test_acpi_rsdt_table(test_data *data)
|
|||
tables = g_new0(uint32_t, tables_nr);
|
||||
ACPI_READ_ARRAY_PTR(tables, tables_nr, addr);
|
||||
|
||||
checksum = acpi_checksum((uint8_t *)rsdt_table, rsdt_table->length) +
|
||||
acpi_checksum((uint8_t *)tables, tables_nr * sizeof(uint32_t));
|
||||
checksum = acpi_calc_checksum((uint8_t *)rsdt_table, rsdt_table->length) +
|
||||
acpi_calc_checksum((uint8_t *)tables,
|
||||
tables_nr * sizeof(uint32_t));
|
||||
g_assert(!checksum);
|
||||
|
||||
/* SSDT tables after FADT */
|
||||
|
@ -279,7 +174,7 @@ static void test_acpi_fadt_table(test_data *data)
|
|||
ACPI_READ_FIELD(fadt_table->flags, addr);
|
||||
|
||||
ACPI_ASSERT_CMP(fadt_table->signature, "FACP");
|
||||
g_assert(!acpi_checksum((uint8_t *)fadt_table, fadt_table->length));
|
||||
g_assert(!acpi_calc_checksum((uint8_t *)fadt_table, fadt_table->length));
|
||||
}
|
||||
|
||||
static void test_acpi_facs_table(test_data *data)
|
||||
|
@ -308,8 +203,10 @@ static void test_dst_table(AcpiSdtTable *sdt_table, uint32_t addr)
|
|||
sdt_table->aml = g_malloc0(sdt_table->aml_len);
|
||||
ACPI_READ_ARRAY_PTR(sdt_table->aml, sdt_table->aml_len, addr);
|
||||
|
||||
checksum = acpi_checksum((uint8_t *)sdt_table, sizeof(AcpiTableHeader)) +
|
||||
acpi_checksum((uint8_t *)sdt_table->aml, sdt_table->aml_len);
|
||||
checksum = acpi_calc_checksum((uint8_t *)sdt_table,
|
||||
sizeof(AcpiTableHeader)) +
|
||||
acpi_calc_checksum((uint8_t *)sdt_table->aml,
|
||||
sdt_table->aml_len);
|
||||
g_assert(!checksum);
|
||||
}
|
||||
|
||||
|
@ -608,8 +505,9 @@ static bool smbios_ep_table_ok(test_data *data)
|
|||
return false;
|
||||
}
|
||||
ACPI_READ_FIELD(ep_table->smbios_bcd_revision, addr);
|
||||
if (acpi_checksum((uint8_t *)ep_table, sizeof *ep_table) ||
|
||||
acpi_checksum((uint8_t *)ep_table + 0x10, sizeof *ep_table - 0x10)) {
|
||||
if (acpi_calc_checksum((uint8_t *)ep_table, sizeof *ep_table) ||
|
||||
acpi_calc_checksum((uint8_t *)ep_table + 0x10,
|
||||
sizeof *ep_table - 0x10)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
Loading…
Reference in a new issue