2020-09-15 12:08:56 +00:00
|
|
|
/* Support for generating ACPI tables and passing them to Guests
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008-2010 Kevin O'Connor <kevin@koconnor.net>
|
|
|
|
* Copyright (C) 2006 Fabrice Bellard
|
|
|
|
* Copyright (C) 2013 Red Hat Inc
|
|
|
|
*
|
|
|
|
* Author: Michael S. Tsirkin <mst@redhat.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "qemu/osdep.h"
|
2020-09-15 12:08:57 +00:00
|
|
|
#include "qemu/cutils.h"
|
2020-09-15 12:08:56 +00:00
|
|
|
#include "qapi/error.h"
|
|
|
|
|
|
|
|
#include "exec/memory.h"
|
|
|
|
#include "hw/acpi/acpi.h"
|
|
|
|
#include "hw/acpi/aml-build.h"
|
|
|
|
#include "hw/acpi/bios-linker-loader.h"
|
|
|
|
#include "hw/acpi/generic_event_device.h"
|
|
|
|
#include "hw/acpi/utils.h"
|
|
|
|
#include "hw/boards.h"
|
|
|
|
#include "hw/i386/fw_cfg.h"
|
|
|
|
#include "hw/i386/microvm.h"
|
2020-09-28 10:42:49 +00:00
|
|
|
#include "hw/pci/pci.h"
|
|
|
|
#include "hw/pci/pcie_host.h"
|
2020-10-20 07:48:39 +00:00
|
|
|
#include "hw/usb/xhci.h"
|
2020-09-15 12:08:57 +00:00
|
|
|
#include "hw/virtio/virtio-mmio.h"
|
2020-09-15 12:08:56 +00:00
|
|
|
|
|
|
|
#include "acpi-common.h"
|
|
|
|
#include "acpi-microvm.h"
|
|
|
|
|
2020-09-15 12:08:57 +00:00
|
|
|
static void acpi_dsdt_add_virtio(Aml *scope,
|
|
|
|
MicrovmMachineState *mms)
|
|
|
|
{
|
|
|
|
gchar *separator;
|
|
|
|
long int index;
|
|
|
|
BusState *bus;
|
|
|
|
BusChild *kid;
|
|
|
|
|
|
|
|
bus = sysbus_get_default();
|
|
|
|
QTAILQ_FOREACH(kid, &bus->children, sibling) {
|
|
|
|
DeviceState *dev = kid->child;
|
|
|
|
Object *obj = object_dynamic_cast(OBJECT(dev), TYPE_VIRTIO_MMIO);
|
|
|
|
|
|
|
|
if (obj) {
|
|
|
|
VirtIOMMIOProxy *mmio = VIRTIO_MMIO(obj);
|
|
|
|
VirtioBusState *mmio_virtio_bus = &mmio->bus;
|
|
|
|
BusState *mmio_bus = &mmio_virtio_bus->parent_obj;
|
|
|
|
|
|
|
|
if (QTAILQ_EMPTY(&mmio_bus->children)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
separator = g_strrstr(mmio_bus->name, ".");
|
|
|
|
if (!separator) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (qemu_strtol(separator + 1, NULL, 10, &index) != 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t irq = mms->virtio_irq_base + index;
|
|
|
|
hwaddr base = VIRTIO_MMIO_BASE + index * 512;
|
|
|
|
hwaddr size = 512;
|
|
|
|
|
|
|
|
Aml *dev = aml_device("VR%02u", (unsigned)index);
|
|
|
|
aml_append(dev, aml_name_decl("_HID", aml_string("LNRO0005")));
|
|
|
|
aml_append(dev, aml_name_decl("_UID", aml_int(index)));
|
|
|
|
aml_append(dev, aml_name_decl("_CCA", aml_int(1)));
|
|
|
|
|
|
|
|
Aml *crs = aml_resource_template();
|
|
|
|
aml_append(crs, aml_memory32_fixed(base, size, AML_READ_WRITE));
|
|
|
|
aml_append(crs,
|
|
|
|
aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
|
|
|
|
AML_EXCLUSIVE, &irq, 1));
|
|
|
|
aml_append(dev, aml_name_decl("_CRS", crs));
|
|
|
|
aml_append(scope, dev);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-20 07:48:39 +00:00
|
|
|
static void acpi_dsdt_add_xhci(Aml *scope, MicrovmMachineState *mms)
|
|
|
|
{
|
|
|
|
if (machine_usb(MACHINE(mms))) {
|
|
|
|
xhci_sysbus_build_aml(scope, MICROVM_XHCI_BASE, MICROVM_XHCI_IRQ);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-28 10:42:49 +00:00
|
|
|
static void acpi_dsdt_add_pci(Aml *scope, MicrovmMachineState *mms)
|
|
|
|
{
|
|
|
|
if (mms->pcie != ON_OFF_AUTO_ON) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
acpi_dsdt_add_gpex(scope, &mms->gpex);
|
|
|
|
}
|
|
|
|
|
2020-09-15 12:08:56 +00:00
|
|
|
static void
|
|
|
|
build_dsdt_microvm(GArray *table_data, BIOSLinker *linker,
|
|
|
|
MicrovmMachineState *mms)
|
|
|
|
{
|
|
|
|
X86MachineState *x86ms = X86_MACHINE(mms);
|
|
|
|
Aml *dsdt, *sb_scope, *scope, *pkg;
|
|
|
|
bool ambiguous;
|
|
|
|
Object *isabus;
|
|
|
|
|
|
|
|
isabus = object_resolve_path_type("", TYPE_ISA_BUS, &ambiguous);
|
|
|
|
assert(isabus);
|
|
|
|
assert(!ambiguous);
|
|
|
|
|
|
|
|
dsdt = init_aml_allocator();
|
|
|
|
|
|
|
|
/* Reserve space for header */
|
|
|
|
acpi_data_push(dsdt->buf, sizeof(AcpiTableHeader));
|
|
|
|
|
|
|
|
sb_scope = aml_scope("_SB");
|
|
|
|
fw_cfg_add_acpi_dsdt(sb_scope, x86ms->fw_cfg);
|
|
|
|
isa_build_aml(ISA_BUS(isabus), sb_scope);
|
2020-09-15 12:09:02 +00:00
|
|
|
build_ged_aml(sb_scope, GED_DEVICE, x86ms->acpi_dev,
|
2020-09-15 12:08:56 +00:00
|
|
|
GED_MMIO_IRQ, AML_SYSTEM_MEMORY, GED_MMIO_BASE);
|
|
|
|
acpi_dsdt_add_power_button(sb_scope);
|
2020-09-15 12:08:57 +00:00
|
|
|
acpi_dsdt_add_virtio(sb_scope, mms);
|
2020-10-20 07:48:39 +00:00
|
|
|
acpi_dsdt_add_xhci(sb_scope, mms);
|
2020-09-28 10:42:49 +00:00
|
|
|
acpi_dsdt_add_pci(sb_scope, mms);
|
2020-09-15 12:08:56 +00:00
|
|
|
aml_append(dsdt, sb_scope);
|
|
|
|
|
|
|
|
/* ACPI 5.0: Table 7-209 System State Package */
|
|
|
|
scope = aml_scope("\\");
|
|
|
|
pkg = aml_package(4);
|
|
|
|
aml_append(pkg, aml_int(ACPI_GED_SLP_TYP_S5));
|
|
|
|
aml_append(pkg, aml_int(0)); /* ignored */
|
|
|
|
aml_append(pkg, aml_int(0)); /* reserved */
|
|
|
|
aml_append(pkg, aml_int(0)); /* reserved */
|
|
|
|
aml_append(scope, aml_name_decl("_S5", pkg));
|
|
|
|
aml_append(dsdt, scope);
|
|
|
|
|
|
|
|
/* copy AML table into ACPI tables blob and patch header there */
|
|
|
|
g_array_append_vals(table_data, dsdt->buf->data, dsdt->buf->len);
|
|
|
|
build_header(linker, table_data,
|
|
|
|
(void *)(table_data->data + table_data->len - dsdt->buf->len),
|
2021-01-19 00:32:13 +00:00
|
|
|
"DSDT", dsdt->buf->len, 2, mms->oem_id, mms->oem_table_id);
|
2020-09-15 12:08:56 +00:00
|
|
|
free_aml_allocator();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void acpi_build_microvm(AcpiBuildTables *tables,
|
|
|
|
MicrovmMachineState *mms)
|
|
|
|
{
|
|
|
|
MachineState *machine = MACHINE(mms);
|
2020-09-15 12:09:02 +00:00
|
|
|
X86MachineState *x86ms = X86_MACHINE(mms);
|
2020-09-15 12:08:56 +00:00
|
|
|
GArray *table_offsets;
|
|
|
|
GArray *tables_blob = tables->table_data;
|
|
|
|
unsigned dsdt, xsdt;
|
|
|
|
AcpiFadtData pmfadt = {
|
|
|
|
/* ACPI 5.0: 4.1 Hardware-Reduced ACPI */
|
|
|
|
.rev = 5,
|
|
|
|
.flags = ((1 << ACPI_FADT_F_HW_REDUCED_ACPI) |
|
|
|
|
(1 << ACPI_FADT_F_RESET_REG_SUP)),
|
|
|
|
|
|
|
|
/* ACPI 5.0: 4.8.3.7 Sleep Control and Status Registers */
|
|
|
|
.sleep_ctl = {
|
|
|
|
.space_id = AML_AS_SYSTEM_MEMORY,
|
|
|
|
.bit_width = 8,
|
|
|
|
.address = GED_MMIO_BASE_REGS + ACPI_GED_REG_SLEEP_CTL,
|
|
|
|
},
|
|
|
|
.sleep_sts = {
|
|
|
|
.space_id = AML_AS_SYSTEM_MEMORY,
|
|
|
|
.bit_width = 8,
|
|
|
|
.address = GED_MMIO_BASE_REGS + ACPI_GED_REG_SLEEP_STS,
|
|
|
|
},
|
|
|
|
|
|
|
|
/* ACPI 5.0: 4.8.3.6 Reset Register */
|
|
|
|
.reset_reg = {
|
|
|
|
.space_id = AML_AS_SYSTEM_MEMORY,
|
|
|
|
.bit_width = 8,
|
|
|
|
.address = GED_MMIO_BASE_REGS + ACPI_GED_REG_RESET,
|
|
|
|
},
|
|
|
|
.reset_val = ACPI_GED_RESET_VALUE,
|
|
|
|
};
|
|
|
|
|
|
|
|
table_offsets = g_array_new(false, true /* clear */,
|
|
|
|
sizeof(uint32_t));
|
|
|
|
bios_linker_loader_alloc(tables->linker,
|
|
|
|
ACPI_BUILD_TABLE_FILE, tables_blob,
|
|
|
|
64 /* Ensure FACS is aligned */,
|
|
|
|
false /* high memory */);
|
|
|
|
|
|
|
|
dsdt = tables_blob->len;
|
|
|
|
build_dsdt_microvm(tables_blob, tables->linker, mms);
|
|
|
|
|
|
|
|
pmfadt.dsdt_tbl_offset = &dsdt;
|
|
|
|
pmfadt.xdsdt_tbl_offset = &dsdt;
|
|
|
|
acpi_add_table(table_offsets, tables_blob);
|
2021-01-19 00:32:13 +00:00
|
|
|
build_fadt(tables_blob, tables->linker, &pmfadt, mms->oem_id,
|
|
|
|
mms->oem_table_id);
|
2020-09-15 12:08:56 +00:00
|
|
|
|
|
|
|
acpi_add_table(table_offsets, tables_blob);
|
|
|
|
acpi_build_madt(tables_blob, tables->linker, X86_MACHINE(machine),
|
2021-01-19 00:32:13 +00:00
|
|
|
ACPI_DEVICE_IF(x86ms->acpi_dev), mms->oem_id,
|
|
|
|
mms->oem_table_id);
|
2020-09-15 12:08:56 +00:00
|
|
|
|
|
|
|
xsdt = tables_blob->len;
|
2021-01-19 00:32:13 +00:00
|
|
|
build_xsdt(tables_blob, tables->linker, table_offsets, mms->oem_id,
|
|
|
|
mms->oem_table_id);
|
2020-09-15 12:08:56 +00:00
|
|
|
|
|
|
|
/* RSDP is in FSEG memory, so allocate it separately */
|
|
|
|
{
|
|
|
|
AcpiRsdpData rsdp_data = {
|
|
|
|
/* ACPI 2.0: 5.2.4.3 RSDP Structure */
|
|
|
|
.revision = 2, /* xsdt needs v2 */
|
2021-01-19 00:32:13 +00:00
|
|
|
.oem_id = mms->oem_id,
|
2020-09-15 12:08:56 +00:00
|
|
|
.xsdt_tbl_offset = &xsdt,
|
|
|
|
.rsdt_tbl_offset = NULL,
|
|
|
|
};
|
|
|
|
build_rsdp(tables->rsdp, tables->linker, &rsdp_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Cleanup memory that's no longer used. */
|
|
|
|
g_array_free(table_offsets, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void acpi_build_no_update(void *build_opaque)
|
|
|
|
{
|
|
|
|
/* nothing, microvm tables don't change at runtime */
|
|
|
|
}
|
|
|
|
|
|
|
|
void acpi_setup_microvm(MicrovmMachineState *mms)
|
|
|
|
{
|
|
|
|
X86MachineState *x86ms = X86_MACHINE(mms);
|
|
|
|
AcpiBuildTables tables;
|
|
|
|
|
|
|
|
assert(x86ms->fw_cfg);
|
|
|
|
|
|
|
|
if (!x86_machine_is_acpi_enabled(x86ms)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
acpi_build_tables_init(&tables);
|
|
|
|
acpi_build_microvm(&tables, mms);
|
|
|
|
|
|
|
|
/* Now expose it all to Guest */
|
|
|
|
acpi_add_rom_blob(acpi_build_no_update, NULL,
|
|
|
|
tables.table_data,
|
|
|
|
ACPI_BUILD_TABLE_FILE,
|
|
|
|
ACPI_BUILD_TABLE_MAX_SIZE);
|
|
|
|
acpi_add_rom_blob(acpi_build_no_update, NULL,
|
|
|
|
tables.linker->cmd_blob,
|
acpi: Set proper maximum size for "etc/table-loader" blob
The resizeable memory region / RAMBlock that is created for the cmd blob
has a maximum size of whole host pages (e.g., 4k), because RAMBlocks
work on full host pages. In addition, in i386 ACPI code:
acpi_align_size(tables->linker->cmd_blob, ACPI_BUILD_ALIGN_SIZE);
makes sure to align to multiples of 4k, padding with 0.
For example, if our cmd_blob is created with a size of 2k, the maximum
size is 4k - we cannot grow beyond that. Growing might be required
due to guest action when rebuilding the tables, but also on incoming
migration.
This automatic generation of the maximum size used to be sufficient,
however, there are cases where we cross host pages now when growing at
runtime: we exceed the maximum size of the RAMBlock and can crash QEMU when
trying to resize the resizeable memory region / RAMBlock:
$ build/qemu-system-x86_64 --enable-kvm \
-machine q35,nvdimm=on \
-smp 1 \
-cpu host \
-m size=2G,slots=8,maxmem=4G \
-object memory-backend-file,id=mem0,mem-path=/tmp/nvdimm,size=256M \
-device nvdimm,label-size=131072,memdev=mem0,id=nvdimm0,slot=1 \
-nodefaults \
-device vmgenid \
-device intel-iommu
Results in:
Unexpected error in qemu_ram_resize() at ../softmmu/physmem.c:1850:
qemu-system-x86_64: Size too large: /rom@etc/table-loader:
0x2000 > 0x1000: Invalid argument
In this configuration, we consume exactly 4k (32 entries, 128 bytes each)
when creating the VM. However, once the guest boots up and maps the MCFG,
we also create the MCFG table and end up consuming 2 additional entries
(pointer + checksum) -- which is where we try resizing the memory region
/ RAMBlock, however, the maximum size does not allow for it.
Currently, we get the following maximum sizes for our different
mutable tables based on behavior of resizeable RAMBlock:
hw table max_size
------- ---------------------------------------------------------
virt "etc/acpi/tables" ACPI_BUILD_TABLE_MAX_SIZE (0x200000)
virt "etc/table-loader" HOST_PAGE_ALIGN(initial_size)
virt "etc/acpi/rsdp" HOST_PAGE_ALIGN(initial_size)
i386 "etc/acpi/tables" ACPI_BUILD_TABLE_MAX_SIZE (0x200000)
i386 "etc/table-loader" HOST_PAGE_ALIGN(initial_size)
i386 "etc/acpi/rsdp" HOST_PAGE_ALIGN(initial_size)
microvm "etc/acpi/tables" ACPI_BUILD_TABLE_MAX_SIZE (0x200000)
microvm "etc/table-loader" HOST_PAGE_ALIGN(initial_size)
microvm "etc/acpi/rsdp" HOST_PAGE_ALIGN(initial_size)
Let's set the maximum table size for "etc/table-loader" to 64k, so we
can properly grow at runtime, which should be good enough for the future.
Migration is not concerned with the maximum size of a RAMBlock, only
with the used size - so existing setups are not affected. Of course, we
cannot migrate a VM that would have crash when started on older QEMU from
new QEMU to older QEMU without failing early on the destination when
synchronizing the RAM state:
qemu-system-x86_64: Size too large: /rom@etc/table-loader: 0x2000 > 0x1000: Invalid argument
qemu-system-x86_64: error while loading state for instance 0x0 of device 'ram'
qemu-system-x86_64: load of migration failed: Invalid argument
We'll refactor the code next, to make sure we get rid of this implicit
behavior for "etc/acpi/rsdp" as well and to make the code easier to
grasp.
Reviewed-by: Igor Mammedov <imammedo@redhat.com>
Cc: Alistair Francis <alistair.francis@xilinx.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: "Michael S. Tsirkin" <mst@redhat.com>
Cc: Igor Mammedov <imammedo@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Shannon Zhao <shannon.zhaosl@gmail.com>
Cc: Marcel Apfelbaum <marcel.apfelbaum@gmail.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: Richard Henderson <richard.henderson@linaro.org>
Cc: Laszlo Ersek <lersek@redhat.com>
Signed-off-by: David Hildenbrand <david@redhat.com>
Message-Id: <20210304105554.121674-2-david@redhat.com>
Reviewed-by: Laszlo Ersek <lersek@redhat.com>
Reviewed-by: Igor Mammedov <imammedo@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2021-03-04 10:55:51 +00:00
|
|
|
"etc/table-loader", ACPI_BUILD_LOADER_MAX_SIZE);
|
2020-09-15 12:08:56 +00:00
|
|
|
acpi_add_rom_blob(acpi_build_no_update, NULL,
|
|
|
|
tables.rsdp,
|
|
|
|
ACPI_BUILD_RSDP_FILE, 0);
|
|
|
|
|
|
|
|
acpi_build_tables_cleanup(&tables, false);
|
|
|
|
}
|