fdt: Add fdt_first/next_region() functions

These have been sent upstream but not accepted to libfdt. For now, bring
these into U-Boot to enable fdtgrep to operate. We will use fdtgrep to
cut device tree files down for SPL.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2015-06-23 15:38:27 -06:00
parent 8f6e2e1ee1
commit c3c4c00563
3 changed files with 731 additions and 2 deletions

View file

@ -121,7 +121,12 @@
/* FDT_ERR_BADNCELLS: Device tree has a #address-cells, #size-cells
* or similar property with a bad format or value */
#define FDT_ERR_MAX 14
#define FDT_ERR_TOODEEP 15
/* FDT_ERR_TOODEEP: The depth of a node has exceeded the internal
* libfdt limit. This can happen if you have more than
* FDT_MAX_DEPTH nested nodes. */
#define FDT_ERR_MAX 15
/**********************************************************************/
/* Low-level functions (you probably don't need these) */
@ -1668,6 +1673,77 @@ struct fdt_region {
int size;
};
/*
* Flags for fdt_find_regions()
*
* Add a region for the string table (always the last region)
*/
#define FDT_REG_ADD_STRING_TAB (1 << 0)
/*
* Add all supernodes of a matching node/property, useful for creating a
* valid subset tree
*/
#define FDT_REG_SUPERNODES (1 << 1)
/* Add the FDT_BEGIN_NODE tags of subnodes, including their names */
#define FDT_REG_DIRECT_SUBNODES (1 << 2)
/* Add all subnodes of a matching node */
#define FDT_REG_ALL_SUBNODES (1 << 3)
/* Add a region for the mem_rsvmap table (always the first region) */
#define FDT_REG_ADD_MEM_RSVMAP (1 << 4)
/* Indicates what an fdt part is (node, property, value) */
#define FDT_IS_NODE (1 << 0)
#define FDT_IS_PROP (1 << 1)
#define FDT_IS_VALUE (1 << 2) /* not supported */
#define FDT_IS_COMPAT (1 << 3) /* used internally */
#define FDT_NODE_HAS_PROP (1 << 4) /* node contains prop */
#define FDT_ANY_GLOBAL (FDT_IS_NODE | FDT_IS_PROP | FDT_IS_VALUE | \
FDT_IS_COMPAT)
#define FDT_IS_ANY 0x1f /* all the above */
/* We set a reasonable limit on the number of nested nodes */
#define FDT_MAX_DEPTH 32
/* Decribes what we want to include from the current tag */
enum want_t {
WANT_NOTHING,
WANT_NODES_ONLY, /* No properties */
WANT_NODES_AND_PROPS, /* Everything for one level */
WANT_ALL_NODES_AND_PROPS /* Everything for all levels */
};
/* Keeps track of the state at parent nodes */
struct fdt_subnode_stack {
int offset; /* Offset of node */
enum want_t want; /* The 'want' value here */
int included; /* 1 if we included this node, 0 if not */
};
struct fdt_region_ptrs {
int depth; /* Current tree depth */
int done; /* What we have completed scanning */
enum want_t want; /* What we are currently including */
char *end; /* Pointer to end of full node path */
int nextoffset; /* Next node offset to check */
};
/* The state of our finding algortihm */
struct fdt_region_state {
struct fdt_subnode_stack stack[FDT_MAX_DEPTH]; /* node stack */
struct fdt_region *region; /* Contains list of regions found */
int count; /* Numnber of regions found */
const void *fdt; /* FDT blob */
int max_regions; /* Maximum regions to find */
int can_merge; /* 1 if we can merge with previous region */
int start; /* Start position of current region */
struct fdt_region_ptrs ptrs; /* Pointers for what we are up to */
};
/**
* fdt_find_regions() - find regions in device tree
*
@ -1727,4 +1803,165 @@ int fdt_find_regions(const void *fdt, char * const inc[], int inc_count,
struct fdt_region region[], int max_regions,
char *path, int path_len, int add_string_tab);
/**
* fdt_first_region() - find regions in device tree
*
* Given a nodes and properties to include and properties to exclude, find
* the regions of the device tree which describe those included parts.
*
* The use for this function is twofold. Firstly it provides a convenient
* way of performing a structure-aware grep of the tree. For example it is
* possible to grep for a node and get all the properties associated with
* that node. Trees can be subsetted easily, by specifying the nodes that
* are required, and then writing out the regions returned by this function.
* This is useful for small resource-constrained systems, such as boot
* loaders, which want to use an FDT but do not need to know about all of
* it.
*
* Secondly it makes it easy to hash parts of the tree and detect changes.
* The intent is to get a list of regions which will be invariant provided
* those parts are invariant. For example, if you request a list of regions
* for all nodes but exclude the property "data", then you will get the
* same region contents regardless of any change to "data" properties.
*
* This function can be used to produce a byte-stream to send to a hashing
* function to verify that critical parts of the FDT have not changed.
* Note that semantically null changes in order could still cause false
* hash misses. Such reordering might happen if the tree is regenerated
* from source, and nodes are reordered (the bytes-stream will be emitted
* in a different order and mnay hash functions will detect this). However
* if an existing tree is modified using libfdt functions, such as
* fdt_add_subnode() and fdt_setprop(), then this problem is avoided.
*
* The nodes/properties to include/exclude are defined by a function
* provided by the caller. This function is called for each node and
* property, and must return:
*
* 0 - to exclude this part
* 1 - to include this part
* -1 - for FDT_IS_PROP only: no information is available, so include
* if its containing node is included
*
* The last case is only used to deal with properties. Often a property is
* included if its containing node is included - this is the case where
* -1 is returned.. However if the property is specifically required to be
* included/excluded, then 0 or 1 can be returned. Note that including a
* property when the FDT_REG_SUPERNODES flag is given will force its
* containing node to be included since it is not valid to have a property
* that is not in a node.
*
* Using the information provided, the inclusion of a node can be controlled
* either by a node name or its compatible string, or any other property
* that the function can determine.
*
* As an example, including node "/" means to include the root node and all
* root properties. A flag provides a way of also including supernodes (of
* which there is none for the root node), and another flag includes
* immediate subnodes, so in this case we would get the FDT_BEGIN_NODE and
* FDT_END_NODE of all subnodes of /.
*
* The subnode feature helps in a hashing situation since it prevents the
* root node from changing at all. Any change to non-excluded properties,
* names of subnodes or number of subnodes would be detected.
*
* When used with FITs this provides the ability to hash and sign parts of
* the FIT based on different configurations in the FIT. Then it is
* impossible to change anything about that configuration (include images
* attached to the configuration), but it may be possible to add new
* configurations, new images or new signatures within the existing
* framework.
*
* Adding new properties to a device tree may result in the string table
* being extended (if the new property names are different from those
* already added). This function can optionally include a region for
* the string table so that this can be part of the hash too. This is always
* the last region.
*
* The FDT also has a mem_rsvmap table which can also be included, and is
* always the first region if so.
*
* The device tree header is not included in the region list. Since the
* contents of the FDT are changing (shrinking, often), the caller will need
* to regenerate the header anyway.
*
* @fdt: Device tree to check
* @h_include: Function to call to determine whether to include a part or
* not:
*
* @priv: Private pointer as passed to fdt_find_regions()
* @fdt: Pointer to FDT blob
* @offset: Offset of this node / property
* @type: Type of this part, FDT_IS_...
* @data: Pointer to data (node name, property name, compatible
* string, value (not yet supported)
* @size: Size of data, or 0 if none
* @return 0 to exclude, 1 to include, -1 if no information is
* available
* @priv: Private pointer passed to h_include
* @region: Returns list of regions, sorted by offset
* @max_regions: Maximum length of region list
* @path: Pointer to a temporary string for the function to use for
* building path names
* @path_len: Length of path, must be large enough to hold the longest
* path in the tree
* @flags: Various flags that control the region algortihm, see
* FDT_REG_...
* @return number of regions in list. If this is >max_regions then the
* region array was exhausted. You should increase max_regions and try
* the call again. Only the first max_regions elements are available in the
* array.
*
* On error a -ve value is return, which can be:
*
* -FDT_ERR_BADSTRUCTURE (too deep or more END tags than BEGIN tags
* -FDT_ERR_BADLAYOUT
* -FDT_ERR_NOSPACE (path area is too small)
*/
int fdt_first_region(const void *fdt,
int (*h_include)(void *priv, const void *fdt, int offset,
int type, const char *data, int size),
void *priv, struct fdt_region *region,
char *path, int path_len, int flags,
struct fdt_region_state *info);
/** fdt_next_region() - find next region
*
* See fdt_first_region() for full description. This function finds the
* next region according to the provided parameters, which must be the same
* as passed to fdt_first_region().
*
* This function can additionally return -FDT_ERR_NOTFOUND when there are no
* more regions
*/
int fdt_next_region(const void *fdt,
int (*h_include)(void *priv, const void *fdt, int offset,
int type, const char *data, int size),
void *priv, struct fdt_region *region,
char *path, int path_len, int flags,
struct fdt_region_state *info);
/**
* fdt_add_alias_regions() - find aliases that point to existing regions
*
* Once a device tree grep is complete some of the nodes will be present
* and some will have been dropped. This function checks all the alias nodes
* to figure out which points point to nodes which are still present. These
* aliases need to be kept, along with the nodes they reference.
*
* Given a list of regions function finds the aliases that still apply and
* adds more regions to the list for these. This function is called after
* fdt_next_region() has finished returning regions and requires the same
* state.
*
* @fdt: Device tree file to reference
* @region: List of regions that will be kept
* @count: Number of regions
* @max_regions: Number of entries that can fit in @region
* @info: Region state as returned from fdt_next_region()
* @return new number of regions in @region (i.e. count + the number added)
* or -FDT_ERR_NOSPACE if there was not enough space.
*/
int fdt_add_alias_regions(const void *fdt, struct fdt_region *region, int count,
int max_regions, struct fdt_region_state *info);
#endif /* _LIBFDT_H */

