aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-04-02 17:46:49 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-04-02 17:47:03 +0200
commitb2c3f25b453f1b71552fd93de8d11efbda36fcd1 (patch)
treeb9f097cbcf14e264a6818103a73c48a31830ffe4
parent1f010078983e6ab4e74ee3d1efcfb3284620b002 (diff)
parentc5afb5c1ce1c084c840dbb58d73af6fe2b235ec7 (diff)
downloadteachos-b2c3f25b453f1b71552fd93de8d11efbda36fcd1.tar.xz
teachos-b2c3f25b453f1b71552fd93de8d11efbda36fcd1.zip
Merge branch 'fmorgner/develop-BA-FS26/pit-device' into develop-BA-FS26
This changeset introduces a central device management system. The system comprises the following components: - kapi::devices::bus (type) A bus can be both a real (physically present inside the machine) or virtual (purely conceptual) bus. Busses form a hierarchy rooted in the virtual root_bus (accessible via `kapi::devices::get_root_bus`). Busses are derived from `kapi::devices::device`, and own devices. This means that a bus can be attached to another bus. This facilitates a uniform structure for device ownership. Each device needs to be attached to exactly one bus. Virtual devices, e.g. RAM disks, should be attached to the root root bus (via the `add_child` member function). Once a device, or bus, has been attached to a bus, it will be initialized (by means of a call to the `init` member function). Busses are responsible to initialize their children. If a bus has already been initialized, any newly attached children will automatically be initialized. During initialization, the `probe` member function of the bus under initialization weill be invoked. This allows a bus implementation to determine if the hardware, if any, is ready to be initialized and to also perform any required initialization steps. IMPORTANT: busses take ownership of their children in the form of a `unique_ptr`. - kapi::devices (namespace) The functions `allocate_major_number`, `register_device`, `unregister_device`, and `find_device` form the device manager. The device manager is responsible for keeping a record of active devices, allowing O(log n) lookup of devices by major and minor, and O(n) lookup by name. Additionally, the device manager is responsible for handing out major numbers (via `allocate_major_number`) and keeping track of active devices. When a child is attached to a bus, it will also automatically be registered with the device manager. Lookup of devices via the device manager can be achieved by means of either overload of `find_device`. Devices should never ask the manager directly for a major number. Instead, a controller should be used, which requests a major number per device "class" (as in kind, not language class). For example, a RAM disk controller may request a single major number for all RAM disks it is going to create. When the controller creates a new device of a given "class", e.g. a RAM disk, it should then supply that major number to the new device instance. IMPORTANT: the device manager does not own the active devices. Rather it holds a `flat_map` indexed by `pair{major, minor}` and resolving to `kstd::observer_ptr<device>`. - kstd::observer_ptr (type) An observer pointer is a simple vocabulary type to express the concept of having no ownership of the pointed-to object. In essence it is equivalent to a standard C-style pointer, but expresses semantics by virtue of being a separate type.
-rw-r--r--.clang-tidy2
-rw-r--r--arch/x86_64/CMakeLists.txt7
-rw-r--r--arch/x86_64/include/arch/bus/isa.hpp18
-rw-r--r--arch/x86_64/include/arch/device_io/port_io.hpp5
-rw-r--r--arch/x86_64/include/arch/devices/legacy_pit.hpp29
-rw-r--r--arch/x86_64/kapi/devices.cpp35
-rw-r--r--arch/x86_64/src/bus/isa.cpp14
-rw-r--r--arch/x86_64/src/cpu/initialization.cpp2
-rw-r--r--arch/x86_64/src/devices/legacy_pit.cpp60
-rw-r--r--kapi/include/kapi/devices.hpp36
-rw-r--r--kapi/include/kapi/devices/bus.hpp95
-rw-r--r--kapi/include/kapi/devices/manager.hpp53
-rw-r--r--kernel/CMakeLists.txt2
-rw-r--r--kernel/include/kernel/devices/root_bus.hpp16
-rw-r--r--kernel/kapi/devices.cpp87
-rw-r--r--kernel/src/devices/root_bus.cpp12
-rw-r--r--kernel/src/main.cpp11
-rw-r--r--libs/kstd/CMakeLists.txt1
-rw-r--r--libs/kstd/include/kstd/bits/flat_map.hpp2
-rw-r--r--libs/kstd/include/kstd/bits/observer_ptr.hpp163
-rw-r--r--libs/kstd/include/kstd/bits/unique_ptr.hpp25
-rw-r--r--libs/kstd/include/kstd/memory9
-rw-r--r--libs/kstd/include/kstd/string13
-rw-r--r--libs/kstd/tests/include/kstd/tests/test_types.hpp71
-rw-r--r--libs/kstd/tests/src/observer_ptr.cpp359
-rw-r--r--libs/kstd/tests/src/vector.cpp119
26 files changed, 1165 insertions, 81 deletions
diff --git a/.clang-tidy b/.clang-tidy
index 8fa3943..61ae9c9 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -60,7 +60,7 @@ CheckOptions:
modernize-use-std-print.ReplacementPrintlnFunction: 'kstd::println'
modernize-use-std-print.PrintHeader: 'kstd/print'
modernize-use-trailing-return-type.TransformLambdas: none
- readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;5;6;7;10'
+ readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;5;6;7;10;255'
readability-magic-numbers.IgnorePowersOf2IntegerValues: true
readability-magic-numbers.IgnoreBitFieldsWidths: true
readability-magic-numbers.IgnoreTypeAliases: true
diff --git a/arch/x86_64/CMakeLists.txt b/arch/x86_64/CMakeLists.txt
index 4427e4c..83cae0b 100644
--- a/arch/x86_64/CMakeLists.txt
+++ b/arch/x86_64/CMakeLists.txt
@@ -15,6 +15,7 @@ target_sources("x86_64" PRIVATE
"kapi/boot_modules.cpp"
"kapi/cio.cpp"
"kapi/cpu.cpp"
+ "kapi/devices.cpp"
"kapi/interrupts.cpp"
"kapi/memory.cpp"
"kapi/system.cpp"
@@ -24,6 +25,9 @@ target_sources("x86_64" PRIVATE
"src/cpu/interrupts.cpp"
"src/cpu/interrupt_stubs.S"
+ # Bus Initialization
+ "src/bus/isa.cpp"
+
# Low-level bootstrap
"src/boot/boot32.S"
"src/boot/entry64.s"
@@ -33,6 +37,9 @@ target_sources("x86_64" PRIVATE
# Debug interfaces
"src/debug/qemu_output.cpp"
+ # Devices
+ "src/devices/legacy_pit.cpp"
+
# Memory management
"src/memory/kernel_mapper.cpp"
"src/memory/higher_half_mapper.cpp"
diff --git a/arch/x86_64/include/arch/bus/isa.hpp b/arch/x86_64/include/arch/bus/isa.hpp
new file mode 100644
index 0000000..5deed25
--- /dev/null
+++ b/arch/x86_64/include/arch/bus/isa.hpp
@@ -0,0 +1,18 @@
+#ifndef TEACHOS_X86_64_BUS_ISA_HPP
+#define TEACHOS_X86_64_BUS_ISA_HPP
+
+#include "kapi/devices/bus.hpp"
+
+#include <cstddef>
+
+namespace arch::bus
+{
+
+ struct isa final : public kapi::devices::bus
+ {
+ isa(std::size_t major);
+ };
+
+} // namespace arch::bus
+
+#endif // TEACHOS_X86_64_BUS_ISA_HPP
diff --git a/arch/x86_64/include/arch/device_io/port_io.hpp b/arch/x86_64/include/arch/device_io/port_io.hpp
index 70773dd..4c8d66a 100644
--- a/arch/x86_64/include/arch/device_io/port_io.hpp
+++ b/arch/x86_64/include/arch/device_io/port_io.hpp
@@ -102,6 +102,11 @@ namespace arch::io
: std::string_view{"eax"};
};
+ auto inline wait() -> void
+ {
+ port<0x80, std::uint8_t, port_write>::write<std::uint8_t>(0);
+ }
+
} // namespace arch::io
#endif \ No newline at end of file
diff --git a/arch/x86_64/include/arch/devices/legacy_pit.hpp b/arch/x86_64/include/arch/devices/legacy_pit.hpp
new file mode 100644
index 0000000..de742ae
--- /dev/null
+++ b/arch/x86_64/include/arch/devices/legacy_pit.hpp
@@ -0,0 +1,29 @@
+#ifndef TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP
+#define TEACHOS_ARCH_X86_64_DEVICES_LEGACY_PIT_HPP
+
+#include "kapi/devices/device.hpp"
+#include "kapi/interrupts.hpp"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ struct legacy_pit : kapi::devices::device, kapi::interrupts::handler
+ {
+ legacy_pit(std::size_t major, std::uint32_t frequency_in_hz);
+
+ auto init() -> bool override;
+
+ auto handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status override;
+
+ private:
+ std::uint32_t m_irq_number{};
+ std::uint32_t m_frequency_in_hz{};
+ std::uint64_t m_ticks{};
+ };
+
+} // namespace arch::devices
+
+#endif \ No newline at end of file
diff --git a/arch/x86_64/kapi/devices.cpp b/arch/x86_64/kapi/devices.cpp
new file mode 100644
index 0000000..b15503d
--- /dev/null
+++ b/arch/x86_64/kapi/devices.cpp
@@ -0,0 +1,35 @@
+#include "kapi/devices.hpp"
+
+#include "arch/bus/isa.hpp"
+#include "arch/devices/legacy_pit.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <cstdint>
+#include <utility>
+
+namespace kapi::devices
+{
+
+ namespace
+ {
+ constexpr auto pit_frequency_in_hz = std::uint32_t{100u};
+ }
+
+ auto init_platform_devices() -> void
+ {
+ kstd::println("[x86_64:devices] Initializing ISA bus...");
+
+ auto isa_major_number = kapi::devices::allocate_major_number();
+ auto isa_bus = kstd::make_unique<arch::bus::isa>(isa_major_number);
+
+ auto pit_major_number = kapi::devices::allocate_major_number();
+ auto pit = kstd::make_unique<arch::devices::legacy_pit>(pit_major_number, pit_frequency_in_hz);
+ isa_bus->add_child(std::move(pit));
+
+ auto & root_bus = get_root_bus();
+ root_bus.add_child(std::move(isa_bus));
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/arch/x86_64/src/bus/isa.cpp b/arch/x86_64/src/bus/isa.cpp
new file mode 100644
index 0000000..ff4ad71
--- /dev/null
+++ b/arch/x86_64/src/bus/isa.cpp
@@ -0,0 +1,14 @@
+#include "arch/bus/isa.hpp"
+
+#include "kapi/devices.hpp"
+
+#include <cstddef>
+
+namespace arch::bus
+{
+
+ isa::isa(std::size_t major)
+ : kapi::devices::bus{major, 0, "isa"}
+ {}
+
+} // namespace arch::bus \ No newline at end of file
diff --git a/arch/x86_64/src/cpu/initialization.cpp b/arch/x86_64/src/cpu/initialization.cpp
index 878fa07..b808c76 100644
--- a/arch/x86_64/src/cpu/initialization.cpp
+++ b/arch/x86_64/src/cpu/initialization.cpp
@@ -139,7 +139,7 @@ namespace arch::cpu
constexpr auto pic_cascade_address = std::uint8_t{0x04};
constexpr auto pic_cascade_slave_identity = std::uint8_t{0x02};
constexpr auto pic_use_8086_mode = std::uint8_t{0x01};
- constexpr auto pic_master_mask = std::uint8_t{0x01};
+ constexpr auto pic_master_mask = std::uint8_t{0x00};
constexpr auto pic_slave_mask = std::uint8_t{0x00};
pic_master_control_port::write(pic_init_command);
diff --git a/arch/x86_64/src/devices/legacy_pit.cpp b/arch/x86_64/src/devices/legacy_pit.cpp
new file mode 100644
index 0000000..a8df3c3
--- /dev/null
+++ b/arch/x86_64/src/devices/legacy_pit.cpp
@@ -0,0 +1,60 @@
+#include "arch/devices/legacy_pit.hpp"
+
+#include "kapi/devices.hpp"
+#include "kapi/devices/device.hpp"
+#include "kapi/interrupts.hpp"
+
+#include "arch/device_io/port_io.hpp"
+
+#include <cstddef>
+#include <cstdint>
+
+namespace arch::devices
+{
+
+ namespace
+ {
+ using command_port = io::port<0x43, std::uint8_t, io::port_write>;
+ using channel_0_port = io::port<0x40, std::uint8_t, io::port_write>;
+ using channel_1_port = io::port<0x41, std::uint8_t, io::port_write>;
+ using channel_2_port = io::port<0x42, std::uint8_t, io::port_write>;
+
+ constexpr auto base_frequency = 1'193'182u;
+ constexpr auto square_wave_mode = 0x36;
+ } // namespace
+
+ legacy_pit::legacy_pit(std::size_t major, std::uint32_t frequency_in_hz)
+ : kapi::devices::device{major, 0, "legacy_pit"}
+ , m_irq_number{0}
+ , m_frequency_in_hz{frequency_in_hz}
+ {}
+
+ auto legacy_pit::init() -> bool
+ {
+ auto divisor = static_cast<std::uint16_t>(base_frequency / m_frequency_in_hz);
+
+ kapi::interrupts::register_handler(m_irq_number, *this);
+
+ command_port::write<std::uint8_t>(square_wave_mode);
+ io::wait();
+ channel_0_port::write<std::uint8_t>(divisor & 0xff);
+ io::wait();
+ channel_0_port::write<std::uint8_t>(divisor >> 8 & 0xff);
+ io::wait();
+
+ return true;
+ }
+
+ auto legacy_pit::handle_interrupt(std::uint32_t irq_number) -> kapi::interrupts::status
+ {
+ if (irq_number != m_irq_number)
+ {
+ return kapi::interrupts::status::unhandled;
+ }
+
+ ++m_ticks;
+
+ return kapi::interrupts::status::handled;
+ }
+
+} // namespace arch::devices \ No newline at end of file
diff --git a/kapi/include/kapi/devices.hpp b/kapi/include/kapi/devices.hpp
new file mode 100644
index 0000000..5c01b2f
--- /dev/null
+++ b/kapi/include/kapi/devices.hpp
@@ -0,0 +1,36 @@
+#ifndef TEACHOS_KAPI_DEVICES_HPP
+#define TEACHOS_KAPI_DEVICES_HPP
+
+#include "kapi/devices/bus.hpp" // IWYU pragma: export
+#include "kapi/devices/device.hpp" // IWYU pragma: export
+#include "kapi/devices/manager.hpp" // IWYU pragma: export
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kernel-defined
+ //! @{
+
+ //! Initialize the kernel's device management subsystem.
+ auto init() -> void;
+
+ //! Get the virtual system root bus.
+ //!
+ //! @warning This function will panic if the root bus has not been initialized.
+ //!
+ //! @return a reference to the root bus.
+ auto get_root_bus() -> bus &;
+
+ //! @}
+
+ //! @addtogroup platform-defined
+ //! @{
+
+ //! Initialize the platform's device tree.
+ auto init_platform_devices() -> void;
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/devices/bus.hpp b/kapi/include/kapi/devices/bus.hpp
new file mode 100644
index 0000000..ee774b7
--- /dev/null
+++ b/kapi/include/kapi/devices/bus.hpp
@@ -0,0 +1,95 @@
+#ifndef TEACHOS_KAPI_DEVICES_BUS_HPP
+#define TEACHOS_KAPI_DEVICES_BUS_HPP
+
+#include "kapi/devices/device.hpp"
+#include "kapi/devices/manager.hpp"
+#include "kapi/system.hpp"
+
+#include <kstd/memory>
+#include <kstd/print>
+#include <kstd/string>
+#include <kstd/vector>
+
+#include <algorithm>
+#include <atomic>
+#include <cstddef>
+#include <utility>
+
+namespace kapi::devices
+{
+ //! A bus device that represents a logical/physical tree of devices and busses.
+ struct bus : device
+ {
+ //! Construct a bus with the given major number, minor number, and name.
+ //!
+ //! @param major The major number of the bus.
+ //! @param minor The minor number of the bus.
+ //! @param name The name of the bus.
+ bus(std::size_t major, std::size_t minor, kstd::string const & name)
+ : device(major, minor, name)
+ {}
+
+ //! Initialize the bus and all of its children.
+ //!
+ //! @return true iff. the bus and all of its children are healthy, false otherwise.
+ auto init() -> bool final
+ {
+ if (m_initialized.test_and_set())
+ {
+ return true;
+ }
+
+ if (!probe())
+ {
+ return false;
+ }
+
+ return std::ranges::fold_left(m_devices, true, [&](bool acc, auto & child) -> bool {
+ kstd::println("[kAPI:BUS] Initializing child device {}@{}", child->name(), name());
+ return child->init() && acc;
+ });
+ }
+
+ //! Attach a child device to this bus.
+ //!
+ //! Whenever a device is attached to a bus, the bus takes sole ownership of the device.
+ //!
+ //! @param child The child device to attach.
+ auto add_child(kstd::unique_ptr<device> child) -> void
+ {
+ auto observer = m_observers.emplace_back(child.get());
+ m_devices.push_back(std::move(child));
+ kapi::devices::register_device(*observer);
+
+ if (m_initialized.test())
+ {
+ kstd::println("[kAPI:BUS] Initializing child device {}@{}", observer->name(), name());
+ if (!observer->init())
+ {
+ kapi::system::panic("[kAPI:BUS] Failed to initialize child device");
+ }
+ }
+ }
+
+ [[nodiscard]] auto children() const -> kstd::vector<kstd::observer_ptr<device>> const &
+ {
+ return m_observers;
+ }
+
+ protected:
+ //! Probe the bus hardware state.
+ //!
+ //! @return true iff. the bus hardware is healthy, false otherwise.
+ auto virtual probe() -> bool
+ {
+ return true;
+ }
+
+ private:
+ kstd::vector<kstd::unique_ptr<device>> m_devices;
+ kstd::vector<kstd::observer_ptr<device>> m_observers;
+ std::atomic_flag m_initialized{};
+ };
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kapi/include/kapi/devices/manager.hpp b/kapi/include/kapi/devices/manager.hpp
new file mode 100644
index 0000000..7817fbc
--- /dev/null
+++ b/kapi/include/kapi/devices/manager.hpp
@@ -0,0 +1,53 @@
+#ifndef TEACHOS_KAPI_DEVICES_MANAGER_HPP
+#define TEACHOS_KAPI_DEVICES_MANAGER_HPP
+
+// IWYU pragma: private, include "kapi/devices.hpp"
+
+#include "kapi/devices/device.hpp"
+
+#include <kstd/memory>
+
+#include <cstddef>
+#include <string_view>
+
+namespace kapi::devices
+{
+
+ //! @addtogroup kernel-defined
+ //! @{
+
+ //! Ask the kernel to allocate a new major number.
+ //!
+ //! @return a new, unused major number.
+ auto allocate_major_number() -> std::size_t;
+
+ //! Register a new device with the kernel's device manager.
+ //!
+ //! @param device The device to register.
+ //! @return true if the device was registered successfully, false otherwise.
+ auto register_device(device & device) -> bool;
+
+ //! Unregister a device from the kernel's device manager.
+ //!
+ //! @param device The device to unregister.
+ //! @return true if the device was unregistered successfully, false otherwise.
+ auto unregister_device(device & device) -> bool;
+
+ //! Find a device by its major and minor numbers.
+ //!
+ //! @param major the major number of the device.
+ //! @param minor the minor number of the device.
+ //! @return a pointer to the device iff. the device was found, nullptr otherwise.
+ auto find_device(std::size_t major, std::size_t minor) -> kstd::observer_ptr<device>;
+
+ //! Find a device by its name.
+ //!
+ //! @param name the name of the device.
+ //! @return a pointer to the device iff. the device was found, nullptr otherwise.
+ auto find_device(std::string_view name) -> kstd::observer_ptr<device>;
+
+ //! @}
+
+} // namespace kapi::devices
+
+#endif \ No newline at end of file
diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt
index f283588..9868eb9 100644
--- a/kernel/CMakeLists.txt
+++ b/kernel/CMakeLists.txt
@@ -3,6 +3,7 @@ add_library("kernel_objs" OBJECT
"kapi/boot_modules.cpp"
"kapi/cio.cpp"
"kapi/cpu.cpp"
+ "kapi/devices.cpp"
"kapi/interrupts.cpp"
"kapi/memory.cpp"
"kapi/system.cpp"
@@ -17,6 +18,7 @@ add_library("kernel_objs" OBJECT
"src/memory.cpp"
"src/devices/block_device.cpp"
"src/devices/block_device_utils.cpp"
+ "src/devices/root_bus.cpp"
"src/devices/storage/controller.cpp"
"src/devices/storage/management.cpp"
"src/devices/storage/ram_disk/controller.cpp"
diff --git a/kernel/include/kernel/devices/root_bus.hpp b/kernel/include/kernel/devices/root_bus.hpp
new file mode 100644
index 0000000..660b715
--- /dev/null
+++ b/kernel/include/kernel/devices/root_bus.hpp
@@ -0,0 +1,16 @@
+#ifndef TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP
+#define TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP
+
+#include "kapi/devices/bus.hpp"
+
+namespace kernel::devices
+{
+
+ struct root_bus final : kapi::devices::bus
+ {
+ root_bus();
+ };
+
+} // namespace kernel::devices
+
+#endif \ No newline at end of file
diff --git a/kernel/kapi/devices.cpp b/kernel/kapi/devices.cpp
new file mode 100644
index 0000000..031f2c9
--- /dev/null
+++ b/kernel/kapi/devices.cpp
@@ -0,0 +1,87 @@
+#include "kapi/devices.hpp"
+
+#include "kapi/system.hpp"
+
+#include "kernel/devices/root_bus.hpp"
+
+#include <kstd/flat_map>
+#include <kstd/memory>
+#include <kstd/print>
+
+#include <atomic>
+#include <cstddef>
+#include <optional>
+#include <string_view>
+#include <utility>
+
+namespace kapi::devices
+{
+
+ namespace
+ {
+ auto constinit next_major_number = std::atomic_size_t{1};
+ auto constinit root_bus = std::optional<kernel::devices::root_bus>{};
+ auto constinit device_tree = kstd::flat_map<std::pair<std::size_t, std::size_t>, kstd::observer_ptr<device>>{};
+ } // namespace
+
+ auto init() -> void
+ {
+ auto static is_initialized = std::atomic_flag{};
+ if (is_initialized.test_and_set())
+ {
+ return;
+ }
+
+ auto & bus = root_bus.emplace();
+ register_device(bus);
+ bus.init();
+ }
+
+ auto get_root_bus() -> bus &
+ {
+ if (!root_bus.has_value())
+ {
+ kapi::system::panic("[OS:DEV] Root bus not initialized!");
+ }
+ return *root_bus;
+ }
+
+ auto allocate_major_number() -> std::size_t
+ {
+ return next_major_number++;
+ }
+
+ auto register_device(device & device) -> bool
+ {
+ kstd::println("[OS:DEV] Registering device {}@{}:{}", device.name(), device.major(), device.minor());
+ return device_tree.emplace(std::pair{device.major(), device.minor()}, &device).second;
+ }
+
+ auto unregister_device(device &) -> bool
+ {
+ kstd::println("[OS:DEV] TODO: implement device deregistration");
+ return false;
+ }
+
+ auto find_device(std::size_t major, std::size_t minor) -> kstd::observer_ptr<device>
+ {
+ if (device_tree.contains(std::pair{major, minor}))
+ {
+ return device_tree.at(std::pair{major, minor});
+ }
+ return nullptr;
+ }
+
+ auto find_device(std::string_view name) -> kstd::observer_ptr<device>
+ {
+ for (auto const & [key, value] : device_tree)
+ {
+ if (value->name() == name)
+ {
+ return value;
+ }
+ }
+ return nullptr;
+ }
+
+} // namespace kapi::devices \ No newline at end of file
diff --git a/kernel/src/devices/root_bus.cpp b/kernel/src/devices/root_bus.cpp
new file mode 100644
index 0000000..43a35bf
--- /dev/null
+++ b/kernel/src/devices/root_bus.cpp
@@ -0,0 +1,12 @@
+#include "kernel/devices/root_bus.hpp"
+
+#include "kapi/devices.hpp"
+
+namespace kernel::devices
+{
+
+ root_bus::root_bus()
+ : kapi::devices::bus{0, 0, "system"}
+ {}
+
+} // namespace kernel::devices \ No newline at end of file
diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp
index 37b4c5b..2eaa2d8 100644
--- a/kernel/src/main.cpp
+++ b/kernel/src/main.cpp
@@ -1,6 +1,7 @@
#include "kapi/boot_modules.hpp"
#include "kapi/cio.hpp"
#include "kapi/cpu.hpp"
+#include "kapi/devices.hpp"
#include "kapi/interrupts.hpp"
#include "kapi/memory.hpp"
#include "kapi/system.hpp"
@@ -172,13 +173,21 @@ auto main() -> int
kstd::println("[OS] IO subsystem initialized.");
kapi::cpu::init();
- kapi::interrupts::enable();
kapi::memory::init();
kernel::memory::init_heap(kapi::memory::heap_base);
kstd::println("[OS] Memory subsystem initialized.");
kapi::system::memory_initialized();
+ kapi::devices::init();
+ kstd::println("[OS] System root bus initialized.");
+
+ kapi::devices::init_platform_devices();
+ kstd::println("[OS] Platform devices initialized.");
+
+ kapi::interrupts::enable();
+ kstd::println("[OS] Interrupts enabled.");
+
kapi::boot_modules::init();
kstd::println("[OS] Boot module registry initialized.");
diff --git a/libs/kstd/CMakeLists.txt b/libs/kstd/CMakeLists.txt
index ec0f441..240118e 100644
--- a/libs/kstd/CMakeLists.txt
+++ b/libs/kstd/CMakeLists.txt
@@ -44,6 +44,7 @@ else()
add_executable("kstd_tests"
"tests/src/flat_map.cpp"
"tests/src/vector.cpp"
+ "tests/src/observer_ptr.cpp"
"tests/src/os_panic.cpp"
"tests/src/string.cpp"
)
diff --git a/libs/kstd/include/kstd/bits/flat_map.hpp b/libs/kstd/include/kstd/bits/flat_map.hpp
index 9455549..fe46203 100644
--- a/libs/kstd/include/kstd/bits/flat_map.hpp
+++ b/libs/kstd/include/kstd/bits/flat_map.hpp
@@ -45,7 +45,7 @@ namespace kstd::bits
template<std::size_t Index>
requires(Index >= 0 && Index <= 1)
- constexpr auto get() const noexcept -> decltype(auto)
+ [[nodiscard]] constexpr auto get() const noexcept -> decltype(auto)
{
if constexpr (Index == 0)
{
diff --git a/libs/kstd/include/kstd/bits/observer_ptr.hpp b/libs/kstd/include/kstd/bits/observer_ptr.hpp
new file mode 100644
index 0000000..1c5da15
--- /dev/null
+++ b/libs/kstd/include/kstd/bits/observer_ptr.hpp
@@ -0,0 +1,163 @@
+#ifndef KSTD_OBSERVER_PTR_HPP
+#define KSTD_OBSERVER_PTR_HPP
+
+// IWYU pragma: private, include <kstd/memory>
+
+#include "kstd/os/error.hpp"
+
+#include <compare>
+#include <concepts>
+#include <cstddef>
+#include <type_traits>
+#include <utility>
+
+namespace kstd
+{
+
+ template<typename ElementType>
+ struct observer_ptr
+ {
+ //! The type of the element being pointed to.
+ using element_type = ElementType;
+
+ //! Construct an empty observer pointer.
+ constexpr observer_ptr() noexcept = default;
+
+ //! Construct an empty observer pointer from a null pointer.
+ constexpr observer_ptr(std::nullptr_t) noexcept {}
+
+ //! Construct an observer pointer from a raw pointer.
+ constexpr explicit observer_ptr(element_type * pointer)
+ : m_ptr{pointer}
+ {}
+
+ //! Construct an observer pointer from another observer pointer.
+ template<typename OtherElementType>
+ requires std::convertible_to<OtherElementType *, ElementType *>
+ constexpr observer_ptr(observer_ptr<OtherElementType> other) noexcept
+ : m_ptr{other.get()}
+ {}
+
+ //! Copy construct an observer pointer.
+ constexpr observer_ptr(observer_ptr const & other) noexcept = default;
+
+ //! Move construct an observer pointer.
+ constexpr observer_ptr(observer_ptr && other) noexcept = default;
+
+ //! Copy assign an observer pointer.
+ constexpr auto operator=(observer_ptr const & other) noexcept -> observer_ptr & = default;
+
+ //! Move assign an observer pointer.
+ constexpr auto operator=(observer_ptr && other) noexcept -> observer_ptr & = default;
+
+ //! Stop watching the the watched object.
+ //!
+ //! @return The currently watched object, or nullptr if no object is being watched.
+ [[nodiscard]] constexpr auto release() noexcept -> element_type *
+ {
+ return std::exchange(m_ptr, nullptr);
+ }
+
+ //! Reset the observer pointer.
+ //!
+ //! @param pointer The new object to watch.
+ constexpr auto reset(element_type * pointer = nullptr) noexcept -> void
+ {
+ m_ptr = pointer;
+ }
+
+ //! Swap the observer pointer with another observer pointer.
+ //!
+ //! @param other The other observer pointer to swap with.
+ constexpr auto swap(observer_ptr & other) noexcept -> void
+ {
+ std::swap(m_ptr, other.m_ptr);
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return The currently watched object, or nullptr if no object is being watched.
+ [[nodiscard]] constexpr auto get() const noexcept -> element_type *
+ {
+ return m_ptr;
+ }
+
+ //! Check if the observer pointer is watching an object.
+ //!
+ //! @return True if the observer pointer is watching an object, false otherwise.
+ [[nodiscard]] constexpr explicit operator bool() const noexcept
+ {
+ return m_ptr != nullptr;
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return A reference to the currently watched object.
+ [[nodiscard]] constexpr auto operator*() const -> std::add_lvalue_reference_t<element_type>
+ {
+ throw_on_null();
+ return *m_ptr;
+ }
+
+ //! Get the currently watched object.
+ //!
+ //! @return A pointer to the currently watched object.
+ [[nodiscard]] constexpr auto operator->() const -> element_type *
+ {
+ throw_on_null();
+ return m_ptr;
+ }
+
+ //! Convert the observer pointer to a raw pointer.
+ //!
+ //! @return A pointer to the currently watched object.
+ constexpr explicit operator element_type *() const noexcept
+ {
+ return m_ptr;
+ }
+
+ //! Compare the observer pointer with another observer pointer.
+ //!>
+ //! @param other The other observer pointer to compare with.
+ //! @return The result of the comparison.
+ constexpr auto operator<=>(observer_ptr const & other) const noexcept -> std::strong_ordering = default;
+
+ private:
+ //! Throw an exception if the observer pointer is null.
+ //!
+ //! @throws std::runtime_error if the observer pointer is null.
+ constexpr auto throw_on_null() const -> void
+ {
+ if (m_ptr == nullptr)
+ {
+ os::panic("[kstd:observer_ptr] Dereferencing a null observer pointer");
+ }
+ }
+
+