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 condvar_prepare_wait(struct condvar *cv) {
15 struct thread *curr = thread_get_current();
16 curr->wake_reason = WAKE_REASON_NONE;
17 curr->wait_cookie++;
18 thread_block_on(q: &cv->waiters, type: THREAD_WAIT_UNINTERRUPTIBLE, wake_src: cv);
19}
20
21static enum wake_reason condvar_finish_wait(struct condvar *cv,
22 struct spinlock *lock,
23 enum irql irql, enum irql *out) {
24 spin_unlock(lock, irql);
25 thread_yield_until_wake_match();
26 *out = condvar_lock_internal(cv, lock);
27 return thread_get_current()->wake_reason;
28}
29
30enum wake_reason condvar_wait(struct condvar *cv, struct spinlock *lock,
31 enum irql irql, enum irql *out) {
32 condvar_prepare_wait(cv);
33 return condvar_finish_wait(cv, lock, irql, out);
34}
35
36void condvar_init(struct condvar *cv, bool irq_disable) {
37 thread_queue_init(q: &cv->waiters);
38 cv->irq_disable = irq_disable;
39}
40
41static void set_wake_reason_and_wake(struct condvar *cv, struct thread *t,
42 enum wake_reason reason) {
43 if (!t)
44 return;
45
46 t->wake_reason = reason;
47 enum thread_wake_reason r = reason == WAKE_REASON_TIMEOUT
48 ? THREAD_WAKE_REASON_SLEEP_TIMEOUT
49 : THREAD_WAKE_REASON_SLEEP_MANUAL;
50
51 thread_wake(t, reason: r, prio: t->perceived_prio_class, wake_src: cv);
52}
53
54static void nop_callback(struct thread *unused) {
55 (void) unused;
56}
57
58struct thread *condvar_signal_callback(struct condvar *cv,
59 thread_action_callback tac) {
60 struct thread *t = thread_queue_pop_front(q: &cv->waiters);
61 tac(t);
62 set_wake_reason_and_wake(cv, t, reason: WAKE_REASON_SIGNAL);
63 return t;
64}
65
66struct thread *condvar_signal(struct condvar *cv) {
67 return condvar_signal_callback(cv, tac: nop_callback);
68}
69
70void condvar_broadcast_callback(struct condvar *cv,
71 thread_action_callback tac) {
72 struct thread *t;
73 while ((t = thread_queue_pop_front(q: &cv->waiters)) != NULL) {
74 tac(t);
75 set_wake_reason_and_wake(cv, t, reason: WAKE_REASON_SIGNAL);
76 }
77}
78
79void condvar_broadcast(struct condvar *cv) {
80 condvar_broadcast_callback(cv, tac: nop_callback);
81}
82
83static void condvar_timeout_wakeup(struct timer *timer) {
84 struct condvar_with_cb *ck = timer->data;
85 struct thread *t = ck->thread;
86
87 if (t->wait_cookie != ck->cookie)
88 return;
89
90 enum irql irql = spin_lock_irq_disable(&ck->cv->waiters.lock);
91
92 if (!list_empty(head: &t->wq_list_node))
93 list_del_init(entry: &t->wq_list_node);
94
95 spin_unlock(&ck->cv->waiters.lock, irql);
96 set_wake_reason_and_wake(cv: ck->cv, t, reason: WAKE_REASON_TIMEOUT);
97}
98
99enum wake_reason condvar_wait_timeout(struct condvar *cv, struct spinlock *lock,
100 time_ms_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->thread = curr;
108
109 condvar_prepare_wait(cv);
110 cwcb->cookie = curr->wait_cookie;
111 timer_init(timer: &cwcb->timer, func: condvar_timeout_wakeup, data: cwcb);
112
113 /* TOPC_NONE because it is not imperative that we have the timer run here */
114 cwcb->timer.flags =
115 TIMER_FLAG_IRQ | TIMER_FLAG_PINNED | TIMER_FLAG_CPU(smp_id(TOPC_NONE));
116 timer_modify(timer: &cwcb->timer, new: timer_delta_us(MS_TO_US(timeout_ms)));
117
118 enum wake_reason reason = condvar_finish_wait(cv, lock, irql, out);
119
120 timer_delete_sync(timer: &cwcb->timer);
121
122 return reason;
123}
124