7251c2d8bc
Specifically, parse errors within a node would lead to a segfault due to an unconditional dereference after emitting the error. Obtained from: https://github.com/davidchisnall/dtc/commit/e5ecf9319fd3f MFC after: 3 days
2204 lines
48 KiB
C++
2204 lines
48 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 <string.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;
|
|
}
|
|
}
|
|
[[fallthrough]];
|
|
default:
|
|
input.parse_error("Invalid property value.");
|
|
valid = false;
|
|
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();
|
|
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;
|
|
[[fallthrough]];
|
|
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;
|
|
}
|
|
|
|
node::visit_behavior
|
|
node::visit(std::function<visit_behavior(node&, node*)> fn, node *parent)
|
|
{
|
|
visit_behavior behavior;
|
|
behavior = fn(*this, parent);
|
|
if (behavior == VISIT_BREAK)
|
|
{
|
|
return VISIT_BREAK;
|
|
}
|
|
else if (behavior != VISIT_CONTINUE)
|
|
{
|
|
for (auto &&c : children)
|
|
{
|
|
behavior = c->visit(fn, this);
|
|
// Any status other than VISIT_RECURSE stops our execution and
|
|
// bubbles up to our caller. The caller may then either continue
|
|
// visiting nodes that are siblings to this one or completely halt
|
|
// visiting.
|
|
if (behavior != VISIT_RECURSE)
|
|
{
|
|
return behavior;
|
|
}
|
|
}
|
|
}
|
|
// Continue recursion by default
|
|
return VISIT_RECURSE;
|
|
}
|
|
|
|
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,
|
|
device_tree &tree,
|
|
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;
|
|
// 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)
|
|
{
|
|
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;
|
|
}
|
|
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(':'))
|
|
{
|
|
// 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, 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
|
|
{
|
|
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,
|
|
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),
|
|
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);
|
|
}
|
|
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
|
|
// 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(c);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
children.push_back(std::move(c));
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
ordered_node_paths.push_back({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();
|
|
ordered_node_paths.clear();
|
|
cross_references.clear();
|
|
fixups.clear();
|
|
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::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)
|
|
{
|
|
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, node *) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
// Allow recursion
|
|
return node::VISIT_RECURSE;
|
|
}, nullptr);
|
|
assert(sorted_phandles.size() == fixups.size());
|
|
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 = assign_phandle(target, phandle);
|
|
p->begin()->push_to_buffer(i.get().val.byte_data);
|
|
assert(i.get().val.byte_data.size() == 4);
|
|
}
|
|
}
|
|
|
|
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,
|
|
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, *this, string(), string_set(), string(), &defines);
|
|
}
|
|
else if (input.consume('&'))
|
|
{
|
|
input.next_token();
|
|
string name;
|
|
bool name_is_path_reference = false;
|
|
// This is to deal with names intended as path references, e.g. &{/path}.
|
|
// While it may make sense in a non-plugin context, we don't support such
|
|
// usage at this time.
|
|
if (input.consume('{') && is_plugin)
|
|
{
|
|
name = input.parse_to('}');
|
|
input.consume('}');
|
|
name_is_path_reference = true;
|
|
}
|
|
else
|
|
{
|
|
name = input.parse_node_name();
|
|
}
|
|
input.next_token();
|
|
n = node::parse(input, *this, std::move(name), string_set(), string(), &defines);
|
|
if (n)
|
|
{
|
|
n->name_is_path_reference = name_is_path_reference;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
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 $propname = <&name>
|
|
property_value v;
|
|
std::string propname;
|
|
v.string_data = node->name;
|
|
if (!node->name_is_path_reference)
|
|
{
|
|
propname = "target";
|
|
v.type = property_value::PHANDLE;
|
|
}
|
|
else
|
|
{
|
|
propname = "target-path";
|
|
v.type = property_value::STRING;
|
|
}
|
|
auto prop = std::make_shared<property>(std::string(propname));
|
|
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)
|
|
{
|
|
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;
|
|
int fragnum = 0;
|
|
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 = generate_root(roots[0], fragnum);
|
|
if (!root)
|
|
{
|
|
valid = false;
|
|
input.parse_error("Failed to find root node /.");
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
{
|
|
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())
|
|
{
|
|
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
|
|
{
|
|
auto existing = node_names.find(name);
|
|
if (existing == node_names.end())
|
|
{
|
|
collect_names();
|
|
existing = node_names.find(name);
|
|
}
|
|
if (existing == node_names.end())
|
|
{
|
|
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(node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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
|
|
// order semi-predictable and stable.
|
|
if (write_symbols)
|
|
{
|
|
assign_phandles(root, phandle);
|
|
}
|
|
resolve_cross_references(phandle);
|
|
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 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;
|
|
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)
|
|
{
|
|
std::vector<property_ptr> symbols;
|
|
// 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())
|
|
{
|
|
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 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;
|
|
}
|
|
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)
|
|
{
|
|
if (c->unit_address == p.second)
|
|
{
|
|
n = c.get();
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
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 (local_fixups->child_begin() != local_fixups->child_end())
|
|
{
|
|
root->add_child(std::move(local_fixups));
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
|