mirror of
https://github.com/AsahiLinux/m1n1
synced 2024-12-22 19:43:05 +00:00
4167a18a64
Signed-off-by: Hector Martin <marcan@marcan.st>
343 lines
8.7 KiB
C
343 lines
8.7 KiB
C
/* SPDX-License-Identifier: (GPL-2.0-or-later OR BSD-2-Clause) */
|
|
|
|
#include "adt.h"
|
|
#include "string.h"
|
|
|
|
/* This API is designed to match libfdt's read-only API */
|
|
|
|
#define ADT_CHECK_HEADER(adt) \
|
|
{ \
|
|
int err; \
|
|
if ((err = adt_check_header(adt)) != 0) \
|
|
return err; \
|
|
}
|
|
|
|
//#define DEBUG
|
|
|
|
#ifdef DEBUG
|
|
#include "utils.h"
|
|
#define dprintf printf
|
|
#else
|
|
#define dprintf(...) \
|
|
do { \
|
|
} while (0)
|
|
#endif
|
|
|
|
int _adt_check_node_offset(const void *adt, int offset)
|
|
{
|
|
if ((offset < 0) || (offset % ADT_ALIGN))
|
|
return -ADT_ERR_BADOFFSET;
|
|
|
|
const struct adt_node_hdr *node = ADT_NODE(adt, offset);
|
|
|
|
// Sanity check
|
|
if (node->property_count > 2048 || !node->property_count || node->child_count > 2048)
|
|
return -ADT_ERR_BADOFFSET;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int _adt_check_prop_offset(const void *adt, int offset)
|
|
{
|
|
if ((offset < 0) || (offset % ADT_ALIGN))
|
|
return -ADT_ERR_BADOFFSET;
|
|
|
|
const struct adt_property *prop = ADT_PROP(adt, offset);
|
|
|
|
if (prop->size & 0x7ff00000) // up to 1MB properties
|
|
return -ADT_ERR_BADOFFSET;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int adt_check_header(const void *adt)
|
|
{
|
|
return _adt_check_node_offset(adt, 0);
|
|
}
|
|
|
|
static int _adt_string_eq(const char *a, const char *b, size_t len)
|
|
{
|
|
return (strlen(a) == len) && (memcmp(a, b, len) == 0);
|
|
}
|
|
|
|
static int _adt_nodename_eq(const char *a, const char *b, size_t len)
|
|
{
|
|
if (memcmp(a, b, len) != 0)
|
|
return 0;
|
|
|
|
if (a[len] == '\0')
|
|
return 1;
|
|
else if (!memchr(b, '@', len) && (a[len] == '@'))
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
const struct adt_property *adt_get_property_namelen(const void *adt, int offset, const char *name,
|
|
size_t namelen)
|
|
{
|
|
dprintf("adt_get_property_namelen(%p, %d, \"%s\", %u)\n", adt, offset, name, namelen);
|
|
|
|
ADT_FOREACH_PROPERTY(adt, offset, prop)
|
|
{
|
|
dprintf(" off=0x%x name=\"%s\"\n", offset, prop->name);
|
|
if (_adt_string_eq(prop->name, name, namelen))
|
|
return prop;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const struct adt_property *adt_get_property(const void *adt, int nodeoffset, const char *name)
|
|
{
|
|
return adt_get_property_namelen(adt, nodeoffset, name, strlen(name));
|
|
}
|
|
|
|
const void *adt_getprop_namelen(const void *adt, int nodeoffset, const char *name, size_t namelen,
|
|
u32 *lenp)
|
|
{
|
|
const struct adt_property *prop;
|
|
|
|
prop = adt_get_property_namelen(adt, nodeoffset, name, namelen);
|
|
|
|
if (!prop)
|
|
return NULL;
|
|
|
|
if (lenp)
|
|
*lenp = prop->size;
|
|
|
|
return prop->value;
|
|
}
|
|
|
|
const void *adt_getprop_by_offset(const void *adt, int offset, const char **namep, u32 *lenp)
|
|
{
|
|
const struct adt_property *prop;
|
|
|
|
prop = adt_get_property_by_offset(adt, offset);
|
|
if (!prop)
|
|
return NULL;
|
|
|
|
if (namep)
|
|
*namep = prop->name;
|
|
if (lenp)
|
|
*lenp = prop->size;
|
|
return prop->value;
|
|
}
|
|
|
|
const void *adt_getprop(const void *adt, int nodeoffset, const char *name, u32 *lenp)
|
|
{
|
|
return adt_getprop_namelen(adt, nodeoffset, name, strlen(name), lenp);
|
|
}
|
|
|
|
int adt_getprop_copy(const void *adt, int nodeoffset, const char *name, void *out, size_t len)
|
|
{
|
|
u32 plen;
|
|
|
|
const void *p = adt_getprop(adt, nodeoffset, name, &plen);
|
|
|
|
if (!p)
|
|
return -ADT_ERR_NOTFOUND;
|
|
|
|
if (plen != len)
|
|
return -ADT_ERR_BADLENGTH;
|
|
|
|
memcpy(out, p, len);
|
|
return len;
|
|
}
|
|
|
|
int adt_first_child_offset(const void *adt, int offset)
|
|
{
|
|
const struct adt_node_hdr *node = ADT_NODE(adt, offset);
|
|
|
|
u32 cnt = node->property_count;
|
|
offset = adt_first_property_offset(adt, offset);
|
|
|
|
while (cnt--) {
|
|
offset = adt_next_property_offset(adt, offset);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
int adt_next_sibling_offset(const void *adt, int offset)
|
|
{
|
|
const struct adt_node_hdr *node = ADT_NODE(adt, offset);
|
|
|
|
u32 cnt = node->child_count;
|
|
offset = adt_first_child_offset(adt, offset);
|
|
|
|
while (cnt--) {
|
|
offset = adt_next_sibling_offset(adt, offset);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
int adt_subnode_offset_namelen(const void *adt, int offset, const char *name, size_t namelen)
|
|
{
|
|
ADT_CHECK_HEADER(adt);
|
|
|
|
ADT_FOREACH_CHILD(adt, offset)
|
|
{
|
|
const char *cname = adt_get_name(adt, offset);
|
|
|
|
if (_adt_nodename_eq(cname, name, namelen))
|
|
return offset;
|
|
}
|
|
|
|
return -ADT_ERR_NOTFOUND;
|
|
}
|
|
|
|
int adt_subnode_offset(const void *adt, int parentoffset, const char *name)
|
|
{
|
|
return adt_subnode_offset_namelen(adt, parentoffset, name, strlen(name));
|
|
}
|
|
|
|
int adt_path_offset(const void *adt, const char *path)
|
|
{
|
|
return adt_path_offset_trace(adt, path, NULL);
|
|
}
|
|
|
|
int adt_path_offset_trace(const void *adt, const char *path, int *offsets)
|
|
{
|
|
const char *end = path + strlen(path);
|
|
const char *p = path;
|
|
int offset = 0;
|
|
|
|
ADT_CHECK_HEADER(adt);
|
|
|
|
while (*p) {
|
|
const char *q;
|
|
|
|
while (*p == '/')
|
|
p++;
|
|
if (!*p)
|
|
break;
|
|
q = strchr(p, '/');
|
|
if (!q)
|
|
q = end;
|
|
|
|
offset = adt_subnode_offset_namelen(adt, offset, p, q - p);
|
|
if (offset < 0)
|
|
break;
|
|
|
|
if (offsets)
|
|
*offsets++ = offset;
|
|
|
|
p = q;
|
|
}
|
|
|
|
if (offsets)
|
|
*offsets++ = 0;
|
|
|
|
return offset;
|
|
}
|
|
|
|
const char *adt_get_name(const void *adt, int nodeoffset)
|
|
{
|
|
return adt_getprop(adt, nodeoffset, "name", NULL);
|
|
}
|
|
|
|
static void get_cells(u64 *dst, const u32 **src, int cells)
|
|
{
|
|
*dst = 0;
|
|
for (int i = 0; i < cells; i++)
|
|
*dst |= ((u64) * ((*src)++)) << (32 * i);
|
|
}
|
|
|
|
int adt_get_reg(const void *adt, int *path, const char *prop, int idx, u64 *paddr, u64 *psize)
|
|
{
|
|
int cur = 0;
|
|
|
|
if (!*path)
|
|
return -ADT_ERR_BADOFFSET;
|
|
|
|
while (path[cur + 1])
|
|
cur++;
|
|
|
|
int node = path[cur];
|
|
int parent = cur > 0 ? path[cur - 1] : 0;
|
|
u32 a_cells = 2, s_cells = 1;
|
|
|
|
ADT_GETPROP(adt, parent, "#address-cells", &a_cells);
|
|
ADT_GETPROP(adt, parent, "#size-cells", &s_cells);
|
|
|
|
dprintf("adt_get_reg: node '%s' @ %d, parent @ %d, address-cells=%d size-cells=%d idx=%d\n",
|
|
adt_get_name(adt, node), node, parent, a_cells, s_cells, idx);
|
|
|
|
if (a_cells < 1 || a_cells > 2 || s_cells > 2) {
|
|
dprintf("bad n-cells\n");
|
|
return ADT_ERR_BADNCELLS;
|
|
}
|
|
|
|
u32 reg_len = 0;
|
|
const u32 *reg = adt_getprop(adt, node, prop, ®_len);
|
|
|
|
if (!reg || !reg_len) {
|
|
dprintf("reg not found or empty\n");
|
|
return -ADT_ERR_NOTFOUND;
|
|
}
|
|
|
|
if (reg_len < (idx + 1) * (a_cells + s_cells) * 4) {
|
|
dprintf("bad reg property length %d\n", reg_len);
|
|
return -ADT_ERR_BADVALUE;
|
|
}
|
|
|
|
reg += idx * (a_cells + s_cells);
|
|
|
|
u64 addr, size = 0;
|
|
get_cells(&addr, ®, a_cells);
|
|
get_cells(&size, ®, s_cells);
|
|
|
|
dprintf(" addr=0x%lx size=0x%lx\n", addr, size);
|
|
|
|
while (parent) {
|
|
cur--;
|
|
node = parent;
|
|
parent = cur > 0 ? path[cur - 1] : 0;
|
|
|
|
dprintf(" walking up to %s\n", adt_get_name(adt, node));
|
|
|
|
u32 ranges_len;
|
|
const u32 *ranges = adt_getprop(adt, node, "ranges", &ranges_len);
|
|
if (!ranges)
|
|
break;
|
|
|
|
u32 pa_cells = 2, ps_cells = 1;
|
|
ADT_GETPROP(adt, parent, "#address-cells", &pa_cells);
|
|
ADT_GETPROP(adt, parent, "#size-cells", &ps_cells);
|
|
|
|
dprintf(" translate range to address-cells=%d size-cells=%d\n", pa_cells, ps_cells);
|
|
|
|
if (pa_cells < 1 || pa_cells > 2 || ps_cells > 2)
|
|
return ADT_ERR_BADNCELLS;
|
|
|
|
int range_cnt = ranges_len / (4 * (pa_cells + a_cells + s_cells));
|
|
|
|
while (range_cnt--) {
|
|
u64 c_addr, p_addr, c_size;
|
|
get_cells(&c_addr, &ranges, a_cells);
|
|
get_cells(&p_addr, &ranges, pa_cells);
|
|
get_cells(&c_size, &ranges, s_cells);
|
|
|
|
dprintf(" ranges %lx %lx %lx\n", c_addr, p_addr, c_size);
|
|
|
|
if (addr >= c_addr && (addr + size) <= (c_addr + c_size)) {
|
|
dprintf(" translate %lx", addr);
|
|
addr = addr - c_addr + p_addr;
|
|
dprintf(" -> %lx\n", addr);
|
|
break;
|
|
}
|
|
}
|
|
|
|
a_cells = pa_cells;
|
|
s_cells = ps_cells;
|
|
}
|
|
|
|
if (paddr)
|
|
*paddr = addr;
|
|
if (psize)
|
|
*psize = size;
|
|
|
|
return 0;
|
|
}
|