mirror of
https://github.com/switchbrew/libnx.git
synced 2025-08-07 08:39:24 +02:00
Add light event synchronization primitive [4.0.0+]
This commit is contained in:
parent
dee3a7a241
commit
e0c1dfe2ce
@ -25,6 +25,7 @@ extern "C" {
|
|||||||
#include "switch/kernel/shmem.h"
|
#include "switch/kernel/shmem.h"
|
||||||
#include "switch/kernel/mutex.h"
|
#include "switch/kernel/mutex.h"
|
||||||
#include "switch/kernel/event.h"
|
#include "switch/kernel/event.h"
|
||||||
|
#include "switch/kernel/levent.h"
|
||||||
#include "switch/kernel/uevent.h"
|
#include "switch/kernel/uevent.h"
|
||||||
#include "switch/kernel/utimer.h"
|
#include "switch/kernel/utimer.h"
|
||||||
#include "switch/kernel/rwlock.h"
|
#include "switch/kernel/rwlock.h"
|
||||||
|
54
nx/include/switch/kernel/levent.h
Normal file
54
nx/include/switch/kernel/levent.h
Normal file
@ -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);
|
191
nx/source/kernel/levent.c
Normal file
191
nx/source/kernel/levent.c
Normal file
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user