From a892cde653d40e39d399b1bc4c438e3dc2d00cd6 Mon Sep 17 00:00:00 2001 From: Patrick Rudolph Date: Thu, 19 Apr 2018 14:39:07 +0200 Subject: lib: Add FIT payload support * Add support for parsing and booting FIT payloads. * Build fit loader code from depthcharge. * Fix coding style. * Add Kconfig option to add compiletime support for FIT. * Add support for initrd. * Add default compat strings * Apply optional devicetree fixups using dt_apply_fixups Starting at this point the CBFS payload/ can be either SELF or FIT. Tested on Cavium SoC: Parses and loads a Linux kernel 4.16.3. Tested on Cavium SoC: Parses and loads a Linux kernel 4.15.0. Tested on Cavium SoC: Parses and loads a Linux kernel 4.1.52. Change-Id: I0f27b92a5e074966f893399eb401eb97d784850d Signed-off-by: Patrick Rudolph Reviewed-on: https://review.coreboot.org/25019 Tested-by: build bot (Jenkins) Reviewed-by: Philipp Deppenwiese --- src/lib/fit.c | 503 ++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 277 insertions(+), 226 deletions(-) (limited to 'src/lib/fit.c') diff --git a/src/lib/fit.c b/src/lib/fit.c index 79af36c0b4..fe8a82ef97 100644 --- a/src/lib/fit.c +++ b/src/lib/fit.c @@ -1,8 +1,8 @@ /* * Copyright 2013 Google Inc. + * Copyright 2018-present Facebook, Inc. * - * See file CREDITS for list of people who contributed to this - * project. + * 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 @@ -17,93 +17,99 @@ #include #include -#include #include - -#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) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 '_' with '-'. */ +static char *clean_compat_string(char *str) { - assert(num_fit_kernel_compat < ARRAY_SIZE(fit_kernel_compat)); - fit_kernel_compat[num_fit_kernel_compat++] = compat; + for (size_t i = 0; i < strlen(str); i++) { + str[i] = tolower(str[i]); + if (str[i] == '_') + str[i] = '-'; + } + + return str; } -static void fit_add_default_compats(void) +static void fit_add_default_compat_strings(void) { - const char pattern[] = "google,%s-rev%u-sku%u"; - u32 rev = lib_sysinfo.board_id; - u32 sku = lib_sysinfo.sku_id; + char compat_string[80] = {}; - static int done = 0; - if (done) - return; - done = 1; + 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()); - char *compat = xmalloc(sizeof(pattern) + sizeof(CONFIG_BOARD) + 20); - sprintf(compat, pattern, CONFIG_BOARD, - lib_sysinfo.board_id, lib_sysinfo.sku_id); + fit_add_compat_string(compat_string); + } - char *c; - for (c = compat; *c != '\0'; c++) - if (*c == '_') - *c = '-'; + 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()); - if (sku != UNDEFINED_STRAPPING_ID && rev != UNDEFINED_STRAPPING_ID) - fit_add_compat(strdup(compat)); + fit_add_compat_string(compat_string); + } - *strrchr(compat, '-') = '\0'; - if (rev != UNDEFINED_STRAPPING_ID) - fit_add_compat(strdup(compat)); + snprintf(compat_string, sizeof(compat_string), "%s,%s", + CONFIG_MAINBOARD_VENDOR, CONFIG_MAINBOARD_PART_NUMBER); - *strrchr(compat, '-') = '\0'; - fit_add_compat(compat); + fit_add_compat_string(compat_string); } - - - -static void image_node(DeviceTreeNode *node) +static void image_node(struct device_tree_node *node) { - FitImageNode *image = xzalloc(sizeof(*image)); - image->compression = CompressionNone; + struct fit_image_node *image = xzalloc(sizeof(*image)); + image->compression = CBFS_COMPRESS_NONE; image->name = node->name; - DeviceTreeProperty *prop; + 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; + image->compression = CBFS_COMPRESS_NONE; else if (!strcmp("lzma", prop->prop.data)) - image->compression = CompressionLzma; + image->compression = CBFS_COMPRESS_LZMA; else if (!strcmp("lz4", prop->prop.data)) - image->compression = CompressionLz4; + image->compression = CBFS_COMPRESS_LZ4; else - image->compression = CompressionInvalid; + image->compression = -1; } } list_insert_after(&image->list_node, &image_nodes); } -static void config_node(DeviceTreeNode *node) +static void config_node(struct device_tree_node *node) { - FitConfigNode *config = xzalloc(sizeof(*config)); + struct fit_config_node *config = xzalloc(sizeof(*config)); config->name = node->name; - DeviceTreeProperty *prop; + struct device_tree_property *prop; list_for_each(prop, node->properties, list_node) { if (!strcmp("kernel", prop->prop.name)) config->kernel = prop->prop.data; @@ -116,21 +122,20 @@ static void config_node(DeviceTreeNode *node) list_insert_after(&config->list_node, &config_nodes); } -static void fit_unpack(DeviceTree *tree, const char **default_config) +static void fit_unpack(struct device_tree *tree, const char **default_config) { assert(tree && tree->root); - DeviceTreeNode *top; + struct device_tree_node *top; list_for_each(top, tree->root->children, list_node) { - DeviceTreeNode *child; + struct device_tree_node *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; + struct device_tree_property *prop; list_for_each(prop, top->properties, list_node) { if (!strcmp("default", prop->prop.name) && default_config) @@ -143,9 +148,9 @@ static void fit_unpack(DeviceTree *tree, const char **default_config) } } -static FitImageNode *find_image(const char *name) +static struct fit_image_node *find_image(const char *name) { - FitImageNode *image; + struct fit_image_node *image; list_for_each(image, image_nodes, list_node) { if (!strcmp(image->name, name)) return image; @@ -153,7 +158,8 @@ static FitImageNode *find_image(const char *name) return NULL; } -static int fdt_find_compat(void *blob, uint32_t start_offset, FdtProperty *prop) +static int fdt_find_compat(void *blob, uint32_t start_offset, + struct fdt_property *prop) { int offset = start_offset; int size; @@ -174,7 +180,8 @@ static int fdt_find_compat(void *blob, uint32_t start_offset, FdtProperty *prop) return -1; } -static int fit_check_compat(FdtProperty *compat_prop, const char *compat_name) +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; @@ -189,18 +196,21 @@ static int fit_check_compat(FdtProperty *compat_prop, const char *compat_name) return -1; } -static void update_chosen(DeviceTree *tree, char *cmd_line) +void fit_update_chosen(struct device_tree *tree, char *cmd_line) { const char *path[] = { "chosen", NULL }; - DeviceTreeNode *node = dt_find_node(tree->root, path, NULL, NULL, 1); + 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(DeviceTree *tree, void *ramdisk_addr, size_t ramdisk_size) +void fit_add_ramdisk(struct device_tree *tree, void *ramdisk_addr, + size_t ramdisk_size) { const char *path[] = { "chosen", NULL }; - DeviceTreeNode *node = dt_find_node(tree->root, path, NULL, NULL, 1); + struct device_tree_node *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; @@ -210,49 +220,40 @@ void fit_add_ramdisk(DeviceTree *tree, void *ramdisk_addr, size_t ramdisk_size) dt_add_u32_prop(node, "linux,initrd-end", end); } -static void update_reserve_map(uint64_t start, uint64_t end, void *data) +static void update_reserve_map(uint64_t start, uint64_t end, + struct device_tree *tree) { - DeviceTree *tree = (DeviceTree *)data; + struct device_tree_reserve_map_entry *entry = xzalloc(sizeof(*entry)); - DeviceTreeReserveMapEntry *entry = xzalloc(sizeof(*entry)); entry->start = start; entry->size = end - start; list_insert_after(&entry->list_node, &tree->reserve_map); } -typedef struct EntryParams -{ +struct entry_params { 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. + /* + * 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) +static void update_mem_property(u64 start, u64 end, struct entry_params *params) { - 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); + 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); @@ -265,109 +266,189 @@ static void update_mem_property(u64 start, u64 end, void *pdata) params->data = data; } -static void update_memory(DeviceTree *tree) +struct mem_map { + struct memranges mem; + struct memranges reserved; +}; + +static bool walk_memory_table(const struct range_entry *r, void *arg) { - Ranges mem; - Ranges reserved; - DeviceTreeNode *node; + 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. + /* + * 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); - } - } + dt_add_string_prop(node, "device_type", (char *)"memory"); + + memranges_init_empty(&map.mem, NULL, 0); + memranges_init_empty(&map.reserved, NULL, 0); - // CBMEM regions are both carved out and explicitly reserved. - ranges_for_each(&reserved, &update_reserve_map, tree); + bootmem_walk_os_mem(walk_memory_table, &map); - // 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); + /* 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. + /* 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); + + 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. + /* 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 fdt_blob Pointer to FDT + * @param config The current config node to operate on + */ +static void fit_update_compat(void *fdt_blob, struct fit_config_node *config) +{ + struct compat_string_entry *compat_node; + struct fdt_header *fdt_header = (struct fdt_header *)fdt_blob; + uint32_t fdt_offset = be32_to_cpu(fdt_header->structure_offset); + size_t i = 0; + + if (!fdt_find_compat(fdt_blob, fdt_offset, &config->compat)) { + 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; + break; + } + i++; + } + } } -FitImageNode *fit_load(void *fit, char *cmd_line, DeviceTree **dt) +struct fit_config_node *fit_load(void *fit) { - FdtHeader *header = (FdtHeader *)fit; - FitImageNode *image; - FitConfigNode *config; - int i; + struct fdt_header *header = (struct fdt_header *)fit; + struct fit_image_node *image; + struct fit_config_node *config; + struct compat_string_entry *compat_node; - printf("Loading FIT.\n"); + printk(BIOS_DEBUG, "FIT: Loading FIT from %p\n", fit); - if (betohl(header->magic) != FdtMagic) { - printf("Bad FIT header magic value 0x%08x.\n", - betohl(header->magic)); + if (be32toh(header->magic) != FDT_HEADER_MAGIC) { + printk(BIOS_ERR, "FIT: Bad header magic value 0x%08x.\n", + be32toh(header->magic)); return NULL; } - - DeviceTree *tree = fdt_unflatten(fit); + struct device_tree *tree = fdt_unflatten(fit); const char *default_config_name = NULL; - FitConfigNode *default_config = NULL; - FitConfigNode *compat_config = 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 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. + 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) config->kernel_node = find_image(config->kernel); @@ -376,112 +457,82 @@ FitImageNode *fit_load(void *fit, char *cmd_line, DeviceTree **dt) if (config->ramdisk) config->ramdisk_node = find_image(config->ramdisk); + if (config->ramdisk_node && + config->ramdisk_node->compression < 0) { + printk(BIOS_WARNING, "WARN: Ramdisk is compressed with " + "an unsupported algorithm, discarding config %s." + "\n", config->name); + list_remove(&config->list_node); + continue; + } + if (!config->kernel_node || - (config->fdt && !config->fdt_node)) { - printf("Missing image, discarding config %s.\n", - config->name); + (config->fdt && !config->fdt_node)) { + printk(BIOS_DEBUG, "FIT: 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); + if (config->fdt_node->compression != + CBFS_COMPRESS_NONE) { + printk(BIOS_DEBUG, + "FIT: 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); + fit_update_compat(config->fdt_node->data, config); + } + printk(BIOS_DEBUG, "FIT: config %s", config->name); if (default_config_name && - !strcmp(config->name, default_config_name)) { - printf(" (default)"); + !strcmp(config->name, default_config_name)) { + printk(BIOS_DEBUG, " (default)"); default_config = config; } - printf(", kernel %s", config->kernel); if (config->fdt) - printf(", fdt %s", config->fdt); + printk(BIOS_DEBUG, ", fdt %s", config->fdt); if (config->ramdisk) - printf(", ramdisk %s", config->ramdisk); + printk(BIOS_DEBUG, ", ramdisk %s", config->ramdisk); if (config->compat.name) { - printf(", compat"); + 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++) { - printf(" %s", compat_str); + printk(BIOS_DEBUG, " %s", compat_str); if (pos == config->compat_pos) - printf(" (match)"); + 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)) + config->compat_rank > compat_config->compat_rank)) compat_config = config; } - printf("\n"); + printk(BIOS_DEBUG, "\n"); } - FitConfigNode *to_boot = NULL; + struct fit_config_node *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]); + 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; - printf("No match, choosing default %s.\n", to_boot->name); + printk(BIOS_INFO, "FIT: 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. + printk(BIOS_ERR, "FIT: No compatible or default configs. " + "Giving up.\n"); 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; + return to_boot; } -- cgit v1.2.3