ifittool.c
1 /* cbfstool, CLI utility for creating rmodules */ 2 /* SPDX-License-Identifier: GPL-2.0-only */ 3 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 #include <unistd.h> 8 #include <getopt.h> 9 10 #include "common.h" 11 #include "cbfs_image.h" 12 #include "partitioned_file.h" 13 #include "fit.h" 14 15 /* Global variables */ 16 partitioned_file_t *image_file; 17 18 static const char *optstring = "H:j:f:r:d:t:n:s:cAaDvhF?"; 19 static struct option long_options[] = { 20 {"file", required_argument, 0, 'f' }, 21 {"region", required_argument, 0, 'r' }, 22 {"add-cbfs-entry", no_argument, 0, 'a' }, 23 {"add-region", no_argument, 0, 'A' }, 24 {"del-entry", required_argument, 0, 'd' }, 25 {"clear-table", no_argument, 0, 'c' }, 26 {"set-fit-pointer", no_argument, 0, 'F' }, 27 {"fit-type", required_argument, 0, 't' }, 28 {"cbfs-filename", required_argument, 0, 'n' }, 29 {"max-table-size", required_argument, 0, 's' }, 30 {"topswap-size", required_argument, 0, 'j' }, 31 {"dump", no_argument, 0, 'D' }, 32 {"verbose", no_argument, 0, 'v' }, 33 {"help", no_argument, 0, 'h' }, 34 {"header-offset", required_argument, 0, 'H' }, 35 {NULL, 0, 0, 0 } 36 }; 37 38 static void usage(const char *name) 39 { 40 printf( 41 "ifittool: utility for modifying Intel Firmware Interface Table\n\n" 42 "USAGE: %s [-h] [-H] [-v] [-D] [-c] <-f|--file name> <-s|--max-table-size size> <-r|--region fmap region> OPERATION\n" 43 "\tOPERATION:\n" 44 "\t\t-a|--add-entry : Add a CBFS file as new entry to FIT\n" 45 "\t\t-A|--add-region : Add region as new entry to FIT (for microcodes)\n" 46 "\t\t-d|--del-entry number : Delete existing <number> entry\n" 47 "\t\t-F|--set-fit-pointer : Set the FIT pointer to a CBFS file\n" 48 "\t\t-t|--fit-type : Type of new entry\n" 49 "\t\t-n|--name : The CBFS filename or region to add to table\n" 50 "\tOPTIONAL ARGUMENTS:\n" 51 "\t\t-h|--help : Display this text\n" 52 "\t\t-H|--header-offset : Do not search for header, use this offset\n" 53 "\t\t-v|--verbose : Be verbose (-v=INFO -vv=DEBUG output)\n" 54 "\t\t-D|--dump : Dump FIT table (at end of operation)\n" 55 "\t\t-c|--clear-table : Remove all existing entries (do not update)\n" 56 "\t\t-j|--topswap-size : Use second FIT table if non zero\n" 57 "\tREQUIRED ARGUMENTS:\n" 58 "\t\t-f|--file name : The file containing the CBFS\n" 59 "\t\t-s|--max-table-size : The number of possible FIT entries in table\n" 60 "\t\t-r|--region : The FMAP region to operate on\n" 61 , name); 62 } 63 64 static int is_valid_topswap(size_t topswap_size) 65 { 66 switch (topswap_size) { 67 case (64 * KiB): 68 case (128 * KiB): 69 case (256 * KiB): 70 case (512 * KiB): 71 case (1 * MiB): 72 break; 73 default: 74 ERROR("Invalid topswap_size %zd\n", topswap_size); 75 ERROR("topswap can be 64K|128K|256K|512K|1M\n"); 76 return 0; 77 } 78 return 1; 79 } 80 81 /* 82 * Converts between offsets from the start of the specified image region and 83 * "top-aligned" offsets from the top of the entire boot media. See comment 84 * below for convert_to_from_top_aligned() about forming addresses. 85 */ 86 static unsigned int convert_to_from_absolute_top_aligned( 87 const struct buffer *region, unsigned int offset) 88 { 89 assert(region); 90 91 size_t image_size = partitioned_file_total_size(image_file); 92 93 return image_size - region->offset - offset; 94 } 95 96 /* 97 * Converts between offsets from the start of the specified image region and 98 * "top-aligned" offsets from the top of the image region. Works in either 99 * direction: pass in one type of offset and receive the other type. 100 * N.B. A top-aligned offset is always a positive number, and should not be 101 * confused with a top-aligned *address*, which is its arithmetic inverse. */ 102 static unsigned int convert_to_from_top_aligned(const struct buffer *region, 103 unsigned int offset) 104 { 105 assert(region); 106 107 /* Cover the situation where a negative base address is given by the 108 * user. Callers of this function negate it, so it'll be a positive 109 * number smaller than the region. 110 */ 111 if ((offset > 0) && (offset < region->size)) 112 return region->size - offset; 113 114 return convert_to_from_absolute_top_aligned(region, offset); 115 } 116 117 /* 118 * Get a pointer from an offset. This function assumes the ROM is located 119 * in the host address space at [4G - romsize -> 4G). It also assume all 120 * pointers have values within this address range. 121 */ 122 static inline uint32_t offset_to_ptr(fit_offset_converter_t helper, 123 const struct buffer *region, int offset) 124 { 125 return -helper(region, offset); 126 } 127 128 enum fit_operation { 129 NO_OP = 0, 130 ADD_CBFS_OP, 131 ADD_REGI_OP, 132 DEL_OP, 133 SET_FIT_PTR_OP 134 }; 135 136 int main(int argc, char *argv[]) 137 { 138 int c; 139 const char *input_file = NULL; 140 const char *name = NULL; 141 const char *region_name = NULL; 142 enum fit_operation op = NO_OP; 143 bool dump = false, clear_table = false; 144 size_t max_table_size = 0; 145 size_t table_entry = 0; 146 uint32_t addr = 0; 147 size_t topswap_size = 0; 148 enum fit_type fit_type = 0; 149 uint32_t headeroffset = HEADER_OFFSET_UNKNOWN; 150 151 verbose = 0; 152 153 if (argc < 4) { 154 usage(argv[0]); 155 return 1; 156 } 157 158 while (1) { 159 int optindex = 0; 160 char *suffix = NULL; 161 162 c = getopt_long(argc, argv, optstring, long_options, &optindex); 163 164 if (c == -1) 165 break; 166 167 switch (c) { 168 case 'h': 169 usage(argv[0]); 170 return 1; 171 case 'a': 172 if (op != NO_OP) { 173 ERROR("specified multiple actions at once\n"); 174 usage(argv[0]); 175 return 1; 176 } 177 op = ADD_CBFS_OP; 178 break; 179 case 'A': 180 if (op != NO_OP) { 181 ERROR("specified multiple actions at once\n"); 182 usage(argv[0]); 183 return 1; 184 } 185 op = ADD_REGI_OP; 186 break; 187 case 'c': 188 clear_table = true; 189 break; 190 case 'd': 191 if (op != NO_OP) { 192 ERROR("specified multiple actions at once\n"); 193 usage(argv[0]); 194 return 1; 195 } 196 op = DEL_OP; 197 table_entry = atoi(optarg); 198 break; 199 case 'D': 200 dump = true; 201 break; 202 case 'f': 203 input_file = optarg; 204 break; 205 case 'F': 206 if (op != NO_OP) { 207 ERROR("specified multiple actions at once\n"); 208 usage(argv[0]); 209 return 1; 210 } 211 op = SET_FIT_PTR_OP; 212 break; 213 case 'H': 214 headeroffset = strtoul(optarg, &suffix, 0); 215 if (!*optarg || (suffix && *suffix)) { 216 ERROR("Invalid header offset '%s'.\n", optarg); 217 return 1; 218 } 219 break; 220 case 'j': 221 topswap_size = strtol(optarg, NULL, 0); 222 if (!is_valid_topswap(topswap_size)) 223 return 1; 224 break; 225 case 'n': 226 name = optarg; 227 break; 228 case 'r': 229 region_name = optarg; 230 break; 231 case 's': 232 max_table_size = atoi(optarg); 233 break; 234 case 't': 235 fit_type = atoi(optarg); 236 break; 237 case 'v': 238 verbose++; 239 break; 240 default: 241 break; 242 } 243 } 244 245 if (input_file == NULL) { 246 ERROR("No input file given\n"); 247 usage(argv[0]); 248 return 1; 249 } 250 251 if (op == ADD_CBFS_OP || op == ADD_REGI_OP) { 252 if (fit_type == 0) { 253 ERROR("Adding FIT entry, but no type given\n"); 254 usage(argv[0]); 255 return 1; 256 } else if (name == NULL) { 257 ERROR("Adding FIT entry, but no name set\n"); 258 usage(argv[0]); 259 return 1; 260 } else if (max_table_size == 0) { 261 ERROR("Maximum table size not given\n"); 262 usage(argv[0]); 263 return 1; 264 } 265 } 266 267 if (op == SET_FIT_PTR_OP) { 268 if (name == NULL) { 269 ERROR("Adding FIT entry, but no name set\n"); 270 usage(argv[0]); 271 return 1; 272 } 273 } 274 275 if (!region_name) { 276 ERROR("Region not given\n"); 277 usage(argv[0]); 278 return 1; 279 } 280 281 image_file = partitioned_file_reopen(input_file, 282 op != NO_OP || clear_table); 283 284 struct buffer image_region; 285 286 if (!partitioned_file_read_region(&image_region, image_file, 287 region_name)) { 288 partitioned_file_close(image_file); 289 ERROR("The image will be left unmodified.\n"); 290 return 1; 291 } 292 293 struct buffer bootblock; 294 // The bootblock is part of the CBFS on x86 295 buffer_clone(&bootblock, &image_region); 296 297 struct cbfs_image image; 298 if (cbfs_image_from_buffer(&image, &image_region, headeroffset)) { 299 partitioned_file_close(image_file); 300 return 1; 301 } 302 303 struct fit_table *fit = NULL; 304 if (op != SET_FIT_PTR_OP) { 305 fit = fit_get_table(&bootblock, convert_to_from_top_aligned, topswap_size); 306 if (!fit) { 307 partitioned_file_close(image_file); 308 ERROR("FIT not found.\n"); 309 return 1; 310 } 311 if (clear_table) { 312 if (fit_clear_table(fit)) { 313 partitioned_file_close(image_file); 314 ERROR("Failed to clear table.\n"); 315 return 1; 316 } 317 } 318 } 319 320 switch (op) { 321 case ADD_REGI_OP: 322 { 323 struct buffer region; 324 325 if (partitioned_file_read_region(®ion, image_file, name)) { 326 addr = -convert_to_from_top_aligned(®ion, 0); 327 } else { 328 partitioned_file_close(image_file); 329 return 1; 330 } 331 332 if (fit_add_entry(fit, addr, 0, fit_type, max_table_size)) { 333 partitioned_file_close(image_file); 334 ERROR("Adding type %u FIT entry\n", fit_type); 335 return 1; 336 } 337 break; 338 } 339 case ADD_CBFS_OP: 340 { 341 if (fit_type == FIT_TYPE_MICROCODE) { 342 if (fit_add_microcode_file(fit, &image, name, 343 convert_to_from_top_aligned, 344 max_table_size)) { 345 return 1; 346 } 347 } else { 348 uint32_t offset, len; 349 struct cbfs_file *cbfs_file; 350 351 cbfs_file = cbfs_get_entry(&image, name); 352 if (!cbfs_file) { 353 partitioned_file_close(image_file); 354 ERROR("%s not found in CBFS.\n", name); 355 return 1; 356 } 357 358 len = be32toh(cbfs_file->len); 359 offset = offset_to_ptr(convert_to_from_top_aligned, 360 &image.buffer, 361 cbfs_get_entry_addr(&image, cbfs_file) + 362 be32toh(cbfs_file->offset)); 363 364 365 if (fit_add_entry(fit, offset, len, fit_type, 366 max_table_size)) { 367 partitioned_file_close(image_file); 368 ERROR("Adding type %u FIT entry\n", fit_type); 369 return 1; 370 } 371 } 372 break; 373 } 374 case SET_FIT_PTR_OP: 375 { 376 uint32_t fit_address; 377 struct cbfs_file *cbfs_file = cbfs_get_entry(&image, name); 378 if (!cbfs_file) { 379 partitioned_file_close(image_file); 380 ERROR("%s not found in CBFS.\n", name); 381 return 1; 382 } 383 384 fit_address = offset_to_ptr(convert_to_from_top_aligned, &image.buffer, 385 cbfs_get_entry_addr(&image, cbfs_file) 386 + be32toh(cbfs_file->offset)); 387 388 389 if (set_fit_pointer(&bootblock, fit_address, convert_to_from_top_aligned, 390 topswap_size)) { 391 partitioned_file_close(image_file); 392 ERROR("%s is not a FIT table\n", name); 393 return 1; 394 } 395 fit = fit_get_table(&bootblock, convert_to_from_top_aligned, topswap_size); 396 397 if (clear_table) { 398 if (fit_clear_table(fit)) { 399 partitioned_file_close(image_file); 400 ERROR("Failed to clear table.\n"); 401 return 1; 402 } 403 } 404 405 break; 406 } 407 case DEL_OP: 408 { 409 if (fit_delete_entry(fit, table_entry)) { 410 partitioned_file_close(image_file); 411 ERROR("Deleting FIT entry %zu failed\n", table_entry); 412 return 1; 413 } 414 break; 415 } 416 case NO_OP: 417 default: 418 break; 419 } 420 421 if (op != NO_OP || clear_table) { 422 if (!partitioned_file_write_region(image_file, &bootblock)) { 423 ERROR("Failed to write changes to disk.\n"); 424 partitioned_file_close(image_file); 425 return 1; 426 } 427 } 428 429 if (dump) { 430 if (fit_dump(fit)) { 431 partitioned_file_close(image_file); 432 return 1; 433 } 434 } 435 436 partitioned_file_close(image_file); 437 438 return 0; 439 }