ifdtool: Improve PCH strap handling

Read the PCH Strap Length field in FLMAP1 as described in the
"SPI Programming Guide" and print the number of fields specified there.

This code dumps the following straps:
* Intel GM45: 8 straps
* Intel C216: 72 straps
* Intel C240: 360 straps

Add a new function to easily set PCH straps, which is useful for debugging.

Change-Id: Ieb7891b214d82c984379794de9b3fe1a6d0d3466
Signed-off-by: Patrick Rudolph <patrick.rudolph@9elements.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/41693
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Philipp Hug <philipp@hug.cx>
Reviewed-by: Christian Walter <christian.walter@9elements.com>
Reviewed-by: Philipp Deppenwiese <zaolin.daisuki@gmail.com>
diff --git a/util/ifdtool/ifdtool.c b/util/ifdtool/ifdtool.c
index fcc760e..3db3977 100644
--- a/util/ifdtool/ifdtool.c
+++ b/util/ifdtool/ifdtool.c
@@ -145,7 +145,11 @@
 		return NULL;
 	fpsba_t *fpsba =
 		(fpsba_t *) (image + (((fdb->flmap1 >> 16) & 0xff) << 4));
-	return PTR_IN_RANGE(fpsba, image, size) ? fpsba : NULL;
+
+	int SSL = ((fdb->flmap1 >> 24) & 0xff) * sizeof(uint32_t);
+	if ((((char *)fpsba) + SSL) >= (image + size))
+		return NULL;
+	return fpsba;
 }
 
 static fmsba_t *find_fmsba(char *image, int size)
@@ -516,13 +520,15 @@
 		(fcba->flpb & 0xfff) << 12);
 }
 
