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