util/cbfstool: Port elogtool to libflashrom

This also uncouples cbfstool from being overly Chromium
specific. However the main objective is to not subprocess
flashrom any more and instead use the programmatic API.

BUG=b:207808292
TEST=built and ran `elogtool (list|clear|add 0x16 C0FFEE)`.

Change-Id: I79df2934b9b0492a554a4fecdd533a0abe1df231
Signed-off-by: Edward O'Callaghan <quasisec@google.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/59714
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Sam McNally <sammc@google.com>
diff --git a/util/cbfstool/Makefile.inc b/util/cbfstool/Makefile.inc
index 3787a56..a568e9a 100644
--- a/util/cbfstool/Makefile.inc
+++ b/util/cbfstool/Makefile.inc
@@ -1,3 +1,5 @@
+HOSTPKGCONFIG ?= /usr/bin/pkg-config
+
 compressionobj :=
 compressionobj += compress.o
 # LZ4
@@ -92,6 +94,7 @@
 elogobj :=
 elogobj := elogtool.o
 elogobj += eventlog.o
+elogobj += uflashrom.o
 elogobj += valstr.o
 elogobj += elog.o
 elogobj += common.o
@@ -144,6 +147,12 @@
 TOOLCFLAGS+=-std=c11
 endif
 
+.PHONY: check-flashrom-presence
+check-flashrom-presence:
+	$(HOSTPKGCONFIG) --exists flashrom || \
+	(echo "Error: Ensure that pkg-config and flashrom are installed."; exit 1)
+
+FLASHROM := $$($(HOSTPKGCONFIG) --libs flashrom)
 VBOOT_HOSTLIB = $(VBOOT_HOST_BUILD)/libvboot_host.a
 
 $(VBOOT_HOSTLIB):
@@ -224,9 +233,9 @@
 	printf "    HOSTCC     $(subst $(objutil)/,,$(@)) (link)\n"
 	$(HOSTCC) $(TOOLLDFLAGS) -o $@ $(addprefix $(objutil)/cbfstool/,$(amdcompobj)) -lz
 
-$(objutil)/cbfstool/elogtool: $(addprefix $(objutil)/cbfstool/,$(elogobj)) $(VBOOT_HOSTLIB)
+$(objutil)/cbfstool/elogtool: | check-flashrom-presence $(addprefix $(objutil)/cbfstool/,$(elogobj))
 	printf "    HOSTCC     $(subst $(objutil)/,,$(@)) (link)\n"
-	$(HOSTCC) $(TOOLLDFLAGS) -o $@ $(addprefix $(objutil)/cbfstool/,$(elogobj)) $(VBOOT_HOSTLIB)
+	$(HOSTCC) $(TOOLLDFLAGS) -o $@ $(addprefix $(objutil)/cbfstool/,$(elogobj)) $(FLASHROM)
 
 $(objutil)/cbfstool/cse_fpt: $(addprefix $(objutil)/cbfstool/,$(cse_fpt_obj))
 	printf "    HOSTCC     $(subst $(objutil)/,,$(@)) (link)\n"
diff --git a/util/cbfstool/elogtool.c b/util/cbfstool/elogtool.c
index 253e2ee..44823dd 100644
--- a/util/cbfstool/elogtool.c
+++ b/util/cbfstool/elogtool.c
@@ -11,9 +11,9 @@
 
 #include <common.h>
 #include <commonlib/bsd/elog.h>
-#include <flashrom.h>
 
 #include "eventlog.h"
+#include "uflashrom.h"
 
 /* Only refers to the data max size. The "-1" is the checksum byte */
 #define ELOG_MAX_EVENT_DATA_SIZE  (ELOG_MAX_EVENT_SIZE - sizeof(struct event_header) - 1)
