util/ifittool: Add an option to set the FIT pointer a CBFS file

The purpose of this is to eventually move the FIT table out of the
bootblock, generate it separately as a cbfs file and then have the FIT
pointer point to that cbfs file.

TESTED: extracted a FIT table using dd, added it as a cbfs file and see
that the FIT pointer correctly points to it. Also test that trying to
add a non valid FIT cbfs file results in an error.

Change-Id: I6e38b7df31e6b30f75b0ae57a5332f386e00f16b
Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/50925
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-by: Christian Walter <christian.walter@9elements.com>
diff --git a/util/cbfstool/fit.c b/util/cbfstool/fit.c
index 63956dd..da93143 100644
--- a/util/cbfstool/fit.c
+++ b/util/cbfstool/fit.c
@@ -519,6 +519,40 @@
 	return 0;
 }
 
+static uint32_t *get_fit_ptr(struct buffer *bootblock, fit_offset_converter_t offset_fn,
+		      uint32_t topswap_size)
+{
+	return rom_buffer_pointer(bootblock,
+				  ptr_to_offset(offset_fn, bootblock,
+						FIT_POINTER_LOCATION - topswap_size));
+}
+
+/* Set the FIT pointer to a FIT table. */
+int set_fit_pointer(struct buffer *bootblock,
+		    const uint32_t fit_address,
+		    fit_offset_converter_t offset_fn,
+		    uint32_t topswap_size)
+{
+	struct fit_table *fit;
+	uint32_t *fit_pointer = get_fit_ptr(bootblock, offset_fn, topswap_size);
+
+	fit = rom_buffer_pointer(bootblock, ptr_to_offset(offset_fn, bootblock, fit_address));
+
+	if (fit_address < FIT_TABLE_LOWEST_ADDRESS) {
+		ERROR("FIT must be reside in the top 16MiB.\n");
+		return 1;
+	}
+
+	if (!fit_table_verified(fit)) {
+		ERROR("FIT not found at address.\n");
+		return 1;
+	}
+
+	fit_pointer[0] = fit_address;
+	fit_pointer[1] = 0;
+	return 0;
+}
+
 /*
  * Return a pointer to the active FIT.
  */
@@ -527,11 +561,7 @@
 				uint32_t topswap_size)
 {
 	struct fit_table *fit;
-	uint32_t *fit_pointer;
-
-	fit_pointer = rom_buffer_pointer(bootblock,
-			ptr_to_offset(offset_fn, bootblock,
-			FIT_POINTER_LOCATION));
+	uint32_t *fit_pointer = get_fit_ptr(bootblock, offset_fn, 0);
 
 	/* Ensure pointer is below 4GiB and within 16MiB of 4GiB */
 	if (fit_pointer[1] != 0 || fit_pointer[0] < FIT_TABLE_LOWEST_ADDRESS) {
diff --git a/util/cbfstool/fit.h b/util/cbfstool/fit.h
index d900cf5..a0c956c 100644
--- a/util/cbfstool/fit.h
+++ b/util/cbfstool/fit.h
@@ -57,5 +57,6 @@
 			   const char *blob_name,
 			   fit_offset_converter_t offset_helper,
 			   const size_t max_fit_entries);
-
+int set_fit_pointer(struct buffer *bootblock, const uint32_t offset,
+		    fit_offset_converter_t offset_fn, uint32_t topswap_size);
 #endif
diff --git a/util/cbfstool/ifittool.c b/util/cbfstool/ifittool.c
index 10ed2eb..264f538 100644
--- a/util/cbfstool/ifittool.c
+++ b/util/cbfstool/ifittool.c
@@ -15,23 +15,24 @@
 /* Global variables */
 partitioned_file_t *image_file;
 
-static const char *optstring  = "H:j:f:r:d:t:n:s:cAaDvh?";
+static const char *optstring  = "H:j:f:r:d:t:n:s:cAaDvhF?";
 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  }
+	{"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' },
+	{"set-fit-pointer", no_argument,       0, 'F' },
+	{"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)
@@ -43,6 +44,7 @@
 		"\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-F|--set-fit-pointer  :   Set the FIT pointer to a CBFS file\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"
@@ -127,7 +129,8 @@
 	NO_OP = 0,
 	ADD_CBFS_OP,
 	ADD_REGI_OP,
-	DEL_OP
+	DEL_OP,
+	SET_FIT_PTR_OP
 };
 
 int main(int argc, char *argv[])
@@ -199,6 +202,14 @@
 		case 'f':
 			input_file = optarg;
 			break;
+		case 'F':
+			if (op != NO_OP) {
+				ERROR("specified multiple actions at once\n");
+				usage(argv[0]);
+				return 1;
+			}
+			op = SET_FIT_PTR_OP;
+			break;
 		case 'H':
 			headeroffset = strtoul(optarg, &suffix, 0);
 			if (!*optarg || (suffix && *suffix)) {
@@ -253,6 +264,14 @@
 		}
 	}
 
+	if (op == SET_FIT_PTR_OP) {
+		if (name == NULL) {
+			ERROR("Adding FIT entry, but no name set\n");
+			usage(argv[0]);
+			return 1;
+		}
+	}
+
 	if (!region_name) {
 		ERROR("Region not given\n");
 		usage(argv[0]);
@@ -281,13 +300,14 @@
 		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;
+	struct fit_table *fit = NULL;
+	if (op != SET_FIT_PTR_OP) {
+		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) {
@@ -352,6 +372,30 @@
 		}
 		break;
 	}
+	case SET_FIT_PTR_OP:
+	{
+		uint32_t fit_address;
+		struct 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;
+		}
+
+		fit_address = offset_to_ptr(convert_to_from_top_aligned, &image.buffer,
+				       cbfs_get_entry_addr(&image, cbfs_file)
+					       + ntohl(cbfs_file->offset));
+
+
+		if (set_fit_pointer(&bootblock, fit_address, convert_to_from_top_aligned,
+				    topswap_size)) {
+			partitioned_file_close(image_file);
+			ERROR("%s is not a FIT table\n", name);
+			return 1;
+		}
+		fit = fit_get_table(&bootblock, convert_to_from_top_aligned, topswap_size);
+		break;
+	}
 	case DEL_OP:
 	{
 		if (fit_delete_entry(fit, table_entry)) {