aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-07-27 12:26:23 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-07-27 12:26:23 +0200
commit1cad7524b291fd7a6f77e01bb90e763e6b557620 (patch)
treed1271b3ddd94b32c2a2efbaaa6fde99c0f854afe
parent12002c540c9de2342d829c5b073c65cfb4549b35 (diff)
downloadkernel-1cad7524b291fd7a6f77e01bb90e763e6b557620.tar.xz
kernel-1cad7524b291fd7a6f77e01bb90e763e6b557620.zip
kapi: add device resources
-rw-r--r--kapi/kapi/devices/device.hpp34
-rw-r--r--kapi/kapi/devices/resource.hpp152
-rw-r--r--kernel/kapi/devices/device.cpp38
-rw-r--r--kernel/kapi/devices/device.tests.cpp406
4 files changed, 630 insertions, 0 deletions
diff --git a/kapi/kapi/devices/device.hpp b/kapi/kapi/devices/device.hpp
index 573cc0d0..bb7eb9da 100644
--- a/kapi/kapi/devices/device.hpp
+++ b/kapi/kapi/devices/device.hpp
@@ -5,10 +5,15 @@
#include <kapi/capabilities/facet_id.hpp>
#include <kapi/devices/driver.hpp>
+#include <kapi/devices/resource.hpp>
#include <kapi/tracked_mutex.hpp>
#include <kstd/memory.hpp>
+#include <kstd/result.hpp>
#include <kstd/string.hpp>
+#include <kstd/vector.hpp>
+
+#include <cstddef>
namespace kapi::devices
{
@@ -125,6 +130,34 @@ namespace kapi::devices
//! Set the driver data for this device.
auto set_driver_data(kstd::shared_ptr<void> data) -> void;
+ //! Get all resources assigned to this device.
+ //!
+ //! @return A copy of all resources assigned to this device.
+ [[nodiscard]] auto resources() const -> kstd::vector<resource>;
+
+ //! Get a specific resource assigned to this device.
+ //!
+ //! @param type The type of the resource to request.
+ //! @param index The type-relative index of the resource to request.
+ //! @return The requested resource on success, or an error if the device has not such resource.
+ [[nodiscard]] auto request_resource(resource_type type, std::size_t index = 0) const -> kstd::result<resource>;
+
+ //! Get a specific resource assigned to this device.
+ //!
+ //! @tparam Type The type of the resource to request.
+ //! @param index The type-relative index of the resource to request.
+ //! @return The requested resource on success, or an error if the device has not such resource.
+ template<resource_type Type>
+ [[nodiscard]] auto request_resource(std::size_t index = 0) const
+ {
+ return request_resource(Type, index).transform([](auto resource) { return resource.template get<Type>(); });
+ }
+
+ //! Assign resources to this device.
+ //!
+ //! @param resources The resources to assign to this device.
+ auto set_resources(kstd::vector<resource> resources) -> void;
+
protected:
auto virtual query_facet(kapi::capabilities::facet_id facet) -> void *;
@@ -147,6 +180,7 @@ namespace kapi::devices
devices::state m_state{state::uninitialized};
kstd::weak_ptr<struct driver> m_driver{};
kstd::shared_ptr<void> m_driver_data{};
+ kstd::vector<resource> m_resources{};
};
//! @}
diff --git a/kapi/kapi/devices/resource.hpp b/kapi/kapi/devices/resource.hpp
new file mode 100644
index 00000000..5c01aba5
--- /dev/null
+++ b/kapi/kapi/devices/resource.hpp
@@ -0,0 +1,152 @@
+#ifndef TEACHOS_KAPI_DEVICES_RESOURCE_HPP
+#define TEACHOS_KAPI_DEVICES_RESOURCE_HPP
+
+#include <kapi/memory.hpp>
+
+#include <kstd/units.hpp>
+
+#include <cstddef>
+#include <cstdint>
+
+namespace kapi::devices
+{
+
+ //! The type of a resource.
+ enum struct resource_type
+ {
+ //! An invalid resource.
+ invalid,
+ //! A range of the memory-mapped I/O address space.
+ mmio,
+ //! A range of I/O ports.
+ port,
+ //! An interrupt line.
+ irq,
+ //! A direct memory access channel.
+ dma,
+ };
+
+ struct invalid_resource
+ {
+ constexpr auto static type = resource_type::invalid;
+ };
+
+ //! A range of the memory-mapped I/O address space.
+ struct mmio_range
+ {
+ constexpr auto static type = resource_type::mmio;
+
+ constexpr auto friend operator==(mmio_range const &, mmio_range const &) noexcept -> bool = default;
+
+ //! The physical start address of this range.
+ kapi::memory::physical_address start;
+ //! The size of this range.
+ kstd::units::bytes size;
+ };
+
+ //! A range of I/O ports.
+ struct io_port
+ {
+ constexpr auto static type = resource_type::port;
+
+ constexpr auto friend operator==(io_port const &, io_port const &) noexcept -> bool = default;
+
+ //! The first port in this range.
+ std::uint16_t start;
+ //! The number of ports in this range.
+ std::size_t count;
+ };
+
+ //! An interrupt line
+ struct interrupt_line
+ {
+ constexpr auto static type = resource_type::irq;
+
+ constexpr auto friend operator==(interrupt_line const &, interrupt_line const &) noexcept -> bool = default;
+
+ //! The interrupt number associated with this line.
+ std::size_t number;
+ };
+
+ //! A direct memory access channel.
+ struct dma_channel
+ {
+ constexpr auto static type = resource_type::dma;
+
+ constexpr auto friend operator==(dma_channel const &, dma_channel const &) noexcept -> bool = default;
+
+ //! The channel number.
+ std::size_t number;
+ };
+
+ //! A single hardware resource assigned to a device.
+ struct resource
+ {
+ explicit resource(mmio_range range)
+ : type{range.type}
+ , m_value{.range = range}
+ {}
+
+ explicit resource(io_port port)
+ : type{port.type}
+ , m_value{.port = port}
+ {}
+
+ explicit resource(interrupt_line irq)
+ : type{irq.type}
+ , m_value{.irq = irq}
+ {}
+
+ explicit resource(dma_channel channel)
+ : type{channel.type}
+ , m_value{.channel = channel}
+ {}
+
+ //! The exact type of this resource.
+ resource_type type{resource_type::invalid};
+
+ //! Get the resource value.
+ template<resource_type Type>
+ [[nodiscard]] constexpr auto get() const
+ {
+ if constexpr (Type == resource_type::mmio)
+ {
+ return m_value.range;
+ }
+ else if constexpr (Type == resource_type::port)
+ {
+ return m_value.port;
+ }
+ else if constexpr (Type == resource_type::irq)
+ {
+ return m_value.irq;
+ }
+ else if constexpr (Type == resource_type::dma)
+ {
+ return m_value.channel;
+ }
+ else
+ {
+ return m_value.invalid;
+ }
+ }
+
+ [[nodiscard]] constexpr auto unsafe_get() const
+ {
+ return m_value;
+ }
+
+ private:
+ union
+ {
+ invalid_resource invalid{};
+ mmio_range range;
+ io_port port;
+ interrupt_line irq;
+ dma_channel channel;
+ } m_value;
+ };
+
+} // namespace kapi::devices
+
+#endif
diff --git a/kernel/kapi/devices/device.cpp b/kernel/kapi/devices/device.cpp
index 9c2d8d40..a05a1653 100644
--- a/kernel/kapi/devices/device.cpp
+++ b/kernel/kapi/devices/device.cpp
@@ -2,10 +2,18 @@
#include <kapi/devices.hpp>
#include <kapi/devices/bus.hpp>
+#include <kapi/devices/resource.hpp>
#include <kstd/memory.hpp>
#include <kstd/mutex.hpp>
+#include <kstd/result.hpp>
#include <kstd/string.hpp>
+#include <kstd/system_error.hpp>
+#include <kstd/vector.hpp>
+
+#include <algorithm>
+#include <cstddef>
+#include <ranges>
namespace kapi::devices
{
@@ -75,6 +83,36 @@ namespace kapi::devices
m_driver_data = data;
}
+ auto device::resources() const -> kstd::vector<resource>
+ {
+ auto guard = kstd::lock_guard{m_lock};
+ return m_resources;
+ }
+
+ auto device::request_resource(resource_type type, std::size_t index) const -> kstd::result<resource>
+ {
+ auto guard = kstd::lock_guard{m_lock};
+
+ auto numbered_resources = std::views::enumerate(m_resources);
+ auto found = std::ranges::find_if(numbered_resources, [&](auto entry) {
+ auto const & [number, resource] = entry;
+ return resource.type == type && static_cast<std::size_t>(number) == index;
+ });
+
+ if (found != numbered_resources.end())
+ {
+ return kstd::success(*found.base());
+ }
+
+ return kstd::failure(make_error_code(kstd::errc::invalid_argument));
+ }
+
+ auto device::set_resources(kstd::vector<resource> resources) -> void
+ {
+ auto guard = kstd::lock_guard{m_lock};
+ m_resources = resources;
+ }
+
auto device::query_facet(kapi::capabilities::facet_id) -> void *
{
return nullptr;
diff --git a/kernel/kapi/devices/device.tests.cpp b/kernel/kapi/devices/device.tests.cpp
new file mode 100644
index 00000000..8104f6eb
--- /dev/null
+++ b/kernel/kapi/devices/device.tests.cpp
@@ -0,0 +1,406 @@
+#include <kapi/devices/device.hpp>
+
+#include <kapi/devices/resource.hpp>
+
+#include <kstd/memory.hpp>
+#include <kstd/system_error.hpp>
+#include <kstd/units.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("Devices allow access to their resources", "[kapi][devices]")
+{
+ GIVEN("A device without any resources")
+ {
+ auto device = kstd::make_shared<kapi::devices::device>("test_device");
+
+ THEN("its resources vector is empty")
+ {
+ REQUIRE(device->resources().empty());
+ }
+
+ WHEN("trying to get the first MMIO resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::mmio>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ WHEN("trying to get the first MMIO resource via the unsafe accessor")
+ {
+ auto result = device->request_resource(kapi::devices::resource_type::mmio, 0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ WHEN("trying to get the first I/O port resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::port>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ WHEN("trying to get the first I/O port resource via the unsafe accessor")
+ {
+ auto result = device->request_resource(kapi::devices::resource_type::port, 0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ WHEN("trying to get the first IRQ resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::irq>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ WHEN("trying to get the first IRQ resource via the unsafe accessor")
+ {
+ auto result = device->request_resource(kapi::devices::resource_type::irq, 0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ WHEN("trying to get the first DMA resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::dma>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ WHEN("trying to get the first DMA resource via the unsafe accessor")
+ {
+ auto result = device->request_resource(kapi::devices::resource_type::dma, 0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ WHEN("adding an MMIO resource")
+ {
+ auto range = kapi::devices::mmio_range{{}, kstd::units::bytes{100}};
+ device->set_resources({kapi::devices::resource{range}});
+
+ AND_WHEN("trying to get the first MMIO resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::mmio>();
+
+ THEN("the result is a success")
+ {
+ REQUIRE(result);
+ }
+
+ THEN("the result is the expected MMIO range")
+ {
+ REQUIRE(result == range);
+ }
+ }
+
+ AND_WHEN("trying to get the first I/O port resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::port>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first IRQ resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::irq>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first DMA resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::dma>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+ }
+
+ WHEN("adding an I/O port resource")
+ {
+ auto port = kapi::devices::io_port{0x40, 1};
+ device->set_resources({kapi::devices::resource{port}});
+
+ AND_WHEN("trying to get the first MMIO resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::mmio>();
+
+ THEN("the result is en error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first I/O port resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::port>();
+
+ THEN("the result is a success")
+ {
+ REQUIRE(result);
+ }
+
+ THEN("the result is the expected MMIO range")
+ {
+ REQUIRE(result == port);
+ }
+ }
+
+ AND_WHEN("trying to get the first IRQ resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::irq>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first DMA resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::dma>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+ }
+
+ WHEN("adding an interrupt line resource")
+ {
+ auto line = kapi::devices::interrupt_line{128};
+ device->set_resources({kapi::devices::resource{line}});
+
+ AND_WHEN("trying to get the first MMIO resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::mmio>();
+
+ THEN("the result is en error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first I/O port resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::port>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first IRQ resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::irq>(0);
+
+ THEN("the result is a success")
+ {
+ REQUIRE(result);
+ }
+
+ THEN("the result is the expected interrupt line")
+ {
+ REQUIRE(result == line);
+ }
+ }
+
+ AND_WHEN("trying to get the first DMA resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::dma>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+ }
+
+ WHEN("adding a DMA channel resource")
+ {
+ auto channel = kapi::devices::dma_channel{1024};
+ device->set_resources({kapi::devices::resource{channel}});
+
+ AND_WHEN("trying to get the first MMIO resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::mmio>();
+
+ THEN("the result is en error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first I/O port resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::port>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first IRQ resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::irq>(0);
+
+ THEN("the result is an error")
+ {
+ REQUIRE_FALSE(result);
+ }
+
+ THEN("the error is 'invalid argument'")
+ {
+ REQUIRE(result.error() == kstd::errc::invalid_argument);
+ }
+ }
+
+ AND_WHEN("trying to get the first DMA resource via the type-safe accessor")
+ {
+ auto result = device->request_resource<kapi::devices::resource_type::dma>(0);
+
+ THEN("the result is a success")
+ {
+ REQUIRE(result);
+ }
+
+ THEN("the result it the expected DMA channel")
+ {
+ REQUIRE(result == channel);
+ }
+ }
+ }
+ }
+}