1151 lines
25 KiB
C
1151 lines
25 KiB
C
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
|
|
/*
|
|
* Manipulate the device tree
|
|
*
|
|
* Copyright 2013-2019 IBM Corp.
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
#include <device.h>
|
|
#include <stdlib.h>
|
|
#include <skiboot.h>
|
|
#include <libfdt/libfdt.h>
|
|
#include <libfdt/libfdt_internal.h>
|
|
#include <ccan/str/str.h>
|
|
#include <ccan/endian/endian.h>
|
|
#include <inttypes.h>
|
|
|
|
/* Used to give unique handles. */
|
|
u32 last_phandle = 0;
|
|
|
|
struct dt_node *dt_root;
|
|
struct dt_node *dt_chosen;
|
|
|
|
static const char *take_name(const char *name)
|
|
{
|
|
if (!is_rodata(name) && !(name = strdup(name))) {
|
|
prerror("Failed to allocate copy of name");
|
|
abort();
|
|
}
|
|
return name;
|
|
}
|
|
|
|
static void free_name(const char *name)
|
|
{
|
|
if (!is_rodata(name))
|
|
free((char *)name);
|
|
}
|
|
|
|
static struct dt_node *new_node(const char *name)
|
|
{
|
|
struct dt_node *node = malloc(sizeof *node);
|
|
if (!node) {
|
|
prerror("Failed to allocate node\n");
|
|
abort();
|
|
}
|
|
|
|
node->name = take_name(name);
|
|
node->parent = NULL;
|
|
list_head_init(&node->properties);
|
|
list_head_init(&node->children);
|
|
/* FIXME: locking? */
|
|
node->phandle = new_phandle();
|
|
return node;
|
|
}
|
|
|
|
struct dt_node *dt_new_root(const char *name)
|
|
{
|
|
return new_node(name);
|
|
}
|
|
|
|
static const char *get_unitname(const struct dt_node *node)
|
|
{
|
|
const char *c = strchr(node->name, '@');
|
|
|
|
if (!c)
|
|
return NULL;
|
|
|
|
return c + 1;
|
|
}
|
|
|
|
int dt_cmp_subnodes(const struct dt_node *a, const struct dt_node *b)
|
|
{
|
|
const char *a_unit = get_unitname(a);
|
|
const char *b_unit = get_unitname(b);
|
|
|
|
ptrdiff_t basenamelen = a_unit - a->name;
|
|
|
|
/* sort hex unit addresses by number */
|
|
if (a_unit && b_unit && !strncmp(a->name, b->name, basenamelen)) {
|
|
unsigned long long a_num, b_num;
|
|
char *a_end, *b_end;
|
|
|
|
a_num = strtoul(a_unit, &a_end, 16);
|
|
b_num = strtoul(b_unit, &b_end, 16);
|
|
|
|
/* only compare if the unit addr parsed correctly */
|
|
if (*a_end == 0 && *b_end == 0)
|
|
return (a_num > b_num) - (a_num < b_num);
|
|
}
|
|
|
|
return strcmp(a->name, b->name);
|
|
}
|
|
|
|
bool dt_attach_root(struct dt_node *parent, struct dt_node *root)
|
|
{
|
|
struct dt_node *node;
|
|
|
|
assert(!root->parent);
|
|
|
|
if (list_empty(&parent->children)) {
|
|
list_add(&parent->children, &root->list);
|
|
root->parent = parent;
|
|
|
|
return true;
|
|
}
|
|
|
|
dt_for_each_child(parent, node) {
|
|
int cmp = dt_cmp_subnodes(node, root);
|
|
|
|
/* Look for duplicates */
|
|
if (cmp == 0) {
|
|
prerror("DT: %s failed, duplicate %s\n",
|
|
__func__, root->name);
|
|
return false;
|
|
}
|
|
|
|
/* insert before the first node that's larger
|
|
* the the node we're inserting */
|
|
if (cmp > 0)
|
|
break;
|
|
}
|
|
|
|
list_add_before(&parent->children, &node->list, &root->list);
|
|
root->parent = parent;
|
|
|
|
return true;
|
|
}
|
|
|
|
static inline void dt_destroy(struct dt_node *dn)
|
|
{
|
|
if (!dn)
|
|
return;
|
|
|
|
free_name(dn->name);
|
|
free(dn);
|
|
}
|
|
|
|
struct dt_node *dt_new(struct dt_node *parent, const char *name)
|
|
{
|
|
struct dt_node *new;
|
|
assert(parent);
|
|
|
|
new = new_node(name);
|
|
if (!dt_attach_root(parent, new)) {
|
|
dt_destroy(new);
|
|
return NULL;
|
|
}
|
|
return new;
|
|
}
|
|
|
|
/*
|
|
* low level variant, we export this because there are "weird" address
|
|
* formats, such as LPC/ISA bus addresses which have a letter to identify
|
|
* which bus space the address is inside of.
|
|
*/
|
|
struct dt_node *__dt_find_by_name_addr(struct dt_node *parent, const char *name,
|
|
const char *addr)
|
|
{
|
|
struct dt_node *node;
|
|
|
|
if (list_empty(&parent->children))
|
|
return NULL;
|
|
|
|
dt_for_each_child(parent, node) {
|
|
const char *unit = get_unitname(node);
|
|
int len;
|
|
|
|
if (!unit)
|
|
continue;
|
|
|
|
/* match the name */
|
|
len = (int) (unit - node->name) - 1;
|
|
if (strncmp(node->name, name, len))
|
|
continue;
|
|
|
|
/* match the unit */
|
|
if (strcmp(unit, addr) == 0)
|
|
return node;
|
|
}
|
|
|
|
dt_for_each_child(parent, node) {
|
|
struct dt_node *ret = __dt_find_by_name_addr(node, name, addr);
|
|
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct dt_node *dt_find_by_name_addr(struct dt_node *parent, const char *name,
|
|
uint64_t addr)
|
|
{
|
|
char addr_str[16 + 1]; /* max size of a 64bit int */
|
|
snprintf(addr_str, sizeof(addr_str), "%" PRIx64, addr);
|
|
|
|
return __dt_find_by_name_addr(parent, name, addr_str);
|
|
}
|
|
|
|
struct dt_node *dt_new_addr(struct dt_node *parent, const char *name,
|
|
uint64_t addr)
|
|
{
|
|
char *lname;
|
|
struct dt_node *new;
|
|
size_t len;
|
|
|
|
assert(parent);
|
|
len = strlen(name) + STR_MAX_CHARS(addr) + 2;
|
|
lname = malloc(len);
|
|
if (!lname)
|
|
return NULL;
|
|
snprintf(lname, len, "%s@%llx", name, (long long)addr);
|
|
new = new_node(lname);
|
|
free(lname);
|
|
if (!dt_attach_root(parent, new)) {
|
|
dt_destroy(new);
|
|
return NULL;
|
|
}
|
|
return new;
|
|
}
|
|
|
|
struct dt_node *dt_new_2addr(struct dt_node *parent, const char *name,
|
|
uint64_t addr0, uint64_t addr1)
|
|
{
|
|
char *lname;
|
|
struct dt_node *new;
|
|
size_t len;
|
|
assert(parent);
|
|
|
|
len = strlen(name) + 2*STR_MAX_CHARS(addr0) + 3;
|
|
lname = malloc(len);
|
|
if (!lname)
|
|
return NULL;
|
|
snprintf(lname, len, "%s@%llx,%llx",
|
|
name, (long long)addr0, (long long)addr1);
|
|
new = new_node(lname);
|
|
free(lname);
|
|
if (!dt_attach_root(parent, new)) {
|
|
dt_destroy(new);
|
|
return NULL;
|
|
}
|
|
return new;
|
|
}
|
|
|
|
static struct dt_node *__dt_copy(struct dt_node *node, struct dt_node *parent,
|
|
bool root)
|
|
{
|
|
struct dt_property *prop, *new_prop;
|
|
struct dt_node *new_node, *child;
|
|
|
|
new_node = dt_new(parent, node->name);
|
|
if (!new_node)
|
|
return NULL;
|
|
|
|
list_for_each(&node->properties, prop, list) {
|
|
new_prop = dt_add_property(new_node, prop->name, prop->prop,
|
|
prop->len);
|
|
if (!new_prop)
|
|
goto fail;
|
|
}
|
|
|
|
list_for_each(&node->children, child, list) {
|
|
child = __dt_copy(child, new_node, false);
|
|
if (!child)
|
|
goto fail;
|
|
}
|
|
|
|
return new_node;
|
|
|
|
fail:
|
|
/* dt_free will recurse for us, so only free when we unwind to the
|
|
* top-level failure */
|
|
if (root)
|
|
dt_free(new_node);
|
|
return NULL;
|
|
}
|
|
|
|
struct dt_node *dt_copy(struct dt_node *node, struct dt_node *parent)
|
|
{
|
|
return __dt_copy(node, parent, true);
|
|
}
|
|
|
|
char *dt_get_path(const struct dt_node *node)
|
|
{
|
|
unsigned int len = 0;
|
|
const struct dt_node *n;
|
|
char *path, *p;
|
|
|
|
/* Dealing with NULL is for test/debug purposes */
|
|
if (!node)
|
|
return strdup("<NULL>");
|
|
|
|
for (n = node; n; n = n->parent) {
|
|
len += strlen(n->name);
|
|
if (n->parent || n == node)
|
|
len++;
|
|
}
|
|
path = zalloc(len + 1);
|
|
assert(path);
|
|
p = path + len;
|
|
for (n = node; n; n = n->parent) {
|
|
len = strlen(n->name);
|
|
p -= len;
|
|
memcpy(p, n->name, len);
|
|
if (n->parent || n == node)
|
|
*(--p) = '/';
|
|
}
|
|
assert(p == path);
|
|
|
|
return p;
|
|
}
|
|
|
|
static const char *__dt_path_split(const char *p,
|
|
const char **namep, unsigned int *namel,
|
|
const char **addrp, unsigned int *addrl)
|
|
{
|
|
const char *at, *sl;
|
|
|
|
*namel = *addrl = 0;
|
|
|
|
/* Skip initial '/' */
|
|
while (*p == '/')
|
|
p++;
|
|
|
|
/* Check empty path */
|
|
if (*p == 0)
|
|
return p;
|
|
|
|
at = strchr(p, '@');
|
|
sl = strchr(p, '/');
|
|
if (sl == NULL)
|
|
sl = p + strlen(p);
|
|
if (sl < at)
|
|
at = NULL;
|
|
if (at) {
|
|
*addrp = at + 1;
|
|
*addrl = sl - at - 1;
|
|
}
|
|
*namep = p;
|
|
*namel = at ? (at - p) : (sl - p);
|
|
|
|
return sl;
|
|
}
|
|
|
|
struct dt_node *dt_find_by_path(struct dt_node *root, const char *path)
|
|
{
|
|
struct dt_node *n;
|
|
const char *pn, *pa, *p = path, *nn, *na;
|
|
unsigned int pnl, pal, nnl, nal;
|
|
bool match;
|
|
|
|
/* Walk path components */
|
|
while (*p) {
|
|
/* Extract next path component */
|
|
p = __dt_path_split(p, &pn, &pnl, &pa, &pal);
|
|
if (pnl == 0 && pal == 0)
|
|
break;
|
|
|
|
/* Compare with each child node */
|
|
match = false;
|
|
list_for_each(&root->children, n, list) {
|
|
match = true;
|
|
__dt_path_split(n->name, &nn, &nnl, &na, &nal);
|
|
if (pnl && (pnl != nnl || strncmp(pn, nn, pnl)))
|
|
match = false;
|
|
if (pal && (pal != nal || strncmp(pa, na, pal)))
|
|
match = false;
|
|
if (match) {
|
|
root = n;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* No child match */
|
|
if (!match)
|
|
return NULL;
|
|
}
|
|
return root;
|
|
}
|
|
|
|
struct dt_node *dt_find_by_name(struct dt_node *root, const char *name)
|
|
{
|
|
struct dt_node *child, *match;
|
|
|
|
list_for_each(&root->children, child, list) {
|
|
if (!strcmp(child->name, name))
|
|
return child;
|
|
|
|
match = dt_find_by_name(child, name);
|
|
if (match)
|
|
return match;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct dt_node *dt_find_by_name_before_addr(struct dt_node *root, const char *name)
|
|
{
|
|
struct dt_node *child, *match;
|
|
char *child_name;
|
|
|
|
list_for_each(&root->children, child, list) {
|
|
child_name = strdup(child->name);
|
|
if (!child_name)
|
|
return NULL;
|
|
|
|
child_name = strtok(child_name, "@");
|
|
if (!strcmp(child_name, name))
|
|
match = child;
|
|
else
|
|
match = dt_find_by_name_before_addr(child, name);
|
|
|
|
free(child_name);
|
|
if (match)
|
|
return match;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct dt_node *dt_new_check(struct dt_node *parent, const char *name)
|
|
{
|
|
struct dt_node *node = dt_find_by_name(parent, name);
|
|
|
|
if (!node) {
|
|
node = dt_new(parent, name);
|
|
assert(node);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
struct dt_node *dt_find_by_phandle(struct dt_node *root, u32 phandle)
|
|
{
|
|
struct dt_node *node;
|
|
|
|
dt_for_each_node(root, node)
|
|
if (node->phandle == phandle)
|
|
return node;
|
|
return NULL;
|
|
}
|
|
|
|
static struct dt_property *new_property(struct dt_node *node,
|
|
const char *name, size_t size)
|
|
{
|
|
struct dt_property *p = malloc(sizeof(*p) + size);
|
|
char *path;
|
|
|
|
if (!p) {
|
|
path = dt_get_path(node);
|
|
prerror("Failed to allocate property \"%s\" for %s of %zu bytes\n",
|
|
name, path, size);
|
|
free(path);
|
|
abort();
|
|
}
|
|
if (dt_find_property(node, name)) {
|
|
path = dt_get_path(node);
|
|
prerror("Duplicate property \"%s\" in node %s\n",
|
|
name, path);
|
|
free(path);
|
|
abort();
|
|
|
|
}
|
|
|
|
p->name = take_name(name);
|
|
p->len = size;
|
|
list_add_tail(&node->properties, &p->list);
|
|
return p;
|
|
}
|
|
|
|
struct dt_property *dt_add_property(struct dt_node *node,
|
|
const char *name,
|
|
const void *val, size_t size)
|
|
{
|
|
struct dt_property *p;
|
|
|
|
/*
|
|
* Filter out phandle properties, we re-generate them
|
|
* when flattening
|
|
*/
|
|
if (strcmp(name, "linux,phandle") == 0 ||
|
|
strcmp(name, "phandle") == 0) {
|
|
assert(size == 4);
|
|
node->phandle = *(const u32 *)val;
|
|
if (node->phandle >= last_phandle)
|
|
set_last_phandle(node->phandle);
|
|
return NULL;
|
|
}
|
|
|
|
p = new_property(node, name, size);
|
|
if (size)
|
|
memcpy(p->prop, val, size);
|
|
return p;
|
|
}
|
|
|
|
void dt_resize_property(struct dt_property **prop, size_t len)
|
|
{
|
|
size_t new_len = sizeof(**prop) + len;
|
|
|
|
*prop = realloc(*prop, new_len);
|
|
(*prop)->len = len;
|
|
|
|
/* Fix up linked lists in case we moved. (note: not an empty list). */
|
|
(*prop)->list.next->prev = &(*prop)->list;
|
|
(*prop)->list.prev->next = &(*prop)->list;
|
|
}
|
|
|
|
struct dt_property *dt_add_property_string(struct dt_node *node,
|
|
const char *name,
|
|
const char *value)
|
|
{
|
|
size_t len = 0;
|
|
if (value)
|
|
len = strlen(value) + 1;
|
|
return dt_add_property(node, name, value, len);
|
|
}
|
|
|
|
struct dt_property *dt_add_property_nstr(struct dt_node *node,
|
|
const char *name,
|
|
const char *value, unsigned int vlen)
|
|
{
|
|
struct dt_property *p;
|
|
char *tmp = zalloc(vlen + 1);
|
|
|
|
if (!tmp)
|
|
return NULL;
|
|
|
|
strncpy(tmp, value, vlen);
|
|
p = dt_add_property(node, name, tmp, strlen(tmp)+1);
|
|
free(tmp);
|
|
|
|
return p;
|
|
}
|
|
|
|
struct dt_property *__dt_add_property_cells(struct dt_node *node,
|
|
const char *name,
|
|
int count, ...)
|
|
{
|
|
struct dt_property *p;
|
|
fdt32_t *val;
|
|
unsigned int i;
|
|
va_list args;
|
|
|
|
p = new_property(node, name, count * sizeof(u32));
|
|
val = (fdt32_t *)p->prop;
|
|
va_start(args, count);
|
|
for (i = 0; i < count; i++)
|
|
val[i] = cpu_to_fdt32(va_arg(args, u32));
|
|
va_end(args);
|
|
return p;
|
|
}
|
|
|
|
struct dt_property *__dt_add_property_u64s(struct dt_node *node,
|
|
const char *name,
|
|
int count, ...)
|
|
{
|
|
struct dt_property *p;
|
|
fdt64_t *val;
|
|
unsigned int i;
|
|
va_list args;
|
|
|
|
p = new_property(node, name, count * sizeof(u64));
|
|
val = (fdt64_t *)p->prop;
|
|
va_start(args, count);
|
|
for (i = 0; i < count; i++)
|
|
val[i] = cpu_to_fdt64(va_arg(args, u64));
|
|
va_end(args);
|
|
return p;
|
|
}
|
|
|
|
struct dt_property *__dt_add_property_strings(struct dt_node *node,
|
|
const char *name,
|
|
int count, ...)
|
|
{
|
|
struct dt_property *p;
|
|
unsigned int i, size;
|
|
va_list args;
|
|
const char *sstr;
|
|
char *s;
|
|
|
|
va_start(args, count);
|
|
for (i = size = 0; i < count; i++) {
|
|
sstr = va_arg(args, const char *);
|
|
if (sstr)
|
|
size += strlen(sstr) + 1;
|
|
}
|
|
va_end(args);
|
|
if (!size)
|
|
size = 1;
|
|
p = new_property(node, name, size);
|
|
s = (char *)p->prop;
|
|
*s = 0;
|
|
va_start(args, count);
|
|
for (i = 0; i < count; i++) {
|
|
sstr = va_arg(args, const char *);
|
|
if (sstr) {
|
|
strcpy(s, sstr);
|
|
s = s + strlen(sstr) + 1;
|
|
}
|
|
}
|
|
va_end(args);
|
|
return p;
|
|
}
|
|
|
|
void dt_del_property(struct dt_node *node, struct dt_property *prop)
|
|
{
|
|
list_del_from(&node->properties, &prop->list);
|
|
free_name(prop->name);
|
|
free(prop);
|
|
}
|
|
|
|
u32 dt_property_get_cell(const struct dt_property *prop, u32 index)
|
|
{
|
|
assert(prop->len >= (index+1)*sizeof(u32));
|
|
/* Always aligned, so this works. */
|
|
return fdt32_to_cpu(((const fdt32_t *)prop->prop)[index]);
|
|
}
|
|
|
|
u64 dt_property_get_u64(const struct dt_property *prop, u32 index)
|
|
{
|
|
assert(prop->len >= (index+1)*sizeof(u64));
|
|
/* Always aligned, so this works. */
|
|
return fdt64_to_cpu(((const fdt64_t *)prop->prop)[index]);
|
|
}
|
|
|
|
void dt_property_set_cell(struct dt_property *prop, u32 index, u32 val)
|
|
{
|
|
assert(prop->len >= (index+1)*sizeof(u32));
|
|
/* Always aligned, so this works. */
|
|
((fdt32_t *)prop->prop)[index] = cpu_to_fdt32(val);
|
|
}
|
|
|
|
/* First child of this node. */
|
|
struct dt_node *dt_first(const struct dt_node *root)
|
|
{
|
|
return list_top(&root->children, struct dt_node, list);
|
|
}
|
|
|
|
/* Return next node, or NULL. */
|
|
struct dt_node *dt_next(const struct dt_node *root,
|
|
const struct dt_node *prev)
|
|
{
|
|
if (!prev) {
|
|
struct dt_node *first = dt_first(root);
|
|
|
|
if (!first)
|
|
return NULL;
|
|
else
|
|
return first;
|
|
}
|
|
|
|
/* Children? */
|
|
if (!list_empty(&prev->children))
|
|
return dt_first(prev);
|
|
|
|
do {
|
|
/* More siblings? */
|
|
if (prev->list.next != &prev->parent->children.n)
|
|
return list_entry(prev->list.next, struct dt_node,list);
|
|
|
|
/* No more siblings, move up to parent. */
|
|
prev = prev->parent;
|
|
} while (prev != root);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct dt_property *__dt_find_property(struct dt_node *node, const char *name)
|
|
{
|
|
struct dt_property *i;
|
|
|
|
list_for_each(&node->properties, i, list)
|
|
if (strcmp(i->name, name) == 0)
|
|
return i;
|
|
return NULL;
|
|
}
|
|
|
|
const struct dt_property *dt_find_property(const struct dt_node *node,
|
|
const char *name)
|
|
{
|
|
const struct dt_property *i;
|
|
|
|
list_for_each(&node->properties, i, list)
|
|
if (strcmp(i->name, name) == 0)
|
|
return i;
|
|
return NULL;
|
|
}
|
|
|
|
void dt_check_del_prop(struct dt_node *node, const char *name)
|
|
{
|
|
struct dt_property *p;
|
|
|
|
p = __dt_find_property(node, name);
|
|
if (p)
|
|
dt_del_property(node, p);
|
|
}
|
|
const struct dt_property *dt_require_property(const struct dt_node *node,
|
|
const char *name, int wanted_len)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, name);
|
|
|
|
if (!p) {
|
|
const char *path = dt_get_path(node);
|
|
|
|
prerror("DT: Missing required property %s/%s\n",
|
|
path, name);
|
|
assert(false);
|
|
}
|
|
if (wanted_len >= 0 && p->len != wanted_len) {
|
|
const char *path = dt_get_path(node);
|
|
|
|
prerror("DT: Unexpected property length %s/%s\n",
|
|
path, name);
|
|
prerror("DT: Expected len: %d got len: %zu\n",
|
|
wanted_len, p->len);
|
|
assert(false);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
bool dt_has_node_property(const struct dt_node *node,
|
|
const char *name, const char *val)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, name);
|
|
|
|
if (!p)
|
|
return false;
|
|
if (!val)
|
|
return true;
|
|
|
|
return p->len == strlen(val) + 1 && memcmp(p->prop, val, p->len) == 0;
|
|
}
|
|
|
|
bool dt_prop_find_string(const struct dt_property *p, const char *s)
|
|
{
|
|
const char *c, *end;
|
|
|
|
if (!p)
|
|
return false;
|
|
c = p->prop;
|
|
end = c + p->len;
|
|
|
|
while(c < end) {
|
|
if (!strcasecmp(s, c))
|
|
return true;
|
|
c += strlen(c) + 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool dt_node_is_compatible(const struct dt_node *node, const char *compat)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, "compatible");
|
|
|
|
return dt_prop_find_string(p, compat);
|
|
}
|
|
|
|
struct dt_node *dt_find_compatible_node(struct dt_node *root,
|
|
struct dt_node *prev,
|
|
const char *compat)
|
|
{
|
|
struct dt_node *node = prev;
|
|
|
|
while ((node = dt_next(root, node)))
|
|
if (dt_node_is_compatible(node, compat))
|
|
return node;
|
|
return NULL;
|
|
}
|
|
|
|
u64 dt_prop_get_u64(const struct dt_node *node, const char *prop)
|
|
{
|
|
const struct dt_property *p = dt_require_property(node, prop, 8);
|
|
|
|
return ((u64)dt_property_get_cell(p, 0) << 32)
|
|
| dt_property_get_cell(p, 1);
|
|
}
|
|
|
|
u64 dt_prop_get_u64_def(const struct dt_node *node, const char *prop, u64 def)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, prop);
|
|
|
|
if (!p)
|
|
return def;
|
|
|
|
return ((u64)dt_property_get_cell(p, 0) << 32)
|
|
| dt_property_get_cell(p, 1);
|
|
}
|
|
|
|
u32 dt_prop_get_u32(const struct dt_node *node, const char *prop)
|
|
{
|
|
const struct dt_property *p = dt_require_property(node, prop, 4);
|
|
|
|
return dt_property_get_cell(p, 0);
|
|
}
|
|
|
|
u32 dt_prop_get_u32_def(const struct dt_node *node, const char *prop, u32 def)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, prop);
|
|
|
|
if (!p)
|
|
return def;
|
|
|
|
return dt_property_get_cell(p, 0);
|
|
}
|
|
|
|
const void *dt_prop_get(const struct dt_node *node, const char *prop)
|
|
{
|
|
const struct dt_property *p = dt_require_property(node, prop, -1);
|
|
|
|
return p->prop;
|
|
}
|
|
|
|
const void *dt_prop_get_def(const struct dt_node *node, const char *prop,
|
|
void *def)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, prop);
|
|
|
|
return p ? p->prop : def;
|
|
}
|
|
|
|
const void *dt_prop_get_def_size(const struct dt_node *node, const char *prop,
|
|
void *def, size_t *len)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, prop);
|
|
*len = 0;
|
|
if (p)
|
|
*len = p->len;
|
|
|
|
return p ? p->prop : def;
|
|
}
|
|
|
|
u32 dt_prop_get_cell(const struct dt_node *node, const char *prop, u32 cell)
|
|
{
|
|
const struct dt_property *p = dt_require_property(node, prop, -1);
|
|
|
|
return dt_property_get_cell(p, cell);
|
|
}
|
|
|
|
u32 dt_prop_get_cell_def(const struct dt_node *node, const char *prop,
|
|
u32 cell, u32 def)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, prop);
|
|
|
|
if (!p)
|
|
return def;
|
|
|
|
return dt_property_get_cell(p, cell);
|
|
}
|
|
|
|
void dt_free(struct dt_node *node)
|
|
{
|
|
struct dt_node *child;
|
|
struct dt_property *p;
|
|
|
|
while ((child = list_top(&node->children, struct dt_node, list)))
|
|
dt_free(child);
|
|
|
|
while ((p = list_pop(&node->properties, struct dt_property, list))) {
|
|
free_name(p->name);
|
|
free(p);
|
|
}
|
|
|
|
if (node->parent)
|
|
list_del_from(&node->parent->children, &node->list);
|
|
dt_destroy(node);
|
|
}
|
|
|
|
int dt_expand_node(struct dt_node *node, const void *fdt, int fdt_node)
|
|
{
|
|
const struct fdt_property *prop;
|
|
int offset, nextoffset, err;
|
|
struct dt_node *child;
|
|
const char *name;
|
|
uint32_t tag;
|
|
|
|
if (((err = fdt_check_header(fdt)) != 0)
|
|
|| ((err = fdt_check_node_offset_(fdt, fdt_node)) < 0)) {
|
|
prerror("FDT: Error %d parsing node 0x%x\n", err, fdt_node);
|
|
return -1;
|
|
}
|
|
|
|
nextoffset = err;
|
|
do {
|
|
offset = nextoffset;
|
|
|
|
tag = fdt_next_tag(fdt, offset, &nextoffset);
|
|
switch (tag) {
|
|
case FDT_PROP:
|
|
prop = fdt_offset_ptr_(fdt, offset);
|
|
name = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
|
|
dt_add_property(node, name, prop->data,
|
|
fdt32_to_cpu(prop->len));
|
|
break;
|
|
case FDT_BEGIN_NODE:
|
|
name = fdt_get_name(fdt, offset, NULL);
|
|
child = dt_new_root(name);
|
|
assert(child);
|
|
nextoffset = dt_expand_node(child, fdt, offset);
|
|
|
|
/*
|
|
* This may fail in case of duplicate, keep it
|
|
* going for now, we may ultimately want to
|
|
* assert
|
|
*/
|
|
if (!dt_attach_root(node, child))
|
|
/**
|
|
* @fwts-label DTHasDuplicateNodeID
|
|
* @fwts-advice OPAL will parse the Flattened
|
|
* Device Tree(FDT), which can be generated
|
|
* from different firmware sources. During
|
|
* expansion of FDT, OPAL observed a node
|
|
* assigned multiple times (a duplicate). This
|
|
* indicates either a Hostboot bug *OR*, more
|
|
* likely, a bug in the platform XML. Check
|
|
* the platform XML for duplicate IDs for
|
|
* this type of device. Because of this
|
|
* duplicate node, OPAL won't add the hardware
|
|
* device found with a duplicate node ID into
|
|
* DT, rendering the corresponding device not
|
|
* functional.
|
|
*/
|
|
prlog(PR_ERR, "DT: Found duplicate node: %s\n",
|
|
child->name);
|
|
break;
|
|
case FDT_END:
|
|
return -1;
|
|
}
|
|
} while (tag != FDT_END_NODE);
|
|
|
|
return nextoffset;
|
|
}
|
|
|
|
void dt_expand(const void *fdt)
|
|
{
|
|
prlog(PR_DEBUG, "FDT: Parsing fdt @%p\n", fdt);
|
|
|
|
if (dt_expand_node(dt_root, fdt, 0) < 0)
|
|
abort();
|
|
}
|
|
|
|
u64 dt_get_number(const void *pdata, unsigned int cells)
|
|
{
|
|
const __be32 *p = pdata;
|
|
u64 ret = 0;
|
|
|
|
while(cells--)
|
|
ret = (ret << 32) | be32_to_cpu(*(p++));
|
|
return ret;
|
|
}
|
|
|
|
u32 dt_n_address_cells(const struct dt_node *node)
|
|
{
|
|
if (!node->parent)
|
|
return 0;
|
|
return dt_prop_get_u32_def(node->parent, "#address-cells", 2);
|
|
}
|
|
|
|
u32 dt_n_size_cells(const struct dt_node *node)
|
|
{
|
|
if (!node->parent)
|
|
return 0;
|
|
return dt_prop_get_u32_def(node->parent, "#size-cells", 1);
|
|
}
|
|
|
|
u64 dt_get_address(const struct dt_node *node, unsigned int index,
|
|
u64 *out_size)
|
|
{
|
|
const struct dt_property *p;
|
|
u32 na = dt_n_address_cells(node);
|
|
u32 ns = dt_n_size_cells(node);
|
|
u32 pos, n;
|
|
|
|
p = dt_require_property(node, "reg", -1);
|
|
n = (na + ns) * sizeof(u32);
|
|
pos = n * index;
|
|
assert((pos + n) <= p->len);
|
|
if (out_size)
|
|
*out_size = dt_get_number(p->prop + pos + na * sizeof(u32), ns);
|
|
return dt_get_number(p->prop + pos, na);
|
|
}
|
|
|
|
u32 __dt_get_chip_id(const struct dt_node *node)
|
|
{
|
|
const struct dt_property *prop;
|
|
|
|
for (; node; node = node->parent) {
|
|
prop = dt_find_property(node, "ibm,chip-id");
|
|
if (prop)
|
|
return dt_property_get_cell(prop, 0);
|
|
}
|
|
return 0xffffffff;
|
|
}
|
|
|
|
u32 dt_get_chip_id(const struct dt_node *node)
|
|
{
|
|
u32 id = __dt_get_chip_id(node);
|
|
assert(id != 0xffffffff);
|
|
return id;
|
|
}
|
|
|
|
struct dt_node *dt_find_compatible_node_on_chip(struct dt_node *root,
|
|
struct dt_node *prev,
|
|
const char *compat,
|
|
uint32_t chip_id)
|
|
{
|
|
struct dt_node *node = prev;
|
|
|
|
while ((node = dt_next(root, node))) {
|
|
u32 cid = __dt_get_chip_id(node);
|
|
if (cid == chip_id &&
|
|
dt_node_is_compatible(node, compat))
|
|
return node;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
unsigned int dt_count_addresses(const struct dt_node *node)
|
|
{
|
|
const struct dt_property *p;
|
|
u32 na = dt_n_address_cells(node);
|
|
u32 ns = dt_n_size_cells(node);
|
|
u32 n;
|
|
|
|
p = dt_require_property(node, "reg", -1);
|
|
n = (na + ns) * sizeof(u32);
|
|
|
|
if (n == 0)
|
|
return 0;
|
|
|
|
return p->len / n;
|
|
}
|
|
|
|
/* Translates an address from the given bus into its parent's address space */
|
|
static u64 dt_translate_one(const struct dt_node *bus, u64 addr)
|
|
{
|
|
u32 ranges_count, na, ns, parent_na;
|
|
const struct dt_property *p;
|
|
const u32 *ranges;
|
|
int i, stride;
|
|
|
|
assert(bus->parent);
|
|
|
|
na = dt_prop_get_u32_def(bus, "#address-cells", 2);
|
|
ns = dt_prop_get_u32_def(bus, "#size-cells", 2);
|
|
parent_na = dt_n_address_cells(bus);
|
|
|
|
stride = na + ns + parent_na;
|
|
|
|
/*
|
|
* FIXME: We should handle arbitrary length addresses, rather than
|
|
* limiting it to 64bit. If someone wants/needs that they
|
|
* can implement the bignum math for it :)
|
|
*/
|
|
assert(na <= 2);
|
|
assert(parent_na <= 2);
|
|
|
|
/* We should never be trying to translate an address without a ranges */
|
|
p = dt_require_property(bus, "ranges", -1);
|
|
|
|
ranges = (u32 *) &p->prop;
|
|
ranges_count = (p->len / 4) / (na + parent_na + ns);
|
|
|
|
/* An empty ranges property implies 1-1 translation */
|
|
if (ranges_count == 0)
|
|
return addr;
|
|
|
|
for (i = 0; i < ranges_count; i++, ranges += stride) {
|
|
/* ranges format: <child base> <parent base> <size> */
|
|
u64 child_base = dt_get_number(ranges, na);
|
|
u64 parent_base = dt_get_number(ranges + na, parent_na);
|
|
u64 size = dt_get_number(ranges + na + parent_na, ns);
|
|
|
|
if (addr >= child_base && addr < child_base + size)
|
|
return (addr - child_base) + parent_base;
|
|
}
|
|
|
|
/* input address was outside the any of our mapped ranges */
|
|
return 0;
|
|
}
|
|
|
|
u64 dt_translate_address(const struct dt_node *node, unsigned int index,
|
|
u64 *out_size)
|
|
{
|
|
u64 addr = dt_get_address(node, index, NULL);
|
|
struct dt_node *bus = node->parent;
|
|
|
|
/* FIXME: One day we will probably want to use this, but for now just
|
|
* force it it to be zero since we only support returning a u64 or u32
|
|
*/
|
|
assert(!out_size);
|
|
|
|
/* apply each translation until we hit the root bus */
|
|
while (bus->parent) {
|
|
addr = dt_translate_one(bus, addr);
|
|
bus = bus->parent;
|
|
}
|
|
|
|
return addr;
|
|
}
|
|
|
|
bool dt_node_is_enabled(struct dt_node *node)
|
|
{
|
|
const struct dt_property *p = dt_find_property(node, "status");
|
|
|
|
if (!p)
|
|
return true;
|
|
|
|
return p->len > 1 && p->prop[0] == 'o' && p->prop[1] == 'k';
|
|
}
|
|
|
|
/*
|
|
* Function to fixup the phandle in the subtree.
|
|
*/
|
|
void dt_adjust_subtree_phandle(struct dt_node *dev,
|
|
const char** (get_properties_to_fix)(struct dt_node *n))
|
|
{
|
|
struct dt_node *node;
|
|
struct dt_property *prop;
|
|
u32 phandle, max_phandle = 0, import_phandle = new_phandle();
|
|
__be32 p;
|
|
const char **name;
|
|
|
|
dt_for_each_node(dev, node) {
|
|
const char **props_to_update;
|
|
node->phandle += import_phandle;
|
|
|
|
/*
|
|
* calculate max_phandle(new_tree), needed to update
|
|
* last_phandle.
|
|
*/
|
|
if (node->phandle >= max_phandle)
|
|
max_phandle = node->phandle;
|
|
|
|
props_to_update = get_properties_to_fix(node);
|
|
if (!props_to_update)
|
|
continue;
|
|
for (name = props_to_update; *name != NULL; name++) {
|
|
prop = __dt_find_property(node, *name);
|
|
if (!prop)
|
|
continue;
|
|
phandle = dt_prop_get_u32(node, *name);
|
|
phandle += import_phandle;
|
|
p = cpu_to_be32(phandle);
|
|
memcpy((char *)&prop->prop, &p, prop->len);
|
|
}
|
|
}
|
|
|
|
set_last_phandle(max_phandle);
|
|
}
|