summaryrefslogtreecommitdiff
path: root/util/cbfstool/ifittool.c
diff options
context:
space:
mode:
Diffstat (limited to 'util/cbfstool/ifittool.c')
-rw-r--r--util/cbfstool/ifittool.c431
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(&region, image_file, name)) {
+ addr = -convert_to_from_top_aligned(&region, 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;
+}