aboutsummaryrefslogtreecommitdiffstats
path: root/hw/acpi
diff options
context:
space:
mode:
Diffstat (limited to 'hw/acpi')
-rw-r--r--hw/acpi/Kconfig62
-rw-r--r--hw/acpi/acpi-cpu-hotplug-stub.c50
-rw-r--r--hw/acpi/acpi-mem-hotplug-stub.c35
-rw-r--r--hw/acpi/acpi-nvdimm-stub.c8
-rw-r--r--hw/acpi/acpi-pci-hotplug-stub.c47
-rw-r--r--hw/acpi/acpi-stub.c29
-rw-r--r--hw/acpi/acpi-x86-stub.c14
-rw-r--r--hw/acpi/acpi_interface.c25
-rw-r--r--hw/acpi/aml-build-stub.c93
-rw-r--r--hw/acpi/aml-build.c2439
-rw-r--r--hw/acpi/bios-linker-loader.c351
-rw-r--r--hw/acpi/core.c738
-rw-r--r--hw/acpi/cpu.c711
-rw-r--r--hw/acpi/cpu_hotplug.c333
-rw-r--r--hw/acpi/generic_event_device.c433
-rw-r--r--hw/acpi/ghes-stub.c22
-rw-r--r--hw/acpi/ghes.c460
-rw-r--r--hw/acpi/hmat.c267
-rw-r--r--hw/acpi/hmat.h43
-rw-r--r--hw/acpi/ich9.c595
-rw-r--r--hw/acpi/ipmi-stub.c15
-rw-r--r--hw/acpi/ipmi.c107
-rw-r--r--hw/acpi/memory_hotplug.c730
-rw-r--r--hw/acpi/meson.build33
-rw-r--r--hw/acpi/nvdimm.c1378
-rw-r--r--hw/acpi/pci.c61
-rw-r--r--hw/acpi/pcihp.c564
-rw-r--r--hw/acpi/piix4.c710
-rw-r--r--hw/acpi/tco.c261
-rw-r--r--hw/acpi/tpm.c459
-rw-r--r--hw/acpi/trace-events57
-rw-r--r--hw/acpi/trace.h1
-rw-r--r--hw/acpi/utils.c48
-rw-r--r--hw/acpi/viot.c114
-rw-r--r--hw/acpi/viot.h13
-rw-r--r--hw/acpi/vmgenid.c263
36 files changed, 11569 insertions, 0 deletions
diff --git a/hw/acpi/Kconfig b/hw/acpi/Kconfig
new file mode 100644
index 000000000..622b0b50b
--- /dev/null
+++ b/hw/acpi/Kconfig
@@ -0,0 +1,62 @@
+config ACPI
+ bool
+
+config ACPI_X86
+ bool
+ select ACPI
+ select ACPI_NVDIMM
+ select ACPI_CPU_HOTPLUG
+ select ACPI_MEMORY_HOTPLUG
+ select ACPI_HMAT
+ select ACPI_PIIX4
+ select ACPI_PCIHP
+
+config ACPI_X86_ICH
+ bool
+ select ACPI_X86
+
+config ACPI_CPU_HOTPLUG
+ bool
+
+config ACPI_MEMORY_HOTPLUG
+ bool
+ select MEM_DEVICE
+
+config ACPI_NVDIMM
+ bool
+ depends on ACPI
+
+config ACPI_PIIX4
+ bool
+ depends on ACPI
+
+config ACPI_PCIHP
+ bool
+ depends on ACPI
+
+config ACPI_HMAT
+ bool
+ depends on ACPI
+
+config ACPI_APEI
+ bool
+ depends on ACPI
+
+config ACPI_PCI
+ bool
+ depends on ACPI && PCI
+
+config ACPI_VMGENID
+ bool
+ default y
+ depends on PC
+
+config ACPI_VIOT
+ bool
+ depends on ACPI
+
+config ACPI_HW_REDUCED
+ bool
+ select ACPI
+ select ACPI_MEMORY_HOTPLUG
+ select ACPI_NVDIMM
diff --git a/hw/acpi/acpi-cpu-hotplug-stub.c b/hw/acpi/acpi-cpu-hotplug-stub.c
new file mode 100644
index 000000000..3fc4b14c2
--- /dev/null
+++ b/hw/acpi/acpi-cpu-hotplug-stub.c
@@ -0,0 +1,50 @@
+#include "qemu/osdep.h"
+#include "hw/acpi/cpu_hotplug.h"
+#include "migration/vmstate.h"
+
+
+/* Following stubs are all related to ACPI cpu hotplug */
+const VMStateDescription vmstate_cpu_hotplug;
+
+void acpi_switch_to_modern_cphp(AcpiCpuHotplug *gpe_cpu,
+ CPUHotplugState *cpuhp_state,
+ uint16_t io_port)
+{
+ return;
+}
+
+void legacy_acpi_cpu_hotplug_init(MemoryRegion *parent, Object *owner,
+ AcpiCpuHotplug *gpe_cpu, uint16_t base)
+{
+ return;
+}
+
+void acpi_cpu_ospm_status(CPUHotplugState *cpu_st, ACPIOSTInfoList ***list)
+{
+ return;
+}
+
+void acpi_cpu_plug_cb(HotplugHandler *hotplug_dev,
+ CPUHotplugState *cpu_st, DeviceState *dev, Error **errp)
+{
+ return;
+}
+
+void legacy_acpi_cpu_plug_cb(HotplugHandler *hotplug_dev,
+ AcpiCpuHotplug *g, DeviceState *dev, Error **errp)
+{
+ return;
+}
+
+void acpi_cpu_unplug_cb(CPUHotplugState *cpu_st,
+ DeviceState *dev, Error **errp)
+{
+ return;
+}
+
+void acpi_cpu_unplug_request_cb(HotplugHandler *hotplug_dev,
+ CPUHotplugState *cpu_st,
+ DeviceState *dev, Error **errp)
+{
+ return;
+}
diff --git a/hw/acpi/acpi-mem-hotplug-stub.c b/hw/acpi/acpi-mem-hotplug-stub.c
new file mode 100644
index 000000000..73a076a26
--- /dev/null
+++ b/hw/acpi/acpi-mem-hotplug-stub.c
@@ -0,0 +1,35 @@
+#include "qemu/osdep.h"
+#include "hw/acpi/memory_hotplug.h"
+#include "migration/vmstate.h"
+
+const VMStateDescription vmstate_memory_hotplug;
+
+void acpi_memory_hotplug_init(MemoryRegion *as, Object *owner,
+ MemHotplugState *state, hwaddr io_base)
+{
+ return;
+}
+
+void acpi_memory_ospm_status(MemHotplugState *mem_st, ACPIOSTInfoList ***list)
+{
+ return;
+}
+
+void acpi_memory_plug_cb(HotplugHandler *hotplug_dev, MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ return;
+}
+
+void acpi_memory_unplug_cb(MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ return;
+}
+
+void acpi_memory_unplug_request_cb(HotplugHandler *hotplug_dev,
+ MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ return;
+}
diff --git a/hw/acpi/acpi-nvdimm-stub.c b/hw/acpi/acpi-nvdimm-stub.c
new file mode 100644
index 000000000..8baff9be6
--- /dev/null
+++ b/hw/acpi/acpi-nvdimm-stub.c
@@ -0,0 +1,8 @@
+#include "qemu/osdep.h"
+#include "hw/mem/nvdimm.h"
+#include "hw/hotplug.h"
+
+void nvdimm_acpi_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev)
+{
+ return;
+}
diff --git a/hw/acpi/acpi-pci-hotplug-stub.c b/hw/acpi/acpi-pci-hotplug-stub.c
new file mode 100644
index 000000000..734e4c598
--- /dev/null
+++ b/hw/acpi/acpi-pci-hotplug-stub.c
@@ -0,0 +1,47 @@
+#include "qemu/osdep.h"
+#include "hw/acpi/pcihp.h"
+#include "migration/vmstate.h"
+
+const VMStateDescription vmstate_acpi_pcihp_pci_status;
+
+void acpi_pcihp_init(Object *owner, AcpiPciHpState *s, PCIBus *root_bus,
+ MemoryRegion *address_space_io, bool bridges_enabled,
+ uint16_t io_base)
+{
+ return;
+}
+
+void acpi_pcihp_device_plug_cb(HotplugHandler *hotplug_dev, AcpiPciHpState *s,
+ DeviceState *dev, Error **errp)
+{
+ return;
+}
+
+void acpi_pcihp_device_pre_plug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ return;
+}
+
+void acpi_pcihp_device_unplug_cb(HotplugHandler *hotplug_dev, AcpiPciHpState *s,
+ DeviceState *dev, Error **errp)
+{
+ return;
+}
+
+void acpi_pcihp_device_unplug_request_cb(HotplugHandler *hotplug_dev,
+ AcpiPciHpState *s, DeviceState *dev,
+ Error **errp)
+{
+ return;
+}
+
+void acpi_pcihp_reset(AcpiPciHpState *s, bool acpihp_root_off)
+{
+ return;
+}
+
+bool vmstate_acpi_pcihp_use_acpi_index(void *opaque, int version_id)
+{
+ return false;
+}
diff --git a/hw/acpi/acpi-stub.c b/hw/acpi/acpi-stub.c
new file mode 100644
index 000000000..4c9d081ed
--- /dev/null
+++ b/hw/acpi/acpi-stub.c
@@ -0,0 +1,29 @@
+/*
+ * ACPI stubs for platforms that don't support ACPI.
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * 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"
+#include "qapi/error.h"
+#include "qapi/qmp/qerror.h"
+#include "hw/acpi/acpi.h"
+
+void acpi_table_add(const QemuOpts *opts, Error **errp)
+{
+ error_setg(errp, QERR_UNSUPPORTED);
+}
diff --git a/hw/acpi/acpi-x86-stub.c b/hw/acpi/acpi-x86-stub.c
new file mode 100644
index 000000000..3df1e090f
--- /dev/null
+++ b/hw/acpi/acpi-x86-stub.c
@@ -0,0 +1,14 @@
+#include "qemu/osdep.h"
+#include "hw/i386/pc.h"
+#include "hw/i386/acpi-build.h"
+
+void pc_madt_cpu_entry(AcpiDeviceIf *adev, int uid,
+ const CPUArchIdList *apic_ids, GArray *entry,
+ bool force_enabled)
+{
+}
+
+Object *acpi_get_i386_pci_host(void)
+{
+ return NULL;
+}
diff --git a/hw/acpi/acpi_interface.c b/hw/acpi/acpi_interface.c
new file mode 100644
index 000000000..6583917b8
--- /dev/null
+++ b/hw/acpi/acpi_interface.c
@@ -0,0 +1,25 @@
+#include "qemu/osdep.h"
+#include "hw/acpi/acpi_dev_interface.h"
+#include "qemu/module.h"
+
+void acpi_send_event(DeviceState *dev, AcpiEventStatusBits event)
+{
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_GET_CLASS(dev);
+ if (adevc->send_event) {
+ AcpiDeviceIf *adev = ACPI_DEVICE_IF(dev);
+ adevc->send_event(adev, event);
+ }
+}
+
+static void register_types(void)
+{
+ static const TypeInfo acpi_dev_if_info = {
+ .name = TYPE_ACPI_DEVICE_IF,
+ .parent = TYPE_INTERFACE,
+ .class_size = sizeof(AcpiDeviceIfClass),
+ };
+
+ type_register_static(&acpi_dev_if_info);
+}
+
+type_init(register_types)
diff --git a/hw/acpi/aml-build-stub.c b/hw/acpi/aml-build-stub.c
new file mode 100644
index 000000000..8d8ad1a31
--- /dev/null
+++ b/hw/acpi/aml-build-stub.c
@@ -0,0 +1,93 @@
+/*
+ * ACPI aml builder stubs for platforms that don't support ACPI.
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2016 Red Hat, Inc.
+ *
+ * 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"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/aml-build.h"
+
+void aml_append(Aml *parent_ctx, Aml *child)
+{
+}
+
+Aml *aml_resource_template(void)
+{
+ return NULL;
+}
+
+Aml *aml_device(const char *name_format, ...)
+{
+ return NULL;
+}
+
+Aml *aml_eisaid(const char *str)
+{
+ return NULL;
+}
+
+Aml *aml_name_decl(const char *name, Aml *val)
+{
+ return NULL;
+}
+
+Aml *aml_io(AmlIODecode dec, uint16_t min_base, uint16_t max_base,
+ uint8_t aln, uint8_t len)
+{
+ return NULL;
+}
+
+Aml *aml_irq_no_flags(uint8_t irq)
+{
+ return NULL;
+}
+
+Aml *aml_interrupt(AmlConsumerAndProducer con_and_pro,
+ AmlLevelAndEdge level_and_edge,
+ AmlActiveHighAndLow high_and_low, AmlShared shared,
+ uint32_t *irq_list, uint8_t irq_count)
+{
+ return NULL;
+}
+
+Aml *aml_memory32_fixed(uint32_t addr, uint32_t size,
+ AmlReadAndWrite read_and_write)
+{
+ return NULL;
+}
+
+Aml *aml_int(const uint64_t val)
+{
+ return NULL;
+}
+
+Aml *aml_package(uint8_t num_elements)
+{
+ return NULL;
+}
+
+Aml *aml_dma(AmlDmaType typ, AmlDmaBusMaster bm, AmlTransferSize sz,
+ uint8_t channel)
+{
+ return NULL;
+}
+
+Aml *aml_buffer(int buffer_size, uint8_t *byte_list)
+{
+ return NULL;
+}
diff --git a/hw/acpi/aml-build.c b/hw/acpi/aml-build.c
new file mode 100644
index 000000000..b3b3310df
--- /dev/null
+++ b/hw/acpi/aml-build.c
@@ -0,0 +1,2439 @@
+/* Support for generating ACPI tables and passing them to Guests
+ *
+ * Copyright (C) 2015 Red Hat Inc
+ *
+ * Author: Michael S. Tsirkin <mst@redhat.com>
+ * Author: Igor Mammedov <imammedo@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"
+#include <glib/gprintf.h>
+#include "hw/acpi/aml-build.h"
+#include "qemu/bswap.h"
+#include "qemu/bitops.h"
+#include "sysemu/numa.h"
+#include "hw/boards.h"
+#include "hw/acpi/tpm.h"
+#include "hw/pci/pci_host.h"
+#include "hw/pci/pci_bus.h"
+#include "hw/pci/pci_bridge.h"
+#include "qemu/cutils.h"
+
+static GArray *build_alloc_array(void)
+{
+ return g_array_new(false, true /* clear */, 1);
+}
+
+static void build_free_array(GArray *array)
+{
+ g_array_free(array, true);
+}
+
+static void build_prepend_byte(GArray *array, uint8_t val)
+{
+ g_array_prepend_val(array, val);
+}
+
+static void build_append_byte(GArray *array, uint8_t val)
+{
+ g_array_append_val(array, val);
+}
+
+static void build_append_padded_str(GArray *array, const char *str,
+ size_t maxlen, char pad)
+{
+ size_t i;
+ size_t len = strlen(str);
+
+ g_assert(len <= maxlen);
+ g_array_append_vals(array, str, len);
+ for (i = maxlen - len; i > 0; i--) {
+ g_array_append_val(array, pad);
+ }
+}
+
+static void build_append_array(GArray *array, GArray *val)
+{
+ g_array_append_vals(array, val->data, val->len);
+}
+
+#define ACPI_NAMESEG_LEN 4
+
+void crs_range_insert(GPtrArray *ranges, uint64_t base, uint64_t limit)
+{
+ CrsRangeEntry *entry;
+
+ entry = g_malloc(sizeof(*entry));
+ entry->base = base;
+ entry->limit = limit;
+
+ g_ptr_array_add(ranges, entry);
+}
+
+static void crs_range_free(gpointer data)
+{
+ CrsRangeEntry *entry = (CrsRangeEntry *)data;
+ g_free(entry);
+}
+
+void crs_range_set_init(CrsRangeSet *range_set)
+{
+ range_set->io_ranges = g_ptr_array_new_with_free_func(crs_range_free);
+ range_set->mem_ranges = g_ptr_array_new_with_free_func(crs_range_free);
+ range_set->mem_64bit_ranges =
+ g_ptr_array_new_with_free_func(crs_range_free);
+}
+
+void crs_range_set_free(CrsRangeSet *range_set)
+{
+ g_ptr_array_free(range_set->io_ranges, true);
+ g_ptr_array_free(range_set->mem_ranges, true);
+ g_ptr_array_free(range_set->mem_64bit_ranges, true);
+}
+
+static gint crs_range_compare(gconstpointer a, gconstpointer b)
+{
+ CrsRangeEntry *entry_a = *(CrsRangeEntry **)a;
+ CrsRangeEntry *entry_b = *(CrsRangeEntry **)b;
+
+ if (entry_a->base < entry_b->base) {
+ return -1;
+ } else if (entry_a->base > entry_b->base) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*
+ * crs_replace_with_free_ranges - given the 'used' ranges within [start - end]
+ * interval, computes the 'free' ranges from the same interval.
+ * Example: If the input array is { [a1 - a2],[b1 - b2] }, the function
+ * will return { [base - a1], [a2 - b1], [b2 - limit] }.
+ */
+void crs_replace_with_free_ranges(GPtrArray *ranges,
+ uint64_t start, uint64_t end)
+{
+ GPtrArray *free_ranges = g_ptr_array_new();
+ uint64_t free_base = start;
+ int i;
+
+ g_ptr_array_sort(ranges, crs_range_compare);
+ for (i = 0; i < ranges->len; i++) {
+ CrsRangeEntry *used = g_ptr_array_index(ranges, i);
+
+ if (free_base < used->base) {
+ crs_range_insert(free_ranges, free_base, used->base - 1);
+ }
+
+ free_base = used->limit + 1;
+ }
+
+ if (free_base < end) {
+ crs_range_insert(free_ranges, free_base, end);
+ }
+
+ g_ptr_array_set_size(ranges, 0);
+ for (i = 0; i < free_ranges->len; i++) {
+ g_ptr_array_add(ranges, g_ptr_array_index(free_ranges, i));
+ }
+
+ g_ptr_array_free(free_ranges, true);
+}
+
+/*
+ * crs_range_merge - merges adjacent ranges in the given array.
+ * Array elements are deleted and replaced with the merged ranges.
+ */
+static void crs_range_merge(GPtrArray *range)
+{
+ GPtrArray *tmp = g_ptr_array_new_with_free_func(crs_range_free);
+ CrsRangeEntry *entry;
+ uint64_t range_base, range_limit;
+ int i;
+
+ if (!range->len) {
+ return;
+ }
+
+ g_ptr_array_sort(range, crs_range_compare);
+
+ entry = g_ptr_array_index(range, 0);
+ range_base = entry->base;
+ range_limit = entry->limit;
+ for (i = 1; i < range->len; i++) {
+ entry = g_ptr_array_index(range, i);
+ if (entry->base - 1 == range_limit) {
+ range_limit = entry->limit;
+ } else {
+ crs_range_insert(tmp, range_base, range_limit);
+ range_base = entry->base;
+ range_limit = entry->limit;
+ }
+ }
+ crs_range_insert(tmp, range_base, range_limit);
+
+ g_ptr_array_set_size(range, 0);
+ for (i = 0; i < tmp->len; i++) {
+ entry = g_ptr_array_index(tmp, i);
+ crs_range_insert(range, entry->base, entry->limit);
+ }
+ g_ptr_array_free(tmp, true);
+}
+
+static void
+build_append_nameseg(GArray *array, const char *seg)
+{
+ int len;
+
+ len = strlen(seg);
+ assert(len <= ACPI_NAMESEG_LEN);
+
+ g_array_append_vals(array, seg, len);
+ /* Pad up to ACPI_NAMESEG_LEN characters if necessary. */
+ g_array_append_vals(array, "____", ACPI_NAMESEG_LEN - len);
+}
+
+static void GCC_FMT_ATTR(2, 0)
+build_append_namestringv(GArray *array, const char *format, va_list ap)
+{
+ char *s;
+ char **segs;
+ char **segs_iter;
+ int seg_count = 0;
+
+ s = g_strdup_vprintf(format, ap);
+ segs = g_strsplit(s, ".", 0);
+ g_free(s);
+
+ /* count segments */
+ segs_iter = segs;
+ while (*segs_iter) {
+ ++segs_iter;
+ ++seg_count;
+ }
+ /*
+ * ACPI 5.0 spec: 20.2.2 Name Objects Encoding:
+ * "SegCount can be from 1 to 255"
+ */
+ assert(seg_count > 0 && seg_count <= 255);
+
+ /* handle RootPath || PrefixPath */
+ s = *segs;
+ while (*s == '\\' || *s == '^') {
+ build_append_byte(array, *s);
+ ++s;
+ }
+
+ switch (seg_count) {
+ case 1:
+ if (!*s) {
+ build_append_byte(array, 0x00); /* NullName */
+ } else {
+ build_append_nameseg(array, s);
+ }
+ break;
+
+ case 2:
+ build_append_byte(array, 0x2E); /* DualNamePrefix */
+ build_append_nameseg(array, s);
+ build_append_nameseg(array, segs[1]);
+ break;
+ default:
+ build_append_byte(array, 0x2F); /* MultiNamePrefix */
+ build_append_byte(array, seg_count);
+
+ /* handle the 1st segment manually due to prefix/root path */
+ build_append_nameseg(array, s);
+
+ /* add the rest of segments */
+ segs_iter = segs + 1;
+ while (*segs_iter) {
+ build_append_nameseg(array, *segs_iter);
+ ++segs_iter;
+ }
+ break;
+ }
+ g_strfreev(segs);
+}
+
+GCC_FMT_ATTR(2, 3)
+static void build_append_namestring(GArray *array, const char *format, ...)
+{
+ va_list ap;
+
+ va_start(ap, format);
+ build_append_namestringv(array, format, ap);
+ va_end(ap);
+}
+
+/* 5.4 Definition Block Encoding */
+enum {
+ PACKAGE_LENGTH_1BYTE_SHIFT = 6, /* Up to 63 - use extra 2 bits. */
+ PACKAGE_LENGTH_2BYTE_SHIFT = 4,
+ PACKAGE_LENGTH_3BYTE_SHIFT = 12,
+ PACKAGE_LENGTH_4BYTE_SHIFT = 20,
+};
+
+static void
+build_prepend_package_length(GArray *package, unsigned length, bool incl_self)
+{
+ uint8_t byte;
+ unsigned length_bytes;
+
+ if (length + 1 < (1 << PACKAGE_LENGTH_1BYTE_SHIFT)) {
+ length_bytes = 1;
+ } else if (length + 2 < (1 << PACKAGE_LENGTH_3BYTE_SHIFT)) {
+ length_bytes = 2;
+ } else if (length + 3 < (1 << PACKAGE_LENGTH_4BYTE_SHIFT)) {
+ length_bytes = 3;
+ } else {
+ length_bytes = 4;
+ }
+
+ /*
+ * NamedField uses PkgLength encoding but it doesn't include length
+ * of PkgLength itself.
+ */
+ if (incl_self) {
+ /*
+ * PkgLength is the length of the inclusive length of the data
+ * and PkgLength's length itself when used for terms with
+ * explitit length.
+ */
+ length += length_bytes;
+ }
+
+ switch (length_bytes) {
+ case 1:
+ byte = length;
+ build_prepend_byte(package, byte);
+ return;
+ case 4:
+ byte = length >> PACKAGE_LENGTH_4BYTE_SHIFT;
+ build_prepend_byte(package, byte);
+ length &= (1 << PACKAGE_LENGTH_4BYTE_SHIFT) - 1;
+ /* fall through */
+ case 3:
+ byte = length >> PACKAGE_LENGTH_3BYTE_SHIFT;
+ build_prepend_byte(package, byte);
+ length &= (1 << PACKAGE_LENGTH_3BYTE_SHIFT) - 1;
+ /* fall through */
+ case 2:
+ byte = length >> PACKAGE_LENGTH_2BYTE_SHIFT;
+ build_prepend_byte(package, byte);
+ length &= (1 << PACKAGE_LENGTH_2BYTE_SHIFT) - 1;
+ /* fall through */
+ }
+ /*
+ * Most significant two bits of byte zero indicate how many following bytes
+ * are in PkgLength encoding.
+ */
+ byte = ((length_bytes - 1) << PACKAGE_LENGTH_1BYTE_SHIFT) | length;
+ build_prepend_byte(package, byte);
+}
+
+static void
+build_append_pkg_length(GArray *array, unsigned length, bool incl_self)
+{
+ GArray *tmp = build_alloc_array();
+
+ build_prepend_package_length(tmp, length, incl_self);
+ build_append_array(array, tmp);
+ build_free_array(tmp);
+}
+
+static void build_package(GArray *package, uint8_t op)
+{
+ build_prepend_package_length(package, package->len, true);
+ build_prepend_byte(package, op);
+}
+
+static void build_extop_package(GArray *package, uint8_t op)
+{
+ build_package(package, op);
+ build_prepend_byte(package, 0x5B); /* ExtOpPrefix */
+}
+
+void build_append_int_noprefix(GArray *table, uint64_t value, int size)
+{
+ int i;
+
+ for (i = 0; i < size; ++i) {
+ build_append_byte(table, value & 0xFF);
+ value = value >> 8;
+ }
+}
+
+static void build_append_int(GArray *table, uint64_t value)
+{
+ if (value == 0x00) {
+ build_append_byte(table, 0x00); /* ZeroOp */
+ } else if (value == 0x01) {
+ build_append_byte(table, 0x01); /* OneOp */
+ } else if (value <= 0xFF) {
+ build_append_byte(table, 0x0A); /* BytePrefix */
+ build_append_int_noprefix(table, value, 1);
+ } else if (value <= 0xFFFF) {
+ build_append_byte(table, 0x0B); /* WordPrefix */
+ build_append_int_noprefix(table, value, 2);
+ } else if (value <= 0xFFFFFFFF) {
+ build_append_byte(table, 0x0C); /* DWordPrefix */
+ build_append_int_noprefix(table, value, 4);
+ } else {
+ build_append_byte(table, 0x0E); /* QWordPrefix */
+ build_append_int_noprefix(table, value, 8);
+ }
+}
+
+/* Generic Address Structure (GAS)
+ * ACPI 2.0/3.0: 5.2.3.1 Generic Address Structure
+ * 2.0 compat note:
+ * @access_width must be 0, see ACPI 2.0:Table 5-1
+ */
+void build_append_gas(GArray *table, AmlAddressSpace as,
+ uint8_t bit_width, uint8_t bit_offset,
+ uint8_t access_width, uint64_t address)
+{
+ build_append_int_noprefix(table, as, 1);
+ build_append_int_noprefix(table, bit_width, 1);
+ build_append_int_noprefix(table, bit_offset, 1);
+ build_append_int_noprefix(table, access_width, 1);
+ build_append_int_noprefix(table, address, 8);
+}
+
+/*
+ * Build NAME(XXXX, 0x00000000) where 0x00000000 is encoded as a dword,
+ * and return the offset to 0x00000000 for runtime patching.
+ *
+ * Warning: runtime patching is best avoided. Only use this as
+ * a replacement for DataTableRegion (for guests that don't
+ * support it).
+ */
+int
+build_append_named_dword(GArray *array, const char *name_format, ...)
+{
+ int offset;
+ va_list ap;
+
+ build_append_byte(array, 0x08); /* NameOp */
+ va_start(ap, name_format);
+ build_append_namestringv(array, name_format, ap);
+ va_end(ap);
+
+ build_append_byte(array, 0x0C); /* DWordPrefix */
+
+ offset = array->len;
+ build_append_int_noprefix(array, 0x00000000, 4);
+ assert(array->len == offset + 4);
+
+ return offset;
+}
+
+static GPtrArray *alloc_list;
+
+static Aml *aml_alloc(void)
+{
+ Aml *var = g_new0(typeof(*var), 1);
+
+ g_ptr_array_add(alloc_list, var);
+ var->block_flags = AML_NO_OPCODE;
+ var->buf = build_alloc_array();
+ return var;
+}
+
+static Aml *aml_opcode(uint8_t op)
+{
+ Aml *var = aml_alloc();
+
+ var->op = op;
+ var->block_flags = AML_OPCODE;
+ return var;
+}
+
+static Aml *aml_bundle(uint8_t op, AmlBlockFlags flags)
+{
+ Aml *var = aml_alloc();
+
+ var->op = op;
+ var->block_flags = flags;
+ return var;
+}
+
+static void aml_free(gpointer data, gpointer user_data)
+{
+ Aml *var = data;
+ build_free_array(var->buf);
+ g_free(var);
+}
+
+Aml *init_aml_allocator(void)
+{
+ assert(!alloc_list);
+ alloc_list = g_ptr_array_new();
+ return aml_alloc();
+}
+
+void free_aml_allocator(void)
+{
+ g_ptr_array_foreach(alloc_list, aml_free, NULL);
+ g_ptr_array_free(alloc_list, true);
+ alloc_list = 0;
+}
+
+/* pack data with DefBuffer encoding */
+static void build_buffer(GArray *array, uint8_t op)
+{
+ GArray *data = build_alloc_array();
+
+ build_append_int(data, array->len);
+ g_array_prepend_vals(array, data->data, data->len);
+ build_free_array(data);
+ build_package(array, op);
+}
+
+void aml_append(Aml *parent_ctx, Aml *child)
+{
+ GArray *buf = build_alloc_array();
+ build_append_array(buf, child->buf);
+
+ switch (child->block_flags) {
+ case AML_OPCODE:
+ build_append_byte(parent_ctx->buf, child->op);
+ break;
+ case AML_EXT_PACKAGE:
+ build_extop_package(buf, child->op);
+ break;
+ case AML_PACKAGE:
+ build_package(buf, child->op);
+ break;
+ case AML_RES_TEMPLATE:
+ build_append_byte(buf, 0x79); /* EndTag */
+ /*
+ * checksum operations are treated as succeeded if checksum
+ * field is zero. [ACPI Spec 1.0b, 6.4.2.8 End Tag]
+ */
+ build_append_byte(buf, 0);
+ /* fall through, to pack resources in buffer */
+ case AML_BUFFER:
+ build_buffer(buf, child->op);
+ break;
+ case AML_NO_OPCODE:
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ build_append_array(parent_ctx->buf, buf);
+ build_free_array(buf);
+}
+
+/* ACPI 1.0b: 16.2.5.1 Namespace Modifier Objects Encoding: DefScope */
+Aml *aml_scope(const char *name_format, ...)
+{
+ va_list ap;
+ Aml *var = aml_bundle(0x10 /* ScopeOp */, AML_PACKAGE);
+ va_start(ap, name_format);
+ build_append_namestringv(var->buf, name_format, ap);
+ va_end(ap);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefReturn */
+Aml *aml_return(Aml *val)
+{
+ Aml *var = aml_opcode(0xA4 /* ReturnOp */);
+ aml_append(var, val);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.6.3 Debug Objects Encoding: DebugObj */
+Aml *aml_debug(void)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x5B); /* ExtOpPrefix */
+ build_append_byte(var->buf, 0x31); /* DebugOp */
+ return var;
+}
+
+/*
+ * ACPI 1.0b: 16.2.3 Data Objects Encoding:
+ * encodes: ByteConst, WordConst, DWordConst, QWordConst, ZeroOp, OneOp
+ */
+Aml *aml_int(const uint64_t val)
+{
+ Aml *var = aml_alloc();
+ build_append_int(var->buf, val);
+ return var;
+}
+
+/*
+ * helper to construct NameString, which returns Aml object
+ * for using with aml_append or other aml_* terms
+ */
+Aml *aml_name(const char *name_format, ...)
+{
+ va_list ap;
+ Aml *var = aml_alloc();
+ va_start(ap, name_format);
+ build_append_namestringv(var->buf, name_format, ap);
+ va_end(ap);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.1 Namespace Modifier Objects Encoding: DefName */
+Aml *aml_name_decl(const char *name, Aml *val)
+{
+ Aml *var = aml_opcode(0x08 /* NameOp */);
+ build_append_namestring(var->buf, "%s", name);
+ aml_append(var, val);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.6.1 Arg Objects Encoding */
+Aml *aml_arg(int pos)
+{
+ uint8_t op = 0x68 /* ARG0 op */ + pos;
+
+ assert(pos <= 6);
+ return aml_opcode(op);
+}
+
+/* ACPI 2.0a: 17.2.4.4 Type 2 Opcodes Encoding: DefToInteger */
+Aml *aml_to_integer(Aml *arg)
+{
+ Aml *var = aml_opcode(0x99 /* ToIntegerOp */);
+ aml_append(var, arg);
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ return var;
+}
+
+/* ACPI 2.0a: 17.2.4.4 Type 2 Opcodes Encoding: DefToHexString */
+Aml *aml_to_hexstring(Aml *src, Aml *dst)
+{
+ Aml *var = aml_opcode(0x98 /* ToHexStringOp */);
+ aml_append(var, src);
+ if (dst) {
+ aml_append(var, dst);
+ } else {
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ }
+ return var;
+}
+
+/* ACPI 2.0a: 17.2.4.4 Type 2 Opcodes Encoding: DefToBuffer */
+Aml *aml_to_buffer(Aml *src, Aml *dst)
+{
+ Aml *var = aml_opcode(0x96 /* ToBufferOp */);
+ aml_append(var, src);
+ if (dst) {
+ aml_append(var, dst);
+ } else {
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ }
+ return var;
+}
+
+/* ACPI 2.0a: 17.2.4.4 Type 2 Opcodes Encoding: DefToDecimalString */
+Aml *aml_to_decimalstring(Aml *src, Aml *dst)
+{
+ Aml *var = aml_opcode(0x97 /* ToDecimalStringOp */);
+ aml_append(var, src);
+ if (dst) {
+ aml_append(var, dst);
+ } else {
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ }
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefStore */
+Aml *aml_store(Aml *val, Aml *target)
+{
+ Aml *var = aml_opcode(0x70 /* StoreOp */);
+ aml_append(var, val);
+ aml_append(var, target);
+ return var;
+}
+
+/**
+ * build_opcode_2arg_dst:
+ * @op: 1-byte opcode
+ * @arg1: 1st operand
+ * @arg2: 2nd operand
+ * @dst: optional target to store to, set to NULL if it's not required
+ *
+ * An internal helper to compose AML terms that have
+ * "Op Operand Operand Target"
+ * pattern.
+ *
+ * Returns: The newly allocated and composed according to patter Aml object.
+ */
+static Aml *
+build_opcode_2arg_dst(uint8_t op, Aml *arg1, Aml *arg2, Aml *dst)
+{
+ Aml *var = aml_opcode(op);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ if (dst) {
+ aml_append(var, dst);
+ } else {
+ build_append_byte(var->buf, 0x00 /* NullNameOp */);
+ }
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefAnd */
+Aml *aml_and(Aml *arg1, Aml *arg2, Aml *dst)
+{
+ return build_opcode_2arg_dst(0x7B /* AndOp */, arg1, arg2, dst);
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefOr */
+Aml *aml_or(Aml *arg1, Aml *arg2, Aml *dst)
+{
+ return build_opcode_2arg_dst(0x7D /* OrOp */, arg1, arg2, dst);
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLAnd */
+Aml *aml_land(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x90 /* LAndOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLOr */
+Aml *aml_lor(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x91 /* LOrOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefShiftLeft */
+Aml *aml_shiftleft(Aml *arg1, Aml *count)
+{
+ return build_opcode_2arg_dst(0x79 /* ShiftLeftOp */, arg1, count, NULL);
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefShiftRight */
+Aml *aml_shiftright(Aml *arg1, Aml *count, Aml *dst)
+{
+ return build_opcode_2arg_dst(0x7A /* ShiftRightOp */, arg1, count, dst);
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLLess */
+Aml *aml_lless(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x95 /* LLessOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefAdd */
+Aml *aml_add(Aml *arg1, Aml *arg2, Aml *dst)
+{
+ return build_opcode_2arg_dst(0x72 /* AddOp */, arg1, arg2, dst);
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefSubtract */
+Aml *aml_subtract(Aml *arg1, Aml *arg2, Aml *dst)
+{
+ return build_opcode_2arg_dst(0x74 /* SubtractOp */, arg1, arg2, dst);
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefIncrement */
+Aml *aml_increment(Aml *arg)
+{
+ Aml *var = aml_opcode(0x75 /* IncrementOp */);
+ aml_append(var, arg);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefDecrement */
+Aml *aml_decrement(Aml *arg)
+{
+ Aml *var = aml_opcode(0x76 /* DecrementOp */);
+ aml_append(var, arg);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefIndex */
+Aml *aml_index(Aml *arg1, Aml *idx)
+{
+ return build_opcode_2arg_dst(0x88 /* IndexOp */, arg1, idx, NULL);
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefNotify */
+Aml *aml_notify(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x86 /* NotifyOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefBreak */
+Aml *aml_break(void)
+{
+ Aml *var = aml_opcode(0xa5 /* BreakOp */);
+ return var;
+}
+
+/* helper to call method without argument */
+Aml *aml_call0(const char *method)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ return var;
+}
+
+/* helper to call method with 1 argument */
+Aml *aml_call1(const char *method, Aml *arg1)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ return var;
+}
+
+/* helper to call method with 2 arguments */
+Aml *aml_call2(const char *method, Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* helper to call method with 3 arguments */
+Aml *aml_call3(const char *method, Aml *arg1, Aml *arg2, Aml *arg3)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ aml_append(var, arg3);
+ return var;
+}
+
+/* helper to call method with 4 arguments */
+Aml *aml_call4(const char *method, Aml *arg1, Aml *arg2, Aml *arg3, Aml *arg4)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ aml_append(var, arg3);
+ aml_append(var, arg4);
+ return var;
+}
+
+/* helper to call method with 5 arguments */
+Aml *aml_call5(const char *method, Aml *arg1, Aml *arg2, Aml *arg3, Aml *arg4,
+ Aml *arg5)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ aml_append(var, arg3);
+ aml_append(var, arg4);
+ aml_append(var, arg5);
+ return var;
+}
+
+/* helper to call method with 5 arguments */
+Aml *aml_call6(const char *method, Aml *arg1, Aml *arg2, Aml *arg3, Aml *arg4,
+ Aml *arg5, Aml *arg6)
+{
+ Aml *var = aml_alloc();
+ build_append_namestring(var->buf, "%s", method);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ aml_append(var, arg3);
+ aml_append(var, arg4);
+ aml_append(var, arg5);
+ aml_append(var, arg6);
+ return var;
+}
+
+/*
+ * ACPI 5.0: 6.4.3.8.1 GPIO Connection Descriptor
+ * Type 1, Large Item Name 0xC
+ */
+
+static Aml *aml_gpio_connection(AmlGpioConnectionType type,
+ AmlConsumerAndProducer con_and_pro,
+ uint8_t flags, AmlPinConfig pin_config,
+ uint16_t output_drive,
+ uint16_t debounce_timeout,
+ const uint32_t pin_list[], uint32_t pin_count,
+ const char *resource_source_name,
+ const uint8_t *vendor_data,
+ uint16_t vendor_data_len)
+{
+ Aml *var = aml_alloc();
+ const uint16_t min_desc_len = 0x16;
+ uint16_t resource_source_name_len, length;
+ uint16_t pin_table_offset, resource_source_name_offset, vendor_data_offset;
+ uint32_t i;
+
+ assert(resource_source_name);
+ resource_source_name_len = strlen(resource_source_name) + 1;
+ length = min_desc_len + resource_source_name_len + vendor_data_len;
+ pin_table_offset = min_desc_len + 1;
+ resource_source_name_offset = pin_table_offset + pin_count * 2;
+ vendor_data_offset = resource_source_name_offset + resource_source_name_len;
+
+ build_append_byte(var->buf, 0x8C); /* GPIO Connection Descriptor */
+ build_append_int_noprefix(var->buf, length, 2); /* Length */
+ build_append_byte(var->buf, 1); /* Revision ID */
+ build_append_byte(var->buf, type); /* GPIO Connection Type */
+ /* General Flags (2 bytes) */
+ build_append_int_noprefix(var->buf, con_and_pro, 2);
+ /* Interrupt and IO Flags (2 bytes) */
+ build_append_int_noprefix(var->buf, flags, 2);
+ /* Pin Configuration 0 = Default 1 = Pull-up 2 = Pull-down 3 = No Pull */
+ build_append_byte(var->buf, pin_config);
+ /* Output Drive Strength (2 bytes) */
+ build_append_int_noprefix(var->buf, output_drive, 2);
+ /* Debounce Timeout (2 bytes) */
+ build_append_int_noprefix(var->buf, debounce_timeout, 2);
+ /* Pin Table Offset (2 bytes) */
+ build_append_int_noprefix(var->buf, pin_table_offset, 2);
+ build_append_byte(var->buf, 0); /* Resource Source Index */
+ /* Resource Source Name Offset (2 bytes) */
+ build_append_int_noprefix(var->buf, resource_source_name_offset, 2);
+ /* Vendor Data Offset (2 bytes) */
+ build_append_int_noprefix(var->buf, vendor_data_offset, 2);
+ /* Vendor Data Length (2 bytes) */
+ build_append_int_noprefix(var->buf, vendor_data_len, 2);
+ /* Pin Number (2n bytes)*/
+ for (i = 0; i < pin_count; i++) {
+ build_append_int_noprefix(var->buf, pin_list[i], 2);
+ }
+
+ /* Resource Source Name */
+ build_append_namestring(var->buf, "%s", resource_source_name);
+ build_append_byte(var->buf, '\0');
+
+ /* Vendor-defined Data */
+ if (vendor_data != NULL) {
+ g_array_append_vals(var->buf, vendor_data, vendor_data_len);
+ }
+
+ return var;
+}
+
+/*
+ * ACPI 5.0: 19.5.53
+ * GpioInt(GPIO Interrupt Connection Resource Descriptor Macro)
+ */
+Aml *aml_gpio_int(AmlConsumerAndProducer con_and_pro,
+ AmlLevelAndEdge edge_level,
+ AmlActiveHighAndLow active_level, AmlShared shared,
+ AmlPinConfig pin_config, uint16_t debounce_timeout,
+ const uint32_t pin_list[], uint32_t pin_count,
+ const char *resource_source_name,
+ const uint8_t *vendor_data, uint16_t vendor_data_len)
+{
+ uint8_t flags = edge_level | (active_level << 1) | (shared << 3);
+
+ return aml_gpio_connection(AML_INTERRUPT_CONNECTION, con_and_pro, flags,
+ pin_config, 0, debounce_timeout, pin_list,
+ pin_count, resource_source_name, vendor_data,
+ vendor_data_len);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.4 32-Bit Fixed Location Memory Range Descriptor
+ * (Type 1, Large Item Name 0x6)
+ */
+Aml *aml_memory32_fixed(uint32_t addr, uint32_t size,
+ AmlReadAndWrite read_and_write)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x86); /* Memory32Fixed Resource Descriptor */
+ build_append_byte(var->buf, 9); /* Length, bits[7:0] value = 9 */
+ build_append_byte(var->buf, 0); /* Length, bits[15:8] value = 0 */
+ build_append_byte(var->buf, read_and_write); /* Write status, 1 rw 0 ro */
+
+ /* Range base address */
+ build_append_byte(var->buf, extract32(addr, 0, 8)); /* bits[7:0] */
+ build_append_byte(var->buf, extract32(addr, 8, 8)); /* bits[15:8] */
+ build_append_byte(var->buf, extract32(addr, 16, 8)); /* bits[23:16] */
+ build_append_byte(var->buf, extract32(addr, 24, 8)); /* bits[31:24] */
+
+ /* Range length */
+ build_append_byte(var->buf, extract32(size, 0, 8)); /* bits[7:0] */
+ build_append_byte(var->buf, extract32(size, 8, 8)); /* bits[15:8] */
+ build_append_byte(var->buf, extract32(size, 16, 8)); /* bits[23:16] */
+ build_append_byte(var->buf, extract32(size, 24, 8)); /* bits[31:24] */
+ return var;
+}
+
+/*
+ * ACPI 5.0: 6.4.3.6 Extended Interrupt Descriptor
+ * Type 1, Large Item Name 0x9
+ */
+Aml *aml_interrupt(AmlConsumerAndProducer con_and_pro,
+ AmlLevelAndEdge level_and_edge,
+ AmlActiveHighAndLow high_and_low, AmlShared shared,
+ uint32_t *irq_list, uint8_t irq_count)
+{
+ int i;
+ Aml *var = aml_alloc();
+ uint8_t irq_flags = con_and_pro | (level_and_edge << 1)
+ | (high_and_low << 2) | (shared << 3);
+ const int header_bytes_in_len = 2;
+ uint16_t len = header_bytes_in_len + irq_count * sizeof(uint32_t);
+
+ assert(irq_count > 0);
+
+ build_append_byte(var->buf, 0x89); /* Extended irq descriptor */
+ build_append_byte(var->buf, len & 0xFF); /* Length, bits[7:0] */
+ build_append_byte(var->buf, len >> 8); /* Length, bits[15:8] */
+ build_append_byte(var->buf, irq_flags); /* Interrupt Vector Information. */
+ build_append_byte(var->buf, irq_count); /* Interrupt table length */
+
+ /* Interrupt Number List */
+ for (i = 0; i < irq_count; i++) {
+ build_append_int_noprefix(var->buf, irq_list[i], 4);
+ }
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.2.5 I/O Port Descriptor */
+Aml *aml_io(AmlIODecode dec, uint16_t min_base, uint16_t max_base,
+ uint8_t aln, uint8_t len)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x47); /* IO port descriptor */
+ build_append_byte(var->buf, dec);
+ build_append_byte(var->buf, min_base & 0xff);
+ build_append_byte(var->buf, (min_base >> 8) & 0xff);
+ build_append_byte(var->buf, max_base & 0xff);
+ build_append_byte(var->buf, (max_base >> 8) & 0xff);
+ build_append_byte(var->buf, aln);
+ build_append_byte(var->buf, len);
+ return var;
+}
+
+/*
+ * ACPI 1.0b: 6.4.2.1.1 ASL Macro for IRQ Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.64 IRQNoFlags (Interrupt Resource Descriptor Macro)
+ * 6.4.2.1 IRQ Descriptor
+ */
+Aml *aml_irq_no_flags(uint8_t irq)
+{
+ uint16_t irq_mask;
+ Aml *var = aml_alloc();
+
+ assert(irq < 16);
+ build_append_byte(var->buf, 0x22); /* IRQ descriptor 2 byte form */
+
+ irq_mask = 1U << irq;
+ build_append_byte(var->buf, irq_mask & 0xFF); /* IRQ mask bits[7:0] */
+ build_append_byte(var->buf, irq_mask >> 8); /* IRQ mask bits[15:8] */
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLNot */
+Aml *aml_lnot(Aml *arg)
+{
+ Aml *var = aml_opcode(0x92 /* LNotOp */);
+ aml_append(var, arg);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLEqual */
+Aml *aml_equal(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x93 /* LequalOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLGreater */
+Aml *aml_lgreater(Aml *arg1, Aml *arg2)
+{
+ Aml *var = aml_opcode(0x94 /* LGreaterOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefLGreaterEqual */
+Aml *aml_lgreater_equal(Aml *arg1, Aml *arg2)
+{
+ /* LGreaterEqualOp := LNotOp LLessOp */
+ Aml *var = aml_opcode(0x92 /* LNotOp */);
+ build_append_byte(var->buf, 0x95 /* LLessOp */);
+ aml_append(var, arg1);
+ aml_append(var, arg2);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefIfElse */
+Aml *aml_if(Aml *predicate)
+{
+ Aml *var = aml_bundle(0xA0 /* IfOp */, AML_PACKAGE);
+ aml_append(var, predicate);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefElse */
+Aml *aml_else(void)
+{
+ Aml *var = aml_bundle(0xA1 /* ElseOp */, AML_PACKAGE);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefWhile */
+Aml *aml_while(Aml *predicate)
+{
+ Aml *var = aml_bundle(0xA2 /* WhileOp */, AML_PACKAGE);
+ aml_append(var, predicate);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefMethod */
+Aml *aml_method(const char *name, int arg_count, AmlSerializeFlag sflag)
+{
+ Aml *var = aml_bundle(0x14 /* MethodOp */, AML_PACKAGE);
+ int methodflags;
+
+ /*
+ * MethodFlags:
+ * bit 0-2: ArgCount (0-7)
+ * bit 3: SerializeFlag
+ * 0: NotSerialized
+ * 1: Serialized
+ * bit 4-7: reserved (must be 0)
+ */
+ assert(arg_count < 8);
+ methodflags = arg_count | (sflag << 3);
+
+ build_append_namestring(var->buf, "%s", name);
+ build_append_byte(var->buf, methodflags); /* MethodFlags: ArgCount */
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefDevice */
+Aml *aml_device(const char *name_format, ...)
+{
+ va_list ap;
+ Aml *var = aml_bundle(0x82 /* DeviceOp */, AML_EXT_PACKAGE);
+ va_start(ap, name_format);
+ build_append_namestringv(var->buf, name_format, ap);
+ va_end(ap);
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.1 ASL Macros for Resource Descriptors */
+Aml *aml_resource_template(void)
+{
+ /* ResourceTemplate is a buffer of Resources with EndTag at the end */
+ Aml *var = aml_bundle(0x11 /* BufferOp */, AML_RES_TEMPLATE);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefBuffer
+ * Pass byte_list as NULL to request uninitialized buffer to reserve space.
+ */
+Aml *aml_buffer(int buffer_size, uint8_t *byte_list)
+{
+ int i;
+ Aml *var = aml_bundle(0x11 /* BufferOp */, AML_BUFFER);
+
+ for (i = 0; i < buffer_size; i++) {
+ if (byte_list == NULL) {
+ build_append_byte(var->buf, 0x0);
+ } else {
+ build_append_byte(var->buf, byte_list[i]);
+ }
+ }
+
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefPackage */
+Aml *aml_package(uint8_t num_elements)
+{
+ Aml *var = aml_bundle(0x12 /* PackageOp */, AML_PACKAGE);
+ build_append_byte(var->buf, num_elements);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefOpRegion */
+Aml *aml_operation_region(const char *name, AmlRegionSpace rs,
+ Aml *offset, uint32_t len)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x5B); /* ExtOpPrefix */
+ build_append_byte(var->buf, 0x80); /* OpRegionOp */
+ build_append_namestring(var->buf, "%s", name);
+ build_append_byte(var->buf, rs);
+ aml_append(var, offset);
+ build_append_int(var->buf, len);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: NamedField */
+Aml *aml_named_field(const char *name, unsigned length)
+{
+ Aml *var = aml_alloc();
+ build_append_nameseg(var->buf, name);
+ build_append_pkg_length(var->buf, length, false);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: ReservedField */
+Aml *aml_reserved_field(unsigned length)
+{
+ Aml *var = aml_alloc();
+ /* ReservedField := 0x00 PkgLength */
+ build_append_byte(var->buf, 0x00);
+ build_append_pkg_length(var->buf, length, false);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefField */
+Aml *aml_field(const char *name, AmlAccessType type, AmlLockRule lock,
+ AmlUpdateRule rule)
+{
+ Aml *var = aml_bundle(0x81 /* FieldOp */, AML_EXT_PACKAGE);
+ uint8_t flags = rule << 5 | type;
+
+ flags |= lock << 4; /* LockRule at 4 bit offset */
+
+ build_append_namestring(var->buf, "%s", name);
+ build_append_byte(var->buf, flags);
+ return var;
+}
+
+static
+Aml *create_field_common(int opcode, Aml *srcbuf, Aml *index, const char *name)
+{
+ Aml *var = aml_opcode(opcode);
+ aml_append(var, srcbuf);
+ aml_append(var, index);
+ build_append_namestring(var->buf, "%s", name);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefCreateField */
+Aml *aml_create_field(Aml *srcbuf, Aml *bit_index, Aml *num_bits,
+ const char *name)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x5B); /* ExtOpPrefix */
+ build_append_byte(var->buf, 0x13); /* CreateFieldOp */
+ aml_append(var, srcbuf);
+ aml_append(var, bit_index);
+ aml_append(var, num_bits);
+ build_append_namestring(var->buf, "%s", name);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefCreateDWordField */
+Aml *aml_create_dword_field(Aml *srcbuf, Aml *index, const char *name)
+{
+ return create_field_common(0x8A /* CreateDWordFieldOp */,
+ srcbuf, index, name);
+}
+
+/* ACPI 2.0a: 17.2.4.2 Named Objects Encoding: DefCreateQWordField */
+Aml *aml_create_qword_field(Aml *srcbuf, Aml *index, const char *name)
+{
+ return create_field_common(0x8F /* CreateQWordFieldOp */,
+ srcbuf, index, name);
+}
+
+/* ACPI 1.0b: 16.2.3 Data Objects Encoding: String */
+Aml *aml_string(const char *name_format, ...)
+{
+ Aml *var = aml_opcode(0x0D /* StringPrefix */);
+ va_list ap;
+ char *s;
+ int len;
+
+ va_start(ap, name_format);
+ len = g_vasprintf(&s, name_format, ap);
+ va_end(ap);
+
+ g_array_append_vals(var->buf, s, len + 1);
+ g_free(s);
+
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.6.2 Local Objects Encoding */
+Aml *aml_local(int num)
+{
+ uint8_t op = 0x60 /* Local0Op */ + num;
+
+ assert(num <= 7);
+ return aml_opcode(op);
+}
+
+/* ACPI 2.0a: 17.2.2 Data Objects Encoding: DefVarPackage */
+Aml *aml_varpackage(uint32_t num_elements)
+{
+ Aml *var = aml_bundle(0x13 /* VarPackageOp */, AML_PACKAGE);
+ build_append_int(var->buf, num_elements);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefProcessor */
+Aml *aml_processor(uint8_t proc_id, uint32_t pblk_addr, uint8_t pblk_len,
+ const char *name_format, ...)
+{
+ va_list ap;
+ Aml *var = aml_bundle(0x83 /* ProcessorOp */, AML_EXT_PACKAGE);
+ va_start(ap, name_format);
+ build_append_namestringv(var->buf, name_format, ap);
+ va_end(ap);
+ build_append_byte(var->buf, proc_id); /* ProcID */
+ build_append_int_noprefix(var->buf, pblk_addr, sizeof(pblk_addr));
+ build_append_byte(var->buf, pblk_len); /* PblkLen */
+ return var;
+}
+
+static uint8_t Hex2Digit(char c)
+{
+ if (c >= 'A') {
+ return c - 'A' + 10;
+ }
+
+ return c - '0';
+}
+
+/* ACPI 1.0b: 15.2.3.6.4.1 EISAID Macro - Convert EISA ID String To Integer */
+Aml *aml_eisaid(const char *str)
+{
+ Aml *var = aml_alloc();
+ uint32_t id;
+
+ g_assert(strlen(str) == 7);
+ id = (str[0] - 0x40) << 26 |
+ (str[1] - 0x40) << 21 |
+ (str[2] - 0x40) << 16 |
+ Hex2Digit(str[3]) << 12 |
+ Hex2Digit(str[4]) << 8 |
+ Hex2Digit(str[5]) << 4 |
+ Hex2Digit(str[6]);
+
+ build_append_byte(var->buf, 0x0C); /* DWordPrefix */
+ build_append_int_noprefix(var->buf, bswap32(id), sizeof(id));
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.3.5.5 Word Address Space Descriptor: bytes 3-5 */
+static Aml *aml_as_desc_header(AmlResourceType type, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlDecode dec,
+ uint8_t type_flags)
+{
+ uint8_t flags = max_fixed | min_fixed | dec;
+ Aml *var = aml_alloc();
+
+ build_append_byte(var->buf, type);
+ build_append_byte(var->buf, flags);
+ build_append_byte(var->buf, type_flags); /* Type Specific Flags */
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.3.5.5 Word Address Space Descriptor */
+static Aml *aml_word_as_desc(AmlResourceType type, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlDecode dec,
+ uint16_t addr_gran, uint16_t addr_min,
+ uint16_t addr_max, uint16_t addr_trans,
+ uint16_t len, uint8_t type_flags)
+{
+ Aml *var = aml_alloc();
+
+ build_append_byte(var->buf, 0x88); /* Word Address Space Descriptor */
+ /* minimum length since we do not encode optional fields */
+ build_append_byte(var->buf, 0x0D);
+ build_append_byte(var->buf, 0x0);
+
+ aml_append(var,
+ aml_as_desc_header(type, min_fixed, max_fixed, dec, type_flags));
+ build_append_int_noprefix(var->buf, addr_gran, sizeof(addr_gran));
+ build_append_int_noprefix(var->buf, addr_min, sizeof(addr_min));
+ build_append_int_noprefix(var->buf, addr_max, sizeof(addr_max));
+ build_append_int_noprefix(var->buf, addr_trans, sizeof(addr_trans));
+ build_append_int_noprefix(var->buf, len, sizeof(len));
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.3.5.3 DWord Address Space Descriptor */
+static Aml *aml_dword_as_desc(AmlResourceType type, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlDecode dec,
+ uint32_t addr_gran, uint32_t addr_min,
+ uint32_t addr_max, uint32_t addr_trans,
+ uint32_t len, uint8_t type_flags)
+{
+ Aml *var = aml_alloc();
+
+ build_append_byte(var->buf, 0x87); /* DWord Address Space Descriptor */
+ /* minimum length since we do not encode optional fields */
+ build_append_byte(var->buf, 23);
+ build_append_byte(var->buf, 0x0);
+
+
+ aml_append(var,
+ aml_as_desc_header(type, min_fixed, max_fixed, dec, type_flags));
+ build_append_int_noprefix(var->buf, addr_gran, sizeof(addr_gran));
+ build_append_int_noprefix(var->buf, addr_min, sizeof(addr_min));
+ build_append_int_noprefix(var->buf, addr_max, sizeof(addr_max));
+ build_append_int_noprefix(var->buf, addr_trans, sizeof(addr_trans));
+ build_append_int_noprefix(var->buf, len, sizeof(len));
+ return var;
+}
+
+/* ACPI 1.0b: 6.4.3.5.1 QWord Address Space Descriptor */
+static Aml *aml_qword_as_desc(AmlResourceType type, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlDecode dec,
+ uint64_t addr_gran, uint64_t addr_min,
+ uint64_t addr_max, uint64_t addr_trans,
+ uint64_t len, uint8_t type_flags)
+{
+ Aml *var = aml_alloc();
+
+ build_append_byte(var->buf, 0x8A); /* QWord Address Space Descriptor */
+ /* minimum length since we do not encode optional fields */
+ build_append_byte(var->buf, 0x2B);
+ build_append_byte(var->buf, 0x0);
+
+ aml_append(var,
+ aml_as_desc_header(type, min_fixed, max_fixed, dec, type_flags));
+ build_append_int_noprefix(var->buf, addr_gran, sizeof(addr_gran));
+ build_append_int_noprefix(var->buf, addr_min, sizeof(addr_min));
+ build_append_int_noprefix(var->buf, addr_max, sizeof(addr_max));
+ build_append_int_noprefix(var->buf, addr_trans, sizeof(addr_trans));
+ build_append_int_noprefix(var->buf, len, sizeof(len));
+ return var;
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.6 ASL Macros for WORD Address Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.141 WordBusNumber (Word Bus Number Resource Descriptor Macro)
+ */
+Aml *aml_word_bus_number(AmlMinFixed min_fixed, AmlMaxFixed max_fixed,
+ AmlDecode dec, uint16_t addr_gran,
+ uint16_t addr_min, uint16_t addr_max,
+ uint16_t addr_trans, uint16_t len)
+
+{
+ return aml_word_as_desc(AML_BUS_NUMBER_RANGE, min_fixed, max_fixed, dec,
+ addr_gran, addr_min, addr_max, addr_trans, len, 0);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.6 ASL Macros for WORD Address Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.142 WordIO (Word IO Resource Descriptor Macro)
+ */
+Aml *aml_word_io(AmlMinFixed min_fixed, AmlMaxFixed max_fixed,
+ AmlDecode dec, AmlISARanges isa_ranges,
+ uint16_t addr_gran, uint16_t addr_min,
+ uint16_t addr_max, uint16_t addr_trans,
+ uint16_t len)
+
+{
+ return aml_word_as_desc(AML_IO_RANGE, min_fixed, max_fixed, dec,
+ addr_gran, addr_min, addr_max, addr_trans, len,
+ isa_ranges);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.4 ASL Macros for DWORD Address Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.33 DWordIO (DWord IO Resource Descriptor Macro)
+ */
+Aml *aml_dword_io(AmlMinFixed min_fixed, AmlMaxFixed max_fixed,
+ AmlDecode dec, AmlISARanges isa_ranges,
+ uint32_t addr_gran, uint32_t addr_min,
+ uint32_t addr_max, uint32_t addr_trans,
+ uint32_t len)
+
+{
+ return aml_dword_as_desc(AML_IO_RANGE, min_fixed, max_fixed, dec,
+ addr_gran, addr_min, addr_max, addr_trans, len,
+ isa_ranges);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.4 ASL Macros for DWORD Address Space Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.34 DWordMemory (DWord Memory Resource Descriptor Macro)
+ */
+Aml *aml_dword_memory(AmlDecode dec, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlCacheable cacheable,
+ AmlReadAndWrite read_and_write,
+ uint32_t addr_gran, uint32_t addr_min,
+ uint32_t addr_max, uint32_t addr_trans,
+ uint32_t len)
+{
+ uint8_t flags = read_and_write | (cacheable << 1);
+
+ return aml_dword_as_desc(AML_MEMORY_RANGE, min_fixed, max_fixed,
+ dec, addr_gran, addr_min, addr_max,
+ addr_trans, len, flags);
+}
+
+/*
+ * ACPI 1.0b: 6.4.3.5.2 ASL Macros for QWORD Address Space Descriptor
+ *
+ * More verbose description at:
+ * ACPI 5.0: 19.5.102 QWordMemory (QWord Memory Resource Descriptor Macro)
+ */
+Aml *aml_qword_memory(AmlDecode dec, AmlMinFixed min_fixed,
+ AmlMaxFixed max_fixed, AmlCacheable cacheable,
+ AmlReadAndWrite read_and_write,
+ uint64_t addr_gran, uint64_t addr_min,
+ uint64_t addr_max, uint64_t addr_trans,
+ uint64_t len)
+{
+ uint8_t flags = read_and_write | (cacheable << 1);
+
+ return aml_qword_as_desc(AML_MEMORY_RANGE, min_fixed, max_fixed,
+ dec, addr_gran, addr_min, addr_max,
+ addr_trans, len, flags);
+}
+
+/* ACPI 1.0b: 6.4.2.2 DMA Format/6.4.2.2.1 ASL Macro for DMA Descriptor */
+Aml *aml_dma(AmlDmaType typ, AmlDmaBusMaster bm, AmlTransferSize sz,
+ uint8_t channel)
+{
+ Aml *var = aml_alloc();
+ uint8_t flags = sz | bm << 2 | typ << 5;
+
+ assert(channel < 8);
+ build_append_byte(var->buf, 0x2A); /* Byte 0: DMA Descriptor */
+ build_append_byte(var->buf, 1U << channel); /* Byte 1: _DMA - DmaChannel */
+ build_append_byte(var->buf, flags); /* Byte 2 */
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefSleep */
+Aml *aml_sleep(uint64_t msec)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x5B); /* ExtOpPrefix */
+ build_append_byte(var->buf, 0x22); /* SleepOp */
+ aml_append(var, aml_int(msec));
+ return var;
+}
+
+static uint8_t Hex2Byte(const char *src)
+{
+ int hi, lo;
+
+ hi = Hex2Digit(src[0]);
+ assert(hi >= 0);
+ assert(hi <= 15);
+
+ lo = Hex2Digit(src[1]);
+ assert(lo >= 0);
+ assert(lo <= 15);
+ return (hi << 4) | lo;
+}
+
+/*
+ * ACPI 3.0: 17.5.124 ToUUID (Convert String to UUID Macro)
+ * e.g. UUID: aabbccdd-eeff-gghh-iijj-kkllmmnnoopp
+ * call aml_touuid("aabbccdd-eeff-gghh-iijj-kkllmmnnoopp");
+ */
+Aml *aml_touuid(const char *uuid)
+{
+ Aml *var = aml_bundle(0x11 /* BufferOp */, AML_BUFFER);
+
+ assert(strlen(uuid) == 36);
+ assert(uuid[8] == '-');
+ assert(uuid[13] == '-');
+ assert(uuid[18] == '-');
+ assert(uuid[23] == '-');
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 6)); /* dd - at offset 00 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 4)); /* cc - at offset 01 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 2)); /* bb - at offset 02 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 0)); /* aa - at offset 03 */
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 11)); /* ff - at offset 04 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 9)); /* ee - at offset 05 */
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 16)); /* hh - at offset 06 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 14)); /* gg - at offset 07 */
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 19)); /* ii - at offset 08 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 21)); /* jj - at offset 09 */
+
+ build_append_byte(var->buf, Hex2Byte(uuid + 24)); /* kk - at offset 10 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 26)); /* ll - at offset 11 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 28)); /* mm - at offset 12 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 30)); /* nn - at offset 13 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 32)); /* oo - at offset 14 */
+ build_append_byte(var->buf, Hex2Byte(uuid + 34)); /* pp - at offset 15 */
+
+ return var;
+}
+
+/*
+ * ACPI 2.0b: 16.2.3.6.4.3 Unicode Macro (Convert Ascii String To Unicode)
+ */
+Aml *aml_unicode(const char *str)
+{
+ int i = 0;
+ Aml *var = aml_bundle(0x11 /* BufferOp */, AML_BUFFER);
+
+ do {
+ build_append_byte(var->buf, str[i]);
+ build_append_byte(var->buf, 0);
+ i++;
+ } while (i <= strlen(str));
+
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefRefOf */
+Aml *aml_refof(Aml *arg)
+{
+ Aml *var = aml_opcode(0x71 /* RefOfOp */);
+ aml_append(var, arg);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefDerefOf */
+Aml *aml_derefof(Aml *arg)
+{
+ Aml *var = aml_opcode(0x83 /* DerefOfOp */);
+ aml_append(var, arg);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefSizeOf */
+Aml *aml_sizeof(Aml *arg)
+{
+ Aml *var = aml_opcode(0x87 /* SizeOfOp */);
+ aml_append(var, arg);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.2 Named Objects Encoding: DefMutex */
+Aml *aml_mutex(const char *name, uint8_t sync_level)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x5B); /* ExtOpPrefix */
+ build_append_byte(var->buf, 0x01); /* MutexOp */
+ build_append_namestring(var->buf, "%s", name);
+ assert(!(sync_level & 0xF0));
+ build_append_byte(var->buf, sync_level);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefAcquire */
+Aml *aml_acquire(Aml *mutex, uint16_t timeout)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x5B); /* ExtOpPrefix */
+ build_append_byte(var->buf, 0x23); /* AcquireOp */
+ aml_append(var, mutex);
+ build_append_int_noprefix(var->buf, timeout, sizeof(timeout));
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.3 Type 1 Opcodes Encoding: DefRelease */
+Aml *aml_release(Aml *mutex)
+{
+ Aml *var = aml_alloc();
+ build_append_byte(var->buf, 0x5B); /* ExtOpPrefix */
+ build_append_byte(var->buf, 0x27); /* ReleaseOp */
+ aml_append(var, mutex);
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.1 Name Space Modifier Objects Encoding: DefAlias */
+Aml *aml_alias(const char *source_object, const char *alias_object)
+{
+ Aml *var = aml_opcode(0x06 /* AliasOp */);
+ aml_append(var, aml_name("%s", source_object));
+ aml_append(var, aml_name("%s", alias_object));
+ return var;
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefConcat */
+Aml *aml_concatenate(Aml *source1, Aml *source2, Aml *target)
+{
+ return build_opcode_2arg_dst(0x73 /* ConcatOp */, source1, source2,
+ target);
+}
+
+/* ACPI 1.0b: 16.2.5.4 Type 2 Opcodes Encoding: DefObjectType */
+Aml *aml_object_type(Aml *object)
+{
+ Aml *var = aml_opcode(0x8E /* ObjectTypeOp */);
+ aml_append(var, object);
+ return var;
+}
+
+void acpi_table_begin(AcpiTable *desc, GArray *array)
+{
+
+ desc->array = array;
+ desc->table_offset = array->len;
+
+ /*
+ * ACPI spec 1.0b
+ * 5.2.3 System Description Table Header
+ */
+ g_assert(strlen(desc->sig) == 4);
+ g_array_append_vals(array, desc->sig, 4); /* Signature */
+ /*
+ * reserve space for Length field, which will be patched by
+ * acpi_table_end() when the table creation is finished.
+ */
+ build_append_int_noprefix(array, 0, 4); /* Length */
+ build_append_int_noprefix(array, desc->rev, 1); /* Revision */
+ build_append_int_noprefix(array, 0, 1); /* Checksum */
+ build_append_padded_str(array, desc->oem_id, 6, ' '); /* OEMID */
+ /* OEM Table ID */
+ build_append_padded_str(array, desc->oem_table_id, 8, ' ');
+ build_append_int_noprefix(array, 1, 4); /* OEM Revision */
+ g_array_append_vals(array, ACPI_BUILD_APPNAME8, 4); /* Creator ID */
+ build_append_int_noprefix(array, 1, 4); /* Creator Revision */
+}
+
+void acpi_table_end(BIOSLinker *linker, AcpiTable *desc)
+{
+ /*
+ * ACPI spec 1.0b
+ * 5.2.3 System Description Table Header
+ * Table 5-2 DESCRIPTION_HEADER Fields
+ */
+ const unsigned checksum_offset = 9;
+ uint32_t table_len = desc->array->len - desc->table_offset;
+ uint32_t table_len_le = cpu_to_le32(table_len);
+ gchar *len_ptr = &desc->array->data[desc->table_offset + 4];
+
+ /* patch "Length" field that has been reserved by acpi_table_begin()
+ * to the actual length, i.e. accumulated table length from
+ * acpi_table_begin() till acpi_table_end()
+ */
+ memcpy(len_ptr, &table_len_le, sizeof table_len_le);
+
+ bios_linker_loader_add_checksum(linker, ACPI_BUILD_TABLE_FILE,
+ desc->table_offset, table_len, desc->table_offset + checksum_offset);
+}
+
+void *acpi_data_push(GArray *table_data, unsigned size)
+{
+ unsigned off = table_data->len;
+ g_array_set_size(table_data, off + size);
+ return table_data->data + off;
+}
+
+unsigned acpi_data_len(GArray *table)
+{
+ assert(g_array_get_element_size(table) == 1);
+ return table->len;
+}
+
+void acpi_add_table(GArray *table_offsets, GArray *table_data)
+{
+ uint32_t offset = table_data->len;
+ g_array_append_val(table_offsets, offset);
+}
+
+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->hardware_errors = g_array_new(false, true /* clear */, 1);
+ tables->linker = bios_linker_loader_init();
+}
+
+void acpi_build_tables_cleanup(AcpiBuildTables *tables, bool mfre)
+{
+ bios_linker_loader_cleanup(tables->linker);
+ 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);
+ g_array_free(tables->hardware_errors, mfre);
+}
+
+/*
+ * ACPI spec 5.2.5.3 Root System Description Pointer (RSDP).
+ * (Revision 1.0 or later)
+ */
+void
+build_rsdp(GArray *tbl, BIOSLinker *linker, AcpiRsdpData *rsdp_data)
+{
+ int tbl_off = tbl->len; /* Table offset in the RSDP file */
+
+ switch (rsdp_data->revision) {
+ case 0:
+ /* With ACPI 1.0, we must have an RSDT pointer */
+ g_assert(rsdp_data->rsdt_tbl_offset);
+ break;
+ case 2:
+ /* With ACPI 2.0+, we must have an XSDT pointer */
+ g_assert(rsdp_data->xsdt_tbl_offset);
+ break;
+ default:
+ /* Only revisions 0 (ACPI 1.0) and 2 (ACPI 2.0+) are valid for RSDP */
+ g_assert_not_reached();
+ }
+
+ bios_linker_loader_alloc(linker, ACPI_BUILD_RSDP_FILE, tbl, 16,
+ true /* fseg memory */);
+
+ g_array_append_vals(tbl, "RSD PTR ", 8); /* Signature */
+ build_append_int_noprefix(tbl, 0, 1); /* Checksum */
+ g_array_append_vals(tbl, rsdp_data->oem_id, 6); /* OEMID */
+ build_append_int_noprefix(tbl, rsdp_data->revision, 1); /* Revision */
+ build_append_int_noprefix(tbl, 0, 4); /* RsdtAddress */
+ if (rsdp_data->rsdt_tbl_offset) {
+ /* RSDT address to be filled by guest linker */
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_RSDP_FILE,
+ tbl_off + 16, 4,
+ ACPI_BUILD_TABLE_FILE,
+ *rsdp_data->rsdt_tbl_offset);
+ }
+
+ /* Checksum to be filled by guest linker */
+ bios_linker_loader_add_checksum(linker, ACPI_BUILD_RSDP_FILE,
+ tbl_off, 20, /* ACPI rev 1.0 RSDP size */
+ 8);
+
+ if (rsdp_data->revision == 0) {
+ /* ACPI 1.0 RSDP, we're done */
+ return;
+ }
+
+ build_append_int_noprefix(tbl, 36, 4); /* Length */
+
+ /* XSDT address to be filled by guest linker */
+ build_append_int_noprefix(tbl, 0, 8); /* XsdtAddress */
+ /* We already validated our xsdt pointer */
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_RSDP_FILE,
+ tbl_off + 24, 8,
+ ACPI_BUILD_TABLE_FILE,
+ *rsdp_data->xsdt_tbl_offset);
+
+ build_append_int_noprefix(tbl, 0, 1); /* Extended Checksum */
+ build_append_int_noprefix(tbl, 0, 3); /* Reserved */
+
+ /* Extended checksum to be filled by Guest linker */
+ bios_linker_loader_add_checksum(linker, ACPI_BUILD_RSDP_FILE,
+ tbl_off, 36, /* ACPI rev 2.0 RSDP size */
+ 32);
+}
+
+/*
+ * ACPI 1.0 Root System Description Table (RSDT)
+ */
+void
+build_rsdt(GArray *table_data, BIOSLinker *linker, GArray *table_offsets,
+ const char *oem_id, const char *oem_table_id)
+{
+ int i;
+ AcpiTable table = { .sig = "RSDT", .rev = 1,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, table_data);
+ for (i = 0; i < table_offsets->len; ++i) {
+ uint32_t ref_tbl_offset = g_array_index(table_offsets, uint32_t, i);
+ uint32_t rsdt_entry_offset = table.array->len;
+
+ /* reserve space for entry */
+ build_append_int_noprefix(table.array, 0, 4);
+
+ /* mark position of RSDT entry to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker,
+ ACPI_BUILD_TABLE_FILE, rsdt_entry_offset, 4,
+ ACPI_BUILD_TABLE_FILE, ref_tbl_offset);
+
+ }
+ acpi_table_end(linker, &table);
+}
+
+/*
+ * ACPI 2.0 eXtended System Description Table (XSDT)
+ */
+void
+build_xsdt(GArray *table_data, BIOSLinker *linker, GArray *table_offsets,
+ const char *oem_id, const char *oem_table_id)
+{
+ int i;
+ AcpiTable table = { .sig = "XSDT", .rev = 1,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, table_data);
+
+ for (i = 0; i < table_offsets->len; ++i) {
+ uint64_t ref_tbl_offset = g_array_index(table_offsets, uint32_t, i);
+ uint64_t xsdt_entry_offset = table.array->len;
+
+ /* reserve space for entry */
+ build_append_int_noprefix(table.array, 0, 8);
+
+ /* mark position of RSDT entry to be filled by Guest linker */
+ bios_linker_loader_add_pointer(linker,
+ ACPI_BUILD_TABLE_FILE, xsdt_entry_offset, 8,
+ ACPI_BUILD_TABLE_FILE, ref_tbl_offset);
+ }
+ acpi_table_end(linker, &table);
+}
+
+/*
+ * ACPI spec, Revision 4.0
+ * 5.2.16.2 Memory Affinity Structure
+ */
+void build_srat_memory(GArray *table_data, uint64_t base,
+ uint64_t len, int node, MemoryAffinityFlags flags)
+{
+ build_append_int_noprefix(table_data, 1, 1); /* Type */
+ build_append_int_noprefix(table_data, 40, 1); /* Length */
+ build_append_int_noprefix(table_data, node, 4); /* Proximity Domain */
+ build_append_int_noprefix(table_data, 0, 2); /* Reserved */
+ build_append_int_noprefix(table_data, base, 4); /* Base Address Low */
+ /* Base Address High */
+ build_append_int_noprefix(table_data, base >> 32, 4);
+ build_append_int_noprefix(table_data, len, 4); /* Length Low */
+ build_append_int_noprefix(table_data, len >> 32, 4); /* Length High */
+ build_append_int_noprefix(table_data, 0, 4); /* Reserved */
+ build_append_int_noprefix(table_data, flags, 4); /* Flags */
+ build_append_int_noprefix(table_data, 0, 8); /* Reserved */
+}
+
+/*
+ * ACPI spec 5.2.17 System Locality Distance Information Table
+ * (Revision 2.0 or later)
+ */
+void build_slit(GArray *table_data, BIOSLinker *linker, MachineState *ms,
+ const char *oem_id, const char *oem_table_id)
+{
+ int i, j;
+ int nb_numa_nodes = ms->numa_state->num_nodes;
+ AcpiTable table = { .sig = "SLIT", .rev = 1,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, table_data);
+
+ build_append_int_noprefix(table_data, nb_numa_nodes, 8);
+ for (i = 0; i < nb_numa_nodes; i++) {
+ for (j = 0; j < nb_numa_nodes; j++) {
+ assert(ms->numa_state->nodes[i].distance[j]);
+ build_append_int_noprefix(table_data,
+ ms->numa_state->nodes[i].distance[j],
+ 1);
+ }
+ }
+ acpi_table_end(linker, &table);
+}
+
+/*
+ * ACPI spec, Revision 6.3
+ * 5.2.29.1 Processor hierarchy node structure (Type 0)
+ */
+static void build_processor_hierarchy_node(GArray *tbl, uint32_t flags,
+ uint32_t parent, uint32_t id,
+ uint32_t *priv_rsrc,
+ uint32_t priv_num)
+{
+ int i;
+
+ build_append_byte(tbl, 0); /* Type 0 - processor */
+ build_append_byte(tbl, 20 + priv_num * 4); /* Length */
+ build_append_int_noprefix(tbl, 0, 2); /* Reserved */
+ build_append_int_noprefix(tbl, flags, 4); /* Flags */
+ build_append_int_noprefix(tbl, parent, 4); /* Parent */
+ build_append_int_noprefix(tbl, id, 4); /* ACPI Processor ID */
+
+ /* Number of private resources */
+ build_append_int_noprefix(tbl, priv_num, 4);
+
+ /* Private resources[N] */
+ if (priv_num > 0) {
+ assert(priv_rsrc);
+ for (i = 0; i < priv_num; i++) {
+ build_append_int_noprefix(tbl, priv_rsrc[i], 4);
+ }
+ }
+}
+
+/*
+ * ACPI spec, Revision 6.3
+ * 5.2.29 Processor Properties Topology Table (PPTT)
+ */
+void build_pptt(GArray *table_data, BIOSLinker *linker, MachineState *ms,
+ const char *oem_id, const char *oem_table_id)
+{
+ int pptt_start = table_data->len;
+ int uid = 0;
+ int socket;
+ AcpiTable table = { .sig = "PPTT", .rev = 2,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, table_data);
+
+ for (socket = 0; socket < ms->smp.sockets; socket++) {
+ uint32_t socket_offset = table_data->len - pptt_start;
+ int core;
+
+ build_processor_hierarchy_node(
+ table_data,
+ /*
+ * Physical package - represents the boundary
+ * of a physical package
+ */
+ (1 << 0),
+ 0, socket, NULL, 0);
+
+ for (core = 0; core < ms->smp.cores; core++) {
+ uint32_t core_offset = table_data->len - pptt_start;
+ int thread;
+
+ if (ms->smp.threads > 1) {
+ build_processor_hierarchy_node(
+ table_data,
+ (0 << 0), /* not a physical package */
+ socket_offset, core, NULL, 0);
+
+ for (thread = 0; thread < ms->smp.threads; thread++) {
+ build_processor_hierarchy_node(
+ table_data,
+ (1 << 1) | /* ACPI Processor ID valid */
+ (1 << 2) | /* Processor is a Thread */
+ (1 << 3), /* Node is a Leaf */
+ core_offset, uid++, NULL, 0);
+ }
+ } else {
+ build_processor_hierarchy_node(
+ table_data,
+ (1 << 1) | /* ACPI Processor ID valid */
+ (1 << 3), /* Node is a Leaf */
+ socket_offset, uid++, NULL, 0);
+ }
+ }
+ }
+
+ acpi_table_end(linker, &table);
+}
+
+/* build rev1/rev3/rev5.1 FADT */
+void build_fadt(GArray *tbl, BIOSLinker *linker, const AcpiFadtData *f,
+ const char *oem_id, const char *oem_table_id)
+{
+ int off;
+ AcpiTable table = { .sig = "FACP", .rev = f->rev,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, tbl);
+
+ /* FACS address to be filled by Guest linker at runtime */
+ off = tbl->len;
+ build_append_int_noprefix(tbl, 0, 4); /* FIRMWARE_CTRL */
+ if (f->facs_tbl_offset) { /* don't patch if not supported by platform */
+ bios_linker_loader_add_pointer(linker,
+ ACPI_BUILD_TABLE_FILE, off, 4,
+ ACPI_BUILD_TABLE_FILE, *f->facs_tbl_offset);
+ }
+
+ /* DSDT address to be filled by Guest linker at runtime */
+ off = tbl->len;
+ build_append_int_noprefix(tbl, 0, 4); /* DSDT */
+ if (f->dsdt_tbl_offset) { /* don't patch if not supported by platform */
+ bios_linker_loader_add_pointer(linker,
+ ACPI_BUILD_TABLE_FILE, off, 4,
+ ACPI_BUILD_TABLE_FILE, *f->dsdt_tbl_offset);
+ }
+
+ /* ACPI1.0: INT_MODEL, ACPI2.0+: Reserved */
+ build_append_int_noprefix(tbl, f->int_model /* Multiple APIC */, 1);
+ /* Preferred_PM_Profile */
+ build_append_int_noprefix(tbl, 0 /* Unspecified */, 1);
+ build_append_int_noprefix(tbl, f->sci_int, 2); /* SCI_INT */
+ build_append_int_noprefix(tbl, f->smi_cmd, 4); /* SMI_CMD */
+ build_append_int_noprefix(tbl, f->acpi_enable_cmd, 1); /* ACPI_ENABLE */
+ build_append_int_noprefix(tbl, f->acpi_disable_cmd, 1); /* ACPI_DISABLE */
+ build_append_int_noprefix(tbl, 0 /* not supported */, 1); /* S4BIOS_REQ */
+ /* ACPI1.0: Reserved, ACPI2.0+: PSTATE_CNT */
+ build_append_int_noprefix(tbl, 0, 1);
+ build_append_int_noprefix(tbl, f->pm1a_evt.address, 4); /* PM1a_EVT_BLK */
+ build_append_int_noprefix(tbl, 0, 4); /* PM1b_EVT_BLK */
+ build_append_int_noprefix(tbl, f->pm1a_cnt.address, 4); /* PM1a_CNT_BLK */
+ build_append_int_noprefix(tbl, 0, 4); /* PM1b_CNT_BLK */
+ build_append_int_noprefix(tbl, 0, 4); /* PM2_CNT_BLK */
+ build_append_int_noprefix(tbl, f->pm_tmr.address, 4); /* PM_TMR_BLK */
+ build_append_int_noprefix(tbl, f->gpe0_blk.address, 4); /* GPE0_BLK */
+ build_append_int_noprefix(tbl, 0, 4); /* GPE1_BLK */
+ /* PM1_EVT_LEN */
+ build_append_int_noprefix(tbl, f->pm1a_evt.bit_width / 8, 1);
+ /* PM1_CNT_LEN */
+ build_append_int_noprefix(tbl, f->pm1a_cnt.bit_width / 8, 1);
+ build_append_int_noprefix(tbl, 0, 1); /* PM2_CNT_LEN */
+ build_append_int_noprefix(tbl, f->pm_tmr.bit_width / 8, 1); /* PM_TMR_LEN */
+ /* GPE0_BLK_LEN */
+ build_append_int_noprefix(tbl, f->gpe0_blk.bit_width / 8, 1);
+ build_append_int_noprefix(tbl, 0, 1); /* GPE1_BLK_LEN */
+ build_append_int_noprefix(tbl, 0, 1); /* GPE1_BASE */
+ build_append_int_noprefix(tbl, 0, 1); /* CST_CNT */
+ build_append_int_noprefix(tbl, f->plvl2_lat, 2); /* P_LVL2_LAT */
+ build_append_int_noprefix(tbl, f->plvl3_lat, 2); /* P_LVL3_LAT */
+ build_append_int_noprefix(tbl, 0, 2); /* FLUSH_SIZE */
+ build_append_int_noprefix(tbl, 0, 2); /* FLUSH_STRIDE */
+ build_append_int_noprefix(tbl, 0, 1); /* DUTY_OFFSET */
+ build_append_int_noprefix(tbl, 0, 1); /* DUTY_WIDTH */
+ build_append_int_noprefix(tbl, 0, 1); /* DAY_ALRM */
+ build_append_int_noprefix(tbl, 0, 1); /* MON_ALRM */
+ build_append_int_noprefix(tbl, f->rtc_century, 1); /* CENTURY */
+ build_append_int_noprefix(tbl, 0, 2); /* IAPC_BOOT_ARCH */
+ build_append_int_noprefix(tbl, 0, 1); /* Reserved */
+ build_append_int_noprefix(tbl, f->flags, 4); /* Flags */
+
+ if (f->rev == 1) {
+ goto done;
+ }
+
+ build_append_gas_from_struct(tbl, &f->reset_reg); /* RESET_REG */
+ build_append_int_noprefix(tbl, f->reset_val, 1); /* RESET_VALUE */
+ /* Since ACPI 5.1 */
+ if ((f->rev >= 6) || ((f->rev == 5) && f->minor_ver > 0)) {
+ build_append_int_noprefix(tbl, f->arm_boot_arch, 2); /* ARM_BOOT_ARCH */
+ /* FADT Minor Version */
+ build_append_int_noprefix(tbl, f->minor_ver, 1);
+ } else {
+ build_append_int_noprefix(tbl, 0, 3); /* Reserved upto ACPI 5.0 */
+ }
+ build_append_int_noprefix(tbl, 0, 8); /* X_FIRMWARE_CTRL */
+
+ /* XDSDT address to be filled by Guest linker at runtime */
+ off = tbl->len;
+ build_append_int_noprefix(tbl, 0, 8); /* X_DSDT */
+ if (f->xdsdt_tbl_offset) {
+ bios_linker_loader_add_pointer(linker,
+ ACPI_BUILD_TABLE_FILE, off, 8,
+ ACPI_BUILD_TABLE_FILE, *f->xdsdt_tbl_offset);
+ }
+
+ build_append_gas_from_struct(tbl, &f->pm1a_evt); /* X_PM1a_EVT_BLK */
+ /* X_PM1b_EVT_BLK */
+ build_append_gas(tbl, AML_AS_SYSTEM_MEMORY, 0 , 0, 0, 0);
+ build_append_gas_from_struct(tbl, &f->pm1a_cnt); /* X_PM1a_CNT_BLK */
+ /* X_PM1b_CNT_BLK */
+ build_append_gas(tbl, AML_AS_SYSTEM_MEMORY, 0 , 0, 0, 0);
+ /* X_PM2_CNT_BLK */
+ build_append_gas(tbl, AML_AS_SYSTEM_MEMORY, 0 , 0, 0, 0);
+ build_append_gas_from_struct(tbl, &f->pm_tmr); /* X_PM_TMR_BLK */
+ build_append_gas_from_struct(tbl, &f->gpe0_blk); /* X_GPE0_BLK */
+ build_append_gas(tbl, AML_AS_SYSTEM_MEMORY, 0 , 0, 0, 0); /* X_GPE1_BLK */
+
+ if (f->rev <= 4) {
+ goto done;
+ }
+
+ /* SLEEP_CONTROL_REG */
+ build_append_gas_from_struct(tbl, &f->sleep_ctl);
+ /* SLEEP_STATUS_REG */
+ build_append_gas_from_struct(tbl, &f->sleep_sts);
+
+ /* TODO: extra fields need to be added to support revisions above rev5 */
+ assert(f->rev == 5);
+
+done:
+ acpi_table_end(linker, &table);
+}
+
+#ifdef CONFIG_TPM
+/*
+ * build_tpm2 - Build the TPM2 table as specified in
+ * table 7: TCG Hardware Interface Description Table Format for TPM 2.0
+ * of TCG ACPI Specification, Family “1.2” and “2.0”, Version 1.2, Rev 8
+ */
+void build_tpm2(GArray *table_data, BIOSLinker *linker, GArray *tcpalog,
+ const char *oem_id, const char *oem_table_id)
+{
+ uint8_t start_method_params[12] = {};
+ unsigned log_addr_offset;
+ uint64_t control_area_start_address;
+ TPMIf *tpmif = tpm_find();
+ uint32_t start_method;
+ AcpiTable table = { .sig = "TPM2", .rev = 4,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, table_data);
+
+ /* Platform Class */
+ build_append_int_noprefix(table_data, TPM2_ACPI_CLASS_CLIENT, 2);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 2);
+ if (TPM_IS_TIS_ISA(tpmif) || TPM_IS_TIS_SYSBUS(tpmif)) {
+ control_area_start_address = 0;
+ start_method = TPM2_START_METHOD_MMIO;
+ } else if (TPM_IS_CRB(tpmif)) {
+ control_area_start_address = TPM_CRB_ADDR_CTRL;
+ start_method = TPM2_START_METHOD_CRB;
+ } else {
+ g_assert_not_reached();
+ }
+ /* Address of Control Area */
+ build_append_int_noprefix(table_data, control_area_start_address, 8);
+ /* Start Method */
+ build_append_int_noprefix(table_data, start_method, 4);
+
+ /* Platform Specific Parameters */
+ g_array_append_vals(table_data, &start_method_params,
+ ARRAY_SIZE(start_method_params));
+
+ /* Log Area Minimum Length */
+ build_append_int_noprefix(table_data, TPM_LOG_AREA_MINIMUM_SIZE, 4);
+
+ acpi_data_push(tcpalog, TPM_LOG_AREA_MINIMUM_SIZE);
+ bios_linker_loader_alloc(linker, ACPI_BUILD_TPMLOG_FILE, tcpalog, 1,
+ false);
+
+ log_addr_offset = table_data->len;
+
+ /* Log Area Start Address to be filled by Guest linker */
+ build_append_int_noprefix(table_data, 0, 8);
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE,
+ log_addr_offset, 8,
+ ACPI_BUILD_TPMLOG_FILE, 0);
+ acpi_table_end(linker, &table);
+}
+#endif
+
+Aml *build_crs(PCIHostState *host, CrsRangeSet *range_set, uint32_t io_offset,
+ uint32_t mmio32_offset, uint64_t mmio64_offset,
+ uint16_t bus_nr_offset)
+{
+ Aml *crs = aml_resource_template();
+ CrsRangeSet temp_range_set;
+ CrsRangeEntry *entry;
+ uint8_t max_bus = pci_bus_num(host->bus);
+ uint8_t type;
+ int devfn;
+ int i;
+
+ crs_range_set_init(&temp_range_set);
+ for (devfn = 0; devfn < ARRAY_SIZE(host->bus->devices); devfn++) {
+ uint64_t range_base, range_limit;
+ PCIDevice *dev = host->bus->devices[devfn];
+
+ if (!dev) {
+ continue;
+ }
+
+ for (i = 0; i < PCI_NUM_REGIONS; i++) {
+ PCIIORegion *r = &dev->io_regions[i];
+
+ range_base = r->addr;
+ range_limit = r->addr + r->size - 1;
+
+ /*
+ * Work-around for old bioses
+ * that do not support multiple root buses
+ */
+ if (!range_base || range_base > range_limit) {
+ continue;
+ }
+
+ if (r->type & PCI_BASE_ADDRESS_SPACE_IO) {
+ crs_range_insert(temp_range_set.io_ranges,
+ range_base, range_limit);
+ } else { /* "memory" */
+ uint64_t length = range_limit - range_base + 1;
+ if (range_limit <= UINT32_MAX && length <= UINT32_MAX) {
+ crs_range_insert(temp_range_set.mem_ranges, range_base,
+ range_limit);
+ } else {
+ crs_range_insert(temp_range_set.mem_64bit_ranges,
+ range_base, range_limit);
+ }
+ }
+ }
+
+ type = dev->config[PCI_HEADER_TYPE] & ~PCI_HEADER_TYPE_MULTI_FUNCTION;
+ if (type == PCI_HEADER_TYPE_BRIDGE) {
+ uint8_t subordinate = dev->config[PCI_SUBORDINATE_BUS];
+ if (subordinate > max_bus) {
+ max_bus = subordinate;
+ }
+
+ range_base = pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_IO);
+ range_limit = pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_IO);
+
+ /*
+ * Work-around for old bioses
+ * that do not support multiple root buses
+ */
+ if (range_base && range_base <= range_limit) {
+ crs_range_insert(temp_range_set.io_ranges,
+ range_base, range_limit);
+ }
+
+ range_base =
+ pci_bridge_get_base(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
+ range_limit =
+ pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_SPACE_MEMORY);
+
+ /*
+ * Work-around for old bioses
+ * that do not support multiple root buses
+ */
+ if (range_base && range_base <= range_limit) {
+ uint64_t length = range_limit - range_base + 1;
+ if (range_limit <= UINT32_MAX && length <= UINT32_MAX) {
+ crs_range_insert(temp_range_set.mem_ranges,
+ range_base, range_limit);
+ } else {
+ crs_range_insert(temp_range_set.mem_64bit_ranges,
+ range_base, range_limit);
+ }
+ }
+
+ range_base =
+ pci_bridge_get_base(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+ range_limit =
+ pci_bridge_get_limit(dev, PCI_BASE_ADDRESS_MEM_PREFETCH);
+
+ /*
+ * Work-around for old bioses
+ * that do not support multiple root buses
+ */
+ if (range_base && range_base <= range_limit) {
+ uint64_t length = range_limit - range_base + 1;
+ if (range_limit <= UINT32_MAX && length <= UINT32_MAX) {
+ crs_range_insert(temp_range_set.mem_ranges,
+ range_base, range_limit);
+ } else {
+ crs_range_insert(temp_range_set.mem_64bit_ranges,
+ range_base, range_limit);
+ }
+ }
+ }
+ }
+
+ crs_range_merge(temp_range_set.io_ranges);
+ for (i = 0; i < temp_range_set.io_ranges->len; i++) {
+ entry = g_ptr_array_index(temp_range_set.io_ranges, i);
+ aml_append(crs,
+ aml_dword_io(AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_POS_DECODE, AML_ENTIRE_RANGE,
+ 0, entry->base, entry->limit, io_offset,
+ entry->limit - entry->base + 1));
+ crs_range_insert(range_set->io_ranges, entry->base, entry->limit);
+ }
+
+ crs_range_merge(temp_range_set.mem_ranges);
+ for (i = 0; i < temp_range_set.mem_ranges->len; i++) {
+ entry = g_ptr_array_index(temp_range_set.mem_ranges, i);
+ assert(entry->limit <= UINT32_MAX &&
+ (entry->limit - entry->base + 1) <= UINT32_MAX);
+ aml_append(crs,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
+ AML_MAX_FIXED, AML_NON_CACHEABLE,
+ AML_READ_WRITE,
+ 0, entry->base, entry->limit, mmio32_offset,
+ entry->limit - entry->base + 1));
+ crs_range_insert(range_set->mem_ranges, entry->base, entry->limit);
+ }
+
+ crs_range_merge(temp_range_set.mem_64bit_ranges);
+ for (i = 0; i < temp_range_set.mem_64bit_ranges->len; i++) {
+ entry = g_ptr_array_index(temp_range_set.mem_64bit_ranges, i);
+ aml_append(crs,
+ aml_qword_memory(AML_POS_DECODE, AML_MIN_FIXED,
+ AML_MAX_FIXED, AML_NON_CACHEABLE,
+ AML_READ_WRITE,
+ 0, entry->base, entry->limit, mmio64_offset,
+ entry->limit - entry->base + 1));
+ crs_range_insert(range_set->mem_64bit_ranges,
+ entry->base, entry->limit);
+ }
+
+ crs_range_set_free(&temp_range_set);
+
+ aml_append(crs,
+ aml_word_bus_number(AML_MIN_FIXED, AML_MAX_FIXED, AML_POS_DECODE,
+ 0,
+ pci_bus_num(host->bus),
+ max_bus,
+ bus_nr_offset,
+ max_bus - pci_bus_num(host->bus) + 1));
+
+ return crs;
+}
+
+/* ACPI 5.0: 6.4.3.8.2 Serial Bus Connection Descriptors */
+static Aml *aml_serial_bus_device(uint8_t serial_bus_type, uint8_t flags,
+ uint16_t type_flags,
+ uint8_t revid, uint16_t data_length,
+ uint16_t resource_source_len)
+{
+ Aml *var = aml_alloc();
+ uint16_t length = data_length + resource_source_len + 9;
+
+ build_append_byte(var->buf, 0x8e); /* Serial Bus Connection Descriptor */
+ build_append_int_noprefix(var->buf, length, sizeof(length));
+ build_append_byte(var->buf, 1); /* Revision ID */
+ build_append_byte(var->buf, 0); /* Resource Source Index */
+ build_append_byte(var->buf, serial_bus_type); /* Serial Bus Type */
+ build_append_byte(var->buf, flags); /* General Flags */
+ build_append_int_noprefix(var->buf, type_flags, /* Type Specific Flags */
+ sizeof(type_flags));
+ build_append_byte(var->buf, revid); /* Type Specification Revision ID */
+ build_append_int_noprefix(var->buf, data_length, sizeof(data_length));
+
+ return var;
+}
+
+/* ACPI 5.0: 6.4.3.8.2.1 I2C Serial Bus Connection Resource Descriptor */
+Aml *aml_i2c_serial_bus_device(uint16_t address, const char *resource_source)
+{
+ uint16_t resource_source_len = strlen(resource_source) + 1;
+ Aml *var = aml_serial_bus_device(AML_SERIAL_BUS_TYPE_I2C, 0, 0, 1,
+ 6, resource_source_len);
+
+ /* Connection Speed. Just set to 100K for now, it doesn't really matter. */
+ build_append_int_noprefix(var->buf, 100000, 4);
+ build_append_int_noprefix(var->buf, address, sizeof(address));
+
+ /* This is a string, not a name, so just copy it directly in. */
+ g_array_append_vals(var->buf, resource_source, resource_source_len);
+
+ return var;
+}
diff --git a/hw/acpi/bios-linker-loader.c b/hw/acpi/bios-linker-loader.c
new file mode 100644
index 000000000..108061828
--- /dev/null
+++ b/hw/acpi/bios-linker-loader.c
@@ -0,0 +1,351 @@
+/* Dynamic linker/loader of ACPI tables
+ *
+ * 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"
+#include "hw/acpi/bios-linker-loader.h"
+#include "hw/nvram/fw_cfg.h"
+
+#include "qemu/bswap.h"
+
+/*
+ * Linker/loader is a paravirtualized interface that passes commands to guest.
+ * The commands can be used to request guest to
+ * - allocate memory chunks and initialize them from QEMU FW CFG files
+ * - link allocated chunks by storing pointer to one chunk into another
+ * - calculate ACPI checksum of part of the chunk and store into same chunk
+ */
+#define BIOS_LINKER_LOADER_FILESZ FW_CFG_MAX_FILE_PATH
+
+struct BiosLinkerLoaderEntry {
+ uint32_t command;
+ union {
+ /*
+ * COMMAND_ALLOCATE - allocate a table from @alloc.file
+ * subject to @alloc.align alignment (must be power of 2)
+ * and @alloc.zone (can be HIGH or FSEG) requirements.
+ *
+ * Must appear exactly once for each file, and before
+ * this file is referenced by any other command.
+ */
+ struct {
+ char file[BIOS_LINKER_LOADER_FILESZ];
+ uint32_t align;
+ uint8_t zone;
+ } alloc;
+
+ /*
+ * COMMAND_ADD_POINTER - patch the table (originating from
+ * @dest_file) at @pointer.offset, by adding a pointer to the table
+ * originating from @src_file. 1,2,4 or 8 byte unsigned
+ * addition is used depending on @pointer.size.
+ */
+ struct {
+ char dest_file[BIOS_LINKER_LOADER_FILESZ];
+ char src_file[BIOS_LINKER_LOADER_FILESZ];
+ uint32_t offset;
+ uint8_t size;
+ } pointer;
+
+ /*
+ * COMMAND_ADD_CHECKSUM - calculate checksum of the range specified by
+ * @cksum_start and @cksum_length fields,
+ * and then add the value at @cksum.offset.
+ * Checksum simply sums -X for each byte X in the range
+ * using 8-bit math.
+ */
+ struct {
+ char file[BIOS_LINKER_LOADER_FILESZ];
+ uint32_t offset;
+ uint32_t start;
+ 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];
+ };
+} QEMU_PACKED;
+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_WRITE_POINTER = 0x4,
+};
+
+enum {
+ BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1,
+ BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2,
+};
+
+/*
+ * BiosLinkerFileEntry:
+ *
+ * An internal type used for book-keeping file entries
+ */
+typedef struct BiosLinkerFileEntry {
+ char *name; /* file name */
+ GArray *blob; /* data accosiated with @name */
+} BiosLinkerFileEntry;
+
+/*
+ * bios_linker_loader_init: allocate a new linker object instance.
+ *
+ * After initialization, linker commands can be added, and will
+ * be stored in the linker.cmd_blob array.
+ */
+BIOSLinker *bios_linker_loader_init(void)
+{
+ BIOSLinker *linker = g_new(BIOSLinker, 1);
+
+ linker->cmd_blob = g_array_new(false, true /* clear */, 1);
+ linker->file_list = g_array_new(false, true /* clear */,
+ sizeof(BiosLinkerFileEntry));
+ return linker;
+}
+
+/* Free linker wrapper */
+void bios_linker_loader_cleanup(BIOSLinker *linker)
+{
+ int i;
+ BiosLinkerFileEntry *entry;
+
+ g_array_free(linker->cmd_blob, true);
+
+ for (i = 0; i < linker->file_list->len; i++) {
+ entry = &g_array_index(linker->file_list, BiosLinkerFileEntry, i);
+ g_free(entry->name);
+ }
+ g_array_free(linker->file_list, true);
+ g_free(linker);
+}
+
+static const BiosLinkerFileEntry *
+bios_linker_find_file(const BIOSLinker *linker, const char *name)
+{
+ int i;
+ BiosLinkerFileEntry *entry;
+
+ for (i = 0; i < linker->file_list->len; i++) {
+ entry = &g_array_index(linker->file_list, BiosLinkerFileEntry, i);
+ if (!strcmp(entry->name, name)) {
+ return entry;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * board code must realize fw_cfg first, as a fixed device, before
+ * another device realize function call bios_linker_loader_can_write_pointer()
+ */
+bool bios_linker_loader_can_write_pointer(void)
+{
+ FWCfgState *fw_cfg = fw_cfg_find();
+ return fw_cfg && fw_cfg_dma_enabled(fw_cfg);
+}
+
+/*
+ * bios_linker_loader_alloc: ask guest to load file into guest memory.
+ *
+ * @linker: linker object instance
+ * @file_name: name of the file blob to be loaded
+ * @file_blob: pointer to blob corresponding to @file_name
+ * @alloc_align: required minimal alignment in bytes. Must be a power of 2.
+ * @alloc_fseg: request allocation in FSEG zone (useful for the RSDP ACPI table)
+ *
+ * Note: this command must precede any other linker command using this file.
+ */
+void bios_linker_loader_alloc(BIOSLinker *linker,
+ const char *file_name,
+ GArray *file_blob,
+ uint32_t alloc_align,
+ bool alloc_fseg)
+{
+ BiosLinkerLoaderEntry entry;
+ BiosLinkerFileEntry file = { g_strdup(file_name), file_blob};
+
+ assert(!(alloc_align & (alloc_align - 1)));
+
+ assert(!bios_linker_find_file(linker, file_name));
+ g_array_append_val(linker->file_list, file);
+
+ memset(&entry, 0, sizeof entry);
+ strncpy(entry.alloc.file, file_name, sizeof entry.alloc.file - 1);
+ entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ALLOCATE);
+ entry.alloc.align = cpu_to_le32(alloc_align);
+ entry.alloc.zone = alloc_fseg ? BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG :
+ BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH;
+
+ /* Alloc entries must come first, so prepend them */
+ g_array_prepend_vals(linker->cmd_blob, &entry, sizeof entry);
+}
+
+/*
+ * bios_linker_loader_add_checksum: ask guest to add checksum of ACPI
+ * table in the specified file at the specified offset.
+ *
+ * Checksum calculation simply sums -X for each byte X in the range
+ * using 8-bit math (i.e. ACPI checksum).
+ *
+ * @linker: linker object instance
+ * @file: file that includes the checksum to be calculated
+ * and the data to be checksummed
+ * @start_offset, @size: range of data in the file to checksum,
+ * relative to the start of file blob
+ * @checksum_offset: location of the checksum to be patched within file blob,
+ * relative to the start of file blob
+ */
+void bios_linker_loader_add_checksum(BIOSLinker *linker, const char *file_name,
+ unsigned start_offset, unsigned size,
+ unsigned checksum_offset)
+{
+ BiosLinkerLoaderEntry entry;
+ const BiosLinkerFileEntry *file = bios_linker_find_file(linker, file_name);
+
+ assert(file);
+ assert(start_offset < file->blob->len);
+ assert(start_offset + size <= file->blob->len);
+ assert(checksum_offset >= start_offset);
+ assert(checksum_offset + 1 <= start_offset + size);
+
+ *(file->blob->data + checksum_offset) = 0;
+ memset(&entry, 0, sizeof entry);
+ strncpy(entry.cksum.file, file_name, sizeof entry.cksum.file - 1);
+ entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM);
+ entry.cksum.offset = cpu_to_le32(checksum_offset);
+ entry.cksum.start = cpu_to_le32(start_offset);
+ entry.cksum.length = cpu_to_le32(size);
+
+ g_array_append_vals(linker->cmd_blob, &entry, sizeof entry);
+}
+
+/*
+ * bios_linker_loader_add_pointer: ask guest to patch address in
+ * destination file with a pointer to source file
+ *
+ * @linker: linker object instance
+ * @dest_file: destination file that must be changed
+ * @dst_patched_offset: location within destination file blob to be patched
+ * with the pointer to @src_file+@src_offset (i.e. source
+ * blob allocated in guest memory + @src_offset), 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 ADD_POINTER command
+ */
+void bios_linker_loader_add_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)
+{
+ uint64_t le_src_offset;
+ BiosLinkerLoaderEntry entry;
+ const BiosLinkerFileEntry *dst_file =
+ bios_linker_find_file(linker, dest_file);
+ const BiosLinkerFileEntry *source_file =
+ bios_linker_find_file(linker, src_file);
+
+ assert(dst_file);
+ assert(source_file);
+ assert(dst_patched_offset < dst_file->blob->len);
+ assert(dst_patched_offset + dst_patched_size <= dst_file->blob->len);
+ assert(src_offset < source_file->blob->len);
+
+ memset(&entry, 0, sizeof entry);
+ strncpy(entry.pointer.dest_file, dest_file,
+ sizeof entry.pointer.dest_file - 1);
+ strncpy(entry.pointer.src_file, src_file,
+ sizeof entry.pointer.src_file - 1);
+ entry.command = cpu_to_le32(BIOS_LINKER_LOADER_COMMAND_ADD_POINTER);
+ entry.pointer.offset = cpu_to_le32(dst_patched_offset);
+ entry.pointer.size = dst_patched_size;
+ assert(dst_patched_size == 1 || dst_patched_size == 2 ||
+ dst_patched_size == 4 || dst_patched_size == 8);
+
+ le_src_offset = cpu_to_le64(src_offset);
+ memcpy(dst_file->blob->data + dst_patched_offset,
+ &le_src_offset, dst_patched_size);
+
+ 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);
+}
diff --git a/hw/acpi/core.c b/hw/acpi/core.c
new file mode 100644
index 000000000..1e004d007
--- /dev/null
+++ b/hw/acpi/core.c
@@ -0,0 +1,738 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/acpi/acpi.h"
+#include "hw/nvram/fw_cfg.h"
+#include "qemu/config-file.h"
+#include "qapi/error.h"
+#include "qapi/opts-visitor.h"
+#include "qapi/qapi-events-run-state.h"
+#include "qapi/qapi-visit-acpi.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "sysemu/runstate.h"
+
+struct acpi_table_header {
+ uint16_t _length; /* our length, not actual part of the hdr */
+ /* allows easier parsing for fw_cfg clients */
+ char sig[4]
+ QEMU_NONSTRING; /* ACPI signature (4 ASCII characters) */
+ uint32_t length; /* Length of table, in bytes, including header */
+ uint8_t revision; /* ACPI Specification minor version # */
+ uint8_t checksum; /* To make sum of entire table == 0 */
+ char oem_id[6]
+ QEMU_NONSTRING; /* OEM identification */
+ char oem_table_id[8]
+ QEMU_NONSTRING; /* OEM table identification */
+ uint32_t oem_revision; /* OEM revision number */
+ char asl_compiler_id[4]
+ QEMU_NONSTRING; /* ASL compiler vendor ID */
+ uint32_t asl_compiler_revision; /* ASL compiler revision number */
+} QEMU_PACKED;
+
+#define ACPI_TABLE_HDR_SIZE sizeof(struct acpi_table_header)
+#define ACPI_TABLE_PFX_SIZE sizeof(uint16_t) /* size of the extra prefix */
+
+static const char unsigned dfl_hdr[ACPI_TABLE_HDR_SIZE - ACPI_TABLE_PFX_SIZE] =
+ "QEMU\0\0\0\0\1\0" /* sig (4), len(4), revno (1), csum (1) */
+ "QEMUQEQEMUQEMU\1\0\0\0" /* OEM id (6), table (8), revno (4) */
+ "QEMU\1\0\0\0" /* ASL compiler ID (4), version (4) */
+ ;
+
+char unsigned *acpi_tables;
+size_t acpi_tables_len;
+
+static QemuOptsList qemu_acpi_opts = {
+ .name = "acpi",
+ .implied_opt_name = "data",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_acpi_opts.head),
+ .desc = { { 0 } } /* validated with OptsVisitor */
+};
+
+static void acpi_register_config(void)
+{
+ qemu_add_opts(&qemu_acpi_opts);
+}
+
+opts_init(acpi_register_config);
+
+static int acpi_checksum(const uint8_t *data, int len)
+{
+ int sum, i;
+ sum = 0;
+ for (i = 0; i < len; i++) {
+ sum += data[i];
+ }
+ return (-sum) & 0xff;
+}
+
+
+/* Install a copy of the ACPI table specified in @blob.
+ *
+ * If @has_header is set, @blob starts with the System Description Table Header
+ * structure. Otherwise, "dfl_hdr" is prepended. In any case, each header field
+ * is optionally overwritten from @hdrs.
+ *
+ * It is valid to call this function with
+ * (@blob == NULL && bloblen == 0 && !has_header).
+ *
+ * @hdrs->file and @hdrs->data are ignored.
+ *
+ * SIZE_MAX is considered "infinity" in this function.
+ *
+ * The number of tables that can be installed is not limited, but the 16-bit
+ * counter at the beginning of "acpi_tables" wraps around after UINT16_MAX.
+ */
+static void acpi_table_install(const char unsigned *blob, size_t bloblen,
+ bool has_header,
+ const struct AcpiTableOptions *hdrs,
+ Error **errp)
+{
+ size_t body_start;
+ const char unsigned *hdr_src;
+ size_t body_size, acpi_payload_size;
+ struct acpi_table_header *ext_hdr;
+ unsigned changed_fields;
+
+ /* Calculate where the ACPI table body starts within the blob, plus where
+ * to copy the ACPI table header from.
+ */
+ if (has_header) {
+ /* _length | ACPI header in blob | blob body
+ * ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^
+ * ACPI_TABLE_PFX_SIZE sizeof dfl_hdr body_size
+ * == body_start
+ *
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * acpi_payload_size == bloblen
+ */
+ body_start = sizeof dfl_hdr;
+
+ if (bloblen < body_start) {
+ error_setg(errp, "ACPI table claiming to have header is too "
+ "short, available: %zu, expected: %zu", bloblen,
+ body_start);
+ return;
+ }
+ hdr_src = blob;
+ } else {
+ /* _length | ACPI header in template | blob body
+ * ^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^
+ * ACPI_TABLE_PFX_SIZE sizeof dfl_hdr body_size
+ * == bloblen
+ *
+ * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ * acpi_payload_size
+ */
+ body_start = 0;
+ hdr_src = dfl_hdr;
+ }
+ body_size = bloblen - body_start;
+ acpi_payload_size = sizeof dfl_hdr + body_size;
+
+ if (acpi_payload_size > UINT16_MAX) {
+ error_setg(errp, "ACPI table too big, requested: %zu, max: %u",
+ acpi_payload_size, (unsigned)UINT16_MAX);
+ return;
+ }
+
+ /* We won't fail from here on. Initialize / extend the globals. */
+ if (acpi_tables == NULL) {
+ acpi_tables_len = sizeof(uint16_t);
+ acpi_tables = g_malloc0(acpi_tables_len);
+ }
+
+ acpi_tables = g_realloc(acpi_tables, acpi_tables_len +
+ ACPI_TABLE_PFX_SIZE +
+ sizeof dfl_hdr + body_size);
+
+ ext_hdr = (struct acpi_table_header *)(acpi_tables + acpi_tables_len);
+ acpi_tables_len += ACPI_TABLE_PFX_SIZE;
+
+ memcpy(acpi_tables + acpi_tables_len, hdr_src, sizeof dfl_hdr);
+ acpi_tables_len += sizeof dfl_hdr;
+
+ if (blob != NULL) {
+ memcpy(acpi_tables + acpi_tables_len, blob + body_start, body_size);
+ acpi_tables_len += body_size;
+ }
+
+ /* increase number of tables */
+ stw_le_p(acpi_tables, lduw_le_p(acpi_tables) + 1u);
+
+ /* Update the header fields. The strings need not be NUL-terminated. */
+ changed_fields = 0;
+ ext_hdr->_length = cpu_to_le16(acpi_payload_size);
+
+ if (hdrs->has_sig) {
+ strncpy(ext_hdr->sig, hdrs->sig, sizeof ext_hdr->sig);
+ ++changed_fields;
+ }
+
+ if (has_header && le32_to_cpu(ext_hdr->length) != acpi_payload_size) {
+ warn_report("ACPI table has wrong length, header says "
+ "%" PRIu32 ", actual size %zu bytes",
+ le32_to_cpu(ext_hdr->length), acpi_payload_size);
+ }
+ ext_hdr->length = cpu_to_le32(acpi_payload_size);
+
+ if (hdrs->has_rev) {
+ ext_hdr->revision = hdrs->rev;
+ ++changed_fields;
+ }
+
+ ext_hdr->checksum = 0;
+
+ if (hdrs->has_oem_id) {
+ strncpy(ext_hdr->oem_id, hdrs->oem_id, sizeof ext_hdr->oem_id);
+ ++changed_fields;
+ }
+ if (hdrs->has_oem_table_id) {
+ strncpy(ext_hdr->oem_table_id, hdrs->oem_table_id,
+ sizeof ext_hdr->oem_table_id);
+ ++changed_fields;
+ }
+ if (hdrs->has_oem_rev) {
+ ext_hdr->oem_revision = cpu_to_le32(hdrs->oem_rev);
+ ++changed_fields;
+ }
+ if (hdrs->has_asl_compiler_id) {
+ strncpy(ext_hdr->asl_compiler_id, hdrs->asl_compiler_id,
+ sizeof ext_hdr->asl_compiler_id);
+ ++changed_fields;
+ }
+ if (hdrs->has_asl_compiler_rev) {
+ ext_hdr->asl_compiler_revision = cpu_to_le32(hdrs->asl_compiler_rev);
+ ++changed_fields;
+ }
+
+ if (!has_header && changed_fields == 0) {
+ warn_report("ACPI table: no headers are specified");
+ }
+
+ /* recalculate checksum */
+ ext_hdr->checksum = acpi_checksum((const char unsigned *)ext_hdr +
+ ACPI_TABLE_PFX_SIZE, acpi_payload_size);
+}
+
+void acpi_table_add(const QemuOpts *opts, Error **errp)
+{
+ AcpiTableOptions *hdrs = NULL;
+ char **pathnames = NULL;
+ char **cur;
+ size_t bloblen = 0;
+ char unsigned *blob = NULL;
+
+ {
+ Visitor *v;
+
+ v = opts_visitor_new(opts);
+ visit_type_AcpiTableOptions(v, NULL, &hdrs, errp);
+ visit_free(v);
+ }
+
+ if (!hdrs) {
+ goto out;
+ }
+ if (hdrs->has_file == hdrs->has_data) {
+ error_setg(errp, "'-acpitable' requires one of 'data' or 'file'");
+ goto out;
+ }
+
+ pathnames = g_strsplit(hdrs->has_file ? hdrs->file : hdrs->data, ":", 0);
+ if (pathnames == NULL || pathnames[0] == NULL) {
+ error_setg(errp, "'-acpitable' requires at least one pathname");
+ goto out;
+ }
+
+ /* now read in the data files, reallocating buffer as needed */
+ for (cur = pathnames; *cur; ++cur) {
+ int fd = open(*cur, O_RDONLY | O_BINARY);
+
+ if (fd < 0) {
+ error_setg(errp, "can't open file %s: %s", *cur, strerror(errno));
+ goto out;
+ }
+
+ for (;;) {
+ char unsigned data[8192];
+ ssize_t r;
+
+ r = read(fd, data, sizeof data);
+ if (r == 0) {
+ break;
+ } else if (r > 0) {
+ blob = g_realloc(blob, bloblen + r);
+ memcpy(blob + bloblen, data, r);
+ bloblen += r;
+ } else if (errno != EINTR) {
+ error_setg(errp, "can't read file %s: %s", *cur,
+ strerror(errno));
+ close(fd);
+ goto out;
+ }
+ }
+
+ close(fd);
+ }
+
+ acpi_table_install(blob, bloblen, hdrs->has_file, hdrs, errp);
+
+out:
+ g_free(blob);
+ g_strfreev(pathnames);
+ qapi_free_AcpiTableOptions(hdrs);
+}
+
+unsigned acpi_table_len(void *current)
+{
+ struct acpi_table_header *hdr = current - sizeof(hdr->_length);
+ return hdr->_length;
+}
+
+static
+void *acpi_table_hdr(void *h)
+{
+ struct acpi_table_header *hdr = h;
+ return &hdr->sig;
+}
+
+uint8_t *acpi_table_first(void)
+{
+ if (!acpi_tables) {
+ return NULL;
+ }
+ return acpi_table_hdr(acpi_tables + ACPI_TABLE_PFX_SIZE);
+}
+
+uint8_t *acpi_table_next(uint8_t *current)
+{
+ uint8_t *next = current + acpi_table_len(current);
+
+ if (next - acpi_tables >= acpi_tables_len) {
+ return NULL;
+ } else {
+ return acpi_table_hdr(next);
+ }
+}
+
+int acpi_get_slic_oem(AcpiSlicOem *oem)
+{
+ uint8_t *u;
+
+ for (u = acpi_table_first(); u; u = acpi_table_next(u)) {
+ struct acpi_table_header *hdr = (void *)(u - sizeof(hdr->_length));
+
+ if (memcmp(hdr->sig, "SLIC", 4) == 0) {
+ oem->id = hdr->oem_id;
+ oem->table_id = hdr->oem_table_id;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void acpi_notify_wakeup(Notifier *notifier, void *data)
+{
+ ACPIREGS *ar = container_of(notifier, ACPIREGS, wakeup);
+ WakeupReason *reason = data;
+
+ switch (*reason) {
+ case QEMU_WAKEUP_REASON_RTC:
+ ar->pm1.evt.sts |=
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_RT_CLOCK_STATUS);
+ break;
+ case QEMU_WAKEUP_REASON_PMTIMER:
+ ar->pm1.evt.sts |=
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_TIMER_STATUS);
+ break;
+ case QEMU_WAKEUP_REASON_OTHER:
+ /* ACPI_BITMASK_WAKE_STATUS should be set on resume.
+ Pretend that resume was caused by power button */
+ ar->pm1.evt.sts |=
+ (ACPI_BITMASK_WAKE_STATUS | ACPI_BITMASK_POWER_BUTTON_STATUS);
+ break;
+ default:
+ break;
+ }
+}
+
+/* ACPI PM1a EVT */
+uint16_t acpi_pm1_evt_get_sts(ACPIREGS *ar)
+{
+ /* Compare ns-clock, not PM timer ticks, because
+ acpi_pm_tmr_update function uses ns for setting the timer. */
+ int64_t d = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ if (d >= muldiv64(ar->tmr.overflow_time,
+ NANOSECONDS_PER_SECOND, PM_TIMER_FREQUENCY)) {
+ ar->pm1.evt.sts |= ACPI_BITMASK_TIMER_STATUS;
+ }
+ return ar->pm1.evt.sts;
+}
+
+static void acpi_pm1_evt_write_sts(ACPIREGS *ar, uint16_t val)
+{
+ uint16_t pm1_sts = acpi_pm1_evt_get_sts(ar);
+ if (pm1_sts & val & ACPI_BITMASK_TIMER_STATUS) {
+ /* if TMRSTS is reset, then compute the new overflow time */
+ acpi_pm_tmr_calc_overflow_time(ar);
+ }
+ ar->pm1.evt.sts &= ~val;
+}
+
+static void acpi_pm1_evt_write_en(ACPIREGS *ar, uint16_t val)
+{
+ ar->pm1.evt.en = val;
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_RTC,
+ val & ACPI_BITMASK_RT_CLOCK_ENABLE);
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_PMTIMER,
+ val & ACPI_BITMASK_TIMER_ENABLE);
+}
+
+void acpi_pm1_evt_power_down(ACPIREGS *ar)
+{
+ if (ar->pm1.evt.en & ACPI_BITMASK_POWER_BUTTON_ENABLE) {
+ ar->pm1.evt.sts |= ACPI_BITMASK_POWER_BUTTON_STATUS;
+ ar->tmr.update_sci(ar);
+ }
+}
+
+void acpi_pm1_evt_reset(ACPIREGS *ar)
+{
+ ar->pm1.evt.sts = 0;
+ ar->pm1.evt.en = 0;
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_RTC, 0);
+ qemu_system_wakeup_enable(QEMU_WAKEUP_REASON_PMTIMER, 0);
+}
+
+static uint64_t acpi_pm_evt_read(void *opaque, hwaddr addr, unsigned width)
+{
+ ACPIREGS *ar = opaque;
+ switch (addr) {
+ case 0:
+ return acpi_pm1_evt_get_sts(ar);
+ case 2:
+ return ar->pm1.evt.en;
+ default:
+ return 0;
+ }
+}
+
+static void acpi_pm_evt_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ ACPIREGS *ar = opaque;
+ switch (addr) {
+ case 0:
+ acpi_pm1_evt_write_sts(ar, val);
+ ar->pm1.evt.update_sci(ar);
+ break;
+ case 2:
+ acpi_pm1_evt_write_en(ar, val);
+ ar->pm1.evt.update_sci(ar);
+ break;
+ }
+}
+
+static const MemoryRegionOps acpi_pm_evt_ops = {
+ .read = acpi_pm_evt_read,
+ .write = acpi_pm_evt_write,
+ .impl.min_access_size = 2,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void acpi_pm1_evt_init(ACPIREGS *ar, acpi_update_sci_fn update_sci,
+ MemoryRegion *parent)
+{
+ ar->pm1.evt.update_sci = update_sci;
+ memory_region_init_io(&ar->pm1.evt.io, memory_region_owner(parent),
+ &acpi_pm_evt_ops, ar, "acpi-evt", 4);
+ memory_region_add_subregion(parent, 0, &ar->pm1.evt.io);
+}
+
+/* ACPI PM_TMR */
+void acpi_pm_tmr_update(ACPIREGS *ar, bool enable)
+{
+ int64_t expire_time;
+
+ /* schedule a timer interruption if needed */
+ if (enable) {
+ expire_time = muldiv64(ar->tmr.overflow_time, NANOSECONDS_PER_SECOND,
+ PM_TIMER_FREQUENCY);
+ timer_mod(ar->tmr.timer, expire_time);
+ } else {
+ timer_del(ar->tmr.timer);
+ }
+}
+
+static inline int64_t acpi_pm_tmr_get_clock(void)
+{
+ return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), PM_TIMER_FREQUENCY,
+ NANOSECONDS_PER_SECOND);
+}
+
+void acpi_pm_tmr_calc_overflow_time(ACPIREGS *ar)
+{
+ int64_t d = acpi_pm_tmr_get_clock();
+ ar->tmr.overflow_time = (d + 0x800000LL) & ~0x7fffffLL;
+}
+
+static uint32_t acpi_pm_tmr_get(ACPIREGS *ar)
+{
+ uint32_t d = acpi_pm_tmr_get_clock();
+ return d & 0xffffff;
+}
+
+static void acpi_pm_tmr_timer(void *opaque)
+{
+ ACPIREGS *ar = opaque;
+
+ qemu_system_wakeup_request(QEMU_WAKEUP_REASON_PMTIMER, NULL);
+ ar->tmr.update_sci(ar);
+}
+
+static uint64_t acpi_pm_tmr_read(void *opaque, hwaddr addr, unsigned width)
+{
+ return acpi_pm_tmr_get(opaque);
+}
+
+static void acpi_pm_tmr_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ /* nothing */
+}
+
+static const MemoryRegionOps acpi_pm_tmr_ops = {
+ .read = acpi_pm_tmr_read,
+ .write = acpi_pm_tmr_write,
+ .impl.min_access_size = 4,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void acpi_pm_tmr_init(ACPIREGS *ar, acpi_update_sci_fn update_sci,
+ MemoryRegion *parent)
+{
+ ar->tmr.update_sci = update_sci;
+ ar->tmr.timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, acpi_pm_tmr_timer, ar);
+ memory_region_init_io(&ar->tmr.io, memory_region_owner(parent),
+ &acpi_pm_tmr_ops, ar, "acpi-tmr", 4);
+ memory_region_add_subregion(parent, 8, &ar->tmr.io);
+}
+
+void acpi_pm_tmr_reset(ACPIREGS *ar)
+{
+ ar->tmr.overflow_time = 0;
+ timer_del(ar->tmr.timer);
+}
+
+/* ACPI PM1aCNT */
+static void acpi_pm1_cnt_write(ACPIREGS *ar, uint16_t val)
+{
+ ar->pm1.cnt.cnt = val & ~(ACPI_BITMASK_SLEEP_ENABLE);
+
+ if (val & ACPI_BITMASK_SLEEP_ENABLE) {
+ /* change suspend type */
+ uint16_t sus_typ = (val >> 10) & 7;
+ switch (sus_typ) {
+ case 0: /* soft power off */
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ break;
+ case 1:
+ qemu_system_suspend_request();
+ break;
+ default:
+ if (sus_typ == ar->pm1.cnt.s4_val) { /* S4 request */
+ qapi_event_send_suspend_disk();
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ }
+ break;
+ }
+ }
+}
+
+void acpi_pm1_cnt_update(ACPIREGS *ar,
+ bool sci_enable, bool sci_disable)
+{
+ /* ACPI specs 3.0, 4.7.2.5 */
+ if (ar->pm1.cnt.acpi_only) {
+ return;
+ }
+
+ if (sci_enable) {
+ ar->pm1.cnt.cnt |= ACPI_BITMASK_SCI_ENABLE;
+ } else if (sci_disable) {
+ ar->pm1.cnt.cnt &= ~ACPI_BITMASK_SCI_ENABLE;
+ }
+}
+
+static uint64_t acpi_pm_cnt_read(void *opaque, hwaddr addr, unsigned width)
+{
+ ACPIREGS *ar = opaque;
+ return ar->pm1.cnt.cnt;
+}
+
+static void acpi_pm_cnt_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ acpi_pm1_cnt_write(opaque, val);
+}
+
+static const MemoryRegionOps acpi_pm_cnt_ops = {
+ .read = acpi_pm_cnt_read,
+ .write = acpi_pm_cnt_write,
+ .impl.min_access_size = 2,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void acpi_pm1_cnt_init(ACPIREGS *ar, MemoryRegion *parent,
+ bool disable_s3, bool disable_s4, uint8_t s4_val,
+ bool acpi_only)
+{
+ FWCfgState *fw_cfg;
+
+ ar->pm1.cnt.s4_val = s4_val;
+ ar->pm1.cnt.acpi_only = acpi_only;
+ ar->wakeup.notify = acpi_notify_wakeup;
+ qemu_register_wakeup_notifier(&ar->wakeup);
+
+ /*
+ * Register wake-up support in QMP query-current-machine API
+ */
+ qemu_register_wakeup_support();
+
+ memory_region_init_io(&ar->pm1.cnt.io, memory_region_owner(parent),
+ &acpi_pm_cnt_ops, ar, "acpi-cnt", 2);
+ memory_region_add_subregion(parent, 4, &ar->pm1.cnt.io);
+
+ fw_cfg = fw_cfg_find();
+ if (fw_cfg) {
+ uint8_t suspend[6] = {128, 0, 0, 129, 128, 128};
+ suspend[3] = 1 | ((!disable_s3) << 7);
+ suspend[4] = s4_val | ((!disable_s4) << 7);
+
+ fw_cfg_add_file(fw_cfg, "etc/system-states", g_memdup(suspend, 6), 6);
+ }
+}
+
+void acpi_pm1_cnt_reset(ACPIREGS *ar)
+{
+ ar->pm1.cnt.cnt = 0;
+ if (ar->pm1.cnt.acpi_only) {
+ ar->pm1.cnt.cnt |= ACPI_BITMASK_SCI_ENABLE;
+ }
+}
+
+/* ACPI GPE */
+void acpi_gpe_init(ACPIREGS *ar, uint8_t len)
+{
+ ar->gpe.len = len;
+ /* Only first len / 2 bytes are ever used,
+ * but the caller in ich9.c migrates full len bytes.
+ * TODO: fix ich9.c and drop the extra allocation.
+ */
+ ar->gpe.sts = g_malloc0(len);
+ ar->gpe.en = g_malloc0(len);
+}
+
+void acpi_gpe_reset(ACPIREGS *ar)
+{
+ memset(ar->gpe.sts, 0, ar->gpe.len / 2);
+ memset(ar->gpe.en, 0, ar->gpe.len / 2);
+}
+
+static uint8_t *acpi_gpe_ioport_get_ptr(ACPIREGS *ar, uint32_t addr)
+{
+ uint8_t *cur = NULL;
+
+ if (addr < ar->gpe.len / 2) {
+ cur = ar->gpe.sts + addr;
+ } else if (addr < ar->gpe.len) {
+ cur = ar->gpe.en + addr - ar->gpe.len / 2;
+ } else {
+ abort();
+ }
+
+ return cur;
+}
+
+void acpi_gpe_ioport_writeb(ACPIREGS *ar, uint32_t addr, uint32_t val)
+{
+ uint8_t *cur;
+
+ cur = acpi_gpe_ioport_get_ptr(ar, addr);
+ if (addr < ar->gpe.len / 2) {
+ /* GPE_STS */
+ *cur = (*cur) & ~val;
+ } else if (addr < ar->gpe.len) {
+ /* GPE_EN */
+ *cur = val;
+ } else {
+ abort();
+ }
+}
+
+uint32_t acpi_gpe_ioport_readb(ACPIREGS *ar, uint32_t addr)
+{
+ uint8_t *cur;
+ uint32_t val;
+
+ cur = acpi_gpe_ioport_get_ptr(ar, addr);
+ val = 0;
+ if (cur != NULL) {
+ val = *cur;
+ }
+
+ return val;
+}
+
+void acpi_send_gpe_event(ACPIREGS *ar, qemu_irq irq,
+ AcpiEventStatusBits status)
+{
+ ar->gpe.sts[0] |= status;
+ acpi_update_sci(ar, irq);
+}
+
+void acpi_update_sci(ACPIREGS *regs, qemu_irq irq)
+{
+ int sci_level, pm1a_sts;
+
+ pm1a_sts = acpi_pm1_evt_get_sts(regs);
+
+ sci_level = ((pm1a_sts &
+ regs->pm1.evt.en & ACPI_BITMASK_PM1_COMMON_ENABLED) != 0) ||
+ ((regs->gpe.sts[0] & regs->gpe.en[0]) != 0);
+
+ qemu_set_irq(irq, sci_level);
+
+ /* schedule a timer interruption if needed */
+ acpi_pm_tmr_update(regs,
+ (regs->pm1.evt.en & ACPI_BITMASK_TIMER_ENABLE) &&
+ !(pm1a_sts & ACPI_BITMASK_TIMER_STATUS));
+}
diff --git a/hw/acpi/cpu.c b/hw/acpi/cpu.c
new file mode 100644
index 000000000..b20903ea3
--- /dev/null
+++ b/hw/acpi/cpu.c
@@ -0,0 +1,711 @@
+#include "qemu/osdep.h"
+#include "migration/vmstate.h"
+#include "hw/acpi/cpu.h"
+#include "qapi/error.h"
+#include "qapi/qapi-events-acpi.h"
+#include "trace.h"
+#include "sysemu/numa.h"
+
+#define ACPI_CPU_HOTPLUG_REG_LEN 12
+#define ACPI_CPU_SELECTOR_OFFSET_WR 0
+#define ACPI_CPU_FLAGS_OFFSET_RW 4
+#define ACPI_CPU_CMD_OFFSET_WR 5
+#define ACPI_CPU_CMD_DATA_OFFSET_RW 8
+#define ACPI_CPU_CMD_DATA2_OFFSET_R 0
+
+#define OVMF_CPUHP_SMI_CMD 4
+
+enum {
+ CPHP_GET_NEXT_CPU_WITH_EVENT_CMD = 0,
+ CPHP_OST_EVENT_CMD = 1,
+ CPHP_OST_STATUS_CMD = 2,
+ CPHP_GET_CPU_ID_CMD = 3,
+ CPHP_CMD_MAX
+};
+
+static ACPIOSTInfo *acpi_cpu_device_status(int idx, AcpiCpuStatus *cdev)
+{
+ ACPIOSTInfo *info = g_new0(ACPIOSTInfo, 1);
+
+ info->slot_type = ACPI_SLOT_TYPE_CPU;
+ info->slot = g_strdup_printf("%d", idx);
+ info->source = cdev->ost_event;
+ info->status = cdev->ost_status;
+ if (cdev->cpu) {
+ DeviceState *dev = DEVICE(cdev->cpu);
+ if (dev->id) {
+ info->device = g_strdup(dev->id);
+ info->has_device = true;
+ }
+ }
+ return info;
+}
+
+void acpi_cpu_ospm_status(CPUHotplugState *cpu_st, ACPIOSTInfoList ***list)
+{
+ ACPIOSTInfoList ***tail = list;
+ int i;
+
+ for (i = 0; i < cpu_st->dev_count; i++) {
+ QAPI_LIST_APPEND(*tail, acpi_cpu_device_status(i, &cpu_st->devs[i]));
+ }
+}
+
+static uint64_t cpu_hotplug_rd(void *opaque, hwaddr addr, unsigned size)
+{
+ uint64_t val = 0;
+ CPUHotplugState *cpu_st = opaque;
+ AcpiCpuStatus *cdev;
+
+ if (cpu_st->selector >= cpu_st->dev_count) {
+ return val;
+ }
+
+ cdev = &cpu_st->devs[cpu_st->selector];
+ switch (addr) {
+ case ACPI_CPU_FLAGS_OFFSET_RW: /* pack and return is_* fields */
+ val |= cdev->cpu ? 1 : 0;
+ val |= cdev->is_inserting ? 2 : 0;
+ val |= cdev->is_removing ? 4 : 0;
+ val |= cdev->fw_remove ? 16 : 0;
+ trace_cpuhp_acpi_read_flags(cpu_st->selector, val);
+ break;
+ case ACPI_CPU_CMD_DATA_OFFSET_RW:
+ switch (cpu_st->command) {
+ case CPHP_GET_NEXT_CPU_WITH_EVENT_CMD:
+ val = cpu_st->selector;
+ break;
+ case CPHP_GET_CPU_ID_CMD:
+ val = cdev->arch_id & 0xFFFFFFFF;
+ break;
+ default:
+ break;
+ }
+ trace_cpuhp_acpi_read_cmd_data(cpu_st->selector, val);
+ break;
+ case ACPI_CPU_CMD_DATA2_OFFSET_R:
+ switch (cpu_st->command) {
+ case CPHP_GET_NEXT_CPU_WITH_EVENT_CMD:
+ val = 0;
+ break;
+ case CPHP_GET_CPU_ID_CMD:
+ val = cdev->arch_id >> 32;
+ break;
+ default:
+ break;
+ }
+ trace_cpuhp_acpi_read_cmd_data2(cpu_st->selector, val);
+ break;
+ default:
+ break;
+ }
+ return val;
+}
+
+static void cpu_hotplug_wr(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ CPUHotplugState *cpu_st = opaque;
+ AcpiCpuStatus *cdev;
+ ACPIOSTInfo *info;
+
+ assert(cpu_st->dev_count);
+
+ if (addr) {
+ if (cpu_st->selector >= cpu_st->dev_count) {
+ trace_cpuhp_acpi_invalid_idx_selected(cpu_st->selector);
+ return;
+ }
+ }
+
+ switch (addr) {
+ case ACPI_CPU_SELECTOR_OFFSET_WR: /* current CPU selector */
+ cpu_st->selector = data;
+ trace_cpuhp_acpi_write_idx(cpu_st->selector);
+ break;
+ case ACPI_CPU_FLAGS_OFFSET_RW: /* set is_* fields */
+ cdev = &cpu_st->devs[cpu_st->selector];
+ if (data & 2) { /* clear insert event */
+ cdev->is_inserting = false;
+ trace_cpuhp_acpi_clear_inserting_evt(cpu_st->selector);
+ } else if (data & 4) { /* clear remove event */
+ cdev->is_removing = false;
+ trace_cpuhp_acpi_clear_remove_evt(cpu_st->selector);
+ } else if (data & 8) {
+ DeviceState *dev = NULL;
+ HotplugHandler *hotplug_ctrl = NULL;
+
+ if (!cdev->cpu || cdev->cpu == first_cpu) {
+ trace_cpuhp_acpi_ejecting_invalid_cpu(cpu_st->selector);
+ break;
+ }
+
+ trace_cpuhp_acpi_ejecting_cpu(cpu_st->selector);
+ dev = DEVICE(cdev->cpu);
+ hotplug_ctrl = qdev_get_hotplug_handler(dev);
+ hotplug_handler_unplug(hotplug_ctrl, dev, NULL);
+ object_unparent(OBJECT(dev));
+ cdev->fw_remove = false;
+ } else if (data & 16) {
+ if (!cdev->cpu || cdev->cpu == first_cpu) {
+ trace_cpuhp_acpi_fw_remove_invalid_cpu(cpu_st->selector);
+ break;
+ }
+ trace_cpuhp_acpi_fw_remove_cpu(cpu_st->selector);
+ cdev->fw_remove = true;
+ }
+ break;
+ case ACPI_CPU_CMD_OFFSET_WR:
+ trace_cpuhp_acpi_write_cmd(cpu_st->selector, data);
+ if (data < CPHP_CMD_MAX) {
+ cpu_st->command = data;
+ if (cpu_st->command == CPHP_GET_NEXT_CPU_WITH_EVENT_CMD) {
+ uint32_t iter = cpu_st->selector;
+
+ do {
+ cdev = &cpu_st->devs[iter];
+ if (cdev->is_inserting || cdev->is_removing ||
+ cdev->fw_remove) {
+ cpu_st->selector = iter;
+ trace_cpuhp_acpi_cpu_has_events(cpu_st->selector,
+ cdev->is_inserting, cdev->is_removing);
+ break;
+ }
+ iter = iter + 1 < cpu_st->dev_count ? iter + 1 : 0;
+ } while (iter != cpu_st->selector);
+ }
+ }
+ break;
+ case ACPI_CPU_CMD_DATA_OFFSET_RW:
+ switch (cpu_st->command) {
+ case CPHP_OST_EVENT_CMD: {
+ cdev = &cpu_st->devs[cpu_st->selector];
+ cdev->ost_event = data;
+ trace_cpuhp_acpi_write_ost_ev(cpu_st->selector, cdev->ost_event);
+ break;
+ }
+ case CPHP_OST_STATUS_CMD: {
+ cdev = &cpu_st->devs[cpu_st->selector];
+ cdev->ost_status = data;
+ info = acpi_cpu_device_status(cpu_st->selector, cdev);
+ qapi_event_send_acpi_device_ost(info);
+ qapi_free_ACPIOSTInfo(info);
+ trace_cpuhp_acpi_write_ost_status(cpu_st->selector,
+ cdev->ost_status);
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps cpu_hotplug_ops = {
+ .read = cpu_hotplug_rd,
+ .write = cpu_hotplug_wr,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+void cpu_hotplug_hw_init(MemoryRegion *as, Object *owner,
+ CPUHotplugState *state, hwaddr base_addr)
+{
+ MachineState *machine = MACHINE(qdev_get_machine());
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+ const CPUArchIdList *id_list;
+ int i;
+
+ assert(mc->possible_cpu_arch_ids);
+ id_list = mc->possible_cpu_arch_ids(machine);
+ state->dev_count = id_list->len;
+ state->devs = g_new0(typeof(*state->devs), state->dev_count);
+ for (i = 0; i < id_list->len; i++) {
+ state->devs[i].cpu = CPU(id_list->cpus[i].cpu);
+ state->devs[i].arch_id = id_list->cpus[i].arch_id;
+ }
+ memory_region_init_io(&state->ctrl_reg, owner, &cpu_hotplug_ops, state,
+ "acpi-cpu-hotplug", ACPI_CPU_HOTPLUG_REG_LEN);
+ memory_region_add_subregion(as, base_addr, &state->ctrl_reg);
+}
+
+static AcpiCpuStatus *get_cpu_status(CPUHotplugState *cpu_st, DeviceState *dev)
+{
+ CPUClass *k = CPU_GET_CLASS(dev);
+ uint64_t cpu_arch_id = k->get_arch_id(CPU(dev));
+ int i;
+
+ for (i = 0; i < cpu_st->dev_count; i++) {
+ if (cpu_arch_id == cpu_st->devs[i].arch_id) {
+ return &cpu_st->devs[i];
+ }
+ }
+ return NULL;
+}
+
+void acpi_cpu_plug_cb(HotplugHandler *hotplug_dev,
+ CPUHotplugState *cpu_st, DeviceState *dev, Error **errp)
+{
+ AcpiCpuStatus *cdev;
+
+ cdev = get_cpu_status(cpu_st, dev);
+ if (!cdev) {
+ return;
+ }
+
+ cdev->cpu = CPU(dev);
+ if (dev->hotplugged) {
+ cdev->is_inserting = true;
+ acpi_send_event(DEVICE(hotplug_dev), ACPI_CPU_HOTPLUG_STATUS);
+ }
+}
+
+void acpi_cpu_unplug_request_cb(HotplugHandler *hotplug_dev,
+ CPUHotplugState *cpu_st,
+ DeviceState *dev, Error **errp)
+{
+ AcpiCpuStatus *cdev;
+
+ cdev = get_cpu_status(cpu_st, dev);
+ if (!cdev) {
+ return;
+ }
+
+ cdev->is_removing = true;
+ acpi_send_event(DEVICE(hotplug_dev), ACPI_CPU_HOTPLUG_STATUS);
+}
+
+void acpi_cpu_unplug_cb(CPUHotplugState *cpu_st,
+ DeviceState *dev, Error **errp)
+{
+ AcpiCpuStatus *cdev;
+
+ cdev = get_cpu_status(cpu_st, dev);
+ if (!cdev) {
+ return;
+ }
+
+ cdev->cpu = NULL;
+}
+
+static const VMStateDescription vmstate_cpuhp_sts = {
+ .name = "CPU hotplug device state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(is_inserting, AcpiCpuStatus),
+ VMSTATE_BOOL(is_removing, AcpiCpuStatus),
+ VMSTATE_UINT32(ost_event, AcpiCpuStatus),
+ VMSTATE_UINT32(ost_status, AcpiCpuStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_cpu_hotplug = {
+ .name = "CPU hotplug state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(selector, CPUHotplugState),
+ VMSTATE_UINT8(command, CPUHotplugState),
+ VMSTATE_STRUCT_VARRAY_POINTER_UINT32(devs, CPUHotplugState, dev_count,
+ vmstate_cpuhp_sts, AcpiCpuStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+#define CPU_NAME_FMT "C%.03X"
+#define CPUHP_RES_DEVICE "PRES"
+#define CPU_LOCK "CPLK"
+#define CPU_STS_METHOD "CSTA"
+#define CPU_SCAN_METHOD "CSCN"
+#define CPU_NOTIFY_METHOD "CTFY"
+#define CPU_EJECT_METHOD "CEJ0"
+#define CPU_OST_METHOD "COST"
+#define CPU_ADDED_LIST "CNEW"
+
+#define CPU_ENABLED "CPEN"
+#define CPU_SELECTOR "CSEL"
+#define CPU_COMMAND "CCMD"
+#define CPU_DATA "CDAT"
+#define CPU_INSERT_EVENT "CINS"
+#define CPU_REMOVE_EVENT "CRMV"
+#define CPU_EJECT_EVENT "CEJ0"
+#define CPU_FW_EJECT_EVENT "CEJF"
+
+void build_cpus_aml(Aml *table, MachineState *machine, CPUHotplugFeatures opts,
+ hwaddr io_base,
+ const char *res_root,
+ const char *event_handler_method)
+{
+ Aml *ifctx;
+ Aml *field;
+ Aml *method;
+ Aml *cpu_ctrl_dev;
+ Aml *cpus_dev;
+ Aml *zero = aml_int(0);
+ Aml *one = aml_int(1);
+ Aml *sb_scope = aml_scope("_SB");
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+ const CPUArchIdList *arch_ids = mc->possible_cpu_arch_ids(machine);
+ char *cphp_res_path = g_strdup_printf("%s." CPUHP_RES_DEVICE, res_root);
+ Object *obj = object_resolve_path_type("", TYPE_ACPI_DEVICE_IF, NULL);
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_GET_CLASS(obj);
+ AcpiDeviceIf *adev = ACPI_DEVICE_IF(obj);
+
+ cpu_ctrl_dev = aml_device("%s", cphp_res_path);
+ {
+ Aml *crs;
+
+ aml_append(cpu_ctrl_dev,
+ aml_name_decl("_HID", aml_eisaid("PNP0A06")));
+ aml_append(cpu_ctrl_dev,
+ aml_name_decl("_UID", aml_string("CPU Hotplug resources")));
+ aml_append(cpu_ctrl_dev, aml_mutex(CPU_LOCK, 0));
+
+ crs = aml_resource_template();
+ aml_append(crs, aml_io(AML_DECODE16, io_base, io_base, 1,
+ ACPI_CPU_HOTPLUG_REG_LEN));
+ aml_append(cpu_ctrl_dev, aml_name_decl("_CRS", crs));
+
+ /* declare CPU hotplug MMIO region with related access fields */
+ aml_append(cpu_ctrl_dev,
+ aml_operation_region("PRST", AML_SYSTEM_IO, aml_int(io_base),
+ ACPI_CPU_HOTPLUG_REG_LEN));
+
+ field = aml_field("PRST", AML_BYTE_ACC, AML_NOLOCK,
+ AML_WRITE_AS_ZEROS);
+ aml_append(field, aml_reserved_field(ACPI_CPU_FLAGS_OFFSET_RW * 8));
+ /* 1 if enabled, read only */
+ aml_append(field, aml_named_field(CPU_ENABLED, 1));
+ /* (read) 1 if has a insert event. (write) 1 to clear event */
+ aml_append(field, aml_named_field(CPU_INSERT_EVENT, 1));
+ /* (read) 1 if has a remove event. (write) 1 to clear event */
+ aml_append(field, aml_named_field(CPU_REMOVE_EVENT, 1));
+ /* initiates device eject, write only */
+ aml_append(field, aml_named_field(CPU_EJECT_EVENT, 1));
+ /* tell firmware to do device eject, write only */
+ aml_append(field, aml_named_field(CPU_FW_EJECT_EVENT, 1));
+ aml_append(field, aml_reserved_field(3));
+ aml_append(field, aml_named_field(CPU_COMMAND, 8));
+ aml_append(cpu_ctrl_dev, field);
+
+ field = aml_field("PRST", AML_DWORD_ACC, AML_NOLOCK, AML_PRESERVE);
+ /* CPU selector, write only */
+ aml_append(field, aml_named_field(CPU_SELECTOR, 32));
+ /* flags + cmd + 2byte align */
+ aml_append(field, aml_reserved_field(4 * 8));
+ aml_append(field, aml_named_field(CPU_DATA, 32));
+ aml_append(cpu_ctrl_dev, field);
+
+ if (opts.has_legacy_cphp) {
+ method = aml_method("_INI", 0, AML_SERIALIZED);
+ /* switch off legacy CPU hotplug HW and use new one,
+ * on reboot system is in new mode and writing 0
+ * in CPU_SELECTOR selects BSP, which is NOP at
+ * the time _INI is called */
+ aml_append(method, aml_store(zero, aml_name(CPU_SELECTOR)));
+ aml_append(cpu_ctrl_dev, method);
+ }
+ }
+ aml_append(sb_scope, cpu_ctrl_dev);
+
+ cpus_dev = aml_device("\\_SB.CPUS");
+ {
+ int i;
+ Aml *ctrl_lock = aml_name("%s.%s", cphp_res_path, CPU_LOCK);
+ Aml *cpu_selector = aml_name("%s.%s", cphp_res_path, CPU_SELECTOR);
+ Aml *is_enabled = aml_name("%s.%s", cphp_res_path, CPU_ENABLED);
+ Aml *cpu_cmd = aml_name("%s.%s", cphp_res_path, CPU_COMMAND);
+ Aml *cpu_data = aml_name("%s.%s", cphp_res_path, CPU_DATA);
+ Aml *ins_evt = aml_name("%s.%s", cphp_res_path, CPU_INSERT_EVENT);
+ Aml *rm_evt = aml_name("%s.%s", cphp_res_path, CPU_REMOVE_EVENT);
+ Aml *ej_evt = aml_name("%s.%s", cphp_res_path, CPU_EJECT_EVENT);
+ Aml *fw_ej_evt = aml_name("%s.%s", cphp_res_path, CPU_FW_EJECT_EVENT);
+
+ aml_append(cpus_dev, aml_name_decl("_HID", aml_string("ACPI0010")));
+ aml_append(cpus_dev, aml_name_decl("_CID", aml_eisaid("PNP0A05")));
+
+ method = aml_method(CPU_NOTIFY_METHOD, 2, AML_NOTSERIALIZED);
+ for (i = 0; i < arch_ids->len; i++) {
+ Aml *cpu = aml_name(CPU_NAME_FMT, i);
+ Aml *uid = aml_arg(0);
+ Aml *event = aml_arg(1);
+
+ ifctx = aml_if(aml_equal(uid, aml_int(i)));
+ {
+ aml_append(ifctx, aml_notify(cpu, event));
+ }
+ aml_append(method, ifctx);
+ }
+ aml_append(cpus_dev, method);
+
+ method = aml_method(CPU_STS_METHOD, 1, AML_SERIALIZED);
+ {
+ Aml *idx = aml_arg(0);
+ Aml *sta = aml_local(0);
+
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ aml_append(method, aml_store(idx, cpu_selector));
+ aml_append(method, aml_store(zero, sta));
+ ifctx = aml_if(aml_equal(is_enabled, one));
+ {
+ aml_append(ifctx, aml_store(aml_int(0xF), sta));
+ }
+ aml_append(method, ifctx);
+ aml_append(method, aml_release(ctrl_lock));
+ aml_append(method, aml_return(sta));
+ }
+ aml_append(cpus_dev, method);
+
+ method = aml_method(CPU_EJECT_METHOD, 1, AML_SERIALIZED);
+ {
+ Aml *idx = aml_arg(0);
+
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ aml_append(method, aml_store(idx, cpu_selector));
+ if (opts.fw_unplugs_cpu) {
+ aml_append(method, aml_store(one, fw_ej_evt));
+ aml_append(method, aml_store(aml_int(OVMF_CPUHP_SMI_CMD),
+ aml_name("%s", opts.smi_path)));
+ } else {
+ aml_append(method, aml_store(one, ej_evt));
+ }
+ aml_append(method, aml_release(ctrl_lock));
+ }
+ aml_append(cpus_dev, method);
+
+ method = aml_method(CPU_SCAN_METHOD, 0, AML_SERIALIZED);
+ {
+ const uint8_t max_cpus_per_pass = 255;
+ Aml *else_ctx;
+ Aml *while_ctx, *while_ctx2;
+ Aml *has_event = aml_local(0);
+ Aml *dev_chk = aml_int(1);
+ Aml *eject_req = aml_int(3);
+ Aml *next_cpu_cmd = aml_int(CPHP_GET_NEXT_CPU_WITH_EVENT_CMD);
+ Aml *num_added_cpus = aml_local(1);
+ Aml *cpu_idx = aml_local(2);
+ Aml *uid = aml_local(3);
+ Aml *has_job = aml_local(4);
+ Aml *new_cpus = aml_name(CPU_ADDED_LIST);
+
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+
+ /*
+ * Windows versions newer than XP (including Windows 10/Windows
+ * Server 2019), do support* VarPackageOp but, it is cripled to hold
+ * the same elements number as old PackageOp.
+ * For compatibility with Windows XP (so it won't crash) use ACPI1.0
+ * PackageOp which can hold max 255 elements.
+ *
+ * use named package as old Windows don't support it in local var
+ */
+ aml_append(method, aml_name_decl(CPU_ADDED_LIST,
+ aml_package(max_cpus_per_pass)));
+
+ aml_append(method, aml_store(zero, uid));
+ aml_append(method, aml_store(one, has_job));
+ /*
+ * CPU_ADDED_LIST can hold limited number of elements, outer loop
+ * allows to process CPUs in batches which let us to handle more
+ * CPUs than CPU_ADDED_LIST can hold.
+ */
+ while_ctx2 = aml_while(aml_equal(has_job, one));
+ {
+ aml_append(while_ctx2, aml_store(zero, has_job));
+
+ aml_append(while_ctx2, aml_store(one, has_event));
+ aml_append(while_ctx2, aml_store(zero, num_added_cpus));
+
+ /*
+ * Scan CPUs, till there are CPUs with events or
+ * CPU_ADDED_LIST capacity is exhausted
+ */
+ while_ctx = aml_while(aml_land(aml_equal(has_event, one),
+ aml_lless(uid, aml_int(arch_ids->len))));
+ {
+ /*
+ * clear loop exit condition, ins_evt/rm_evt checks will
+ * set it to 1 while next_cpu_cmd returns a CPU with events
+ */
+ aml_append(while_ctx, aml_store(zero, has_event));
+
+ aml_append(while_ctx, aml_store(uid, cpu_selector));
+ aml_append(while_ctx, aml_store(next_cpu_cmd, cpu_cmd));
+
+ /*
+ * wrap around case, scan is complete, exit loop.
+ * It happens since events are not cleared in scan loop,
+ * so next_cpu_cmd continues to find already processed CPUs
+ */
+ ifctx = aml_if(aml_lless(cpu_data, uid));
+ {
+ aml_append(ifctx, aml_break());
+ }
+ aml_append(while_ctx, ifctx);
+
+ /*
+ * if CPU_ADDED_LIST is full, exit inner loop and process
+ * collected CPUs
+ */
+ ifctx = aml_if(
+ aml_equal(num_added_cpus, aml_int(max_cpus_per_pass)));
+ {
+ aml_append(ifctx, aml_store(one, has_job));
+ aml_append(ifctx, aml_break());
+ }
+ aml_append(while_ctx, ifctx);
+
+ aml_append(while_ctx, aml_store(cpu_data, uid));
+ ifctx = aml_if(aml_equal(ins_evt, one));
+ {
+ /* cache added CPUs to Notify/Wakeup later */
+ aml_append(ifctx, aml_store(uid,
+ aml_index(new_cpus, num_added_cpus)));
+ aml_append(ifctx, aml_increment(num_added_cpus));
+ aml_append(ifctx, aml_store(one, has_event));
+ }
+ aml_append(while_ctx, ifctx);
+ else_ctx = aml_else();
+ ifctx = aml_if(aml_equal(rm_evt, one));
+ {
+ aml_append(ifctx,
+ aml_call2(CPU_NOTIFY_METHOD, uid, eject_req));
+ aml_append(ifctx, aml_store(one, rm_evt));
+ aml_append(ifctx, aml_store(one, has_event));
+ }
+ aml_append(else_ctx, ifctx);
+ aml_append(while_ctx, else_ctx);
+ aml_append(while_ctx, aml_increment(uid));
+ }
+ aml_append(while_ctx2, while_ctx);
+
+ /*
+ * in case FW negotiated ICH9_LPC_SMI_F_CPU_HOTPLUG_BIT,
+ * make upcall to FW, so it can pull in new CPUs before
+ * OS is notified and wakes them up
+ */
+ if (opts.smi_path) {
+ ifctx = aml_if(aml_lgreater(num_added_cpus, zero));
+ {
+ aml_append(ifctx, aml_store(aml_int(OVMF_CPUHP_SMI_CMD),
+ aml_name("%s", opts.smi_path)));
+ }
+ aml_append(while_ctx2, ifctx);
+ }
+
+ /* Notify OSPM about new CPUs and clear insert events */
+ aml_append(while_ctx2, aml_store(zero, cpu_idx));
+ while_ctx = aml_while(aml_lless(cpu_idx, num_added_cpus));
+ {
+ aml_append(while_ctx,
+ aml_store(aml_derefof(aml_index(new_cpus, cpu_idx)),
+ uid));
+ aml_append(while_ctx,
+ aml_call2(CPU_NOTIFY_METHOD, uid, dev_chk));
+ aml_append(while_ctx, aml_store(uid, aml_debug()));
+ aml_append(while_ctx, aml_store(uid, cpu_selector));
+ aml_append(while_ctx, aml_store(one, ins_evt));
+ aml_append(while_ctx, aml_increment(cpu_idx));
+ }
+ aml_append(while_ctx2, while_ctx);
+ /*
+ * If another batch is needed, then it will resume scanning
+ * exactly at -- and not after -- the last CPU that's currently
+ * in CPU_ADDED_LIST. In other words, the last CPU in
+ * CPU_ADDED_LIST is going to be re-checked. That's OK: we've
+ * just cleared the insert event for *all* CPUs in
+ * CPU_ADDED_LIST, including the last one. So the scan will
+ * simply seek past it.
+ */
+ }
+ aml_append(method, while_ctx2);
+ aml_append(method, aml_release(ctrl_lock));
+ }
+ aml_append(cpus_dev, method);
+
+ method = aml_method(CPU_OST_METHOD, 4, AML_SERIALIZED);
+ {
+ Aml *uid = aml_arg(0);
+ Aml *ev_cmd = aml_int(CPHP_OST_EVENT_CMD);
+ Aml *st_cmd = aml_int(CPHP_OST_STATUS_CMD);
+
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ aml_append(method, aml_store(uid, cpu_selector));
+ aml_append(method, aml_store(ev_cmd, cpu_cmd));
+ aml_append(method, aml_store(aml_arg(1), cpu_data));
+ aml_append(method, aml_store(st_cmd, cpu_cmd));
+ aml_append(method, aml_store(aml_arg(2), cpu_data));
+ aml_append(method, aml_release(ctrl_lock));
+ }
+ aml_append(cpus_dev, method);
+
+ /* build Processor object for each processor */
+ for (i = 0; i < arch_ids->len; i++) {
+ Aml *dev;
+ Aml *uid = aml_int(i);
+ GArray *madt_buf = g_array_new(0, 1, 1);
+ int arch_id = arch_ids->cpus[i].arch_id;
+
+ if (opts.acpi_1_compatible && arch_id < 255) {
+ dev = aml_processor(i, 0, 0, CPU_NAME_FMT, i);
+ } else {
+ dev = aml_device(CPU_NAME_FMT, i);
+ aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0007")));
+ aml_append(dev, aml_name_decl("_UID", uid));
+ }
+
+ method = aml_method("_STA", 0, AML_SERIALIZED);
+ aml_append(method, aml_return(aml_call1(CPU_STS_METHOD, uid)));
+ aml_append(dev, method);
+
+ /* build _MAT object */
+ assert(adevc && adevc->madt_cpu);
+ adevc->madt_cpu(adev, i, arch_ids, madt_buf,
+ true); /* set enabled flag */
+ aml_append(dev, aml_name_decl("_MAT",
+ aml_buffer(madt_buf->len, (uint8_t *)madt_buf->data)));
+ g_array_free(madt_buf, true);
+
+ if (CPU(arch_ids->cpus[i].cpu) != first_cpu) {
+ method = aml_method("_EJ0", 1, AML_NOTSERIALIZED);
+ aml_append(method, aml_call1(CPU_EJECT_METHOD, uid));
+ aml_append(dev, method);
+ }
+
+ method = aml_method("_OST", 3, AML_SERIALIZED);
+ aml_append(method,
+ aml_call4(CPU_OST_METHOD, uid, aml_arg(0),
+ aml_arg(1), aml_arg(2))
+ );
+ aml_append(dev, method);
+
+ /* Linux guests discard SRAT info for non-present CPUs
+ * as a result _PXM is required for all CPUs which might
+ * be hot-plugged. For simplicity, add it for all CPUs.
+ */
+ if (arch_ids->cpus[i].props.has_node_id) {
+ aml_append(dev, aml_name_decl("_PXM",
+ aml_int(arch_ids->cpus[i].props.node_id)));
+ }
+
+ aml_append(cpus_dev, dev);
+ }
+ }
+ aml_append(sb_scope, cpus_dev);
+ aml_append(table, sb_scope);
+
+ method = aml_method(event_handler_method, 0, AML_NOTSERIALIZED);
+ aml_append(method, aml_call0("\\_SB.CPUS." CPU_SCAN_METHOD));
+ aml_append(table, method);
+
+ g_free(cphp_res_path);
+}
diff --git a/hw/acpi/cpu_hotplug.c b/hw/acpi/cpu_hotplug.c
new file mode 100644
index 000000000..53654f863
--- /dev/null
+++ b/hw/acpi/cpu_hotplug.c
@@ -0,0 +1,333 @@
+/*
+ * QEMU ACPI hotplug utilities
+ *
+ * Copyright (C) 2013 Red Hat Inc
+ *
+ * Authors:
+ * Igor Mammedov <imammedo@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.
+ */
+#include "qemu/osdep.h"
+#include "hw/acpi/cpu_hotplug.h"
+#include "qapi/error.h"
+#include "hw/core/cpu.h"
+#include "hw/i386/pc.h"
+#include "hw/pci/pci.h"
+#include "qemu/error-report.h"
+
+#define CPU_EJECT_METHOD "CPEJ"
+#define CPU_MAT_METHOD "CPMA"
+#define CPU_ON_BITMAP "CPON"
+#define CPU_STATUS_METHOD "CPST"
+#define CPU_STATUS_MAP "PRS"
+#define CPU_SCAN_METHOD "PRSC"
+
+static uint64_t cpu_status_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ AcpiCpuHotplug *cpus = opaque;
+ uint64_t val = cpus->sts[addr];
+
+ return val;
+}
+
+static void cpu_status_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ /* firmware never used to write in CPU present bitmap so use
+ this fact as means to switch QEMU into modern CPU hotplug
+ mode by writing 0 at the beginning of legacy CPU bitmap
+ */
+ if (addr == 0 && data == 0) {
+ AcpiCpuHotplug *cpus = opaque;
+ object_property_set_bool(cpus->device, "cpu-hotplug-legacy", false,
+ &error_abort);
+ }
+}
+
+static const MemoryRegionOps AcpiCpuHotplug_ops = {
+ .read = cpu_status_read,
+ .write = cpu_status_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void acpi_set_cpu_present_bit(AcpiCpuHotplug *g, CPUState *cpu)
+{
+ CPUClass *k = CPU_GET_CLASS(cpu);
+ int64_t cpu_id;
+
+ cpu_id = k->get_arch_id(cpu);
+ if ((cpu_id / 8) >= ACPI_GPE_PROC_LEN) {
+ object_property_set_bool(g->device, "cpu-hotplug-legacy", false,
+ &error_abort);
+ return;
+ }
+
+ g->sts[cpu_id / 8] |= (1 << (cpu_id % 8));
+}
+
+void legacy_acpi_cpu_plug_cb(HotplugHandler *hotplug_dev,
+ AcpiCpuHotplug *g, DeviceState *dev, Error **errp)
+{
+ acpi_set_cpu_present_bit(g, CPU(dev));
+ acpi_send_event(DEVICE(hotplug_dev), ACPI_CPU_HOTPLUG_STATUS);
+}
+
+void legacy_acpi_cpu_hotplug_init(MemoryRegion *parent, Object *owner,
+ AcpiCpuHotplug *gpe_cpu, uint16_t base)
+{
+ CPUState *cpu;
+
+ memory_region_init_io(&gpe_cpu->io, owner, &AcpiCpuHotplug_ops,
+ gpe_cpu, "acpi-cpu-hotplug", ACPI_GPE_PROC_LEN);
+ memory_region_add_subregion(parent, base, &gpe_cpu->io);
+ gpe_cpu->device = owner;
+
+ CPU_FOREACH(cpu) {
+ acpi_set_cpu_present_bit(gpe_cpu, cpu);
+ }
+}
+
+void acpi_switch_to_modern_cphp(AcpiCpuHotplug *gpe_cpu,
+ CPUHotplugState *cpuhp_state,
+ uint16_t io_port)
+{
+ MemoryRegion *parent = pci_address_space_io(PCI_DEVICE(gpe_cpu->device));
+
+ memory_region_del_subregion(parent, &gpe_cpu->io);
+ cpu_hotplug_hw_init(parent, gpe_cpu->device, cpuhp_state, io_port);
+}
+
+void build_legacy_cpu_hotplug_aml(Aml *ctx, MachineState *machine,
+ uint16_t io_base)
+{
+ Aml *dev;
+ Aml *crs;
+ Aml *pkg;
+ Aml *field;
+ Aml *method;
+ Aml *if_ctx;
+ Aml *else_ctx;
+ int i, apic_idx;
+ Aml *sb_scope = aml_scope("_SB");
+ uint8_t madt_tmpl[8] = {0x00, 0x08, 0x00, 0x00, 0x00, 0, 0, 0};
+ Aml *cpu_id = aml_arg(1);
+ Aml *apic_id = aml_arg(0);
+ Aml *cpu_on = aml_local(0);
+ Aml *madt = aml_local(1);
+ Aml *cpus_map = aml_name(CPU_ON_BITMAP);
+ Aml *zero = aml_int(0);
+ Aml *one = aml_int(1);
+ MachineClass *mc = MACHINE_GET_CLASS(machine);
+ const CPUArchIdList *apic_ids = mc->possible_cpu_arch_ids(machine);
+ X86MachineState *x86ms = X86_MACHINE(machine);
+
+ /*
+ * _MAT method - creates an madt apic buffer
+ * apic_id = Arg0 = Local APIC ID
+ * cpu_id = Arg1 = Processor ID
+ * cpu_on = Local0 = CPON flag for this cpu
+ * madt = Local1 = Buffer (in madt apic form) to return
+ */
+ method = aml_method(CPU_MAT_METHOD, 2, AML_NOTSERIALIZED);
+ aml_append(method,
+ aml_store(aml_derefof(aml_index(cpus_map, apic_id)), cpu_on));
+ aml_append(method,
+ aml_store(aml_buffer(sizeof(madt_tmpl), madt_tmpl), madt));
+ /* Update the processor id, lapic id, and enable/disable status */
+ aml_append(method, aml_store(cpu_id, aml_index(madt, aml_int(2))));
+ aml_append(method, aml_store(apic_id, aml_index(madt, aml_int(3))));
+ aml_append(method, aml_store(cpu_on, aml_index(madt, aml_int(4))));
+ aml_append(method, aml_return(madt));
+ aml_append(sb_scope, method);
+
+ /*
+ * _STA method - return ON status of cpu
+ * apic_id = Arg0 = Local APIC ID
+ * cpu_on = Local0 = CPON flag for this cpu
+ */
+ method = aml_method(CPU_STATUS_METHOD, 1, AML_NOTSERIALIZED);
+ aml_append(method,
+ aml_store(aml_derefof(aml_index(cpus_map, apic_id)), cpu_on));
+ if_ctx = aml_if(cpu_on);
+ {
+ aml_append(if_ctx, aml_return(aml_int(0xF)));
+ }
+ aml_append(method, if_ctx);
+ else_ctx = aml_else();
+ {
+ aml_append(else_ctx, aml_return(zero));
+ }
+ aml_append(method, else_ctx);
+ aml_append(sb_scope, method);
+
+ method = aml_method(CPU_EJECT_METHOD, 2, AML_NOTSERIALIZED);
+ aml_append(method, aml_sleep(200));
+ aml_append(sb_scope, method);
+
+ method = aml_method(CPU_SCAN_METHOD, 0, AML_NOTSERIALIZED);
+ {
+ Aml *while_ctx, *if_ctx2, *else_ctx2;
+ Aml *bus_check_evt = aml_int(1);
+ Aml *remove_evt = aml_int(3);
+ Aml *status_map = aml_local(5); /* Local5 = active cpu bitmap */
+ Aml *byte = aml_local(2); /* Local2 = last read byte from bitmap */
+ Aml *idx = aml_local(0); /* Processor ID / APIC ID iterator */
+ Aml *is_cpu_on = aml_local(1); /* Local1 = CPON flag for cpu */
+ Aml *status = aml_local(3); /* Local3 = active state for cpu */
+
+ aml_append(method, aml_store(aml_name(CPU_STATUS_MAP), status_map));
+ aml_append(method, aml_store(zero, byte));
+ aml_append(method, aml_store(zero, idx));
+
+ /* While (idx < SizeOf(CPON)) */
+ while_ctx = aml_while(aml_lless(idx, aml_sizeof(cpus_map)));
+ aml_append(while_ctx,
+ aml_store(aml_derefof(aml_index(cpus_map, idx)), is_cpu_on));
+
+ if_ctx = aml_if(aml_and(idx, aml_int(0x07), NULL));
+ {
+ /* Shift down previously read bitmap byte */
+ aml_append(if_ctx, aml_shiftright(byte, one, byte));
+ }
+ aml_append(while_ctx, if_ctx);
+
+ else_ctx = aml_else();
+ {
+ /* Read next byte from cpu bitmap */
+ aml_append(else_ctx, aml_store(aml_derefof(aml_index(status_map,
+ aml_shiftright(idx, aml_int(3), NULL))), byte));
+ }
+ aml_append(while_ctx, else_ctx);
+
+ aml_append(while_ctx, aml_store(aml_and(byte, one, NULL), status));
+ if_ctx = aml_if(aml_lnot(aml_equal(is_cpu_on, status)));
+ {
+ /* State change - update CPON with new state */
+ aml_append(if_ctx, aml_store(status, aml_index(cpus_map, idx)));
+ if_ctx2 = aml_if(aml_equal(status, one));
+ {
+ aml_append(if_ctx2,
+ aml_call2(AML_NOTIFY_METHOD, idx, bus_check_evt));
+ }
+ aml_append(if_ctx, if_ctx2);
+ else_ctx2 = aml_else();
+ {
+ aml_append(else_ctx2,
+ aml_call2(AML_NOTIFY_METHOD, idx, remove_evt));
+ }
+ }
+ aml_append(if_ctx, else_ctx2);
+ aml_append(while_ctx, if_ctx);
+
+ aml_append(while_ctx, aml_increment(idx)); /* go to next cpu */
+ aml_append(method, while_ctx);
+ }
+ aml_append(sb_scope, method);
+
+ /* The current AML generator can cover the APIC ID range [0..255],
+ * inclusive, for VCPU hotplug. */
+ QEMU_BUILD_BUG_ON(ACPI_CPU_HOTPLUG_ID_LIMIT > 256);
+ if (x86ms->apic_id_limit > ACPI_CPU_HOTPLUG_ID_LIMIT) {
+ error_report("max_cpus is too large. APIC ID of last CPU is %u",
+ x86ms->apic_id_limit - 1);
+ exit(1);
+ }
+
+ /* create PCI0.PRES device and its _CRS to reserve CPU hotplug MMIO */
+ dev = aml_device("PCI0." stringify(CPU_HOTPLUG_RESOURCE_DEVICE));
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0A06")));
+ aml_append(dev,
+ aml_name_decl("_UID", aml_string("CPU Hotplug resources"))
+ );
+ /* device present, functioning, decoding, not shown in UI */
+ aml_append(dev, aml_name_decl("_STA", aml_int(0xB)));
+ crs = aml_resource_template();
+ aml_append(crs,
+ aml_io(AML_DECODE16, io_base, io_base, 1, ACPI_GPE_PROC_LEN)
+ );
+ aml_append(dev, aml_name_decl("_CRS", crs));
+ aml_append(sb_scope, dev);
+ /* declare CPU hotplug MMIO region and PRS field to access it */
+ aml_append(sb_scope, aml_operation_region(
+ "PRST", AML_SYSTEM_IO, aml_int(io_base), ACPI_GPE_PROC_LEN));
+ field = aml_field("PRST", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE);
+ aml_append(field, aml_named_field("PRS", 256));
+ aml_append(sb_scope, field);
+
+ /* build Processor object for each processor */
+ for (i = 0; i < apic_ids->len; i++) {
+ int apic_id = apic_ids->cpus[i].arch_id;
+
+ assert(apic_id < ACPI_CPU_HOTPLUG_ID_LIMIT);
+
+ dev = aml_processor(i, 0, 0, "CP%.02X", apic_id);
+
+ method = aml_method("_MAT", 0, AML_NOTSERIALIZED);
+ aml_append(method,
+ aml_return(aml_call2(CPU_MAT_METHOD, aml_int(apic_id), aml_int(i))
+ ));
+ aml_append(dev, method);
+
+ method = aml_method("_STA", 0, AML_NOTSERIALIZED);
+ aml_append(method,
+ aml_return(aml_call1(CPU_STATUS_METHOD, aml_int(apic_id))));
+ aml_append(dev, method);
+
+ method = aml_method("_EJ0", 1, AML_NOTSERIALIZED);
+ aml_append(method,
+ aml_return(aml_call2(CPU_EJECT_METHOD, aml_int(apic_id),
+ aml_arg(0)))
+ );
+ aml_append(dev, method);
+
+ aml_append(sb_scope, dev);
+ }
+
+ /* build this code:
+ * Method(NTFY, 2) {If (LEqual(Arg0, 0x00)) {Notify(CP00, Arg1)} ...}
+ */
+ /* Arg0 = APIC ID */
+ method = aml_method(AML_NOTIFY_METHOD, 2, AML_NOTSERIALIZED);
+ for (i = 0; i < apic_ids->len; i++) {
+ int apic_id = apic_ids->cpus[i].arch_id;
+
+ if_ctx = aml_if(aml_equal(aml_arg(0), aml_int(apic_id)));
+ aml_append(if_ctx,
+ aml_notify(aml_name("CP%.02X", apic_id), aml_arg(1))
+ );
+ aml_append(method, if_ctx);
+ }
+ aml_append(sb_scope, method);
+
+ /* build "Name(CPON, Package() { One, One, ..., Zero, Zero, ... })"
+ *
+ * Note: The ability to create variable-sized packages was first
+ * introduced in ACPI 2.0. ACPI 1.0 only allowed fixed-size packages
+ * ith up to 255 elements. Windows guests up to win2k8 fail when
+ * VarPackageOp is used.
+ */
+ pkg = x86ms->apic_id_limit <= 255 ? aml_package(x86ms->apic_id_limit) :
+ aml_varpackage(x86ms->apic_id_limit);
+
+ for (i = 0, apic_idx = 0; i < apic_ids->len; i++) {
+ int apic_id = apic_ids->cpus[i].arch_id;
+
+ for (; apic_idx < apic_id; apic_idx++) {
+ aml_append(pkg, aml_int(0));
+ }
+ aml_append(pkg, aml_int(apic_ids->cpus[i].cpu ? 1 : 0));
+ apic_idx = apic_id + 1;
+ }
+ aml_append(sb_scope, aml_name_decl(CPU_ON_BITMAP, pkg));
+ aml_append(ctx, sb_scope);
+
+ method = aml_method("\\_GPE._E02", 0, AML_NOTSERIALIZED);
+ aml_append(method, aml_call0("\\_SB." CPU_SCAN_METHOD));
+ aml_append(ctx, method);
+}
diff --git a/hw/acpi/generic_event_device.c b/hw/acpi/generic_event_device.c
new file mode 100644
index 000000000..e28457a7d
--- /dev/null
+++ b/hw/acpi/generic_event_device.c
@@ -0,0 +1,433 @@
+/*
+ *
+ * Copyright (c) 2018 Intel Corporation
+ * Copyright (c) 2019 Huawei Technologies R & D (UK) Ltd
+ * Written by Samuel Ortiz, Shameer Kolothum
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/generic_event_device.h"
+#include "hw/irq.h"
+#include "hw/mem/pc-dimm.h"
+#include "hw/mem/nvdimm.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/error-report.h"
+#include "sysemu/runstate.h"
+
+static const uint32_t ged_supported_events[] = {
+ ACPI_GED_MEM_HOTPLUG_EVT,
+ ACPI_GED_PWR_DOWN_EVT,
+ ACPI_GED_NVDIMM_HOTPLUG_EVT,
+};
+
+/*
+ * The ACPI Generic Event Device (GED) is a hardware-reduced specific
+ * device[ACPI v6.1 Section 5.6.9] that handles all platform events,
+ * including the hotplug ones. Platforms need to specify their own
+ * GED Event bitmap to describe what kind of events they want to support
+ * through GED. This routine uses a single interrupt for the GED device,
+ * relying on IO memory region to communicate the type of device
+ * affected by the interrupt. This way, we can support up to 32 events
+ * with a unique interrupt.
+ */
+void build_ged_aml(Aml *table, const char *name, HotplugHandler *hotplug_dev,
+ uint32_t ged_irq, AmlRegionSpace rs, hwaddr ged_base)
+{
+ AcpiGedState *s = ACPI_GED(hotplug_dev);
+ Aml *crs = aml_resource_template();
+ Aml *evt, *field;
+ Aml *dev = aml_device("%s", name);
+ Aml *evt_sel = aml_local(0);
+ Aml *esel = aml_name(AML_GED_EVT_SEL);
+
+ /* _CRS interrupt */
+ aml_append(crs, aml_interrupt(AML_CONSUMER, AML_EDGE, AML_ACTIVE_HIGH,
+ AML_EXCLUSIVE, &ged_irq, 1));
+
+ aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0013")));
+ aml_append(dev, aml_name_decl("_UID", aml_string(GED_DEVICE)));
+ aml_append(dev, aml_name_decl("_CRS", crs));
+
+ /* Append IO region */
+ aml_append(dev, aml_operation_region(AML_GED_EVT_REG, rs,
+ aml_int(ged_base + ACPI_GED_EVT_SEL_OFFSET),
+ ACPI_GED_EVT_SEL_LEN));
+ field = aml_field(AML_GED_EVT_REG, AML_DWORD_ACC, AML_NOLOCK,
+ AML_WRITE_AS_ZEROS);
+ aml_append(field, aml_named_field(AML_GED_EVT_SEL,
+ ACPI_GED_EVT_SEL_LEN * BITS_PER_BYTE));
+ aml_append(dev, field);
+
+ /*
+ * For each GED event we:
+ * - Add a conditional block for each event, inside a loop.
+ * - Call a method for each supported GED event type.
+ *
+ * The resulting ASL code looks like:
+ *
+ * Local0 = ESEL
+ * If ((Local0 & One) == One)
+ * {
+ * MethodEvent0()
+ * }
+ *
+ * If ((Local0 & 0x2) == 0x2)
+ * {
+ * MethodEvent1()
+ * }
+ * ...
+ */
+ evt = aml_method("_EVT", 1, AML_SERIALIZED);
+ {
+ Aml *if_ctx;
+ uint32_t i;
+ uint32_t ged_events = ctpop32(s->ged_event_bitmap);
+
+ /* Local0 = ESEL */
+ aml_append(evt, aml_store(esel, evt_sel));
+
+ for (i = 0; i < ARRAY_SIZE(ged_supported_events) && ged_events; i++) {
+ uint32_t event = s->ged_event_bitmap & ged_supported_events[i];
+
+ if (!event) {
+ continue;
+ }
+
+ if_ctx = aml_if(aml_equal(aml_and(evt_sel, aml_int(event), NULL),
+ aml_int(event)));
+ switch (event) {
+ case ACPI_GED_MEM_HOTPLUG_EVT:
+ aml_append(if_ctx, aml_call0(MEMORY_DEVICES_CONTAINER "."
+ MEMORY_SLOT_SCAN_METHOD));
+ break;
+ case ACPI_GED_PWR_DOWN_EVT:
+ aml_append(if_ctx,
+ aml_notify(aml_name(ACPI_POWER_BUTTON_DEVICE),
+ aml_int(0x80)));
+ break;
+ case ACPI_GED_NVDIMM_HOTPLUG_EVT:
+ aml_append(if_ctx,
+ aml_notify(aml_name("\\_SB.NVDR"),
+ aml_int(0x80)));
+ break;
+ default:
+ /*
+ * Please make sure all the events in ged_supported_events[]
+ * are handled above.
+ */
+ g_assert_not_reached();
+ }
+
+ aml_append(evt, if_ctx);
+ ged_events--;
+ }
+
+ if (ged_events) {
+ error_report("Unsupported events specified");
+ abort();
+ }
+ }
+
+ /* Append _EVT method */
+ aml_append(dev, evt);
+
+ aml_append(table, dev);
+}
+
+void acpi_dsdt_add_power_button(Aml *scope)
+{
+ Aml *dev = aml_device(ACPI_POWER_BUTTON_DEVICE);
+ aml_append(dev, aml_name_decl("_HID", aml_string("PNP0C0C")));
+ aml_append(dev, aml_name_decl("_UID", aml_int(0)));
+ aml_append(scope, dev);
+}
+
+/* Memory read by the GED _EVT AML dynamic method */
+static uint64_t ged_evt_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint64_t val = 0;
+ GEDState *ged_st = opaque;
+
+ switch (addr) {
+ case ACPI_GED_EVT_SEL_OFFSET:
+ /* Read the selector value and reset it */
+ val = ged_st->sel;
+ ged_st->sel = 0;
+ break;
+ default:
+ break;
+ }
+
+ return val;
+}
+
+/* Nothing is expected to be written to the GED memory region */
+static void ged_evt_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+}
+
+static const MemoryRegionOps ged_evt_ops = {
+ .read = ged_evt_read,
+ .write = ged_evt_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static uint64_t ged_regs_read(void *opaque, hwaddr addr, unsigned size)
+{
+ return 0;
+}
+
+static void ged_regs_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ bool slp_en;
+ int slp_typ;
+
+ switch (addr) {
+ case ACPI_GED_REG_SLEEP_CTL:
+ slp_typ = (data >> 2) & 0x07;
+ slp_en = (data >> 5) & 0x01;
+ if (slp_en && slp_typ == 5) {
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ }
+ return;
+ case ACPI_GED_REG_SLEEP_STS:
+ return;
+ case ACPI_GED_REG_RESET:
+ if (data == ACPI_GED_RESET_VALUE) {
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ }
+ return;
+ }
+}
+
+static const MemoryRegionOps ged_regs_ops = {
+ .read = ged_regs_read,
+ .write = ged_regs_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void acpi_ged_device_plug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ AcpiGedState *s = ACPI_GED(hotplug_dev);
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ if (object_dynamic_cast(OBJECT(dev), TYPE_NVDIMM)) {
+ nvdimm_acpi_plug_cb(hotplug_dev, dev);
+ } else {
+ acpi_memory_plug_cb(hotplug_dev, &s->memhp_state, dev, errp);
+ }
+ } else {
+ error_setg(errp, "virt: device plug request for unsupported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void acpi_ged_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ AcpiGedState *s = ACPI_GED(hotplug_dev);
+
+ if ((object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM) &&
+ !(object_dynamic_cast(OBJECT(dev), TYPE_NVDIMM)))) {
+ acpi_memory_unplug_request_cb(hotplug_dev, &s->memhp_state, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug request for unsupported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void acpi_ged_unplug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ AcpiGedState *s = ACPI_GED(hotplug_dev);
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_cb(&s->memhp_state, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug for unsupported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void acpi_ged_send_event(AcpiDeviceIf *adev, AcpiEventStatusBits ev)
+{
+ AcpiGedState *s = ACPI_GED(adev);
+ GEDState *ged_st = &s->ged_state;
+ uint32_t sel;
+
+ if (ev & ACPI_MEMORY_HOTPLUG_STATUS) {
+ sel = ACPI_GED_MEM_HOTPLUG_EVT;
+ } else if (ev & ACPI_POWER_DOWN_STATUS) {
+ sel = ACPI_GED_PWR_DOWN_EVT;
+ } else if (ev & ACPI_NVDIMM_HOTPLUG_STATUS) {
+ sel = ACPI_GED_NVDIMM_HOTPLUG_EVT;
+ } else {
+ /* Unknown event. Return without generating interrupt. */
+ warn_report("GED: Unsupported event %d. No irq injected", ev);
+ return;
+ }
+
+ /*
+ * Set the GED selector field to communicate the event type.
+ * This will be read by GED aml code to select the appropriate
+ * event method.
+ */
+ ged_st->sel |= sel;
+
+ /* Trigger the event by sending an interrupt to the guest. */
+ qemu_irq_pulse(s->irq);
+}
+
+static Property acpi_ged_properties[] = {
+ DEFINE_PROP_UINT32("ged-event", AcpiGedState, ged_event_bitmap, 0),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_memhp_state = {
+ .name = "acpi-ged/memhp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_MEMORY_HOTPLUG(memhp_state, AcpiGedState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ged_state = {
+ .name = "acpi-ged-state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(sel, GEDState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_ghes = {
+ .name = "acpi-ghes",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(ghes_addr_le, AcpiGhesState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static bool ghes_needed(void *opaque)
+{
+ AcpiGedState *s = opaque;
+ return s->ghes_state.ghes_addr_le;
+}
+
+static const VMStateDescription vmstate_ghes_state = {
+ .name = "acpi-ged/ghes",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ghes_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(ghes_state, AcpiGedState, 1,
+ vmstate_ghes, AcpiGhesState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_acpi_ged = {
+ .name = "acpi-ged",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(ged_state, AcpiGedState, 1, vmstate_ged_state, GEDState),
+ VMSTATE_END_OF_LIST(),
+ },
+ .subsections = (const VMStateDescription * []) {
+ &vmstate_memhp_state,
+ &vmstate_ghes_state,
+ NULL
+ }
+};
+
+static void acpi_ged_initfn(Object *obj)
+{
+ DeviceState *dev = DEVICE(obj);
+ AcpiGedState *s = ACPI_GED(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ GEDState *ged_st = &s->ged_state;
+
+ memory_region_init_io(&ged_st->evt, obj, &ged_evt_ops, ged_st,
+ TYPE_ACPI_GED, ACPI_GED_EVT_SEL_LEN);
+ sysbus_init_mmio(sbd, &ged_st->evt);
+
+ sysbus_init_irq(sbd, &s->irq);
+
+ s->memhp_state.is_enabled = true;
+ /*
+ * GED handles memory hotplug event and acpi-mem-hotplug
+ * memory region gets initialized here. Create an exclusive
+ * container for memory hotplug IO and expose it as GED sysbus
+ * MMIO so that boards can map it separately.
+ */
+ memory_region_init(&s->container_memhp, OBJECT(dev), "memhp container",
+ MEMORY_HOTPLUG_IO_LEN);
+ sysbus_init_mmio(sbd, &s->container_memhp);
+ acpi_memory_hotplug_init(&s->container_memhp, OBJECT(dev),
+ &s->memhp_state, 0);
+
+ memory_region_init_io(&ged_st->regs, obj, &ged_regs_ops, ged_st,
+ TYPE_ACPI_GED "-regs", ACPI_GED_REG_COUNT);
+ sysbus_init_mmio(sbd, &ged_st->regs);
+}
+
+static void acpi_ged_class_init(ObjectClass *class, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(class);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(class);
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(class);
+
+ dc->desc = "ACPI Generic Event Device";
+ device_class_set_props(dc, acpi_ged_properties);
+ dc->vmsd = &vmstate_acpi_ged;
+
+ hc->plug = acpi_ged_device_plug_cb;
+ hc->unplug_request = acpi_ged_unplug_request_cb;
+ hc->unplug = acpi_ged_unplug_cb;
+
+ adevc->send_event = acpi_ged_send_event;
+}
+
+static const TypeInfo acpi_ged_info = {
+ .name = TYPE_ACPI_GED,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(AcpiGedState),
+ .instance_init = acpi_ged_initfn,
+ .class_init = acpi_ged_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { TYPE_ACPI_DEVICE_IF },
+ { }
+ }
+};
+
+static void acpi_ged_register_types(void)
+{
+ type_register_static(&acpi_ged_info);
+}
+
+type_init(acpi_ged_register_types)
diff --git a/hw/acpi/ghes-stub.c b/hw/acpi/ghes-stub.c
new file mode 100644
index 000000000..c315de180
--- /dev/null
+++ b/hw/acpi/ghes-stub.c
@@ -0,0 +1,22 @@
+/*
+ * Support for generating APEI tables and recording CPER for Guests:
+ * stub functions.
+ *
+ * Copyright (c) 2021 Linaro, Ltd
+ *
+ * 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 "hw/acpi/ghes.h"
+
+int acpi_ghes_record_errors(uint8_t source_id, uint64_t physical_address)
+{
+ return -1;
+}
+
+bool acpi_ghes_present(void)
+{
+ return false;
+}
diff --git a/hw/acpi/ghes.c b/hw/acpi/ghes.c
new file mode 100644
index 000000000..45d9a809c
--- /dev/null
+++ b/hw/acpi/ghes.c
@@ -0,0 +1,460 @@
+/*
+ * Support for generating APEI tables and recording CPER for Guests
+ *
+ * Copyright (c) 2020 HUAWEI TECHNOLOGIES CO., LTD.
+ *
+ * Author: Dongjiu Geng <gengdongjiu@huawei.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"
+#include "qemu/units.h"
+#include "hw/acpi/ghes.h"
+#include "hw/acpi/aml-build.h"
+#include "qemu/error-report.h"
+#include "hw/acpi/generic_event_device.h"
+#include "hw/nvram/fw_cfg.h"
+#include "qemu/uuid.h"
+
+#define ACPI_GHES_ERRORS_FW_CFG_FILE "etc/hardware_errors"
+#define ACPI_GHES_DATA_ADDR_FW_CFG_FILE "etc/hardware_errors_addr"
+
+/* The max size in bytes for one error block */
+#define ACPI_GHES_MAX_RAW_DATA_LENGTH (1 * KiB)
+
+/* Now only support ARMv8 SEA notification type error source */
+#define ACPI_GHES_ERROR_SOURCE_COUNT 1
+
+/* Generic Hardware Error Source version 2 */
+#define ACPI_GHES_SOURCE_GENERIC_ERROR_V2 10
+
+/* Address offset in Generic Address Structure(GAS) */
+#define GAS_ADDR_OFFSET 4
+
+/*
+ * The total size of Generic Error Data Entry
+ * ACPI 6.1/6.2: 18.3.2.7.1 Generic Error Data,
+ * Table 18-343 Generic Error Data Entry
+ */
+#define ACPI_GHES_DATA_LENGTH 72
+
+/* The memory section CPER size, UEFI 2.6: N.2.5 Memory Error Section */
+#define ACPI_GHES_MEM_CPER_LENGTH 80
+
+/* Masks for block_status flags */
+#define ACPI_GEBS_UNCORRECTABLE 1
+
+/*
+ * Total size for Generic Error Status Block except Generic Error Data Entries
+ * ACPI 6.2: 18.3.2.7.1 Generic Error Data,
+ * Table 18-380 Generic Error Status Block
+ */
+#define ACPI_GHES_GESB_SIZE 20
+
+/*
+ * Values for error_severity field
+ */
+enum AcpiGenericErrorSeverity {
+ ACPI_CPER_SEV_RECOVERABLE = 0,
+ ACPI_CPER_SEV_FATAL = 1,
+ ACPI_CPER_SEV_CORRECTED = 2,
+ ACPI_CPER_SEV_NONE = 3,
+};
+
+/*
+ * Hardware Error Notification
+ * ACPI 4.0: 17.3.2.7 Hardware Error Notification
+ * Composes dummy Hardware Error Notification descriptor of specified type
+ */
+static void build_ghes_hw_error_notification(GArray *table, const uint8_t type)
+{
+ /* Type */
+ build_append_int_noprefix(table, type, 1);
+ /*
+ * Length:
+ * Total length of the structure in bytes
+ */
+ build_append_int_noprefix(table, 28, 1);
+ /* Configuration Write Enable */
+ build_append_int_noprefix(table, 0, 2);
+ /* Poll Interval */
+ build_append_int_noprefix(table, 0, 4);
+ /* Vector */
+ build_append_int_noprefix(table, 0, 4);
+ /* Switch To Polling Threshold Value */
+ build_append_int_noprefix(table, 0, 4);
+ /* Switch To Polling Threshold Window */
+ build_append_int_noprefix(table, 0, 4);
+ /* Error Threshold Value */
+ build_append_int_noprefix(table, 0, 4);
+ /* Error Threshold Window */
+ build_append_int_noprefix(table, 0, 4);
+}
+
+/*
+ * Generic Error Data Entry
+ * ACPI 6.1: 18.3.2.7.1 Generic Error Data
+ */
+static void acpi_ghes_generic_error_data(GArray *table,
+ const uint8_t *section_type, uint32_t error_severity,
+ uint8_t validation_bits, uint8_t flags,
+ uint32_t error_data_length, QemuUUID fru_id,
+ uint64_t time_stamp)
+{
+ const uint8_t fru_text[20] = {0};
+
+ /* Section Type */
+ g_array_append_vals(table, section_type, 16);
+
+ /* Error Severity */
+ build_append_int_noprefix(table, error_severity, 4);
+ /* Revision */
+ build_append_int_noprefix(table, 0x300, 2);
+ /* Validation Bits */
+ build_append_int_noprefix(table, validation_bits, 1);
+ /* Flags */
+ build_append_int_noprefix(table, flags, 1);
+ /* Error Data Length */
+ build_append_int_noprefix(table, error_data_length, 4);
+
+ /* FRU Id */
+ g_array_append_vals(table, fru_id.data, ARRAY_SIZE(fru_id.data));
+
+ /* FRU Text */
+ g_array_append_vals(table, fru_text, sizeof(fru_text));
+
+ /* Timestamp */
+ build_append_int_noprefix(table, time_stamp, 8);
+}
+
+/*
+ * Generic Error Status Block
+ * ACPI 6.1: 18.3.2.7.1 Generic Error Data
+ */
+static void acpi_ghes_generic_error_status(GArray *table, uint32_t block_status,
+ uint32_t raw_data_offset, uint32_t raw_data_length,
+ uint32_t data_length, uint32_t error_severity)
+{
+ /* Block Status */
+ build_append_int_noprefix(table, block_status, 4);
+ /* Raw Data Offset */
+ build_append_int_noprefix(table, raw_data_offset, 4);
+ /* Raw Data Length */
+ build_append_int_noprefix(table, raw_data_length, 4);
+ /* Data Length */
+ build_append_int_noprefix(table, data_length, 4);
+ /* Error Severity */
+ build_append_int_noprefix(table, error_severity, 4);
+}
+
+/* UEFI 2.6: N.2.5 Memory Error Section */
+static void acpi_ghes_build_append_mem_cper(GArray *table,
+ uint64_t error_physical_addr)
+{
+ /*
+ * Memory Error Record
+ */
+
+ /* Validation Bits */
+ build_append_int_noprefix(table,
+ (1ULL << 14) | /* Type Valid */
+ (1ULL << 1) /* Physical Address Valid */,
+ 8);
+ /* Error Status */
+ build_append_int_noprefix(table, 0, 8);
+ /* Physical Address */
+ build_append_int_noprefix(table, error_physical_addr, 8);
+ /* Skip all the detailed information normally found in such a record */
+ build_append_int_noprefix(table, 0, 48);
+ /* Memory Error Type */
+ build_append_int_noprefix(table, 0 /* Unknown error */, 1);
+ /* Skip all the detailed information normally found in such a record */
+ build_append_int_noprefix(table, 0, 7);
+}
+
+static int acpi_ghes_record_mem_error(uint64_t error_block_address,
+ uint64_t error_physical_addr)
+{
+ GArray *block;
+
+ /* Memory Error Section Type */
+ const uint8_t uefi_cper_mem_sec[] =
+ UUID_LE(0xA5BC1114, 0x6F64, 0x4EDE, 0xB8, 0x63, 0x3E, 0x83, \
+ 0xED, 0x7C, 0x83, 0xB1);
+
+ /* invalid fru id: ACPI 4.0: 17.3.2.6.1 Generic Error Data,
+ * Table 17-13 Generic Error Data Entry
+ */
+ QemuUUID fru_id = {};
+ uint32_t data_length;
+
+ block = g_array_new(false, true /* clear */, 1);
+
+ /* This is the length if adding a new generic error data entry*/
+ data_length = ACPI_GHES_DATA_LENGTH + ACPI_GHES_MEM_CPER_LENGTH;
+ /*
+ * It should not run out of the preallocated memory if adding a new generic
+ * error data entry
+ */
+ assert((data_length + ACPI_GHES_GESB_SIZE) <=
+ ACPI_GHES_MAX_RAW_DATA_LENGTH);
+
+ /* Build the new generic error status block header */
+ acpi_ghes_generic_error_status(block, ACPI_GEBS_UNCORRECTABLE,
+ 0, 0, data_length, ACPI_CPER_SEV_RECOVERABLE);
+
+ /* Build this new generic error data entry header */
+ acpi_ghes_generic_error_data(block, uefi_cper_mem_sec,
+ ACPI_CPER_SEV_RECOVERABLE, 0, 0,
+ ACPI_GHES_MEM_CPER_LENGTH, fru_id, 0);
+
+ /* Build the memory section CPER for above new generic error data entry */
+ acpi_ghes_build_append_mem_cper(block, error_physical_addr);
+
+ /* Write the generic error data entry into guest memory */
+ cpu_physical_memory_write(error_block_address, block->data, block->len);
+
+ g_array_free(block, true);
+
+ return 0;
+}
+
+/*
+ * Build table for the hardware error fw_cfg blob.
+ * Initialize "etc/hardware_errors" and "etc/hardware_errors_addr" fw_cfg blobs.
+ * See docs/specs/acpi_hest_ghes.rst for blobs format.
+ */
+void build_ghes_error_table(GArray *hardware_errors, BIOSLinker *linker)
+{
+ int i, error_status_block_offset;
+
+ /* Build error_block_address */
+ for (i = 0; i < ACPI_GHES_ERROR_SOURCE_COUNT; i++) {
+ build_append_int_noprefix(hardware_errors, 0, sizeof(uint64_t));
+ }
+
+ /* Build read_ack_register */
+ for (i = 0; i < ACPI_GHES_ERROR_SOURCE_COUNT; i++) {
+ /*
+ * Initialize the value of read_ack_register to 1, so GHES can be
+ * writeable after (re)boot.
+ * ACPI 6.2: 18.3.2.8 Generic Hardware Error Source version 2
+ * (GHESv2 - Type 10)
+ */
+ build_append_int_noprefix(hardware_errors, 1, sizeof(uint64_t));
+ }
+
+ /* Generic Error Status Block offset in the hardware error fw_cfg blob */
+ error_status_block_offset = hardware_errors->len;
+
+ /* Reserve space for Error Status Data Block */
+ acpi_data_push(hardware_errors,
+ ACPI_GHES_MAX_RAW_DATA_LENGTH * ACPI_GHES_ERROR_SOURCE_COUNT);
+
+ /* Tell guest firmware to place hardware_errors blob into RAM */
+ bios_linker_loader_alloc(linker, ACPI_GHES_ERRORS_FW_CFG_FILE,
+ hardware_errors, sizeof(uint64_t), false);
+
+ for (i = 0; i < ACPI_GHES_ERROR_SOURCE_COUNT; i++) {
+ /*
+ * Tell firmware to patch error_block_address entries to point to
+ * corresponding "Generic Error Status Block"
+ */
+ bios_linker_loader_add_pointer(linker,
+ ACPI_GHES_ERRORS_FW_CFG_FILE, sizeof(uint64_t) * i,
+ sizeof(uint64_t), ACPI_GHES_ERRORS_FW_CFG_FILE,
+ error_status_block_offset + i * ACPI_GHES_MAX_RAW_DATA_LENGTH);
+ }
+
+ /*
+ * tell firmware to write hardware_errors GPA into
+ * hardware_errors_addr fw_cfg, once the former has been initialized.
+ */
+ bios_linker_loader_write_pointer(linker, ACPI_GHES_DATA_ADDR_FW_CFG_FILE,
+ 0, sizeof(uint64_t), ACPI_GHES_ERRORS_FW_CFG_FILE, 0);
+}
+
+/* Build Generic Hardware Error Source version 2 (GHESv2) */
+static void build_ghes_v2(GArray *table_data, int source_id, BIOSLinker *linker)
+{
+ uint64_t address_offset;
+ /*
+ * Type:
+ * Generic Hardware Error Source version 2(GHESv2 - Type 10)
+ */
+ build_append_int_noprefix(table_data, ACPI_GHES_SOURCE_GENERIC_ERROR_V2, 2);
+ /* Source Id */
+ build_append_int_noprefix(table_data, source_id, 2);
+ /* Related Source Id */
+ build_append_int_noprefix(table_data, 0xffff, 2);
+ /* Flags */
+ build_append_int_noprefix(table_data, 0, 1);
+ /* Enabled */
+ build_append_int_noprefix(table_data, 1, 1);
+
+ /* Number of Records To Pre-allocate */
+ build_append_int_noprefix(table_data, 1, 4);
+ /* Max Sections Per Record */
+ build_append_int_noprefix(table_data, 1, 4);
+ /* Max Raw Data Length */
+ build_append_int_noprefix(table_data, ACPI_GHES_MAX_RAW_DATA_LENGTH, 4);
+
+ address_offset = table_data->len;
+ /* Error Status Address */
+ build_append_gas(table_data, AML_AS_SYSTEM_MEMORY, 0x40, 0,
+ 4 /* QWord access */, 0);
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE,
+ address_offset + GAS_ADDR_OFFSET, sizeof(uint64_t),
+ ACPI_GHES_ERRORS_FW_CFG_FILE, source_id * sizeof(uint64_t));
+
+ switch (source_id) {
+ case ACPI_HEST_SRC_ID_SEA:
+ /*
+ * Notification Structure
+ * Now only enable ARMv8 SEA notification type
+ */
+ build_ghes_hw_error_notification(table_data, ACPI_GHES_NOTIFY_SEA);
+ break;
+ default:
+ error_report("Not support this error source");
+ abort();
+ }
+
+ /* Error Status Block Length */
+ build_append_int_noprefix(table_data, ACPI_GHES_MAX_RAW_DATA_LENGTH, 4);
+
+ /*
+ * Read Ack Register
+ * ACPI 6.1: 18.3.2.8 Generic Hardware Error Source
+ * version 2 (GHESv2 - Type 10)
+ */
+ address_offset = table_data->len;
+ build_append_gas(table_data, AML_AS_SYSTEM_MEMORY, 0x40, 0,
+ 4 /* QWord access */, 0);
+ bios_linker_loader_add_pointer(linker, ACPI_BUILD_TABLE_FILE,
+ address_offset + GAS_ADDR_OFFSET,
+ sizeof(uint64_t), ACPI_GHES_ERRORS_FW_CFG_FILE,
+ (ACPI_GHES_ERROR_SOURCE_COUNT + source_id) * sizeof(uint64_t));
+
+ /*
+ * Read Ack Preserve field
+ * We only provide the first bit in Read Ack Register to OSPM to write
+ * while the other bits are preserved.
+ */
+ build_append_int_noprefix(table_data, ~0x1ULL, 8);
+ /* Read Ack Write */
+ build_append_int_noprefix(table_data, 0x1, 8);
+}
+
+/* Build Hardware Error Source Table */
+void acpi_build_hest(GArray *table_data, BIOSLinker *linker,
+ const char *oem_id, const char *oem_table_id)
+{
+ AcpiTable table = { .sig = "HEST", .rev = 1,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, table_data);
+
+ /* Error Source Count */
+ build_append_int_noprefix(table_data, ACPI_GHES_ERROR_SOURCE_COUNT, 4);
+ build_ghes_v2(table_data, ACPI_HEST_SRC_ID_SEA, linker);
+
+ acpi_table_end(linker, &table);
+}
+
+void acpi_ghes_add_fw_cfg(AcpiGhesState *ags, FWCfgState *s,
+ GArray *hardware_error)
+{
+ /* Create a read-only fw_cfg file for GHES */
+ fw_cfg_add_file(s, ACPI_GHES_ERRORS_FW_CFG_FILE, hardware_error->data,
+ hardware_error->len);
+
+ /* Create a read-write fw_cfg file for Address */
+ fw_cfg_add_file_callback(s, ACPI_GHES_DATA_ADDR_FW_CFG_FILE, NULL, NULL,
+ NULL, &(ags->ghes_addr_le), sizeof(ags->ghes_addr_le), false);
+
+ ags->present = true;
+}
+
+int acpi_ghes_record_errors(uint8_t source_id, uint64_t physical_address)
+{
+ uint64_t error_block_addr, read_ack_register_addr, read_ack_register = 0;
+ uint64_t start_addr;
+ bool ret = -1;
+ AcpiGedState *acpi_ged_state;
+ AcpiGhesState *ags;
+
+ assert(source_id < ACPI_HEST_SRC_ID_RESERVED);
+
+ acpi_ged_state = ACPI_GED(object_resolve_path_type("", TYPE_ACPI_GED,
+ NULL));
+ g_assert(acpi_ged_state);
+ ags = &acpi_ged_state->ghes_state;
+
+ start_addr = le64_to_cpu(ags->ghes_addr_le);
+
+ if (physical_address) {
+
+ if (source_id < ACPI_HEST_SRC_ID_RESERVED) {
+ start_addr += source_id * sizeof(uint64_t);
+ }
+
+ cpu_physical_memory_read(start_addr, &error_block_addr,
+ sizeof(error_block_addr));
+
+ error_block_addr = le64_to_cpu(error_block_addr);
+
+ read_ack_register_addr = start_addr +
+ ACPI_GHES_ERROR_SOURCE_COUNT * sizeof(uint64_t);
+
+ cpu_physical_memory_read(read_ack_register_addr,
+ &read_ack_register, sizeof(read_ack_register));
+
+ /* zero means OSPM does not acknowledge the error */
+ if (!read_ack_register) {
+ error_report("OSPM does not acknowledge previous error,"
+ " so can not record CPER for current error anymore");
+ } else if (error_block_addr) {
+ read_ack_register = cpu_to_le64(0);
+ /*
+ * Clear the Read Ack Register, OSPM will write it to 1 when
+ * it acknowledges this error.
+ */
+ cpu_physical_memory_write(read_ack_register_addr,
+ &read_ack_register, sizeof(uint64_t));
+
+ ret = acpi_ghes_record_mem_error(error_block_addr,
+ physical_address);
+ } else
+ error_report("can not find Generic Error Status Block");
+ }
+
+ return ret;
+}
+
+bool acpi_ghes_present(void)
+{
+ AcpiGedState *acpi_ged_state;
+ AcpiGhesState *ags;
+
+ acpi_ged_state = ACPI_GED(object_resolve_path_type("", TYPE_ACPI_GED,
+ NULL));
+
+ if (!acpi_ged_state) {
+ return false;
+ }
+ ags = &acpi_ged_state->ghes_state;
+ return ags->present;
+}
diff --git a/hw/acpi/hmat.c b/hw/acpi/hmat.c
new file mode 100644
index 000000000..6913ebf73
--- /dev/null
+++ b/hw/acpi/hmat.c
@@ -0,0 +1,267 @@
+/*
+ * HMAT ACPI Implementation
+ *
+ * Copyright(C) 2019 Intel Corporation.
+ *
+ * Author:
+ * Liu jingqi <jingqi.liu@linux.intel.com>
+ * Tao Xu <tao3.xu@intel.com>
+ *
+ * HMAT is defined in ACPI 6.3: 5.2.27 Heterogeneous Memory Attribute Table
+ * (HMAT)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "sysemu/numa.h"
+#include "hw/acpi/hmat.h"
+
+/*
+ * ACPI 6.3:
+ * 5.2.27.3 Memory Proximity Domain Attributes Structure: Table 5-145
+ */
+static void build_hmat_mpda(GArray *table_data, uint16_t flags,
+ uint32_t initiator, uint32_t mem_node)
+{
+
+ /* Memory Proximity Domain Attributes Structure */
+ /* Type */
+ build_append_int_noprefix(table_data, 0, 2);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 2);
+ /* Length */
+ build_append_int_noprefix(table_data, 40, 4);
+ /* Flags */
+ build_append_int_noprefix(table_data, flags, 2);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 2);
+ /* Proximity Domain for the Attached Initiator */
+ build_append_int_noprefix(table_data, initiator, 4);
+ /* Proximity Domain for the Memory */
+ build_append_int_noprefix(table_data, mem_node, 4);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 4);
+ /*
+ * Reserved:
+ * Previously defined as the Start Address of the System Physical
+ * Address Range. Deprecated since ACPI Spec 6.3.
+ */
+ build_append_int_noprefix(table_data, 0, 8);
+ /*
+ * Reserved:
+ * Previously defined as the Range Length of the region in bytes.
+ * Deprecated since ACPI Spec 6.3.
+ */
+ build_append_int_noprefix(table_data, 0, 8);
+}
+
+/*
+ * ACPI 6.3: 5.2.27.4 System Locality Latency and Bandwidth Information
+ * Structure: Table 5-146
+ */
+static void build_hmat_lb(GArray *table_data, HMAT_LB_Info *hmat_lb,
+ uint32_t num_initiator, uint32_t num_target,
+ uint32_t *initiator_list)
+{
+ int i, index;
+ HMAT_LB_Data *lb_data;
+ uint16_t *entry_list;
+ uint32_t base;
+ /* Length in bytes for entire structure */
+ uint32_t lb_length
+ = 32 /* Table length upto and including Entry Base Unit */
+ + 4 * num_initiator /* Initiator Proximity Domain List */
+ + 4 * num_target /* Target Proximity Domain List */
+ + 2 * num_initiator * num_target; /* Latency or Bandwidth Entries */
+
+ /* Type */
+ build_append_int_noprefix(table_data, 1, 2);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 2);
+ /* Length */
+ build_append_int_noprefix(table_data, lb_length, 4);
+ /* Flags: Bits [3:0] Memory Hierarchy, Bits[7:4] Reserved */
+ assert(!(hmat_lb->hierarchy >> 4));
+ build_append_int_noprefix(table_data, hmat_lb->hierarchy, 1);
+ /* Data Type */
+ build_append_int_noprefix(table_data, hmat_lb->data_type, 1);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 2);
+ /* Number of Initiator Proximity Domains (s) */
+ build_append_int_noprefix(table_data, num_initiator, 4);
+ /* Number of Target Proximity Domains (t) */
+ build_append_int_noprefix(table_data, num_target, 4);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 4);
+
+ /* Entry Base Unit */
+ if (hmat_lb->data_type <= HMAT_LB_DATA_WRITE_LATENCY) {
+ /* Convert latency base from nanoseconds to picosecond */
+ base = hmat_lb->base * 1000;
+ } else {
+ /* Convert bandwidth base from Byte to Megabyte */
+ base = hmat_lb->base / MiB;
+ }
+ build_append_int_noprefix(table_data, base, 8);
+
+ /* Initiator Proximity Domain List */
+ for (i = 0; i < num_initiator; i++) {
+ build_append_int_noprefix(table_data, initiator_list[i], 4);
+ }
+
+ /* Target Proximity Domain List */
+ for (i = 0; i < num_target; i++) {
+ build_append_int_noprefix(table_data, i, 4);
+ }
+
+ /* Latency or Bandwidth Entries */
+ entry_list = g_malloc0(num_initiator * num_target * sizeof(uint16_t));
+ for (i = 0; i < hmat_lb->list->len; i++) {
+ lb_data = &g_array_index(hmat_lb->list, HMAT_LB_Data, i);
+ index = lb_data->initiator * num_target + lb_data->target;
+
+ entry_list[index] = (uint16_t)(lb_data->data / hmat_lb->base);
+ }
+
+ for (i = 0; i < num_initiator * num_target; i++) {
+ build_append_int_noprefix(table_data, entry_list[i], 2);
+ }
+
+ g_free(entry_list);
+}
+
+/* ACPI 6.3: 5.2.27.5 Memory Side Cache Information Structure: Table 5-147 */
+static void build_hmat_cache(GArray *table_data, uint8_t total_levels,
+ NumaHmatCacheOptions *hmat_cache)
+{
+ /*
+ * Cache Attributes: Bits [3:0] – Total Cache Levels
+ * for this Memory Proximity Domain
+ */
+ uint32_t cache_attr = total_levels;
+
+ /* Bits [7:4] : Cache Level described in this structure */
+ cache_attr |= (uint32_t) hmat_cache->level << 4;
+
+ /* Bits [11:8] - Cache Associativity */
+ cache_attr |= (uint32_t) hmat_cache->associativity << 8;
+
+ /* Bits [15:12] - Write Policy */
+ cache_attr |= (uint32_t) hmat_cache->policy << 12;
+
+ /* Bits [31:16] - Cache Line size in bytes */
+ cache_attr |= (uint32_t) hmat_cache->line << 16;
+
+ /* Type */
+ build_append_int_noprefix(table_data, 2, 2);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 2);
+ /* Length */
+ build_append_int_noprefix(table_data, 32, 4);
+ /* Proximity Domain for the Memory */
+ build_append_int_noprefix(table_data, hmat_cache->node_id, 4);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 4);
+ /* Memory Side Cache Size */
+ build_append_int_noprefix(table_data, hmat_cache->size, 8);
+ /* Cache Attributes */
+ build_append_int_noprefix(table_data, cache_attr, 4);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 2);
+ /*
+ * Number of SMBIOS handles (n)
+ * Linux kernel uses Memory Side Cache Information Structure
+ * without SMBIOS entries for now, so set Number of SMBIOS handles
+ * as 0.
+ */
+ build_append_int_noprefix(table_data, 0, 2);
+}
+
+/* Build HMAT sub table structures */
+static void hmat_build_table_structs(GArray *table_data, NumaState *numa_state)
+{
+ uint16_t flags;
+ uint32_t num_initiator = 0;
+ uint32_t initiator_list[MAX_NODES];
+ int i, hierarchy, type, cache_level, total_levels;
+ HMAT_LB_Info *hmat_lb;
+ NumaHmatCacheOptions *hmat_cache;
+
+ build_append_int_noprefix(table_data, 0, 4); /* Reserved */
+
+ for (i = 0; i < numa_state->num_nodes; i++) {
+ flags = 0;
+
+ if (numa_state->nodes[i].initiator < MAX_NODES) {
+ flags |= HMAT_PROXIMITY_INITIATOR_VALID;
+ }
+
+ build_hmat_mpda(table_data, flags, numa_state->nodes[i].initiator, i);
+ }
+
+ for (i = 0; i < numa_state->num_nodes; i++) {
+ if (numa_state->nodes[i].has_cpu) {
+ initiator_list[num_initiator++] = i;
+ }
+ }
+
+ /*
+ * ACPI 6.3: 5.2.27.4 System Locality Latency and Bandwidth Information
+ * Structure: Table 5-146
+ */
+ for (hierarchy = HMAT_LB_MEM_MEMORY;
+ hierarchy <= HMAT_LB_MEM_CACHE_3RD_LEVEL; hierarchy++) {
+ for (type = HMAT_LB_DATA_ACCESS_LATENCY;
+ type <= HMAT_LB_DATA_WRITE_BANDWIDTH; type++) {
+ hmat_lb = numa_state->hmat_lb[hierarchy][type];
+
+ if (hmat_lb && hmat_lb->list->len) {
+ build_hmat_lb(table_data, hmat_lb, num_initiator,
+ numa_state->num_nodes, initiator_list);
+ }
+ }
+ }
+
+ /*
+ * ACPI 6.3: 5.2.27.5 Memory Side Cache Information Structure:
+ * Table 5-147
+ */
+ for (i = 0; i < numa_state->num_nodes; i++) {
+ total_levels = 0;
+ for (cache_level = 1; cache_level < HMAT_LB_LEVELS; cache_level++) {
+ if (numa_state->hmat_cache[i][cache_level]) {
+ total_levels++;
+ }
+ }
+ for (cache_level = 0; cache_level <= total_levels; cache_level++) {
+ hmat_cache = numa_state->hmat_cache[i][cache_level];
+ if (hmat_cache) {
+ build_hmat_cache(table_data, total_levels, hmat_cache);
+ }
+ }
+ }
+}
+
+void build_hmat(GArray *table_data, BIOSLinker *linker, NumaState *numa_state,
+ const char *oem_id, const char *oem_table_id)
+{
+ AcpiTable table = { .sig = "HMAT", .rev = 2,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, table_data);
+ hmat_build_table_structs(table_data, numa_state);
+ acpi_table_end(linker, &table);
+}
diff --git a/hw/acpi/hmat.h b/hw/acpi/hmat.h
new file mode 100644
index 000000000..b57f0e7e8
--- /dev/null
+++ b/hw/acpi/hmat.h
@@ -0,0 +1,43 @@
+/*
+ * HMAT ACPI Implementation Header
+ *
+ * Copyright(C) 2019 Intel Corporation.
+ *
+ * Author:
+ * Liu jingqi <jingqi.liu@linux.intel.com>
+ * Tao Xu <tao3.xu@intel.com>
+ *
+ * HMAT is defined in ACPI 6.3: 5.2.27 Heterogeneous Memory Attribute Table
+ * (HMAT)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef HMAT_H
+#define HMAT_H
+
+#include "hw/acpi/aml-build.h"
+
+/*
+ * ACPI 6.3: 5.2.27.3 Memory Proximity Domain Attributes Structure,
+ * Table 5-145, Field "flag", Bit [0]: set to 1 to indicate that data in
+ * the Proximity Domain for the Attached Initiator field is valid.
+ * Other bits reserved.
+ */
+#define HMAT_PROXIMITY_INITIATOR_VALID 0x1
+
+void build_hmat(GArray *table_data, BIOSLinker *linker, NumaState *numa_state,
+ const char *oem_id, const char *oem_table_id);
+
+#endif
diff --git a/hw/acpi/ich9.c b/hw/acpi/ich9.c
new file mode 100644
index 000000000..ebe08ed83
--- /dev/null
+++ b/hw/acpi/ich9.c
@@ -0,0 +1,595 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ * Copyright (c) 2009 Isaku Yamahata <yamahata at valinux co jp>
+ * VA Linux Systems Japan K.K.
+ * Copyright (C) 2012 Jason Baron <jbaron@redhat.com>
+ *
+ * This is based on acpi.c.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qapi/visitor.h"
+#include "hw/pci/pci.h"
+#include "migration/vmstate.h"
+#include "qemu/timer.h"
+#include "hw/core/cpu.h"
+#include "sysemu/reset.h"
+#include "sysemu/runstate.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/tco.h"
+
+#include "hw/i386/ich9.h"
+#include "hw/mem/pc-dimm.h"
+#include "hw/mem/nvdimm.h"
+
+//#define DEBUG
+
+#ifdef DEBUG
+#define ICH9_DEBUG(fmt, ...) \
+do { printf("%s "fmt, __func__, ## __VA_ARGS__); } while (0)
+#else
+#define ICH9_DEBUG(fmt, ...) do { } while (0)
+#endif
+
+static void ich9_pm_update_sci_fn(ACPIREGS *regs)
+{
+ ICH9LPCPMRegs *pm = container_of(regs, ICH9LPCPMRegs, acpi_regs);
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
+}
+
+static uint64_t ich9_gpe_readb(void *opaque, hwaddr addr, unsigned width)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ return acpi_gpe_ioport_readb(&pm->acpi_regs, addr);
+}
+
+static void ich9_gpe_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ acpi_gpe_ioport_writeb(&pm->acpi_regs, addr, val);
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
+}
+
+static const MemoryRegionOps ich9_gpe_ops = {
+ .read = ich9_gpe_readb,
+ .write = ich9_gpe_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t ich9_smi_readl(void *opaque, hwaddr addr, unsigned width)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ switch (addr) {
+ case 0:
+ return pm->smi_en;
+ case 4:
+ return pm->smi_sts;
+ default:
+ return 0;
+ }
+}
+
+static void ich9_smi_writel(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ TCOIORegs *tr = &pm->tco_regs;
+ uint64_t tco_en;
+
+ switch (addr) {
+ case 0:
+ tco_en = pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN;
+ /* once TCO_LOCK bit is set, TCO_EN bit cannot be overwritten */
+ if (tr->tco.cnt1 & TCO_LOCK) {
+ val = (val & ~ICH9_PMIO_SMI_EN_TCO_EN) | tco_en;
+ }
+ pm->smi_en &= ~pm->smi_en_wmask;
+ pm->smi_en |= (val & pm->smi_en_wmask);
+ break;
+ }
+}
+
+static const MemoryRegionOps ich9_smi_ops = {
+ .read = ich9_smi_readl,
+ .write = ich9_smi_writel,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void ich9_pm_iospace_update(ICH9LPCPMRegs *pm, uint32_t pm_io_base)
+{
+ ICH9_DEBUG("to 0x%x\n", pm_io_base);
+
+ assert((pm_io_base & ICH9_PMIO_MASK) == 0);
+
+ pm->pm_io_base = pm_io_base;
+ memory_region_transaction_begin();
+ memory_region_set_enabled(&pm->io, pm->pm_io_base != 0);
+ memory_region_set_address(&pm->io, pm->pm_io_base);
+ memory_region_transaction_commit();
+}
+
+static int ich9_pm_post_load(void *opaque, int version_id)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ uint32_t pm_io_base = pm->pm_io_base;
+ pm->pm_io_base = 0;
+ ich9_pm_iospace_update(pm, pm_io_base);
+ return 0;
+}
+
+#define VMSTATE_GPE_ARRAY(_field, _state) \
+ { \
+ .name = (stringify(_field)), \
+ .version_id = 0, \
+ .num = ICH9_PMIO_GPE0_LEN, \
+ .info = &vmstate_info_uint8, \
+ .size = sizeof(uint8_t), \
+ .flags = VMS_ARRAY | VMS_POINTER, \
+ .offset = vmstate_offset_pointer(_state, _field, uint8_t), \
+ }
+
+static bool vmstate_test_use_memhp(void *opaque)
+{
+ ICH9LPCPMRegs *s = opaque;
+ return s->acpi_memory_hotplug.is_enabled;
+}
+
+static const VMStateDescription vmstate_memhp_state = {
+ .name = "ich9_pm/memhp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .needed = vmstate_test_use_memhp,
+ .fields = (VMStateField[]) {
+ VMSTATE_MEMORY_HOTPLUG(acpi_memory_hotplug, ICH9LPCPMRegs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool vmstate_test_use_tco(void *opaque)
+{
+ ICH9LPCPMRegs *s = opaque;
+ return s->enable_tco;
+}
+
+static const VMStateDescription vmstate_tco_io_state = {
+ .name = "ich9_pm/tco",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .needed = vmstate_test_use_tco,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(tco_regs, ICH9LPCPMRegs, 1, vmstate_tco_io_sts,
+ TCOIORegs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool vmstate_test_use_cpuhp(void *opaque)
+{
+ ICH9LPCPMRegs *s = opaque;
+ return !s->cpu_hotplug_legacy;
+}
+
+static int vmstate_cpuhp_pre_load(void *opaque)
+{
+ ICH9LPCPMRegs *s = opaque;
+ Object *obj = OBJECT(s->gpe_cpu.device);
+ object_property_set_bool(obj, "cpu-hotplug-legacy", false, &error_abort);
+ return 0;
+}
+
+static const VMStateDescription vmstate_cpuhp_state = {
+ .name = "ich9_pm/cpuhp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .needed = vmstate_test_use_cpuhp,
+ .pre_load = vmstate_cpuhp_pre_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_CPU_HOTPLUG(cpuhp_state, ICH9LPCPMRegs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool vmstate_test_use_pcihp(void *opaque)
+{
+ ICH9LPCPMRegs *s = opaque;
+
+ return s->use_acpi_hotplug_bridge;
+}
+
+static const VMStateDescription vmstate_pcihp_state = {
+ .name = "ich9_pm/pcihp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = vmstate_test_use_pcihp,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_HOTPLUG(acpi_pci_hotplug,
+ ICH9LPCPMRegs,
+ NULL, NULL),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_ich9_pm = {
+ .name = "ich9_pm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = ich9_pm_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(acpi_regs.pm1.evt.sts, ICH9LPCPMRegs),
+ VMSTATE_UINT16(acpi_regs.pm1.evt.en, ICH9LPCPMRegs),
+ VMSTATE_UINT16(acpi_regs.pm1.cnt.cnt, ICH9LPCPMRegs),
+ VMSTATE_TIMER_PTR(acpi_regs.tmr.timer, ICH9LPCPMRegs),
+ VMSTATE_INT64(acpi_regs.tmr.overflow_time, ICH9LPCPMRegs),
+ VMSTATE_GPE_ARRAY(acpi_regs.gpe.sts, ICH9LPCPMRegs),
+ VMSTATE_GPE_ARRAY(acpi_regs.gpe.en, ICH9LPCPMRegs),
+ VMSTATE_UINT32(smi_en, ICH9LPCPMRegs),
+ VMSTATE_UINT32(smi_sts, ICH9LPCPMRegs),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_memhp_state,
+ &vmstate_tco_io_state,
+ &vmstate_cpuhp_state,
+ &vmstate_pcihp_state,
+ NULL
+ }
+};
+
+static void pm_reset(void *opaque)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ ich9_pm_iospace_update(pm, 0);
+
+ acpi_pm1_evt_reset(&pm->acpi_regs);
+ acpi_pm1_cnt_reset(&pm->acpi_regs);
+ acpi_pm_tmr_reset(&pm->acpi_regs);
+ acpi_gpe_reset(&pm->acpi_regs);
+
+ pm->smi_en = 0;
+ if (!pm->smm_enabled) {
+ /* Mark SMM as already inited to prevent SMM from running. */
+ pm->smi_en |= ICH9_PMIO_SMI_EN_APMC_EN;
+ }
+ pm->smi_en_wmask = ~0;
+
+ if (pm->use_acpi_hotplug_bridge) {
+ acpi_pcihp_reset(&pm->acpi_pci_hotplug, true);
+ }
+
+ acpi_update_sci(&pm->acpi_regs, pm->irq);
+}
+
+static void pm_powerdown_req(Notifier *n, void *opaque)
+{
+ ICH9LPCPMRegs *pm = container_of(n, ICH9LPCPMRegs, powerdown_notifier);
+
+ acpi_pm1_evt_power_down(&pm->acpi_regs);
+}
+
+void ich9_pm_init(PCIDevice *lpc_pci, ICH9LPCPMRegs *pm,
+ bool smm_enabled,
+ qemu_irq sci_irq)
+{
+ memory_region_init(&pm->io, OBJECT(lpc_pci), "ich9-pm", ICH9_PMIO_SIZE);
+ memory_region_set_enabled(&pm->io, false);
+ memory_region_add_subregion(pci_address_space_io(lpc_pci),
+ 0, &pm->io);
+
+ acpi_pm_tmr_init(&pm->acpi_regs, ich9_pm_update_sci_fn, &pm->io);
+ acpi_pm1_evt_init(&pm->acpi_regs, ich9_pm_update_sci_fn, &pm->io);
+ acpi_pm1_cnt_init(&pm->acpi_regs, &pm->io, pm->disable_s3, pm->disable_s4,
+ pm->s4_val, !pm->smm_compat && !smm_enabled);
+
+ acpi_gpe_init(&pm->acpi_regs, ICH9_PMIO_GPE0_LEN);
+ memory_region_init_io(&pm->io_gpe, OBJECT(lpc_pci), &ich9_gpe_ops, pm,
+ "acpi-gpe0", ICH9_PMIO_GPE0_LEN);
+ memory_region_add_subregion(&pm->io, ICH9_PMIO_GPE0_STS, &pm->io_gpe);
+
+ memory_region_init_io(&pm->io_smi, OBJECT(lpc_pci), &ich9_smi_ops, pm,
+ "acpi-smi", 8);
+ memory_region_add_subregion(&pm->io, ICH9_PMIO_SMI_EN, &pm->io_smi);
+
+ pm->smm_enabled = smm_enabled;
+
+ pm->enable_tco = true;
+ acpi_pm_tco_init(&pm->tco_regs, &pm->io);
+
+ if (pm->use_acpi_hotplug_bridge) {
+ acpi_pcihp_init(OBJECT(lpc_pci),
+ &pm->acpi_pci_hotplug,
+ pci_get_bus(lpc_pci),
+ pci_address_space_io(lpc_pci),
+ true,
+ ACPI_PCIHP_ADDR_ICH9);
+
+ qbus_set_hotplug_handler(BUS(pci_get_bus(lpc_pci)),
+ OBJECT(lpc_pci));
+ }
+
+ pm->irq = sci_irq;
+ qemu_register_reset(pm_reset, pm);
+ pm->powerdown_notifier.notify = pm_powerdown_req;
+ qemu_register_powerdown_notifier(&pm->powerdown_notifier);
+
+ legacy_acpi_cpu_hotplug_init(pci_address_space_io(lpc_pci),
+ OBJECT(lpc_pci), &pm->gpe_cpu, ICH9_CPU_HOTPLUG_IO_BASE);
+
+ if (pm->acpi_memory_hotplug.is_enabled) {
+ acpi_memory_hotplug_init(pci_address_space_io(lpc_pci), OBJECT(lpc_pci),
+ &pm->acpi_memory_hotplug,
+ ACPI_MEMORY_HOTPLUG_BASE);
+ }
+}
+
+static void ich9_pm_get_gpe0_blk(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ ICH9LPCPMRegs *pm = opaque;
+ uint32_t value = pm->pm_io_base + ICH9_PMIO_GPE0_STS;
+
+ visit_type_uint32(v, name, &value, errp);
+}
+
+static bool ich9_pm_get_memory_hotplug_support(Object *obj, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ return s->pm.acpi_memory_hotplug.is_enabled;
+}
+
+static void ich9_pm_set_memory_hotplug_support(Object *obj, bool value,
+ Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ s->pm.acpi_memory_hotplug.is_enabled = value;
+}
+
+static bool ich9_pm_get_cpu_hotplug_legacy(Object *obj, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ return s->pm.cpu_hotplug_legacy;
+}
+
+static void ich9_pm_set_cpu_hotplug_legacy(Object *obj, bool value,
+ Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ assert(!value);
+ if (s->pm.cpu_hotplug_legacy && value == false) {
+ acpi_switch_to_modern_cphp(&s->pm.gpe_cpu, &s->pm.cpuhp_state,
+ ICH9_CPU_HOTPLUG_IO_BASE);
+ }
+ s->pm.cpu_hotplug_legacy = value;
+}
+
+static bool ich9_pm_get_enable_tco(Object *obj, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+ return s->pm.enable_tco;
+}
+
+static void ich9_pm_set_enable_tco(Object *obj, bool value, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+ s->pm.enable_tco = value;
+}
+
+static bool ich9_pm_get_acpi_pci_hotplug(Object *obj, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ return s->pm.use_acpi_hotplug_bridge;
+}
+
+static void ich9_pm_set_acpi_pci_hotplug(Object *obj, bool value, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ s->pm.use_acpi_hotplug_bridge = value;
+}
+
+static bool ich9_pm_get_keep_pci_slot_hpc(Object *obj, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ return s->pm.keep_pci_slot_hpc;
+}
+
+static void ich9_pm_set_keep_pci_slot_hpc(Object *obj, bool value, Error **errp)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(obj);
+
+ s->pm.keep_pci_slot_hpc = value;
+}
+
+void ich9_pm_add_properties(Object *obj, ICH9LPCPMRegs *pm)
+{
+ static const uint32_t gpe0_len = ICH9_PMIO_GPE0_LEN;
+ pm->acpi_memory_hotplug.is_enabled = true;
+ pm->cpu_hotplug_legacy = true;
+ pm->disable_s3 = 0;
+ pm->disable_s4 = 0;
+ pm->s4_val = 2;
+ pm->use_acpi_hotplug_bridge = true;
+ pm->keep_pci_slot_hpc = true;
+
+ object_property_add_uint32_ptr(obj, ACPI_PM_PROP_PM_IO_BASE,
+ &pm->pm_io_base, OBJ_PROP_FLAG_READ);
+ object_property_add(obj, ACPI_PM_PROP_GPE0_BLK, "uint32",
+ ich9_pm_get_gpe0_blk,
+ NULL, NULL, pm);
+ object_property_add_uint32_ptr(obj, ACPI_PM_PROP_GPE0_BLK_LEN,
+ &gpe0_len, OBJ_PROP_FLAG_READ);
+ object_property_add_bool(obj, "memory-hotplug-support",
+ ich9_pm_get_memory_hotplug_support,
+ ich9_pm_set_memory_hotplug_support);
+ object_property_add_bool(obj, "cpu-hotplug-legacy",
+ ich9_pm_get_cpu_hotplug_legacy,
+ ich9_pm_set_cpu_hotplug_legacy);
+ object_property_add_uint8_ptr(obj, ACPI_PM_PROP_S3_DISABLED,
+ &pm->disable_s3, OBJ_PROP_FLAG_READWRITE);
+ object_property_add_uint8_ptr(obj, ACPI_PM_PROP_S4_DISABLED,
+ &pm->disable_s4, OBJ_PROP_FLAG_READWRITE);
+ object_property_add_uint8_ptr(obj, ACPI_PM_PROP_S4_VAL,
+ &pm->s4_val, OBJ_PROP_FLAG_READWRITE);
+ object_property_add_bool(obj, ACPI_PM_PROP_TCO_ENABLED,
+ ich9_pm_get_enable_tco,
+ ich9_pm_set_enable_tco);
+ object_property_add_bool(obj, ACPI_PM_PROP_ACPI_PCIHP_BRIDGE,
+ ich9_pm_get_acpi_pci_hotplug,
+ ich9_pm_set_acpi_pci_hotplug);
+ object_property_add_bool(obj, "x-keep-pci-slot-hpc",
+ ich9_pm_get_keep_pci_slot_hpc,
+ ich9_pm_set_keep_pci_slot_hpc);
+}
+
+void ich9_pm_device_pre_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(hotplug_dev);
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_pre_plug_cb(hotplug_dev, dev, errp);
+ return;
+ }
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM) &&
+ !lpc->pm.acpi_memory_hotplug.is_enabled) {
+ error_setg(errp,
+ "memory hotplug is not enabled: %s.memory-hotplug-support "
+ "is not set", object_get_typename(OBJECT(lpc)));
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
+ uint64_t negotiated = lpc->smi_negotiated_features;
+
+ if (negotiated & BIT_ULL(ICH9_LPC_SMI_F_BROADCAST_BIT) &&
+ !(negotiated & BIT_ULL(ICH9_LPC_SMI_F_CPU_HOTPLUG_BIT))) {
+ error_setg(errp, "cpu hotplug with SMI wasn't enabled by firmware");
+ error_append_hint(errp, "update machine type to newer than 5.1 "
+ "and firmware that suppors CPU hotplug with SMM");
+ }
+ }
+}
+
+void ich9_pm_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(hotplug_dev);
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ if (object_dynamic_cast(OBJECT(dev), TYPE_NVDIMM)) {
+ nvdimm_acpi_plug_cb(hotplug_dev, dev);
+ } else {
+ acpi_memory_plug_cb(hotplug_dev, &lpc->pm.acpi_memory_hotplug,
+ dev, errp);
+ }
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
+ if (lpc->pm.cpu_hotplug_legacy) {
+ legacy_acpi_cpu_plug_cb(hotplug_dev, &lpc->pm.gpe_cpu, dev, errp);
+ } else {
+ acpi_cpu_plug_cb(hotplug_dev, &lpc->pm.cpuhp_state, dev, errp);
+ }
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_plug_cb(hotplug_dev, &lpc->pm.acpi_pci_hotplug,
+ dev, errp);
+ } else {
+ error_setg(errp, "acpi: device plug request for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+void ich9_pm_device_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(hotplug_dev);
+
+ if (lpc->pm.acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_request_cb(hotplug_dev,
+ &lpc->pm.acpi_memory_hotplug, dev,
+ errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU) &&
+ !lpc->pm.cpu_hotplug_legacy) {
+ uint64_t negotiated = lpc->smi_negotiated_features;
+
+ if (negotiated & BIT_ULL(ICH9_LPC_SMI_F_BROADCAST_BIT) &&
+ !(negotiated & BIT_ULL(ICH9_LPC_SMI_F_CPU_HOT_UNPLUG_BIT))) {
+ error_setg(errp, "cpu hot-unplug with SMI wasn't enabled "
+ "by firmware");
+ error_append_hint(errp, "update machine type to a version having "
+ "x-smi-cpu-hotunplug=on and firmware that "
+ "supports CPU hot-unplug with SMM");
+ return;
+ }
+
+ acpi_cpu_unplug_request_cb(hotplug_dev, &lpc->pm.cpuhp_state,
+ dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_unplug_request_cb(hotplug_dev,
+ &lpc->pm.acpi_pci_hotplug,
+ dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug request for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+void ich9_pm_device_unplug_cb(HotplugHandler *hotplug_dev, DeviceState *dev,
+ Error **errp)
+{
+ ICH9LPCState *lpc = ICH9_LPC_DEVICE(hotplug_dev);
+
+ if (lpc->pm.acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_cb(&lpc->pm.acpi_memory_hotplug, dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU) &&
+ !lpc->pm.cpu_hotplug_legacy) {
+ acpi_cpu_unplug_cb(&lpc->pm.cpuhp_state, dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_unplug_cb(hotplug_dev, &lpc->pm.acpi_pci_hotplug,
+ dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+void ich9_pm_ospm_status(AcpiDeviceIf *adev, ACPIOSTInfoList ***list)
+{
+ ICH9LPCState *s = ICH9_LPC_DEVICE(adev);
+
+ acpi_memory_ospm_status(&s->pm.acpi_memory_hotplug, list);
+ if (!s->pm.cpu_hotplug_legacy) {
+ acpi_cpu_ospm_status(&s->pm.cpuhp_state, list);
+ }
+}
diff --git a/hw/acpi/ipmi-stub.c b/hw/acpi/ipmi-stub.c
new file mode 100644
index 000000000..8634fb325
--- /dev/null
+++ b/hw/acpi/ipmi-stub.c
@@ -0,0 +1,15 @@
+/*
+ * IPMI ACPI firmware handling
+ *
+ * Copyright (c) 2015,2016 Corey Minyard, MontaVista Software, LLC
+ *
+ * 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 "hw/acpi/ipmi.h"
+
+void build_acpi_ipmi_devices(Aml *table, BusState *bus, const char *resource)
+{
+}
diff --git a/hw/acpi/ipmi.c b/hw/acpi/ipmi.c
new file mode 100644
index 000000000..96e48eba1
--- /dev/null
+++ b/hw/acpi/ipmi.c
@@ -0,0 +1,107 @@
+/*
+ * IPMI ACPI firmware handling
+ *
+ * Copyright (c) 2015,2016 Corey Minyard, MontaVista Software, LLC
+ *
+ * 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 "hw/ipmi/ipmi.h"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/ipmi.h"
+
+static Aml *aml_ipmi_crs(IPMIFwInfo *info, const char *resource)
+{
+ Aml *crs = aml_resource_template();
+
+ /*
+ * The base address is fixed and cannot change. That may be different
+ * if someone does PCI, but we aren't there yet.
+ */
+ switch (info->memspace) {
+ case IPMI_MEMSPACE_IO:
+ aml_append(crs, aml_io(AML_DECODE16, info->base_address,
+ info->base_address + info->register_length - 1,
+ info->register_spacing, info->register_length));
+ break;
+ case IPMI_MEMSPACE_MEM32:
+ aml_append(crs,
+ aml_dword_memory(AML_POS_DECODE,
+ AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_NON_CACHEABLE, AML_READ_WRITE,
+ 0xffffffff,
+ info->base_address,
+ info->base_address + info->register_length - 1,
+ info->register_spacing, info->register_length));
+ break;
+ case IPMI_MEMSPACE_MEM64:
+ aml_append(crs,
+ aml_qword_memory(AML_POS_DECODE,
+ AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_NON_CACHEABLE, AML_READ_WRITE,
+ 0xffffffffffffffffULL,
+ info->base_address,
+ info->base_address + info->register_length - 1,
+ info->register_spacing, info->register_length));
+ break;
+ case IPMI_MEMSPACE_SMBUS:
+ aml_append(crs, aml_i2c_serial_bus_device(info->base_address,
+ resource));
+ break;
+ default:
+ abort();
+ }
+
+ if (info->interrupt_number) {
+ aml_append(crs, aml_irq_no_flags(info->interrupt_number));
+ }
+
+ return crs;
+}
+
+static Aml *aml_ipmi_device(IPMIFwInfo *info, const char *resource)
+{
+ Aml *dev;
+ uint16_t version = ((info->ipmi_spec_major_revision << 8)
+ | (info->ipmi_spec_minor_revision << 4));
+
+ assert(info->ipmi_spec_minor_revision <= 15);
+
+ dev = aml_device("MI%d", info->uuid);
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("IPI0001")));
+ aml_append(dev, aml_name_decl("_STR", aml_string("ipmi_%s",
+ info->interface_name)));
+ aml_append(dev, aml_name_decl("_UID", aml_int(info->uuid)));
+ aml_append(dev, aml_name_decl("_CRS", aml_ipmi_crs(info, resource)));
+ aml_append(dev, aml_name_decl("_IFT", aml_int(info->interface_type)));
+ aml_append(dev, aml_name_decl("_SRV", aml_int(version)));
+
+ return dev;
+}
+
+void build_acpi_ipmi_devices(Aml *scope, BusState *bus, const char *resource)
+{
+
+ BusChild *kid;
+
+ QTAILQ_FOREACH(kid, &bus->children, sibling) {
+ IPMIInterface *ii;
+ IPMIInterfaceClass *iic;
+ IPMIFwInfo info;
+ Object *obj = object_dynamic_cast(OBJECT(kid->child),
+ TYPE_IPMI_INTERFACE);
+
+ if (!obj) {
+ continue;
+ }
+
+ ii = IPMI_INTERFACE(obj);
+ iic = IPMI_INTERFACE_GET_CLASS(obj);
+ memset(&info, 0, sizeof(info));
+ iic->get_fwinfo(ii, &info);
+ aml_append(scope, aml_ipmi_device(&info, resource));
+ }
+}
diff --git a/hw/acpi/memory_hotplug.c b/hw/acpi/memory_hotplug.c
new file mode 100644
index 000000000..d0fffcf78
--- /dev/null
+++ b/hw/acpi/memory_hotplug.c
@@ -0,0 +1,730 @@
+#include "qemu/osdep.h"
+#include "hw/acpi/memory_hotplug.h"
+#include "hw/acpi/pc-hotplug.h"
+#include "hw/mem/pc-dimm.h"
+#include "hw/qdev-core.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+#include "qapi/error.h"
+#include "qapi/qapi-events-acpi.h"
+#include "qapi/qapi-events-machine.h"
+#include "qapi/qapi-events-qdev.h"
+
+#define MEMORY_SLOTS_NUMBER "MDNR"
+#define MEMORY_HOTPLUG_IO_REGION "HPMR"
+#define MEMORY_SLOT_ADDR_LOW "MRBL"
+#define MEMORY_SLOT_ADDR_HIGH "MRBH"
+#define MEMORY_SLOT_SIZE_LOW "MRLL"
+#define MEMORY_SLOT_SIZE_HIGH "MRLH"
+#define MEMORY_SLOT_PROXIMITY "MPX"
+#define MEMORY_SLOT_ENABLED "MES"
+#define MEMORY_SLOT_INSERT_EVENT "MINS"
+#define MEMORY_SLOT_REMOVE_EVENT "MRMV"
+#define MEMORY_SLOT_EJECT "MEJ"
+#define MEMORY_SLOT_SLECTOR "MSEL"
+#define MEMORY_SLOT_OST_EVENT "MOEV"
+#define MEMORY_SLOT_OST_STATUS "MOSC"
+#define MEMORY_SLOT_LOCK "MLCK"
+#define MEMORY_SLOT_STATUS_METHOD "MRST"
+#define MEMORY_SLOT_CRS_METHOD "MCRS"
+#define MEMORY_SLOT_OST_METHOD "MOST"
+#define MEMORY_SLOT_PROXIMITY_METHOD "MPXM"
+#define MEMORY_SLOT_EJECT_METHOD "MEJ0"
+#define MEMORY_SLOT_NOTIFY_METHOD "MTFY"
+#define MEMORY_HOTPLUG_DEVICE "MHPD"
+
+static ACPIOSTInfo *acpi_memory_device_status(int slot, MemStatus *mdev)
+{
+ ACPIOSTInfo *info = g_new0(ACPIOSTInfo, 1);
+
+ info->slot_type = ACPI_SLOT_TYPE_DIMM;
+ info->slot = g_strdup_printf("%d", slot);
+ info->source = mdev->ost_event;
+ info->status = mdev->ost_status;
+ if (mdev->dimm) {
+ DeviceState *dev = DEVICE(mdev->dimm);
+ if (dev->id) {
+ info->device = g_strdup(dev->id);
+ info->has_device = true;
+ }
+ }
+ return info;
+}
+
+void acpi_memory_ospm_status(MemHotplugState *mem_st, ACPIOSTInfoList ***list)
+{
+ ACPIOSTInfoList ***tail = list;
+ int i;
+
+ for (i = 0; i < mem_st->dev_count; i++) {
+ QAPI_LIST_APPEND(*tail,
+ acpi_memory_device_status(i, &mem_st->devs[i]));
+ }
+}
+
+static uint64_t acpi_memory_hotplug_read(void *opaque, hwaddr addr,
+ unsigned int size)
+{
+ uint32_t val = 0;
+ MemHotplugState *mem_st = opaque;
+ MemStatus *mdev;
+ Object *o;
+
+ if (mem_st->selector >= mem_st->dev_count) {
+ trace_mhp_acpi_invalid_slot_selected(mem_st->selector);
+ return 0;
+ }
+
+ mdev = &mem_st->devs[mem_st->selector];
+ o = OBJECT(mdev->dimm);
+ switch (addr) {
+ case 0x0: /* Lo part of phys address where DIMM is mapped */
+ val = o ? object_property_get_uint(o, PC_DIMM_ADDR_PROP, NULL) : 0;
+ trace_mhp_acpi_read_addr_lo(mem_st->selector, val);
+ break;
+ case 0x4: /* Hi part of phys address where DIMM is mapped */
+ val =
+ o ? object_property_get_uint(o, PC_DIMM_ADDR_PROP, NULL) >> 32 : 0;
+ trace_mhp_acpi_read_addr_hi(mem_st->selector, val);
+ break;
+ case 0x8: /* Lo part of DIMM size */
+ val = o ? object_property_get_uint(o, PC_DIMM_SIZE_PROP, NULL) : 0;
+ trace_mhp_acpi_read_size_lo(mem_st->selector, val);
+ break;
+ case 0xc: /* Hi part of DIMM size */
+ val =
+ o ? object_property_get_uint(o, PC_DIMM_SIZE_PROP, NULL) >> 32 : 0;
+ trace_mhp_acpi_read_size_hi(mem_st->selector, val);
+ break;
+ case 0x10: /* node proximity for _PXM method */
+ val = o ? object_property_get_uint(o, PC_DIMM_NODE_PROP, NULL) : 0;
+ trace_mhp_acpi_read_pxm(mem_st->selector, val);
+ break;
+ case 0x14: /* pack and return is_* fields */
+ val |= mdev->is_enabled ? 1 : 0;
+ val |= mdev->is_inserting ? 2 : 0;
+ val |= mdev->is_removing ? 4 : 0;
+ trace_mhp_acpi_read_flags(mem_st->selector, val);
+ break;
+ default:
+ val = ~0;
+ break;
+ }
+ return val;
+}
+
+static void acpi_memory_hotplug_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ MemHotplugState *mem_st = opaque;
+ MemStatus *mdev;
+ ACPIOSTInfo *info;
+ DeviceState *dev = NULL;
+ HotplugHandler *hotplug_ctrl = NULL;
+ Error *local_err = NULL;
+
+ if (!mem_st->dev_count) {
+ return;
+ }
+
+ if (addr) {
+ if (mem_st->selector >= mem_st->dev_count) {
+ trace_mhp_acpi_invalid_slot_selected(mem_st->selector);
+ return;
+ }
+ }
+
+ switch (addr) {
+ case 0x0: /* DIMM slot selector */
+ mem_st->selector = data;
+ trace_mhp_acpi_write_slot(mem_st->selector);
+ break;
+ case 0x4: /* _OST event */
+ mdev = &mem_st->devs[mem_st->selector];
+ if (data == 1) {
+ /* TODO: handle device insert OST event */
+ } else if (data == 3) {
+ /* TODO: handle device remove OST event */
+ }
+ mdev->ost_event = data;
+ trace_mhp_acpi_write_ost_ev(mem_st->selector, mdev->ost_event);
+ break;
+ case 0x8: /* _OST status */
+ mdev = &mem_st->devs[mem_st->selector];
+ mdev->ost_status = data;
+ trace_mhp_acpi_write_ost_status(mem_st->selector, mdev->ost_status);
+ /* TODO: implement memory removal on guest signal */
+
+ info = acpi_memory_device_status(mem_st->selector, mdev);
+ qapi_event_send_acpi_device_ost(info);
+ qapi_free_ACPIOSTInfo(info);
+ break;
+ case 0x14: /* set is_* fields */
+ mdev = &mem_st->devs[mem_st->selector];
+ if (data & 2) { /* clear insert event */
+ mdev->is_inserting = false;
+ trace_mhp_acpi_clear_insert_evt(mem_st->selector);
+ } else if (data & 4) {
+ mdev->is_removing = false;
+ trace_mhp_acpi_clear_remove_evt(mem_st->selector);
+ } else if (data & 8) {
+ if (!mdev->is_enabled) {
+ trace_mhp_acpi_ejecting_invalid_slot(mem_st->selector);
+ break;
+ }
+
+ dev = DEVICE(mdev->dimm);
+ hotplug_ctrl = qdev_get_hotplug_handler(dev);
+ /* call pc-dimm unplug cb */
+ hotplug_handler_unplug(hotplug_ctrl, dev, &local_err);
+ if (local_err) {
+ trace_mhp_acpi_pc_dimm_delete_failed(mem_st->selector);
+
+ /*
+ * Send both MEM_UNPLUG_ERROR and DEVICE_UNPLUG_GUEST_ERROR
+ * while the deprecation of MEM_UNPLUG_ERROR is
+ * pending.
+ */
+ qapi_event_send_mem_unplug_error(dev->id ? : "",
+ error_get_pretty(local_err));
+ qapi_event_send_device_unplug_guest_error(!!dev->id, dev->id,
+ dev->canonical_path);
+ error_free(local_err);
+ break;
+ }
+ object_unparent(OBJECT(dev));
+ trace_mhp_acpi_pc_dimm_deleted(mem_st->selector);
+ }
+ break;
+ default:
+ break;
+ }
+
+}
+static const MemoryRegionOps acpi_memory_hotplug_ops = {
+ .read = acpi_memory_hotplug_read,
+ .write = acpi_memory_hotplug_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+void acpi_memory_hotplug_init(MemoryRegion *as, Object *owner,
+ MemHotplugState *state, hwaddr io_base)
+{
+ MachineState *machine = MACHINE(qdev_get_machine());
+
+ state->dev_count = machine->ram_slots;
+ if (!state->dev_count) {
+ return;
+ }
+
+ state->devs = g_malloc0(sizeof(*state->devs) * state->dev_count);
+ memory_region_init_io(&state->io, owner, &acpi_memory_hotplug_ops, state,
+ "acpi-mem-hotplug", MEMORY_HOTPLUG_IO_LEN);
+ memory_region_add_subregion(as, io_base, &state->io);
+}
+
+/**
+ * acpi_memory_slot_status:
+ * @mem_st: memory hotplug state
+ * @dev: device
+ * @errp: set in case of an error
+ *
+ * Obtain a single memory slot status.
+ *
+ * This function will be called by memory unplug request cb and unplug cb.
+ */
+static MemStatus *
+acpi_memory_slot_status(MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ Error *local_err = NULL;
+ int slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP,
+ &local_err);
+
+ if (local_err) {
+ error_propagate(errp, local_err);
+ return NULL;
+ }
+
+ if (slot >= mem_st->dev_count) {
+ char *dev_path = object_get_canonical_path(OBJECT(dev));
+ error_setg(errp, "acpi_memory_slot_status: "
+ "device [%s] returned invalid memory slot[%d]",
+ dev_path, slot);
+ g_free(dev_path);
+ return NULL;
+ }
+
+ return &mem_st->devs[slot];
+}
+
+void acpi_memory_plug_cb(HotplugHandler *hotplug_dev, MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ MemStatus *mdev;
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+
+ if (!dc->hotpluggable) {
+ return;
+ }
+
+ mdev = acpi_memory_slot_status(mem_st, dev, errp);
+ if (!mdev) {
+ return;
+ }
+
+ mdev->dimm = dev;
+ mdev->is_enabled = true;
+ if (dev->hotplugged) {
+ mdev->is_inserting = true;
+ acpi_send_event(DEVICE(hotplug_dev), ACPI_MEMORY_HOTPLUG_STATUS);
+ }
+}
+
+void acpi_memory_unplug_request_cb(HotplugHandler *hotplug_dev,
+ MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ MemStatus *mdev;
+
+ mdev = acpi_memory_slot_status(mem_st, dev, errp);
+ if (!mdev) {
+ return;
+ }
+
+ mdev->is_removing = true;
+ acpi_send_event(DEVICE(hotplug_dev), ACPI_MEMORY_HOTPLUG_STATUS);
+}
+
+void acpi_memory_unplug_cb(MemHotplugState *mem_st,
+ DeviceState *dev, Error **errp)
+{
+ MemStatus *mdev;
+
+ mdev = acpi_memory_slot_status(mem_st, dev, errp);
+ if (!mdev) {
+ return;
+ }
+
+ mdev->is_enabled = false;
+ mdev->dimm = NULL;
+}
+
+static const VMStateDescription vmstate_memhp_sts = {
+ .name = "memory hotplug device state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_BOOL(is_enabled, MemStatus),
+ VMSTATE_BOOL(is_inserting, MemStatus),
+ VMSTATE_UINT32(ost_event, MemStatus),
+ VMSTATE_UINT32(ost_status, MemStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+const VMStateDescription vmstate_memory_hotplug = {
+ .name = "memory hotplug state",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(selector, MemHotplugState),
+ VMSTATE_STRUCT_VARRAY_POINTER_UINT32(devs, MemHotplugState, dev_count,
+ vmstate_memhp_sts, MemStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+void build_memory_hotplug_aml(Aml *table, uint32_t nr_mem,
+ const char *res_root,
+ const char *event_handler_method,
+ AmlRegionSpace rs, hwaddr memhp_io_base)
+{
+ int i;
+ Aml *ifctx;
+ Aml *method;
+ Aml *dev_container;
+ Aml *mem_ctrl_dev;
+ char *mhp_res_path;
+
+ mhp_res_path = g_strdup_printf("%s." MEMORY_HOTPLUG_DEVICE, res_root);
+ mem_ctrl_dev = aml_device("%s", mhp_res_path);
+ {
+ Aml *crs;
+
+ aml_append(mem_ctrl_dev, aml_name_decl("_HID", aml_string("PNP0A06")));
+ aml_append(mem_ctrl_dev,
+ aml_name_decl("_UID", aml_string("Memory hotplug resources")));
+
+ crs = aml_resource_template();
+ if (rs == AML_SYSTEM_IO) {
+ aml_append(crs,
+ aml_io(AML_DECODE16, memhp_io_base, memhp_io_base, 0,
+ MEMORY_HOTPLUG_IO_LEN)
+ );
+ } else {
+ aml_append(crs, aml_memory32_fixed(memhp_io_base,
+ MEMORY_HOTPLUG_IO_LEN, AML_READ_WRITE));
+ }
+ aml_append(mem_ctrl_dev, aml_name_decl("_CRS", crs));
+
+ aml_append(mem_ctrl_dev, aml_operation_region(
+ MEMORY_HOTPLUG_IO_REGION, rs,
+ aml_int(memhp_io_base), MEMORY_HOTPLUG_IO_LEN)
+ );
+
+ }
+ aml_append(table, mem_ctrl_dev);
+
+ dev_container = aml_device(MEMORY_DEVICES_CONTAINER);
+ {
+ Aml *field;
+ Aml *one = aml_int(1);
+ Aml *zero = aml_int(0);
+ Aml *ret_val = aml_local(0);
+ Aml *slot_arg0 = aml_arg(0);
+ Aml *slots_nr = aml_name(MEMORY_SLOTS_NUMBER);
+ Aml *ctrl_lock = aml_name(MEMORY_SLOT_LOCK);
+ Aml *slot_selector = aml_name(MEMORY_SLOT_SLECTOR);
+ char *mmio_path = g_strdup_printf("%s." MEMORY_HOTPLUG_IO_REGION,
+ mhp_res_path);
+
+ aml_append(dev_container, aml_name_decl("_HID", aml_string("PNP0A06")));
+ aml_append(dev_container,
+ aml_name_decl("_UID", aml_string("DIMM devices")));
+
+ assert(nr_mem <= ACPI_MAX_RAM_SLOTS);
+ aml_append(dev_container,
+ aml_name_decl(MEMORY_SLOTS_NUMBER, aml_int(nr_mem))
+ );
+
+ field = aml_field(mmio_path, AML_DWORD_ACC,
+ AML_NOLOCK, AML_PRESERVE);
+ aml_append(field, /* read only */
+ aml_named_field(MEMORY_SLOT_ADDR_LOW, 32));
+ aml_append(field, /* read only */
+ aml_named_field(MEMORY_SLOT_ADDR_HIGH, 32));
+ aml_append(field, /* read only */
+ aml_named_field(MEMORY_SLOT_SIZE_LOW, 32));
+ aml_append(field, /* read only */
+ aml_named_field(MEMORY_SLOT_SIZE_HIGH, 32));
+ aml_append(field, /* read only */
+ aml_named_field(MEMORY_SLOT_PROXIMITY, 32));
+ aml_append(dev_container, field);
+
+ field = aml_field(mmio_path, AML_BYTE_ACC,
+ AML_NOLOCK, AML_WRITE_AS_ZEROS);
+ aml_append(field, aml_reserved_field(160 /* bits, Offset(20) */));
+ aml_append(field, /* 1 if enabled, read only */
+ aml_named_field(MEMORY_SLOT_ENABLED, 1));
+ aml_append(field,
+ /*(read) 1 if has a insert event. (write) 1 to clear event */
+ aml_named_field(MEMORY_SLOT_INSERT_EVENT, 1));
+ aml_append(field,
+ /* (read) 1 if has a remove event. (write) 1 to clear event */
+ aml_named_field(MEMORY_SLOT_REMOVE_EVENT, 1));
+ aml_append(field,
+ /* initiates device eject, write only */
+ aml_named_field(MEMORY_SLOT_EJECT, 1));
+ aml_append(dev_container, field);
+
+ field = aml_field(mmio_path, AML_DWORD_ACC,
+ AML_NOLOCK, AML_PRESERVE);
+ aml_append(field, /* DIMM selector, write only */
+ aml_named_field(MEMORY_SLOT_SLECTOR, 32));
+ aml_append(field, /* _OST event code, write only */
+ aml_named_field(MEMORY_SLOT_OST_EVENT, 32));
+ aml_append(field, /* _OST status code, write only */
+ aml_named_field(MEMORY_SLOT_OST_STATUS, 32));
+ aml_append(dev_container, field);
+ g_free(mmio_path);
+
+ method = aml_method("_STA", 0, AML_NOTSERIALIZED);
+ ifctx = aml_if(aml_equal(slots_nr, zero));
+ {
+ aml_append(ifctx, aml_return(zero));
+ }
+ aml_append(method, ifctx);
+ /* present, functioning, decoding, not shown in UI */
+ aml_append(method, aml_return(aml_int(0xB)));
+ aml_append(dev_container, method);
+
+ aml_append(dev_container, aml_mutex(MEMORY_SLOT_LOCK, 0));
+
+ method = aml_method(MEMORY_SLOT_SCAN_METHOD, 0, AML_NOTSERIALIZED);
+ {
+ Aml *else_ctx;
+ Aml *while_ctx;
+ Aml *idx = aml_local(0);
+ Aml *eject_req = aml_int(3);
+ Aml *dev_chk = aml_int(1);
+
+ ifctx = aml_if(aml_equal(slots_nr, zero));
+ {
+ aml_append(ifctx, aml_return(zero));
+ }
+ aml_append(method, ifctx);
+
+ aml_append(method, aml_store(zero, idx));
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ /* build AML that:
+ * loops over all slots and Notifies DIMMs with
+ * Device Check or Eject Request notifications if
+ * slot has corresponding status bit set and clears
+ * slot status.
+ */
+ while_ctx = aml_while(aml_lless(idx, slots_nr));
+ {
+ Aml *ins_evt = aml_name(MEMORY_SLOT_INSERT_EVENT);
+ Aml *rm_evt = aml_name(MEMORY_SLOT_REMOVE_EVENT);
+
+ aml_append(while_ctx, aml_store(idx, slot_selector));
+ ifctx = aml_if(aml_equal(ins_evt, one));
+ {
+ aml_append(ifctx,
+ aml_call2(MEMORY_SLOT_NOTIFY_METHOD,
+ idx, dev_chk));
+ aml_append(ifctx, aml_store(one, ins_evt));
+ }
+ aml_append(while_ctx, ifctx);
+
+ else_ctx = aml_else();
+ ifctx = aml_if(aml_equal(rm_evt, one));
+ {
+ aml_append(ifctx,
+ aml_call2(MEMORY_SLOT_NOTIFY_METHOD,
+ idx, eject_req));
+ aml_append(ifctx, aml_store(one, rm_evt));
+ }
+ aml_append(else_ctx, ifctx);
+ aml_append(while_ctx, else_ctx);
+
+ aml_append(while_ctx, aml_add(idx, one, idx));
+ }
+ aml_append(method, while_ctx);
+ aml_append(method, aml_release(ctrl_lock));
+ aml_append(method, aml_return(one));
+ }
+ aml_append(dev_container, method);
+
+ method = aml_method(MEMORY_SLOT_STATUS_METHOD, 1, AML_NOTSERIALIZED);
+ {
+ Aml *slot_enabled = aml_name(MEMORY_SLOT_ENABLED);
+
+ aml_append(method, aml_store(zero, ret_val));
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ aml_append(method,
+ aml_store(aml_to_integer(slot_arg0), slot_selector));
+
+ ifctx = aml_if(aml_equal(slot_enabled, one));
+ {
+ aml_append(ifctx, aml_store(aml_int(0xF), ret_val));
+ }
+ aml_append(method, ifctx);
+
+ aml_append(method, aml_release(ctrl_lock));
+ aml_append(method, aml_return(ret_val));
+ }
+ aml_append(dev_container, method);
+
+ method = aml_method(MEMORY_SLOT_CRS_METHOD, 1, AML_SERIALIZED);
+ {
+ Aml *mr64 = aml_name("MR64");
+ Aml *mr32 = aml_name("MR32");
+ Aml *crs_tmpl = aml_resource_template();
+ Aml *minl = aml_name("MINL");
+ Aml *minh = aml_name("MINH");
+ Aml *maxl = aml_name("MAXL");
+ Aml *maxh = aml_name("MAXH");
+ Aml *lenl = aml_name("LENL");
+ Aml *lenh = aml_name("LENH");
+
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ aml_append(method, aml_store(aml_to_integer(slot_arg0),
+ slot_selector));
+
+ aml_append(crs_tmpl,
+ aml_qword_memory(AML_POS_DECODE, AML_MIN_FIXED, AML_MAX_FIXED,
+ AML_CACHEABLE, AML_READ_WRITE,
+ 0, 0x0, 0xFFFFFFFFFFFFFFFEULL, 0,
+ 0xFFFFFFFFFFFFFFFFULL));
+ aml_append(method, aml_name_decl("MR64", crs_tmpl));
+ aml_append(method,
+ aml_create_dword_field(mr64, aml_int(14), "MINL"));
+ aml_append(method,
+ aml_create_dword_field(mr64, aml_int(18), "MINH"));
+ aml_append(method,
+ aml_create_dword_field(mr64, aml_int(38), "LENL"));
+ aml_append(method,
+ aml_create_dword_field(mr64, aml_int(42), "LENH"));
+ aml_append(method,
+ aml_create_dword_field(mr64, aml_int(22), "MAXL"));
+ aml_append(method,
+ aml_create_dword_field(mr64, aml_int(26), "MAXH"));
+
+ aml_append(method,
+ aml_store(aml_name(MEMORY_SLOT_ADDR_HIGH), minh));
+ aml_append(method,
+ aml_store(aml_name(MEMORY_SLOT_ADDR_LOW), minl));
+ aml_append(method,
+ aml_store(aml_name(MEMORY_SLOT_SIZE_HIGH), lenh));
+ aml_append(method,
+ aml_store(aml_name(MEMORY_SLOT_SIZE_LOW), lenl));
+
+ /* 64-bit math: MAX = MIN + LEN - 1 */
+ aml_append(method, aml_add(minl, lenl, maxl));
+ aml_append(method, aml_add(minh, lenh, maxh));
+ ifctx = aml_if(aml_lless(maxl, minl));
+ {
+ aml_append(ifctx, aml_add(maxh, one, maxh));
+ }
+ aml_append(method, ifctx);
+ ifctx = aml_if(aml_lless(maxl, one));
+ {
+ aml_append(ifctx, aml_subtract(maxh, one, maxh));
+ }
+ aml_append(method, ifctx);
+ aml_append(method, aml_subtract(maxl, one, maxl));
+
+ /* return 32-bit _CRS if addr/size is in low mem */
+ /* TODO: remove it since all hotplugged DIMMs are in high mem */
+ ifctx = aml_if(aml_equal(maxh, zero));
+ {
+ crs_tmpl = aml_resource_template();
+ aml_append(crs_tmpl,
+ aml_dword_memory(AML_POS_DECODE, AML_MIN_FIXED,
+ AML_MAX_FIXED, AML_CACHEABLE,
+ AML_READ_WRITE,
+ 0, 0x0, 0xFFFFFFFE, 0,
+ 0xFFFFFFFF));
+ aml_append(ifctx, aml_name_decl("MR32", crs_tmpl));
+ aml_append(ifctx,
+ aml_create_dword_field(mr32, aml_int(10), "MIN"));
+ aml_append(ifctx,
+ aml_create_dword_field(mr32, aml_int(14), "MAX"));
+ aml_append(ifctx,
+ aml_create_dword_field(mr32, aml_int(22), "LEN"));
+ aml_append(ifctx, aml_store(minl, aml_name("MIN")));
+ aml_append(ifctx, aml_store(maxl, aml_name("MAX")));
+ aml_append(ifctx, aml_store(lenl, aml_name("LEN")));
+
+ aml_append(ifctx, aml_release(ctrl_lock));
+ aml_append(ifctx, aml_return(mr32));
+ }
+ aml_append(method, ifctx);
+
+ aml_append(method, aml_release(ctrl_lock));
+ aml_append(method, aml_return(mr64));
+ }
+ aml_append(dev_container, method);
+
+ method = aml_method(MEMORY_SLOT_PROXIMITY_METHOD, 1,
+ AML_NOTSERIALIZED);
+ {
+ Aml *proximity = aml_name(MEMORY_SLOT_PROXIMITY);
+
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ aml_append(method, aml_store(aml_to_integer(slot_arg0),
+ slot_selector));
+ aml_append(method, aml_store(proximity, ret_val));
+ aml_append(method, aml_release(ctrl_lock));
+ aml_append(method, aml_return(ret_val));
+ }
+ aml_append(dev_container, method);
+
+ method = aml_method(MEMORY_SLOT_OST_METHOD, 4, AML_NOTSERIALIZED);
+ {
+ Aml *ost_evt = aml_name(MEMORY_SLOT_OST_EVENT);
+ Aml *ost_status = aml_name(MEMORY_SLOT_OST_STATUS);
+
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ aml_append(method, aml_store(aml_to_integer(slot_arg0),
+ slot_selector));
+ aml_append(method, aml_store(aml_arg(1), ost_evt));
+ aml_append(method, aml_store(aml_arg(2), ost_status));
+ aml_append(method, aml_release(ctrl_lock));
+ }
+ aml_append(dev_container, method);
+
+ method = aml_method(MEMORY_SLOT_EJECT_METHOD, 2, AML_NOTSERIALIZED);
+ {
+ Aml *eject = aml_name(MEMORY_SLOT_EJECT);
+
+ aml_append(method, aml_acquire(ctrl_lock, 0xFFFF));
+ aml_append(method, aml_store(aml_to_integer(slot_arg0),
+ slot_selector));
+ aml_append(method, aml_store(one, eject));
+ aml_append(method, aml_release(ctrl_lock));
+ }
+ aml_append(dev_container, method);
+
+ /* build memory devices */
+ for (i = 0; i < nr_mem; i++) {
+ Aml *dev;
+ const char *s;
+
+ dev = aml_device("MP%02X", i);
+ aml_append(dev, aml_name_decl("_UID", aml_string("0x%02X", i)));
+ aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C80")));
+
+ method = aml_method("_CRS", 0, AML_NOTSERIALIZED);
+ s = MEMORY_SLOT_CRS_METHOD;
+ aml_append(method, aml_return(aml_call1(s, aml_name("_UID"))));
+ aml_append(dev, method);
+
+ method = aml_method("_STA", 0, AML_NOTSERIALIZED);
+ s = MEMORY_SLOT_STATUS_METHOD;
+ aml_append(method, aml_return(aml_call1(s, aml_name("_UID"))));
+ aml_append(dev, method);
+
+ method = aml_method("_PXM", 0, AML_NOTSERIALIZED);
+ s = MEMORY_SLOT_PROXIMITY_METHOD;
+ aml_append(method, aml_return(aml_call1(s, aml_name("_UID"))));
+ aml_append(dev, method);
+
+ method = aml_method("_OST", 3, AML_NOTSERIALIZED);
+ s = MEMORY_SLOT_OST_METHOD;
+ aml_append(method,
+ aml_call4(s, aml_name("_UID"), aml_arg(0),
+ aml_arg(1), aml_arg(2)));
+ aml_append(dev, method);
+
+ method = aml_method("_EJ0", 1, AML_NOTSERIALIZED);
+ s = MEMORY_SLOT_EJECT_METHOD;
+ aml_append(method,
+ aml_call2(s, aml_name("_UID"), aml_arg(0)));
+ aml_append(dev, method);
+
+ aml_append(dev_container, dev);
+ }
+
+ /* build Method(MEMORY_SLOT_NOTIFY_METHOD, 2) {
+ * If (LEqual(Arg0, 0x00)) {Notify(MP00, Arg1)} ... }
+ */
+ method = aml_method(MEMORY_SLOT_NOTIFY_METHOD, 2, AML_NOTSERIALIZED);
+ for (i = 0; i < nr_mem; i++) {
+ ifctx = aml_if(aml_equal(aml_arg(0), aml_int(i)));
+ aml_append(ifctx,
+ aml_notify(aml_name("MP%.02X", i), aml_arg(1))
+ );
+ aml_append(method, ifctx);
+ }
+ aml_append(dev_container, method);
+ }
+ aml_append(table, dev_container);
+
+ if (event_handler_method) {
+ method = aml_method(event_handler_method, 0, AML_NOTSERIALIZED);
+ aml_append(method, aml_call0(MEMORY_DEVICES_CONTAINER "."
+ MEMORY_SLOT_SCAN_METHOD));
+ aml_append(table, method);
+ }
+
+ g_free(mhp_res_path);
+}
diff --git a/hw/acpi/meson.build b/hw/acpi/meson.build
new file mode 100644
index 000000000..adf6347bc
--- /dev/null
+++ b/hw/acpi/meson.build
@@ -0,0 +1,33 @@
+acpi_ss = ss.source_set()
+acpi_ss.add(files(
+ 'acpi_interface.c',
+ 'aml-build.c',
+ 'bios-linker-loader.c',
+ 'core.c',
+ 'utils.c',
+))
+acpi_ss.add(when: 'CONFIG_ACPI_CPU_HOTPLUG', if_true: files('cpu.c', 'cpu_hotplug.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_CPU_HOTPLUG', if_false: files('acpi-cpu-hotplug-stub.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_MEMORY_HOTPLUG', if_true: files('memory_hotplug.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_MEMORY_HOTPLUG', if_false: files('acpi-mem-hotplug-stub.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_NVDIMM', if_true: files('nvdimm.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_NVDIMM', if_false: files('acpi-nvdimm-stub.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_PCI', if_true: files('pci.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_VMGENID', if_true: files('vmgenid.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_HW_REDUCED', if_true: files('generic_event_device.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_HMAT', if_true: files('hmat.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_APEI', if_true: files('ghes.c'), if_false: files('ghes-stub.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_PIIX4', if_true: files('piix4.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_PCIHP', if_true: files('pcihp.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_PCIHP', if_false: files('acpi-pci-hotplug-stub.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_VIOT', if_true: files('viot.c'))
+acpi_ss.add(when: 'CONFIG_ACPI_X86_ICH', if_true: files('ich9.c', 'tco.c'))
+acpi_ss.add(when: 'CONFIG_IPMI', if_true: files('ipmi.c'), if_false: files('ipmi-stub.c'))
+acpi_ss.add(when: 'CONFIG_PC', if_false: files('acpi-x86-stub.c'))
+acpi_ss.add(when: 'CONFIG_TPM', if_true: files('tpm.c'))
+softmmu_ss.add(when: 'CONFIG_ACPI', if_false: files('acpi-stub.c', 'aml-build-stub.c', 'ghes-stub.c'))
+softmmu_ss.add_all(when: 'CONFIG_ACPI', if_true: acpi_ss)
+softmmu_ss.add(when: 'CONFIG_ALL', if_true: files('acpi-stub.c', 'aml-build-stub.c',
+ 'acpi-x86-stub.c', 'ipmi-stub.c', 'ghes-stub.c',
+ 'acpi-mem-hotplug-stub.c', 'acpi-cpu-hotplug-stub.c',
+ 'acpi-pci-hotplug-stub.c', 'acpi-nvdimm-stub.c'))
diff --git a/hw/acpi/nvdimm.c b/hw/acpi/nvdimm.c
new file mode 100644
index 000000000..0d43da19e
--- /dev/null
+++ b/hw/acpi/nvdimm.c
@@ -0,0 +1,1378 @@
+/*
+ * NVDIMM ACPI Implementation
+ *
+ * Copyright(C) 2015 Intel Corporation.
+ *
+ * Author:
+ * Xiao Guangrong <guangrong.xiao@linux.intel.com>
+ *
+ * NFIT is defined in ACPI 6.0: 5.2.25 NVDIMM Firmware Interface Table (NFIT)
+ * and the DSM specification can be found at:
+ * http://pmem.io/documents/NVDIMM_DSM_Interface_Example.pdf
+ *
+ * Currently, it only supports PMEM Virtualization.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/uuid.h"
+#include "qapi/error.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/bios-linker-loader.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/mem/nvdimm.h"
+#include "qemu/nvdimm-utils.h"
+
+/*
+ * define Byte Addressable Persistent Memory (PM) Region according to
+ * ACPI 6.0: 5.2.25.1 System Physical Address Range Structure.
+ */
+static const uint8_t nvdimm_nfit_spa_uuid[] =
+ UUID_LE(0x66f0d379, 0xb4f3, 0x4074, 0xac, 0x43, 0x0d, 0x33,
+ 0x18, 0xb7, 0x8c, 0xdb);
+
+/*
+ * define NFIT structures according to ACPI 6.0: 5.2.25 NVDIMM Firmware
+ * Interface Table (NFIT).
+ */
+
+/*
+ * System Physical Address Range Structure
+ *
+ * It describes the system physical address ranges occupied by NVDIMMs and
+ * the types of the regions.
+ */
+struct NvdimmNfitSpa {
+ uint16_t type;
+ uint16_t length;
+ uint16_t spa_index;
+ uint16_t flags;
+ uint32_t reserved;
+ uint32_t proximity_domain;
+ uint8_t type_guid[16];
+ uint64_t spa_base;
+ uint64_t spa_length;
+ uint64_t mem_attr;
+} QEMU_PACKED;
+typedef struct NvdimmNfitSpa NvdimmNfitSpa;
+
+/*
+ * Memory Device to System Physical Address Range Mapping Structure
+ *
+ * It enables identifying each NVDIMM region and the corresponding SPA
+ * describing the memory interleave
+ */
+struct NvdimmNfitMemDev {
+ uint16_t type;
+ uint16_t length;
+ uint32_t nfit_handle;
+ uint16_t phys_id;
+ uint16_t region_id;
+ uint16_t spa_index;
+ uint16_t dcr_index;
+ uint64_t region_len;
+ uint64_t region_offset;
+ uint64_t region_dpa;
+ uint16_t interleave_index;
+ uint16_t interleave_ways;
+ uint16_t flags;
+ uint16_t reserved;
+} QEMU_PACKED;
+typedef struct NvdimmNfitMemDev NvdimmNfitMemDev;
+
+#define ACPI_NFIT_MEM_NOT_ARMED (1 << 3)
+
+/*
+ * NVDIMM Control Region Structure
+ *
+ * It describes the NVDIMM and if applicable, Block Control Window.
+ */
+struct NvdimmNfitControlRegion {
+ uint16_t type;
+ uint16_t length;
+ uint16_t dcr_index;
+ uint16_t vendor_id;
+ uint16_t device_id;
+ uint16_t revision_id;
+ uint16_t sub_vendor_id;
+ uint16_t sub_device_id;
+ uint16_t sub_revision_id;
+ uint8_t reserved[6];
+ uint32_t serial_number;
+ uint16_t fic;
+ uint16_t num_bcw;
+ uint64_t bcw_size;
+ uint64_t cmd_offset;
+ uint64_t cmd_size;
+ uint64_t status_offset;
+ uint64_t status_size;
+ uint16_t flags;
+ uint8_t reserved2[6];
+} QEMU_PACKED;
+typedef struct NvdimmNfitControlRegion NvdimmNfitControlRegion;
+
+/*
+ * NVDIMM Platform Capabilities Structure
+ *
+ * Defined in section 5.2.25.9 of ACPI 6.2 Errata A, September 2017
+ */
+struct NvdimmNfitPlatformCaps {
+ uint16_t type;
+ uint16_t length;
+ uint8_t highest_cap;
+ uint8_t reserved[3];
+ uint32_t capabilities;
+ uint8_t reserved2[4];
+} QEMU_PACKED;
+typedef struct NvdimmNfitPlatformCaps NvdimmNfitPlatformCaps;
+
+/*
+ * Module serial number is a unique number for each device. We use the
+ * slot id of NVDIMM device to generate this number so that each device
+ * associates with a different number.
+ *
+ * 0x123456 is a magic number we arbitrarily chose.
+ */
+static uint32_t nvdimm_slot_to_sn(int slot)
+{
+ return 0x123456 + slot;
+}
+
+/*
+ * handle is used to uniquely associate nfit_memdev structure with NVDIMM
+ * ACPI device - nfit_memdev.nfit_handle matches with the value returned
+ * by ACPI device _ADR method.
+ *
+ * We generate the handle with the slot id of NVDIMM device and reserve
+ * 0 for NVDIMM root device.
+ */
+static uint32_t nvdimm_slot_to_handle(int slot)
+{
+ return slot + 1;
+}
+
+/*
+ * index uniquely identifies the structure, 0 is reserved which indicates
+ * that the structure is not valid or the associated structure is not
+ * present.
+ *
+ * Each NVDIMM device needs two indexes, one for nfit_spa and another for
+ * nfit_dc which are generated by the slot id of NVDIMM device.
+ */
+static uint16_t nvdimm_slot_to_spa_index(int slot)
+{
+ return (slot + 1) << 1;
+}
+
+/* See the comments of nvdimm_slot_to_spa_index(). */
+static uint32_t nvdimm_slot_to_dcr_index(int slot)
+{
+ return nvdimm_slot_to_spa_index(slot) + 1;
+}
+
+static NVDIMMDevice *nvdimm_get_device_by_handle(uint32_t handle)
+{
+ NVDIMMDevice *nvdimm = NULL;
+ GSList *list, *device_list = nvdimm_get_device_list();
+
+ for (list = device_list; list; list = list->next) {
+ NVDIMMDevice *nvd = list->data;
+ int slot = object_property_get_int(OBJECT(nvd), PC_DIMM_SLOT_PROP,
+ NULL);
+
+ if (nvdimm_slot_to_handle(slot) == handle) {
+ nvdimm = nvd;
+ break;
+ }
+ }
+
+ g_slist_free(device_list);
+ return nvdimm;
+}
+
+/* ACPI 6.0: 5.2.25.1 System Physical Address Range Structure */
+static void
+nvdimm_build_structure_spa(GArray *structures, DeviceState *dev)
+{
+ NvdimmNfitSpa *nfit_spa;
+ uint64_t addr = object_property_get_uint(OBJECT(dev), PC_DIMM_ADDR_PROP,
+ NULL);
+ uint64_t size = object_property_get_uint(OBJECT(dev), PC_DIMM_SIZE_PROP,
+ NULL);
+ uint32_t node = object_property_get_uint(OBJECT(dev), PC_DIMM_NODE_PROP,
+ NULL);
+ int slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP,
+ NULL);
+
+ nfit_spa = acpi_data_push(structures, sizeof(*nfit_spa));
+
+ nfit_spa->type = cpu_to_le16(0 /* System Physical Address Range
+ Structure */);
+ nfit_spa->length = cpu_to_le16(sizeof(*nfit_spa));
+ nfit_spa->spa_index = cpu_to_le16(nvdimm_slot_to_spa_index(slot));
+
+ /*
+ * Control region is strict as all the device info, such as SN, index,
+ * is associated with slot id.
+ */
+ nfit_spa->flags = cpu_to_le16(1 /* Control region is strictly for
+ management during hot add/online
+ operation */ |
+ 2 /* Data in Proximity Domain field is
+ valid*/);
+
+ /* NUMA node. */
+ nfit_spa->proximity_domain = cpu_to_le32(node);
+ /* the region reported as PMEM. */
+ memcpy(nfit_spa->type_guid, nvdimm_nfit_spa_uuid,
+ sizeof(nvdimm_nfit_spa_uuid));
+
+ nfit_spa->spa_base = cpu_to_le64(addr);
+ nfit_spa->spa_length = cpu_to_le64(size);
+
+ /* It is the PMEM and can be cached as writeback. */
+ nfit_spa->mem_attr = cpu_to_le64(0x8ULL /* EFI_MEMORY_WB */ |
+ 0x8000ULL /* EFI_MEMORY_NV */);
+}
+
+/*
+ * ACPI 6.0: 5.2.25.2 Memory Device to System Physical Address Range Mapping
+ * Structure
+ */
+static void
+nvdimm_build_structure_memdev(GArray *structures, DeviceState *dev)
+{
+ NvdimmNfitMemDev *nfit_memdev;
+ NVDIMMDevice *nvdimm = NVDIMM(OBJECT(dev));
+ uint64_t size = object_property_get_uint(OBJECT(dev), PC_DIMM_SIZE_PROP,
+ NULL);
+ int slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP,
+ NULL);
+ uint32_t handle = nvdimm_slot_to_handle(slot);
+
+ nfit_memdev = acpi_data_push(structures, sizeof(*nfit_memdev));
+
+ nfit_memdev->type = cpu_to_le16(1 /* Memory Device to System Address
+ Range Map Structure*/);
+ nfit_memdev->length = cpu_to_le16(sizeof(*nfit_memdev));
+ nfit_memdev->nfit_handle = cpu_to_le32(handle);
+
+ /*
+ * associate memory device with System Physical Address Range
+ * Structure.
+ */
+ nfit_memdev->spa_index = cpu_to_le16(nvdimm_slot_to_spa_index(slot));
+ /* associate memory device with Control Region Structure. */
+ nfit_memdev->dcr_index = cpu_to_le16(nvdimm_slot_to_dcr_index(slot));
+
+ /* The memory region on the device. */
+ nfit_memdev->region_len = cpu_to_le64(size);
+ /* The device address starts from 0. */
+ nfit_memdev->region_dpa = cpu_to_le64(0);
+
+ /* Only one interleave for PMEM. */
+ nfit_memdev->interleave_ways = cpu_to_le16(1);
+
+ if (nvdimm->unarmed) {
+ nfit_memdev->flags |= cpu_to_le16(ACPI_NFIT_MEM_NOT_ARMED);
+ }
+}
+
+/*
+ * ACPI 6.0: 5.2.25.5 NVDIMM Control Region Structure.
+ */
+static void nvdimm_build_structure_dcr(GArray *structures, DeviceState *dev)
+{
+ NvdimmNfitControlRegion *nfit_dcr;
+ int slot = object_property_get_int(OBJECT(dev), PC_DIMM_SLOT_PROP,
+ NULL);
+ uint32_t sn = nvdimm_slot_to_sn(slot);
+
+ nfit_dcr = acpi_data_push(structures, sizeof(*nfit_dcr));
+
+ nfit_dcr->type = cpu_to_le16(4 /* NVDIMM Control Region Structure */);
+ nfit_dcr->length = cpu_to_le16(sizeof(*nfit_dcr));
+ nfit_dcr->dcr_index = cpu_to_le16(nvdimm_slot_to_dcr_index(slot));
+
+ /* vendor: Intel. */
+ nfit_dcr->vendor_id = cpu_to_le16(0x8086);
+ nfit_dcr->device_id = cpu_to_le16(1);
+
+ /* The _DSM method is following Intel's DSM specification. */
+ nfit_dcr->revision_id = cpu_to_le16(1 /* Current Revision supported
+ in ACPI 6.0 is 1. */);
+ nfit_dcr->serial_number = cpu_to_le32(sn);
+ nfit_dcr->fic = cpu_to_le16(0x301 /* Format Interface Code:
+ Byte addressable, no energy backed.
+ See ACPI 6.2, sect 5.2.25.6 and
+ JEDEC Annex L Release 3. */);
+}
+
+/*
+ * ACPI 6.2 Errata A: 5.2.25.9 NVDIMM Platform Capabilities Structure
+ */
+static void
+nvdimm_build_structure_caps(GArray *structures, uint32_t capabilities)
+{
+ NvdimmNfitPlatformCaps *nfit_caps;
+
+ nfit_caps = acpi_data_push(structures, sizeof(*nfit_caps));
+
+ nfit_caps->type = cpu_to_le16(7 /* NVDIMM Platform Capabilities */);
+ nfit_caps->length = cpu_to_le16(sizeof(*nfit_caps));
+ nfit_caps->highest_cap = 31 - clz32(capabilities);
+ nfit_caps->capabilities = cpu_to_le32(capabilities);
+}
+
+static GArray *nvdimm_build_device_structure(NVDIMMState *state)
+{
+ GSList *device_list, *list = nvdimm_get_device_list();
+ GArray *structures = g_array_new(false, true /* clear */, 1);
+
+ for (device_list = list; device_list; device_list = device_list->next) {
+ DeviceState *dev = device_list->data;
+
+ /* build System Physical Address Range Structure. */
+ nvdimm_build_structure_spa(structures, dev);
+
+ /*
+ * build Memory Device to System Physical Address Range Mapping
+ * Structure.
+ */
+ nvdimm_build_structure_memdev(structures, dev);
+
+ /* build NVDIMM Control Region Structure. */
+ nvdimm_build_structure_dcr(structures, dev);
+ }
+ g_slist_free(list);
+
+ if (state->persistence) {
+ nvdimm_build_structure_caps(structures, state->persistence);
+ }
+
+ return structures;
+}
+
+static void nvdimm_init_fit_buffer(NvdimmFitBuffer *fit_buf)
+{
+ fit_buf->fit = g_array_new(false, true /* clear */, 1);
+}
+
+static void nvdimm_build_fit_buffer(NVDIMMState *state)
+{
+ NvdimmFitBuffer *fit_buf = &state->fit_buf;
+
+ g_array_free(fit_buf->fit, true);
+ fit_buf->fit = nvdimm_build_device_structure(state);
+ fit_buf->dirty = true;
+}
+
+void nvdimm_plug(NVDIMMState *state)
+{
+ nvdimm_build_fit_buffer(state);
+}
+
+/*
+ * NVDIMM Firmware Interface Table
+ * @signature: "NFIT"
+ *
+ * It provides information that allows OSPM to enumerate NVDIMM present in
+ * the platform and associate system physical address ranges created by the
+ * NVDIMMs.
+ *
+ * It is defined in ACPI 6.0: 5.2.25 NVDIMM Firmware Interface Table (NFIT)
+ */
+
+static void nvdimm_build_nfit(NVDIMMState *state, GArray *table_offsets,
+ GArray *table_data, BIOSLinker *linker,
+ const char *oem_id, const char *oem_table_id)
+{
+ NvdimmFitBuffer *fit_buf = &state->fit_buf;
+ AcpiTable table = { .sig = "NFIT", .rev = 1,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_add_table(table_offsets, table_data);
+
+ acpi_table_begin(&table, table_data);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 4);
+ /* NVDIMM device structures. */
+ g_array_append_vals(table_data, fit_buf->fit->data, fit_buf->fit->len);
+ acpi_table_end(linker, &table);
+}
+
+#define NVDIMM_DSM_MEMORY_SIZE 4096
+
+struct NvdimmDsmIn {
+ uint32_t handle;
+ uint32_t revision;
+ uint32_t function;
+ /* the remaining size in the page is used by arg3. */
+ union {
+ uint8_t arg3[4084];
+ };
+} QEMU_PACKED;
+typedef struct NvdimmDsmIn NvdimmDsmIn;
+QEMU_BUILD_BUG_ON(sizeof(NvdimmDsmIn) != NVDIMM_DSM_MEMORY_SIZE);
+
+struct NvdimmDsmOut {
+ /* the size of buffer filled by QEMU. */
+ uint32_t len;
+ uint8_t data[4092];
+} QEMU_PACKED;
+typedef struct NvdimmDsmOut NvdimmDsmOut;
+QEMU_BUILD_BUG_ON(sizeof(NvdimmDsmOut) != NVDIMM_DSM_MEMORY_SIZE);
+
+struct NvdimmDsmFunc0Out {
+ /* the size of buffer filled by QEMU. */
+ uint32_t len;
+ uint32_t supported_func;
+} QEMU_PACKED;
+typedef struct NvdimmDsmFunc0Out NvdimmDsmFunc0Out;
+
+struct NvdimmDsmFuncNoPayloadOut {
+ /* the size of buffer filled by QEMU. */
+ uint32_t len;
+ uint32_t func_ret_status;
+} QEMU_PACKED;
+typedef struct NvdimmDsmFuncNoPayloadOut NvdimmDsmFuncNoPayloadOut;
+
+struct NvdimmFuncGetLabelSizeOut {
+ /* the size of buffer filled by QEMU. */
+ uint32_t len;
+ uint32_t func_ret_status; /* return status code. */
+ uint32_t label_size; /* the size of label data area. */
+ /*
+ * Maximum size of the namespace label data length supported by
+ * the platform in Get/Set Namespace Label Data functions.
+ */
+ uint32_t max_xfer;
+} QEMU_PACKED;
+typedef struct NvdimmFuncGetLabelSizeOut NvdimmFuncGetLabelSizeOut;
+QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncGetLabelSizeOut) > NVDIMM_DSM_MEMORY_SIZE);
+
+struct NvdimmFuncGetLabelDataIn {
+ uint32_t offset; /* the offset in the namespace label data area. */
+ uint32_t length; /* the size of data is to be read via the function. */
+} QEMU_PACKED;
+typedef struct NvdimmFuncGetLabelDataIn NvdimmFuncGetLabelDataIn;
+QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncGetLabelDataIn) +
+ offsetof(NvdimmDsmIn, arg3) > NVDIMM_DSM_MEMORY_SIZE);
+
+struct NvdimmFuncGetLabelDataOut {
+ /* the size of buffer filled by QEMU. */
+ uint32_t len;
+ uint32_t func_ret_status; /* return status code. */
+ uint8_t out_buf[]; /* the data got via Get Namesapce Label function. */
+} QEMU_PACKED;
+typedef struct NvdimmFuncGetLabelDataOut NvdimmFuncGetLabelDataOut;
+QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncGetLabelDataOut) > NVDIMM_DSM_MEMORY_SIZE);
+
+struct NvdimmFuncSetLabelDataIn {
+ uint32_t offset; /* the offset in the namespace label data area. */
+ uint32_t length; /* the size of data is to be written via the function. */
+ uint8_t in_buf[]; /* the data written to label data area. */
+} QEMU_PACKED;
+typedef struct NvdimmFuncSetLabelDataIn NvdimmFuncSetLabelDataIn;
+QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncSetLabelDataIn) +
+ offsetof(NvdimmDsmIn, arg3) > NVDIMM_DSM_MEMORY_SIZE);
+
+struct NvdimmFuncReadFITIn {
+ uint32_t offset; /* the offset into FIT buffer. */
+} QEMU_PACKED;
+typedef struct NvdimmFuncReadFITIn NvdimmFuncReadFITIn;
+QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncReadFITIn) +
+ offsetof(NvdimmDsmIn, arg3) > NVDIMM_DSM_MEMORY_SIZE);
+
+struct NvdimmFuncReadFITOut {
+ /* the size of buffer filled by QEMU. */
+ uint32_t len;
+ uint32_t func_ret_status; /* return status code. */
+ uint8_t fit[]; /* the FIT data. */
+} QEMU_PACKED;
+typedef struct NvdimmFuncReadFITOut NvdimmFuncReadFITOut;
+QEMU_BUILD_BUG_ON(sizeof(NvdimmFuncReadFITOut) > NVDIMM_DSM_MEMORY_SIZE);
+
+static void
+nvdimm_dsm_function0(uint32_t supported_func, hwaddr dsm_mem_addr)
+{
+ NvdimmDsmFunc0Out func0 = {
+ .len = cpu_to_le32(sizeof(func0)),
+ .supported_func = cpu_to_le32(supported_func),
+ };
+ cpu_physical_memory_write(dsm_mem_addr, &func0, sizeof(func0));
+}
+
+static void
+nvdimm_dsm_no_payload(uint32_t func_ret_status, hwaddr dsm_mem_addr)
+{
+ NvdimmDsmFuncNoPayloadOut out = {
+ .len = cpu_to_le32(sizeof(out)),
+ .func_ret_status = cpu_to_le32(func_ret_status),
+ };
+ cpu_physical_memory_write(dsm_mem_addr, &out, sizeof(out));
+}
+
+#define NVDIMM_DSM_RET_STATUS_SUCCESS 0 /* Success */
+#define NVDIMM_DSM_RET_STATUS_UNSUPPORT 1 /* Not Supported */
+#define NVDIMM_DSM_RET_STATUS_NOMEMDEV 2 /* Non-Existing Memory Device */
+#define NVDIMM_DSM_RET_STATUS_INVALID 3 /* Invalid Input Parameters */
+#define NVDIMM_DSM_RET_STATUS_FIT_CHANGED 0x100 /* FIT Changed */
+
+#define NVDIMM_QEMU_RSVD_HANDLE_ROOT 0x10000
+
+/* Read FIT data, defined in docs/specs/acpi_nvdimm.txt. */
+static void nvdimm_dsm_func_read_fit(NVDIMMState *state, NvdimmDsmIn *in,
+ hwaddr dsm_mem_addr)
+{
+ NvdimmFitBuffer *fit_buf = &state->fit_buf;
+ NvdimmFuncReadFITIn *read_fit;
+ NvdimmFuncReadFITOut *read_fit_out;
+ GArray *fit;
+ uint32_t read_len = 0, func_ret_status;
+ int size;
+
+ read_fit = (NvdimmFuncReadFITIn *)in->arg3;
+ read_fit->offset = le32_to_cpu(read_fit->offset);
+
+ fit = fit_buf->fit;
+
+ nvdimm_debug("Read FIT: offset 0x%x FIT size 0x%x Dirty %s.\n",
+ read_fit->offset, fit->len, fit_buf->dirty ? "Yes" : "No");
+
+ if (read_fit->offset > fit->len) {
+ func_ret_status = NVDIMM_DSM_RET_STATUS_INVALID;
+ goto exit;
+ }
+
+ /* It is the first time to read FIT. */
+ if (!read_fit->offset) {
+ fit_buf->dirty = false;
+ } else if (fit_buf->dirty) { /* FIT has been changed during RFIT. */
+ func_ret_status = NVDIMM_DSM_RET_STATUS_FIT_CHANGED;
+ goto exit;
+ }
+
+ func_ret_status = NVDIMM_DSM_RET_STATUS_SUCCESS;
+ read_len = MIN(fit->len - read_fit->offset,
+ NVDIMM_DSM_MEMORY_SIZE - sizeof(NvdimmFuncReadFITOut));
+
+exit:
+ size = sizeof(NvdimmFuncReadFITOut) + read_len;
+ read_fit_out = g_malloc(size);
+
+ read_fit_out->len = cpu_to_le32(size);
+ read_fit_out->func_ret_status = cpu_to_le32(func_ret_status);
+ memcpy(read_fit_out->fit, fit->data + read_fit->offset, read_len);
+
+ cpu_physical_memory_write(dsm_mem_addr, read_fit_out, size);
+
+ g_free(read_fit_out);
+}
+
+static void
+nvdimm_dsm_handle_reserved_root_method(NVDIMMState *state,
+ NvdimmDsmIn *in, hwaddr dsm_mem_addr)
+{
+ switch (in->function) {
+ case 0x0:
+ nvdimm_dsm_function0(0x1 | 1 << 1 /* Read FIT */, dsm_mem_addr);
+ return;
+ case 0x1 /* Read FIT */:
+ nvdimm_dsm_func_read_fit(state, in, dsm_mem_addr);
+ return;
+ }
+
+ nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_UNSUPPORT, dsm_mem_addr);
+}
+
+static void nvdimm_dsm_root(NvdimmDsmIn *in, hwaddr dsm_mem_addr)
+{
+ /*
+ * function 0 is called to inquire which functions are supported by
+ * OSPM
+ */
+ if (!in->function) {
+ nvdimm_dsm_function0(0 /* No function supported other than
+ function 0 */, dsm_mem_addr);
+ return;
+ }
+
+ /* No function except function 0 is supported yet. */
+ nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_UNSUPPORT, dsm_mem_addr);
+}
+
+/*
+ * the max transfer size is the max size transferred by both a
+ * 'Get Namespace Label Data' function and a 'Set Namespace Label Data'
+ * function.
+ */
+static uint32_t nvdimm_get_max_xfer_label_size(void)
+{
+ uint32_t max_get_size, max_set_size, dsm_memory_size;
+
+ dsm_memory_size = NVDIMM_DSM_MEMORY_SIZE;
+
+ /*
+ * the max data ACPI can read one time which is transferred by
+ * the response of 'Get Namespace Label Data' function.
+ */
+ max_get_size = dsm_memory_size - sizeof(NvdimmFuncGetLabelDataOut);
+
+ /*
+ * the max data ACPI can write one time which is transferred by
+ * 'Set Namespace Label Data' function.
+ */
+ max_set_size = dsm_memory_size - offsetof(NvdimmDsmIn, arg3) -
+ sizeof(NvdimmFuncSetLabelDataIn);
+
+ return MIN(max_get_size, max_set_size);
+}
+
+/*
+ * DSM Spec Rev1 4.4 Get Namespace Label Size (Function Index 4).
+ *
+ * It gets the size of Namespace Label data area and the max data size
+ * that Get/Set Namespace Label Data functions can transfer.
+ */
+static void nvdimm_dsm_label_size(NVDIMMDevice *nvdimm, hwaddr dsm_mem_addr)
+{
+ NvdimmFuncGetLabelSizeOut label_size_out = {
+ .len = cpu_to_le32(sizeof(label_size_out)),
+ };
+ uint32_t label_size, mxfer;
+
+ label_size = nvdimm->label_size;
+ mxfer = nvdimm_get_max_xfer_label_size();
+
+ nvdimm_debug("label_size 0x%x, max_xfer 0x%x.\n", label_size, mxfer);
+
+ label_size_out.func_ret_status = cpu_to_le32(NVDIMM_DSM_RET_STATUS_SUCCESS);
+ label_size_out.label_size = cpu_to_le32(label_size);
+ label_size_out.max_xfer = cpu_to_le32(mxfer);
+
+ cpu_physical_memory_write(dsm_mem_addr, &label_size_out,
+ sizeof(label_size_out));
+}
+
+static uint32_t nvdimm_rw_label_data_check(NVDIMMDevice *nvdimm,
+ uint32_t offset, uint32_t length)
+{
+ uint32_t ret = NVDIMM_DSM_RET_STATUS_INVALID;
+
+ if (offset + length < offset) {
+ nvdimm_debug("offset 0x%x + length 0x%x is overflow.\n", offset,
+ length);
+ return ret;
+ }
+
+ if (nvdimm->label_size < offset + length) {
+ nvdimm_debug("position 0x%x is beyond label data (len = %" PRIx64 ").\n",
+ offset + length, nvdimm->label_size);
+ return ret;
+ }
+
+ if (length > nvdimm_get_max_xfer_label_size()) {
+ nvdimm_debug("length (0x%x) is larger than max_xfer (0x%x).\n",
+ length, nvdimm_get_max_xfer_label_size());
+ return ret;
+ }
+
+ return NVDIMM_DSM_RET_STATUS_SUCCESS;
+}
+
+/*
+ * DSM Spec Rev1 4.5 Get Namespace Label Data (Function Index 5).
+ */
+static void nvdimm_dsm_get_label_data(NVDIMMDevice *nvdimm, NvdimmDsmIn *in,
+ hwaddr dsm_mem_addr)
+{
+ NVDIMMClass *nvc = NVDIMM_GET_CLASS(nvdimm);
+ NvdimmFuncGetLabelDataIn *get_label_data;
+ NvdimmFuncGetLabelDataOut *get_label_data_out;
+ uint32_t status;
+ int size;
+
+ get_label_data = (NvdimmFuncGetLabelDataIn *)in->arg3;
+ get_label_data->offset = le32_to_cpu(get_label_data->offset);
+ get_label_data->length = le32_to_cpu(get_label_data->length);
+
+ nvdimm_debug("Read Label Data: offset 0x%x length 0x%x.\n",
+ get_label_data->offset, get_label_data->length);
+
+ status = nvdimm_rw_label_data_check(nvdimm, get_label_data->offset,
+ get_label_data->length);
+ if (status != NVDIMM_DSM_RET_STATUS_SUCCESS) {
+ nvdimm_dsm_no_payload(status, dsm_mem_addr);
+ return;
+ }
+
+ size = sizeof(*get_label_data_out) + get_label_data->length;
+ assert(size <= NVDIMM_DSM_MEMORY_SIZE);
+ get_label_data_out = g_malloc(size);
+
+ get_label_data_out->len = cpu_to_le32(size);
+ get_label_data_out->func_ret_status =
+ cpu_to_le32(NVDIMM_DSM_RET_STATUS_SUCCESS);
+ nvc->read_label_data(nvdimm, get_label_data_out->out_buf,
+ get_label_data->length, get_label_data->offset);
+
+ cpu_physical_memory_write(dsm_mem_addr, get_label_data_out, size);
+ g_free(get_label_data_out);
+}
+
+/*
+ * DSM Spec Rev1 4.6 Set Namespace Label Data (Function Index 6).
+ */
+static void nvdimm_dsm_set_label_data(NVDIMMDevice *nvdimm, NvdimmDsmIn *in,
+ hwaddr dsm_mem_addr)
+{
+ NVDIMMClass *nvc = NVDIMM_GET_CLASS(nvdimm);
+ NvdimmFuncSetLabelDataIn *set_label_data;
+ uint32_t status;
+
+ set_label_data = (NvdimmFuncSetLabelDataIn *)in->arg3;
+
+ set_label_data->offset = le32_to_cpu(set_label_data->offset);
+ set_label_data->length = le32_to_cpu(set_label_data->length);
+
+ nvdimm_debug("Write Label Data: offset 0x%x length 0x%x.\n",
+ set_label_data->offset, set_label_data->length);
+
+ status = nvdimm_rw_label_data_check(nvdimm, set_label_data->offset,
+ set_label_data->length);
+ if (status != NVDIMM_DSM_RET_STATUS_SUCCESS) {
+ nvdimm_dsm_no_payload(status, dsm_mem_addr);
+ return;
+ }
+
+ assert(offsetof(NvdimmDsmIn, arg3) + sizeof(*set_label_data) +
+ set_label_data->length <= NVDIMM_DSM_MEMORY_SIZE);
+
+ nvc->write_label_data(nvdimm, set_label_data->in_buf,
+ set_label_data->length, set_label_data->offset);
+ nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_SUCCESS, dsm_mem_addr);
+}
+
+static void nvdimm_dsm_device(NvdimmDsmIn *in, hwaddr dsm_mem_addr)
+{
+ NVDIMMDevice *nvdimm = nvdimm_get_device_by_handle(in->handle);
+
+ /* See the comments in nvdimm_dsm_root(). */
+ if (!in->function) {
+ uint32_t supported_func = 0;
+
+ if (nvdimm && nvdimm->label_size) {
+ supported_func |= 0x1 /* Bit 0 indicates whether there is
+ support for any functions other
+ than function 0. */ |
+ 1 << 4 /* Get Namespace Label Size */ |
+ 1 << 5 /* Get Namespace Label Data */ |
+ 1 << 6 /* Set Namespace Label Data */;
+ }
+ nvdimm_dsm_function0(supported_func, dsm_mem_addr);
+ return;
+ }
+
+ if (!nvdimm) {
+ nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_NOMEMDEV,
+ dsm_mem_addr);
+ return;
+ }
+
+ /* Encode DSM function according to DSM Spec Rev1. */
+ switch (in->function) {
+ case 4 /* Get Namespace Label Size */:
+ if (nvdimm->label_size) {
+ nvdimm_dsm_label_size(nvdimm, dsm_mem_addr);
+ return;
+ }
+ break;
+ case 5 /* Get Namespace Label Data */:
+ if (nvdimm->label_size) {
+ nvdimm_dsm_get_label_data(nvdimm, in, dsm_mem_addr);
+ return;
+ }
+ break;
+ case 0x6 /* Set Namespace Label Data */:
+ if (nvdimm->label_size) {
+ nvdimm_dsm_set_label_data(nvdimm, in, dsm_mem_addr);
+ return;
+ }
+ break;
+ }
+
+ nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_UNSUPPORT, dsm_mem_addr);
+}
+
+static uint64_t
+nvdimm_dsm_read(void *opaque, hwaddr addr, unsigned size)
+{
+ nvdimm_debug("BUG: we never read _DSM IO Port.\n");
+ return 0;
+}
+
+static void
+nvdimm_dsm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+ NVDIMMState *state = opaque;
+ NvdimmDsmIn *in;
+ hwaddr dsm_mem_addr = val;
+
+ nvdimm_debug("dsm memory address 0x%" HWADDR_PRIx ".\n", dsm_mem_addr);
+
+ /*
+ * The DSM memory is mapped to guest address space so an evil guest
+ * can change its content while we are doing DSM emulation. Avoid
+ * this by copying DSM memory to QEMU local memory.
+ */
+ in = g_new(NvdimmDsmIn, 1);
+ cpu_physical_memory_read(dsm_mem_addr, in, sizeof(*in));
+
+ in->revision = le32_to_cpu(in->revision);
+ in->function = le32_to_cpu(in->function);
+ in->handle = le32_to_cpu(in->handle);
+
+ nvdimm_debug("Revision 0x%x Handler 0x%x Function 0x%x.\n", in->revision,
+ in->handle, in->function);
+
+ if (in->revision != 0x1 /* Currently we only support DSM Spec Rev1. */) {
+ nvdimm_debug("Revision 0x%x is not supported, expect 0x%x.\n",
+ in->revision, 0x1);
+ nvdimm_dsm_no_payload(NVDIMM_DSM_RET_STATUS_UNSUPPORT, dsm_mem_addr);
+ goto exit;
+ }
+
+ if (in->handle == NVDIMM_QEMU_RSVD_HANDLE_ROOT) {
+ nvdimm_dsm_handle_reserved_root_method(state, in, dsm_mem_addr);
+ goto exit;
+ }
+
+ /* Handle 0 is reserved for NVDIMM Root Device. */
+ if (!in->handle) {
+ nvdimm_dsm_root(in, dsm_mem_addr);
+ goto exit;
+ }
+
+ nvdimm_dsm_device(in, dsm_mem_addr);
+
+exit:
+ g_free(in);
+}
+
+static const MemoryRegionOps nvdimm_dsm_ops = {
+ .read = nvdimm_dsm_read,
+ .write = nvdimm_dsm_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+void nvdimm_acpi_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev)
+{
+ if (dev->hotplugged) {
+ acpi_send_event(DEVICE(hotplug_dev), ACPI_NVDIMM_HOTPLUG_STATUS);
+ }
+}
+
+void nvdimm_init_acpi_state(NVDIMMState *state, MemoryRegion *io,
+ struct AcpiGenericAddress dsm_io,
+ FWCfgState *fw_cfg, Object *owner)
+{
+ state->dsm_io = dsm_io;
+ memory_region_init_io(&state->io_mr, owner, &nvdimm_dsm_ops, state,
+ "nvdimm-acpi-io", dsm_io.bit_width >> 3);
+ memory_region_add_subregion(io, dsm_io.address, &state->io_mr);
+
+ state->dsm_mem = g_array_new(false, true /* clear */, 1);
+ acpi_data_push(state->dsm_mem, sizeof(NvdimmDsmIn));
+ fw_cfg_add_file(fw_cfg, NVDIMM_DSM_MEM_FILE, state->dsm_mem->data,
+ state->dsm_mem->len);
+
+ nvdimm_init_fit_buffer(&state->fit_buf);
+}
+
+#define NVDIMM_COMMON_DSM "NCAL"
+#define NVDIMM_ACPI_MEM_ADDR "MEMA"
+
+#define NVDIMM_DSM_MEMORY "NRAM"
+#define NVDIMM_DSM_IOPORT "NPIO"
+
+#define NVDIMM_DSM_NOTIFY "NTFI"
+#define NVDIMM_DSM_HANDLE "HDLE"
+#define NVDIMM_DSM_REVISION "REVS"
+#define NVDIMM_DSM_FUNCTION "FUNC"
+#define NVDIMM_DSM_ARG3 "FARG"
+
+#define NVDIMM_DSM_OUT_BUF_SIZE "RLEN"
+#define NVDIMM_DSM_OUT_BUF "ODAT"
+
+#define NVDIMM_DSM_RFIT_STATUS "RSTA"
+
+#define NVDIMM_QEMU_RSVD_UUID "648B9CF2-CDA1-4312-8AD9-49C4AF32BD62"
+
+static void nvdimm_build_common_dsm(Aml *dev,
+ NVDIMMState *nvdimm_state)
+{
+ Aml *method, *ifctx, *function, *handle, *uuid, *dsm_mem, *elsectx2;
+ Aml *elsectx, *unsupport, *unpatched, *expected_uuid, *uuid_invalid;
+ Aml *pckg, *pckg_index, *pckg_buf, *field, *dsm_out_buf, *dsm_out_buf_size;
+ Aml *whilectx, *offset;
+ uint8_t byte_list[1];
+ AmlRegionSpace rs;
+
+ method = aml_method(NVDIMM_COMMON_DSM, 5, AML_SERIALIZED);
+ uuid = aml_arg(0);
+ function = aml_arg(2);
+ handle = aml_arg(4);
+ dsm_mem = aml_local(6);
+ dsm_out_buf = aml_local(7);
+
+ aml_append(method, aml_store(aml_name(NVDIMM_ACPI_MEM_ADDR), dsm_mem));
+
+ if (nvdimm_state->dsm_io.space_id == AML_AS_SYSTEM_IO) {
+ rs = AML_SYSTEM_IO;
+ } else {
+ rs = AML_SYSTEM_MEMORY;
+ }
+
+ /* map DSM memory and IO into ACPI namespace. */
+ aml_append(method, aml_operation_region(NVDIMM_DSM_IOPORT, rs,
+ aml_int(nvdimm_state->dsm_io.address),
+ nvdimm_state->dsm_io.bit_width >> 3));
+ aml_append(method, aml_operation_region(NVDIMM_DSM_MEMORY,
+ AML_SYSTEM_MEMORY, dsm_mem, sizeof(NvdimmDsmIn)));
+
+ /*
+ * DSM notifier:
+ * NVDIMM_DSM_NOTIFY: write the address of DSM memory and notify QEMU to
+ * emulate the access.
+ *
+ * It is the IO port so that accessing them will cause VM-exit, the
+ * control will be transferred to QEMU.
+ */
+ field = aml_field(NVDIMM_DSM_IOPORT, AML_DWORD_ACC, AML_NOLOCK,
+ AML_PRESERVE);
+ aml_append(field, aml_named_field(NVDIMM_DSM_NOTIFY,
+ nvdimm_state->dsm_io.bit_width));
+ aml_append(method, field);
+
+ /*
+ * DSM input:
+ * NVDIMM_DSM_HANDLE: store device's handle, it's zero if the _DSM call
+ * happens on NVDIMM Root Device.
+ * NVDIMM_DSM_REVISION: store the Arg1 of _DSM call.
+ * NVDIMM_DSM_FUNCTION: store the Arg2 of _DSM call.
+ * NVDIMM_DSM_ARG3: store the Arg3 of _DSM call which is a Package
+ * containing function-specific arguments.
+ *
+ * They are RAM mapping on host so that these accesses never cause
+ * VM-EXIT.
+ */
+ field = aml_field(NVDIMM_DSM_MEMORY, AML_DWORD_ACC, AML_NOLOCK,
+ AML_PRESERVE);
+ aml_append(field, aml_named_field(NVDIMM_DSM_HANDLE,
+ sizeof(typeof_field(NvdimmDsmIn, handle)) * BITS_PER_BYTE));
+ aml_append(field, aml_named_field(NVDIMM_DSM_REVISION,
+ sizeof(typeof_field(NvdimmDsmIn, revision)) * BITS_PER_BYTE));
+ aml_append(field, aml_named_field(NVDIMM_DSM_FUNCTION,
+ sizeof(typeof_field(NvdimmDsmIn, function)) * BITS_PER_BYTE));
+ aml_append(field, aml_named_field(NVDIMM_DSM_ARG3,
+ (sizeof(NvdimmDsmIn) - offsetof(NvdimmDsmIn, arg3)) * BITS_PER_BYTE));
+ aml_append(method, field);
+
+ /*
+ * DSM output:
+ * NVDIMM_DSM_OUT_BUF_SIZE: the size of the buffer filled by QEMU.
+ * NVDIMM_DSM_OUT_BUF: the buffer QEMU uses to store the result.
+ *
+ * Since the page is reused by both input and out, the input data
+ * will be lost after storing new result into ODAT so we should fetch
+ * all the input data before writing the result.
+ */
+ field = aml_field(NVDIMM_DSM_MEMORY, AML_DWORD_ACC, AML_NOLOCK,
+ AML_PRESERVE);
+ aml_append(field, aml_named_field(NVDIMM_DSM_OUT_BUF_SIZE,
+ sizeof(typeof_field(NvdimmDsmOut, len)) * BITS_PER_BYTE));
+ aml_append(field, aml_named_field(NVDIMM_DSM_OUT_BUF,
+ (sizeof(NvdimmDsmOut) - offsetof(NvdimmDsmOut, data)) * BITS_PER_BYTE));
+ aml_append(method, field);
+
+ /*
+ * do not support any method if DSM memory address has not been
+ * patched.
+ */
+ unpatched = aml_equal(dsm_mem, aml_int(0x0));
+
+ expected_uuid = aml_local(0);
+
+ ifctx = aml_if(aml_equal(handle, aml_int(0x0)));
+ aml_append(ifctx, aml_store(
+ aml_touuid("2F10E7A4-9E91-11E4-89D3-123B93F75CBA")
+ /* UUID for NVDIMM Root Device */, expected_uuid));
+ aml_append(method, ifctx);
+ elsectx = aml_else();
+ ifctx = aml_if(aml_equal(handle, aml_int(NVDIMM_QEMU_RSVD_HANDLE_ROOT)));
+ aml_append(ifctx, aml_store(aml_touuid(NVDIMM_QEMU_RSVD_UUID
+ /* UUID for QEMU internal use */), expected_uuid));
+ aml_append(elsectx, ifctx);
+ elsectx2 = aml_else();
+ aml_append(elsectx2, aml_store(
+ aml_touuid("4309AC30-0D11-11E4-9191-0800200C9A66")
+ /* UUID for NVDIMM Devices */, expected_uuid));
+ aml_append(elsectx, elsectx2);
+ aml_append(method, elsectx);
+
+ uuid_invalid = aml_lnot(aml_equal(uuid, expected_uuid));
+
+ unsupport = aml_if(aml_or(unpatched, uuid_invalid, NULL));
+
+ /*
+ * function 0 is called to inquire what functions are supported by
+ * OSPM
+ */
+ ifctx = aml_if(aml_equal(function, aml_int(0)));
+ byte_list[0] = 0 /* No function Supported */;
+ aml_append(ifctx, aml_return(aml_buffer(1, byte_list)));
+ aml_append(unsupport, ifctx);
+
+ /* No function is supported yet. */
+ byte_list[0] = NVDIMM_DSM_RET_STATUS_UNSUPPORT;
+ aml_append(unsupport, aml_return(aml_buffer(1, byte_list)));
+ aml_append(method, unsupport);
+
+ /*
+ * The HDLE indicates the DSM function is issued from which device,
+ * it reserves 0 for root device and is the handle for NVDIMM devices.
+ * See the comments in nvdimm_slot_to_handle().
+ */
+ aml_append(method, aml_store(handle, aml_name(NVDIMM_DSM_HANDLE)));
+ aml_append(method, aml_store(aml_arg(1), aml_name(NVDIMM_DSM_REVISION)));
+ aml_append(method, aml_store(function, aml_name(NVDIMM_DSM_FUNCTION)));
+
+ /*
+ * The fourth parameter (Arg3) of _DSM is a package which contains
+ * a buffer, the layout of the buffer is specified by UUID (Arg0),
+ * Revision ID (Arg1) and Function Index (Arg2) which are documented
+ * in the DSM Spec.
+ */
+ pckg = aml_arg(3);
+ ifctx = aml_if(aml_and(aml_equal(aml_object_type(pckg),
+ aml_int(4 /* Package */)) /* It is a Package? */,
+ aml_equal(aml_sizeof(pckg), aml_int(1)) /* 1 element? */,
+ NULL));
+
+ pckg_index = aml_local(2);
+ pckg_buf = aml_local(3);
+ aml_append(ifctx, aml_store(aml_index(pckg, aml_int(0)), pckg_index));
+ aml_append(ifctx, aml_store(aml_derefof(pckg_index), pckg_buf));
+ aml_append(ifctx, aml_store(pckg_buf, aml_name(NVDIMM_DSM_ARG3)));
+ aml_append(method, ifctx);
+
+ /*
+ * tell QEMU about the real address of DSM memory, then QEMU
+ * gets the control and fills the result in DSM memory.
+ */
+ aml_append(method, aml_store(dsm_mem, aml_name(NVDIMM_DSM_NOTIFY)));
+
+ dsm_out_buf_size = aml_local(1);
+ /* RLEN is not included in the payload returned to guest. */
+ aml_append(method, aml_subtract(aml_name(NVDIMM_DSM_OUT_BUF_SIZE),
+ aml_int(4), dsm_out_buf_size));
+
+ /*
+ * As per ACPI spec 6.3, Table 19-419 Object Conversion Rules, if
+ * the Buffer Field <= to the size of an Integer (in bits), it will
+ * be treated as an integer. Moreover, the integer size depends on
+ * DSDT tables revision number. If revision number is < 2, integer
+ * size is 32 bits, otherwise it is 64 bits.
+ * Because of this CreateField() canot be used if RLEN < Integer Size.
+ *
+ * Also please note that APCI ASL operator SizeOf() doesn't support
+ * Integer and there isn't any other way to figure out the Integer
+ * size. Hence we assume 8 byte as Integer size and if RLEN < 8 bytes,
+ * build dsm_out_buf byte by byte.
+ */
+ ifctx = aml_if(aml_lless(dsm_out_buf_size, aml_int(8)));
+ offset = aml_local(2);
+ aml_append(ifctx, aml_store(aml_int(0), offset));
+ aml_append(ifctx, aml_name_decl("TBUF", aml_buffer(1, NULL)));
+ aml_append(ifctx, aml_store(aml_buffer(0, NULL), dsm_out_buf));
+
+ whilectx = aml_while(aml_lless(offset, dsm_out_buf_size));
+ /* Copy 1 byte at offset from ODAT to temporary buffer(TBUF). */
+ aml_append(whilectx, aml_store(aml_derefof(aml_index(
+ aml_name(NVDIMM_DSM_OUT_BUF), offset)),
+ aml_index(aml_name("TBUF"), aml_int(0))));
+ aml_append(whilectx, aml_concatenate(dsm_out_buf, aml_name("TBUF"),
+ dsm_out_buf));
+ aml_append(whilectx, aml_increment(offset));
+ aml_append(ifctx, whilectx);
+
+ aml_append(ifctx, aml_return(dsm_out_buf));
+ aml_append(method, ifctx);
+
+ /* If RLEN >= Integer size, just use CreateField() operator */
+ aml_append(method, aml_store(aml_shiftleft(dsm_out_buf_size, aml_int(3)),
+ dsm_out_buf_size));
+ aml_append(method, aml_create_field(aml_name(NVDIMM_DSM_OUT_BUF),
+ aml_int(0), dsm_out_buf_size, "OBUF"));
+ aml_append(method, aml_return(aml_name("OBUF")));
+
+ aml_append(dev, method);
+}
+
+static void nvdimm_build_device_dsm(Aml *dev, uint32_t handle)
+{
+ Aml *method;
+
+ method = aml_method("_DSM", 4, AML_NOTSERIALIZED);
+ aml_append(method, aml_return(aml_call5(NVDIMM_COMMON_DSM, aml_arg(0),
+ aml_arg(1), aml_arg(2), aml_arg(3),
+ aml_int(handle))));
+ aml_append(dev, method);
+}
+
+static void nvdimm_build_fit(Aml *dev)
+{
+ Aml *method, *pkg, *buf, *buf_size, *offset, *call_result;
+ Aml *whilectx, *ifcond, *ifctx, *elsectx, *fit;
+
+ buf = aml_local(0);
+ buf_size = aml_local(1);
+ fit = aml_local(2);
+
+ aml_append(dev, aml_name_decl(NVDIMM_DSM_RFIT_STATUS, aml_int(0)));
+
+ /* build helper function, RFIT. */
+ method = aml_method("RFIT", 1, AML_SERIALIZED);
+ aml_append(method, aml_name_decl("OFST", aml_int(0)));
+
+ /* prepare input package. */
+ pkg = aml_package(1);
+ aml_append(method, aml_store(aml_arg(0), aml_name("OFST")));
+ aml_append(pkg, aml_name("OFST"));
+
+ /* call Read_FIT function. */
+ call_result = aml_call5(NVDIMM_COMMON_DSM,
+ aml_touuid(NVDIMM_QEMU_RSVD_UUID),
+ aml_int(1) /* Revision 1 */,
+ aml_int(0x1) /* Read FIT */,
+ pkg, aml_int(NVDIMM_QEMU_RSVD_HANDLE_ROOT));
+ aml_append(method, aml_store(call_result, buf));
+
+ /* handle _DSM result. */
+ aml_append(method, aml_create_dword_field(buf,
+ aml_int(0) /* offset at byte 0 */, "STAU"));
+
+ aml_append(method, aml_store(aml_name("STAU"),
+ aml_name(NVDIMM_DSM_RFIT_STATUS)));
+
+ /* if something is wrong during _DSM. */
+ ifcond = aml_equal(aml_int(NVDIMM_DSM_RET_STATUS_SUCCESS),
+ aml_name("STAU"));
+ ifctx = aml_if(aml_lnot(ifcond));
+ aml_append(ifctx, aml_return(aml_buffer(0, NULL)));
+ aml_append(method, ifctx);
+
+ aml_append(method, aml_store(aml_sizeof(buf), buf_size));
+ aml_append(method, aml_subtract(buf_size,
+ aml_int(4) /* the size of "STAU" */,
+ buf_size));
+
+ /* if we read the end of fit. */
+ ifctx = aml_if(aml_equal(buf_size, aml_int(0)));
+ aml_append(ifctx, aml_return(aml_buffer(0, NULL)));
+ aml_append(method, ifctx);
+
+ aml_append(method, aml_create_field(buf,
+ aml_int(4 * BITS_PER_BYTE), /* offset at byte 4.*/
+ aml_shiftleft(buf_size, aml_int(3)), "BUFF"));
+ aml_append(method, aml_return(aml_name("BUFF")));
+ aml_append(dev, method);
+
+ /* build _FIT. */
+ method = aml_method("_FIT", 0, AML_SERIALIZED);
+ offset = aml_local(3);
+
+ aml_append(method, aml_store(aml_buffer(0, NULL), fit));
+ aml_append(method, aml_store(aml_int(0), offset));
+
+ whilectx = aml_while(aml_int(1));
+ aml_append(whilectx, aml_store(aml_call1("RFIT", offset), buf));
+ aml_append(whilectx, aml_store(aml_sizeof(buf), buf_size));
+
+ /*
+ * if fit buffer was changed during RFIT, read from the beginning
+ * again.
+ */
+ ifctx = aml_if(aml_equal(aml_name(NVDIMM_DSM_RFIT_STATUS),
+ aml_int(NVDIMM_DSM_RET_STATUS_FIT_CHANGED)));
+ aml_append(ifctx, aml_store(aml_buffer(0, NULL), fit));
+ aml_append(ifctx, aml_store(aml_int(0), offset));
+ aml_append(whilectx, ifctx);
+
+ elsectx = aml_else();
+
+ /* finish fit read if no data is read out. */
+ ifctx = aml_if(aml_equal(buf_size, aml_int(0)));
+ aml_append(ifctx, aml_return(fit));
+ aml_append(elsectx, ifctx);
+
+ /* update the offset. */
+ aml_append(elsectx, aml_add(offset, buf_size, offset));
+ /* append the data we read out to the fit buffer. */
+ aml_append(elsectx, aml_concatenate(fit, buf, fit));
+ aml_append(whilectx, elsectx);
+ aml_append(method, whilectx);
+
+ aml_append(dev, method);
+}
+
+static void nvdimm_build_nvdimm_devices(Aml *root_dev, uint32_t ram_slots)
+{
+ uint32_t slot;
+
+ for (slot = 0; slot < ram_slots; slot++) {
+ uint32_t handle = nvdimm_slot_to_handle(slot);
+ Aml *nvdimm_dev;
+
+ nvdimm_dev = aml_device("NV%02X", slot);
+
+ /*
+ * ACPI 6.0: 9.20 NVDIMM Devices:
+ *
+ * _ADR object that is used to supply OSPM with unique address
+ * of the NVDIMM device. This is done by returning the NFIT Device
+ * handle that is used to identify the associated entries in ACPI
+ * table NFIT or _FIT.
+ */
+ aml_append(nvdimm_dev, aml_name_decl("_ADR", aml_int(handle)));
+
+ nvdimm_build_device_dsm(nvdimm_dev, handle);
+ aml_append(root_dev, nvdimm_dev);
+ }
+}
+
+static void nvdimm_build_ssdt(GArray *table_offsets, GArray *table_data,
+ BIOSLinker *linker,
+ NVDIMMState *nvdimm_state,
+ uint32_t ram_slots, const char *oem_id)
+{
+ int mem_addr_offset;
+ Aml *ssdt, *sb_scope, *dev;
+ AcpiTable table = { .sig = "SSDT", .rev = 1,
+ .oem_id = oem_id, .oem_table_id = "NVDIMM" };
+
+ acpi_add_table(table_offsets, table_data);
+
+ acpi_table_begin(&table, table_data);
+ ssdt = init_aml_allocator();
+ sb_scope = aml_scope("\\_SB");
+
+ dev = aml_device("NVDR");
+
+ /*
+ * ACPI 6.0: 9.20 NVDIMM Devices:
+ *
+ * The ACPI Name Space device uses _HID of ACPI0012 to identify the root
+ * NVDIMM interface device. Platform firmware is required to contain one
+ * such device in _SB scope if NVDIMMs support is exposed by platform to
+ * OSPM.
+ * For each NVDIMM present or intended to be supported by platform,
+ * platform firmware also exposes an ACPI Namespace Device under the
+ * root device.
+ */
+ aml_append(dev, aml_name_decl("_HID", aml_string("ACPI0012")));
+
+ nvdimm_build_common_dsm(dev, nvdimm_state);
+
+ /* 0 is reserved for root device. */
+ nvdimm_build_device_dsm(dev, 0);
+ nvdimm_build_fit(dev);
+
+ nvdimm_build_nvdimm_devices(dev, ram_slots);
+
+ aml_append(sb_scope, dev);
+ aml_append(ssdt, sb_scope);
+
+ /* copy AML table into ACPI tables blob and patch header there */
+ g_array_append_vals(table_data, ssdt->buf->data, ssdt->buf->len);
+ mem_addr_offset = build_append_named_dword(table_data,
+ NVDIMM_ACPI_MEM_ADDR);
+
+ bios_linker_loader_alloc(linker,
+ NVDIMM_DSM_MEM_FILE, nvdimm_state->dsm_mem,
+ sizeof(NvdimmDsmIn), false /* high memory */);
+ bios_linker_loader_add_pointer(linker,
+ ACPI_BUILD_TABLE_FILE, mem_addr_offset, sizeof(uint32_t),
+ NVDIMM_DSM_MEM_FILE, 0);
+ free_aml_allocator();
+ /*
+ * must be executed as the last so that pointer patching command above
+ * would be executed by guest before it recalculated checksum which were
+ * scheduled by acpi_table_end()
+ */
+ acpi_table_end(linker, &table);
+}
+
+void nvdimm_build_srat(GArray *table_data)
+{
+ GSList *device_list, *list = nvdimm_get_device_list();
+
+ for (device_list = list; device_list; device_list = device_list->next) {
+ DeviceState *dev = device_list->data;
+ Object *obj = OBJECT(dev);
+ uint64_t addr, size;
+ int node;
+
+ node = object_property_get_int(obj, PC_DIMM_NODE_PROP, &error_abort);
+ addr = object_property_get_uint(obj, PC_DIMM_ADDR_PROP, &error_abort);
+ size = object_property_get_uint(obj, PC_DIMM_SIZE_PROP, &error_abort);
+
+ build_srat_memory(table_data, addr, size, node,
+ MEM_AFFINITY_ENABLED | MEM_AFFINITY_NON_VOLATILE);
+ }
+ g_slist_free(list);
+}
+
+void nvdimm_build_acpi(GArray *table_offsets, GArray *table_data,
+ BIOSLinker *linker, NVDIMMState *state,
+ uint32_t ram_slots, const char *oem_id,
+ const char *oem_table_id)
+{
+ GSList *device_list;
+
+ /* no nvdimm device can be plugged. */
+ if (!ram_slots) {
+ return;
+ }
+
+ nvdimm_build_ssdt(table_offsets, table_data, linker, state,
+ ram_slots, oem_id);
+
+ device_list = nvdimm_get_device_list();
+ /* no NVDIMM device is plugged. */
+ if (!device_list) {
+ return;
+ }
+
+ nvdimm_build_nfit(state, table_offsets, table_data, linker,
+ oem_id, oem_table_id);
+ g_slist_free(device_list);
+}
diff --git a/hw/acpi/pci.c b/hw/acpi/pci.c
new file mode 100644
index 000000000..20b70dcd8
--- /dev/null
+++ b/hw/acpi/pci.c
@@ -0,0 +1,61 @@
+/*
+ * Support for generating PCI related ACPI tables and passing them to Guests
+ *
+ * Copyright (C) 2006 Fabrice Bellard
+ * Copyright (C) 2008-2010 Kevin O'Connor <kevin@koconnor.net>
+ * Copyright (C) 2013-2019 Red Hat Inc
+ * Copyright (C) 2019 Intel Corporation
+ *
+ * Author: Wei Yang <richardw.yang@linux.intel.com>
+ * 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"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/pci.h"
+#include "hw/pci/pcie_host.h"
+
+/*
+ * PCI Firmware Specification, Revision 3.0
+ * 4.1.2 MCFG Table Description.
+ */
+void build_mcfg(GArray *table_data, BIOSLinker *linker, AcpiMcfgInfo *info,
+ const char *oem_id, const char *oem_table_id)
+{
+ AcpiTable table = { .sig = "MCFG", .rev = 1,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+
+ acpi_table_begin(&table, table_data);
+
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 8);
+ /*
+ * Memory Mapped Enhanced Configuration Space Base Address Allocation
+ * Structure
+ */
+ /* Base address, processor-relative */
+ build_append_int_noprefix(table_data, info->base, 8);
+ /* PCI segment group number */
+ build_append_int_noprefix(table_data, 0, 2);
+ /* Starting PCI Bus number */
+ build_append_int_noprefix(table_data, 0, 1);
+ /* Final PCI Bus number */
+ build_append_int_noprefix(table_data, PCIE_MMCFG_BUS(info->size - 1), 1);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 4);
+
+ acpi_table_end(linker, &table);
+}
diff --git a/hw/acpi/pcihp.c b/hw/acpi/pcihp.c
new file mode 100644
index 000000000..30405b511
--- /dev/null
+++ b/hw/acpi/pcihp.c
@@ -0,0 +1,564 @@
+/*
+ * QEMU<->ACPI BIOS PCI hotplug interface
+ *
+ * QEMU supports PCI hotplug via ACPI. This module
+ * implements the interface between QEMU and the ACPI BIOS.
+ * Interface specification - see docs/specs/acpi_pci_hotplug.txt
+ *
+ * Copyright (c) 2013, Red Hat Inc, Michael S. Tsirkin (mst@redhat.com)
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/acpi/pcihp.h"
+
+#include "hw/pci-host/i440fx.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_bridge.h"
+#include "hw/pci/pci_host.h"
+#include "hw/pci/pcie_port.h"
+#include "hw/i386/acpi-build.h"
+#include "hw/acpi/acpi.h"
+#include "hw/pci/pci_bus.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qom/qom-qobject.h"
+#include "trace.h"
+
+#define ACPI_PCIHP_SIZE 0x0018
+#define PCI_UP_BASE 0x0000
+#define PCI_DOWN_BASE 0x0004
+#define PCI_EJ_BASE 0x0008
+#define PCI_RMV_BASE 0x000c
+#define PCI_SEL_BASE 0x0010
+#define PCI_AIDX_BASE 0x0014
+
+typedef struct AcpiPciHpFind {
+ int bsel;
+ PCIBus *bus;
+} AcpiPciHpFind;
+
+static gint g_cmp_uint32(gconstpointer a, gconstpointer b, gpointer user_data)
+{
+ return a - b;
+}
+
+static GSequence *pci_acpi_index_list(void)
+{
+ static GSequence *used_acpi_index_list;
+
+ if (!used_acpi_index_list) {
+ used_acpi_index_list = g_sequence_new(NULL);
+ }
+ return used_acpi_index_list;
+}
+
+static int acpi_pcihp_get_bsel(PCIBus *bus)
+{
+ Error *local_err = NULL;
+ uint64_t bsel = object_property_get_uint(OBJECT(bus), ACPI_PCIHP_PROP_BSEL,
+ &local_err);
+
+ if (local_err || bsel >= ACPI_PCIHP_MAX_HOTPLUG_BUS) {
+ if (local_err) {
+ error_free(local_err);
+ }
+ return -1;
+ } else {
+ return bsel;
+ }
+}
+
+/* Assign BSEL property to all buses. In the future, this can be changed
+ * to only assign to buses that support hotplug.
+ */
+static void *acpi_set_bsel(PCIBus *bus, void *opaque)
+{
+ unsigned *bsel_alloc = opaque;
+ unsigned *bus_bsel;
+
+ if (qbus_is_hotpluggable(BUS(bus))) {
+ bus_bsel = g_malloc(sizeof *bus_bsel);
+
+ *bus_bsel = (*bsel_alloc)++;
+ object_property_add_uint32_ptr(OBJECT(bus), ACPI_PCIHP_PROP_BSEL,
+ bus_bsel, OBJ_PROP_FLAG_READ);
+ }
+
+ return bsel_alloc;
+}
+
+static void acpi_set_pci_info(void)
+{
+ static bool bsel_is_set;
+ Object *host = acpi_get_i386_pci_host();
+ PCIBus *bus;
+ unsigned bsel_alloc = ACPI_PCIHP_BSEL_DEFAULT;
+
+ if (bsel_is_set) {
+ return;
+ }
+ bsel_is_set = true;
+
+ if (!host) {
+ return;
+ }
+
+ bus = PCI_HOST_BRIDGE(host)->bus;
+ if (bus) {
+ /* Scan all PCI buses. Set property to enable acpi based hotplug. */
+ pci_for_each_bus_depth_first(bus, acpi_set_bsel, NULL, &bsel_alloc);
+ }
+}
+
+static void acpi_pcihp_disable_root_bus(void)
+{
+ static bool root_hp_disabled;
+ Object *host = acpi_get_i386_pci_host();
+ PCIBus *bus;
+
+ if (root_hp_disabled) {
+ return;
+ }
+
+ bus = PCI_HOST_BRIDGE(host)->bus;
+ if (bus) {
+ /* setting the hotplug handler to NULL makes the bus non-hotpluggable */
+ qbus_set_hotplug_handler(BUS(bus), NULL);
+ }
+ root_hp_disabled = true;
+ return;
+}
+
+static void acpi_pcihp_test_hotplug_bus(PCIBus *bus, void *opaque)
+{
+ AcpiPciHpFind *find = opaque;
+ if (find->bsel == acpi_pcihp_get_bsel(bus)) {
+ find->bus = bus;
+ }
+}
+
+static PCIBus *acpi_pcihp_find_hotplug_bus(AcpiPciHpState *s, int bsel)
+{
+ AcpiPciHpFind find = { .bsel = bsel, .bus = NULL };
+
+ if (bsel < 0) {
+ return NULL;
+ }
+
+ pci_for_each_bus(s->root, acpi_pcihp_test_hotplug_bus, &find);
+
+ /* Make bsel 0 eject root bus if bsel property is not set,
+ * for compatibility with non acpi setups.
+ * TODO: really needed?
+ */
+ if (!bsel && !find.bus) {
+ find.bus = s->root;
+ }
+
+ /*
+ * Check if find.bus is actually hotpluggable. If bsel is set to
+ * NULL for example on the root bus in order to make it
+ * non-hotpluggable, find.bus will match the root bus when bsel
+ * is 0. See acpi_pcihp_test_hotplug_bus() above. Since the
+ * bus is not hotpluggable however, we should not select the bus.
+ * Instead, we should set find.bus to NULL in that case. In the check
+ * below, we generalize this case for all buses, not just the root bus.
+ * The callers of this function check for a null return value and
+ * handle them appropriately.
+ */
+ if (find.bus && !qbus_is_hotpluggable(BUS(find.bus))) {
+ find.bus = NULL;
+ }
+ return find.bus;
+}
+
+static bool acpi_pcihp_pc_no_hotplug(AcpiPciHpState *s, PCIDevice *dev)
+{
+ PCIDeviceClass *pc = PCI_DEVICE_GET_CLASS(dev);
+ DeviceClass *dc = DEVICE_GET_CLASS(dev);
+ /*
+ * ACPI doesn't allow hotplug of bridge devices. Don't allow
+ * hot-unplug of bridge devices unless they were added by hotplug
+ * (and so, not described by acpi).
+ */
+ return (pc->is_bridge && !dev->qdev.hotplugged) || !dc->hotpluggable;
+}
+
+static void acpi_pcihp_eject_slot(AcpiPciHpState *s, unsigned bsel, unsigned slots)
+{
+ HotplugHandler *hotplug_ctrl;
+ BusChild *kid, *next;
+ int slot = ctz32(slots);
+ PCIBus *bus = acpi_pcihp_find_hotplug_bus(s, bsel);
+
+ trace_acpi_pci_eject_slot(bsel, slot);
+
+ if (!bus || slot > 31) {
+ return;
+ }
+
+ /* Mark request as complete */
+ s->acpi_pcihp_pci_status[bsel].down &= ~(1U << slot);
+ s->acpi_pcihp_pci_status[bsel].up &= ~(1U << slot);
+
+ QTAILQ_FOREACH_SAFE(kid, &bus->qbus.children, sibling, next) {
+ DeviceState *qdev = kid->child;
+ PCIDevice *dev = PCI_DEVICE(qdev);
+ if (PCI_SLOT(dev->devfn) == slot) {
+ if (!acpi_pcihp_pc_no_hotplug(s, dev)) {
+ /*
+ * partially_hotplugged is used by virtio-net failover:
+ * failover has asked the guest OS to unplug the device
+ * but we need to keep some references to the device
+ * to be able to plug it back in case of failure so
+ * we don't execute hotplug_handler_unplug().
+ */
+ if (dev->partially_hotplugged) {
+ /*
+ * pending_deleted_event is set to true when
+ * virtio-net failover asks to unplug the device,
+ * and set to false here when the operation is done
+ * This is used by the migration loop to detect the
+ * end of the operation and really start the migration.
+ */
+ qdev->pending_deleted_event = false;
+ } else {
+ hotplug_ctrl = qdev_get_hotplug_handler(qdev);
+ hotplug_handler_unplug(hotplug_ctrl, qdev, &error_abort);
+ object_unparent(OBJECT(qdev));
+ }
+ }
+ }
+ }
+}
+
+static void acpi_pcihp_update_hotplug_bus(AcpiPciHpState *s, int bsel)
+{
+ BusChild *kid, *next;
+ PCIBus *bus = acpi_pcihp_find_hotplug_bus(s, bsel);
+
+ /* Execute any pending removes during reset */
+ while (s->acpi_pcihp_pci_status[bsel].down) {
+ acpi_pcihp_eject_slot(s, bsel, s->acpi_pcihp_pci_status[bsel].down);
+ }
+
+ s->acpi_pcihp_pci_status[bsel].hotplug_enable = ~0;
+
+ if (!bus) {
+ return;
+ }
+ QTAILQ_FOREACH_SAFE(kid, &bus->qbus.children, sibling, next) {
+ DeviceState *qdev = kid->child;
+ PCIDevice *pdev = PCI_DEVICE(qdev);
+ int slot = PCI_SLOT(pdev->devfn);
+
+ if (acpi_pcihp_pc_no_hotplug(s, pdev)) {
+ s->acpi_pcihp_pci_status[bsel].hotplug_enable &= ~(1U << slot);
+ }
+ }
+}
+
+static void acpi_pcihp_update(AcpiPciHpState *s)
+{
+ int i;
+
+ for (i = 0; i < ACPI_PCIHP_MAX_HOTPLUG_BUS; ++i) {
+ acpi_pcihp_update_hotplug_bus(s, i);
+ }
+}
+
+void acpi_pcihp_reset(AcpiPciHpState *s, bool acpihp_root_off)
+{
+ if (acpihp_root_off) {
+ acpi_pcihp_disable_root_bus();
+ }
+ acpi_set_pci_info();
+ acpi_pcihp_update(s);
+}
+
+#define ONBOARD_INDEX_MAX (16 * 1024 - 1)
+
+void acpi_pcihp_device_pre_plug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PCIDevice *pdev = PCI_DEVICE(dev);
+
+ /* Only hotplugged devices need the hotplug capability. */
+ if (dev->hotplugged &&
+ acpi_pcihp_get_bsel(pci_get_bus(pdev)) < 0) {
+ error_setg(errp, "Unsupported bus. Bus doesn't have property '"
+ ACPI_PCIHP_PROP_BSEL "' set");
+ return;
+ }
+
+ /*
+ * capped by systemd (see: udev-builtin-net_id.c)
+ * as it's the only known user honor it to avoid users
+ * misconfigure QEMU and then wonder why acpi-index doesn't work
+ */
+ if (pdev->acpi_index > ONBOARD_INDEX_MAX) {
+ error_setg(errp, "acpi-index should be less or equal to %u",
+ ONBOARD_INDEX_MAX);
+ return;
+ }
+
+ /*
+ * make sure that acpi-index is unique across all present PCI devices
+ */
+ if (pdev->acpi_index) {
+ GSequence *used_indexes = pci_acpi_index_list();
+
+ if (g_sequence_lookup(used_indexes, GINT_TO_POINTER(pdev->acpi_index),
+ g_cmp_uint32, NULL)) {
+ error_setg(errp, "a PCI device with acpi-index = %" PRIu32
+ " already exist", pdev->acpi_index);
+ return;
+ }
+ g_sequence_insert_sorted(used_indexes,
+ GINT_TO_POINTER(pdev->acpi_index),
+ g_cmp_uint32, NULL);
+ }
+}
+
+void acpi_pcihp_device_plug_cb(HotplugHandler *hotplug_dev, AcpiPciHpState *s,
+ DeviceState *dev, Error **errp)
+{
+ PCIDevice *pdev = PCI_DEVICE(dev);
+ int slot = PCI_SLOT(pdev->devfn);
+ int bsel;
+
+ /* Don't send event when device is enabled during qemu machine creation:
+ * it is present on boot, no hotplug event is necessary. We do send an
+ * event when the device is disabled later. */
+ if (!dev->hotplugged) {
+ /*
+ * Overwrite the default hotplug handler with the ACPI PCI one
+ * for cold plugged bridges only.
+ */
+ if (!s->legacy_piix &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PCI_BRIDGE)) {
+ PCIBus *sec = pci_bridge_get_sec_bus(PCI_BRIDGE(pdev));
+
+ /* Remove all hot-plug handlers if hot-plug is disabled on slot */
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PCIE_SLOT) &&
+ !PCIE_SLOT(pdev)->hotplug) {
+ qbus_set_hotplug_handler(BUS(sec), NULL);
+ return;
+ }
+
+ qbus_set_hotplug_handler(BUS(sec), OBJECT(hotplug_dev));
+ /* We don't have to overwrite any other hotplug handler yet */
+ assert(QLIST_EMPTY(&sec->child));
+ }
+
+ return;
+ }
+
+ bsel = acpi_pcihp_get_bsel(pci_get_bus(pdev));
+ g_assert(bsel >= 0);
+ s->acpi_pcihp_pci_status[bsel].up |= (1U << slot);
+ acpi_send_event(DEVICE(hotplug_dev), ACPI_PCI_HOTPLUG_STATUS);
+}
+
+void acpi_pcihp_device_unplug_cb(HotplugHandler *hotplug_dev, AcpiPciHpState *s,
+ DeviceState *dev, Error **errp)
+{
+ PCIDevice *pdev = PCI_DEVICE(dev);
+
+ trace_acpi_pci_unplug(PCI_SLOT(pdev->devfn),
+ acpi_pcihp_get_bsel(pci_get_bus(pdev)));
+
+ /*
+ * clean up acpi-index so it could reused by another device
+ */
+ if (pdev->acpi_index) {
+ GSequence *used_indexes = pci_acpi_index_list();
+
+ g_sequence_remove(g_sequence_lookup(used_indexes,
+ GINT_TO_POINTER(pdev->acpi_index),
+ g_cmp_uint32, NULL));
+ }
+
+ qdev_unrealize(dev);
+}
+
+void acpi_pcihp_device_unplug_request_cb(HotplugHandler *hotplug_dev,
+ AcpiPciHpState *s, DeviceState *dev,
+ Error **errp)
+{
+ PCIDevice *pdev = PCI_DEVICE(dev);
+ int slot = PCI_SLOT(pdev->devfn);
+ int bsel = acpi_pcihp_get_bsel(pci_get_bus(pdev));
+
+ trace_acpi_pci_unplug_request(bsel, slot);
+
+ if (bsel < 0) {
+ error_setg(errp, "Unsupported bus. Bus doesn't have property '"
+ ACPI_PCIHP_PROP_BSEL "' set");
+ return;
+ }
+
+ /*
+ * pending_deleted_event is used by virtio-net failover to detect the
+ * end of the unplug operation, the flag is set to false in
+ * acpi_pcihp_eject_slot() when the operation is completed.
+ */
+ pdev->qdev.pending_deleted_event = true;
+ s->acpi_pcihp_pci_status[bsel].down |= (1U << slot);
+ acpi_send_event(DEVICE(hotplug_dev), ACPI_PCI_HOTPLUG_STATUS);
+}
+
+static uint64_t pci_read(void *opaque, hwaddr addr, unsigned int size)
+{
+ AcpiPciHpState *s = opaque;
+ uint32_t val = 0;
+ int bsel = s->hotplug_select;
+
+ if (bsel < 0 || bsel >= ACPI_PCIHP_MAX_HOTPLUG_BUS) {
+ return 0;
+ }
+
+ switch (addr) {
+ case PCI_UP_BASE:
+ val = s->acpi_pcihp_pci_status[bsel].up;
+ if (!s->legacy_piix) {
+ s->acpi_pcihp_pci_status[bsel].up = 0;
+ }
+ trace_acpi_pci_up_read(val);
+ break;
+ case PCI_DOWN_BASE:
+ val = s->acpi_pcihp_pci_status[bsel].down;
+ trace_acpi_pci_down_read(val);
+ break;
+ case PCI_EJ_BASE:
+ trace_acpi_pci_features_read(val);
+ break;
+ case PCI_RMV_BASE:
+ val = s->acpi_pcihp_pci_status[bsel].hotplug_enable;
+ trace_acpi_pci_rmv_read(val);
+ break;
+ case PCI_SEL_BASE:
+ val = s->hotplug_select;
+ trace_acpi_pci_sel_read(val);
+ break;
+ case PCI_AIDX_BASE:
+ val = s->acpi_index;
+ s->acpi_index = 0;
+ trace_acpi_pci_acpi_index_read(val);
+ break;
+ default:
+ break;
+ }
+
+ return val;
+}
+
+static void pci_write(void *opaque, hwaddr addr, uint64_t data,
+ unsigned int size)
+{
+ int slot;
+ PCIBus *bus;
+ BusChild *kid, *next;
+ AcpiPciHpState *s = opaque;
+
+ s->acpi_index = 0;
+ switch (addr) {
+ case PCI_AIDX_BASE:
+ /*
+ * fetch acpi-index for specified slot so that follow up read from
+ * PCI_AIDX_BASE can return it to guest
+ */
+ slot = ctz32(data);
+
+ if (s->hotplug_select >= ACPI_PCIHP_MAX_HOTPLUG_BUS) {
+ break;
+ }
+
+ bus = acpi_pcihp_find_hotplug_bus(s, s->hotplug_select);
+ QTAILQ_FOREACH_SAFE(kid, &bus->qbus.children, sibling, next) {
+ Object *o = OBJECT(kid->child);
+ PCIDevice *dev = PCI_DEVICE(o);
+ if (PCI_SLOT(dev->devfn) == slot) {
+ s->acpi_index = object_property_get_uint(o, "acpi-index", NULL);
+ break;
+ }
+ }
+ trace_acpi_pci_acpi_index_write(s->hotplug_select, slot, s->acpi_index);
+ break;
+ case PCI_EJ_BASE:
+ if (s->hotplug_select >= ACPI_PCIHP_MAX_HOTPLUG_BUS) {
+ break;
+ }
+ acpi_pcihp_eject_slot(s, s->hotplug_select, data);
+ trace_acpi_pci_ej_write(addr, data);
+ break;
+ case PCI_SEL_BASE:
+ s->hotplug_select = s->legacy_piix ? ACPI_PCIHP_BSEL_DEFAULT : data;
+ trace_acpi_pci_sel_write(addr, data);
+ default:
+ break;
+ }
+}
+
+static const MemoryRegionOps acpi_pcihp_io_ops = {
+ .read = pci_read,
+ .write = pci_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+void acpi_pcihp_init(Object *owner, AcpiPciHpState *s, PCIBus *root_bus,
+ MemoryRegion *address_space_io, bool bridges_enabled,
+ uint16_t io_base)
+{
+ s->io_len = ACPI_PCIHP_SIZE;
+ s->io_base = io_base;
+
+ s->root = root_bus;
+ s->legacy_piix = !bridges_enabled;
+
+ memory_region_init_io(&s->io, owner, &acpi_pcihp_io_ops, s,
+ "acpi-pci-hotplug", s->io_len);
+ memory_region_add_subregion(address_space_io, s->io_base, &s->io);
+
+ object_property_add_uint16_ptr(owner, ACPI_PCIHP_IO_BASE_PROP, &s->io_base,
+ OBJ_PROP_FLAG_READ);
+ object_property_add_uint16_ptr(owner, ACPI_PCIHP_IO_LEN_PROP, &s->io_len,
+ OBJ_PROP_FLAG_READ);
+}
+
+bool vmstate_acpi_pcihp_use_acpi_index(void *opaque, int version_id)
+{
+ AcpiPciHpState *s = opaque;
+ return s->acpi_index;
+}
+
+const VMStateDescription vmstate_acpi_pcihp_pci_status = {
+ .name = "acpi_pcihp_pci_status",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(up, AcpiPciHpPciStatus),
+ VMSTATE_UINT32(down, AcpiPciHpPciStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
diff --git a/hw/acpi/piix4.c b/hw/acpi/piix4.c
new file mode 100644
index 000000000..f0b5fac44
--- /dev/null
+++ b/hw/acpi/piix4.c
@@ -0,0 +1,710 @@
+/*
+ * ACPI implementation
+ *
+ * Copyright (c) 2006 Fabrice Bellard
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ *
+ * Contributions after 2012-01-13 are licensed under the terms of the
+ * GNU GPL, version 2 or (at your option) any later version.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/i386/pc.h"
+#include "hw/southbridge/piix.h"
+#include "hw/irq.h"
+#include "hw/isa/apm.h"
+#include "hw/i2c/pm_smbus.h"
+#include "hw/pci/pci.h"
+#include "hw/qdev-properties.h"
+#include "hw/acpi/acpi.h"
+#include "sysemu/runstate.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/xen.h"
+#include "qapi/error.h"
+#include "qemu/range.h"
+#include "hw/acpi/pcihp.h"
+#include "hw/acpi/cpu_hotplug.h"
+#include "hw/acpi/cpu.h"
+#include "hw/hotplug.h"
+#include "hw/mem/pc-dimm.h"
+#include "hw/mem/nvdimm.h"
+#include "hw/acpi/memory_hotplug.h"
+#include "hw/acpi/acpi_dev_interface.h"
+#include "migration/vmstate.h"
+#include "hw/core/cpu.h"
+#include "trace.h"
+#include "qom/object.h"
+
+#define GPE_BASE 0xafe0
+#define GPE_LEN 4
+
+#define ACPI_PCIHP_ADDR_PIIX4 0xae00
+
+struct pci_status {
+ uint32_t up; /* deprecated, maintained for migration compatibility */
+ uint32_t down;
+};
+
+struct PIIX4PMState {
+ /*< private >*/
+ PCIDevice parent_obj;
+ /*< public >*/
+
+ MemoryRegion io;
+ uint32_t io_base;
+
+ MemoryRegion io_gpe;
+ ACPIREGS ar;
+
+ APMState apm;
+
+ PMSMBus smb;
+ uint32_t smb_io_base;
+
+ qemu_irq irq;
+ qemu_irq smi_irq;
+ int smm_enabled;
+ bool smm_compat;
+ Notifier machine_ready;
+ Notifier powerdown_notifier;
+
+ AcpiPciHpState acpi_pci_hotplug;
+ bool use_acpi_hotplug_bridge;
+ bool use_acpi_root_pci_hotplug;
+
+ uint8_t disable_s3;
+ uint8_t disable_s4;
+ uint8_t s4_val;
+
+ bool cpu_hotplug_legacy;
+ AcpiCpuHotplug gpe_cpu;
+ CPUHotplugState cpuhp_state;
+
+ MemHotplugState acpi_memory_hotplug;
+};
+
+OBJECT_DECLARE_SIMPLE_TYPE(PIIX4PMState, PIIX4_PM)
+
+static void piix4_acpi_system_hot_add_init(MemoryRegion *parent,
+ PCIBus *bus, PIIX4PMState *s);
+
+#define ACPI_ENABLE 0xf1
+#define ACPI_DISABLE 0xf0
+
+static void pm_tmr_timer(ACPIREGS *ar)
+{
+ PIIX4PMState *s = container_of(ar, PIIX4PMState, ar);
+ acpi_update_sci(&s->ar, s->irq);
+}
+
+static void apm_ctrl_changed(uint32_t val, void *arg)
+{
+ PIIX4PMState *s = arg;
+ PCIDevice *d = PCI_DEVICE(s);
+
+ /* ACPI specs 3.0, 4.7.2.5 */
+ acpi_pm1_cnt_update(&s->ar, val == ACPI_ENABLE, val == ACPI_DISABLE);
+ if (val == ACPI_ENABLE || val == ACPI_DISABLE) {
+ return;
+ }
+
+ if (d->config[0x5b] & (1 << 1)) {
+ if (s->smi_irq) {
+ qemu_irq_raise(s->smi_irq);
+ }
+ }
+}
+
+static void pm_io_space_update(PIIX4PMState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ s->io_base = le32_to_cpu(*(uint32_t *)(d->config + 0x40));
+ s->io_base &= 0xffc0;
+
+ memory_region_transaction_begin();
+ memory_region_set_enabled(&s->io, d->config[0x80] & 1);
+ memory_region_set_address(&s->io, s->io_base);
+ memory_region_transaction_commit();
+}
+
+static void smbus_io_space_update(PIIX4PMState *s)
+{
+ PCIDevice *d = PCI_DEVICE(s);
+
+ s->smb_io_base = le32_to_cpu(*(uint32_t *)(d->config + 0x90));
+ s->smb_io_base &= 0xffc0;
+
+ memory_region_transaction_begin();
+ memory_region_set_enabled(&s->smb.io, d->config[0xd2] & 1);
+ memory_region_set_address(&s->smb.io, s->smb_io_base);
+ memory_region_transaction_commit();
+}
+
+static void pm_write_config(PCIDevice *d,
+ uint32_t address, uint32_t val, int len)
+{
+ pci_default_write_config(d, address, val, len);
+ if (range_covers_byte(address, len, 0x80) ||
+ ranges_overlap(address, len, 0x40, 4)) {
+ pm_io_space_update((PIIX4PMState *)d);
+ }
+ if (range_covers_byte(address, len, 0xd2) ||
+ ranges_overlap(address, len, 0x90, 4)) {
+ smbus_io_space_update((PIIX4PMState *)d);
+ }
+}
+
+static int vmstate_acpi_post_load(void *opaque, int version_id)
+{
+ PIIX4PMState *s = opaque;
+
+ pm_io_space_update(s);
+ smbus_io_space_update(s);
+ return 0;
+}
+
+#define VMSTATE_GPE_ARRAY(_field, _state) \
+ { \
+ .name = (stringify(_field)), \
+ .version_id = 0, \
+ .info = &vmstate_info_uint16, \
+ .size = sizeof(uint16_t), \
+ .flags = VMS_SINGLE | VMS_POINTER, \
+ .offset = vmstate_offset_pointer(_state, _field, uint8_t), \
+ }
+
+static const VMStateDescription vmstate_gpe = {
+ .name = "gpe",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_GPE_ARRAY(sts, ACPIGPE),
+ VMSTATE_GPE_ARRAY(en, ACPIGPE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pci_status = {
+ .name = "pci_status",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(up, struct AcpiPciHpPciStatus),
+ VMSTATE_UINT32(down, struct AcpiPciHpPciStatus),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool vmstate_test_use_acpi_hotplug_bridge(void *opaque, int version_id)
+{
+ PIIX4PMState *s = opaque;
+ return s->use_acpi_hotplug_bridge;
+}
+
+static bool vmstate_test_no_use_acpi_hotplug_bridge(void *opaque,
+ int version_id)
+{
+ PIIX4PMState *s = opaque;
+ return !s->use_acpi_hotplug_bridge;
+}
+
+static bool vmstate_test_use_memhp(void *opaque)
+{
+ PIIX4PMState *s = opaque;
+ return s->acpi_memory_hotplug.is_enabled;
+}
+
+static const VMStateDescription vmstate_memhp_state = {
+ .name = "piix4_pm/memhp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .needed = vmstate_test_use_memhp,
+ .fields = (VMStateField[]) {
+ VMSTATE_MEMORY_HOTPLUG(acpi_memory_hotplug, PIIX4PMState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool vmstate_test_use_cpuhp(void *opaque)
+{
+ PIIX4PMState *s = opaque;
+ return !s->cpu_hotplug_legacy;
+}
+
+static int vmstate_cpuhp_pre_load(void *opaque)
+{
+ Object *obj = OBJECT(opaque);
+ object_property_set_bool(obj, "cpu-hotplug-legacy", false, &error_abort);
+ return 0;
+}
+
+static const VMStateDescription vmstate_cpuhp_state = {
+ .name = "piix4_pm/cpuhp",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .needed = vmstate_test_use_cpuhp,
+ .pre_load = vmstate_cpuhp_pre_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_CPU_HOTPLUG(cpuhp_state, PIIX4PMState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool piix4_vmstate_need_smbus(void *opaque, int version_id)
+{
+ return pm_smbus_vmstate_needed();
+}
+
+/* qemu-kvm 1.2 uses version 3 but advertised as 2
+ * To support incoming qemu-kvm 1.2 migration, change version_id
+ * and minimum_version_id to 2 below (which breaks migration from
+ * qemu 1.2).
+ *
+ */
+static const VMStateDescription vmstate_acpi = {
+ .name = "piix4_pm",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .post_load = vmstate_acpi_post_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj, PIIX4PMState),
+ VMSTATE_UINT16(ar.pm1.evt.sts, PIIX4PMState),
+ VMSTATE_UINT16(ar.pm1.evt.en, PIIX4PMState),
+ VMSTATE_UINT16(ar.pm1.cnt.cnt, PIIX4PMState),
+ VMSTATE_STRUCT(apm, PIIX4PMState, 0, vmstate_apm, APMState),
+ VMSTATE_STRUCT_TEST(smb, PIIX4PMState, piix4_vmstate_need_smbus, 3,
+ pmsmb_vmstate, PMSMBus),
+ VMSTATE_TIMER_PTR(ar.tmr.timer, PIIX4PMState),
+ VMSTATE_INT64(ar.tmr.overflow_time, PIIX4PMState),
+ VMSTATE_STRUCT(ar.gpe, PIIX4PMState, 2, vmstate_gpe, ACPIGPE),
+ VMSTATE_STRUCT_TEST(
+ acpi_pci_hotplug.acpi_pcihp_pci_status[ACPI_PCIHP_BSEL_DEFAULT],
+ PIIX4PMState,
+ vmstate_test_no_use_acpi_hotplug_bridge,
+ 2, vmstate_pci_status,
+ struct AcpiPciHpPciStatus),
+ VMSTATE_PCI_HOTPLUG(acpi_pci_hotplug, PIIX4PMState,
+ vmstate_test_use_acpi_hotplug_bridge,
+ vmstate_acpi_pcihp_use_acpi_index),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription*[]) {
+ &vmstate_memhp_state,
+ &vmstate_cpuhp_state,
+ NULL
+ }
+};
+
+static void piix4_pm_reset(DeviceState *dev)
+{
+ PIIX4PMState *s = PIIX4_PM(dev);
+ PCIDevice *d = PCI_DEVICE(s);
+ uint8_t *pci_conf = d->config;
+
+ pci_conf[0x58] = 0;
+ pci_conf[0x59] = 0;
+ pci_conf[0x5a] = 0;
+ pci_conf[0x5b] = 0;
+
+ pci_conf[0x40] = 0x01; /* PM io base read only bit */
+ pci_conf[0x80] = 0;
+
+ if (!s->smm_enabled) {
+ /* Mark SMM as already inited (until KVM supports SMM). */
+ pci_conf[0x5B] = 0x02;
+ }
+
+ acpi_pm1_evt_reset(&s->ar);
+ acpi_pm1_cnt_reset(&s->ar);
+ acpi_pm_tmr_reset(&s->ar);
+ acpi_gpe_reset(&s->ar);
+ acpi_update_sci(&s->ar, s->irq);
+
+ pm_io_space_update(s);
+ acpi_pcihp_reset(&s->acpi_pci_hotplug, !s->use_acpi_root_pci_hotplug);
+}
+
+static void piix4_pm_powerdown_req(Notifier *n, void *opaque)
+{
+ PIIX4PMState *s = container_of(n, PIIX4PMState, powerdown_notifier);
+
+ assert(s != NULL);
+ acpi_pm1_evt_power_down(&s->ar);
+}
+
+static void piix4_device_pre_plug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(hotplug_dev);
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_pre_plug_cb(hotplug_dev, dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ if (!s->acpi_memory_hotplug.is_enabled) {
+ error_setg(errp,
+ "memory hotplug is not enabled: %s.memory-hotplug-support "
+ "is not set", object_get_typename(OBJECT(s)));
+ }
+ } else if (
+ !object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
+ error_setg(errp, "acpi: device pre plug request for not supported"
+ " device type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void piix4_device_plug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(hotplug_dev);
+
+ if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ if (object_dynamic_cast(OBJECT(dev), TYPE_NVDIMM)) {
+ nvdimm_acpi_plug_cb(hotplug_dev, dev);
+ } else {
+ acpi_memory_plug_cb(hotplug_dev, &s->acpi_memory_hotplug,
+ dev, errp);
+ }
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_plug_cb(hotplug_dev, &s->acpi_pci_hotplug, dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
+ if (s->cpu_hotplug_legacy) {
+ legacy_acpi_cpu_plug_cb(hotplug_dev, &s->gpe_cpu, dev, errp);
+ } else {
+ acpi_cpu_plug_cb(hotplug_dev, &s->cpuhp_state, dev, errp);
+ }
+ } else {
+ g_assert_not_reached();
+ }
+}
+
+static void piix4_device_unplug_request_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(hotplug_dev);
+
+ if (s->acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_request_cb(hotplug_dev, &s->acpi_memory_hotplug,
+ dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_unplug_request_cb(hotplug_dev, &s->acpi_pci_hotplug,
+ dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU) &&
+ !s->cpu_hotplug_legacy) {
+ acpi_cpu_unplug_request_cb(hotplug_dev, &s->cpuhp_state, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug request for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void piix4_device_unplug_cb(HotplugHandler *hotplug_dev,
+ DeviceState *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(hotplug_dev);
+
+ if (s->acpi_memory_hotplug.is_enabled &&
+ object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
+ acpi_memory_unplug_cb(&s->acpi_memory_hotplug, dev, errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
+ acpi_pcihp_device_unplug_cb(hotplug_dev, &s->acpi_pci_hotplug, dev,
+ errp);
+ } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU) &&
+ !s->cpu_hotplug_legacy) {
+ acpi_cpu_unplug_cb(&s->cpuhp_state, dev, errp);
+ } else {
+ error_setg(errp, "acpi: device unplug for not supported device"
+ " type: %s", object_get_typename(OBJECT(dev)));
+ }
+}
+
+static void piix4_pm_machine_ready(Notifier *n, void *opaque)
+{
+ PIIX4PMState *s = container_of(n, PIIX4PMState, machine_ready);
+ PCIDevice *d = PCI_DEVICE(s);
+ MemoryRegion *io_as = pci_address_space_io(d);
+ uint8_t *pci_conf;
+
+ pci_conf = d->config;
+ pci_conf[0x5f] = 0x10 |
+ (memory_region_present(io_as, 0x378) ? 0x80 : 0);
+ pci_conf[0x63] = 0x60;
+ pci_conf[0x67] = (memory_region_present(io_as, 0x3f8) ? 0x08 : 0) |
+ (memory_region_present(io_as, 0x2f8) ? 0x90 : 0);
+}
+
+static void piix4_pm_add_properties(PIIX4PMState *s)
+{
+ static const uint8_t acpi_enable_cmd = ACPI_ENABLE;
+ static const uint8_t acpi_disable_cmd = ACPI_DISABLE;
+ static const uint32_t gpe0_blk = GPE_BASE;
+ static const uint32_t gpe0_blk_len = GPE_LEN;
+ static const uint16_t sci_int = 9;
+
+ object_property_add_uint8_ptr(OBJECT(s), ACPI_PM_PROP_ACPI_ENABLE_CMD,
+ &acpi_enable_cmd, OBJ_PROP_FLAG_READ);
+ object_property_add_uint8_ptr(OBJECT(s), ACPI_PM_PROP_ACPI_DISABLE_CMD,
+ &acpi_disable_cmd, OBJ_PROP_FLAG_READ);
+ object_property_add_uint32_ptr(OBJECT(s), ACPI_PM_PROP_GPE0_BLK,
+ &gpe0_blk, OBJ_PROP_FLAG_READ);
+ object_property_add_uint32_ptr(OBJECT(s), ACPI_PM_PROP_GPE0_BLK_LEN,
+ &gpe0_blk_len, OBJ_PROP_FLAG_READ);
+ object_property_add_uint16_ptr(OBJECT(s), ACPI_PM_PROP_SCI_INT,
+ &sci_int, OBJ_PROP_FLAG_READ);
+ object_property_add_uint32_ptr(OBJECT(s), ACPI_PM_PROP_PM_IO_BASE,
+ &s->io_base, OBJ_PROP_FLAG_READ);
+}
+
+static void piix4_pm_realize(PCIDevice *dev, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(dev);
+ uint8_t *pci_conf;
+
+ pci_conf = dev->config;
+ pci_conf[0x06] = 0x80;
+ pci_conf[0x07] = 0x02;
+ pci_conf[0x09] = 0x00;
+ pci_conf[0x3d] = 0x01; // interrupt pin 1
+
+ /* APM */
+ apm_init(dev, &s->apm, apm_ctrl_changed, s);
+
+ if (!s->smm_enabled) {
+ /* Mark SMM as already inited to prevent SMM from running. KVM does not
+ * support SMM mode. */
+ pci_conf[0x5B] = 0x02;
+ }
+
+ /* XXX: which specification is used ? The i82731AB has different
+ mappings */
+ pci_conf[0x90] = s->smb_io_base | 1;
+ pci_conf[0x91] = s->smb_io_base >> 8;
+ pci_conf[0xd2] = 0x09;
+ pm_smbus_init(DEVICE(dev), &s->smb, true);
+ memory_region_set_enabled(&s->smb.io, pci_conf[0xd2] & 1);
+ memory_region_add_subregion(pci_address_space_io(dev),
+ s->smb_io_base, &s->smb.io);
+
+ memory_region_init(&s->io, OBJECT(s), "piix4-pm", 64);
+ memory_region_set_enabled(&s->io, false);
+ memory_region_add_subregion(pci_address_space_io(dev),
+ 0, &s->io);
+
+ acpi_pm_tmr_init(&s->ar, pm_tmr_timer, &s->io);
+ acpi_pm1_evt_init(&s->ar, pm_tmr_timer, &s->io);
+ acpi_pm1_cnt_init(&s->ar, &s->io, s->disable_s3, s->disable_s4, s->s4_val,
+ !s->smm_compat && !s->smm_enabled);
+ acpi_gpe_init(&s->ar, GPE_LEN);
+
+ s->powerdown_notifier.notify = piix4_pm_powerdown_req;
+ qemu_register_powerdown_notifier(&s->powerdown_notifier);
+
+ s->machine_ready.notify = piix4_pm_machine_ready;
+ qemu_add_machine_init_done_notifier(&s->machine_ready);
+
+ piix4_acpi_system_hot_add_init(pci_address_space_io(dev),
+ pci_get_bus(dev), s);
+ qbus_set_hotplug_handler(BUS(pci_get_bus(dev)), OBJECT(s));
+
+ piix4_pm_add_properties(s);
+}
+
+I2CBus *piix4_pm_init(PCIBus *bus, int devfn, uint32_t smb_io_base,
+ qemu_irq sci_irq, qemu_irq smi_irq,
+ int smm_enabled, DeviceState **piix4_pm)
+{
+ PCIDevice *pci_dev;
+ DeviceState *dev;
+ PIIX4PMState *s;
+
+ pci_dev = pci_new(devfn, TYPE_PIIX4_PM);
+ dev = DEVICE(pci_dev);
+ qdev_prop_set_uint32(dev, "smb_io_base", smb_io_base);
+ if (piix4_pm) {
+ *piix4_pm = dev;
+ }
+
+ s = PIIX4_PM(dev);
+ s->irq = sci_irq;
+ s->smi_irq = smi_irq;
+ s->smm_enabled = smm_enabled;
+ if (xen_enabled()) {
+ s->use_acpi_hotplug_bridge = false;
+ }
+
+ pci_realize_and_unref(pci_dev, bus, &error_fatal);
+
+ return s->smb.smbus;
+}
+
+static uint64_t gpe_readb(void *opaque, hwaddr addr, unsigned width)
+{
+ PIIX4PMState *s = opaque;
+ uint32_t val = acpi_gpe_ioport_readb(&s->ar, addr);
+
+ trace_piix4_gpe_readb(addr, width, val);
+ return val;
+}
+
+static void gpe_writeb(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ PIIX4PMState *s = opaque;
+
+ trace_piix4_gpe_writeb(addr, width, val);
+ acpi_gpe_ioport_writeb(&s->ar, addr, val);
+ acpi_update_sci(&s->ar, s->irq);
+}
+
+static const MemoryRegionOps piix4_gpe_ops = {
+ .read = gpe_readb,
+ .write = gpe_writeb,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+
+static bool piix4_get_cpu_hotplug_legacy(Object *obj, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(obj);
+
+ return s->cpu_hotplug_legacy;
+}
+
+static void piix4_set_cpu_hotplug_legacy(Object *obj, bool value, Error **errp)
+{
+ PIIX4PMState *s = PIIX4_PM(obj);
+
+ assert(!value);
+ if (s->cpu_hotplug_legacy && value == false) {
+ acpi_switch_to_modern_cphp(&s->gpe_cpu, &s->cpuhp_state,
+ PIIX4_CPU_HOTPLUG_IO_BASE);
+ }
+ s->cpu_hotplug_legacy = value;
+}
+
+static void piix4_acpi_system_hot_add_init(MemoryRegion *parent,
+ PCIBus *bus, PIIX4PMState *s)
+{
+ memory_region_init_io(&s->io_gpe, OBJECT(s), &piix4_gpe_ops, s,
+ "acpi-gpe0", GPE_LEN);
+ memory_region_add_subregion(parent, GPE_BASE, &s->io_gpe);
+
+ if (s->use_acpi_hotplug_bridge || s->use_acpi_root_pci_hotplug) {
+ acpi_pcihp_init(OBJECT(s), &s->acpi_pci_hotplug, bus, parent,
+ s->use_acpi_hotplug_bridge, ACPI_PCIHP_ADDR_PIIX4);
+ }
+
+ s->cpu_hotplug_legacy = true;
+ object_property_add_bool(OBJECT(s), "cpu-hotplug-legacy",
+ piix4_get_cpu_hotplug_legacy,
+ piix4_set_cpu_hotplug_legacy);
+ legacy_acpi_cpu_hotplug_init(parent, OBJECT(s), &s->gpe_cpu,
+ PIIX4_CPU_HOTPLUG_IO_BASE);
+
+ if (s->acpi_memory_hotplug.is_enabled) {
+ acpi_memory_hotplug_init(parent, OBJECT(s), &s->acpi_memory_hotplug,
+ ACPI_MEMORY_HOTPLUG_BASE);
+ }
+}
+
+static void piix4_ospm_status(AcpiDeviceIf *adev, ACPIOSTInfoList ***list)
+{
+ PIIX4PMState *s = PIIX4_PM(adev);
+
+ acpi_memory_ospm_status(&s->acpi_memory_hotplug, list);
+ if (!s->cpu_hotplug_legacy) {
+ acpi_cpu_ospm_status(&s->cpuhp_state, list);
+ }
+}
+
+static void piix4_send_gpe(AcpiDeviceIf *adev, AcpiEventStatusBits ev)
+{
+ PIIX4PMState *s = PIIX4_PM(adev);
+
+ acpi_send_gpe_event(&s->ar, s->irq, ev);
+}
+
+static Property piix4_pm_properties[] = {
+ DEFINE_PROP_UINT32("smb_io_base", PIIX4PMState, smb_io_base, 0),
+ DEFINE_PROP_UINT8(ACPI_PM_PROP_S3_DISABLED, PIIX4PMState, disable_s3, 0),
+ DEFINE_PROP_UINT8(ACPI_PM_PROP_S4_DISABLED, PIIX4PMState, disable_s4, 0),
+ DEFINE_PROP_UINT8(ACPI_PM_PROP_S4_VAL, PIIX4PMState, s4_val, 2),
+ DEFINE_PROP_BOOL(ACPI_PM_PROP_ACPI_PCIHP_BRIDGE, PIIX4PMState,
+ use_acpi_hotplug_bridge, true),
+ DEFINE_PROP_BOOL(ACPI_PM_PROP_ACPI_PCI_ROOTHP, PIIX4PMState,
+ use_acpi_root_pci_hotplug, true),
+ DEFINE_PROP_BOOL("memory-hotplug-support", PIIX4PMState,
+ acpi_memory_hotplug.is_enabled, true),
+ DEFINE_PROP_BOOL("smm-compat", PIIX4PMState, smm_compat, false),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void piix4_pm_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ HotplugHandlerClass *hc = HOTPLUG_HANDLER_CLASS(klass);
+ AcpiDeviceIfClass *adevc = ACPI_DEVICE_IF_CLASS(klass);
+
+ k->realize = piix4_pm_realize;
+ k->config_write = pm_write_config;
+ k->vendor_id = PCI_VENDOR_ID_INTEL;
+ k->device_id = PCI_DEVICE_ID_INTEL_82371AB_3;
+ k->revision = 0x03;
+ k->class_id = PCI_CLASS_BRIDGE_OTHER;
+ dc->reset = piix4_pm_reset;
+ dc->desc = "PM";
+ dc->vmsd = &vmstate_acpi;
+ device_class_set_props(dc, piix4_pm_properties);
+ /*
+ * Reason: part of PIIX4 southbridge, needs to be wired up,
+ * e.g. by mips_malta_init()
+ */
+ dc->user_creatable = false;
+ dc->hotpluggable = false;
+ hc->pre_plug = piix4_device_pre_plug_cb;
+ hc->plug = piix4_device_plug_cb;
+ hc->unplug_request = piix4_device_unplug_request_cb;
+ hc->unplug = piix4_device_unplug_cb;
+ adevc->ospm_status = piix4_ospm_status;
+ adevc->send_event = piix4_send_gpe;
+ adevc->madt_cpu = pc_madt_cpu_entry;
+}
+
+static const TypeInfo piix4_pm_info = {
+ .name = TYPE_PIIX4_PM,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(PIIX4PMState),
+ .class_init = piix4_pm_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_HOTPLUG_HANDLER },
+ { TYPE_ACPI_DEVICE_IF },
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { }
+ }
+};
+
+static void piix4_pm_register_types(void)
+{
+ type_register_static(&piix4_pm_info);
+}
+
+type_init(piix4_pm_register_types)
diff --git a/hw/acpi/tco.c b/hw/acpi/tco.c
new file mode 100644
index 000000000..cf1e68a53
--- /dev/null
+++ b/hw/acpi/tco.c
@@ -0,0 +1,261 @@
+/*
+ * QEMU ICH9 TCO emulation
+ *
+ * Copyright (c) 2015 Paulo Alcantara <pcacjr@zytor.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 "sysemu/watchdog.h"
+#include "hw/i386/ich9.h"
+#include "migration/vmstate.h"
+
+#include "hw/acpi/tco.h"
+#include "trace.h"
+
+enum {
+ TCO_RLD_DEFAULT = 0x0000,
+ TCO_DAT_IN_DEFAULT = 0x00,
+ TCO_DAT_OUT_DEFAULT = 0x00,
+ TCO1_STS_DEFAULT = 0x0000,
+ TCO2_STS_DEFAULT = 0x0000,
+ TCO1_CNT_DEFAULT = 0x0000,
+ TCO2_CNT_DEFAULT = 0x0008,
+ TCO_MESSAGE1_DEFAULT = 0x00,
+ TCO_MESSAGE2_DEFAULT = 0x00,
+ TCO_WDCNT_DEFAULT = 0x00,
+ TCO_TMR_DEFAULT = 0x0004,
+ SW_IRQ_GEN_DEFAULT = 0x03,
+};
+
+static inline void tco_timer_reload(TCOIORegs *tr)
+{
+ int ticks = tr->tco.tmr & TCO_TMR_MASK;
+ int64_t nsec = (int64_t)ticks * TCO_TICK_NSEC;
+
+ trace_tco_timer_reload(ticks, nsec / 1000000);
+ tr->expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + nsec;
+ timer_mod(tr->tco_timer, tr->expire_time);
+}
+
+static inline void tco_timer_stop(TCOIORegs *tr)
+{
+ tr->expire_time = -1;
+ timer_del(tr->tco_timer);
+}
+
+static void tco_timer_expired(void *opaque)
+{
+ TCOIORegs *tr = opaque;
+ ICH9LPCPMRegs *pm = container_of(tr, ICH9LPCPMRegs, tco_regs);
+ ICH9LPCState *lpc = container_of(pm, ICH9LPCState, pm);
+ uint32_t gcs = pci_get_long(lpc->chip_config + ICH9_CC_GCS);
+
+ trace_tco_timer_expired(tr->timeouts_no,
+ lpc->pin_strap.spkr_hi,
+ !!(gcs & ICH9_CC_GCS_NO_REBOOT));
+ tr->tco.rld = 0;
+ tr->tco.sts1 |= TCO_TIMEOUT;
+ if (++tr->timeouts_no == 2) {
+ tr->tco.sts2 |= TCO_SECOND_TO_STS;
+ tr->tco.sts2 |= TCO_BOOT_STS;
+ tr->timeouts_no = 0;
+
+ if (!lpc->pin_strap.spkr_hi && !(gcs & ICH9_CC_GCS_NO_REBOOT)) {
+ watchdog_perform_action();
+ tco_timer_stop(tr);
+ return;
+ }
+ }
+
+ if (pm->smi_en & ICH9_PMIO_SMI_EN_TCO_EN) {
+ ich9_generate_smi();
+ }
+ tr->tco.rld = tr->tco.tmr;
+ tco_timer_reload(tr);
+}
+
+/* NOTE: values of 0 or 1 will be ignored by ICH */
+static inline int can_start_tco_timer(TCOIORegs *tr)
+{
+ return !(tr->tco.cnt1 & TCO_TMR_HLT) && tr->tco.tmr > 1;
+}
+
+static uint32_t tco_ioport_readw(TCOIORegs *tr, uint32_t addr)
+{
+ uint16_t rld;
+
+ switch (addr) {
+ case TCO_RLD:
+ if (tr->expire_time != -1) {
+ int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ int64_t elapsed = (tr->expire_time - now) / TCO_TICK_NSEC;
+ rld = (uint16_t)elapsed | (tr->tco.rld & ~TCO_RLD_MASK);
+ } else {
+ rld = tr->tco.rld;
+ }
+ return rld;
+ case TCO_DAT_IN:
+ return tr->tco.din;
+ case TCO_DAT_OUT:
+ return tr->tco.dout;
+ case TCO1_STS:
+ return tr->tco.sts1;
+ case TCO2_STS:
+ return tr->tco.sts2;
+ case TCO1_CNT:
+ return tr->tco.cnt1;
+ case TCO2_CNT:
+ return tr->tco.cnt2;
+ case TCO_MESSAGE1:
+ return tr->tco.msg1;
+ case TCO_MESSAGE2:
+ return tr->tco.msg2;
+ case TCO_WDCNT:
+ return tr->tco.wdcnt;
+ case TCO_TMR:
+ return tr->tco.tmr;
+ case SW_IRQ_GEN:
+ return tr->sw_irq_gen;
+ }
+ return 0;
+}
+
+static void tco_ioport_writew(TCOIORegs *tr, uint32_t addr, uint32_t val)
+{
+ switch (addr) {
+ case TCO_RLD:
+ tr->timeouts_no = 0;
+ if (can_start_tco_timer(tr)) {
+ tr->tco.rld = tr->tco.tmr;
+ tco_timer_reload(tr);
+ } else {
+ tr->tco.rld = val;
+ }
+ break;
+ case TCO_DAT_IN:
+ tr->tco.din = val;
+ tr->tco.sts1 |= SW_TCO_SMI;
+ ich9_generate_smi();
+ break;
+ case TCO_DAT_OUT:
+ tr->tco.dout = val;
+ tr->tco.sts1 |= TCO_INT_STS;
+ /* TODO: cause an interrupt, as selected by the TCO_INT_SEL bits */
+ break;
+ case TCO1_STS:
+ tr->tco.sts1 = val & TCO1_STS_MASK;
+ break;
+ case TCO2_STS:
+ tr->tco.sts2 = val & TCO2_STS_MASK;
+ break;
+ case TCO1_CNT:
+ val &= TCO1_CNT_MASK;
+ /*
+ * once TCO_LOCK bit is set, it can not be cleared by software. a reset
+ * is required to change this bit from 1 to 0 -- it defaults to 0.
+ */
+ tr->tco.cnt1 = val | (tr->tco.cnt1 & TCO_LOCK);
+ if (can_start_tco_timer(tr)) {
+ tr->tco.rld = tr->tco.tmr;
+ tco_timer_reload(tr);
+ } else {
+ tco_timer_stop(tr);
+ }
+ break;
+ case TCO2_CNT:
+ tr->tco.cnt2 = val;
+ break;
+ case TCO_MESSAGE1:
+ tr->tco.msg1 = val;
+ break;
+ case TCO_MESSAGE2:
+ tr->tco.msg2 = val;
+ break;
+ case TCO_WDCNT:
+ tr->tco.wdcnt = val;
+ break;
+ case TCO_TMR:
+ tr->tco.tmr = val;
+ break;
+ case SW_IRQ_GEN:
+ tr->sw_irq_gen = val;
+ break;
+ }
+}
+
+static uint64_t tco_io_readw(void *opaque, hwaddr addr, unsigned width)
+{
+ TCOIORegs *tr = opaque;
+ return tco_ioport_readw(tr, addr);
+}
+
+static void tco_io_writew(void *opaque, hwaddr addr, uint64_t val,
+ unsigned width)
+{
+ TCOIORegs *tr = opaque;
+ tco_ioport_writew(tr, addr, val);
+}
+
+static const MemoryRegionOps tco_io_ops = {
+ .read = tco_io_readw,
+ .write = tco_io_writew,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 2,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+void acpi_pm_tco_init(TCOIORegs *tr, MemoryRegion *parent)
+{
+ *tr = (TCOIORegs) {
+ .tco = {
+ .rld = TCO_RLD_DEFAULT,
+ .din = TCO_DAT_IN_DEFAULT,
+ .dout = TCO_DAT_OUT_DEFAULT,
+ .sts1 = TCO1_STS_DEFAULT,
+ .sts2 = TCO2_STS_DEFAULT,
+ .cnt1 = TCO1_CNT_DEFAULT,
+ .cnt2 = TCO2_CNT_DEFAULT,
+ .msg1 = TCO_MESSAGE1_DEFAULT,
+ .msg2 = TCO_MESSAGE2_DEFAULT,
+ .wdcnt = TCO_WDCNT_DEFAULT,
+ .tmr = TCO_TMR_DEFAULT,
+ },
+ .sw_irq_gen = SW_IRQ_GEN_DEFAULT,
+ .tco_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tco_timer_expired, tr),
+ .expire_time = -1,
+ .timeouts_no = 0,
+ };
+ memory_region_init_io(&tr->io, memory_region_owner(parent),
+ &tco_io_ops, tr, "sm-tco", ICH9_PMIO_TCO_LEN);
+ memory_region_add_subregion(parent, ICH9_PMIO_TCO_RLD, &tr->io);
+}
+
+const VMStateDescription vmstate_tco_io_sts = {
+ .name = "tco io device status",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(tco.rld, TCOIORegs),
+ VMSTATE_UINT8(tco.din, TCOIORegs),
+ VMSTATE_UINT8(tco.dout, TCOIORegs),
+ VMSTATE_UINT16(tco.sts1, TCOIORegs),
+ VMSTATE_UINT16(tco.sts2, TCOIORegs),
+ VMSTATE_UINT16(tco.cnt1, TCOIORegs),
+ VMSTATE_UINT16(tco.cnt2, TCOIORegs),
+ VMSTATE_UINT8(tco.msg1, TCOIORegs),
+ VMSTATE_UINT8(tco.msg2, TCOIORegs),
+ VMSTATE_UINT8(tco.wdcnt, TCOIORegs),
+ VMSTATE_UINT16(tco.tmr, TCOIORegs),
+ VMSTATE_UINT8(sw_irq_gen, TCOIORegs),
+ VMSTATE_TIMER_PTR(tco_timer, TCOIORegs),
+ VMSTATE_INT64(expire_time, TCOIORegs),
+ VMSTATE_UINT8(timeouts_no, TCOIORegs),
+ VMSTATE_END_OF_LIST()
+ }
+};
diff --git a/hw/acpi/tpm.c b/hw/acpi/tpm.c
new file mode 100644
index 000000000..cdc022753
--- /dev/null
+++ b/hw/acpi/tpm.c
@@ -0,0 +1,459 @@
+/* Support for generating ACPI TPM tables
+ *
+ * Copyright (C) 2018 IBM, Corp.
+ * Copyright (C) 2018 Red Hat Inc
+ *
+ * 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"
+#include "qapi/error.h"
+#include "hw/acpi/tpm.h"
+
+void tpm_build_ppi_acpi(TPMIf *tpm, Aml *dev)
+{
+ Aml *method, *field, *ifctx, *ifctx2, *ifctx3, *func_mask,
+ *not_implemented, *pak, *tpm2, *tpm3, *pprm, *pprq, *zero, *one;
+
+ if (!object_property_get_bool(OBJECT(tpm), "ppi", &error_abort)) {
+ return;
+ }
+
+ zero = aml_int(0);
+ one = aml_int(1);
+ func_mask = aml_int(TPM_PPI_FUNC_MASK);
+ not_implemented = aml_int(TPM_PPI_FUNC_NOT_IMPLEMENTED);
+
+ /*
+ * TPP2 is for the registers that ACPI code used to pass
+ * the PPI code and parameter (PPRQ, PPRM) to the firmware.
+ */
+ aml_append(dev,
+ aml_operation_region("TPP2", AML_SYSTEM_MEMORY,
+ aml_int(TPM_PPI_ADDR_BASE + 0x100),
+ 0x5A));
+ field = aml_field("TPP2", AML_ANY_ACC, AML_NOLOCK, AML_PRESERVE);
+ aml_append(field, aml_named_field("PPIN", 8));
+ aml_append(field, aml_named_field("PPIP", 32));
+ aml_append(field, aml_named_field("PPRP", 32));
+ aml_append(field, aml_named_field("PPRQ", 32));
+ aml_append(field, aml_named_field("PPRM", 32));
+ aml_append(field, aml_named_field("LPPR", 32));
+ aml_append(dev, field);
+ pprq = aml_name("PPRQ");
+ pprm = aml_name("PPRM");
+
+ aml_append(dev,
+ aml_operation_region(
+ "TPP3", AML_SYSTEM_MEMORY,
+ aml_int(TPM_PPI_ADDR_BASE +
+ 0x15a /* movv, docs/specs/tpm.rst */),
+ 0x1));
+ field = aml_field("TPP3", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE);
+ aml_append(field, aml_named_field("MOVV", 8));
+ aml_append(dev, field);
+
+ /*
+ * DerefOf in Windows is broken with SYSTEM_MEMORY. Use a dynamic
+ * operation region inside of a method for getting FUNC[op].
+ */
+ method = aml_method("TPFN", 1, AML_SERIALIZED);
+ {
+ Aml *op = aml_arg(0);
+ ifctx = aml_if(aml_lgreater_equal(op, aml_int(0x100)));
+ {
+ aml_append(ifctx, aml_return(zero));
+ }
+ aml_append(method, ifctx);
+
+ aml_append(method,
+ aml_operation_region("TPP1", AML_SYSTEM_MEMORY,
+ aml_add(aml_int(TPM_PPI_ADDR_BASE), op, NULL), 0x1));
+ field = aml_field("TPP1", AML_BYTE_ACC, AML_NOLOCK, AML_PRESERVE);
+ aml_append(field, aml_named_field("TPPF", 8));
+ aml_append(method, field);
+ aml_append(method, aml_return(aml_name("TPPF")));
+ }
+ aml_append(dev, method);
+
+ /*
+ * Use global TPM2 & TPM3 variables to workaround Windows ACPI bug
+ * when returning packages.
+ */
+ pak = aml_package(2);
+ aml_append(pak, zero);
+ aml_append(pak, zero);
+ aml_append(dev, aml_name_decl("TPM2", pak));
+ tpm2 = aml_name("TPM2");
+
+ pak = aml_package(3);
+ aml_append(pak, zero);
+ aml_append(pak, zero);
+ aml_append(pak, zero);
+ aml_append(dev, aml_name_decl("TPM3", pak));
+ tpm3 = aml_name("TPM3");
+
+ method = aml_method("_DSM", 4, AML_SERIALIZED);
+ {
+ uint8_t zerobyte[1] = { 0 };
+ Aml *function, *arguments, *rev, *op, *op_arg, *op_flags, *uuid;
+
+ uuid = aml_arg(0);
+ rev = aml_arg(1);
+ function = aml_arg(2);
+ arguments = aml_arg(3);
+ op = aml_local(0);
+ op_flags = aml_local(1);
+
+ /* Physical Presence Interface */
+ ifctx = aml_if(
+ aml_equal(uuid,
+ aml_touuid("3DDDFAA6-361B-4EB4-A424-8D10089D1653")));
+ {
+ /* standard DSM query function */
+ ifctx2 = aml_if(aml_equal(function, zero));
+ {
+ uint8_t byte_list[2] = { 0xff, 0x01 }; /* functions 1-8 */
+
+ aml_append(ifctx2,
+ aml_return(aml_buffer(sizeof(byte_list),
+ byte_list)));
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * PPI 1.0: 2.1.1 Get Physical Presence Interface Version
+ *
+ * Arg 2 (Integer): Function Index = 1
+ * Arg 3 (Package): Arguments = Empty Package
+ * Returns: Type: String
+ */
+ ifctx2 = aml_if(aml_equal(function, one));
+ {
+ aml_append(ifctx2, aml_return(aml_string("1.3")));
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * PPI 1.0: 2.1.3 Submit TPM Operation Request to Pre-OS Environment
+ *
+ * Arg 2 (Integer): Function Index = 2
+ * Arg 3 (Package): Arguments = Package: Type: Integer
+ * Operation Value of the Request
+ * Returns: Type: Integer
+ * 0: Success
+ * 1: Operation Value of the Request Not Supported
+ * 2: General Failure
+ */
+ ifctx2 = aml_if(aml_equal(function, aml_int(2)));
+ {
+ /* get opcode */
+ aml_append(ifctx2,
+ aml_store(aml_derefof(aml_index(arguments,
+ zero)), op));
+
+ /* get opcode flags */
+ aml_append(ifctx2,
+ aml_store(aml_call1("TPFN", op), op_flags));
+
+ /* if func[opcode] & TPM_PPI_FUNC_NOT_IMPLEMENTED */
+ ifctx3 = aml_if(
+ aml_equal(
+ aml_and(op_flags, func_mask, NULL),
+ not_implemented));
+ {
+ /* 1: Operation Value of the Request Not Supported */
+ aml_append(ifctx3, aml_return(one));
+ }
+ aml_append(ifctx2, ifctx3);
+
+ aml_append(ifctx2, aml_store(op, pprq));
+ aml_append(ifctx2, aml_store(zero, pprm));
+ /* 0: success */
+ aml_append(ifctx2, aml_return(zero));
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * PPI 1.0: 2.1.4 Get Pending TPM Operation Requested By the OS
+ *
+ * Arg 2 (Integer): Function Index = 3
+ * Arg 3 (Package): Arguments = Empty Package
+ * Returns: Type: Package of Integers
+ * Integer 1: Function Return code
+ * 0: Success
+ * 1: General Failure
+ * Integer 2: Pending operation requested by the OS
+ * 0: None
+ * >0: Operation Value of the Pending Request
+ * Integer 3: Optional argument to pending operation
+ * requested by the OS
+ * 0: None
+ * >0: Argument Value of the Pending Request
+ */
+ ifctx2 = aml_if(aml_equal(function, aml_int(3)));
+ {
+ /*
+ * Revision ID of 1, no integer parameter beyond
+ * parameter two are expected
+ */
+ ifctx3 = aml_if(aml_equal(rev, one));
+ {
+ /* TPM2[1] = PPRQ */
+ aml_append(ifctx3,
+ aml_store(pprq, aml_index(tpm2, one)));
+ aml_append(ifctx3, aml_return(tpm2));
+ }
+ aml_append(ifctx2, ifctx3);
+
+ /*
+ * A return value of {0, 23, 1} indicates that
+ * operation 23 with argument 1 is pending.
+ */
+ ifctx3 = aml_if(aml_equal(rev, aml_int(2)));
+ {
+ /* TPM3[1] = PPRQ */
+ aml_append(ifctx3,
+ aml_store(pprq, aml_index(tpm3, one)));
+ /* TPM3[2] = PPRM */
+ aml_append(ifctx3,
+ aml_store(pprm, aml_index(tpm3, aml_int(2))));
+ aml_append(ifctx3, aml_return(tpm3));
+ }
+ aml_append(ifctx2, ifctx3);
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * PPI 1.0: 2.1.5 Get Platform-Specific Action to Transition to
+ * Pre-OS Environment
+ *
+ * Arg 2 (Integer): Function Index = 4
+ * Arg 3 (Package): Arguments = Empty Package
+ * Returns: Type: Integer
+ * 0: None
+ * 1: Shutdown
+ * 2: Reboot
+ * 3: OS Vendor-specific
+ */
+ ifctx2 = aml_if(aml_equal(function, aml_int(4)));
+ {
+ /* reboot */
+ aml_append(ifctx2, aml_return(aml_int(2)));
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * PPI 1.0: 2.1.6 Return TPM Operation Response to OS Environment
+ *
+ * Arg 2 (Integer): Function Index = 5
+ * Arg 3 (Package): Arguments = Empty Package
+ * Returns: Type: Package of Integer
+ * Integer 1: Function Return code
+ * 0: Success
+ * 1: General Failure
+ * Integer 2: Most recent operation request
+ * 0: None
+ * >0: Operation Value of the most recent request
+ * Integer 3: Response to the most recent operation request
+ * 0: Success
+ * 0x00000001..0x00000FFF: Corresponding TPM
+ * error code
+ * 0xFFFFFFF0: User Abort or timeout of dialog
+ * 0xFFFFFFF1: firmware Failure
+ */
+ ifctx2 = aml_if(aml_equal(function, aml_int(5)));
+ {
+ /* TPM3[1] = LPPR */
+ aml_append(ifctx2,
+ aml_store(aml_name("LPPR"),
+ aml_index(tpm3, one)));
+ /* TPM3[2] = PPRP */
+ aml_append(ifctx2,
+ aml_store(aml_name("PPRP"),
+ aml_index(tpm3, aml_int(2))));
+ aml_append(ifctx2, aml_return(tpm3));
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * PPI 1.0: 2.1.7 Submit preferred user language
+ *
+ * Arg 2 (Integer): Function Index = 6
+ * Arg 3 (Package): Arguments = String Package
+ * Preferred language code
+ * Returns: Type: Integer
+ * Function Return Code
+ * 3: Not implemented
+ */
+ ifctx2 = aml_if(aml_equal(function, aml_int(6)));
+ {
+ /* 3 = not implemented */
+ aml_append(ifctx2, aml_return(aml_int(3)));
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * PPI 1.1: 2.1.7 Submit TPM Operation Request to
+ * Pre-OS Environment 2
+ *
+ * Arg 2 (Integer): Function Index = 7
+ * Arg 3 (Package): Arguments = Package: Type: Integer
+ * Integer 1: Operation Value of the Request
+ * Integer 2: Argument for Operation (optional)
+ * Returns: Type: Integer
+ * 0: Success
+ * 1: Not Implemented
+ * 2: General Failure
+ * 3: Operation blocked by current firmware settings
+ */
+ ifctx2 = aml_if(aml_equal(function, aml_int(7)));
+ {
+ /* get opcode */
+ aml_append(ifctx2, aml_store(aml_derefof(aml_index(arguments,
+ zero)),
+ op));
+
+ /* get opcode flags */
+ aml_append(ifctx2, aml_store(aml_call1("TPFN", op),
+ op_flags));
+ /* if func[opcode] & TPM_PPI_FUNC_NOT_IMPLEMENTED */
+ ifctx3 = aml_if(
+ aml_equal(
+ aml_and(op_flags, func_mask, NULL),
+ not_implemented));
+ {
+ /* 1: not implemented */
+ aml_append(ifctx3, aml_return(one));
+ }
+ aml_append(ifctx2, ifctx3);
+
+ /* if func[opcode] & TPM_PPI_FUNC_BLOCKED */
+ ifctx3 = aml_if(
+ aml_equal(
+ aml_and(op_flags, func_mask, NULL),
+ aml_int(TPM_PPI_FUNC_BLOCKED)));
+ {
+ /* 3: blocked by firmware */
+ aml_append(ifctx3, aml_return(aml_int(3)));
+ }
+ aml_append(ifctx2, ifctx3);
+
+ /* revision to integer */
+ ifctx3 = aml_if(aml_equal(rev, one));
+ {
+ /* revision 1 */
+ /* PPRQ = op */
+ aml_append(ifctx3, aml_store(op, pprq));
+ /* no argument, PPRM = 0 */
+ aml_append(ifctx3, aml_store(zero, pprm));
+ }
+ aml_append(ifctx2, ifctx3);
+
+ ifctx3 = aml_if(aml_equal(rev, aml_int(2)));
+ {
+ /* revision 2 */
+ /* PPRQ = op */
+ op_arg = aml_derefof(aml_index(arguments, one));
+ aml_append(ifctx3, aml_store(op, pprq));
+ /* PPRM = arg3[1] */
+ aml_append(ifctx3, aml_store(op_arg, pprm));
+ }
+ aml_append(ifctx2, ifctx3);
+ /* 0: success */
+ aml_append(ifctx2, aml_return(zero));
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * PPI 1.1: 2.1.8 Get User Confirmation Status for Operation
+ *
+ * Arg 2 (Integer): Function Index = 8
+ * Arg 3 (Package): Arguments = Package: Type: Integer
+ * Operation Value that may need user confirmation
+ * Returns: Type: Integer
+ * 0: Not implemented
+ * 1: Firmware only
+ * 2: Blocked for OS by firmware configuration
+ * 3: Allowed and physically present user required
+ * 4: Allowed and physically present user not required
+ */
+ ifctx2 = aml_if(aml_equal(function, aml_int(8)));
+ {
+ /* get opcode */
+ aml_append(ifctx2,
+ aml_store(aml_derefof(aml_index(arguments,
+ zero)),
+ op));
+
+ /* get opcode flags */
+ aml_append(ifctx2, aml_store(aml_call1("TPFN", op),
+ op_flags));
+ /* return confirmation status code */
+ aml_append(ifctx2,
+ aml_return(
+ aml_and(op_flags, func_mask, NULL)));
+ }
+ aml_append(ifctx, ifctx2);
+
+ aml_append(ifctx, aml_return(aml_buffer(1, zerobyte)));
+ }
+ aml_append(method, ifctx);
+
+ /*
+ * "TCG Platform Reset Attack Mitigation Specification 1.00",
+ * Chapter 6 "ACPI _DSM Function"
+ */
+ ifctx = aml_if(
+ aml_equal(uuid,
+ aml_touuid("376054ED-CC13-4675-901C-4756D7F2D45D")));
+ {
+ /* standard DSM query function */
+ ifctx2 = aml_if(aml_equal(function, zero));
+ {
+ uint8_t byte_list[1] = { 0x03 }; /* functions 1-2 supported */
+
+ aml_append(ifctx2,
+ aml_return(aml_buffer(sizeof(byte_list),
+ byte_list)));
+ }
+ aml_append(ifctx, ifctx2);
+
+ /*
+ * TCG Platform Reset Attack Mitigation Specification 1.0 Ch.6
+ *
+ * Arg 2 (Integer): Function Index = 1
+ * Arg 3 (Package): Arguments = Package: Type: Integer
+ * Operation Value of the Request
+ * Returns: Type: Integer
+ * 0: Success
+ * 1: General Failure
+ */
+ ifctx2 = aml_if(aml_equal(function, one));
+ {
+ aml_append(ifctx2,
+ aml_store(aml_derefof(aml_index(arguments, zero)),
+ op));
+ {
+ aml_append(ifctx2, aml_store(op, aml_name("MOVV")));
+
+ /* 0: success */
+ aml_append(ifctx2, aml_return(zero));
+ }
+ }
+ aml_append(ifctx, ifctx2);
+ }
+ aml_append(method, ifctx);
+ }
+ aml_append(dev, method);
+}
diff --git a/hw/acpi/trace-events b/hw/acpi/trace-events
new file mode 100644
index 000000000..974d770e8
--- /dev/null
+++ b/hw/acpi/trace-events
@@ -0,0 +1,57 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# memory_hotplug.c
+mhp_acpi_invalid_slot_selected(uint32_t slot) "0x%"PRIx32
+mhp_acpi_ejecting_invalid_slot(uint32_t slot) "0x%"PRIx32
+mhp_acpi_read_addr_lo(uint32_t slot, uint32_t addr) "slot[0x%"PRIx32"] addr lo: 0x%"PRIx32
+mhp_acpi_read_addr_hi(uint32_t slot, uint32_t addr) "slot[0x%"PRIx32"] addr hi: 0x%"PRIx32
+mhp_acpi_read_size_lo(uint32_t slot, uint32_t size) "slot[0x%"PRIx32"] size lo: 0x%"PRIx32
+mhp_acpi_read_size_hi(uint32_t slot, uint32_t size) "slot[0x%"PRIx32"] size hi: 0x%"PRIx32
+mhp_acpi_read_pxm(uint32_t slot, uint32_t pxm) "slot[0x%"PRIx32"] proximity: 0x%"PRIx32
+mhp_acpi_read_flags(uint32_t slot, uint32_t flags) "slot[0x%"PRIx32"] flags: 0x%"PRIx32
+mhp_acpi_write_slot(uint32_t slot) "set active slot: 0x%"PRIx32
+mhp_acpi_write_ost_ev(uint32_t slot, uint32_t ev) "slot[0x%"PRIx32"] OST EVENT: 0x%"PRIx32
+mhp_acpi_write_ost_status(uint32_t slot, uint32_t st) "slot[0x%"PRIx32"] OST STATUS: 0x%"PRIx32
+mhp_acpi_clear_insert_evt(uint32_t slot) "slot[0x%"PRIx32"] clear insert event"
+mhp_acpi_clear_remove_evt(uint32_t slot) "slot[0x%"PRIx32"] clear remove event"
+mhp_acpi_pc_dimm_deleted(uint32_t slot) "slot[0x%"PRIx32"] pc-dimm deleted"
+mhp_acpi_pc_dimm_delete_failed(uint32_t slot) "slot[0x%"PRIx32"] pc-dimm delete failed"
+
+# cpu.c
+cpuhp_acpi_invalid_idx_selected(uint32_t idx) "0x%"PRIx32
+cpuhp_acpi_read_flags(uint32_t idx, uint8_t flags) "idx[0x%"PRIx32"] flags: 0x%"PRIx8
+cpuhp_acpi_write_idx(uint32_t idx) "set active cpu idx: 0x%"PRIx32
+cpuhp_acpi_write_cmd(uint32_t idx, uint8_t cmd) "idx[0x%"PRIx32"] cmd: 0x%"PRIx8
+cpuhp_acpi_read_cmd_data(uint32_t idx, uint32_t data) "idx[0x%"PRIx32"] data: 0x%"PRIx32
+cpuhp_acpi_read_cmd_data2(uint32_t idx, uint32_t data) "idx[0x%"PRIx32"] data: 0x%"PRIx32
+cpuhp_acpi_cpu_has_events(uint32_t idx, bool ins, bool rm) "idx[0x%"PRIx32"] inserting: %d, removing: %d"
+cpuhp_acpi_clear_inserting_evt(uint32_t idx) "idx[0x%"PRIx32"]"
+cpuhp_acpi_clear_remove_evt(uint32_t idx) "idx[0x%"PRIx32"]"
+cpuhp_acpi_ejecting_invalid_cpu(uint32_t idx) "0x%"PRIx32
+cpuhp_acpi_ejecting_cpu(uint32_t idx) "0x%"PRIx32
+cpuhp_acpi_fw_remove_invalid_cpu(uint32_t idx) "0x%"PRIx32
+cpuhp_acpi_fw_remove_cpu(uint32_t idx) "0x%"PRIx32
+cpuhp_acpi_write_ost_ev(uint32_t slot, uint32_t ev) "idx[0x%"PRIx32"] OST EVENT: 0x%"PRIx32
+cpuhp_acpi_write_ost_status(uint32_t slot, uint32_t st) "idx[0x%"PRIx32"] OST STATUS: 0x%"PRIx32
+
+# pcihp.c
+acpi_pci_eject_slot(unsigned bsel, unsigned slot) "bsel: %u slot: %u"
+acpi_pci_unplug(int bsel, int slot) "bsel: %d slot: %d"
+acpi_pci_unplug_request(int bsel, int slot) "bsel: %d slot: %d"
+acpi_pci_up_read(uint32_t val) "%" PRIu32
+acpi_pci_down_read(uint32_t val) "%" PRIu32
+acpi_pci_features_read(uint32_t val) "%" PRIu32
+acpi_pci_acpi_index_read(uint32_t val) "%" PRIu32
+acpi_pci_acpi_index_write(unsigned bsel, unsigned slot, uint32_t aidx) "bsel: %u slot: %u aidx: %" PRIu32
+acpi_pci_rmv_read(uint32_t val) "%" PRIu32
+acpi_pci_sel_read(uint32_t val) "%" PRIu32
+acpi_pci_ej_write(uint64_t addr, uint64_t data) "0x%" PRIx64 " <== %" PRIu64
+acpi_pci_sel_write(uint64_t addr, uint64_t data) "0x%" PRIx64 " <== %" PRIu64
+
+# piix4.c
+piix4_gpe_readb(uint64_t addr, unsigned width, uint64_t val) "addr: 0x%" PRIx64 " width: %d ==> 0x%" PRIx64
+piix4_gpe_writeb(uint64_t addr, unsigned width, uint64_t val) "addr: 0x%" PRIx64 " width: %d <== 0x%" PRIx64
+
+# tco.c
+tco_timer_reload(int ticks, int msec) "ticks=%d (%d ms)"
+tco_timer_expired(int timeouts_no, bool strap, bool no_reboot) "timeouts_no=%d no_reboot=%d/%d"
diff --git a/hw/acpi/trace.h b/hw/acpi/trace.h
new file mode 100644
index 000000000..a7f7da700
--- /dev/null
+++ b/hw/acpi/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_acpi.h"
diff --git a/hw/acpi/utils.c b/hw/acpi/utils.c
new file mode 100644
index 000000000..0c486ea29
--- /dev/null
+++ b/hw/acpi/utils.c
@@ -0,0 +1,48 @@
+/*
+ * Utilities for generating ACPI tables and passing them to Guests
+ *
+ * Copyright (C) 2019 Intel Corporation
+ * Copyright (C) 2019 Red Hat Inc
+ *
+ * Author: Wei Yang <richardw.yang@linux.intel.com>
+ * 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"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/utils.h"
+#include "hw/loader.h"
+
+MemoryRegion *acpi_add_rom_blob(FWCfgCallback update, void *opaque,
+ GArray *blob, const char *name)
+{
+ uint64_t max_size;
+
+ /* Reserve RAM space for tables: add another order of magnitude. */
+ if (!strcmp(name, ACPI_BUILD_TABLE_FILE)) {
+ max_size = 0x200000;
+ } else if (!strcmp(name, ACPI_BUILD_LOADER_FILE)) {
+ max_size = 0x10000;
+ } else if (!strcmp(name, ACPI_BUILD_RSDP_FILE)) {
+ max_size = 0x1000;
+ } else {
+ g_assert_not_reached();
+ }
+ g_assert(acpi_data_len(blob) <= max_size);
+
+ return rom_add_blob(name, blob->data, acpi_data_len(blob), max_size, -1,
+ name, update, opaque, NULL, true);
+}
diff --git a/hw/acpi/viot.c b/hw/acpi/viot.c
new file mode 100644
index 000000000..c1af75206
--- /dev/null
+++ b/hw/acpi/viot.c
@@ -0,0 +1,114 @@
+/*
+ * ACPI Virtual I/O Translation table implementation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "qemu/osdep.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/viot.h"
+#include "hw/pci/pci.h"
+#include "hw/pci/pci_host.h"
+
+struct viot_pci_ranges {
+ GArray *blob;
+ size_t count;
+ uint16_t output_node;
+};
+
+/* Build PCI range for a given PCI host bridge */
+static int build_pci_range_node(Object *obj, void *opaque)
+{
+ struct viot_pci_ranges *pci_ranges = opaque;
+ GArray *blob = pci_ranges->blob;
+
+ if (object_dynamic_cast(obj, TYPE_PCI_HOST_BRIDGE)) {
+ PCIBus *bus = PCI_HOST_BRIDGE(obj)->bus;
+
+ if (bus && !pci_bus_bypass_iommu(bus)) {
+ int min_bus, max_bus;
+
+ pci_bus_range(bus, &min_bus, &max_bus);
+
+ /* Type */
+ build_append_int_noprefix(blob, 1 /* PCI range */, 1);
+ /* Reserved */
+ build_append_int_noprefix(blob, 0, 1);
+ /* Length */
+ build_append_int_noprefix(blob, 24, 2);
+ /* Endpoint start */
+ build_append_int_noprefix(blob, PCI_BUILD_BDF(min_bus, 0), 4);
+ /* PCI Segment start */
+ build_append_int_noprefix(blob, 0, 2);
+ /* PCI Segment end */
+ build_append_int_noprefix(blob, 0, 2);
+ /* PCI BDF start */
+ build_append_int_noprefix(blob, PCI_BUILD_BDF(min_bus, 0), 2);
+ /* PCI BDF end */
+ build_append_int_noprefix(blob, PCI_BUILD_BDF(max_bus, 0xff), 2);
+ /* Output node */
+ build_append_int_noprefix(blob, pci_ranges->output_node, 2);
+ /* Reserved */
+ build_append_int_noprefix(blob, 0, 6);
+
+ pci_ranges->count++;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Generate a VIOT table with one PCI-based virtio-iommu that manages PCI
+ * endpoints.
+ *
+ * Defined in the ACPI Specification (Version TBD)
+ */
+void build_viot(MachineState *ms, GArray *table_data, BIOSLinker *linker,
+ uint16_t virtio_iommu_bdf, const char *oem_id,
+ const char *oem_table_id)
+{
+ /* The virtio-iommu node follows the 48-bytes header */
+ int viommu_off = 48;
+ AcpiTable table = { .sig = "VIOT", .rev = 0,
+ .oem_id = oem_id, .oem_table_id = oem_table_id };
+ struct viot_pci_ranges pci_ranges = {
+ .output_node = viommu_off,
+ .blob = g_array_new(false, true /* clear */, 1),
+ };
+
+ /* Build the list of PCI ranges that this viommu manages */
+ object_child_foreach_recursive(OBJECT(ms), build_pci_range_node,
+ &pci_ranges);
+
+ /* ACPI table header */
+ acpi_table_begin(&table, table_data);
+ /* Node count */
+ build_append_int_noprefix(table_data, pci_ranges.count + 1, 2);
+ /* Node offset */
+ build_append_int_noprefix(table_data, viommu_off, 2);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 8);
+
+ /* Virtio-iommu node */
+ /* Type */
+ build_append_int_noprefix(table_data, 3 /* virtio-pci IOMMU */, 1);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 1);
+ /* Length */
+ build_append_int_noprefix(table_data, 16, 2);
+ /* PCI Segment */
+ build_append_int_noprefix(table_data, 0, 2);
+ /* PCI BDF number */
+ build_append_int_noprefix(table_data, virtio_iommu_bdf, 2);
+ /* Reserved */
+ build_append_int_noprefix(table_data, 0, 8);
+
+ /* PCI ranges found above */
+ g_array_append_vals(table_data, pci_ranges.blob->data,
+ pci_ranges.blob->len);
+ g_array_free(pci_ranges.blob, true);
+
+ acpi_table_end(linker, &table);
+}
+
diff --git a/hw/acpi/viot.h b/hw/acpi/viot.h
new file mode 100644
index 000000000..9fe565bb8
--- /dev/null
+++ b/hw/acpi/viot.h
@@ -0,0 +1,13 @@
+/*
+ * ACPI Virtual I/O Translation Table implementation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#ifndef VIOT_H
+#define VIOT_H
+
+void build_viot(MachineState *ms, GArray *table_data, BIOSLinker *linker,
+ uint16_t virtio_iommu_bdf, const char *oem_id,
+ const char *oem_table_id);
+
+#endif /* VIOT_H */
diff --git a/hw/acpi/vmgenid.c b/hw/acpi/vmgenid.c
new file mode 100644
index 000000000..0c9f158ac
--- /dev/null
+++ b/hw/acpi/vmgenid.c
@@ -0,0 +1,263 @@
+/*
+ * 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 "qapi/error.h"
+#include "qapi/qapi-commands-machine.h"
+#include "qemu/module.h"
+#include "hw/acpi/acpi.h"
+#include "hw/acpi/aml-build.h"
+#include "hw/acpi/vmgenid.h"
+#include "hw/nvram/fw_cfg.h"
+#include "hw/qdev-properties.h"
+#include "hw/qdev-properties-system.h"
+#include "migration/vmstate.h"
+#include "sysemu/reset.h"
+
+void vmgenid_build_acpi(VmGenIdState *vms, GArray *table_data, GArray *guid,
+ BIOSLinker *linker, const char *oem_id)
+{
+ Aml *ssdt, *dev, *scope, *method, *addr, *if_ctx;
+ uint32_t vgia_offset;
+ QemuUUID guid_le;
+ AcpiTable table = { .sig = "SSDT", .rev = 1,
+ .oem_id = oem_id, .oem_table_id = "VMGENID" };
+
+ /* 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 = qemu_uuid_bswap(vms->guid);
+ /* 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 VMGNEID into a separate SSDT table */
+ acpi_table_begin(&table, table_data);
+ ssdt = init_aml_allocator();
+
+ /* 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);
+
+ /* must be called after above command to ensure correct table checksum */
+ acpi_table_end(linker, &table);
+ 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, 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 = qemu_uuid_bswap(vms->guid);
+ /* 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);
+ }
+ }
+}
+
+/* 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);
+
+ if (!bios_linker_loader_can_write_pointer()) {
+ error_setg(errp, "%s requires DMA write support in fw_cfg, "
+ "which this machine type does not provide", TYPE_VMGENID);
+ return;
+ }
+
+ /* Given that this function is executing, there is at least one VMGENID
+ * device. Check if there are several.
+ */
+ if (!find_vmgenid_dev()) {
+ error_setg(errp, "at most one %s device is permitted", TYPE_VMGENID);
+ return;
+ }
+
+ qemu_register_reset(vmgenid_handle_reset, vms);
+
+ vmgenid_update_guest(vms);
+}
+
+static Property vmgenid_device_properties[] = {
+ DEFINE_PROP_UUID(VMGENID_GUID, VmGenIdState, guid),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void vmgenid_device_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &vmstate_vmgenid;
+ dc->realize = vmgenid_realize;
+ device_class_set_props(dc, vmgenid_device_properties);
+ dc->hotpluggable = false;
+ set_bit(DEVICE_CATEGORY_MISC, dc->categories);
+}
+
+static const TypeInfo vmgenid_device_info = {
+ .name = TYPE_VMGENID,
+ .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) {
+ error_setg(errp, "VM Generation ID device not found");
+ return NULL;
+ }
+ vms = VMGENID(obj);
+
+ info = g_malloc0(sizeof(*info));
+ info->guid = qemu_uuid_unparse_strdup(&vms->guid);
+ return info;
+}