diff options
-rw-r--r-- | src/include/fit.h | 78 | ||||
-rw-r--r-- | src/lib/fit.c | 487 |
2 files changed, 565 insertions, 0 deletions
diff --git a/src/include/fit.h b/src/include/fit.h new file mode 100644 index 0000000000..1b2f975042 --- /dev/null +++ b/src/include/fit.h @@ -0,0 +1,78 @@ +/* + * Copyright 2013 Google Inc. + * + * 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. + */ + +#ifndef __BOOT_FIT_H__ +#define __BOOT_FIT_H__ + +#include <stddef.h> +#include <stdint.h> + +#include "base/device_tree.h" +#include "base/list.h" + +typedef enum CompressionType +{ + CompressionInvalid, + CompressionNone, + CompressionLzma, + CompressionLz4, +} CompressionType; + +typedef struct FitImageNode +{ + const char *name; + void *data; + uint32_t size; + CompressionType compression; + + ListNode list_node; +} FitImageNode; + +typedef struct FitConfigNode +{ + const char *name; + const char *kernel; + FitImageNode *kernel_node; + const char *fdt; + FitImageNode *fdt_node; + const char *ramdisk; + FitImageNode *ramdisk_node; + FdtProperty compat; + int compat_rank; + int compat_pos; + + ListNode list_node; +} FitConfigNode; + +/* + * Unpack a FIT image into memory, choosing the right configuration through the + * compatible string set by fit_add_compat() and unflattening the corresponding + * kernel device tree. + */ +FitImageNode *fit_load(void *fit, char *cmd_line, DeviceTree **dt); + +/* + * Add a compatible string for the preferred kernel DT to the list for this + * platform. This should be called before the first fit_load() so it will be + * ranked as a better match than the default compatible strings. |compat| must + * stay accessible throughout depthcharge's runtime (i.e. not stack-allocated)! + */ +void fit_add_compat(const char *compat); + +void fit_add_ramdisk(DeviceTree *tree, void *ramdisk_addr, size_t ramdisk_size); + +#endif /* __BOOT_FIT_H__ */ diff --git a/src/lib/fit.c b/src/lib/fit.c new file mode 100644 index 0000000000..79af36c0b4 --- /dev/null +++ b/src/lib/fit.c @@ -0,0 +1,487 @@ +/* + * Copyright 2013 Google Inc. + * + * 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 <stdint.h> + +#include "base/ranges.h" +#include "boot/fit.h" + + + +static ListNode image_nodes; +static ListNode 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) +{ + const char pattern[] = "google,%s-rev%u-sku%u"; + u32 rev = lib_sysinfo.board_id; + u32 sku = lib_sysinfo.sku_id; + + static int done = 0; + if (done) + return; + done = 1; + + char *compat = xmalloc(sizeof(pattern) + sizeof(CONFIG_BOARD) + 20); + sprintf(compat, pattern, CONFIG_BOARD, + lib_sysinfo.board_id, lib_sysinfo.sku_id); + + char *c; + for (c = compat; *c != '\0'; c++) + if (*c == '_') + *c = '-'; + + if (sku != UNDEFINED_STRAPPING_ID && rev != UNDEFINED_STRAPPING_ID) + fit_add_compat(strdup(compat)); + + *strrchr(compat, '-') = '\0'; + if (rev != UNDEFINED_STRAPPING_ID) + fit_add_compat(strdup(compat)); + + *strrchr(compat, '-') = '\0'; + fit_add_compat(compat); +} + + + + +static void image_node(DeviceTreeNode *node) +{ + FitImageNode *image = xzalloc(sizeof(*image)); + image->compression = CompressionNone; + + image->name = node->name; + + DeviceTreeProperty *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(DeviceTreeNode *node) +{ + FitConfigNode *config = xzalloc(sizeof(*config)); + config->name = node->name; + + DeviceTreeProperty *prop; + list_for_each(prop, node->properties, list_node) { + if (!strcmp("kernel", prop->prop.name)) + config->kernel = prop->prop.data; + else if (!strcmp("fdt", prop->prop.name)) + config->fdt = prop->prop.data; + else if (!strcmp("ramdisk", prop->prop.name)) + config->ramdisk = prop->prop.data; + } + + list_insert_after(&config->list_node, &config_nodes); +} + +static void fit_unpack(DeviceTree *tree, const char **default_config) +{ + assert(tree && tree->root); + + DeviceTreeNode *top; + list_for_each(top, tree->root->children, list_node) { + DeviceTreeNode *child; + if (!strcmp("images", top->name)) { + + list_for_each(child, top->children, list_node) + image_node(child); + + } else if (!strcmp("configurations", top->name)) { + + DeviceTreeProperty *prop; + list_for_each(prop, top->properties, list_node) { + if (!strcmp("default", prop->prop.name) && + default_config) + *default_config = prop->prop.data; + } + + list_for_each(child, top->children, list_node) + config_node(child); + } + } +} + +static FitImageNode *find_image(const char *name) +{ + FitImageNode *image; + list_for_each(image, image_nodes, list_node) { + if (!strcmp(image->name, name)) + return image; + } + return NULL; +} + +static int fdt_find_compat(void *blob, uint32_t start_offset, FdtProperty *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(FdtProperty *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 void update_chosen(DeviceTree *tree, char *cmd_line) +{ + const char *path[] = { "chosen", NULL }; + DeviceTreeNode *node = dt_find_node(tree->root, path, NULL, NULL, 1); + + dt_add_string_prop(node, "bootargs", cmd_line); +} + +void fit_add_ramdisk(DeviceTree *tree, void *ramdisk_addr, size_t ramdisk_size) +{ + const char *path[] = { "chosen", NULL }; + DeviceTreeNode *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) +{ + DeviceTree *tree = (DeviceTree *)data; + + DeviceTreeReserveMapEntry *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 u32 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(DeviceTree *tree) +{ + Ranges mem; + Ranges reserved; + DeviceTreeNode *node; + u32 addr_cells = 1, 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, DeviceTree **dt) +{ + FdtHeader *header = (FdtHeader *)fit; + FitImageNode *image; + FitConfigNode *config; + int i; + + printf("Loading FIT.\n"); + + if (betohl(header->magic) != FdtMagic) { + printf("Bad FIT header magic value 0x%08x.\n", + betohl(header->magic)); + return NULL; + } + + DeviceTree *tree = fdt_unflatten(fit); + + 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 (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) + config->kernel_node = find_image(config->kernel); + if (config->fdt) + config->fdt_node = find_image(config->fdt); + if (config->ramdisk) + config->ramdisk_node = find_image(config->ramdisk); + + if (!config->kernel_node || + (config->fdt && !config->fdt_node)) { + printf("Missing image, discarding config %s.\n", + config->name); + list_remove(&config->list_node); + continue; + } + + if (config->fdt_node) { + if (config->fdt_node->compression != CompressionNone) { + printf("FDT compression not yet supported, " + "skipping config %s.\n", config->name); + list_remove(&config->list_node); + continue; + } + + void *fdt_blob = config->fdt_node->data; + FdtHeader *fdt_header = (FdtHeader *)fdt_blob; + uint32_t fdt_offset = + betohl(fdt_header->structure_offset); + config->compat_pos = -1; + config->compat_rank = -1; + if (!fdt_find_compat(fdt_blob, fdt_offset, + &config->compat)) { + for (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; + } + } + } + } + + 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); + if (config->fdt) + printf(", fdt %s", config->fdt); + if (config->ramdisk) + printf(", ramdisk %s", config->ramdisk); + 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; + } + + if (to_boot->fdt_node) { + *dt = fdt_unflatten(to_boot->fdt_node->data); + if (!*dt) { + printf("Failed to unflatten the kernel's fdt.\n"); + return NULL; + } + + /* Update only if non-NULL cmd line */ + if (cmd_line) + update_chosen(*dt, cmd_line); + + update_memory(*dt); + + if (to_boot->ramdisk_node) { + if (to_boot->ramdisk_node->compression + != CompressionNone) { + printf("Ramdisk compression not supported.\n"); + return NULL; + } + fit_add_ramdisk(*dt, to_boot->ramdisk_node->data, + to_boot->ramdisk_node->size); + } + } + + return to_boot->kernel_node; +} |