mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-11 17:00:37 +00:00
LibDeviceTree: Add a simple DeviceTree class
This makes it easier to work with device tree nodes and properties, then writing simple state machines to parse the device tree. This also makes the old slow traversal methods use the DeviceTreeProperty helper class, and adds a simple test.
This commit is contained in:
parent
0fa718ead8
commit
21a21c6a11
Notes:
sideshowbarker
2024-07-17 02:55:44 +09:00
Author: https://github.com/Hendiadyoin1 Commit: https://github.com/SerenityOS/serenity/commit/21a21c6a11 Pull-request: https://github.com/SerenityOS/serenity/pull/22948 Reviewed-by: https://github.com/ADKaster Reviewed-by: https://github.com/kleinesfilmroellchen Reviewed-by: https://github.com/spholz
10 changed files with 316 additions and 31 deletions
|
@ -5,6 +5,7 @@ add_subdirectory(LibC)
|
|||
add_subdirectory(LibCompress)
|
||||
add_subdirectory(LibCore)
|
||||
add_subdirectory(LibCpp)
|
||||
add_subdirectory(LibDeviceTree)
|
||||
add_subdirectory(LibDiff)
|
||||
add_subdirectory(LibEDID)
|
||||
add_subdirectory(LibELF)
|
||||
|
|
9
Tests/LibDeviceTree/CMakeLists.txt
Normal file
9
Tests/LibDeviceTree/CMakeLists.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
set(TEST_SOURCES
|
||||
TestLookup.cpp
|
||||
)
|
||||
|
||||
foreach(source IN LISTS TEST_SOURCES)
|
||||
serenity_test("${source}" LibDeviceTree LIBS LibDeviceTree LibFileSystem)
|
||||
endforeach()
|
||||
|
||||
install(FILES dtb.dtb DESTINATION usr/Tests/LibDeviceTree)
|
29
Tests/LibDeviceTree/TestLookup.cpp
Normal file
29
Tests/LibDeviceTree/TestLookup.cpp
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Leon Albrecht <leon.a@serenityos.org>
|
||||
*
|
||||
* 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(basic_functionality)
|
||||
{
|
||||
auto fdt_file = TRY_OR_FAIL(Core::File::open("/usr/Tests/LibDeviceTree/dtb.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 boot_args = device_tree->resolve_property("/chosen/bootargs"sv);
|
||||
EXPECT(boot_args.has_value());
|
||||
EXPECT_EQ(boot_args->as_string(), "hello root=nvme0:1:0 serial_debug"sv);
|
||||
|
||||
EXPECT(device_tree->phandle(1));
|
||||
auto device_type = device_tree->phandle(1)->get_property("device_type"sv);
|
||||
EXPECT(device_type.has_value());
|
||||
EXPECT_EQ(device_type->as_string(), "cpu"sv);
|
||||
}
|
BIN
Tests/LibDeviceTree/dtb.dtb
Normal file
BIN
Tests/LibDeviceTree/dtb.dtb
Normal file
Binary file not shown.
|
@ -1,5 +1,6 @@
|
|||
|
||||
set(SOURCES
|
||||
DeviceTree.cpp
|
||||
FlattenedDeviceTree.cpp
|
||||
Validation.cpp
|
||||
)
|
||||
|
|
64
Userland/Libraries/LibDeviceTree/DeviceTree.cpp
Normal file
64
Userland/Libraries/LibDeviceTree/DeviceTree.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Leon Albrecht <leon.a@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "DeviceTree.h"
|
||||
#include "FlattenedDeviceTree.h"
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace DeviceTree {
|
||||
|
||||
ErrorOr<NonnullOwnPtr<DeviceTree>> DeviceTree::parse(ReadonlyBytes flattened_device_tree)
|
||||
{
|
||||
// Device tree must be 8-byte aligned
|
||||
if ((bit_cast<FlatPtr>(flattened_device_tree.data()) & 0b111) != 0)
|
||||
return Error::from_errno(EINVAL);
|
||||
|
||||
auto device_tree = TRY(adopt_nonnull_own_or_enomem(new (nothrow) DeviceTree { flattened_device_tree }));
|
||||
DeviceTreeNodeView* current_node = device_tree.ptr();
|
||||
|
||||
auto const& header = *reinterpret_cast<FlattenedDeviceTreeHeader const*>(flattened_device_tree.data());
|
||||
|
||||
TRY(walk_device_tree(header, flattened_device_tree,
|
||||
{
|
||||
.on_node_begin = [¤t_node, &device_tree](StringView name) -> ErrorOr<IterationDecision> {
|
||||
// Skip the root node, which has an empty name
|
||||
if (current_node == device_tree.ptr() && name.is_empty())
|
||||
return IterationDecision::Continue;
|
||||
|
||||
// FIXME: Use something like children.emplace
|
||||
TRY(current_node->children().try_set(name, DeviceTreeNodeView { current_node }));
|
||||
auto& new_node = current_node->children().get(name).value();
|
||||
current_node = &new_node;
|
||||
return IterationDecision::Continue;
|
||||
},
|
||||
.on_node_end = [¤t_node](StringView) -> ErrorOr<IterationDecision> {
|
||||
current_node = current_node->parent();
|
||||
return IterationDecision::Continue;
|
||||
},
|
||||
.on_property = [&device_tree, ¤t_node](StringView name, ReadonlyBytes value) -> ErrorOr<IterationDecision> {
|
||||
DeviceTreeProperty property { value };
|
||||
|
||||
if (name == "phandle"sv) {
|
||||
auto phandle = property.as<u32>();
|
||||
TRY(device_tree->set_phandle(phandle, current_node));
|
||||
}
|
||||
|
||||
TRY(current_node->properties().try_set(name, DeviceTreeProperty { value }));
|
||||
return IterationDecision::Continue;
|
||||
},
|
||||
.on_noop = []() -> ErrorOr<IterationDecision> {
|
||||
return IterationDecision::Continue;
|
||||
},
|
||||
.on_end = [&]() -> ErrorOr<void> {
|
||||
return {};
|
||||
},
|
||||
}));
|
||||
|
||||
return device_tree;
|
||||
}
|
||||
|
||||
}
|
201
Userland/Libraries/LibDeviceTree/DeviceTree.h
Normal file
201
Userland/Libraries/LibDeviceTree/DeviceTree.h
Normal file
|
@ -0,0 +1,201 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Leon Albrecht <leon.a@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/Endian.h>
|
||||
#include <AK/Function.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/IterationDecision.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/Optional.h>
|
||||
#include <AK/Span.h>
|
||||
|
||||
namespace DeviceTree {
|
||||
|
||||
struct DeviceTreeProperty {
|
||||
ReadonlyBytes raw_data;
|
||||
|
||||
size_t size() const { return raw_data.size(); }
|
||||
|
||||
StringView as_string() const { return StringView(raw_data.data(), raw_data.size() - 1); }
|
||||
Vector<StringView> as_strings() const { return as_string().split_view('\0'); }
|
||||
template<typename T>
|
||||
auto for_each_string(T callback) const { return as_string().for_each_split_view('\0', SplitBehavior::Nothing, callback); }
|
||||
|
||||
// Note: as<T> does not convert endianness, so all structures passed in
|
||||
// should use BigEndian<T>s for their members and keep ordering in mind
|
||||
// Note: The Integral variant does convert endianness, so no need to pass in BigEndian<T>s
|
||||
template<typename T>
|
||||
T as() const
|
||||
{
|
||||
VERIFY(raw_data.size() == sizeof(T));
|
||||
T value;
|
||||
__builtin_memcpy(&value, raw_data.data(), sizeof(T));
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(alignof(T) <= 4 && !IsIntegral<T>)
|
||||
T const& as() const
|
||||
{
|
||||
return *reinterpret_cast<T const*>(raw_data.data());
|
||||
}
|
||||
|
||||
template<Integral I>
|
||||
I as() const
|
||||
{
|
||||
VERIFY(raw_data.size() == sizeof(I));
|
||||
BigEndian<I> value;
|
||||
__builtin_memcpy(&value, raw_data.data(), sizeof(I));
|
||||
return value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ErrorOr<void> for_each_in_array_of(CallableAs<ErrorOr<IterationDecision>, T const&> auto callback) const
|
||||
{
|
||||
VERIFY(raw_data.size() % sizeof(T) == 0);
|
||||
size_t count = raw_data.size() / sizeof(T);
|
||||
size_t offset = 0;
|
||||
for (size_t i = 0; i < count; ++i, offset += sizeof(T)) {
|
||||
auto sub_property = DeviceTreeProperty { raw_data.slice(offset, sizeof(T)) };
|
||||
auto result = callback(sub_property.as<T>());
|
||||
if (result.is_error())
|
||||
return result;
|
||||
if (result.value() == IterationDecision::Break)
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
FixedMemoryStream as_stream() const { return FixedMemoryStream { raw_data }; }
|
||||
};
|
||||
|
||||
class DeviceTreeNodeView {
|
||||
public:
|
||||
bool has_property(StringView prop) const { return m_properties.contains(prop); }
|
||||
bool has_child(StringView child) const { return m_children.contains(child); }
|
||||
bool child(StringView name) const { return has_property(name) || has_child(name); }
|
||||
|
||||
Optional<DeviceTreeProperty> get_property(StringView prop) const { return m_properties.get(prop); }
|
||||
|
||||
// FIXME: The spec says that @address parts of the name should be ignored when looking up nodes
|
||||
// when they do not appear in the queried name, and all nodes with the same name should be returned
|
||||
Optional<DeviceTreeNodeView const&> get_child(StringView child) const { return m_children.get(child); }
|
||||
|
||||
HashMap<StringView, DeviceTreeNodeView> const& children() const { return m_children; }
|
||||
HashMap<StringView, DeviceTreeProperty> const& properties() const { return m_properties; }
|
||||
|
||||
DeviceTreeNodeView const* parent() const { return m_parent; }
|
||||
|
||||
// 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
|
||||
|
||||
// FIXME: Stringify?
|
||||
// FIXME: Flatten?
|
||||
// Note: That we dont have a oder of children and properties in this view
|
||||
protected:
|
||||
friend class DeviceTree;
|
||||
DeviceTreeNodeView(DeviceTreeNodeView* parent)
|
||||
: m_parent(parent)
|
||||
{
|
||||
}
|
||||
HashMap<StringView, DeviceTreeNodeView>& children() { return m_children; }
|
||||
HashMap<StringView, DeviceTreeProperty>& properties() { return m_properties; }
|
||||
DeviceTreeNodeView* parent() { return m_parent; }
|
||||
|
||||
private:
|
||||
DeviceTreeNodeView* m_parent;
|
||||
HashMap<StringView, DeviceTreeNodeView> m_children;
|
||||
HashMap<StringView, DeviceTreeProperty> m_properties;
|
||||
};
|
||||
|
||||
class DeviceTree : public DeviceTreeNodeView {
|
||||
public:
|
||||
static ErrorOr<NonnullOwnPtr<DeviceTree>> parse(ReadonlyBytes);
|
||||
|
||||
DeviceTreeNodeView const* resolve_node(StringView path) const
|
||||
{
|
||||
// FIXME: May children of aliases be referenced?
|
||||
// Note: Aliases may not contain a '/' in their name
|
||||
// And as all paths other than aliases should start with '/', we can just check for the first '/'
|
||||
if (!path.starts_with('/')) {
|
||||
if (auto alias_list = get_child("aliases"sv); alias_list.has_value()) {
|
||||
if (auto alias = alias_list->get_property(path); alias.has_value()) {
|
||||
path = alias.value().as_string();
|
||||
} else {
|
||||
dbgln("DeviceTree: '{}' not found in /aliases, treating as absolute path", path);
|
||||
}
|
||||
} else {
|
||||
dbgln("DeviceTree: No /aliases node found, treating '{}' as absolute path", path);
|
||||
}
|
||||
}
|
||||
|
||||
DeviceTreeNodeView const* node = this;
|
||||
path.for_each_split_view('/', SplitBehavior::Nothing, [&](auto const& part) {
|
||||
if (auto child = node->get_child(part); child.has_value()) {
|
||||
node = &child.value();
|
||||
} else {
|
||||
node = nullptr;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
Optional<DeviceTreeProperty> resolve_property(StringView path) const
|
||||
{
|
||||
auto property_name = path.find_last_split_view('/');
|
||||
auto node_path = path.substring_view(0, path.length() - property_name.length() - 1);
|
||||
auto const* node = resolve_node(node_path);
|
||||
if (!node)
|
||||
return {};
|
||||
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
|
||||
|
||||
DeviceTreeNodeView const* phandle(u32 phandle) const
|
||||
{
|
||||
if (phandle >= m_phandles.size())
|
||||
return nullptr;
|
||||
return m_phandles[phandle];
|
||||
}
|
||||
|
||||
ReadonlyBytes flattened_device_tree() const { return m_flattened_device_tree; }
|
||||
|
||||
private:
|
||||
DeviceTree(ReadonlyBytes flattened_device_tree)
|
||||
: DeviceTreeNodeView(nullptr)
|
||||
, m_flattened_device_tree(flattened_device_tree)
|
||||
{
|
||||
}
|
||||
|
||||
ErrorOr<void> set_phandle(u32 phandle, DeviceTreeNodeView* node)
|
||||
{
|
||||
if (m_phandles.size() > phandle && m_phandles[phandle] != nullptr)
|
||||
return Error::from_string_view_or_print_error_and_return_errno("Duplicate phandle entry in DeviceTree"sv, EINVAL);
|
||||
if (m_phandles.size() <= phandle)
|
||||
TRY(m_phandles.try_resize(phandle + 1));
|
||||
m_phandles[phandle] = node;
|
||||
return {};
|
||||
}
|
||||
|
||||
ReadonlyBytes m_flattened_device_tree;
|
||||
Vector<DeviceTreeNodeView*> m_phandles;
|
||||
};
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
#include <AK/IterationDecision.h>
|
||||
#include <AK/MemoryStream.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <LibDeviceTree/DeviceTree.h>
|
||||
#include <LibDeviceTree/FlattenedDeviceTree.h>
|
||||
|
||||
#ifdef KERNEL
|
||||
|
@ -137,6 +138,7 @@ static ErrorOr<ReadonlyBytes> slow_get_property_raw(StringView name, FlattenedDe
|
|||
++current_path_idx; // Root node
|
||||
return IterationDecision::Continue;
|
||||
}
|
||||
// FIXME: This might need to ignore address details in the path
|
||||
if (token_name == path[current_path_idx]) {
|
||||
++current_path_idx;
|
||||
if (current_path_idx == static_cast<ssize_t>(path.size() - 1)) {
|
||||
|
@ -169,26 +171,9 @@ static ErrorOr<ReadonlyBytes> slow_get_property_raw(StringView name, FlattenedDe
|
|||
return found_property_value;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ErrorOr<T> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree)
|
||||
ErrorOr<DeviceTreeProperty> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree)
|
||||
{
|
||||
[[maybe_unused]] auto bytes = TRY(slow_get_property_raw(name, header, raw_device_tree));
|
||||
if constexpr (IsVoid<T>) {
|
||||
return {};
|
||||
} else if constexpr (IsArithmetic<T>) {
|
||||
if (bytes.size() != sizeof(T)) {
|
||||
return Error::from_errno(EINVAL);
|
||||
}
|
||||
return *bit_cast<T*>(bytes.data());
|
||||
} else {
|
||||
static_assert(IsSame<T, StringView>);
|
||||
return T(bytes);
|
||||
}
|
||||
return DeviceTreeProperty { TRY(slow_get_property_raw(name, header, raw_device_tree)) };
|
||||
}
|
||||
|
||||
template ErrorOr<void> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree);
|
||||
template ErrorOr<u32> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree);
|
||||
template ErrorOr<u64> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree);
|
||||
template ErrorOr<StringView> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree);
|
||||
|
||||
} // namespace DeviceTree
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include <AK/StringView.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
#include "DeviceTree.h"
|
||||
|
||||
namespace DeviceTree {
|
||||
|
||||
// https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html
|
||||
|
@ -60,12 +62,6 @@ struct DeviceTreeCallbacks {
|
|||
|
||||
ErrorOr<void> walk_device_tree(FlattenedDeviceTreeHeader const&, ReadonlyBytes raw_device_tree, DeviceTreeCallbacks);
|
||||
|
||||
template<typename T>
|
||||
ErrorOr<T> slow_get_property(StringView name, FlattenedDeviceTreeHeader const&, ReadonlyBytes raw_device_tree);
|
||||
|
||||
extern template ErrorOr<void> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree);
|
||||
extern template ErrorOr<u32> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree);
|
||||
extern template ErrorOr<u64> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree);
|
||||
extern template ErrorOr<StringView> slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree);
|
||||
ErrorOr<DeviceTreeProperty> slow_get_property(StringView name, FlattenedDeviceTreeHeader const&, ReadonlyBytes raw_device_tree);
|
||||
|
||||
} // namespace DeviceTree
|
||||
|
|
|
@ -35,14 +35,13 @@ ErrorOr<int> serenity_main(Main::Arguments arguments)
|
|||
|
||||
TRY(DeviceTree::dump(*fdt_header, bytes));
|
||||
|
||||
auto compatible = TRY(DeviceTree::slow_get_property<StringView>("/compatible"sv, *fdt_header, bytes));
|
||||
auto compatible_strings = compatible.split_view('\0');
|
||||
dbgln("compatible with: {}", compatible_strings);
|
||||
auto compatible = TRY(DeviceTree::slow_get_property("/compatible"sv, *fdt_header, bytes)).as_strings();
|
||||
dbgln("compatible with: {}", compatible);
|
||||
|
||||
auto bootargs = TRY(DeviceTree::slow_get_property<StringView>("/chosen/bootargs"sv, *fdt_header, bytes));
|
||||
auto bootargs = TRY(DeviceTree::slow_get_property("/chosen/bootargs"sv, *fdt_header, bytes)).as_string();
|
||||
dbgln("bootargs: {}", bootargs);
|
||||
|
||||
auto cpu_compatible = TRY(DeviceTree::slow_get_property<StringView>("/cpus/cpu@0/compatible"sv, *fdt_header, bytes));
|
||||
auto cpu_compatible = TRY(DeviceTree::slow_get_property("/cpus/cpu@0/compatible"sv, *fdt_header, bytes)).as_string();
|
||||
dbgln("cpu0 compatible: {}", cpu_compatible);
|
||||
|
||||
return 0;
|
||||
|
|
Loading…
Reference in a new issue