libnx/nx/source/kernel/levent.c

192 lines
5.7 KiB
C

#include "result.h"
#include "arm/counter.h"
#include "kernel/svc.h"
#include "kernel/levent.h"
/*
Possible states for a light event's counter:
0 - Unsignaled without waiters
1 - Unsignaled with waiters
2 - Signaled
*/
NX_INLINE u32 _LoadExclusive(u32 *ptr) {
u32 value;
__asm__ __volatile__("ldaxr %w[value], %[ptr]" : [value]"=&r"(value) : [ptr]"Q"(*ptr) : "memory");
return value;
}
NX_INLINE bool _StoreExclusive(u32 *ptr, u32 value) {
u32 result;
__asm__ __volatile__("stlxr %w[result], %w[value], %[ptr]" : [result]"=&r"(result) : [value]"r"(value), [ptr]"Q"(*ptr) : "memory");
return result != 0;
}
NX_INLINE void _ClearExclusive(void) {
__asm__ __volatile__("clrex" ::: "memory");
}
static bool _leventTryWait(u32* counter) {
u32 val = __atomic_load_n(counter, __ATOMIC_SEQ_CST);
return val == 2;
}
static bool _leventTryWaitAndClear(u32* counter) {
u32 val;
do {
val = _LoadExclusive(counter);
if (val != 2) {
// State isn't "signaled", so we fail.
_ClearExclusive();
return false;
}
} while (_StoreExclusive(counter, 0));
return true;
}
static bool _leventWait(u32* counter, bool clear, u64 timeout_ns) {
const bool has_timeout = timeout_ns != UINT64_MAX;
u64 deadline = 0;
if (has_timeout) {
deadline = armGetSystemTick() + armNsToTicks(timeout_ns); // timeout: ns->ticks
}
u32 val = __atomic_load_n(counter, __ATOMIC_SEQ_CST);
while (val != 2) {
if (val == 0) {
// State is "unsignaled without waiters" -> transition to "unsignaled with waiters"
do {
val = _LoadExclusive(counter);
if (val != 0) {
// Unexpected state -> try again
_ClearExclusive();
break;
}
} while (_StoreExclusive(counter, (val = 1)));
}
else if (val == 1) {
// State is "unsignaled with waiters" -> let's actually wait!
s64 this_timeout = -1;
if (has_timeout) {
s64 remaining = deadline - armGetSystemTick();
if (remaining <= 0)
return false; // whoops, timed out
this_timeout = armTicksToNs(remaining); // ticks->ns
}
Result res = svcWaitForAddress(counter, ArbitrationType_WaitIfEqual, 1, this_timeout);
if (R_FAILED(res)) {
if (R_VALUE(res) == KERNELRESULT(TimedOut))
return false; // whoops, timed out
if (R_VALUE(res) != KERNELRESULT(InvalidState))
svcBreak(BreakReason_Assert, 0, 0); // should not happen
}
if (clear) {
// If clear is requested, transition to "unsignaled without waiters"
do {
val = _LoadExclusive(counter);
if (val != 2) {
// Unexpected state -> try again
_ClearExclusive();
break;
}
} while (_StoreExclusive(counter, 0));
}
else {
// Otherwise the wait is done
val = 2;
}
}
else {
// Invalid state - should not happen
svcBreak(BreakReason_Assert, 0, 0);
}
}
return true;
}
static Result _leventSignalImpl(u32* counter, SignalType type, s32 count) {
u32 val;
bool try_again;
// Attempt to transition to "signaled" state
do {
try_again = true;
val = _LoadExclusive(counter);
if (val != 0) {
// We are not in "unsignaled without waiters" state, so we can't use a RMW loop.
_ClearExclusive();
if (val == 1) {
// State is "unsignaled with waiters" -> let's do it using the arbiter instead!
Result res = svcSignalToAddress(counter, type, 1, count);
if (R_SUCCEEDED(res))
return 0;
if (R_VALUE(res) != KERNELRESULT(InvalidState))
return res; // shouldn't happen
continue; // unexpected state -> try the RMW loop again
}
else if (val == 2) {
// State is already "signaled" - so we don't need to do anything
return 0;
}
else {
// Invalid state - should not happen
return KERNELRESULT(InvalidState);
}
}
try_again = _StoreExclusive(counter, 2);
} while (try_again);
return 0;
}
static void _leventSignal(u32* counter, bool autoclear) {
Result res;
if (autoclear) {
res = _leventSignalImpl(counter, SignalType_SignalAndModifyBasedOnWaitingThreadCountIfEqual, 1);
} else {
res = _leventSignalImpl(counter, SignalType_SignalAndIncrementIfEqual, -1);
}
if (R_FAILED(res))
svcBreak(BreakReason_Assert, 0, 0); // should not happen
}
static void _leventClear(u32* counter) {
u32 val;
do {
val = _LoadExclusive(counter);
if (val != 2) {
// State isn't "signaled" so we do nothing
_ClearExclusive();
break;
}
} while (_StoreExclusive(counter, 0));
}
bool leventWait(LEvent* le, u64 timeout_ns) {
return _leventWait(&le->counter, le->autoclear, timeout_ns);
}
bool leventTryWait(LEvent* le) {
if (le->autoclear) {
return _leventTryWaitAndClear(&le->counter);
} else {
return _leventTryWait(&le->counter);
}
}
void leventSignal(LEvent* le) {
_leventSignal(&le->counter, le->autoclear);
}
void leventClear(LEvent* le) {
_leventClear(&le->counter);
}