flashrom.c: Add function to get a flattened view of the chip erase blocks

Add a function to flatten out the addresses of the flash chip as per the
different erase functions. This function will return a list of layouts
which is dynamically allocated. So after use all the layouts as well as
the list itself should be freed. The free_erase_layout function does
that.

Change-Id: Iafe78de00daa55f7114bd4ce09465dd88074ece4
Signed-off-by: Aarya Chaumal <aarya.chaumal@gmail.com>
Reviewed-on: https://review.coreboot.org/c/flashrom-stable/+/72551
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Nico Huber <nico.h@gmx.de>
diff --git a/flashrom.c b/flashrom.c
index b6548c0..634820e 100644
--- a/flashrom.c
+++ b/flashrom.c
@@ -920,12 +920,148 @@
 	chipoff_t erase_end;
 };
 
+struct eraseblock_data {
+	chipoff_t start_addr;
+	chipoff_t end_addr;
+	bool selected;
+	size_t block_num;
+	size_t first_sub_block_index;
+	size_t last_sub_block_index;
+};
+
+struct erase_layout {
+	struct eraseblock_data* layout_list;
+	size_t block_count;
+	const struct block_eraser *eraser;
+};
+
 static bool explicit_erase(const struct walk_info *const info)
 {
 	/* For explicit erase, we are called without new contents. */
 	return !info->newcontents;
 }
 
+static size_t calculate_block_count(const struct flashchip *chip, size_t eraser_idx)
+{
+	size_t block_count = 0;
+
+	chipoff_t addr = 0;
+	for (size_t i = 0; addr < chip->total_size * 1024; i++) {
+		const struct eraseblock *block = &chip->block_erasers[eraser_idx].eraseblocks[i];
+		block_count += block->count;
+		addr += block->size * block->count;
+	}
+
+	return block_count;
+}
+
+static void init_eraseblock(struct erase_layout *layout, size_t idx, size_t block_num,
+		chipoff_t start_addr, chipoff_t end_addr, size_t *sub_block_index)
+{
+	struct eraseblock_data *edata = &layout[idx].layout_list[block_num];
+	edata->start_addr = start_addr;
+	edata->end_addr = end_addr;
+	edata->selected = false;
+	edata->block_num = block_num;
+
+	if (!idx)
+		return;
+
+	edata->first_sub_block_index = *sub_block_index;
+	struct eraseblock_data *subedata = &layout[idx - 1].layout_list[*sub_block_index];
+	while (subedata->start_addr >= start_addr && subedata->end_addr <= end_addr &&
+		*sub_block_index < layout[idx-1].block_count) {
+		(*sub_block_index)++;
+		subedata++;
+	}
+	edata->last_sub_block_index = *sub_block_index - 1;
+}
+
+/*
+ * @brief Function to free the created erase_layout
+ *
+ * @param layout pointer to allocated layout
+ * @param erasefn_count number of erase functions for which the layout was created
+ *
+ */
+static void free_erase_layout(struct erase_layout *layout, unsigned int erasefn_count)
+{
+	if (!layout)
+		return;
+	for (size_t i = 0; i < erasefn_count; i++) {
+		free(layout[i].layout_list);
+	}
+	free(layout);
+}
+
+/*
+ * @brief Function to create an erase layout
+ *
+ * @param	flashctx	flash context
+ * @param	e_layout	address to the pointer to store the layout
+ * @return	0 on success,
+ *		-1 if layout creation fails
+ *
+ * This function creates a layout of which erase functions erase which regions
+ * of the flash chip. This helps to optimally select the erase functions for
+ * erase/write operations.
+ */
+int create_erase_layout(struct flashctx *const flashctx, struct erase_layout **e_layout);
+int create_erase_layout(struct flashctx *const flashctx, struct erase_layout **e_layout)
+{
+	const struct flashchip *chip = flashctx->chip;
+	const size_t erasefn_count = count_usable_erasers(flashctx);
+	struct erase_layout *layout = calloc(erasefn_count, sizeof(struct erase_layout));
+
+	if (!layout) {
+		msg_gerr("Out of memory!\n");
+		return -1;
+	}
+
+	if (!erasefn_count) {
+		msg_gerr("No erase functions supported\n");
+		return 0;
+	}
+
+	size_t layout_idx = 0;
+	for (size_t eraser_idx = 0; eraser_idx < NUM_ERASEFUNCTIONS; eraser_idx++) {
+		if (check_block_eraser(flashctx, eraser_idx, 0))
+			continue;
+
+		layout[layout_idx].eraser = &chip->block_erasers[eraser_idx];
+		const size_t block_count = calculate_block_count(flashctx->chip, eraser_idx);
+		size_t sub_block_index = 0;
+
+		layout[layout_idx].block_count = block_count;
+		layout[layout_idx].layout_list = (struct eraseblock_data *)calloc(block_count,
+									sizeof(struct eraseblock_data));
+
+		if (!layout[layout_idx].layout_list) {
+			free_erase_layout(layout, layout_idx);
+			return -1;
+		}
+
+		size_t block_num = 0;
+		chipoff_t start_addr = 0;
+
+		for (int i = 0; block_num < block_count;  i++) {
+			const struct eraseblock *block = &chip->block_erasers[eraser_idx].eraseblocks[i];
+
+			for (size_t num = 0; num < block->count; num++) {
+				chipoff_t end_addr = start_addr + block->size - 1;
+				init_eraseblock(layout, layout_idx, block_num,
+						start_addr, end_addr, &sub_block_index);
+				block_num += 1;
+				start_addr = end_addr + 1;
+			}
+		}
+		layout_idx++;
+	}
+
+	*e_layout = layout;
+	return layout_idx;
+}
+
 static int write_range(struct flashctx *const flashctx, const chipoff_t flash_offset,
 		       const uint8_t *const curcontents, const uint8_t *const newcontents,
 		       const chipsize_t len, bool *const skipped)