Add option to read ROM layout from IFD

Add an option --ifd to read the ROM layout from an Intel Firmware
Descriptor (IFD). Works the same as the -l option, if given, -i
specifies the images to update.

v2: o Rebased on libflashrom, use libflashrom interface.
    o Use functions from ich_descriptors.c.

v3: o Move ich_descriptors.o to LIB_OBJS, thus build it independent
      of arch and programmers.
    o Bail out if we aren't compiled for little endian.
    o Update flashrom.8.tmpl.

v4: o Incorporated David's comments.
    o Removed single-character `-d` option.

v5: Changed region names to match the output of `ifdtool --layout ...`

Change-Id: Ifafff2bf6d5c5e62283416b3269723f81fdc0fa3
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: https://review.coreboot.org/17953
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
diff --git a/Makefile b/Makefile
index 01a179d..65026da 100644
--- a/Makefile
+++ b/Makefile
@@ -519,7 +519,7 @@
 ###############################################################################
 # Library code.
 
-LIB_OBJS = libflashrom.o layout.o flashrom.o udelay.o programmer.o helpers.o
+LIB_OBJS = libflashrom.o layout.o flashrom.o udelay.o programmer.o helpers.o ich_descriptors.o
 
 ###############################################################################
 # Frontend related stuff.
@@ -731,7 +731,7 @@
 PROGRAMMER_OBJS += processor_enable.o chipset_enable.o board_enable.o cbtable.o internal.o
 ifeq ($(ARCH), x86)
 PROGRAMMER_OBJS += it87spi.o it85spi.o sb600spi.o amd_imc.o wbsio_spi.o mcp6x_spi.o
-PROGRAMMER_OBJS += ichspi.o ich_descriptors.o dmi.o
+PROGRAMMER_OBJS += ichspi.o dmi.o
 ifeq ($(CONFIG_INTERNAL_DMI), yes)
 FEATURE_CFLAGS += -D'CONFIG_INTERNAL_DMI=1'
 endif
diff --git a/cli_classic.c b/cli_classic.c
index 7e3dfd5..0a09cfd 100644
--- a/cli_classic.c
+++ b/cli_classic.c
@@ -42,7 +42,7 @@
 	       "-z|"
 #endif
 	       "-p <programmername>[:<parameters>] [-c <chipname>]\n"
-	       "[-E|(-r|-w|-v) <file>] [-l <layoutfile> [-i <imagename>]...] [-n] [-N] [-f]]\n"
+	       "[-E|(-r|-w|-v) <file>] [(-l <layoutfile>|--ifd) [-i <imagename>]...] [-n] [-N] [-f]]\n"
 	       "[-V[V[V]]] [-o <logfile>]\n\n", name);
 
 	printf(" -h | --help                        print this help text\n"
@@ -57,6 +57,7 @@
 	       " -n | --noverify                    don't auto-verify\n"
 	       " -N | --noverify-all                verify included regions only (cf. -i)\n"
 	       " -l | --layout <layoutfile>         read ROM layout from <layoutfile>\n"
+	       "      --ifd                         read layout from an Intel Firmware Descriptor\n"
 	       " -i | --image <name>                only flash image <name> from flash layout\n"
 	       " -o | --output <logfile>            log output to <logfile>\n"
 	       " -L | --list-supported              print supported devices\n"
@@ -99,12 +100,13 @@
 	struct flashctx *fill_flash;
 	const char *name;
 	int namelen, opt, i, j;
-	int startchip = -1, chipcount = 0, option_index = 0, force = 0;
+	int startchip = -1, chipcount = 0, option_index = 0, force = 0, ifd = 0;
 #if CONFIG_PRINT_WIKI == 1
 	int list_supported_wiki = 0;
 #endif
 	int read_it = 0, write_it = 0, erase_it = 0, verify_it = 0;
 	int dont_verify_it = 0, dont_verify_all = 0, list_supported = 0, operation_specified = 0;
+	struct flashrom_layout *layout = NULL;
 	enum programmer prog = PROGRAMMER_INVALID;
 	int ret = 0;
 
@@ -120,6 +122,7 @@
 		{"verbose",		0, NULL, 'V'},
 		{"force",		0, NULL, 'f'},
 		{"layout",		1, NULL, 'l'},
+		{"ifd",			0, NULL, 0x0100},
 		{"image",		1, NULL, 'i'},
 		{"list-supported",	0, NULL, 'L'},
 		{"list-supported-wiki",	0, NULL, 'z'},
@@ -220,8 +223,19 @@
 					"more than once. Aborting.\n");
 				cli_classic_abort_usage();
 			}
