blob: 43897513016f744c47423693f840bbaf813bb58b [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "lib/devicetree/devicetree.h"
#include <inttypes.h>
#include <lib/devicetree/internal/devicetree.h>
#include <lib/fit/defer.h>
#include <lib/fit/result.h>
#include <lib/zircon-internal/align.h>
#include <zircon/assert.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string_view>
#include <type_traits>
namespace devicetree {
namespace {
constexpr uint32_t kMagic = 0xd00dfeed;
// The structure block tokens, named as in the spec for clarity.
constexpr uint32_t FDT_BEGIN_NODE = 0x00000001;
constexpr uint32_t FDT_END_NODE = 0x00000002;
constexpr uint32_t FDT_PROP = 0x00000003;
constexpr uint32_t FDT_NOP = 0x00000004;
constexpr uint32_t FDT_END = 0x00000009;
// https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html#header
struct struct_fdt_header {
uint32_t magic;
uint32_t totalsize;
uint32_t off_dt_struct;
uint32_t off_dt_strings;
uint32_t off_mem_rsvmap;
uint32_t version;
uint32_t last_comp_version;
uint32_t boot_cpuid_phys;
uint32_t size_dt_strings;
uint32_t size_dt_struct;
};
// https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html#lexical-structure
struct FdtProperty {
uint32_t len;
uint32_t nameoff;
};
// Structure block tokens are 4-byte aligned.
inline size_t StructBlockAlign(size_t x) { return ZX_ALIGN(x, sizeof(uint32_t)); }
using internal::ReadBigEndianUint32;
using internal::ReadBigEndianUint32Result;
struct ReadBigEndianUint64Result {
uint64_t value;
ByteView tail;
};
ReadBigEndianUint64Result ReadBigEndianUint64(ByteView bytes) {
auto [high, tail] = ReadBigEndianUint32(bytes);
auto [low, rest] = ReadBigEndianUint32(tail);
return {
(static_cast<uint64_t>(high) << 32) | static_cast<uint64_t>(low),
rest,
};
}
struct PropertyBlockContents {
// The underlying property value.
ByteView value;
uint32_t name_offset;
// The 4-byte aligned tail of the property block view after the value.
ByteView tail;
};
PropertyBlockContents ReadPropertyBlock(ByteView bytes) {
ZX_ASSERT(bytes.size() >= sizeof(FdtProperty));
uint32_t prop_size = ReadBigEndianUint32(bytes.subspan(offsetof(FdtProperty, len))).value;
auto [name_offset, block_end] =
ReadBigEndianUint32(bytes.subspan(offsetof(FdtProperty, nameoff)));
ByteView value = block_end.subspan(0, prop_size);
return {value, name_offset, block_end.subspan(StructBlockAlign(prop_size))};
}
} // namespace
std::optional<uint32_t> PropertyValue::AsUint32() const {
if (bytes_.size() != sizeof(uint32_t)) {
return std::nullopt;
}
return ReadBigEndianUint32(bytes_).value;
}
std::optional<uint64_t> PropertyValue::AsUint64() const {
if (bytes_.size() != sizeof(uint64_t)) {
return std::nullopt;
}
return ReadBigEndianUint64(bytes_).value;
}
Property Properties::iterator::operator*() const {
auto [value, name_offset, tail] = ReadPropertyBlock(position_);
ZX_ASSERT_MSG(name_offset < string_block_.size(),
"property name does not live in the string block");
std::string_view name_and_tail = string_block_.substr(name_offset);
size_t name_end = name_and_tail.find_first_of('\0');
ZX_ASSERT_MSG(name_end != std::string_view::npos, "property name was not null-terminated");
return {name_and_tail.substr(0, name_end), value};
}
Properties::iterator& Properties::iterator::operator++() {
position_ = ReadPropertyBlock(position_).tail;
// A property block might be followed by no-op tokens; seek past them if so
// and, space provided, stop just after the next property token.
while (!position_.empty()) {
auto [token, tail] = ReadBigEndianUint32(position_);
position_ = tail;
switch (token) {
case FDT_NOP:
continue;
case FDT_PROP:
break;
default:
ZX_PANIC("unexpected token in property block: %#" PRIx32, token);
};
break;
}
return *this;
}
fit::result<PropertyDecoder::PathResolveError, ResolvedPath> PropertyDecoder::ResolvePath(
std::string_view maybe_aliased_path) const {
if (maybe_aliased_path.empty()) {
return fit::ok(ResolvedPath{});
}
if (maybe_aliased_path[0] != '/') {
if (!aliases_) {
return fit::error(PathResolveError::kNoAliases);
}
auto& aliases = *aliases_;
std::string_view alias = maybe_aliased_path.substr(0, maybe_aliased_path.find('/'));
std::string_view suffix = maybe_aliased_path.substr(alias.size());
if (!suffix.empty()) {
suffix.remove_prefix(1);
}
for (auto [name, value] : aliases) {
if (name != alias) {
continue;
}
auto maybe_abs_path = value.AsString();
if (!maybe_abs_path.has_value() || maybe_abs_path->empty()) {
return fit::error(PathResolveError::kBadAlias);
}
return fit::ok(ResolvedPath{.prefix = *maybe_abs_path, .suffix = suffix});
}
// We did not find a matching alias.
return fit::error(PathResolveError::kBadAlias);
}
return fit::ok(ResolvedPath{.prefix = maybe_aliased_path});
}
std::optional<uint64_t> PropertyDecoder::TranslateAddress(uint64_t address) const {
// Root, we are done.
if (!parent()) {
return address;
}
auto ranges_prop = parent()->FindProperty("ranges");
// No more translations are possible.
if (!ranges_prop) {
return address;
}
auto ranges = ranges_prop->AsRanges(*parent());
// We can't translate if we fail to parse.
if (!ranges) {
return std::nullopt;
}
if (auto parent_address = ranges->TranslateChildAddress(address)) {
return parent()->TranslateAddress(*parent_address);
}
return std::nullopt;
}
std::optional<RegProperty> RegProperty::Create(uint32_t num_address_cells, uint32_t num_size_cells,
ByteView bytes) {
if (num_address_cells > 2 || num_size_cells > 2) {
return std::nullopt;
}
if (bytes.size() % (num_address_cells + num_size_cells)) {
return std::nullopt;
}
return RegProperty(bytes, num_address_cells, num_size_cells);
}
std::optional<RegProperty> RegProperty::Create(const PropertyDecoder& decoder, ByteView bytes) {
if (!decoder.parent()) {
return std::nullopt;
}
const auto& parent = *decoder.parent();
return RegProperty::Create(parent.num_address_cells().value_or(kRegDefaultAddressCells),
parent.num_size_cells().value_or(kRegDefaultSizeCells), bytes);
}
std::optional<RangesProperty> RangesProperty::Create(uint32_t num_address_cells,
uint32_t num_size_cells,
uint32_t num_parent_address_cells,
ByteView bytes) {
if (num_address_cells > 2 || num_size_cells > 2) {
return std::nullopt;
}
if (bytes.size() %
(sizeof(uint32_t) * (num_address_cells + num_size_cells + num_parent_address_cells))) {
return std::nullopt;
}
return RangesProperty(bytes, num_address_cells, num_size_cells, num_parent_address_cells);
}
std::optional<RangesProperty> RangesProperty::Create(const PropertyDecoder& decoder,
ByteView bytes) {
if (!decoder.parent()) {
return std::nullopt;
}
return RangesProperty::Create(
decoder.num_address_cells().value_or(kRegDefaultAddressCells),
decoder.num_size_cells().value_or(kRegDefaultSizeCells),
decoder.parent()->num_address_cells().value_or(kRegDefaultAddressCells), bytes);
}
Devicetree::Devicetree(ByteView blob) {
ZX_ASSERT(ReadBigEndianUint32(blob).value == kMagic);
const uint32_t size =
ReadBigEndianUint32(blob.subspan(offsetof(struct_fdt_header, totalsize))).value;
ZX_ASSERT(size <= blob.size());
ByteView fdt(blob.data(), size);
const uint32_t struct_block_offset =
ReadBigEndianUint32(fdt.subspan(offsetof(struct_fdt_header, off_dt_struct))).value;
const uint32_t struct_block_size =
ReadBigEndianUint32(fdt.subspan(offsetof(struct_fdt_header, size_dt_struct))).value;
ZX_ASSERT(struct_block_offset < fdt.size());
ZX_ASSERT(fdt.size() - struct_block_offset >= struct_block_size);
const uint8_t* struct_block_base = fdt.data() + struct_block_offset;
ByteView struct_block(struct_block_base, struct_block_size);
ZX_ASSERT(struct_block_size > 0);
ZX_ASSERT(ReadBigEndianUint32(struct_block.subspan(struct_block_size - sizeof(uint32_t))).value ==
FDT_END);
const uint32_t string_block_offset =
ReadBigEndianUint32(fdt.subspan(offsetof(struct_fdt_header, off_dt_strings))).value;
const uint32_t string_block_size =
ReadBigEndianUint32(fdt.subspan(offsetof(struct_fdt_header, size_dt_strings))).value;
ZX_ASSERT(string_block_offset <= fdt.size());
ZX_ASSERT(fdt.size() - struct_block_offset >= struct_block_size);
const uint8_t* string_block_base = fdt.data() + string_block_offset;
std::string_view string_block(reinterpret_cast<const char*>(string_block_base),
string_block_size);
const uint32_t mem_rsvmap_offset =
ReadBigEndianUint32(fdt.subspan(offsetof(struct_fdt_header, off_mem_rsvmap))).value;
ZX_ASSERT(mem_rsvmap_offset <= fdt.size());
const uint8_t* mem_rsvmap_base = fdt.data() + mem_rsvmap_offset;
ByteView mem_rsvmap{mem_rsvmap_base, fdt.size() - mem_rsvmap_offset};
fdt_ = fdt;
struct_block_ = struct_block;
string_block_ = string_block;
mem_rsvmap_ = mem_rsvmap;
}
ByteView Devicetree::EndOfPropertyBlock(ByteView prop) const {
return ReadPropertyBlock(prop).tail;
}
void Devicetree::WalkTree(PreOrderNodeVisitor pre_order_visitor,
PostOrderNodeVisitor post_order_visitor) const {
// |path| will point to stack-owned Nodes as we recurse.
NodePath path;
ByteView unprocessed = struct_block_;
while (!unprocessed.empty()) {
auto [token, tail] = ReadBigEndianUint32(unprocessed);
unprocessed = tail;
switch (token) {
case FDT_NOP:
break;
case FDT_BEGIN_NODE:
unprocessed =
WalkSubtree(unprocessed, &path, nullptr, pre_order_visitor, post_order_visitor, true);
break;
case FDT_END:
return;
default:
ZX_PANIC("unknown devicetree token: %#" PRIx32 "\n", token);
}
}
}
// Recursively walks a subtree and returns its unprocessed tail.
// It should be invariant that the subtree begins just after the
// FDT_BEGIN_NODE token.
ByteView Devicetree::WalkSubtree(ByteView subtree, NodePath* path, PropertyDecoder* parent,
PreOrderNodeVisitor& pre_order_visitor,
PostOrderNodeVisitor& post_order_visitor, bool visit) const {
ByteView unprocessed = subtree;
// The node name follows the begin token.
auto name_end = std::find(unprocessed.begin(), unprocessed.end(), 0);
ZX_ASSERT_MSG(name_end != unprocessed.end(), "unterminated node name");
size_t name_len = std::distance(unprocessed.begin(), name_end);
std::string_view name{reinterpret_cast<const char*>(unprocessed.data()), name_len};
// GCC detects this as a dangling pointer that "escapes", but we know it's
// safe because the `path->pop_back();` always runs before `node` dies.
#pragma GCC diagnostic push
#if __GNUC__ >= 13
#pragma GCC diagnostic ignored "-Wdangling-pointer"
#endif
Node node{name};
path->push_back(&node);
auto unwind_path = fit::defer([path]() { path->pop_back(); });
#pragma GCC diagnostic pop
unprocessed = unprocessed.subspan(StructBlockAlign(name_len + 1));
// Seek past all no-op tokens and properties.
ByteView props_block = unprocessed;
PropertyDecoder decoder;
while (!unprocessed.empty()) {
auto [token, tail] = ReadBigEndianUint32(unprocessed);
switch (token) {
case FDT_NOP:
unprocessed = tail;
continue;
case FDT_PROP:
unprocessed = EndOfPropertyBlock(tail);
continue;
case FDT_END_NODE:
break;
}
break;
}
// Recall that it is a simplifying assumption of Properties that it must
// be instantiated with a block that is either empty or that which begins
// just after a property token.
props_block = props_block.subspan(0, props_block.size() - unprocessed.size());
auto call = [&](auto&& walker) { return walker(*path, decoder); };
bool post_visit = visit;
if (visit) {
while (!props_block.empty()) {
auto [token, tail] = ReadBigEndianUint32(props_block);
props_block = tail;
switch (token) {
case FDT_PROP:
break;
case FDT_END_NODE:
default:
continue;
}
// Reached only at the end of the property block.
break;
}
// Re-initialize the property decoder with this node's properties.
new (&decoder) PropertyDecoder(parent, Properties(props_block, string_block_), aliases_);
constexpr std::string_view kAliasNodePath = "/aliases";
if (!aliases_ && *path == kAliasNodePath) {
aliases_.emplace(decoder.properties());
}
visit = call(pre_order_visitor);
}
// Walk all subtrees of |node|.
while (!unprocessed.empty()) {
auto [token, tail] = ReadBigEndianUint32(unprocessed);
unprocessed = tail;
switch (token) {
case FDT_NOP:
continue;
case FDT_BEGIN_NODE:
unprocessed =
WalkSubtree(unprocessed, path, &decoder, pre_order_visitor, post_order_visitor, visit);
continue;
case FDT_END_NODE:
break;
}
break;
}
if (post_visit) {
call(post_order_visitor);
}
return unprocessed;
}
MemoryReservations::iterator MemoryReservations::begin() const {
iterator it;
it.mem_rsvmap_ = mem_rsvmap_;
it.Normalize();
return it;
}
using RawRsvMapEntry = std::array<uint64_t, 2>;
void MemoryReservations::iterator::Normalize() {
constexpr RawRsvMapEntry kEnd{};
if (mem_rsvmap_.size() < sizeof(RawRsvMapEntry) ||
*reinterpret_cast<const RawRsvMapEntry*>(mem_rsvmap_.data()) == kEnd) {
*this = {};
}
}
MemoryReservations::iterator& MemoryReservations::iterator::operator++() {
mem_rsvmap_ = mem_rsvmap_.subspan(sizeof(RawRsvMapEntry));
Normalize();
return *this;
}
MemoryReservations::value_type MemoryReservations::iterator::operator*() const {
auto [start, tail] = ReadBigEndianUint64(mem_rsvmap_);
auto [size, rest] = ReadBigEndianUint64(tail);
return {start, size};
}
} // namespace devicetree