diff options
-rw-r--r-- | src/mainboard/emulation/qemu-i440fx/acpi_tables.c | 6 | ||||
-rw-r--r-- | src/mainboard/emulation/qemu-i440fx/fw_cfg.c | 191 | ||||
-rw-r--r-- | src/mainboard/emulation/qemu-i440fx/fw_cfg.h | 1 | ||||
-rw-r--r-- | src/mainboard/emulation/qemu-q35/acpi_tables.c | 6 |
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 */ |