aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-07-26 11:37:52 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-07-26 11:37:52 +0200
commit5a5fefba28a52feffadf2b810b2f935934aa7b0b (patch)
tree4583579a3b964091455344969a3e9641fb8fef33
parentad43e9c9eac69b862789f2e132201cd5ceebaffa (diff)
downloadkernel-5a5fefba28a52feffadf2b810b2f935934aa7b0b.tar.xz
kernel-5a5fefba28a52feffadf2b810b2f935934aa7b0b.zip
kapi/device: allow removing devices from busses
-rw-r--r--kapi/kapi/devices/bus.hpp11
-rw-r--r--kernel/kapi/devices.cpp4
-rw-r--r--kernel/kapi/devices/bus.cpp66
-rw-r--r--kernel/kapi/devices/bus.tests.cpp61
4 files changed, 140 insertions, 2 deletions
diff --git a/kapi/kapi/devices/bus.hpp b/kapi/kapi/devices/bus.hpp
index 26b46830..13951e46 100644
--- a/kapi/kapi/devices/bus.hpp
+++ b/kapi/kapi/devices/bus.hpp
@@ -37,6 +37,9 @@ namespace kapi::devices
//! @param protocol The bus protocol to use.
bus(kstd::string const & name, bus_protocol & protocol);
+ //! Tear down this bus recursively.
+ ~bus() override;
+
//! Attach a child device to this bus.
//!
//! Whenever a device is attached to a bus, the bus takes sole ownership of the device.
@@ -44,6 +47,12 @@ namespace kapi::devices
//! @param child The child device to attach.
auto add_child(kstd::shared_ptr<device> child) -> void;
+ //! Detach a child device from this bus and tear it down.
+ //!
+ //! @param child The device to detach.
+ //! @return true iff. the child was found an removed, false otherwise.
+ auto remove_child(device & child) -> bool;
+
//! Get the children attached to this bus.
//!
//! @return A vector of all children attached to this bus.
@@ -54,6 +63,8 @@ namespace kapi::devices
auto query_facet(kapi::capabilities::facet_id facet) -> void * override;
private:
+ auto do_remove_child(device & child) -> void;
+
bus_protocol * m_protocol{};
kstd::vector<kstd::shared_ptr<device>> m_devices{};
};
diff --git a/kernel/kapi/devices.cpp b/kernel/kapi/devices.cpp
index 2505779a..f289bbf2 100644
--- a/kernel/kapi/devices.cpp
+++ b/kernel/kapi/devices.cpp
@@ -48,10 +48,10 @@ namespace kapi::test_support::devices
{
auto deinit() -> void
{
+ kapi::devices::root_bus.reset();
+
deinit_driver_registry();
deinit_facet_registry();
deinit_device_registry();
-
- kapi::devices::root_bus.reset();
}
} // namespace kapi::test_support::devices \ No newline at end of file
diff --git a/kernel/kapi/devices/bus.cpp b/kernel/kapi/devices/bus.cpp
index dad90a15..d917eb96 100644
--- a/kernel/kapi/devices/bus.cpp
+++ b/kernel/kapi/devices/bus.cpp
@@ -9,6 +9,7 @@
#include <kstd/string.hpp>
#include <kstd/vector.hpp>
+#include <algorithm>
#include <span>
#include <utility>
@@ -23,6 +24,15 @@ namespace kapi::devices
, m_protocol{&protocol}
{}
+ bus::~bus()
+ {
+ auto children = m_devices;
+ for (auto const & child : children)
+ {
+ remove_child(*child);
+ }
+ }
+
auto bus::add_child(kstd::shared_ptr<device> child) -> void
{
child->set_parent(kstd::static_pointer_cast<bus>(shared_from_this()));
@@ -32,12 +42,35 @@ namespace kapi::devices
kapi::system::panic("[OS:DEV] Failed to register child device {}", child->name());
}
+ // TODO: lock bus
+
auto & attached = m_devices.emplace_back(std::move(child));
attached->set_state(state::present);
+ // TODO: unlock bus
+
driver_registry::get().device_attached(attached);
}
+ auto bus::remove_child(device & device) -> bool // NOLINT(misc-no-recursion)
+ {
+ // TODO: lock bus
+
+ auto found = std::ranges::find_if(m_devices, [&](auto const & d) { return d.get() == &device; });
+ if (!found)
+ {
+ return false;
+ }
+
+ auto lifeline = *found;
+ m_devices.erase(found);
+
+ // TODO: unlock bus
+
+ do_remove_child(*lifeline);
+ return true;
+ }
+
[[nodiscard]] auto bus::children() const -> std::span<kstd::shared_ptr<device> const>
{
return {m_devices.data(), m_devices.size()};
@@ -57,4 +90,37 @@ namespace kapi::devices
return device::query_facet(facet);
}
+ auto bus::do_remove_child(device & device) -> void // NOLINT(misc-no-recursion)
+ {
+ if (device.state() == state::removed)
+ {
+ return;
+ }
+
+ if (auto child_bus = device.facet<bus>())
+ {
+ auto grandchildren = kstd::vector<kstd::shared_ptr<kapi::devices::device>>{};
+ // TODO: lock child bus
+ // NOTE: this could be more efficient one vector::assign is implemented.
+ grandchildren = kstd::vector(child_bus->m_devices.begin(), child_bus->m_devices.end());
+ // TODO: unlock child bus
+
+ for (auto & grandchild : grandchildren)
+ {
+ child_bus->remove_child(*grandchild);
+ }
+ }
+
+ device.set_state(state::removed);
+
+ if (auto bound_driver = device.bound_driver())
+ {
+ bound_driver->unbind(device);
+ }
+
+ facet_registry::get().withdraw_all_for(device);
+ device_registry::get().remove(device);
+ device.set_parent(kstd::weak_ptr<bus>{});
+ }
+
} // namespace kapi::devices \ No newline at end of file
diff --git a/kernel/kapi/devices/bus.tests.cpp b/kernel/kapi/devices/bus.tests.cpp
index 9f95513b..0a33df1a 100644
--- a/kernel/kapi/devices/bus.tests.cpp
+++ b/kernel/kapi/devices/bus.tests.cpp
@@ -92,3 +92,64 @@ SCENARIO("A bus shares ownership of its children, holding a weak link to its par
}
}
}
+
+SCENARIO("Removing a child from a bus tears down the while subtree", "[devices][bus]")
+{
+ GIVEN("A bus with a child bus")
+ {
+ auto root = kstd::make_shared<test_bus>("bus_removal_root");
+ auto middle = kstd::make_shared<test_bus>("bus_removal_middle");
+ auto leaf_destroyed = false;
+ auto leaf = kstd::make_shared<flagging_device>("bus_removal_leaf", leaf_destroyed);
+
+ root->add_child(middle);
+ middle->add_child(leaf);
+
+ REQUIRE(root->children().size() == 1);
+ REQUIRE(middle->children().size() == 1);
+
+ WHEN("the middle bus is removed from the root")
+ {
+ REQUIRE(root->remove_child(*middle));
+
+ THEN("the middle bus's own subtree was torn down")
+ {
+ REQUIRE(middle->children().empty());
+ }
+
+ THEN("the leaf is marked as removed but not destroyed")
+ {
+ REQUIRE(leaf->state() == kapi::devices::state::removed);
+ REQUIRE(leaf->parent() == nullptr);
+ REQUIRE_FALSE(leaf_destroyed);
+ }
+
+ AND_WHEN("dropping the outside leaf reference")
+ {
+ leaf.reset();
+
+ THEN("the leaf is destroyed")
+ {
+ REQUIRE(leaf_destroyed);
+ }
+ }
+
+ THEN("the root no longer considers the middle its child")
+ {
+ REQUIRE(root->children().empty());
+ REQUIRE(middle->state() == kapi::devices::state::removed);
+ }
+ }
+ }
+
+ GIVEN("A bus and a device that was never actually attached to it")
+ {
+ auto bus = kstd::make_shared<test_bus>("bus_removal_unrelated_bus");
+ auto unrelated = kstd::make_shared<test_device>("bus_removal_unrelated_device");
+
+ THEN("removing the child fails")
+ {
+ REQUIRE_FALSE(bus->remove_child(*unrelated));
+ }
+ }
+}