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> |
| 7 | #include <unistd.h> |
| 8 | #include "amdfwtool.h" |
| 9 | |
| 10 | /* An address can be relative to the image/file start but it can also be the address when |
| 11 | * the image is mapped at 0xff000000. Used to ensure that we only attempt to read within |
| 12 | * the limits of the file. */ |
| 13 | #define FILE_REL_MASK 0xffffff |
| 14 | |
| 15 | #define ERR(...) fprintf(stderr, __VA_ARGS__) |
| 16 | |
| 17 | /* Possible locations for the header */ |
| 18 | const uint32_t fw_header_offsets[] = { |
| 19 | 0xfa0000, |
| 20 | 0xe20000, |
| 21 | 0xc20000, |
| 22 | 0x820000, |
| 23 | 0x020000, |
| 24 | }; |
| 25 | |
| 26 | /* Converts addresses to be relative to the start of the file */ |
| 27 | static uint64_t relative_offset(const psp_directory_header *header, uint32_t header_offset, |
| 28 | const psp_directory_entry *entry, size_t entry_index) |
| 29 | { |
| 30 | amd_addr_mode mode = header->additional_info_fields.address_mode; |
| 31 | if (mode == AMD_ADDR_REL_BIOS) { |
| 32 | /* Entry address mode override directory mode with this value */ |
| 33 | mode = entry->address_mode; |
| 34 | } |
| 35 | |
| 36 | switch (mode) { |
| 37 | case AMD_ADDR_REL_BIOS: |
| 38 | return entry->addr + header_offset; |
| 39 | |
| 40 | case AMD_ADDR_REL_SLOT: |
| 41 | return entry->addr + header_offset + sizeof(psp_directory_header) + |
| 42 | entry_index * sizeof(psp_directory_entry); |
| 43 | |
| 44 | default: |
| 45 | break; |
| 46 | } |
| 47 | |
| 48 | return entry->addr & FILE_REL_MASK; |
| 49 | } |
| 50 | |
| 51 | static int read_fw_header(FILE *fw, uint32_t offset, embedded_firmware *fw_header) |
| 52 | { |
| 53 | if (fseek(fw, offset, SEEK_SET) != 0) { |
| 54 | ERR("Failed to seek to fw header offset 0x%x\n", offset); |
| 55 | return 1; |
| 56 | } |
| 57 | |
| 58 | if (fread(fw_header, sizeof(embedded_firmware), 1, fw) != 1) { |
| 59 | ERR("Failed to read fw header at 0x%x\n", offset); |
| 60 | return 1; |
| 61 | } |
| 62 | |
| 63 | return fw_header->signature != EMBEDDED_FW_SIGNATURE; |
| 64 | } |
| 65 | |
| 66 | static int read_psp_directory(FILE *fw, uint32_t offset, uint32_t expected_cookie, |
| 67 | psp_directory_header *header, psp_directory_entry **entries, |
| 68 | size_t *num_entries) |
| 69 | { |
| 70 | offset &= FILE_REL_MASK; |
| 71 | |
| 72 | if (fseek(fw, offset, SEEK_SET) != 0) { |
| 73 | ERR("Failed to seek to PSP header at 0x%x\n", offset); |
| 74 | return 1; |
| 75 | } |
| 76 | |
| 77 | if (fread(header, sizeof(psp_directory_header), 1, fw) != 1) { |
| 78 | ERR("Failed to read PSP header cookie\n"); |
| 79 | return 1; |
| 80 | } |
| 81 | |
| 82 | /* Ensure that we have a PSP directory */ |
| 83 | if (header->cookie != expected_cookie) { |
| 84 | ERR("Invalid PSP header cookie value found: 0x%x, expected: 0x%x\n", |
| 85 | expected_cookie, header->cookie); |
| 86 | return 1; |
| 87 | } |
| 88 | |
| 89 | /* Read the entries */ |
| 90 | *num_entries = header->num_entries; |
| 91 | *entries = malloc(sizeof(psp_directory_entry) * header->num_entries); |
| 92 | if (fread(*entries, sizeof(psp_directory_entry), header->num_entries, fw) |
| 93 | != header->num_entries) { |
| 94 | ERR("Failed to read %d PSP entries\n", header->num_entries); |
| 95 | return 1; |
| 96 | } |
| 97 | |
| 98 | return 0; |
| 99 | } |
| 100 | |
| 101 | static int read_soft_fuse(FILE *fw, const embedded_firmware *fw_header) |
| 102 | { |
| 103 | psp_directory_entry *current_entries = NULL; |
| 104 | size_t num_current_entries = 0; |
| 105 | |
| 106 | uint32_t psp_offset = 0; |
| 107 | /* 0xffffffff indicates that the offset is in new_psp_directory */ |
| 108 | if (fw_header->psp_directory != 0xffffffff) |
| 109 | psp_offset = fw_header->psp_directory; |
| 110 | else |
| 111 | psp_offset = fw_header->new_psp_directory; |
| 112 | |
| 113 | psp_directory_header header; |
| 114 | if (read_psp_directory(fw, psp_offset, PSP_COOKIE, &header, |
| 115 | ¤t_entries, &num_current_entries) != 0) |
| 116 | return 1; |
| 117 | |
| 118 | while (1) { |
| 119 | uint32_t l2_dir_offset = 0; |
| 120 | |
| 121 | for (size_t i = 0; i < num_current_entries; i++) { |
| 122 | uint32_t type = current_entries[i].type; |
| 123 | if (type == AMD_PSP_FUSE_CHAIN) { |
| 124 | uint64_t mode = current_entries[i].address_mode; |
| 125 | uint64_t addr = current_entries[i].addr; |
| 126 | uint64_t fuse = mode << 62 | addr; |
| 127 | |
| 128 | printf("Soft-fuse:0x%lx\n", fuse); |
| 129 | return 0; |
| 130 | } else if (type == AMD_FW_L2_PTR) { |
| 131 | /* There's a second level PSP directory to read */ |
| 132 | if (l2_dir_offset != 0) |
| 133 | return 1; |
| 134 | |
| 135 | l2_dir_offset = relative_offset(&header, psp_offset, |
| 136 | ¤t_entries[i], i); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | free(current_entries); |
| 141 | |
| 142 | /* Didn't find an L2 PSP directory so we can stop */ |
| 143 | if (l2_dir_offset == 0) |
| 144 | break; |
| 145 | |
| 146 | /* Read the L2 PSP directory */ |
| 147 | if (read_psp_directory(fw, l2_dir_offset, PSPL2_COOKIE, &header, |
| 148 | ¤t_entries, &num_current_entries) != 0) |
| 149 | break; |
| 150 | } |
| 151 | |
| 152 | return 1; |
| 153 | } |
| 154 | |
| 155 | enum { |
| 156 | AMDFW_OPT_HELP = 'h', |
| 157 | |
| 158 | /* When bit 31 is set, options are a bitfield of info to print from the |
| 159 | firmware image. */ |
| 160 | AMDFW_OPT_SOFT_FUSE = 0xF0000001, |
| 161 | }; |
| 162 | |
| 163 | static char const optstring[] = {AMDFW_OPT_HELP}; |
| 164 | |
| 165 | static struct option long_options[] = { |
| 166 | {"help", no_argument, 0, AMDFW_OPT_HELP}, |
| 167 | {"soft-fuse", no_argument, 0, AMDFW_OPT_SOFT_FUSE}, |
| 168 | }; |
| 169 | |
| 170 | static void print_usage(void) |
| 171 | { |
| 172 | printf("amdfwread: Examine AMD firmware images\n"); |
| 173 | printf("Usage: amdfwread [options] <file>\n"); |
| 174 | printf("--soft-fuse Print soft fuse value\n"); |
| 175 | } |
| 176 | |
| 177 | int main(int argc, char **argv) |
| 178 | { |
| 179 | char *fw_file = NULL; |
| 180 | |
| 181 | int selected_functions = 0; |
| 182 | int index = 0; |
| 183 | while (1) { |
| 184 | int opt = getopt_long(argc, argv, optstring, long_options, &index); |
| 185 | |
| 186 | if (opt == -1) { |
| 187 | index++; |
| 188 | if (index >= argc) { |
| 189 | /* Print usage if we didn't get any arguments */ |
| 190 | print_usage(); |
| 191 | return 0; |
| 192 | } |
| 193 | |
| 194 | fw_file = argv[index]; |
| 195 | break; |
| 196 | } |
| 197 | |
| 198 | if (opt == AMDFW_OPT_HELP) { |
| 199 | print_usage(); |
| 200 | return 0; |
| 201 | } |
| 202 | |
| 203 | selected_functions |= opt; |
| 204 | } |
| 205 | |
| 206 | FILE *fw = fopen(fw_file, "rb"); |
| 207 | if (!fw) { |
| 208 | ERR("Failed to open FW file %s\n", fw_file); |
| 209 | return 1; |
| 210 | } |
| 211 | |
| 212 | /* Find the FW header by checking each possible location */ |
| 213 | embedded_firmware fw_header; |
| 214 | int found_header = 0; |
| 215 | for (size_t i = 0; i < ARRAY_SIZE(fw_header_offsets); i++) { |
| 216 | if (read_fw_header(fw, fw_header_offsets[i], &fw_header) == 0) { |
| 217 | found_header = 1; |
| 218 | break; |
| 219 | } |
| 220 | } |
| 221 | |
| 222 | if (!found_header) { |
| 223 | ERR("Failed to find FW header\n"); |
| 224 | fclose(fw); |
| 225 | return 1; |
| 226 | } |
| 227 | |
| 228 | if (selected_functions & AMDFW_OPT_SOFT_FUSE) { |
| 229 | if (read_soft_fuse(fw, &fw_header) != 0) { |
| 230 | fclose(fw); |
| 231 | return 1; |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | fclose(fw); |
| 236 | return 0; |
| 237 | } |