// SPDX-License-Identifier: GPL-2.0+ /* * Copyright 2009 Benjamin Herrenschmidt, IBM Corp * benh@kernel.crashing.org * * Based on parts of drivers/of/fdt.c from Linux v4.9 * Modifications for U-Boot * Copyright (c) 2017 Google, Inc */ #define LOG_CATEGORY LOGC_DT #include #include #include #include #include #include #include #include #include enum { BUF_STEP = SZ_64K, }; static void *unflatten_dt_alloc(void **mem, unsigned long size, unsigned long align) { void *res; *mem = PTR_ALIGN(*mem, align); res = *mem; *mem += size; return res; } /** * unflatten_dt_node() - Alloc and populate a device_node from the flat tree * @blob: The parent device tree blob * @mem: Memory chunk to use for allocating device nodes and properties * @poffset: pointer to node in flat tree * @dad: Parent struct device_node * @nodepp: The device_node tree created by the call * @fpsize: Size of the node path up at t05he current depth. * @dryrun: If true, do not allocate device nodes but still calculate needed * memory size */ static void *unflatten_dt_node(const void *blob, void *mem, int *poffset, struct device_node *dad, struct device_node **nodepp, unsigned long fpsize, bool dryrun) { const __be32 *p; struct device_node *np; struct property *pp, **prev_pp = NULL; const char *pathp; int l; unsigned int allocl; static int depth; int old_depth; int offset; int has_name = 0; int new_format = 0; pathp = fdt_get_name(blob, *poffset, &l); if (!pathp) return mem; allocl = ++l; /* * version 0x10 has a more compact unit name here instead of the full * path. we accumulate the full path size using "fpsize", we'll rebuild * it later. We detect this because the first character of the name is * not '/'. */ if ((*pathp) != '/') { new_format = 1; if (fpsize == 0) { /* * root node: special case. fpsize accounts for path * plus terminating zero. root node only has '/', so * fpsize should be 2, but we want to avoid the first * level nodes to have two '/' so we use fpsize 1 here */ fpsize = 1; allocl = 2; l = 1; pathp = ""; } else { /* * account for '/' and path size minus terminal 0 * already in 'l' */ fpsize += l; allocl = fpsize; } } np = unflatten_dt_alloc(&mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node)); if (!dryrun) { char *fn; fn = (char *)np + sizeof(*np); if (new_format) { np->name = pathp; has_name = 1; } np->full_name = fn; if (new_format) { /* rebuild full path for new format */ if (dad && dad->parent) { strcpy(fn, dad->full_name); #ifdef DEBUG if ((strlen(fn) + l + 1) != allocl) { debug("%s: p: %d, l: %d, a: %d\n", pathp, (int)strlen(fn), l, allocl); } #endif fn += strlen(fn); } *(fn++) = '/'; } memcpy(fn, pathp, l); prev_pp = &np->properties; if (dad != NULL) { np->parent = dad; np->sibling = dad->child; dad->child = np; } } /* process properties */ for (offset = fdt_first_property_offset(blob, *poffset); (offset >= 0); (offset = fdt_next_property_offset(blob, offset))) { const char *pname; int sz; p = fdt_getprop_by_offset(blob, offset, &pname, &sz); if (!p) { offset = -FDT_ERR_INTERNAL; break; } if (pname == NULL) { debug("Can't find property name in list !\n"); break; } if (strcmp(pname, "name") == 0) has_name = 1; pp = unflatten_dt_alloc(&mem, sizeof(struct property), __alignof__(struct property)); if (!dryrun) { /* * We accept flattened tree phandles either in * ePAPR-style "phandle" properties, or the * legacy "linux,phandle" properties. If both * appear and have different values, things * will get weird. Don't do that. */ if ((strcmp(pname, "phandle") == 0) || (strcmp(pname, "linux,phandle") == 0)) { if (np->phandle == 0) np->phandle = be32_to_cpup(p); } /* * And we process the "ibm,phandle" property * used in pSeries dynamic device tree * stuff */ if (strcmp(pname, "ibm,phandle") == 0) np->phandle = be32_to_cpup(p); pp->name = (char *)pname; pp->length = sz; pp->value = (__be32 *)p; *prev_pp = pp; prev_pp = &pp->next; } } /* * with version 0x10 we may not have the name property, recreate * it here from the unit name if absent */ if (!has_name) { const char *p1 = pathp, *ps = pathp, *pa = NULL; int sz; while (*p1) { if ((*p1) == '@') pa = p1; if ((*p1) == '/') ps = p1 + 1; p1++; } if (pa < ps) pa = p1; sz = (pa - ps) + 1; pp = unflatten_dt_alloc(&mem, sizeof(struct property) + sz, __alignof__(struct property)); if (!dryrun) { pp->name = "name"; pp->length = sz; pp->value = pp + 1; *prev_pp = pp; prev_pp = &pp->next; memcpy(pp->value, ps, sz - 1); ((char *)pp->value)[sz - 1] = 0; debug("fixed up name for %s -> %s\n", pathp, (char *)pp->value); } } if (!dryrun) { *prev_pp = NULL; if (!has_name) np->name = of_get_property(np, "name", NULL); np->type = of_get_property(np, "device_type", NULL); if (!np->name) np->name = ""; if (!np->type) np->type = ""; } old_depth = depth; *poffset = fdt_next_node(blob, *poffset, &depth); if (depth < 0) depth = 0; while (*poffset > 0 && depth > old_depth) { mem = unflatten_dt_node(blob, mem, poffset, np, NULL, fpsize, dryrun); if (!mem) return NULL; } if (*poffset < 0 && *poffset != -FDT_ERR_NOTFOUND) { debug("unflatten: error %d processing FDT\n", *poffset); return NULL; } /* * Reverse the child list. Some drivers assumes node order matches .dts * node order */ if (!dryrun && np->child) { struct device_node *child = np->child; np->child = NULL; while (child) { struct device_node *next = child->sibling; child->sibling = np->child; np->child = child; child = next; } } if (nodepp) *nodepp = np; return mem; } int unflatten_device_tree(const void *blob, struct device_node **mynodes) { unsigned long size; int start; void *mem; debug(" -> unflatten_device_tree()\n"); if (!blob) { debug("No device tree pointer\n"); return -EINVAL; } debug("Unflattening device tree:\n"); debug("magic: %08x\n", fdt_magic(blob)); debug("size: %08x\n", fdt_totalsize(blob)); debug("version: %08x\n", fdt_version(blob)); if (fdt_check_header(blob)) { debug("Invalid device tree blob header\n"); return -EINVAL; } /* First pass, scan for size */ start = 0; size = (unsigned long)unflatten_dt_node(blob, NULL, &start, NULL, NULL, 0, true); if (!size) return -EFAULT; size = ALIGN(size, 4); debug(" size is %lx, allocating...\n", size); /* Allocate memory for the expanded device tree */ mem = memalign(__alignof__(struct device_node), size + 4); memset(mem, '\0', size); /* Set up value for dm_test_livetree_align() */ *(u32 *)mem = BAD_OF_ROOT; *(__be32 *)(mem + size) = cpu_to_be32(0xdeadbeef); debug(" unflattening %p...\n", mem); /* Second pass, do actual unflattening */ start = 0; unflatten_dt_node(blob, mem, &start, NULL, mynodes, 0, false); if (be32_to_cpup(mem + size) != 0xdeadbeef) { debug("End of tree marker overwritten: %08x\n", be32_to_cpup(mem + size)); return -ENOSPC; } debug(" <- unflatten_device_tree()\n"); return 0; } int of_live_build(const void *fdt_blob, struct device_node **rootp) { int ret; debug("%s: start\n", __func__); ret = unflatten_device_tree(fdt_blob, rootp); if (ret) { debug("Failed to create live tree: err=%d\n", ret); return ret; } ret = of_alias_scan(); if (ret) { debug("Failed to scan live tree aliases: err=%d\n", ret); return ret; } debug("%s: stop\n", __func__); return ret; } void of_live_free(struct device_node *root) { /* the tree is stored as a contiguous block of memory */ free(root); } int of_live_create_empty(struct device_node **rootp) { struct device_node *root; root = calloc(1, sizeof(struct device_node)); if (!root) return -ENOMEM; root->name = strdup(""); if (!root->name) { free(root); return -ENOMEM; } root->type = ""; root->full_name = ""; *rootp = root; return 0; } static int check_space(int ret, struct abuf *buf) { if (ret == -FDT_ERR_NOSPACE) { if (!abuf_realloc_inc(buf, BUF_STEP)) return log_msg_ret("spc", -ENOMEM); ret = fdt_resize(abuf_data(buf), abuf_data(buf), abuf_size(buf)); if (ret) return log_msg_ret("res", -EFAULT); return -EAGAIN; } return 0; } /** * flatten_node() - Write out the node and its properties into a flat tree */ static int flatten_node(struct abuf *buf, const struct device_node *node) { const struct device_node *np; const struct property *pp; int ret; ret = fdt_begin_node(abuf_data(buf), node->name); ret = check_space(ret, buf); if (ret == -EAGAIN) { ret = fdt_begin_node(abuf_data(buf), node->name); if (ret) { log_debug("Internal error a %d\n", ret); return -EFAULT; } } if (ret) return log_msg_ret("beg", ret); /* First write out the properties */ for (pp = node->properties; !ret && pp; pp = pp->next) { ret = fdt_property(abuf_data(buf), pp->name, pp->value, pp->length); ret = check_space(ret, buf); if (ret == -EAGAIN) { ret = fdt_property(abuf_data(buf), pp->name, pp->value, pp->length); } } /* Next write out the subnodes */ for (np = node->child; np; np = np->sibling) { ret = flatten_node(buf, np); if (ret) return log_msg_ret("sub", ret); } ret = fdt_end_node(abuf_data(buf)); ret = check_space(ret, buf); if (ret == -EAGAIN) { ret = fdt_end_node(abuf_data(buf)); if (ret) { log_debug("Internal error b %d\n", ret); return -EFAULT; } } if (ret) return log_msg_ret("end", ret); return 0; } int of_live_flatten(const struct device_node *root, struct abuf *buf) { int ret; abuf_init(buf); if (!abuf_realloc(buf, BUF_STEP)) return log_msg_ret("ini", -ENOMEM); ret = fdt_create(abuf_data(buf), abuf_size(buf)); if (!ret) ret = fdt_finish_reservemap(abuf_data(buf)); if (ret) { log_debug("Failed to start FDT (err=%d)\n", ret); return log_msg_ret("sta", -EINVAL); } ret = flatten_node(buf, root); if (ret) return log_msg_ret("flt", ret); ret = fdt_finish(abuf_data(buf)); ret = check_space(ret, buf); if (ret == -EAGAIN) { ret = fdt_finish(abuf_data(buf)); if (ret) { log_debug("Internal error c %d\n", ret); return -EFAULT; } } if (ret) return log_msg_ret("fin", ret); ret = fdt_pack(abuf_data(buf)); if (ret) { log_debug("Failed to pack (err=%d)\n", ret); return log_msg_ret("pac", -EFAULT); } if (!abuf_realloc(buf, fdt_totalsize(abuf_data(buf)))) return log_msg_ret("abu", -EFAULT); return 0; }