dtc(1): Update to upstream ea3c233

Highlights of this update:
- /__local_fixups__ is now generated to be GPL dtc and libfdt compliant
- Compiling with -@ will now cause dtc to assign phandles to all labelled
  nodes
- /include/ and /incbin/ now handle absolute paths correctly
- The manpage now has information about overlays, including how to apply
  them and how to generate them
- Syntactic sugar for overlays is now supported, allowing an overlay DTS
  like:

=
/dts-v1/;
/plugin/;

&foo {
    foo,status = "okay";
};
=

to generate a fragment targetting <&foo>.
This commit is contained in:
kevans 2018-01-19 21:20:24 +00:00
parent a409fce80d
commit 748a3d44a1
7 changed files with 439 additions and 80 deletions

View File

@ -4,8 +4,6 @@ PROG_CXX=dtc
SRCS= dtc.cc input_buffer.cc string.cc dtb.cc fdt.cc checking.cc
MAN= dtc.1
WARNS?= 3
CXXFLAGS+= -std=c++11 -fno-rtti -fno-exceptions
NO_SHARED?=NO

View File

@ -30,7 +30,7 @@
.\"
.\" $FreeBSD$
.\"/
.Dd January 1, 2013
.Dd January 17, 2018
.Dt DTC 1
.Os
.Sh NAME
@ -57,7 +57,7 @@
The
.Nm
utility converts flattened device tree (FDT) representations.
It is most commonly used to generate device tree blobs (DTB), the binary
It is most commonly used to generate device tree blobs (DTB), the binary
representation of an FDT, from device tree sources (DTS), the ASCII text source
representation.
.Pp
@ -153,9 +153,9 @@ format for property values.
These allow property value to be specified on the command line.
.It Fl R Ar entries
The number of empty reservation table entries to pad the table with.
This is
useful if you are generating a device tree blob for bootloader or similar that
needs to reserve some memory before passing control to the operating system.
This is useful if you are generating a device tree blob for bootloader or
similar that needs to reserve some memory before passing control to the
operating system.
.It Fl S Ar bytes
The minimum size in bytes of the blob.
The blob will be padded after the strings table to ensure that it is the
@ -244,6 +244,54 @@ Checks that all
.Va /delete-node/
statements refer to nodes that are merged.
.El
.Sh OVERLAYS
The utility provides support for generating overlays, also known as plugins.
Overlays are a method of patching a base device tree that has been compiled with
the
.Fl @
flag, with some limited support for patching device trees that were not compiled
with the
.Fl @
flag.
.Pp
To denote that a DTS is intended to be used as an overlay,
.Va /plugin/;
should be included in the header, following any applicable
.Va /dts-v1/;
tag.
.Pp
Conventional overlays are crafted by creating
.Va fragment
nodes in a root.
Each fragment node must have either a
.Va target
property set to a label reference, or a
.Va target-path
string property set to a path.
It must then have an
.Va __overlay__
child node, whose properties and child nodes are merged into the base device
tree when the overlay is applied.
.Pp
Much simpler syntactic sugar was later invented to simplify generating overlays.
Instead of creating targetted fragments manually, one can instead create a root
node that targets a label in the base node using the
.Va &label
syntax supported in conventional DTS.
This will indicate that a fragment should be generated for the node, with the
given
.Va label
being the target, and the properties and child nodes will be used as the
__overlay__.
.Pp
Both conventional overlays and the later-added syntactic sugar are supported.
.Pp
Overlay blobs can be applied at boot time by setting
.Va fdt_overlays
in
.Xr loader.conf 5 .
Multiple overlays may be specified, and they will be applied in the order given.
.El
.Sh EXAMPLES
The command:
.Pp
@ -254,8 +302,7 @@ will generate a
file from the device tree source
.Pa device.dts
and print errors if any occur during parsing or property checking.
The
resulting file can be assembled and linked into a binary.
The resulting file can be assembled and linked into a binary.
.Pp
The command:
.Pp
@ -265,6 +312,33 @@ will write the device tree source for the device tree blob
.Pa device.dtb
to the standard output.
This is useful when debugging device trees.
.Pp
The command:
.Pp
.Dl "dtc -@ -O dtb -I dts -o device.dtb device.dts"
.Pp
will generate a
.Pa device.dtb
file from the device tree source
.Pa device.dts
with a __symbols__ node included so that overlays may be applied to it.
.Pp
The command:
.Pp
.Dl "dtc -@ -O dtb -I dts -o device_overlay.dtbo device_overlay.dts"
.Pp
will generate a
.Pa device_overlay.dtbo
file, using the standard extension for a device tree overlay, from the device
tree source
.Pa device_overlay.dts .
A __symbols__ node will be included so that overlays may be applied to it.
The presence of a
.Va /plugin/;
directive in
.Pa device_overlay.dts
will indicate to the utility that it should also generate the underlying
metadata required in overlays.
.Sh COMPATIBILITY
This utility is intended to be compatible with the device tree compiler
provided by elinux.org.

