7551d83c35
Mainly focus on files that use BSD 2-Clause license, however the tool I was using misidentified many licenses so this was mostly a manual - error prone - task. The Software Package Data Exchange (SPDX) group provides a specification to make it easier for automated tools to detect and summarize well known opensource licenses. We are gradually adopting the specification, noting that the tags are considered only advisory and do not, in any way, superceed or replace the license texts. No functional change intended.
1824 lines
39 KiB
C++
1824 lines
39 KiB
C++
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2013 David Chisnall
|
|
* All rights reserved.
|
|
*
|
|
* This software was developed by SRI International and the University of
|
|
* Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
|
|
* ("CTSRD"), as part of the DARPA CRASH research programme.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
#define __STDC_LIMIT_MACROS 1
|
|
|
|
#include "fdt.hh"
|
|
#include "dtb.hh"
|
|
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
#include <libgen.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <errno.h>
|
|
|
|
using std::string;
|
|
|
|
namespace dtc
|
|
{
|
|
|
|
namespace fdt
|
|
{
|
|
|
|
uint32_t
|
|
property_value::get_as_uint32()
|
|
{
|
|
if (byte_data.size() != 4)
|
|
{
|
|
return 0;
|
|
}
|
|
uint32_t v = 0;
|
|
v &= byte_data[0] << 24;
|
|
v &= byte_data[1] << 16;
|
|
v &= byte_data[2] << 8;
|
|
v &= byte_data[3] << 0;
|
|
return v;
|
|
}
|
|
|
|
void
|
|
property_value::push_to_buffer(byte_buffer &buffer)
|
|
{
|
|
if (!byte_data.empty())
|
|
{
|
|
buffer.insert(buffer.end(), byte_data.begin(), byte_data.end());
|
|
}
|
|
else
|
|
{
|
|
push_string(buffer, string_data, true);
|
|
// Trailing nul
|
|
buffer.push_back(0);
|
|
}
|
|
}
|
|
|
|
void
|
|
property_value::write_dts(FILE *file)
|
|
{
|
|
resolve_type();
|
|
switch (type)
|
|
{
|
|
default:
|
|
assert(0 && "Invalid type");
|
|
case STRING:
|
|
case STRING_LIST:
|
|
case CROSS_REFERENCE:
|
|
write_as_string(file);
|
|
break;
|
|
case PHANDLE:
|
|
write_as_cells(file);
|
|
break;
|
|
case BINARY:
|
|
if (byte_data.size() % 4 == 0)
|
|
{
|
|
write_as_cells(file);
|
|
break;
|
|
}
|
|
write_as_bytes(file);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
property_value::resolve_type()
|
|
{
|
|
if (type != UNKNOWN)
|
|
{
|
|
return;
|
|
}
|
|
if (byte_data.empty())
|
|
{
|
|
type = STRING;
|
|
return;
|
|
}
|
|
if (byte_data.back() == 0)
|
|
{
|
|
bool is_all_printable = true;
|
|
int nuls = 0;
|
|
int bytes = 0;
|
|
bool lastWasNull = false;
|
|
for (auto i : byte_data)
|
|
{
|
|
bytes++;
|
|
is_all_printable &= (i == '\0') || isprint(i);
|
|
if (i == '\0')
|
|
{
|
|
// If there are two nulls in a row, then we're probably binary.
|
|
if (lastWasNull)
|
|
{
|
|
type = BINARY;
|
|
return;
|
|
}
|
|
nuls++;
|
|
lastWasNull = true;
|
|
}
|
|
else
|
|
{
|
|
lastWasNull = false;
|
|
}
|
|
if (!is_all_printable)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if ((is_all_printable && (bytes > nuls)) || bytes == 0)
|
|
{
|
|
type = STRING;
|
|
if (nuls > 1)
|
|
{
|
|
type = STRING_LIST;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
type = BINARY;
|
|
}
|
|
|
|
size_t
|
|
property_value::size()
|
|
{
|
|
if (!byte_data.empty())
|
|
{
|
|
return byte_data.size();
|
|
}
|
|
return string_data.size() + 1;
|
|
}
|
|
|
|
void
|
|
property_value::write_as_string(FILE *file)
|
|
{
|
|
putc('"', file);
|
|
if (byte_data.empty())
|
|
{
|
|
fputs(string_data.c_str(), file);
|
|
}
|
|
else
|
|
{
|
|
bool hasNull = (byte_data.back() == '\0');
|
|
// Remove trailing null bytes from the string before printing as dts.
|
|
if (hasNull)
|
|
{
|
|
byte_data.pop_back();
|
|
}
|
|
for (auto i : byte_data)
|
|
{
|
|
// FIXME Escape tabs, newlines, and so on.
|
|
if (i == '\0')
|
|
{
|
|
fputs("\", \"", file);
|
|
continue;
|
|
}
|
|
putc(i, file);
|
|
}
|
|
if (hasNull)
|
|
{
|
|
byte_data.push_back('\0');
|
|
}
|
|
}
|
|
putc('"', file);
|
|
}
|
|
|
|
void
|
|
property_value::write_as_cells(FILE *file)
|
|
{
|
|
putc('<', file);
|
|
assert((byte_data.size() % 4) == 0);
|
|
for (auto i=byte_data.begin(), e=byte_data.end(); i!=e ; ++i)
|
|
{
|
|
uint32_t v = 0;
|
|
v = (v << 8) | *i;
|
|
++i;
|
|
v = (v << 8) | *i;
|
|
++i;
|
|
v = (v << 8) | *i;
|
|
++i;
|
|
v = (v << 8) | *i;
|
|
fprintf(file, "0x%" PRIx32, v);
|
|
if (i+1 != e)
|
|
{
|
|
putc(' ', file);
|
|
}
|
|
}
|
|
putc('>', file);
|
|
}
|
|
|
|
void
|
|
property_value::write_as_bytes(FILE *file)
|
|
{
|
|
putc('[', file);
|
|
for (auto i=byte_data.begin(), e=byte_data.end(); i!=e ; i++)
|
|
{
|
|
fprintf(file, "%02hhx", *i);
|
|
if (i+1 != e)
|
|
{
|
|
putc(' ', file);
|
|
}
|
|
}
|
|
putc(']', file);
|
|
}
|
|
|
|
void
|
|
property::parse_string(text_input_buffer &input)
|
|
{
|
|
property_value v;
|
|
assert(*input == '"');
|
|
++input;
|
|
std::vector<char> bytes;
|
|
bool isEscaped = false;
|
|
while (char c = *input)
|
|
{
|
|
if (c == '"' && !isEscaped)
|
|
{
|
|
input.consume('"');
|
|
break;
|
|
}
|
|
isEscaped = (c == '\\');
|
|
bytes.push_back(c);
|
|
++input;
|
|
}
|
|
v.string_data = string(bytes.begin(), bytes.end());
|
|
values.push_back(v);
|
|
}
|
|
|
|
void
|
|
property::parse_cells(text_input_buffer &input, int cell_size)
|
|
{
|
|
assert(*input == '<');
|
|
++input;
|
|
property_value v;
|
|
input.next_token();
|
|
while (!input.consume('>'))
|
|
{
|
|
input.next_token();
|
|
// If this is a phandle then we need to get the name of the
|
|
// referenced node
|
|
if (input.consume('&'))
|
|
{
|
|
if (cell_size != 32)
|
|
{
|
|
input.parse_error("reference only permitted in 32-bit arrays");
|
|
valid = false;
|
|
return;
|
|
}
|
|
input.next_token();
|
|
string referenced;
|
|
if (!input.consume('{'))
|
|
{
|
|
referenced = input.parse_node_name();
|
|
}
|
|
else
|
|
{
|
|
referenced = input.parse_to('}');
|
|
input.consume('}');
|
|
}
|
|
if (referenced.empty())
|
|
{
|
|
input.parse_error("Expected node name");
|
|
valid = false;
|
|
return;
|
|
}
|
|
input.next_token();
|
|
// If we already have some bytes, make the phandle a
|
|
// separate component.
|
|
if (!v.byte_data.empty())
|
|
{
|
|
values.push_back(v);
|
|
v = property_value();
|
|
}
|
|
v.string_data = referenced;
|
|
v.type = property_value::PHANDLE;
|
|
values.push_back(v);
|
|
v = property_value();
|
|
}
|
|
else
|
|
{
|
|
//FIXME: We should support labels in the middle
|
|
//of these, but we don't.
|
|
unsigned long long val;
|
|
if (!input.consume_integer_expression(val))
|
|
{
|
|
input.parse_error("Expected numbers in array of cells");
|
|
valid = false;
|
|
return;
|
|
}
|
|
switch (cell_size)
|
|
{
|
|
case 8:
|
|
v.byte_data.push_back(val);
|
|
break;
|
|
case 16:
|
|
push_big_endian(v.byte_data, (uint16_t)val);
|
|
break;
|
|
case 32:
|
|
push_big_endian(v.byte_data, (uint32_t)val);
|
|
break;
|
|
case 64:
|
|
push_big_endian(v.byte_data, (uint64_t)val);
|
|
break;
|
|
default:
|
|
assert(0 && "Invalid cell size!");
|
|
}
|
|
input.next_token();
|
|
}
|
|
}
|
|
// Don't store an empty string value here.
|
|
if (v.byte_data.size() > 0)
|
|
{
|
|
values.push_back(v);
|
|
}
|
|
}
|
|
|
|
void
|
|
property::parse_bytes(text_input_buffer &input)
|
|
{
|
|
assert(*input == '[');
|
|
++input;
|
|
property_value v;
|
|
input.next_token();
|
|
while (!input.consume(']'))
|
|
{
|
|
{
|
|
//FIXME: We should support
|
|
//labels in the middle of
|
|
//these, but we don't.
|
|
uint8_t val;
|
|
if (!input.consume_hex_byte(val))
|
|
{
|
|
input.parse_error("Expected hex bytes in array of bytes");
|
|
valid = false;
|
|
return;
|
|
}
|
|
v.byte_data.push_back(val);
|
|
input.next_token();
|
|
}
|
|
}
|
|
values.push_back(v);
|
|
}
|
|
|
|
void
|
|
property::parse_reference(text_input_buffer &input)
|
|
{
|
|
assert(*input == '&');
|
|
++input;
|
|
input.next_token();
|
|
property_value v;
|
|
v.string_data = input.parse_node_name();
|
|
if (v.string_data.empty())
|
|
{
|
|
input.parse_error("Expected node name");
|
|
valid = false;
|
|
return;
|
|
}
|
|
v.type = property_value::CROSS_REFERENCE;
|
|
values.push_back(v);
|
|
}
|
|
|
|
property::property(input_buffer &structs, input_buffer &strings)
|
|
{
|
|
uint32_t name_offset;
|
|
uint32_t length;
|
|
valid = structs.consume_binary(length) &&
|
|
structs.consume_binary(name_offset);
|
|
if (!valid)
|
|
{
|
|
fprintf(stderr, "Failed to read property\n");
|
|
return;
|
|
}
|
|
// Find the name
|
|
input_buffer name_buffer = strings.buffer_from_offset(name_offset);
|
|
if (name_buffer.finished())
|
|
{
|
|
fprintf(stderr, "Property name offset %" PRIu32
|
|
" is past the end of the strings table\n",
|
|
name_offset);
|
|
valid = false;
|
|
return;
|
|
}
|
|
key = name_buffer.parse_to(0);
|
|
|
|
// If we're empty, do not push anything as value.
|
|
if (!length)
|
|
return;
|
|
|
|
// Read the value
|
|
uint8_t byte;
|
|
property_value v;
|
|
for (uint32_t i=0 ; i<length ; i++)
|
|
{
|
|
if (!(valid = structs.consume_binary(byte)))
|
|
{
|
|
fprintf(stderr, "Failed to read property value\n");
|
|
return;
|
|
}
|
|
v.byte_data.push_back(byte);
|
|
}
|
|
values.push_back(v);
|
|
}
|
|
|
|
void property::parse_define(text_input_buffer &input, define_map *defines)
|
|
{
|
|
input.consume('$');
|
|
if (!defines)
|
|
{
|
|
input.parse_error("No predefined properties to match name\n");
|
|
valid = false;
|
|
return;
|
|
}
|
|
string name = input.parse_property_name();
|
|
define_map::iterator found;
|
|
if ((name == string()) ||
|
|
((found = defines->find(name)) == defines->end()))
|
|
{
|
|
input.parse_error("Undefined property name\n");
|
|
valid = false;
|
|
return;
|
|
}
|
|
values.push_back((*found).second->values[0]);
|
|
}
|
|
|
|
property::property(text_input_buffer &input,
|
|
string &&k,
|
|
string_set &&l,
|
|
bool semicolonTerminated,
|
|
define_map *defines) : key(k), labels(l), valid(true)
|
|
{
|
|
do {
|
|
input.next_token();
|
|
switch (*input)
|
|
{
|
|
case '$':
|
|
{
|
|
parse_define(input, defines);
|
|
if (valid)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
default:
|
|
input.parse_error("Invalid property value.");
|
|
valid = false;
|
|
return;
|
|
case '/':
|
|
{
|
|
unsigned long long bits = 0;
|
|
valid = input.consume("/bits/");
|
|
input.next_token();
|
|
valid &= input.consume_integer(bits);
|
|
if ((bits != 8) &&
|
|
(bits != 16) &&
|
|
(bits != 32) &&
|
|
(bits != 64)) {
|
|
input.parse_error("Invalid size for elements");
|
|
valid = false;
|
|
}
|
|
if (!valid) return;
|
|
input.next_token();
|
|
if (*input != '<')
|
|
{
|
|
input.parse_error("/bits/ directive is only valid on arrays");
|
|
valid = false;
|
|
return;
|
|
}
|
|
parse_cells(input, bits);
|
|
break;
|
|
}
|
|
case '"':
|
|
parse_string(input);
|
|
break;
|
|
case '<':
|
|
parse_cells(input, 32);
|
|
break;
|
|
case '[':
|
|
parse_bytes(input);
|
|
break;
|
|
case '&':
|
|
parse_reference(input);
|
|
break;
|
|
case ';':
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
input.next_token();
|
|
} while (input.consume(','));
|
|
if (semicolonTerminated && !input.consume(';'))
|
|
{
|
|
input.parse_error("Expected ; at end of property");
|
|
valid = false;
|
|
}
|
|
}
|
|
|
|
property_ptr
|
|
property::parse_dtb(input_buffer &structs, input_buffer &strings)
|
|
{
|
|
property_ptr p(new property(structs, strings));
|
|
if (!p->valid)
|
|
{
|
|
p = nullptr;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
property_ptr
|
|
property::parse(text_input_buffer &input, string &&key, string_set &&label,
|
|
bool semicolonTerminated, define_map *defines)
|
|
{
|
|
property_ptr p(new property(input,
|
|
std::move(key),
|
|
std::move(label),
|
|
semicolonTerminated,
|
|
defines));
|
|
if (!p->valid)
|
|
{
|
|
p = nullptr;
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void
|
|
property::write(dtb::output_writer &writer, dtb::string_table &strings)
|
|
{
|
|
writer.write_token(dtb::FDT_PROP);
|
|
byte_buffer value_buffer;
|
|
for (value_iterator i=begin(), e=end() ; i!=e ; ++i)
|
|
{
|
|
i->push_to_buffer(value_buffer);
|
|
}
|
|
writer.write_data((uint32_t)value_buffer.size());
|
|
writer.write_comment(key);
|
|
writer.write_data(strings.add_string(key));
|
|
writer.write_data(value_buffer);
|
|
}
|
|
|
|
bool
|
|
property_value::try_to_merge(property_value &other)
|
|
{
|
|
resolve_type();
|
|
switch (type)
|
|
{
|
|
case UNKNOWN:
|
|
__builtin_unreachable();
|
|
assert(0);
|
|
return false;
|
|
case EMPTY:
|
|
*this = other;
|
|
case STRING:
|
|
case STRING_LIST:
|
|
case CROSS_REFERENCE:
|
|
return false;
|
|
case PHANDLE:
|
|
case BINARY:
|
|
if (other.type == PHANDLE || other.type == BINARY)
|
|
{
|
|
type = BINARY;
|
|
byte_data.insert(byte_data.end(), other.byte_data.begin(),
|
|
other.byte_data.end());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
property::write_dts(FILE *file, int indent)
|
|
{
|
|
for (int i=0 ; i<indent ; i++)
|
|
{
|
|
putc('\t', file);
|
|
}
|
|
#ifdef PRINT_LABELS
|
|
for (auto &l : labels)
|
|
{
|
|
fputs(l.c_str(), file);
|
|
fputs(": ", file);
|
|
}
|
|
#endif
|
|
if (key != string())
|
|
{
|
|
fputs(key.c_str(), file);
|
|
}
|
|
if (!values.empty())
|
|
{
|
|
std::vector<property_value> *vals = &values;
|
|
std::vector<property_value> v;
|
|
// If we've got multiple values then try to merge them all together.
|
|
if (values.size() > 1)
|
|
{
|
|
vals = &v;
|
|
v.push_back(values.front());
|
|
for (auto i=(++begin()), e=end() ; i!=e ; ++i)
|
|
{
|
|
if (!v.back().try_to_merge(*i))
|
|
{
|
|
v.push_back(*i);
|
|
}
|
|
}
|
|
}
|
|
fputs(" = ", file);
|
|
for (auto i=vals->begin(), e=vals->end() ; i!=e ; ++i)
|
|
{
|
|
i->write_dts(file);
|
|
if (i+1 != e)
|
|
{
|
|
putc(',', file);
|
|
putc(' ', file);
|
|
}
|
|
}
|
|
}
|
|
fputs(";\n", file);
|
|
}
|
|
|
|
size_t
|
|
property::offset_of_value(property_value &val)
|
|
{
|
|
size_t off = 0;
|
|
for (auto &v : values)
|
|
{
|
|
if (&v == &val)
|
|
{
|
|
return off;
|
|
}
|
|
off += v.size();
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
string
|
|
node::parse_name(text_input_buffer &input, bool &is_property, const char *error)
|
|
{
|
|
if (!valid)
|
|
{
|
|
return string();
|
|
}
|
|
input.next_token();
|
|
if (is_property)
|
|
{
|
|
return input.parse_property_name();
|
|
}
|
|
string n = input.parse_node_or_property_name(is_property);
|
|
if (n.empty())
|
|
{
|
|
if (n.empty())
|
|
{
|
|
input.parse_error(error);
|
|
valid = false;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
void
|
|
node::visit(std::function<void(node&)> fn)
|
|
{
|
|
fn(*this);
|
|
for (auto &&c : children)
|
|
{
|
|
c->visit(fn);
|
|
}
|
|
}
|
|
|
|
node::node(input_buffer &structs, input_buffer &strings) : valid(true)
|
|
{
|
|
std::vector<char> bytes;
|
|
while (structs[0] != '\0' && structs[0] != '@')
|
|
{
|
|
bytes.push_back(structs[0]);
|
|
++structs;
|
|
}
|
|
name = string(bytes.begin(), bytes.end());
|
|
bytes.clear();
|
|
if (structs[0] == '@')
|
|
{
|
|
++structs;
|
|
while (structs[0] != '\0')
|
|
{
|
|
bytes.push_back(structs[0]);
|
|
++structs;
|
|
}
|
|
unit_address = string(bytes.begin(), bytes.end());
|
|
}
|
|
++structs;
|
|
uint32_t token;
|
|
while (structs.consume_binary(token))
|
|
{
|
|
switch (token)
|
|
{
|
|
default:
|
|
fprintf(stderr, "Unexpected token 0x%" PRIx32
|
|
" while parsing node.\n", token);
|
|
valid = false;
|
|
return;
|
|
// Child node, parse it.
|
|
case dtb::FDT_BEGIN_NODE:
|
|
{
|
|
node_ptr child = node::parse_dtb(structs, strings);
|
|
if (child == 0)
|
|
{
|
|
valid = false;
|
|
return;
|
|
}
|
|
children.push_back(std::move(child));
|
|
break;
|
|
}
|
|
// End of this node, no errors.
|
|
case dtb::FDT_END_NODE:
|
|
return;
|
|
// Property, parse it.
|
|
case dtb::FDT_PROP:
|
|
{
|
|
property_ptr prop = property::parse_dtb(structs, strings);
|
|
if (prop == 0)
|
|
{
|
|
valid = false;
|
|
return;
|
|
}
|
|
props.push_back(prop);
|
|
break;
|
|
}
|
|
break;
|
|
// End of structs table. Should appear after
|
|
// the end of the last node.
|
|
case dtb::FDT_END:
|
|
fprintf(stderr, "Unexpected FDT_END token while parsing node.\n");
|
|
valid = false;
|
|
return;
|
|
// NOPs are padding. Ignore them.
|
|
case dtb::FDT_NOP:
|
|
break;
|
|
}
|
|
}
|
|
fprintf(stderr, "Failed to read token from structs table while parsing node.\n");
|
|
valid = false;
|
|
return;
|
|
}
|
|
|
|
|
|
node::node(const string &n,
|
|
const std::vector<property_ptr> &p)
|
|
: name(n)
|
|
{
|
|
props.insert(props.begin(), p.begin(), p.end());
|
|
}
|
|
|
|
node_ptr node::create_special_node(const string &name,
|
|
const std::vector<property_ptr> &props)
|
|
{
|
|
node_ptr n(new node(name, props));
|
|
return n;
|
|
}
|
|
|
|
node::node(text_input_buffer &input,
|
|
string &&n,
|
|
std::unordered_set<string> &&l,
|
|
string &&a,
|
|
define_map *defines)
|
|
: labels(l), name(n), unit_address(a), valid(true)
|
|
{
|
|
if (!input.consume('{'))
|
|
{
|
|
input.parse_error("Expected { to start new device tree node.\n");
|
|
}
|
|
input.next_token();
|
|
while (valid && !input.consume('}'))
|
|
{
|
|
// flag set if we find any characters that are only in
|
|
// the property name character set, not the node
|
|
bool is_property = false;
|
|
string child_name, child_address;
|
|
std::unordered_set<string> child_labels;
|
|
auto parse_delete = [&](const char *expected, bool at)
|
|
{
|
|
if (child_name == string())
|
|
{
|
|
input.parse_error(expected);
|
|
valid = false;
|
|
return;
|
|
}
|
|
input.next_token();
|
|
if (at && input.consume('@'))
|
|
{
|
|
child_name += '@';
|
|
child_name += parse_name(input, is_property, "Expected unit address");
|
|
}
|
|
if (!input.consume(';'))
|
|
{
|
|
input.parse_error("Expected semicolon");
|
|
valid = false;
|
|
return;
|
|
}
|
|
input.next_token();
|
|
};
|
|
if (input.consume("/delete-node/"))
|
|
{
|
|
input.next_token();
|
|
child_name = input.parse_node_name();
|
|
parse_delete("Expected node name", true);
|
|
if (valid)
|
|
{
|
|
deleted_children.insert(child_name);
|
|
}
|
|
continue;
|
|
}
|
|
if (input.consume("/delete-property/"))
|
|
{
|
|
input.next_token();
|
|
child_name = input.parse_property_name();
|
|
parse_delete("Expected property name", false);
|
|
if (valid)
|
|
{
|
|
deleted_props.insert(child_name);
|
|
}
|
|
continue;
|
|
}
|
|
child_name = parse_name(input, is_property,
|
|
"Expected property or node name");
|
|
while (input.consume(':'))
|
|
{
|
|
// Node labels can contain any characters? The
|
|
// spec doesn't say, so we guess so...
|
|
is_property = false;
|
|
child_labels.insert(std::move(child_name));
|
|
child_name = parse_name(input, is_property, "Expected property or node name");
|
|
}
|
|
if (input.consume('@'))
|
|
{
|
|
child_address = parse_name(input, is_property, "Expected unit address");
|
|
}
|
|
if (!valid)
|
|
{
|
|
return;
|
|
}
|
|
input.next_token();
|
|
// If we're parsing a property, then we must actually do that.
|
|
if (input.consume('='))
|
|
{
|
|
property_ptr p = property::parse(input, std::move(child_name),
|
|
std::move(child_labels), true, defines);
|
|
if (p == 0)
|
|
{
|
|
valid = false;
|
|
}
|
|
else
|
|
{
|
|
props.push_back(p);
|
|
}
|
|
}
|
|
else if (!is_property && *input == ('{'))
|
|
{
|
|
node_ptr child = node::parse(input, std::move(child_name),
|
|
std::move(child_labels), std::move(child_address), defines);
|
|
if (child)
|
|
{
|
|
children.push_back(std::move(child));
|
|
}
|
|
else
|
|
{
|
|
valid = false;
|
|
}
|
|
}
|
|
else if (input.consume(';'))
|
|
{
|
|
props.push_back(property_ptr(new property(std::move(child_name), std::move(child_labels))));
|
|
}
|
|
else
|
|
{
|
|
input.parse_error("Error parsing property. Expected property value");
|
|
valid = false;
|
|
}
|
|
input.next_token();
|
|
}
|
|
input.next_token();
|
|
input.consume(';');
|
|
}
|
|
|
|
bool
|
|
node::cmp_properties(property_ptr &p1, property_ptr &p2)
|
|
{
|
|
return p1->get_key() < p2->get_key();
|
|
}
|
|
|
|
bool
|
|
node::cmp_children(node_ptr &c1, node_ptr &c2)
|
|
{
|
|
if (c1->name == c2->name)
|
|
{
|
|
return c1->unit_address < c2->unit_address;
|
|
}
|
|
return c1->name < c2->name;
|
|
}
|
|
|
|
void
|
|
node::sort()
|
|
{
|
|
std::sort(property_begin(), property_end(), cmp_properties);
|
|
std::sort(child_begin(), child_end(), cmp_children);
|
|
for (auto &c : child_nodes())
|
|
{
|
|
c->sort();
|
|
}
|
|
}
|
|
|
|
node_ptr
|
|
node::parse(text_input_buffer &input,
|
|
string &&name,
|
|
string_set &&label,
|
|
string &&address,
|
|
define_map *defines)
|
|
{
|
|
node_ptr n(new node(input,
|
|
std::move(name),
|
|
std::move(label),
|
|
std::move(address),
|
|
defines));
|
|
if (!n->valid)
|
|
{
|
|
n = 0;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
node_ptr
|
|
node::parse_dtb(input_buffer &structs, input_buffer &strings)
|
|
{
|
|
node_ptr n(new node(structs, strings));
|
|
if (!n->valid)
|
|
{
|
|
n = 0;
|
|
}
|
|
return n;
|
|
}
|
|
|
|
property_ptr
|
|
node::get_property(const string &key)
|
|
{
|
|
for (auto &i : props)
|
|
{
|
|
if (i->get_key() == key)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
node::merge_node(node_ptr other)
|
|
{
|
|
for (auto &l : other->labels)
|
|
{
|
|
labels.insert(l);
|
|
}
|
|
// 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
|
|
// entire vector will fit (easily) into cache, so iterating
|
|
// over it repeatedly isn't that expensive.
|
|
for (auto &p : other->properties())
|
|
{
|
|
bool found = false;
|
|
for (auto &mp : properties())
|
|
{
|
|
if (mp->get_key() == p->get_key())
|
|
{
|
|
mp = p;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
add_property(p);
|
|
}
|
|
}
|
|
for (auto &c : other->children)
|
|
{
|
|
bool found = false;
|
|
for (auto &i : children)
|
|
{
|
|
if (i->name == c->name && i->unit_address == c->unit_address)
|
|
{
|
|
i->merge_node(std::move(c));
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
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
|
|
node::write(dtb::output_writer &writer, dtb::string_table &strings)
|
|
{
|
|
writer.write_token(dtb::FDT_BEGIN_NODE);
|
|
byte_buffer name_buffer;
|
|
push_string(name_buffer, name);
|
|
if (unit_address != string())
|
|
{
|
|
name_buffer.push_back('@');
|
|
push_string(name_buffer, unit_address);
|
|
}
|
|
writer.write_comment(name);
|
|
writer.write_data(name_buffer);
|
|
writer.write_data((uint8_t)0);
|
|
for (auto p : properties())
|
|
{
|
|
p->write(writer, strings);
|
|
}
|
|
for (auto &c : child_nodes())
|
|
{
|
|
c->write(writer, strings);
|
|
}
|
|
writer.write_token(dtb::FDT_END_NODE);
|
|
}
|
|
|
|
void
|
|
node::write_dts(FILE *file, int indent)
|
|
{
|
|
for (int i=0 ; i<indent ; i++)
|
|
{
|
|
putc('\t', file);
|
|
}
|
|
#ifdef PRINT_LABELS
|
|
for (auto &label : labels)
|
|
{
|
|
fprintf(file, "%s: ", label.c_str());
|
|
}
|
|
#endif
|
|
if (name != string())
|
|
{
|
|
fputs(name.c_str(), file);
|
|
}
|
|
if (unit_address != string())
|
|
{
|
|
putc('@', file);
|
|
fputs(unit_address.c_str(), file);
|
|
}
|
|
fputs(" {\n\n", file);
|
|
for (auto p : properties())
|
|
{
|
|
p->write_dts(file, indent+1);
|
|
}
|
|
for (auto &c : child_nodes())
|
|
{
|
|
c->write_dts(file, indent+1);
|
|
}
|
|
for (int i=0 ; i<indent ; i++)
|
|
{
|
|
putc('\t', file);
|
|
}
|
|
fputs("};\n", file);
|
|
}
|
|
|
|
void
|
|
device_tree::collect_names_recursive(node_ptr &n, node_path &path)
|
|
{
|
|
path.push_back(std::make_pair(n->name, n->unit_address));
|
|
for (const string &name : n->labels)
|
|
{
|
|
if (name != string())
|
|
{
|
|
auto iter = node_names.find(name);
|
|
if (iter == node_names.end())
|
|
{
|
|
node_names.insert(std::make_pair(name, n.get()));
|
|
node_paths.insert(std::make_pair(name, path));
|
|
}
|
|
else
|
|
{
|
|
node_names.erase(iter);
|
|
auto i = node_paths.find(name);
|
|
if (i != node_paths.end())
|
|
{
|
|
node_paths.erase(name);
|
|
}
|
|
fprintf(stderr, "Label not unique: %s. References to this label will not be resolved.\n", name.c_str());
|
|
}
|
|
}
|
|
}
|
|
for (auto &c : n->child_nodes())
|
|
{
|
|
collect_names_recursive(c, path);
|
|
}
|
|
// Now we collect the phandles and properties that reference
|
|
// other nodes.
|
|
for (auto &p : n->properties())
|
|
{
|
|
for (auto &v : *p)
|
|
{
|
|
if (v.is_phandle())
|
|
{
|
|
fixups.push_back({path, p, v});
|
|
}
|
|
if (v.is_cross_reference())
|
|
{
|
|
cross_references.push_back(&v);
|
|
}
|
|
}
|
|
if ((p->get_key() == "phandle") ||
|
|
(p->get_key() == "linux,phandle"))
|
|
{
|
|
if (p->begin()->byte_data.size() != 4)
|
|
{
|
|
fprintf(stderr, "Invalid phandle value for node %s. Should be a 4-byte value.\n", n->name.c_str());
|
|
valid = false;
|
|
}
|
|
else
|
|
{
|
|
uint32_t phandle = p->begin()->get_as_uint32();
|
|
used_phandles.insert(std::make_pair(phandle, n.get()));
|
|
}
|
|
}
|
|
}
|
|
path.pop_back();
|
|
}
|
|
|
|
void
|
|
device_tree::collect_names()
|
|
{
|
|
node_path p;
|
|
node_names.clear();
|
|
node_paths.clear();
|
|
cross_references.clear();
|
|
fixups.clear();
|
|
collect_names_recursive(root, p);
|
|
}
|
|
|
|
void
|
|
device_tree::resolve_cross_references()
|
|
{
|
|
for (auto *pv : cross_references)
|
|
{
|
|
node_path path = node_paths[pv->string_data];
|
|
auto p = path.begin();
|
|
auto pe = path.end();
|
|
if (p != pe)
|
|
{
|
|
// Skip the first name in the path. It's always "", and implicitly /
|
|
for (++p ; p!=pe ; ++p)
|
|
{
|
|
pv->byte_data.push_back('/');
|
|
push_string(pv->byte_data, p->first);
|
|
if (!(p->second.empty()))
|
|
{
|
|
pv->byte_data.push_back('@');
|
|
push_string(pv->byte_data, p->second);
|
|
}
|
|
}
|
|
pv->byte_data.push_back(0);
|
|
}
|
|
}
|
|
std::unordered_map<property_value*, fixup&> phandle_set;
|
|
for (auto &i : fixups)
|
|
{
|
|
phandle_set.insert({&i.val, i});
|
|
}
|
|
std::vector<std::reference_wrapper<fixup>> sorted_phandles;
|
|
root->visit([&](node &n) {
|
|
for (auto &p : n.properties())
|
|
{
|
|
for (auto &v : *p)
|
|
{
|
|
auto i = phandle_set.find(&v);
|
|
if (i != phandle_set.end())
|
|
{
|
|
sorted_phandles.push_back(i->second);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
assert(sorted_phandles.size() == fixups.size());
|
|
|
|
uint32_t phandle = 1;
|
|
for (auto &i : sorted_phandles)
|
|
{
|
|
string target_name = i.get().val.string_data;
|
|
node *target = nullptr;
|
|
string possible;
|
|
// If the node name is a path, then look it up by following the path,
|
|
// otherwise jump directly to the named node.
|
|
if (target_name[0] == '/')
|
|
{
|
|
string path;
|
|
target = root.get();
|
|
std::istringstream ss(target_name);
|
|
string path_element;
|
|
// Read the leading /
|
|
std::getline(ss, path_element, '/');
|
|
// Iterate over path elements
|
|
while (!ss.eof())
|
|
{
|
|
path += '/';
|
|
std::getline(ss, path_element, '/');
|
|
std::istringstream nss(path_element);
|
|
string node_name, node_address;
|
|
std::getline(nss, node_name, '@');
|
|
std::getline(nss, node_address, '@');
|
|
node *next = nullptr;
|
|
for (auto &c : target->child_nodes())
|
|
{
|
|
if (c->name == node_name)
|
|
{
|
|
if (c->unit_address == node_address)
|
|
{
|
|
next = c.get();
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
possible = path + c->name;
|
|
if (c->unit_address != string())
|
|
{
|
|
possible += '@';
|
|
possible += c->unit_address;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
path += node_name;
|
|
if (node_address != string())
|
|
{
|
|
path += '@';
|
|
path += node_address;
|
|
}
|
|
target = next;
|
|
if (target == nullptr)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
target = node_names[target_name];
|
|
}
|
|
if (target == nullptr)
|
|
{
|
|
if (is_plugin)
|
|
{
|
|
unresolved_fixups.push_back(i);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "Failed to find node with label: %s\n", target_name.c_str());
|
|
if (possible != string())
|
|
{
|
|
fprintf(stderr, "Possible intended match: %s\n", possible.c_str());
|
|
}
|
|
valid = 0;
|
|
return;
|
|
}
|
|
}
|
|
// 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);
|
|
}
|
|
}
|
|
p->begin()->push_to_buffer(i.get().val.byte_data);
|
|
assert(i.get().val.byte_data.size() == 4);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
device_tree::parse_file(text_input_buffer &input,
|
|
std::vector<node_ptr> &roots,
|
|
bool &read_header)
|
|
{
|
|
input.next_token();
|
|
// Read the header
|
|
if (input.consume("/dts-v1/;"))
|
|
{
|
|
read_header = true;
|
|
}
|
|
input.next_token();
|
|
if (input.consume("/plugin/;"))
|
|
{
|
|
is_plugin = true;
|
|
}
|
|
input.next_token();
|
|
if (!read_header)
|
|
{
|
|
input.parse_error("Expected /dts-v1/; version string");
|
|
}
|
|
// Read any memory reservations
|
|
while (input.consume("/memreserve/"))
|
|
{
|
|
unsigned long long start, len;
|
|
input.next_token();
|
|
// Read the start and length.
|
|
if (!(input.consume_integer_expression(start) &&
|
|
(input.next_token(),
|
|
input.consume_integer_expression(len))))
|
|
{
|
|
input.parse_error("Expected size on /memreserve/ node.");
|
|
}
|
|
input.next_token();
|
|
input.consume(';');
|
|
reservations.push_back(reservation(start, len));
|
|
input.next_token();
|
|
}
|
|
while (valid && !input.finished())
|
|
{
|
|
node_ptr n;
|
|
if (input.consume('/'))
|
|
{
|
|
input.next_token();
|
|
n = node::parse(input, string(), string_set(), string(), &defines);
|
|
}
|
|
else if (input.consume('&'))
|
|
{
|
|
input.next_token();
|
|
string name = input.parse_node_name();
|
|
input.next_token();
|
|
n = node::parse(input, std::move(name), string_set(), string(), &defines);
|
|
}
|
|
else
|
|
{
|
|
input.parse_error("Failed to find root node /.");
|
|
}
|
|
if (n)
|
|
{
|
|
roots.push_back(std::move(n));
|
|
}
|
|
else
|
|
{
|
|
valid = false;
|
|
}
|
|
input.next_token();
|
|
}
|
|
}
|
|
|
|
template<class writer> void
|
|
device_tree::write(int fd)
|
|
{
|
|
dtb::string_table st;
|
|
dtb::header head;
|
|
writer head_writer;
|
|
writer reservation_writer;
|
|
writer struct_writer;
|
|
writer strings_writer;
|
|
|
|
// Build the reservation table
|
|
reservation_writer.write_comment(string("Memory reservations"));
|
|
reservation_writer.write_label(string("dt_reserve_map"));
|
|
for (auto &i : reservations)
|
|
{
|
|
reservation_writer.write_comment(string("Reservation start"));
|
|
reservation_writer.write_data(i.first);
|
|
reservation_writer.write_comment(string("Reservation length"));
|
|
reservation_writer.write_data(i.first);
|
|
}
|
|
// Write n spare reserve map entries, plus the trailing 0.
|
|
for (uint32_t i=0 ; i<=spare_reserve_map_entries ; i++)
|
|
{
|
|
reservation_writer.write_data((uint64_t)0);
|
|
reservation_writer.write_data((uint64_t)0);
|
|
}
|
|
|
|
|
|
struct_writer.write_comment(string("Device tree"));
|
|
struct_writer.write_label(string("dt_struct_start"));
|
|
root->write(struct_writer, st);
|
|
struct_writer.write_token(dtb::FDT_END);
|
|
struct_writer.write_label(string("dt_struct_end"));
|
|
|
|
st.write(strings_writer);
|
|
// Find the strings size before we stick padding on the end.
|
|
// Note: We should possibly use a new writer for the padding.
|
|
head.size_dt_strings = strings_writer.size();
|
|
|
|
// Stick the padding in the strings writer, but after the
|
|
// marker indicating that it's the end.
|
|
// Note: We probably should add a padding call to the writer so
|
|
// that the asm back end can write padding directives instead
|
|
// of a load of 0 bytes.
|
|
for (uint32_t i=0 ; i<blob_padding ; i++)
|
|
{
|
|
strings_writer.write_data((uint8_t)0);
|
|
}
|
|
head.totalsize = sizeof(head) + strings_writer.size() +
|
|
struct_writer.size() + reservation_writer.size();
|
|
while (head.totalsize < minimum_blob_size)
|
|
{
|
|
head.totalsize++;
|
|
strings_writer.write_data((uint8_t)0);
|
|
}
|
|
head.off_dt_struct = sizeof(head) + reservation_writer.size();;
|
|
head.off_dt_strings = head.off_dt_struct + struct_writer.size();
|
|
head.off_mem_rsvmap = sizeof(head);
|
|
head.boot_cpuid_phys = boot_cpu;
|
|
head.size_dt_struct = struct_writer.size();
|
|
head.write(head_writer);
|
|
|
|
head_writer.write_to_file(fd);
|
|
reservation_writer.write_to_file(fd);
|
|
struct_writer.write_to_file(fd);
|
|
strings_writer.write_label(string("dt_blob_end"));
|
|
strings_writer.write_to_file(fd);
|
|
}
|
|
|
|
node*
|
|
device_tree::referenced_node(property_value &v)
|
|
{
|
|
if (v.is_phandle())
|
|
{
|
|
return node_names[v.string_data];
|
|
}
|
|
if (v.is_binary())
|
|
{
|
|
return used_phandles[v.get_as_uint32()];
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
device_tree::write_binary(int fd)
|
|
{
|
|
write<dtb::binary_writer>(fd);
|
|
}
|
|
|
|
void
|
|
device_tree::write_asm(int fd)
|
|
{
|
|
write<dtb::asm_writer>(fd);
|
|
}
|
|
|
|
void
|
|
device_tree::write_dts(int fd)
|
|
{
|
|
FILE *file = fdopen(fd, "w");
|
|
fputs("/dts-v1/;\n\n", file);
|
|
|
|
if (!reservations.empty())
|
|
{
|
|
const char msg[] = "/memreserve/";
|
|
fwrite(msg, sizeof(msg), 1, file);
|
|
for (auto &i : reservations)
|
|
{
|
|
fprintf(file, " %" PRIx64 " %" PRIx64, i.first, i.second);
|
|
}
|
|
fputs(";\n\n", file);
|
|
}
|
|
putc('/', file);
|
|
putc(' ', file);
|
|
root->write_dts(file, 0);
|
|
fclose(file);
|
|
}
|
|
|
|
void
|
|
device_tree::parse_dtb(const string &fn, FILE *)
|
|
{
|
|
auto in = input_buffer::buffer_for_file(fn);
|
|
if (in == 0)
|
|
{
|
|
valid = false;
|
|
return;
|
|
}
|
|
input_buffer &input = *in;
|
|
dtb::header h;
|
|
valid = h.read_dtb(input);
|
|
boot_cpu = h.boot_cpuid_phys;
|
|
if (h.last_comp_version > 17)
|
|
{
|
|
fprintf(stderr, "Don't know how to read this version of the device tree blob");
|
|
valid = false;
|
|
}
|
|
if (!valid)
|
|
{
|
|
return;
|
|
}
|
|
input_buffer reservation_map =
|
|
input.buffer_from_offset(h.off_mem_rsvmap, 0);
|
|
uint64_t start, length;
|
|
do
|
|
{
|
|
if (!(reservation_map.consume_binary(start) &&
|
|
reservation_map.consume_binary(length)))
|
|
{
|
|
fprintf(stderr, "Failed to read memory reservation table\n");
|
|
valid = false;
|
|
return;
|
|
}
|
|
} while (!((start == 0) && (length == 0)));
|
|
input_buffer struct_table =
|
|
input.buffer_from_offset(h.off_dt_struct, h.size_dt_struct);
|
|
input_buffer strings_table =
|
|
input.buffer_from_offset(h.off_dt_strings, h.size_dt_strings);
|
|
uint32_t token;
|
|
if (!(struct_table.consume_binary(token) &&
|
|
(token == dtb::FDT_BEGIN_NODE)))
|
|
{
|
|
fprintf(stderr, "Expected FDT_BEGIN_NODE token.\n");
|
|
valid = false;
|
|
return;
|
|
}
|
|
root = node::parse_dtb(struct_table, strings_table);
|
|
if (!(struct_table.consume_binary(token) && (token == dtb::FDT_END)))
|
|
{
|
|
fprintf(stderr, "Expected FDT_END token after parsing root node.\n");
|
|
valid = false;
|
|
return;
|
|
}
|
|
valid = (root != 0);
|
|
}
|
|
|
|
string
|
|
device_tree::node_path::to_string() const
|
|
{
|
|
string path;
|
|
auto p = begin();
|
|
auto pe = end();
|
|
if ((p == pe) || (p+1 == pe))
|
|
{
|
|
return string("/");
|
|
}
|
|
// Skip the first name in the path. It's always "", and implicitly /
|
|
for (++p ; p!=pe ; ++p)
|
|
{
|
|
path += '/';
|
|
path += p->first;
|
|
if (!(p->second.empty()))
|
|
{
|
|
path += '@';
|
|
path += p->second;
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
void
|
|
device_tree::parse_dts(const string &fn, FILE *depfile)
|
|
{
|
|
auto in = input_buffer::buffer_for_file(fn);
|
|
if (!in)
|
|
{
|
|
valid = false;
|
|
return;
|
|
}
|
|
std::vector<node_ptr> roots;
|
|
std::unordered_set<string> defnames;
|
|
for (auto &i : defines)
|
|
{
|
|
defnames.insert(i.first);
|
|
}
|
|
text_input_buffer input(std::move(in),
|
|
std::move(defnames),
|
|
std::vector<string>(include_paths),
|
|
dirname(fn),
|
|
depfile);
|
|
bool read_header = false;
|
|
parse_file(input, roots, read_header);
|
|
switch (roots.size())
|
|
{
|
|
case 0:
|
|
valid = false;
|
|
input.parse_error("Failed to find root node /.");
|
|
return;
|
|
case 1:
|
|
root = std::move(roots[0]);
|
|
break;
|
|
default:
|
|
{
|
|
root = std::move(roots[0]);
|
|
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));
|
|
}
|
|
else
|
|
{
|
|
auto existing = node_names.find(name);
|
|
if (existing == node_names.end())
|
|
{
|
|
collect_names();
|
|
existing = node_names.find(name);
|
|
}
|
|
if (existing == node_names.end())
|
|
{
|
|
fprintf(stderr, "Unable to merge node: %s\n", name.c_str());
|
|
}
|
|
else
|
|
{
|
|
existing->second->merge_node(std::move(node));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
collect_names();
|
|
resolve_cross_references();
|
|
if (write_symbols)
|
|
{
|
|
std::vector<property_ptr> symbols;
|
|
// Create a symbol table. Each label in this device tree may be
|
|
// 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)
|
|
{
|
|
property_value v;
|
|
v.string_data = s.second.to_string();
|
|
v.type = property_value::STRING;
|
|
string name = s.first;
|
|
auto prop = std::make_shared<property>(std::move(name));
|
|
prop->add_value(v);
|
|
symbols.push_back(prop);
|
|
}
|
|
root->add_child(node::create_special_node("__symbols__", symbols));
|
|
// If this is a plugin, then we also need to create two extra nodes.
|
|
// Internal phandles will need to be renumbered to avoid conflicts with
|
|
// already-loaded nodes and external references will need to be
|
|
// resolved.
|
|
if (is_plugin)
|
|
{
|
|
// Create the fixups entry. This is of the form:
|
|
// {target} = {path}:{property name}:{offset}
|
|
auto create_fixup_entry = [&](fixup &i, string target)
|
|
{
|
|
string value = i.path.to_string();
|
|
value += ':';
|
|
value += i.prop->get_key();
|
|
value += ':';
|
|
value += std::to_string(i.prop->offset_of_value(i.val));
|
|
property_value v;
|
|
v.string_data = value;
|
|
v.type = property_value::STRING;
|
|
auto prop = std::make_shared<property>(std::move(target));
|
|
prop->add_value(v);
|
|
return prop;
|
|
};
|
|
// If we have any unresolved phandle references in this plugin,
|
|
// then we must update them to 0xdeadbeef and leave a property in
|
|
// the /__fixups__ node whose key is the label and whose value is
|
|
// as described above.
|
|
if (!unresolved_fixups.empty())
|
|
{
|
|
symbols.clear();
|
|
for (auto &i : unresolved_fixups)
|
|
{
|
|
auto &val = i.get().val;
|
|
symbols.push_back(create_fixup_entry(i, val.string_data));
|
|
val.byte_data.push_back(0xde);
|
|
val.byte_data.push_back(0xad);
|
|
val.byte_data.push_back(0xbe);
|
|
val.byte_data.push_back(0xef);
|
|
val.type = property_value::BINARY;
|
|
}
|
|
root->add_child(node::create_special_node("__fixups__", symbols));
|
|
}
|
|
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.
|
|
for (auto &i : fixups)
|
|
{
|
|
if (!i.val.is_phandle())
|
|
{
|
|
continue;
|
|
}
|
|
symbols.push_back(create_fixup_entry(i, "fixup"));
|
|
}
|
|
// We've iterated over all fixups, but only emit the
|
|
// __local_fixups__ if we found some that were resolved internally.
|
|
if (!symbols.empty())
|
|
{
|
|
root->add_child(node::create_special_node("__local_fixups__", symbols));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool device_tree::parse_define(const char *def)
|
|
{
|
|
const char *val = strchr(def, '=');
|
|
if (!val)
|
|
{
|
|
if (strlen(def) != 0)
|
|
{
|
|
string name(def);
|
|
defines[name];
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
string name(def, val-def);
|
|
string name_copy = name;
|
|
val++;
|
|
std::unique_ptr<input_buffer> raw(new input_buffer(val, strlen(val)));
|
|
text_input_buffer in(std::move(raw),
|
|
std::unordered_set<string>(),
|
|
std::vector<string>(),
|
|
string(),
|
|
nullptr);
|
|
property_ptr p = property::parse(in, std::move(name_copy), string_set(), false);
|
|
if (p)
|
|
defines[name] = p;
|
|
return (bool)p;
|
|
}
|
|
|
|
} // namespace fdt
|
|
|
|
} // namespace dtc
|
|
|