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 ¤t_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 ¤t_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 ¤t_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 ¤t_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 }