diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-08-17 15:20:57 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-08-17 15:20:57 +0200 |
| commit | 824cc5a96f8a6f666a5c963897c8ab9076b48373 (patch) | |
| tree | 3c0f28e79702487f4ebcc12cf5d94bc3ef751887 /kernel/kapi/devices | |
| parent | 44f28c5ceafafd88b68de6fe2c05fac332ff3c33 (diff) | |
| download | kernel-824cc5a96f8a6f666a5c963897c8ab9076b48373.tar.xz kernel-824cc5a96f8a6f666a5c963897c8ab9076b48373.zip | |
kapi: add power management support to drivers
Diffstat (limited to 'kernel/kapi/devices')
| -rw-r--r-- | kernel/kapi/devices/driver.cpp | 12 | ||||
| -rw-r--r-- | kernel/kapi/devices/power.cpp | 93 | ||||
| -rw-r--r-- | kernel/kapi/devices/power.tests.cpp | 203 |
3 files changed, 308 insertions, 0 deletions
diff --git a/kernel/kapi/devices/driver.cpp b/kernel/kapi/devices/driver.cpp index 4f0ce0e4..5ca92dc8 100644 --- a/kernel/kapi/devices/driver.cpp +++ b/kernel/kapi/devices/driver.cpp @@ -1,6 +1,8 @@ #include <kapi/capabilities/facet_id.hpp> #include <kapi/devices.hpp> +#include <kstd/result.hpp> + #include <cstdint> #include <optional> @@ -17,6 +19,16 @@ namespace kapi::devices return std::nullopt; } + auto driver::suspend(device &) -> kstd::result<void> + { + return kstd::success(); + } + + auto driver::resume(device &) -> kstd::result<void> + { + return kstd::success(); + } + auto driver::query_facet(kapi::capabilities::facet_id) -> void * { return nullptr; diff --git a/kernel/kapi/devices/power.cpp b/kernel/kapi/devices/power.cpp new file mode 100644 index 00000000..3bc66848 --- /dev/null +++ b/kernel/kapi/devices/power.cpp @@ -0,0 +1,93 @@ +#include <kapi/devices/power.hpp> + +#include <kapi/devices.hpp> + +#include <kstd/memory.hpp> +#include <kstd/print.hpp> +#include <kstd/result.hpp> +#include <kstd/vector.hpp> + +#include <ranges> + +namespace kapi::devices +{ + + namespace + { + auto resume_subtree(device & node) -> void; + + // NOLINTNEXTLINE(misc-no-recursion) + auto suspend_subtree(device & node) -> kstd::result<void> + { + auto suspended_children = kstd::vector<kstd::shared_ptr<device>>{}; + + if (auto as_bus = node.facet<bus>()) + { + for (auto const & child : as_bus->children()) + { + auto result = suspend_subtree(*child); + if (!result) + { + for (auto const & already_suspended : std::views::reverse(suspended_children)) + { + resume_subtree(*already_suspended); + } + + return result; + } + + suspended_children.push_back(child); + } + } + + if (auto driver = node.bound_driver()) + { + auto result = driver->suspend(node); + if (!result) + { + for (auto const & already_suspended : std::views::reverse(suspended_children)) + { + resume_subtree(*already_suspended); + } + + return result; + } + } + + return kstd::success(); + } + + // NOLINTNEXTLINE(misc-no-recursion) + auto resume_subtree(device & node) -> void + { + if (auto driver = node.bound_driver()) + { + auto result = driver->resume(node); + if (!result) + { + kstd::println(kstd::print_sink::stderr, "[OS:PM] failed to resume device '{}': {}", node.name(), + result.error().message()); + } + } + + if (auto as_bus = node.facet<bus>()) + { + for (auto const & child : as_bus->children()) + { + resume_subtree(*child); + } + } + } + } // namespace + + auto suspend_tree(bus & root) -> kstd::result<void> + { + return suspend_subtree(root); + } + + auto resume_tree(bus & root) -> void + { + resume_subtree(root); + } + +} // namespace kapi::devices
\ No newline at end of file diff --git a/kernel/kapi/devices/power.tests.cpp b/kernel/kapi/devices/power.tests.cpp new file mode 100644 index 00000000..8f522040 --- /dev/null +++ b/kernel/kapi/devices/power.tests.cpp @@ -0,0 +1,203 @@ +#include <kapi/devices/power.hpp> + +#include <kapi/devices.hpp> + +#include <kstd/format.hpp> +#include <kstd/memory.hpp> +#include <kstd/result.hpp> +#include <kstd/string.hpp> +#include <kstd/system_error.hpp> +#include <kstd/vector.hpp> + +#include <catch2/catch_test_macros.hpp> + +#include <algorithm> +#include <cstddef> +#include <string_view> + +namespace +{ + + struct test_device : kapi::devices::device + { + using kapi::devices::device::device; + }; + + struct recording_driver final : kapi::devices::driver + { + explicit recording_driver(kstd::vector<kstd::string> & log, kstd::string fail_on_suspend = {}) + : m_log{&log} + , m_fail_on_suspend{fail_on_suspend} + {} + + [[nodiscard]] auto probe(kapi::devices::device &) -> kstd::result<void> override + { + return kstd::success(); + } + + auto unbind(kapi::devices::device &) -> void override {} + + [[nodiscard]] auto suspend(kapi::devices::device & device) -> kstd::result<void> override + { + if (!m_fail_on_suspend.empty() && device.name() == m_fail_on_suspend) + { + return kstd::failure(make_error_code(kstd::errc::io_error)); + } + + m_log->push_back(kstd::format("suspend:{}", device.name())); + return kstd::success(); + } + + [[nodiscard]] auto resume(kapi::devices::device & device) -> kstd::result<void> override + { + m_log->push_back(kstd::format("resume:{}", device.name())); + return kstd::success(); + } + + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "recording_driver"; + } + + private: + kstd::vector<kstd::string> * m_log; + kstd::string m_fail_on_suspend; + }; + + auto index_of(kstd::vector<kstd::string> const & log, std::string_view entry) -> std::size_t + { + auto found = std::ranges::find(log, entry); + REQUIRE(found != log.end()); + return static_cast<std::size_t>(found - log.begin()); + } + +} // namespace + +SCENARIO("Suspension and resumption happens in the correct order", "[devices][power]") +{ + GIVEN("a bus with two bound lead devices, itself bound to its own driver, attached to a root") + { + auto log = kstd::vector<kstd::string>{}; + + auto root = kstd::make_shared<kapi::devices::bus>("power_test_root"); + auto middle = kstd::make_shared<kapi::devices::bus>("power_test_middle"); + auto leaf_1 = kstd::make_shared<test_device>("power_test_leaf_1"); + auto leaf_2 = kstd::make_shared<test_device>("power_test_leaf_2"); + + auto middle_driver = kstd::make_shared<recording_driver>(log); + auto leaf_1_driver = kstd::make_shared<recording_driver>(log); + auto leaf_2_driver = kstd::make_shared<recording_driver>(log); + + root->add_child(middle); + middle->add_child(leaf_1); + middle->add_child(leaf_2); + + middle->bind_driver(middle_driver); + middle->set_state(kapi::devices::state::bound); + + leaf_1->bind_driver(leaf_1_driver); + leaf_1->set_state(kapi::devices::state::bound); + + leaf_2->bind_driver(leaf_2_driver); + leaf_2->set_state(kapi::devices::state::bound); + + WHEN("the bus is suspended") + { + auto result = kapi::devices::suspend_tree(*root); + + THEN("it succeeds") + { + REQUIRE(result); + } + + THEN("the leaves were suspended before the middle bus") + { + REQUIRE(log.size() == 3); + + auto middle_index = index_of(log, "suspend:power_test_middle"); + auto leaf_1_index = index_of(log, "suspend:power_test_leaf_1"); + auto leaf_2_index = index_of(log, "suspend:power_test_leaf_2"); + + REQUIRE(leaf_1_index < middle_index); + REQUIRE(leaf_2_index < middle_index); + } + + AND_WHEN("the bus is resumed") + { + kapi::devices::resume_tree(*root); + + THEN("the leaves were resumed after the middle bus") + { + auto middle_index = index_of(log, "resume:power_test_middle"); + auto leaf_1_index = index_of(log, "resume:power_test_leaf_1"); + auto leaf_2_index = index_of(log, "resume:power_test_leaf_2"); + + REQUIRE(middle_index < leaf_1_index); + REQUIRE(middle_index < leaf_2_index); + } + } + } + } + + GIVEN("a subtree where one leaf will fail to suspend") + { + auto log = kstd::vector<kstd::string>{}; + + auto root = kstd::make_shared<kapi::devices::bus>("power_test_root_failing"); + auto middle = kstd::make_shared<kapi::devices::bus>("power_test_middle_failing"); + auto leaf_1 = kstd::make_shared<test_device>("power_test_leaf_1_failing"); + auto leaf_2 = kstd::make_shared<test_device>("power_test_leaf_2_failing"); + + auto middle_driver = kstd::make_shared<recording_driver>(log); + // leaf_1 suspends normally; leaf_2 is told to fail. + auto leaf_1_driver = kstd::make_shared<recording_driver>(log); + auto leaf_2_driver = kstd::make_shared<recording_driver>(log, kstd::string{"power_test_leaf_2_failing"}); + + root->add_child(middle); + middle->add_child(leaf_1); + middle->add_child(leaf_2); + + middle->bind_driver(middle_driver); + middle->set_state(kapi::devices::state::bound); + leaf_1->bind_driver(leaf_1_driver); + leaf_1->set_state(kapi::devices::state::bound); + leaf_2->bind_driver(leaf_2_driver); + leaf_2->set_state(kapi::devices::state::bound); + + WHEN("the subtree is suspended") + { + auto result = kapi::devices::suspend_tree(*root); + + THEN("the failure is reported") + { + REQUIRE_FALSE(result); + } + + THEN("the leaf that suspended successfully before the failure was resumed again") + { + REQUIRE(log.size() == 2); + REQUIRE(log[0] == "suspend:power_test_leaf_1_failing"); + REQUIRE(log[1] == "resume:power_test_leaf_1_failing"); + } + + THEN("the middle bus, whose child failed to suspend, was never suspended at all") + { + REQUIRE(std::ranges::find(log, "suspend:power_test_middle_failing") == log.end()); + } + } + } + + GIVEN("a bus with a child that has no bound driver at all") + { + auto root = kstd::make_shared<kapi::devices::bus>("power_test_root_unbound"); + auto unbound_child = kstd::make_shared<test_device>("power_test_unbound_child"); + + root->add_child(unbound_child); + + THEN("suspending and resuming it does not crash, and simply does nothing for that device") + { + REQUIRE(kapi::devices::suspend_tree(*root)); + REQUIRE_NOTHROW(kapi::devices::resume_tree(*root)); + } + } +}
\ No newline at end of file |