View File

@ -49,6 +49,8 @@
using namespace dtc;
using std::string;
namespace {
/**
* The current major version of the tool.
*/
@ -65,7 +67,7 @@ int version_minor_compatible = 4;
int version_patch = 0;
int version_patch_compatible = 0;
static void usage(const string &argv0)
void usage(const string &argv0)
{
fprintf(stderr, "Usage:\n"
"\t%s\t[-fhsv@] [-b boot_cpu_id] [-d dependency_file]"
@ -80,7 +82,7 @@ static void usage(const string &argv0)
/**
* Prints the current version of this program..
*/
static void version(const char* progname)
void version(const char* progname)
{
fprintf(stdout, "Version: %s %d.%d.%d compatible with gpl dtc %d.%d.%d\n", progname,
version_major, version_minor, version_patch,
@ -88,6 +90,8 @@ static void version(const char* progname)
version_patch_compatible);
}
} // Anonymous namespace
using fdt::device_tree;
int

View File

@ -497,6 +497,29 @@ property::property(text_input_buffer &input,
return;
case '/':
{
if (input.consume("/incbin/(\""))
{
auto loc = input.location();
std::string filename = input.parse_to('"');
if (!(valid = input.consume('"')))
{
loc.report_error("Syntax error, expected '\"' to terminate /incbin/(");
return;
}
property_value v;
if (!(valid = input.read_binary_file(filename, v.byte_data)))
{
input.parse_error("Cannot open binary include file");
return;
}
if (!(valid &= input.consume(')')))
{
input.parse_error("Syntax error, expected ')' to terminate /incbin/(");
return;
}
values.push_back(v);
break;
}
unsigned long long bits = 0;
valid = input.consume("/bits/");
input.next_token();
@ -999,7 +1022,7 @@ node::get_property(const string &key)
}
void
node::merge_node(node_ptr other)
node::merge_node(node_ptr &other)
{
for (auto &l : other->labels)
{
@ -1034,7 +1057,7 @@ node::merge_node(node_ptr other)
{
if (i->name == c->name && i->unit_address == c->unit_address)
{
i->merge_node(std::move(c));
i->merge_node(c);
found = true;
break;
}
@ -1207,8 +1230,67 @@ device_tree::collect_names()
collect_names_recursive(root, p);
}
property_ptr
device_tree::assign_phandle(node *n, uint32_t &phandle)
{
// If there is an existing phandle, use it
property_ptr p = n->get_property("phandle");
if (p == 0)
{
p = n->get_property("linux,phandle");
}
if (p == 0)
{
// Otherwise insert a new phandle node
property_value v;
while (used_phandles.find(phandle) != used_phandles.end())
{
// Note that we only don't need to
// store this phandle in the set,
// because we are monotonically
// increasing the value of phandle and
// so will only ever revisit this value
// if we have used 2^32 phandles, at
// which point our blob won't fit in
// any 32-bit system and we've done
// something badly wrong elsewhere
// already.
phandle++;
}
push_big_endian(v.byte_data, phandle++);
if (phandle_node_name == BOTH || phandle_node_name == LINUX)
{
p.reset(new property("linux,phandle"));
p->add_value(v);
n->add_property(p);
}
if (phandle_node_name == BOTH || phandle_node_name == EPAPR)
{
p.reset(new property("phandle"));
p->add_value(v);
n->add_property(p);
}
}
return (p);
}
void
device_tree::resolve_cross_references()
device_tree::assign_phandles(node_ptr &n, uint32_t &next)
{
if (!n->labels.empty())
{
assign_phandle(n.get(), next);
}
for (auto &c : n->child_nodes())
{
assign_phandles(c, next);
}
}
void
device_tree::resolve_cross_references(uint32_t &phandle)
{
for (auto *pv : cross_references)
{
@ -1252,7 +1334,6 @@ device_tree::resolve_cross_references()
});
assert(sorted_phandles.size() == fixups.size());
uint32_t phandle = 1;
for (auto &i : sorted_phandles)
{
string target_name = i.get().val.string_data;
@ -1334,43 +1415,7 @@ device_tree::resolve_cross_references()
}
}
// If there is an existing phandle, use it
property_ptr p = target->get_property("phandle");
if (p == 0)
{
p = target->get_property("linux,phandle");
}
if (p == 0)
{
// Otherwise insert a new phandle node
property_value v;
while (used_phandles.find(phandle) != used_phandles.end())
{
// Note that we only don't need to
// store this phandle in the set,
// because we are monotonically
// increasing the value of phandle and
// so will only ever revisit this value
// if we have used 2^32 phandles, at
// which point our blob won't fit in
// any 32-bit system and we've done
// something badly wrong elsewhere
// already.
phandle++;
}
push_big_endian(v.byte_data, phandle++);
if (phandle_node_name == BOTH || phandle_node_name == LINUX)
{
p.reset(new property("linux,phandle"));
p->add_value(v);
target->add_property(p);
}
if (phandle_node_name == BOTH || phandle_node_name == EPAPR)
{
p.reset(new property("phandle"));
p->add_value(v);
target->add_property(p);
}
}
property_ptr p = assign_phandle(target, phandle);
p->begin()->push_to_buffer(i.get().val.byte_data);
assert(i.get().val.byte_data.size() == 4);
}
@ -1644,6 +1689,72 @@ device_tree::node_path::to_string() const
return path;
}
node_ptr
device_tree::create_fragment_wrapper(node_ptr &node, int &fragnum)
{
// In a plugin, we can massage these non-/ root nodes into into a fragment
std::string fragment_address = "fragment@" + std::to_string(fragnum);
++fragnum;
std::vector<property_ptr> symbols;
// Intentionally left empty
node_ptr newroot = node::create_special_node("", symbols);
node_ptr wrapper = node::create_special_node("__overlay__", symbols);
// Generate the fragment with target = <&name>
property_value v;
v.string_data = node->name;
v.type = property_value::PHANDLE;
auto prop = std::make_shared<property>(std::string("target"));
prop->add_value(v);
symbols.push_back(prop);
node_ptr fragment = node::create_special_node(fragment_address, symbols);
wrapper->merge_node(node);
fragment->add_child(std::move(wrapper));
newroot->add_child(std::move(fragment));
return newroot;
}
node_ptr
device_tree::generate_root(node_ptr &node, int &fragnum)
{
string name = node->name;
if (name == string())
{
return std::move(node);
}
else if (!is_plugin)
{
return nullptr;
}
return create_fragment_wrapper(node, fragnum);
}
void
device_tree::reassign_fragment_numbers(node_ptr &node, int &delta)
{
for (auto &c : node->child_nodes())
{
if (c->name == std::string("fragment"))
{
int current_address = std::stoi(c->unit_address, nullptr, 16);
std::ostringstream new_address;
current_address += delta;
// It's possible that we hopped more than one somewhere, so just reset
// delta to the next in sequence.
delta = current_address + 1;
new_address << std::hex << current_address;
c->unit_address = new_address.str();
}
}
}
void
device_tree::parse_dts(const string &fn, FILE *depfile)
{
@ -1665,6 +1776,7 @@ device_tree::parse_dts(const string &fn, FILE *depfile)
dirname(fn),
depfile);
bool read_header = false;
int fragnum = 0;
parse_file(input, roots, read_header);
switch (roots.size())
{
@ -1673,18 +1785,36 @@ device_tree::parse_dts(const string &fn, FILE *depfile)
input.parse_error("Failed to find root node /.");
return;
case 1:
root = std::move(roots[0]);
root = generate_root(roots[0], fragnum);
if (!root)
{
valid = false;
input.parse_error("Failed to find root node /.");
return;
}
break;
default:
{
root = std::move(roots[0]);
root = generate_root(roots[0], fragnum);
if (!root)
{
valid = false;
input.parse_error("Failed to find root node /.");
return;
}
for (auto i=++(roots.begin()), e=roots.end() ; i!=e ; ++i)
{
auto &node = *i;
string name = node->name;
if (name == string())
{
root->merge_node(std::move(node));
if (is_plugin)
{
// Re-assign any fragment numbers based on a delta of
// fragnum before we merge it
reassign_fragment_numbers(node, fragnum);
}
root->merge_node(node);
}
else
{
@ -1696,18 +1826,34 @@ device_tree::parse_dts(const string &fn, FILE *depfile)
}
if (existing == node_names.end())
{
fprintf(stderr, "Unable to merge node: %s\n", name.c_str());
if (is_plugin)
{
auto fragment = create_fragment_wrapper(node, fragnum);
root->merge_node(fragment);
}
else
{
existing->second->merge_node(std::move(node));
fprintf(stderr, "Unable to merge node: %s\n", name.c_str());
}
}
else
{
existing->second->merge_node(node);
}
}
}
}
}
collect_names();
resolve_cross_references();
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
// order semi-predictable and stable.
if (write_symbols)
{
assign_phandles(root, phandle);
}
resolve_cross_references(phandle);
if (write_symbols)
{
std::vector<property_ptr> symbols;
@ -1769,21 +1915,72 @@ device_tree::parse_dts(const string &fn, FILE *depfile)
}
symbols.clear();
// If we have any resolved phandle references in this plugin, then
// we must leave a property in the /__local_fixups__ node whose key
// is 'fixup' and whose value is as described above.
// we must create a child in the __local_fixups__ node whose path
// matches the node path from the root and whose value contains the
// location of the reference within a property.
// Create a local_fixups node that is initially empty.
node_ptr local_fixups = node::create_special_node("__local_fixups__", symbols);
for (auto &i : fixups)
{
if (!i.val.is_phandle())
{
continue;
}
symbols.push_back(create_fixup_entry(i, "fixup"));
node *n = local_fixups.get();
for (auto &p : i.path)
{
// Skip the implicit root
if (p.first.empty())
{
continue;
}
bool found = false;
for (auto &c : n->child_nodes())
{
if (c->name == p.first)
{
n = c.get();
found = true;
break;
}
}
if (!found)
{
n->add_child(node::create_special_node(p.first, symbols));
n = (--n->child_end())->get();
}
}
assert(n);
property_value pv;
push_big_endian(pv.byte_data, static_cast<uint32_t>(i.prop->offset_of_value(i.val)));
pv.type = property_value::BINARY;
auto key = i.prop->get_key();
property_ptr prop = n->get_property(key);
// If we don't have an existing property then create one and
// use this property value
if (!prop)
{
prop = std::make_shared<property>(std::move(key));
n->add_property(prop);
prop->add_value(pv);
}
else
{
// If we do have an existing property value, try to append
// this value.
property_value &old_val = *(--prop->end());
if (!old_val.try_to_merge(pv))
{
prop->add_value(pv);
}
}
}
// We've iterated over all fixups, but only emit the
// __local_fixups__ if we found some that were resolved internally.
if (!symbols.empty())
if (local_fixups->child_begin() != local_fixups->child_end())
{
root->add_child(node::create_special_node("__local_fixups__", symbols));
root->add_child(std::move(local_fixups));
}
}
}

