aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mainboard/emulation/qemu-i440fx/acpi_tables.c6
-rw-r--r--src/mainboard/emulation/qemu-i440fx/fw_cfg.c191
-rw-r--r--src/mainboard/emulation/qemu-i440fx/fw_cfg.h1
-rw-r--r--src/mainboard/emulation/qemu-q35/acpi_tables.c6
4 files changed, 204 insertions, 0 deletions
diff --git a/src/mainboard/emulation/qemu-i440fx/acpi_tables.c b/src/mainboard/emulation/qemu-i440fx/acpi_tables.c
index 904fe0fdd2..8be187ab77 100644
--- a/src/mainboard/emulation/qemu-i440fx/acpi_tables.c
+++ b/src/mainboard/emulation/qemu-i440fx/acpi_tables.c
@@ -29,6 +29,8 @@
#include <device/pci_ids.h>
#include <cpu/x86/msr.h>
+#include "fw_cfg.h"
+
extern const unsigned char AmlCode[];
#if CONFIG_HAVE_ACPI_SLIC
unsigned long acpi_create_slic(unsigned long current);
@@ -88,6 +90,10 @@ unsigned long write_acpi_tables(unsigned long start)
acpi_header_t *ssdt;
acpi_header_t *dsdt;
+ current = fw_cfg_acpi_tables(start);
+ if (current)
+ return current;
+
current = start;
/* Align ACPI tables to 16byte */
diff --git a/src/mainboard/emulation/qemu-i440fx/fw_cfg.c b/src/mainboard/emulation/qemu-i440fx/fw_cfg.c
index b85a163bf8..615aa2be3b 100644
--- a/src/mainboard/emulation/qemu-i440fx/fw_cfg.c
+++ b/src/mainboard/emulation/qemu-i440fx/fw_cfg.c
@@ -19,6 +19,7 @@
#include <swab.h>
#include <console/console.h>
#include <arch/io.h>
+#include <arch/acpigen.h>
#include "fw_cfg.h"
#include "fw_cfg_if.h"
@@ -116,3 +117,193 @@ int fw_cfg_max_cpus(void)
fw_cfg_get(FW_CFG_MAX_CPUS, &max_cpus, sizeof(max_cpus));
return max_cpus;
}
+
+/* ---------------------------------------------------------------------- */
+/*
+ * Starting with release 1.7 qemu provides acpi tables via fw_cfg.
+ * Main advantage is that new (virtual) hardware which needs acpi
+ * support JustWorks[tm] without having to patch & update the firmware
+ * (seabios, coreboot, ...) accordingly.
+ *
+ * Qemu provides a etc/table-loader file with instructions for the
+ * firmware:
+ * - A "load" instruction to fetch acpi data from fw_cfg.
+ * - A "pointer" instruction to patch a pointer. This is needed to
+ * get table-to-table references right, it is basically a
+ * primitive dynamic linker for acpi tables.
+ * - A "checksum" instruction to generate acpi table checksums.
+ *
+ * If a etc/table-loader file is found we'll go try loading the acpi
+ * tables from fw_cfg, otherwise we'll fallback to the acpi tables
+ * compiled in.
+ */
+
+#define BIOS_LINKER_LOADER_FILESZ 56
+
+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;
+
+ /* padding */
+ char pad[124];
+ };
+} __attribute__((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,
+};
+
+enum {
+ BIOS_LINKER_LOADER_ALLOC_ZONE_HIGH = 0x1,
+ BIOS_LINKER_LOADER_ALLOC_ZONE_FSEG = 0x2,
+};
+
+unsigned long fw_cfg_acpi_tables(unsigned long start)
+{
+ BiosLinkerLoaderEntry *s;
+ unsigned long *addrs, current;
+ uint32_t *ptr4;
+ uint64_t *ptr8;
+ int rc, i, j, src, dst, max;
+
+ rc = fw_cfg_check_file("etc/table-loader");
+ if (rc < 0)
+ return 0;
+
+ printk(BIOS_DEBUG, "QEMU: found acpi tables in fw_cfg.\n");
+
+ max = rc / sizeof(*s);
+ s = malloc(rc);
+ addrs = malloc(max * sizeof(*addrs));
+ fw_cfg_load_file("etc/table-loader", s);
+
+ current = start;
+ for (i = 0; i < max && s[i].command != 0; i++) {
+ switch (s[i].command) {
+ case BIOS_LINKER_LOADER_COMMAND_ALLOCATE:
+ current = ALIGN(current, s[i].alloc.align);
+ printk(BIOS_DEBUG, "QEMU: loading \"%s\" to 0x%lx\n",
+ s[i].alloc.file, current);
+
+ rc = fw_cfg_check_file(s[i].alloc.file);
+ if (rc < 0)
+ goto err;
+ fw_cfg_load_file(s[i].alloc.file, (void*)current);
+ addrs[i] = current;
+ current += rc;
+ break;
+
+ case BIOS_LINKER_LOADER_COMMAND_ADD_POINTER:
+ src = -1; dst = -1;
+ for (j = 0; j < i; j++) {
+ if (s[j].command != BIOS_LINKER_LOADER_COMMAND_ALLOCATE)
+ continue;
+ if (strcmp(s[j].alloc.file, s[i].pointer.dest_file) == 0)
+ dst = j;
+ if (strcmp(s[j].alloc.file, s[i].pointer.src_file) == 0)
+ src = j;
+ }
+ if (src == -1 || dst == -1)
+ goto err;
+
+ switch (s[i].pointer.size) {
+ case 4:
+ ptr4 = (uint32_t*)(addrs[dst] + s[i].pointer.offset);
+ *ptr4 += addrs[src];
+ break;
+
+ case 8:
+ ptr8 = (uint64_t*)(addrs[dst] + s[i].pointer.offset);
+ *ptr8 += addrs[src];
+ break;
+
+ default:
+ /*
+ * Should not happen. acpi knows 1 and 2 byte ptrs
+ * too, but we are operating with 32bit offsets which
+ * would simply not fit in there ...
+ */
+ printk(BIOS_DEBUG, "QEMU: acpi: unimplemented ptr size %d\n",
+ s[i].pointer.size);
+ goto err;
+ }
+ break;
+
+ case BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM:
+ dst = -1;
+ for (j = 0; j < i; j++) {
+ if (s[j].command != BIOS_LINKER_LOADER_COMMAND_ALLOCATE)
+ continue;
+ if (strcmp(s[j].alloc.file, s[i].cksum.file) == 0)
+ dst = j;
+ }
+ if (dst == -1)
+ goto err;
+
+ ptr4 = (uint32_t*)(addrs[dst] + s[i].cksum.offset);
+ *ptr4 = 0;
+ *ptr4 = acpi_checksum((void *)(addrs[dst] + s[i].cksum.start),
+ s[i].cksum.length);
+ break;
+
+ default:
+ printk(BIOS_DEBUG, "QEMU: acpi: unknown script cmd 0x%x @ %p\n",
+ s[i].command, s+i);
+ goto err;
+ };
+ }
+
+ printk(BIOS_DEBUG, "QEMU: loaded acpi tables from fw_cfg.\n");
+ free(s);
+ free(addrs);
+ return ALIGN(current, 16);
+
+err:
+ printk(BIOS_DEBUG, "QEMU: loading acpi tables from fw_cfg failed.\n");
+ free(s);
+ free(addrs);
+ return 0;
+}
diff --git a/src/mainboard/emulation/qemu-i440fx/fw_cfg.h b/src/mainboard/emulation/qemu-i440fx/fw_cfg.h
index 2a10d8bce9..5ab024f667 100644
--- a/src/mainboard/emulation/qemu-i440fx/fw_cfg.h
+++ b/src/mainboard/emulation/qemu-i440fx/fw_cfg.h
@@ -19,3 +19,4 @@ void fw_cfg_get(int entry, void *dst, int dstlen);
int fw_cfg_check_file(const char *name);
void fw_cfg_load_file(const char *name, void *dst);
int fw_cfg_max_cpus(void);
+unsigned long fw_cfg_acpi_tables(unsigned long start);
diff --git a/src/mainboard/emulation/qemu-q35/acpi_tables.c b/src/mainboard/emulation/qemu-q35/acpi_tables.c
index d894dc187b..4e79b2c8cd 100644
--- a/src/mainboard/emulation/qemu-q35/acpi_tables.c
+++ b/src/mainboard/emulation/qemu-q35/acpi_tables.c
@@ -29,6 +29,8 @@
#include <device/pci_ids.h>
#include <cpu/x86/msr.h>
+#include "../qemu-i440fx/fw_cfg.h"
+
extern const unsigned char AmlCode[];
#if CONFIG_HAVE_ACPI_SLIC
unsigned long acpi_create_slic(unsigned long current);
@@ -238,6 +240,10 @@ unsigned long write_acpi_tables(unsigned long start)
acpi_header_t *ssdt;
acpi_header_t *dsdt;
+ current = fw_cfg_acpi_tables(start);
+ if (current)
+ return current;
+
current = start;
/* Align ACPI tables to 16byte */