cbfs: Add metadata cache

This patch adds a new CBFS "mcache" (metadata cache) -- a memory buffer
that stores the headers of all CBFS files. Similar to the existing FMAP
cache, this cache should reduce the amount of SPI accesses we need to do
every boot: rather than having to re-read all CBFS headers from SPI
flash every time we're looking for a file, we can just walk the same
list in this in-memory copy and finally use it to directly access the
flash at the right position for the file data.

This patch adds the code to support the cache but doesn't enable it on
any platform. The next one will turn it on by default.

Change-Id: I5b1084bfdad1c6ab0ee1b143ed8dd796827f4c65
Signed-off-by: Julius Werner <jwerner@chromium.org>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/38423
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
This commit is contained in:
Julius Werner 2019-12-11 17:09:39 -08:00 committed by Philipp Deppenwiese
parent 7d11513ab3
commit 1e37c9ca46
13 changed files with 364 additions and 33 deletions

View File

@ -37,6 +37,13 @@ postcar-y += bsd/cbfs_private.c
ramstage-y += bsd/cbfs_private.c
smm-y += bsd/cbfs_private.c
bootblock-y += bsd/cbfs_mcache.c
verstage-y += bsd/cbfs_mcache.c
romstage-y += bsd/cbfs_mcache.c
postcar-y += bsd/cbfs_mcache.c
ramstage-y += bsd/cbfs_mcache.c
smm-y += bsd/cbfs_mcache.c
decompressor-y += bsd/lz4_wrapper.c
bootblock-y += bsd/lz4_wrapper.c
verstage-y += bsd/lz4_wrapper.c

View File

