aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clangd2
-rw-r--r--arch/x86_64/CMakeLists.txt1
-rw-r--r--arch/x86_64/arch/cpu/flags.cpp16
-rw-r--r--arch/x86_64/arch/cpu/flags.hpp72
-rw-r--r--arch/x86_64/kapi/interrupts.cpp20
-rw-r--r--kapi/kapi/interrupts.hpp21
-rw-r--r--kapi/kapi/interrupts/irq_lock.hpp163
-rw-r--r--kapi/kapi/interrupts/state.hpp26
-rw-r--r--kernel/kapi/interrupts.stress.cpp65
-rw-r--r--kernel/kapi/interrupts.tests.cpp189
-rw-r--r--kernel/kernel/main.cpp2
-rw-r--r--kernel/kernel/test_support/kapi/interrupts.cpp14
-rw-r--r--libs/kstd/kstd/bits/concepts.hpp27
-rw-r--r--libs/kstd/kstd/mutex.hpp9
14 files changed, 602 insertions, 25 deletions
diff --git a/.clangd b/.clangd
index 35158b6b..4a841859 100644
--- a/.clangd
+++ b/.clangd
@@ -4,6 +4,8 @@ CompileFlags:
Diagnostics:
UnusedIncludes: Strict
MissingIncludes: Strict
+ Includes:
+ AnalyzeAngledIncludes: true
Documentation:
CommentFormat: Doxygen
Style:
diff --git a/arch/x86_64/CMakeLists.txt b/arch/x86_64/CMakeLists.txt
index 28199f35..59df016e 100644
--- a/arch/x86_64/CMakeLists.txt
+++ b/arch/x86_64/CMakeLists.txt
@@ -26,6 +26,7 @@ target_sources("x86_64" PRIVATE
target_sources("x86_64" PRIVATE
# CPU Initialization
+ "arch/cpu/flags.cpp"
"arch/cpu/initialization.cpp"
"arch/cpu/interrupts.cpp"
"arch/cpu/interrupts.S"
diff --git a/arch/x86_64/arch/cpu/flags.cpp b/arch/x86_64/arch/cpu/flags.cpp
new file mode 100644
index 00000000..12a2ceee
--- /dev/null
+++ b/arch/x86_64/arch/cpu/flags.cpp
@@ -0,0 +1,16 @@
+#include <arch/cpu/flags.hpp>
+
+namespace arch::cpu
+{
+
+ auto read_flags() noexcept -> flags
+ {
+ auto rflags = flags{};
+ asm volatile("pushfq\n"
+ "popq %0\n"
+ : "=r"(rflags));
+
+ return rflags;
+ }
+
+} // namespace arch::cpu \ No newline at end of file
diff --git a/arch/x86_64/arch/cpu/flags.hpp b/arch/x86_64/arch/cpu/flags.hpp
new file mode 100644
index 00000000..d19750f4
--- /dev/null
+++ b/arch/x86_64/arch/cpu/flags.hpp
@@ -0,0 +1,72 @@
+#ifndef TEACHOS_ARCH_X86_64_CPU_FLAGS_HPP
+#define TEACHOS_ARCH_X86_64_CPU_FLAGS_HPP
+
+#include <kstd/bitfield_enum.hpp>
+
+#include <cstdint>
+#include <type_traits>
+
+namespace arch::cpu
+{
+
+ enum struct flags : std::uint64_t
+ {
+ carry = 1u << 0,
+ parity = 1u << 2,
+ auxiliary_carry = 1u << 4,
+ zero = 1u << 6,
+ sign = 1u << 7,
+ trap = 1u << 8,
+ interrupt = 1u << 9,
+ direction = 1u << 10,
+ overflow = 1u << 11,
+ io_privilege_level_low = 1u << 12,
+ io_privilege_level_high = 1u << 13,
+ nested_task = 1u << 14,
+ resume = 1u << 16,
+ virtual_8086_mode = 1u << 17,
+ alignment_check = 1u << 18,
+ virtual_interrupt = 1u << 19,
+ virtual_interrupt_pending = 1u << 20,
+ cpuid_available = 1u << 21,
+ aes_key_schedule_loaded = 1u << 30,
+ rex32 = 1u << 31,
+ };
+
+} // namespace arch::cpu
+
+template<>
+struct kstd::is_bitfield_enum<arch::cpu::flags> : std::true_type
+{
+};
+
+namespace arch::cpu
+{
+
+ //! Read the current CPU flags.
+ //!
+ //! @return The current set of active CPU flags.
+ auto read_flags() noexcept -> flags;
+
+ //! Read a given set of CPU flags.
+ //!
+ //! @return The state of the selected flags.
+ template<flags Flags>
+ auto read_flags() noexcept -> flags
+ {
+ auto flags = read_flags();
+ return flags & Flags;
+ }
+
+ //! Check if a given set of CPU flags is set.
+ //!
+ //! @return @p true iff. all of the selected flags are set, @p false otherwise.
+ template<flags Flags>
+ auto check_flags() noexcept -> bool
+ {
+ return read_flags() == Flags;
+ }
+
+} // namespace arch::cpu
+
+#endif \ No newline at end of file
diff --git a/arch/x86_64/kapi/interrupts.cpp b/arch/x86_64/kapi/interrupts.cpp
index 85acc0fa..37745122 100644
--- a/arch/x86_64/kapi/interrupts.cpp
+++ b/arch/x86_64/kapi/interrupts.cpp
@@ -1,16 +1,26 @@
#include <kapi/interrupts.hpp>
+#include <arch/cpu/flags.hpp>
+
namespace kapi::interrupts
{
- auto enable() -> void
+ auto enabled() -> bool
{
- asm volatile("sti");
+ return arch::cpu::check_flags<arch::cpu::flags::interrupt>();
}
- auto disable() -> void
+ auto enabled(bool value) -> void
{
- asm volatile("cli");
+ // NOLINTNEXTLINE(bugprone-branch-clone)
+ if (value)
+ {
+ asm volatile("sti");
+ }
+ else
+ {
+ asm volatile("cli");
+ }
}
-} // namespace kapi::interrupts \ No newline at end of file
+} // namespace kapi::interrupts
diff --git a/kapi/kapi/interrupts.hpp b/kapi/kapi/interrupts.hpp
index 9e41b727..f5b992e2 100644
--- a/kapi/kapi/interrupts.hpp
+++ b/kapi/kapi/interrupts.hpp
@@ -1,6 +1,9 @@
#ifndef TEACHOS_KAPI_INTERRUPTS_HPP
#define TEACHOS_KAPI_INTERRUPTS_HPP
+#include <kapi/interrupts/irq_lock.hpp>
+#include <kapi/interrupts/state.hpp>
+
#include <kstd/memory.hpp>
#include <cstdint>
@@ -8,9 +11,6 @@
namespace kapi::interrupts
{
- //! @addtogroup kapi-interrupts
- //! @{
-
//! A status that indicates whether an interrupt was handled by a handler.
enum struct status : bool
{
@@ -36,9 +36,7 @@ namespace kapi::interrupts
virtual auto handle_interrupt(std::uint32_t irq_number, kstd::weak_ptr<void> context) -> status = 0;
};
- //! @}
-
- //! @addtogroup kapi-interrupts-kernel-defined
+ //! @name Kernel-defined API
//! @{
//! Register an interrupt handler for the given IRQ number.
@@ -62,17 +60,6 @@ namespace kapi::interrupts
//! @}
- //! @addtogroup kapi-interrupts-platform-defined
- //! @{
-
- //! Enable external interrupts.
- auto enable() -> void;
-
- //! Disable external interrupts.
- auto disable() -> void;
-
- //! @}
-
} // namespace kapi::interrupts
#endif \ No newline at end of file
diff --git a/kapi/kapi/interrupts/irq_lock.hpp b/kapi/kapi/interrupts/irq_lock.hpp
new file mode 100644
index 00000000..72a9a564
--- /dev/null
+++ b/kapi/kapi/interrupts/irq_lock.hpp
@@ -0,0 +1,163 @@
+#ifndef TEACHOS_KAPI_INTERRUPTS_IRQ_LOCK_HPP
+#define TEACHOS_KAPI_INTERRUPTS_IRQ_LOCK_HPP
+
+// IWYU pragma: private, include <kapi/interrupts.hpp>
+
+#include <kapi/cpu.hpp>
+#include <kapi/interrupts/state.hpp>
+#include <kapi/system.hpp>
+
+#include <kstd/mutex.hpp>
+
+namespace kapi::interrupts
+{
+
+ template<typename = void>
+ struct irq_lock;
+
+ //! @name Kernel-defined API
+ //! @{
+
+ //! A lock to temporarily disable IRQs.
+ template<>
+ struct irq_lock<void>
+ {
+ //! Create a new IRQ lock
+ //!
+ //! This function records the creator CPU of the new lock to ensure instances are not passed across cores.
+ constexpr irq_lock()
+ : m_owner{kapi::cpu::current_id()}
+ {}
+
+ constexpr irq_lock(irq_lock const &) = delete;
+ constexpr irq_lock(irq_lock &&) = delete;
+
+ //! Destroy this IRQ lock.
+ //!
+ //! @warning This functions panics if the CPU executing is not the creator of this lock, or the lock is still
+ //! locked.
+ ~irq_lock() noexcept(false)
+ {
+ assert_owner();
+ if (m_locked)
+ {
+ kapi::system::panic("[OS:INT] IRQ lock destroyed while it is still locked!");
+ }
+ };
+
+ constexpr auto operator=(irq_lock const &) = delete;
+ constexpr auto operator=(irq_lock &&) = delete;
+
+ //! Save the current IRQ state and disable further IRQs.
+ auto lock() -> void
+ {
+ if (!try_lock())
+ {
+ kapi::system::panic("[OS:INT] IRQ lock reacquired while it is already held!");
+ }
+ }
+
+ //! Restore the previously saved IRQ state.
+ auto unlock() -> void
+ {
+ assert_owner();
+
+ if (!m_locked)
+ {
+ kapi::system::panic("[OS:INT] IRQ lock unlocked while it is not being held!");
+ }
+
+ enabled(m_old_irq_state);
+ m_locked = false;
+ }
+
+ //! Save the current IRQ state and disable further IRQs.
+ auto try_lock() -> bool
+ {
+ assert_owner();
+ auto old_state = enabled();
+ enabled(false);
+ if (m_locked)
+ {
+ enabled(old_state);
+ return false;
+ }
+ m_locked = true;
+ m_old_irq_state = old_state;
+ return true;
+ }
+
+ private:
+ auto assert_owner() -> void
+ {
+ if (m_owner != kapi::cpu::current_id())
+ {
+ kapi::system::panic("[OS:INT] Tried to modify IRQ lock on a CPU that does not own it!");
+ }
+ }
+
+ bool m_locked{};
+ bool m_old_irq_state{};
+ kapi::cpu::id m_owner{};
+ };
+
+ //! A lock to temporarily disable IRQs while also acquiring a further lock.
+ template<kstd::basic_lockable BasicLockable>
+ struct irq_lock<BasicLockable> : irq_lock<void>
+ {
+ using lockable_type = BasicLockable;
+
+ explicit irq_lock(BasicLockable & lockable)
+ : m_lockable{lockable}
+ {}
+
+ //! Save the current IRQ state, disable further IRQs, and lock this lock's lockable object.
+ auto lock() -> void
+ {
+ irq_lock<>::lock();
+ m_lockable.lock();
+ }
+
+ //! Unlock this lock's lockable objects and restore the previously saved IRQ state.
+ auto unlock() -> void
+ {
+ m_lockable.unlock();
+ irq_lock<>::unlock();
+ }
+
+ //! Save the current IRQ state, disable further IRQs, and attempt to lock this lock's lockable object.
+ //!
+ //! This function will restore the previous IRQ state if this lock's lockable object cannot be locked.
+ //!
+ //! @return @p true iff. this lock's lockable object could successfully be locked, @p false otherwise.
+ [[nodiscard]] auto try_lock() -> bool
+ requires(kstd::lockable<BasicLockable>)
+ {
+ if (!irq_lock<>::try_lock())
+ {
+ return false;
+ }
+
+ if (m_lockable.try_lock())
+ {
+ return true;
+ }
+
+ irq_lock<>::unlock();
+ return false;
+ }
+
+ private:
+ lockable_type & m_lockable;
+ };
+
+ //! @}
+
+ irq_lock() -> irq_lock<void>;
+
+ template<kstd::basic_lockable Lockable>
+ irq_lock(Lockable &) -> irq_lock<Lockable>;
+
+} // namespace kapi::interrupts
+
+#endif \ No newline at end of file
diff --git a/kapi/kapi/interrupts/state.hpp b/kapi/kapi/interrupts/state.hpp
new file mode 100644
index 00000000..a33a871b
--- /dev/null
+++ b/kapi/kapi/interrupts/state.hpp
@@ -0,0 +1,26 @@
+#ifndef TEACHOS_KAPI_INTERRUPTS_STATE_HPP
+#define TEACHOS_KAPI_INTERRUPTS_STATE_HPP
+
+// IWYU pragma: private, include <kapi/interrupts.hpp>
+
+namespace kapi::interrupts
+{
+
+ //! @name Platform-defined API
+ //! @{
+
+ //! Check if interrupts are currently enabled or not.
+ //!
+ //! @return @p true iff. interrupts are currently enabled, @p false otherwise.
+ auto enabled() -> bool;
+
+ //! Enable or disable interrupts.
+ //!
+ //! @param value @p true if interrupts shall be enabled, @p false otherwise.
+ auto enabled(bool value) -> void;
+
+ //! @}
+
+} // namespace kapi::interrupts
+
+#endif \ No newline at end of file
diff --git a/kernel/kapi/interrupts.stress.cpp b/kernel/kapi/interrupts.stress.cpp
new file mode 100644
index 00000000..d3baf7b8
--- /dev/null
+++ b/kernel/kapi/interrupts.stress.cpp
@@ -0,0 +1,65 @@
+#include <kapi/interrupts.hpp>
+
+#include <kernel/test_support/cpu.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <future>
+#include <mutex>
+
+SCENARIO("irq_lock remote unlock protection", "[kapi][interrupts]")
+{
+ GIVEN("a simple irq_lock")
+ {
+ auto lock = kapi::interrupts::irq_lock{};
+
+ WHEN("it is locked on the main core")
+ {
+ auto guard = std::lock_guard{lock};
+
+ THEN("unlocking it on a different core panics")
+ {
+ auto unlocker = [&] {
+ try
+ {
+ lock.unlock();
+ return false;
+ }
+ catch (kernel::tests::cpu::halt const &)
+ {
+ return true;
+ }
+ };
+
+ auto result = std::async(std::launch::async, unlocker);
+
+ REQUIRE(result.get());
+ }
+ }
+ }
+
+ GIVEN("A heap allocated irq_lock")
+ {
+ // We use plain new-delete here, since the destruction path of std::shared_ptr is fully noexcept.
+ auto lock = new kapi::interrupts::irq_lock<>{};
+
+ THEN("destroying it on a different core panics")
+ {
+ auto destroyer = [&] {
+ try
+ {
+ delete lock;
+ return false;
+ }
+ catch (kernel::tests::cpu::halt const &)
+ {
+ return true;
+ }
+ };
+
+ auto result = std::async(std::launch::async, destroyer);
+
+ REQUIRE(result.get());
+ }
+ }
+} \ No newline at end of file
diff --git a/kernel/kapi/interrupts.tests.cpp b/kernel/kapi/interrupts.tests.cpp
new file mode 100644
index 00000000..e87c6a00
--- /dev/null
+++ b/kernel/kapi/interrupts.tests.cpp
@@ -0,0 +1,189 @@
+#include <kapi/interrupts.hpp>
+
+#include <kapi/tracked_mutex.hpp>
+
+#include <kstd/mutex.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+SCENARIO("irq_lock semantics", "[kapi][interrupts]")
+{
+ GIVEN("a simple irq_lock")
+ {
+ auto lock = kapi::interrupts::irq_lock{};
+
+ AND_GIVEN("IRQs are currently disabled")
+ {
+ kapi::interrupts::enabled(false);
+ REQUIRE_FALSE(kapi::interrupts::enabled());
+
+ WHEN("locking the irq_lock")
+ {
+ lock.lock();
+
+ THEN("IRQs are still disabled")
+ {
+ REQUIRE_FALSE(kapi::interrupts::enabled());
+
+ AND_WHEN("unlocking the irq_lock")
+ {
+ lock.unlock();
+
+ THEN("IRQs are still disabled")
+ {
+ REQUIRE_FALSE(kapi::interrupts::enabled());
+ }
+ }
+ }
+
+ THEN("the irq_lock is locked")
+ {
+ REQUIRE_FALSE(lock.try_lock());
+ lock.unlock();
+ }
+ }
+
+ THEN("the irq_lock can be locked")
+ {
+ REQUIRE(lock.try_lock());
+ lock.unlock();
+ }
+ }
+
+ AND_GIVEN("IRQs are currently enabled")
+ {
+ kapi::interrupts::enabled(true);
+ REQUIRE(kapi::interrupts::enabled());
+
+ WHEN("locking the irq_lock")
+ {
+ lock.lock();
+
+ THEN("IRQs are disabled")
+ {
+ REQUIRE_FALSE(kapi::interrupts::enabled());
+
+ AND_WHEN("unlocking the irq_lock")
+ {
+ lock.unlock();
+
+ THEN("IRQs are enabled again")
+ {
+ REQUIRE(kapi::interrupts::enabled());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ GIVEN("a wrapping irq_lock")
+ {
+ auto mutex = kapi::tracked_mutex{};
+ auto lock = kapi::interrupts::irq_lock{mutex};
+
+ AND_GIVEN("IRQs are currently disabled")
+ {
+ kapi::interrupts::enabled(false);
+ REQUIRE_FALSE(kapi::interrupts::enabled());
+
+ WHEN("locking the irq_lock")
+ {
+ lock.lock();
+
+ THEN("IRQs are still disabled")
+ {
+ REQUIRE_FALSE(kapi::interrupts::enabled());
+
+ AND_WHEN("unlocking the irq_lock")
+ {
+ lock.unlock();
+
+ THEN("IRQs are still disabled")
+ {
+ REQUIRE_FALSE(kapi::interrupts::enabled());
+ }
+
+ THEN("the inner mutex is unlocked")
+ {
+ REQUIRE(mutex.try_lock());
+ mutex.unlock();
+ }
+ }
+ }
+
+ THEN("the irq_lock is locked")
+ {
+ REQUIRE_FALSE(lock.try_lock());
+ lock.unlock();
+ }
+
+ THEN("the inner mutex is locked")
+ {
+ REQUIRE_FALSE(mutex.try_lock());
+ lock.unlock();
+ }
+ }
+
+ THEN("the irq_lock can be locked")
+ {
+ REQUIRE(lock.try_lock());
+ lock.unlock();
+ }
+ }
+
+ AND_GIVEN("IRQs are currently enabled")
+ {
+ kapi::interrupts::enabled(true);
+ REQUIRE(kapi::interrupts::enabled());
+
+ WHEN("locking the irq_lock")
+ {
+ lock.lock();
+
+ THEN("IRQs are disabled")
+ {
+ REQUIRE_FALSE(kapi::interrupts::enabled());
+
+ AND_WHEN("unlocking the irq_lock")
+ {
+ lock.unlock();
+
+ THEN("IRQs are enabled again")
+ {
+ REQUIRE(kapi::interrupts::enabled());
+ }
+
+ THEN("the inner mutex is unlocked")
+ {
+ REQUIRE(mutex.try_lock());
+ mutex.unlock();
+ }
+ }
+ }
+
+ THEN("the irq_lock is locked")
+ {
+ REQUIRE_FALSE(lock.try_lock());
+ lock.unlock();
+ }
+
+ THEN("the inner mutex is locked")
+ {
+ REQUIRE_FALSE(mutex.try_lock());
+ lock.unlock();
+ }
+ }
+ }
+
+ AND_GIVEN("the inner mutex is already locked")
+ {
+ auto guard = kstd::lock_guard{mutex};
+
+ THEN("the irq_lock cannot be locked")
+ {
+ REQUIRE_FALSE(lock.try_lock());
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/kernel/kernel/main.cpp b/kernel/kernel/main.cpp
index bc192950..bbbd46c0 100644
--- a/kernel/kernel/main.cpp
+++ b/kernel/kernel/main.cpp
@@ -293,7 +293,7 @@ auto main() -> int
kapi::devices::init_platform_devices();
kstd::println("[OS] Platform devices initialized.");
- kapi::interrupts::enable();
+ kapi::interrupts::enabled(true);
kstd::println("[OS] Interrupts enabled.");
auto modules_bus = kernel::devices::storage::init();
diff --git a/kernel/kernel/test_support/kapi/interrupts.cpp b/kernel/kernel/test_support/kapi/interrupts.cpp
index 00772669..3791b0dc 100644
--- a/kernel/kernel/test_support/kapi/interrupts.cpp
+++ b/kernel/kernel/test_support/kapi/interrupts.cpp
@@ -3,9 +3,19 @@
namespace kapi::interrupts
{
- auto enable() -> void
+ namespace
{
- // TODO: enable simulated interrupts.
+ thread_local bool interrupt_state{};
+ }
+
+ auto enabled() -> bool
+ {
+ return interrupt_state;
+ }
+
+ auto enabled(bool value) -> void
+ {
+ interrupt_state = value;
}
} // namespace kapi::interrupts \ No newline at end of file
diff --git a/libs/kstd/kstd/bits/concepts.hpp b/libs/kstd/kstd/bits/concepts.hpp
index 3e89caa9..d40b8de2 100644
--- a/libs/kstd/kstd/bits/concepts.hpp
+++ b/libs/kstd/kstd/bits/concepts.hpp
@@ -11,12 +11,39 @@ namespace kstd::bits
concept container_compatible_range =
std::ranges::input_range<RangeType> && std::convertible_to<std::ranges::range_reference_t<RangeType>, ValueType>;
+ //! Requirements on a basic lockable object.
+ //!
+ //! Given:
+ //! - @p L, a class type
+ //! - @p l, an instance of @p L
+ //!
+ //! @p L is said to be **lockable**, iff. the following expressions are valid:
+ //! - @p l.lock();
+ //! - @p l.unlock();
template<typename LockableType>
concept basic_lockable = requires(LockableType & lockable) {
+ //! Lock the lockable object, blocking until the lock has been acquired.
lockable.lock();
+
+ //! Unlock the lockable object.
lockable.unlock();
};
+ //! Requirements on a basic lockable object.
+ //!
+ //! Given:
+ //! - @p L, a class type
+ //! - @p l, an instance of @p L
+ //!
+ //! @p L is said to be **lockable**, iff. it is a basic_lockable and the following expression is valid:
+ //! - @p l.try_lock();
+ template<typename LockableType>
+ concept lockable = basic_lockable<LockableType> && requires(LockableType & lockable) {
+ //! Try to lock the lockable object, returning @p true iff. the locking was successful, @p false
+ //! otherwise.
+ { lockable.try_lock() } -> std::same_as<bool>;
+ };
+
template<typename SuspectType>
concept allocator_like = requires(SuspectType & suspect) {
typename SuspectType::value_type;
diff --git a/libs/kstd/kstd/mutex.hpp b/libs/kstd/kstd/mutex.hpp
index 6d482b6d..8573e5a3 100644
--- a/libs/kstd/kstd/mutex.hpp
+++ b/libs/kstd/kstd/mutex.hpp
@@ -1,8 +1,17 @@
#ifndef KSTD_MUTEX_HPP
#define KSTD_MUTEX_HPP
+#include <kstd/bits/concepts.hpp>
#include <kstd/bits/mutex/lock_guard.hpp> // IWYU pragma: export
#include <kstd/bits/mutex/mutex.hpp> // IWYU pragma: export
#include <kstd/bits/mutex/tags.hpp> // IWYU pragma: export
+namespace kstd
+{
+
+ using bits::basic_lockable;
+ using bits::lockable;
+
+} // namespace kstd
+
#endif \ No newline at end of file