| /* |
| * Copyright 2017, Data61, CSIRO (ABN 41 687 119 230) |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| */ |
| |
| #pragma once |
| |
| #include <autoconf.h> |
| #include <sel4/sel4.h> |
| #ifdef CONFIG_DEBUG_BUILD |
| #include <sel4debug/debug.h> |
| #endif |
| #include <vka/vka.h> |
| #include <vka/object.h> |
| #include <platsupport/sync/atomic.h> |
| #include <stdbool.h> |
| |
| typedef struct { |
| vka_object_t notification; |
| volatile int waiters; |
| volatile bool broadcasting; |
| } sync_cv_t; |
| |
| /* Initialise an unmanaged condition variable |
| * @param cv A condition variable object to be initialised. |
| * @param notification A notification object to use for wake up. |
| * @return 0 on success, an error code on failure. */ |
| static inline int sync_cv_init(sync_cv_t *cv, seL4_CPtr notification) |
| { |
| if (cv == NULL) { |
| ZF_LOGE("Condition variable passed to sync_cv_init is NULL"); |
| return -1; |
| } |
| |
| #ifdef CONFIG_DEBUG_BUILD |
| /* Check the cap actually is a notification. */ |
| assert(debug_cap_is_notification(notification)); |
| #endif |
| |
| cv->notification.cptr = notification; |
| cv->waiters = 0; |
| cv->broadcasting = false; |
| return 0; |
| } |
| |
| /* Wait on a condition variable. |
| * This assumes that you already hold the lock and will block until notified |
| * by sync_cv_signal or sync_cv_broadcast. It returns once you hold the lock |
| * again. Note that a spurious wake up is possible and the condition should |
| * always be checked again after sync_cv_wait returns. |
| * @param lock The lock on the monitor. |
| * @param cv The condition variable to wait on. |
| * @return 0 on success, an error code on failure. */ |
| static inline int sync_cv_wait(sync_bin_sem_t *lock, sync_cv_t *cv) |
| { |
| if (cv == NULL) { |
| ZF_LOGE("Condition variable passed to sync_cv_wait is NULL"); |
| return -1; |
| } |
| |
| /* Increment waiters count and release the lock */ |
| cv->waiters++; |
| cv->broadcasting = false; |
| int error = sync_bin_sem_post(lock); |
| if (error != 0) { |
| return error; |
| } |
| |
| /* Wait to be notified */ |
| seL4_Wait(cv->notification.cptr, NULL); |
| |
| /* Reacquire the lock */ |
| error = sync_bin_sem_wait(lock); |
| if (error != 0) { |
| return error; |
| } |
| |
| /* Wake up and decrement waiters count */ |
| cv->waiters--; |
| |
| /* Handle the case where a broadcast is ongoing */ |
| if (cv->broadcasting) { |
| if (cv->waiters > 0) { |
| /* Signal the next thread and continue */ |
| seL4_Signal(cv->notification.cptr); |
| } else { |
| /* This is the last thread, so stop broadcasting */ |
| cv->broadcasting = false; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Signal a condition variable. |
| * This assumes that you hold the lock and notifies one waiter |
| * @param cv The condition variable to signal. |
| * @return 0 on success, an error code on failure. */ |
| static inline int sync_cv_signal(sync_cv_t *cv) |
| { |
| if (cv == NULL) { |
| ZF_LOGE("Condition variable passed to sync_cv_signal is NULL"); |
| return -1; |
| } |
| if (cv->waiters > 0) { |
| seL4_Signal(cv->notification.cptr); |
| } |
| |
| return 0; |
| } |
| |
| /* Broadcast to a condition variable. |
| * This assumes that you hold the lock and notifies all waiters |
| * @param cv The condition variable to broadcast to. |
| * @return 0 on success, an error code on failure. */ |
| static inline int sync_cv_broadcast(sync_cv_t *cv) |
| { |
| if (cv == NULL) { |
| ZF_LOGE("Condition variable passed to sync_cv_broadcast is NULL"); |
| return -1; |
| } |
| |
| if (cv->waiters > 0) { |
| cv->broadcasting = true; |
| seL4_Signal(cv->notification.cptr); |
| } |
| |
| return 0; |
| } |
| |
| /* Broadcast to a condition variable and release the lock. |
| * This function is useful in situations where the scheduler might wake up a |
| * waiter immediately after a signal (i.e. if the broadcaster is lower priority). |
| * For performance reasons it is useful to release the lock prior to signalling |
| * the condition variable in this case. |
| * @param cv The condition variable to broadcast to. |
| * @return 0 on success, an error code on failure. */ |
| static inline int sync_cv_broadcast_release(sync_bin_sem_t *lock, sync_cv_t *cv) |
| { |
| if (cv == NULL) { |
| ZF_LOGE("Condition variable passed to sync_cv_broadcast_release is NULL"); |
| return -1; |
| } |
| |
| if (cv->waiters > 0) { |
| cv->broadcasting = true; |
| |
| int error = sync_bin_sem_post(lock); |
| if (error != 0) { |
| return error; |
| } |
| |
| seL4_Signal(cv->notification.cptr); |
| return 0; |
| } else { |
| return sync_bin_sem_post(lock); |
| } |
| } |
| |
| /* Initialise a managed condition variable. |
| * @param vka A VKA instance used to allocate the notification object. |
| * @param cv A condition variable object to initialise. |
| * @return 0 on success, an error code on failure. */ |
| static inline int sync_cv_new(vka_t *vka, sync_cv_t *cv) |
| { |
| if (cv == NULL) { |
| ZF_LOGE("Condition variable passed to sync_cv_new is NULL"); |
| return -1; |
| } |
| |
| int error = vka_alloc_notification(vka, &(cv->notification)); |
| |
| if (error != 0) { |
| return error; |
| } else { |
| return sync_cv_init(cv, cv->notification.cptr); |
| } |
| } |
| |
| /* Destroy a managed condition variable. |
| * Uses the passed vka instance to deallocate the notification object. |
| * This function is not to be used on unmanaged condition variables. |
| * @param vka A VKA instance used to deallocate the notification object. |
| * @param cv A condition variable object initialised by sync_cv_new. |
| * @return 0 on success, an error code on failure. */ |
| static inline int sync_cv_destroy(vka_t *vka, sync_cv_t *cv) |
| { |
| if (cv == NULL) { |
| ZF_LOGE("Condition variable passed to sync_cv_destroy is NULL"); |
| return -1; |
| } |
| vka_free_object(vka, &(cv->notification)); |
| return 0; |
| } |