aboutsummaryrefslogtreecommitdiff
path: root/kernel/kapi/devices
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/kapi/devices')
-rw-r--r--kernel/kapi/devices/interface_registry.cpp96
-rw-r--r--kernel/kapi/devices/interface_registry.tests.cpp131
2 files changed, 218 insertions, 9 deletions
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