|
@@ -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;
|
|
|
+}
|