09f2921b5d
This patch ports the LZ4 decompression code that debuted in libpayload last year to coreboot for use in CBFS stages (upgrading the base algorithm to LZ4's dev branch to access the new in-place decompression checks). This is especially useful for pre-RAM stages in constrained SRAM-based systems, which previously could not be compressed due to the size requirements of the LZMA scratchpad and bounce buffer. The LZ4 algorithm offers a very lean decompressor function and in-place decompression support to achieve roughly the same boot speed gains (trading compression ratio for decompression time) with nearly no memory overhead. For now we only activate it for the stages that had previously not been compressed at all on non-XIP (read: non-x86) boards. In the future we may also consider replacing LZMA completely for certain boards, since which algorithm wins out on boot speed depends on board-specific parameters (architecture, processor speed, SPI transfer rate, etc.). BRANCH=None BUG=None TEST=Built and booted Oak, Jerry, Nyan and Falco. Measured boot time on Oak to be about ~20ms faster (cutting load times for affected stages almost in half). Change-Id: Iec256c0e6d585d1b69985461939884a54e3ab900 Signed-off-by: Julius Werner <jwerner@chromium.org> Reviewed-on: https://review.coreboot.org/13638 Tested-by: build bot (Jenkins) Reviewed-by: Aaron Durbin <adurbin@chromium.org>
310 lines
7.3 KiB
C
310 lines
7.3 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>
|
|
|
|
#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 (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 && !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);
|
|
}
|
|
|
|
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_ARCH_X86) &&
|
|
IS_ENABLED(CONFIG_SPI_FLASH_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);
|
|
|
|
arch_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;
|
|
}
|
|
|
|
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;
|
|
|
|
/* Find location of header using signed 32-bit offset from
|
|
* end of CBFS region. */
|
|
offset = CONFIG_CBFS_SIZE - sizeof(int32_t);
|
|
if (rdev_readat(bdev, &rel_offset, offset, sizeof(int32_t)) < 0)
|
|
return -1;
|
|
|
|
offset = CONFIG_CBFS_SIZE + 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_VERIFY_FIRMWARE
|
|
&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();
|
|
}
|
|
}
|