coreboot-kgpe-d16/src/lib/cbfs.c
Julius Werner f975e55dcd cbfs: Add "struct" file type and associated helpers
This patch adds functionality to compile a C data structure into a raw
binary file, add it to CBFS and allow coreboot to load it at runtime.
This is useful in all cases where we need to be able to have several
larger data sets available in an image, but will only require a small
subset of them at boot (a classic example would be DRAM parameters) or
only require it in certain boot modes. This allows us to load less data
from flash and increase boot speed compared to solutions that compile
all data sets into a stage.

Each structure has to be defined in a separate .c file which contains no
functions and only a single global variable. The data type must be
serialization safe (composed of only fixed-width types, paying attention
to padding). It must be added to CBFS in a Makefile with the 'struct'
file processor.

Change-Id: Iab65c0b6ebea235089f741eaa8098743e54d6ccc
Signed-off-by: Julius Werner <jwerner@chromium.org>
Reviewed-on: https://review.coreboot.org/16272
Tested-by: build bot (Jenkins)
Reviewed-by: Aaron Durbin <adurbin@chromium.org>
2016-08-27 01:16:22 +02:00

336 lines
8 KiB
C

/*
* This file is part of the coreboot project.
*
* Copyright (C) 2011 secunet Security Networks AG
* Copyright 2015 Google Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <boot_device.h>
#include <cbfs.h>
#include <commonlib/compression.h>
#include <endian.h>
#include <lib.h>
#include <symbols.h>
#include <timestamp.h>
#include "fmap_config.h"
#define ERROR(x...) printk(BIOS_ERR, "CBFS: " x)
#define LOG(x...) printk(BIOS_INFO, "CBFS: " x)
#if IS_ENABLED(CONFIG_DEBUG_CBFS)
#define DEBUG(x...) printk(BIOS_SPEW, "CBFS: " x)
#else
#define DEBUG(x...)
#endif
int cbfs_boot_locate(struct cbfsf *fh, const char *name, uint32_t *type)
{
struct region_device rdev;
const struct region_device *boot_dev;
struct cbfs_props props;
if (cbfs_boot_region_properties(&props))
return -1;
/* All boot CBFS operations are performed using the RO devie. */
boot_dev = boot_device_ro();
if (boot_dev == NULL)
return -1;
if (rdev_chain(&rdev, boot_dev, props.offset, props.size))
return -1;
return cbfs_locate(fh, &rdev, name, type);
}
void *cbfs_boot_map_with_leak(const char *name, uint32_t type, size_t *size)
{
struct cbfsf fh;
size_t fsize;
if (cbfs_boot_locate(&fh, name, &type))
return NULL;
fsize = region_device_sz(&fh.data);
if (size != NULL)
*size = fsize;
return rdev_mmap(&fh.data, 0, fsize);
}
size_t cbfs_load_and_decompress(const struct region_device *rdev, size_t offset,
size_t in_size, void *buffer, size_t buffer_size, uint32_t compression)
{
size_t out_size;
switch (compression) {
case CBFS_COMPRESS_NONE:
if (buffer_size < in_size)
return 0;
if (rdev_readat(rdev, buffer, offset, in_size) != in_size)
return 0;
return in_size;
case CBFS_COMPRESS_LZ4:
if ((ENV_BOOTBLOCK || ENV_VERSTAGE) &&
!IS_ENABLED(CONFIG_COMPRESS_PRERAM_STAGES))
return 0;
/* Load the compressed image to the end of the available memory
* area for in-place decompression. It is the responsibility of
* the caller to ensure that buffer_size is large enough
* (see compression.h, guaranteed by cbfstool for stages). */
void *compr_start = buffer + buffer_size - in_size;
if (rdev_readat(rdev, compr_start, offset, in_size) != in_size)
return 0;
timestamp_add_now(TS_START_ULZ4F);
out_size = ulz4fn(compr_start, in_size, buffer, buffer_size);
timestamp_add_now(TS_END_ULZ4F);
return out_size;
case CBFS_COMPRESS_LZMA:
if (ENV_BOOTBLOCK || ENV_VERSTAGE)
return 0;
if ((ENV_ROMSTAGE || ENV_POSTCAR)
&& !IS_ENABLED(CONFIG_COMPRESS_RAMSTAGE))
return 0;
void *map = rdev_mmap(rdev, offset, in_size);
if (map == NULL)
return 0;
/* Note: timestamp not useful for memory-mapped media (x86) */
timestamp_add_now(TS_START_ULZMA);
out_size = ulzman(map, in_size, buffer, buffer_size);
timestamp_add_now(TS_END_ULZMA);
rdev_munmap(rdev, map);
return out_size;
default:
return 0;
}
}
static inline int tohex4(unsigned int c)
{
return (c <= 9) ? (c + '0') : (c - 10 + 'a');
}
static void tohex16(unsigned int val, char* dest)
{
dest[0] = tohex4(val>>12);
dest[1] = tohex4((val>>8) & 0xf);
dest[2] = tohex4((val>>4) & 0xf);
dest[3] = tohex4(val & 0xf);
}
void *cbfs_boot_map_optionrom(uint16_t vendor, uint16_t device)
{
char name[17] = "pciXXXX,XXXX.rom";
tohex16(vendor, name+3);
tohex16(device, name+8);
return cbfs_boot_map_with_leak(name, CBFS_TYPE_OPTIONROM, NULL);
}
void *cbfs_boot_load_stage_by_name(const char *name)
{
struct cbfsf fh;
struct prog stage = PROG_INIT(PROG_UNKNOWN, name);
uint32_t type = CBFS_TYPE_STAGE;
if (cbfs_boot_locate(&fh, name, &type))
return NULL;
/* Chain data portion in the prog. */
cbfs_file_data(prog_rdev(&stage), &fh);
if (cbfs_prog_stage_load(&stage))
return NULL;
return prog_entry(&stage);
}
size_t cbfs_boot_load_struct(const char *name, void *buf, size_t buf_size)
{
struct cbfsf fh;
uint32_t compression_algo;
size_t decompressed_size;
uint32_t type = CBFS_TYPE_STRUCT;
if (cbfs_boot_locate(&fh, name, &type) < 0)
return 0;
if (cbfsf_decompression_info(&fh, &compression_algo,
&decompressed_size) < 0
|| decompressed_size > buf_size)
return 0;
return cbfs_load_and_decompress(&fh.data, 0, region_device_sz(&fh.data),
buf, buf_size, compression_algo);
}
int cbfs_prog_stage_load(struct prog *pstage)
{
struct cbfs_stage stage;
uint8_t *load;
void *entry;
size_t fsize;
size_t foffset;
const struct region_device *fh = prog_rdev(pstage);
if (rdev_readat(fh, &stage, 0, sizeof(stage)) != sizeof(stage))
return -1;
fsize = region_device_sz(fh);
fsize -= sizeof(stage);
foffset = 0;
foffset += sizeof(stage);
assert(fsize == stage.len);
/* Note: cbfs_stage fields are currently in the endianness of the
* running processor. */
load = (void *)(uintptr_t)stage.load;
entry = (void *)(uintptr_t)stage.entry;
/* Hacky way to not load programs over read only media. The stages
* that would hit this path initialize themselves. */
if (ENV_VERSTAGE && !IS_ENABLED(CONFIG_NO_XIP_EARLY_STAGES) &&
IS_ENABLED(CONFIG_BOOT_DEVICE_MEMORY_MAPPED)) {
void *mapping = rdev_mmap(fh, foffset, fsize);
rdev_munmap(fh, mapping);
if (mapping == load)
goto out;
}
fsize = cbfs_load_and_decompress(fh, foffset, fsize, load,
stage.memlen, stage.compression);
if (!fsize)
return -1;
/* Clear area not covered by file. */
memset(&load[fsize], 0, stage.memlen - fsize);
prog_segment_loaded((uintptr_t)load, stage.memlen, SEG_FINAL);
out:
prog_set_area(pstage, load, stage.memlen);
prog_set_entry(pstage, entry, NULL);
return 0;
}
/* This only supports the "COREBOOT" fmap region. */
static int cbfs_master_header_props(struct cbfs_props *props)
{
struct cbfs_header header;
const struct region_device *bdev;
int32_t rel_offset;
size_t offset;
bdev = boot_device_ro();
if (bdev == NULL)
return -1;
size_t fmap_top = ___FMAP__COREBOOT_BASE + ___FMAP__COREBOOT_SIZE;
/* Find location of header using signed 32-bit offset from
* end of CBFS region. */
offset = fmap_top - sizeof(int32_t);
if (rdev_readat(bdev, &rel_offset, offset, sizeof(int32_t)) < 0)
return -1;
offset = fmap_top + rel_offset;
if (rdev_readat(bdev, &header, offset, sizeof(header)) < 0)
return -1;
header.magic = ntohl(header.magic);
header.romsize = ntohl(header.romsize);
header.offset = ntohl(header.offset);
if (header.magic != CBFS_HEADER_MAGIC)
return -1;
props->offset = header.offset;
props->size = header.romsize;
props->size -= props->offset;
printk(BIOS_SPEW, "CBFS @ %zx size %zx\n", props->offset, props->size);
return 0;
}
/* This struct is marked as weak to allow a particular platform to
* override the master header logic. This implementation should work for most
* devices. */
const struct cbfs_locator __attribute__((weak)) cbfs_master_header_locator = {
.name = "Master Header Locator",
.locate = cbfs_master_header_props,
};
extern const struct cbfs_locator vboot_locator;
static const struct cbfs_locator *locators[] = {
#if CONFIG_VBOOT
&vboot_locator,
#endif
&cbfs_master_header_locator,
};
int cbfs_boot_region_properties(struct cbfs_props *props)
{
int i;
boot_device_init();
for (i = 0; i < ARRAY_SIZE(locators); i++) {
const struct cbfs_locator *ops;
ops = locators[i];
if (ops->locate == NULL)
continue;
if (ops->locate(props))
continue;
LOG("'%s' located CBFS at [%zx:%zx)\n",
ops->name, props->offset, props->offset + props->size);
return 0;
}
return -1;
}
void cbfs_prepare_program_locate(void)
{
int i;
boot_device_init();
for (i = 0; i < ARRAY_SIZE(locators); i++) {
if (locators[i]->prepare == NULL)
continue;
locators[i]->prepare();
}
}