diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-07-25 11:25:59 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-07-25 11:29:44 +0200 |
| commit | 9c6f14583169100b7cd70346c7dc3d13d82806aa (patch) | |
| tree | a562a0c6b283573e989ef4162ffe32a9ce491b9e | |
| parent | dda6499eb6b718631034a274bba83053e1db8725 (diff) | |
| download | kernel-9c6f14583169100b7cd70346c7dc3d13d82806aa.tar.xz kernel-9c6f14583169100b7cd70346c7dc3d13d82806aa.zip | |
kapi: implement interface registry notifications
| -rw-r--r-- | kapi/kapi/devices/interface_registry.hpp | 145 | ||||
| -rw-r--r-- | kernel/kapi/devices/interface_registry.cpp | 96 | ||||
| -rw-r--r-- | kernel/kapi/devices/interface_registry.tests.cpp | 131 | ||||
| -rw-r--r-- | kernel/kernel/drivers/storage/ram_disk.cpp | 2 |
4 files changed, 359 insertions, 15 deletions
diff --git a/kapi/kapi/devices/interface_registry.hpp b/kapi/kapi/devices/interface_registry.hpp index 25a8b90f..e0e70654 100644 --- a/kapi/kapi/devices/interface_registry.hpp +++ b/kapi/kapi/devices/interface_registry.hpp @@ -18,10 +18,42 @@ namespace kapi::devices { + //! @addtogroup kapi-devices-kernel-defined + //! @{ + + struct interface_registry_observer; + + //! The device capability interface registry. + //! + //! The purpose of the interface registry is to allow system components, for + //! example drivers, to plush interfaces/capabilities for devices. An example + //! for such a capability are block devices. A driver for a given type of block + //! devices may choose too publish a the "block" capability on a bound device. + //! Other subsystems may then query if a given device exposes the "block" + //! capability or get all devices implementing that capability. + //! + //! @note The device registry holds only non-owning references to the published + //! devices. If a device dies after publishing, without being unpublished, the + //! registry accessors will prevent a dead handle from being exported. + //! + //! @see kernel::drivers::storage::ram_disk + //! @see kernel::filesystem::device_inode + //! @see kernel::filesystem::device_number_registry struct interface_registry { + //! A registry entry. + //! + //! Registry entries are used to record published interfaces on devices. struct entry { + //! Create a new registry entry, associating a device with a name, + //! interface, and interface implementation. + //! + //! @param device The device to record the published interface for. + //! @param name The stable name to associate with this interface record. + //! @param interface The id of the implemented interface. + //! @param implementation A pointer to the actual implementation of the + //! interface for the given device. constexpr entry(kstd::shared_ptr<struct device> device, kstd::string name, interface_id interface, void * implementation) : m_device{device} @@ -30,36 +62,65 @@ namespace kapi::devices , m_implementation{implementation} {} + //! Get a handle to the device of this entry. + //! + //! @return A non-null pointer to the device if it is still alive, a null + //! pointer otherwise. [[nodiscard]] constexpr auto device() const noexcept -> kstd::shared_ptr<device> { return m_device.lock(); } + //! Get the name of this entry. + //! + //! @return The name associated with this entry. [[nodiscard]] constexpr auto name() const noexcept -> kstd::string const & { return m_name; } + //! Get the interface id associated with this entry. + //! + //! @return The interface id of this entry. [[nodiscard]] constexpr auto interface() const noexcept -> interface_id { return m_interface; } + //! Get the interface implementation associated with this entry. + //! + //! @return An untyped pointer to the interface implementation. [[nodiscard]] constexpr auto implementation() const noexcept -> void * { return m_implementation; } + //! Get the interface implementation associated with this entry. + //! + //! @return A typed pointer to the implementation if the interface type + //! matches, nullptr otherwise. template<typename Interface> [[nodiscard]] constexpr auto as() noexcept -> Interface * { - return static_cast<Interface *>(m_implementation); + if (m_interface == Interface::id) + { + return static_cast<Interface *>(m_implementation); + } + return nullptr; } + //! Get the interface implementation associated with this entry. + //! + //! @return A typed pointer to the implementation if the interface type + //! matches, nullptr otherwise. template<typename Interface> [[nodiscard]] constexpr auto as() const noexcept -> Interface const * { - return static_cast<Interface const *>(m_implementation); + if (m_interface == Interface::id) + { + return static_cast<Interface const *>(m_implementation); + } + return nullptr; } private: @@ -69,10 +130,19 @@ namespace kapi::devices void * m_implementation; }; + //! Construct an empty interface registry. interface_registry() = default; + //! Initialize the system global singleton. + //! + //! @warning This function will panic if the global singleton has already been + //! initialized. auto static init() -> void; + //! Get the system global singleton. + //! + //! @warning This function will panic if the global singleton has not been + //! initialized. auto static get() -> interface_registry &; //! Publish the fact that a given device implements a given interface. @@ -107,14 +177,20 @@ namespace kapi::devices //! //! @param device The device to withdraw the interface for. //! @param interface The interface to withdraw for the given device. - auto unpublish(device const & device, interface_id interface) -> void; + auto withdraw(device const & device, interface_id interface) -> void; + + //! Withdraw every interface previously published for a given device. + //! + //! @param device The device to withdraw all interfaces for. + auto withdraw_all_for(device const & device) -> void; //! Get all devices implementing a given interface. //! //! @param interface The interface for which to get the implementing devices. [[nodiscard]] auto all(interface_id interface) const -> kstd::vector<entry>; - //! Attempt to resolve a given device, by name, to a given interface implementation + //! Attempt to resolve a given device, by name, to a given interface + //! implementation //! //! @param interface The interface to look for. //! @param name The stable name of the device to look for. @@ -126,7 +202,8 @@ namespace kapi::devices //! @param device The device to look for. [[nodiscard]] auto resolve(interface_id interface, device & device) -> void *; - //! Attempt to resolve a given device, by name, to a given interface implementation + //! Attempt to resolve a given device, by name, to a given interface + //! implementation //! //! @tparam Interface The interface to look for. //! @param name The stable name of the device to look for. @@ -146,6 +223,31 @@ namespace kapi::devices return static_cast<Interface *>(resolve(Interface::id, device)); } + //! Subscribe to interface publish/withdraw notifications. + //! + //! Observers subscribed through this function are held through non-owning + //! pointers. It is safe of an observer subscribed through this function to be + //! destroyed without being unsubscribed. + //! + //! @param observer The observer to subscribe. + auto subscribe(kstd::weak_ptr<interface_registry_observer> observer) -> void; + + //! Subscribe to interface publish/withdraw notifications. + //! + //! @warning The registry has no way of detecting liveness of observers + //! subscribed through this function. This function is intended to be used + //! solely with long-lived observers. If an observer subscribed through this + //! function is destroyed before being unregistered, the behavior of any + //! triggered notifications becomes undefined. + //! + //! @param observer The observer to subscribe. + auto subscribe(interface_registry_observer & observer) -> void; + + //! Cancel a subscription previously made for a given observer. + //! + //! @param observer The observer to unsubscribe. + auto unsubscribe(interface_registry_observer & observer) -> void; + private: //! Publish the fact that a given device implements a given interface. //! @@ -156,9 +258,42 @@ namespace kapi::devices auto do_publish(kstd::shared_ptr<device> device, kstd::string name, interface_id interface, void * implementation) -> kstd::result<void>; + auto do_notify_published(interface_id interface, entry const & published) -> void; + + auto do_notify_withdrawn(interface_id interface, device const & device) -> void; + kstd::vector<entry> m_entries; + kstd::vector<kstd::weak_ptr<interface_registry_observer>> m_observers; + kstd::vector<interface_registry_observer *> m_static_observers; }; + //! @} + + //! @addtogroup kapi-devices + //! @{ + + //! The interface for types interested in observing interface transaction on an interface registry. + struct interface_registry_observer + { + //! Enable correct destruction through base pointers. + virtual ~interface_registry_observer() = default; + + //! Called after an interface has been published for a device. + //! + //! @param interface The interface id of the published interface. + //! @param published The published entry. + virtual auto on_interface_published(interface_id interface, interface_registry::entry const & published) + -> void = 0; + + //! Called after an interface has been withdrawn. + //! + //! @param interface The interface that was withdrawn. + //! @param device The device for which the interface was withdrawn. + virtual auto on_interface_withdrawn(interface_id interface, device & device) -> void = 0; + }; + + //! @} + } // namespace kapi::devices #endif diff --git a/kernel/kapi/devices/interface_registry.cpp b/kernel/kapi/devices/interface_registry.cpp index 2ec336ed..1d456b30 100644 --- a/kernel/kapi/devices/interface_registry.cpp +++ b/kernel/kapi/devices/interface_registry.cpp @@ -46,6 +46,8 @@ namespace kapi::devices auto interface_registry::do_publish(kstd::shared_ptr<device> device, kstd::string name, interface_id interface, void * implementation) -> kstd::result<void> { + // TODO: lock registry + erase_if(m_entries, [interface](auto e) { return e.interface() == interface && !e.device(); }); if (!device || !implementation || name.empty()) @@ -53,30 +55,87 @@ namespace kapi::devices return kstd::failure(make_error_code(kstd::errc::invalid_argument)); } - auto published = std::ranges::any_of(m_entries, [&](auto const & entry) { + auto already_published = std::ranges::any_of(m_entries, [&](auto const & entry) { return entry.interface() == interface && entry.device().get() == device.get(); }); - if (published) + if (already_published) { return kstd::failure(make_error_code(kstd::errc::file_exists)); } - m_entries.emplace_back(device, std::move(name), interface, implementation); + auto & published = m_entries.emplace_back(device, std::move(name), interface, implementation); + + do_notify_published(interface, published); return kstd::success(); } - auto interface_registry::unpublish(device const & device, interface_id interface) -> void + auto interface_registry::do_notify_published(interface_id interface, entry const & published) -> void + { + std::ranges::for_each(m_observers, [&](auto observer) { + if (auto locked_observer = observer.lock()) + { + locked_observer->on_interface_published(interface, published); + } + }); + + std::ranges::for_each(m_static_observers, + [&](auto observer) { observer->on_interface_published(interface, published); }); + } + + auto interface_registry::do_notify_withdrawn(interface_id interface, device const & device) -> void + { + std::ranges::for_each(m_observers, [&](auto observer) { + if (auto locked_observer = observer.lock()) + { + locked_observer->on_interface_withdrawn(interface, const_cast<devices::device &>(device)); + } + }); + + std::ranges::for_each(m_static_observers, [&](auto observer) { + observer->on_interface_withdrawn(interface, const_cast<devices::device &>(device)); + }); + } + + auto interface_registry::withdraw(device const & device, interface_id interface) -> void + { + // TODO: lock registry + + auto did_remove = erase_if(m_entries, [&](auto e) { + auto locked_device = e.device(); + return e.interface() == interface && locked_device && locked_device.get() == &device; + }) != 0; + + if (did_remove) + { + do_notify_withdrawn(interface, device); + } + } + + auto interface_registry::withdraw_all_for(device const & device) -> void { + // TODO: lock registry + + auto withdrawn_interfaces = kstd::vector<interface_id>{}; + erase_if(m_entries, [&](auto e) { auto locked_device = e.device(); - return e.interface() == interface && locked_device && locked_device.get() == &device; + auto do_erase = locked_device && locked_device.get() == &device; + if (do_erase) + { + withdrawn_interfaces.push_back(e.interface()); + } + return do_erase; }); + + std::ranges::for_each(withdrawn_interfaces, [&](auto interface) { do_notify_withdrawn(interface, device); }); } auto interface_registry::all(interface_id interface) const -> kstd::vector<entry> { + // TODO: lock registry + auto filtered = m_entries; erase_if(filtered, [&](auto e) { auto locked_device = e.device(); @@ -87,6 +146,8 @@ namespace kapi::devices auto interface_registry::resolve(interface_id interface, std::string_view name) -> void * { + // TODO: lock registry + auto found = std::ranges::find_if(m_entries, [&](auto e) { return e.name() == name && e.device(); }); if (found == m_entries.cend()) @@ -103,6 +164,8 @@ namespace kapi::devices auto interface_registry::resolve(interface_id interface, device & device) -> void * { + // TODO: lock registry + if (auto by_device = device.as(interface)) { return by_device; @@ -121,6 +184,29 @@ namespace kapi::devices return found->implementation(); } + auto interface_registry::subscribe(kstd::weak_ptr<interface_registry_observer> observer) -> void + { + // TODO: lock registry + + erase_if(m_observers, [](auto const & observer) { return observer.expired(); }); + m_observers.push_back(observer); + } + + auto interface_registry::subscribe(interface_registry_observer & observer) -> void + { + // TODO: lock registry + + m_static_observers.push_back(&observer); + } + + auto interface_registry::unsubscribe(interface_registry_observer & observer) -> void + { + // TODO: lock registry + + erase_if(m_observers, [&](auto const & subscribed) { return subscribed.lock().get() == &observer; }); + erase(m_static_observers, &observer); + } + } // namespace kapi::devices namespace kapi::test_support::devices diff --git a/kernel/kapi/devices/interface_registry.tests.cpp b/kernel/kapi/devices/interface_registry.tests.cpp index 4d2e2097..071b9a6d 100644 --- a/kernel/kapi/devices/interface_registry.tests.cpp +++ b/kernel/kapi/devices/interface_registry.tests.cpp @@ -4,6 +4,8 @@ #include <catch2/catch_test_macros.hpp> +#include <cstddef> + namespace { struct probe_device @@ -66,6 +68,44 @@ namespace const_device * m_const_device; }; + struct counting_observer final : kapi::devices::interface_registry_observer + { + auto on_interface_published(kapi::devices::interface_id, kapi::devices::interface_registry::entry const &) + -> void override + { + ++published; + } + + auto on_interface_withdrawn(kapi::devices::interface_id, kapi::devices::device &) -> void override + { + ++withdrawn; + } + + std::size_t published{}; + std::size_t withdrawn{}; + }; + + struct evil_observer final : kapi::devices::interface_registry_observer + { + explicit evil_observer(kstd::shared_ptr<counting_observer> & victim) + : m_victim{victim} + {} + + auto on_interface_published(kapi::devices::interface_id, kapi::devices::interface_registry::entry const &) + -> void override + { + m_victim.reset(); + } + + auto on_interface_withdrawn(kapi::devices::interface_id, kapi::devices::device &) -> void override + { + m_victim.reset(); + } + + private: + kstd::shared_ptr<counting_observer> & m_victim; // NOLINT + }; + } // namespace SCENARIO("Publishing and finding a device", "[kapi][devices][interface_registry]") @@ -138,7 +178,7 @@ SCENARIO("Publishing and finding a device", "[kapi][devices][interface_registry] } } - THEN("find finds the published capability by name") + THEN("resolve finds the published capability by name") { REQUIRE(registry.resolve<probe_device>("probe0")); } @@ -153,14 +193,14 @@ SCENARIO("Publishing and finding a device", "[kapi][devices][interface_registry] REQUIRE_FALSE(registry.resolve<unimplemented_device>("probe0")); } - THEN("find for an unpublished device does not find a device") + THEN("resolve for an unpublished device does not find a device") { REQUIRE_FALSE(registry.resolve<probe_device>("probe1")); } THEN("withdrawing an interface for a device removes it from the registry") { - registry.unpublish(*device, probe_device::id); + registry.withdraw(*device, probe_device::id); REQUIRE_FALSE(registry.resolve<probe_device>("probe0")); } } @@ -175,7 +215,7 @@ SCENARIO("Publishing and finding a device", "[kapi][devices][interface_registry] REQUIRE(registry.all(probe_device::id).empty()); } - THEN("find() no longer finds it") + THEN("resolve() no longer finds it") { REQUIRE_FALSE(registry.resolve<probe_device>("probe0")); } @@ -204,3 +244,86 @@ SCENARIO("Publishing and finding a device", "[kapi][devices][interface_registry] } } } + +SCENARIO("Interface registry notifies subscribers", "[kapi][devices][interface_registry]") +{ + GIVEN("a device and a subscribed observer") + { + auto registry = kapi::devices::interface_registry{}; + auto free_standing_interface = const_device{}; + auto device = kstd::make_shared<test_device>(128, free_standing_interface); + auto observer = kstd::make_shared<counting_observer>(); + + registry.subscribe(observer); + + WHEN("an interface is published for the device") + { + CHECK(registry.publish(device, "probe0", device->as<probe_device>())); + + THEN("the observer is notified exactly once") + { + REQUIRE(observer->published == 1); + REQUIRE(observer->withdrawn == 0); + } + } + + WHEN("an interface is published and then withdrawn") + { + CHECK(registry.publish(device, "probe0", device->as<probe_device>())); + registry.withdraw(*device, probe_device::id); + + THEN("the observer is notified of both exactly one") + { + REQUIRE(observer->published == 1); + REQUIRE(observer->withdrawn == 1); + } + } + + WHEN("the observer is destroyed before anything is published") + { + observer.reset(); + + THEN("publishing afterward does not crash") + { + REQUIRE_NOTHROW(registry.publish(device, "probe0", device->as<probe_device>())); + } + } + + WHEN("the observer unsubscribes before anything is published") + { + registry.unsubscribe(*observer); + + THEN("publishing afterward does not crash") + { + REQUIRE_NOTHROW(registry.publish(device, "probe0", device->as<probe_device>())); + } + + THEN("the subscriber is not notified") + { + REQUIRE_NOTHROW(registry.publish(device, "probe0", device->as<probe_device>())); + REQUIRE_NOTHROW(registry.withdraw(*device, probe_device::id)); + REQUIRE(observer->published == 0); + REQUIRE(observer->withdrawn == 0); + } + } + } + + GIVEN("two subscriber, the first destroying the second") + { + auto registry = kapi::devices::interface_registry{}; + auto free_standing_interface = const_device{}; + auto device = kstd::make_shared<test_device>(128, free_standing_interface); + + auto second = kstd::make_shared<counting_observer>(); + auto first = kstd::make_shared<evil_observer>(second); + + registry.subscribe(first); + registry.subscribe(second); + + THEN("publishing an interface does not crash") + { + REQUIRE_NOTHROW(registry.publish(device, "probe0", device->as<probe_device>())); + REQUIRE(second == nullptr); + } + } +}
\ No newline at end of file diff --git a/kernel/kernel/drivers/storage/ram_disk.cpp b/kernel/kernel/drivers/storage/ram_disk.cpp index 4c9f412d..52aedc82 100644 --- a/kernel/kernel/drivers/storage/ram_disk.cpp +++ b/kernel/kernel/drivers/storage/ram_disk.cpp @@ -132,7 +132,7 @@ namespace kernel::drivers::storage auto ram_disk::unbind(kapi::devices::device & device) -> void { - kapi::devices::interface_registry::get().unpublish(device, kapi::filesystem::block_special_file::id); + kapi::devices::interface_registry::get().withdraw(device, kapi::filesystem::block_special_file::id); device.set_driver_data(nullptr); } |
