| /* |
| * (C) Copyright David Gibson <dwg@au1.ibm.com>, IBM Corporation. 2005. |
| * |
| * |
| * 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 <stdint.h> |
| #include <string.h> |
| |
| #include "flat_dt.h" |
| |
| #define ALIGN(x, a) (((x) + ((a) - 1)) & ~((a) - 1)) |
| #define PALIGN(p, a) ((void *)(ALIGN((unsigned long)(p), (a)))) |
| #define GET_CELL(p) (p += 4, *((uint32_t *)(p-4))) |
| |
| static char *skip_name(char *p) |
| { |
| while (*p != '\0') |
| p++; |
| |
| return PALIGN(p, sizeof(uint32_t)); |
| } |
| |
| static char *skip_prop(void *blob, char *p) |
| { |
| struct boot_param_header *bph = blob; |
| uint32_t len, nameoff; |
| |
| len = GET_CELL(p); |
| nameoff = GET_CELL(p); |
| if ((bph->version < 0x10) && (len >= sizeof(uint64_t))) |
| p = PALIGN(p, sizeof(uint64_t)); |
| return PALIGN(p + len, sizeof(uint32_t)); |
| } |
| |
| static char *get_unit(char *dtpath) |
| { |
| char *p; |
| |
| if (dtpath[0] != '/') |
| return dtpath; |
| |
| p = dtpath + strlen(dtpath); |
| while (*p != '/') |
| p--; |
| |
| return p+1; |
| } |
| |
| static int first_seg_len(char *dtpath) |
| { |
| int len = 0; |
| |
| while ((dtpath[len] != '/') && (dtpath[len] != '\0')) |
| len++; |
| |
| return len; |
| } |
| |
| char *flat_dt_get_string(void *blob, uint32_t offset) |
| { |
| struct boot_param_header *bph = blob; |
| |
| return (char *)blob + bph->off_dt_strings + offset; |
| } |
| |
| void *flat_dt_get_subnode(void *blob, void *node, char *uname, int unamelen) |
| { |
| struct boot_param_header *bph = blob; |
| char *p = node; |
| uint32_t tag; |
| int depth = 0; |
| char *nuname; |
| |
| if (! unamelen) |
| unamelen = strlen(uname); |
| |
| do { |
| tag = GET_CELL(p); |
| |
| switch (tag) { |
| case OF_DT_PROP: |
| p = skip_prop(blob, p); |
| break; |
| |
| case OF_DT_BEGIN_NODE: |
| if (depth == 0) { |
| nuname = p; |
| |
| if (bph->version < 0x10) |
| nuname = get_unit(nuname); |
| |
| p = skip_name(p); |
| |
| if (strncmp(nuname, uname, unamelen) == 0) |
| return p; |
| } |
| depth++; |
| break; |
| |
| case OF_DT_END_NODE: |
| depth--; |
| break; |
| |
| case OF_DT_END: |
| /* looks like a malformed tree */ |
| return NULL; |
| break; |
| |
| default: |
| /* FIXME: throw some sort of error */ |
| return NULL; |
| } |
| } while (depth >= 0); |
| |
| return NULL; |
| } |
| |
| void *flat_dt_get_node(void *blob, char *path) |
| { |
| struct boot_param_header *bph = blob; |
| char *node; |
| int seglen; |
| |
| node = blob + bph->off_dt_struct; |
| node += sizeof(uint32_t); /* skip initial OF_DT_BEGIN_NODE */ |
| node = skip_name(node); /* skip root node name */ |
| |
| while (node && (*path)) { |
| if (path[0] == '/') |
| path++; |
| |
| seglen = first_seg_len(path); |
| |
| node = flat_dt_get_subnode(blob, node, path, seglen); |
| |
| path += seglen; |
| } |
| |
| return node; |
| } |
| |
| void flat_dt_traverse(void *blob, |
| int (*fn)(void *blob, void *node, void *priv), |
| void *private) |
| { |
| struct boot_param_header *bph = blob; |
| char *p; |
| uint32_t tag; |
| int depth = 0; |
| char *uname; |
| |
| p = (char *)blob + bph->off_dt_struct; |
| |
| tag = GET_CELL(p); |
| while (tag != OF_DT_END) { |
| switch (tag) { |
| case OF_DT_BEGIN_NODE: |
| uname = p; |
| |
| if (bph->version < 0x10) |
| uname = get_unit(uname); |
| |
| p = skip_name(p); |
| |
| (*fn)(blob, p, private); |
| depth++; |
| break; |
| |
| case OF_DT_END_NODE: |
| depth--; |
| break; |
| |
| case OF_DT_PROP: |
| p = skip_prop(blob, p); |
| break; |
| |
| default: |
| /* FIXME: badly formed tree */ |
| return; |
| } |
| } |
| } |
| |
| void *flat_dt_get_prop(void *blob, void *node, char *name, uint32_t *len) |
| { |
| struct boot_param_header *bph = blob; |
| char *p = node; |
| |
| do { |
| uint32_t tag = GET_CELL(p); |
| uint32_t sz, noff; |
| const char *nstr; |
| |
| if (tag != OF_DT_PROP) |
| return NULL; |
| |
| sz = GET_CELL(p); |
| noff = GET_CELL(p); |
| |
| /* Old versions have variable alignment of the |
| * property value */ |
| if ((bph->version < 0x10) && (sz >= 8)) |
| p = PALIGN(p, 8); |
| |
| nstr = flat_dt_get_string(blob, noff); |
| |
| if (strcmp(name, nstr) == 0) { |
| if (len) |
| *len = sz; |
| return (void *)p; |
| } |
| |
| p = PALIGN(p + sz, sizeof(uint32_t)); |
| } while(1); |
| } |