aboutsummaryrefslogtreecommitdiff
path: root/hw/acpi.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/acpi.c')
-rw-r--r--hw/acpi.c284
1 files changed, 163 insertions, 121 deletions
diff --git a/hw/acpi.c b/hw/acpi.c
index 1aca458dfc..ec1ade7c3f 100644
--- a/hw/acpi.c
+++ b/hw/acpi.c
@@ -30,7 +30,7 @@
struct acpi_table_header {
uint16_t _length; /* our length, not actual part of the hdr */
- /* XXX why we have 2 length fields here? */
+ /* allows easier parsing for fw_cfg clients */
char sig[4]; /* ACPI signature (4 ASCII characters) */
uint32_t length; /* Length of table, in bytes, including header */
uint8_t revision; /* ACPI Specification minor version # */
@@ -45,8 +45,7 @@ struct acpi_table_header {
#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] =
- "\0\0" /* fake _length (2) */
+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) */
@@ -79,19 +78,165 @@ static int acpi_checksum(const uint8_t *data, int len)
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 */
+ cpu_to_le16wu((uint16_t *)acpi_tables,
+ le16_to_cpupu((uint16_t *)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) {
+ fprintf(stderr,
+ "warning: ACPI table has wrong length, header says "
+ "%" PRIu32 ", actual size %zu bytes\n",
+ 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) {
+ fprintf(stderr, "warning: ACPI table: no headers are specified\n");
+ }
+
+ /* recalculate checksum */
+ ext_hdr->checksum = acpi_checksum((const char unsigned *)ext_hdr +
+ ACPI_TABLE_PFX_SIZE, acpi_payload_size);
+}
+
int acpi_table_add(const QemuOpts *opts)
{
AcpiTableOptions *hdrs = NULL;
Error *err = NULL;
char **pathnames = NULL;
char **cur;
-
- size_t len, start, allen;
- bool has_header;
- int changed;
- int r;
- struct acpi_table_header hdr;
- char unsigned *table_start;
+ size_t bloblen = 0;
+ char unsigned *blob = NULL;
{
OptsVisitor *ov;
@@ -108,7 +253,6 @@ int acpi_table_add(const QemuOpts *opts)
error_setg(&err, "'-acpitable' requires one of 'data' or 'file'");
goto out;
}
- has_header = hdrs->has_file;
pathnames = g_strsplit(hdrs->has_file ? hdrs->file : hdrs->data, ":", 0);
if (pathnames == NULL || pathnames[0] == NULL) {
@@ -116,19 +260,7 @@ int acpi_table_add(const QemuOpts *opts)
goto out;
}
- if (!acpi_tables) {
- allen = sizeof(uint16_t);
- acpi_tables = g_malloc0(allen);
- } else {
- allen = acpi_tables_len;
- }
-
- start = allen;
- acpi_tables = g_realloc(acpi_tables, start + ACPI_TABLE_HDR_SIZE);
- allen += has_header ? ACPI_TABLE_PFX_SIZE : ACPI_TABLE_HDR_SIZE;
-
/* now read in the data files, reallocating buffer as needed */
-
for (cur = pathnames; *cur; ++cur) {
int fd = open(*cur, O_RDONLY | O_BINARY);
@@ -139,13 +271,15 @@ int acpi_table_add(const QemuOpts *opts)
for (;;) {
char unsigned data[8192];
- r = read(fd, data, sizeof(data));
+ ssize_t r;
+
+ r = read(fd, data, sizeof data);
if (r == 0) {
break;
} else if (r > 0) {
- acpi_tables = g_realloc(acpi_tables, allen + r);
- memcpy(acpi_tables + allen, data, r);
- allen += r;
+ blob = g_realloc(blob, bloblen + r);
+ memcpy(blob + bloblen, data, r);
+ bloblen += r;
} else if (errno != EINTR) {
error_setg(&err, "can't read file %s: %s",
*cur, strerror(errno));
@@ -157,102 +291,10 @@ int acpi_table_add(const QemuOpts *opts)
close(fd);
}
- /* now fill in the header fields */
-
- table_start = acpi_tables + start; /* start of the table */
- changed = 0;
-
- /* copy the header to temp place to align the fields */
- memcpy(&hdr, has_header ? table_start : dfl_hdr, ACPI_TABLE_HDR_SIZE);
-
- /* length of the table minus our prefix */
- len = allen - start - ACPI_TABLE_PFX_SIZE;
-
- hdr._length = cpu_to_le16(len);
-
- if (hdrs->has_sig) {
- /* strncpy is justified: the field need not be NUL-terminated. */
- strncpy(hdr.sig, hdrs->sig, sizeof(hdr.sig));
- ++changed;
- }
-
- /* length of the table including header, in bytes */
- if (has_header) {
- unsigned long val;
-
- /* check if actual length is correct */
- val = le32_to_cpu(hdr.length);
- if (val != len) {
- fprintf(stderr,
- "warning: acpitable has wrong length,"
- " header says %lu, actual size %zu bytes\n",
- val, len);
- ++changed;
- }
- }
- /* we may avoid putting length here if has_header is true */
- hdr.length = cpu_to_le32(len);
-
- if (hdrs->has_rev) {
- hdr.revision = hdrs->rev;
- ++changed;
- }
-
- if (hdrs->has_oem_id) {
- /* strncpy is justified: the field need not be NUL-terminated. */
- strncpy(hdr.oem_id, hdrs->oem_id, sizeof(hdr.oem_id));
- ++changed;
- }
-
- if (hdrs->has_oem_table_id) {
- /* strncpy is justified: the field need not be NUL-terminated. */
- strncpy(hdr.oem_table_id, hdrs->oem_table_id,
- sizeof(hdr.oem_table_id));
- ++changed;
- }
-
- if (hdrs->has_oem_rev) {
- hdr.oem_revision = cpu_to_le32(hdrs->oem_rev);
- ++changed;
- }
-
- if (hdrs->has_asl_compiler_id) {
- /* strncpy is justified: the field need not be NUL-terminated. */
- strncpy(hdr.asl_compiler_id, hdrs->asl_compiler_id,
- sizeof(hdr.asl_compiler_id));
- ++changed;
- }
-
- if (hdrs->has_asl_compiler_rev) {
- hdr.asl_compiler_revision = cpu_to_le32(hdrs->asl_compiler_rev);
- ++changed;
- }
-
- if (!has_header && !changed) {
- fprintf(stderr, "warning: acpitable: no table headers are specified\n");
- }
-
-
- /* now calculate checksum of the table, complete with the header */
- /* we may as well leave checksum intact if has_header is true */
- /* alternatively there may be a way to set cksum to a given value */
- hdr.checksum = 0; /* for checksum calculation */
-
- /* put header back */
- memcpy(table_start, &hdr, sizeof(hdr));
-
- if (changed || !has_header || 1) {
- ((struct acpi_table_header *)table_start)->checksum =
- acpi_checksum((uint8_t *)table_start + ACPI_TABLE_PFX_SIZE, len);
- }
-
- /* increase number of tables */
- (*(uint16_t *)acpi_tables) =
- cpu_to_le32(le32_to_cpu(*(uint16_t *)acpi_tables) + 1);
-
- acpi_tables_len = allen;
+ acpi_table_install(blob, bloblen, hdrs->has_file, hdrs, &err);
out:
+ g_free(blob);
g_strfreev(pathnames);
if (hdrs != NULL) {