libctru/libctru/source/synchronization.c

427 lines
9.0 KiB
C

#include <string.h>
#include <3ds/types.h>
#include <3ds/svc.h>
#include <3ds/result.h>
#include <3ds/synchronization.h>
static Handle arbiter;
Result __sync_init(void)
{
return svcCreateAddressArbiter(&arbiter);
}
void __sync_fini(void)
{
if (arbiter)
svcCloseHandle(arbiter);
}
Result syncArbitrateAddress(s32* addr, ArbitrationType type, s32 value)
{
return svcArbitrateAddressNoTimeout(arbiter, (u32)addr, type, value);
}
Result syncArbitrateAddressWithTimeout(s32* addr, ArbitrationType type, s32 value, s64 timeout_ns)
{
return svcArbitrateAddress(arbiter, (u32)addr, type, value, timeout_ns);
}
void LightLock_Init(LightLock* lock)
{
do
__ldrex(lock);
while (__strex(lock, 1));
}
void LightLock_Lock(LightLock* lock)
{
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));
// While the lock is held by a different thread:
while (bAlreadyLocked)
{
// Wait for the lock holder thread to wake us up
syncArbitrateAddress(lock, ARBITRATION_WAIT_IF_LESS_THAN, 0);
// 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();
break;
}
} while (__strex(lock, val));
}
__dmb();
}
int LightLock_TryLock(LightLock* lock)
{
s32 val;
do
{
val = __ldrex(lock);
if (val == 0) val = 1; // 0 is an invalid state - treat it as 1 (unlocked)
if (val < 0)
{
__clrex();
return 1; // Failure
}
} while (__strex(lock, -val));
__dmb();
return 0; // Success
}
void LightLock_Unlock(LightLock* lock)
{
__dmb();
s32 val;
do
val = -__ldrex(lock);
while (__strex(lock, val));
if (val > 1)
// Wake up exactly one thread
syncArbitrateAddress(lock, ARBITRATION_SIGNAL, 1);
}
void RecursiveLock_Init(RecursiveLock* lock)
{
LightLock_Init(&lock->lock);
lock->thread_tag = 0;
lock->counter = 0;
}
void RecursiveLock_Lock(RecursiveLock* lock)
{
u32 tag = (u32)getThreadLocalStorage();
if (lock->thread_tag != tag)
{
LightLock_Lock(&lock->lock);
lock->thread_tag = tag;
}
lock->counter ++;
}
int RecursiveLock_TryLock(RecursiveLock* lock)
{
u32 tag = (u32)getThreadLocalStorage();
if (lock->thread_tag != tag)
{
if (LightLock_TryLock(&lock->lock))
return 1; // Failure
lock->thread_tag = tag;
}
lock->counter ++;
return 0; // Success
}
void RecursiveLock_Unlock(RecursiveLock* lock)
{
if (!--lock->counter)
{
lock->thread_tag = 0;
LightLock_Unlock(&lock->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();
}
// LightEvent state
enum
{
CLEARED_STICKY = -2,
CLEARED_ONESHOT = -1,
SIGNALED_ONESHOT = 0,
SIGNALED_STICKY = 1
};
static inline void LightEvent_SetState(LightEvent* event, int state)
{
do
__ldrex(&event->state);
while (__strex(&event->state, state));
}
static inline int LightEvent_TryReset(LightEvent* event)
{
__dmb();
do
{
if (__ldrex(&event->state))
{
__clrex();
return 0;
}
} while (__strex(&event->state, CLEARED_ONESHOT));
__dmb();
return 1;
}
void LightEvent_Init(LightEvent* event, ResetType reset_type)
{
LightLock_Init(&event->lock);
LightEvent_SetState(event, reset_type == RESET_STICKY ? CLEARED_STICKY : CLEARED_ONESHOT);
}
void LightEvent_Clear(LightEvent* event)
{
if (event->state == SIGNALED_STICKY)
{
LightLock_Lock(&event->lock);
LightEvent_SetState(event, CLEARED_STICKY);
LightLock_Unlock(&event->lock);
} else if (event->state == SIGNALED_ONESHOT)
{
__dmb();
LightEvent_SetState(event, CLEARED_ONESHOT);
__dmb();
}
}
void LightEvent_Pulse(LightEvent* event)
{
if (event->state == CLEARED_STICKY)
syncArbitrateAddress(&event->state, ARBITRATION_SIGNAL, -1);
else if (event->state == CLEARED_ONESHOT)
syncArbitrateAddress(&event->state, ARBITRATION_SIGNAL, 1);
else
LightEvent_Clear(event);
}
void LightEvent_Signal(LightEvent* event)
{
if (event->state == CLEARED_ONESHOT)
{
__dmb();
LightEvent_SetState(event, SIGNALED_ONESHOT);
syncArbitrateAddress(&event->state, ARBITRATION_SIGNAL, 1);
} else if (event->state == CLEARED_STICKY)
{
LightLock_Lock(&event->lock);
LightEvent_SetState(event, SIGNALED_STICKY);
syncArbitrateAddress(&event->state, ARBITRATION_SIGNAL, -1);
LightLock_Unlock(&event->lock);
}
}
int LightEvent_TryWait(LightEvent* event)
{
if (event->state == SIGNALED_STICKY)
return 1;
return LightEvent_TryReset(event);
}
void LightEvent_Wait(LightEvent* event)
{
for (;;)
{
if (event->state == CLEARED_STICKY)
{
syncArbitrateAddress(&event->state, ARBITRATION_WAIT_IF_LESS_THAN, SIGNALED_ONESHOT);
return;
}
if (event->state != CLEARED_ONESHOT)
{
if (event->state == SIGNALED_STICKY)
return;
if (event->state == SIGNALED_ONESHOT && LightEvent_TryReset(event))
return;
}
syncArbitrateAddress(&event->state, ARBITRATION_WAIT_IF_LESS_THAN, SIGNALED_ONESHOT);
}
}
int LightEvent_WaitTimeout(LightEvent* event, s64 timeout_ns)
{
Result timeoutRes = 0x09401BFE;
Result res = 0;
while (res != timeoutRes)
{
if (event->state == CLEARED_STICKY)
{
res = syncArbitrateAddressWithTimeout(&event->state, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, SIGNALED_ONESHOT, timeout_ns);
return res == timeoutRes;
}
if (event->state != CLEARED_ONESHOT)
{
if (event->state == SIGNALED_STICKY)
return 0;
if (event->state == SIGNALED_ONESHOT && LightEvent_TryReset(event))
return 0;
}
res = syncArbitrateAddressWithTimeout(&event->state, ARBITRATION_WAIT_IF_LESS_THAN_TIMEOUT, SIGNALED_ONESHOT, timeout_ns);
}
return res == timeoutRes;
}
void LightSemaphore_Init(LightSemaphore* semaphore, s16 initial_count, s16 max_count)
{
semaphore->current_count = (s32)initial_count;
semaphore->num_threads_acq = 0;
semaphore->max_count = max_count;
}
void LightSemaphore_Acquire(LightSemaphore* semaphore, s32 count)
{
s32 old_count;
s16 num_threads_acq;
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));
syncArbitrateAddress(&semaphore->current_count, ARBITRATION_WAIT_IF_LESS_THAN, count);
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();
}
int LightSemaphore_TryAcquire(LightSemaphore* semaphore, s32 count)
{
s32 old_count;
do
{
old_count = __ldrex(&semaphore->current_count);
if (old_count < count)
{
__clrex();
return 1; // failure
}
} while (__strex(&semaphore->current_count, old_count - count));
__dmb();
return 0; // success
}
void LightSemaphore_Release(LightSemaphore* semaphore, s32 count)
{
__dmb();
s32 old_count, new_count;
do
{
old_count = __ldrex(&semaphore->current_count);
new_count = old_count + count;
if (new_count >= semaphore->max_count)
new_count = semaphore->max_count;
} while (__strex(&semaphore->current_count, new_count));
if(old_count <= 0 || semaphore->num_threads_acq > 0)
syncArbitrateAddress(&semaphore->current_count, ARBITRATION_SIGNAL, count);
}