When merging two overlay blobs, fragment nodes whose target node can't be found in base node would need to be retained as-is (including the fragment names) in the combined blob. Such unresolved symbols will also need to be listed in __fixups__ section of combined blob. This could lead to name comflicts in combined blob (two nodes with same name/path such as /fragment@0). To avoid such name conflicts in combined blob, rename all fragment@xyz in overlay blob as fragment@xyz+delta, where delta is the maximum count of fragment nodes found in base blob Signed-off-by: Srivatsa Vaddagiri <vatsa@xxxxxxxxxxxxxx> --- libfdt/fdt_overlay.c | 390 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 390 insertions(+) diff --git a/libfdt/fdt_overlay.c b/libfdt/fdt_overlay.c index 81b9feb..556551e 100644 --- a/libfdt/fdt_overlay.c +++ b/libfdt/fdt_overlay.c @@ -53,6 +53,8 @@ #include <fdt.h> #include <libfdt.h> +#include <stdio.h> +#include <assert.h> #include "libfdt_internal.h" @@ -67,6 +69,114 @@ void fdt_overlay_set_verbose(int val) verbose_overlay = val; } +int base_has_fixups; +int base_fixup_search_done; +unsigned long max_base_fragments; + +#define PATH_MAX 512 +#define ULONG_MAX (~0UL) + +#define ALIGN(x) (((x) + (FDT_TAGSIZE) - 1) & ~((FDT_TAGSIZE) - 1)) + +static inline void *xrealloc(void *p, size_t len) +{ + void *new = realloc(p, len); + + if (!new) { + dprintf("FATAL ERROR: realloc() failed for %lu bytes\n", len); + exit(1); + } + + return new; +} + +static inline void *xmalloc(size_t len) +{ + void *new = malloc(len); + + if (!new) { + dprintf("FATAL ERROR: malloc() failed for %lu bytes\n", len); + exit(1); + } + + return new; +} + +static char *realloc_fdt(char *fdt, int delta) +{ + int new_sz; + + delta = ALIGN(delta); + assert(delta > 0); + + new_sz = fdt_totalsize(fdt) + delta; + + fdt = xrealloc(fdt, new_sz); + fdt_set_totalsize(fdt, new_sz); + + return fdt; +} + +/** + * fdt_set_name_memcheck - Change name of a node, increasing the size of the + * blob in the process if required + * + * @fdt - Pointer to pointer that points to device-tree blob + * @offset - Offset to the node + * @orig_len - Length of node's current name + * @name - new name of the node + */ +static int fdt_set_name_memcheck(void **fdt, int offset, + int orig_len, char *name) +{ + int err, lendiff; + + err = fdt_set_name(*fdt, offset, name); + if (!err) + return 0; + + if (err != -FDT_ERR_NOSPACE) + return err; + + lendiff = strlen(name) + 1 - orig_len; + + *fdt = realloc_fdt(*fdt, lendiff); + + return fdt_set_name(*fdt, offset, name); +} + +/** + * fdt_setprop_placeholder_memcheck - Set property value + * + * This is a wrapper around fdt_setprop_placeholder(). It increases the size of + * DT blob in case fdt_setprop_placeholder() fails because of lack of space + * + * @fdt - pointer to pointer that point to a device-tree blob + * @offset - Offset of property whose length needs to be increased to @new_len + * @name - name of the property + * @orig_len - Current length of the property value + * @new_len - New length of the property value + * @prop_data - Used to store pointer to property value of length @new_len + */ +static int fdt_setprop_placeholder_memcheck(void **fdt, int offset, + const char *name, int orig_len, int new_len, void **prop_data) +{ + int err; + + assert(new_len >= orig_len); + + err = fdt_setprop_placeholder(*fdt, offset, name, new_len, prop_data); + if (!err) + return 0; + + if (err != -FDT_ERR_NOSPACE) + return err; + + *fdt = realloc_fdt(*fdt, new_len - orig_len); + + return fdt_setprop_placeholder(*fdt, offset, name, new_len, prop_data); +} + /** * overlay_get_target_phandle - retrieves the target phandle of a fragment * @fdto: pointer to the device tree overlay blob @@ -874,15 +984,295 @@ static int overlay_symbol_update(void *fdt, void *fdto) return 0; } +/* Return maximum count of nodes that are named fragment@xyz */ +static unsigned long count_fragments(void *fdt) +{ + int offset = -1; + const char *name, *idx; + char *stop; + unsigned long index, max = 0; + int len; + + do { + offset = fdt_next_node(fdt, offset, NULL); + if (offset < 0) + break; + + name = fdt_get_name(fdt, offset, &len); + if (len > 9 && !strncmp(name, "fragment@", 9)) { + idx = name + 9; + index = strtoul(idx, &stop, 10); + if (index > max) + max = index; + } + } while (1); + + return max; +} + +static void copy_buf(const char **start, int len, char **pp, + int *rembufsize, char **bufp, int *bufsize) +{ + int delta; + + if (*rembufsize < len) { + delta = (len - *rembufsize) + 1024; + *bufp = xrealloc(*bufp, *bufsize + delta); + *bufsize += delta; + *rembufsize += delta; + } + memcpy(*pp, *start, len); + *start += len; + *pp += len; + *rembufsize -= len; +} + +/** + * rename_fragments_in_section - Rename fragment@xyz instances in a section + * + * @fdto - pointer to pointer that points to a device-tree blob + * @secname - Section in which fragments need to be renamed + * @delta - Increment to be applied to fragment index + */ +static int rename_fragments_in_section(void **fdto, const char *secname, + unsigned long delta) +{ + int offset, len, new_len, property; + unsigned long index, new_index; + int bufsize, rembufsize, err; + + offset = fdt_path_offset(*fdto, secname); + if (offset < 0) { + dprintf("%s section not found\n", secname); + return -FDT_ERR_NOTFOUND; + } + + fdt_for_each_property_offset(property, *fdto, offset) { + const char *value, *end, *sep, *start, *copy_start; + const char *label; + int modified = 0, needed; + char *buf, *p; + void *q; + + value = fdt_getprop_by_offset(*fdto, property, + &label, &len); + if (!value) { + dprintf("Failed to get value of property at offset %d" + " in section %s (err %d)\n", property, + secname, len); + return -1; + } + + /* + * Property value could be in this format + * fragment@M ...fragment@N....fragment@O.. + * + * This need to be converted to + * fragment@M+delta...fragment@N+delta....fragment@O+delta + * + * which could require more space than what is currently + * allocated to the property. 'buf' will be used to create the + * new value for the property, which can then be used to + * modify property's value + */ + + /* + * 1024 bytes of additional space for holding extra characters + * if required + */ + buf = xmalloc(len + 1024); + bufsize = len + 1024; + rembufsize = len + 1024; + + start = value; + end = value + len; + p = buf; + copy_start = start; + + while (start < end) { + char *stop; + + sep = memchr(start, '@', (end - start)); + if (!sep) + break; + + /* Check if string "fragment" exists */ + sep -= 8; + if (sep < start || strncmp(sep, "fragment", 8)) { + /* Start scan again after '@' */ + start = sep + 9; + continue; + } + + sep += 9; + + /* + * copy characters from @copy_start to @sep into @p + * increasing the size of @buf if available space is + * less. + */ + copy_buf(©_start, sep - copy_start, &p, &rembufsize, + &buf, &bufsize); + stop = NULL; + index = strtoul(sep, &stop, 10); + if (!stop || ULONG_MAX - index < delta) { + free(buf); + return -FDT_ERR_BADSTATE; + } + + new_index = index + delta; + needed = snprintf(NULL, 0, "%lu", new_index); + if (needed > rembufsize) { + long diff = needed - rembufsize; + if (diff < 1024) + diff = 1024; + buf = xrealloc(buf, bufsize + diff); + bufsize += diff; + rembufsize += diff; + } + modified = 1; + p += snprintf(p, rembufsize, "%lu", new_index); + start = stop; + copy_start = start; + } + + if (end > copy_start) + copy_buf(©_start, end - copy_start, &p, + &rembufsize, &buf, &bufsize); + + if (modified) { + new_len = p - buf; + err = fdt_setprop_placeholder_memcheck(fdto, offset, + label, len, new_len, &q); + if (err) { + free(buf); + return err; + } + + memcpy(q, buf, new_len); + } + + free(buf); + } + + return 0; +} + +/** + * rename_nodes - Rename all fragement@xyz nodes + * + * @fdto - pointer to pointer that points to device-tree blob + * @delta - increment to be added to fragment number + */ +static int rename_nodes(void **fdto, unsigned long delta) +{ + int offset = -1, err; + const char *name, *idx; + char *stop = NULL; + int len; + char new_name[PATH_MAX]; + unsigned long index, new_index; + + do { + offset = fdt_next_node(*fdto, offset, NULL); + if (offset < 0) + break; + + name = fdt_get_name(*fdto, offset, &len); + if (len > 9 && !strncmp(name, "fragment@", 9)) { + idx = name + 9; + stop = NULL; + index = strtoul(idx, &stop, 10); + if (ULONG_MAX - delta < index) + return -FDT_ERR_BADSTATE; + new_index = index + delta; + snprintf(new_name, sizeof(new_name), + "fragment@%lu", new_index); + + err = fdt_set_name_memcheck(fdto, offset, + len, new_name); + if (err < 0) + return err; + } + } while (1); + + return 0; +} + +/* + * In case of overlaying onto a base blob which itself has unresolved external + * phandle references, we will need to merge overlay fragments found in overlay + * blob into root node of base blob, which can lead to naming conflicts for + * nodes. To prevent such conflicts, rename all occurence of fragment@xyz in + * overlay blob with fragment@abc (abc = xyz + delta) + */ +static int rename_fragments(void **fdto, unsigned long delta) +{ + int rc; + + rc = rename_nodes(fdto, delta); + if (rc < 0) { + dprintf("rename_nodes failed %d\n", rc); + return rc; + } + + rc = rename_fragments_in_section(fdto, "/__symbols__", delta); + if (rc < 0 && rc != -FDT_ERR_NOTFOUND) { + dprintf("rename of fragments in __symbols__ section " + "failed %d\n", rc); + return rc; + } + + rc = rename_fragments_in_section(fdto, "/__fixups__", delta); + if (rc < 0) { + dprintf("rename of fragments in __fixups__ section " + "failed %d\n", rc); + return rc; + } + + /* + * renaming fragments in __local_fixups__ section should be covered by + * rename_nodes() + */ + + return 0; +} + int fdt_overlay_apply(void **fdt, void **fdto) { uint32_t delta = fdt_get_max_phandle(*fdt); int ret; + unsigned long max_fdto_fragments; FDT_CHECK_HEADER(*fdt); FDT_CHECK_HEADER(*fdto); dprintf("Adjusting local phandles\n"); + /* Does base blob have unresolved external references? */ + if (!base_fixup_search_done) { + if (fdt_path_offset(*fdt, "/__fixups__") >= 0) + base_has_fixups = 1; + base_fixup_search_done = 1; + } + + if (base_has_fixups) { + if (!max_base_fragments) + max_base_fragments = count_fragments(*fdt) + 1; + if ((long)max_base_fragments < 0) + return -FDT_ERR_BADSTATE; + max_fdto_fragments = count_fragments(*fdto) + 1; + if ((long)max_fdto_fragments < 0) + return -FDT_ERR_BADSTATE; + dprintf("max_base fragments %lu, max_overlay fragments %lu\n", + max_base_fragments, max_fdto_fragments); + ret = rename_fragments(fdto, max_base_fragments); + if (ret < 0) { + dprintf("rename_fragments failed %d\n", ret); + return ret; + } + max_base_fragments += max_fdto_fragments; + } + ret = overlay_adjust_local_phandles(*fdto, delta); if (ret) goto err; -- 2.7.4 -- QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, hosted by The Linux Foundation -- To unsubscribe from this list: send the line "unsubscribe devicetree-compiler" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html