[coreboot-gerrit] Patch set updated for coreboot: 69ecdfc cbfstool: New image format w/ required FMAP and w/o CBFS master header

Sol Boucher (solb@chromium.org) gerrit at coreboot.org
Fri May 8 20:28:25 CEST 2015


Sol Boucher (solb at chromium.org) just uploaded a new patch set to gerrit, which you can find at http://review.coreboot.org/10135

-gerrit

commit 69ecdfcfc41cf9afd9a03bf98a30135688255327
Author: Sol Boucher <solb at chromium.org>
Date:   Wed Mar 18 12:36:27 2015 -0700

    cbfstool: New image format w/ required FMAP and w/o CBFS master header
    
    These new-style firmware images use the FMAP of the root of knowledge
    about their layout, which allows them to have sections containing raw
    data whose offset and size can easily be determined at runtime or when
    modifying or flashing the image. Furthermore, they can even have
    multiple CBFSes, each of which occupies a different FMAP region. It is
    assumed that the first entry of each CBFS, including the primary one,
    will be located right at the start of its region. This means that the
    bootblock needs to be moved into its own FMAP region, but makes the
    CBFS master header obsolete because, with the exception of the version
    and alignment, all its fields are redundant once its CBFS has an entry
    in the FMAP. The version code will be addressed in a future commit
    before the new format comes into use, while the alignment will just be
    defined to 64 bytes in both cbfstool and coreboot itself, since
    there's almost no reason to ever change it in practice. The version
    code field and all necessary coreboot changes will come separately.
    
    BUG=chromium:470407
    TEST=Build panther and nyan_big coreboot.rom and image.bin images with
    and without this patch, diff their hexdumps, and note that no
    locations differ except for those that do between subsequent builds of
    the same codebase. Try working with new-style images: use fmaptool to
    produce an FMAP section from an fmd file having raw sections and
    multiple CBFSes, pass the resulting file to cbfstool create -M -F,
    then try printing its layout and CBFSes' contents, add and remove CBFS
    files, and read and write raw sections.
    BRANCH=None
    
    Change-Id: I7dd2578d2143d0cedd652fdba5b22221fcc2184a
    Signed-off-by: Sol Boucher <solb at chromium.org>
    Original-Commit-Id: 8a670322297f83135b929a5b20ff2bd0e7d2abd3
    Original-Change-Id: Ib86fb50edc66632f4e6f717909bbe4efb6c874e5
    Original-Signed-off-by: Sol Boucher <solb at chromium.org>
    Original-Reviewed-on: https://chromium-review.googlesource.com/265863
    Original-Reviewed-by: Aaron Durbin <adurbin at chromium.org>
---
 util/cbfstool/cbfs.h       |   3 +
 util/cbfstool/cbfs_image.c | 154 ++++++++++----
 util/cbfstool/cbfs_image.h |  39 +++-
 util/cbfstool/cbfstool.c   | 519 ++++++++++++++++++++++++++++++++++++---------
 util/cbfstool/common.h     |   2 +
 5 files changed, 563 insertions(+), 154 deletions(-)

diff --git a/util/cbfstool/cbfs.h b/util/cbfstool/cbfs.h
index f26b14c..ef39594 100644
--- a/util/cbfstool/cbfs.h
+++ b/util/cbfstool/cbfs.h
@@ -32,6 +32,9 @@
 #define makemagic(b3, b2, b1, b0)\
 	(((b3)<<24) | ((b2) << 16) | ((b1) << 8) | (b0))
 
+// Alignment (in bytes) to be used when no master header is present
+#define CBFS_ENTRY_ALIGNMENT 64
+
 #define CBFS_HEADER_MAGIC  0x4F524243
 #define CBFS_HEADPTR_ADDR_X86 0xFFFFFFFC
 #define CBFS_HEADER_VERSION1 0x31313131
diff --git a/util/cbfstool/cbfs_image.c b/util/cbfstool/cbfs_image.c
index d07f806..4ecb461 100644
--- a/util/cbfstool/cbfs_image.c
+++ b/util/cbfstool/cbfs_image.c
@@ -109,8 +109,11 @@ static size_t cbfs_calculate_file_header_size(const char *name)
 		align_up(strlen(name) + 1, CBFS_FILENAME_ALIGN));
 }
 
+/* Only call on legacy CBFSes possessing a master header. */
 static int cbfs_fix_legacy_size(struct cbfs_image *image, char *hdr_loc)
 {
+	assert(image);
+	assert(cbfs_is_legacy_cbfs(image));
 	// A bug in old cbfstool may produce extra few bytes (by alignment) and
 	// cause cbfstool to overwrite things after free space -- which is
 	// usually CBFS header on x86. We need to workaround that.
@@ -182,41 +185,78 @@ void cbfs_get_header(struct cbfs_header *header, void *src)
 	header->architecture = xdr_be.get32(&outheader);
 }
 
