From 68d057f20d7c3a93b441d2892c4749392bc83b45 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Sat, 21 Jan 2012 10:14:47 -0800 Subject: [PATCH] Add fdtget utility to read property values from a device tree This simply utility makes it easy for scripts to read values from the device tree. It is written in C and uses the same libfdt as the rest of the dtc package. What is it for: - Reading fdt values from scripts - Extracting fdt information within build systems - Looking at particular values without having to dump the entire tree To use it, specify the fdt binary file on command line followed by a list of node, property pairs. The utility then looks up each node, finds the property and displays the value. Each value is printed on a new line. fdtget tries to guess the type of each property based on its contents. This is not always reliable, so you can use the -t option to force fdtget to decode the value as a string, or byte, etc. To read from stdin, use - as the file. Usage: fdtget
[ ]... Options: -t Type of data -h Print this help s=string, i=int, u=unsigned, x=hex Optional modifier prefix: hh or b=byte, h=2 byte, l=4 byte (default) Signed-off-by: Simon Glass --- .gitignore | 1 + Makefile | 4 + Makefile.utils | 7 ++ fdtget.c | 226 ++++++++++++++++++++++++++++++++++++++++ tests/fdtget-runtest.sh | 35 +++++++ tests/run_tests.sh | 43 +++++++- tests/tests.sh | 1 + util.h | 10 ++ 8 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 fdtget.c create mode 100755 tests/fdtget-runtest.sh diff --git a/.gitignore b/.gitignore index 74714cd..2d82b71 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ lex.yy.c /fdtdump /convert-dtsv0 /version_gen.h +/fdtget diff --git a/Makefile b/Makefile index 4582f5d..a54a209 100644 --- a/Makefile +++ b/Makefile @@ -110,6 +110,7 @@ include Makefile.utils BIN += convert-dtsv0 BIN += dtc BIN += fdtdump +BIN += fdtget SCRIPTS = dtdiff @@ -120,6 +121,7 @@ ifneq ($(DEPTARGETS),) -include $(DTC_OBJS:%.o=%.d) -include $(CONVERT_OBJS:%.o=%.d) -include $(FDTDUMP_OBJS:%.o=%.d) +-include $(FDTGET_OBJS:%.o=%.d) endif @@ -180,6 +182,8 @@ convert-dtsv0: $(CONVERT_OBJS) fdtdump: $(FDTDUMP_OBJS) +fdtget: $(FDTGET_OBJS) $(LIBFDT_archive) + # # Testsuite rules diff --git a/Makefile.utils b/Makefile.utils index fae5b00..38efa3c 100644 --- a/Makefile.utils +++ b/Makefile.utils @@ -8,3 +8,10 @@ FDTDUMP_SRCS = \ util.c FDTDUMP_OBJS = $(FDTDUMP_SRCS:%.c=%.o) + + +FDTGET_SRCS = \ + fdtget.c \ + util.c + +FDTGET_OBJS = $(FDTGET_SRCS:%.c=%.o) diff --git a/fdtget.c b/fdtget.c new file mode 100644 index 0000000..48ab615 --- /dev/null +++ b/fdtget.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include +#include +#include +#include +#include + +#include + +#include "util.h" + +/* Holds information which controls our output and options */ +struct display_info { + int type; /* data type (s/i/u/x or 0 for default) */ + int size; /* data size (1/2/4) */ +}; + +static void report_error(const char *where, int err) +{ + fprintf(stderr, "Error at '%s': %s\n", where, fdt_strerror(err)); +} + +/** + * Displays data of a given length according to selected options + * + * If a specific data type is provided in disp, then this is used. Otherwise + * we try to guess the data type / size from the contents. + * + * @param disp Display information / options + * @param data Data to display + * @param len Maximum length of buffer + * @return 0 if ok, -1 if data does not match format + */ +static int show_data(struct display_info *disp, const char *data, int len) +{ + int i, size; + const uint8_t *p = (const uint8_t *)data; + const char *s; + int value; + int is_string; + char fmt[3]; + + /* no data, don't print */ + if (len == 0) + return 0; + + is_string = (disp->type) == 's' || + (!disp->type && util_is_printable_string(data, len)); + if (is_string) { + if (data[len - 1] != '\0') { + fprintf(stderr, "Unterminated string\n"); + return -1; + } + for (s = data; s - data < len; s += strlen(s) + 1) { + if (s != data) + printf(" "); + printf("%s", (const char *)s); + } + return 0; + } + size = disp->size; + if (size == -1) + size = (len % 4) == 0 ? 4 : 1; + else if (len % size) { + fprintf(stderr, "Property length must be a multiple of " + "selected data size\n"); + return -1; + } + fmt[0] = '%'; + fmt[1] = disp->type ? disp->type : 'd'; + fmt[2] = '\0'; + for (i = 0; i < len; i += size, p += size) { + if (i) + printf(" "); + value = size == 4 ? fdt32_to_cpu(*(const uint32_t *)p) : + size == 2 ? (*p << 8) | p[1] : *p; + printf(fmt, value); + } + return 0; +} + +/** + * Show the data for a given node (and perhaps property) according to the + * display option provided. + * + * @param blob FDT blob + * @param disp Display information / options + * @param node Node to display + * @param property Name of property to display, or NULL if none + * @return 0 if ok, -ve on error + */ +static int show_data_for_item(const void *blob, struct display_info *disp, + int node, const char *property) +{ + const void *value = NULL; + int len, err = 0; + + value = fdt_getprop(blob, node, property, &len); + if (value) { + if (show_data(disp, value, len)) + err = -1; + else + printf("\n"); + } else { + report_error(property, len); + err = -1; + } + return err; +} + +/** + * Run the main fdtget operation, given a filename and valid arguments + * + * @param disp Display information / options + * @param filename Filename of blob file + * @param arg List of arguments to process + * @param arg_count Number of arguments + * @param return 0 if ok, -ve on error + */ +static int do_fdtget(struct display_info *disp, const char *filename, + char **arg, int arg_count) +{ + char *blob; + int i, node; + + blob = utilfdt_read(filename); + if (!blob) + return -1; + + for (i = 0; i + 2 <= arg_count; i += 2) { + node = fdt_path_offset(blob, arg[0]); + if (node < 0) { + report_error(arg[0], node); + return -1; + } + + if (show_data_for_item(blob, disp, node, arg[1])) + return -1; + } + return 0; +} + +static const char *usage_msg = + "fdtget - read values from device tree\n" + "\n" + "Each value is printed on a new line.\n\n" + "Usage:\n" + " fdtget
[ ]...\n" + "Options:\n" + "\t-t \tType of data\n" + "\t-h\t\tPrint this help\n\n" + USAGE_TYPE_MSG; + +static void usage(const char *msg) +{ + if (msg) + fprintf(stderr, "Error: %s\n\n", msg); + + fprintf(stderr, "%s", usage_msg); + exit(2); +} + +int main(int argc, char *argv[]) +{ + char *filename = NULL; + struct display_info disp; + + /* set defaults */ + memset(&disp, '\0', sizeof(disp)); + disp.size = -1; + for (;;) { + int c = getopt(argc, argv, "ht:"); + if (c == -1) + break; + + switch (c) { + case 'h': + case '?': + usage(NULL); + + case 't': + if (utilfdt_decode_type(optarg, &disp.type, + &disp.size)) + usage("Invalid type string"); + break; + } + } + + if (optind < argc) + filename = argv[optind++]; + if (!filename) + usage("Missing filename"); + + argv += optind; + argc -= optind; + + /* Allow no arguments, and silently succeed */ + if (!argc) + return 0; + + /* Check for node, property arguments */ + if (argc % 2) + usage("Must have an even number of arguments"); + + if (do_fdtget(&disp, filename, argv, argc)) + return 1; + return 0; +} diff --git a/tests/fdtget-runtest.sh b/tests/fdtget-runtest.sh new file mode 100755 index 0000000..f38184f --- /dev/null +++ b/tests/fdtget-runtest.sh @@ -0,0 +1,35 @@ +#! /bin/sh + +. ./tests.sh + +LOG="tmp.log.$$" +EXPECT="tmp.expect.$$" + +rm -f $TMPFILE $LOG + +expect="$1" +echo "$expect" >$EXPECT +shift + +verbose_run_log "$LOG" $VALGRIND "$DTGET" "$@" +ret="$?" + +if [ "$ret" -ne 0 -a "$expect" = "ERR" ]; then + PASS +fi + +if [ "$ret" -gt 127 ]; then + signame=$(kill -l $[ret - 128]) + FAIL "Killed by SIG$signame" +fi + +diff $EXPECT $LOG +ret="$?" + +rm -f $LOG $EXPECT + +if [ "$ret" -eq 0 ]; then + PASS +else + FAIL +fi diff --git a/tests/run_tests.sh b/tests/run_tests.sh index e42154b..e6184df 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -83,6 +83,13 @@ asm_to_so_test () { run_wrap_test asm_to_so "$@" } +run_fdtget_test () { + # run_fdtget_test name expected_output dtb_file args... + echo -n "$1: " + shift + base_run_test sh fdtget-runtest.sh "$@" +} + tree1_tests () { TREE=$1 @@ -402,6 +409,37 @@ dtbs_equal_tests () { cmp_tests test_tree1.dtb $WRONG_TREE1 } +fdtget_tests () { + file=label01.dtb + $DTC -O dtb -o $file ${file%.dtb}.dts 2>/dev/null + + # run_fdtget_test ... + run_fdtget_test "Simple string" "MyBoardName" $file / model + run_fdtget_test "Multiple string i" "77 121 66 111 \ +97 114 100 78 97 109 101 0 77 121 66 111 97 114 100 70 97 109 105 \ +108 121 78 97 109 101 0" $file / compatible + run_fdtget_test "Multiple string s" "MyBoardName MyBoardFamilyName" \ + -t s $file / compatible + run_fdtget_test "Integer" "32768" $file /cpus/PowerPC,970@1 d-cache-size + run_fdtget_test "Integer hex" "8000" -tx $file \ + /cpus/PowerPC,970@1 d-cache-size + run_fdtget_test "Integer list" "61 62 63 0" -tbx $file \ + /randomnode tricky1 + run_fdtget_test "Byte list short" "a b c d de ea ad be ef" -tbx \ + $file /randomnode blob + + # Here the property size is not a multiple of 4 bytes, so it should fail + run_fdtget_test "Integer list invalid" ERR -tlx \ + $file /randomnode mixed + run_fdtget_test "Integer list halfword" "6162 6300 1234 0 a 0 b 0 c" -thx \ + $file /randomnode mixed + run_fdtget_test "Integer list byte" \ + "61 62 63 0 12 34 0 0 0 a 0 0 0 b 0 0 0 c" -thhx \ + $file /randomnode mixed + run_fdtget_test "Missing property" ERR -ts \ + $file /randomnode doctor-who +} + utilfdt_tests () { run_test utilfdt_test } @@ -421,7 +459,7 @@ while getopts "vt:m" ARG ; do done if [ -z "$TESTSETS" ]; then - TESTSETS="libfdt utilfdt dtc dtbs_equal" + TESTSETS="libfdt utilfdt dtc dtbs_equal fdtget" fi # Make sure we don't have stale blobs lying around @@ -441,6 +479,9 @@ for set in $TESTSETS; do "dtbs_equal") dtbs_equal_tests ;; + "fdtget") + fdtget_tests + ;; esac done diff --git a/tests/tests.sh b/tests/tests.sh index 30ffead..d9a0524 100644 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -11,6 +11,7 @@ FAIL () { } DTC=../dtc +DTGET=../fdtget verbose_run () { if [ -z "$QUIET_TEST" ]; then diff --git a/util.h b/util.h index 730918e..c8eb45d 100644 --- a/util.h +++ b/util.h @@ -140,4 +140,14 @@ int utilfdt_write_err(const char *filename, const void *blob); */ int utilfdt_decode_type(const char *fmt, int *type, int *size); +/* + * This is a usage message fragment for the -t option. It is the format + * supported by utilfdt_decode_type. + */ + +#define USAGE_TYPE_MSG \ + "\ts=string, i=int, u=unsigned, x=hex\n" \ + "\tOptional modifier prefix:\n" \ + "\t\thh or b=byte, h=2 byte, l=4 byte (default)\n"; + #endif /* _UTIL_H */