mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-22 09:21:57 -05:00
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:
parent
7d41d5191b
commit
ddd9ab21e6
8 changed files with 407 additions and 11 deletions
|
@ -82,6 +82,10 @@
|
|||
# cmakedefine01 CSS_TRANSITIONS_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef DEVICETREE_DEBUG
|
||||
# cmakedefine01 DEVICETREE_DEBUG
|
||||
#endif
|
||||
|
||||
#ifndef DDS_DEBUG
|
||||
# cmakedefine01 DDS_DEBUG
|
||||
#endif
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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=",
|
||||
|
|
|
@ -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)
|
||||
|
|
47
Tests/LibDeviceTree/TestAddressTranslation.cpp
Normal file
47
Tests/LibDeviceTree/TestAddressTranslation.cpp
Normal 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);
|
||||
}
|
||||
}
|
BIN
Tests/LibDeviceTree/address-translation.dtb
Normal file
BIN
Tests/LibDeviceTree/address-translation.dtb
Normal file
Binary file not shown.
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Reference in a new issue