-static void dump_fpsba(const fpsba_t *fpsba)
+static void dump_fpsba(const fdbar_t *fdb, const fpsba_t *fpsba)
 {
 	unsigned int i;
+	/* SoC Strap Length, aka PSL, aka ISL */
+	unsigned int SSL = ((fdb->flmap1 >> 24) & 0xff) * sizeof(uint32_t);
+
 	printf("Found PCH Strap Section\n");
-	for (i = 0; i < ARRAY_SIZE(fpsba->pchstrp); i++)
-		printf("PCHSTRP%u:%s 0x%08x\n", i,
-		       i < 10 ? " " : "", fpsba->pchstrp[i]);
+	for (i = 0; i < SSL; i++)
+		printf("PCHSTRP%-3u: 0x%08x\n", i, fpsba->pchstrp[i]);
 
 	if (ifd_version >= IFD_VERSION_2) {
 		printf("HAP bit is %sset\n",
@@ -751,7 +757,7 @@
 	if (frba && fcba && fpsba && fmba && fmsba) {
 		dump_frba(frba);
 		dump_fcba(fcba);
-		dump_fpsba(fpsba);
+		dump_fpsba(fdb, fpsba);
 		dump_fmba(fmba);
 		dump_fmsba(fmsba);
 	} else {
@@ -1099,6 +1105,23 @@
 	write_image(filename, image, size);
 }
 
+static void set_pchstrap(fpsba_t *fpsba, const fdbar_t *fdb, const int strap,
+			 const unsigned int value)
+{
+	if (!fpsba || !fdb) {
+		fprintf(stderr, "Internal error\n");
+		exit(EXIT_FAILURE);
+	}
+
+	/* SoC Strap Length, aka PSL, aka ISL */
+	int SSL = ((fdb->flmap1 >> 24) & 0xff) * sizeof(uint32_t);
+	if (strap >= SSL) {
+		fprintf(stderr, "Strap index %d out of range (max: %d)\n", strap, SSL);
+		exit(EXIT_FAILURE);
+	}
+	fpsba->pchstrp[strap] = value;
+}
+
 /* Set the AltMeDisable (or HAP for >= IFD_VERSION_2) */
 static void fpsba_set_altmedisable(fpsba_t *fpsba, fmsba_t *fmsba, bool altmedisable)
 {
@@ -1428,6 +1451,8 @@
 	       "                                         cnl - Cannon Lake\n"
 	       "                                         glk - Gemini Lake\n"
 	       "                                         sklkbl - Skylake/Kaby Lake\n"
+	       "   -S | --setpchstrap                    Write a PCH strap\n"
+	       "   -V | --newvalue                       The new value to write into PCH strap specified by -S\n"
 	       "   -v | --version:                       print the version\n"
 	       "   -h | --help:                          print this help\n\n"
 	       "<region> is one of Descriptor, BIOS, ME, GbE, Platform\n"
@@ -1439,12 +1464,14 @@
 	int opt, option_index = 0;
 	int mode_dump = 0, mode_extract = 0, mode_inject = 0, mode_spifreq = 0;
 	int mode_em100 = 0, mode_locked = 0, mode_unlocked = 0, mode_validate = 0;
-	int mode_layout = 0, mode_newlayout = 0, mode_density = 0;
+	int mode_layout = 0, mode_newlayout = 0, mode_density = 0, mode_setstrap = 0;
 	int mode_altmedisable = 0, altmedisable = 0;
 	char *region_type_string = NULL, *region_fname = NULL;
 	const char *layout_fname = NULL;
 	char *new_filename = NULL;
 	int region_type = -1, inputfreq = 0;
+	unsigned int value = 0;
+	unsigned int pchstrap = 0;
 	unsigned int new_density = 0;
 	enum spi_frequency spifreq = SPI_FREQUENCY_20MHZ;
 
@@ -1466,15 +1493,24 @@
 		{"help", 0, NULL, 'h'},
 		{"platform", 0, NULL, 'p'},
 		{"validate", 0, NULL, 't'},
+		{"setpchstrap", 1, NULL, 'S'},
+		{"newvalue", 1, NULL, 'V'},
 		{0, 0, 0, 0}
 	};
 
-	while ((opt = getopt_long(argc, argv, "df:D:C:M:xi:n:O:s:p:eluvth?",
+	while ((opt = getopt_long(argc, argv, "S:V:df:D:C:M:xi:n:O:s:p:eluvth?",
 				  long_options, &option_index)) != EOF) {
 		switch (opt) {
 		case 'd':
 			mode_dump = 1;
 			break;
+		case 'S':
+			mode_setstrap = 1;
+			pchstrap = strtoul(optarg, NULL, 0);
+			break;
+		case 'V':
+			value = strtoul(optarg, NULL, 0);
+			break;
 		case 'f':
 			mode_layout = 1;
 			layout_fname = strdup(optarg);
@@ -1674,7 +1710,7 @@
 		}
 	}
 
-	if ((mode_dump + mode_layout + mode_extract + mode_inject +
+	if ((mode_dump + mode_layout + mode_extract + mode_inject + mode_setstrap +
 		mode_newlayout + (mode_spifreq | mode_em100 | mode_unlocked |
 		 mode_locked) + mode_altmedisable + mode_validate) > 1) {
 		fprintf(stderr, "You may not specify more than one mode.\n\n");
@@ -1682,7 +1718,7 @@
 		exit(EXIT_FAILURE);
 	}
 
-	if ((mode_dump + mode_layout + mode_extract + mode_inject +
+	if ((mode_dump + mode_layout + mode_extract + mode_inject + mode_setstrap +
 	     mode_newlayout + mode_spifreq + mode_em100 + mode_locked +
 	     mode_unlocked + mode_density + mode_altmedisable + mode_validate) == 0) {
 		fprintf(stderr, "You need to specify a mode.\n\n");
@@ -1772,6 +1808,13 @@
 	if (mode_unlocked)
 		unlock_descriptor(new_filename, image, size);
 
+	if (mode_setstrap) {
+		fpsba_t *fpsba = find_fpsba(image, size);
+		const fdbar_t *fdb = find_fd(image, size);
+		set_pchstrap(fpsba, fdb, pchstrap, value);
+		write_image(new_filename, image, size);
+	}
+
 	if (mode_altmedisable) {
 		fpsba_t *fpsba = find_fpsba(image, size);
 		fmsba_t *fmsba = find_fmsba(image, size);