diff options
| -rw-r--r-- | .clangd | 2 | ||||
| -rw-r--r-- | arch/x86_64/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | arch/x86_64/arch/cpu/flags.cpp | 16 | ||||
| -rw-r--r-- | arch/x86_64/arch/cpu/flags.hpp | 72 | ||||
| -rw-r--r-- | arch/x86_64/kapi/interrupts.cpp | 20 | ||||
| -rw-r--r-- | kapi/kapi/interrupts.hpp | 21 | ||||
| -rw-r--r-- | kapi/kapi/interrupts/irq_lock.hpp | 163 | ||||
| -rw-r--r-- | kapi/kapi/interrupts/state.hpp | 26 | ||||
| -rw-r--r-- | kernel/kapi/interrupts.stress.cpp | 65 | ||||
| -rw-r--r-- | kernel/kapi/interrupts.tests.cpp | 189 | ||||
| -rw-r--r-- | kernel/kernel/main.cpp | 2 | ||||
| -rw-r--r-- | kernel/kernel/test_support/kapi/interrupts.cpp | 14 | ||||
| -rw-r--r-- | libs/kstd/kstd/bits/concepts.hpp | 27 | ||||
| -rw-r--r-- | libs/kstd/kstd/mutex.hpp | 9 |
14 files changed, 602 insertions, 25 deletions
@@ -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 |
