summaryrefslogtreecommitdiff
path: root/na/kernel/waitqueue.cc
blob: 9689417815af0451f4d803b9151f93f9d348b07d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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;
}