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>
This commit is contained in:
Raul E Rangel 2021-11-01 13:40:14 -06:00 committed by Raul Rangel
parent 58618c26a1
commit 4cfb862fb2
3 changed files with 190 additions and 6 deletions

View File

@ -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, static inline void *cbfs_ro_type_cbmem_alloc(const char *name, uint32_t cbmem_id,
size_t *size_out, enum cbfs_type *type); 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 /* 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. */ order where possible, since mapping backends often don't support more complicated cases. */
void cbfs_unmap(void *mapping); void cbfs_unmap(void *mapping);

View File

@ -105,6 +105,16 @@ config CBFS_CACHE_ALIGN
help help
Sets the alignment of the buffers returned by the cbfs_cache. 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 config PAYLOAD_PRELOAD
bool bool
depends on COOP_MULTITASKING depends on COOP_MULTITASKING

View File

@ -10,12 +10,14 @@
#include <console/console.h> #include <console/console.h>
#include <fmap.h> #include <fmap.h>
#include <lib.h> #include <lib.h>
#include <list.h>
#include <metadata_hash.h> #include <metadata_hash.h>
#include <security/tpm/tspi/crtm.h> #include <security/tpm/tspi/crtm.h>
#include <security/vboot/vboot_common.h> #include <security/vboot/vboot_common.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <symbols.h> #include <symbols.h>
#include <thread.h>
#include <timestamp.h> #include <timestamp.h>
#if ENV_STAGE_HAS_DATA_SECTION #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, void *_cbfs_alloc(const char *name, cbfs_allocator_t allocator, void *arg,
size_t *size_out, bool force_ro, enum cbfs_type *type) size_t *size_out, bool force_ro, enum cbfs_type *type)
{ {
struct region_device rdev; struct region_device rdev;
bool preload_successful = false;
union cbfs_mdata mdata; 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, DEBUG("%s(name='%s', alloc=%p(%p), force_ro=%s, type=%d)\n", __func__, name, allocator,
arg, force_ro ? "true" : "false", type ? *type : -1); 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)) if (CONFIG(CBFS_VERIFICATION))
file_hash = cbfs_file_hash(&mdata); 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() */ /* allocator == NULL means do a cbfs_map() */
if (allocator) { if (allocator) {
loc = allocator(arg, size, &mdata); 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); void *mapping = rdev_mmap_full(&rdev);
if (!mapping) if (!mapping)
return NULL; goto out;
if (cbfs_file_hash_mismatch(mapping, size, file_hash)) { if (cbfs_file_hash_mismatch(mapping, size, file_hash)) {
rdev_munmap(&rdev, mapping); rdev_munmap(&rdev, mapping);
return NULL; goto out;
} }
return mapping; 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. * it is not possible to add a CBFS_CACHE.
*/ */
ERROR("Cannot map compressed file %s without cbfs_cache\n", mdata.h.filename); ERROR("Cannot map compressed file %s without cbfs_cache\n", mdata.h.filename);
return NULL; goto out;
} else { } else {
loc = mem_pool_alloc(&cbfs_cache, size); loc = mem_pool_alloc(&cbfs_cache, size);
} }
if (!loc) { if (!loc) {
ERROR("'%s' allocation failure\n", mdata.h.filename); ERROR("'%s' allocation failure\n", mdata.h.filename);
return NULL; goto out;
} }
size = cbfs_load_and_decompress(&rdev, loc, size, compression, file_hash); size = cbfs_load_and_decompress(&rdev, loc, size, compression, file_hash);
if (!size) 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; return loc;
} }