#include <sys/types.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <dev/ofw/fdt.h>
#include <dev/ofw/openfirm.h>
unsigned int fdt_check_head(void *);
char *fdt_get_str(u_int32_t);
void *skip_property(u_int32_t *);
void *skip_props(u_int32_t *);
void *skip_node_name(u_int32_t *);
void *skip_node(void *);
void *skip_nops(u_int32_t *);
void *fdt_parent_node_recurse(void *, void *);
void *fdt_find_phandle_recurse(void *, uint32_t);
int fdt_node_property_int(void *, char *, int *);
int fdt_node_property_ints(void *, char *, int *, int);
int fdt_translate_reg(void *, struct fdt_reg *);
#ifdef DEBUG
void fdt_print_node_recurse(void *, int);
#endif
static int tree_inited = 0;
static struct fdt tree;
unsigned int
fdt_check_head(void *fdt)
{
struct fdt_head *fh;
u_int32_t *ptr, *tok;
fh = fdt;
ptr = (u_int32_t *)fdt;
if (betoh32(fh->fh_magic) != FDT_MAGIC)
return 0;
if (betoh32(fh->fh_version) > FDT_CODE_VERSION)
return 0;
tok = skip_nops(ptr + (betoh32(fh->fh_struct_off) / 4));
if (betoh32(*tok) != FDT_NODE_BEGIN)
return 0;
if ((betoh32(fh->fh_version) >= 17) &&
(betoh32(*(ptr + (betoh32(fh->fh_struct_off) / 4) +
(betoh32(fh->fh_struct_size) / 4) - 1)) != FDT_END))
return 0;
return betoh32(fh->fh_version);
}
int
fdt_init(void *fdt)
{
int version;
bzero(&tree, sizeof(struct fdt));
tree_inited = 0;
if (!fdt)
return 0;
if (!(version = fdt_check_head(fdt)))
return 0;
tree.header = (struct fdt_head *)fdt;
tree.tree = (char *)fdt + betoh32(tree.header->fh_struct_off);
tree.strings = (char *)fdt + betoh32(tree.header->fh_strings_off);
tree.memory = (char *)fdt + betoh32(tree.header->fh_reserve_off);
tree.end = (char *)fdt + betoh32(tree.header->fh_size);
tree.version = version;
tree.strings_size = betoh32(tree.header->fh_strings_size);
if (tree.version >= 17)
tree.struct_size = betoh32(tree.header->fh_struct_size);
tree_inited = 1;
return version;
}
void
fdt_finalize(void)
{
char *start = (char *)tree.header;
tree.header->fh_size = htobe32(tree.end - start);
tree.header->fh_struct_off = htobe32(tree.tree - start);
tree.header->fh_strings_off = htobe32(tree.strings - start);
tree.header->fh_reserve_off = htobe32(tree.memory - start);
tree.header->fh_strings_size = htobe32(tree.strings_size);
if (tree.version >= 17)
tree.header->fh_struct_size = htobe32(tree.struct_size);
}
size_t
fdt_get_size(void *fdt)
{
if (!fdt)
return 0;
if (!fdt_check_head(fdt))
return 0;
return betoh32(((struct fdt_head *)fdt)->fh_size);
}
char *
fdt_get_str(u_int32_t num)
{
if (num > tree.strings_size)
return NULL;
return (tree.strings) ? (tree.strings + num) : NULL;
}
int
fdt_add_str(char *name)
{
size_t len = roundup(strlen(name) + 1, sizeof(uint32_t));
char *end = tree.strings + tree.strings_size;
memmove(end + len, end, tree.end - end);
tree.strings_size += len;
if (tree.tree > tree.strings)
tree.tree += len;
if (tree.memory > tree.strings)
tree.memory += len;
tree.end += len;
memset(end, 0, len);
memcpy(end, name, strlen(name));
return (end - tree.strings);
}
void *
skip_nops(u_int32_t *ptr)
{
while (betoh32(*ptr) == FDT_NOP)
ptr++;
return ptr;
}
void *
skip_property(u_int32_t *ptr)
{
u_int32_t size;
size = betoh32(*(ptr + 1));
ptr += 3 + roundup(size, sizeof(u_int32_t)) / sizeof(u_int32_t);
return skip_nops(ptr);
}
void *
skip_props(u_int32_t *ptr)
{
while (betoh32(*ptr) == FDT_PROPERTY) {
ptr = skip_property(ptr);
}
return ptr;
}
void *
skip_node_name(u_int32_t *ptr)
{
ptr += roundup(strlen((char *)ptr) + 1,
sizeof(u_int32_t)) / sizeof(u_int32_t);
return skip_nops(ptr);
}
int
fdt_node_property(void *node, char *name, char **out)
{
u_int32_t *ptr;
u_int32_t nameid;
char *tmp;
if (!tree_inited)
return -1;
ptr = (u_int32_t *)node;
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return -1;
ptr = skip_node_name(ptr + 1);
while (betoh32(*ptr) == FDT_PROPERTY) {
nameid = betoh32(*(ptr + 2));
tmp = fdt_get_str(nameid);
if (!strcmp(name, tmp)) {
*out = (char *)(ptr + 3);
return betoh32(*(ptr + 1));
}
ptr = skip_property(ptr);
}
return -1;
}
int
fdt_node_set_property(void *node, char *name, void *data, int len)
{
uint32_t *ptr, *next;
uint32_t nameid;
uint32_t curlen;
size_t delta;
char *tmp;
if (!tree_inited)
return 0;
ptr = (uint32_t *)node;
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return 0;
ptr = skip_node_name(ptr + 1);
while (betoh32(*ptr) == FDT_PROPERTY) {
nameid = betoh32(*(ptr + 2));
tmp = fdt_get_str(nameid);
next = skip_property(ptr);
if (!strcmp(name, tmp)) {
curlen = betoh32(*(ptr + 1));
delta = roundup(len, sizeof(uint32_t)) -
roundup(curlen, sizeof(uint32_t));
memmove((char *)next + delta, next,
tree.end - (char *)next);
tree.struct_size += delta;
if (tree.strings > tree.tree)
tree.strings += delta;
if (tree.memory > tree.tree)
tree.memory += delta;
tree.end += delta;
*(ptr + 1) = htobe32(len);
memcpy(ptr + 3, data, len);
return 1;
}
ptr = next;
}
return 0;
}
int
fdt_node_add_property(void *node, char *name, void *data, int len)
{
char *dummy;
if (!tree_inited)
return 0;
if (fdt_node_property(node, name, &dummy) == -1) {
uint32_t *ptr = (uint32_t *)node;
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return 0;
ptr = skip_node_name(ptr + 1);
memmove(ptr + 3, ptr, tree.end - (char *)ptr);
tree.struct_size += 3 * sizeof(uint32_t);
if (tree.strings > tree.tree)
tree.strings += 3 * sizeof(uint32_t);
if (tree.memory > tree.tree)
tree.memory += 3 * sizeof(uint32_t);
tree.end += 3 * sizeof(uint32_t);
*ptr++ = htobe32(FDT_PROPERTY);
*ptr++ = htobe32(0);
*ptr++ = htobe32(fdt_add_str(name));
}
return fdt_node_set_property(node, name, data, len);
}
void *
skip_node(void *node)
{
u_int32_t *ptr = node;
ptr++;
ptr = skip_node_name(ptr);
ptr = skip_props(ptr);
while (betoh32(*ptr) == FDT_NODE_BEGIN)
ptr = skip_node(ptr);
return skip_nops(ptr + 1);
}
void *
fdt_next_node(void *node)
{
u_int32_t *ptr;
if (!tree_inited)
return NULL;
ptr = node;
if (node == NULL) {
ptr = skip_nops((uint32_t *)tree.tree);
return (betoh32(*ptr) == FDT_NODE_BEGIN) ? ptr : NULL;
}
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return NULL;
ptr++;
ptr = skip_node_name(ptr);
ptr = skip_props(ptr);
while (betoh32(*ptr) == FDT_NODE_BEGIN)
ptr = skip_node(ptr);
if (betoh32(*ptr) != FDT_NODE_END)
return NULL;
ptr = skip_nops(ptr + 1);
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return NULL;
return ptr;
}
int
fdt_next_property(void *node, char *name, char **nextname)
{
u_int32_t *ptr;
u_int32_t nameid;
if (!tree_inited)
return 0;
ptr = (u_int32_t *)node;
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return 0;
ptr = skip_node_name(ptr + 1);
while (betoh32(*ptr) == FDT_PROPERTY) {
nameid = betoh32(*(ptr + 2));
if (strcmp(name, "") == 0) {
*nextname = fdt_get_str(nameid);
return 1;
}
if (strcmp(name, fdt_get_str(nameid)) == 0) {
ptr = skip_property(ptr);
if (betoh32(*ptr) != FDT_PROPERTY)
break;
nameid = betoh32(*(ptr + 2));
*nextname = fdt_get_str(nameid);
return 1;
}
ptr = skip_property(ptr);
}
*nextname = "";
return 1;
}
int
fdt_node_property_ints(void *node, char *name, int *out, int outlen)
{
int *data;
int i, inlen;
inlen = fdt_node_property(node, name, (char **)&data) / sizeof(int);
if (inlen <= 0)
return -1;
for (i = 0; i < inlen && i < outlen; i++)
out[i] = betoh32(data[i]);
return i;
}
int
fdt_node_property_int(void *node, char *name, int *out)
{
return fdt_node_property_ints(node, name, out, 1);
}
void *
fdt_child_node(void *node)
{
u_int32_t *ptr;
if (!tree_inited)
return NULL;
ptr = node;
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return NULL;
ptr++;
ptr = skip_node_name(ptr);
ptr = skip_props(ptr);
return (betoh32(*ptr) == FDT_NODE_BEGIN) ? (ptr) : NULL;
}
char *
fdt_node_name(void *node)
{
u_int32_t *ptr;
if (!tree_inited)
return NULL;
ptr = node;
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return NULL;
return (char *)(ptr + 1);
}
void *
fdt_find_node(char *name)
{
void *node = fdt_next_node(0);
const char *p = name;
if (!tree_inited)
return NULL;
if (*p != '/')
return NULL;
while (*p) {
void *child;
const char *q;
const char *s;
while (*p == '/')
p++;
if (*p == 0)
return node;
q = strchr(p, '/');
if (q == NULL)
q = p + strlen(p);
for (child = fdt_child_node(node); child;
child = fdt_next_node(child)) {
s = fdt_node_name(child);
if (strncmp(p, s, q - p) == 0 && s[q - p] == '\0')
break;
}
if (child) {
node = child;
p = q;
continue;
}
for (child = fdt_child_node(node); child;
child = fdt_next_node(child)) {
s = fdt_node_name(child);
if (strncmp(p, s, q - p) == 0 && s[q - p] == '@')
break;
}
if (child) {
node = child;
p = q;
continue;
}
return NULL;
}
return node;
}
void *
fdt_parent_node_recurse(void *pnode, void *child)
{
void *node = fdt_child_node(pnode);
void *tmp;
while (node && (node != child)) {
if ((tmp = fdt_parent_node_recurse(node, child)))
return tmp;
node = fdt_next_node(node);
}
return (node) ? pnode : NULL;
}
void *
fdt_parent_node(void *node)
{
void *pnode = fdt_next_node(0);
if (!tree_inited)
return NULL;
if (node == pnode)
return NULL;
return fdt_parent_node_recurse(pnode, node);
}
void *
fdt_find_phandle_recurse(void *node, uint32_t phandle)
{
void *child;
char *data;
void *tmp;
int len;
len = fdt_node_property(node, "phandle", &data);
if (len < 0)
len = fdt_node_property(node, "linux,phandle", &data);
if (len == sizeof(uint32_t) && bemtoh32(data) == phandle)
return node;
for (child = fdt_child_node(node); child; child = fdt_next_node(child))
if ((tmp = fdt_find_phandle_recurse(child, phandle)))
return tmp;
return NULL;
}
void *
fdt_find_phandle(uint32_t phandle)
{
return fdt_find_phandle_recurse(fdt_next_node(0), phandle);
}
void
fdt_get_cells(void *node, int *ac, int *sc)
{
void *parent;
parent = fdt_parent_node(node);
if (parent == NULL)
*ac = *sc = 1;
else
fdt_get_cells(parent, ac, sc);
fdt_node_property_int(node, "#address-cells", ac);
fdt_node_property_int(node, "#size-cells", sc);
}
int
fdt_translate_reg(void *node, struct fdt_reg *reg)
{
void *parent;
int pac, psc, ac, sc, rlen, rone, *range;
uint64_t from, to, size;
parent = fdt_parent_node(node);
if (parent == NULL)
return 0;
rlen = fdt_node_property(node, "ranges", (char **)&range) / sizeof(int);
if (range == NULL)
return 0;
if (rlen <= 0)
return fdt_translate_reg(parent, reg);
fdt_get_cells(parent, &pac, &psc);
if (pac <= 0 || pac > 2 || psc <= 0 || psc > 2)
return EINVAL;
fdt_get_cells(node, &ac, &sc);
if (ac <= 0 || ac > 2 || sc <= 0 || sc > 2)
return EINVAL;
rone = pac + ac + sc;
if (rlen < rone)
return ESRCH;
for (; rlen >= rone; rlen -= rone, range += rone) {
from = betoh32(range[0]);
if (ac == 2)
from = (from << 32) + betoh32(range[1]);
size = betoh32(range[ac + pac]);
if (sc == 2)
size = (size << 32) + betoh32(range[ac + pac + 1]);
if (reg->addr < from || (reg->addr + reg->size) > (from + size))
continue;
to = betoh32(range[ac]);
if (pac == 2)
to = (to << 32) + betoh32(range[ac + 1]);
reg->addr -= from;
reg->addr += to;
return fdt_translate_reg(parent, reg);
}
return ESRCH;
}
int
fdt_get_reg(void *node, int idx, struct fdt_reg *reg)
{
void *parent;
int ac, sc, off, *in, inlen;
if (node == NULL || reg == NULL)
return EINVAL;
parent = fdt_parent_node(node);
if (parent == NULL)
return EINVAL;
fdt_get_cells(parent, &ac, &sc);
if (ac <= 0 || ac > 2 || sc <= 0 || sc > 2)
return EINVAL;
inlen = fdt_node_property(node, "reg", (char **)&in) / sizeof(int);
if (inlen < ((idx + 1) * (ac + sc)))
return EINVAL;
off = idx * (ac + sc);
reg->addr = betoh32(in[off]);
if (ac == 2)
reg->addr = (reg->addr << 32) + betoh32(in[off + 1]);
reg->size = betoh32(in[off + ac]);
if (sc == 2)
reg->size = (reg->size << 32) + betoh32(in[off + ac + 1]);
return fdt_translate_reg(parent, reg);
}
int
fdt_is_compatible(void *node, const char *name)
{
char *data;
int len;
len = fdt_node_property(node, "compatible", &data);
while (len > 0) {
if (strcmp(data, name) == 0)
return 1;
len -= strlen(data) + 1;
data += strlen(data) + 1;
}
return 0;
}
#ifdef DEBUG
void *
fdt_print_property(void *node, int level)
{
u_int32_t *ptr;
char *tmp, *value;
int cnt;
u_int32_t nameid, size;
ptr = (u_int32_t *)node;
if (!tree_inited)
return NULL;
if (betoh32(*ptr) != FDT_PROPERTY)
return ptr;
size = betoh32(*++ptr);
nameid = betoh32(*++ptr);
for (cnt = 0; cnt < level; cnt++)
printf("\t");
tmp = fdt_get_str(nameid);
printf("\t%s : ", tmp ? tmp : "NO_NAME");
ptr++;
value = (char *)ptr;
if (!strcmp(tmp, "device_type") || !strcmp(tmp, "compatible") ||
!strcmp(tmp, "model") || !strcmp(tmp, "bootargs") ||
!strcmp(tmp, "linux,stdout-path")) {
printf("%s", value);
} else if (!strcmp(tmp, "clock-frequency") ||
!strcmp(tmp, "timebase-frequency")) {
printf("%d", betoh32(*((unsigned int *)value)));
} else {
for (cnt = 0; cnt < size; cnt++) {
if ((cnt % sizeof(u_int32_t)) == 0)
printf(" ");
printf("%02x", value[cnt]);
}
}
ptr += roundup(size, sizeof(u_int32_t)) / sizeof(u_int32_t);
printf("\n");
return ptr;
}
void
fdt_print_node(void *node, int level)
{
u_int32_t *ptr;
int cnt;
ptr = (u_int32_t *)node;
if (betoh32(*ptr) != FDT_NODE_BEGIN)
return;
ptr++;
for (cnt = 0; cnt < level; cnt++)
printf("\t");
printf("%s :\n", fdt_node_name(node));
ptr = skip_node_name(ptr);
while (betoh32(*ptr) == FDT_PROPERTY)
ptr = fdt_print_property(ptr, level);
}
void
fdt_print_node_recurse(void *node, int level)
{
void *child;
fdt_print_node(node, level);
for (child = fdt_child_node(node); child; child = fdt_next_node(child))
fdt_print_node_recurse(child, level + 1);
}
void
fdt_print_tree(void)
{
fdt_print_node_recurse(fdt_next_node(0), 0);
}
#endif
int
OF_peer(int handle)
{
void *node = (char *)tree.header + handle;
if (handle == 0)
node = fdt_find_node("/");
else
node = fdt_next_node(node);
return node ? ((char *)node - (char *)tree.header) : 0;
}
int
OF_child(int handle)
{
void *node = (char *)tree.header + handle;
node = fdt_child_node(node);
return node ? ((char *)node - (char *)tree.header) : 0;
}
int
OF_parent(int handle)
{
void *node = (char *)tree.header + handle;
node = fdt_parent_node(node);
return node ? ((char *)node - (char *)tree.header) : 0;
}
int
OF_finddevice(char *name)
{
void *node;
node = fdt_find_node(name);
return node ? ((char *)node - (char *)tree.header) : -1;
}
int
OF_getnodebyname(int handle, const char *name)
{
void *node = (char *)tree.header + handle;
void *child;
char *data;
int len;
if (handle == 0)
node = fdt_find_node("/");
for (child = fdt_child_node(node); child;
child = fdt_next_node(child)) {
if (strcmp(name, fdt_node_name(child)) == 0)
break;
}
if (child)
return (char *)child - (char *)tree.header;
len = strlen(name);
for (child = fdt_child_node(node); child;
child = fdt_next_node(child)) {
data = fdt_node_name(child);
if (strncmp(name, data, len) == 0 &&
strlen(data) > len && data[len] == '@')
break;
}
if (child)
return (char *)child - (char *)tree.header;
return 0;
}
int
OF_getnodebyphandle(uint32_t phandle)
{
void *node;
node = fdt_find_phandle(phandle);
return node ? ((char *)node - (char *)tree.header) : 0;
}
int
OF_getproplen(int handle, char *prop)
{
void *node = (char *)tree.header + handle;
char *data, *name;
int len;
len = fdt_node_property(node, prop, &data);
if (len < 0 && strcmp(prop, "name") == 0) {
name = fdt_node_name(node);
data = strchr(name, '@');
if (data)
len = data - name;
else
len = strlen(name);
return len + 1;
}
return len;
}
int
OF_getprop(int handle, char *prop, void *buf, int buflen)
{
void *node = (char *)tree.header + handle;
char *data;
int len;
len = fdt_node_property(node, prop, &data);
if (len < 0 && strcmp(prop, "name") == 0) {
data = fdt_node_name(node);
if (data) {
len = strlcpy(buf, data, buflen);
data = strchr(buf, '@');
if (data) {
*data = 0;
len = data - (char *)buf;
}
return len + 1;
}
}
if (len > 0)
memcpy(buf, data, min(len, buflen));
return len;
}
int
OF_getpropbool(int handle, char *prop)
{
void *node = (char *)tree.header + handle;
char *data;
return (fdt_node_property(node, prop, &data) >= 0);
}
uint32_t
OF_getpropint(int handle, char *prop, uint32_t defval)
{
uint32_t val;
int len;
len = OF_getprop(handle, prop, &val, sizeof(val));
if (len != sizeof(val))
return defval;
return betoh32(val);
}
int
OF_getpropintarray(int handle, char *prop, uint32_t *buf, int buflen)
{
int len;
int i;
len = OF_getprop(handle, prop, buf, buflen);
if (len < 0 || (len % sizeof(uint32_t)))
return -1;
for (i = 0; i < min(len, buflen) / sizeof(uint32_t); i++)
buf[i] = betoh32(buf[i]);
return len;
}
uint64_t
OF_getpropint64(int handle, char *prop, uint64_t defval)
{
uint64_t val;
int len;
len = OF_getprop(handle, prop, &val, sizeof(val));
if (len != sizeof(val))
return defval;
return betoh64(val);
}
int
OF_getpropint64array(int handle, char *prop, uint64_t *buf, int buflen)
{
int len;
int i;
len = OF_getprop(handle, prop, buf, buflen);
if (len < 0 || (len % sizeof(uint64_t)))
return -1;
for (i = 0; i < min(len, buflen) / sizeof(uint64_t); i++)
buf[i] = betoh64(buf[i]);
return len;
}
int
OF_nextprop(int handle, char *prop, void *nextprop)
{
void *node = (char *)tree.header + handle;
char *data;
if (fdt_node_property(node, "name", &data) == -1) {
if (strcmp(prop, "") == 0)
return strlcpy(nextprop, "name", OFMAXPARAM);
if (strcmp(prop, "name") == 0)
prop = "";
}
if (fdt_next_property(node, prop, &data))
return strlcpy(nextprop, data, OFMAXPARAM);
return -1;
}
int
OF_is_compatible(int handle, const char *name)
{
void *node = (char *)tree.header + handle;
return (fdt_is_compatible(node, name));
}
int
OF_is_enabled(int handle)
{
char status[32];
if (OF_getprop(handle, "status", status, sizeof(status)) > 0) {
if (strcmp(status, "disabled") == 0)
return 0;
if (strcmp(status, "reserved") == 0)
return 0;
}
return 1;
}
int
OF_getindex(int handle, const char *entry, const char *prop)
{
char *names;
char *name;
char *end;
int idx = 0;
int len;
if (entry == NULL)
return 0;
len = OF_getproplen(handle, (char *)prop);
if (len <= 0)
return -1;
names = malloc(len, M_TEMP, M_WAITOK);
OF_getprop(handle, (char *)prop, names, len);
end = names + len;
name = names;
while (name < end) {
if (strcmp(name, entry) == 0) {
free(names, M_TEMP, len);
return idx;
}
name += strlen(name) + 1;
idx++;
}
free(names, M_TEMP, len);
return -1;
}