coreboot-kgpe-d16/src/lib/fit.c

584 lines
16 KiB
C

/*
* Copyright 2013 Google Inc.
* Copyright 2018-present Facebook, Inc.
*
* Taken from depthcharge: src/boot/fit.c
*
* 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 <console/console.h>
#include <endian.h>
#include <stdint.h>
#include <bootmem.h>
#include <stdlib.h>
#include <string.h>
#include <program_loading.h>
#include <memrange.h>
#include <fit.h>
#include <boardid.h>
#include <commonlib/cbfs_serialized.h>
#include <commonlib/stdlib.h>
static struct list_node image_nodes;
static struct list_node config_nodes;
static struct list_node compat_strings;
struct compat_string_entry {
const char *compat_string;
struct list_node list_node;
};
/* Convert string to lowercase and replace '_' and spaces with '-'. */
static char *clean_compat_string(char *str)
{
for (size_t i = 0; i < strlen(str); i++) {
str[i] = tolower(str[i]);
if (str[i] == '_' || str[i] == ' ')
str[i] = '-';
}
return str;
}
static void fit_add_default_compat_strings(void)
{
char compat_string[80] = {};
if ((board_id() != UNDEFINED_STRAPPING_ID) &&
(sku_id() != UNDEFINED_STRAPPING_ID)) {
snprintf(compat_string, sizeof(compat_string),
"%s,%s-rev%u-sku%u", CONFIG_MAINBOARD_VENDOR,
CONFIG_MAINBOARD_PART_NUMBER, board_id(), sku_id());
fit_add_compat_string(compat_string);
}
if (sku_id() != UNDEFINED_STRAPPING_ID) {
snprintf(compat_string, sizeof(compat_string), "%s,%s-sku%u",
CONFIG_MAINBOARD_VENDOR, CONFIG_MAINBOARD_PART_NUMBER,
sku_id());
fit_add_compat_string(compat_string);
}
if (board_id() != UNDEFINED_STRAPPING_ID) {
snprintf(compat_string, sizeof(compat_string), "%s,%s-rev%u",
CONFIG_MAINBOARD_VENDOR, CONFIG_MAINBOARD_PART_NUMBER,
board_id());
fit_add_compat_string(compat_string);
}
snprintf(compat_string, sizeof(compat_string), "%s,%s",
CONFIG_MAINBOARD_VENDOR, CONFIG_MAINBOARD_PART_NUMBER);
fit_add_compat_string(compat_string);
}
static struct fit_image_node *find_image(const char *name)
{
struct fit_image_node *image;
list_for_each(image, image_nodes, list_node) {
if (!strcmp(image->name, name))
return image;
}
printk(BIOS_ERR, "ERROR: Cannot find image node %s!\n", name);
return NULL;
}
static struct fit_image_node *find_image_with_overlays(const char *name,
int bytes, struct list_node *prev)
{
struct fit_image_node *base = find_image(name);
if (!base)
return NULL;
int len = strnlen(name, bytes) + 1;
bytes -= len;
name += len;
while (bytes > 0) {
struct fit_overlay_chain *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)
{
struct fit_image_node *image = xzalloc(sizeof(*image));
image->compression = CBFS_COMPRESS_NONE;
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 = CBFS_COMPRESS_NONE;
else if (!strcmp("lzma", prop->prop.data))
image->compression = CBFS_COMPRESS_LZMA;
else if (!strcmp("lz4", prop->prop.data))
image->compression = CBFS_COMPRESS_LZ4;
else
image->compression = -1;
}
}
list_insert_after(&image->list_node, &image_nodes);
}
static void config_node(struct device_tree_node *node)
{
struct fit_config_node *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(const void *blob, uint32_t start_offset,
struct fdt_property *prop)
{
int offset = start_offset;
int size;
size = fdt_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;
}
void fit_update_chosen(struct device_tree *tree, const char *cmd_line)
{
const char *path[] = { "chosen", NULL };
struct device_tree_node *node;
node = dt_find_node(tree->root, path, NULL, NULL, 1);
dt_add_string_prop(node, "bootargs", cmd_line);
}
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;
node = dt_find_node(tree->root, path, NULL, NULL, 1);
u64 start = (uintptr_t)ramdisk_addr;
u64 end = start + ramdisk_size;
dt_add_u64_prop(node, "linux,initrd-start", start);
dt_add_u64_prop(node, "linux,initrd-end", end);
}
static void update_reserve_map(uint64_t start, uint64_t end,
struct device_tree *tree)
{
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);
}
struct entry_params {
unsigned addr_cells;
unsigned size_cells;
void *data;
};
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 update_mem_property(u64 start, u64 end, struct entry_params *params)
{
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;
}
struct mem_map {
struct memranges mem;
struct memranges reserved;
};
static bool walk_memory_table(const struct range_entry *r, void *arg)
{
struct mem_map *arg_map = arg;
/*
* Kernel likes its available memory areas at least 1MB
* aligned, let's trim the regions such that unaligned padding
* is added to reserved memory.
*/
if (range_entry_tag(r) == BM_MEM_RAM) {
uint64_t new_start = ALIGN_UP(range_entry_base(r), 1 * MiB);
uint64_t new_end = ALIGN_DOWN(range_entry_end(r), 1 * MiB);
if (new_start != range_entry_base(r))
memranges_insert(&arg_map->reserved,
range_entry_base(r),
new_start - range_entry_base(r),
BM_MEM_RESERVED);
if (new_start != new_end)
memranges_insert(&arg_map->mem, new_start,
new_end - new_start, BM_MEM_RAM);
if (new_end != range_entry_end(r))
memranges_insert(&arg_map->reserved, new_end,
range_entry_end(r) - new_end,
BM_MEM_RESERVED);
} else
memranges_insert(&arg_map->reserved, range_entry_base(r),
range_entry_size(r),
BM_MEM_RESERVED);
return true;
}
void fit_add_compat_string(const char *str)
{
struct compat_string_entry *compat_node;
compat_node = xzalloc(sizeof(*compat_node));
compat_node->compat_string = strdup(str);
clean_compat_string((char *)compat_node->compat_string);
list_insert_after(&compat_node->list_node, &compat_strings);
}
void fit_update_memory(struct device_tree *tree)
{
const struct range_entry *r;
struct device_tree_node *node;
u32 addr_cells = 1, size_cells = 1;
struct mem_map map;
printk(BIOS_INFO, "FIT: Updating devicetree memory entries\n");
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", (char *)"memory");
memranges_init_empty(&map.mem, NULL, 0);
memranges_init_empty(&map.reserved, NULL, 0);
bootmem_walk_os_mem(walk_memory_table, &map);
/* CBMEM regions are both carved out and explicitly reserved. */
memranges_each_entry(r, &map.reserved) {
update_reserve_map(range_entry_base(r), range_entry_end(r),
tree);
}
/*
* Count the amount of 'reg' entries we need (account for size limits).
*/
size_t count = 0;
memranges_each_entry(r, &map.mem) {
uint64_t size = range_entry_size(r);
uint64_t max_size = max_range(size_cells);
count += DIV_ROUND_UP(size, max_size);
}
/* Allocate the right amount of space and fill up the entries. */
size_t length = count * (addr_cells + size_cells) * sizeof(u32);
void *data = xzalloc(length);
struct entry_params add_params = { addr_cells, size_cells, data };
memranges_each_entry(r, &map.mem) {
update_mem_property(range_entry_base(r), range_entry_end(r),
&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);
memranges_teardown(&map.mem);
memranges_teardown(&map.reserved);
}
/*
* Finds a compat string and updates the compat position and rank.
* @param config The current config node to operate on
* @return 0 if compat updated, -1 if this FDT cannot be used.
*/
static int fit_update_compat(struct fit_config_node *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;
const struct fdt_header *fdt_header = fdt_blob;
uint32_t fdt_offset = be32_to_cpu(fdt_header->structure_offset);
if (config->fdt->compression != CBFS_COMPRESS_NONE) {
printk(BIOS_ERR,
"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) {
printk(BIOS_ERR,
"ERROR: config %s has overlay but no compat!\n",
config->name);
return -1;
}
if (fdt_find_compat(fdt_blob, fdt_offset, &config->compat)) {
printk(BIOS_ERR,
"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;
size_t i = 0;
struct compat_string_entry *compat_node;
list_for_each(compat_node, compat_strings, list_node) {
int pos = fit_check_compat(&config->compat,
compat_node->compat_string);
if (pos >= 0) {
config->compat_pos = pos;
config->compat_rank = i;
config->compat_string =
compat_node->compat_string;
}
i++;
}
return 0;
}
struct fit_config_node *fit_load(void *fit)
{
struct fit_image_node *image;
struct fit_config_node *config;
struct compat_string_entry *compat_node;
struct fit_overlay_chain *overlay_chain;
printk(BIOS_DEBUG, "FIT: Loading FIT from %p\n", fit);
struct device_tree *tree = fdt_unflatten(fit);
if (!tree) {
printk(BIOS_ERR, "ERROR: Failed to unflatten FIT image!\n");
return NULL;
}
const char *default_config_name = NULL;
struct fit_config_node *default_config = NULL;
struct fit_config_node *compat_config = NULL;
fit_unpack(tree, &default_config_name);
/* List the images we found. */
list_for_each(image, image_nodes, list_node)
printk(BIOS_DEBUG, "FIT: Image %s has %d bytes.\n", image->name,
image->size);
fit_add_default_compat_strings();
printk(BIOS_DEBUG, "FIT: Compat preference "
"(lowest to highest priority) :");
list_for_each(compat_node, compat_strings, list_node) {
printk(BIOS_DEBUG, " %s", compat_node->compat_string);
}
printk(BIOS_DEBUG, "\n");
/* Process and list the configs. */
list_for_each(config, config_nodes, list_node) {
if (!config->kernel) {
printk(BIOS_ERR,
"ERROR: config %s has no kernel, skipping.\n",
config->name);
continue;
}
if (!config->fdt) {
printk(BIOS_ERR,
"ERROR: config %s has no FDT, skipping.\n",
config->name);
continue;
}
if (config->ramdisk &&
config->ramdisk->compression < 0) {
printk(BIOS_WARNING, "WARN: Ramdisk is compressed with "
"an unsupported algorithm, discarding config %s."
"\n", config->name);
continue;
}
if (fit_update_compat(config))
continue;
printk(BIOS_DEBUG, "FIT: config %s", config->name);
if (default_config_name &&
!strcmp(config->name, default_config_name)) {
printk(BIOS_DEBUG, " (default)");
default_config = config;
}
printk(BIOS_DEBUG, ", kernel %s", config->kernel->name);
printk(BIOS_DEBUG, ", fdt %s", config->fdt->name);
list_for_each(overlay_chain, config->overlays, list_node)
printk(BIOS_DEBUG, " %s", overlay_chain->overlay->name);
if (config->ramdisk)
printk(BIOS_DEBUG, ", ramdisk %s",
config->ramdisk->name);
if (config->compat.name) {
printk(BIOS_DEBUG, ", compat");
int bytes = config->compat.size;
const char *compat_str = config->compat.data;
for (int pos = 0; bytes && compat_str[0]; pos++) {
printk(BIOS_DEBUG, " %s", compat_str);
if (pos == config->compat_pos)
printk(BIOS_DEBUG, " (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;
}
printk(BIOS_DEBUG, "\n");
}
struct fit_config_node *to_boot = NULL;
if (compat_config) {
to_boot = compat_config;
printk(BIOS_INFO, "FIT: Choosing best match %s for compat "
"%s.\n", to_boot->name, to_boot->compat_string);
} else if (default_config) {
to_boot = default_config;
printk(BIOS_INFO, "FIT: No match, choosing default %s.\n",
to_boot->name);
} else {
printk(BIOS_ERR, "FIT: No compatible or default configs. "
"Giving up.\n");
return NULL;
}
return to_boot;
}