futility: Add read/write flash capability to gbb command

gbb command can read and modify flash in addition to acting on firmware
files.

BUG=b:260531154
BRANCH=None
TEST=FEATURES=test emerge-grunt vboot_reference
TEST=futility gbb -s --flags 0x0 /tmp/bios /tmp/bios2
TEST=futility gbb -g --flash
TEST=futility gbb --set --flash --flags=0x40b9 --flash
TEST=env SERVOD_NAME=grunt futility gbb --get --servo
TEST=env SERVOD_NAME=grunt futility gbb --set --servo --flags=0

Change-Id: I66b008ed7325d125eb305e84185e53eccd243898
Signed-off-by: Evan Benn <evanbenn@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/4075311
Reviewed-by: Yu-Ping Wu <yupingso@chromium.org>
Commit-Queue: Edward O'Callaghan <quasisec@chromium.org>
Reviewed-by: Edward O'Callaghan <quasisec@chromium.org>
diff --git a/futility/cmd_gbb_utility.c b/futility/cmd_gbb_utility.c
index c0e5fc5..3ca93b9 100644
--- a/futility/cmd_gbb_utility.c
+++ b/futility/cmd_gbb_utility.c
@@ -16,17 +16,34 @@
 #include <unistd.h>
 
 #include "futility.h"
+#include "updater.h"
 #include "updater_utils.h"
 
+#ifdef USE_FLASHROM
+#define FLASH_ARG_HELP                                                         \
+	"     --flash         \tRead from and write to flash"                  \
+	", ignore file arguments.\n"
+#define FLASH_MORE_HELP                                                        \
+	"In GET and SET mode, the following options modify the "               \
+	"behaviour of flashing. Presence of any of these implies "             \
+	"--flash.\n"                                                           \
+	SHARED_FLASH_ARGS_HELP                                                 \
+	"\n"
+#else
+#define FLASH_ARG_HELP
+#define FLASH_MORE_HELP
+#endif /* USE_FLASHROM */
+
 static void print_help(int argc, char *argv[])
 {
 	printf("\n"
 		"Usage:  " MYNAME " %s [-g|-s|-c] [OPTIONS] "
-	       "bios_file [output_file]\n"
+		"[bios_file] [output_file]\n"
 		"\n"
 		"GET MODE:\n"
-		"-g, --get   (default)\tGet (read) from bios_file, "
+		"-g, --get   (default)\tGet (read) from bios_file or flash, "
 		"with following options:\n"
+		FLASH_ARG_HELP
 		"     --hwid          \tReport hardware id (default).\n"
 		"     --flags         \tReport header flags.\n"
 		"     --digest        \tReport digest of hwid (>= v1.2)\n"
@@ -35,8 +52,9 @@
 		" -r  --recoverykey=FILE\tFile name to export Recovery Key.\n"
 		"\n"
 		"SET MODE:\n"
-		"-s, --set            \tSet (write) to bios_file, "
+		"-s, --set            \tSet (write) to flash or file, "
 		"with following options:\n"
+		FLASH_ARG_HELP
 		" -o, --output=FILE   \tNew file name for ouptput.\n"
 		"     --hwid=HWID     \tThe new hardware id to be changed.\n"
 		"     --flags=FLAGS   \tThe new (numeric) flags value.\n"
@@ -47,7 +65,8 @@
 		"CREATE MODE:\n"
 		"-c, --create=hwid_size,rootkey_size,bmpfv_size,"
 		"recoverykey_size\n"
-		"                     \tCreate a GBB blob by given size list.\n"
+		"                     \tCreate a GBB blob by given size list.\n\n"
+		FLASH_MORE_HELP
 		"SAMPLE:\n"
 		"  %s -g bios.bin\n"
 		"  %s --set --hwid='New Model' -k key.bin"
@@ -57,14 +76,16 @@
 }
 
 enum {
-	OPT_HWID = 1000,
+	OPT_HWID = 0x1000,
 	OPT_FLAGS,
 	OPT_DIGEST,
+	OPT_FLASH,
 	OPT_HELP,
 };
 
 /* Command line options */
 static struct option long_opts[] = {
+	SHARED_FLASH_ARGS_LONGOPTS
 	/* name  has_arg *flag val */
 	{"get", 0, NULL, 'g'},
 	{"set", 0, NULL, 's'},
@@ -76,11 +97,12 @@
 	{"hwid", 0, NULL, OPT_HWID},
 	{"flags", 0, NULL, OPT_FLAGS},
 	{"digest", 0, NULL, OPT_DIGEST},
+	{"flash", 0, NULL, OPT_FLASH},
 	{"help", 0, NULL, OPT_HELP},
 	{NULL, 0, NULL, 0},
 };
 
-static const char *short_opts = ":gsc:o:k:b:r:";
+static const char *short_opts = ":gsc:o:k:b:r:" SHARED_FLASH_ARGS_SHORTOPTS;
 
 /* Change the has_arg field of a long_opts entry */
 static void opt_has_arg(const char *name, int val)
@@ -167,9 +189,9 @@
 			size, strerror(errno));
 		free(sizes);
 		return NULL;
