Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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> |
Karthikeyan Ramasubramanian | 8b86f21 | 2022-08-04 17:18:38 -0600 | [diff] [blame] | 7 | #include <string.h> |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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. */ |
Karthikeyan Ramasubramanian | 852c5dc | 2022-08-08 22:14:27 -0600 | [diff] [blame] | 14 | #define SPI_ROM_BASE 0xff000000 |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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 */ |
Karthikeyan Ramasubramanian | 852c5dc | 2022-08-08 22:14:27 -0600 | [diff] [blame] | 29 | static uint64_t relative_offset(uint32_t header_offset, uint64_t addr, uint64_t mode) |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 30 | { |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 31 | switch (mode) { |
Karthikeyan Ramasubramanian | 852c5dc | 2022-08-08 22:14:27 -0600 | [diff] [blame] | 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; |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 43 | |
Karthikeyan Ramasubramanian | 852c5dc | 2022-08-08 22:14:27 -0600 | [diff] [blame] | 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; |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 53 | |
| 54 | default: |
Karthikeyan Ramasubramanian | 852c5dc | 2022-08-08 22:14:27 -0600 | [diff] [blame] | 55 | ERR("Unsupported mode %lu\n", mode); |
| 56 | exit(1); |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 57 | } |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 58 | } |
| 59 | |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 60 | static int read_header(FILE *fw, uint32_t offset, void *header, size_t header_size) |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 61 | { |
| 62 | if (fseek(fw, offset, SEEK_SET) != 0) { |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 63 | ERR("Failed to seek to file offset 0x%x\n", offset); |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 64 | return 1; |
| 65 | } |
| 66 | |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 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))) { |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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 | |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 91 | if (read_header(fw, offset, header, sizeof(psp_directory_header))) { |
| 92 | ERR("Failed to read PSP header\n"); |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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", |
Arthur Heymans | ee0f5d7 | 2022-10-08 11:53:13 +0200 | [diff] [blame] | 99 | header->cookie, expected_cookie); |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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 | |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 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 | |
Karthikeyan Ramasubramanian | 8b86f21 | 2022-08-04 17:18:38 -0600 | [diff] [blame] | 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 | |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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; |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 169 | uint32_t ish_dir_offset; |
| 170 | ish_directory_table ish_dir; |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 171 | |
| 172 | for (size_t i = 0; i < num_current_entries; i++) { |
| 173 | uint32_t type = current_entries[i].type; |
Karthikeyan Ramasubramanian | 852c5dc | 2022-08-08 22:14:27 -0600 | [diff] [blame] | 174 | uint64_t mode = current_entries[i].address_mode; |
| 175 | uint64_t addr = current_entries[i].addr; |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 176 | uint64_t fuse; |
Karthikeyan Ramasubramanian | 852c5dc | 2022-08-08 22:14:27 -0600 | [diff] [blame] | 177 | |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 178 | switch (type) { |
| 179 | case AMD_PSP_FUSE_CHAIN: |
| 180 | fuse = mode << 62 | addr; |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 181 | |
| 182 | printf("Soft-fuse:0x%lx\n", fuse); |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 183 | free(current_entries); |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 184 | return 0; |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 185 | |
| 186 | case AMD_FW_L2_PTR: |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 187 | /* There's a second level PSP directory to read */ |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 188 | if (l2_dir_offset != 0) { |
| 189 | ERR("Duplicate PSP L2 Entry, prior offset: %08x\n", |
| 190 | l2_dir_offset); |
| 191 | free(current_entries); |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 192 | return 1; |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 193 | } |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 194 | |
Karthikeyan Ramasubramanian | 852c5dc | 2022-08-08 22:14:27 -0600 | [diff] [blame] | 195 | l2_dir_offset = relative_offset(psp_offset, addr, mode); |
Karthikeyan Ramasubramanian | 0b6e632 | 2022-08-08 15:50:36 -0600 | [diff] [blame] | 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; |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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 | |
Karthikeyan Ramasubramanian | 8b86f21 | 2022-08-04 17:18:38 -0600 | [diff] [blame] | 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 | |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 416 | enum { |
| 417 | AMDFW_OPT_HELP = 'h', |
Karthikeyan Ramasubramanian | 45257ab | 2022-08-08 22:20:48 -0600 | [diff] [blame] | 418 | AMDFW_OPT_SOFT_FUSE = 1UL << 0, /* Print Softfuse */ |
Karthikeyan Ramasubramanian | 8b86f21 | 2022-08-04 17:18:38 -0600 | [diff] [blame] | 419 | AMDFW_OPT_RO_LIST = 1UL << 1, /* List entries in AMDFW RO */ |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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}, |
Karthikeyan Ramasubramanian | 8b86f21 | 2022-08-04 17:18:38 -0600 | [diff] [blame] | 427 | {"ro-list", no_argument, 0, AMDFW_OPT_RO_LIST}, |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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"); |
Karthikeyan Ramasubramanian | 8b86f21 | 2022-08-04 17:18:38 -0600 | [diff] [blame] | 435 | printf("--ro-list List the programs under AMDFW in RO region\n"); |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 436 | } |
| 437 | |
| 438 | int main(int argc, char **argv) |
| 439 | { |
| 440 | char *fw_file = NULL; |
| 441 | |
| 442 | int selected_functions = 0; |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 443 | while (1) { |
Karthikeyan Ramasubramanian | 0dd3cf4 | 2022-08-08 14:30:03 -0600 | [diff] [blame] | 444 | int opt = getopt_long(argc, argv, optstring, long_options, NULL); |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 445 | |
| 446 | if (opt == -1) { |
Karthikeyan Ramasubramanian | 0dd3cf4 | 2022-08-08 14:30:03 -0600 | [diff] [blame] | 447 | if (optind != (argc - 1)) { |
| 448 | /* Print usage if one and only one option i.e. filename is |
| 449 | not found. */ |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 450 | print_usage(); |
| 451 | return 0; |
| 452 | } |
| 453 | |
Karthikeyan Ramasubramanian | 0dd3cf4 | 2022-08-08 14:30:03 -0600 | [diff] [blame] | 454 | fw_file = argv[optind]; |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 455 | break; |
| 456 | } |
| 457 | |
Karthikeyan Ramasubramanian | 45257ab | 2022-08-08 22:20:48 -0600 | [diff] [blame] | 458 | switch (opt) { |
| 459 | case AMDFW_OPT_HELP: |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 460 | print_usage(); |
| 461 | return 0; |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 462 | |
Karthikeyan Ramasubramanian | 45257ab | 2022-08-08 22:20:48 -0600 | [diff] [blame] | 463 | case AMDFW_OPT_SOFT_FUSE: |
Karthikeyan Ramasubramanian | 8b86f21 | 2022-08-04 17:18:38 -0600 | [diff] [blame] | 464 | case AMDFW_OPT_RO_LIST: |
Karthikeyan Ramasubramanian | 45257ab | 2022-08-08 22:20:48 -0600 | [diff] [blame] | 465 | selected_functions |= opt; |
| 466 | break; |
| 467 | |
| 468 | default: |
| 469 | break; |
| 470 | } |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 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 | |
Karthikeyan Ramasubramanian | 8b86f21 | 2022-08-04 17:18:38 -0600 | [diff] [blame] | 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 | |
Robert Zieba | 29bc79f | 2022-03-14 15:59:12 -0600 | [diff] [blame] | 509 | fclose(fw); |
| 510 | return 0; |
| 511 | } |