lib/cbfs: Add cbfs_preload()

This API will hide all the complexity of preloading a CBFS file. It
makes it so the callers simply specify the file to preload and CBFS
takes care of the rest. It will start a new thread to read the file into
the cbfs_cache. When the file is actually required (i.e., cbfs_load,
etc) it will wait for the preload thread to complete (if it hasn't
already) and perform verification/decompression using the preloaded
buffer. This design allows decompression/verification to happen in the
main BSP thread so that timestamps are correctly reflected.

BUG=b:179699789
TEST=Test with whole CL chain, verify VGA bios was preloaded and boot
time was reduced by 12ms.

Logs:
Preloading VGA ROM
CBFS DEBUG: _cbfs_preload(name='pci1002,1638.rom', force_ro=false)
CBFS: Found 'pci1002,1638.rom' @0x20ac40 size 0xd800 in mcache @0xcb7dd0f0
spi_dma_readat_dma: start: dest: 0x021c0000, source: 0x51cc80, size: 55296
took 0 us to acquire mutex
start_spi_dma_transaction: dest: 0x021c0000, source: 0x51cc80, remaining: 55296
...
spi_dma_readat_dma: end: dest: 0x021c0000, source: 0x51cc80, remaining: 0
...
CBFS DEBUG: _cbfs_alloc(name='pci1002,1638.rom', alloc=0x00000000(0x00000000), force_ro=false, type=-1)
CBFS: Found 'pci1002,1638.rom' @0x20ac40 size 0xd800 in mcache @0xcb7dd0f0
waiting for thread
took 0 us
CBFS DEBUG: get_preload_rdev(name='pci1002,1638.rom', force_ro=false) preload successful
In CBFS, ROM address for PCI: 03:00.0 = 0x021c0000
PCI expansion ROM, signature 0xaa55, INIT size 0xd800, data ptr 0x01b0
PCI ROM image, vendor ID 1002, device ID 1638,
PCI ROM image, Class Code 030000, Code Type 00
Copying VGA ROM Image from 0x021c0000 to 0xc0000, 0xd800 bytes

$ cbmem
  ...
  40:device configuration                              5,399,404 (8,575)
  65:Option ROM initialization                         5,403,474 (4,070)
  66:Option ROM copy done                              5,403,488 (14)
  ...

Signed-off-by: Raul E Rangel <rrangel@chromium.org>
Change-Id: I879fc1316f97417a4b82483d353abdbd02b98a31
Reviewed-on: https://review.coreboot.org/c/coreboot/+/56491
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Patrick Georgi <pgeorgi@google.com>
diff --git a/src/lib/cbfs.c b/src/lib/cbfs.c
index 1c79b65..dbb3b1a 100644
--- a/src/lib/cbfs.c
+++ b/src/lib/cbfs.c
@@ -10,12 +10,14 @@
 #include <console/console.h>
 #include <fmap.h>
 #include <lib.h>
+#include <list.h>
 #include <metadata_hash.h>
 #include <security/tpm/tspi/crtm.h>
 #include <security/vboot/vboot_common.h>
 #include <stdlib.h>
 #include <string.h>
 #include <symbols.h>
+#include <thread.h>
 #include <timestamp.h>
 
 #if ENV_STAGE_HAS_DATA_SECTION
@@ -266,12 +268,156 @@
 	}
 }
 