-	} else if (sizeptr) {
-		*sizeptr = size;
 	}
+	if (sizeptr)
+		*sizeptr = size;
 
 	gbb = (struct vb2_gbb_header *) buf;
 	memcpy(gbb->signature, VB2_GBB_SIGNATURE, VB2_GBB_SIGNATURE_SIZE);
@@ -317,6 +339,96 @@
 	return r;
 }
 
+/*
+ * Prepare for flashrom interaction. Setup cfg from args and put servo into
+ * flash mode if servo is in use. If this succeeds teardown_flash must be
+ * called.
+ */
+static int setup_flash(struct updater_config **cfg,
+		       struct updater_config_arguments *args,
+		       const char **prepare_ctrl_name)
+{
+#ifdef USE_FLASHROM
+	*prepare_ctrl_name = NULL;
+	*cfg = updater_new_config();
+	if (!*cfg) {
+		fprintf(stderr, "\nERROR: Out of memory\n");
+		return 1;
+	}
+	if (args->detect_servo) {
+		char *servo_programmer = host_detect_servo(prepare_ctrl_name);
+
+		if (!servo_programmer) {
+			fprintf(stderr,
+				"\nERROR: Problem communicating with servo\n");
+			goto errdelete;
+		}
+
+		if (!args->programmer)
+			args->programmer = servo_programmer;
+		else
+			free(servo_programmer);
+	}
+	int ignored;
+	if (updater_setup_config(*cfg, args, &ignored)) {
+		fprintf(stderr, "\nERROR: Bad servo options\n");
+		goto errdelete;
+	}
+	prepare_servo_control(*prepare_ctrl_name, 1);
+	return 0;
+errdelete:
+	updater_delete_config(*cfg);
+	*cfg = NULL;
+	return 1;
+#else
+	return 1;
+#endif /* USE_FLASHROM */
+}
+
+/* Cleanup objects created in setup_flash and release servo from flash mode. */
+static void teardown_flash(struct updater_config *cfg,
+			   const char *prepare_ctrl_name,
+			   char *servo_programmer)
+{
+#ifdef USE_FLASHROM
+	prepare_servo_control(prepare_ctrl_name, 0);
+	free(servo_programmer);
+	updater_delete_config(cfg);
+#endif /* USE_FLASHROM */
+}
+
+/* Read firmware from flash. */
+static uint8_t *read_from_flash(struct updater_config *cfg, off_t *filesize)
+{
+#ifdef USE_FLASHROM
+	if (load_system_firmware(cfg, &cfg->image_current))
+		return NULL;
+	uint8_t *ret = cfg->image_current.data;
+	cfg->image_current.data = NULL;
+	*filesize = cfg->image_current.size;
+	cfg->image_current.size = 0;
+	return ret;
+#else
+	return NULL;
+#endif /* USE_FLASHROM */
+}
+
+/* Write firmware to flash. Takes ownership of inbuf and outbuf data. */
+static int write_to_flash(struct updater_config *cfg, uint8_t *outbuf,
+			  off_t filesize)
+{
+#ifdef USE_FLASHROM
+	cfg->image.data = outbuf;
+	cfg->image.size = filesize;
+	int ret = write_firmware(cfg, &cfg->image, FMAP_RO_GBB);
+	cfg->image.data = NULL;
+	cfg->image.size = 0;
+	return ret;
+#else
+	return 1;
+#endif /* USE_FLASHROM */
+}
+
 static int do_gbb(int argc, char *argv[])
 {
 	enum do_what_now { DO_GET, DO_SET, DO_CREATE } mode = DO_GET;
@@ -337,9 +449,17 @@
 	struct vb2_gbb_header *gbb;
 	uint8_t *gbb_base;
 	int i;
+	struct updater_config *cfg = NULL;
+	struct updater_config_arguments args = {0};
+	const char *prepare_ctrl_name = NULL;
+	char *servo_programmer = NULL;
 
 	opterr = 0;		/* quiet, you */
 	while ((i = getopt_long(argc, argv, short_opts, long_opts, 0)) != -1) {
+#ifdef USE_FLASHROM
+		if (handle_flash_argument(&args, i, optarg))
+			continue;
+#endif
 		switch (i) {
 		case 'g':
 			mode = DO_GET;
@@ -380,6 +500,14 @@
 		case OPT_DIGEST:
 			sel_digest = 1;
 			break;
+		case OPT_FLASH:
+#ifndef USE_FLASHROM
+			fprintf(stderr, "ERROR: futility was built without "
+					"flashrom support\n");
+			return 1;
+#endif
+			args.use_flash = 1;
+			break;
 		case OPT_HELP:
 			print_help(argc, argv);
 			return !!errorcnt;
@@ -422,16 +550,34 @@
 		return 1;
 	}
 
+	if (args.use_flash) {
+		if (setup_flash(&cfg, &args, &prepare_ctrl_name)) {
+			fprintf(stderr,
+				"ERROR: error while preparing flash\n");
+			return 1;
+		}
+	}
+
 	/* Now try to do something */
 	switch (mode) {
 	case DO_GET:
-		if (argc - optind < 1) {
-			fprintf(stderr, "\nERROR: missing input filename\n");
-			print_help(argc, argv);
+		if (args.use_flash) {
+			inbuf = read_from_flash(cfg, &filesize);
+		} else {
+			if (argc - optind < 1) {
+				fprintf(stderr,
+					"\nERROR: missing input filename\n");
+				print_help(argc, argv);
+				errorcnt++;
+				break;
+			}
+			infile = argv[optind++];
+			inbuf = read_entire_file(infile, &filesize);
+
+		}
+		if (!inbuf) {
 			errorcnt++;
 			break;
-		} else {
-			infile = argv[optind++];
 		}
 
 		/* With no args, show the HWID */
@@ -439,12 +585,6 @@
 		    && !sel_flags && !sel_digest)
 			sel_hwid = 1;
 
-		inbuf = read_entire_file(infile, &filesize);
-		if (!inbuf) {
-			errorcnt++;
-			break;
-		}
-
 		gbb = FindGbbHeader(inbuf, filesize);
 		if (!gbb) {
 			fprintf(stderr, "ERROR: No GBB found in %s\n", infile);
@@ -491,15 +631,26 @@
 		break;
 
 	case DO_SET:
-		if (argc - optind < 1) {
-			fprintf(stderr, "\nERROR: missing input filename\n");
-			print_help(argc, argv);
+		if (args.use_flash) {
+			inbuf = read_from_flash(cfg, &filesize);
+		} else {
+			if (argc - optind < 1) {
+				fprintf(stderr,
+					"\nERROR: missing input filename\n");
+				print_help(argc, argv);
+				errorcnt++;
+				break;
+			}
+			infile = argv[optind++];
+			inbuf = read_entire_file(infile, &filesize);
+			if (!outfile)
+				outfile = (argc - optind < 1) ? infile
+							      : argv[optind++];
+		}
+		if (!inbuf) {
 			errorcnt++;
 			break;
 		}
-		infile = argv[optind++];
-		if (!outfile)
-			outfile = (argc - optind < 1) ? infile : argv[optind++];
 
 		if (sel_hwid && !opt_hwid) {
 			fprintf(stderr, "\nERROR: missing new HWID value\n");
@@ -514,13 +665,6 @@
 			break;
 		}
 
-		/* With no args, we'll either copy it unchanged or do nothing */
-		inbuf = read_entire_file(infile, &filesize);
-		if (!inbuf) {
-			errorcnt++;
-			break;
-		}
-
 		gbb = FindGbbHeader(inbuf, filesize);
 		if (!gbb) {
 			fprintf(stderr, "ERROR: No GBB found in %s\n", infile);
@@ -557,14 +701,13 @@
 					gbb->hwid_size);
 				errorcnt++;
 				break;
-			} else {
-				/* Wipe data before writing new value. */
-				memset(gbb_base + gbb->hwid_offset, 0,
-				       gbb->hwid_size);
-				strcpy((char *)(gbb_base + gbb->hwid_offset),
-				       opt_hwid);
-				update_hwid_digest(gbb);
 			}
+			/* Wipe data before writing new value. */
+			memset(gbb_base + gbb->hwid_offset, 0,
+				gbb->hwid_size);
+			strcpy((char *)(gbb_base + gbb->hwid_offset),
+				opt_hwid);
+			update_hwid_digest(gbb);
 		}
 
 		if (opt_flags) {
@@ -577,9 +720,8 @@
 					opt_flags);
 				errorcnt++;
 				break;
-			} else {
-				gbb->flags = val;
 			}
+			gbb->flags = val;
 		}
 
 		if (opt_rootkey) {
@@ -606,13 +748,19 @@
 			}
 
 		/* Write it out if there are no problems. */
-		if (!errorcnt)
-			if (write_to_file("successfully saved new image to:",
-					  outfile, outbuf, filesize)) {
+		if (!errorcnt) {
+			if (args.use_flash) {
+				if (write_to_flash(cfg, outbuf, filesize)) {
+					errorcnt++;
+					break;
+				}
+			} else if (write_to_file(
+					   "successfully saved new image to:",
+					   outfile, outbuf, filesize)) {
 				errorcnt++;
 				break;
 			}
-
+		}
 		break;
 
 	case DO_CREATE:
@@ -645,6 +793,8 @@
 		break;
 	}
 
+	if (args.use_flash)
+		teardown_flash(cfg, prepare_ctrl_name, servo_programmer);
 	if (inbuf)
 		free(inbuf);
 	if (outbuf)
diff --git a/futility/updater.c b/futility/updater.c
index 909339e..e6ba344 100644
--- a/futility/updater.c
+++ b/futility/updater.c
@@ -323,20 +323,14 @@
 	return 0;
 }
 
