1/* 2 * Copyright 2017, Data61 3 * Commonwealth Scientific and Industrial Research Organisation (CSIRO) 4 * ABN 41 687 119 230. 5 * 6 * This software may be distributed and modified according to the terms of 7 * the BSD 2-Clause license. Note that NO WARRANTY is provided. 8 * See "LICENSE_BSD2.txt" for details. 9 * 10 * @TAG(DATA61_BSD) 11 */ 12 13#pragma once 14 15#include <autoconf.h> 16#include <sel4/sel4.h> 17#ifdef CONFIG_DEBUG_BUILD 18#include <sel4debug/debug.h> 19#endif 20#include <vka/vka.h> 21#include <vka/object.h> 22#include <platsupport/sync/atomic.h> 23#include <stdbool.h> 24 25typedef struct { 26 vka_object_t notification; 27 volatile int waiters; 28 volatile bool broadcasting; 29} sync_cv_t; 30 31/* Initialise an unmanaged condition variable 32 * @param cv A condition variable object to be initialised. 33 * @param notification A notification object to use for wake up. 34 * @return 0 on success, an error code on failure. */ 35static inline int sync_cv_init(sync_cv_t *cv, seL4_CPtr notification) 36{ 37 if (cv == NULL) { 38 ZF_LOGE("Condition variable passed to sync_cv_init is NULL"); 39 return -1; 40 } 41 42#ifdef CONFIG_DEBUG_BUILD 43 /* Check the cap actually is a notification. */ 44 assert(debug_cap_is_notification(notification)); 45#endif 46 47 cv->notification.cptr = notification; 48 cv->waiters = 0; 49 cv->broadcasting = false; 50 return 0; 51} 52 53/* Wait on a condition variable. 54 * This assumes that you already hold the lock and will block until notified 55 * by sync_cv_signal or sync_cv_broadcast. It returns once you hold the lock 56 * again. Note that a spurious wake up is possible and the condition should 57 * always be checked again after sync_cv_wait returns. 58 * @param lock The lock on the monitor. 59 * @param cv The condition variable to wait on. 60 * @return 0 on success, an error code on failure. */ 61static inline int sync_cv_wait(sync_bin_sem_t *lock, sync_cv_t *cv) 62{ 63 if (cv == NULL) { 64 ZF_LOGE("Condition variable passed to sync_cv_wait is NULL"); 65 return -1; 66 } 67 68 /* Increment waiters count and release the lock */ 69 cv->waiters++; 70 cv->broadcasting = false; 71 int error = sync_bin_sem_post(lock); 72 if (error != 0) { 73 return error; 74 } 75 76 /* Wait to be notified */ 77 seL4_Wait(cv->notification.cptr, NULL); 78 79 /* Reacquire the lock */ 80 error = sync_bin_sem_wait(lock); 81 if (error != 0) { 82 return error; 83 } 84 85 /* Wake up and decrement waiters count */ 86 cv->waiters--; 87 88 /* Handle the case where a broadcast is ongoing */ 89 if (cv->broadcasting) { 90 if (cv->waiters > 0) { 91 /* Signal the next thread and continue */ 92 seL4_Signal(cv->notification.cptr); 93 } else { 94 /* This is the last thread, so stop broadcasting */ 95 cv->broadcasting = false; 96 } 97 } 98 99 return 0; 100} 101 102/* Signal a condition variable. 103 * This assumes that you hold the lock and notifies one waiter 104 * @param cv The condition variable to signal. 105 * @return 0 on success, an error code on failure. */ 106static inline int sync_cv_signal(sync_cv_t *cv) 107{ 108 if (cv == NULL) { 109 ZF_LOGE("Condition variable passed to sync_cv_signal is NULL"); 110 return -1; 111 } 112 if (cv->waiters > 0) { 113 seL4_Signal(cv->notification.cptr); 114 } 115 116 return 0; 117} 118 119/* Broadcast to a condition variable. 120 * This assumes that you hold the lock and notifies all waiters 121 * @param cv The condition variable to broadcast to. 122 * @return 0 on success, an error code on failure. */ 123static inline int sync_cv_broadcast(sync_cv_t *cv) 124{ 125 if (cv == NULL) { 126 ZF_LOGE("Condition variable passed to sync_cv_broadcast is NULL"); 127 return -1; 128 } 129 130 if (cv->waiters > 0) { 131 cv->broadcasting = true; 132 seL4_Signal(cv->notification.cptr); 133 } 134 135 return 0; 136} 137 138/* Broadcast to a condition variable and release the lock. 139 * This function is useful in situations where the scheduler might wake up a 140 * waiter immediately after a signal (i.e. if the broadcaster is lower priority). 141 * For performance reasons it is useful to release the lock prior to signalling 142 * the condition variable in this case. 143 * @param cv The condition variable to broadcast to. 144 * @return 0 on success, an error code on failure. */ 145static inline int sync_cv_broadcast_release(sync_bin_sem_t *lock, sync_cv_t *cv) 146{ 147 if (cv == NULL) { 148 ZF_LOGE("Condition variable passed to sync_cv_broadcast_release is NULL"); 149 return -1; 150 } 151 152 if (cv->waiters > 0) { 153 cv->broadcasting = true; 154 155 int error = sync_bin_sem_post(lock); 156 if (error != 0) { 157 return error; 158 } 159 160 seL4_Signal(cv->notification.cptr); 161 return 0; 162 } else { 163 return sync_bin_sem_post(lock); 164 } 165} 166 167/* Initialise a managed condition variable. 168 * @param vka A VKA instance used to allocate the notification object. 169 * @param cv A condition variable object to initialise. 170 * @return 0 on success, an error code on failure. */ 171static inline int sync_cv_new(vka_t *vka, sync_cv_t *cv) 172{ 173 if (cv == NULL) { 174 ZF_LOGE("Condition variable passed to sync_cv_new is NULL"); 175 return -1; 176 } 177 178 int error = vka_alloc_notification(vka, &(cv->notification)); 179 180 if (error != 0) { 181 return error; 182 } else { 183 return sync_cv_init(cv, cv->notification.cptr); 184 } 185} 186 187/* Destroy a managed condition variable. 188 * Uses the passed vka instance to deallocate the notification object. 189 * This function is not to be used on unmanaged condition variables. 190 * @param vka A VKA instance used to deallocate the notification object. 191 * @param cv A condition variable object initialised by sync_cv_new. 192 * @return 0 on success, an error code on failure. */ 193static inline int sync_cv_destroy(vka_t *vka, sync_cv_t *cv) 194{ 195 if (cv == NULL) { 196 ZF_LOGE("Condition variable passed to sync_cv_destroy is NULL"); 197 return -1; 198 } 199 vka_free_object(vka, &(cv->notification)); 200 return 0; 201} 202