blob: bae76b4b3f182adcbc2142289473f7dea56a735c [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
#ifndef ZIRCON_KERNEL_LIB_DEVICETREE_INCLUDE_LIB_DEVICETREE_DEVICETREE_H_
#define ZIRCON_KERNEL_LIB_DEVICETREE_INCLUDE_LIB_DEVICETREE_DEVICETREE_H_
#include <zircon/assert.h>
#include <zircon/types.h>
#include <cstdint>
#include <string_view>
#include <type_traits>
#include <fbl/intrusive_container_utils.h>
#include <fbl/intrusive_double_list.h>
// This library provides abstractions and utilities for dealing with
// 'devicetree's in their flattened, binary form (.dtb). Although not used in
// Fuchsia-compliant bootloaders (which use the ZBI protocol), dealing with
// devicetrees is necessary for our boot shims.
//
// We follow the v0.3 spec available at
// https://devicetree-specification.readthedocs.io/en/v0.3
namespace devicetree {
using ByteView = std::basic_string_view<uint8_t>;
// Represents the node name of a devicetree. This has the same API as
// std::string_view and is meant to be used the same way. It requires its own
// type solely to enable use of the fbl intrusive containers with on-stack
// elements to avoid dynamic allocation.
struct Node : public std::string_view,
fbl::DoublyLinkedListable<Node*, fbl::NodeOptions::AllowCopy> {
Node(std::string_view name) : std::string_view(name) {}
};
// See
// https://devicetree-specification.readthedocs.io/en/v0.3/devicetree-basics.html#node-name-requirements
// for specification and definition of name and unit address.
struct NodeNameTokens {
std::string_view name;
std::string_view unit_addr;
};
// Splits a node's name into the tokens of interest.
inline NodeNameTokens SplitNodeName(std::string_view node) {
size_t ind = node.find_first_of('@');
return {
(ind == std::string_view::npos) ? node : node.substr(0, ind),
(ind == std::string_view::npos || ind + 1 >= node.size()) ? std::string_view{}
: node.substr(ind + 1),
};
}
// Represents a rooted path of nodes in a devicetree.
// This can be used interchangeably with `const std::list<std::string_view>`
// to iterate over the elements in a path with implied `/` separators.
using NodePath = fbl::DoublyLinkedList<Node*>;
// Some property values encode a list of NUL-terminated strings.
// This is also useful for separating path strings at '/' characters.
template <char Separator = '\0'>
class StringList {
public:
class iterator {
public:
constexpr iterator() = default;
constexpr iterator(const iterator&) = default;
constexpr iterator& operator=(const iterator&) = default;
constexpr bool operator==(const iterator& other) const {
return len_ == other.len_ && rest_.size() == other.rest_.size();
}
constexpr bool operator!=(const iterator& other) const { return !(*this == other); }
constexpr iterator& operator++() { // prefix
if (len_ == std::string_view::npos) {
// This was the last word.
rest_ = {};
} else if (rest_.empty()) {
// This was the last word and it was empty.
len_ = std::string_view::npos;
} else {
// Move to the next word. If it's empty, record len_ = 0.
// Otherwise len_ = npos if it's the last word and not empty.
rest_ = rest_.substr(len_ + 1);
len_ = rest_.empty() ? 0 : rest_.find_first_of(Separator);
}
return *this;
}
constexpr iterator operator++(int) { // postfix
iterator old = *this;
++*this;
return old;
}
constexpr std::string_view operator*() const { return rest_.substr(0, len_); }
private:
friend StringList;
std::string_view rest_;
size_t len_ = std::string_view::npos;
};
using const_iterator = iterator;
constexpr explicit StringList(std::string_view str) : data_(str) {}
constexpr iterator begin() const {
iterator it;
it.rest_ = data_;
it.len_ = data_.find_first_of(Separator);
return it;
}
constexpr iterator end() const { return iterator{}; }
private:
std::string_view data_;
};
// See
// https://devicetree-specification.readthedocs.io/en/v0.3/devicetree-basics.html#property-values
// for the types and representations of possible property values.
class PropertyValue {
public:
PropertyValue(ByteView bytes) : bytes_(bytes) {}
ByteView AsBytes() const { return bytes_; }
// Note that the spec requires this to be a NUL-terminated string.
std::string_view AsString() const {
ZX_ASSERT(!bytes_.empty());
ZX_ASSERT(bytes_.back() == '\0');
// Exclude NUL terminator from factoring into the string_view's size.
return {reinterpret_cast<const char*>(bytes_.data()), bytes_.size() - 1};
}
StringList<> AsStringList() const { return StringList<>{AsString()}; }
uint32_t AsUint32() const;
uint64_t AsUint64() const;
// A value without size represents a Boolean property whose truthiness is a
// function of the nature of the property's name and its presence in the tree.
bool AsBool() const {
ZX_ASSERT(bytes_.empty());
return true;
}
private:
ByteView bytes_;
};
struct Property {
std::string_view name;
PropertyValue value;
};
// A view-like object representing a set of properties given by a range within the
// structure block of a flattened devicetree.
// https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html#structure-block
class Properties {
public:
// Constructed from a byte span beginning just after the first internal::FDT_PROP token
// in a flattened block of properties (or an otherwise empty one), along with
// a string block for property name look-up.
Properties(ByteView property_block, std::string_view string_block)
: property_block_(property_block), string_block_(string_block) {}
// A property iterator is identified with the position in a block of
// properties at which a property is encoded. Incrementing the iterator
// amounts to seeking in the range for the offset of the next property.
//
// It implements std's LegacyInputIterator.
// https://en.cppreference.com/w/cpp/named_req/InputIterator
class iterator {
public:
using value_type = Property;
using difference_type = ptrdiff_t; // std::next complains without it.
using pointer = Property*;
using reference = Property&;
using iterator_category = std::input_iterator_tag;
iterator() = default;
iterator(const iterator&) = default;
iterator& operator=(const iterator&) = default;
bool operator==(const iterator& it) const { return position_ == it.position_; }
bool operator!=(const iterator& it) const { return !(*this == it); }
Property operator*() const;
iterator& operator++(); // prefix incrementing.
iterator operator++(int) { // postfix incrementing.
auto prev = *this;
++(*this);
return prev;
}
private:
// Only to be called by Properties::begin and Properties::end().
iterator(ByteView position, std::string_view string_block)
: position_(position), string_block_(string_block) {}
friend class Properties;
// Pointer into the block of properties along with remaining size from that
// point onward.
ByteView position_;
std::string_view string_block_;
};
iterator begin() const { return iterator{property_block_, string_block_}; }
iterator end() const { return iterator{{property_block_.end(), 0}, string_block_}; }
private:
const ByteView property_block_;
const std::string_view string_block_;
};
class MemoryReservations {
public:
struct value_type {
uint64_t start, size;
};
class iterator {
public:
iterator() = default;
iterator(const iterator&) = default;
iterator& operator=(const iterator&) = default;
bool operator==(const iterator& other) const {
return mem_rsvmap_.size() == other.mem_rsvmap_.size();
}
bool operator!=(const iterator& other) const { return !(*this == other); }
iterator& operator++(); // prefix
iterator operator++(int) { // postfix
iterator old = *this;
++*this;
return old;
}
value_type operator*() const;
private:
friend MemoryReservations;
void Normalize();
ByteView mem_rsvmap_;
};
using const_iterator = iterator;
iterator begin() const;
iterator end() const { return iterator{}; }
private:
friend class Devicetree;
ByteView mem_rsvmap_;
};
// Represents a devicetree. This class does not dynamically allocate
// memory and is appropriate for use in all low-level environments.
class Devicetree {
public:
// Consumes a view representing the range of memory the flattened devicetree
// is expected to take up, its beginning pointing to that of the binary data
// and its size giving an upper bound on the size that the data is permitted
// to occupy.
//
// It is okay to pass a view size of SIZE_MAX if an upper bound on the size
// is not known; only up to the size encoded in the devicetree header will
// be dereferenced.
explicit Devicetree(ByteView fdt);
// The size in bytes of the flattened devicetree blob.
size_t size_bytes() const { return fdt_.size(); }
// Walk provides a means of walking a devicetree. It purposefully avoids
// reliance on specifying a 'walker' by means of inheritance so as to avoid
// vtables, which are not permitted in the phys environment. A WalkCallback
// here is an object callable with the signature of
// `(const NodePath&, Properties) -> bool`
// and is called depth-first at every node for which no ancestor node
// returned false. That is, if a walker returns false at a given node, then
// the subtree rooted there and its member nodes are said to be "pruned" and
// the walker will not be called on them.
//
// This method will only have one instantiation in practice (in any
// conceivable context), so the templating should not result in undue bloat.
//
// TODO: If boot shims start using multiple walks, refactor this to move the
// logic into a non-template function and use a function pointer callback,
// with this templated wrapper calling that with a captureless lambda to call
// the templated walker.
template <typename F>
void Walk(F&& walker) {
static_assert(std::is_invocable_r_v<bool, F, const NodePath&, Properties>,
"wrong callback signature");
if constexpr (std::is_rvalue_reference_v<F>) {
// An rvalue reference argument has to be moved into a local copy to be
// passed by reference.
F moved_walker = std::move(walker);
WalkTree(moved_walker);
} else {
// An lvalue reference or an argument passed by value is just forwarded.
WalkTree(walker);
}
}
MemoryReservations memory_reservations() const {
MemoryReservations result;
result.mem_rsvmap_ = mem_rsvmap_;
return result;
}
private:
using WalkerCallback = bool(void*, const NodePath&, Properties);
Devicetree(ByteView fdt, ByteView struct_block, std::string_view string_block)
: fdt_(fdt), struct_block_(struct_block), string_block_(string_block) {}
// Given a byte span that starts at a flattened property block, returns the
// iterator in that span pointing to the 4-byte aligned end of that block.
ByteView EndOfPropertyBlock(ByteView bytes);
template <typename Walker>
void WalkTree(Walker& walker) {
WalkerCallback* callback = [](void* callback_arg, const NodePath& path,
Properties props) -> bool {
return (*static_cast<Walker*>(callback_arg))(path, props);
};
WalkTree(callback, &walker);
}
void WalkTree(WalkerCallback* callback, void* callback_arg);
ByteView WalkSubtree(ByteView subtree, NodePath* path, WalkerCallback* callback,
void* callback_arg, bool visit);
ByteView fdt_;
// https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html#structure-block
ByteView struct_block_;
// https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html#strings-block
std::string_view string_block_;
// https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html#memory-reservation-block
ByteView mem_rsvmap_;
};
} // namespace devicetree
#endif // ZIRCON_KERNEL_LIB_DEVICETREE_INCLUDE_LIB_DEVICETREE_DEVICETREE_H_