| // Copyright 2015 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #define _GNU_SOURCE |
| #include <err.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <getopt.h> |
| #include <inttypes.h> |
| #include <libfdt.h> |
| #include <stdarg.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| |
| |
| static const char CONFIG_NODE_PATH[] = "/configurations"; |
| static const char IMAGES_NODE_PATH[] = "/images"; |
| |
| static const char DATA_PROP_NAME[] = "data"; |
| static const char COMPAT_PROP_NAME[] = "compatible"; |
| static const char KERNEL_PROP_NAME[] = "kernel"; |
| static const char DTB_PROP_NAME[] = "fdt"; |
| |
| |
| typedef struct Image { |
| // Name of the image node. |
| const char *name; |
| // Pointer to the image data. |
| const void *data; |
| // Length of the image. |
| int len; |
| } Image; |
| |
| typedef struct Config { |
| // Name of the config node. |
| const char *name; |
| // Offset to the config in the FIT. |
| int offset; |
| |
| // Information about the kernel image. |
| Image kernel; |
| // Information about the dtb image. |
| Image dtb; |
| |
| // Used when comparing multiple configs that match a compat string. |
| int rank; |
| |
| struct Config *next; |
| } Config; |
| |
| |
| // A linked list of configurations found in the FIT file. |
| static Config *fp_configs = NULL; |
| |
| // Whether verbose output has been enabled. |
| static int fp_verbose = 0; |
| |
| |
| // Prints informational messages to stdout if verbose output has been enabled. |
| // Accepts the same arguments as printf. |
| static void __attribute__((__format__(__printf__, 1, 2))) |
| fp_log(const char *format, ...) { |
| va_list args; |
| |
| if (fp_verbose) { |
| va_start(args, format); |
| vprintf(format, args); |
| va_end(args); |
| } |
| } |
| |
| // Usage information for the program. |
| static void fp_print_usage(void) { |
| printf("Usage: %s [options] <fit> <compat> <kernel> <dtb>\n" |
| "\n" |
| "Options:\n" |
| "\t-v:\tVerbose output.\n" |
| "\t-h:\tShow this help.\n" |
| "\n" |
| "fit:\tPath to the FIT file to pick a kernel/dtb from.\n" |
| "compat:\tThe \"compat\" property to search for.\n" |
| "kernel:\tFile name to write the chosen kernel to.\n" |
| "dtb:\tFile name to write the chosen device tree to.\n", |
| program_invocation_short_name); |
| } |
| |
| |
| // Allocate a buffer and read the contents of a file into it. |
| static const void *fp_read_file(const char *path) { |
| const int fd = open(path, 0, O_RDONLY | O_CLOEXEC); |
| if (fd < 0) { |
| warn("Failed to open %s for reading.", path); |
| return NULL; |
| } |
| |
| // Figure out how big the file is. |
| struct stat stat_buf; |
| if (fstat(fd, &stat_buf)) { |
| warn("Failed to stat %s.", path); |
| return NULL; |
| } |
| const off_t size = stat_buf.st_size; |
| |
| // Allocate a buffer. |
| void *buf = malloc(size); |
| if (!buf) { |
| warn("Couldn't allocate %"PRIi64" bytes.", size); |
| return NULL; |
| } |
| |
| // Read data into it until we hit an error or get the whole file. |
| uint8_t *ptr = buf; |
| off_t left = size; |
| do { |
| ssize_t ret = read(fd, ptr, left); |
| if (ret < 0) { |
| warn("Failed to read from %s.", path); |
| free(buf); |
| return NULL; |
| } |
| ptr += ret; |
| left -= ret; |
| } while (left); |
| |
| return buf; |
| } |
| |
| // Write data in a buffer into a file. |
| static int fp_write_file(const char *path, const void *data, off_t len) { |
| const int fd = open(path, |
| O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC | O_NOFOLLOW, |
| 0644); |
| if (fd < 0) { |
| warn("Failed to open %s for writing.", path); |
| return -1; |
| } |
| |
| // Write data until we hit an error or write the whole file. |
| const uint8_t *ptr = data; |
| off_t left = len; |
| do { |
| int ret = write(fd, ptr, left); |
| if (ret < 0) { |
| warn("Failed to write to %s.", path); |
| return ret; |
| } |
| ptr += ret; |
| left -= ret; |
| } while (left); |
| |
| return len; |
| } |
| |
| |
| // Read an image with a given name from the image nodes in the FIT image. |
| // If lenp is non-NULL, it'll be set to the length of the image data. |
| static const void *fp_read_image(const void *fit_buf, const char *name, |
| int *lenp) { |
| // Find the parent node which houses all the image nodes. |
| int images_offset = fdt_path_offset(fit_buf, IMAGES_NODE_PATH); |
| if (images_offset < 0) { |
| warn("Failed to find '%s' node.", IMAGES_NODE_PATH); |
| return NULL; |
| } |
| |
| // Find the node which holds the image we're looking for. |
| int image_offset = fdt_subnode_offset(fit_buf, images_offset, name); |
| if (image_offset < 0) { |
| warn("Failed to find image %s.", name); |
| return NULL; |
| } |
| |
| // Get the "data" property from it which holds image data. |
| const struct fdt_property *image_prop = |
| fdt_get_property(fit_buf, image_offset, DATA_PROP_NAME, lenp); |
| if (!image_prop) { |
| warn("Failed to read image data for %s.", name); |
| return NULL; |
| } |
| |
| return image_prop->data; |
| } |
| |
| // Find the "compatible" property at the root of a device tree. |
| static const char *fp_get_compat(const void *dtb_buf, int *lenp) { |
| const struct fdt_property *compatible = |
| fdt_get_property(dtb_buf, 0, COMPAT_PROP_NAME, lenp); |
| if (!compatible) { |
| warn("Couldn't find '%s' string.", COMPAT_PROP_NAME); |
| return NULL; |
| } |
| |
| return compatible->data; |
| } |
| |
| // Find the offset of the first configuration node in the FIT image. |
| static int fp_first_config(const void *fit_buf) { |
| // Find the parent node which houses all the configurations. |
| int configs_offset = fdt_path_offset(fit_buf, CONFIG_NODE_PATH); |
| if (configs_offset < 0) { |
| warn("Failed to find '%s' in the FIT.", CONFIG_NODE_PATH); |
| return configs_offset; |
| } |
| |
| // Get the first configuration node. |
| int config_offset = fdt_first_subnode(fit_buf, configs_offset); |
| if (config_offset < 0) |
| return 0; |
| |
| return config_offset; |
| } |
| |
| // Find the offset of the next configuration node after the one at "offset" |
| // in a FIT image. |
| static int fp_next_config(const void *fit_buf, int offset) { |
| offset = fdt_next_subnode(fit_buf, offset); |
| if (offset < 0) |
| return 0; |
| |
| return offset; |
| } |
| |
| |
| // Fill in info about an image associated with a configuration in the FIT. |
| static int fp_init_image(const void *fit_buf, Config *config, |
| const char *prop_name, Image *image) { |
| // Find the property which holds the name of the image of this type. |
| const struct fdt_property *prop = |
| fdt_get_property(fit_buf, config->offset, prop_name, NULL); |
| if (!prop) { |
| warn("Failed to find %s image name for config %s.", |
| prop_name, config->name); |
| return -1; |
| } |
| image->name = prop->data; |
| |
| // Read the image itself from the FIT image. |
| image->data = fp_read_image(fit_buf, image->name, &image->len); |
| if (!image->data) |
| return -1; |
| |
| return 0; |
| } |
| |
| // Initialize and fill out a structure describing a configuration in the FIT. |
| static Config *fp_init_config(const void *fit_buf, int offset) { |
| Config *config = malloc(sizeof(Config)); |
| config->next = NULL; |
| config->rank = -1; |
| config->offset = offset; |
| |
| // Get the name of this config node. |
| config->name = fdt_get_name(fit_buf, offset, NULL); |
| |
| // Read in information for the kernel and dtb images for this config. |
| if (fp_init_image(fit_buf, config, KERNEL_PROP_NAME, &config->kernel) < 0 || |
| fp_init_image(fit_buf, config, DTB_PROP_NAME, &config->dtb) < 0) { |
| free(config); |
| return NULL; |
| } |
| |
| return config; |
| } |
| |
| |
| int main(int argc, char * const argv[]) { |
| int c; |
| while ((c = getopt(argc, argv, "vh")) != -1) { |
| switch (c) { |
| case 'v': |
| fp_verbose = 1; |
| break; |
| case 'h': |
| fp_print_usage(); |
| return 0; |
| } |
| } |
| |
| if (argc - optind != 4) { |
| fp_print_usage(); |
| return 1; |
| } |
| |
| const char *fit_path = argv[optind + 0]; |
| const char *compat = argv[optind + 1]; |
| const char *kernel_path = argv[optind + 2]; |
| const char *dtb_path = argv[optind + 3]; |
| |
| const void *fit_buf = fp_read_file(fit_path); |
| if (!fit_buf) |
| return 1; |
| |
| int offset = fp_first_config(fit_buf); |
| if (offset < 0) |
| return 1; |
| |
| // Read all the configurations from the FIT into a linked list. |
| for (Config **end = &fp_configs; offset; end = &(*end)->next) { |
| *end = fp_init_config(fit_buf, offset); |
| if (!*end) |
| return 1; |
| offset = fp_next_config(fit_buf, offset); |
| } |
| |
| // Go through all the configurations and find the "best" match to the |
| // compat string we were given. Best is defined to be the one whose |
| // most specific (earliest) compatible property element matches the |
| // compat string. |
| Config *best = NULL; |
| for (Config *config = fp_configs; config; config = config->next) { |
| fp_log("Config %s: kernel = %s, dtb = %s.\n", |
| config->name, config->kernel.name, config->dtb.name); |
| |
| int compat_len; |
| const char *dtb_compat = fp_get_compat(config->dtb.data, &compat_len); |
| if (!dtb_compat) |
| return 1; |
| |
| for (int compat_idx = 0, left = compat_len, len; left; |
| compat_idx++, dtb_compat += len, left -= len) { |
| fp_log(" Compatible: %s", dtb_compat); |
| len = strlen(dtb_compat) + 1; |
| |
| if (!strcmp(dtb_compat, compat)) { |
| fp_log(" (match)"); |
| if (config->rank < 0) { |
| config->rank = compat_idx; |
| if (!best || config->rank < best->rank) |
| best = config; |
| } |
| } |
| fp_log("\n"); |
| } |
| } |
| |
| if (!best) { |
| warn("\nNo match found!\n"); |
| return 1; |
| } else { |
| fp_log("\nBest match is config %s.\n\n", best->name); |
| } |
| |
| // Now that we've picked a configuration, write the kernel and device |
| // tree associated with it to the paths provided. |
| fp_write_file(kernel_path, best->kernel.data, best->kernel.len); |
| fp_write_file(dtb_path, best->dtb.data, best->dtb.len); |
| |
| return 0; |
| } |