From 21a21c6a11d8a84dbd46ed8b458c6e25f4c1854c Mon Sep 17 00:00:00 2001 From: Hediadyoin1 Date: Wed, 14 Feb 2024 14:57:17 +0100 Subject: [PATCH] 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. --- Tests/CMakeLists.txt | 1 + Tests/LibDeviceTree/CMakeLists.txt | 9 + Tests/LibDeviceTree/TestLookup.cpp | 29 +++ Tests/LibDeviceTree/dtb.dtb | Bin 0 -> 4375 bytes .../Libraries/LibDeviceTree/CMakeLists.txt | 1 + .../Libraries/LibDeviceTree/DeviceTree.cpp | 64 ++++++ Userland/Libraries/LibDeviceTree/DeviceTree.h | 201 ++++++++++++++++++ .../LibDeviceTree/FlattenedDeviceTree.cpp | 23 +- .../LibDeviceTree/FlattenedDeviceTree.h | 10 +- Userland/Utilities/fdtdump.cpp | 9 +- 10 files changed, 316 insertions(+), 31 deletions(-) create mode 100644 Tests/LibDeviceTree/CMakeLists.txt create mode 100644 Tests/LibDeviceTree/TestLookup.cpp create mode 100644 Tests/LibDeviceTree/dtb.dtb create mode 100644 Userland/Libraries/LibDeviceTree/DeviceTree.cpp create mode 100644 Userland/Libraries/LibDeviceTree/DeviceTree.h diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index eff2a1c661..de523f1280 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -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) diff --git a/Tests/LibDeviceTree/CMakeLists.txt b/Tests/LibDeviceTree/CMakeLists.txt new file mode 100644 index 0000000000..86ed04371c --- /dev/null +++ b/Tests/LibDeviceTree/CMakeLists.txt @@ -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) diff --git a/Tests/LibDeviceTree/TestLookup.cpp b/Tests/LibDeviceTree/TestLookup.cpp new file mode 100644 index 0000000000..bc0d8084f1 --- /dev/null +++ b/Tests/LibDeviceTree/TestLookup.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +#include +#include +#include +#include + +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); +} diff --git a/Tests/LibDeviceTree/dtb.dtb b/Tests/LibDeviceTree/dtb.dtb new file mode 100644 index 0000000000000000000000000000000000000000..def7fcb6e48185df5d9cc3085a8678b3a2f57e1e GIT binary patch literal 4375 zcmcb>`|m9SgP=GA1A_$v1Ahzy1A_(w1A`y~1A_nq7%=W;VBl8)Gf@dfD3_Umfq{jA zfkBpmfq{vEfdR$`nE~VTGB7Z(6=fDDm+6*e7L{b?GZd!gLe&a^%t4mZDNM~R1qm`R zFcv^qATw%K^lx9Y()k}21N!C z8{#%DxLb5UrW=540=bhhtz0)bE#1M;z|a5$K;j^KK~{qN4`PGDqz5D+0Ahg{3=9%b ze}MeU26DO%#30?=+{}Dv*g(wBNi5EAFanuvU;wrj7FLW+P<0?X6c`v7z;=KbAS*zc zL9`NxfO;BgC)h8^X_>ldAhQ`5M4{4*$r<^@sd-=xATyO17#KHYq~_%0D-`ADm)Pc& z<)#`~8Cn@A6sHztCg#Maq$ZW7L&687UV(vuab-i#y<2A|scUcA?xnQC%q>bs(vG#e zdB@t{-|Tn)*}L`@)D5Bx42)&^#retl5VIU0!3XkTXei7*1^MNvMfquIU>8B`VPRlk zOkiLDs{z>w;zy#%2O)(ED8$){D~pr!^K_Ao0|gIbQEF0tehFAN%&Y_khVpW#8wr`s z2TBuAv!O0kkI4LC7@ z%wuO@V3+`r1G@ku267KLJvD%Z7#JKNG}xTvg3@9L7nT;m`55LNnEOCVKxTpX#tbv2 zfsA5IE+};X+l&Z@IiSpfl?v(+hS7RGyWAfuS`&JFybV2dQBL`5#;sK+FV* zf$RkN8{}*lACyjD{3Hej2LGZmGn35R#I%&;jQFa|e(^GXU5ON&$Es}jqLisP%&6633qK{zG8Dk(9(Dk&+xDk(X>DycZWxVR)azPKzg zr4->FZUzPhH?SLZipxyR89?F3n3-3ST2xe8P@1?k_$?8GxJK4!DSrGub?mm#T5*L!d*8vu>fQSV{%SuaYZIrAq@-mQXXqA{fZYpnKO;s_1Bo9{ID_m2 zm5bQy1eF763>KiI1GWcbA=nKd2H54`pa;u={K%G9Y-na`YLE!^FWByq)Z!9oAqUa| zvj-GUAp1dlka1vhVYMikXu0*R_%bq zKp5snWd;Tg5FdtN<}0F!gTf4I4vKn^7zm@A12bO%8fNI`E1`+Q%vVMe2bm4R=;px8 zS40atnERE{#9`(upoxRb24Qq_VCI9u4J04Jz`&pbG92a+5Dk(5(V*l8vH-*er%DER z`3x?nq3S?pf|Q`sAZd77gXVi!k^{+svOcWs!ok46C- z=1(X{&V=#BKxwrgIa4qC^9Y=cK$RHCLWG+bKzSIHSU`F}`mvT90t}#P2&5Af1mHZ&19C6Kd>xSa zAW5utn!{RP5PyK|0NDx6Kj3N`hn?VZ7uGxmxsPZ&&9K`Es`EhRfZTw?&IFJ@$g;Q!=$bT?C4u8UO0O|1wDj%`f39=7_L3ZP?6D8Mx)Dj(^ zpq4xqJ3;9UWDXuXVMQ?M{sgtHvDgVJUI^F;wu`7VRFIRI?2rtpaM0`m)o~zuKp5n| zM+^)M@z8t=vH)D?fdUY$50Y#_YC&@6p!FH3Jpc+LP`Lw=11W{Ih2%i_7g`;HoS|!| zXP{@m0BLQ31PmB5LA@G~K9K#Oas{Lx#)p+VAoCfMb29TvVErwSDo9>Ob_+-!NG&Yi zgXF+|011M%fI=3cPa4T>V54E~f^tBO6Oc9-=FH7cNzGwM&d)7KEXhpDNo7zj&a6t+ zO-{|pDP~YkOi3w9EiQ&~ixTtFQ;Qi&GILXt5{pxH(~43HOH=cbD;aWgOLa>s3sRwh zkXf7vWq}&Gx=A_t$=SLfa}lCd$fCt1i6y1Q3`MEw3@NE)naQc~V8aSB67y1WQW>ze zTa}SS!HxyB;^AV&x~UZDNrl(Qc}y13@S)0O3f=l2oy8qWagDt=zyw`l>FSp z%shsq(qdh(j~P;O6LpjGGg48tfSb2)3qd^H+{EH+kS{&eYBm?Ty)UwpP65Wz~-Q0|V+~oXHP^-O|p(rn1 Sw>UL5g#pxuOe{(-W&i-(h+stk literal 0 HcmV?d00001 diff --git a/Userland/Libraries/LibDeviceTree/CMakeLists.txt b/Userland/Libraries/LibDeviceTree/CMakeLists.txt index 6ab6fc7380..78b2e0241c 100644 --- a/Userland/Libraries/LibDeviceTree/CMakeLists.txt +++ b/Userland/Libraries/LibDeviceTree/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES + DeviceTree.cpp FlattenedDeviceTree.cpp Validation.cpp ) diff --git a/Userland/Libraries/LibDeviceTree/DeviceTree.cpp b/Userland/Libraries/LibDeviceTree/DeviceTree.cpp new file mode 100644 index 0000000000..51cfef6457 --- /dev/null +++ b/Userland/Libraries/LibDeviceTree/DeviceTree.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "DeviceTree.h" +#include "FlattenedDeviceTree.h" +#include +#include + +namespace DeviceTree { + +ErrorOr> DeviceTree::parse(ReadonlyBytes flattened_device_tree) +{ + // Device tree must be 8-byte aligned + if ((bit_cast(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(flattened_device_tree.data()); + + TRY(walk_device_tree(header, flattened_device_tree, + { + .on_node_begin = [¤t_node, &device_tree](StringView name) -> ErrorOr { + // 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 { + current_node = current_node->parent(); + return IterationDecision::Continue; + }, + .on_property = [&device_tree, ¤t_node](StringView name, ReadonlyBytes value) -> ErrorOr { + DeviceTreeProperty property { value }; + + if (name == "phandle"sv) { + auto phandle = property.as(); + TRY(device_tree->set_phandle(phandle, current_node)); + } + + TRY(current_node->properties().try_set(name, DeviceTreeProperty { value })); + return IterationDecision::Continue; + }, + .on_noop = []() -> ErrorOr { + return IterationDecision::Continue; + }, + .on_end = [&]() -> ErrorOr { + return {}; + }, + })); + + return device_tree; +} + +} diff --git a/Userland/Libraries/LibDeviceTree/DeviceTree.h b/Userland/Libraries/LibDeviceTree/DeviceTree.h new file mode 100644 index 0000000000..b50cba5002 --- /dev/null +++ b/Userland/Libraries/LibDeviceTree/DeviceTree.h @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +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 as_strings() const { return as_string().split_view('\0'); } + template + auto for_each_string(T callback) const { return as_string().for_each_split_view('\0', SplitBehavior::Nothing, callback); } + + // Note: as does not convert endianness, so all structures passed in + // should use BigEndians for their members and keep ordering in mind + // Note: The Integral variant does convert endianness, so no need to pass in BigEndians + template + T as() const + { + VERIFY(raw_data.size() == sizeof(T)); + T value; + __builtin_memcpy(&value, raw_data.data(), sizeof(T)); + return value; + } + + template + requires(alignof(T) <= 4 && !IsIntegral) + T const& as() const + { + return *reinterpret_cast(raw_data.data()); + } + + template + I as() const + { + VERIFY(raw_data.size() == sizeof(I)); + BigEndian value; + __builtin_memcpy(&value, raw_data.data(), sizeof(I)); + return value; + } + + template + ErrorOr for_each_in_array_of(CallableAs, 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()); + 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 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 get_child(StringView child) const { return m_children.get(child); } + + HashMap const& children() const { return m_children; } + HashMap 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& children() { return m_children; } + HashMap& properties() { return m_properties; } + DeviceTreeNodeView* parent() { return m_parent; } + +private: + DeviceTreeNodeView* m_parent; + HashMap m_children; + HashMap m_properties; +}; + +class DeviceTree : public DeviceTreeNodeView { +public: + static ErrorOr> 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 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 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 m_phandles; +}; + +} diff --git a/Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.cpp b/Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.cpp index 9112619e81..af523beed7 100644 --- a/Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.cpp +++ b/Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #ifdef KERNEL @@ -137,6 +138,7 @@ static ErrorOr 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(path.size() - 1)) { @@ -169,26 +171,9 @@ static ErrorOr slow_get_property_raw(StringView name, FlattenedDe return found_property_value; } -template -ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree) +ErrorOr 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) { - return {}; - } else if constexpr (IsArithmetic) { - if (bytes.size() != sizeof(T)) { - return Error::from_errno(EINVAL); - } - return *bit_cast(bytes.data()); - } else { - static_assert(IsSame); - return T(bytes); - } + return DeviceTreeProperty { TRY(slow_get_property_raw(name, header, raw_device_tree)) }; } -template ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree); -template ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree); -template ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree); -template ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree); - } // namespace DeviceTree diff --git a/Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.h b/Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.h index 36c85ef6c3..7b2d263725 100644 --- a/Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.h +++ b/Userland/Libraries/LibDeviceTree/FlattenedDeviceTree.h @@ -13,6 +13,8 @@ #include #include +#include "DeviceTree.h" + namespace DeviceTree { // https://devicetree-specification.readthedocs.io/en/v0.3/flattened-format.html @@ -60,12 +62,6 @@ struct DeviceTreeCallbacks { ErrorOr walk_device_tree(FlattenedDeviceTreeHeader const&, ReadonlyBytes raw_device_tree, DeviceTreeCallbacks); -template -ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const&, ReadonlyBytes raw_device_tree); - -extern template ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree); -extern template ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree); -extern template ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree); -extern template ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const& header, ReadonlyBytes raw_device_tree); +ErrorOr slow_get_property(StringView name, FlattenedDeviceTreeHeader const&, ReadonlyBytes raw_device_tree); } // namespace DeviceTree diff --git a/Userland/Utilities/fdtdump.cpp b/Userland/Utilities/fdtdump.cpp index 3e48453512..689a285e78 100644 --- a/Userland/Utilities/fdtdump.cpp +++ b/Userland/Utilities/fdtdump.cpp @@ -35,14 +35,13 @@ ErrorOr serenity_main(Main::Arguments arguments) TRY(DeviceTree::dump(*fdt_header, bytes)); - auto compatible = TRY(DeviceTree::slow_get_property("/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("/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("/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;