View file

@ -6,4 +6,4 @@
#
obj-y += fdt.o fdt_ro.o fdt_rw.o fdt_strerror.o fdt_sw.o fdt_wip.o \
fdt_empty_tree.o fdt_addresses.o
fdt_empty_tree.o fdt_addresses.o fdt_region.o

492
lib/libfdt/fdt_region.c Normal file
View file

@ -0,0 +1,492 @@
/*
* libfdt - Flat Device Tree manipulation
* Copyright (C) 2013 Google, Inc
* Written by Simon Glass <sjg@chromium.org>
* SPDX-License-Identifier: GPL-2.0+ BSD-2-Clause
*/
#include "libfdt_env.h"
#ifndef USE_HOSTCC
#include <fdt.h>
#include <libfdt.h>
#else
#include "fdt_host.h"
#endif
#include "libfdt_internal.h"
/**
* fdt_add_region() - Add a new region to our list
*
* The region is added if there is space, but in any case we increment the
* count. If permitted, and the new region overlaps the last one, we merge
* them.
*
* @info: State information
* @offset: Start offset of region
* @size: Size of region
*/
static int fdt_add_region(struct fdt_region_state *info, int offset, int size)
{
struct fdt_region *reg;
reg = info->region ? &info->region[info->count - 1] : NULL;
if (info->can_merge && info->count &&
info->count <= info->max_regions &&
reg && offset <= reg->offset + reg->size) {
reg->size = offset + size - reg->offset;
} else if (info->count++ < info->max_regions) {
if (reg) {
reg++;
reg->offset = offset;
reg->size = size;
}
} else {
return -1;
}
return 0;
}
static int region_list_contains_offset(struct fdt_region_state *info,
const void *fdt, int target)
{
struct fdt_region *reg;
int num;
target += fdt_off_dt_struct(fdt);
for (reg = info->region, num = 0; num < info->count; reg++, num++) {
if (target >= reg->offset && target < reg->offset + reg->size)
return 1;
}
return 0;
}
int fdt_add_alias_regions(const void *fdt, struct fdt_region *region, int count,
int max_regions, struct fdt_region_state *info)
{
int base = fdt_off_dt_struct(fdt);
int node, node_end, offset;
int did_alias_header;
node = fdt_subnode_offset(fdt, 0, "aliases");
if (node < 0)
return -FDT_ERR_NOTFOUND;
/* The aliases node must come before the others */
node_end = fdt_next_subnode(fdt, node);
if (node_end <= 0)
return -FDT_ERR_BADLAYOUT;
node_end -= sizeof(fdt32_t);
did_alias_header = 0;
info->region = region;
info->count = count;
info->can_merge = 0;
info->max_regions = max_regions;
for (offset = fdt_first_property_offset(fdt, node);
offset >= 0;
offset = fdt_next_property_offset(fdt, offset)) {
const struct fdt_property *prop;
const char *name;
int target, next;
prop = fdt_get_property_by_offset(fdt, offset, NULL);
name = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
target = fdt_path_offset(fdt, name);
if (!region_list_contains_offset(info, fdt, target))
continue;
next = fdt_next_property_offset(fdt, offset);
if (next < 0)
next = node_end - sizeof(fdt32_t);
if (!did_alias_header) {
fdt_add_region(info, base + node, 12);
did_alias_header = 1;
}
fdt_add_region(info, base + offset, next - offset);
}
/* Add the 'end' tag */
if (did_alias_header)
fdt_add_region(info, base + node_end, sizeof(fdt32_t));
return info->count < max_regions ? info->count : -FDT_ERR_NOSPACE;
}
/**
* fdt_include_supernodes() - Include supernodes required by this node
*
* When we decided to include a node or property which is not at the top
* level, this function forces the inclusion of higher level nodes. For
* example, given this tree:
*
* / {
* testing {
* }
* }
*
* If we decide to include testing then we need the root node to have a valid
* tree. This function adds those regions.
*
* @info: State information
* @depth: Current stack depth
*/
static int fdt_include_supernodes(struct fdt_region_state *info, int depth)
{
int base = fdt_off_dt_struct(info->fdt);
int start, stop_at;
int i;
/*
* Work down the stack looking for supernodes that we didn't include.
* The algortihm here is actually pretty simple, since we know that
* no previous subnode had to include these nodes, or if it did, we
* marked them as included (on the stack) already.
*/
for (i = 0; i <= depth; i++) {
if (!info->stack[i].included) {
start = info->stack[i].offset;
/* Add the FDT_BEGIN_NODE tag of this supernode */
fdt_next_tag(info->fdt, start, &stop_at);
if (fdt_add_region(info, base + start, stop_at - start))
return -1;
/* Remember that this supernode is now included */
info->stack[i].included = 1;
info->can_merge = 1;
}
/* Force (later) generation of the FDT_END_NODE tag */
if (!info->stack[i].want)
info->stack[i].want = WANT_NODES_ONLY;
}
return 0;
}
enum {
FDT_DONE_NOTHING,
FDT_DONE_MEM_RSVMAP,
FDT_DONE_STRUCT,
FDT_DONE_END,
FDT_DONE_STRINGS,
FDT_DONE_ALL,
};
int fdt_first_region(const void *fdt,
int (*h_include)(void *priv, const void *fdt, int offset,
int type, const char *data, int size),
void *priv, struct fdt_region *region,
char *path, int path_len, int flags,
struct fdt_region_state *info)
{
struct fdt_region_ptrs *p = &info->ptrs;
/* Set up our state */
info->fdt = fdt;
info->can_merge = 1;
info->max_regions = 1;
info->start = -1;
p->want = WANT_NOTHING;
p->end = path;
*p->end = '\0';
p->nextoffset = 0;
p->depth = -1;
p->done = FDT_DONE_NOTHING;
return fdt_next_region(fdt, h_include, priv, region,
path, path_len, flags, info);
}
/*
* Theory of operation
*
*
*
Note: in this description 'included' means that a node (or other part of
the tree) should be included in the region list, i.e. it will have a region
which covers its part of the tree.
This function maintains some state from the last time it is called. It
checks the next part of the tree that it is supposed to look at
(p.nextoffset) to see if that should be included or not. When it finds
something to include, it sets info->start to its offset. This marks the
start of the region we want to include.
Once info->start is set to the start (i.e. not -1), we continue scanning
until we find something that we don't want included. This will be the end
of a region. At this point we can close off the region and add it to the
list. So we do so, and reset info->start to -1.
One complication here is that we want to merge regions. So when we come to
add another region later, we may in fact merge it with the previous one if
one ends where the other starts.
The function fdt_add_region() will return -1 if it fails to add the region,
because we already have a region ready to be returned, and the new one
cannot be merged in with it. In this case, we must return the region we
found, and wait for another call to this function. When it comes, we will
repeat the processing of the tag and again try to add a region. This time it
will succeed.
The current state of the pointers (stack, offset, etc.) is maintained in
a ptrs member. At the start of every loop iteration we make a copy of it.
The copy is then updated as the tag is processed. Only if we get to the end
of the loop iteration (and successfully call fdt_add_region() if we need
to) can we commit the changes we have made to these pointers. For example,
if we see an FDT_END_NODE tag we will decrement the depth value. But if we
need to add a region for this tag (let's say because the previous tag is
included and this FDT_END_NODE tag is not included) then we will only commit
the result if we were able to add the region. That allows us to retry again
next time.
We keep track of a variable called 'want' which tells us what we want to
include when there is no specific information provided by the h_include
function for a particular property. This basically handles the inclusion of
properties which are pulled in by virtue of the node they are in. So if you
include a node, its properties are also included. In this case 'want' will
be WANT_NODES_AND_PROPS. The FDT_REG_DIRECT_SUBNODES feature also makes use
of 'want'. While we are inside the subnode, 'want' will be set to
WANT_NODES_ONLY, so that only the subnode's FDT_BEGIN_NODE and FDT_END_NODE
tags will be included, and properties will be skipped. If WANT_NOTHING is
selected, then we will just rely on what the h_include() function tells us.
Using 'want' we work out 'include', which tells us whether this current tag
should be included or not. As you can imagine, if the value of 'include'
changes, that means we are on a boundary between nodes to include and nodes
to exclude. At this point we either close off a previous region and add it
to the list, or mark the start of a new region.
Apart from the nodes, we have mem_rsvmap, the FDT_END tag and the string
list. Each of these dealt with as a whole (i.e. we create a region for each
if it is to be included). For mem_rsvmap we don't allow it to merge with
the first struct region. For the stringlist we don't allow it to merge with
the last struct region (which contains at minimum the FDT_END tag).
*/
int fdt_next_region(const void *fdt,
int (*h_include)(void *priv, const void *fdt, int offset,
int type, const char *data, int size),
void *priv, struct fdt_region *region,
char *path, int path_len, int flags,
struct fdt_region_state *info)
{
int base = fdt_off_dt_struct(fdt);
int last_node = 0;
const char *str;
info->region = region;
info->count = 0;
if (info->ptrs.done < FDT_DONE_MEM_RSVMAP &&
(flags & FDT_REG_ADD_MEM_RSVMAP)) {
/* Add the memory reserve map into its own region */
if (fdt_add_region(info, fdt_off_mem_rsvmap(fdt),
fdt_off_dt_struct(fdt) -
fdt_off_mem_rsvmap(fdt)))
return 0;
info->can_merge = 0; /* Don't allow merging with this */
info->ptrs.done = FDT_DONE_MEM_RSVMAP;
}
/*
* Work through the tags one by one, deciding whether each needs to
* be included or not. We set the variable 'include' to indicate our
* decision. 'want' is used to track what we want to include - it
* allows us to pick up all the properties (and/or subnode tags) of
* a node.
*/
while (info->ptrs.done < FDT_DONE_STRUCT) {
const struct fdt_property *prop;
struct fdt_region_ptrs p;
const char *name;
int include = 0;
int stop_at = 0;
uint32_t tag;
int offset;
int val;
int len;
/*
* Make a copy of our pointers. If we make it to the end of
* this block then we will commit them back to info->ptrs.
* Otherwise we can try again from the same starting state
* next time we are called.
*/
p = info->ptrs;
/*
* Find the tag, and the offset of the next one. If we need to
* stop including tags, then by default we stop *after*
* including the current tag
*/
offset = p.nextoffset;
tag = fdt_next_tag(fdt, offset, &p.nextoffset);
stop_at = p.nextoffset;
switch (tag) {
case FDT_PROP:
stop_at = offset;
prop = fdt_get_property_by_offset(fdt, offset, NULL);
str = fdt_string(fdt, fdt32_to_cpu(prop->nameoff));
val = h_include(priv, fdt, last_node, FDT_IS_PROP, str,
strlen(str) + 1);
if (val == -1) {
include = p.want >= WANT_NODES_AND_PROPS;
} else {
include = val;
/*
* Make sure we include the } for this block.
* It might be more correct to have this done
* by the call to fdt_include_supernodes() in
* the case where it adds the node we are
* currently in, but this is equivalent.
*/
if ((flags & FDT_REG_SUPERNODES) && val &&
!p.want)
p.want = WANT_NODES_ONLY;
}
/* Value grepping is not yet supported */
break;
case FDT_NOP:
include = p.want >= WANT_NODES_AND_PROPS;
stop_at = offset;
break;
case FDT_BEGIN_NODE:
last_node = offset;
p.depth++;
if (p.depth == FDT_MAX_DEPTH)
return -FDT_ERR_TOODEEP;
name = fdt_get_name(fdt, offset, &len);
if (p.end - path + 2 + len >= path_len)
return -FDT_ERR_NOSPACE;
/* Build the full path of this node */
if (p.end != path + 1)
*p.end++ = '/';
strcpy(p.end, name);
p.end += len;
info->stack[p.depth].want = p.want;
info->stack[p.depth].offset = offset;
/*
* If we are not intending to include this node unless
* it matches, make sure we stop *before* its tag.
*/
if (p.want == WANT_NODES_ONLY ||
!(flags & (FDT_REG_DIRECT_SUBNODES |
FDT_REG_ALL_SUBNODES))) {
stop_at = offset;
p.want = WANT_NOTHING;
}
val = h_include(priv, fdt, offset, FDT_IS_NODE, path,
p.end - path + 1);
/* Include this if requested */
if (val) {
p.want = (flags & FDT_REG_ALL_SUBNODES) ?
WANT_ALL_NODES_AND_PROPS :
WANT_NODES_AND_PROPS;
}
/* If not requested, decay our 'p.want' value */
else if (p.want) {
if (p.want != WANT_ALL_NODES_AND_PROPS)
p.want--;
/* Not including this tag, so stop now */
} else {
stop_at = offset;
}
/*
* Decide whether to include this tag, and update our
* stack with the state for this node
*/
include = p.want;
info->stack[p.depth].included = include;
break;
case FDT_END_NODE:
include = p.want;
if (p.depth < 0)
return -FDT_ERR_BADSTRUCTURE;
/*
* If we don't want this node, stop right away, unless
* we are including subnodes
*/
if (!p.want && !(flags & FDT_REG_DIRECT_SUBNODES))
stop_at = offset;
p.want = info->stack[p.depth].want;
p.depth--;
while (p.end > path && *--p.end != '/')
;
*p.end = '\0';
break;
case FDT_END:
/* We always include the end tag */
include = 1;
p.done = FDT_DONE_STRUCT;
break;
}
/* If this tag is to be included, mark it as region start */
if (include && info->start == -1) {
/* Include any supernodes required by this one */
if (flags & FDT_REG_SUPERNODES) {
if (fdt_include_supernodes(info, p.depth))
return 0;
}
info->start = offset;
}
/*
* If this tag is not to be included, finish up the current
* region.
*/
if (!include && info->start != -1) {
if (fdt_add_region(info, base + info->start,
stop_at - info->start))
return 0;
info->start = -1;
info->can_merge = 1;
}
/* If we have made it this far, we can commit our pointers */
info->ptrs = p;
}
/* Add a region for the END tag and a separate one for string table */
if (info->ptrs.done < FDT_DONE_END) {
if (info->ptrs.nextoffset != fdt_size_dt_struct(fdt))
return -FDT_ERR_BADSTRUCTURE;
if (fdt_add_region(info, base + info->start,
info->ptrs.nextoffset - info->start))
return 0;
info->ptrs.done++;
}
if (info->ptrs.done < FDT_DONE_STRINGS) {
if (flags & FDT_REG_ADD_STRING_TAB) {
info->can_merge = 0;
if (fdt_off_dt_strings(fdt) <
base + info->ptrs.nextoffset)
return -FDT_ERR_BADLAYOUT;
if (fdt_add_region(info, fdt_off_dt_strings(fdt),
fdt_size_dt_strings(fdt)))
return 0;
}
info->ptrs.done++;
}
return info->count > 0 ? 0 : -FDT_ERR_NOTFOUND;
}