summaryrefslogtreecommitdiffstats
path: root/drivers/of/fdt.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/of/fdt.c')
-rw-r--r--drivers/of/fdt.c343
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;
+}