|  | /* | 
|  | * Copyright 2017, Data61 | 
|  | * Commonwealth Scientific and Industrial Research Organisation (CSIRO) | 
|  | * ABN 41 687 119 230. | 
|  | * | 
|  | * This software may be distributed and modified according to the terms of | 
|  | * the BSD 2-Clause license. Note that NO WARRANTY is provided. | 
|  | * See "LICENSE_BSD2.txt" for details. | 
|  | * | 
|  | * @TAG(DATA61_BSD) | 
|  | */ | 
|  |  | 
|  | #pragma once | 
|  |  | 
|  | #include <autoconf.h> | 
|  | #include <sel4/sel4.h> | 
|  | #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(seL4_DebugCapIdentify(notification) == 6); | 
|  | #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; | 
|  | } |