+			if (ifd) {
+				fprintf(stderr, "Error: --layout and --ifd both specified. Aborting.\n");
+				cli_classic_abort_usage();
+			}
 			layoutfile = strdup(optarg);
 			break;
+		case 0x0100:
+			if (layoutfile) {
+				fprintf(stderr, "Error: --layout and --ifd both specified. Aborting.\n");
+				cli_classic_abort_usage();
+			}
+			ifd = 1;
+			break;
 		case 'i':
 			tempstr = strdup(optarg);
 			if (register_include_arg(tempstr)) {
@@ -376,7 +390,7 @@
 		ret = 1;
 		goto out;
 	}
-	if (process_include_args()) {
+	if (!ifd && process_include_args(get_global_layout())) {
 		ret = 1;
 		goto out;
 	}
@@ -529,9 +543,15 @@
 		goto out_shutdown;
 	}
 
-	if (layoutfile)
-		flashrom_layout_set(fill_flash, get_global_layout());
+	if (layoutfile) {
+		layout = get_global_layout();
+	} else if (ifd && (flashrom_layout_read_from_ifd(&layout, fill_flash, NULL, 0) ||
+			   process_include_args(layout))) {
+		ret = 1;
+		goto out_shutdown;
+	}
 
+	flashrom_layout_set(fill_flash, layout);
 	flashrom_flag_set(fill_flash, FLASHROM_FLAG_FORCE, !!force);
 	flashrom_flag_set(fill_flash, FLASHROM_FLAG_FORCE_BOARDMISMATCH, !!force_boardmismatch);
 	flashrom_flag_set(fill_flash, FLASHROM_FLAG_VERIFY_AFTER_WRITE, !dont_verify_it);
@@ -551,6 +571,8 @@
 	else if (verify_it)
 		ret = do_verify(fill_flash, filename);
 
+	flashrom_layout_release(layout);
+
 out_shutdown:
 	programmer_shutdown();
 out:
diff --git a/flash.h b/flash.h
index 7c7ecd5..47c32f4 100644
--- a/flash.h
+++ b/flash.h
@@ -287,6 +287,8 @@
 int selfcheck(void);
 int read_buf_from_file(unsigned char *buf, unsigned long size, const char *filename);
 int write_buf_to_file(const unsigned char *buf, unsigned long size, const char *filename);
+int prepare_flash_access(struct flashctx *, bool read_it, bool write_it, bool erase_it, bool verify_it);
+void finalize_flash_access(struct flashctx *);
 int do_read(struct flashctx *, const char *filename);
 int do_erase(struct flashctx *);
 int do_write(struct flashctx *, const char *const filename);
@@ -354,7 +356,6 @@
 
 /* layout.c */
 int register_include_arg(char *name);
-int process_include_args(void);
 int read_romlayout(const char *name);
 int normalize_romentries(const struct flashctx *flash);
 void layout_cleanup(void);
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
index 34e7f02..143d76c 100644
--- a/flashrom.8.tmpl
+++ b/flashrom.8.tmpl
@@ -48,8 +48,8 @@
 \fB\-p\fR <programmername>[:<parameters>]
                [\fB\-E\fR|\fB\-r\fR <file>|\fB\-w\fR <file>|\fB\-v\fR <file>] \
 [\fB\-c\fR <chipname>]