@ -0,0 +1,143 @@
/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0-or-later */
#include <assert.h>
#include <commonlib/bsd/cbfs_private.h>
/*
* A CBFS metadata cache is an in memory data structure storing CBFS file headers (= metadata).
* It is defined by its start pointer and size. It contains a sequence of variable-length
* union mcache_entry entries. There is no overall header structure for the cache.
*
* Each mcache_entry is the raw metadata for a CBFS file (including attributes) in the same form
* as stored on flash (i.e. values in big-endian), except that the CBFS magic signature in the
* first 8 bytes ('LARCHIVE') is overwritten with mcache-internal bookkeeping data. The first 4
* bytes are a magic number (MCACHE_MAGIC_FILE) and the next 4 bytes are the absolute offset in
* bytes on the cbfs_dev_t that this metadata blob was found at. (Note that depending on the
* implementation of cbfs_dev_t, this offset may still be relative to the start of a subregion
* of the underlying storage device.)
*
* The length of an mcache_entry (i.e. length of the underlying metadata blob) is encoded in the
* metadata (entry->file.h.offset). The next mcache_entry begins at the next
* CBFS_MCACHE_ALIGNMENT boundary after that. The cache is terminated by a special 4-byte
* mcache_entry that consists only of a magic number (MCACHE_MAGIC_END or MCACHE_MAGIC_FULL).
*/
#define MCACHE_MAGIC_FILE 0x454c4946 /* 'FILE' */
#define MCACHE_MAGIC_FULL 0x4c4c5546 /* 'FULL' */
#define MCACHE_MAGIC_END 0x444e4524 /* '$END' */
union mcache_entry {
union cbfs_mdata file;
struct { /* These fields exactly overlap file.h.magic */
uint32_t magic;
uint32_t offset;
};
};
struct cbfs_mcache_build_args {
void *mcache;
void *end;
int count;
};
static cb_err_t build_walker(cbfs_dev_t dev, size_t offset, const union cbfs_mdata *mdata,
size_t already_read, void *arg)
{
struct cbfs_mcache_build_args *args = arg;
union mcache_entry *entry = args->mcache;
const uint32_t data_offset = be32toh(mdata->h.offset);
if (args->end - args->mcache < data_offset)
return CB_CBFS_CACHE_FULL;
if (cbfs_copy_fill_metadata(args->mcache, mdata, already_read, dev, offset))
return CB_CBFS_IO;
entry->magic = MCACHE_MAGIC_FILE;
entry->offset = offset;
args->mcache += ALIGN_UP(data_offset, CBFS_MCACHE_ALIGNMENT);
args->count++;
return CB_CBFS_NOT_FOUND;
}
cb_err_t cbfs_mcache_build(cbfs_dev_t dev, void *mcache, size_t size,
struct vb2_hash *metadata_hash)
{
struct cbfs_mcache_build_args args = {
.mcache = mcache,
.end = mcache + ALIGN_DOWN(size, CBFS_MCACHE_ALIGNMENT)
- sizeof(uint32_t), /* leave space for terminating magic */
.count = 0,
};
assert(size > sizeof(uint32_t) && IS_ALIGNED((uintptr_t)mcache, CBFS_MCACHE_ALIGNMENT));
cb_err_t ret = cbfs_walk(dev, build_walker, &args, metadata_hash, 0);
union mcache_entry *entry = args.mcache;
if (ret == CB_CBFS_NOT_FOUND) {
ret = CB_SUCCESS;
entry->magic = MCACHE_MAGIC_END;
} else if (ret == CB_CBFS_CACHE_FULL) {
ERROR("mcache overflow, should increase CBFS_MCACHE size!\n");
entry->magic = MCACHE_MAGIC_FULL;
}
LOG("mcache @%p built for %d files, used %#zx of %#zx bytes\n", mcache,
args.count, args.mcache + sizeof(entry->magic) - mcache, size);
return ret;
}
cb_err_t cbfs_mcache_lookup(const void *mcache, size_t mcache_size, const char *name,
union cbfs_mdata *mdata_out, size_t *data_offset_out)
{
const size_t namesize = strlen(name) + 1; /* Count trailing \0 so we can memcmp() it. */
const void *end = mcache + mcache_size;
const void *current = mcache;
while (current + sizeof(uint32_t) < end) {
const union mcache_entry *entry = current;
if (entry->magic == MCACHE_MAGIC_END)
return CB_CBFS_NOT_FOUND;
if (entry->magic == MCACHE_MAGIC_FULL)
return CB_CBFS_CACHE_FULL;
assert(entry->magic == MCACHE_MAGIC_FILE);
const uint32_t data_offset = be32toh(entry->file.h.offset);
const uint32_t data_length = be32toh(entry->file.h.len);
if (namesize <= data_offset - offsetof(union cbfs_mdata, filename) &&
memcmp(name, entry->file.filename, namesize) == 0) {
LOG("Found '%s' @%#x size %#x in mcache @%p\n",
name, entry->offset, data_length, current);
*data_offset_out = entry->offset + data_offset;
memcpy(mdata_out, &entry->file, data_offset);
return CB_SUCCESS;
}
current += ALIGN_UP(data_offset, CBFS_MCACHE_ALIGNMENT);
}
ERROR("CBFS mcache overflow!\n");
return CB_ERR;
}
size_t cbfs_mcache_real_size(const void *mcache, size_t mcache_size)
{
const void *end = mcache + mcache_size;
const void *current = mcache;
while (current + sizeof(uint32_t) < end) {
const union mcache_entry *entry = current;
if (entry->magic == MCACHE_MAGIC_FULL || entry->magic == MCACHE_MAGIC_END) {
current += sizeof(entry->magic);
break;
}
assert(entry->magic == MCACHE_MAGIC_FILE);
current += ALIGN_UP(be32toh(entry->file.h.offset), CBFS_MCACHE_ALIGNMENT);
}
return current - mcache;
}

View File

@ -39,6 +39,7 @@ enum cb_err {
CB_CBFS_IO = -400, /**< Underlying I/O error */
CB_CBFS_NOT_FOUND = -401, /**< File not found in directory */
CB_CBFS_HASH_MISMATCH = -402, /**< Master hash validation failed */
CB_CBFS_CACHE_FULL = -403, /**< Metadata cache overflowed */
};
/* Don't typedef the enum directly, so the size is unambiguous for serialization. */

View File

