blob: 647099cb1e97be326136b5d5162f8eb7828c28eb [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 <lib/zircon-internal/align.h>
#include <zircon/assert.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <string_view>
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)); }
struct ReadBigEndianUint32Result {
uint32_t value;
ByteView tail;
};
inline ReadBigEndianUint32Result ReadBigEndianUint32(ByteView bytes) {
ZX_ASSERT(bytes.size() >= sizeof(uint32_t));
return {
(static_cast<uint32_t>(bytes[0]) << 24) | (static_cast<uint32_t>(bytes[1]) << 16) |
(static_cast<uint32_t>(bytes[2]) << 8) | static_cast<uint32_t>(bytes[3]),
bytes.substr(sizeof(uint32_t)),
};
}
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.substr(offsetof(FdtProperty, len))).value;
auto [name_offset, block_end] = ReadBigEndianUint32(bytes.substr(offsetof(FdtProperty, nameoff)));
ByteView value = block_end.substr(0, prop_size);
return {value, name_offset, block_end.substr(StructBlockAlign(prop_size))};
}
} // namespace
uint32_t PropertyValue::AsUint32() const {
ZX_ASSERT(bytes_.size() == sizeof(uint32_t));
return ReadBigEndianUint32(bytes_).value;
}
uint64_t PropertyValue::AsUint64() const {
ZX_ASSERT(bytes_.size() == sizeof(uint64_t));
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: %#x", token);
};
break;
}
return *this;
}
Devicetree::Devicetree(ByteView blob) {
ZX_ASSERT(ReadBigEndianUint32(blob).value == kMagic);
const uint32_t size =
ReadBigEndianUint32(blob.substr(offsetof(struct_fdt_header, totalsize))).value;
ZX_ASSERT(size <= blob.size());
ByteView fdt(blob.data(), size);
const uint32_t struct_block_offset =
ReadBigEndianUint32(fdt.substr(offsetof(struct_fdt_header, off_dt_struct))).value;
const uint32_t struct_block_size =
ReadBigEndianUint32(fdt.substr(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.substr(struct_block_size - sizeof(uint32_t))).value ==
FDT_END);
const uint32_t string_block_offset =
ReadBigEndianUint32(fdt.substr(offsetof(struct_fdt_header, off_dt_strings))).value;
const uint32_t string_block_size =
ReadBigEndianUint32(fdt.substr(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.substr(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) { return ReadPropertyBlock(prop).tail; }
void Devicetree::WalkTree(WalkerCallback* callback, void* arg) {
// |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, callback, arg, true);
break;
case FDT_END:
return;
default:
ZX_PANIC("unknown devicetree token: %#x\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, WalkerCallback* callback,
void* callback_arg, bool visit) {
ByteView unprocessed = subtree;
// The node name follows the begin token.
size_t name_end = unprocessed.find_first_of('\0');
ZX_ASSERT_MSG(name_end != ByteView::npos, "unterminated node name");
std::string_view name{reinterpret_cast<const char*>(unprocessed.data()), name_end};
Node node{name};
path->push_back(&node);
unprocessed.remove_prefix(StructBlockAlign(name_end + 1));
// Seek past all no-op tokens and properties.
ByteView props_block = unprocessed;
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.remove_suffix(unprocessed.size());
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;
}
Properties props{props_block, string_block_};
visit = callback(callback_arg, *path, props);
}
// 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, callback, callback_arg, visit);
continue;
case FDT_END_NODE:
break;
}
break;
}
path->pop_back();
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_.remove_prefix(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