-int cbfs_image_create(struct cbfs_image *image,
-		      uint32_t architecture,
-		      uint32_t align,
-		      struct buffer *bootblock,
-		      uint32_t bootblock_offset,
-		      uint32_t header_offset,
-		      uint32_t entries_offset)
+int cbfs_image_create(struct cbfs_image *image, size_t entries_size)
 {
 	assert(image);
 	assert(image->buffer.data);
-	assert(bootblock);
 
-	struct cbfs_file *entry;
-	int32_t *rel_offset;
-	uint32_t cbfs_len;
-	void *header_loc;
-	size_t empty_header_len;
-	size_t size = image->buffer.size;
+	size_t empty_header_len = cbfs_calculate_file_header_size("");
+	uint32_t entries_offset = 0;
+	uint32_t align = CBFS_ENTRY_ALIGNMENT;
+	if (image->has_header) {
+		entries_offset = image->header.offset;
 
-	DEBUG("cbfs_image_create: bootblock=0x%x+0x%zx, "
-	      "header=0x%x+0x%zx, entries_offset=0x%x\n",
-	      bootblock_offset, bootblock->size, header_offset,
-	      sizeof(image->header), entries_offset);
+		if (entries_offset > image->buffer.size) {
+			ERROR("CBFS file entries are located outside CBFS itself\n");
+			return -1;
+		}
+
+		align = image->header.align;
+	}
 
 	// This attribute must be given in order to prove that this module
 	// correctly preserves certain CBFS properties. See the block comment
 	// near the top of this file (and the associated commit message).
-	empty_header_len = cbfs_calculate_file_header_size("");
 	if (align < empty_header_len) {
 		ERROR("CBFS must be aligned to at least %zu bytes\n",
 							empty_header_len);
 		return -1;
 	}
 
-	// Adjust legcay top-aligned address to ROM offset.
+	if (entries_size > image->buffer.size - entries_offset) {
+		ERROR("CBFS doesn't have enough space to fit its file entries\n");
+		return -1;
+	}
+
+	if (empty_header_len > entries_size) {
+		ERROR("CBFS is too small to fit any header\n");
+		return -1;
+	}
+	struct cbfs_file *entry_header =
+		(struct cbfs_file *)(image->buffer.data + entries_offset);
+	// This alignment is necessary in order to prove that this module
+	// correctly preserves certain CBFS properties. See the block comment
+	// near the top of this file (and the associated commit message).
+	entries_size -= entries_size % align;
+
+	size_t capacity = entries_size - empty_header_len;
+	LOG("Created CBFS (capacity = %zu bytes)\n", capacity);
+	return cbfs_create_empty_entry(entry_header, capacity, "");
+}
+
+int cbfs_legacy_image_create(struct cbfs_image *image,
+			     uint32_t architecture,
+			     uint32_t align,
+			     struct buffer *bootblock,
+			     uint32_t bootblock_offset,
+			     uint32_t header_offset,
+			     uint32_t entries_offset)
+{
+	assert(image);
+	assert(image->buffer.data);
+	assert(bootblock);
+
+	int32_t *rel_offset;
+	uint32_t cbfs_len;
+	void *header_loc;
+	size_t size = image->buffer.size;
+
+	DEBUG("cbfs_image_create: bootblock=0x%x+0x%zx, "
+	      "header=0x%x+0x%zx, entries_offset=0x%x\n",
+	      bootblock_offset, bootblock->size, header_offset,
+	      sizeof(image->header), entries_offset);
+
+	// Adjust legacy top-aligned address to ROM offset.
 	if (IS_TOP_ALIGNED_ADDRESS(entries_offset))
 		entries_offset = size + (int32_t)entries_offset;
 	if (IS_TOP_ALIGNED_ADDRESS(bootblock_offset))
@@ -259,6 +299,7 @@ int cbfs_image_create(struct cbfs_image *image,
 
 	header_loc = (image->buffer.data + header_offset);
 	cbfs_put_header(header_loc, &image->header);
+	image->has_header = true;
 
 	// The last 4 byte of the image contain the relative offset from the end
 	// of the image to the master header as a 32-bit signed integer. x86
@@ -274,12 +315,6 @@ int cbfs_image_create(struct cbfs_image *image,
 		      entries_offset, align);
 		return -1;
 	}
-	if (entries_offset + empty_header_len > size) {
-		ERROR("Offset (0x%x+0x%zx) exceed ROM size(0x%zx)\n",
-		      entries_offset, empty_header_len, size);
-		return -1;
-	}
-	entry = (struct cbfs_file *)(image->buffer.data + entries_offset);
 	// To calculate available length, find
 	//   e = min(bootblock, header, rel_offset) where e > entries_offset.
 	cbfs_len = size - sizeof(int32_t);
@@ -287,13 +322,9 @@ int cbfs_image_create(struct cbfs_image *image,
 		cbfs_len = bootblock_offset;
 	if (header_offset > entries_offset && header_offset < cbfs_len)
 		cbfs_len = header_offset;
-	// This alignment is necessary in order to prove that this module
-	// correctly preserves certain CBFS properties. See the block comment
-	// near the top of this file (and the associated commit message).
-	cbfs_len -= cbfs_len % align;
-	cbfs_len -= entries_offset + empty_header_len;
-	cbfs_create_empty_entry(entry, cbfs_len, "");
-	LOG("Created CBFS image (capacity = %d bytes)\n", cbfs_len);
+
+	if (cbfs_image_create(image, cbfs_len - entries_offset))
+		return -1;
 	return 0;
 }
 
@@ -305,17 +336,31 @@ int cbfs_image_from_buffer(struct cbfs_image *out, struct buffer *in,
 	assert(in->data);
 
 	buffer_clone(&out->buffer, in);
+	out->has_header = false;
+
 	void *header_loc = cbfs_find_header(in->data, in->size, offset);
 	if (header_loc) {
 		cbfs_get_header(&out->header, header_loc);
+		out->has_header = true;
 		cbfs_fix_legacy_size(out, header_loc);
+	} else if (offset != ~0u) {
+		ERROR("The -H switch is only valid on legacy images having CBFS master headers.\n");
+		return 1;
+	} else if (!cbfs_is_valid_cbfs(out)) {
+		ERROR("Selected image region is not a valid CBFS.\n");
+		return 1;
 	}
-	return !header_loc;
+
+	return 0;
 }
 
 int cbfs_copy_instance(struct cbfs_image *image, size_t copy_offset,
 			size_t copy_size)
 {
+	assert(image);
+	if (!cbfs_is_legacy_cbfs(image))
+		return -1;
+
 	struct cbfs_file *src_entry, *dst_entry;
 	struct cbfs_header *copy_header;
 	size_t align, entry_offset;
@@ -411,7 +456,8 @@ static int cbfs_add_entry_at(struct cbfs_image *image,
 	uint32_t header_size = cbfs_calculate_file_header_size(name),
 		 min_entry_size = cbfs_calculate_file_header_size("");
 	uint32_t len, target;
-	uint32_t align = image->header.align;
+	uint32_t align = image->has_header ? image->header.align :
+							CBFS_ENTRY_ALIGNMENT;
 
 	target = content_offset - header_size;
 	if (target % align)
@@ -492,6 +538,11 @@ int cbfs_add_entry(struct cbfs_image *image, struct buffer *buffer,
 	      name, content_offset, header_size, buffer->size, need_size);
 
 	if (IS_TOP_ALIGNED_ADDRESS(content_offset)) {
+		if (!cbfs_is_legacy_cbfs(image)) {
+			ERROR("Top-aligned offsets are only supported for legacy CBFSes (with master headers)\n");
+			return -1;
+		}
+
 		// legacy cbfstool takes top-aligned address.
 		uint32_t theromsize = image->header.romsize;
 		INFO("Converting top-aligned address 0x%x to offset: 0x%x\n",
@@ -798,7 +849,8 @@ int cbfs_print_entry_info(struct cbfs_image *image, struct cbfs_file *entry,
 
 int cbfs_print_directory(struct cbfs_image *image)
 {
-	cbfs_print_header_info(image);
+	if (cbfs_is_legacy_cbfs(image))
+		cbfs_print_header_info(image);
 	printf("%-30s %-10s %-12s Size\n", "Name", "Offset", "Type");
 	cbfs_walk(image, cbfs_print_entry_info, NULL);
 	return 0;
@@ -923,15 +975,17 @@ struct cbfs_header *cbfs_find_header(char *data, size_t size,
 struct cbfs_file *cbfs_find_first_entry(struct cbfs_image *image)
 {
 	assert(image);
-	return (struct cbfs_file *)(image->buffer.data +
-				   image->header.offset);
+	return image->has_header ? (struct cbfs_file *)(image->buffer.data +
+						   image->header.offset) :
+				   (struct cbfs_file *)image->buffer.data;
 }
 
 struct cbfs_file *cbfs_find_next_entry(struct cbfs_image *image,
 				       struct cbfs_file *entry)
 {
 	uint32_t addr = cbfs_get_entry_addr(image, entry);
-	int align = image->header.align;
+	int align = image->has_header ? image->header.align :
+							CBFS_ENTRY_ALIGNMENT;
 	assert(entry && cbfs_is_valid_entry(image, entry));
 	addr += ntohl(entry->offset) + ntohl(entry->len);
 	addr = align_up(addr, align);
@@ -944,6 +998,17 @@ uint32_t cbfs_get_entry_addr(struct cbfs_image *image, struct cbfs_file *entry)
 	return (int32_t)((char *)entry - image->buffer.data);
 }
 
+int cbfs_is_valid_cbfs(struct cbfs_image *image)
+{
+	return buffer_check_magic(&image->buffer, CBFS_FILE_MAGIC,
+						strlen(CBFS_FILE_MAGIC));
+}
+
+int cbfs_is_legacy_cbfs(struct cbfs_image *image)
+{
+	return image->has_header;
+}
+
 int cbfs_is_valid_entry(struct cbfs_image *image, struct cbfs_file *entry)
 {
 	uint32_t offset = cbfs_get_entry_addr(image, entry);
@@ -955,7 +1020,7 @@ int cbfs_is_valid_entry(struct cbfs_image *image, struct cbfs_file *entry)
 	buffer_clone(&entry_data, &image->buffer);
 	buffer_seek(&entry_data, offset);
 	return buffer_check_magic(&entry_data, CBFS_FILE_MAGIC,
-					strlen(CBFS_FILE_MAGIC));
+						strlen(CBFS_FILE_MAGIC));
 }
 
 int cbfs_create_empty_entry(struct cbfs_file *entry,
@@ -999,7 +1064,8 @@ int32_t cbfs_locate_entry(struct cbfs_image *image, const char *name,
 
 	/* Default values: allow fitting anywhere in ROM. */
 	if (!page_size)
-		page_size = image->header.romsize;
+		page_size = image->has_header ? image->header.romsize :
+							image->buffer.size;
 	if (!align)
 		align = 1;
 
@@ -1007,9 +1073,11 @@ int32_t cbfs_locate_entry(struct cbfs_image *image, const char *name,
 		ERROR("Input file size (%d) greater than page size (%d).\n",
 		      size, page_size);
 
-	if (page_size % image->header.align)
+	uint32_t image_align = image->has_header ? image->header.align :
+							CBFS_ENTRY_ALIGNMENT;
+	if (page_size % image_align)
 		WARN("%s: Page size (%#x) not aligned with CBFS image (%#x).\n",
-		     __func__, page_size, image->header.align);
+		     __func__, page_size, image_align);
 
 	/* TODO Old cbfstool always assume input is a stage file (and adding
 	 * sizeof(cbfs_stage) for header. We should fix that by adding "-t"
diff --git a/util/cbfstool/cbfs_image.h b/util/cbfstool/cbfs_image.h
index ddee3a7..024edc5 100644
--- a/util/cbfstool/cbfs_image.h
+++ b/util/cbfstool/cbfs_image.h
@@ -28,6 +28,9 @@
 
 struct cbfs_image {
 	struct buffer buffer;
+	/* An image has a header iff it's a legacy CBFS. */
+	bool has_header;
+	/* Only meaningful if has_header is selected. */
 	struct cbfs_header header;
 };
 
@@ -37,18 +40,28 @@ void cbfs_put_header(void *dest, const struct cbfs_header *header);
 /* Or deserialize into host-native format */
 void cbfs_get_header(struct cbfs_header *header, void *src);
 
+/* Populates a CBFS with a single empty entry filling all available space
+ * (entries_size bytes). If image's header field is already present, its
+ * contents will be used to place an empty entry of the requested length at the
+ * appropriate position in the existing buffer; otherwise, if not has_header,
+ * the first entries_size bytes of buffer will be filled exclusively with the
+ * single empty entry (and no CBFS master header).
+ * Returns 0 on success, otherwise nonzero. */
+int cbfs_image_create(struct cbfs_image *image, size_t entries_size);
+
 /* Creates an empty CBFS image by given size, and description to its content
  * (bootblock, align, header location, starting offset of CBFS entries).
  * The output image will contain a valid cbfs_header, with one cbfs_file
  * entry with type CBFS_COMPONENT_NULL, with max available size.
- * Returns 0 on success, otherwise none-zero. */
-int cbfs_image_create(struct cbfs_image *image,
-		      uint32_t arch,
-		      uint32_t align,
-		      struct buffer *bootblock,
-		      uint32_t bootblock_offset,
-		      uint32_t header_offset,
-		      uint32_t entries_offset);
+ * Only call this if you want a legacy CBFS with a master header.
+ * Returns 0 on success, otherwise nonzero. */
+int cbfs_legacy_image_create(struct cbfs_image *image,
+			      uint32_t arch,
+			      uint32_t align,
+			      struct buffer *bootblock,
+			      uint32_t bootblock_offset,
+			      uint32_t header_offset,
+			      uint32_t entries_offset);
 
 /* Constructs a cbfs_image from a buffer. The resulting image contains a shallow
  * copy of the buffer; releasing either one is the legal way to clean up after
@@ -57,7 +70,8 @@ int cbfs_image_create(struct cbfs_image *image,
 int cbfs_image_from_buffer(struct cbfs_image *out, struct buffer *in,
 			   uint32_t offset);
 
-/* Create a duplicate CBFS image. Returns 0 on success, otherwise non-zero. */
+/* Create a duplicate CBFS image. Returns 0 on success, otherwise non-zero.
+ * Will not succeed on new-style images without a master header. */
 int cbfs_copy_instance(struct cbfs_image *image, size_t copy_offset,
 			size_t copy_size);
 
@@ -74,6 +88,7 @@ int cbfs_export_entry(struct cbfs_image *image, const char *entry_name,
 
 /* Adds an entry to CBFS image by given name and type. If content_offset is
  * non-zero, try to align "content" (CBFS_SUBHEADER(p)) at content_offset.
+ * Note that top-aligned addresses are only supported for legacy CBFSes.
  * Returns 0 on success, otherwise non-zero. */
 int cbfs_add_entry(struct cbfs_image *image, struct buffer *buffer,
 		   const char *name, uint32_t type, uint32_t content_offset);
@@ -126,6 +141,12 @@ struct cbfs_file *cbfs_find_next_entry(struct cbfs_image *image,
  * This is different from entry->offset (pointer to content). */
 uint32_t cbfs_get_entry_addr(struct cbfs_image *image, struct cbfs_file *entry);
 
+/* Returns 1 if valid new-format CBFS (without a master header), otherwise 0. */
+int cbfs_is_valid_cbfs(struct cbfs_image *image);
+
+/* Returns 1 if valid legacy CBFS (with a master header), otherwise 0. */
+int cbfs_is_legacy_cbfs(struct cbfs_image *image);
+
 /* Returns 1 if entry has valid data (by checking magic number), otherwise 0. */
 int cbfs_is_valid_entry(struct cbfs_image *image, struct cbfs_file *entry);
 
diff --git a/util/cbfstool/cbfstool.c b/util/cbfstool/cbfstool.c
index a242a94..cb2f01a 100644
--- a/util/cbfstool/cbfstool.c
+++ b/util/cbfstool/cbfstool.c
@@ -46,10 +46,12 @@ struct command {
 static struct param {
 	partitioned_file_t *image_file;
 	struct buffer *image_region;
-	char *name;
-	char *filename;
-	char *bootblock;
-	char *ignore_section;
+	const char *name;
+	const char *filename;
+	const char *fmap;
+	const char *region_name;
+	const char *bootblock;
+	const char *ignore_section;
 	uint64_t u64val;
 	uint32_t type;
 	uint32_t baseaddress;
@@ -65,8 +67,11 @@ static struct param {
 	uint32_t pagesize;
 	uint32_t cbfsoffset;
 	uint32_t cbfsoffset_assigned;
-	uint32_t top_aligned;
 	uint32_t arch;
+	bool top_aligned;
+	bool fill_partial_upward;
+	bool fill_partial_downward;
+	bool show_immutable;
 	int fit_empty_entries;
 	comp_algo algo;
 	/* for linux payloads */
@@ -77,8 +82,22 @@ static struct param {
 	.arch = CBFS_ARCHITECTURE_UNKNOWN,
 	.algo = CBFS_COMPRESS_NONE,
 	.headeroffset = ~0,
+	.region_name = SECTION_NAME_PRIMARY_CBFS,
 };
 
+static bool region_is_flashmap(const char *region)
+{
+	return partitioned_file_region_check_magic(param.image_file, region,
+					FMAP_SIGNATURE, strlen(FMAP_SIGNATURE));
+}
+
+/* @return Same as cbfs_is_valid_cbfs(), but for a named region. */
+static bool region_is_modern_cbfs(const char *region)
+{
+	return partitioned_file_region_check_magic(param.image_file, region,
+				CBFS_FILE_MAGIC, strlen(CBFS_FILE_MAGIC));
+}
+
 typedef int (*convert_buffer_t)(struct buffer *buffer, uint32_t *offset);
 
 static int cbfs_add_integer_component(const char *name,
@@ -147,10 +166,8 @@ static int cbfs_add_component(const char *filename,
 	}
 
 	struct cbfs_image image;
-	if (cbfs_image_from_buffer(&image, param.image_region, headeroffset)) {
-		ERROR("Selected image region is not a CBFS.\n");
+	if (cbfs_image_from_buffer(&image, param.image_region, headeroffset))
 		return 1;
-	}
 
 	struct buffer buffer;
 	if (buffer_from_file(&buffer, filename) != 0) {
@@ -307,10 +324,8 @@ static int cbfs_remove(void)
 
 	struct cbfs_image image;
 	if (cbfs_image_from_buffer(&image, param.image_region,
-							param.headeroffset)) {
-		ERROR("Selected image region is not a CBFS.\n");
+							param.headeroffset))
 		return 1;
-	}
 
 	if (cbfs_remove_entry(&image, param.name) != 0) {
 		ERROR("Removing file '%s' failed.\n",
@@ -323,6 +338,24 @@ static int cbfs_remove(void)
 
 static int cbfs_create(void)
 {
+	struct cbfs_image image;
+	memset(&image, 0, sizeof(image));
+	buffer_clone(&image.buffer, param.image_region);
+
+	if (param.fmap) {
+		if (param.arch != CBFS_ARCHITECTURE_UNKNOWN || param.size ||
+						param.baseaddress_assigned ||
+						param.headeroffset_assigned ||
+						param.cbfsoffset_assigned ||
+							param.alignment ||
+							param.bootblock) {
+			ERROR("Since -M was provided, -m, -s, -b, -o, -H, -a, and -B should be omitted\n");
+			return 1;
+		}
+
+		return cbfs_image_create(&image, image.buffer.size);
+	}
+
 	if (param.arch == CBFS_ARCHITECTURE_UNKNOWN) {
 		ERROR("You need to specify -m/--machine arch.\n");
 		return 1;
@@ -343,9 +376,8 @@ static int cbfs_create(void)
 	if (!param.baseaddress_assigned) {
 		if (param.arch == CBFS_ARCHITECTURE_X86) {
 			// Make sure there's at least enough room for rel_offset
-			param.baseaddress = param.size - (
-				bootblock.size > sizeof(int32_t) ?
-				bootblock.size : sizeof(int32_t));
+			param.baseaddress = param.size -
+					MAX(bootblock.size, sizeof(int32_t));
 			DEBUG("x86 -> bootblock lies at end of ROM (%#x).\n",
 			      param.baseaddress);
 		} else {
@@ -379,25 +411,15 @@ static int cbfs_create(void)
 		}
 	}
 
-	struct cbfs_image image;
-	if (!cbfs_image_from_buffer(&image, param.image_region, -1))
-		// It *already* contains a CBFS?! This should be a blank file.
-		assert(false);
-
-	if (cbfs_image_create(&image,
-			      param.arch,
-			      param.alignment,
-			      &bootblock,
-			      param.baseaddress,
-			      param.headeroffset,
-			      param.cbfsoffset) != 0) {
-		ERROR("Failed to initialize CBFS structure.\n");
-		buffer_delete(&bootblock);
-		return 1;
-	}
-
+	int ret = cbfs_legacy_image_create(&image,
+					   param.arch,
+					   param.alignment,
+					   &bootblock,
+					   param.baseaddress,
+					   param.headeroffset,
+					   param.cbfsoffset);
 	buffer_delete(&bootblock);
-	return 0;
+	return ret;
 }
 
 static int cbfs_locate(void)
@@ -414,8 +436,11 @@ static int cbfs_locate(void)
 
 	struct cbfs_image image;
 	if (cbfs_image_from_buffer(&image, param.image_region,
-							param.headeroffset)) {
-		ERROR("Selected image region is not a CBFS.\n");
+							param.headeroffset))
+		return 1;
+
+	if (!cbfs_is_legacy_cbfs(&image) && param.top_aligned) {
+		ERROR("The -T switch is only valid on legacy images having CBFS master headers\n");
 		return 1;
 	}
 
@@ -445,15 +470,76 @@ static int cbfs_locate(void)
 	return 0;
 }
 
+static int cbfs_layout(void)
+{
+	const struct fmap *fmap = partitioned_file_get_fmap(param.image_file);
+	if (!fmap) {
+		LOG("This is a legacy image composed entirely of a single CBFS.\n");
+		return 1;
+	}
+
+	printf("This image contains the following sections that can be %s with this tool:\n",
+			param.show_immutable ? "accessed" : "manipulated");
+	puts("");
+	for (unsigned index = 0; index < fmap->nareas; ++index) {
+		const struct fmap_area *current = fmap->areas + index;
+
+		bool readonly = partitioned_file_fmap_count(param.image_file,
+			partitioned_file_fmap_select_children_of, current) ||
+				region_is_flashmap((const char *)current->name);
+		if (!param.show_immutable && readonly)
+			continue;
+
+		printf("'%s'", current->name);
+
+		// Detect consecutive sections that describe the same region and
+		// show them as aliases. This cannot find equivalent entries
+		// that aren't adjacent; however, fmaptool doesn't generate
+		// FMAPs with such sections, so this convenience feature works
+		// for all but the strangest manually created FMAP binaries.
+		// TODO: This could be done by parsing the FMAP into some kind
+		// of tree that had duplicate lists in addition to child lists,
+		// which would allow covering that weird, unlikely case as well.
+		unsigned lookahead;
+		for (lookahead = 1; index + lookahead < fmap->nareas;
+								++lookahead) {
+			const struct fmap_area *consecutive =
+					fmap->areas + index + lookahead;
+			if (consecutive->offset != current->offset ||
+					consecutive->size != current->size)
+				break;
+			printf(", '%s'", consecutive->name);
+		}
+		if (lookahead > 1)
+			fputs(" are aliases for the same region", stdout);
+
+		const char *qualifier = "";
+		if (readonly)
+			qualifier = "read-only, ";
+		else if (region_is_modern_cbfs((const char *)current->name))
+			qualifier = "CBFS, ";
+		printf(" (%ssize %u)\n", qualifier, current->size);
+
+		index += lookahead - 1;
+	}
+	puts("");
+
+	if (param.show_immutable) {
+		puts("It is at least possible to perform the read action on every section listed above.");
+	} else {
+		puts("It is possible to perform either the write action or the CBFS add/remove actions on every section listed above.");
+		puts("To see the image's read-only sections as well, rerun with the -w option.");
+	}
+
+	return 0;
+}
+
 static int cbfs_print(void)
 {
 	struct cbfs_image image;
 	if (cbfs_image_from_buffer(&image, param.image_region,
-							param.headeroffset)) {
-		ERROR("Selected image region is not a CBFS.\n");
+							param.headeroffset))
 		return 1;
-	}
-
 	cbfs_print_directory(&image);
 	return 0;
 }
@@ -472,14 +558,93 @@ static int cbfs_extract(void)
 
 	struct cbfs_image image;
 	if (cbfs_image_from_buffer(&image, param.image_region,
-							param.headeroffset)) {
-		ERROR("Selected image region is not a CBFS.\n");
+							param.headeroffset))
 		return 1;
-	}
 
 	return cbfs_export_entry(&image, param.name, param.filename);
 }
 
+static int cbfs_write(void)
+{
+	if (!param.filename) {
+		ERROR("You need to specify a valid input -f/--file.\n");
+		return 1;
+	}
+	if (!partitioned_file_is_partitioned(param.image_file)) {
+		ERROR("This operation isn't valid on legacy images having CBFS master headers\n");
+		return 1;
+	}
+
+	if (region_is_modern_cbfs(param.region_name)) {
+		ERROR("Target image region '%s' is a CBFS and must be manipulated using add and remove\n",
+							param.region_name);
+		return 1;
+	}
+
+	struct buffer new_content;
+	if (buffer_from_file(&new_content, param.filename))
+		return 1;
+
+	if (buffer_check_magic(&new_content, FMAP_SIGNATURE,
+						strlen(FMAP_SIGNATURE))) {
+		ERROR("File '%s' appears to be an FMAP and cannot be added to an existing image\n",
+								param.filename);
+		buffer_delete(&new_content);
+		return 1;
+	}
+	if (buffer_check_magic(&new_content, CBFS_FILE_MAGIC,
+						strlen(CBFS_FILE_MAGIC))) {
+		ERROR("File '%s' appears to be a CBFS and cannot be inserted into a raw region\n",
+								param.filename);
+		buffer_delete(&new_content);
+		return 1;
+	}
+
+	unsigned offset = 0;
+	if (param.fill_partial_upward && param.fill_partial_downward) {
+		ERROR("You may only specify one of -u and -d.\n");
+		buffer_delete(&new_content);
+		return 1;
+	} else if (!param.fill_partial_upward && !param.fill_partial_downward) {
+		if (new_content.size != param.image_region->size) {
+			ERROR("File to add is %zu bytes and would not fill %zu-byte target region (did you mean to pass either -u or -d?)\n",
+				new_content.size, param.image_region->size);
+			buffer_delete(&new_content);
+			return 1;
+		}
+	} else {
+		if (new_content.size > param.image_region->size) {
+			ERROR("File to add is %zu bytes and would overflow %zu-byte target region\n",
+				new_content.size, param.image_region->size);
+			buffer_delete(&new_content);
+			return 1;
+		}
+		WARN("Written area will abut %s of target region: any unused space will keep its current contents\n",
+				param.fill_partial_upward ? "bottom" : "top");
+		if (param.fill_partial_downward)
+			offset = param.image_region->size - new_content.size;
+	}
+
+	memcpy(param.image_region->data + offset, new_content.data,
+							new_content.size);
+	buffer_delete(&new_content);
+	return 0;
+}
+
+static int cbfs_read(void)
+{
+	if (!param.filename) {
+		ERROR("You need to specify a valid output -f/--file.\n");
+		return 1;
+	}
+	if (!partitioned_file_is_partitioned(param.image_file)) {
+		ERROR("This operation isn't valid on legacy images having CBFS master headers\n");
+		return 1;
+	}
+
+	return buffer_write_file(param.image_region, param.filename);
+}
+
 static int cbfs_update_fit(void)
 {
 	if (!param.name) {
@@ -495,10 +660,8 @@ static int cbfs_update_fit(void)
 
 	struct cbfs_image image;
 	if (cbfs_image_from_buffer(&image, param.image_region,
-							param.headeroffset)) {
-		ERROR("Selected image region is not a CBFS.\n");
+							param.headeroffset))
 		return 1;
-	}
 
 	return fit_update_table(&image, param.fit_empty_entries, param.name);
 }
@@ -517,28 +680,40 @@ static int cbfs_copy(void)
 
 	struct cbfs_image image;
 	if (cbfs_image_from_buffer(&image, param.image_region,
-							param.headeroffset)) {
-		ERROR("Selected image region is not a CBFS.\n");
+							param.headeroffset))
+		return 1;
+
+	if (!cbfs_is_legacy_cbfs(&image)) {
+		ERROR("This operation is only valid on legacy images having CBFS master headers\n");
 		return 1;
 	}
 
 	return cbfs_copy_instance(&image, param.copyoffset, param.size);
 }
 
+static bool cbfs_is_legacy_format(struct buffer *buffer)
+{
+	// Legacy CBFSes are those containing the deprecated CBFS master header.
+	return cbfs_find_header(buffer->data, buffer->size, -1);
+}
+
 static const struct command commands[] = {
-	{"add", "H:f:n:t:b:vh?", cbfs_add, true, true},
-	{"add-flat-binary", "H:f:n:l:e:c:b:vh?", cbfs_add_flat_binary, true,
+	{"add", "H:r:f:n:t:b:vh?", cbfs_add, true, true},
+	{"add-flat-binary", "H:r:f:n:l:e:c:b:vh?", cbfs_add_flat_binary, true,
 									true},
-	{"add-payload", "H:f:n:t:c:b:vh?C:I:", cbfs_add_payload, true, true},
-	{"add-stage", "H:f:n:t:c:b:S:vh?", cbfs_add_stage, true, true},
-	{"add-int", "H:i:n:b:vh?", cbfs_add_integer, true, true},
-	{"copy", "H:D:s:", cbfs_copy, true, true},
-	{"create", "s:B:b:H:a:o:m:vh?", cbfs_create, true, true},
-	{"extract", "H:n:f:vh?", cbfs_extract, true, false},
-	{"locate", "H:f:n:P:a:Tvh?", cbfs_locate, true, false},
-	{"print", "H:vh?", cbfs_print, true, false},
-	{"remove", "H:n:vh?", cbfs_remove, true, true},
-	{"update-fit", "H:n:x:vh?", cbfs_update_fit, true, true},
+	{"add-payload", "H:r:f:n:t:c:b:C:I:vh?", cbfs_add_payload, true, true},
+	{"add-stage", "H:r:f:n:t:c:b:S:vh?", cbfs_add_stage, true, true},
+	{"add-int", "H:r:i:n:b:vh?", cbfs_add_integer, true, true},
+	{"copy", "H:D:s:h?", cbfs_copy, true, true},
+	{"create", "M:r:s:B:b:H:a:o:m:vh?", cbfs_create, true, true},
+	{"extract", "H:r:n:f:vh?", cbfs_extract, true, false},
+	{"locate", "H:r:f:n:P:a:Tvh?", cbfs_locate, true, false},
+	{"layout", "wvh?", cbfs_layout, false, false},
+	{"print", "H:r:vh?", cbfs_print, true, false},
+	{"read", "r:f:vh?", cbfs_read, true, false},
+	{"remove", "H:r:n:vh?", cbfs_remove, true, true},
+	{"update-fit", "H:r:n:x:vh?", cbfs_update_fit, true, true},
+	{"write", "r:f:udvh?", cbfs_write, true, true},
 };
 
 static struct option long_options[] = {
@@ -551,6 +726,10 @@ static struct option long_options[] = {
 	{"empty-fits",    required_argument, 0, 'x' },
 	{"entry-point",   required_argument, 0, 'e' },
 	{"file",          required_argument, 0, 'f' },
+	{"fill-downward", no_argument,       0, 'd' },
+	{"fill-upward",   no_argument,       0, 'u' },
+	{"flashmap",      required_argument, 0, 'M' },
+	{"fmap-regions",  required_argument, 0, 'r' },
 	{"header-offset", required_argument, 0, 'H' },
 	{"help",          no_argument,       0, 'h' },
 	{"ignore-sec",    required_argument, 0, 'S' },
@@ -565,53 +744,121 @@ static struct option long_options[] = {
 	{"top-aligned",   required_argument, 0, 'T' },
 	{"type",          required_argument, 0, 't' },
 	{"verbose",       no_argument,       0, 'v' },
+	{"with-readonly", no_argument,       0, 'w' },
 	{NULL,            0,                 0,  0  }
 };
 
+static int dispatch_command(struct command command)
+{
+	if (command.accesses_region) {
+		assert(param.image_file);
+
+		if (partitioned_file_is_partitioned(param.image_file)) {
+			LOG("Performing operation on '%s' region...\n",
+					param.region_name);
+		}
+		if (!partitioned_file_read_region(param.image_region,
+					param.image_file, param.region_name)) {
+			ERROR("The image will be left unmodified.\n");
+			return 1;
+		}
+
+		if (command.modifies_region) {
+			// We (intentionally) don't support overwriting the FMAP
+			// section. If you find yourself wanting to do this,
+			// consider creating a new image rather than performing
+			// whatever hacky transformation you were planning.
+			if (region_is_flashmap(param.region_name)) {
+				ERROR("Image region '%s' is read-only because it contains the FMAP.\n",
+							param.region_name);
+				ERROR("The image will be left unmodified.\n");
+				return 1;
+			}
+			// We don't allow writing raw data to regions that
+			// contain nested regions, since doing so would
+			// overwrite all such subregions.
+			if (partitioned_file_region_contains_nested(
+					param.image_file, param.region_name)) {
+				ERROR("Image region '%s' is read-only because it contains nested regions.\n",
+							param.region_name);
+				ERROR("The image will be left unmodified.\n");
+				return 1;
+			}
+		}
+	}
+
+	if (command.function()) {
+		if (partitioned_file_is_partitioned(param.image_file)) {
+			ERROR("Failed while operating on '%s' region!\n",
+							param.region_name);
+			ERROR("The image will be left unmodified.\n");
+		}
+		return 1;
+	}
+
+	return 0;
+}
+
 static void usage(char *name)
 {
 	printf
 	    ("cbfstool: Management utility for CBFS formatted ROM images\n\n"
 	     "USAGE:\n" " %s [-h]\n"
 	     " %s FILE COMMAND [-v] [PARAMETERS]...\n\n" "OPTIONs:\n"
-	     "  -H header_offset  Do not search for header, use this offset\n"
-	     "  -T                Output top-aligned memory address\n"
-	     "  -v                Provide verbose output\n"
-	     "  -h                Display this help message\n\n"
+	     "  -H header_offset Do not search for header; use this offset*\n"
+	     "  -T               Output top-aligned memory address*\n"
+	     "  -u               Accept short data; fill upward/from bottom\n"
+	     "  -d               Accept short data; fill downward/from top\n"
+	     "  -v               Provide verbose output\n"
+	     "  -h               Display this help message\n\n"
 	     "COMMANDs:\n"
-	     " add -f FILE -n NAME -t TYPE [-b base-address]               "
+	     " add [-r image,regions] -f FILE -n NAME -t TYPE \\\n"
+	     "        [-b base-address]                                    "
 			"Add a component\n"
-	     " add-payload -f FILE -n NAME [-c compression] [-b base]      "
+	     " add-payload [-r image,regions] -f FILE -n NAME \\\n"
+	     "        [-c compression] [-b base-address]                   "
 			"Add a payload to the ROM\n"
 	     "        (linux specific: [-C cmdline] [-I initrd])\n"
-	     " add-stage -f FILE -n NAME [-c compression] [-b base] \\\n"
-	     "        [-S section-to-ignore]                               "
+	     " add-stage [-r image,regions] -f FILE -n NAME \\\n"
+	     "        [-c compression] [-b base] [-S section-to-ignore]    "
 			"Add a stage to the ROM\n"
-	     " add-flat-binary -f FILE -n NAME -l load-address \\\n"
-	     "        -e entry-point [-c compression] [-b base]            "
+	     " add-flat-binary [-r image,regions] -f FILE -n NAME \\\n"
+	     "        -l load-address -e entry-point [-c compression] \\\n"
+	     "        [-b base]                                            "
 			"Add a 32bit flat mode binary\n"
-	     " add-int -i INTEGER -n NAME [-b base]                        "
+	     " add-int [-r image,regions] -i INTEGER -n NAME [-b base]     "
 			"Add a raw 64-bit integer value\n"
-	     " remove -n NAME                                              "
+	     " remove [-r image,regions] -n NAME                           "
 			"Remove a component\n"
 	     " copy -D new_header_offset -s region size \\\n"
-	     "        [-H source header offset]         "
-			"Create a copy (duplicate) cbfs instance\n"
-	     " create -s size -m ARCH [-B bootblock] [-b bootblock offset] \\\n"
-	     "        [-o CBFS offset] [-H header offset] [-a align]       "
-			"Create a ROM file\n"
-	     " locate -f FILE -n NAME [-P page-size] [-a align] [-T]       "
+	     "        [-H source header offset]                            "
+			"Create a copy (duplicate) cbfs instance*\n"
+	     " create -m ARCH -s size [-b bootblock offset] \\\n"
+	     "        [-o CBFS offset] [-H header offset] [-B bootblock] \\\n"
+	     "        [-a align]                                           "
+			"Create a legacy ROM file with CBFS master header*\n"
+	     " create -M flashmap [-r list,of,regions,containing,cbfses]   "
+			"Create a new-style partitioned firmware image\n"
+	     " locate [-r image,regions] -f FILE -n NAME [-P page-size] \\\n"
+	     "        [-a align] [-T]                                      "
 			"Find a place for a file of that size\n"
-	     " print                                                       "
+	     " layout [-w]                                                 "
+			"List mutable (or, with -w, readable) image regions\n"
+	     " print [-r image,regions]                                    "
 			"Show the contents of the ROM\n"
-	     " extract -n NAME -f FILE                                     "
+	     " extract [-r image,regions] -n NAME -f FILE                  "
 			"Extracts a raw payload from ROM\n"
-	     " update-fit -n MICROCODE_BLOB_NAME -x EMTPY_FIT_ENTRIES\n  "
+	     " write -r image,regions -f file [-u | -d]                    "
+			"Write file into same-size [or larger] raw region\n"
+	     " read [-r fmap-region] -f file                               "
+			"Extract raw region contents into binary file\n"
+	     " update-fit [-r image,regions] -n MICROCODE_BLOB_NAME \\\n"
+	     "          -x EMTPY_FIT_ENTRIES                               "
 			"Updates the FIT table with microcode entries\n"
 	     "\n"
 	     "OFFSETs:\n"
 	     "  Numbers accompanying -b, -H, and -o switches may be provided\n"
-	     "  in two possible formats: if their value is greater than\n"
+	     "  in two possible formats*: if their value is greater than\n"
 	     "  0x80000000, they are interpreted as a top-aligned x86 memory\n"
 	     "  address; otherwise, they are treated as an offset into flash.\n"
 	     "ARCHes:\n"
@@ -619,6 +866,22 @@ static void usage(char *name)
 	     "TYPEs:\n", name, name
 	    );
 	print_supported_filetypes();
+
+	printf(
+	     "\n* Note that these actions and switches are only valid when\n"
+	     "  working with legacy images whose structure is described\n"
+	     "  primarily by a CBFS master header. New-style images, in\n"
+	     "  contrast, exclusively make use of an FMAP to describe their\n"
+	     "  layout: this must minimally contain an '%s' section\n"
+	     "  specifying the location of this FMAP itself and a '%s'\n"
+	     "  section describing the primary CBFS. It should also be noted\n"
+	     "  that, when working with such images, the -F and -r switches\n"
+	     "  default to '%s' for convenience, and both the -b switch to\n"
+	     "  CBFS operations and the output of the locate action become\n"
+	     "  relative to the selected CBFS region's lowest address.\n",
+	     SECTION_NAME_FMAP, SECTION_NAME_PRIMARY_CBFS,
+	     SECTION_NAME_PRIMARY_CBFS
+	     );
 }
 
 int main(int argc, char **argv)
@@ -678,6 +941,12 @@ int main(int argc, char **argv)
 					WARN("Unknown compression '%s'"
 					     " ignored.\n", optarg);
 				break;
+			case 'M':
+				param.fmap = optarg;
+				break;
+			case 'r':
+				param.region_name = optarg;
+				break;
 			case 'b':
 				param.baseaddress = strtoul(optarg, NULL, 0);
 				// baseaddress may be zero on non-x86, so we
@@ -729,7 +998,16 @@ int main(int argc, char **argv)
 				param.u64val = strtoull(optarg, NULL, 0);
 				break;
 			case 'T':
-				param.top_aligned = 1;
+				param.top_aligned = true;
+				break;
+			case 'u':
+				param.fill_partial_upward = true;
+				break;
+			case 'd':
+				param.fill_partial_downward = true;
+				break;
+			case 'w':
+				param.show_immutable = true;
 				break;
 			case 'x':
 				param.fit_empty_entries = strtol(optarg, NULL, 0);
@@ -759,20 +1037,33 @@ int main(int argc, char **argv)
 		}
 
 		if (commands[i].function == cbfs_create) {
-			if (param.size == 0) {
-				ERROR("You need to specify a valid -s/--size.\n");
+			if (param.fmap) {
+				struct buffer flashmap;
+				if (buffer_from_file(&flashmap, param.fmap))
+					return 1;
+				param.image_file = partitioned_file_create(
+							image_name, &flashmap);
+				buffer_delete(&flashmap);
+			} else if (param.size) {
+				param.image_file = partitioned_file_create_flat(
+							image_name, param.size);
+			} else {
+				ERROR("You need to specify a valid -M/--flashmap or -s/--size.\n");
 				return 1;
 			}
-			param.image_file = partitioned_file_create_flat(
-							image_name, param.size);
 		} else {
 			param.image_file =
 				partitioned_file_reopen(image_name,
-						partitioned_file_open_as_flat);
+							cbfs_is_legacy_format);
 		}
 		if (!param.image_file)
 			return 1;
 
+		unsigned num_regions = 1;
+		for (const char *list = strchr(param.region_name, ','); list;
+						list = strchr(list + 1, ','))
+			++num_regions;
+
 		// If the action needs to read an image region, as indicated by
 		// having accesses_region set in its command struct, that
 		// region's buffer struct will be stored here and the client
@@ -781,36 +1072,60 @@ int main(int argc, char **argv)
 		// since this behavior can be requested via its modifies_region
 		// field. Additionally, it should never free the region buffer,
 		// as that is performed automatically once it completes.
-		struct buffer image_region;
-		memset(&image_region, 0, sizeof(image_region));
+		struct buffer image_regions[num_regions];
+		memset(image_regions, 0, sizeof(image_regions));
+
+		bool seen_primary_cbfs = false;
+		char region_name_scratch[strlen(param.region_name) + 1];
+		strcpy(region_name_scratch, param.region_name);
+		param.region_name = strtok(region_name_scratch, ",");
+		for (unsigned region = 0; region < num_regions; ++region) {
+			if (!param.region_name) {
+				ERROR("Encountered illegal degenerate region name in -r list\n");
+				ERROR("The image will be left unmodified.\n");
+				partitioned_file_close(param.image_file);
+				return 1;
+			}
 
-		if (commands[i].accesses_region) {
-			assert(param.image_file);
+			if (strcmp(param.region_name, SECTION_NAME_PRIMARY_CBFS)
+									== 0)
+				seen_primary_cbfs = true;
 
-			if (!partitioned_file_read_region(&image_region,
-				param.image_file, SECTION_NAME_PRIMARY_CBFS)) {
+			param.image_region = image_regions + region;
+			if (dispatch_command(commands[i])) {
 				partitioned_file_close(param.image_file);
 				return 1;
 			}
-			param.image_region = &image_region;
+
+			param.region_name = strtok(NULL, ",");
 		}
 
-		int error = commands[i].function();
+		if (commands[i].function == cbfs_create && !seen_primary_cbfs) {
+			ERROR("The creation -r list must include the mandatory '%s' section.\n",
+						SECTION_NAME_PRIMARY_CBFS);
+			ERROR("The image will be left unmodified.\n");
+			partitioned_file_close(param.image_file);
+			return 1;
+		}
 
-		if (!error && commands[i].modifies_region) {
+		if (commands[i].modifies_region) {
 			assert(param.image_file);
 			assert(commands[i].accesses_region);
-
-			if (!partitioned_file_write_region(param.image_file,
-							&image_region)) {
-				partitioned_file_close(param.image_file);
-				return 1;
+			for (unsigned region = 0; region < num_regions;
+								++region) {
+
+				if (!partitioned_file_write_region(
+							param.image_file,
+						image_regions + region)) {
+					partitioned_file_close(
+							param.image_file);
+					return 1;
+				}
 			}
 		}
 
 		partitioned_file_close(param.image_file);
-
-		return error;
+		return 0;
 	}
 
 	ERROR("Unknown command '%s'.\n", cmd);
diff --git a/util/cbfstool/common.h b/util/cbfstool/common.h
index 416d0a4..831d3eb 100644
--- a/util/cbfstool/common.h
+++ b/util/cbfstool/common.h
@@ -47,6 +47,8 @@ extern int verbose;
 /* Helpers */
 #define ARRAY_SIZE(a) (int)(sizeof(a) / sizeof((a)[0]))
 #define ALIGN(val, by) (((val) + (by)-1)&~((by)-1))
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
 
 #define unused __attribute__((unused))
 



More information about the coreboot-gerrit mailing list