From ad93552b86579afd29e99da1b2fcacb0d872cd1a Mon Sep 17 00:00:00 2001 From: Aaron Durbin Date: Mon, 24 Dec 2012 14:28:37 -0600 Subject: [PATCH] lib: add rmodule support A rmodule is short for relocation module. Relocaiton modules are standalone programs. These programs are linked at address 0 as a shared object with a special linker script that maintains the relocation entries for the object. These modules can then be embedded as a raw binary (objcopy -O binary) to be loaded at any location desired. Initially, the only arch support is for x86. All comments below apply to x86 specific properties. The intial user of this support would be for SMM handlers since those handlers sometimes need to be located at a dynamic address (e.g. TSEG region). The relocation entries are currently Elf32_Rel. They are 8 bytes large, and the entries are not necessarily in sorted order. An future optimization would be to have a tool convert the unsorted relocations into just sorted offsets. This would reduce the size of the blob produced after being processed. Essentialy, 8 bytes per relocation meta entry would reduce to 4 bytes. Change-Id: I2236dcb66e9d2b494ce2d1ae40777c62429057ef Signed-off-by: Aaron Durbin Reviewed-on: http://review.coreboot.org/2692 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich --- src/Kconfig | 8 ++ src/include/rmodule.h | 126 ++++++++++++++++++++++ src/lib/Makefile.inc | 18 ++++ src/lib/rmodule.c | 245 ++++++++++++++++++++++++++++++++++++++++++ src/lib/rmodule.ld | 115 ++++++++++++++++++++ 5 files changed, 512 insertions(+) create mode 100644 src/include/rmodule.h create mode 100644 src/lib/rmodule.c create mode 100644 src/lib/rmodule.ld diff --git a/src/Kconfig b/src/Kconfig index 3e249679b8..7206878b28 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -357,6 +357,14 @@ config GFXUMA help Enable Unified Memory Architecture for graphics. +config RELOCATABLE_MODULES + bool "Relocatable Modules" + default n + help + If RELOCATABLE_MODULES is selected then support is enabled for + building relocatable modules in the ram stage. Those modules can be + loaded anywhere and all the relocations are handled automatically. + config HAVE_ACPI_TABLES bool help diff --git a/src/include/rmodule.h b/src/include/rmodule.h new file mode 100644 index 0000000000..b51700cc3a --- /dev/null +++ b/src/include/rmodule.h @@ -0,0 +1,126 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 ChromeOS Authors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef RMODULE_H +#define RMODULE_H + +#include + +#define RMODULE_MAGIC 0xf8fe +#define RMODULE_VERSION_1 1 + +enum { + RMODULE_TYPE_SMM, +}; + +struct rmodule; + +/* Public API for loading rmdoules. */ +int rmodule_parse(void *ptr, struct rmodule *m); +void *rmodule_parameters(const struct rmodule *m); +void *rmodule_entry(const struct rmodule *m); +int rmodule_entry_offset(const struct rmodule *m); +int rmodule_memory_size(const struct rmodule *m); +int rmodule_load(void *loc, struct rmodule *m); +int rmodule_load_alignment(const struct rmodule *m); + +#define FIELD_ENTRY(x_) ((u32)&x_) +#define RMODULE_HEADER(entry_, type_) \ +{ \ + .magic = RMODULE_MAGIC, \ + .version = RMODULE_VERSION_1, \ + .type = type_, \ + .payload_begin_offset = FIELD_ENTRY(_payload_begin_offset), \ + .payload_end_offset = FIELD_ENTRY(_payload_end_offset), \ + .relocations_begin_offset = \ + FIELD_ENTRY(_relocations_begin_offset), \ + .relocations_end_offset = \ + FIELD_ENTRY(_relocations_end_offset), \ + .module_link_start_address = \ + FIELD_ENTRY(_module_link_start_addr), \ + .module_program_size = FIELD_ENTRY(_module_program_size), \ + .module_entry_point = FIELD_ENTRY(entry_), \ + .parameters_begin = FIELD_ENTRY(_module_params_begin), \ + .parameters_end = FIELD_ENTRY(_module_params_end), \ + .bss_begin = FIELD_ENTRY(_bss_begin), \ + .bss_end = FIELD_ENTRY(_bss_end), \ +} + +#define DEFINE_RMODULE_HEADER(name_, entry_, type_) \ + struct rmodule_header name_ \ + __attribute__ ((section (".module_header"))) = \ + RMODULE_HEADER(entry_, type_) + + +/* Private data structures below should not be used directly. */ + +/* All fields with '_offset' in the name are byte offsets into the flat blob. + * The linker and the linker script takes are of assigning the values. */ +struct rmodule_header { + u16 magic; + u8 version; + u8 type; + /* The payload represents the program's loadable code and data. */ + u32 payload_begin_offset; + u32 payload_end_offset; + /* Begin and of relocation information about the program module. */ + u32 relocations_begin_offset; + u32 relocations_end_offset; + /* The starting address of the linked program. This address is vital + * for determining relocation offsets as the reloction info and other + * symbols (bss, entry point) need this value as a basis to calculate + * the offsets. + */ + u32 module_link_start_address; + /* The module_program_size is the size of memory used while running + * the program. The program is assumed to consume a contiguos amount + * of memory. */ + u32 module_program_size; + /* This is program's execution entry point. */ + u32 module_entry_point; + /* Optional paramter structure that can be used to pass data into + * the module. */ + u32 parameters_begin; + u32 parameters_end; + /* BSS section information so the loader can clear the bss. */ + u32 bss_begin; + u32 bss_end; +} __attribute__ ((packed)); + +struct rmodule { + void *location; + struct rmodule_header *header; + const void *payload; + int payload_size; + void *relocations; +}; + +/* These are the symbols assumed that every module contains. The linker script + * provides these symbols. */ +extern char _relocations_begin_offset[]; +extern char _relocations_end_offset[]; +extern char _payload_end_offset[]; +extern char _payload_begin_offset[]; +extern char _bss_begin[]; +extern char _bss_end[]; +extern char _module_program_size[]; +extern char _module_link_start_addr[]; +extern char _module_params_begin[]; +extern char _module_params_end[]; + +#endif /* RMODULE_H */ diff --git a/src/lib/Makefile.inc b/src/lib/Makefile.inc index be57f29411..160fce6714 100644 --- a/src/lib/Makefile.inc +++ b/src/lib/Makefile.inc @@ -107,3 +107,21 @@ endif $(obj)/lib/uart8250mem.smm.o : $(OPTION_TABLE_H) $(obj)/lib/uart8250.smm.o : $(OPTION_TABLE_H) +ifeq ($(CONFIG_RELOCATABLE_MODULES),y) +ramstage-y += rmodule.c + +RMODULE_LDSCRIPT := $(src)/lib/rmodule.ld +RMODULE_LDFLAGS := -nostartfiles -shared -z defs -nostdlib -Bsymbolic -T $(RMODULE_LDSCRIPT) + +# rmodule_link_rules is a function that should be called with: +# (1) the object name to link +# (2) the dependencies +# (3) heap size of the relocatable module +# It will create the necessary Make rules. +define rmodule_link +$(strip $(1)): $(strip $(2)) $$(RMODULE_LDSCRIPT) $$(obj)/ldoptions + $$(LD) $$(RMODULE_LDFLAGS) --defsym=__heap_size=$(strip $(3)) -o $$@ $(strip $(2)) + $$(NM) -n $$@ > $$(basename $$@).map +endef + +endif diff --git a/src/lib/rmodule.c b/src/lib/rmodule.c new file mode 100644 index 0000000000..56d7c6d646 --- /dev/null +++ b/src/lib/rmodule.c @@ -0,0 +1,245 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 ChromeOS Authors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include +#include +#include +#include + +/* Change this define to get more verbose debugging for module loading. */ +#define PK_ADJ_LEVEL BIOS_NEVER + +#if CONFIG_ARCH_X86 +/* + * On X86, the only relocations currently allowed are R_386_RELATIVE which + * have '0' for the symbol info in the relocation metadata (in r_info). + * The reason is that the module is fully linked and just has the relocations' + * locations. + */ +typedef struct { + u32 r_offset; + u32 r_info; +} Elf32_Rel; + +#define R_386_RELATIVE 8 + +#define RELOCTION_ENTRY_SIZE sizeof(Elf32_Rel) +static inline int rmodule_reloc_offset(const void *reloc) +{ + const Elf32_Rel *rel = reloc; + return rel->r_offset; +} + +static inline int rmodule_reloc_valid(const void *reloc) +{ + const Elf32_Rel *rel = reloc; + return (rel->r_info == R_386_RELATIVE); +} + +static inline void *remodule_next_reloc(const void *reloc) +{ + const Elf32_Rel *rel = reloc; + rel++; + return (void *)rel; +} + +#else +#error Arch needs to add relocation information support for RMODULE +#endif + +static inline int rmodule_is_loaded(const struct rmodule *module) +{ + return module->location != NULL; +} + +/* Calculate a loaded program address based on the blob address. */ +static inline void *rmodule_load_addr(const struct rmodule *module, + u32 blob_addr) +{ + char *loc = module->location; + return &loc[blob_addr - module->header->module_link_start_address]; +} + +/* Initialize a rmodule structure based on raw data. */ +int rmodule_parse(void *ptr, struct rmodule *module) +{ + char *base; + struct rmodule_header *rhdr; + + base = ptr; + rhdr = ptr; + + if (rhdr == NULL) + return -1; + + /* Sanity check the raw data. */ + if (rhdr->magic != RMODULE_MAGIC) + return -1; + if (rhdr->version != RMODULE_VERSION_1) + return -1; + + /* Indicate the module hasn't been loaded yet. */ + module->location = NULL; + + /* The rmodule only needs a reference to the reloc_header. */ + module->header = rhdr; + + /* The payload lives after the header. */ + module->payload = &base[rhdr->payload_begin_offset]; + module->payload_size = rhdr->payload_end_offset - + rhdr->payload_begin_offset; + module->relocations = &base[rhdr->relocations_begin_offset]; + + return 0; +} + +int rmodule_memory_size(const struct rmodule *module) +{ + return module->header->module_program_size; +} + +void *rmodule_parameters(const struct rmodule *module) +{ + if (!rmodule_is_loaded(module)) + return NULL; + + /* Indicate if there are no parameters. */ + if (module->header->parameters_begin == module->header->parameters_end) + return NULL; + + return rmodule_load_addr(module, module->header->parameters_begin); +} + +int rmodule_entry_offset(const struct rmodule *module) +{ + return module->header->module_entry_point - + module->header->module_link_start_address; +} + +void *rmodule_entry(const struct rmodule *module) +{ + if (!rmodule_is_loaded(module)) + return NULL; + + return rmodule_load_addr(module, module->header->module_entry_point); +} + +static void rmodule_clear_bss(struct rmodule *module) +{ + char *begin; + int size; + + begin = rmodule_load_addr(module, module->header->bss_begin); + size = module->header->bss_end - module->header->bss_begin; + memset(begin, 0, size); +} + +static inline int rmodule_number_relocations(const struct rmodule *module) +{ + int r; + + r = module->header->relocations_end_offset; + r -= module->header->relocations_begin_offset; + r /= RELOCTION_ENTRY_SIZE; + return r; +} + +static void rmodule_copy_payload(const struct rmodule *module) +{ + printk(BIOS_DEBUG, "Loading module at %p with entry %p. " + "filesize: 0x%x memsize: 0x%x\n", + module->location, rmodule_entry(module), + module->payload_size, rmodule_memory_size(module)); + memcpy(module->location, module->payload, module->payload_size); +} + +static inline u32 *rmodule_adjustment_location(const struct rmodule *module, + const void *reloc) +{ + int reloc_offset; + + /* Don't relocate header field entries -- only program relocations. */ + reloc_offset = rmodule_reloc_offset(reloc); + if (reloc_offset < module->header->module_link_start_address) + return NULL; + + return rmodule_load_addr(module, reloc_offset); +} + +static int rmodule_relocate(const struct rmodule *module) +{ + int num_relocations; + const void *reloc; + u32 adjustment; + + /* Each relocation needs to be adjusted relative to the beginning of + * the loaded program. */ + adjustment = (u32)rmodule_load_addr(module, 0); + + reloc = module->relocations; + num_relocations = rmodule_number_relocations(module); + + printk(BIOS_DEBUG, "Processing %d relocs with adjust value of 0x%08x\n", + num_relocations, adjustment); + + while (num_relocations > 0) { + u32 *adjust_loc; + + if (!rmodule_reloc_valid(reloc)) + return -1; + + /* If the adjustment location is non-NULL adjust it. */ + adjust_loc = rmodule_adjustment_location(module, reloc); + if (adjust_loc != NULL) { + printk(PK_ADJ_LEVEL, "Adjusting %p: 0x%08x -> 0x%08x\n", + adjust_loc, *adjust_loc, + *adjust_loc + adjustment); + *adjust_loc += adjustment; + } + + reloc = remodule_next_reloc(reloc); + num_relocations--; + } + + return 0; +} + +int rmodule_load_alignment(const struct rmodule *module) +{ + /* The load alignment is the start of the program's linked address. + * The base address where the program is loaded needs to be a multiple + * of the program's starting link address. That way all data alignment + * in the program is presered. */ + return module->header->module_link_start_address; +} + +int rmodule_load(void *base, struct rmodule *module) +{ + /* + * In order to load the module at a given address, the following steps + * take place: + * 1. Copy payload to base address. + * 2. Clear the bss segment. + * 3. Adjust relocations within the module to new base address. + */ + module->location = base; + rmodule_copy_payload(module); + rmodule_clear_bss(module); + return rmodule_relocate(module); +} + diff --git a/src/lib/rmodule.ld b/src/lib/rmodule.ld new file mode 100644 index 0000000000..4c13c84666 --- /dev/null +++ b/src/lib/rmodule.ld @@ -0,0 +1,115 @@ +OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386") +OUTPUT_ARCH(i386) + +/* + * This linker script is used to link rmodules (relocatable modules). It + * links at zero so that relocation fixups are easy when placing the binaries + * anywhere in the address space. + * + * NOTE: The program's loadable sections (text, module_params, and data) are + * packed into the flat blob using the AT directive. The rmodule loader assumes + * the entire program resides in one contiguous address space. Therefore, + * alignment for a given section (if required) needs to be done at the end of + * the preceeding section. e.g. if the data section should be aligned to an 8 + * byte address the text section should have ALIGN(8) at the end of its section. + * Otherwise there won't be a consistent mapping between the flat blob and the + * loaded program. + */ + +BASE_ADDRESS = 0x00000; + +SECTIONS +{ + . = BASE_ADDRESS; + + .header : AT (0) { + *(.module_header); + . = ALIGN(8); + } + + /* Align the start of the module program to a large enough alignment + * so that any data in the program with an alignement property is met. + * Essentially, this alignment is the maximum possible data alignment + * property a program can have. */ + . = ALIGN(4096); + _module_link_start_addr = .; + _payload_begin_offset = LOADADDR(.header) + SIZEOF(.header); + + .text : AT (_payload_begin_offset) { + /* C code of the module. */ + *(.text); + *(.text.*); + /* C read-only data. */ + . = ALIGN(16); + *(.rodata); + *(.rodata.*); + . = ALIGN(4); + } + + .module_params : AT (LOADADDR(.text) + SIZEOF(.text)) { + /* The parameters section can be used to pass parameters + * to a module, however there has to be an prior agreement + * on how to interpret the parameters. */ + _module_params_begin = .; + *(.module_parameters); + _module_params_end = .; + . = ALIGN(4); + } + + .data : AT (LOADADDR(.module_params) + SIZEOF(.module_params)) { + _sdata = .; + *(.data); + . = ALIGN(4); + _edata = .; + } + + /* _payload_end marks the end of the module's code and data. */ + _payload_end_offset = LOADADDR(.data) + SIZEOF(.data); + + .bss (NOLOAD) : { + /* C uninitialized data of the SMM handler */ + _bss_begin = .; + *(.bss); + *(.sbss); + *(COMMON); + . = ALIGN(8); + _bss_end = .; + } + + .heap (NOLOAD) : { + /* + * Place the heap after BSS. The heap size is passed in by + * by way of ld --defsym=__heap_size=<> + */ + _heap = .; + . = . + __heap_size; + _eheap = .; + } + + /* _module_program_size is the total memory used by the program. */ + _module_program_size = _eheap - _module_link_start_addr; + + /* The relocation information is linked on top of the BSS section + * because the BSS section takes no space on disk. The relocation data + * resides directly after the data section in the flat binary. */ + .relocations ADDR(.bss) : AT (_payload_end_offset) { + *(.rel.*); + } + _relocations_begin_offset = LOADADDR(.relocations); + _relocations_end_offset = _relocations_begin_offset + + SIZEOF(.relocations); + + /DISCARD/ : { + /* Drop unnecessary sections. Since these modules are linked + * as shared objects there are dynamic sections. These sections + * aren't needed so drop them. */ + *(.comment); + *(.note); + *(.note.*); + *(.dynamic); + *(.dynsym); + *(.dynstr); + *(.gnu.hash); + *(.eh_frame); + } +}