diff options
Diffstat (limited to 'drivers/of/fdt.c')
-rw-r--r-- | drivers/of/fdt.c | 343 |
1 files changed, 261 insertions, 82 deletions
diff --git a/drivers/of/fdt.c b/drivers/of/fdt.c index cf3f1ee147..8dca41990c 100644 --- a/drivers/of/fdt.c +++ b/drivers/of/fdt.c @@ -1,21 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * fdt.c - flat devicetree functions * * Copyright (c) 2013 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix * * based on Linux devicetree support - * - * See file CREDITS for list of people who contributed to this - * project. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. */ #include <common.h> #include <of.h> @@ -25,8 +14,24 @@ #include <memory.h> #include <linux/sizes.h> #include <linux/ctype.h> +#include <linux/log2.h> +#include <linux/overflow.h> +#include <linux/string_helpers.h> #include <linux/err.h> +static inline bool __dt_ptr_ok(const struct fdt_header *fdt, const void *p, + unsigned elem_size, unsigned elem_align) +{ + if (!p || (const void *)fdt > p || !PTR_IS_ALIGNED(p, elem_align) || + p + elem_size > (const void *)fdt + be32_to_cpu(fdt->totalsize)) { + pr_err("unflatten: offset overflows or misaligns FDT\n"); + return false; + } + + return true; +} +#define dt_ptr_ok(fdt, p) __dt_ptr_ok(fdt, p, sizeof(*(p)), __alignof__(*(p))) + static inline uint32_t dt_struct_advance(struct fdt_header *f, uint32_t dt, int size) { dt += size; @@ -38,29 +43,40 @@ static inline uint32_t dt_struct_advance(struct fdt_header *f, uint32_t dt, int return dt; } -static inline char *dt_string(struct fdt_header *f, char *strstart, uint32_t ofs) +static inline const char *dt_string(struct fdt_header *f, const char *strstart, uint32_t ofs) { + const char *str; + if (ofs > f->size_dt_strings) return NULL; - else - return strstart + ofs; + + str = strstart + ofs; + + return string_is_terminated(str, f->size_dt_strings - ofs) ? str : NULL; } static int of_reservemap_num_entries(const struct fdt_header *fdt) { - const struct fdt_reserve_entry *r; + /* + * FDT may violate spec mandated 8-byte alignment if unflattening it out of + * a FIT image property, so play it safe here. + */ + const struct fdt_reserve_entry_unaligned { + fdt64_t address; + fdt64_t size; + } __packed *r; int n = 0; r = (void *)fdt + be32_to_cpu(fdt->off_mem_rsvmap); - while (r->size) { + while (dt_ptr_ok(fdt, r) && r->size) { n++; r++; if (n == OF_MAX_RESERVE_MAP) return -EINVAL; } - return n; + return r->size == 0 ? n : -ESPIPE; } /** @@ -81,7 +97,6 @@ static int of_unflatten_reservemap(struct device_node *root, int n; struct property *p; struct device_node *memreserve; - __be32 cells; n = of_reservemap_num_entries(fdt); if (n <= 0) @@ -91,16 +106,6 @@ static int of_unflatten_reservemap(struct device_node *root, if (!memreserve) return -ENOMEM; - cells = cpu_to_be32(2); - - p = of_new_property(memreserve, "#address-cells", &cells, sizeof(__be32)); - if (!p) - return -ENOMEM; - - p = of_new_property(memreserve, "#size-cells", &cells, sizeof(__be32)); - if (!p) - return -ENOMEM; - p = of_new_property(memreserve, "reg", (void *)fdt + be32_to_cpu(fdt->off_mem_rsvmap), n * sizeof(struct fdt_reserve_entry)); @@ -110,6 +115,44 @@ static int of_unflatten_reservemap(struct device_node *root, return 0; } +static int fdt_parse_header(const struct fdt_header *fdt, size_t fdt_size, + struct fdt_header *out) +{ + if (fdt_size < sizeof(struct fdt_header)) + return -EINVAL; + + if (fdt->magic != cpu_to_fdt32(FDT_MAGIC)) { + pr_err("bad magic: 0x%08x\n", fdt32_to_cpu(fdt->magic)); + return -EINVAL; + } + + if (fdt->version != cpu_to_fdt32(17)) { + pr_err("bad dt version: 0x%08x\n", fdt32_to_cpu(fdt->version)); + return -EINVAL; + } + + out->totalsize = fdt32_to_cpu(fdt->totalsize); + out->off_dt_struct = fdt32_to_cpu(fdt->off_dt_struct); + out->size_dt_struct = fdt32_to_cpu(fdt->size_dt_struct); + out->off_dt_strings = fdt32_to_cpu(fdt->off_dt_strings); + out->size_dt_strings = fdt32_to_cpu(fdt->size_dt_strings); + + if (out->totalsize > fdt_size) + return -EINVAL; + + if (size_add(out->off_dt_struct, out->size_dt_struct) > out->totalsize) { + pr_err("unflatten: dt size exceeds total size\n"); + return -ESPIPE; + } + + if (size_add(out->off_dt_strings, out->size_dt_strings) > out->totalsize) { + pr_err("unflatten: string size exceeds total size\n"); + return -ESPIPE; + } + + return 0; +} + /** * of_unflatten_dtb - unflatten a dtb binary blob * @infdt - the fdt blob to unflatten @@ -117,7 +160,8 @@ static int of_unflatten_reservemap(struct device_node *root, * Parse a flat device tree binary blob and return a pointer to the * unflattened tree. */ -static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops) +static struct device_node *__of_unflatten_dtb(const void *infdt, int size, + bool constprops) { const void *nodep; /* property node pointer */ uint32_t tag; /* tag */ @@ -134,31 +178,9 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops unsigned int maxlen; const struct fdt_header *fdt = infdt; - if (fdt->magic != cpu_to_fdt32(FDT_MAGIC)) { - pr_err("bad magic: 0x%08x\n", fdt32_to_cpu(fdt->magic)); - return ERR_PTR(-EINVAL); - } - - if (fdt->version != cpu_to_fdt32(17)) { - pr_err("bad dt version: 0x%08x\n", fdt32_to_cpu(fdt->version)); - return ERR_PTR(-EINVAL); - } - - f.totalsize = fdt32_to_cpu(fdt->totalsize); - f.off_dt_struct = fdt32_to_cpu(fdt->off_dt_struct); - f.size_dt_struct = fdt32_to_cpu(fdt->size_dt_struct); - f.off_dt_strings = fdt32_to_cpu(fdt->off_dt_strings); - f.size_dt_strings = fdt32_to_cpu(fdt->size_dt_strings); - - if (f.off_dt_struct + f.size_dt_struct > f.totalsize) { - pr_err("unflatten: dt size exceeds total size\n"); - return ERR_PTR(-ESPIPE); - } - - if (f.off_dt_strings + f.size_dt_strings > f.totalsize) { - pr_err("unflatten: string size exceeds total size\n"); - return ERR_PTR(-ESPIPE); - } + ret = fdt_parse_header(infdt, size, &f); + if (ret < 0) + return ERR_PTR(ret); dt_struct = f.off_dt_struct; dt_strings = (void *)fdt + f.off_dt_strings; @@ -172,7 +194,13 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops goto err; while (1) { - tag = be32_to_cpu(*(uint32_t *)(infdt + dt_struct)); + __be32 *tagp = (uint32_t *)(infdt + dt_struct); + if (!dt_ptr_ok(infdt, tagp)) { + ret = -ESPIPE; + goto err; + } + + tag = be32_to_cpu(*tagp); switch (tag) { case FDT_BEGIN_NODE: @@ -187,10 +215,21 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops goto err; } - if (!node) + if (!node) { + /* The root node must have an empty name */ + if (*pathp) { + ret = -EINVAL; + goto err; + } node = root; - else + } else { + /* Only the root node may have an empty name */ + if (!*pathp) { + ret = -EINVAL; + goto err; + } node = of_new_node(node, pathp); + } dt_struct = dt_struct_advance(&f, dt_struct, sizeof(struct fdt_node_header) + len + 1); @@ -216,7 +255,7 @@ static struct device_node *__of_unflatten_dtb(const void *infdt, bool constprops nodep = fdt_prop->data; name = dt_string(&f, dt_strings, fdt32_to_cpu(fdt_prop->nameoff)); - if (!name) { + if (!name || !node) { ret = -ESPIPE; goto err; } @@ -266,9 +305,9 @@ err: * Parse a flat device tree binary blob and return a pointer to the unflattened * tree. The tree must be freed after use with of_delete_node(). */ -struct device_node *of_unflatten_dtb(const void *infdt) +struct device_node *of_unflatten_dtb(const void *infdt, int size) { - return __of_unflatten_dtb(infdt, false); + return __of_unflatten_dtb(infdt, size, false); } /** @@ -282,9 +321,9 @@ struct device_node *of_unflatten_dtb(const void *infdt) * whole lifetime of the returned tree. This is normally not what you want, so * use of_unflatten_dtb() instead. */ -struct device_node *of_unflatten_dtb_const(const void *infdt) +struct device_node *of_unflatten_dtb_const(const void *infdt, int size) { - return __of_unflatten_dtb(infdt, true); + return __of_unflatten_dtb(infdt, size, true); } struct fdt { @@ -306,15 +345,15 @@ static int lstrcpy(char *dest, const char *src) int len = 0; int maxlen = 1023; - while (*src) { - *dest++ = *src++; + do { + *dest++ = *src; len++; if (!maxlen) return -ENOSPC; maxlen--; - } + } while (*src++); - return len; + return len - 1; } static void *memalign_realloc(void *orig, size_t oldsize, size_t newsize) @@ -347,24 +386,41 @@ static void *memalign_realloc(void *orig, size_t oldsize, size_t newsize) static int fdt_ensure_space(struct fdt *fdt, int dtsize) { + size_t new_size; + void *previous; + /* * We assume strings and names have a maximum length of 1024 * whereas properties can be longer. We allocate new memory * if we have less than 1024 bytes (+ the property size left. */ if (fdt->str_size - fdt->str_nextofs < 1024) { - fdt->strings = realloc(fdt->strings, fdt->str_size * 2); - if (!fdt->strings) + previous = fdt->strings; + new_size = fdt->str_size * 2; + + fdt->strings = realloc(previous, new_size); + if (!fdt->strings) { + free(previous); return -ENOMEM; - fdt->str_size *= 2; + } + + fdt->str_size = new_size; } if (fdt->dt_size - fdt->dt_nextofs < 1024 + dtsize) { - fdt->dt = memalign_realloc(fdt->dt, fdt->dt_size, - fdt->dt_size * 2); - if (!fdt->dt) + previous = fdt->dt; + new_size = fdt->dt_size * 2; + + if (new_size <= dtsize) + new_size = roundup_pow_of_two(fdt->dt_size + dtsize); + + fdt->dt = memalign_realloc(previous, fdt->dt_size, new_size); + if (!fdt->dt) { + free(previous); return -ENOMEM; - fdt->dt_size *= 2; + } + + fdt->dt_size = new_size; } return 0; @@ -479,7 +535,7 @@ void *of_flatten_dtb(struct device_node *node) if (ret) goto out_free; - memreserve = of_find_node_by_name(node, "memreserve"); + memreserve = of_find_node_by_name_address(node, "memreserve"); if (memreserve) { const void *entries = of_get_property(memreserve, "reg", &len); @@ -558,9 +614,7 @@ void of_clean_reserve_map(void) * @__fdt: The devicetree blob * * This adds the reservemap entries previously collected in - * of_add_reserve_entry() to a devicetree binary blob. This also - * adds the devicetree itself to the reserved list, so after calling - * this function the tree should not be relocated anymore. + * of_add_reserve_entry() to a devicetree binary blob. */ void fdt_add_reserve_map(void *__fdt) { @@ -588,10 +642,135 @@ void fdt_add_reserve_map(void *__fdt) fdt_res++; } - of_write_number(&fdt_res->address, (unsigned long)__fdt, 2); - of_write_number(&fdt_res->size, be32_to_cpu(fdt->totalsize), 2); - fdt_res++; - of_write_number(&fdt_res->address, 0, 2); of_write_number(&fdt_res->size, 0, 2); } + +void fdt_print_reserve_map(const void *__fdt) +{ + const struct fdt_header *fdt = __fdt; + const struct fdt_reserve_entry *fdt_res = + __fdt + be32_to_cpu(fdt->off_mem_rsvmap); + int n = 0; + + while (1) { + uint64_t size = fdt64_to_cpu(fdt_res->size); + uint64_t address = fdt64_to_cpu(fdt_res->address); + + if (!size) + break; + + printf("/memreserve/ #%d: 0x%08llx - 0x%08llx\n", n, address, address + size - 1); + + n++; + fdt_res++; + if (n == OF_MAX_RESERVE_MAP) + return; + } +} + +static int fdt_string_is_compatible(const char *haystack, int haystack_len, + const char *needle, int needle_len) +{ + const char *p; + int index = 0; + + while (haystack_len >= needle_len) { + if (memcmp(needle, haystack, needle_len + 1) == 0) + return OF_DEVICE_COMPATIBLE_MAX_SCORE - (index << 2); + + p = memchr(haystack, '\0', haystack_len); + if (!p) + return 0; + haystack_len -= (p - haystack) + 1; + haystack = p + 1; + index++; + } + + return 0; +} + +int fdt_machine_is_compatible(const struct fdt_header *fdt, size_t fdt_size, const char *compat) +{ + uint32_t tag; + const struct fdt_property *fdt_prop; + const char *name; + uint32_t dt_struct; + const struct fdt_node_header *fnh; + const void *dt_strings; + struct fdt_header f; + int ret, len; + int expect = FDT_BEGIN_NODE; + int compat_len = strlen(compat); + + ret = fdt_parse_header(fdt, fdt_size, &f); + if (ret < 0) + return 0; + + dt_struct = f.off_dt_struct; + dt_strings = (const void *)fdt + f.off_dt_strings; + + while (1) { + const __be32 *tagp = (const void *)fdt + dt_struct; + if (!dt_ptr_ok(fdt, tagp)) + return 0; + + tag = be32_to_cpu(*tagp); + if (tag != FDT_NOP && tag != expect) + return 0; + + switch (tag) { + case FDT_BEGIN_NODE: + fnh = (const void *)fdt + dt_struct; + + /* The root node must have an empty name */ + if (fnh->name[0] != '\0') + return 0; + + dt_struct = dt_struct_advance(&f, dt_struct, + sizeof(struct fdt_node_header) + 1); + + /* + * Quoting Device Tree Specification v0.4 ยง5.4.2: + * + * [T]his process requires that all property definitions for + * a particular node precede any subnode definitions for that + * node. Although the structure would not be ambiguous if + * properties and subnodes were intermingled, the code needed + * to process a flat tree is simplified by this requirement. + * + * So let's make use of this simplification. + */ + expect = FDT_PROP; + break; + + case FDT_PROP: + fdt_prop = (const void *)fdt + dt_struct; + len = fdt32_to_cpu(fdt_prop->len); + + name = dt_string(&f, dt_strings, fdt32_to_cpu(fdt_prop->nameoff)); + if (!name) + return 0; + + if (strcmp(name, "compatible")) { + dt_struct = dt_struct_advance(&f, dt_struct, + sizeof(struct fdt_property) + len); + break; + } + + return fdt_string_is_compatible(fdt_prop->data, len, compat, compat_len); + + case FDT_NOP: + dt_struct = dt_struct_advance(&f, dt_struct, FDT_TAGSIZE); + break; + + default: + return 0; + } + + if (!dt_struct) + return 0; + } + + return 0; +} |