-               [\fB\-l\fR <file> [\fB\-i\fR <image>]] [\fB\-n\fR] [\fB\-N\fR] \
-[\fB\-f\fR]]
+               [(\fB\-l\fR <file>|\fB\-\-ifd\fR) [\fB\-i\fR <image>]] \
+[\fB\-n\fR] [\fB\-N\fR] [\fB\-f\fR]]
          [\fB\-V\fR[\fBV\fR[\fBV\fR]]] [\fB-o\fR <logfile>]
 .SH DESCRIPTION
 .B flashrom
@@ -195,6 +195,22 @@
 .sp
 Overlapping sections are not supported.
 .TP
+.B "\-\-ifd"
+Read ROM layout from Intel Firmware Descriptor.
+.sp
+flashrom supports ROM layouts given by an Intel Firmware Descriptor
+(IFD). The on-chip descriptor will be read and used to generate the
+layout. If you need to change the layout, you have to update the IFD
+only first.
+.sp
+The following ROM images may be present in an IFD:
+.sp
+  fd    the IFD itself
+  bios  the host firmware aka. BIOS
+  me    Intel Management Engine firmware
+  gbe   gigabit ethernet firmware
+  pd    platform specific data
+.TP
 .B "\-i, \-\-image <imagename>"
 Only flash region/image
 .B <imagename>
diff --git a/flashrom.c b/flashrom.c
index 325a0c1..503a199 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -2168,9 +2168,9 @@
 	return 0;
 }
 
-static int prepare_flash_access(struct flashctx *const flash,
-				const bool read_it, const bool write_it,
-				const bool erase_it, const bool verify_it)
+int prepare_flash_access(struct flashctx *const flash,
+			 const bool read_it, const bool write_it,
+			 const bool erase_it, const bool verify_it)
 {
 	if (chip_safety_check(flash, flash->flags.force, read_it, write_it, erase_it, verify_it)) {
 		msg_cerr("Aborting.\n");
@@ -2193,7 +2193,7 @@
 	return 0;
 }
 
-static void finalize_flash_access(struct flashctx *const flash)
+void finalize_flash_access(struct flashctx *const flash)
 {
 	unmap_flash(flash);
 }
diff --git a/ich_descriptors.c b/ich_descriptors.c
index a0b2c9a..b6453ce 100644
--- a/ich_descriptors.c
+++ b/ich_descriptors.c
@@ -23,6 +23,7 @@
 
 #ifdef ICH_DESCRIPTORS_FROM_DUMP_ONLY
 #include <stdio.h>
+#include <string.h>
 #define print(t, ...) printf(__VA_ARGS__)
 #endif
 
@@ -38,7 +39,7 @@
 #include "programmer.h"
 
 #ifndef min
-#define min(a, b) (a < b) ? a : b
+#define min(a, b) (((a) < (b)) ? (a) : (b))
 #endif
 
 void prettyprint_ich_reg_vscc(uint32_t reg_val, int verbosity, bool print_vcl)
@@ -916,4 +917,42 @@
 	msg_pdbg2(" done.\n");
 	return ICH_RET_OK;
 }
+
+/**
+ * @brief Read a layout from the dump of an Intel ICH descriptor.
+ *
+ * @param layout Pointer where to store the layout.
+ * @param dump   The descriptor dump to read from.
+ * @param len    The length of the descriptor dump.
+ *
+ * @return 0 on success,
+ *	   1 if the descriptor couldn't be parsed.
+ */
+int layout_from_ich_descriptors(struct ich_layout *const layout, const void *const dump, const size_t len)
+{
+	static const char *regions[] = { "fd", "bios", "me", "gbe", "pd" };
+
+	struct ich_descriptors desc;
+	if (read_ich_descriptors_from_dump(dump, len, &desc))
+		return 1;
+
+	memset(layout, 0x00, sizeof(*layout));
+
+	size_t i, j;
+	for (i = 0, j = 0; i < min(desc.content.NR + 1, ARRAY_SIZE(regions)); ++i) {
+		const chipoff_t base = ICH_FREG_BASE(desc.region.FLREGs[i]);
+		const chipoff_t limit = ICH_FREG_LIMIT(desc.region.FLREGs[i]) + 0xfff;
+		if (limit <= base)
+			continue;
+		layout->entries[j].start = base;
+		layout->entries[j].end = limit;
+		layout->entries[j].included = false;
+		snprintf(layout->entries[j].name, sizeof(layout->entries[j].name), "%s", regions[i]);
+		++j;
+	}
+	layout->base.entries = layout->entries;
+	layout->base.num_entries = j;
+	return 0;
+}
+
 #endif /* ICH_DESCRIPTORS_FROM_DUMP_ONLY */
