From a50a91c16021c29f6ba34167f324e427f7ca66c8 Mon Sep 17 00:00:00 2001 From: Chris Feger Date: Tue, 2 May 2023 14:15:47 -0700 Subject: [PATCH] Add timeout versions for LightLock, RecursiveLock, and LightSemaphore --- libctru/include/3ds/synchronization.h | 25 ++++ libctru/source/synchronization.c | 159 ++++++++++++++++++++++++-- 2 files changed, 176 insertions(+), 8 deletions(-) mode change 100755 => 100644 libctru/include/3ds/synchronization.h diff --git a/libctru/include/3ds/synchronization.h b/libctru/include/3ds/synchronization.h old mode 100755 new mode 100644 index d8edfdc..3e64dc7 --- a/libctru/include/3ds/synchronization.h +++ b/libctru/include/3ds/synchronization.h @@ -176,6 +176,14 @@ Result syncArbitrateAddressWithTimeout(s32* addr, ArbitrationType type, s32 valu */ void LightLock_Init(LightLock* lock); +/** + * @brief Attempts to lock a light lock, failing if it exceeeds a timeout. + * @param lock Pointer to the lock. + * @param timeout_ns Timeout in nanoseconds. + * @return Zero on success, non-zero on failure. + */ +int LightLock_LockTimeout(LightLock* lock, s64 timeout_ns); + /** * @brief Locks a light lock. * @param lock Pointer to the lock. @@ -201,6 +209,14 @@ void LightLock_Unlock(LightLock* lock); */ void RecursiveLock_Init(RecursiveLock* lock); +/** + * @brief Attempts to lock a recursive lock, failing if it exceeeds a timeout. + * @param lock Pointer to the lock. + * @param timeout_ns Timeout in nanoseconds. + * @return Zero on success, non-zero on failure. + */ +int RecursiveLock_LockTimeout(RecursiveLock* lock, s64 timeout_ns); + /** * @brief Locks a recursive lock. * @param lock Pointer to the lock. @@ -321,6 +337,15 @@ int LightEvent_WaitTimeout(LightEvent* event, s64 timeout_ns); */ void LightSemaphore_Init(LightSemaphore* semaphore, s16 initial_count, s16 max_count); +/** + * @brief Attempts to acquire a light semaphore, with a timeout. + * @param semaphore Pointer to the semaphore. + * @param count Acquire count + * @param timeout_ns Timeout in nanoseconds + * @return Zero on success, non-zero on failure + */ +int LightSemaphore_AcquireTimeout(LightSemaphore* semaphore, s32 count, s64 timeout_ns); + /** * @brief Acquires a light semaphore. * @param semaphore Pointer to the semaphore. diff --git a/libctru/source/synchronization.c b/libctru/source/synchronization.c index fe2ade3..266c2e5 100644 --- a/libctru/source/synchronization.c +++ b/libctru/source/synchronization.c @@ -1,9 +1,17 @@ #include +#include #include <3ds/types.h> #include <3ds/svc.h> #include <3ds/result.h> #include <3ds/synchronization.h> +#define RES_IS_TIMEOUT(res) (R_DESCRIPTION(res) == RD_TIMEOUT) + +static inline s64 calc_new_timeout(s64 timeout_ns, struct timespec* startTime, struct timespec* currentTime) +{ + return timeout_ns - (currentTime->tv_sec - startTime->tv_sec) * 1000000000ULL - (currentTime->tv_nsec - startTime->tv_nsec); +} + static Handle arbiter; Result __sync_init(void) @@ -82,6 +90,72 @@ void LightLock_Lock(LightLock* lock) __dmb(); } +int LightLock_LockTimeout(LightLock* lock, s64 timeout_ns) +{ + s32 val; + bool bAlreadyLocked; + + // Try to lock, or if that's not possible, increment the number of waiting threads + do + { + // Read the current lock state + val = __ldrex(lock); + if (val == 0) val = 1; // 0 is an invalid state - treat it as 1 (unlocked) + bAlreadyLocked = val < 0; + + // Calculate the desired next state of the lock + if (!bAlreadyLocked) + val = -val; // transition into locked state + else + --val; // increment the number of waiting threads (which has the sign reversed during locked state) + } while (__strex(lock, val)); + + // If the lock is held by a different thread, we need to do some timing initialization + if (bAlreadyLocked) + { + struct timespec startTime; + clock_gettime(CLOCK_MONOTONIC, &startTime); + struct timespec currentTime = startTime; + + // While the lock is held by a different thread: + do + { + s64 target_ns = calc_new_timeout(timeout_ns, &startTime, ¤tTime); + // Wait for the lock holder thread to wake us up + Result res = syncArbitrateAddressWithTimeout(lock, ARBITRATION_WAIT_IF_LESS_THAN, 0, target_ns); + if (RES_IS_TIMEOUT(res)) + { + return 1; + } + + // Try to lock again + do + { + // Read the current lock state + val = __ldrex(lock); + bAlreadyLocked = val < 0; + + // Calculate the desired next state of the lock + if (!bAlreadyLocked) + val = -(val-1); // decrement the number of waiting threads *and* transition into locked state + else + { + // Since the lock is still held, we need to cancel the atomic update and wait again + __clrex(); + + // Get the time for the new wait as well + clock_gettime(CLOCK_MONOTONIC, ¤tTime); + break; + } + } while (__strex(lock, val)); + } while (bAlreadyLocked); + } + + __dmb(); + + return 0; +} + int LightLock_TryLock(LightLock* lock) { s32 val; @@ -132,6 +206,21 @@ void RecursiveLock_Lock(RecursiveLock* lock) lock->counter ++; } +int RecursiveLock_LockTimeout(RecursiveLock* lock, s64 timeout_ns) +{ + u32 tag = (u32)getThreadLocalStorage(); + if (lock->thread_tag != tag) + { + if (LightLock_LockTimeout(&lock->lock, timeout_ns) != 0) + { + return 1; + } + lock->thread_tag = tag; + } + lock->counter ++; + return 0; +} + int RecursiveLock_TryLock(RecursiveLock* lock) { u32 tag = (u32)getThreadLocalStorage(); @@ -203,7 +292,7 @@ int CondVar_WaitTimeout(CondVar* cv, LightLock* lock, s64 timeout_ns) bool timedOut = false; Result rc = syncArbitrateAddressWithTimeout(cv, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, 0, timeout_ns); - if (R_DESCRIPTION(rc) == RD_TIMEOUT) + if (RES_IS_TIMEOUT(rc)) { timedOut = CondVar_EndWait(cv, 1); __dmb(); @@ -329,15 +418,18 @@ void LightEvent_Wait(LightEvent* event) int LightEvent_WaitTimeout(LightEvent* event, s64 timeout_ns) { - Result timeoutRes = 0x09401BFE; - Result res = 0; + Result res = 0; - while (res != timeoutRes) + struct timespec startTime; + clock_gettime(CLOCK_MONOTONIC, &startTime); + struct timespec currentTime = startTime; + + for(;;) { if (event->state == CLEARED_STICKY) { res = syncArbitrateAddressWithTimeout(&event->state, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, SIGNALED_ONESHOT, timeout_ns); - return res == timeoutRes; + return RES_IS_TIMEOUT(res); } if (event->state != CLEARED_ONESHOT) @@ -349,10 +441,17 @@ int LightEvent_WaitTimeout(LightEvent* event, s64 timeout_ns) return 0; } - res = syncArbitrateAddressWithTimeout(&event->state, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, SIGNALED_ONESHOT, timeout_ns); - } + s64 target_ns = calc_new_timeout(timeout_ns, &startTime, ¤tTime); - return res == timeoutRes; + res = syncArbitrateAddressWithTimeout(&event->state, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, SIGNALED_ONESHOT, target_ns); + + if (RES_IS_TIMEOUT(res)) + { + return 1; + } + + clock_gettime(CLOCK_MONOTONIC, ¤tTime); + } } void LightSemaphore_Init(LightSemaphore* semaphore, s16 initial_count, s16 max_count) @@ -391,6 +490,50 @@ void LightSemaphore_Acquire(LightSemaphore* semaphore, s32 count) __dmb(); } +int LightSemaphore_AcquireTimeout(LightSemaphore* semaphore, s32 count, s64 timeout_ns) +{ + s32 old_count; + s16 num_threads_acq; + + struct timespec startTime; + clock_gettime(CLOCK_MONOTONIC, &startTime); + struct timespec currentTime = startTime; + + do + { + for (;;) + { + old_count = __ldrex(&semaphore->current_count); + if (old_count >= count) + break; + __clrex(); + + do + num_threads_acq = (s16)__ldrexh((u16 *)&semaphore->num_threads_acq); + while (__strexh((u16 *)&semaphore->num_threads_acq, num_threads_acq + 1)); + + s64 target_ns = calc_new_timeout(timeout_ns, &startTime, ¤tTime); + + Result res = syncArbitrateAddressWithTimeout(&semaphore->current_count, ARBITRATION_WAIT_IF_LESS_THAN, count, target_ns); + + if (RES_IS_TIMEOUT(res)) + { + return 1; + } + + clock_gettime(CLOCK_MONOTONIC, ¤tTime); + + do + num_threads_acq = (s16)__ldrexh((u16 *)&semaphore->num_threads_acq); + while (__strexh((u16 *)&semaphore->num_threads_acq, num_threads_acq - 1)); + } + } while (__strex(&semaphore->current_count, old_count - count)); + + __dmb(); + + return 0; +} + int LightSemaphore_TryAcquire(LightSemaphore* semaphore, s32 count) { s32 old_count;