+struct cbfs_preload_context {
+	struct region_device rdev;
+	struct thread_handle handle;
+	struct list_node list_node;
+	void *buffer;
+	char name[];
+};
+
+static struct list_node cbfs_preload_context_list;
+
+static struct cbfs_preload_context *alloc_cbfs_preload_context(size_t additional)
+{
+	struct cbfs_preload_context *context;
+	size_t size = sizeof(*context) + additional;
+
+	context = mem_pool_alloc(&cbfs_cache, size);
+
+	if (!context)
+		return NULL;
+
+	memset(context, 0, size);
+
+	return context;
+}
+
+static void append_cbfs_preload_context(struct cbfs_preload_context *context)
+{
+	list_append(&context->list_node, &cbfs_preload_context_list);
+}
+
+static void free_cbfs_preload_context(struct cbfs_preload_context *context)
+{
+	list_remove(&context->list_node);
+
+	mem_pool_free(&cbfs_cache, context);
+}
+
+static enum cb_err cbfs_preload_thread_entry(void *arg)
+{
+	struct cbfs_preload_context *context = arg;
+
+	if (rdev_readat_full(&context->rdev, context->buffer) < 0) {
+		ERROR("%s(name='%s') readat failed\n", __func__, context->name);
+		return CB_ERR;
+	}
+
+	return CB_SUCCESS;
+}
+
+void cbfs_preload(const char *name)
+{
+	struct region_device rdev;
+	union cbfs_mdata mdata;
+	struct cbfs_preload_context *context;
+	bool force_ro = false;
+	size_t size;
+
+	if (!CONFIG(CBFS_PRELOAD))
+		dead_code();
+
+	DEBUG("%s(name='%s')\n", __func__, name);
+
+	if (cbfs_boot_lookup(name, force_ro, &mdata, &rdev))
+		return;
+
+	size = region_device_sz(&rdev);
+
+	context = alloc_cbfs_preload_context(strlen(name) + 1);
+	if (!context) {
+		ERROR("%s(name='%s') failed to allocate preload context\n", __func__, name);
+		return;
+	}
+
+	context->buffer = mem_pool_alloc(&cbfs_cache, size);
+	if (context->buffer == NULL) {
+		ERROR("%s(name='%s') failed to allocate %zu bytes for preload buffer\n",
+		      __func__, name, size);
+		goto out;
+	}
+
+	context->rdev = rdev;
+	strcpy(context->name, name);
+
+	append_cbfs_preload_context(context);
+
+	if (thread_run(&context->handle, cbfs_preload_thread_entry, context) == 0)
+		return;
+
+	ERROR("%s(name='%s') failed to start preload thread\n", __func__, name);
+	mem_pool_free(&cbfs_cache, context->buffer);
+
+out:
+	free_cbfs_preload_context(context);
+}
+
+static struct cbfs_preload_context *find_cbfs_preload_context(const char *name)
+{
+	struct cbfs_preload_context *context;
+
+	list_for_each(context, cbfs_preload_context_list, list_node) {
+		if (strcmp(context->name, name) == 0)
+			return context;
+	}
+
+	return NULL;
+}
+
+static enum cb_err get_preload_rdev(struct region_device *rdev, const char *name)
+{
+	enum cb_err err;
+	struct cbfs_preload_context *context;
+
+	if (!CONFIG(CBFS_PRELOAD) || (!ENV_RAMSTAGE && !ENV_ROMSTAGE))
+		return CB_ERR_ARG;
+
+	context = find_cbfs_preload_context(name);
+	if (!context)
+		return CB_ERR_ARG;
+
+	err = thread_join(&context->handle);
+	if (err != CB_SUCCESS) {
+		ERROR("%s(name='%s') Preload thread failed: %u\n", __func__, name, err);
+
+		goto out;
+	}
+
+	if (rdev_chain_mem(rdev, context->buffer, region_device_sz(&context->rdev)) != 0) {
+		ERROR("%s(name='%s') chaining failed\n", __func__, name);
+
+		err = CB_ERR;
+		goto out;
+	}
+
+	err = CB_SUCCESS;
+
+	DEBUG("%s(name='%s') preload successful\n", __func__, name);
+
+out:
+	free_cbfs_preload_context(context);
+
+	return err;
+}
+
 void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg,
 		  size_t *size_out, bool force_ro, enum cbfs_type *type)
 {
 	struct region_device rdev;
+	bool preload_successful = false;
 	union cbfs_mdata mdata;
-	void *loc;
+	void *loc = NULL;
 
 	DEBUG("%s(name='%s', alloc=%p(%p), force_ro=%s, type=%d)\n", __func__, name, allocator,
 	      arg, force_ro ? "true" : "false", type ? *type : -1);
@@ -306,6 +452,10 @@
 	if (CONFIG(CBFS_VERIFICATION))
 		file_hash = cbfs_file_hash(&mdata);
 
+	/* Update the rdev with the preload content */
+	if (!force_ro && get_preload_rdev(&rdev, name) == CB_SUCCESS)
+		preload_successful = true;
+
 	/* allocator == NULL means do a cbfs_map() */
 	if (allocator) {
 		loc = allocator(arg, size, &mdata);
@@ -313,11 +463,11 @@
 		void *mapping = rdev_mmap_full(&rdev);
 
 		if (!mapping)
-			return NULL;
+			goto out;
 
 		if (cbfs_file_hash_mismatch(mapping, size, file_hash)) {
 			rdev_munmap(&rdev, mapping);
-			return NULL;
+			goto out;
 		}
 
 		return mapping;
@@ -328,19 +478,28 @@
 		 * it is not possible to add a CBFS_CACHE.
 		 */
 		ERROR("Cannot map compressed file %s without cbfs_cache\n", mdata.h.filename);
-		return NULL;
+		goto out;
 	} else {
 		loc = mem_pool_alloc(&cbfs_cache, size);
 	}
 
 	if (!loc) {
 		ERROR("'%s' allocation failure\n", mdata.h.filename);
-		return NULL;
+		goto out;
 	}
 
 	size = cbfs_load_and_decompress(&rdev, loc, size, compression, file_hash);
+
 	if (!size)
-		return NULL;
+		loc = NULL;
+
+out:
+	/*
+	 * When using cbfs_preload we need to free the preload buffer after populating the
+	 * destination buffer.
+	 */
+	if (preload_successful)
+		cbfs_unmap(rdev_mmap_full(&rdev));
 
 	return loc;
 }