From e0c1dfe2cee43364b2757922f9c58fa861ccd075 Mon Sep 17 00:00:00 2001 From: fincs Date: Tue, 4 Aug 2020 13:07:47 +0200 Subject: [PATCH] Add light event synchronization primitive [4.0.0+] --- nx/include/switch.h | 1 + nx/include/switch/kernel/levent.h | 54 +++++++++ nx/source/kernel/levent.c | 191 ++++++++++++++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 nx/include/switch/kernel/levent.h create mode 100644 nx/source/kernel/levent.c diff --git a/nx/include/switch.h b/nx/include/switch.h index 62734024..1a21e546 100644 --- a/nx/include/switch.h +++ b/nx/include/switch.h @@ -25,6 +25,7 @@ extern "C" { #include "switch/kernel/shmem.h" #include "switch/kernel/mutex.h" #include "switch/kernel/event.h" +#include "switch/kernel/levent.h" #include "switch/kernel/uevent.h" #include "switch/kernel/utimer.h" #include "switch/kernel/rwlock.h" diff --git a/nx/include/switch/kernel/levent.h b/nx/include/switch/kernel/levent.h new file mode 100644 index 00000000..d83af2c8 --- /dev/null +++ b/nx/include/switch/kernel/levent.h @@ -0,0 +1,54 @@ +/** + * @file levent.h + * @brief Light event synchronization primitive [4.0.0+] + * @author fincs + * @copyright libnx Authors + */ +#pragma once +#include "../types.h" +#include "../result.h" +#include "svc.h" + +/// User-mode light event structure. +typedef struct LEvent { + u32 counter; + bool autoclear; +} LEvent; + +/** + * @brief Initializes a user-mode light event. + * @param[out] le Pointer to \ref LEvent structure. + * @param[in] signaled Whether the event starts off in signaled state. + * @param[in] autoclear Autoclear flag. + */ +NX_CONSTEXPR void leventInit(LEvent* le, bool signaled, bool autoclear) { + le->counter = signaled ? 2 : 0; + le->autoclear = autoclear; +} + +/** + * @brief Waits on a user-mode light event. + * @param[in] le Pointer to \ref LEvent structure. + * @param[in] timeout_ns Timeout in nanoseconds (pass UINT64_MAX to wait indefinitely). + * @return true if wait succeeded, false if wait timed out. + */ +bool leventWait(LEvent* le, u64 timeout_ns); + +/** + * @brief Polls a user-mode light event. + * @param[in] le Pointer to \ref LEvent structure. + * @return true if event is signaled, false otherwise. + */ +bool leventTryWait(LEvent* le); + +/** + * @brief Signals a user-mode light event. + * @param[in] le Pointer to \ref LEvent structure. + */ +void leventSignal(LEvent* le); + +/** + * @brief Clears a user-mode light event. + * @param[in] le Pointer to \ref LEvent structure. + */ +void leventClear(LEvent* le); diff --git a/nx/source/kernel/levent.c b/nx/source/kernel/levent.c new file mode 100644 index 00000000..0dfcc21b --- /dev/null +++ b/nx/source/kernel/levent.c @@ -0,0 +1,191 @@ +#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(0, 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(0, 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(0, 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); +}