@ -113,4 +113,25 @@ cb_err_t cbfs_copy_fill_metadata(union cbfs_mdata *dst, const union cbfs_mdata *
cb_err_t cbfs_lookup(cbfs_dev_t dev, const char *name, union cbfs_mdata *mdata_out,
size_t *data_offset_out, struct vb2_hash *metadata_hash);
/* Both base address and size of CBFS mcaches must be aligned to this value! */
#define CBFS_MCACHE_ALIGNMENT sizeof(uint32_t) /* Largest data type used in CBFS */
/* Build an in-memory CBFS metadata cache out of the CBFS on |dev| into a |mcache_size| bytes
* memory area at |mcache|. Also verify |metadata_hash| unless it is NULL. If this returns
* CB_CBFS_CACHE_FULL, the mcache is still valid and can be used, but lookups may return
* CB_CBFS_CACHE_FULL for files that didn't fit to indicate that the caller needs to fall back
* to cbfs_lookup(). */
cb_err_t cbfs_mcache_build(cbfs_dev_t dev, void *mcache, size_t mcache_size,
struct vb2_hash *metadata_hash);
/*
* Find a file named |name| in a CBFS metadata cache and copy its metadata into |mdata_out|.
* Pass out offset to the file data (on the original CBFS device used for cbfs_mcache_build()).
*/
cb_err_t cbfs_mcache_lookup(const void *mcache, size_t mcache_size, const char *name,
union cbfs_mdata *mdata_out, size_t *data_offset_out);
/* Returns the amount of bytes actually used by the CBFS metadata cache in |mcache|. */
size_t cbfs_mcache_real_size(const void *mcache, size_t mcache_size);
#endif /* _COMMONLIB_BSD_CBFS_PRIVATE_H_ */

View File

@ -67,6 +67,8 @@
#define CBMEM_ID_ROM2 0x524f4d32
#define CBMEM_ID_ROM3 0x524f4d33
#define CBMEM_ID_FMAP 0x464d4150
#define CBMEM_ID_CBFS_RO_MCACHE 0x524d5346
#define CBMEM_ID_CBFS_RW_MCACHE 0x574d5346
#define CBMEM_ID_FSP_LOGO 0x4c4f474f
#define CBMEM_ID_SMM_COMBUFFER 0x53534d32
@ -129,5 +131,7 @@
{ CBMEM_ID_ROM1, "VGA ROM #1 "}, \
{ CBMEM_ID_ROM2, "VGA ROM #2 "}, \
{ CBMEM_ID_ROM3, "VGA ROM #3 "}, \
{ CBMEM_ID_FMAP, "FMAP "},
{ CBMEM_ID_FMAP, "FMAP "}, \
{ CBMEM_ID_CBFS_RO_MCACHE, "RO MCACHE "}, \
{ CBMEM_ID_CBFS_RW_MCACHE, "RW MCACHE "}
#endif /* _CBMEM_ID_H_ */

View File

@ -3,10 +3,10 @@
#ifndef _CBFS_H_
#define _CBFS_H_
#include <cbmem.h>
#include <commonlib/cbfs.h>
#include <program_loading.h>
#include <stddef.h>
#include <stdint.h>
#include <types.h>
/***********************************************
* Perform CBFS operations on the boot device. *
@ -42,8 +42,21 @@ size_t cbfs_load_and_decompress(const struct region_device *rdev, size_t offset,
/* Load stage into memory filling in prog. Return 0 on success. < 0 on error. */
int cbfs_prog_stage_load(struct prog *prog);
/* Returns the region device of the currently active CBFS.
Return < 0 on error, 0 on success. */
int cbfs_boot_region_device(struct region_device *rdev);
struct cbfs_boot_device {
struct region_device rdev;
void *mcache;
size_t mcache_size;
};
/* Helper to fill out |mcache| and |mcache_size| in a cbfs_boot_device. */
void cbfs_boot_device_find_mcache(struct cbfs_boot_device *cbd, uint32_t id);
/*
* Retrieves the currently active CBFS boot device. If |force_ro| is set, will
* always return the read-only CBFS instead (this only makes a difference when
* CONFIG(VBOOT) is enabled). May perform certain CBFS initialization tasks.
* Returns NULL on error (e.g. boot device IO error).
*/
const struct cbfs_boot_device *cbfs_get_boot_device(bool force_ro);
#endif

View File

@ -71,6 +71,9 @@
_ = ASSERT(sz >= FMAP_SIZE, \
STR(FMAP does not fit in FMAP_CACHE! (sz < FMAP_SIZE)));
#define CBFS_MCACHE(addr, sz) \
REGION(cbfs_mcache, addr, sz, 4)
#if ENV_ROMSTAGE_OR_BEFORE
#define PRERAM_CBFS_CACHE(addr, size) \
REGION(preram_cbfs_cache, addr, size, 4) \

View File

@ -33,6 +33,7 @@ DECLARE_REGION(stack)
DECLARE_REGION(preram_cbfs_cache)
DECLARE_REGION(postram_cbfs_cache)
DECLARE_REGION(cbfs_cache)
DECLARE_REGION(cbfs_mcache)
DECLARE_REGION(fmap_cache)
DECLARE_REGION(tpm_tcpa_log)

View File

@ -80,3 +80,24 @@ config ESPI_DEBUG
help
This option enables eSPI library helper functions for displaying debug
information.
config NO_CBFS_MCACHE
bool
default y
help
Disables the CBFS metadata cache. This means that your platform does
not need to provide a CBFS_MCACHE section in memlayout and can save
the associated CAR/SRAM size. In that case every single CBFS file
lookup must re-read the same CBFS directory entries from flash to find
the respective file.
config CBFS_MCACHE_RW_PERCENTAGE
int
depends on VBOOT && !NO_CBFS_MCACHE
default 25 if CHROMEOS # Chrome OS stores many L10n files in RO only
default 50
help
The amount of the CBFS_MCACHE area that's used for the RW CBFS, in
percent from 0 to 100. The remaining area will be used for the RO
CBFS. Default is an even 50/50 split. When VBOOT is disabled, this
will automatically be 0 (meaning the whole MCACHE is used for RO).

View File

@ -3,6 +3,7 @@
#include <assert.h>
#include <boot_device.h>
#include <cbfs.h>
#include <cbmem.h>
#include <commonlib/bsd/cbfs_private.h>
#include <commonlib/bsd/compression.h>
#include <commonlib/endian.h>
@ -16,22 +17,33 @@
#include <symbols.h>
#include <timestamp.h>
static cb_err_t cbfs_boot_lookup(const struct cbfs_boot_device *cbd,
const char *name, union cbfs_mdata *mdata, size_t *data_offset)
{
cb_err_t err = CB_CBFS_CACHE_FULL;
if (!CONFIG(NO_CBFS_MCACHE) && !ENV_SMM)
err = cbfs_mcache_lookup(cbd->mcache, cbd->mcache_size,
name, mdata, data_offset);
if (err == CB_CBFS_CACHE_FULL)
err = cbfs_lookup(&cbd->rdev, name, mdata, data_offset, NULL);
return err;
}
int cbfs_boot_locate(struct cbfsf *fh, const char *name, uint32_t *type)
{
struct region_device rdev;
if (cbfs_boot_region_device(&rdev))
const struct cbfs_boot_device *cbd = cbfs_get_boot_device(false);
if (!cbd)
return -1;
size_t data_offset;
cb_err_t err = cbfs_lookup(&rdev, name, &fh->mdata, &data_offset, NULL);
cb_err_t err = cbfs_boot_lookup(cbd, name, &fh->mdata, &data_offset);
if (CONFIG(VBOOT_ENABLE_CBFS_FALLBACK) && err == CB_CBFS_NOT_FOUND) {
printk(BIOS_INFO, "CBFS: Fall back to RO region for %s\n",
name);
if (fmap_locate_area_as_rdev("COREBOOT", &rdev))
if (!(cbd = cbfs_get_boot_device(true)))
return -1;
err = cbfs_lookup(&rdev, name, &fh->mdata, &data_offset, NULL);
err = cbfs_boot_lookup(cbd, name, &fh->mdata, &data_offset);
}
if (err)
return -1;
@ -39,7 +51,8 @@ int cbfs_boot_locate(struct cbfsf *fh, const char *name, uint32_t *type)
size_t msize = be32toh(fh->mdata.h.offset);
if (rdev_chain(&fh->metadata, &addrspace_32bit.rdev,
(uintptr_t)&fh->mdata, msize) ||
rdev_chain(&fh->data, &rdev, data_offset, be32toh(fh->mdata.h.len)))
rdev_chain(&fh->data, &cbd->rdev, data_offset,
be32toh(fh->mdata.h.len)))
return -1;
if (type) {
if (!*type)
@ -333,9 +346,86 @@ out:
return 0;
}
int cbfs_boot_region_device(struct region_device *rdev)
void cbfs_boot_device_find_mcache(struct cbfs_boot_device *cbd, uint32_t id)
{
boot_device_init();
return vboot_locate_cbfs(rdev) &&
fmap_locate_area_as_rdev("COREBOOT", rdev);
if (CONFIG(NO_CBFS_MCACHE) || ENV_SMM)
return;
const struct cbmem_entry *entry;
if (cbmem_possibly_online() &&
(entry = cbmem_entry_find(id))) {
cbd->mcache = cbmem_entry_start(entry);
cbd->mcache_size = cbmem_entry_size(entry);
} else if (ENV_ROMSTAGE_OR_BEFORE) {
u8 *boundary = _ecbfs_mcache - REGION_SIZE(cbfs_mcache) *
CONFIG_CBFS_MCACHE_RW_PERCENTAGE / 100;
boundary = (u8 *)ALIGN_DOWN((uintptr_t)boundary,
CBFS_MCACHE_ALIGNMENT);
if (id == CBMEM_ID_CBFS_RO_MCACHE) {
cbd->mcache = _cbfs_mcache;
cbd->mcache_size = boundary - _cbfs_mcache;
} else if (id == CBMEM_ID_CBFS_RW_MCACHE) {
cbd->mcache = boundary;
cbd->mcache_size = _ecbfs_mcache - boundary;
}
}
}
const struct cbfs_boot_device *cbfs_get_boot_device(bool force_ro)
{
static struct cbfs_boot_device ro;
/* Ensure we always init RO mcache, even if first file is from RW.
Otherwise it may not be available when needed in later stages. */
if (ENV_INITIAL_STAGE && !force_ro && !region_device_sz(&ro.rdev))
cbfs_get_boot_device(true);
if (!force_ro) {
const struct cbfs_boot_device *rw = vboot_get_cbfs_boot_device();
/* This will return NULL if vboot isn't enabled, didn't run yet
or decided to boot into recovery mode. */
if (rw)
return rw;
}
if (region_device_sz(&ro.rdev))
return &ro;
if (fmap_locate_area_as_rdev("COREBOOT", &ro.rdev))
return NULL;
cbfs_boot_device_find_mcache(&ro, CBMEM_ID_CBFS_RO_MCACHE);
if (ENV_INITIAL_STAGE && !CONFIG(NO_CBFS_MCACHE)) {
cb_err_t err = cbfs_mcache_build(&ro.rdev, ro.mcache,
ro.mcache_size, NULL);
if (err && err != CB_CBFS_CACHE_FULL)
die("Failed to build RO mcache");
}
return &ro;
}
#if !CONFIG(NO_CBFS_MCACHE)
static void mcache_to_cbmem(const struct cbfs_boot_device *cbd, u32 cbmem_id)
{
if (!cbd)
return;
size_t real_size = cbfs_mcache_real_size(cbd->mcache, cbd->mcache_size);
void *cbmem_mcache = cbmem_add(cbmem_id, real_size);
if (!cbmem_mcache) {
printk(BIOS_ERR, "ERROR: Cannot allocate CBMEM mcache %#x (%#zx bytes)!\n",
cbmem_id, real_size);
return;
}
memcpy(cbmem_mcache, cbd->mcache, real_size);
}
static void cbfs_mcache_migrate(int unused)
{
mcache_to_cbmem(vboot_get_cbfs_boot_device(), CBMEM_ID_CBFS_RW_MCACHE);
mcache_to_cbmem(cbfs_get_boot_device(true), CBMEM_ID_CBFS_RO_MCACHE);
}
ROMSTAGE_CBMEM_INIT_HOOK(cbfs_mcache_migrate)
#endif

View File

@ -220,11 +220,8 @@ static void lb_boot_media_params(struct lb_header *header)
{
struct lb_boot_media_params *bmp;
const struct region_device *boot_dev;
struct region_device cbfs_dev;
boot_device_init();
if (cbfs_boot_region_device(&cbfs_dev))
const struct cbfs_boot_device *cbd = cbfs_get_boot_device(false);
if (!cbd)
return;
boot_dev = boot_device_ro();
@ -235,8 +232,8 @@ static void lb_boot_media_params(struct lb_header *header)
bmp->tag = LB_TAG_BOOT_MEDIA_PARAMS;
bmp->size = sizeof(*bmp);
bmp->cbfs_offset = region_device_offset(&cbfs_dev);
bmp->cbfs_size = region_device_sz(&cbfs_dev);
bmp->cbfs_offset = region_device_offset(&cbd->rdev);
bmp->cbfs_size = region_device_sz(&cbd->rdev);
bmp->boot_media_size = region_device_sz(boot_dev);
bmp->fmap_offset = get_fmap_flash_offset();

View File

@ -3,6 +3,7 @@
#define __VBOOT_VBOOT_COMMON_H__
#include <commonlib/region.h>
#include <cbfs.h>
#include <vb2_api.h>
/*
@ -50,14 +51,17 @@ int vboot_developer_mode_enabled(void);
int vboot_recovery_mode_enabled(void);
int vboot_can_enable_udc(void);
void vboot_run_logic(void);
int vboot_locate_cbfs(struct region_device *rdev);
const struct cbfs_boot_device *vboot_get_cbfs_boot_device(void);
#else /* !CONFIG_VBOOT */
static inline int vboot_developer_mode_enabled(void) { return 0; }
static inline int vboot_recovery_mode_enabled(void) { return 0; }
/* If VBOOT is not enabled, we are okay enabling USB device controller (UDC). */
static inline int vboot_can_enable_udc(void) { return 1; }
static inline void vboot_run_logic(void) {}
static inline int vboot_locate_cbfs(struct region_device *rdev) { return -1; }
static inline const struct cbfs_boot_device *vboot_get_cbfs_boot_device(void)
{
return NULL;
}
#endif
void vboot_save_data(struct vb2_context *ctx);

View File

@ -1,6 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#include <boot_device.h>
#include <cbfs.h>
#include <cbmem.h>
#include <commonlib/bsd/cbfs_private.h>
#include <console/console.h>
#include <ec/google/chromeec/ec.h>
#include <rmodule.h>
@ -22,12 +25,27 @@ _Static_assert(!CONFIG(VBOOT_RETURN_FROM_VERSTAGE) ||
int vboot_executed;
static void build_rw_mcache(void)
{
if (CONFIG(NO_CBFS_MCACHE))
return;
const struct cbfs_boot_device *cbd = vboot_get_cbfs_boot_device();
if (!cbd) /* Don't build RW mcache in recovery mode. */
return;
cb_err_t err = cbfs_mcache_build(&cbd->rdev, cbd->mcache,
cbd->mcache_size, NULL);
if (err && err != CB_CBFS_CACHE_FULL)
die("Failed to build RW mcache."); /* TODO: -> recovery? */
}
void vboot_run_logic(void)
{
if (verification_should_run()) {
/* Note: this path is not used for VBOOT_RETURN_FROM_VERSTAGE */
verstage_main();
vboot_executed = 1;
build_rw_mcache();
} else if (verstage_should_load()) {
struct cbfsf file;
struct prog verstage =
@ -55,21 +73,29 @@ void vboot_run_logic(void)
return;
vboot_executed = 1;
build_rw_mcache();
}
}
int vboot_locate_cbfs(struct region_device *rdev)
const struct cbfs_boot_device *vboot_get_cbfs_boot_device(void)
{
struct vb2_context *ctx;
/* Don't honor vboot results until the vboot logic has run. */
if (!vboot_logic_executed())
return -1;
return NULL;
ctx = vboot_get_context();
static struct cbfs_boot_device cbd;
if (region_device_sz(&cbd.rdev))
return &cbd;
struct vb2_context *ctx = vboot_get_context();
if (ctx->flags & VB2_CONTEXT_RECOVERY_MODE)
return -1;
return NULL;
return vboot_locate_firmware(ctx, rdev);
boot_device_init();
if (vboot_locate_firmware(ctx, &cbd.rdev))
return NULL;
cbfs_boot_device_find_mcache(&cbd, CBMEM_ID_CBFS_RW_MCACHE);
return &cbd;
}