diff --git a/usr.bin/dtc/Makefile b/usr.bin/dtc/Makefile index a834f622601f..5288637a98b5 100644 --- a/usr.bin/dtc/Makefile +++ b/usr.bin/dtc/Makefile @@ -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 diff --git a/usr.bin/dtc/dtc.1 b/usr.bin/dtc/dtc.1 index 2201691fb1fe..17bf50946dfc 100644 --- a/usr.bin/dtc/dtc.1 +++ b/usr.bin/dtc/dtc.1 @@ -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. diff --git a/usr.bin/dtc/dtc.cc b/usr.bin/dtc/dtc.cc index b5c331dd2c05..4a34419b7a0a 100644 --- a/usr.bin/dtc/dtc.cc +++ b/usr.bin/dtc/dtc.cc @@ -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 diff --git a/usr.bin/dtc/fdt.cc b/usr.bin/dtc/fdt.cc index eb7240448945..a153756574f0 100644 --- a/usr.bin/dtc/fdt.cc +++ b/usr.bin/dtc/fdt.cc @@ -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 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(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 + { + fprintf(stderr, "Unable to merge node: %s\n", name.c_str()); + } } else { - existing->second->merge_node(std::move(node)); + 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 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(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(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)); } } } diff --git a/usr.bin/dtc/fdt.hh b/usr.bin/dtc/fdt.hh index d68795aeaf65..cc80aede23b6 100644 --- a/usr.bin/dtc/fdt.hh +++ b/usr.bin/dtc/fdt.hh @@ -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. */ @@ -906,7 +932,10 @@ class device_tree */ void sort() { - root->sort(); + if (root) + { + root->sort(); + } } /** * Adds a path to search for include files. The argument must be a diff --git a/usr.bin/dtc/input_buffer.cc b/usr.bin/dtc/input_buffer.cc index 4058497cbf1e..38b38b46c878 100644 --- a/usr.bin/dtc/input_buffer.cc +++ b/usr.bin/dtc/input_buffer.cc @@ -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) { diff --git a/usr.bin/dtc/input_buffer.hh b/usr.bin/dtc/input_buffer.hh index 9f7ca2e0ce5e..2a6d741f7b61 100644 --- a/usr.bin/dtc/input_buffer.hh +++ b/usr.bin/dtc/input_buffer.hh @@ -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