summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRaul E Rangel <rrangel@chromium.org>2021-11-01 13:40:14 -0600
committerRaul Rangel <rrangel@chromium.org>2021-11-04 17:10:31 +0000
commit4cfb862fb2bc9c9d95fd4f5d3026ec791fe485d6 (patch)
tree8b9f622e925831ad2cdcf788ca001c005af18baa
parent58618c26a10632a98ee7f8be957ba5288a39af69 (diff)
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>
-rw-r--r--src/include/cbfs.h15
-rw-r--r--src/lib/Kconfig10
-rw-r--r--src/lib/cbfs.c171
3 files changed, 190 insertions, 6 deletions
diff --git a/src/include/cbfs.h b/src/include/cbfs.h
index 0d8ac60375..f6309a3e30 100644
--- a/src/include/cbfs.h
+++ b/src/include/cbfs.h
@@ -97,6 +97,21 @@ static inline void *cbfs_type_cbmem_alloc(const char *name, uint32_t cbmem_id, s
static inline void *cbfs_ro_type_cbmem_alloc(const char *name, uint32_t cbmem_id,
size_t *size_out, enum cbfs_type *type);
+/*
+ * Starts the processes of preloading a file into RAM.
+ *
+ * This method depends on COOP_MULTITASKING to parallelize the loading. This method is only
+ * effective when the underlying rdev supports DMA operations.
+ *
+ * When `cbfs_load`, `cbfs_alloc`, or `cbfs_map` are called after a preload has been started,
+ * they will wait for the preload to complete (if it hasn't already) and then perform
+ * verification and/or decompression.
+ *
+ * This method does not have a return value because the system should boot regardless if this
+ * method succeeds or fails.
+ */
+void cbfs_preload(const char *name);
+
/* Removes a previously allocated CBFS mapping. Should try to unmap mappings in strict LIFO
order where possible, since mapping backends often don't support more complicated cases. */
void cbfs_unmap(void *mapping);
diff --git a/src/lib/Kconfig b/src/lib/Kconfig
index 41a23b27c0..41df1f1c89 100644
--- a/src/lib/Kconfig
+++ b/src/lib/Kconfig
@@ -105,6 +105,16 @@ config CBFS_CACHE_ALIGN
help
Sets the alignment of the buffers returned by the cbfs_cache.
+config CBFS_PRELOAD
+ bool
+ depends on COOP_MULTITASKING
+ help
+ When enabled it will be possible to preload CBFS files into the
+ cbfs_cache. This helps reduce boot time by loading the files
+ in the background before they are actually required. This feature
+ depends on the read-only boot_device having a DMA controller to
+ perform the background transfer.
+
config PAYLOAD_PRELOAD
bool
depends on COOP_MULTITASKING
diff --git a/src/lib/cbfs.c b/src/lib/cbfs.c
index 1c79b65d6b..dbb3b1a913 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 @@ static size_t cbfs_load_and_decompress(const struct region_device *rdev, void *b
}
}
+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 @@ void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg,
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 *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg,
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 @@ void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg,
* 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;
}