From a6166befa7421be233ece97e184139870bbc0af7 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Thu, 16 Jul 2026 14:16:33 +0200 Subject: kapi: introduce device interfaces --- kernel/CMakeLists.txt | 1 + kernel/kapi/devices.cpp | 21 ++- kernel/kapi/devices/device.cpp | 6 + kernel/kapi/devices/interface_registry.cpp | 108 +++++++++++ kernel/kapi/devices/interface_registry.tests.cpp | 199 +++++++++++++++++++++ kernel/kernel/devices/storage/ram_disk/device.cpp | 1 - .../kernel/test_support/state_reset_listener.cpp | 5 + 7 files changed, 336 insertions(+), 5 deletions(-) create mode 100644 kernel/kapi/devices/interface_registry.cpp create mode 100644 kernel/kapi/devices/interface_registry.tests.cpp (limited to 'kernel') diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 34d71de1..a73418f0 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -15,6 +15,7 @@ target_sources("kernel_lib" PRIVATE "kapi/devices/bus.cpp" "kapi/devices/cpu.cpp" "kapi/devices/device.cpp" + "kapi/devices/interface_registry.cpp" "kapi/filesystem.cpp" "kapi/interrupts.cpp" "kapi/memory.cpp" diff --git a/kernel/kapi/devices.cpp b/kernel/kapi/devices.cpp index de4bf9e5..ef72c667 100644 --- a/kernel/kapi/devices.cpp +++ b/kernel/kapi/devices.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -25,12 +26,13 @@ namespace kapi::devices auto init() -> void { - auto static is_initialized = std::atomic_flag{}; - if (is_initialized.test_and_set()) + if (root_bus.has_value()) { - return; + kapi::system::panic("[OS:DEV] The device subsystem has already been initialized"); } + interface_registry::init(); + auto & bus = root_bus.emplace(); register_device(bus); bus.init(); @@ -83,4 +85,15 @@ namespace kapi::devices return nullptr; } -} // namespace kapi::devices \ No newline at end of file +} // namespace kapi::devices + +namespace kapi::test_support::devices +{ + auto deinit() -> void + { + deinit_interface_registry(); + + kapi::devices::root_bus.reset(); + kapi::devices::next_major_number = 1; + } +} // namespace kapi::test_support::devices \ No newline at end of file diff --git a/kernel/kapi/devices/device.cpp b/kernel/kapi/devices/device.cpp index 5dc59a27..d8cae29c 100644 --- a/kernel/kapi/devices/device.cpp +++ b/kernel/kapi/devices/device.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -40,4 +41,9 @@ namespace kapi::devices m_parent = parent; } + auto device::query_interface(interface) -> void * + { + return nullptr; + } + } // namespace kapi::devices \ No newline at end of file diff --git a/kernel/kapi/devices/interface_registry.cpp b/kernel/kapi/devices/interface_registry.cpp new file mode 100644 index 00000000..8882e8db --- /dev/null +++ b/kernel/kapi/devices/interface_registry.cpp @@ -0,0 +1,108 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kapi::devices +{ + + namespace + { + constinit auto static registry = std::optional{}; + } + + auto interface_registry::init() -> void + { + if (registry.has_value()) + { + system::panic("[kernel] Device interface registry has already been initialized."); + } + + registry.emplace(); + } + + auto interface_registry::get() -> interface_registry & + { + if (!registry) + { + system::panic("[kernel] Device interface registry has not been initialized."); + } + + return *registry; + } + + auto interface_registry::do_publish(kstd::shared_ptr device, kstd::string name, interface interface, + void * implementation) -> kstd::result + { + erase_if(m_entries, [interface](auto e) { return e.interface() == interface && e.device().expired(); }); + + if (!device || !implementation || name.empty()) + { + return kstd::failure(make_error_code(kstd::errc::invalid_argument)); + } + + auto published = std::ranges::any_of(m_entries, [&](auto const & entry) { + return entry.interface() == interface && entry.device().lock().get() == device.get(); + }); + + if (published) + { + return kstd::failure(make_error_code(kstd::errc::file_exists)); + } + + m_entries.emplace_back(device, std::move(name), interface, implementation); + + return kstd::success(); + } + + auto interface_registry::unpublish(device const & device, interface interface) -> void + { + erase_if(m_entries, [&](auto e) { + auto locked_device = e.device().lock(); + return e.interface() == interface && locked_device && locked_device.get() == &device; + }); + } + + auto interface_registry::all(interface interface) const -> kstd::vector + { + auto filtered = m_entries; + erase_if(filtered, [&](auto e) { + auto locked_device = e.device().lock(); + return !(e.interface() == interface && locked_device); + }); + return filtered; + } + + auto interface_registry::find(interface interface, std::string_view name) const -> kstd::result + { + auto found = std::ranges::find_if( + m_entries, [&](auto e) { return e.interface() == interface && e.name() == name && e.device().lock(); }); + if (found == m_entries.cend()) + { + return kstd::failure(make_error_code(kstd::errc::no_such_device)); + } + + return *found; + } + +} // namespace kapi::devices + +namespace kapi::test_support::devices +{ + auto deinit_interface_registry() -> void + { + kapi::devices::registry.reset(); + } +} // namespace kapi::test_support::devices diff --git a/kernel/kapi/devices/interface_registry.tests.cpp b/kernel/kapi/devices/interface_registry.tests.cpp new file mode 100644 index 00000000..ffd9f0d6 --- /dev/null +++ b/kernel/kapi/devices/interface_registry.tests.cpp @@ -0,0 +1,199 @@ +#include + +#include + +#include + +namespace +{ + struct probe_device + { + constexpr auto static id = kapi::devices::interface{"probe"}; + + virtual ~probe_device() = default; + + [[nodiscard]] auto virtual get_value() const noexcept -> int = 0; + }; + + struct const_device + { + constexpr auto static id = kapi::devices::interface{"flip"}; + + virtual ~const_device() = default; + + [[nodiscard]] auto get_constant() const noexcept -> int + { + return 0; + } + }; + + struct test_device final : kapi::devices::device, probe_device + { + explicit test_device(int value, const_device & const_device) + : device{0, 0, "probeable"} + , m_value{value} + , m_const_device{&const_device} + {} + + auto init() -> bool override + { + return true; + } + + [[nodiscard]] constexpr auto get_value() const noexcept -> int override + { + return m_value; + } + + auto query_interface(kapi::devices::interface interface) -> void * override + { + if (interface == probe_device::id) + { + return static_cast(this); + } + if (interface == const_device::id) + { + return m_const_device; + } + + return kapi::devices::device::query_interface(interface); + } + + private: + int m_value; + const_device * m_const_device; + }; + +} // namespace + +SCENARIO("Publishing and finding a device", "[kapi][devices][interface_registry]") +{ + GIVEN("An empty registry, a device, and an interface the device implements by inheritance") + { + auto registry = kapi::devices::interface_registry{}; + auto free_standing_interface = const_device{}; + auto device = kstd::make_shared(128, free_standing_interface); + + THEN("Publishing an interface without a name fails") + { + REQUIRE_FALSE(registry.publish(device, "")); + } + + WHEN("publishing the interface for the device") + { + CHECK(device->is_a()); + CHECK(device->is_a()); + auto published = registry.publish(device, "probe0", device->as()); + + THEN("publishing is successful") + { + REQUIRE(published); + } + + THEN("publishing the same device and interface with a different name fails") + { + REQUIRE_FALSE(registry.publish(device, "probe1", device->as())); + } + + THEN("publishing a second interface for the same device succeeds") + { + REQUIRE(registry.publish(device, "probe0")); + } + + THEN("publishing a free standing second interface for the same device succeeds") + { + REQUIRE(registry.publish(device, "probe0", &free_standing_interface)); + } + + AND_WHEN("getting all devices implementing that interface") + { + auto probeable_devices = registry.all(probe_device::id); + + THEN("there is exactly one such device") + { + REQUIRE(probeable_devices.size() == 1); + } + + THEN("the name of the device is 'probe0'") + { + REQUIRE(probeable_devices[0].name() == "probe0"); + } + + THEN("the returned device pointer is lockable") + { + REQUIRE(probeable_devices[0].device().lock()); + } + + THEN("the returned implementation equals the result of device::as") + { + REQUIRE(probeable_devices[0].implementation() == device->as()); + } + + THEN("the interface provided function can be invoked") + { + auto implementation = probeable_devices[0].as(); + REQUIRE(implementation->get_value() == device->get_value()); + } + } + + THEN("find finds the published capability by name") + { + REQUIRE(registry.find("probe0")); + } + + THEN("find for an unpublished capability does not find a device") + { + REQUIRE_FALSE(registry.find("probe0")); + } + + THEN("find for an unpublished device does not find a device") + { + REQUIRE_FALSE(registry.find("probe1")); + } + + THEN("withdrawing an interface for a device removes it from the registry") + { + registry.unpublish(*device, probe_device::id); + REQUIRE_FALSE(registry.find("probe0")); + } + } + + WHEN("the device is destroyed without withdrawing it") + { + CHECK(registry.publish(device, "probe0", device->as())); + device.reset(); + + THEN("querying all does no longer report it") + { + REQUIRE(registry.all(probe_device::id).empty()); + } + + THEN("find() no longer finds it") + { + REQUIRE_FALSE(registry.find("probe0")); + } + } + } + + GIVEN("no device has ever been published") + { + auto registry = kapi::devices::interface_registry{}; + + THEN("all() returns an empty vector") + { + REQUIRE(registry.all(probe_device::id).empty()); + } + } + + GIVEN("a null device") + { + auto registry = kapi::devices::interface_registry{}; + auto device = kstd::shared_ptr{}; + auto interface = const_device{}; + + THEN("publishing the interface for the device fails") + { + REQUIRE_FALSE(registry.publish(device, "probe0", &interface)); + } + } +} diff --git a/kernel/kernel/devices/storage/ram_disk/device.cpp b/kernel/kernel/devices/storage/ram_disk/device.cpp index 2b1c02a2..eff6114d 100644 --- a/kernel/kernel/devices/storage/ram_disk/device.cpp +++ b/kernel/kernel/devices/storage/ram_disk/device.cpp @@ -7,7 +7,6 @@ #include #include -#include #include diff --git a/kernel/kernel/test_support/state_reset_listener.cpp b/kernel/kernel/test_support/state_reset_listener.cpp index 6bb7537c..cbb8ef7c 100644 --- a/kernel/kernel/test_support/state_reset_listener.cpp +++ b/kernel/kernel/test_support/state_reset_listener.cpp @@ -9,7 +9,9 @@ #include #include +#include #include +#include #include #include @@ -27,6 +29,7 @@ struct state_reset_listener : Catch::EventListenerBase kapi::cio::init(); kapi::cpu::init(); kapi::memory::init(); + kapi::devices::init(); } void testCaseEnded(Catch::TestCaseStats const &) override @@ -39,6 +42,8 @@ struct state_reset_listener : Catch::EventListenerBase kernel::tests::memory::deinit(); kernel::tests::cpu::deinit(); kernel::tests::cio::deinit(); + + kapi::test_support::devices::deinit(); } }; -- cgit v1.2.3