mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-25 18:18:19 +01:00 
			
		
		
		
	Use realloc and update the loop executed in do_fdtgrep to find all the regions: only test count > max_region after the second pass. This patch solve an issue if the number of region found (count) is greater then the default value (max_region = count = 100): the second pass is never executed, because the loop stops after the first pass (i = 0, count > 100, max_regions = 100) with error -1 and the error message "Internal error with fdtgrep_find_region". I also update the error message. Signed-off-by: Patrick Delaunay <patrick.delaunay@st.com>
		
			
				
	
	
		
			1239 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1239 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  * Copyright (c) 2013, Google Inc.
 | |
|  * Written by Simon Glass <sjg@chromium.org>
 | |
|  *
 | |
|  * 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 <errno.h>
 | |
| #include <getopt.h>
 | |
| #include <fcntl.h>
 | |
| #include <stdbool.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #include "fdt_host.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);
 | |
| 	if (!str)
 | |
| 		goto err_mem;
 | |
| 	node = malloc(sizeof(*node));
 | |
| 	if (!node)
 | |
| 		goto err_mem;
 | |
| 	node->next = *headp;
 | |
| 	node->type = type;
 | |
| 	node->include = include;
 | |
| 	node->string = str;
 | |
| 	*headp = node;
 | |
| 
 | |
| 	return 0;
 | |
| err_mem:
 | |
| 	fprintf(stderr, "Out of memory\n");
 | |
| 	return -1;
 | |
| }
 | |
| 
 | |
| 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 preceded 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.
 | |
| 	 */
 | |
| 	if (data) {
 | |
| 		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++;
 | |
| 	}
 | |
| 	if (ret && ret != -FDT_ERR_NOTFOUND)
 | |
| 		return ret;
 | |
| 
 | |
| 	/* 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 == -FDT_ERR_NOTFOUND) {
 | |
| 			/* No alias node found */
 | |
| 		} else if (new_count < 0) {
 | |
| 			return new_count;
 | |
| 		} else if (new_count <= max_regions) {
 | |
| 			/*
 | |
| 			* The alias regions will now be at the end of the list.
 | |
| 			* Sort the regions by offset to get things into the
 | |
| 			* right order
 | |
| 			*/
 | |
| 			count = new_count;
 | |
| 			qsort(region, count, sizeof(struct fdt_region),
 | |
| 			      h_cmp_region);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	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 = NULL;
 | |
| 	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 = realloc(region, 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);
 | |
| 			free(region);
 | |
| 			return -1;
 | |
| 		}
 | |
| 		if (count <= max_regions)
 | |
| 			break;
 | |
| 	}
 | |
| 	if (count > max_regions) {
 | |
| 		free(region);
 | |
| 		fprintf(stderr, "Internal error with fdtgrep_find_region()\n");
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	/* 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;
 | |
| }
 |