summaryrefslogtreecommitdiff
path: root/kernel/sync/spinlock.cc
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/sync/spinlock.cc')
-rw-r--r--kernel/sync/spinlock.cc120
1 files changed, 120 insertions, 0 deletions
diff --git a/kernel/sync/spinlock.cc b/kernel/sync/spinlock.cc
new file mode 100644
index 0000000..f649fe7
--- /dev/null
+++ b/kernel/sync/spinlock.cc
@@ -0,0 +1,120 @@
+/* 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/klog.h>
+#include <lume/task.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_.store(0, __ATOMIC_RELAXED);
+ 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 (locked_.load(__ATOMIC_RELAXED))
+ ;
+ // Inner: attempt atomic exchange with acquire semantics
+ if (locked_.exchange(1, __ATOMIC_ACQUIRE) == 0)
+ break;
+ }
+
+ // Step 4: record owner
+ cpu_id_ = arch::cpu::id();
+
+ // Step 5: bump preempt_count — prevents schedule() from preempting
+ TaskControlBlock* curr = current_task();
+ if (curr) curr->preempt_count++;
+}
+
+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
+ locked_.store(0, __ATOMIC_RELEASE);
+
+ // Step 4: drop preempt_count
+ TaskControlBlock* curr = current_task();
+ if (curr) curr->preempt_count--;
+
+ // Step 5: 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();
+}