/* | |
* libfdt - Flat Device Tree manipulation | |
* Copyright (C) 2006 David Gibson, IBM Corporation. | |
* | |
* libfdt is dual licensed: you can use it either under the terms of | |
* the GPL, or the BSD license, at your option. | |
* | |
* a) This library 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 library 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 library; if not, write to the Free | |
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, | |
* MA 02110-1301 USA | |
* | |
* Alternatively, | |
* | |
* b) Redistribution and use in source and binary forms, with or | |
* without modification, are permitted provided that the following | |
* conditions are met: | |
* | |
* 1. Redistributions of source code must retain the above | |
* copyright notice, this list of conditions and the following | |
* disclaimer. | |
* 2. Redistributions in binary form must reproduce the above | |
* copyright notice, this list of conditions and the following | |
* disclaimer in the documentation and/or other materials | |
* provided with the distribution. | |
* | |
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND | |
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, | |
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR | |
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | |
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | |
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, | |
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
#include "libfdt_env.h" | |
#include <fdt.h> | |
#include <libfdt.h> | |
#include "libfdt_internal.h" | |
static int | |
_fdt_nodename_eq ( | |
const void *fdt, | |
int offset, | |
const char *s, | |
int len | |
) | |
{ | |
const char *p = fdt_offset_ptr (fdt, offset + FDT_TAGSIZE, len+1); | |
if (!p) { | |
/* short match */ | |
return 0; | |
} | |
if (memcmp (p, s, len) != 0) { | |
return 0; | |
} | |
if (p[len] == '\0') { | |
return 1; | |
} else if (!memchr (s, '@', len) && (p[len] == '@')) { | |
return 1; | |
} else { | |
return 0; | |
} | |
} | |
const char * | |
fdt_string ( | |
const void *fdt, | |
int stroffset | |
) | |
{ | |
return (const char *)fdt + fdt_off_dt_strings (fdt) + stroffset; | |
} | |
static int | |
_fdt_string_eq ( | |
const void *fdt, | |
int stroffset, | |
const char *s, | |
int len | |
) | |
{ | |
const char *p = fdt_string (fdt, stroffset); | |
return (strlen (p) == len) && (memcmp (p, s, len) == 0); | |
} | |
uint32_t | |
fdt_get_max_phandle ( | |
const void *fdt | |
) | |
{ | |
uint32_t max_phandle = 0; | |
int offset; | |
for (offset = fdt_next_node (fdt, -1, NULL); ; | |
offset = fdt_next_node (fdt, offset, NULL)) | |
{ | |
uint32_t phandle; | |
if (offset == -FDT_ERR_NOTFOUND) { | |
return max_phandle; | |
} | |
if (offset < 0) { | |
return (uint32_t)-1; | |
} | |
phandle = fdt_get_phandle (fdt, offset); | |
if (phandle == (uint32_t)-1) { | |
continue; | |
} | |
if (phandle > max_phandle) { | |
max_phandle = phandle; | |
} | |
} | |
return 0; | |
} | |
int | |
fdt_get_mem_rsv ( | |
const void *fdt, | |
int n, | |
uint64_t *address, | |
uint64_t *size | |
) | |
{ | |
FDT_CHECK_HEADER (fdt); | |
*address = fdt64_to_cpu (_fdt_mem_rsv (fdt, n)->address); | |
*size = fdt64_to_cpu (_fdt_mem_rsv (fdt, n)->size); | |
return 0; | |
} | |
int | |
fdt_num_mem_rsv ( | |
const void *fdt | |
) | |
{ | |
int i = 0; | |
while (fdt64_to_cpu (_fdt_mem_rsv (fdt, i)->size) != 0) { | |
i++; | |
} | |
return i; | |
} | |
static int | |
_nextprop ( | |
const void *fdt, | |
int offset | |
) | |
{ | |
uint32_t tag; | |
int nextoffset; | |
do { | |
tag = fdt_next_tag (fdt, offset, &nextoffset); | |
switch (tag) { | |
case FDT_END: | |
if (nextoffset >= 0) { | |
return -FDT_ERR_BADSTRUCTURE; | |
} else { | |
return nextoffset; | |
} | |
case FDT_PROP: | |
return offset; | |
} | |
offset = nextoffset; | |
} while (tag == FDT_NOP); | |
return -FDT_ERR_NOTFOUND; | |
} | |
int | |
fdt_subnode_offset_namelen ( | |
const void *fdt, | |
int offset, | |
const char *name, | |
int namelen | |
) | |
{ | |
int depth; | |
FDT_CHECK_HEADER (fdt); | |
for (depth = 0; | |
(offset >= 0) && (depth >= 0); | |
offset = fdt_next_node (fdt, offset, &depth)) | |
{ | |
if ( (depth == 1) | |
&& _fdt_nodename_eq (fdt, offset, name, namelen)) | |
{ | |
return offset; | |
} | |
} | |
if (depth < 0) { | |
return -FDT_ERR_NOTFOUND; | |
} | |
return offset; /* error */ | |
} | |
int | |
fdt_subnode_offset ( | |
const void *fdt, | |
int parentoffset, | |
const char *name | |
) | |
{ | |
return fdt_subnode_offset_namelen (fdt, parentoffset, name, strlen (name)); | |
} | |
int | |
fdt_path_offset_namelen ( | |
const void *fdt, | |
const char *path, | |
int namelen | |
) | |
{ | |
const char *end = path + namelen; | |
const char *p = path; | |
int offset = 0; | |
FDT_CHECK_HEADER (fdt); | |
/* see if we have an alias */ | |
if (*path != '/') { | |
const char *q = memchr (path, '/', end - p); | |
if (!q) { | |
q = end; | |
} | |
p = fdt_get_alias_namelen (fdt, p, q - p); | |
if (!p) { | |
return -FDT_ERR_BADPATH; | |
} | |
offset = fdt_path_offset (fdt, p); | |
p = q; | |
} | |
while (p < end) { | |
const char *q; | |
while (*p == '/') { | |
p++; | |
if (p == end) { | |
return offset; | |
} | |
} | |
q = memchr (p, '/', end - p); | |
if (!q) { | |
q = end; | |
} | |
offset = fdt_subnode_offset_namelen (fdt, offset, p, q-p); | |
if (offset < 0) { | |
return offset; | |
} | |
p = q; | |
} | |
return offset; | |
} | |
int | |
fdt_path_offset ( | |
const void *fdt, | |
const char *path | |
) | |
{ | |
return fdt_path_offset_namelen (fdt, path, strlen (path)); | |
} | |
const char * | |
fdt_get_name ( | |
const void *fdt, | |
int nodeoffset, | |
int *len | |
) | |
{ | |
const struct fdt_node_header *nh = _fdt_offset_ptr (fdt, nodeoffset); | |
int err; | |
if ( ((err = fdt_check_header (fdt)) != 0) | |
|| ((err = _fdt_check_node_offset (fdt, nodeoffset)) < 0)) | |
{ | |
goto fail; | |
} | |
if (len) { | |
*len = strlen (nh->name); | |
} | |
return nh->name; | |
fail: | |
if (len) { | |
*len = err; | |
} | |
return NULL; | |
} | |
int | |
fdt_first_property_offset ( | |
const void *fdt, | |
int nodeoffset | |
) | |
{ | |
int offset; | |
if ((offset = _fdt_check_node_offset (fdt, nodeoffset)) < 0) { | |
return offset; | |
} | |
return _nextprop (fdt, offset); | |
} | |
int | |
fdt_next_property_offset ( | |
const void *fdt, | |
int offset | |
) | |
{ | |
if ((offset = _fdt_check_prop_offset (fdt, offset)) < 0) { | |
return offset; | |
} | |
return _nextprop (fdt, offset); | |
} | |
const struct fdt_property * | |
fdt_get_property_by_offset ( | |
const void *fdt, | |
int offset, | |
int *lenp | |
) | |
{ | |
int err; | |
const struct fdt_property *prop; | |
if ((err = _fdt_check_prop_offset (fdt, offset)) < 0) { | |
if (lenp) { | |
*lenp = err; | |
} | |
return NULL; | |
} | |
prop = _fdt_offset_ptr (fdt, offset); | |
if (lenp) { | |
*lenp = fdt32_to_cpu (prop->len); | |
} | |
return prop; | |
} | |
const struct fdt_property * | |
fdt_get_property_namelen ( | |
const void *fdt, | |
int offset, | |
const char *name, | |
int namelen, | |
int *lenp | |
) | |
{ | |
for (offset = fdt_first_property_offset (fdt, offset); | |
(offset >= 0); | |
(offset = fdt_next_property_offset (fdt, offset))) | |
{ | |
const struct fdt_property *prop; | |
if (!(prop = fdt_get_property_by_offset (fdt, offset, lenp))) { | |
offset = -FDT_ERR_INTERNAL; | |
break; | |
} | |
if (_fdt_string_eq ( | |
fdt, | |
fdt32_to_cpu (prop->nameoff), | |
name, | |
namelen | |
)) | |
{ | |
return prop; | |
} | |
} | |
if (lenp) { | |
*lenp = offset; | |
} | |
return NULL; | |
} | |
const struct fdt_property * | |
fdt_get_property ( | |
const void *fdt, | |
int nodeoffset, | |
const char *name, | |
int *lenp | |
) | |
{ | |
return fdt_get_property_namelen ( | |
fdt, | |
nodeoffset, | |
name, | |
strlen (name), | |
lenp | |
); | |
} | |
const void * | |
fdt_getprop_namelen ( | |
const void *fdt, | |
int nodeoffset, | |
const char *name, | |
int namelen, | |
int *lenp | |
) | |
{ | |
const struct fdt_property *prop; | |
prop = fdt_get_property_namelen (fdt, nodeoffset, name, namelen, lenp); | |
if (!prop) { | |
return NULL; | |
} | |
return prop->data; | |
} | |
const void * | |
fdt_getprop_by_offset ( | |
const void *fdt, | |
int offset, | |
const char **namep, | |
int *lenp | |
) | |
{ | |
const struct fdt_property *prop; | |
prop = fdt_get_property_by_offset (fdt, offset, lenp); | |
if (!prop) { | |
return NULL; | |
} | |
if (namep) { | |
*namep = fdt_string (fdt, fdt32_to_cpu (prop->nameoff)); | |
} | |
return prop->data; | |
} | |
const void * | |
fdt_getprop ( | |
const void *fdt, | |
int nodeoffset, | |
const char *name, | |
int *lenp | |
) | |
{ | |
return fdt_getprop_namelen (fdt, nodeoffset, name, strlen (name), lenp); | |
} | |
uint32_t | |
fdt_get_phandle ( | |
const void *fdt, | |
int nodeoffset | |
) | |
{ | |
const fdt32_t *php; | |
int len; | |
/* FIXME: This is a bit sub-optimal, since we potentially scan | |
* over all the properties twice. */ | |
php = fdt_getprop (fdt, nodeoffset, "phandle", &len); | |
if (!php || (len != sizeof (*php))) { | |
php = fdt_getprop (fdt, nodeoffset, "linux,phandle", &len); | |
if (!php || (len != sizeof (*php))) { | |
return 0; | |
} | |
} | |
return fdt32_to_cpu (*php); | |
} | |
const char * | |
fdt_get_alias_namelen ( | |
const void *fdt, | |
const char *name, | |
int namelen | |
) | |
{ | |
int aliasoffset; | |
aliasoffset = fdt_path_offset (fdt, "/aliases"); | |
if (aliasoffset < 0) { | |
return NULL; | |
} | |
return fdt_getprop_namelen (fdt, aliasoffset, name, namelen, NULL); | |
} | |
const char * | |
fdt_get_alias ( | |
const void *fdt, | |
const char *name | |
) | |
{ | |
return fdt_get_alias_namelen (fdt, name, strlen (name)); | |
} | |
int | |
fdt_get_path ( | |
const void *fdt, | |
int nodeoffset, | |
char *buf, | |
int buflen | |
) | |
{ | |
int pdepth = 0, p = 0; | |
int offset, depth, namelen; | |
const char *name; | |
FDT_CHECK_HEADER (fdt); | |
if (buflen < 2) { | |
return -FDT_ERR_NOSPACE; | |
} | |
for (offset = 0, depth = 0; | |
(offset >= 0) && (offset <= nodeoffset); | |
offset = fdt_next_node (fdt, offset, &depth)) | |
{ | |
while (pdepth > depth) { | |
do { | |
p--; | |
} while (buf[p-1] != '/'); | |
pdepth--; | |
} | |
if (pdepth >= depth) { | |
name = fdt_get_name (fdt, offset, &namelen); | |
if (!name) { | |
return namelen; | |
} | |
if ((p + namelen + 1) <= buflen) { | |
memcpy (buf + p, name, namelen); | |
p += namelen; | |
buf[p++] = '/'; | |
pdepth++; | |
} | |
} | |
if (offset == nodeoffset) { | |
if (pdepth < (depth + 1)) { | |
return -FDT_ERR_NOSPACE; | |
} | |
if (p > 1) { | |
/* special case so that root path is "/", not "" */ | |
p--; | |
} | |
buf[p] = '\0'; | |
return 0; | |
} | |
} | |
if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) { | |
return -FDT_ERR_BADOFFSET; | |
} else if (offset == -FDT_ERR_BADOFFSET) { | |
return -FDT_ERR_BADSTRUCTURE; | |
} | |
return offset; /* error from fdt_next_node() */ | |
} | |
int | |
fdt_supernode_atdepth_offset ( | |
const void *fdt, | |
int nodeoffset, | |
int supernodedepth, | |
int *nodedepth | |
) | |
{ | |
int offset, depth; | |
int supernodeoffset = -FDT_ERR_INTERNAL; | |
FDT_CHECK_HEADER (fdt); | |
if (supernodedepth < 0) { | |
return -FDT_ERR_NOTFOUND; | |
} | |
for (offset = 0, depth = 0; | |
(offset >= 0) && (offset <= nodeoffset); | |
offset = fdt_next_node (fdt, offset, &depth)) | |
{ | |
if (depth == supernodedepth) { | |
supernodeoffset = offset; | |
} | |
if (offset == nodeoffset) { | |
if (nodedepth) { | |
*nodedepth = depth; | |
} | |
if (supernodedepth > depth) { | |
return -FDT_ERR_NOTFOUND; | |
} else { | |
return supernodeoffset; | |
} | |
} | |
} | |
if ((offset == -FDT_ERR_NOTFOUND) || (offset >= 0)) { | |
return -FDT_ERR_BADOFFSET; | |
} else if (offset == -FDT_ERR_BADOFFSET) { | |
return -FDT_ERR_BADSTRUCTURE; | |
} | |
return offset; /* error from fdt_next_node() */ | |
} | |
int | |
fdt_node_depth ( | |
const void *fdt, | |
int nodeoffset | |
) | |
{ | |
int nodedepth; | |
int err; | |
err = fdt_supernode_atdepth_offset (fdt, nodeoffset, 0, &nodedepth); | |
if (err) { | |
return (err < 0) ? err : -FDT_ERR_INTERNAL; | |
} | |
return nodedepth; | |
} | |
int | |
fdt_parent_offset ( | |
const void *fdt, | |
int nodeoffset | |
) | |
{ | |
int nodedepth = fdt_node_depth (fdt, nodeoffset); | |
if (nodedepth < 0) { | |
return nodedepth; | |
} | |
return fdt_supernode_atdepth_offset ( | |
fdt, | |
nodeoffset, | |
nodedepth - 1, | |
NULL | |
); | |
} | |
int | |
fdt_node_offset_by_prop_value ( | |
const void *fdt, | |
int startoffset, | |
const char *propname, | |
const void *propval, | |
int proplen | |
) | |
{ | |
int offset; | |
const void *val; | |
int len; | |
FDT_CHECK_HEADER (fdt); | |
/* FIXME: The algorithm here is pretty horrible: we scan each | |
* property of a node in fdt_getprop(), then if that didn't | |
* find what we want, we scan over them again making our way | |
* to the next node. Still it's the easiest to implement | |
* approach; performance can come later. */ | |
for (offset = fdt_next_node (fdt, startoffset, NULL); | |
offset >= 0; | |
offset = fdt_next_node (fdt, offset, NULL)) | |
{ | |
val = fdt_getprop (fdt, offset, propname, &len); | |
if ( val && (len == proplen) | |
&& (memcmp (val, propval, len) == 0)) | |
{ | |
return offset; | |
} | |
} | |
return offset; /* error from fdt_next_node() */ | |
} | |
int | |
fdt_node_offset_by_phandle ( | |
const void *fdt, | |
uint32_t phandle | |
) | |
{ | |
int offset; | |
if ((phandle == 0) || (phandle == -1)) { | |
return -FDT_ERR_BADPHANDLE; | |
} | |
FDT_CHECK_HEADER (fdt); | |
/* FIXME: The algorithm here is pretty horrible: we | |
* potentially scan each property of a node in | |
* fdt_get_phandle(), then if that didn't find what | |
* we want, we scan over them again making our way to the next | |
* node. Still it's the easiest to implement approach; | |
* performance can come later. */ | |
for (offset = fdt_next_node (fdt, -1, NULL); | |
offset >= 0; | |
offset = fdt_next_node (fdt, offset, NULL)) | |
{ | |
if (fdt_get_phandle (fdt, offset) == phandle) { | |
return offset; | |
} | |
} | |
return offset; /* error from fdt_next_node() */ | |
} | |
int | |
fdt_stringlist_contains ( | |
const char *strlist, | |
int listlen, | |
const char *str | |
) | |
{ | |
int len = strlen (str); | |
const char *p; | |
while (listlen >= len) { | |
if (memcmp (str, strlist, len+1) == 0) { | |
return 1; | |
} | |
p = memchr (strlist, '\0', listlen); | |
if (!p) { | |
return 0; /* malformed strlist.. */ | |
} | |
listlen -= (p-strlist) + 1; | |
strlist = p + 1; | |
} | |
return 0; | |
} | |
int | |
fdt_stringlist_count ( | |
const void *fdt, | |
int nodeoffset, | |
const char *property | |
) | |
{ | |
const char *list, *end; | |
int length, count = 0; | |
list = fdt_getprop (fdt, nodeoffset, property, &length); | |
if (!list) { | |
return length; | |
} | |
end = list + length; | |
while (list < end) { | |
length = strnlen (list, end - list) + 1; | |
/* Abort if the last string isn't properly NUL-terminated. */ | |
if (list + length > end) { | |
return -FDT_ERR_BADVALUE; | |
} | |
list += length; | |
count++; | |
} | |
return count; | |
} | |
int | |
fdt_stringlist_search ( | |
const void *fdt, | |
int nodeoffset, | |
const char *property, | |
const char *string | |
) | |
{ | |
int length, len, idx = 0; | |
const char *list, *end; | |
list = fdt_getprop (fdt, nodeoffset, property, &length); | |
if (!list) { | |
return length; | |
} | |
len = strlen (string) + 1; | |
end = list + length; | |
while (list < end) { | |
length = strnlen (list, end - list) + 1; | |
/* Abort if the last string isn't properly NUL-terminated. */ | |
if (list + length > end) { | |
return -FDT_ERR_BADVALUE; | |
} | |
if ((length == len) && (memcmp (list, string, length) == 0)) { | |
return idx; | |
} | |
list += length; | |
idx++; | |
} | |
return -FDT_ERR_NOTFOUND; | |
} | |
const char * | |
fdt_stringlist_get ( | |
const void *fdt, | |
int nodeoffset, | |
const char *property, | |
int idx, | |
int *lenp | |
) | |
{ | |
const char *list, *end; | |
int length; | |
list = fdt_getprop (fdt, nodeoffset, property, &length); | |
if (!list) { | |
if (lenp) { | |
*lenp = length; | |
} | |
return NULL; | |
} | |
end = list + length; | |
while (list < end) { | |
length = strnlen (list, end - list) + 1; | |
/* Abort if the last string isn't properly NUL-terminated. */ | |
if (list + length > end) { | |
if (lenp) { | |
*lenp = -FDT_ERR_BADVALUE; | |
} | |
return NULL; | |
} | |
if (idx == 0) { | |
if (lenp) { | |
*lenp = length - 1; | |
} | |
return list; | |
} | |
list += length; | |
idx--; | |
} | |
if (lenp) { | |
*lenp = -FDT_ERR_NOTFOUND; | |
} | |
return NULL; | |
} | |
int | |
fdt_node_check_compatible ( | |
const void *fdt, | |
int nodeoffset, | |
const char *compatible | |
) | |
{ | |
const void *prop; | |
int len; | |
prop = fdt_getprop (fdt, nodeoffset, "compatible", &len); | |
if (!prop) { | |
return len; | |
} | |
return !fdt_stringlist_contains (prop, len, compatible); | |
} | |
int | |
fdt_node_offset_by_compatible ( | |
const void *fdt, | |
int startoffset, | |
const char *compatible | |
) | |
{ | |
int offset, err; | |
FDT_CHECK_HEADER (fdt); | |
/* FIXME: The algorithm here is pretty horrible: we scan each | |
* property of a node in fdt_node_check_compatible(), then if | |
* that didn't find what we want, we scan over them again | |
* making our way to the next node. Still it's the easiest to | |
* implement approach; performance can come later. */ | |
for (offset = fdt_next_node (fdt, startoffset, NULL); | |
offset >= 0; | |
offset = fdt_next_node (fdt, offset, NULL)) | |
{ | |
err = fdt_node_check_compatible (fdt, offset, compatible); | |
if ((err < 0) && (err != -FDT_ERR_NOTFOUND)) { | |
return err; | |
} else if (err == 0) { | |
return offset; | |
} | |
} | |
return offset; /* error from fdt_next_node() */ | |
} |