2478fadefe706a0d323cd5628e3f8a84dac04777
[barrelfish] / lib / barrelfish / thread_sync.c
1 /**
2  * \file
3  * \brief Thread synchronisation primitives.
4  */
5
6 /*
7  * Copyright (c) 2007, 2008, 2009, 2010, 2012, ETH Zurich.
8  * All rights reserved.
9  *
10  * This file is distributed under the terms in the attached LICENSE file.
11  * If you do not find this file, copies can be found by writing to:
12  * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
13  */
14
15 #include <barrelfish/barrelfish.h>
16 #include <barrelfish/dispatch.h>
17 #include <barrelfish/dispatcher_arch.h>
18 #include <trace/trace.h>
19 #include <trace_definitions/trace_defs.h>
20 #include "threads_priv.h"
21
22 #ifndef TRACE_THREADS
23 #define trace_event(a,b,c) ((void)0)
24 #endif
25
26
27 /**
28  * \brief Initialise a condition variable
29  *
30  * \param cond Condition variable pointer
31  */
32 void thread_cond_init(struct thread_cond *cond)
33 {
34     cond->queue = NULL;
35     cond->lock = 0;
36 }
37
38 /**
39  * \brief Wait for a condition variable
40  *
41  * This function waits for the given condition variable to be signalled
42  * (through thread_cond_signal() or thread_cond_broadcast()) before
43  * returning, while atomically unlocking the mutex pointed to by
44  * 'mutex'.
45  *
46  * \param cond  Condition variable pointer
47  * \param mutex Optional pointer to mutex to unlock.
48  */
49 void thread_cond_wait(struct thread_cond *cond, struct thread_mutex *mutex)
50 {
51     dispatcher_handle_t disp = disp_disable();
52
53     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_COND_WAIT_ENTER,
54                 (uintptr_t)cond);
55
56     acquire_spinlock(&cond->lock);
57
58     // Release the lock
59     if (mutex != NULL) {
60         struct thread *wakeup = thread_mutex_unlock_disabled(disp, mutex);
61
62         if(wakeup != NULL) {
63             errval_t err = domain_wakeup_on_disabled(wakeup->disp, wakeup, disp);
64             assert_disabled(err_is_ok(err));
65         }
66     }
67
68     // Block on the condition variable and release spinlock
69     thread_block_and_release_spinlock_disabled(disp, &cond->queue, &cond->lock);
70
71     // Re-acquire the mutex
72     if (mutex != NULL) {
73         thread_mutex_lock(mutex);
74     }
75
76     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_COND_WAIT_LEAVE,
77                 (uintptr_t)cond);
78 }
79
80 /**
81  * \brief Signal a condition variable
82  *
83  * This function signals the condition variable, and wakes up one
84  * thread waiting on it.
85  *
86  * \param cond Condition variable pointer
87  */
88 void thread_cond_signal(struct thread_cond *cond)
89 {
90     struct thread *wakeup = NULL;
91     errval_t err = SYS_ERR_OK;
92
93     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_COND_SIGNAL,
94                 (uintptr_t)cond);
95
96     // Wakeup one waiting thread
97     dispatcher_handle_t disp = disp_disable();
98     acquire_spinlock(&cond->lock);
99     if (cond->queue != NULL) {
100         wakeup = thread_unblock_one_disabled(disp, &cond->queue, NULL);
101         if(wakeup != NULL) {
102             err = domain_wakeup_on_disabled(wakeup->disp, wakeup, disp);
103         }
104     }
105     release_spinlock(&cond->lock);
106     disp_enable(disp);
107
108     if(err_is_fail(err)) {
109         USER_PANIC_ERR(err, "remote wakeup from condition signal");
110     }
111
112     if(wakeup != NULL) {
113         // XXX: Need directed yield to inter-disp thread
114         thread_yield();
115     }
116 }
117
118 /**
119  * \brief Broadcast signal a condition variable
120  *
121  * This function signals the condition variable, and wakes up all
122  * threads waiting on it.
123  *
124  * \param cond Condition variable pointer
125  */
126 void thread_cond_broadcast(struct thread_cond *cond)
127 {
128     struct thread *wakeupq = NULL;
129     bool foreignwakeup = false;
130
131     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_COND_BROADCAST,
132                 (uintptr_t)cond);
133
134     // Wakeup all waiting threads
135     dispatcher_handle_t disp = disp_disable();
136     acquire_spinlock(&cond->lock);
137     wakeupq = thread_unblock_all_disabled(disp, &cond->queue, NULL);
138     release_spinlock(&cond->lock);
139     disp_enable(disp);
140
141     foreignwakeup = (wakeupq != NULL);
142     // Now, wakeup all on foreign dispatchers
143     while (wakeupq != NULL) {
144         struct thread *wakeup = wakeupq;
145         wakeupq = wakeupq->next;
146         errval_t err = domain_wakeup_on(wakeup->disp, wakeup);
147         if(err_is_fail(err)) {
148             USER_PANIC_ERR(err, "remote wakeup from condition broadcast");
149         }
150     }
151
152     if(foreignwakeup) {
153         // XXX: Need directed yield to inter-disp thread
154         thread_yield();
155     }
156 }
157
158 /**
159  * \brief Initialise a mutex
160  *
161  * \param mutex Mutex pointer
162  */
163 void thread_mutex_init(struct thread_mutex *mutex)
164 {
165     mutex->locked = 0;
166     mutex->holder = NULL;
167     mutex->queue = NULL;
168     mutex->lock = 0;
169 }
170
171 /**
172  * \brief Lock a mutex
173  *
174  * This blocks until the given mutex is unlocked, and then atomically locks it.
175  *
176  * \param mutex Mutex pointer
177  */
178 void thread_mutex_lock(struct thread_mutex *mutex)
179 {
180     dispatcher_handle_t handle = disp_disable();
181     struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
182
183     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_MUTEX_LOCK_ENTER,
184                 (uintptr_t)mutex);
185
186     acquire_spinlock(&mutex->lock);
187     while (mutex->locked > 0) {
188         thread_block_and_release_spinlock_disabled(handle, &mutex->queue,
189                                                    &mutex->lock);
190         handle = disp_disable();
191         disp_gen = get_dispatcher_generic(handle);
192         acquire_spinlock(&mutex->lock);
193     }
194     mutex->locked = 1;
195     mutex->holder = disp_gen->current;
196     release_spinlock(&mutex->lock);
197     disp_enable(handle);
198
199     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_MUTEX_LOCK_LEAVE,
200                 (uintptr_t)mutex);
201 }
202
203 /**
204  * \brief Lock a mutex
205  *
206  * This blocks until the given mutex is unlocked, and then atomically locks it.
207  *
208  * \param mutex Mutex pointer
209  */
210 void thread_mutex_lock_nested(struct thread_mutex *mutex)
211 {
212     dispatcher_handle_t handle = disp_disable();
213     struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
214
215     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_MUTEX_LOCK_NESTED_ENTER,
216                 (uintptr_t)mutex);
217
218     acquire_spinlock(&mutex->lock);
219     while (mutex->locked > 0
220         && mutex->holder != disp_gen->current) {
221         thread_block_and_release_spinlock_disabled(handle, &mutex->queue,
222                                                    &mutex->lock);
223         handle = disp_disable();
224         disp_gen = get_dispatcher_generic(handle);
225         acquire_spinlock(&mutex->lock);
226     }
227     mutex->locked++;
228     mutex->holder = disp_gen->current;
229     release_spinlock(&mutex->lock);
230     disp_enable(handle);
231
232     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_MUTEX_LOCK_NESTED_LEAVE,
233                 (uintptr_t)mutex);
234 }
235
236 /**
237  * \brief Try to lock a mutex
238  *
239  * If the given mutex is unlocked, this atomically locks it and returns true,
240  * otherwise it returns false immediately.
241  *
242  * \param mutex Mutex pointer
243  *
244  * \returns true if lock acquired, false otherwise
245  */
246 bool thread_mutex_trylock(struct thread_mutex *mutex)
247 {
248     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_MUTEX_TRYLOCK,
249                 (uintptr_t)mutex);
250
251     // Try first to avoid contention
252     if (mutex->locked > 0) {
253         return false;
254     }
255
256     dispatcher_handle_t handle = disp_disable();
257     struct dispatcher_generic *disp_gen = get_dispatcher_generic(handle);
258     bool ret;
259
260     acquire_spinlock(&mutex->lock);
261     if (mutex->locked > 0) {
262         ret = false;
263     } else {
264         ret = true;
265         mutex->locked = 1;
266         mutex->holder = disp_gen->current;
267     }
268     release_spinlock(&mutex->lock);
269
270     disp_enable(handle);
271     return ret;
272 }
273
274 /**
275  * \brief Unlock a mutex, while disabled
276  *
277  * This function unlocks the given mutex. It may only be called while disabled.
278  *
279  * \param disp Dispatcher pointer
280  * \param mutex Mutex pointer
281  *
282  * \return Pointer to thread to be woken on foreign dispatcher
283  */
284 struct thread *thread_mutex_unlock_disabled(dispatcher_handle_t handle,
285                                             struct thread_mutex *mutex)
286 {
287     struct thread *ft = NULL;
288
289     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_MUTEX_UNLOCK,
290                 (uintptr_t)mutex);
291
292     acquire_spinlock(&mutex->lock);
293     assert_disabled(mutex->locked > 0);
294
295     if(mutex->locked == 1) {
296         // Wakeup one waiting thread
297         if (mutex->queue != NULL) {
298             // XXX: This assumes dequeueing is off the top of the queue
299             mutex->holder = mutex->queue;
300             ft = thread_unblock_one_disabled(handle, &mutex->queue, NULL);
301         } else {
302             mutex->holder = NULL;
303             mutex->locked = 0;
304         }
305     } else {
306         mutex->locked--;
307     }
308
309     release_spinlock(&mutex->lock);
310     return ft;
311 }
312
313 /**
314  * \brief Unlock a mutex
315  *
316  * This unlocks the given mutex.
317  *
318  * \param mutex Mutex pointer
319  */
320 void thread_mutex_unlock(struct thread_mutex *mutex)
321 {
322     dispatcher_handle_t disp = disp_disable();
323     struct thread *wakeup = thread_mutex_unlock_disabled(disp, mutex);
324     errval_t err = SYS_ERR_OK;
325
326     if (wakeup != NULL) {
327         err = domain_wakeup_on_disabled(wakeup->disp, wakeup, disp);
328     }
329     disp_enable(disp);
330
331     if(err_is_fail(err)) {
332         USER_PANIC_ERR(err, "remote wakeup from mutex unlock");
333     }
334
335     if(wakeup != NULL) {
336         // XXX: Need directed yield to inter-disp thread
337         thread_yield();
338     }
339 }
340
341 void thread_sem_init(struct thread_sem *sem, unsigned int value)
342 {
343     assert(sem != NULL);
344
345     sem->value = value;
346     sem->queue = NULL;
347     sem->lock = 0;
348 }
349
350 void thread_sem_wait(struct thread_sem *sem)
351 {
352     assert(sem != NULL);
353
354     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_SEM_WAIT_ENTER,
355                 (uintptr_t)sem);
356
357     dispatcher_handle_t disp = disp_disable();
358     acquire_spinlock(&sem->lock);
359
360     while (sem->value < 1) {
361         // Not possible to decrement -- wait!
362         thread_block_and_release_spinlock_disabled(disp, &sem->queue, &sem->lock);
363         disp = disp_disable();
364         acquire_spinlock(&sem->lock);
365     }
366
367     // Decrement possible
368     sem->value--;
369     release_spinlock(&sem->lock);
370     disp_enable(disp);
371
372     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_SEM_WAIT_LEAVE,
373                 (uintptr_t)sem);
374 }
375
376 bool thread_sem_trywait(struct thread_sem *sem)
377 {
378     assert(sem != NULL);
379     bool ret = false;
380
381     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_SEM_TRYWAIT,
382                 (uintptr_t)sem);
383
384     dispatcher_handle_t disp = disp_disable();
385     acquire_spinlock(&sem->lock);
386
387     if(sem->value >= 1) {
388         // Decrement possible
389         sem->value--;
390         ret = true;
391     }
392
393     release_spinlock(&sem->lock);
394     disp_enable(disp);
395
396     return ret;
397 }
398
399 void thread_sem_post(struct thread_sem *sem)
400 {
401     assert(sem != NULL);
402
403     trace_event(TRACE_SUBSYS_THREADS, TRACE_EVENT_THREADS_SEM_POST, (uintptr_t)sem);
404
405     dispatcher_handle_t disp = disp_disable();
406     struct thread *wakeup = NULL;
407     errval_t err = SYS_ERR_OK;
408     acquire_spinlock(&sem->lock);
409
410     // Wakeup one?
411     if(sem->value == 0 && sem->queue != NULL) {
412         wakeup = thread_unblock_one_disabled(disp, &sem->queue, NULL);
413     } else {
414         sem->value++;
415     }
416
417     if(wakeup != NULL) {
418         err = domain_wakeup_on_disabled(wakeup->disp, wakeup, disp);
419         assert_disabled(err_is_ok(err));
420     }
421
422     release_spinlock(&sem->lock);
423     disp_enable(disp);
424
425     if(err_is_fail(err)) {
426         USER_PANIC_ERR(err, "remote wakeup from semaphore post");
427     }
428
429     if(wakeup != NULL) {
430         // XXX: Need directed yield to inter-disp thread
431         thread_yield();
432     }
433 }