diff options
Diffstat (limited to 'util/cbfstool/ifittool.c')
-rw-r--r-- | util/cbfstool/ifittool.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/util/cbfstool/ifittool.c b/util/cbfstool/ifittool.c new file mode 100644 index 0000000000..a83fd96715 --- /dev/null +++ b/util/cbfstool/ifittool.c @@ -0,0 +1,431 @@ +/* + * cbfstool, CLI utility for creating rmodules + * + * Copyright (C) 2019 9elements Agency GmbH + * Copyright (C) 2019 Facebook Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <getopt.h> + +#include "common.h" +#include "cbfs_image.h" +#include "partitioned_file.h" +#include "fit.h" + +/* Global variables */ +partitioned_file_t *image_file; + +static const char *optstring = "H:j:f:r:d:t:n:s:caDvh?"; +static struct option long_options[] = { + {"file", required_argument, 0, 'f' }, + {"region", required_argument, 0, 'r' }, + {"add-cbfs-entry", no_argument, 0, 'a' }, + {"add-region", no_argument, 0, 'A' }, + {"del-entry", required_argument, 0, 'd' }, + {"clear-table", no_argument, 0, 'c' }, + {"fit-type", required_argument, 0, 't' }, + {"cbfs-filename", required_argument, 0, 'n' }, + {"max-table-size", required_argument, 0, 's' }, + {"topswap-size", required_argument, 0, 'j' }, + {"dump", no_argument, 0, 'D' }, + {"verbose", no_argument, 0, 'v' }, + {"help", no_argument, 0, 'h' }, + {"header-offset", required_argument, 0, 'H' }, + {NULL, 0, 0, 0 } +}; + +static void usage(const char *name) +{ + printf( + "ifittool: utility for modifying Intel Firmware Interface Table\n\n" + "USAGE: %s [-h] [-H] [-v] [-D] [-c] <-f|--file name> <-s|--max-table-size size> <-r|--region fmap region> OPERATION\n" + "\tOPERATION:\n" + "\t\t-a|--add-entry : Add a CBFS file as new entry to FIT\n" + "\t\t-A|--add-region : Add region as new entry to FIT (for microcodes)\n" + "\t\t-d|--del-entry number : Delete existing <number> entry\n" + "\t\t-t|--fit-type : Type of new entry\n" + "\t\t-n|--name : The CBFS filename or region to add to table\n" + "\tOPTIONAL ARGUMENTS:\n" + "\t\t-h|--help : Display this text\n" + "\t\t-H|--header-offset : Do not search for header, use this offset\n" + "\t\t-v|--verbose : Be verbose\n" + "\t\t-D|--dump : Dump FIT table (at end of operation)\n" + "\t\t-c|--clear-table : Remove all existing entries (do not update)\n" + "\t\t-j|--topswap-size : Use second FIT table if non zero\n" + "\tREQUIRED ARGUMENTS:\n" + "\t\t-f|--file name : The file containing the CBFS\n" + "\t\t-s|--max-table-size : The number of possible FIT entries in table\n" + "\t\t-r|--region : The FMAP region to operate on\n" + , name); +} + +static int is_valid_topswap(size_t topswap_size) +{ + switch (topswap_size) { + case (64 * KiB): + case (128 * KiB): + case (256 * KiB): + case (512 * KiB): + case (1 * MiB): + break; + default: + ERROR("Invalid topswap_size %zd\n", topswap_size); + ERROR("topswap can be 64K|128K|256K|512K|1M\n"); + return 0; + } + return 1; +} + +/* + * Converts between offsets from the start of the specified image region and + * "top-aligned" offsets from the top of the entire boot media. See comment + * below for convert_to_from_top_aligned() about forming addresses. + */ +static unsigned int convert_to_from_absolute_top_aligned( + const struct buffer *region, unsigned int offset) +{ + assert(region); + + size_t image_size = partitioned_file_total_size(image_file); + + return image_size - region->offset - offset; +} + +/* + * Converts between offsets from the start of the specified image region and + * "top-aligned" offsets from the top of the image region. Works in either + * direction: pass in one type of offset and receive the other type. + * N.B. A top-aligned offset is always a positive number, and should not be + * confused with a top-aligned *address*, which is its arithmetic inverse. */ +static unsigned int convert_to_from_top_aligned(const struct buffer *region, + unsigned int offset) +{ + assert(region); + + /* Cover the situation where a negative base address is given by the + * user. Callers of this function negate it, so it'll be a positive + * number smaller than the region. + */ + if ((offset > 0) && (offset < region->size)) + return region->size - offset; + + return convert_to_from_absolute_top_aligned(region, offset); +} + +/* + * Get a pointer from an offset. This function assumes the ROM is located + * in the host address space at [4G - romsize -> 4G). It also assume all + * pointers have values within this address range. + */ +static inline uint32_t offset_to_ptr(fit_offset_converter_t helper, + const struct buffer *region, int offset) +{ + return -helper(region, offset); +} + +enum fit_operation { + NO_OP = 0, + ADD_CBFS_OP, + ADD_REGI_OP, + ADD_ADDR_OP, + DEL_OP +}; + +int main(int argc, char *argv[]) +{ + int c; + const char *input_file = NULL; + const char *name = NULL; + const char *region_name = NULL; + enum fit_operation op = NO_OP; + bool dump = false, clear_table = false; + size_t max_table_size = 0; + size_t table_entry = 0; + uint32_t addr = 0; + size_t topswap_size = 0; + enum fit_type fit_type = 0; + uint32_t headeroffset = ~0u; + + verbose = 0; + + if (argc < 4) { + usage(argv[0]); + return 1; + } + + while (1) { + int optindex = 0; + char *suffix = NULL; + + c = getopt_long(argc, argv, optstring, long_options, &optindex); + + if (c == -1) + break; + + switch (c) { + case 'h': + usage(argv[0]); + return 1; + case 'a': + if (op != NO_OP) { + ERROR("specified multiple actions at once\n"); + usage(argv[0]); + return 1; + } + op = ADD_CBFS_OP; + break; + case 'A': + if (op != NO_OP) { + ERROR("specified multiple actions at once\n"); + usage(argv[0]); + return 1; + } + op = ADD_REGI_OP; + break; + case 'x': + if (op != NO_OP) { + ERROR("specified multiple actions at once\n"); + usage(argv[0]); + return 1; + } + op = ADD_ADDR_OP; + addr = atoll(optarg); + break; + case 'c': + clear_table = true; + break; + case 'd': + if (op != NO_OP) { + ERROR("specified multiple actions at once\n"); + usage(argv[0]); + return 1; + } + op = DEL_OP; + table_entry = atoi(optarg); + break; + case 'D': + dump = true; + break; + case 'f': + input_file = optarg; + break; + case 'H': + headeroffset = strtoul(optarg, &suffix, 0); + if (!*optarg || (suffix && *suffix)) { + ERROR("Invalid header offset '%s'.\n", optarg); + return 1; + } + break; + case 'j': + topswap_size = atoi(optarg); + if (!is_valid_topswap(topswap_size)) + return 1; + break; + case 'n': + name = optarg; + break; + case 'r': + region_name = optarg; + break; + case 's': + max_table_size = atoi(optarg); + break; + case 't': + fit_type = atoi(optarg); + break; + case 'v': + verbose++; + break; + default: + break; + } + } + + if (input_file == NULL) { + ERROR("No input file given\n"); + usage(argv[0]); + return 1; + } + + if (op == ADD_CBFS_OP || op == ADD_REGI_OP) { + if (fit_type == 0) { + ERROR("Adding FIT entry, but no type given\n"); + usage(argv[0]); + return 1; + } else if (name == NULL) { + ERROR("Adding FIT entry, but no name set\n"); + usage(argv[0]); + return 1; + } else if (max_table_size == 0) { + ERROR("Maximum table size not given\n"); + usage(argv[0]); + return 1; + } + } + if (op == ADD_ADDR_OP) { + if (fit_type == 0) { + ERROR("Adding FIT entry, but no type given\n"); + usage(argv[0]); + return 1; + } else if (max_table_size == 0) { + ERROR("Maximum table size not given\n"); + usage(argv[0]); + return 1; + } + } + + if (!region_name) { + ERROR("Region not given\n"); + usage(argv[0]); + return 1; + } + + image_file = partitioned_file_reopen(input_file, + op != NO_OP || clear_table); + + struct buffer image_region; + + if (!partitioned_file_read_region(&image_region, image_file, + region_name)) { + partitioned_file_close(image_file); + ERROR("The image will be left unmodified.\n"); + return 1; + } + + struct buffer bootblock; + // The bootblock is part of the CBFS on x86 + buffer_clone(&bootblock, &image_region); + + struct cbfs_image image; + if (cbfs_image_from_buffer(&image, &image_region, headeroffset)) { + partitioned_file_close(image_file); + return 1; + } + + struct fit_table *fit = fit_get_table(&bootblock, + convert_to_from_top_aligned, + topswap_size); + if (!fit) { + partitioned_file_close(image_file); + ERROR("FIT not found.\n"); + return 1; + } + + if (clear_table) { + if (fit_clear_table(fit)) { + partitioned_file_close(image_file); + ERROR("Failed to clear table.\n"); + return 1; + } + } + + switch (op) { + case ADD_REGI_OP: + { + struct buffer region; + addr = 0; + + if (partitioned_file_read_region(®ion, image_file, name)) { + addr = -convert_to_from_top_aligned(®ion, 0); + } else { + partitioned_file_close(image_file); + return 1; + } + + if (fit_add_entry(fit, addr, 0, fit_type, max_table_size)) { + partitioned_file_close(image_file); + ERROR("Adding type %u FIT entry\n", fit_type); + return 1; + } + break; + } + case ADD_CBFS_OP: + { + if (fit_type == FIT_TYPE_MICROCODE) { + if (fit_add_microcode_file(fit, &image, name, + convert_to_from_top_aligned, + max_table_size)) { + return 1; + } + } else { + uint32_t offset, len; + struct cbfs_file *cbfs_file; + + cbfs_file = cbfs_get_entry(&image, name); + if (!cbfs_file) { + partitioned_file_close(image_file); + ERROR("%s not found in CBFS.\n", name); + return 1; + } + + len = ntohl(cbfs_file->len); + offset = offset_to_ptr(convert_to_from_top_aligned, + &image.buffer, + cbfs_get_entry_addr(&image, cbfs_file) + + ntohl(cbfs_file->offset)); + + + if (fit_add_entry(fit, offset, len, fit_type, + max_table_size)) { + partitioned_file_close(image_file); + ERROR("Adding type %u FIT entry\n", fit_type); + return 1; + } + } + break; + } + case ADD_ADDR_OP: + { + if (fit_add_entry(fit, addr, 0, fit_type, max_table_size)) { + partitioned_file_close(image_file); + ERROR("Adding type %u FIT entry\n", fit_type); + return 1; + } + } + break; + case DEL_OP: + { + if (fit_delete_entry(fit, table_entry)) { + partitioned_file_close(image_file); + ERROR("Deleting FIT entry %zu failed\n", table_entry); + return 1; + } + break; + } + case NO_OP: + default: + break; + } + + if (op != NO_OP || clear_table) { + if (!partitioned_file_write_region(image_file, &bootblock)) { + ERROR("Failed to write changes to disk.\n"); + partitioned_file_close(image_file); + return 1; + } + } + + if (dump) { + if (fit_dump(fit)) { + partitioned_file_close(image_file); + return 1; + } + } + + partitioned_file_close(image_file); + + return 0; +} |