@@ -78,16 +78,18 @@
  */
 static int elog_read(struct buffer *buffer, const char *filename)
 {
-	if (filename == NULL) {
-		uint8_t *buf;
-		uint32_t buf_size;
+	struct firmware_programmer image = {
+		.programmer = FLASHROM_PROGRAMMER_INTERNAL_AP,
+		.data = NULL,
+		.size = 0,
+	};
 
-		if (flashrom_read(FLASHROM_PROGRAMMER_INTERNAL_AP, ELOG_RW_REGION_NAME,
-				  &buf, &buf_size) != VB2_SUCCESS) {
+	if (filename == NULL) {
+		if (flashrom_read(&image, ELOG_RW_REGION_NAME) != 0) {
 			fprintf(stderr, "Could not read RW_ELOG region using flashrom\n");
 			return ELOGTOOL_EXIT_READ_ERROR;
 		}
-		buffer_init(buffer, NULL, buf, buf_size);
+		buffer_init(buffer, NULL, image.data, image.size);
 	} else if (buffer_from_file(buffer, filename) != 0) {
 		fprintf(stderr, "Could not read input file: %s\n", filename);
 		return ELOGTOOL_EXIT_READ_ERROR;
@@ -108,9 +110,14 @@
  */
 static int elog_write(struct buffer *buf, const char *filename)
 {
+	struct firmware_programmer image = {
+		.programmer = FLASHROM_PROGRAMMER_INTERNAL_AP,
+		.data = buffer_get(buf),
+		.size = buffer_size(buf),
+	};
+
 	if (filename == NULL) {
-		if (flashrom_write(FLASHROM_PROGRAMMER_INTERNAL_AP, ELOG_RW_REGION_NAME,
-				   buffer_get(buf), buffer_size(buf)) != VB2_SUCCESS) {
+		if (flashrom_write(&image, ELOG_RW_REGION_NAME) != 0) {
 			fprintf(stderr,
 				"Failed to write to RW_ELOG region using flashrom\n");
 			return ELOGTOOL_EXIT_WRITE_ERROR;
diff --git a/util/cbfstool/uflashrom.c b/util/cbfstool/uflashrom.c
new file mode 100644
index 0000000..faa45b6
--- /dev/null
+++ b/util/cbfstool/uflashrom.c
@@ -0,0 +1,210 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <libflashrom.h>
+
+#include "uflashrom.h"
+
+static int flashrom_print_cb(enum flashrom_log_level level, const char *fmt, va_list ap)
+{
+	int ret = 0;
+	FILE *output_type = stderr;
+
+	if (level > FLASHROM_MSG_INFO)
+		return ret;
+
+	ret = vfprintf(output_type, fmt, ap);
+	/* msg_*spew often happens inside chip accessors
+	 * in possibly time-critical operations.
+	 * If increasing verbosity, don't slow them down by flushing.
+	 */
+	fflush(output_type);
+
+	return ret;
+}
+
+static size_t resize_buf_to_offset(uint8_t **buf, unsigned int start, unsigned int len)
+{
+	uint8_t *old = *buf; // make a copy to free the old heap.
+
+	*buf = calloc(1, len);
+	memcpy(*buf, &old[start], len);
+	free(old);
+
+	return len;
+}
+
+static uint8_t *resize_buf_from_offset(const uint8_t *buf, size_t len, unsigned int rstart,
+				       unsigned int rlen)
+{
+	size_t nlen = rstart + rlen;
+	if (nlen > len)
+		return NULL;
+
+	uint8_t *nbuf = calloc(1, len); /* NOTE: full len buf required for writes. */
+	memcpy(nbuf + rstart, buf, rlen);
+
+	return nbuf;
+}
+
+/**
+ * @brief Reads from flash into a buffer with an optional region.
+ *
+ * @param image, containing the programmer to use, unallocated buffer and size.
+ * @param region, (optional) the string of the region to read from.
+ * @return 0 on success
+ */
+int flashrom_read(struct firmware_programmer *image, const char *region)
+{
+	int r = 0;
+	size_t len = 0;
+
+	struct flashrom_programmer *prog = NULL;
+	struct flashrom_flashctx *flashctx = NULL;
+	struct flashrom_layout *layout = NULL;
+
+	flashrom_set_log_callback((flashrom_log_callback *)&flashrom_print_cb);
+
+	r |= flashrom_init(1);
+	r |= flashrom_programmer_init(&prog, image->programmer, NULL);
+	r |= flashrom_flash_probe(&flashctx, prog, NULL);
+	if (r) {
+		r = -1;
+		goto err_cleanup;
+	}
+
+	len = flashrom_flash_getsize(flashctx);
+	if (region) {
+		r = flashrom_layout_read_fmap_from_rom(&layout, flashctx, 0, len);
+		if (r > 0) {
+			fprintf(stderr, "could not read fmap from rom, r=%d\n", r);
+			r = -1;
+			goto err_cleanup;
+		}
+		/* empty region causes seg fault in API. */
+		r |= flashrom_layout_include_region(layout, region);
+		if (r > 0) {
+			fprintf(stderr, "could not include region = '%s'\n", region);
+			r = -1;
+			goto err_cleanup;
+		}
+		flashrom_layout_set(flashctx, layout);
+	}
+	/* Due to how the libflashrom API works we first need a buffer sized
+	 * to the entire flash and after the read has finished, find the
+	 * the precise region size then resize the buffer accordingly.
+	 */
+	image->data = calloc(1, len);
+	image->size = len;
+
+	r |= flashrom_image_read(flashctx, image->data, len);
+
+	/* Here we resize the buffer from being the entire flash down to the specific
+	 * region size read and that we were interested in. Note that we only include
+	 * a singular region.
+	 */
+	if (region) {
+		unsigned int r_start, r_len;
+		flashrom_layout_get_region_range(layout, region, &r_start, &r_len);
+		image->size = resize_buf_to_offset(&image->data, r_start, r_len);
+	}
+
+err_cleanup:
+	flashrom_programmer_shutdown(prog);
+	if (layout)
+		flashrom_layout_release(layout);
+	if (flashctx)
+		flashrom_flash_release(flashctx);
+
+	return r;
+}
+
+/**
+ * @brief Writes flash from a buffer with an optional region.
+ *
+ * @param image, containing the programmer to use, allocated buffer and its size.
+ * @param region, (optional) the string of the region to write to.
+ * @return 0 on success
+ */
+int flashrom_write(struct firmware_programmer *image, const char *region)
+{
+	int r = 0;
+	size_t len = 0;
+	uint8_t *buf = image->data;
+
+	struct flashrom_programmer *prog = NULL;
+	struct flashrom_flashctx *flashctx = NULL;
+	struct flashrom_layout *layout = NULL;
+
+	flashrom_set_log_callback((flashrom_log_callback *)&flashrom_print_cb);
+
+	r |= flashrom_init(1);
+	r |= flashrom_programmer_init(&prog, image->programmer, NULL);
+	r |= flashrom_flash_probe(&flashctx, prog, NULL);
+	if (r) {
+		r = -1;
+		goto err_cleanup;
+	}
+
+	len = flashrom_flash_getsize(flashctx);
+	if (len == 0) {
+		fprintf(stderr, "zero sized flash detected\n");
+		r = -1;
+		goto err_cleanup;
+	}
+	if (region) {
+		r = flashrom_layout_read_fmap_from_buffer(
+			&layout, flashctx, (const uint8_t *)image->data, image->size);
+		if (r > 0) {
+			r = flashrom_layout_read_fmap_from_rom(&layout, flashctx, 0, len);
+			if (r > 0) {
+				fprintf(stderr, "could not read fmap from image or rom, r=%d\n",
+					r);
+				r = -1;
+				goto err_cleanup;
+			}
+		}
+		/* empty region causes seg fault in API. */
+		r |= flashrom_layout_include_region(layout, region);
+		if (r > 0) {
+			fprintf(stderr, "could not include region = '%s'\n", region);
+			r = -1;
+			goto err_cleanup;
+		}
+		flashrom_layout_set(flashctx, layout);
+
+		unsigned int r_start, r_len;
+		flashrom_layout_get_region_range(layout, region, &r_start, &r_len);
+		assert(r_len == image->size);
+		buf = resize_buf_from_offset(image->data, len, r_start, r_len);
+		if (!buf) {
+			r = -1;
+			goto err_cleanup_free;
+		}
+	} else if (image->size != len) {
+		r = -1;
+		goto err_cleanup;
+	}
+
+	flashrom_flag_set(flashctx, FLASHROM_FLAG_VERIFY_WHOLE_CHIP, false);
+	flashrom_flag_set(flashctx, FLASHROM_FLAG_VERIFY_AFTER_WRITE, true);
+
+	r |= flashrom_image_write(flashctx, buf, len, NULL);
+
+err_cleanup_free:
+	if (region)
+		free(buf);
+err_cleanup:
+	flashrom_programmer_shutdown(prog);
+	if (layout)
+		flashrom_layout_release(layout);
+	if (flashctx)
+		flashrom_flash_release(flashctx);
+
+	return r;
+}
diff --git a/util/cbfstool/uflashrom.h b/util/cbfstool/uflashrom.h
new file mode 100644
index 0000000..1ef0085
--- /dev/null
+++ b/util/cbfstool/uflashrom.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: BSD-3-Clause */
+
+#ifndef UFLASHROM_H
+#define UFLASHROM_H
+
+#define FLASHROM_PROGRAMMER_INTERNAL_AP "internal"
+
+struct firmware_programmer {
+	const char *programmer;
+	uint32_t size;
+	uint8_t *data;
+};
+
+int flashrom_read(struct firmware_programmer *image, const char *region);
+int flashrom_write(struct firmware_programmer *image, const char *region);
+
+#endif /* UFLASHROM_H */