blob: 08e1eb82f02b36c6c5492983547a6b5c20f58b8d [file] [log] [blame]
Lee Leahyb5ad8272015-04-20 15:29:16 -07001/*
2 * This file is part of the coreboot project.
3 *
4 * Copyright 2015 Google Inc
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc.
18 */
19
20#include <console/console.h>
21#include <cbmem.h>
22#include <fsp_util.h>
23#include <stdlib.h>
24#include <stdint.h>
25#include <string.h>
26#include <uefi_types.h>
27
28#define FSP_DBG_LVL BIOS_NEVER
29
30static const EFI_GUID ffs2_guid = EFI_FIRMWARE_FILE_SYSTEM2_GUID;
31static const EFI_GUID fih_guid = FSP_INFO_HEADER_GUID;
32
33struct fsp_patch_table {
34 uint32_t signature;
35 uint16_t header_length;
36 uint8_t header_revision;
37 uint8_t reserved;
38 uint32_t patch_entry_num;
39 uint32_t patch_entries[0];
40} __attribute__((packed));
41
42#define FSPP_SIG 0x50505346
43
44static void *relative_offset(void *base, ssize_t offset)
45{
46 uintptr_t loc;
47
48 loc = (uintptr_t)base;
49 loc += offset;
50
51 return (void *)loc;
52}
53
54static uint32_t *fspp_reloc(void *fsp, size_t fsp_size, uint32_t e)
55{
56 size_t offset;
57
58 /* Offsets live in bits 23:0. */
59 offset = e & 0xffffff;
60
61 /* If bit 31 is set then the offset is considered a negative value
62 * relative to the end of the image using 16MiB as the offset's
63 * reference. */
64 if (e & (1 << 31))
65 offset = fsp_size - (16 * MiB - offset);
66
67 /* Determine if offset falls within fsp_size for a 32 bit relocation. */
68 if (offset > fsp_size - sizeof(uint32_t))
69 return NULL;
70
71 return relative_offset(fsp, offset);
72}
73
74static int reloc_type(uint16_t reloc_entry)
75{
76 /* Reloc type in upper 4 bits */
77 return reloc_entry >> 12;
78}
79
80static size_t reloc_offset(uint16_t reloc_entry)
81{
82 /* Offsets are in low 12 bits. */
83 return reloc_entry & ((1 << 12) - 1);
84}
85
86static int te_relocate_in_place(void *te, size_t size)
87{
88 EFI_TE_IMAGE_HEADER *teih;
89 EFI_IMAGE_DATA_DIRECTORY *relocd;
90 EFI_IMAGE_BASE_RELOCATION *relocb;
91 size_t fixup_offset;
92 size_t num_relocs;
93 uint16_t *reloc;
94 size_t relocd_offset;
95 uint8_t *te_base;
96 uint32_t adj;
97
98 teih = te;
99
100 if (teih->Signature != EFI_TE_IMAGE_HEADER_SIGNATURE) {
101 printk(BIOS_ERR, "TE Signature mismatch: %x vs %x\n",
102 teih->Signature, EFI_TE_IMAGE_HEADER_SIGNATURE);
103 return -1;
104 }
105
106 /*
107 * A TE image is created by converting a PE file. Because of this
108 * the offsets within the headers are off. In order to calculate
109 * the correct releative offets one needs to subtract fixup_offset
110 * from the encoded offets. Similarly, the linked address of the
111 * program is found by adding the fixup_offset to the ImageBase.
112 */
113 fixup_offset = teih->StrippedSize - sizeof(EFI_TE_IMAGE_HEADER);
114 /* Keep track of a base that is correctly adjusted so that offsets
115 * can be used directly. */
116 te_base = te;
117 te_base -= fixup_offset;
118
119 adj = (uintptr_t)te - (teih->ImageBase + fixup_offset);
120
121 printk(FSP_DBG_LVL, "TE Image %p -> %p adjust value: %x\n",
122 (void *)(uintptr_t)(teih->ImageBase + fixup_offset),
123 te, adj);
124
125 /* Adjust ImageBase for consistency. */
126 teih->ImageBase = (uint32_t)(teih->ImageBase + adj);
127
128 relocd = &teih->DataDirectory[EFI_TE_IMAGE_DIRECTORY_ENTRY_BASERELOC];
129
130 relocd_offset = 0;
131 /* Though the field name is VirtualAddress it's actually relative to
132 * the beginning of the image which is linked at ImageBase. */
133 relocb = relative_offset(te, relocd->VirtualAddress - fixup_offset);
134 while (relocd_offset < relocd->Size) {
135 size_t rva_offset = relocb->VirtualAddress;
136
137 printk(FSP_DBG_LVL, "Relocs for RVA offset %zx\n", rva_offset);
138 num_relocs = relocb->SizeOfBlock - sizeof(*relocb);
139 num_relocs /= sizeof(uint16_t);
140 reloc = relative_offset(relocb, sizeof(*relocb));
141
142 printk(FSP_DBG_LVL, "Num relocs in block: %zx\n", num_relocs);
143
144 while (num_relocs > 0) {
145 int type = reloc_type(*reloc);
146 size_t offset = reloc_offset(*reloc);
147
148 printk(FSP_DBG_LVL, "reloc type %x offset %zx\n",
149 type, offset);
150
151 if (type == EFI_IMAGE_REL_BASED_HIGHLOW) {
152 uint32_t *reloc_addr;
153
154 offset += rva_offset;
155 reloc_addr = (void *)&te_base[offset];
156
157 printk(FSP_DBG_LVL, "Adjusting %p %x -> %x\n",
158 reloc_addr, *reloc_addr,
159 *reloc_addr + adj);
160 *reloc_addr += adj;
161 } else if (type != EFI_IMAGE_REL_BASED_ABSOLUTE) {
162 printk(BIOS_ERR, "Unknown reloc type: %x\n",
163 type);
164 return -1;
165 }
166 num_relocs--;
167 reloc++;
168 }
169
170 /* Track consumption of relocation directory contents. */
171 relocd_offset += relocb->SizeOfBlock;
172 /* Get next relocation block to process. */
173 relocb = relative_offset(relocb, relocb->SizeOfBlock);
174 }
175
176 return 0;
177}
178
179static size_t csh_size(const EFI_COMMON_SECTION_HEADER *csh)
180{
181 size_t size;
182
183 /* Unpack the array into a type that can be used. */
184 size = 0;
185 size |= csh->Size[0] << 0;
186 size |= csh->Size[1] << 8;
187 size |= csh->Size[2] << 16;
188
189 return size;
190}
191
192static size_t section_data_offset(const EFI_COMMON_SECTION_HEADER *csh)
193{
194 if (csh_size(csh) == 0x00ffffff)
195 return sizeof(EFI_COMMON_SECTION_HEADER2);
196 else
197 return sizeof(EFI_COMMON_SECTION_HEADER);
198}
199
200static size_t section_data_size(const EFI_COMMON_SECTION_HEADER *csh)
201{
202 size_t section_size;
203
204 if (csh_size(csh) == 0x00ffffff)
205 section_size = SECTION2_SIZE(csh);
206 else
207 section_size = csh_size(csh);
208
209 return section_size - section_data_offset(csh);
210}
211
212static size_t file_section_offset(const EFI_FFS_FILE_HEADER *ffsfh)
213{
214 if (IS_FFS_FILE2(ffsfh))
215 return sizeof(EFI_FFS_FILE_HEADER2);
216 else
217 return sizeof(EFI_FFS_FILE_HEADER);
218}
219
220static size_t ffs_file_size(const EFI_FFS_FILE_HEADER *ffsfh)
221{
222 size_t size;
223
224 if (IS_FFS_FILE2(ffsfh))
225 size = FFS_FILE2_SIZE(ffsfh);
226 else {
227 size = ffsfh->Size[0] << 0;
228 size |= ffsfh->Size[1] << 8;
229 size |= ffsfh->Size[2] << 16;
230 }
231 return size;
232}
233
234static int relocate_patch_table(void *fsp, size_t size, size_t offset,
235 ssize_t adjustment)
236{
237 struct fsp_patch_table *table;
238 uint32_t num;
239
240 table = relative_offset(fsp, offset);
241
242 if ((offset + sizeof(*table) > size) ||
243 (table->header_length + offset) > size) {
244 printk(BIOS_ERR, "FSPP not entirely contained in region.\n");
245 return -1;
246 }
247
248 printk(FSP_DBG_LVL, "FSPP relocs: %x\n", table->patch_entry_num);
249
250 for (num = 0; num < table->patch_entry_num; num++) {
251 uint32_t *reloc;
252
253 reloc = fspp_reloc(fsp, size, table->patch_entries[num]);
254
255 if (reloc == NULL) {
256 printk(BIOS_ERR, "Ignoring FSPP entry: %x\n",
257 table->patch_entries[num]);
258 continue;
259 }
260
261 printk(FSP_DBG_LVL, "Adjusting %p %x -> %x\n",
262 reloc, *reloc, (unsigned int)(*reloc + adjustment));
263
264 *reloc += adjustment;
265 }
266
267 return 0;
268}
269
270static void *relocate_remaining_items(void *fsp, size_t size, size_t fih_offset)
271{
272 EFI_FFS_FILE_HEADER *ffsfh;
273 EFI_COMMON_SECTION_HEADER *csh;
274 FSP_INFO_HEADER *fih;
275 ssize_t adjustment;
276 size_t offset;
277
278 printk(FSP_DBG_LVL, "FSP_INFO_HEADER offset is %zx\n", fih_offset);
279
280 if (fih_offset == 0) {
281 printk(BIOS_ERR, "FSP_INFO_HEADER offset is 0.\n");
282 return NULL;
283 }
284
285 /* FSP_INFO_HEADER at first file in FV within first RAW section. */
286 ffsfh = relative_offset(fsp, fih_offset);
287 fih_offset += file_section_offset(ffsfh);
288 csh = relative_offset(fsp, fih_offset);
289 fih_offset += section_data_offset(csh);
290 fih = relative_offset(fsp, fih_offset);
291
292 if (memcmp(&ffsfh->Name, &fih_guid, sizeof(fih_guid))) {
293 printk(BIOS_ERR, "Bad FIH GUID.\n");
294 return NULL;
295 }
296
297 if (csh->Type != EFI_SECTION_RAW) {
298 printk(BIOS_ERR, "FIH file should have raw section: %x\n",
299 csh->Type);
300 return NULL;
301 }
302
303 if (fih->Signature != FSP_SIG) {
304 printk(BIOS_ERR, "Unexpected FIH signature: %08x\n",
305 fih->Signature);
306 return NULL;
307 }
308
309 adjustment = (intptr_t)fsp - fih->ImageBase;
310
311 /* Update ImageBase to reflect FSP's new home. */
312 fih->ImageBase += adjustment;
313
314 /* Need to find patch table and adjust each entry. The tables
315 * following FSP_INFO_HEADER have a 32-bit signature and header
316 * length. The patch table is denoted as having a 'FSPP' signature;
317 * the table format doesn't follow the other tables. */
318 offset = fih_offset + fih->HeaderLength;
319 while (offset + 2 * sizeof(uint32_t) <= size) {
320 uint32_t *table_headers;
321
322 table_headers = relative_offset(fsp, offset);
323
324 printk(FSP_DBG_LVL, "Checking offset %zx for 'FSPP'\n",
325 offset);
326
327 if (table_headers[0] != FSPP_SIG) {
328 offset += table_headers[1];
329 continue;
330 }
331
332 if (relocate_patch_table(fsp, size, offset, adjustment)) {
333 printk(BIOS_ERR, "FSPP relocation failed.\n");
334 return NULL;
335 }
336
337 return fih;
338 }
339
340 printk(BIOS_ERR, "Could not find the FSP patch table.\n");
341 return NULL;
342}
343
344static ssize_t relocate_fvh(void *fsp, size_t fsp_size, size_t fvh_offset,
345 size_t *fih_offset)
346{
347 EFI_FIRMWARE_VOLUME_HEADER *fvh;
348 EFI_FFS_FILE_HEADER *ffsfh;
349 EFI_COMMON_SECTION_HEADER *csh;
350 size_t offset;
351 size_t file_offset;
352 size_t size;
353
354 offset = fvh_offset;
355 fvh = relative_offset(fsp, offset);
356
357 if (fvh->Signature != EFI_FVH_SIGNATURE)
358 return -1;
359
360 printk(FSP_DBG_LVL, "FVH length: %zx Offset: %zx Mapping length: %zx\n",
361 (size_t)fvh->FvLength, offset, fsp_size);
362
363 if (fvh->FvLength + offset > fsp_size)
364 return -1;
365
366 /* Parse only this FV. However, the algorithm uses offsets into the
367 * entire FSP region so make size include the starting offset. */
368 size = fvh->FvLength + offset;
369
370 if (memcmp(&fvh->FileSystemGuid, &ffs2_guid, sizeof(ffs2_guid))) {
371 printk(BIOS_ERR, "FVH not an FFS2 type.\n");
372 return -1;
373 }
374
375 if (fvh->ExtHeaderOffset != 0) {
376 EFI_FIRMWARE_VOLUME_EXT_HEADER *fveh;
377
378 offset += fvh->ExtHeaderOffset;
379 fveh = relative_offset(fsp, offset);
380 printk(FSP_DBG_LVL, "Extended Header Offset: %zx Size: %zx\n",
381 (size_t)fvh->ExtHeaderOffset,
382 (size_t)fveh->ExtHeaderSize);
383 offset += fveh->ExtHeaderSize;
384 /* FFS files are 8 byte aligned after extended header. */
385 offset = ALIGN_UP(offset, 8);
386 } else {
387 offset += fvh->HeaderLength;
388 }
389
390 file_offset = offset;
391 while (file_offset + sizeof(*ffsfh) < size) {
392 offset = file_offset;
393 printk(FSP_DBG_LVL, "file offset: %zx\n", file_offset);
394
395 /* First file and section should be FSP info header. */
396 if (fih_offset != NULL && *fih_offset == 0)
397 *fih_offset = file_offset;
398
399 ffsfh = relative_offset(fsp, file_offset);
400
401 printk(FSP_DBG_LVL, "file type = %x\n", ffsfh->Type);
402 printk(FSP_DBG_LVL, "file attribs = %x\n", ffsfh->Attributes);
403
404 /* Exit FV relocation when empty space found */
405 if (ffsfh->Type == EFI_FV_FILETYPE_FFS_MAX)
406 break;
407
408 /* Next file on 8 byte alignment. */
409 file_offset += ffs_file_size(ffsfh);
410 file_offset = ALIGN_UP(file_offset, 8);
411
412 /* Padding files have no section information. */
413 if (ffsfh->Type == EFI_FV_FILETYPE_FFS_PAD)
414 continue;
415
416 offset += file_section_offset(ffsfh);
417
418 while (offset + sizeof(*csh) < file_offset) {
419 size_t data_size;
420 size_t data_offset;
421
422 csh = relative_offset(fsp, offset);
423
424 printk(FSP_DBG_LVL, "section offset: %zx\n", offset);
425 printk(FSP_DBG_LVL, "section type: %x\n", csh->Type);
426
427 data_size = section_data_size(csh);
428 data_offset = section_data_offset(csh);
429
430 if (data_size + data_offset + offset > file_offset) {
431 printk(BIOS_ERR, "Section exceeds FV size.\n");
432 return -1;
433 }
434
435 if (csh->Type == EFI_SECTION_TE) {
436 void *te;
437 size_t te_offset = offset + data_offset;
438
439 printk(FSP_DBG_LVL, "TE image at offset %zx\n",
440 te_offset);
441 te = relative_offset(fsp, te_offset);
442 te_relocate_in_place(te, data_size);
443 }
444
445 offset += data_size + data_offset;
446 /* Sections are aligned to 4 bytes. */
447 offset = ALIGN_UP(offset, 4);
448 }
449 }
450
451 /* Return amount of buffer parsed: FV size. */
452 return fvh->FvLength;
453}
454
455static FSP_INFO_HEADER *fsp_relocate_in_place(void *fsp, size_t size)
456{
457 size_t offset;
458 size_t fih_offset;
459
460 offset = 0;
461 fih_offset = 0;
462 while (offset < size) {
463 ssize_t nparsed;
464
465 /* Relocate each FV within the FSP region. The FSP_INFO_HEADER
466 * should only be located in the first FV. */
467 if (offset == 0)
468 nparsed = relocate_fvh(fsp, size, offset, &fih_offset);
469 else
470 nparsed = relocate_fvh(fsp, size, offset, NULL);
471
472 /* FV should be larger than 0 or failed to parse. */
473 if (nparsed <= 0) {
474 printk(BIOS_ERR, "FV @ offset %zx relocation failed\n",
475 offset);
476 return NULL;
477 }
478
479 offset += nparsed;
480 }
481
482 return relocate_remaining_items(fsp, size, fih_offset);
483}
484
485FSP_INFO_HEADER *fsp_relocate(void *fsp_src, size_t size)
486{
487 void *new_loc;
488
489 new_loc = cbmem_add(CBMEM_ID_REFCODE, size);
490 if (new_loc == NULL) {
491 printk(BIOS_ERR, "Unable to load FSP into memory.\n");
492 return NULL;
493 }
494 memcpy(new_loc, fsp_src, size);
495 return fsp_relocate_in_place(new_loc, size);
496}