| // 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> |
| |
| 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 |