View File

@ -370,7 +370,7 @@ class property
/**
* Returns the key for this property.
*/
inline std::string get_key()
inline const std::string &get_key()
{
return key;
}
@ -620,7 +620,7 @@ class node
* Merges a node into this one. Any properties present in both are
* overridden, any properties present in only one are preserved.
*/
void merge_node(node_ptr other);
void merge_node(node_ptr &other);
/**
* Write this node to the specified output. Although nodes do not
* refer to a string table directly, their properties do. The string
@ -676,12 +676,12 @@ class device_tree
/**
* The format that we should use for writing phandles.
*/
phandle_format phandle_node_name;
phandle_format phandle_node_name = EPAPR;
/**
* Flag indicating that this tree is valid. This will be set to false
* on parse errors.
*/
bool valid;
bool valid = true;
/**
* Type used for memory reservations. A reservation is two 64-bit
* values indicating a base address and length in memory that the
@ -775,23 +775,23 @@ class device_tree
/**
* The default boot CPU, specified in the device tree header.
*/
uint32_t boot_cpu;
uint32_t boot_cpu = 0;
/**
* The number of empty reserve map entries to generate in the blob.
*/
uint32_t spare_reserve_map_entries;
uint32_t spare_reserve_map_entries = 0;
/**
* The minimum size in bytes of the blob.
*/
uint32_t minimum_blob_size;
uint32_t minimum_blob_size = 0;
/**
* The number of bytes of padding to add to the end of the blob.
*/
uint32_t blob_padding;
uint32_t blob_padding = 0;
/**
* Is this tree a plugin?
*/
bool is_plugin;
bool is_plugin = false;
/**
* Visit all of the nodes recursively, and if they have labels then add
* them to the node_paths and node_names vectors so that they can be
@ -799,6 +799,12 @@ class device_tree
* properties that have been explicitly added.
*/
void collect_names_recursive(node_ptr &n, node_path &path);
/**
* Assign a phandle property to a single node. The next parameter
* holds the phandle to be assigned, and will be incremented upon
* assignment.
*/
property_ptr assign_phandle(node *n, uint32_t &next);
/**
* Assign phandle properties to all nodes that have been referenced and
* require one. This method will recursively visit the tree starting at
@ -812,9 +818,11 @@ class device_tree
/**
* Resolves all cross references. Any properties that refer to another
* node must have their values replaced by either the node path or
* phandle value.
* 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.
*/
void resolve_cross_references();
void resolve_cross_references(uint32_t &phandle);
/**
* 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
@ -859,15 +867,33 @@ class device_tree
/**
* Default constructor. Creates a valid, but empty FDT.
*/
device_tree() : phandle_node_name(EPAPR), valid(true),
boot_cpu(0), spare_reserve_map_entries(0),
minimum_blob_size(0), blob_padding(0) {}
device_tree() {}
/**
* Constructs a device tree from the specified file name, referring to
* a file that contains a device tree blob.
*/
void parse_dtb(const std::string &fn, FILE *depfile);
/**
* Construct a fragment wrapper around node. This will assume that node's
* name may be used as the target of the fragment, and the contents are to
* be wrapped in an __overlay__ node. The fragment wrapper will be assigned
* fragnumas its fragment number, and fragment number will be incremented.
*/
node_ptr create_fragment_wrapper(node_ptr &node, int &fragnum);
/**
* Generate a root node from the node passed in. This is sensitive to
* whether we're in a plugin context or not, so that if we're in a plugin we
* can circumvent any errors that might normally arise from a non-/ root.
* fragnum will be assigned to any fragment wrapper generated as a result
* of the call, and fragnum will be incremented.
*/
node_ptr generate_root(node_ptr &node, int &fragnum);
/**
* Reassign any fragment numbers from this new node, based on the given
* delta.
*/
void reassign_fragment_numbers(node_ptr &node, int &delta);
/*
* Constructs a device tree from the specified file name, referring to
* a file that contains device tree source.
*/
@ -905,9 +931,12 @@ class device_tree
* Sorts the tree. Useful for debugging device trees.
*/
void sort()
{
if (root)
{
root->sort();
}
}
/**
* Adds a path to search for include files. The argument must be a
* nul-terminated string representing the path. The device tree keeps

View File

@ -206,9 +206,9 @@ text_input_buffer::handle_include()
{
next_token();
string name = parse_property_name();
if (defines.count(name) > 0)
if (defines.count(name) == 0)
{
reallyInclude = true;
reallyInclude = false;
}
consume('/');
}
@ -252,6 +252,48 @@ text_input_buffer::handle_include()
input_stack.push(std::move(include_buffer));
}
bool text_input_buffer::read_binary_file(const std::string &filename, byte_buffer &b)
{
bool try_include_paths = true;
string include_file;
if (filename[0] == '/')
{
include_file = filename;
// Don't try include paths if we're given an absolute path.
// Failing is better so that we don't accidentally do the wrong thing,
// but make it seem like everything is alright.
try_include_paths = false;
}
else
{
include_file = dir + '/' + filename;
}
auto include_buffer = input_buffer::buffer_for_file(include_file, false);
if (include_buffer == 0 && try_include_paths)
{
for (auto i : include_paths)
{
include_file = i + '/' + filename;
include_buffer = input_buffer::buffer_for_file(include_file, false);
if (include_buffer != 0)
{
break;
}
}
}
if (!include_buffer)
{
return false;
}
if (depfile)
{
putc(' ', depfile);
fputs(include_file.c_str(), depfile);
}
b.insert(b.begin(), include_buffer->begin(), include_buffer->end());
return true;
}
input_buffer
input_buffer::buffer_from_offset(int offset, int s)
{

View File

@ -166,6 +166,14 @@ class input_buffer
cursor++;
return *this;
}
const char *begin()
{
return buffer;
}
const char *end()
{
return buffer + size;
}
/**
* Consumes a character. Moves the cursor one character forward if the
* next character matches the argument, returning true. If the current
@ -525,6 +533,13 @@ class text_input_buffer
* Prints a message indicating the location of a parse error.
*/
void parse_error(const char *msg);
/**
* Reads the contents of a binary file into `b`. The file name is assumed
* to be relative to one of the include paths.
*
* Returns true if the file exists and can be read, false otherwise.
*/
bool read_binary_file(const std::string &filename, byte_buffer &b);
private:
/**
* Prints a message indicating the location of a parse error, given a