aboutsummaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-08-17 15:20:57 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-08-17 15:20:57 +0200
commit824cc5a96f8a6f666a5c963897c8ab9076b48373 (patch)
tree3c0f28e79702487f4ebcc12cf5d94bc3ef751887 /kernel
parent44f28c5ceafafd88b68de6fe2c05fac332ff3c33 (diff)
downloadkernel-824cc5a96f8a6f666a5c963897c8ab9076b48373.tar.xz
kernel-824cc5a96f8a6f666a5c963897c8ab9076b48373.zip
kapi: add power management support to drivers
Diffstat (limited to 'kernel')
-rw-r--r--kernel/CMakeLists.txt1
-rw-r--r--kernel/kapi/devices/driver.cpp12
-rw-r--r--kernel/kapi/devices/power.cpp93
-rw-r--r--kernel/kapi/devices/power.tests.cpp203
4 files changed, 309 insertions, 0 deletions
diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt
index dd72051d..d286b2aa 100644
--- a/kernel/CMakeLists.txt
+++ b/kernel/CMakeLists.txt
@@ -19,6 +19,7 @@ target_sources("kernel_lib" PRIVATE
"kapi/devices/driver_registry.cpp"
"kapi/devices/error.cpp"
"kapi/devices/facet_registry.cpp"
+ "kapi/devices/power.cpp"
"kapi/filesystem.cpp"
"kapi/interrupts.cpp"
"kapi/memory.cpp"
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