summaryrefslogtreecommitdiff
path: root/kernel/waitqueue.cc
diff options
context:
space:
mode:
authorRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
committerRichard Qin <richard.qin.x@gmail.com>2026-07-10 11:31:46 +0800
commite90b72ec3082588b296956730bd2930cf8d5d671 (patch)
tree36ff6916af9b4176ea5a9662219d11e721f0b691 /kernel/waitqueue.cc
parent1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff)
downloadlume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.gz
lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.xz
refactor: promote new architecture to repository root
Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior.
Diffstat (limited to 'kernel/waitqueue.cc')
-rw-r--r--kernel/waitqueue.cc144
1 files changed, 144 insertions, 0 deletions
diff --git a/kernel/waitqueue.cc b/kernel/waitqueue.cc
new file mode 100644
index 0000000..9689417
--- /dev/null
+++ b/kernel/waitqueue.cc
@@ -0,0 +1,144 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Copyright (C) 2026 Richard Qin */
+
+/*
+ * WaitQueue — blocking synchronization implementation
+ *
+ * Two-phase wait protocol:
+ *
+ * prepare_to_wait(): task → Sleeping, link into queue
+ * [caller checks condition — if still false, calls schedule()]
+ * finish_wait(): remove from queue, restore Runnable
+ *
+ * Legacy sleep() wraps the two-phase pattern for simple callers.
+ *
+ * wake_one()/wake_all(): dequeue waiters → Runnable, sched_enqueue()
+ *
+ * The internal spinlock protects the wait list only.
+ * schedule() is called AFTER releasing all spinlocks to avoid
+ * holding a lock across a context switch.
+ */
+
+#include <lume/waitqueue.h>
+#include <lume/task.h>
+#include <lume/sched.h>
+#include <lume/kprintf.h>
+#include <arch/cpu.h>
+
+/* ------------------------------------------------------------------ */
+/* Two-phase wait API */
+/* ------------------------------------------------------------------ */
+
+void WaitQueue::prepare_to_wait(TaskControlBlock* task, TaskState new_state)
+{
+ LockGuard guard(lock_);
+
+ /* Set state BEFORE linking — this is the key to preventing lost wakeups.
+ * If wake_one fires between now and schedule(), it will set state back
+ * to Runnable, and schedule() will see Runnable and not block. */
+ task->state = new_state;
+
+ /* Only add to queue if not already linked (idempotent) */
+ if (task->run_link.next == &task->run_link) {
+ /* Link into wait queue tail */
+ list_node* node = &task->run_link;
+ node->prev = head_.prev;
+ node->next = &head_;
+ head_.prev->next = node;
+ head_.prev = node;
+ }
+}
+
+void WaitQueue::finish_wait(TaskControlBlock* task)
+{
+ LockGuard guard(lock_);
+
+ /* Remove from wait queue if still linked */
+ if (task->run_link.next != &task->run_link) {
+ list_node* node = &task->run_link;
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->init();
+ }
+
+ /* Ensure task is Runnable (it may have been woken by wake_one/wake_all
+ * already, or it may never have slept if the condition was true). */
+ if (task->state == TaskState::Sleeping) {
+ task->state = TaskState::Runnable;
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Legacy convenience API */
+/* ------------------------------------------------------------------ */
+
+void WaitQueue::sleep(Spinlock* external_lock)
+{
+ TaskControlBlock* curr = current_task();
+ if (!curr) {
+ if (external_lock) external_lock->release();
+ return;
+ }
+
+ /* Phase 1: add to queue, mark Sleeping */
+ prepare_to_wait(curr, TaskState::Sleeping);
+
+ /* Release external lock AFTER we are on the queue (critical ordering) */
+ if (external_lock) {
+ external_lock->release();
+ }
+
+ /* Phase 2: context switch away — will return here when woken */
+ schedule();
+
+ /* Phase 3: clean up */
+ finish_wait(curr);
+}
+
+/* ------------------------------------------------------------------ */
+/* Wakeup */
+/* ------------------------------------------------------------------ */
+
+bool WaitQueue::wake_one()
+{
+ LockGuard guard(lock_);
+
+ if (head_.is_empty())
+ return false;
+
+ /* Dequeue the first (oldest) waiter */
+ list_node* node = head_.next;
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->init();
+
+ TaskControlBlock* task =
+ list_entry<TaskControlBlock, &TaskControlBlock::run_link>(node);
+
+ task->state = TaskState::Runnable;
+ sched_enqueue(task);
+
+ return true;
+}
+
+int WaitQueue::wake_all()
+{
+ LockGuard guard(lock_);
+
+ int count = 0;
+ while (!head_.is_empty()) {
+ list_node* node = head_.next;
+ node->prev->next = node->next;
+ node->next->prev = node->prev;
+ node->init();
+
+ TaskControlBlock* task =
+ list_entry<TaskControlBlock, &TaskControlBlock::run_link>(node);
+
+ task->state = TaskState::Runnable;
+ sched_enqueue(task);
+ count++;
+ }
+
+ return count;
+}