LibDeviceTree: Add functions for getting and translating addresses

I created this test file by running the following command:

    dtc -o address-translation.dtb <<EOF
    /dts-v1/;

    / {
        compatible = "serenity,address-translation-test";
        #address-cells = <2>;
        #size-cells = <1>;

        soc {
            compatible = "simple-bus";
            #address-cells = <1>;
            #size-cells = <1>;
            ranges = <0xa0000000 0xfe 0xd0000000 0x40000000>;

            usb@a0010000 {
                reg = <0xa0010000 0x100000>;
            };

            some-bus@b0000000 {
                compatible = "simple-bus";
                #address-cells = <2>;
                #size-cells = <2>;
                ranges = <0x02 0x00 0xb0000000 0x00 0x200000>;

                leds@200100000 {
                    reg = <0x02 0x100000 0x00 0x1000>;
                };
            };
        };
    };
    EOF
This commit is contained in:
Sönke Holz 2024-11-26 21:33:52 +01:00
parent 7d41d5191b
commit ddd9ab21e6
8 changed files with 407 additions and 11 deletions

View file

@ -82,6 +82,10 @@
# cmakedefine01 CSS_TRANSITIONS_DEBUG
#endif
#ifndef DEVICETREE_DEBUG
# cmakedefine01 DEVICETREE_DEBUG
#endif
#ifndef DDS_DEBUG
# cmakedefine01 DDS_DEBUG
#endif

View file

@ -28,6 +28,7 @@ set(CSS_PARSER_DEBUG ON)
set(CSS_TOKENIZER_DEBUG ON)
set(CSS_TRANSITIONS_DEBUG ON)
set(DDS_DEBUG ON)
set(DEVICETREE_DEBUG ON)
set(DHCPV4CLIENT_DEBUG ON)
set(DHCPV4_DEBUG ON)
set(DIFF_DEBUG ON)

View file