-/*
- * Writes a single section from the given firmware image to the system.
- * Writes the whole firmware image if the section_name is NULL.
- * Returns 0 if success, non-zero if error.
- */
-static int write_firmware(struct updater_config *cfg,
-			  const struct firmware_image *image,
-			  const char *section_name)
+int write_firmware(struct updater_config *cfg,
+		   const struct firmware_image *image, const char *section_name)
 {
 	const char *sections[2] = {0};
 
 	sections[0] = section_name;
-	return write_system_firmware(
-			cfg, image, section_name ? sections : NULL);
+	return write_system_firmware(cfg, image,
+				     section_name ? sections : NULL);
 }
 
 /*
@@ -1714,18 +1708,22 @@
 {
 	switch (opt) {
 	case 'p':
+		args->use_flash = 1;
 		args->programmer = optarg;
 		break;
 	case OPT_CCD:
+		args->use_flash = 1;
 		args->fast_update = 1;
 		args->force_update = 1;
 		args->write_protection = "0";
 		args->programmer = "raiden_debug_spi:target=AP";
 		break;
 	case OPT_EMULATE:
+		args->use_flash = 1;
 		args->emulation = optarg;
 		break;
 	case OPT_SERVO:
+		args->use_flash = 1;
 		args->detect_servo = 1;
 		args->fast_update = 1;
 		args->force_update = 1;
@@ -1734,6 +1732,7 @@
 		break;
 	case OPT_SERVO_PORT:
 		setenv(ENV_SERVOD_PORT, optarg, 1);
+		args->use_flash = 1;
 		args->detect_servo = 1;
 		args->fast_update = 1;
 		args->force_update = 1;
diff --git a/futility/updater.h b/futility/updater.h
index 7e24934..768beec 100644
--- a/futility/updater.h
+++ b/futility/updater.h
@@ -104,6 +104,7 @@
 	int verbosity;
 	int override_gbb_flags;
 	int detect_servo;
+	int use_flash;
 	uint32_t gbb_flags;
 	bool detect_model_only;
 };
@@ -254,6 +255,15 @@
 				struct model_config *model,
 				const char **signature_id);
 
+/*
+ * Writes a single section from the given firmware image to the system.
+ * Writes the whole firmware image if the section_name is NULL.
+ * Returns 0 if success, non-zero if error.
+ */
+int write_firmware(struct updater_config *cfg,
+		   const struct firmware_image *image,
+		   const char *section_name);
+
 /* Functions from updater_archive.c */
 
 /*
diff --git a/tests/futility/test_gbb_utility.sh b/tests/futility/test_gbb_utility.sh
index 7ec90d5..c90ca78 100755
--- a/tests/futility/test_gbb_utility.sh
+++ b/tests/futility/test_gbb_utility.sh
@@ -68,6 +68,12 @@
 cmp "${TMP}.data1" "${TMP}.read1"
 cmp "${TMP}.data2" "${TMP}.read2"
 
+# Basic get and set test using flashrom
+# The implementation requires an FMAP so use a full firmware image
+PEPPY_BIOS="${SCRIPT_DIR}/futility/data/bios_peppy_mp.bin"
+cp "${PEPPY_BIOS}" "${TMP}.full.blob"
+"${FUTILITY}" gbb -s --emulate="${TMP}.full.blob" --flags="0xdeadbeef"
+"${FUTILITY}" gbb -g --emulate="${TMP}.full.blob" --flags | grep -i "0xdeadbeef"
 
 # Okay, creating GBB blobs seems to work. Now let's make sure that corrupted
 # blobs are rejected.