blob: f93e973d5e3930b67aecf0c4e976f9466fbd78a8 [file] [log] [blame]
// 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;
}