summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/include/device_tree.h29
-rw-r--r--src/lib/device_tree.c438
2 files changed, 422 insertions, 45 deletions
diff --git a/src/include/device_tree.h b/src/include/device_tree.h
index e7b79e1a94..6d2d65600f 100644
--- a/src/include/device_tree.h
+++ b/src/include/device_tree.h
@@ -4,6 +4,7 @@
#ifndef __DEVICE_TREE_H__
#define __DEVICE_TREE_H__
+#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <commonlib/list.h>
@@ -33,6 +34,7 @@ struct fdt_header {
#define FDT_TOKEN_BEGIN_NODE 1
#define FDT_TOKEN_END_NODE 2
#define FDT_TOKEN_PROPERTY 3
+#define FDT_TOKEN_NOP 4
#define FDT_TOKEN_END 9
#define FDT_PHANDLE_ILLEGAL 0xdeadbeef
@@ -47,6 +49,11 @@ struct fdt_property
* Unflattened device tree structures.
*/
+struct device_tree_region {
+ u64 addr;
+ u64 size;
+};
+
struct device_tree_property
{
struct fdt_property prop;
@@ -91,6 +98,8 @@ struct device_tree
* which were consumed reading the requested value.
*/
+/* Checks if blob points to a valid FDT */
+bool fdt_is_valid(const void *blob);
/* Read the property at offset, if any exists. */
int fdt_next_property(const void *blob, uint32_t offset,
struct fdt_property *prop);
@@ -100,6 +109,26 @@ int fdt_node_name(const void *blob, uint32_t offset, const char **name);
void fdt_print_node(const void *blob, uint32_t offset);
int fdt_skip_node(const void *blob, uint32_t offset);
+/* Read property and put into fdt_prop. Returns offset to property */
+u32 fdt_read_prop(const void *blob, u32 node_offset, const char *prop_name,
+ struct fdt_property *fdt_prop);
+/* Read reg property and save regions inside 'regions'. Returns number of regions read */
+u32 fdt_read_reg_prop(const void *blob, u32 node_offset, u32 addr_cells, u32 size_cells,
+ struct device_tree_region regions[], size_t regions_count);
+/* Find a node by a given path and return the offset */
+u32 fdt_find_node_by_path(const void *blob, const char *path, u32 *addrcp, u32 *sizecp);
+/* Find multiple nodes matching a given pattern. Returns number of nodes found */
+size_t fdt_find_subnodes_by_prefix(const void *blob, u32 node_offset, const char *prefix,
+ u32 *addrcp, u32 *sizecp, u32 results[], size_t results_len);
+/* Find a node by a given alias and return its offset */
+u32 fdt_find_node_by_alias(const void *blob, const char *alias_name,
+ u32 *addr_cells, u32 *size_cells);
+/*
+ * Read the node name into 'name' of the node behind 'node_offset'
+ * and return total bytes used for name
+ */
+int fdt_next_node_name(const void *blob, uint32_t node_offset, const char **name);
+
/* Read a flattened device tree into a hierarchical structure which refers to
the contents of the flattened tree in place. Modifying the flat tree
invalidates the unflattened one. */
diff --git a/src/lib/device_tree.c b/src/lib/device_tree.c
index a36798619b..2bfd5dd376 100644
--- a/src/lib/device_tree.c
+++ b/src/lib/device_tree.c
@@ -6,21 +6,39 @@
#include <ctype.h>
#include <device_tree.h>
#include <endian.h>
+#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
+#define FDT_PATH_MAX_DEPTH 10 // should be a good enough upper bound
+#define FDT_PATH_MAX_LEN 128 // should be a good enough upper bound
+
/*
* Functions for picking apart flattened trees.
*/
+static int fdt_skip_nops(const void *blob, uint32_t offset)
+{
+ uint32_t *ptr = (uint32_t *)(((uint8_t *)blob) + offset);
+
+ int index = 0;
+ while (be32toh(ptr[index]) == FDT_TOKEN_NOP)
+ index++;
+
+ return index * sizeof(uint32_t);
+}
+
int fdt_next_property(const void *blob, uint32_t offset,
struct fdt_property *prop)
{
struct fdt_header *header = (struct fdt_header *)blob;
uint32_t *ptr = (uint32_t *)(((uint8_t *)blob) + offset);
+ // skip NOP tokens
+ offset += fdt_skip_nops(blob, offset);
+
int index = 0;
if (be32toh(ptr[index++]) != FDT_TOKEN_PROPERTY)
return 0;
@@ -40,24 +58,369 @@ int fdt_next_property(const void *blob, uint32_t offset,
return index * sizeof(uint32_t);
}
-int fdt_node_name(const void *blob, uint32_t offset, const char **name)
+/*
+ * fdt_next_node_name reads a node name
+ *
+ * @params blob address of FDT
+ * @params offset offset to the node to read the name from
+ * @params name parameter to hold the name that has been read or NULL
+ *
+ * @returns Either 0 on error or offset to the properties that come after the node name
+ */
+int fdt_next_node_name(const void *blob, uint32_t offset, const char **name)
{
- uint8_t *ptr = ((uint8_t *)blob) + offset;
+ // skip NOP tokens
+ offset += fdt_skip_nops(blob, offset);
+
+ char *ptr = ((char *)blob) + offset;
if (be32dec(ptr) != FDT_TOKEN_BEGIN_NODE)
return 0;
ptr += 4;
if (name)
- *name = (char *)ptr;
- return ALIGN_UP(strlen((char *)ptr) + 1, sizeof(uint32_t)) + 4;
+ *name = ptr;
+
+ return ALIGN_UP(strlen(ptr) + 1, 4) + 4;
}
-static int dt_prop_is_phandle(struct device_tree_property *prop)
+/*
+ * A utility function to skip past nodes in flattened trees.
+ */
+int fdt_skip_node(const void *blob, uint32_t start_offset)
{
- return !(strcmp("phandle", prop->prop.name) &&
- strcmp("linux,phandle", prop->prop.name));
+ uint32_t offset = start_offset;
+
+ const char *name;
+ int size = fdt_next_node_name(blob, offset, &name);
+ if (!size)
+ return 0;
+ offset += size;
+
+ while ((size = fdt_next_property(blob, offset, NULL)))
+ offset += size;
+
+ while ((size = fdt_skip_node(blob, offset)))
+ offset += size;
+
+ // skip NOP tokens
+ offset += fdt_skip_nops(blob, offset);
+
+ return offset - start_offset + sizeof(uint32_t);
+}
+
+/*
+ * fdt_read_prop reads a property inside a node
+ *
+ * @params blob address of FDT
+ * @params node_offset offset to the node to read the property from
+ * @params prop_name name of the property to read
+ * @params fdt_prop property is saved inside this parameter
+ *
+ * @returns Either 0 if no property has been found or an offset that points to the location
+ * of the property
+ */
+u32 fdt_read_prop(const void *blob, u32 node_offset, const char *prop_name,
+ struct fdt_property *fdt_prop)
+{
+ u32 offset = node_offset;
+
+ offset += fdt_next_node_name(blob, offset, NULL); // skip node name
+
+ size_t size;
+ while ((size = fdt_next_property(blob, offset, fdt_prop))) {
+ if (strcmp(fdt_prop->name, prop_name) == 0)
+ return offset;
+ offset += size;
+ }
+ return 0; // property not found
+}
+
+/*
+ * fdt_read_reg_prop reads the reg property inside a node
+ *
+ * @params blob address of FDT
+ * @params node_offset offset to the node to read the reg property from
+ * @params addr_cells number of cells used for one address
+ * @params size_cells number of cells used for one size
+ * @params regions all regions that are read inside the reg property are saved inside
+ * this array
+ * @params regions_count maximum number of entries that can be saved inside the regions array.
+ *
+ * Returns: Either 0 on error or returns the number of regions put into the regions array.
+ */
+u32 fdt_read_reg_prop(const void *blob, u32 node_offset, u32 addr_cells, u32 size_cells,
+ struct device_tree_region regions[], size_t regions_count)
+{
+ struct fdt_property prop;
+ u32 offset = fdt_read_prop(blob, node_offset, "reg", &prop);
+
+ if (!offset) {
+ printk(BIOS_DEBUG, "no reg property found in node_offset: %x\n", node_offset);
+ return 0;
+ }
+
+ // we found the reg property, now need to parse all regions in 'reg'
+ size_t count = prop.size / (4 * addr_cells + 4 * size_cells);
+ if (count > regions_count) {
+ printk(BIOS_ERR, "reg property at node_offset: %x has more entries (%zd) than regions array can hold (%zd)\n", node_offset, count, regions_count);
+ count = regions_count;
+ }
+ if (addr_cells > 2 || size_cells > 2) {
+ printk(BIOS_ERR, "addr_cells (%d) or size_cells (%d) bigger than 2\n",
+ addr_cells, size_cells);
+ return 0;
+ }
+ uint32_t *ptr = prop.data;
+ for (int i = 0; i < count; i++) {
+ if (addr_cells == 1)
+ regions[i].addr = be32dec(ptr);
+ else if (addr_cells == 2)
+ regions[i].addr = be64dec(ptr);
+ ptr += addr_cells;
+ if (size_cells == 1)
+ regions[i].size = be32dec(ptr);
+ else if (size_cells == 2)
+ regions[i].size = be64dec(ptr);
+ ptr += size_cells;
+ }
+
+ return count; // return the number of regions found in the reg property
}
+static u32 fdt_read_cell_props(const void *blob, u32 node_offset, u32 *addrcp, u32 *sizecp)
+{
+ struct fdt_property prop;
+ u32 offset = node_offset;
+ size_t size;
+ while ((size = fdt_next_property(blob, offset, &prop))) {
+ if (addrcp && !strcmp(prop.name, "#address-cells"))
+ *addrcp = be32dec(prop.data);
+ if (sizecp && !strcmp(prop.name, "#size-cells"))
+ *sizecp = be32dec(prop.data);
+ offset += size;
+ }
+ return offset;
+}
+
+/*
+ * fdt_find_node searches for a node relative to another node
+ *
+ * @params blob address of FDT
+ *
+ * @params parent_node_offset offset to node from which to traverse the tree
+ *
+ * @params path null terminated array of node names specifying a
+ * relative path (e.g: { "cpus", "cpu0", NULL })
+ *
+ * @params addrcp/sizecp If any address-cells and size-cells properties are found that are
+ * part of the parent node of the node we are looking, addrcp and sizecp
+ * are set to these respectively.
+ *
+ * @returns: Either 0 if no node has been found or the offset to the node found
+ */
+static u32 fdt_find_node(const void *blob, u32 parent_node_offset, char **path,
+ u32 *addrcp, u32 *sizecp)
+{
+ if (*path == NULL)
+ return parent_node_offset; // node found
+
+ size_t size = fdt_next_node_name(blob, parent_node_offset, NULL); // skip node name
+
+ /*
+ * get address-cells and size-cells properties while skipping the others.
+ * According to spec address-cells and size-cells are not inherited, but we
+ * intentionally follow the Linux implementation here and treat them as inheritable.
+ */
+ u32 node_offset = fdt_read_cell_props(blob, parent_node_offset + size, addrcp, sizecp);
+
+ const char *node_name;
+ // walk all children nodes
+ while ((size = fdt_next_node_name(blob, node_offset, &node_name))) {
+ if (!strcmp(*path, node_name)) {
+ // traverse one level deeper into the path
+ return fdt_find_node(blob, node_offset, path + 1, addrcp, sizecp);
+ }
+ // node is not the correct one. skip current node
+ node_offset += fdt_skip_node(blob, node_offset);
+ }
+
+ // we have searched everything and could not find a fitting node
+ return 0;
+}
+
+/*
+ * fdt_find_node_by_path finds a node behind a given node path
+ *
+ * @params blob address of FDT
+ * @params path absolute path to the node that should be searched for
+ *
+ * @params addrcp/sizecp Pointer that will be updated with any #address-cells and #size-cells
+ * value found in the node of the node specified by node_offset. Either
+ * may be NULL to ignore. If no #address-cells and #size-cells is found
+ * default values of #address-cells=2 and #size-cells=1 are returned.
+ *
+ * @returns Either 0 on error or the offset to the node found behind the path
+ */
+u32 fdt_find_node_by_path(const void *blob, const char *path, u32 *addrcp, u32 *sizecp)
+{
+ // sanity check
+ if (path[0] != '/') {
+ printk(BIOS_ERR, "devicetree path must start with a /\n");
+ return 0;
+ }
+ if (!blob) {
+ printk(BIOS_ERR, "devicetree blob is NULL\n");
+ return 0;
+ }
+
+ if (addrcp)
+ *addrcp = 2;
+ if (sizecp)
+ *sizecp = 1;
+
+ struct fdt_header *fdt_hdr = (struct fdt_header *)blob;
+
+ /*
+ * split path into separate nodes
+ * e.g: "/cpus/cpu0" -> { "cpus", "cpu0" }
+ */
+ char *path_array[FDT_PATH_MAX_DEPTH];
+ size_t path_size = strlen(path);
+ assert(path_size < FDT_PATH_MAX_LEN);
+ char path_copy[FDT_PATH_MAX_LEN];
+ memcpy(path_copy, path, path_size + 1);
+ char *cur = path_copy;
+ int i;
+ for (i = 0; i < FDT_PATH_MAX_DEPTH; i++) {
+ path_array[i] = strtok_r(NULL, "/", &cur);
+ if (!path_array[i])
+ break;
+ }
+ assert(i < FDT_PATH_MAX_DEPTH);
+
+ return fdt_find_node(blob, be32toh(fdt_hdr->structure_offset), path_array, addrcp, sizecp);
+}
+
+/*
+ * fdt_find_subnodes_by_prefix finds a node with a given prefix relative to a parent node
+ *
+ * @params blob The FDT to search.
+ *
+ * @params node_offset offset to the node of which the children should be searched
+ *
+ * @params prefix A string to search for a node with a given prefix. This can for example
+ * be 'cpu' to look for all nodes matching this prefix. Only children of
+ * node_offset are searched. Therefore in order to search all nodes matching
+ * the 'cpu' prefix, node_offset should probably point to the 'cpus' node.
+ * An empty prefix ("") searches for all children nodes of node_offset.
+ *
+ * @params addrcp/sizecp Pointer that will be updated with any #address-cells and #size-cells
+ * value found in the node of the node specified by node_offset. Either
+ * may be NULL to ignore. If no #address-cells and #size-cells is found
+ * addrcp and sizecp are left untouched.
+ *
+ * @params results Array of offsets pointing to each node matching the given prefix.
+ * @params results_len Number of entries allocated for the 'results' array
+ *
+ * @returns offset to last node found behind path or 0 if no node has been found
+ */
+size_t fdt_find_subnodes_by_prefix(const void *blob, u32 node_offset, const char *prefix,
+ u32 *addrcp, u32 *sizecp, u32 *results, size_t results_len)
+{
+ // sanity checks
+ if (!blob || !results || !prefix) {
+ printk(BIOS_ERR, "%s: input parameter cannot be null/\n", __func__);
+ return 0;
+ }
+
+ u32 offset = node_offset;
+
+ // we don't care about the name of the current node
+ u32 size = fdt_next_node_name(blob, offset, NULL);
+ if (!size) {
+ printk(BIOS_ERR, "%s: node_offset: %x does not point to a node\n",
+ __func__, node_offset);
+ return 0;
+ }
+ offset += size;
+
+ /*
+ * update addrcp and sizecp if the node contains an address-cells and size-cells
+ * property. Otherwise use addrcp and sizecp provided by caller.
+ */
+ offset = fdt_read_cell_props(blob, offset, addrcp, sizecp);
+
+ size_t count_results = 0;
+ int prefix_len = strlen(prefix);
+ const char *node_name;
+ // walk all children nodes of offset
+ while ((size = fdt_next_node_name(blob, offset, &node_name))) {
+
+ if (count_results >= results_len) {
+ printk(BIOS_WARNING,
+ "%s: results_len (%zd) smaller than count_results (%zd)\n",
+ __func__, results_len, count_results);
+ break;
+ }
+
+ if (!strncmp(prefix, node_name, prefix_len)) {
+ // we found a node that matches the prefix
+ results[count_results++] = offset;
+ }
+
+ // node does not match the prefix. skip current node
+ offset += fdt_skip_node(blob, offset);
+ }
+
+ // return last occurrence
+ return count_results;
+}
+
+static const char *fdt_read_alias_prop(const void *blob, const char *alias_name)
+{
+ u32 node_offset = fdt_find_node_by_path(blob, "/aliases", NULL, NULL);
+ if (!node_offset) {
+ printk(BIOS_DEBUG, "no /aliases node found\n");
+ return NULL;
+ }
+ struct fdt_property alias_prop;
+ if (!fdt_read_prop(blob, node_offset, alias_name, &alias_prop)) {
+ printk(BIOS_DEBUG, "property %s in /aliases node not found\n", alias_name);
+ return NULL;
+ }
+ return (const char *)alias_prop.data;
+}
+
+/*
+ * Find a node in the tree from a string device tree path.
+ *
+ * @params blob Address to the FDT
+ * @params alias_name node name alias that should be searched for.
+ * @params addrcp/sizecp Pointer that will be updated with any #address-cells and #size-cells
+ * value found in the node of the node specified by node_offset. Either
+ * may be NULL to ignore. If no #address-cells and #size-cells is found
+ * default values of #address-cells=2 and #size-cells=1 are returned.
+ *
+ * @returns offset to last node found behind path or 0 if no node has been found
+ */
+u32 fdt_find_node_by_alias(const void *blob, const char *alias_name, u32 *addrcp, u32 *sizecp)
+{
+ const char *node_name = fdt_read_alias_prop(blob, alias_name);
+ if (!node_name) {
+ printk(BIOS_DEBUG, "alias %s not found\n", alias_name);
+ return 0;
+ }
+
+ u32 node_offset = fdt_find_node_by_path(blob, node_name, addrcp, sizecp);
+ if (!node_offset) {
+ // This should not happen (invalid devicetree)
+ printk(BIOS_WARNING,
+ "Could not find node '%s', which alias was referring to '%s'\n",
+ node_name, alias_name);
+ return 0;
+ }
+ return node_offset;
+}
/*
@@ -108,7 +471,7 @@ static int print_flat_node(const void *blob, uint32_t start_offset, int depth)
const char *name;
int size;
- size = fdt_node_name(blob, offset, &name);
+ size = fdt_next_node_name(blob, offset, &name);
if (!size)
return 0;
offset += size;
@@ -139,38 +502,16 @@ void fdt_print_node(const void *blob, uint32_t offset)
print_flat_node(blob, offset, 0);
}
-
-
/*
- * A utility function to skip past nodes in flattened trees.
+ * Functions to turn a flattened tree into an unflattened one.
*/
-int fdt_skip_node(const void *blob, uint32_t start_offset)
+static int dt_prop_is_phandle(struct device_tree_property *prop)
{
- int offset = start_offset;
- int size;
-
- const char *name;
- size = fdt_node_name(blob, offset, &name);
- if (!size)
- return 0;
- offset += size;
-
- while ((size = fdt_next_property(blob, offset, NULL)))
- offset += size;
-
- while ((size = fdt_skip_node(blob, offset)))
- offset += size;
-
- return offset - start_offset + sizeof(uint32_t);
+ return !(strcmp("phandle", prop->prop.name) &&
+ strcmp("linux,phandle", prop->prop.name));
}
-
-
-/*
- * Functions to turn a flattened tree into an unflattened one.
- */
-
static int fdt_unflatten_node(const void *blob, uint32_t start_offset,
struct device_tree *tree,
struct device_tree_node **new_node)
@@ -180,7 +521,7 @@ static int fdt_unflatten_node(const void *blob, uint32_t start_offset,
const char *name;
int size;
- size = fdt_node_name(blob, offset, &name);
+ size = fdt_next_node_name(blob, offset, &name);
if (!size)
return 0;
offset += size;
@@ -237,30 +578,37 @@ static int fdt_unflatten_map_entry(const void *blob, uint32_t offset,
return sizeof(uint64_t) * 2;
}
-struct device_tree *fdt_unflatten(const void *blob)
+bool fdt_is_valid(const void *blob)
{
- struct device_tree *tree = xzalloc(sizeof(*tree));
const struct fdt_header *header = (const struct fdt_header *)blob;
- tree->header = header;
uint32_t magic = be32toh(header->magic);
uint32_t version = be32toh(header->version);
uint32_t last_comp_version = be32toh(header->last_comp_version);
if (magic != FDT_HEADER_MAGIC) {
- printk(BIOS_DEBUG, "Invalid device tree magic %#.8x!\n", magic);
- free(tree);
- return NULL;
+ printk(BIOS_ERR, "Invalid device tree magic %#.8x!\n", magic);
+ return false;
}
if (last_comp_version > FDT_SUPPORTED_VERSION) {
- printk(BIOS_DEBUG, "Unsupported device tree version %u(>=%u)\n",
+ printk(BIOS_ERR, "Unsupported device tree version %u(>=%u)\n",
version, last_comp_version);
- free(tree);
- return NULL;
+ return false;
}
if (version > FDT_SUPPORTED_VERSION)
printk(BIOS_NOTICE, "FDT version %u too new, should add support!\n",
version);
+ return true;
+}
+
+struct device_tree *fdt_unflatten(const void *blob)
+{
+ struct device_tree *tree = xzalloc(sizeof(*tree));
+ const struct fdt_header *header = (const struct fdt_header *)blob;
+ tree->header = header;
+
+ if (fdt_is_valid(blob))
+ return NULL;
uint32_t struct_offset = be32toh(header->structure_offset);
uint32_t strings_offset = be32toh(header->strings_offset);
@@ -981,7 +1329,7 @@ void dt_add_u64_prop(struct device_tree_node *node, const char *name, u64 val)
* Add a 'reg' address list property to a node, or update it if it exists.
*
* @param node The device tree node to add to.
- * @param addrs Array of address values to be stored in the property.
+ * @param regions Array of address values to be stored in the property.
* @param sizes Array of corresponding size values to 'addrs'.
* @param count Number of values in 'addrs' and 'sizes' (must be equal).
* @param addr_cells Value of #address-cells property valid for this node.