summaryrefslogtreecommitdiff
path: root/na/kernel/sync
diff options
context:
space:
mode:
Diffstat (limited to 'na/kernel/sync')
-rw-r--r--na/kernel/sync/cpu_state.h34
-rw-r--r--na/kernel/sync/spinlock.cc111
-rw-r--r--na/kernel/sync/spinlock.h60
3 files changed, 205 insertions, 0 deletions
diff --git a/na/kernel/sync/cpu_state.h b/na/kernel/sync/cpu_state.h
new file mode 100644
index 0000000..d0e3814
--- /dev/null
+++ b/na/kernel/sync/cpu_state.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Per-CPU Interrupt Nesting Tracker
+ *
+ * Tracks spinlock nesting depth on each CPU to ensure interrupts
+ * are only restored when the last spinlock is released.
+ *
+ * BSS-segment global array; zero-initialized by entry.S BSS clear.
+ * Reference: docs/specs/spinlock.md §3.1
+ */
+
+#include <lume/types.h>
+
+namespace sync {
+
+struct CpuSyncState {
+ int lock_depth; // Number of spinlocks held by this CPU
+ bool intr_was_on; // Were interrupts enabled before the first lock?
+};
+
+inline constexpr uint64 kInvalidCpuId = ~0ULL;
+
+// BSS-segment global array, zero-initialized by entry.S
+extern CpuSyncState g_cpu_sync_states[kMaxCPUs];
+
+// Internal API — called only by Spinlock::acquire/release
+void push_intr();
+void pop_intr();
+
+} // namespace sync
diff --git a/na/kernel/sync/spinlock.cc b/na/kernel/sync/spinlock.cc
new file mode 100644
index 0000000..a03130d
--- /dev/null
+++ b/na/kernel/sync/spinlock.cc
@@ -0,0 +1,111 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * Spinlock Implementation
+ *
+ * TTAS (Test-and-Test-and-Set) spinlock with:
+ * - Acquire: __ATOMIC_ACQUIRE memory ordering
+ * - Release: __ATOMIC_RELEASE memory ordering
+ * - Automatic interrupt disable/enable via push_intr/pop_intr
+ * - Dead-lock and invalid-release detection via kernel_panic
+ *
+ * Reference: docs/specs/spinlock.md
+ */
+
+#include "spinlock.h"
+#include "cpu_state.h"
+#include <arch/cpu.h>
+#include <lume/panic.h>
+
+// ============================================================
+// Per-CPU sync state (BSS zero-initialized)
+// ============================================================
+namespace sync {
+
+CpuSyncState g_cpu_sync_states[kMaxCPUs];
+
+void push_intr() {
+ bool was_on = arch::cpu::intr_enabled();
+ arch::cpu::intr_off();
+
+ auto& state = g_cpu_sync_states[arch::cpu::id()];
+ if (state.lock_depth == 0) {
+ state.intr_was_on = was_on;
+ }
+ state.lock_depth++;
+}
+
+void pop_intr() {
+ auto& state = g_cpu_sync_states[arch::cpu::id()];
+ state.lock_depth--;
+
+ if (state.lock_depth == 0 && state.intr_was_on) {
+ arch::cpu::intr_on();
+ }
+}
+
+} // namespace sync
+
+// ============================================================
+// Spinlock methods
+// ============================================================
+
+void Spinlock::init(const char* name) {
+ name_ = name;
+ locked_ = 0;
+ cpu_id_ = sync::kInvalidCpuId;
+}
+
+void Spinlock::acquire() {
+#ifdef DEBUG
+ // Optional: detect interrupt-state leakage
+ if (arch::cpu::intr_enabled()) {
+ auto& state = sync::g_cpu_sync_states[arch::cpu::id()];
+ if (state.lock_depth > 0) {
+ kernel_panic("spinlock: interrupts enabled while holding lock");
+ }
+ }
+#endif
+
+ // Step 1: disable interrupts, track nesting depth
+ sync::push_intr();
+
+ // Step 2: dead-lock detection (same CPU re-acquiring)
+ if (is_held_by_current_cpu()) {
+ kernel_panic("spinlock: double lock", name_);
+ }
+
+ // Step 3: TTAS spin loop
+ while (true) {
+ // Outer: spin on local cache copy (no bus traffic)
+ while (__atomic_load_n(&locked_, __ATOMIC_RELAXED))
+ ;
+ // Inner: attempt atomic exchange with acquire semantics
+ if (__atomic_exchange_n(&locked_, 1, __ATOMIC_ACQUIRE) == 0)
+ break;
+ }
+
+ // Step 4: record owner
+ cpu_id_ = arch::cpu::id();
+}
+
+void Spinlock::release() {
+ // Step 1: verify caller is the holder
+ if (!is_held_by_current_cpu()) {
+ kernel_panic("spinlock: invalid release", name_);
+ }
+
+ // Step 2: clear owner
+ cpu_id_ = sync::kInvalidCpuId;
+
+ // Step 3: release the lock with release semantics
+ __atomic_store_n(&locked_, 0, __ATOMIC_RELEASE);
+
+ // Step 4: restore interrupt state if this was the last lock
+ sync::pop_intr();
+}
+
+bool Spinlock::is_held_by_current_cpu() const {
+ return cpu_id_ == arch::cpu::id();
+}
diff --git a/na/kernel/sync/spinlock.h b/na/kernel/sync/spinlock.h
new file mode 100644
index 0000000..b3e3393
--- /dev/null
+++ b/na/kernel/sync/spinlock.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+#pragma once
+
+/*
+ * Spinlock & LockGuard
+ *
+ * Provides mutual exclusion for SMP kernels using TTAS
+ * (Test-and-Test-and-Set) with acquire/release memory ordering.
+ * Interrupt disable/enable is managed automatically via push_intr/pop_intr.
+ *
+ * Reference: docs/specs/spinlock.md
+ */
+
+#include <lume/types.h>
+#include "cpu_state.h"
+
+class Spinlock {
+public:
+ // Default: BSS zero is safe. Call init() to give the lock a name.
+ Spinlock() = default;
+
+ // Explicit initialization (for global objects or Placement New)
+ void init(const char* name);
+
+ // Non-copyable, non-movable (lock identity is its address)
+ Spinlock(const Spinlock&) = delete;
+ Spinlock& operator=(const Spinlock&) = delete;
+ Spinlock(Spinlock&&) = delete;
+ Spinlock& operator=(Spinlock&&) = delete;
+
+ void acquire();
+ void release();
+ bool is_held_by_current_cpu() const;
+
+private:
+ uint32 locked_ = 0; // 0 = free, 1 = held
+ const char* name_ = "uninit"; // Lock name (for panic messages)
+ uint64 cpu_id_ = sync::kInvalidCpuId; // Holder CPU ID
+};
+
+class LockGuard {
+public:
+ explicit LockGuard(Spinlock& lock) : lock_(lock) {
+ lock_.acquire();
+ }
+
+ ~LockGuard() {
+ lock_.release();
+ }
+
+ LockGuard(const LockGuard&) = delete;
+ LockGuard& operator=(const LockGuard&) = delete;
+ LockGuard(LockGuard&&) = delete;
+ LockGuard& operator=(LockGuard&&) = delete;
+
+private:
+ Spinlock& lock_;
+};