blob: faef4cba3904e7b0be25ab05db9603455729c287 [file] [log] [blame] [edit]
/*
* Copyright 2013 Google LLC
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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; either version 2 of
* the License, or (at your option) any later version.
*
* 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 <endian.h>
#include <libpayload.h>
#include <lp_vboot.h>
#include <lzma.h>
#include <lz4.h>
#include <stdint.h>
#include <tlcl.h>
#include <ctype.h>
#include "base/ranges.h"
#include "base/timestamp.h"
#include "boot/fit.h"
#include "drivers/power/power.h"
#include "image/symbols.h"
#include "vboot/stages.h"
static struct list_node image_nodes;
static struct list_node config_nodes;
static const char *fit_kernel_compat[10] = { NULL };
static int num_fit_kernel_compat = 0;
void fit_add_compat(const char *compat)
{
assert(num_fit_kernel_compat < ARRAY_SIZE(fit_kernel_compat));
fit_kernel_compat[num_fit_kernel_compat++] = compat;
}
static void fit_add_default_compats(void)
{
u32 rev = lib_sysinfo.board_id;
u32 sku = lib_sysinfo.sku_id;
char compat[256];
char base[256];
char sku_string[16];
char rev_string[16];
static int done = 0;
if (done)
return;
done = 1;
int ret;
struct cb_mainboard *mainboard =
phys_to_virt(lib_sysinfo.cb_mainboard);
const char *mb_part_string = cb_mb_part_string(mainboard);
ret = snprintf(base, sizeof(base), "google,%s", mb_part_string);
assert(ret < sizeof(base));
sprintf(rev_string, "-rev%u", rev);
sprintf(sku_string, "-sku%u", sku);
char *c;
for (c = base; *c != '\0'; c++) {
if (*c == '_')
*c = '-';
else
*c = tolower(*c);
}
if (sku != UNDEFINED_STRAPPING_ID && rev != UNDEFINED_STRAPPING_ID) {
ret = snprintf(compat, sizeof(compat),
"%s%s%s", base, rev_string, sku_string);
assert(ret < sizeof(compat));
fit_add_compat(strdup(compat));
}
if (rev != UNDEFINED_STRAPPING_ID) {
ret = snprintf(compat, sizeof(compat),
"%s%s", base, rev_string);
assert(ret < sizeof(compat));
fit_add_compat(strdup(compat));
}
if (sku != UNDEFINED_STRAPPING_ID) {
ret = snprintf(compat, sizeof(compat),
"%s%s", base, sku_string);
assert(ret < sizeof(compat));
fit_add_compat(strdup(compat));
}
fit_add_compat(strdup(base));
}
static FitImageNode *find_image(const char *name)
{
FitImageNode *image;
list_for_each(image, image_nodes, list_node) {
if (!strcmp(image->name, name))
return image;
}
printf("ERROR: Can't find image node %s!\n", name);
return NULL;
}
static FitImageNode *find_image_with_overlays(const char *name, int bytes,
struct list_node *prev)
{
FitImageNode *base = find_image(name);
if (!base)
return NULL;
int len = strnlen(name, bytes) + 1;
bytes -= len;
name += len;
while (bytes > 0) {
FitOverlayChain *next = xzalloc(sizeof(*next));
next->overlay = find_image(name);
if (!next->overlay)
return NULL;
list_insert_after(&next->list_node, prev);
prev = &next->list_node;
len = strnlen(name, bytes) + 1;
bytes -= len;
name += len;
}
return base;
}
static void image_node(struct device_tree_node *node)
{
FitImageNode *image = xzalloc(sizeof(*image));
image->compression = CompressionNone;
image->name = node->name;
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node) {
if (!strcmp("data", prop->prop.name)) {
image->data = prop->prop.data;
image->size = prop->prop.size;
} else if (!strcmp("compression", prop->prop.name)) {
if (!strcmp("none", prop->prop.data))
image->compression = CompressionNone;
else if (!strcmp("lzma", prop->prop.data))
image->compression = CompressionLzma;
else if (!strcmp("lz4", prop->prop.data))
image->compression = CompressionLz4;
else
image->compression = CompressionInvalid;
}
}
list_insert_after(&image->list_node, &image_nodes);
}
static void config_node(struct device_tree_node *node)
{
FitConfigNode *config = xzalloc(sizeof(*config));
config->name = node->name;
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node) {
if (!strcmp("kernel", prop->prop.name))
config->kernel = find_image(prop->prop.data);
else if (!strcmp("fdt", prop->prop.name))
config->fdt = find_image_with_overlays(prop->prop.data,
prop->prop.size, &config->overlays);
else if (!strcmp("ramdisk", prop->prop.name))
config->ramdisk = find_image(prop->prop.data);
else if (!strcmp("compatible", prop->prop.name))
config->compat = prop->prop;
}
list_insert_after(&config->list_node, &config_nodes);
}
static void fit_unpack(struct device_tree *tree, const char **default_config)
{
struct device_tree_node *child;
struct device_tree_node *images = dt_find_node_by_path(tree, "/images",
NULL, NULL, 0);
if (images)
list_for_each(child, images->children, list_node)
image_node(child);
struct device_tree_node *configs = dt_find_node_by_path(tree,
"/configurations", NULL, NULL, 0);
if (configs) {
*default_config = dt_find_string_prop(configs, "default");
list_for_each(child, configs->children, list_node)
config_node(child);
}
}
static int fdt_find_compat(void *blob, uint32_t start_offset,
struct fdt_property *prop)
{
int offset = start_offset;
int size;
size = fdt_next_node_name(blob, offset, NULL);
if (!size)
return -1;
offset += size;
while ((size = fdt_next_property(blob, offset, prop))) {
if (!strcmp("compatible", prop->name))
return 0;
offset += size;
}
prop->name = NULL;
return -1;
}
static int fit_check_compat(struct fdt_property *compat_prop,
const char *compat_name)
{
int bytes = compat_prop->size;
const char *compat_str = compat_prop->data;
for (int pos = 0; bytes && compat_str[0]; pos++) {
if (!strncmp(compat_str, compat_name, bytes))
return pos;
int len = strlen(compat_str) + 1;
compat_str += len;
bytes -= len;
}
return -1;
}
static int fit_rank_compat(FitConfigNode *config)
{
// If there was no "compatible" property in config node, this is a
// legacy FIT image. Must extract compat prop from FDT itself.
if (!config->compat.name) {
void *fdt_blob = config->fdt->data;
struct fdt_header *fdt_header = fdt_blob;
uint32_t fdt_offset =
betohl(fdt_header->structure_offset);
if (config->fdt->compression != CompressionNone) {
printf("ERROR: config %s has a compressed FDT without "
"external compatible property, skipping.\n",
config->name);
return -1;
}
// FDT overlays are not supported in legacy FIT images.
if (config->overlays.next) {
printf("ERROR: config %s has overlay but no compat!\n",
config->name);
return -1;
}
if (fdt_find_compat(fdt_blob, fdt_offset, &config->compat)) {
printf("ERROR: Can't find compat string in FDT %s "
"for config %s, skipping.\n",
config->fdt->name, config->name);
return -1;
}
}
config->compat_pos = -1;
config->compat_rank = -1;
for (int i = 0; i < num_fit_kernel_compat; i++) {
int pos = fit_check_compat(&config->compat,
fit_kernel_compat[i]);
if (pos >= 0) {
config->compat_pos = pos;
config->compat_rank = i;
break;
}
}
return 0;
}
size_t fit_decompress(FitImageNode *node, void *buffer, size_t bufsize)
{
switch (node->compression) {
case CompressionNone:
printf("Relocating %s to %p\n", node->name, buffer);
memmove(buffer, node->data, MIN(node->size, bufsize));
return node->size <= bufsize ? node->size : 0;
case CompressionLzma:
printf("LZMA decompressing %s to %p\n", node->name, buffer);
return ulzman(node->data, node->size, buffer, bufsize);
case CompressionLz4:
printf("LZ4 decompressing %s to %p\n", node->name, buffer);
return ulz4fn(node->data, node->size, buffer, bufsize);
default:
printf("ERROR: Illegal compression algorithm (%d) for %s!\n",
node->compression, node->name);
return 0;
}
}
static void *get_fdt_data(FitImageNode *fdt)
{
// If the FDT isn't compressed, no need to alloc anything.
if (fdt->compression == CompressionNone)
return fdt->data;
// Reuse the output FDT buffer as a convenient, guaranteed FDT-sized
// scratchpad that is still unused (for it's real purpose) right now.
void *buffer = (void *)&_fit_fdt_start;
size_t size = _fit_fdt_end - _fit_fdt_start;
size = fit_decompress(fdt, buffer, size);
if (!size)
return NULL;
void *ret = malloc(size);
if (ret)
memcpy(ret, buffer, size);
return ret;
}
static void update_chosen(struct device_tree *tree, char *cmd_line)
{
int ret;
union {
struct {
u8 tpm_buf[64];
};
struct {
uint64_t kaslr;
uint8_t rng[56];
};
} *seed = xzalloc(sizeof(*seed));
uint32_t size;
const char *path[] = { "chosen", NULL };
struct device_tree_node *node = dt_find_node(tree->root, path,
NULL, NULL, 1);
/* Update only if non-NULL cmd line */
if (cmd_line)
dt_add_string_prop(node, "bootargs", cmd_line);
ret = TlclGetRandom(seed->tpm_buf, sizeof(seed->tpm_buf), &size);
if (ret || size != sizeof(seed->tpm_buf)) {
printf("TPM failed to populate kASLR seed buffer.\n");
if (!vboot_in_recovery()) {
struct vb2_context *ctx = vboot_get_context();
vb2api_fail(ctx, VB2_RECOVERY_RW_TPM_R_ERROR, ret);
vb2ex_commit_data(ctx);
reboot();
}
/* In recovery we'd rather continue with a weak seed than risk
tripping up the kernel. We don't expect untrusted code to run
there anyway, so kernel exploits are less of a concern. */
}
timestamp_mix_in_randomness(seed->tpm_buf, sizeof(seed->tpm_buf));
dt_add_u64_prop(node, "kaslr-seed", seed->kaslr);
dt_add_bin_prop(node, "rng-seed", seed->rng, sizeof(seed->rng));
}
void fit_add_ramdisk(struct device_tree *tree, void *ramdisk_addr,
size_t ramdisk_size)
{
const char *path[] = { "chosen", NULL };
struct device_tree_node *node = dt_find_node(tree->root, path,
NULL, NULL, 1);
/* Warning: this assumes the ramdisk is currently located below 4GiB. */
u32 start = (uintptr_t)ramdisk_addr;
u32 end = start + ramdisk_size;
dt_add_u32_prop(node, "linux,initrd-start", start);
dt_add_u32_prop(node, "linux,initrd-end", end);
}
static void update_reserve_map(uint64_t start, uint64_t end, void *data)
{
struct device_tree *tree = (struct device_tree *)data;
struct device_tree_reserve_map_entry *entry = xzalloc(sizeof(*entry));
entry->start = start;
entry->size = end - start;
list_insert_after(&entry->list_node, &tree->reserve_map);
}
typedef struct EntryParams
{
unsigned addr_cells;
unsigned size_cells;
void *data;
} EntryParams;
static uint64_t max_range(unsigned size_cells)
{
// Split up ranges who's sizes are too large to fit in #size-cells.
// The largest value we can store isn't a power of two, so we'll round
// down to make the math easier.
return 0x1ULL << (size_cells * 32 - 1);
}
static void count_entries(u64 start, u64 end, void *pdata)
{
EntryParams *params = (EntryParams *)pdata;
unsigned *count = (unsigned *)params->data;
u64 size = end - start;
u64 max_size = max_range(params->size_cells);
*count += ALIGN_UP(size, max_size) / max_size;
}
static void update_mem_property(u64 start, u64 end, void *pdata)
{
EntryParams *params = (EntryParams *)pdata;
u8 *data = (u8 *)params->data;
u64 full_size = end - start;
while (full_size) {
const u64 max_size = max_range(params->size_cells);
const u64 size = MIN(max_size, full_size);
dt_write_int(data, start, params->addr_cells * sizeof(u32));
data += params->addr_cells * sizeof(uint32_t);
start += size;
dt_write_int(data, size, params->size_cells * sizeof(u32));
data += params->size_cells * sizeof(uint32_t);
full_size -= size;
}
params->data = data;
}
static void update_memory(struct device_tree *tree)
{
Ranges mem;
Ranges reserved;
struct device_tree_node *node;
u32 addr_cells = 2, size_cells = 1;
dt_read_cell_props(tree->root, &addr_cells, &size_cells);
// First remove all existing device_type="memory" nodes, then add ours.
list_for_each(node, tree->root->children, list_node) {
const char *devtype = dt_find_string_prop(node, "device_type");
if (devtype && !strcmp(devtype, "memory"))
list_remove(&node->list_node);
}
node = xzalloc(sizeof(*node));
node->name = "memory";
list_insert_after(&node->list_node, &tree->root->children);
dt_add_string_prop(node, "device_type", "memory");
// Read memory info from coreboot (ranges are merged automatically).
ranges_init(&mem);
ranges_init(&reserved);
#define MEMORY_ALIGNMENT (1 << 20)
for (int i = 0; i < lib_sysinfo.n_memranges; i++) {
struct memrange *range = &lib_sysinfo.memrange[i];
uint64_t start = range->base;
uint64_t end = range->base + range->size;
/*
* Kernel likes its availabe memory areas at least 1MB
* aligned, let's trim the regions such that unaligned padding
* is added to reserved memory.
*/
if (range->type == CB_MEM_RAM) {
uint64_t new_start = ALIGN_UP(start, MEMORY_ALIGNMENT);
uint64_t new_end = ALIGN_DOWN(end, MEMORY_ALIGNMENT);
if (new_start != start)
ranges_add(&reserved, start, new_start);
if (new_start != new_end)
ranges_add(&mem, new_start, new_end);
if (new_end != end)
ranges_add(&reserved, new_end, end);
} else {
ranges_add(&reserved, start, end);
}
}
// CBMEM regions are both carved out and explicitly reserved.
ranges_for_each(&reserved, &update_reserve_map, tree);
// Count the amount of 'reg' entries we need (account for size limits).
unsigned count = 0;
EntryParams count_params = { addr_cells, size_cells, &count };
ranges_for_each(&mem, &count_entries, &count_params);
// Allocate the right amount of space and fill up the entries.
size_t length = count * (addr_cells + size_cells) * sizeof(u32);
void *data = xmalloc(length);
EntryParams add_params = { addr_cells, size_cells, data };
ranges_for_each(&mem, &update_mem_property, &add_params);
assert(add_params.data - data == length);
// Assemble the final property and add it to the device tree.
dt_add_bin_prop(node, "reg", data, length);
}
FitImageNode *fit_load(void *fit, char *cmd_line, struct device_tree **dt)
{
FitImageNode *image;
FitConfigNode *config;
FitOverlayChain *overlay_chain;
printf("Loading FIT.\n");
struct device_tree *tree = fdt_unflatten(fit);
if (!tree) {
printf("Failed to unflatten FIT image!\n");
return NULL;
}
const char *default_config_name = NULL;
FitConfigNode *default_config = NULL;
FitConfigNode *compat_config = NULL;
fit_unpack(tree, &default_config_name);
// List the images we found.
list_for_each(image, image_nodes, list_node)
printf("Image %s has %d bytes.\n", image->name, image->size);
fit_add_default_compats();
printf("Compat preference:");
for (int i = 0; i < num_fit_kernel_compat; i++)
printf(" %s", fit_kernel_compat[i]);
printf("\n");
// Process and list the configs.
list_for_each(config, config_nodes, list_node) {
if (!config->kernel) {
printf("ERROR: config %s has no kernel, skipping.\n",
config->name);
continue;
}
if (!config->fdt) {
printf("ERROR: config %s has no FDT, skipping.\n",
config->name);
continue;
}
if (fit_rank_compat(config) != 0)
continue;
printf("Config %s", config->name);
if (default_config_name &&
!strcmp(config->name, default_config_name)) {
printf(" (default)");
default_config = config;
}
printf(", kernel %s", config->kernel->name);
printf(", fdt %s", config->fdt->name);
list_for_each(overlay_chain, config->overlays, list_node)
printf(" %s", overlay_chain->overlay->name);
if (config->ramdisk)
printf(", ramdisk %s", config->ramdisk->name);
if (config->compat.name) {
printf(", compat");
int bytes = config->compat.size;
const char *compat_str = config->compat.data;
for (int pos = 0; bytes && compat_str[0]; pos++) {
printf(" %s", compat_str);
if (pos == config->compat_pos)
printf(" (match)");
int len = strlen(compat_str) + 1;
compat_str += len;
bytes -= len;
}
if (config->compat_rank >= 0 && (!compat_config ||
config->compat_rank < compat_config->compat_rank))
compat_config = config;
}
printf("\n");
}
FitConfigNode *to_boot = NULL;
if (compat_config) {
to_boot = compat_config;
printf("Choosing best match %s for compat %s.\n",
to_boot->name, fit_kernel_compat[to_boot->compat_rank]);
} else if (default_config) {
to_boot = default_config;
printf("No match, choosing default %s.\n", to_boot->name);
} else {
printf("No compatible or default configs. Giving up.\n");
// We're leaking memory here, but at this point we're beyond
// saving anyway.
return NULL;
}
void *fdt_data = get_fdt_data(to_boot->fdt);
if (!fdt_data) {
printf("ERROR: Can't decompress FDT %s!\n",
to_boot->fdt->name);
return NULL;
}
*dt = fdt_unflatten(fdt_data);
if (!*dt) {
printf("Failed to unflatten the kernel's fdt.\n");
return NULL;
}
list_for_each(overlay_chain, to_boot->overlays, list_node) {
fdt_data = get_fdt_data(overlay_chain->overlay);
if (!fdt_data) {
printf("ERROR: Can't decompress overlay %s!\n",
overlay_chain->overlay->name);
return NULL;
}
struct device_tree *overlay = fdt_unflatten(fdt_data);
if (!overlay) {
printf("ERROR: Can't unflatten overlay %s!\n",
overlay_chain->overlay->name);
return NULL;
}
if (dt_apply_overlay(*dt, overlay) != 0) {
printf("ERROR: Failed to apply overlay %s!\n",
overlay_chain->overlay->name);
return NULL;
}
}
update_chosen(*dt, cmd_line);
update_memory(*dt);
if (to_boot->ramdisk) {
if (to_boot->ramdisk->compression != CompressionNone) {
printf("Ramdisk compression not supported.\n");
return NULL;
}
fit_add_ramdisk(*dt, to_boot->ramdisk->data,
to_boot->ramdisk->size);
}
return to_boot->kernel;
}