| /* 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_setprop(void *adt, int nodeoffset, const char *name, void *value, size_t len) |
| { |
| u32 plen; |
| void *prop = (void *)adt_getprop(adt, nodeoffset, name, &plen); |
| if (!prop) |
| return -ADT_ERR_NOTFOUND; |
| |
| if (len != plen) |
| return -ADT_ERR_BADLENGTH; |
| |
| memcpy(prop, value, len); |
| return len; |
| } |
| |
| 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; |
| ADT_GETPROP(adt, parent, "#address-cells", &pa_cells); |
| |
| dprintf(" translate range to address-cells=%d\n", pa_cells); |
| |
| if (pa_cells < 1 || pa_cells > 2 || s_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; |
| } |
| } |
| |
| ADT_GETPROP(adt, parent, "#size-cells", &s_cells); |
| |
| a_cells = pa_cells; |
| } |
| |
| if (paddr) |
| *paddr = addr; |
| if (psize) |
| *psize = size; |
| |
| return 0; |
| } |
| |
| bool adt_is_compatible(const void *adt, int nodeoffset, const char *compat) |
| { |
| u32 len; |
| const char *list = adt_getprop(adt, nodeoffset, "compatible", &len); |
| if (!list) |
| return false; |
| |
| const char *end = list + len; |
| |
| while (list != end) { |
| if (!strcmp(list, compat)) |
| return true; |
| list += strlen(list) + 1; |
| } |
| |
| return false; |
| } |