@ -250,6 +250,7 @@ write_cmake_config("ak_debug_gen") {
"CSS_TOKENIZER_DEBUG=",
"CSS_TRANSITIONS_DEBUG=",
"DDS_DEBUG=",
"DEVICETREE_DEBUG=",
"DHCPV4CLIENT_DEBUG=",
"DHCPV4_DEBUG=",
"DIFF_DEBUG=",

View file

@ -1,5 +1,6 @@
set(TEST_SOURCES
TestLookup.cpp
TestAddressTranslation.cpp
)
foreach(source IN LISTS TEST_SOURCES)
@ -7,3 +8,4 @@ foreach(source IN LISTS TEST_SOURCES)
endforeach()
install(FILES dtb.dtb DESTINATION usr/Tests/LibDeviceTree)
install(FILES address-translation.dtb DESTINATION usr/Tests/LibDeviceTree)

View file

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024, Sönke Holz <sholz8530@gmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibTest/TestCase.h>
#include <LibCore/File.h>
#include <LibCore/System.h>
#include <LibDeviceTree/DeviceTree.h>
#include <LibDeviceTree/FlattenedDeviceTree.h>
TEST_CASE(address_translation)
{
auto fdt_file = TRY_OR_FAIL(Core::File::open("/usr/Tests/LibDeviceTree/address-translation.dtb"sv, Core::File::OpenMode::Read));
auto fdt = TRY_OR_FAIL(fdt_file->read_until_eof());
auto device_tree = TRY_OR_FAIL(DeviceTree::DeviceTree::parse(fdt));
{
auto const* usb = device_tree->resolve_node("/soc/usb@a0010000"sv);
EXPECT(usb != nullptr);
auto usb_reg_entry_0 = TRY_OR_FAIL(TRY_OR_FAIL(usb->reg()).entry(0));
EXPECT_EQ(TRY_OR_FAIL(usb_reg_entry_0.bus_address().as_flatptr()), 0xa001'0000uz);
EXPECT_EQ(TRY_OR_FAIL(usb_reg_entry_0.length().as_size_t()), 0x10'0000uz);
EXPECT_EQ(TRY_OR_FAIL(TRY_OR_FAIL(usb_reg_entry_0.resolve_root_address()).as_flatptr()), 0xfe'd001'0000uz);
}
{
auto const* leds = device_tree->resolve_node("/soc/some-bus@b0000000/leds@200100000"sv);
EXPECT(leds != nullptr);
auto const* leds_parent = leds->parent();
EXPECT(leds_parent != nullptr);
auto leds_parent_ranges = TRY_OR_FAIL(leds_parent->ranges());
auto leds_reg_entry_0 = TRY_OR_FAIL(TRY_OR_FAIL(leds->reg()).entry(0));
EXPECT_EQ(TRY_OR_FAIL(leds_reg_entry_0.bus_address().as_flatptr()), 0x2'0010'0000uz);
EXPECT_EQ(TRY_OR_FAIL(leds_reg_entry_0.length().as_size_t()), 0x1000uz);
EXPECT_EQ(TRY_OR_FAIL(TRY_OR_FAIL(leds_parent_ranges.translate_child_bus_address_to_parent_bus_address(leds_reg_entry_0.bus_address())).as_flatptr()), 0xb010'0000uz);
EXPECT_EQ(TRY_OR_FAIL(TRY_OR_FAIL(leds_reg_entry_0.resolve_root_address()).as_flatptr()), 0xfe'e010'0000uz);
}
}

Binary file not shown.

View file

@ -6,6 +6,7 @@
#include "DeviceTree.h"
#include "FlattenedDeviceTree.h"
#include <AK/Debug.h>
#include <AK/NonnullOwnPtr.h>
#include <AK/Types.h>
@ -93,4 +94,173 @@ bool Node::is_compatible_with(StringView wanted_compatible_string) const
return is_compatible;
}
ErrorOr<Reg> Node::reg() const
{
if (parent() == nullptr)
return Error::from_errno(EINVAL);
auto reg_prop = get_property("reg"sv);
if (!reg_prop.has_value())
return Error::from_errno(ENOENT);
return Reg { reg_prop->raw_data, *this };
}
ErrorOr<Ranges> Node::ranges() const
{
if (parent() == nullptr)
return Error::from_errno(EINVAL);
auto ranges_prop = get_property("ranges"sv);
if (!ranges_prop.has_value())
return Error::from_errno(ENOENT);
return Ranges { ranges_prop->raw_data, *this };
}
ErrorOr<Address> RegEntry::resolve_root_address() const
{
VERIFY(m_node.parent() != nullptr);
return m_node.parent()->translate_child_bus_address_to_root_address(bus_address());
}
ErrorOr<RegEntry> Reg::entry(size_t index) const
{
if (index >= entry_count())
return Error::from_errno(EINVAL);
VERIFY(m_node.parent() != nullptr);
auto parent_address_cells = m_node.parent()->address_cells();
auto parent_size_cells = m_node.parent()->size_cells();
size_t const start_index = index * (parent_address_cells + parent_size_cells) * sizeof(u32);
auto address = Address { m_raw.slice(start_index, parent_address_cells * sizeof(u32)) };
auto length = Size { m_raw.slice(start_index + (parent_address_cells * sizeof(u32)), parent_size_cells * sizeof(u32)) };
return RegEntry {
move(address),
move(length),
m_node,
};
}
size_t Reg::entry_count() const
{
VERIFY(m_node.parent() != nullptr);
auto parent_address_cells = m_node.parent()->address_cells();
auto parent_size_cells = m_node.parent()->size_cells();
// #address-cells should never be 0, but still avoid dividing by 0.
if (parent_address_cells + parent_size_cells == 0)
return 0;
return m_raw.size() / ((parent_address_cells + parent_size_cells) * sizeof(u32));
}
ErrorOr<Address> RangesEntry::translate_child_bus_address_to_parent_bus_address(Address const& address) const
{
auto maybe_device_type = m_node.get_property("device_type"sv);
if (maybe_device_type.has_value() && maybe_device_type->as_string() == "pci"sv) {
// TODO
return Error::from_errno(ENOTSUP);
}
auto address_as_flatptr = TRY(address.as_flatptr());
auto child_bus_address_as_flatptr = TRY(m_child_bus_address.as_flatptr());
auto parent_bus_address_as_flatptr = TRY(m_parent_bus_address.as_flatptr());
auto length_as_size_t = TRY(m_length.as_size_t());
if (address_as_flatptr >= child_bus_address_as_flatptr && address_as_flatptr < (child_bus_address_as_flatptr + length_as_size_t))
return Address::from_flatptr(address_as_flatptr - child_bus_address_as_flatptr + parent_bus_address_as_flatptr);
return Error::from_errno(EFAULT);
}
ErrorOr<RangesEntry> Ranges::entry(size_t index) const
{
if (index >= entry_count())
return Error::from_errno(EINVAL);
VERIFY(m_node.parent() != nullptr);
auto address_cells = m_node.address_cells();
auto parent_address_cells = m_node.parent()->address_cells();
auto size_cells = m_node.size_cells();
size_t const start_index = index * (address_cells + parent_address_cells + size_cells) * sizeof(u32);
auto child_bus_address = Address { m_raw.slice(start_index, address_cells * sizeof(u32)) };
auto parent_bus_addres = Address { m_raw.slice(start_index + (address_cells * sizeof(u32)), parent_address_cells * sizeof(u32)) };
auto size = Size { m_raw.slice(start_index + ((address_cells + parent_address_cells) * sizeof(u32)), size_cells * sizeof(u32)) };
return RangesEntry {
move(child_bus_address),
move(parent_bus_addres),
move(size),
m_node,
};
}
size_t Ranges::entry_count() const
{
VERIFY(m_node.parent() != nullptr);
auto address_cells = m_node.address_cells();
auto parent_address_cells = m_node.parent()->address_cells();
auto size_cells = m_node.size_cells();
// #address-cells should never be 0, but still avoid dividing by 0.
if (address_cells + parent_address_cells + size_cells == 0)
return 0;
return m_raw.size() / ((address_cells + parent_address_cells + size_cells) * sizeof(u32));
}
ErrorOr<Address> Ranges::translate_child_bus_address_to_parent_bus_address(Address const& addr) const
{
// 2.3.8 ranges
// If the property is defined with an <empty> value, it specifies that the parent and child address space is identical,
// and no address translation is required.
if (entry_count() == 0)
return addr;
for (size_t i = 0; i < entry_count(); i++) {
if (auto translation_or_error = MUST(entry(i)).translate_child_bus_address_to_parent_bus_address(addr); !translation_or_error.is_error())
return translation_or_error.release_value();
}
return Error::from_errno(EFAULT);
}
ErrorOr<Address> Node::translate_child_bus_address_to_root_address(Address const& addr) const
{
dbgln_if(DEVICETREE_DEBUG, "DeviceTree: Translating bus address {:hex-dump}", addr.raw());
auto const* current_node = this;
auto current_address = addr;
while (!current_node->is_root()) {
// 2.3.8 ranges
// If the property is not present in a bus node, it is assumed that no mapping exists between children of the node
// and the parent address space.
auto ranges_or_error = current_node->ranges();
if (ranges_or_error.is_error()) {
VERIFY(ranges_or_error.release_error().code() == ENOENT);
return Error::from_errno(EFAULT);
}
current_address = TRY(ranges_or_error.release_value().translate_child_bus_address_to_parent_bus_address(current_address));
current_node = current_node->parent();
dbgln_if(DEVICETREE_DEBUG, "DeviceTree: -> {} address: {:hex-dump}", current_node->is_root() ? "root" : "parent bus", current_address.raw());
}
return current_address;
}
}

View file

@ -8,6 +8,7 @@
#include <AK/Concepts.h>
#include <AK/Endian.h>
#include <AK/FixedArray.h>
#include <AK/Function.h>
#include <AK/HashMap.h>
#include <AK/IterationDecision.h>
@ -18,6 +19,67 @@
namespace DeviceTree {
// Devicetree Specification 0.4 (DTSpec): https://github.com/devicetree-org/devicetree-specification/releases/download/v0.4/devicetree-specification-v0.4.pdf
class DeviceTree;
class Node;
class Address {
public:
Address() = default;
Address(ReadonlyBytes data)
: m_raw(static_cast<decltype(m_raw)>(data))
{
}
ReadonlyBytes raw() const { return m_raw; }
static Address from_flatptr(FlatPtr flatptr)
{
BigEndian<FlatPtr> big_endian_flatptr { flatptr };
Address address;
address.m_raw.resize(sizeof(FlatPtr));
__builtin_memcpy(address.m_raw.data(), &big_endian_flatptr, sizeof(big_endian_flatptr));
return address;
}
ErrorOr<FlatPtr> as_flatptr() const
{
if (m_raw.size() == sizeof(u32))
return *reinterpret_cast<BigEndian<u32> const*>(m_raw.data());
if (m_raw.size() == 2 * sizeof(u32))
return *reinterpret_cast<BigEndian<u64> const*>(m_raw.data());
return ERANGE;
}
private:
Vector<u8, 4 * sizeof(u32)> m_raw;
};
class Size {
public:
Size() = default;
Size(ReadonlyBytes data)
: m_raw(static_cast<decltype(m_raw)>(data))
{
}
ReadonlyBytes raw() const { return m_raw; }
ErrorOr<size_t> as_size_t() const
{
if (m_raw.size() == sizeof(u32))
return *reinterpret_cast<BigEndian<u32> const*>(m_raw.data());
if (m_raw.size() == 2 * sizeof(u32))
return *reinterpret_cast<BigEndian<u64> const*>(m_raw.data());
return ERANGE;
}
private:
Vector<u8, 2 * sizeof(u32)> m_raw;
};
struct Property {
class ValueStream : public FixedMemoryStream {
public:
@ -95,6 +157,100 @@ struct Property {
ValueStream as_stream() const { return ValueStream { raw_data }; }
};
// 2.3.6 reg
class RegEntry {
public:
RegEntry(Address const& address, Size const& size, Node const& node)
: m_address(address)
, m_length(size)
, m_node(node)
{
}
RegEntry(Address&& address, Size&& size, Node const& node)
: m_address(move(address))
, m_length(move(size))
, m_node(node)
{
}
Address bus_address() const { return m_address; }
Size length() const { return m_length; }
ErrorOr<Address> resolve_root_address() const;
private:
Address m_address;
Size m_length;
Node const& m_node;
};
class Reg {
public:
Reg(ReadonlyBytes data, Node const& node)
: m_raw(data)
, m_node(node)
{
}
ErrorOr<RegEntry> entry(size_t index) const;
size_t entry_count() const;
private:
ReadonlyBytes m_raw;
Node const& m_node;
};
// 2.3.8 ranges
class RangesEntry {
public:
RangesEntry(Address const& child_bus_address, Address const& parent_bus_address, Size const& length, Node const& node)
: m_child_bus_address(child_bus_address)
, m_parent_bus_address(parent_bus_address)
, m_length(length)
, m_node(node)
{
}
RangesEntry(Address&& child_bus_address, Address&& parent_bus_address, Size&& length, Node const& node)
: m_child_bus_address(move(child_bus_address))
, m_parent_bus_address(move(parent_bus_address))
, m_length(move(length))
, m_node(node)
{
}
Address child_bus_address() const { return m_child_bus_address; }
Address parent_bus_address() const { return m_parent_bus_address; }
Size length() const { return m_length; }
ErrorOr<Address> translate_child_bus_address_to_parent_bus_address(Address const&) const;
private:
Address m_child_bus_address;
Address m_parent_bus_address;
Size m_length;
Node const& m_node;
};
class Ranges {
public:
Ranges(ReadonlyBytes data, Node const& node)
: m_raw(data)
, m_node(node)
{
}
ErrorOr<RangesEntry> entry(size_t index) const;
size_t entry_count() const;
ErrorOr<Address> translate_child_bus_address_to_parent_bus_address(Address const&) const;
private:
ReadonlyBytes m_raw;
Node const& m_node;
};
class Node {
AK_MAKE_NONCOPYABLE(Node);
AK_MAKE_DEFAULT_MOVABLE(Node);
@ -112,18 +268,38 @@ public:
HashMap<StringView, Node> const& children() const { return m_children; }
HashMap<StringView, Property> const& properties() const { return m_properties; }
bool is_root() const { return m_parent == nullptr; }
Node const* parent() const { return m_parent; }
// NOTE: When checking for multiple drivers, prefer iterating over the string array instead,
// as the compatible strings are sorted by preference, which this function cannot account for.
bool is_compatible_with(StringView) const;
// FIXME: Add convenience functions for common properties like "reg" and "compatible"
// Note: The "reg" property is a list of address and size pairs, but the address is not always a u32 or u64
// In pci devices the #address-size is 3 cells: (phys.lo phys.mid phys.hi)
// with the following format:
// phys.lo, phys.mid: 64-bit Address - BigEndian
// phys.hi: relocatable(1), prefetchable(1), aliased(1), 000(3), space type(2), bus number(8), device number(5), function number(3), register number(8) - BigEndian
// 2.3.5 #address-cells and #size-cells
u32 address_cells() const
{
if (auto prop = get_property("#address-cells"sv); prop.has_value())
return prop.release_value().as<u32>();
// If missing, a client program should assume a default value of 2 for #address-cells, and a value of 1 for #size-cells.
return 2;
}
// 2.3.5 #address-cells and #size-cells
u32 size_cells() const
{
if (auto prop = get_property("#size-cells"sv); prop.has_value())
return prop.release_value().as<u32>();
// If missing, a client program should assume a default value of 2 for #address-cells, and a value of 1 for #size-cells.
return 1;
}
ErrorOr<Reg> reg() const;
ErrorOr<Ranges> ranges() const;
ErrorOr<Address> translate_child_bus_address_to_root_address(Address const&) const;
// FIXME: Stringify?
// FIXME: Flatten?
@ -189,11 +365,6 @@ public:
return node->get_property(property_name);
}
// FIXME: Add a helper to iterate over each descendant fulfilling some properties
// Like each node with a "compatible" property containing "pci" or "usb",
// bonus points if it could automatically recurse in the tree under some conditions,
// like "simple-bus" or "pci-bridge" nodes
auto for_each_node(CallableAs<ErrorOr<RecursionDecision>, StringView, Node const&> auto callback) const
{
auto iterate = [&](auto self, StringView name, Node const& node) -> ErrorOr<RecursionDecision> {