diff options
Diffstat (limited to 'tools')
-rw-r--r-- | tools/Makefile | 6 | ||||
-rw-r--r-- | tools/fdtgrep.c | 1234 |
2 files changed, 1239 insertions, 1 deletions
diff --git a/tools/Makefile b/tools/Makefile index 8ff9c2e..98414f7 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -58,7 +58,8 @@ hostprogs-$(CONFIG_FIT_SIGNATURE) += fit_info fit_check_sign FIT_SIG_OBJS-$(CONFIG_FIT_SIGNATURE) := common/image-sig.o # Flattened device tree objects LIBFDT_OBJS := $(addprefix lib/libfdt/, \ - fdt.o fdt_ro.o fdt_rw.o fdt_strerror.o fdt_wip.o) + fdt.o fdt_ro.o fdt_rw.o fdt_strerror.o fdt_wip.o \ + fdt_region.o) RSA_OBJS-$(CONFIG_FIT_SIGNATURE) := $(addprefix lib/rsa/, \ rsa-sign.o rsa-verify.o rsa-checksum.o \ rsa-mod-exp.o) @@ -155,6 +156,9 @@ hostprogs-$(CONFIG_ARMADA_XP) += kwboot hostprogs-y += proftool hostprogs-$(CONFIG_STATIC_RELA) += relocate-rela +hostprogs-y += fdtgrep +fdtgrep-objs += $(LIBFDT_OBJS) fdtgrep.o + # We build some files with extra pedantic flags to try to minimize things # that won't build on some weird host compiler -- though there are lots of # exceptions for files that aren't complaint. diff --git a/tools/fdtgrep.c b/tools/fdtgrep.c new file mode 100644 index 0000000..caaf600 --- /dev/null +++ b/tools/fdtgrep.c @@ -0,0 +1,1234 @@ +/* + * Copyright (c) 2013, Google Inc. + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + * + * Perform a grep of an FDT either displaying the source subset or producing + * a new .dtb subset which can be used as required. + */ + +#include <assert.h> +#include <ctype.h> +#include <getopt.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <../include/libfdt.h> +#include <libfdt_internal.h> + +/* Define DEBUG to get some debugging output on stderr */ +#ifdef DEBUG +#define debug(a, b...) fprintf(stderr, a, ## b) +#else +#define debug(a, b...) +#endif + +/* A linked list of values we are grepping for */ +struct value_node { + int type; /* Types this value matches (FDT_IS... mask) */ + int include; /* 1 to include matches, 0 to exclude */ + const char *string; /* String to match */ + struct value_node *next; /* Pointer to next node, or NULL */ +}; + +/* Output formats we support */ +enum output_t { + OUT_DTS, /* Device tree source */ + OUT_DTB, /* Valid device tree binary */ + OUT_BIN, /* Fragment of .dtb, for hashing */ +}; + +/* Holds information which controls our output and options */ +struct display_info { + enum output_t output; /* Output format */ + int add_aliases; /* Add aliases node to output */ + int all; /* Display all properties/nodes */ + int colour; /* Display output in ANSI colour */ + int region_list; /* Output a region list */ + int flags; /* Flags (FDT_REG_...) */ + int list_strings; /* List strings in string table */ + int show_offset; /* Show offset */ + int show_addr; /* Show address */ + int header; /* Output an FDT header */ + int diff; /* Show +/- diff markers */ + int include_root; /* Include the root node and all properties */ + int remove_strings; /* Remove unused strings */ + int show_dts_version; /* Put '/dts-v1/;' on the first line */ + int types_inc; /* Mask of types that we include (FDT_IS...) */ + int types_exc; /* Mask of types that we exclude (FDT_IS...) */ + int invert; /* Invert polarity of match */ + struct value_node *value_head; /* List of values to match */ + const char *output_fname; /* Output filename */ + FILE *fout; /* File to write dts/dtb output */ +}; + +static void report_error(const char *where, int err) +{ + fprintf(stderr, "Error at '%s': %s\n", where, fdt_strerror(err)); +} + +/* Supported ANSI colours */ +enum { + COL_BLACK, + COL_RED, + COL_GREEN, + COL_YELLOW, + COL_BLUE, + COL_MAGENTA, + COL_CYAN, + COL_WHITE, + + COL_NONE = -1, +}; + +/** + * print_ansi_colour() - Print out the ANSI sequence for a colour + * + * @fout: Output file + * @col: Colour to output (COL_...), or COL_NONE to reset colour + */ +static void print_ansi_colour(FILE *fout, int col) +{ + if (col == COL_NONE) + fprintf(fout, "\033[0m"); + else + fprintf(fout, "\033[1;%dm", col + 30); +} + + +/** + * value_add() - Add a new value to our list of things to grep for + * + * @disp: Display structure, holding info about our options + * @headp: Pointer to header pointer of list + * @type: Type of this value (FDT_IS_...) + * @include: 1 if we want to include matches, 0 to exclude + * @str: String value to match + */ +static int value_add(struct display_info *disp, struct value_node **headp, + int type, int include, const char *str) +{ + struct value_node *node; + + /* + * Keep track of which types we are excluding/including. We don't + * allow both including and excluding things, because it doesn't make + * sense. 'Including' means that everything not mentioned is + * excluded. 'Excluding' means that everything not mentioned is + * included. So using the two together would be meaningless. + */ + if (include) + disp->types_inc |= type; + else + disp->types_exc |= type; + if (disp->types_inc & disp->types_exc & type) { + fprintf(stderr, + "Cannot use both include and exclude for '%s'\n", str); + return -1; + } + + str = strdup(str); + node = malloc(sizeof(*node)); + if (!str || !node) { + fprintf(stderr, "Out of memory\n"); + return -1; + } + node->next = *headp; + node->type = type; + node->include = include; + node->string = str; + *headp = node; + + return 0; +} + +static bool util_is_printable_string(const void *data, int len) +{ + const char *s = data; + const char *ss, *se; + + /* zero length is not */ + if (len == 0) + return 0; + + /* must terminate with zero */ + if (s[len - 1] != '\0') + return 0; + + se = s + len; + + while (s < se) { + ss = s; + while (s < se && *s && isprint((unsigned char)*s)) + s++; + + /* not zero, or not done yet */ + if (*s != '\0' || s == ss) + return 0; + + s++; + } + + return 1; +} + +static void utilfdt_print_data(const char *data, int len) +{ + int i; + const char *p = data; + const char *s; + + /* no data, don't print */ + if (len == 0) + return; + + if (util_is_printable_string(data, len)) { + printf(" = "); + + s = data; + do { + printf("\"%s\"", s); + s += strlen(s) + 1; + if (s < data + len) + printf(", "); + } while (s < data + len); + + } else if ((len % 4) == 0) { + const uint32_t *cell = (const uint32_t *)data; + + printf(" = <"); + for (i = 0, len /= 4; i < len; i++) + printf("0x%08x%s", fdt32_to_cpu(cell[i]), + i < (len - 1) ? " " : ""); + printf(">"); + } else { + printf(" = ["); + for (i = 0; i < len; i++) + printf("%02x%s", *p++, i < len - 1 ? " " : ""); + printf("]"); + } +} + +/** + * display_fdt_by_regions() - Display regions of an FDT source + * + * This dumps an FDT as source, but only certain regions of it. This is the + * final stage of the grep - we have a list of regions we want to display, + * and this function displays them. + * + * @disp: Display structure, holding info about our options + * @blob: FDT blob to display + * @region: List of regions to display + * @count: Number of regions + */ +static int display_fdt_by_regions(struct display_info *disp, const void *blob, + struct fdt_region region[], int count) +{ + struct fdt_region *reg = region, *reg_end = region + count; + uint32_t off_mem_rsvmap = fdt_off_mem_rsvmap(blob); + int base = fdt_off_dt_struct(blob); + int version = fdt_version(blob); + int offset, nextoffset; + int tag, depth, shift; + FILE *f = disp->fout; + uint64_t addr, size; + int in_region; + int file_ofs; + int i; + + if (disp->show_dts_version) + fprintf(f, "/dts-v1/;\n"); + + if (disp->header) { + fprintf(f, "// magic:\t\t0x%x\n", fdt_magic(blob)); + fprintf(f, "// totalsize:\t\t0x%x (%d)\n", fdt_totalsize(blob), + fdt_totalsize(blob)); + fprintf(f, "// off_dt_struct:\t0x%x\n", + fdt_off_dt_struct(blob)); + fprintf(f, "// off_dt_strings:\t0x%x\n", + fdt_off_dt_strings(blob)); + fprintf(f, "// off_mem_rsvmap:\t0x%x\n", off_mem_rsvmap); + fprintf(f, "// version:\t\t%d\n", version); + fprintf(f, "// last_comp_version:\t%d\n", + fdt_last_comp_version(blob)); + if (version >= 2) { + fprintf(f, "// boot_cpuid_phys:\t0x%x\n", + fdt_boot_cpuid_phys(blob)); + } + if (version >= 3) { + fprintf(f, "// size_dt_strings:\t0x%x\n", + fdt_size_dt_strings(blob)); + } + if (version >= 17) { + fprintf(f, "// size_dt_struct:\t0x%x\n", + fdt_size_dt_struct(blob)); + } + fprintf(f, "\n"); + } + + if (disp->flags & FDT_REG_ADD_MEM_RSVMAP) { + const struct fdt_reserve_entry *p_rsvmap; + + p_rsvmap = (const struct fdt_reserve_entry *) + ((const char *)blob + off_mem_rsvmap); + for (i = 0; ; i++) { + addr = fdt64_to_cpu(p_rsvmap[i].address); + size = fdt64_to_cpu(p_rsvmap[i].size); + if (addr == 0 && size == 0) + break; + + fprintf(f, "/memreserve/ %llx %llx;\n", + (unsigned long long)addr, + (unsigned long long)size); + } + } + + depth = 0; + nextoffset = 0; + shift = 4; /* 4 spaces per indent */ + do { + const struct fdt_property *prop; + const char *name; + int show; + int len; + + offset = nextoffset; + + /* + * Work out the file offset of this offset, and decide + * whether it is in the region list or not + */ + file_ofs = base + offset; + if (reg < reg_end && file_ofs >= reg->offset + reg->size) + reg++; + in_region = reg < reg_end && file_ofs >= reg->offset && + file_ofs < reg->offset + reg->size; + tag = fdt_next_tag(blob, offset, &nextoffset); + + if (tag == FDT_END) + break; + show = in_region || disp->all; + if (show && disp->diff) + fprintf(f, "%c", in_region ? '+' : '-'); + + if (!show) { + /* Do this here to avoid 'if (show)' in every 'case' */ + if (tag == FDT_BEGIN_NODE) + depth++; + else if (tag == FDT_END_NODE) + depth--; + continue; + } + if (tag != FDT_END) { + if (disp->show_addr) + fprintf(f, "%4x: ", file_ofs); + if (disp->show_offset) + fprintf(f, "%4x: ", file_ofs - base); + } + + /* Green means included, red means excluded */ + if (disp->colour) + print_ansi_colour(f, in_region ? COL_GREEN : COL_RED); + + switch (tag) { + case FDT_PROP: + prop = fdt_get_property_by_offset(blob, offset, NULL); + name = fdt_string(blob, fdt32_to_cpu(prop->nameoff)); + fprintf(f, "%*s%s", depth * shift, "", name); + utilfdt_print_data(prop->data, + fdt32_to_cpu(prop->len)); + fprintf(f, ";"); + break; + + case FDT_NOP: + fprintf(f, "%*s// [NOP]", depth * shift, ""); + break; + + case FDT_BEGIN_NODE: + name = fdt_get_name(blob, offset, &len); + fprintf(f, "%*s%s {", depth++ * shift, "", + *name ? name : "/"); + break; + + case FDT_END_NODE: + fprintf(f, "%*s};", --depth * shift, ""); + break; + } + + /* Reset colour back to normal before end of line */ + if (disp->colour) + print_ansi_colour(f, COL_NONE); + fprintf(f, "\n"); + } while (1); + + /* Print a list of strings if requested */ + if (disp->list_strings) { + const char *str; + int str_base = fdt_off_dt_strings(blob); + + for (offset = 0; offset < fdt_size_dt_strings(blob); + offset += strlen(str) + 1) { + str = fdt_string(blob, offset); + int len = strlen(str) + 1; + int show; + + /* Only print strings that are in the region */ + file_ofs = str_base + offset; + in_region = reg < reg_end && + file_ofs >= reg->offset && + file_ofs + len < reg->offset + + reg->size; + show = in_region || disp->all; + if (show && disp->diff) + printf("%c", in_region ? '+' : '-'); + if (disp->show_addr) + printf("%4x: ", file_ofs); + if (disp->show_offset) + printf("%4x: ", offset); + printf("%s\n", str); + } + } + + return 0; +} + +/** + * dump_fdt_regions() - Dump regions of an FDT as binary data + * + * This dumps an FDT as binary, but only certain regions of it. This is the + * final stage of the grep - we have a list of regions we want to dump, + * and this function dumps them. + * + * The output of this function may or may not be a valid FDT. To ensure it + * is, these disp->flags must be set: + * + * FDT_REG_SUPERNODES: ensures that subnodes are preceeded by their + * parents. Without this option, fragments of subnode data may be + * output without the supernodes above them. This is useful for + * hashing but cannot produce a valid FDT. + * FDT_REG_ADD_STRING_TAB: Adds a string table to the end of the FDT. + * Without this none of the properties will have names + * FDT_REG_ADD_MEM_RSVMAP: Adds a mem_rsvmap table - an FDT is invalid + * without this. + * + * @disp: Display structure, holding info about our options + * @blob: FDT blob to display + * @region: List of regions to display + * @count: Number of regions + * @out: Output destination + */ +static int dump_fdt_regions(struct display_info *disp, const void *blob, + struct fdt_region region[], int count, char *out) +{ + struct fdt_header *fdt; + int size, struct_start; + int ptr; + int i; + + /* Set up a basic header (even if we don't actually write it) */ + fdt = (struct fdt_header *)out; + memset(fdt, '\0', sizeof(*fdt)); + fdt_set_magic(fdt, FDT_MAGIC); + struct_start = FDT_ALIGN(sizeof(struct fdt_header), + sizeof(struct fdt_reserve_entry)); + fdt_set_off_mem_rsvmap(fdt, struct_start); + fdt_set_version(fdt, FDT_LAST_SUPPORTED_VERSION); + fdt_set_last_comp_version(fdt, FDT_FIRST_SUPPORTED_VERSION); + + /* + * Calculate the total size of the regions we are writing out. The + * first will be the mem_rsvmap if the FDT_REG_ADD_MEM_RSVMAP flag + * is set. The last will be the string table if FDT_REG_ADD_STRING_TAB + * is set. + */ + for (i = size = 0; i < count; i++) + size += region[i].size; + + /* Bring in the mem_rsvmap section from the old file if requested */ + if (count > 0 && (disp->flags & FDT_REG_ADD_MEM_RSVMAP)) { + struct_start += region[0].size; + size -= region[0].size; + } + fdt_set_off_dt_struct(fdt, struct_start); + + /* Update the header to have the correct offsets/sizes */ + if (count >= 2 && (disp->flags & FDT_REG_ADD_STRING_TAB)) { + int str_size; + + str_size = region[count - 1].size; + fdt_set_size_dt_struct(fdt, size - str_size); + fdt_set_off_dt_strings(fdt, struct_start + size - str_size); + fdt_set_size_dt_strings(fdt, str_size); + fdt_set_totalsize(fdt, struct_start + size); + } + + /* Write the header if required */ + ptr = 0; + if (disp->header) { + ptr = sizeof(*fdt); + while (ptr < fdt_off_mem_rsvmap(fdt)) + out[ptr++] = '\0'; + } + + /* Output all the nodes including any mem_rsvmap/string table */ + for (i = 0; i < count; i++) { + struct fdt_region *reg = ®ion[i]; + + memcpy(out + ptr, (const char *)blob + reg->offset, reg->size); + ptr += reg->size; + } + + return ptr; +} + +/** + * show_region_list() - Print out a list of regions + * + * The list includes the region offset (absolute offset from start of FDT + * blob in bytes) and size + * + * @reg: List of regions to print + * @count: Number of regions + */ +static void show_region_list(struct fdt_region *reg, int count) +{ + int i; + + printf("Regions: %d\n", count); + for (i = 0; i < count; i++, reg++) { + printf("%d: %-10x %-10x\n", i, reg->offset, + reg->offset + reg->size); + } +} + +static int check_type_include(void *priv, int type, const char *data, int size) +{ + struct display_info *disp = priv; + struct value_node *val; + int match, none_match = FDT_IS_ANY; + + /* If none of our conditions mention this type, we know nothing */ + debug("type=%x, data=%s\n", type, data ? data : "(null)"); + if (!((disp->types_inc | disp->types_exc) & type)) { + debug(" - not in any condition\n"); + return -1; + } + + /* + * Go through the list of conditions. For inclusive conditions, we + * return 1 at the first match. For exclusive conditions, we must + * check that there are no matches. + */ + for (val = disp->value_head; val; val = val->next) { + if (!(type & val->type)) + continue; + match = fdt_stringlist_contains(data, size, val->string); + debug(" - val->type=%x, str='%s', match=%d\n", + val->type, val->string, match); + if (match && val->include) { + debug(" - match inc %s\n", val->string); + return 1; + } + if (match) + none_match &= ~val->type; + } + + /* + * If this is an exclusive condition, and nothing matches, then we + * should return 1. + */ + if ((type & disp->types_exc) && (none_match & type)) { + debug(" - match exc\n"); + /* + * Allow FDT_IS_COMPAT to make the final decision in the + * case where there is no specific type + */ + if (type == FDT_IS_NODE && disp->types_exc == FDT_ANY_GLOBAL) { + debug(" - supressed exc node\n"); + return -1; + } + return 1; + } + + /* + * Allow FDT_IS_COMPAT to make the final decision in the + * case where there is no specific type (inclusive) + */ + if (type == FDT_IS_NODE && disp->types_inc == FDT_ANY_GLOBAL) + return -1; + + debug(" - no match, types_inc=%x, types_exc=%x, none_match=%x\n", + disp->types_inc, disp->types_exc, none_match); + + return 0; +} + +/** + * h_include() - Include handler function for fdt_find_regions() + * + * This function decides whether to include or exclude a node, property or + * compatible string. The function is defined by fdt_find_regions(). + * + * The algorithm is documented in the code - disp->invert is 0 for normal + * operation, and 1 to invert the sense of all matches. + * + * See + */ +static int h_include(void *priv, const void *fdt, int offset, int type, + const char *data, int size) +{ + struct display_info *disp = priv; + int inc, len; + + inc = check_type_include(priv, type, data, size); + if (disp->include_root && type == FDT_IS_PROP && offset == 0 && inc) + return 1; + + /* + * If the node name does not tell us anything, check the + * compatible string + */ + if (inc == -1 && type == FDT_IS_NODE) { + debug(" - checking compatible2\n"); + data = fdt_getprop(fdt, offset, "compatible", &len); + inc = check_type_include(priv, FDT_IS_COMPAT, data, len); + } + + /* If we still have no idea, check for properties in the node */ + if (inc != 1 && type == FDT_IS_NODE && + (disp->types_inc & FDT_NODE_HAS_PROP)) { + debug(" - checking node '%s'\n", + fdt_get_name(fdt, offset, NULL)); + for (offset = fdt_first_property_offset(fdt, offset); + offset > 0 && inc != 1; + offset = fdt_next_property_offset(fdt, offset)) { + const struct fdt_property *prop; + const char *str; + + prop = fdt_get_property_by_offset(fdt, offset, NULL); + if (!prop) + continue; + str = fdt_string(fdt, fdt32_to_cpu(prop->nameoff)); + inc = check_type_include(priv, FDT_NODE_HAS_PROP, str, + strlen(str)); + } + if (inc == -1) + inc = 0; + } + + switch (inc) { + case 1: + inc = !disp->invert; + break; + case 0: + inc = disp->invert; + break; + } + debug(" - returning %d\n", inc); + + return inc; +} + +static int h_cmp_region(const void *v1, const void *v2) +{ + const struct fdt_region *region1 = v1, *region2 = v2; + + return region1->offset - region2->offset; +} + +static int fdtgrep_find_regions(const void *fdt, + int (*include_func)(void *priv, const void *fdt, int offset, + int type, const char *data, int size), + struct display_info *disp, struct fdt_region *region, + int max_regions, char *path, int path_len, int flags) +{ + struct fdt_region_state state; + int count; + int ret; + + count = 0; + ret = fdt_first_region(fdt, include_func, disp, + ®ion[count++], path, path_len, + disp->flags, &state); + while (ret == 0) { + ret = fdt_next_region(fdt, include_func, disp, + count < max_regions ? ®ion[count] : NULL, + path, path_len, disp->flags, &state); + if (!ret) + count++; + } + + /* Find all the aliases and add those regions back in */ + if (disp->add_aliases && count < max_regions) { + int new_count; + + new_count = fdt_add_alias_regions(fdt, region, count, + max_regions, &state); + if (new_count > max_regions) { + region = malloc(new_count * sizeof(struct fdt_region)); + if (!region) { + fprintf(stderr, + "Out of memory for %d regions\n", + count); + return -1; + } + memcpy(region, state.region, + count * sizeof(struct fdt_region)); + free(state.region); + new_count = fdt_add_alias_regions(fdt, region, count, + max_regions, &state); + } + + /* + * The alias regions will now be at the end of the list. Sort + * the regions by offset to get things into the right order + */ + qsort(region, new_count, sizeof(struct fdt_region), + h_cmp_region); + count = new_count; + } + + if (ret != -FDT_ERR_NOTFOUND) + return ret; + + return count; +} + +int utilfdt_read_err_len(const char *filename, char **buffp, off_t *len) +{ + int fd = 0; /* assume stdin */ + char *buf = NULL; + off_t bufsize = 1024, offset = 0; + int ret = 0; + + *buffp = NULL; + if (strcmp(filename, "-") != 0) { + fd = open(filename, O_RDONLY); + if (fd < 0) + return errno; + } + + /* Loop until we have read everything */ + buf = malloc(bufsize); + if (!buf) + return -ENOMEM; + do { + /* Expand the buffer to hold the next chunk */ + if (offset == bufsize) { + bufsize *= 2; + buf = realloc(buf, bufsize); + if (!buf) + return -ENOMEM; + } + + ret = read(fd, &buf[offset], bufsize - offset); + if (ret < 0) { + ret = errno; + break; + } + offset += ret; + } while (ret != 0); + + /* Clean up, including closing stdin; return errno on error */ + close(fd); + if (ret) + free(buf); + else + *buffp = buf; + *len = bufsize; + return ret; +} + +int utilfdt_read_err(const char *filename, char **buffp) +{ + off_t len; + return utilfdt_read_err_len(filename, buffp, &len); +} + +char *utilfdt_read_len(const char *filename, off_t *len) +{ + char *buff; + int ret = utilfdt_read_err_len(filename, &buff, len); + + if (ret) { + fprintf(stderr, "Couldn't open blob from '%s': %s\n", filename, + strerror(ret)); + return NULL; + } + /* Successful read */ + return buff; +} + +char *utilfdt_read(const char *filename) +{ + off_t len; + return utilfdt_read_len(filename, &len); +} + +/** + * Run the main fdtgrep operation, given a filename and valid arguments + * + * @param disp Display information / options + * @param filename Filename of blob file + * @param return 0 if ok, -ve on error + */ +static int do_fdtgrep(struct display_info *disp, const char *filename) +{ + struct fdt_region *region; + int max_regions; + int count = 100; + char path[1024]; + char *blob; + int i, ret; + + blob = utilfdt_read(filename); + if (!blob) + return -1; + ret = fdt_check_header(blob); + if (ret) { + fprintf(stderr, "Error: %s\n", fdt_strerror(ret)); + return ret; + } + + /* Allow old files, but they are untested */ + if (fdt_version(blob) < 17 && disp->value_head) { + fprintf(stderr, + "Warning: fdtgrep does not fully support version %d files\n", + fdt_version(blob)); + } + + /* + * We do two passes, since we don't know how many regions we need. + * The first pass will count the regions, but if it is too many, + * we do another pass to actually record them. + */ + for (i = 0; i < 2; i++) { + region = malloc(count * sizeof(struct fdt_region)); + if (!region) { + fprintf(stderr, "Out of memory for %d regions\n", + count); + return -1; + } + max_regions = count; + count = fdtgrep_find_regions(blob, + h_include, disp, + region, max_regions, path, sizeof(path), + disp->flags); + if (count < 0) { + report_error("fdt_find_regions", count); + return -1; + } + if (count <= max_regions) + break; + free(region); + } + + /* Optionally print a list of regions */ + if (disp->region_list) + show_region_list(region, count); + + /* Output either source .dts or binary .dtb */ + if (disp->output == OUT_DTS) { + ret = display_fdt_by_regions(disp, blob, region, count); + } else { + void *fdt; + /* Allow reserved memory section to expand slightly */ + int size = fdt_totalsize(blob) + 16; + + fdt = malloc(size); + if (!fdt) { + fprintf(stderr, "Out_of_memory\n"); + ret = -1; + goto err; + } + size = dump_fdt_regions(disp, blob, region, count, fdt); + if (disp->remove_strings) { + void *out; + + out = malloc(size); + if (!out) { + fprintf(stderr, "Out_of_memory\n"); + ret = -1; + goto err; + } + ret = fdt_remove_unused_strings(fdt, out); + if (ret < 0) { + fprintf(stderr, + "Failed to remove unused strings: err=%d\n", + ret); + goto err; + } + free(fdt); + fdt = out; + ret = fdt_pack(fdt); + if (ret < 0) { + fprintf(stderr, "Failed to pack: err=%d\n", + ret); + goto err; + } + size = fdt_totalsize(fdt); + } + + if (size != fwrite(fdt, 1, size, disp->fout)) { + fprintf(stderr, "Write failure, %d bytes\n", size); + free(fdt); + ret = 1; + goto err; + } + free(fdt); + } +err: + free(blob); + free(region); + + return ret; +} + +static const char usage_synopsis[] = + "fdtgrep - extract portions from device tree\n" + "\n" + "Usage:\n" + " fdtgrep <options> <dt file>|-\n\n" + "Output formats are:\n" + "\tdts - device tree soure text\n" + "\tdtb - device tree blob (sets -Hmt automatically)\n" + "\tbin - device tree fragment (may not be a valid .dtb)"; + +/* Helper for usage_short_opts string constant */ +#define USAGE_COMMON_SHORT_OPTS "hV" + +/* Helper for aligning long_opts array */ +#define a_argument required_argument + +/* Helper for usage_long_opts option array */ +#define USAGE_COMMON_LONG_OPTS \ + {"help", no_argument, NULL, 'h'}, \ + {"version", no_argument, NULL, 'V'}, \ + {NULL, no_argument, NULL, 0x0} + +/* Helper for usage_opts_help array */ +#define USAGE_COMMON_OPTS_HELP \ + "Print this help and exit", \ + "Print version and exit", \ + NULL + +/* Helper for getopt case statements */ +#define case_USAGE_COMMON_FLAGS \ + case 'h': usage(NULL); \ + case 'V': util_version(); \ + case '?': usage("unknown option"); + +static const char usage_short_opts[] = + "haAc:b:C:defg:G:HIlLmn:N:o:O:p:P:rRsStTv" + USAGE_COMMON_SHORT_OPTS; +static struct option const usage_long_opts[] = { + {"show-address", no_argument, NULL, 'a'}, + {"colour", no_argument, NULL, 'A'}, + {"include-node-with-prop", a_argument, NULL, 'b'}, + {"include-compat", a_argument, NULL, 'c'}, + {"exclude-compat", a_argument, NULL, 'C'}, + {"diff", no_argument, NULL, 'd'}, + {"enter-node", no_argument, NULL, 'e'}, + {"show-offset", no_argument, NULL, 'f'}, + {"include-match", a_argument, NULL, 'g'}, + {"exclude-match", a_argument, NULL, 'G'}, + {"show-header", no_argument, NULL, 'H'}, + {"show-version", no_argument, NULL, 'I'}, + {"list-regions", no_argument, NULL, 'l'}, + {"list-strings", no_argument, NULL, 'L'}, + {"include-mem", no_argument, NULL, 'm'}, + {"include-node", a_argument, NULL, 'n'}, + {"exclude-node", a_argument, NULL, 'N'}, + {"include-prop", a_argument, NULL, 'p'}, + {"exclude-prop", a_argument, NULL, 'P'}, + {"remove-strings", no_argument, NULL, 'r'}, + {"include-root", no_argument, NULL, 'R'}, + {"show-subnodes", no_argument, NULL, 's'}, + {"skip-supernodes", no_argument, NULL, 'S'}, + {"show-stringtab", no_argument, NULL, 't'}, + {"show-aliases", no_argument, NULL, 'T'}, + {"out", a_argument, NULL, 'o'}, + {"out-format", a_argument, NULL, 'O'}, + {"invert-match", no_argument, NULL, 'v'}, + USAGE_COMMON_LONG_OPTS, +}; +static const char * const usage_opts_help[] = { + "Display address", + "Show all nodes/tags, colour those that match", + "Include contains containing property", + "Compatible nodes to include in grep", + "Compatible nodes to exclude in grep", + "Diff: Mark matching nodes with +, others with -", + "Enter direct subnode names of matching nodes", + "Display offset", + "Node/property/compatible string to include in grep", + "Node/property/compatible string to exclude in grep", + "Output a header", + "Put \"/dts-v1/;\" on first line of dts output", + "Output a region list", + "List strings in string table", + "Include mem_rsvmap section in binary output", + "Node to include in grep", + "Node to exclude in grep", + "Property to include in grep", + "Property to exclude in grep", + "Remove unused strings from string table", + "Include root node and all properties", + "Show all subnodes matching nodes", + "Don't include supernodes of matching nodes", + "Include string table in binary output", + "Include matching aliases in output", + "-o <output file>", + "-O <output format>", + "Invert the sense of matching (select non-matching lines)", + USAGE_COMMON_OPTS_HELP +}; + +/** + * Call getopt_long() with standard options + * + * Since all util code runs getopt in the same way, provide a helper. + */ +#define util_getopt_long() getopt_long(argc, argv, usage_short_opts, \ + usage_long_opts, NULL) + +void util_usage(const char *errmsg, const char *synopsis, + const char *short_opts, struct option const long_opts[], + const char * const opts_help[]) +{ + FILE *fp = errmsg ? stderr : stdout; + const char a_arg[] = "<arg>"; + size_t a_arg_len = strlen(a_arg) + 1; + size_t i; + int optlen; + + fprintf(fp, + "Usage: %s\n" + "\n" + "Options: -[%s]\n", synopsis, short_opts); + + /* prescan the --long opt length to auto-align */ + optlen = 0; + for (i = 0; long_opts[i].name; ++i) { + /* +1 is for space between --opt and help text */ + int l = strlen(long_opts[i].name) + 1; + if (long_opts[i].has_arg == a_argument) + l += a_arg_len; + if (optlen < l) + optlen = l; + } + + for (i = 0; long_opts[i].name; ++i) { + /* helps when adding new applets or options */ + assert(opts_help[i] != NULL); + + /* first output the short flag if it has one */ + if (long_opts[i].val > '~') + fprintf(fp, " "); + else + fprintf(fp, " -%c, ", long_opts[i].val); + + /* then the long flag */ + if (long_opts[i].has_arg == no_argument) { + fprintf(fp, "--%-*s", optlen, long_opts[i].name); + } else { + fprintf(fp, "--%s %s%*s", long_opts[i].name, a_arg, + (int)(optlen - strlen(long_opts[i].name) - + a_arg_len), ""); + } + + /* finally the help text */ + fprintf(fp, "%s\n", opts_help[i]); + } + + if (errmsg) { + fprintf(fp, "\nError: %s\n", errmsg); + exit(EXIT_FAILURE); + } else { + exit(EXIT_SUCCESS); + } +} + +/** + * Show usage and exit + * + * If you name all your usage variables with usage_xxx, then you can call this + * help macro rather than expanding all arguments yourself. + * + * @param errmsg If non-NULL, an error message to display + */ +#define usage(errmsg) \ + util_usage(errmsg, usage_synopsis, usage_short_opts, \ + usage_long_opts, usage_opts_help) + +void util_version(void) +{ + printf("Version: %s\n", "(U-Boot)"); + exit(0); +} + +static void scan_args(struct display_info *disp, int argc, char *argv[]) +{ + int opt; + + while ((opt = util_getopt_long()) != EOF) { + int type = 0; + int inc = 1; + + switch (opt) { + case_USAGE_COMMON_FLAGS + case 'a': + disp->show_addr = 1; + break; + case 'A': + disp->all = 1; + break; + case 'b': + type = FDT_NODE_HAS_PROP; + break; + case 'C': + inc = 0; + /* no break */ + case 'c': + type = FDT_IS_COMPAT; + break; + case 'd': + disp->diff = 1; + break; + case 'e': + disp->flags |= FDT_REG_DIRECT_SUBNODES; + break; + case 'f': + disp->show_offset = 1; + break; + case 'G': + inc = 0; + /* no break */ + case 'g': + type = FDT_ANY_GLOBAL; + break; + case 'H': + disp->header = 1; + break; + case 'l': + disp->region_list = 1; + break; + case 'L': + disp->list_strings = 1; + break; + case 'm': + disp->flags |= FDT_REG_ADD_MEM_RSVMAP; + break; + case 'N': + inc = 0; + /* no break */ + case 'n': + type = FDT_IS_NODE; + break; + case 'o': + disp->output_fname = optarg; + break; + case 'O': + if (!strcmp(optarg, "dtb")) + disp->output = OUT_DTB; + else if (!strcmp(optarg, "dts")) + disp->output = OUT_DTS; + else if (!strcmp(optarg, "bin")) + disp->output = OUT_BIN; + else + usage("Unknown output format"); + break; + case 'P': + inc = 0; + /* no break */ + case 'p': + type = FDT_IS_PROP; + break; + case 'r': + disp->remove_strings = 1; + break; + case 'R': + disp->include_root = 1; + break; + case 's': + disp->flags |= FDT_REG_ALL_SUBNODES; + break; + case 'S': + disp->flags &= ~FDT_REG_SUPERNODES; + break; + case 't': + disp->flags |= FDT_REG_ADD_STRING_TAB; + break; + case 'T': + disp->add_aliases = 1; + break; + case 'v': + disp->invert = 1; + break; + case 'I': + disp->show_dts_version = 1; + break; + } + + if (type && value_add(disp, &disp->value_head, type, inc, + optarg)) + usage("Cannot add value"); + } + + if (disp->invert && disp->types_exc) + usage("-v has no meaning when used with 'exclude' conditions"); +} + +int main(int argc, char *argv[]) +{ + char *filename = NULL; + struct display_info disp; + int ret; + + /* set defaults */ + memset(&disp, '\0', sizeof(disp)); + disp.flags = FDT_REG_SUPERNODES; /* Default flags */ + + scan_args(&disp, argc, argv); + + /* Show matched lines in colour if we can */ + disp.colour = disp.all && isatty(0); + + /* Any additional arguments can match anything, just like -g */ + while (optind < argc - 1) { + if (value_add(&disp, &disp.value_head, FDT_IS_ANY, 1, + argv[optind++])) + usage("Cannot add value"); + } + + if (optind < argc) + filename = argv[optind++]; + if (!filename) + usage("Missing filename"); + + /* If a valid .dtb is required, set flags to ensure we get one */ + if (disp.output == OUT_DTB) { + disp.header = 1; + disp.flags |= FDT_REG_ADD_MEM_RSVMAP | FDT_REG_ADD_STRING_TAB; + } + + if (disp.output_fname) { + disp.fout = fopen(disp.output_fname, "w"); + if (!disp.fout) + usage("Cannot open output file"); + } else { + disp.fout = stdout; + } + + /* Run the grep and output the results */ + ret = do_fdtgrep(&disp, filename); + if (disp.output_fname) + fclose(disp.fout); + if (ret) + return 1; + + return 0; +} |