qemu/hw/acpi/core.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

739 lines
21 KiB
C
Raw Normal View History

/*
* 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->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) {
Convert multi-line fprintf() to warn_report() Convert all the multi-line uses of fprintf(stderr, "warning:"..."\n"... to use warn_report() instead. This helps standardise on a single method of printing warnings to the user. All of the warnings were changed using these commands: find ./* -type f -exec sed -i \ 'N; {s|fprintf(.*".*warning[,:] \(.*\)\\n"\(.*\));|warn_report("\1"\2);|Ig}' \ {} + find ./* -type f -exec sed -i \ 'N;N; {s|fprintf(.*".*warning[,:] \(.*\)\\n"\(.*\));|warn_report("\1"\2);|Ig}' \ {} + find ./* -type f -exec sed -i \ 'N;N;N; {s|fprintf(.*".*warning[,:] \(.*\)\\n"\(.*\));|warn_report("\1"\2);|Ig}' \ {} + find ./* -type f -exec sed -i \ 'N;N;N;N {s|fprintf(.*".*warning[,:] \(.*\)\\n"\(.*\));|warn_report("\1"\2);|Ig}' \ {} + find ./* -type f -exec sed -i \ 'N;N;N;N;N {s|fprintf(.*".*warning[,:] \(.*\)\\n"\(.*\));|warn_report("\1"\2);|Ig}' \ {} + find ./* -type f -exec sed -i \ 'N;N;N;N;N;N {s|fprintf(.*".*warning[,:] \(.*\)\\n"\(.*\));|warn_report("\1"\2);|Ig}' \ {} + find ./* -type f -exec sed -i \ 'N;N;N;N;N;N;N; {s|fprintf(.*".*warning[,:] \(.*\)\\n"\(.*\));|warn_report("\1"\2);|Ig}' \ {} + Indentation fixed up manually afterwards. Some of the lines were manually edited to reduce the line length to below 80 charecters. Some of the lines with newlines in the middle of the string were also manually edit to avoid checkpatch errrors. The #include lines were manually updated to allow the code to compile. Several of the warning messages can be improved after this patch, to keep this patch mechanical this has been moved into a later patch. Signed-off-by: Alistair Francis <alistair.francis@xilinx.com> Cc: Paolo Bonzini <pbonzini@redhat.com> Cc: Kevin Wolf <kwolf@redhat.com> Cc: Max Reitz <mreitz@redhat.com> Cc: "Michael S. Tsirkin" <mst@redhat.com> Cc: Igor Mammedov <imammedo@redhat.com> Cc: Peter Maydell <peter.maydell@linaro.org> Cc: Stefano Stabellini <sstabellini@kernel.org> Cc: Anthony Perard <anthony.perard@citrix.com> Cc: Richard Henderson <rth@twiddle.net> Cc: Eduardo Habkost <ehabkost@redhat.com> Cc: Aurelien Jarno <aurelien@aurel32.net> Cc: Yongbok Kim <yongbok.kim@imgtec.com> Cc: Cornelia Huck <cohuck@redhat.com> Cc: Christian Borntraeger <borntraeger@de.ibm.com> Cc: Alexander Graf <agraf@suse.de> Cc: Jason Wang <jasowang@redhat.com> Cc: David Gibson <david@gibson.dropbear.id.au> Cc: Gerd Hoffmann <kraxel@redhat.com> Acked-by: Cornelia Huck <cohuck@redhat.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> Message-Id: <5def63849ca8f551630c6f2b45bcb1c482f765a6.1505158760.git.alistair.francis@xilinx.com> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2017-09-11 22:52:53 +03:00
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->oem_id) {
strncpy(ext_hdr->oem_id, hdrs->oem_id, sizeof ext_hdr->oem_id);
++changed_fields;
}
if (hdrs->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->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->file == !hdrs->data) {
error_setg(errp, "'-acpitable' requires one of 'data' or 'file'");
goto out;
}
pathnames = g_strsplit(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->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) {
acpi: fix QEMU crash when started with SLIC table if QEMU is started with used provided SLIC table blob, -acpitable sig=SLIC,oem_id='CRASH ',oem_table_id="ME",oem_rev=00002210,asl_compiler_id="",asl_compiler_rev=00000000,data=/dev/null it will assert with: hw/acpi/aml-build.c:61:build_append_padded_str: assertion failed: (len <= maxlen) and following backtrace: ... build_append_padded_str (array=0x555556afe320, str=0x555556afdb2e "CRASH ME", maxlen=0x6, pad=0x20) at hw/acpi/aml-build.c:61 acpi_table_begin (desc=0x7fffffffd1b0, array=0x555556afe320) at hw/acpi/aml-build.c:1727 build_fadt (tbl=0x555556afe320, linker=0x555557ca3830, f=0x7fffffffd318, oem_id=0x555556afdb2e "CRASH ME", oem_table_id=0x555556afdb34 "ME") at hw/acpi/aml-build.c:2064 ... which happens due to acpi_table_begin() expecting NULL terminated oem_id and oem_table_id strings, which is normally the case, but in case of user provided SLIC table, oem_id points to table's blob directly and as result oem_id became longer than expected. Fix issue by handling oem_id consistently and make acpi_get_slic_oem() return NULL terminated strings. PS: After [1] refactoring, oem_id semantics became inconsistent, where NULL terminated string was coming from machine and old way pointer into byte array coming from -acpitable option. That used to work since build_header() wasn't expecting NULL terminated string and blindly copied the 1st 6 bytes only. However commit [2] broke that by replacing build_header() with acpi_table_begin(), which was expecting NULL terminated string and was checking oem_id size. 1) 602b45820 ("acpi: Permit OEM ID and OEM table ID fields to be changed") 2) Fixes: 4b56e1e4eb08 ("acpi: build_fadt: use acpi_table_begin()/acpi_table_end() instead of build_header()") Resolves: https://gitlab.com/qemu-project/qemu/-/issues/786 Signed-off-by: Igor Mammedov <imammedo@redhat.com> Message-Id: <20211227193120.1084176-2-imammedo@redhat.com> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com> Tested-by: Denis Lisov <dennis.lissov@gmail.com> Tested-by: Alexander Tsoy <alexander@tsoy.me> Cc: qemu-stable@nongnu.org Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2021-12-27 22:31:17 +03:00
oem->id = g_strndup(hdr->oem_id, 6);
oem->table_id = g_strndup(hdr->oem_table_id, 8);
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;
qmp hmp: Make system_wakeup check wake-up support and run state The qmp/hmp command 'system_wakeup' is simply a direct call to 'qemu_system_wakeup_request' from vl.c. This function verifies if runstate is SUSPENDED and if the wake up reason is valid before proceeding. However, no error or warning is thrown if any of those pre-requirements isn't met. There is no way for the caller to differentiate between a successful wakeup or an error state caused when trying to wake up a guest that wasn't suspended. This means that system_wakeup is silently failing, which can be considered a bug. Adding error handling isn't an API break in this case - applications that didn't check the result will remain broken, the ones that check it will have a chance to deal with it. Adding to that, the commit before previous created a new QMP API called query-current-machine, with a new flag called wakeup-suspend-support, that indicates if the guest has the capability of waking up from suspended state. Although such guest will never reach SUSPENDED state and erroring it out in this scenario would suffice, it is more informative for the user to differentiate between a failure because the guest isn't suspended versus a failure because the guest does not have support for wake up at all. All this considered, this patch changes qmp_system_wakeup to check if the guest is capable of waking up from suspend, and if it is suspended. After this patch, this is the output of system_wakeup in a guest that does not have wake-up from suspend support (ppc64): (qemu) system_wakeup wake-up from suspend is not supported by this guest (qemu) And this is the output of system_wakeup in a x86 guest that has the support but isn't suspended: (qemu) system_wakeup Unable to wake up: guest is not in suspended state (qemu) Reported-by: Balamuruhan S <bala24@linux.vnet.ibm.com> Signed-off-by: Daniel Henrique Barboza <danielhb413@gmail.com> Message-Id: <20181205194701.17836-4-danielhb413@gmail.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> Acked-by: Eduardo Habkost <ehabkost@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Markus Armbruster <armbru@redhat.com>
2018-12-05 22:47:01 +03:00
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);
qmp: query-current-machine with wakeup-suspend-support When issuing the qmp/hmp 'system_wakeup' command, what happens in a nutshell is: - qmp_system_wakeup_request set runstate to RUNNING, sets a wakeup_reason and notify the event - in the main_loop, all vcpus are paused, a system reset is issued, all subscribers of wakeup_notifiers receives a notification, vcpus are then resumed and the wake up QAPI event is fired Note that this procedure alone doesn't ensure that the guest will awake from SUSPENDED state - the subscribers of the wake up event must take action to resume the guest, otherwise the guest will simply reboot. At this moment, only the ACPI machines via acpi_pm1_cnt_init and xen_hvm_init have wake-up from suspend support. However, only the presence of 'system_wakeup' is required for QGA to support 'guest-suspend-ram' and 'guest-suspend-hybrid' at this moment. This means that the user/management will expect to suspend the guest using one of those suspend commands and then resume execution using system_wakeup, regardless of the support offered in system_wakeup in the first place. This patch creates a new API called query-current-machine [1], that holds a new flag called 'wakeup-suspend-support' that indicates if the guest supports wake up from suspend via system_wakeup. The machine is considered to implement wake-up support if a call to a new 'qemu_register_wakeup_support' is made during its init, as it is now being done inside acpi_pm1_cnt_init and xen_hvm_init. This allows for any other machine type to declare wake-up support regardless of ACPI state or wakeup_notifiers subscription, making easier for newer implementations that might have their own mechanisms in the future. This is the expected output of query-current-machine when running a x86 guest: {"execute" : "query-current-machine"} {"return": {"wakeup-suspend-support": true}} Running the same x86 guest, but with the --no-acpi option: {"execute" : "query-current-machine"} {"return": {"wakeup-suspend-support": false}} This is the output when running a pseries guest: {"execute" : "query-current-machine"} {"return": {"wakeup-suspend-support": false}} With this extra tool, management can avoid situations where a guest that does not have proper suspend/wake capabilities ends up in inconsistent state (e.g. https://github.com/open-power-host-os/qemu/issues/31). [1] the decision of creating the query-current-machine API is based on discussions in the QEMU mailing list where it was decided that query-target wasn't a proper place to store the wake-up flag, neither was query-machines because this isn't a static property of the machine object. This new API can then be used to store other dynamic machine properties that are scattered around the code ATM. More info at: https://lists.gnu.org/archive/html/qemu-devel/2018-05/msg04235.html Reported-by: Balamuruhan S <bala24@linux.vnet.ibm.com> Signed-off-by: Daniel Henrique Barboza <danielhb413@gmail.com> Message-Id: <20181205194701.17836-2-danielhb413@gmail.com> Reviewed-by: Markus Armbruster <armbru@redhat.com> Acked-by: Eduardo Habkost <ehabkost@redhat.com> Signed-off-by: Markus Armbruster <armbru@redhat.com>
2018-12-05 22:46:59 +03:00
/*
* 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));
}