1#include <sch/sched.h>
2#include <sync/condvar.h>
3#include <thread/thread.h>
4#include <thread/workqueue.h>
5
6static enum irql condvar_lock_internal(struct condvar *cv,
7 struct spinlock *lock) {
8 if (cv->irq_disable)
9 return spin_lock_irq_disable(lock);
10
11 return spin_lock(lock);
12}
13
14static void do_block_on_queue(struct thread_queue *q, struct spinlock *lock,
15 enum irql irql, struct condvar *cv) {
16 thread_block_on(q, type: THREAD_WAIT_UNINTERRUPTIBLE, wake_src: cv);
17 spin_unlock(lock, old: irql);
18 thread_yield_until_wake_match();
19}
20
21enum wake_reason condvar_wait(struct condvar *cv, struct spinlock *lock,
22 enum irql irql, enum irql *out) {
23 struct thread *curr = thread_get_current();
24 curr->wake_reason = WAKE_REASON_NONE;
25 curr->wait_cookie++;
26
27 do_block_on_queue(q: &cv->waiters, lock, irql, cv);
28 *out = condvar_lock_internal(cv, lock);
29
30 return curr->wake_reason;
31}
32
33void condvar_init(struct condvar *cv, bool irq_disable) {
34 thread_queue_init(q: &cv->waiters);
35 cv->irq_disable = irq_disable;
36}
37
38static void set_wake_reason_and_wake(struct condvar *cv, struct thread *t,
39 enum wake_reason reason) {
40 if (!t)
41 return;
42
43 t->wake_reason = reason;
44 enum thread_wake_reason r = reason == WAKE_REASON_TIMEOUT
45 ? THREAD_WAKE_REASON_SLEEP_TIMEOUT
46 : THREAD_WAKE_REASON_SLEEP_MANUAL;
47
48 thread_wake(t, reason: r, prio: t->perceived_prio_class, wake_src: cv);
49}
50
51static void nop_callback(struct thread *unused) {
52 (void) unused;
53}
54
55struct thread *condvar_signal_callback(struct condvar *cv,
56 thread_action_callback tac) {
57 struct thread *t = thread_queue_pop_front(q: &cv->waiters);
58 tac(t);
59 set_wake_reason_and_wake(cv, t, reason: WAKE_REASON_SIGNAL);
60 return t;
61}
62
63struct thread *condvar_signal(struct condvar *cv) {
64 return condvar_signal_callback(cv, tac: nop_callback);
65}
66
67void condvar_broadcast_callback(struct condvar *cv,
68 thread_action_callback tac) {
69 struct thread *t;
70 while ((t = thread_queue_pop_front(q: &cv->waiters)) != NULL) {
71 tac(t);
72 set_wake_reason_and_wake(cv, t, reason: WAKE_REASON_SIGNAL);
73 }
74}
75
76void condvar_broadcast(struct condvar *cv) {
77 condvar_broadcast_callback(cv, tac: nop_callback);
78}
79
80static void condvar_timeout_wakeup(void *arg, void *arg2) {
81 struct thread *t = arg;
82 struct condvar_with_cb *ck = arg2;
83
84 if (t->wait_cookie != ck->cookie) {
85 thread_put(t);
86 return;
87 }
88
89 enum irql irql = spin_lock_irq_disable(lock: &ck->cv->waiters.lock);
90
91 if (!list_empty(head: &t->wq_list_node))
92 list_del_init(entry: &t->wq_list_node);
93
94 spin_unlock(lock: &ck->cv->waiters.lock, old: irql);
95 set_wake_reason_and_wake(cv: ck->cv, t, reason: WAKE_REASON_TIMEOUT);
96 thread_put(t);
97}
98
99enum wake_reason condvar_wait_timeout(struct condvar *cv, struct spinlock *lock,
100 time_t timeout_ms, enum irql irql,
101 enum irql *out) {
102 struct thread *curr = thread_get_current();
103 curr->wake_reason = WAKE_REASON_NONE;
104
105 struct condvar_with_cb *cwcb = &curr->cv_cb_object;
106 cwcb->cv = cv;
107 cwcb->cookie = curr->wait_cookie + 1; /* +1 from condvar */
108
109 if (!thread_get(obj: curr))
110 panic("What? Someone has pulled the rug out from under me!");
111
112 defer_enqueue(func: condvar_timeout_wakeup, WORK_ARGS(curr, cwcb), delay_ms: timeout_ms);
113 condvar_wait(cv, lock, irql, out);
114
115 return curr->wake_reason;
116}
117