This is the seabios code that adds support for loading acpi tables from QEMU.
Changes from v4: - address Kevin's comments: move loader to src/fw/ simplify some code drop some unused code chunks load from ROM even if !ACPI rename option to make it non ACPI specific Changes from v3: - updated to latest bits - add option to disable loading from QEMU
Changes from v2: - addressed comments from Kevin: fixed coding style, minimized changes to existing code
Changes from v1: - simplified linker interfaces along the lines suggested by Kevin - fixed lots of bugs
Michael S. Tsirkin (4): romfile_loader: utility to patch in-memory ROM files malloc: support looking up a given pattern in FSEG acpi: pack rsdp acpi: load and link tables through romfile loader
Makefile | 2 +- src/fw/romfile_loader.h | 72 ++++++++++++++++++++ src/malloc.h | 1 + src/std/acpi.h | 2 +- src/fw/acpi.c | 30 ++++++++ src/fw/romfile_loader.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++ src/malloc.c | 19 +++++- src/Kconfig | 11 +++ 8 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 src/fw/romfile_loader.h create mode 100644 src/fw/romfile_loader.c
Add ability for a ROM file to point to it's image in memory. When file is in memory, add utility that can patch it, storing pointers to one file within another file.
This is not a lot of code: together with the follow-up patch to load ACPI tables from ROM, it's about 1K extra.
Signed-off-by: Michael S. Tsirkin mst@redhat.com --- Makefile | 2 +- src/fw/romfile_loader.h | 72 ++++++++++++++++++++ src/fw/romfile_loader.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 src/fw/romfile_loader.h create mode 100644 src/fw/romfile_loader.c
diff --git a/Makefile b/Makefile index 3984d35..797c00f 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ SRC32FLAT=$(SRCBOTH) post.c memmap.c malloc.c pmm.c romfile.c optionroms.c \ hw/usb-hub.c \ fw/coreboot.c fw/lzmadecode.c fw/csm.c fw/biostables.c \ fw/paravirt.c fw/shadow.c fw/pciinit.c fw/smm.c fw/mtrr.c fw/xen.c \ - fw/acpi.c fw/mptable.c fw/pirtable.c fw/smbios.c + fw/acpi.c fw/mptable.c fw/pirtable.c fw/smbios.c fw/romfile_loader.c SRC32SEG=string.c output.c pcibios.c apm.c stacks.c hw/pci.c DIRS=src src/hw src/fw vgasrc
diff --git a/src/fw/romfile_loader.h b/src/fw/romfile_loader.h new file mode 100644 index 0000000..15eab2a --- /dev/null +++ b/src/fw/romfile_loader.h @@ -0,0 +1,72 @@ +#ifndef __ROMFILE_LOADER_H +#define __ROMFILE_LOADER_H + +#include "types.h" // u8 +#include "util.h" // romfile_s + +#define ROMFILE_LOADER_FILESZ 56 + +/* ROM file linker/loader interface. Linker uses little endian format */ +struct romfile_loader_entry_s { + u32 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 alloc_file[ROMFILE_LOADER_FILESZ]; + u32 alloc_align; + u8 alloc_zone; + }; + + /* + * 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 pointer_dest_file[ROMFILE_LOADER_FILESZ]; + char pointer_src_file[ROMFILE_LOADER_FILESZ]; + u32 pointer_offset; + u8 pointer_size; + }; + + /* + * 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 cksum_file[ROMFILE_LOADER_FILESZ]; + u32 cksum_offset; + u32 cksum_start; + u32 cksum_length; + }; + + /* padding */ + char pad[124]; + }; +}; + +enum { + ROMFILE_LOADER_COMMAND_ALLOCATE = 0x1, + ROMFILE_LOADER_COMMAND_ADD_POINTER = 0x2, + ROMFILE_LOADER_COMMAND_ADD_CHECKSUM = 0x3, +}; + +enum { + ROMFILE_LOADER_ALLOC_ZONE_HIGH = 0x1, + ROMFILE_LOADER_ALLOC_ZONE_FSEG = 0x2, +}; + +int romfile_loader_execute(const char *name); + +#endif diff --git a/src/fw/romfile_loader.c b/src/fw/romfile_loader.c new file mode 100644 index 0000000..325a0fa --- /dev/null +++ b/src/fw/romfile_loader.c @@ -0,0 +1,177 @@ +#include "romfile_loader.h" +#include "byteorder.h" // leXX_to_cpu/cpu_to_leXX +#include "util.h" // checksum +#include "string.h" // strcmp +#include "romfile.h" // struct romfile_s +#include "malloc.h" // Zone*, _malloc +#include "output.h" // warn_* + +struct romfile_loader_file { + struct romfile_s *file; + void *data; +}; +struct romfile_loader_files { + int nfiles; + struct romfile_loader_file files[]; +}; + +static struct romfile_loader_file * +romfile_loader_find(const char *name, + struct romfile_loader_files *files) +{ + int i; + if (name[ROMFILE_LOADER_FILESZ - 1]) + return NULL; + for (i = 0; i < files->nfiles; ++i) + if (!strcmp(files->files[i].file->name, name)) + return &files->files[i]; + return NULL; +} + +static void romfile_loader_allocate(struct romfile_loader_entry_s *entry, + struct romfile_loader_files *files) +{ + struct zone_s *zone; + struct romfile_loader_file *file = &files->files[files->nfiles]; + void *data; + int ret; + unsigned alloc_align = le32_to_cpu(entry->alloc_align); + + if (alloc_align & (alloc_align - 1)) + goto err; + + switch (entry->alloc_zone) { + case ROMFILE_LOADER_ALLOC_ZONE_HIGH: + zone = &ZoneHigh; + break; + case ROMFILE_LOADER_ALLOC_ZONE_FSEG: + zone = &ZoneFSeg; + break; + default: + goto err; + } + if (alloc_align < MALLOC_MIN_ALIGN) + alloc_align = MALLOC_MIN_ALIGN; + if (entry->alloc_file[ROMFILE_LOADER_FILESZ - 1]) + goto err; + file->file = romfile_find(entry->alloc_file); + if (!file->file || !file->file->size) + return; + data = _malloc(zone, MALLOC_DEFAULT_HANDLE, file->file->size, alloc_align); + if (!data) { + warn_noalloc(); + return; + } + ret = file->file->copy(file->file, data, file->file->size); + if (ret != file->file->size) + goto file_err; + file->data = data; + files->nfiles++; + return; + +file_err: + free(data); +err: + warn_internalerror(); +} + +static void romfile_loader_add_pointer(struct romfile_loader_entry_s *entry, + struct romfile_loader_files *files) +{ + struct romfile_loader_file *dest_file; + struct romfile_loader_file *src_file; + unsigned offset = le32_to_cpu(entry->pointer_offset); + u64 pointer = 0; + + dest_file = romfile_loader_find(entry->pointer_dest_file, files); + src_file = romfile_loader_find(entry->pointer_src_file, files); + + if (!dest_file || !src_file || !dest_file->data || !src_file->data || + offset + entry->pointer_size < offset || + offset + entry->pointer_size > dest_file->file->size || + entry->pointer_size < 1 || entry->pointer_size > 8 || + entry->pointer_size & (entry->pointer_size - 1)) + goto err; + + memcpy(&pointer, dest_file->data + offset, entry->pointer_size); + pointer = le64_to_cpu(pointer); + pointer += (unsigned long)src_file->data; + pointer = cpu_to_le64(pointer); + memcpy(dest_file->data + offset, &pointer, entry->pointer_size); + + return; +err: + warn_internalerror(); +} + +static void romfile_loader_add_checksum(struct romfile_loader_entry_s *entry, + struct romfile_loader_files *files) +{ + struct romfile_loader_file *file; + unsigned offset = le32_to_cpu(entry->cksum_offset); + unsigned start = le32_to_cpu(entry->cksum_start); + unsigned len = le32_to_cpu(entry->cksum_length); + u8 *data; + + file = romfile_loader_find(entry->cksum_file, files); + + if (!file || !file->data || offset >= file->file->size || + start + len < start || start + len > file->file->size) + goto err; + + data = file->data + offset; + *data -= checksum(file->data + start, len); + + return; +err: + warn_internalerror(); +} + +int romfile_loader_execute(const char *name) +{ + struct romfile_loader_entry_s *entry; + int size, offset = 0, nfiles; + struct romfile_loader_files *files; + void *data = romfile_loadfile(name, &size); + if (!data) + return -1; + + if (size % sizeof(*entry)) { + warn_internalerror(); + goto err; + } + + /* (over)estimate the number of files to load. */ + nfiles = size / sizeof(*entry); + files = malloc_tmp(sizeof(*files) + nfiles * sizeof(files->files[0])); + if (!files) { + warn_noalloc(); + goto err; + } + files->nfiles = 0; + + for (offset = 0; offset < size; offset += sizeof(*entry)) { + entry = data + offset; + switch (le32_to_cpu(entry->command)) { + case ROMFILE_LOADER_COMMAND_ALLOCATE: + romfile_loader_allocate(entry, files); + break; + case ROMFILE_LOADER_COMMAND_ADD_POINTER: + romfile_loader_add_pointer(entry, files); + break; + case ROMFILE_LOADER_COMMAND_ADD_CHECKSUM: + romfile_loader_add_checksum(entry, files); + default: + /* Skip commands that we don't recognize. */ + break; + } + } + + free(files); + free(data); + return 0; + +err: + free(data); + return -1; +}
Will be used to find RSDP there.
Signed-off-by: Michael S. Tsirkin mst@redhat.com --- src/malloc.h | 1 + src/malloc.c | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/malloc.h b/src/malloc.h index af8a21d..feb8938 100644 --- a/src/malloc.h +++ b/src/malloc.h @@ -19,6 +19,7 @@ void *_malloc(struct zone_s *zone, u32 handle, u32 size, u32 align); int _free(void *data); u32 malloc_getspace(struct zone_s *zone); void *malloc_find(u32 handle); +void *malloc_find_fseg_pattern(void *pattern, unsigned pattern_size);
#define MALLOC_DEFAULT_HANDLE 0xFFFFFFFF // Minimum alignment of malloc'd memory diff --git a/src/malloc.c b/src/malloc.c index 281f41e..0f5fae7 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -12,7 +12,7 @@ #include "output.h" // dprintf #include "stacks.h" // wait_preempt #include "std/optionrom.h" // OPTION_ROM_ALIGN -#include "string.h" // memset +#include "string.h" // memset, memcmp
// Information on a reserved area. struct allocinfo_s { @@ -273,6 +273,23 @@ _free(void *data) return 0; }
+// Find the data block in zone matching a given pattern. +void *malloc_find_fseg_pattern(void *pattern, unsigned pattern_size) +{ + extern u8 zonefseg_start[], zonefseg_end[]; + unsigned space = zonefseg_end - zonefseg_start; + int off; + + if (space < pattern_size) + return NULL; + + for (off = 0; off < space - pattern_size; ++off) { + if (!memcmp(zonefseg_start + off, pattern, pattern_size)) + return zonefseg_start + off; + } + return NULL; +} + // Find the amount of free space in a given zone. u32 malloc_getspace(struct zone_s *zone)
On Wed, Sep 25, 2013 at 12:54:42PM +0300, Michael S. Tsirkin wrote:
Will be used to find RSDP there.
Signed-off-by: Michael S. Tsirkin mst@redhat.com
src/malloc.h | 1 + src/malloc.c | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/malloc.h b/src/malloc.h index af8a21d..feb8938 100644 --- a/src/malloc.h +++ b/src/malloc.h @@ -19,6 +19,7 @@ void *_malloc(struct zone_s *zone, u32 handle, u32 size, u32 align); int _free(void *data); u32 malloc_getspace(struct zone_s *zone); void *malloc_find(u32 handle); +void *malloc_find_fseg_pattern(void *pattern, unsigned pattern_size);
#define MALLOC_DEFAULT_HANDLE 0xFFFFFFFF // Minimum alignment of malloc'd memory diff --git a/src/malloc.c b/src/malloc.c index 281f41e..0f5fae7 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -12,7 +12,7 @@ #include "output.h" // dprintf #include "stacks.h" // wait_preempt #include "std/optionrom.h" // OPTION_ROM_ALIGN -#include "string.h" // memset +#include "string.h" // memset, memcmp
// Information on a reserved area. struct allocinfo_s { @@ -273,6 +273,23 @@ _free(void *data) return 0; }
+// Find the data block in zone matching a given pattern. +void *malloc_find_fseg_pattern(void *pattern, unsigned pattern_size) +{
- extern u8 zonefseg_start[], zonefseg_end[];
- unsigned space = zonefseg_end - zonefseg_start;
- int off;
- if (space < pattern_size)
return NULL;
- for (off = 0; off < space - pattern_size; ++off) {
if (!memcmp(zonefseg_start + off, pattern, pattern_size))
return zonefseg_start + off;
- }
- return NULL;
+}
This shouldn't be in malloc.c - it's not part of the core memory allocation system. Either scan the whole of 0xf0000-0x100000, use extern zonefseg_end/start in another file, or scan the allocations made in romfile-loader.
Also, this shouldn't scan every byte for the pattern. It should scan on 16 byte boundaries and do the acpi checksum check. See the code in fw/biostable.c:copy_acpi_rsdp as an example. Nothing stops something else from allocating ram which happens to have an rsdp signature in it. It's unlikely, but not worth the risk.
The rest of the series looks okay to me.
-Kevin
On Thu, Sep 26, 2013 at 08:33:53PM -0400, Kevin O'Connor wrote:
On Wed, Sep 25, 2013 at 12:54:42PM +0300, Michael S. Tsirkin wrote:
Will be used to find RSDP there.
Signed-off-by: Michael S. Tsirkin mst@redhat.com
src/malloc.h | 1 + src/malloc.c | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-)
diff --git a/src/malloc.h b/src/malloc.h index af8a21d..feb8938 100644 --- a/src/malloc.h +++ b/src/malloc.h @@ -19,6 +19,7 @@ void *_malloc(struct zone_s *zone, u32 handle, u32 size, u32 align); int _free(void *data); u32 malloc_getspace(struct zone_s *zone); void *malloc_find(u32 handle); +void *malloc_find_fseg_pattern(void *pattern, unsigned pattern_size);
#define MALLOC_DEFAULT_HANDLE 0xFFFFFFFF // Minimum alignment of malloc'd memory diff --git a/src/malloc.c b/src/malloc.c index 281f41e..0f5fae7 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -12,7 +12,7 @@ #include "output.h" // dprintf #include "stacks.h" // wait_preempt #include "std/optionrom.h" // OPTION_ROM_ALIGN -#include "string.h" // memset +#include "string.h" // memset, memcmp
// Information on a reserved area. struct allocinfo_s { @@ -273,6 +273,23 @@ _free(void *data) return 0; }
+// Find the data block in zone matching a given pattern. +void *malloc_find_fseg_pattern(void *pattern, unsigned pattern_size) +{
- extern u8 zonefseg_start[], zonefseg_end[];
- unsigned space = zonefseg_end - zonefseg_start;
- int off;
- if (space < pattern_size)
return NULL;
- for (off = 0; off < space - pattern_size; ++off) {
if (!memcmp(zonefseg_start + off, pattern, pattern_size))
return zonefseg_start + off;
- }
- return NULL;
+}
This shouldn't be in malloc.c - it's not part of the core memory allocation system. Either scan the whole of 0xf0000-0x100000, use extern zonefseg_end/start in another file, or scan the allocations made in romfile-loader.
BTW ACPI spec says 0xe0000 and not 0xf0000:
5.2.5.1 Finding the RSDP on IA-PC Systems OSPM finds the Root System Description Pointer (RSDP) structure by searching physical memory ranges on 16-byte boundaries for a valid Root System Description Pointer structure signature and checksum match as follows: • The first 1 KB of the Extended BIOS Data Area (EBDA). For EISA or MCA systems, the EBDA can be found in the two-byte location 40:0Eh on the BIOS data area. • The BIOS read-only memory space between 0E0000h and 0FFFFFh.
Also, this shouldn't scan every byte for the pattern. It should scan on 16 byte boundaries and do the acpi checksum check. See the code in fw/biostable.c:copy_acpi_rsdp as an example. Nothing stops something else from allocating ram which happens to have an rsdp signature in it. It's unlikely, but not worth the risk.
The rest of the series looks okay to me.
-Kevin
rsdp might not be aligned, so mark it packed.
Signed-off-by: Michael S. Tsirkin mst@redhat.com --- src/std/acpi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/std/acpi.h b/src/std/acpi.h index ecfa472..6ea5b9d 100644 --- a/src/std/acpi.h +++ b/src/std/acpi.h @@ -40,7 +40,7 @@ struct rsdp_descriptor { /* Root System Descriptor Pointer */ u64 xsdt_physical_address; /* 64-bit physical address of XSDT */ u8 extended_checksum; /* Checksum of entire table */ u8 reserved [3]; /* Reserved field must be 0 */ -}; +} PACKED;
/* Table structure from Linux kernel (the ACPI tables are under the BSD license) */
Load files through romfile loader and use for acpi tables. We need the RSDP pointer to hang the rest of the tables off it, to detect that we simply scan all memory in FSEG.
Add an option to disable this feature (useful for old QEMU versions). This saves about 1Kbytes.
enabled: Total size: 134932 Fixed: 61571 Free: 127212 (used 51.5% of 256KiB rom)
disabled: Total size: 133836 Fixed: 61563 Free: 128308 (used 51.1% of 256KiB rom)
Signed-off-by: Michael S. Tsirkin mst@redhat.com --- src/fw/acpi.c | 29 +++++++++++++++++++++++++++++ src/Kconfig | 11 +++++++++++ 2 files changed, 40 insertions(+)
diff --git a/src/fw/acpi.c b/src/fw/acpi.c index 0497d9b..1a1e23c 100644 --- a/src/fw/acpi.c +++ b/src/fw/acpi.c @@ -32,6 +32,7 @@ #include "string.h" // memset #include "util.h" // MaxCountCPUs #include "x86.h" // readl +#include "romfile_loader.h" // romfile_loader_execute
#include "src/fw/acpi-dsdt.hex"
@@ -606,10 +607,38 @@ static const struct pci_device_id acpi_find_tbl[] = {
struct rsdp_descriptor *RsdpAddr;
+/* Look for RSDP signature in FSEG memory */ +static struct rsdp_descriptor * +acpi_find_rsdp_rom(void) +{ + u64 rsdp = cpu_to_le64(RSDP_SIGNATURE); + return malloc_find_fseg_pattern(&rsdp, sizeof(rsdp)); +} + #define MAX_ACPI_TABLES 20 void acpi_setup(void) { + if (CONFIG_FW_ROMFILE_LOAD) { + int loader_err; + + dprintf(3, "load ACPI tables\n"); + + loader_err = romfile_loader_execute("etc/table-loader"); + + RsdpAddr = acpi_find_rsdp_rom(); + + if (RsdpAddr) + return; + + /* If present, loader should have installed an RSDP. + * Not installed? We might still be able to continue + * using the builtin RSDP. + */ + if (!loader_err) + warn_internalerror(); + } + if (! CONFIG_ACPI) return;
diff --git a/src/Kconfig b/src/Kconfig index c40cc61..5780885 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -424,6 +424,17 @@ menu "BIOS Tables" This option can be disabled for QEMU 1.4 and newer to save some space in the ROM file. If unsure, say Y. + config FW_ROMFILE_LOAD + bool "Load BIOS tables from ROM files" + depends on QEMU_HARDWARE + default y + help + Support loading BIOS firmware tables from ROM files. + At the moment, only ACPI tables can be loaded in this way. + Required for QEMU 1.7 and newer. + This option can be disabled for QEMU 1.6 and older + to save some space in the ROM file. + If unsure, say Y. endmenu
source vgasrc/Kconfig