util/ifdtool: Add support for disabling GPR0

On ChromeOS devices with updateable CSE firmware, the GPR0 (Global
Protected Range) register is used to ensure the CSE RO is write
protected even when the FLMSTR-based protection is temporarily disabled
by coreboot to allow updating the CSE RW. For more details see
Documentation/soc/intel/cse_fw_update/cse_fw_update.md

Therefore to allow modifying the CSE firmware from the CPU, the
descriptor must have both the FLMSTR-based protection disabled (which
can be done using ifdtool --unlock), and GPR0 disabled.

Add an ifdtool option for disabling GPR0. For now I've added support for
all platforms for which I have the SPI programming guide. Support for
more platforms can be added in the future if needed.

BUG=b:270275115
TEST=Run `ifdtool -p adl -g image.bin -O image-unlocked.bin` on a locked
craask image, check the GPR0 field is set to 0.

Change-Id: Iee13ce0b702b3c7a443501cb4fc282580869d03a
Signed-off-by: Reka Norman <rekanorman@chromium.org>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/79788
Reviewed-by: Subrata Banik <subratabanik@google.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/util/ifdtool/ifdtool.c b/util/ifdtool/ifdtool.c
index 43451b1..7d69bcf 100644
--- a/util/ifdtool/ifdtool.c
+++ b/util/ifdtool/ifdtool.c
@@ -1497,6 +1497,39 @@
 	write_image(filename, image, size);
 }
 
+static void disable_gpr0(const char *filename, char *image, int size)
+{
+	struct fpsba *fpsba = find_fpsba(image, size);
+	if (!fpsba)
+		exit(EXIT_FAILURE);
+
+	/* Offset expressed as number of 32-bit fields from FPSBA */
+	uint32_t gpr0_offset;
+	switch (platform) {
+	case PLATFORM_CNL:
+		gpr0_offset = 0x10;
+		break;
+	case PLATFORM_JSL:
+		gpr0_offset = 0x12;
+		break;
+	case PLATFORM_TGL:
+	case PLATFORM_ADL:
+		gpr0_offset = 0x15;
+		break;
+	case PLATFORM_MTL:
+		gpr0_offset = 0x40;
+		break;
+	default:
+		fprintf(stderr, "Disabling GPR0 not supported on this platform\n");
+		exit(EXIT_FAILURE);
+	}
+
+	/* 0 means GPR0 protection is disabled */
+	fpsba->pchstrp[gpr0_offset] = 0;
+
+	write_image(filename, image, size);
+}
+
 static void set_pchstrap(struct fpsba *fpsba, const struct fdbar *fdb, const int strap,
 			const unsigned int value)
 {
@@ -1836,6 +1869,7 @@
 	       "   -l | --lock                           Lock firmware descriptor and ME region\n"
 	       "   -r | --read				 Enable CPU/BIOS read access for ME region\n"
 	       "   -u | --unlock                         Unlock firmware descriptor and ME region\n"
+	       "   -g | --gpr0-disable                   Disable GPR0 (Global Protected Range) register\n"
 	       "   -M | --altmedisable <0|1>             Set the MeDisable and AltMeDisable (or HAP for skylake or newer platform)\n"
 	       "                                         bits to disable ME\n"
 	       "   -p | --platform                       Add platform-specific quirks\n"
@@ -1869,6 +1903,7 @@
 	int mode_em100 = 0, mode_locked = 0, mode_unlocked = 0, mode_validate = 0;
 	int mode_layout = 0, mode_newlayout = 0, mode_density = 0, mode_setstrap = 0;
 	int mode_read = 0, mode_altmedisable = 0, altmedisable = 0, mode_fmap_template = 0;
+	int mode_gpr0_disable = 0;
 	char *region_type_string = NULL, *region_fname = NULL;
 	const char *layout_fname = NULL;
 	char *new_filename = NULL;
@@ -1894,6 +1929,7 @@
 		{"lock", 0, NULL, 'l'},
 		{"read", 0, NULL, 'r'},
 		{"unlock", 0, NULL, 'u'},
+		{"gpr0-disable", 0, NULL, 'g'},
 		{"version", 0, NULL, 'v'},
 		{"help", 0, NULL, 'h'},
 		{"platform", 0, NULL, 'p'},
@@ -1903,7 +1939,7 @@
 		{0, 0, 0, 0}
 	};
 
-	while ((opt = getopt_long(argc, argv, "S:V:df:F:D:C:M:xi:n:O:s:p:elruvth?",
+	while ((opt = getopt_long(argc, argv, "S:V:df:F:D:C:M:xi:n:O:s:p:elrugvth?",
 					long_options, &option_index)) != EOF) {
 		switch (opt) {
 		case 'd':
@@ -2106,6 +2142,9 @@
 				exit(EXIT_FAILURE);
 			}
 			break;
+		case 'g':
+			mode_gpr0_disable = 1;
+			break;
 		case 'p':
 			if (!strcmp(optarg, "aplk")) {
 				platform = PLATFORM_APL;
@@ -2158,7 +2197,8 @@
 
 	if ((mode_dump + mode_layout + mode_fmap_template + mode_extract + mode_inject +
 			mode_setstrap + mode_newlayout + (mode_spifreq | mode_em100 |
-			mode_unlocked | mode_locked) + mode_altmedisable + mode_validate) > 1) {
+			mode_unlocked | mode_locked) + mode_altmedisable + mode_validate +
+			mode_gpr0_disable) > 1) {
 		fprintf(stderr, "You may not specify more than one mode.\n\n");
 		fprintf(stderr, "run '%s -h' for usage\n", argv[0]);
 		exit(EXIT_FAILURE);
@@ -2166,7 +2206,8 @@
 
 	if ((mode_dump + mode_layout + mode_fmap_template + mode_extract + mode_inject +
 			mode_setstrap + mode_newlayout + mode_spifreq + mode_em100 +
-			mode_locked + mode_unlocked + mode_density + mode_altmedisable + mode_validate) == 0) {
+			mode_locked + mode_unlocked + mode_density + mode_altmedisable +
+			mode_validate + mode_gpr0_disable) == 0) {
 		fprintf(stderr, "You need to specify a mode.\n\n");
 		fprintf(stderr, "run '%s -h' for usage\n", argv[0]);
 		exit(EXIT_FAILURE);
@@ -2263,6 +2304,9 @@
 	if (mode_unlocked)
 		unlock_descriptor(new_filename, image, size);
 
+	if (mode_gpr0_disable)
+		disable_gpr0(new_filename, image, size);
+
 	if (mode_setstrap) {
 		struct fpsba *fpsba = find_fpsba(image, size);
 		const struct fdbar *fdb = find_fd(image, size);