diff --git a/libctru/include/3ds/synchronization.h b/libctru/include/3ds/synchronization.h index 9ba9339..0f5f327 100755 --- a/libctru/include/3ds/synchronization.h +++ b/libctru/include/3ds/synchronization.h @@ -12,6 +12,9 @@ typedef _LOCK_T LightLock; /// A recursive lock. typedef _LOCK_RECURSIVE_T RecursiveLock; +/// A condition variable. +typedef s32 CondVar; + /// A light event. typedef struct { @@ -217,6 +220,53 @@ int RecursiveLock_TryLock(RecursiveLock* lock); */ void RecursiveLock_Unlock(RecursiveLock* lock); +/** + * @brief Initializes a condition variable. + * @param cv Pointer to the condition variable. + */ +void CondVar_Init(CondVar* cv); + +/** + * @brief Waits on a condition variable. + * @param cv Pointer to the condition variable. + * @param lock Pointer to the lock to atomically unlock/relock during the wait. + */ +void CondVar_Wait(CondVar* cv, LightLock* lock); + +/** + * @brief Waits on a condition variable with a timeout. + * @param cv Pointer to the condition variable. + * @param lock Pointer to the lock to atomically unlock/relock during the wait. + * @param timeout_ns Timeout in nanoseconds. + * @return Zero on success, non-zero on failure. + */ +int CondVar_WaitTimeout(CondVar* cv, LightLock* lock, s64 timeout_ns); + +/** + * @brief Wakes up threads waiting on a condition variable. + * @param cv Pointer to the condition variable. + * @param num_threads Maximum number of threads to wake up (or \ref ARBITRATION_SIGNAL_ALL to wake them all). + */ +void CondVar_WakeUp(CondVar* cv, s32 num_threads); + +/** + * @brief Wakes up a single thread waiting on a condition variable. + * @param cv Pointer to the condition variable. + */ +static inline void CondVar_Signal(CondVar* cv) +{ + CondVar_WakeUp(cv, 1); +} + +/** + * @brief Wakes up all threads waiting on a condition variable. + * @param cv Pointer to the condition variable. + */ +static inline void CondVar_Broadcast(CondVar* cv) +{ + CondVar_WakeUp(cv, ARBITRATION_SIGNAL_ALL); +} + /** * @brief Initializes a light event. * @param event Pointer to the event. diff --git a/libctru/source/synchronization.c b/libctru/source/synchronization.c index 7e343bf..dee0cfa 100644 --- a/libctru/source/synchronization.c +++ b/libctru/source/synchronization.c @@ -1,6 +1,7 @@ #include #include <3ds/types.h> #include <3ds/svc.h> +#include <3ds/result.h> #include <3ds/synchronization.h> static Handle arbiter; @@ -153,6 +154,74 @@ void RecursiveLock_Unlock(RecursiveLock* lock) } } +static inline void CondVar_BeginWait(CondVar* cv, LightLock* lock) +{ + s32 val; + do + val = __ldrex(cv) - 1; + while (!__strex(cv, val)); + LightLock_Unlock(lock); +} + +static inline bool CondVar_EndWait(CondVar* cv, s32 num_threads) +{ + bool hasWaiters; + s32 val; + + do { + val = __ldrex(cv); + hasWaiters = val < 0; + if (hasWaiters) + { + if (num_threads < 0) + val = 0; + else if (val <= -num_threads) + val += num_threads; + else + val = 0; + } + } while (!__strex(cv, val)); + + return hasWaiters; +} + +void CondVar_Init(CondVar* cv) +{ + *cv = 0; +} + +void CondVar_Wait(CondVar* cv, LightLock* lock) +{ + CondVar_BeginWait(cv, lock); + syncArbitrateAddress(cv, ARBITRATION_WAIT_IF_LESS_THAN, 0); + LightLock_Lock(lock); +} + +int CondVar_WaitTimeout(CondVar* cv, LightLock* lock, s64 timeout_ns) +{ + CondVar_BeginWait(cv, lock); + + bool timedOut = false; + Result rc = syncArbitrateAddressWithTimeout(cv, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, 0, timeout_ns); + if (R_DESCRIPTION(rc) == RD_TIMEOUT) + { + timedOut = CondVar_EndWait(cv, 1); + __dmb(); + } + + LightLock_Lock(lock); + return timedOut; +} + +void CondVar_WakeUp(CondVar* cv, s32 num_threads) +{ + __dmb(); + if (CondVar_EndWait(cv, num_threads)) + syncArbitrateAddress(cv, ARBITRATION_SIGNAL, num_threads); + else + __dmb(); +} + static inline void LightEvent_SetState(LightEvent* event, int state) { do