aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-07-25 11:25:59 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-07-25 11:29:44 +0200
commit9c6f14583169100b7cd70346c7dc3d13d82806aa (patch)
treea562a0c6b283573e989ef4162ffe32a9ce491b9e
parentdda6499eb6b718631034a274bba83053e1db8725 (diff)
downloadkernel-9c6f14583169100b7cd70346c7dc3d13d82806aa.tar.xz
kernel-9c6f14583169100b7cd70346c7dc3d13d82806aa.zip
kapi: implement interface registry notifications
-rw-r--r--kapi/kapi/devices/interface_registry.hpp145
-rw-r--r--kernel/kapi/devices/interface_registry.cpp96
-rw-r--r--kernel/kapi/devices/interface_registry.tests.cpp131
-rw-r--r--kernel/kernel/drivers/storage/ram_disk.cpp2
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);
}