futility: flashrom_drv: support partial write for multiple regions

When we have multiple regions to update, invoking flashrom_write_image
multiple times will take much longer because for each write it has to
read the whole flash, write and then verify whole flash (also timer
calibration and programmer init/shutdown every time).

As a result, we want to support writing multiple regions - just like
that flashrom can take arbitrary numbers of "-i REGION".

This change only extended flashrom_write_image, and the firmware updater
is calling flashrom_drv multiple times. That will be addressed in the
follow up changes.

BUG=b:221137867
TEST=build; and run test
BRANCH=None

Signed-off-by: Hung-Te Lin <hungte@chromium.org>
Change-Id: Id335cc9f816f1384f1886422efa97fe2c7b81aec
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/3490388
Reviewed-by: Edward O'Callaghan <quasisec@chromium.org>
diff --git a/futility/updater.c b/futility/updater.c
index bc68040..021916a 100644
--- a/futility/updater.c
+++ b/futility/updater.c
@@ -420,6 +420,9 @@
 			  const char *section_name)
 {
 	struct firmware_image *diff_image = NULL;
+	const char *sections[2] = {0};
+
+	sections[0] = section_name;
 
 	if (cfg->emulation) {
 		INFO("(emulation) Writing %s from %s to %s (emu=%s).\n",
@@ -434,7 +437,8 @@
 	    is_the_same_programmer(&cfg->image_current, image))
 		diff_image = &cfg->image_current;
 
-	return write_system_firmware(image, diff_image, section_name,
+	return write_system_firmware(image, diff_image,
+				     section_name ? sections : NULL,
 				     &cfg->tempfiles, cfg->do_verify,
 				     get_io_retries(cfg), cfg->verbosity + 1);
 }
diff --git a/futility/updater_utils.c b/futility/updater_utils.c
index 9f91129..18e4000 100644
--- a/futility/updater_utils.c
+++ b/futility/updater_utils.c
@@ -543,31 +543,49 @@
 }
 
 /*
- * Writes a section from given firmware image to system firmware.
- * If section_name is NULL, write whole image.
+ * Writes sections from a given firmware image to the system firmware.
+ * Regions should be NULL for writing the whole image, or a list of
+ * FMAP section names (and ended with a NULL).
  * Returns 0 if success, non-zero if error.
  */
 int write_system_firmware(const struct firmware_image *image,
 			  const struct firmware_image *diff_image,
-			  const char *section_name,
+			  const char * const sections[],
 			  struct tempfile *tempfiles,
 			  int do_verify, int retries, int verbosity)
 {
-	int r, i;
+	int r, i, len = 0;
+	char *partial = NULL;
 
-	INFO("flashrom -w <IMAGE> -p %s%s%s%s%s%s\n",
+	for (i = 0; sections && sections[i]; i++)
+		len += strlen(sections[i]) + strlen(" -i ");
+	if (len) {
+		partial = (char *)malloc(len + 1);
+		if (!partial) {
+			ERROR("Failed to allocate a string buffer.\n");
+			return -1;
+		}
+		partial[0] = '\0';
+		for (i = 0; sections[i]; i++) {
+			strcat(partial, " -i ");
+			strcat(partial, sections[i]);
+		}
+		assert(strlen(partial) == len);
+	}
+
+	INFO("flashrom -w <IMAGE> -p %s%s%s%s%s\n",
 	     image->programmer,
 	     diff_image ? " --flash-contents <DIFF_IMAGE>" : "",
 	     do_verify ? "" : " --noverify",
 	     verbosity > 1 ? " -V" : "",
-	     section_name ? " -i " : "",
-	     section_name ? section_name : "");
+	     partial ? partial : "");
+	free(partial);
 
 	for (i = 1, r = -1; i <= retries && r != 0; i++) {
 		if (i > 1)
 			WARN("Retry writing firmware (%d/%d)...\n", i, retries);
-		r = flashrom_write_image(image, section_name, diff_image,
-					 do_verify, verbosity + 1);
+		r = flashrom_write_image(image, sections, diff_image, do_verify,
+					 verbosity + 1);
 		/*
 		 * Force a newline to flush stdout in case if
 		 * flashrom_write_image left some messages in the buffer.
diff --git a/futility/updater_utils.h b/futility/updater_utils.h
index 4ab61c9..3eb1d33 100644
--- a/futility/updater_utils.h
+++ b/futility/updater_utils.h
@@ -93,13 +93,14 @@
 					 struct tempfile *tempfiles);
 
 /*
- * Writes a section from given firmware image to system firmware.
- * If section_name is NULL, write whole image.
+ * Writes sections from a given firmware image to the system firmware.
+ * Regions should be NULL for writing the whole image, or a list of
+ * FMAP section names (and ended with a NULL).
  * Returns 0 if success, non-zero if error.
  */
 int write_system_firmware(const struct firmware_image *image,
 			  const struct firmware_image *diff_image,
-			  const char *section_name,
+			  const char * const sections[],
 			  struct tempfile *tempfiles,
 			  int do_verify, int retries, int verbosity);
 
diff --git a/host/lib/flashrom_drv.c b/host/lib/flashrom_drv.c
index 18e00eb..1d9e3fd 100644
--- a/host/lib/flashrom_drv.c
+++ b/host/lib/flashrom_drv.c
@@ -119,7 +119,7 @@
 }
 
 int flashrom_write_image(const struct firmware_image *image,
-			const char *region,
+			const char * const regions[],
 			const struct firmware_image *diff_image,
 			int do_verify, int verbosity)
 {
@@ -162,7 +162,8 @@
 		}
 	}
 
-	if (region) {
+	if (regions) {
+		int i;
 		r = flashrom_layout_read_fmap_from_buffer(
 			&layout, flashctx, (const uint8_t *)image->data,
 			image->size);
@@ -177,12 +178,15 @@
 				goto err_cleanup;
 			}
 		}
-		// empty region causes seg fault in API.
-		r |= flashrom_layout_include_region(layout, region);
-		if (r > 0) {
-			ERROR("could not include region = '%s'\n", region);
-			r = -1;
-			goto err_cleanup;
+		for (i = 0; regions[i]; i++) {
+			// empty region causes seg fault in API.
+			r |= flashrom_layout_include_region(layout, regions[i]);
+			if (r > 0) {
+				ERROR("could not include region = '%s'\n",
+				      regions[i]);
+				r = -1;
+				goto err_cleanup;
+			}
 		}
 		flashrom_layout_set(flashctx, layout);
 	}
diff --git a/host/lib/include/flashrom.h b/host/lib/include/flashrom.h
index eb6d531..6058d64 100644
--- a/host/lib/include/flashrom.h
+++ b/host/lib/include/flashrom.h
@@ -48,13 +48,14 @@
  *
  * @param image		The parameter that contains the programmer, buffer and
  *			size to use in the write operation.
- * @param region	The name of the fmap region to write, or NULL to
- *			write the entire flash chip.
+ * @param regions	A list of the names of the fmap regions to write, or
+ *			NULL to write the entire flash chip. The list must be
+ *			ended with a NULL pointer.
  *
  * @return VB2_SUCCESS on success, or a relevant error.
  */
 vb2_error_t flashrom_write(struct firmware_image *image, const char *region);
 int flashrom_write_image(const struct firmware_image *image,
-			const char *region,
+			const char * const regions[],
 			const struct firmware_image *diff_image,
 			int do_verify, int verbosity);