blob: b3f8851e9061170b326ed86e56841ee2801a0bd2 [file] [log] [blame]
/*
* 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;
}