drivers/intel: Update FSP 1.1 Driver
Update the FSP driver files from 1.0 to 1.1.
Updates will occur manually to these files only for FSP 1.1 support. An
fsp_x_y should be added in the future to support newer versions of the
FSP specification.
Please note that due to the interface with EDK2, these files make
references to data structures and fields that use CamelCase.
BRANCH=none
BUG=None
TEST=Build for Braswell or Skylake boards using FSP 1.1.
Change-Id: I2914c047d786a3060075356783ac9758bc41f633
Signed-off-by: Lee Leahy <leroy.p.leahy@intel.com>
Reviewed-on: http://review.coreboot.org/10049
Tested-by: build bot (Jenkins)
Reviewed-by: Patrick Georgi <pgeorgi@google.com>
diff --git a/src/drivers/intel/fsp1_1/fsp_relocate.c b/src/drivers/intel/fsp1_1/fsp_relocate.c
new file mode 100644
index 0000000..08e1eb8
--- /dev/null
+++ b/src/drivers/intel/fsp1_1/fsp_relocate.c
@@ -0,0 +1,496 @@
+/*
+ * This file is part of the coreboot project.
+ *
+ * Copyright 2015 Google Inc
+ *
+ * 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.
+ */
+
+#include <console/console.h>
+#include <cbmem.h>
+#include <fsp_util.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <uefi_types.h>
+
+#define FSP_DBG_LVL BIOS_NEVER
+
+static const EFI_GUID ffs2_guid = EFI_FIRMWARE_FILE_SYSTEM2_GUID;
+static const EFI_GUID fih_guid = FSP_INFO_HEADER_GUID;
+
+struct fsp_patch_table {
+ uint32_t signature;
+ uint16_t header_length;
+ uint8_t header_revision;
+ uint8_t reserved;
+ uint32_t patch_entry_num;
+ uint32_t patch_entries[0];
+} __attribute__((packed));
+
+#define FSPP_SIG 0x50505346
+
+static void *relative_offset(void *base, ssize_t offset)
+{
+ uintptr_t loc;
+
+ loc = (uintptr_t)base;
+ loc += offset;
+
+ return (void *)loc;
+}
+
+static uint32_t *fspp_reloc(void *fsp, size_t fsp_size, uint32_t e)
+{
+ size_t offset;
+
+ /* Offsets live in bits 23:0. */
+ offset = e & 0xffffff;
+
+ /* If bit 31 is set then the offset is considered a negative value
+ * relative to the end of the image using 16MiB as the offset's
+ * reference. */
+ if (e & (1 << 31))
+ offset = fsp_size - (16 * MiB - offset);
+
+ /* Determine if offset falls within fsp_size for a 32 bit relocation. */
+ if (offset > fsp_size - sizeof(uint32_t))
+ return NULL;
+
+ return relative_offset(fsp, offset);
+}
+
+static int reloc_type(uint16_t reloc_entry)
+{
+ /* Reloc type in upper 4 bits */
+ return reloc_entry >> 12;
+}
+
+static size_t reloc_offset(uint16_t reloc_entry)
+{
+ /* Offsets are in low 12 bits. */
+ return reloc_entry & ((1 << 12) - 1);
+}
+
+static int te_relocate_in_place(void *te, size_t size)
+{
+ EFI_TE_IMAGE_HEADER *teih;
+ EFI_IMAGE_DATA_DIRECTORY *relocd;
+ EFI_IMAGE_BASE_RELOCATION *relocb;
+ size_t fixup_offset;
+ size_t num_relocs;
+ uint16_t *reloc;
+ size_t relocd_offset;
+ uint8_t *te_base;
+ uint32_t adj;
+
+ teih = te;
+
+ if (teih->Signature != EFI_TE_IMAGE_HEADER_SIGNATURE) {
+ printk(BIOS_ERR, "TE Signature mismatch: %x vs %x\n",
+ teih->Signature, EFI_TE_IMAGE_HEADER_SIGNATURE);
+ return -1;
+ }
+
+ /*
+ * A TE image is created by converting a PE file. Because of this
+ * the offsets within the headers are off. In order to calculate
+ * the correct releative offets one needs to subtract fixup_offset
+ * from the encoded offets. Similarly, the linked address of the
+ * program is found by adding the fixup_offset to the ImageBase.
+ */
+ fixup_offset = teih->StrippedSize - sizeof(EFI_TE_IMAGE_HEADER);
+ /* Keep track of a base that is correctly adjusted so that offsets
+ * can be used directly. */
+ te_base = te;
+ te_base -= fixup_offset;
+
+ adj = (uintptr_t)te - (teih->ImageBase + fixup_offset);
+
+ printk(FSP_DBG_LVL, "TE Image %p -> %p adjust value: %x\n",
+ (void *)(uintptr_t)(teih->ImageBase + fixup_offset),
+ te, adj);
+
+ /* Adjust ImageBase for consistency. */
+ teih->ImageBase = (uint32_t)(teih->ImageBase + adj);
+
+ relocd = &teih->DataDirectory[EFI_TE_IMAGE_DIRECTORY_ENTRY_BASERELOC];
+
+ relocd_offset = 0;
+ /* Though the field name is VirtualAddress it's actually relative to
+ * the beginning of the image which is linked at ImageBase. */
+ relocb = relative_offset(te, relocd->VirtualAddress - fixup_offset);
+ while (relocd_offset < relocd->Size) {
+ size_t rva_offset = relocb->VirtualAddress;
+
+ printk(FSP_DBG_LVL, "Relocs for RVA offset %zx\n", rva_offset);
+ num_relocs = relocb->SizeOfBlock - sizeof(*relocb);
+ num_relocs /= sizeof(uint16_t);
+ reloc = relative_offset(relocb, sizeof(*relocb));
+
+ printk(FSP_DBG_LVL, "Num relocs in block: %zx\n", num_relocs);
+
+ while (num_relocs > 0) {
+ int type = reloc_type(*reloc);
+ size_t offset = reloc_offset(*reloc);
+
+ printk(FSP_DBG_LVL, "reloc type %x offset %zx\n",
+ type, offset);
+
+ if (type == EFI_IMAGE_REL_BASED_HIGHLOW) {
+ uint32_t *reloc_addr;
+
+ offset += rva_offset;
+ reloc_addr = (void *)&te_base[offset];
+
+ printk(FSP_DBG_LVL, "Adjusting %p %x -> %x\n",
+ reloc_addr, *reloc_addr,
+ *reloc_addr + adj);
+ *reloc_addr += adj;
+ } else if (type != EFI_IMAGE_REL_BASED_ABSOLUTE) {
+ printk(BIOS_ERR, "Unknown reloc type: %x\n",
+ type);
+ return -1;
+ }
+ num_relocs--;
+ reloc++;
+ }
+
+ /* Track consumption of relocation directory contents. */
+ relocd_offset += relocb->SizeOfBlock;
+ /* Get next relocation block to process. */
+ relocb = relative_offset(relocb, relocb->SizeOfBlock);
+ }
+
+ return 0;
+}
+
+static size_t csh_size(const EFI_COMMON_SECTION_HEADER *csh)
+{
+ size_t size;
+
+ /* Unpack the array into a type that can be used. */
+ size = 0;
+ size |= csh->Size[0] << 0;
+ size |= csh->Size[1] << 8;
+ size |= csh->Size[2] << 16;
+
+ return size;
+}
+
+static size_t section_data_offset(const EFI_COMMON_SECTION_HEADER *csh)
+{
+ if (csh_size(csh) == 0x00ffffff)
+ return sizeof(EFI_COMMON_SECTION_HEADER2);
+ else
+ return sizeof(EFI_COMMON_SECTION_HEADER);
+}
+
+static size_t section_data_size(const EFI_COMMON_SECTION_HEADER *csh)
+{
+ size_t section_size;
+
+ if (csh_size(csh) == 0x00ffffff)
+ section_size = SECTION2_SIZE(csh);
+ else
+ section_size = csh_size(csh);
+
+ return section_size - section_data_offset(csh);
+}
+
+static size_t file_section_offset(const EFI_FFS_FILE_HEADER *ffsfh)
+{
+ if (IS_FFS_FILE2(ffsfh))
+ return sizeof(EFI_FFS_FILE_HEADER2);
+ else
+ return sizeof(EFI_FFS_FILE_HEADER);
+}
+
+static size_t ffs_file_size(const EFI_FFS_FILE_HEADER *ffsfh)
+{
+ size_t size;
+
+ if (IS_FFS_FILE2(ffsfh))
+ size = FFS_FILE2_SIZE(ffsfh);
+ else {
+ size = ffsfh->Size[0] << 0;
+ size |= ffsfh->Size[1] << 8;
+ size |= ffsfh->Size[2] << 16;
+ }
+ return size;
+}
+
+static int relocate_patch_table(void *fsp, size_t size, size_t offset,
+ ssize_t adjustment)
+{
+ struct fsp_patch_table *table;
+ uint32_t num;
+
+ table = relative_offset(fsp, offset);
+
+ if ((offset + sizeof(*table) > size) ||
+ (table->header_length + offset) > size) {
+ printk(BIOS_ERR, "FSPP not entirely contained in region.\n");
+ return -1;
+ }
+
+ printk(FSP_DBG_LVL, "FSPP relocs: %x\n", table->patch_entry_num);
+
+ for (num = 0; num < table->patch_entry_num; num++) {
+ uint32_t *reloc;
+
+ reloc = fspp_reloc(fsp, size, table->patch_entries[num]);
+
+ if (reloc == NULL) {
+ printk(BIOS_ERR, "Ignoring FSPP entry: %x\n",
+ table->patch_entries[num]);
+ continue;
+ }
+
+ printk(FSP_DBG_LVL, "Adjusting %p %x -> %x\n",
+ reloc, *reloc, (unsigned int)(*reloc + adjustment));
+
+ *reloc += adjustment;
+ }
+
+ return 0;
+}
+
+static void *relocate_remaining_items(void *fsp, size_t size, size_t fih_offset)
+{
+ EFI_FFS_FILE_HEADER *ffsfh;
+ EFI_COMMON_SECTION_HEADER *csh;
+ FSP_INFO_HEADER *fih;
+ ssize_t adjustment;
+ size_t offset;
+
+ printk(FSP_DBG_LVL, "FSP_INFO_HEADER offset is %zx\n", fih_offset);
+
+ if (fih_offset == 0) {
+ printk(BIOS_ERR, "FSP_INFO_HEADER offset is 0.\n");
+ return NULL;
+ }
+
+ /* FSP_INFO_HEADER at first file in FV within first RAW section. */
+ ffsfh = relative_offset(fsp, fih_offset);
+ fih_offset += file_section_offset(ffsfh);
+ csh = relative_offset(fsp, fih_offset);
+ fih_offset += section_data_offset(csh);
+ fih = relative_offset(fsp, fih_offset);
+
+ if (memcmp(&ffsfh->Name, &fih_guid, sizeof(fih_guid))) {
+ printk(BIOS_ERR, "Bad FIH GUID.\n");
+ return NULL;
+ }
+
+ if (csh->Type != EFI_SECTION_RAW) {
+ printk(BIOS_ERR, "FIH file should have raw section: %x\n",
+ csh->Type);
+ return NULL;
+ }
+
+ if (fih->Signature != FSP_SIG) {
+ printk(BIOS_ERR, "Unexpected FIH signature: %08x\n",
+ fih->Signature);
+ return NULL;
+ }
+
+ adjustment = (intptr_t)fsp - fih->ImageBase;
+
+ /* Update ImageBase to reflect FSP's new home. */
+ fih->ImageBase += adjustment;
+
+ /* Need to find patch table and adjust each entry. The tables
+ * following FSP_INFO_HEADER have a 32-bit signature and header
+ * length. The patch table is denoted as having a 'FSPP' signature;
+ * the table format doesn't follow the other tables. */
+ offset = fih_offset + fih->HeaderLength;
+ while (offset + 2 * sizeof(uint32_t) <= size) {
+ uint32_t *table_headers;
+
+ table_headers = relative_offset(fsp, offset);
+
+ printk(FSP_DBG_LVL, "Checking offset %zx for 'FSPP'\n",
+ offset);
+
+ if (table_headers[0] != FSPP_SIG) {
+ offset += table_headers[1];
+ continue;
+ }
+
+ if (relocate_patch_table(fsp, size, offset, adjustment)) {
+ printk(BIOS_ERR, "FSPP relocation failed.\n");
+ return NULL;
+ }
+
+ return fih;
+ }
+
+ printk(BIOS_ERR, "Could not find the FSP patch table.\n");
+ return NULL;
+}
+
+static ssize_t relocate_fvh(void *fsp, size_t fsp_size, size_t fvh_offset,
+ size_t *fih_offset)
+{
+ EFI_FIRMWARE_VOLUME_HEADER *fvh;
+ EFI_FFS_FILE_HEADER *ffsfh;
+ EFI_COMMON_SECTION_HEADER *csh;
+ size_t offset;
+ size_t file_offset;
+ size_t size;
+
+ offset = fvh_offset;
+ fvh = relative_offset(fsp, offset);
+
+ if (fvh->Signature != EFI_FVH_SIGNATURE)
+ return -1;
+
+ printk(FSP_DBG_LVL, "FVH length: %zx Offset: %zx Mapping length: %zx\n",
+ (size_t)fvh->FvLength, offset, fsp_size);
+
+ if (fvh->FvLength + offset > fsp_size)
+ return -1;
+
+ /* Parse only this FV. However, the algorithm uses offsets into the
+ * entire FSP region so make size include the starting offset. */
+ size = fvh->FvLength + offset;
+
+ if (memcmp(&fvh->FileSystemGuid, &ffs2_guid, sizeof(ffs2_guid))) {
+ printk(BIOS_ERR, "FVH not an FFS2 type.\n");
+ return -1;
+ }
+
+ if (fvh->ExtHeaderOffset != 0) {
+ EFI_FIRMWARE_VOLUME_EXT_HEADER *fveh;
+
+ offset += fvh->ExtHeaderOffset;
+ fveh = relative_offset(fsp, offset);
+ printk(FSP_DBG_LVL, "Extended Header Offset: %zx Size: %zx\n",
+ (size_t)fvh->ExtHeaderOffset,
+ (size_t)fveh->ExtHeaderSize);
+ offset += fveh->ExtHeaderSize;
+ /* FFS files are 8 byte aligned after extended header. */
+ offset = ALIGN_UP(offset, 8);
+ } else {
+ offset += fvh->HeaderLength;
+ }
+
+ file_offset = offset;
+ while (file_offset + sizeof(*ffsfh) < size) {
+ offset = file_offset;
+ printk(FSP_DBG_LVL, "file offset: %zx\n", file_offset);
+
+ /* First file and section should be FSP info header. */
+ if (fih_offset != NULL && *fih_offset == 0)
+ *fih_offset = file_offset;
+
+ ffsfh = relative_offset(fsp, file_offset);
+
+ printk(FSP_DBG_LVL, "file type = %x\n", ffsfh->Type);
+ printk(FSP_DBG_LVL, "file attribs = %x\n", ffsfh->Attributes);
+
+ /* Exit FV relocation when empty space found */
+ if (ffsfh->Type == EFI_FV_FILETYPE_FFS_MAX)
+ break;
+
+ /* Next file on 8 byte alignment. */
+ file_offset += ffs_file_size(ffsfh);
+ file_offset = ALIGN_UP(file_offset, 8);
+
+ /* Padding files have no section information. */
+ if (ffsfh->Type == EFI_FV_FILETYPE_FFS_PAD)
+ continue;
+
+ offset += file_section_offset(ffsfh);
+
+ while (offset + sizeof(*csh) < file_offset) {
+ size_t data_size;
+ size_t data_offset;
+
+ csh = relative_offset(fsp, offset);
+
+ printk(FSP_DBG_LVL, "section offset: %zx\n", offset);
+ printk(FSP_DBG_LVL, "section type: %x\n", csh->Type);
+
+ data_size = section_data_size(csh);
+ data_offset = section_data_offset(csh);
+
+ if (data_size + data_offset + offset > file_offset) {
+ printk(BIOS_ERR, "Section exceeds FV size.\n");
+ return -1;
+ }
+
+ if (csh->Type == EFI_SECTION_TE) {
+ void *te;
+ size_t te_offset = offset + data_offset;
+
+ printk(FSP_DBG_LVL, "TE image at offset %zx\n",
+ te_offset);
+ te = relative_offset(fsp, te_offset);
+ te_relocate_in_place(te, data_size);
+ }
+
+ offset += data_size + data_offset;
+ /* Sections are aligned to 4 bytes. */
+ offset = ALIGN_UP(offset, 4);
+ }
+ }
+
+ /* Return amount of buffer parsed: FV size. */
+ return fvh->FvLength;
+}
+
+static FSP_INFO_HEADER *fsp_relocate_in_place(void *fsp, size_t size)
+{
+ size_t offset;
+ size_t fih_offset;
+
+ offset = 0;
+ fih_offset = 0;
+ while (offset < size) {
+ ssize_t nparsed;
+
+ /* Relocate each FV within the FSP region. The FSP_INFO_HEADER
+ * should only be located in the first FV. */
+ if (offset == 0)
+ nparsed = relocate_fvh(fsp, size, offset, &fih_offset);
+ else
+ nparsed = relocate_fvh(fsp, size, offset, NULL);
+
+ /* FV should be larger than 0 or failed to parse. */
+ if (nparsed <= 0) {
+ printk(BIOS_ERR, "FV @ offset %zx relocation failed\n",
+ offset);
+ return NULL;
+ }
+
+ offset += nparsed;
+ }
+
+ return relocate_remaining_items(fsp, size, fih_offset);
+}
+
+FSP_INFO_HEADER *fsp_relocate(void *fsp_src, size_t size)
+{
+ void *new_loc;
+
+ new_loc = cbmem_add(CBMEM_ID_REFCODE, size);
+ if (new_loc == NULL) {
+ printk(BIOS_ERR, "Unable to load FSP into memory.\n");
+ return NULL;
+ }
+ memcpy(new_loc, fsp_src, size);
+ return fsp_relocate_in_place(new_loc, size);
+}