diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-07-27 12:26:23 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-07-27 12:26:23 +0200 |
| commit | 1cad7524b291fd7a6f77e01bb90e763e6b557620 (patch) | |
| tree | d1271b3ddd94b32c2a2efbaaa6fde99c0f854afe | |
| parent | 12002c540c9de2342d829c5b073c65cfb4549b35 (diff) | |
| download | kernel-1cad7524b291fd7a6f77e01bb90e763e6b557620.tar.xz kernel-1cad7524b291fd7a6f77e01bb90e763e6b557620.zip | |
kapi: add device resources
| -rw-r--r-- | kapi/kapi/devices/device.hpp | 34 | ||||
| -rw-r--r-- | kapi/kapi/devices/resource.hpp | 152 | ||||
| -rw-r--r-- | kernel/kapi/devices/device.cpp | 38 | ||||
| -rw-r--r-- | kernel/kapi/devices/device.tests.cpp | 406 |
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); + } + } + } + } +} |
