diff options
Diffstat (limited to 'hw/acpi')
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; +} |