diff --git a/ich_descriptors.h b/ich_descriptors.h
index e355e54..ecf44bf 100644
--- a/ich_descriptors.h
+++ b/ich_descriptors.h
@@ -584,4 +584,6 @@
 int read_ich_descriptors_via_fdo(void *spibar, struct ich_descriptors *desc);
 int getFCBA_component_density(enum ich_chipset cs, const struct ich_descriptors *desc, uint8_t idx);
 
+int layout_from_ich_descriptors(struct ich_layout *, const void *dump, size_t len);
+
 #endif /* __ICH_DESCRIPTORS_H__ */
diff --git a/layout.c b/layout.c
index 2d53295..9bf0b03 100644
--- a/layout.c
+++ b/layout.c
@@ -35,7 +35,7 @@
 static char *include_args[MAX_ROMLAYOUT];
 static int num_include_args = 0; /* the number of valid include_args. */
 
-const struct flashrom_layout *get_global_layout(void)
+struct flashrom_layout *get_global_layout(void)
 {
 	return &layout;
 }
@@ -132,17 +132,17 @@
 }
 
 /* returns the index of the entry (or a negative value if it is not found) */
-static int find_romentry(char *name)
+static int find_romentry(struct flashrom_layout *const l, char *name)
 {
 	int i;
 
-	if (layout.num_entries == 0)
+	if (l->num_entries == 0)
 		return -1;
 
 	msg_gspew("Looking for region \"%s\"... ", name);
-	for (i = 0; i < layout.num_entries; i++) {
-		if (!strcmp(layout.entries[i].name, name)) {
-			layout.entries[i].included = 1;
+	for (i = 0; i < l->num_entries; i++) {
+		if (!strcmp(l->entries[i].name, name)) {
+			l->entries[i].included = 1;
 			msg_gspew("found.\n");
 			return i;
 		}
@@ -154,7 +154,7 @@
 /* process -i arguments
  * returns 0 to indicate success, >0 to indicate failure
  */
-int process_include_args(void)
+int process_include_args(struct flashrom_layout *const l)
 {
 	int i;
 	unsigned int found = 0;
@@ -163,7 +163,7 @@
 		return 0;
 
 	/* User has specified an area, but no layout file is loaded. */
-	if (layout.num_entries == 0) {
+	if (l->num_entries == 0) {
 		msg_gerr("Region requested (with -i \"%s\"), "
 			 "but no layout data is available.\n",
 			 include_args[0]);
@@ -171,7 +171,7 @@
 	}
 
 	for (i = 0; i < num_include_args; i++) {
-		if (find_romentry(include_args[i]) < 0) {
+		if (find_romentry(l, include_args[i]) < 0) {
 			msg_gerr("Invalid region specified: \"%s\".\n",
 				 include_args[i]);
 			return 1;
diff --git a/layout.h b/layout.h
index 349cebc..c93d754 100644
--- a/layout.h
+++ b/layout.h
@@ -57,6 +57,13 @@
 	struct romentry entry;
 };
 
-const struct flashrom_layout *get_global_layout(void);
+struct ich_layout {
+	struct flashrom_layout base;
+	struct romentry entries[5];
+};
+
+struct flashrom_layout *get_global_layout(void);
+
+int process_include_args(struct flashrom_layout *);
 
 #endif				/* !__LAYOUT_H__ */
diff --git a/libflashrom.c b/libflashrom.c
index 5447bae..1176e02 100644
--- a/libflashrom.c
+++ b/libflashrom.c
@@ -31,6 +31,8 @@
 #include "flash.h"
 #include "programmer.h"
 #include "layout.h"
+#include "hwaccess.h"
+#include "ich_descriptors.h"
 #include "libflashrom.h"
 
 /**
@@ -305,6 +307,86 @@
 }
 
 /**
+ * @brief Read a layout from the Intel ICH descriptor in the flash.
+ *
+ * Optionally verify that the layout matches the one in the given
+ * descriptor dump.
+ *
+ * @param[out] layout Points to a struct flashrom_layout pointer that
+ *                    gets set if the descriptor is read and parsed
+ *                    successfully.
+ * @param[in] flashctx Flash context to read the descriptor from flash.
+ * @param[in] dump     The descriptor dump to compare to or NULL.
+ * @param[in] len      The length of the descriptor dump.
+ *
+ * @return 0 on success,
+ *         6 if descriptor parsing isn't implemented for the host,
+ *         5 if the descriptors don't match,
+ *         4 if the descriptor dump couldn't be parsed,
+ *         3 if the descriptor on flash couldn't be parsed,
+ *         2 if the descriptor on flash couldn't be read,
+ *         1 on any other error.
+ */
+int flashrom_layout_read_from_ifd(struct flashrom_layout **const layout, struct flashctx *const flashctx,
+				  const void *const dump, const size_t len)
+{
+#ifndef __FLASHROM_LITTLE_ENDIAN__
+	return 6;
+#else
+	struct ich_layout dump_layout;
+	int ret = 1;
+
+	void *const desc = malloc(0x1000);
+	struct ich_layout *const chip_layout = malloc(sizeof(*chip_layout));
+	if (!desc || !chip_layout) {
+		msg_gerr("Out of memory!\n");
+		goto _free_ret;
+	}
+
+	if (prepare_flash_access(flashctx, true, false, false, false))
+		goto _free_ret;
+
+	msg_cinfo("Reading ich descriptor... ");
+	if (flashctx->chip->read(flashctx, desc, 0, 0x1000)) {
+		msg_cerr("Read operation failed!\n");
+		msg_cinfo("FAILED.\n");
+		ret = 2;
+		goto _finalize_ret;
+	}
+	msg_cinfo("done.\n");
+
+	if (layout_from_ich_descriptors(chip_layout, desc, 0x1000)) {
+		ret = 3;
+		goto _finalize_ret;
+	}
+
+	if (dump) {
+		if (layout_from_ich_descriptors(&dump_layout, dump, len)) {
+			ret = 4;
+			goto _finalize_ret;
+		}
+
+		if (chip_layout->base.num_entries != dump_layout.base.num_entries ||
+		    memcmp(chip_layout->entries, dump_layout.entries, sizeof(dump_layout.entries))) {
+			ret = 5;
+			goto _finalize_ret;
+		}
+	}
+
+	*layout = (struct flashrom_layout *)chip_layout;
+	ret = 0;
+
+_finalize_ret:
+	finalize_flash_access(flashctx);
+_free_ret:
+	if (ret)
+		free(chip_layout);
+	free(desc);
+	return ret;
+#endif
+}
+
+/**
  * @brief Free a layout.
  *
  * @param layout Layout to free.
diff --git a/libflashrom.h b/libflashrom.h
index 42b02f9..1937194 100644
--- a/libflashrom.h
+++ b/libflashrom.h
@@ -63,6 +63,7 @@
 int flashrom_image_verify(struct flashrom_flashctx *, const void *buffer, size_t buffer_len);
 
 struct flashrom_layout;
+int flashrom_layout_read_from_ifd(struct flashrom_layout **, struct flashrom_flashctx *, const void *dump, size_t len);
 int flashrom_layout_include_region(struct flashrom_layout *, const char *name);
 void flashrom_layout_release(struct flashrom_layout *);
 void flashrom_layout_set(struct flashrom_flashctx *, const struct flashrom_layout *);