/ util / amdfwtool / amdfwread.c
amdfwread.c
  1  /* SPDX-License-Identifier: GPL-2.0-only */
  2  #include <getopt.h>
  3  #include <stddef.h>
  4  #include <stdio.h>
  5  #include <stdint.h>
  6  #include <stdlib.h>
  7  #include <string.h>
  8  #include <unistd.h>
  9  #include "amdfwtool.h"
 10  
 11  /* An address can be relative to the image/file start but it can also be the address when
 12   * the image is mapped at 0xff000000. Used to ensure that we only attempt to read within
 13   * the limits of the file. */
 14  #define SPI_ROM_BASE 0xff000000
 15  #define FILE_REL_MASK 0xffffff
 16  
 17  #define ERR(...) fprintf(stderr, __VA_ARGS__)
 18  
 19  /* Possible locations for the header */
 20  const uint32_t fw_header_offsets[] = {
 21  	0xfa0000,
 22  	0xe20000,
 23  	0xc20000,
 24  	0x820000,
 25  	0x020000,
 26  };
 27  
 28  /* Converts addresses to be relative to the start of the file */
 29  static uint64_t relative_offset(uint32_t header_offset, uint64_t addr, uint64_t mode)
 30  {
 31  	switch (mode) {
 32  	/* Since this utility operates on the BIOS file, physical address is converted
 33  	   relative to the start of the BIOS file. */
 34  	case AMD_ADDR_PHYSICAL:
 35  		if (addr < SPI_ROM_BASE || addr > (SPI_ROM_BASE + FILE_REL_MASK)) {
 36  			ERR("Invalid address(%lx) or mode(%lx)\n", addr, mode);
 37  			/* TODO: fix amdfwtool to program the right address/mode. In guybrush,
 38  			 * lots of addresses are marked as physical, but they are relative to
 39  			 * BIOS. Until that is fixed, just leave an error message. */
 40  			// exit(1);
 41  		}
 42  		return addr & FILE_REL_MASK;
 43  
 44  	case AMD_ADDR_REL_BIOS:
 45  		if (addr > FILE_REL_MASK) {
 46  			ERR("Invalid address(%lx) or mode(%lx)\n", addr, mode);
 47  			exit(1);
 48  		}
 49  		return addr & FILE_REL_MASK;
 50  
 51  	case AMD_ADDR_REL_TAB:
 52  		return addr + header_offset;
 53  
 54  	default:
 55  		ERR("Unsupported mode %lu\n", mode);
 56  		exit(1);
 57  	}
 58  }
 59  
 60  static int read_header(FILE *fw, uint32_t offset, void *header, size_t header_size)
 61  {
 62  	if (fseek(fw, offset, SEEK_SET) != 0) {
 63  		ERR("Failed to seek to file offset 0x%x\n", offset);
 64  		return 1;
 65  	}
 66  
 67  	if (fread(header, header_size, 1, fw) != 1) {
 68  		ERR("Failed to read header at 0x%x\n", offset);
 69  		return 1;
 70  	}
 71  
 72  	return 0;
 73  }
 74  
 75  static int read_fw_header(FILE *fw, uint32_t offset, embedded_firmware *fw_header)
 76  {
 77  	if (read_header(fw, offset, fw_header, sizeof(embedded_firmware))) {
 78  		ERR("Failed to read fw header at 0x%x\n", offset);
 79  		return 1;
 80  	}
 81  
 82  	return fw_header->signature != EMBEDDED_FW_SIGNATURE;
 83  }
 84  
 85  static int read_psp_directory(FILE *fw, uint32_t offset, uint32_t expected_cookie,
 86  			psp_directory_header *header, psp_directory_entry **entries,
 87  			size_t *num_entries)
 88  {
 89  	offset &= FILE_REL_MASK;
 90  
 91  	if (read_header(fw, offset, header, sizeof(psp_directory_header))) {
 92  		ERR("Failed to read PSP header\n");
 93  		return 1;
 94  	}
 95  
 96  	/* Ensure that we have a PSP directory */
 97  	if (header->cookie != expected_cookie) {
 98  		ERR("Invalid PSP header cookie value found: 0x%x, expected: 0x%x\n",
 99  		    header->cookie, expected_cookie);
100  		return 1;
101  	}
102  
103  	/* Read the entries */
104  	*num_entries = header->num_entries;
105  	*entries = malloc(sizeof(psp_directory_entry) * header->num_entries);
106  	if (fread(*entries, sizeof(psp_directory_entry), header->num_entries, fw)
107  		!= header->num_entries) {
108  		ERR("Failed to read %d PSP entries\n", header->num_entries);
109  		return 1;
110  	}
111  
112  	return 0;
113  }
114  
115  static int read_ish_directory(FILE *fw, uint32_t offset, ish_directory_table *table)
116  {
117  	return read_header(fw, offset & FILE_REL_MASK, table, sizeof(*table));
118  }
119  
120  static int read_bios_directory(FILE *fw, uint32_t offset, uint32_t expected_cookie,
121  			bios_directory_hdr *header, bios_directory_entry **entries,
122  			size_t *num_entries)
123  {
124  	offset &= FILE_REL_MASK;
125  
126  	if (read_header(fw, offset, header, sizeof(bios_directory_hdr))) {
127  		ERR("Failed to read BIOS header\n");
128  		return 1;
129  	}
130  
131  	/* Ensure that we have a BIOS directory */
132  	if (header->cookie != expected_cookie) {
133  		ERR("Invalid BIOS header cookie value found: 0x%x, expected: 0x%x\n",
134  			header->cookie, expected_cookie);
135  		return 1;
136  	}
137  
138  	/* Read the entries */
139  	*num_entries = header->num_entries;
140  	*entries = malloc(sizeof(bios_directory_entry) * header->num_entries);
141  	if (fread(*entries, sizeof(bios_directory_entry), header->num_entries, fw)
142  		!= header->num_entries) {
143  		ERR("Failed to read %d BIOS entries\n", header->num_entries);
144  		return 1;
145  	}
146  
147  	return 0;
148  }
149  
150  static int read_soft_fuse(FILE *fw, const embedded_firmware *fw_header)
151  {
152  	psp_directory_entry *current_entries = NULL;
153  	size_t num_current_entries = 0;
154  
155  	uint32_t psp_offset = 0;
156  	/* 0xffffffff indicates that the offset is in new_psp_directory */
157  	if (fw_header->psp_directory != 0xffffffff)
158  		psp_offset = fw_header->psp_directory;
159  	else
160  		psp_offset = fw_header->new_psp_directory;
161  
162  	psp_directory_header header;
163  	if (read_psp_directory(fw, psp_offset, PSP_COOKIE, &header,
164  		       &current_entries, &num_current_entries) != 0)
165  		return 1;
166  
167  	while (1) {
168  		uint32_t l2_dir_offset = 0;
169  		uint32_t ish_dir_offset;
170  		ish_directory_table ish_dir;
171  
172  		for (size_t i = 0; i < num_current_entries; i++) {
173  			uint32_t type = current_entries[i].type;
174  			uint64_t mode = current_entries[i].address_mode;
175  			uint64_t addr = current_entries[i].addr;
176  			uint64_t fuse;
177  
178  			switch (type) {
179  			case AMD_PSP_FUSE_CHAIN:
180  				fuse = mode << 62 | addr;
181  
182  				printf("Soft-fuse:0x%lx\n", fuse);
183  				free(current_entries);
184  				return 0;
185  
186  			case AMD_FW_L2_PTR:
187  				/* There's a second level PSP directory to read */
188  				if (l2_dir_offset != 0) {
189  					ERR("Duplicate PSP L2 Entry, prior offset: %08x\n",
190  										l2_dir_offset);
191  					free(current_entries);
192  					return 1;
193  				}
194  
195  				l2_dir_offset = relative_offset(psp_offset, addr, mode);
196  				break;
197  
198  			case AMD_FW_RECOVERYAB_A:
199  				if (l2_dir_offset != 0) {
200  					ERR("Duplicate PSP L2 Entry, prior offset: %08x\n",
201  										l2_dir_offset);
202  					free(current_entries);
203  					return 1;
204  				}
205  
206  				ish_dir_offset = relative_offset(psp_offset, addr, mode);
207  				if (read_ish_directory(fw, ish_dir_offset, &ish_dir) != 0) {
208  					ERR("Error reading ISH directory\n");
209  					free(current_entries);
210  					return 1;
211  				}
212  
213  				l2_dir_offset = ish_dir.pl2_location;
214  				break;
215  
216  			default:
217  				/* No-op, continue to the next entry. */
218  				break;
219  			}
220  		}
221  
222  		free(current_entries);
223  
224  		/* Didn't find an L2 PSP directory so we can stop */
225  		if (l2_dir_offset == 0)
226  			break;
227  
228  		/* Read the L2 PSP directory */
229  		if (read_psp_directory(fw, l2_dir_offset, PSPL2_COOKIE, &header,
230  			       &current_entries, &num_current_entries) != 0)
231  			break;
232  	}
233  
234  	return 1;
235  }
236  
237  #define MAX_NUM_LEVELS 10
238  #define MAX_INDENT_PER_LEVEL 4
239  #define MAX_INDENTATION_LEN (MAX_NUM_LEVELS * MAX_INDENT_PER_LEVEL + 1)
240  static void do_indentation_string(char *dest, uint8_t level)
241  {
242  	for (uint8_t i = 0; i < level && i < MAX_NUM_LEVELS; i++)
243  		strcat(dest, "    ");
244  	strcat(dest, "+-->");
245  }
246  
247  static int amdfw_bios_dir_walk(FILE *fw, uint32_t bios_offset, uint32_t cookie, uint8_t level)
248  {
249  	bios_directory_entry *current_entries = NULL;
250  	size_t num_current_entries = 0;
251  	bios_directory_hdr header;
252  	uint32_t l2_dir_offset = 0;
253  	char indent[MAX_INDENTATION_LEN] = {0};
254  
255  	if (read_bios_directory(fw, bios_offset, cookie, &header,
256  		       &current_entries, &num_current_entries) != 0)
257  		return 1;
258  
259  	do_indentation_string(indent, level);
260  	for (size_t i = 0; i < num_current_entries; i++) {
261  		uint32_t type = current_entries[i].type;
262  		uint64_t mode = current_entries[i].address_mode;
263  		uint64_t addr = current_entries[i].source;
264  
265  		if (type == AMD_BIOS_APOB || type == AMD_BIOS_PSP_SHARED_MEM)
266  			printf("%sBIOS%s: 0x%02x 0x%lx(DRAM-Address)\n",
267  				indent, cookie == BHD_COOKIE ? "L1" : "L2",
268  				type, current_entries[i].dest);
269  		else if (type == AMD_BIOS_APOB_NV)
270  			printf("%sBIOS%s: 0x%02x 0x%08lx 0x%08x\n",
271  				indent, cookie == BHD_COOKIE ? "L1" : "L2",
272  				type, relative_offset(bios_offset, addr, AMD_ADDR_PHYSICAL),
273  				current_entries[i].size);
274  		else
275  			printf("%sBIOS%s: 0x%02x 0x%08lx 0x%08x\n",
276  				indent, cookie == BHD_COOKIE ? "L1" : "L2",
277  				type, relative_offset(bios_offset, addr, mode),
278  				current_entries[i].size);
279  
280  		if (type == AMD_BIOS_L2_PTR) {
281  			/* There's a second level BIOS directory to read */
282  			if (l2_dir_offset != 0) {
283  				ERR("Duplicate BIOS L2 Entry, prior offset: %08x\n",
284  									l2_dir_offset);
285  				free(current_entries);
286  				return 1;
287  			}
288  
289  			l2_dir_offset = relative_offset(bios_offset, addr, mode);
290  			printf("    %sBIOSL2: Dir  0x%08x\n", indent, l2_dir_offset);
291  			amdfw_bios_dir_walk(fw, l2_dir_offset, BHDL2_COOKIE, level + 2);
292  		}
293  	}
294  
295  	free(current_entries);
296  	return 0;
297  }
298  
299  static int amdfw_psp_dir_walk(FILE *fw, uint32_t psp_offset, uint32_t cookie, uint8_t level)
300  {
301  	psp_directory_entry *current_entries = NULL;
302  	size_t num_current_entries = 0;
303  	psp_directory_header header;
304  	uint32_t l2_dir_offset = 0;
305  	uint32_t bios_dir_offset = 0;
306  	uint32_t ish_dir_offset = 0;
307  	ish_directory_table ish_dir;
308  	char indent[MAX_INDENTATION_LEN] = {0};
309  
310  	if (read_psp_directory(fw, psp_offset, cookie, &header,
311  		       &current_entries, &num_current_entries) != 0)
312  		return 1;
313  
314  	do_indentation_string(indent, level);
315  	for (size_t i = 0; i < num_current_entries; i++) {
316  		uint32_t type = current_entries[i].type;
317  		uint64_t mode = current_entries[i].address_mode;
318  		uint64_t addr = current_entries[i].addr;
319  
320  		if (type == AMD_PSP_FUSE_CHAIN)
321  			printf("%sPSP%s: 0x%02x 0x%lx(Soft-fuse)\n",
322  				indent, cookie == PSP_COOKIE ? "L1" : "L2",
323  				type, mode << 62 | addr);
324  		else
325  			printf("%sPSP%s: 0x%02x 0x%08lx 0x%08x\n",
326  				indent, cookie == PSP_COOKIE ? "L1" : "L2",
327  				type, relative_offset(psp_offset, addr, mode),
328  				current_entries[i].size);
329  
330  		switch (type) {
331  		case AMD_FW_L2_PTR:
332  			/* There's a second level PSP directory to read */
333  			if (l2_dir_offset != 0) {
334  				ERR("Duplicate PSP L2 Entry, prior offset: %08x\n",
335  									l2_dir_offset);
336  				free(current_entries);
337  				return 1;
338  			}
339  
340  			l2_dir_offset = relative_offset(psp_offset, addr, mode);
341  			printf("    %sPSPL2: Dir  0x%08x\n", indent, l2_dir_offset);
342  			amdfw_psp_dir_walk(fw, l2_dir_offset, PSPL2_COOKIE, level + 2);
343  			break;
344  
345  		case AMD_FW_RECOVERYAB_A:
346  			if (l2_dir_offset != 0) {
347  				ERR("Duplicate PSP L2 Entry, prior offset: %08x\n",
348  									l2_dir_offset);
349  				free(current_entries);
350  				return 1;
351  			}
352  
353  			ish_dir_offset = relative_offset(psp_offset, addr, mode);
354  			if (read_ish_directory(fw, ish_dir_offset, &ish_dir) != 0) {
355  				ERR("Error reading ISH directory\n");
356  				free(current_entries);
357  				return 1;
358  			}
359  
360  			l2_dir_offset = ish_dir.pl2_location;
361  			printf("    %sPSPL2: Dir  0x%08x\n", indent, l2_dir_offset);
362  			amdfw_psp_dir_walk(fw, l2_dir_offset, PSPL2_COOKIE, level + 2);
363  			break;
364  
365  		case AMD_FW_BIOS_TABLE:
366  			bios_dir_offset = relative_offset(psp_offset, addr, mode);
367  			printf("    %sBIOSL2: Dir  0x%08x\n", indent, bios_dir_offset);
368  			amdfw_bios_dir_walk(fw, bios_dir_offset, BHDL2_COOKIE, level + 2);
369  			break;
370  
371  		default:
372  			/* No additional processing required, continue to the next entry. */
373  			break;
374  		}
375  	}
376  
377  	free(current_entries);
378  	return 0;
379  }
380  
381  static int list_amdfw_psp_dir(FILE *fw, const embedded_firmware *fw_header)
382  {
383  	uint32_t psp_offset = 0;
384  
385  	/* 0xffffffff indicates that the offset is in new_psp_directory */
386  	if (fw_header->psp_directory != 0xffffffff)
387  		psp_offset = fw_header->psp_directory;
388  	else
389  		psp_offset = fw_header->new_psp_directory;
390  
391  	printf("PSPL1: Dir  0x%08x\n", psp_offset);
392  	amdfw_psp_dir_walk(fw, psp_offset, PSP_COOKIE, 0);
393  	return 0;
394  }
395  
396  static int list_amdfw_bios_dir(FILE *fw, const embedded_firmware *fw_header)
397  {
398  	/* 0xffffffff implies that the SoC uses recovery A/B layout. Only BIOS L2 directory
399  	   is present and that too as part of PSP L2 directory. */
400  	if (fw_header->bios3_entry != 0xffffffff) {
401  		printf("BIOSL1: Dir  0x%08x\n", fw_header->bios3_entry);
402  		amdfw_bios_dir_walk(fw, fw_header->bios3_entry, BHD_COOKIE, 0);
403  	}
404  	return 0;
405  }
406  
407  
408  static int list_amdfw_ro(FILE *fw, const embedded_firmware *fw_header)
409  {
410  	printf("Table: FW   Offset     Size\n");
411  	list_amdfw_psp_dir(fw, fw_header);
412  	list_amdfw_bios_dir(fw, fw_header);
413  	return 0;
414  }
415  
416  enum {
417  	AMDFW_OPT_HELP = 'h',
418  	AMDFW_OPT_SOFT_FUSE = 1UL << 0, /* Print Softfuse */
419  	AMDFW_OPT_RO_LIST = 1UL << 1, /* List entries in AMDFW RO */
420  };
421  
422  static char const optstring[] = {AMDFW_OPT_HELP};
423  
424  static struct option long_options[] = {
425  	{"help", no_argument, 0, AMDFW_OPT_HELP},
426  	{"soft-fuse", no_argument, 0, AMDFW_OPT_SOFT_FUSE},
427  	{"ro-list", no_argument, 0, AMDFW_OPT_RO_LIST},
428  };
429  
430  static void print_usage(void)
431  {
432  	printf("amdfwread: Examine AMD firmware images\n");
433  	printf("Usage: amdfwread [options] <file>\n");
434  	printf("--soft-fuse                Print soft fuse value\n");
435  	printf("--ro-list                  List the programs under AMDFW in RO region\n");
436  }
437  
438  int main(int argc, char **argv)
439  {
440  	char *fw_file = NULL;
441  
442  	int selected_functions = 0;
443  	while (1) {
444  		int opt = getopt_long(argc, argv, optstring, long_options, NULL);
445  
446  		if (opt == -1) {
447  			if (optind != (argc - 1)) {
448  				/* Print usage if one and only one option i.e. filename is
449  				   not found. */
450  				print_usage();
451  				return 0;
452  			}
453  
454  			fw_file = argv[optind];
455  			break;
456  		}
457  
458  		switch (opt) {
459  		case AMDFW_OPT_HELP:
460  			print_usage();
461  			return 0;
462  
463  		case AMDFW_OPT_SOFT_FUSE:
464  		case AMDFW_OPT_RO_LIST:
465  			selected_functions |= opt;
466  			break;
467  
468  		default:
469  			break;
470  		}
471  	}
472  
473  	FILE *fw = fopen(fw_file, "rb");
474  	if (!fw) {
475  		ERR("Failed to open FW file %s\n", fw_file);
476  		return 1;
477  	}
478  
479  	/* Find the FW header by checking each possible location */
480  	embedded_firmware fw_header;
481  	int found_header = 0;
482  	for (size_t i = 0; i < ARRAY_SIZE(fw_header_offsets); i++) {
483  		if (read_fw_header(fw, fw_header_offsets[i], &fw_header) == 0) {
484  			found_header = 1;
485  			break;
486  		}
487  	}
488  
489  	if (!found_header) {
490  		ERR("Failed to find FW header\n");
491  		fclose(fw);
492  		return 1;
493  	}
494  
495  	if (selected_functions & AMDFW_OPT_SOFT_FUSE) {
496  		if (read_soft_fuse(fw, &fw_header) != 0) {
497  			fclose(fw);
498  			return 1;
499  		}
500  	}
501  
502  	if (selected_functions & AMDFW_OPT_RO_LIST) {
503  		if (list_amdfw_ro(fw, &fw_header) != 0) {
504  			fclose(fw);
505  			return 1;
506  		}
507  	}
508  
509  	fclose(fw);
510  	return 0;
511  }