diff options
author | Sascha Hauer <s.hauer@pengutronix.de> | 2011-08-24 21:03:05 +0200 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2011-10-19 10:09:48 +0200 |
commit | c0f9d1bad238790056d9d7916ef40c84ec3185c1 (patch) | |
tree | d718a890131c99a453bfa44ddad1b11dca02d3bc /common/oftree.c | |
parent | 9135dd5f672e2805f8ad08a6535fc36b7eb8957c (diff) | |
download | barebox-c0f9d1bad238790056d9d7916ef40c84ec3185c1.tar.gz barebox-c0f9d1bad238790056d9d7916ef40c84ec3185c1.tar.xz |
initial oftree command support
This adds basic device tree command support. So far we can
parse a flat device tree (-p), which also stores the tree
in memory, dump it (-d) and free (-f) the internally stored tree.
The chosen node can be updated with barebox bootargs, no other
device tree manipulation is implemented yet.
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'common/oftree.c')
-rw-r--r-- | common/oftree.c | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/common/oftree.c b/common/oftree.c new file mode 100644 index 0000000000..2a2f464204 --- /dev/null +++ b/common/oftree.c @@ -0,0 +1,312 @@ +#include <common.h> +#include <environment.h> +#include <fdt.h> +#include <of.h> +#include <command.h> +#include <fs.h> +#include <malloc.h> +#include <libfdt.h> +#include <linux/ctype.h> +#include <asm/byteorder.h> +#include <errno.h> +#include <getopt.h> +#include <init.h> + +#define MAX_LEVEL 32 /* how deeply nested we will go */ + +static int is_printable_string(const void *data, int len) +{ + const char *s = data; + + /* zero length is not */ + if (len == 0) + return 0; + + /* must terminate with zero */ + if (s[len - 1] != '\0') + return 0; + + /* printable or a null byte (concatenated strings) */ + while (((*s == '\0') || isprint(*s)) && (len > 0)) { + /* + * If we see a null, there are three possibilities: + * 1) If len == 1, it is the end of the string, printable + * 2) Next character also a null, not printable. + * 3) Next character not a null, continue to check. + */ + if (s[0] == '\0') { + if (len == 1) + return 1; + if (s[1] == '\0') + return 0; + } + s++; + len--; + } + + /* Not the null termination, or not done yet: not printable */ + if (*s != '\0' || (len != 0)) + return 0; + + return 1; +} + +/* + * Print the property in the best format, a heuristic guess. Print as + * a string, concatenated strings, a byte, word, double word, or (if all + * else fails) it is printed as a stream of bytes. + */ +static void print_data(const void *data, int len) +{ + int j; + + /* no data, don't print */ + if (len == 0) + return; + + /* + * It is a string, but it may have multiple strings (embedded '\0's). + */ + if (is_printable_string(data, len)) { + puts("\""); + j = 0; + while (j < len) { + if (j > 0) + puts("\", \""); + puts(data); + j += strlen(data) + 1; + data += strlen(data) + 1; + } + puts("\""); + return; + } + + if ((len % 4) == 0) { + const u32 *p; + + printf("<"); + for (j = 0, p = data; j < len/4; j ++) + printf("0x%x%s", fdt32_to_cpu(p[j]), j < (len/4 - 1) ? " " : ""); + printf(">"); + } else { /* anything else... hexdump */ + const u8 *s; + + printf("["); + for (j = 0, s = data; j < len; j++) + printf("%02x%s", s[j], j < len - 1 ? " " : ""); + printf("]"); + } +} + +static void printf_indent(int level, const char *fmt, ...) +{ + va_list args; + + printf("%*s", level * 8, ""); + + va_start (args, fmt); + vprintf(fmt, args); + va_end (args); +} + +int fdt_print(struct fdt_header *working_fdt, const char *pathp) +{ + const void *nodep; /* property node pointer */ + int nodeoffset; /* node offset from libfdt */ + int nextoffset; /* next node offset from libfdt */ + uint32_t tag; /* tag */ + int len; /* length of the property */ + int level = 0; /* keep track of nesting level */ + const struct fdt_property *fdt_prop; + + nodeoffset = fdt_path_offset(working_fdt, pathp); + if (nodeoffset < 0) { + /* + * Not found or something else bad happened. + */ + printf("libfdt fdt_path_offset() returned %s\n", + fdt_strerror(nodeoffset)); + return 1; + } + + while (level >= 0) { + tag = fdt_next_tag(working_fdt, nodeoffset, &nextoffset); + switch (tag) { + case FDT_BEGIN_NODE: + pathp = fdt_get_name(working_fdt, nodeoffset, NULL); + if (pathp == NULL) + pathp = "/* NULL pointer error */"; + if (*pathp == '\0') + pathp = "/"; /* root is nameless */ + printf_indent(level, "%s {\n",pathp); + level++; + if (level >= MAX_LEVEL) { + printf("Nested too deep, aborting.\n"); + return 1; + } + break; + case FDT_END_NODE: + level--; + printf_indent(level, "};\n"); + if (level == 0) { + level = -1; /* exit the loop */ + } + break; + case FDT_PROP: + fdt_prop = fdt_offset_ptr(working_fdt, nodeoffset, + sizeof(*fdt_prop)); + pathp = fdt_string(working_fdt, + fdt32_to_cpu(fdt_prop->nameoff)); + len = fdt32_to_cpu(fdt_prop->len); + nodep = fdt_prop->data; + if (len < 0) { + printf("libfdt fdt_getprop(): %s\n", + fdt_strerror(len)); + return 1; + } else if (len == 0) { + /* the property has no value */ + printf_indent(level, "%s;\n", pathp); + } else { + printf_indent(level, "%s = ", pathp); + print_data(nodep, len); + printf(";\n"); + } + break; + case FDT_NOP: + printf_indent(level, "/* NOP */\n"); + break; + case FDT_END: + return 1; + default: + printf("Unknown tag 0x%08X\n", tag); + return 1; + } + nodeoffset = nextoffset; + } + return 0; +} + +/** + * fdt_find_and_setprop: Find a node and set it's property + * + * @fdt: ptr to device tree + * @node: path of node + * @prop: property name + * @val: ptr to new value + * @len: length of new property value + * @create: flag to create the property if it doesn't exist + * + * Convenience function to directly set a property given the path to the node. + */ +int fdt_find_and_setprop(struct fdt_header *fdt, const char *node, + const char *prop, const void *val, int len, int create) +{ + int nodeoff = fdt_path_offset(fdt, node); + + if (nodeoff < 0) + return nodeoff; + + if ((!create) && (fdt_get_property(fdt, nodeoff, prop, 0) == NULL)) + return 0; /* create flag not set; so exit quietly */ + + return fdt_setprop(fdt, nodeoff, prop, val, len); +} + +void do_fixup_by_path(struct fdt_header *fdt, const char *path, + const char *prop, const void *val, int len, int create) +{ + int rc = fdt_find_and_setprop(fdt, path, prop, val, len, create); + if (rc) + printf("Unable to update property %s:%s, err=%s\n", + path, prop, fdt_strerror(rc)); +} + +void do_fixup_by_path_u32(struct fdt_header *fdt, const char *path, + const char *prop, u32 val, int create) +{ + val = cpu_to_fdt32(val); + do_fixup_by_path(fdt, path, prop, &val, sizeof(val), create); +} + +int fdt_get_path_or_create(struct fdt_header *fdt, const char *path) +{ + int nodeoffset; + + nodeoffset = fdt_path_offset (fdt, path); + if (nodeoffset < 0) { + nodeoffset = fdt_add_subnode(fdt, 0, path + 1); + if (nodeoffset < 0) { + printf("WARNING: could not create %s %s.\n", + path, fdt_strerror(nodeoffset)); + return -EINVAL; + } + } + + return nodeoffset; +} + +struct fdt_header *barebox_fdt; + +static int of_fixup_bootargs(struct fdt_header *fdt) +{ + int nodeoffset; + const char *str; + int err; + + nodeoffset = fdt_get_path_or_create(fdt, "/chosen"); + if (nodeoffset < 0) + return nodeoffset; + + str = getenv("bootargs"); + if (str) { + err = fdt_setprop(fdt, nodeoffset, + "bootargs", str, strlen(str)+1); + if (err < 0) + printf("WARNING: could not set bootargs %s.\n", + fdt_strerror(err)); + } + + return 0; +} + +static int of_register_bootargs_fixup(void) +{ + return of_register_fixup(of_fixup_bootargs); +} +late_initcall(of_register_bootargs_fixup); + +struct of_fixup { + int (*fixup)(struct fdt_header *); + struct list_head list; +}; + +static LIST_HEAD(of_fixup_list); + +int of_register_fixup(int (*fixup)(struct fdt_header *)) +{ + struct of_fixup *of_fixup = xzalloc(sizeof(*of_fixup)); + + of_fixup->fixup = fixup; + + list_add_tail(&of_fixup->list, &of_fixup_list); + + return 0; +} + +struct fdt_header *of_get_fixed_tree(void) +{ + struct of_fixup *of_fixup; + int ret; + + if (!barebox_fdt) + return NULL; + + list_for_each_entry(of_fixup, &of_fixup_list, list) { + ret = of_fixup->fixup(barebox_fdt); + if (ret) + return NULL; + } + + return barebox_fdt; +} + |