dtc(1): Update to 1a79f5f26631

Highlights:
- Bugfix for order in which /delete-node/ and /delete-property/ are
  processed [0]
- /omit-if-no-ref/ support has been added (used only by U-Boot at this
  point, in theory)
- GPL dtc compat version bumped to 1.4.7
- Various small fixes and compatibility improvements

Reported by:	strejda [0]
MFC after:	1 week
This commit is contained in:
Kyle Evans 2019-03-28 03:48:51 +00:00
parent 93c9d31918
commit d37eb02eb9
8 changed files with 299 additions and 47 deletions

View File

@ -37,9 +37,33 @@
#include <inttypes.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
using std::string;
namespace {
void write(dtc::byte_buffer &buffer, int fd)
{
size_t size = buffer.size();
uint8_t *data = buffer.data();
while (size > 0)
{
ssize_t r = ::write(fd, data, size);
if (r >= 0)
{
data += r;
size -= r;
}
else if (errno != EAGAIN)
{
fprintf(stderr, "Writing to file failed\n");
exit(-1);
}
}
}
}
namespace dtc
{
namespace dtb
@ -90,8 +114,7 @@ binary_writer::write_data(uint64_t v)
void
binary_writer::write_to_file(int fd)
{
// FIXME: Check return
write(fd, buffer.data(), buffer.size());
write(buffer, fd);
}
uint32_t
@ -222,8 +245,7 @@ asm_writer::write_data(uint64_t v)
void
asm_writer::write_to_file(int fd)
{
// FIXME: Check return
write(fd, buffer.data(), buffer.size());
write(buffer, fd);
}
uint32_t

View File

@ -109,6 +109,8 @@ inline const char *token_type_name(token_type t)
return "FDT_END";
}
assert(0);
// Not reached.
return nullptr;
}
/**

View File

@ -30,7 +30,7 @@
.\"
.\" $FreeBSD$
.\"/
.Dd April 7, 2018
.Dd March 27, 2019
.Dt DTC 1
.Os
.Sh NAME
@ -304,7 +304,18 @@ Overlay blobs can be applied at boot time by setting
in
.Xr loader.conf 5 .
Multiple overlays may be specified, and they will be applied in the order given.
.El
.Sh NODE OMISSION
This utility supports the
.Va /omit-if-no-ref/
statement to mark nodes for omission if they are ultimately not referenced
elsewhere in the device tree.
This may be used in more space-constrained environments to remove nodes that may
not be applicable to the specific device the tree is being compiled for.
.Pp
When the
.Fl @
flag is used to write symbols, nodes with labels will be considered referenced
and will not be removed from the tree.
.Sh EXAMPLES
The command:
.Pp
@ -403,7 +414,11 @@ A dtc tool first appeared in
This version of the tool first appeared in
.Fx 10.0 .
.Sh AUTHORS
.An David T. Chisnall
.Nm
was written by
.An David T. Chisnall .
Some features were added later by
.An Kyle Evans .
.Pp
Note: The fact that the tool and the author share the same initials is entirely
coincidental.

View File

@ -38,6 +38,7 @@
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
@ -65,7 +66,7 @@ int version_minor_compatible = 4;
* The current patch level of the tool.
*/
int version_patch = 0;
int version_patch_compatible = 0;
int version_patch_compatible = 7;
void usage(const string &argv0)
{
@ -105,7 +106,7 @@ main(int argc, char **argv)
bool debug_mode = false;
auto write_fn = &device_tree::write_binary;
auto read_fn = &device_tree::parse_dts;
uint32_t boot_cpu;
uint32_t boot_cpu = 0;
bool boot_cpu_specified = false;
bool keep_going = false;
bool sort = false;

View File

@ -46,6 +46,7 @@
#include <libgen.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
@ -491,6 +492,7 @@ property::property(text_input_buffer &input,
break;
}
}
[[fallthrough]];
default:
input.parse_error("Invalid property value.");
valid = false;
@ -622,6 +624,7 @@ property_value::try_to_merge(property_value &other)
return false;
case EMPTY:
*this = other;
[[fallthrough]];
case STRING:
case STRING_LIST:
case CROSS_REFERENCE:
@ -846,6 +849,7 @@ node_ptr node::create_special_node(const string &name,
}
node::node(text_input_buffer &input,
device_tree &tree,
string &&n,
std::unordered_set<string> &&l,
string &&a,
@ -862,6 +866,9 @@ node::node(text_input_buffer &input,
// flag set if we find any characters that are only in
// the property name character set, not the node
bool is_property = false;
// flag set if our node is marked as /omit-if-no-ref/ to be
// garbage collected later if nothing references it
bool marked_omit_if_no_ref = false;
string child_name, child_address;
std::unordered_set<string> child_labels;
auto parse_delete = [&](const char *expected, bool at)
@ -908,6 +915,12 @@ node::node(text_input_buffer &input,
}
continue;
}
if (input.consume("/omit-if-no-ref/"))
{
input.next_token();
marked_omit_if_no_ref = true;
tree.set_needs_garbage_collection();
}
child_name = parse_name(input, is_property,
"Expected property or node name");
while (input.consume(':'))
@ -943,10 +956,11 @@ node::node(text_input_buffer &input,
}
else if (!is_property && *input == ('{'))
{
node_ptr child = node::parse(input, std::move(child_name),
node_ptr child = node::parse(input, tree, std::move(child_name),
std::move(child_labels), std::move(child_address), defines);
if (child)
{
child->omit_if_no_ref = marked_omit_if_no_ref;
children.push_back(std::move(child));
}
else
@ -998,12 +1012,14 @@ node::sort()
node_ptr
node::parse(text_input_buffer &input,
device_tree &tree,
string &&name,
string_set &&label,
string &&address,
define_map *defines)
{
node_ptr n(new node(input,
tree,
std::move(name),
std::move(label),
std::move(address),
@ -1046,6 +1062,30 @@ node::merge_node(node_ptr &other)
{
labels.insert(l);
}
children.erase(std::remove_if(children.begin(), children.end(),
[&](const node_ptr &p) {
string full_name = p->name;
if (p->unit_address != string())
{
full_name += '@';
full_name += p->unit_address;
}
if (other->deleted_children.count(full_name) > 0)
{
other->deleted_children.erase(full_name);
return true;
}
return false;
}), children.end());
props.erase(std::remove_if(props.begin(), props.end(),
[&](const property_ptr &p) {
if (other->deleted_props.count(p->get_key()) > 0)
{
other->deleted_props.erase(p->get_key());
return true;
}
return false;
}), props.end());
// Note: this is an O(n*m) operation. It might be sensible to
// optimise this if we find that there are nodes with very
// large numbers of properties, but for typical usage the
@ -1085,30 +1125,6 @@ node::merge_node(node_ptr &other)
children.push_back(std::move(c));
}
}
children.erase(std::remove_if(children.begin(), children.end(),
[&](const node_ptr &p) {
string full_name = p->name;
if (p->unit_address != string())
{
full_name += '@';
full_name += p->unit_address;
}
if (other->deleted_children.count(full_name) > 0)
{
other->deleted_children.erase(full_name);
return true;
}
return false;
}), children.end());
props.erase(std::remove_if(props.begin(), props.end(),
[&](const property_ptr &p) {
if (other->deleted_props.count(p->get_key()) > 0)
{
other->deleted_props.erase(p->get_key());
return true;
}
return false;
}), props.end());
}
void
@ -1187,6 +1203,7 @@ device_tree::collect_names_recursive(node_ptr &n, node_path &path)
{
node_names.insert(std::make_pair(name, n.get()));
node_paths.insert(std::make_pair(name, path));
ordered_node_paths.push_back({name, path});
}
else
{
@ -1243,6 +1260,7 @@ device_tree::collect_names()
node_path p;
node_names.clear();
node_paths.clear();
ordered_node_paths.clear();
cross_references.clear();
fixups.clear();
collect_names_recursive(root, p);
@ -1353,7 +1371,6 @@ device_tree::resolve_cross_references(uint32_t &phandle)
return node::VISIT_RECURSE;
}, nullptr);
assert(sorted_phandles.size() == fixups.size());
for (auto &i : sorted_phandles)
{
string target_name = i.get().val.string_data;
@ -1441,6 +1458,103 @@ device_tree::resolve_cross_references(uint32_t &phandle)
}
}
bool
device_tree::garbage_collect_marked_nodes()
{
std::unordered_set<node*> previously_referenced_nodes;
std::unordered_set<node*> newly_referenced_nodes;
auto mark_referenced_nodes_used = [&](node &n)
{
for (auto &p : n.properties())
{
for (auto &v : *p)
{
if (v.is_phandle())
{
node *nx = node_names[v.string_data];
if (nx == nullptr)
{
// Try it again, but as a path
for (auto &s : node_paths)
{
if (v.string_data == s.second.to_string())
{
nx = node_names[s.first];
break;
}
}
}
if (nx == nullptr)
{
// Couldn't resolve this one?
continue;
}
// Only mark those currently unmarked
if (!nx->used)
{
nx->used = 1;
newly_referenced_nodes.insert(nx);
}
}
}
}
};
// Seed our referenced nodes with those that have been seen by a node that
// either will not be omitted if it's unreferenced or has a symbol.
// Nodes with symbols are explicitly not garbage collected because they may
// be expected for referencing by an overlay, and we do not want surprises
// there.
root->visit([&](node &n, node *) {
if (!n.omit_if_no_ref || (write_symbols && !n.labels.empty()))
{
mark_referenced_nodes_used(n);
}
// Recurse as normal
return node::VISIT_RECURSE;
}, nullptr);
while (!newly_referenced_nodes.empty())
{
previously_referenced_nodes = std::move(newly_referenced_nodes);
for (auto *n : previously_referenced_nodes)
{
mark_referenced_nodes_used(*n);
}
}
previously_referenced_nodes.clear();
bool children_deleted = false;
// Delete
root->visit([&](node &n, node *) {
bool gc_children = false;
for (auto &cn : n.child_nodes())
{
if (cn->omit_if_no_ref && !cn->used)
{
gc_children = true;
break;
}
}
if (gc_children)
{
children_deleted = true;
n.delete_children_if([](node_ptr &nx) {
return (nx->omit_if_no_ref && !nx->used);
});
return node::VISIT_CONTINUE;
}
return node::VISIT_RECURSE;
}, nullptr);
return children_deleted;
}
void
device_tree::parse_file(text_input_buffer &input,
@ -1486,7 +1600,7 @@ device_tree::parse_file(text_input_buffer &input,
if (input.consume('/'))
{
input.next_token();
n = node::parse(input, string(), string_set(), string(), &defines);
n = node::parse(input, *this, string(), string_set(), string(), &defines);
}
else if (input.consume('&'))
{
@ -1507,7 +1621,7 @@ device_tree::parse_file(text_input_buffer &input,
name = input.parse_node_name();
}
input.next_token();
n = node::parse(input, std::move(name), string_set(), string(), &defines);
n = node::parse(input, *this, std::move(name), string_set(), string(), &defines);
n->name_is_path_reference = name_is_path_reference;
}
else
@ -1890,6 +2004,12 @@ device_tree::parse_dts(const string &fn, FILE *depfile)
}
}
collect_names();
// Return value indicates whether we've dirtied the tree or not and need to
// recollect names
if (garbage_collect && garbage_collect_marked_nodes())
{
collect_names();
}
uint32_t phandle = 1;
// If we're writing symbols, go ahead and assign phandles to the entire
// tree. We'll do this before we resolve cross references, just to keep
@ -1906,8 +2026,14 @@ device_tree::parse_dts(const string &fn, FILE *depfile)
// referenced by other plugins, so we create a __symbols__ node inside
// the root that contains mappings (properties) from label names to
// paths.
for (auto &s : node_paths)
for (auto i=ordered_node_paths.rbegin(), e=ordered_node_paths.rend() ; i!=e ; ++i)
{
auto &s = *i;
if (node_paths.find(s.first) == node_paths.end())
{
// Erased node, skip it.
continue;
}
property_value v;
v.string_data = s.second.to_string();
v.type = property_value::STRING;
@ -1986,19 +2112,23 @@ device_tree::parse_dts(const string &fn, FILE *depfile)
{
if (c->name == p.first)
{
string path = p.first;
if (!(p.second.empty()))
if (c->unit_address == p.second)
{
path += '@';
path += p.second;
n = c.get();
found = true;
break;
}
n->add_child(node::create_special_node(path, symbols));
n = (--n->child_end())->get();
}
}
if (!found)
{
n->add_child(node::create_special_node(p.first, symbols));
string path = p.first;
if (!(p.second.empty()))
{
path += '@';
path += p.second;
}
n->add_child(node::create_special_node(path, symbols));
n = (--n->child_end())->get();
}
}

View File

@ -56,6 +56,7 @@ namespace fdt
{
class property;
class node;
class device_tree;
/**
* Type for (owned) pointers to properties.
*/
@ -417,6 +418,17 @@ class node
* name followed by an at symbol.
*/
std::string unit_address;
/**
* A flag indicating that this node has been marked /omit-if-no-ref/ and
* will be omitted if it is not referenced, either directly or indirectly,
* by a node that is not similarly denoted.
*/
bool omit_if_no_ref = false;
/**
* A flag indicating that this node has been referenced, either directly
* or indirectly, by a node that is not marked /omit-if-no-ref/.
*/
bool used = false;
/**
* The type for the property vector.
*/
@ -507,6 +519,7 @@ class node
* already been parsed.
*/
node(text_input_buffer &input,
device_tree &tree,
std::string &&n,
std::unordered_set<std::string> &&l,
std::string &&a,
@ -603,6 +616,7 @@ class node
* have been parsed.
*/
static node_ptr parse(text_input_buffer &input,
device_tree &tree,
std::string &&name,
std::unordered_set<std::string> &&label=std::unordered_set<std::string>(),
std::string &&address=std::string(),
@ -639,6 +653,13 @@ class node
{
children.push_back(std::move(n));
}
/**
* Deletes any children from this node.
*/
inline void delete_children_if(bool (*predicate)(node_ptr &))
{
children.erase(std::remove_if(children.begin(), children.end(), predicate), children.end());
}
/**
* Merges a node into this one. Any properties present in both are
* overridden, any properties present in only one are preserved.
@ -709,6 +730,11 @@ class device_tree
* on parse errors.
*/
bool valid = true;
/**
* Flag indicating that this tree requires garbage collection. This will be
* set to true if a node marked /omit-if-no-ref/ is encountered.
*/
bool garbage_collect = false;
/**
* Type used for memory reservations. A reservation is two 64-bit
* values indicating a base address and length in memory that the
@ -735,6 +761,12 @@ class device_tree
* with the full path to its target.
*/
std::unordered_map<std::string, node_path> node_paths;
/**
* All of the elements in `node_paths` in the order that they were
* created. This is used for emitting the `__symbols__` section, where
* we want to guarantee stable ordering.
*/
std::vector<std::pair<std::string, node_path>> ordered_node_paths;
/**
* A collection of property values that are references to other nodes.
* These should be expanded to the full path of their targets.
@ -847,9 +879,19 @@ class device_tree
* node must have their values replaced by either the node path or
* phandle value. The phandle parameter holds the next phandle to be
* assigned, should the need arise. It will be incremented upon each
* assignment of a phandle.
* assignment of a phandle. Garbage collection of unreferenced nodes
* marked for "delete if unreferenced" will also occur here.
*/
void resolve_cross_references(uint32_t &phandle);
/**
* Garbage collects nodes that have been marked /omit-if-no-ref/ and do not
* have any references to them from nodes that are similarly marked. This
* is a fairly expensive operation. The return value indicates whether the
* tree has been dirtied as a result of this operation, so that the caller
* may take appropriate measures to bring the device tree into a consistent
* state as needed.
*/
bool garbage_collect_marked_nodes();
/**
* Parses a dts file in the given buffer and adds the roots to the parsed
* set. The `read_header` argument indicates whether the header has
@ -932,6 +974,14 @@ class device_tree
{
return valid;
}
/**
* Mark this tree as needing garbage collection, because an /omit-if-no-ref/
* node has been encountered.
*/
void set_needs_garbage_collection()
{
garbage_collect = true;
}
/**
* Sets the format for writing phandle properties.
*/

View File

@ -126,7 +126,7 @@ mmap_input_buffer::~mmap_input_buffer()
{
if (buffer != 0)
{
munmap((void*)buffer, size);
munmap(const_cast<char*>(buffer), size);
}
}

View File

@ -47,6 +47,38 @@
#endif
#endif
#ifdef MISSING_DIGITTOINT
namespace
{
/**
* Glibc doesn't have a definition of digittoint, so provide our own.
*/
inline int digittoint(int c)
{
switch (c)
{
default:
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': return 10;
case 'b': return 11;
case 'c': return 12;
case 'd': return 13;
case 'e': return 14;
case 'f': return 15;
}
}
}
#endif
namespace dtc {
/**