From 02464e386bb5f0a022c121f95ae75cf583759d95 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Thu, 6 Aug 2015 15:31:02 -0600 Subject: [PATCH] fdt: add new fdt address parsing functions fdtdec_get_addr_size() hard-codes the number of cells used to represent an address or size in DT. This is incorrect in many cases depending on the DT binding for a particular node or property (e.g. it is incorrect for the "reg" property). In most cases, DT parsing code must use the properties #address-cells and #size-cells to parse addres properties. This change splits up the implementation of fdtdec_get_addr_size() so that the core logic can be used for both hard-coded and non-hard-coded cases. Various wrapper functions are implemented that support cases where hard-coded cell counts should or should not be used, and where the client does and doesn't know the parent node ID that contains the properties #address-cells and #size-cells. dev_get_addr() is updated to use the new functions. Core functionality in fdtdec_get_addr_size_fixed() is widely tested via fdtdec_get_addr_size(). I tested fdtdec_get_addr_size_auto_noparent() and dev_get_addr() by manually modifying the Tegra I2C driver to invoke them. Much of the core implementation of fdtdec_get_addr_size_fixed(), fdtdec_get_addr_size_auto_parent(), and fdtdec_get_addr_size_auto_noparent() comes from Thierry Reding's previous commit "fdt: Fix fdtdec_get_addr_size() for 64-bit". Based-on-work-by: Thierry Reding Cc: Thierry Reding Cc: Simon Glass Cc: Michal Suchanek Signed-off-by: Stephen Warren Acked-by: Simon Glass Dropped #define DEBUG at the top of fdtdec.c: Signed-off-by: Simon Glass --- drivers/core/device.c | 5 +- include/fdtdec.h | 111 +++++++++++++++++++++++++++++++++++++--- lib/fdtdec.c | 114 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 200 insertions(+), 30 deletions(-) diff --git a/drivers/core/device.c b/drivers/core/device.c index 0ccd443f25..0bc04d4876 100644 --- a/drivers/core/device.c +++ b/drivers/core/device.c @@ -581,7 +581,10 @@ fdt_addr_t dev_get_addr(struct udevice *dev) #if CONFIG_IS_ENABLED(OF_CONTROL) fdt_addr_t addr; - addr = fdtdec_get_addr(gd->fdt_blob, dev->of_offset, "reg"); + addr = fdtdec_get_addr_size_auto_parent(gd->fdt_blob, + dev->parent->of_offset, + dev->of_offset, "reg", + 0, NULL); if (CONFIG_IS_ENABLED(SIMPLE_BUS) && addr != FDT_ADDR_T_NONE) { if (device_get_uclass_id(dev->parent) == UCLASS_SIMPLE_BUS) addr = simple_bus_translate(dev->parent, addr); diff --git a/include/fdtdec.h b/include/fdtdec.h index a422ecedb8..2de6dda04c 100644 --- a/include/fdtdec.h +++ b/include/fdtdec.h @@ -295,10 +295,90 @@ int fdtdec_next_compatible(const void *blob, int node, int fdtdec_next_compatible_subnode(const void *blob, int node, enum fdt_compat_id id, int *depthp); -/** - * Look up an address property in a node and return it as an address. - * The property must hold either one address with no trailing data or - * one address with a length. This is only tested on 32-bit machines. +/* + * Look up an address property in a node and return the parsed address, and + * optionally the parsed size. + * + * This variant assumes a known and fixed number of cells are used to + * represent the address and size. + * + * You probably don't want to use this function directly except to parse + * non-standard properties, and never to parse the "reg" property. Instead, + * use one of the "auto" variants below, which automatically honor the + * #address-cells and #size-cells properties in the parent node. + * + * @param blob FDT blob + * @param node node to examine + * @param prop_name name of property to find + * @param index which address to retrieve from a list of addresses. Often 0. + * @param na the number of cells used to represent an address + * @param ns the number of cells used to represent a size + * @param sizep a pointer to store the size into. Use NULL if not required + * @return address, if found, or FDT_ADDR_T_NONE if not + */ +fdt_addr_t fdtdec_get_addr_size_fixed(const void *blob, int node, + const char *prop_name, int index, int na, int ns, + fdt_size_t *sizep); + +/* + * Look up an address property in a node and return the parsed address, and + * optionally the parsed size. + * + * This variant automatically determines the number of cells used to represent + * the address and size by parsing the provided parent node's #address-cells + * and #size-cells properties. + * + * @param blob FDT blob + * @param parent parent node of @node + * @param node node to examine + * @param prop_name name of property to find + * @param index which address to retrieve from a list of addresses. Often 0. + * @param sizep a pointer to store the size into. Use NULL if not required + * @return address, if found, or FDT_ADDR_T_NONE if not + */ +fdt_addr_t fdtdec_get_addr_size_auto_parent(const void *blob, int parent, + int node, const char *prop_name, int index, fdt_size_t *sizep); + +/* + * Look up an address property in a node and return the parsed address, and + * optionally the parsed size. + * + * This variant automatically determines the number of cells used to represent + * the address and size by parsing the parent node's #address-cells + * and #size-cells properties. The parent node is automatically found. + * + * The automatic parent lookup implemented by this function is slow. + * Consequently, fdtdec_get_addr_size_auto_parent() should be used where + * possible. + * + * @param blob FDT blob + * @param parent parent node of @node + * @param node node to examine + * @param prop_name name of property to find + * @param index which address to retrieve from a list of addresses. Often 0. + * @param sizep a pointer to store the size into. Use NULL if not required + * @return address, if found, or FDT_ADDR_T_NONE if not + */ +fdt_addr_t fdtdec_get_addr_size_auto_noparent(const void *blob, int node, + const char *prop_name, int index, fdt_size_t *sizep); + +/* + * Look up an address property in a node and return the parsed address. + * + * This variant hard-codes the number of cells used to represent the address + * and size based on sizeof(fdt_addr_t) and sizeof(fdt_size_t). It also + * always returns the first address value in the property (index 0). + * + * Use of this function is not recommended due to the hard-coding of cell + * counts. There is no programmatic validation that these hard-coded values + * actually match the device tree content in any way at all. This assumption + * can be satisfied by manually ensuring CONFIG_PHYS_64BIT is appropriately + * set in the U-Boot build and exercising strict control over DT content to + * ensure use of matching #address-cells/#size-cells properties. However, this + * approach is error-prone; those familiar with DT will not expect the + * assumption to exist, and could easily invalidate it. If the assumption is + * invalidated, this function will not report the issue, and debugging will + * be required. Instead, use fdtdec_get_addr_size_auto_parent(). * * @param blob FDT blob * @param node node to examine @@ -308,14 +388,29 @@ int fdtdec_next_compatible_subnode(const void *blob, int node, fdt_addr_t fdtdec_get_addr(const void *blob, int node, const char *prop_name); -/** - * Look up an address property in a node and return it as an address. - * The property must hold one address with a length. This is only tested - * on 32-bit machines. +/* + * Look up an address property in a node and return the parsed address, and + * optionally the parsed size. + * + * This variant hard-codes the number of cells used to represent the address + * and size based on sizeof(fdt_addr_t) and sizeof(fdt_size_t). It also + * always returns the first address value in the property (index 0). + * + * Use of this function is not recommended due to the hard-coding of cell + * counts. There is no programmatic validation that these hard-coded values + * actually match the device tree content in any way at all. This assumption + * can be satisfied by manually ensuring CONFIG_PHYS_64BIT is appropriately + * set in the U-Boot build and exercising strict control over DT content to + * ensure use of matching #address-cells/#size-cells properties. However, this + * approach is error-prone; those familiar with DT will not expect the + * assumption to exist, and could easily invalidate it. If the assumption is + * invalidated, this function will not report the issue, and debugging will + * be required. Instead, use fdtdec_get_addr_size_auto_parent(). * * @param blob FDT blob * @param node node to examine * @param prop_name name of property to find + * @param sizep a pointer to store the size into. Use NULL if not required * @return address, if found, or FDT_ADDR_T_NONE if not */ fdt_addr_t fdtdec_get_addr_size(const void *blob, int node, diff --git a/lib/fdtdec.c b/lib/fdtdec.c index 5770094fbf..9f0b65de38 100644 --- a/lib/fdtdec.c +++ b/lib/fdtdec.c @@ -86,32 +86,104 @@ const char *fdtdec_get_compatible(enum fdt_compat_id id) return compat_names[id]; } -fdt_addr_t fdtdec_get_addr_size(const void *blob, int node, - const char *prop_name, fdt_size_t *sizep) +fdt_addr_t fdtdec_get_addr_size_fixed(const void *blob, int node, + const char *prop_name, int index, int na, int ns, + fdt_size_t *sizep) { - const fdt_addr_t *cell; + const fdt32_t *prop, *prop_end; + const fdt32_t *prop_addr, *prop_size, *prop_after_size; int len; + fdt_addr_t addr; debug("%s: %s: ", __func__, prop_name); - cell = fdt_getprop(blob, node, prop_name, &len); - if (cell && ((!sizep && len == sizeof(fdt_addr_t)) || - len == sizeof(fdt_addr_t) * 2)) { - fdt_addr_t addr = fdt_addr_to_cpu(*cell); - if (sizep) { - const fdt_size_t *size; - - size = (fdt_size_t *)((char *)cell + - sizeof(fdt_addr_t)); - *sizep = fdt_size_to_cpu(*size); - debug("addr=%08lx, size=%llx\n", - (ulong)addr, (u64)*sizep); - } else { - debug("%08lx\n", (ulong)addr); - } - return addr; + + if (na > (sizeof(fdt_addr_t) / sizeof(fdt32_t))) { + debug("(na too large for fdt_addr_t type)\n"); + return FDT_ADDR_T_NONE; } - debug("(not found)\n"); - return FDT_ADDR_T_NONE; + + if (ns > (sizeof(fdt_size_t) / sizeof(fdt32_t))) { + debug("(ns too large for fdt_size_t type)\n"); + return FDT_ADDR_T_NONE; + } + + prop = fdt_getprop(blob, node, prop_name, &len); + if (!prop) { + debug("(not found)\n"); + return FDT_ADDR_T_NONE; + } + prop_end = prop + (len / sizeof(*prop)); + + prop_addr = prop + (index * (na + ns)); + prop_size = prop_addr + na; + prop_after_size = prop_size + ns; + if (prop_after_size > prop_end) { + debug("(not enough data: expected >= %d cells, got %d cells)\n", + (u32)(prop_after_size - prop), ((u32)(prop_end - prop))); + return FDT_ADDR_T_NONE; + } + + addr = fdtdec_get_number(prop_addr, na); + + if (sizep) { + *sizep = fdtdec_get_number(prop_size, ns); + debug("addr=%08llx, size=%llx\n", (u64)addr, (u64)*sizep); + } else { + debug("addr=%08llx\n", (u64)addr); + } + + return addr; +} + +fdt_addr_t fdtdec_get_addr_size_auto_parent(const void *blob, int parent, + int node, const char *prop_name, int index, fdt_size_t *sizep) +{ + int na, ns; + + debug("%s: ", __func__); + + na = fdt_address_cells(blob, parent); + if (na < 1) { + debug("(bad #address-cells)\n"); + return FDT_ADDR_T_NONE; + } + + ns = fdt_size_cells(blob, parent); + if (ns < 1) { + debug("(bad #size-cells)\n"); + return FDT_ADDR_T_NONE; + } + + debug("na=%d, ns=%d, ", na, ns); + + return fdtdec_get_addr_size_fixed(blob, node, prop_name, index, na, + ns, sizep); +} + +fdt_addr_t fdtdec_get_addr_size_auto_noparent(const void *blob, int node, + const char *prop_name, int index, fdt_size_t *sizep) +{ + int parent; + + debug("%s: ", __func__); + + parent = fdt_parent_offset(blob, node); + if (parent < 0) { + debug("(no parent found)\n"); + return FDT_ADDR_T_NONE; + } + + return fdtdec_get_addr_size_auto_parent(blob, parent, node, prop_name, + index, sizep); +} + +fdt_addr_t fdtdec_get_addr_size(const void *blob, int node, + const char *prop_name, fdt_size_t *sizep) +{ + return fdtdec_get_addr_size_fixed(blob, node, prop_name, 0, + sizeof(fdt_addr_t) / sizeof(fdt32_t), + sizeof(fdt_size_t) / sizeof(fdt32_t), + sizep); } fdt_addr_t fdtdec_get_addr(const void *blob, int node, -- 2.39.5