diff --git a/nx/include/switch/kernel/uevent.h b/nx/include/switch/kernel/uevent.h index e26896c7..cd2a9d66 100644 --- a/nx/include/switch/kernel/uevent.h +++ b/nx/include/switch/kernel/uevent.h @@ -19,8 +19,8 @@ struct UEvent { static inline Waiter waiterForUEvent(UEvent* e) { Waiter wait_obj; - wait_obj.type = WaiterType_UEvent; - wait_obj.event = e; + wait_obj.type = WaiterType_Waitable; + wait_obj.waitable = &e->waitable; return wait_obj; } diff --git a/nx/include/switch/kernel/utimer.h b/nx/include/switch/kernel/utimer.h index fb7b98bd..cc96bf8c 100644 --- a/nx/include/switch/kernel/utimer.h +++ b/nx/include/switch/kernel/utimer.h @@ -25,8 +25,8 @@ struct UTimer { static inline Waiter waiterForUTimer(UTimer* t) { Waiter wait_obj; - wait_obj.type = WaiterType_UTimer; - wait_obj.timer = t; + wait_obj.type = WaiterType_Waitable; + wait_obj.waitable = &t->waitable; return wait_obj; } diff --git a/nx/include/switch/kernel/wait.h b/nx/include/switch/kernel/wait.h index d35d6476..1b8f34e5 100644 --- a/nx/include/switch/kernel/wait.h +++ b/nx/include/switch/kernel/wait.h @@ -10,6 +10,7 @@ // Implementation details. typedef struct Waitable Waitable; +typedef struct WaitableMethods WaitableMethods; typedef struct WaitableNode WaitableNode; struct WaitableNode { @@ -18,6 +19,7 @@ struct WaitableNode { }; struct Waitable { + const WaitableMethods* vt; WaitableNode list; Mutex mutex; }; @@ -25,8 +27,7 @@ struct Waitable { typedef enum { WaiterType_Handle, WaiterType_HandleWithClear, - WaiterType_UTimer, - WaiterType_UEvent, + WaiterType_Waitable, } WaiterType; // User-facing API starts here. @@ -37,8 +38,7 @@ typedef struct { union { Handle handle; - struct UTimer* timer; - struct UEvent* event; + Waitable* waitable; }; } Waiter; diff --git a/nx/source/kernel/uevent.c b/nx/source/kernel/uevent.c index 0407ac53..7ee29b6a 100644 --- a/nx/source/kernel/uevent.c +++ b/nx/source/kernel/uevent.c @@ -3,11 +3,21 @@ #include "kernel/svc.h" #include "kernel/mutex.h" #include "kernel/uevent.h" -#include "uevent.h" +#include "wait.h" + +static bool _ueventBeginWait(Waitable* ww, WaiterNode* w, u64 cur_tick, u64* next_tick); +static Result _ueventOnTimeout(Waitable* ww, u64 old_tick); +static Result _ueventOnSignal(Waitable* ww); + +static const WaitableMethods g_ueventVt = { + .beginWait = _ueventBeginWait, + .onTimeout = _ueventOnTimeout, + .onSignal = _ueventOnSignal, +}; void ueventCreate(UEvent* e, bool auto_clear) { - _waitableInitialize(&e->waitable); + _waitableInitialize(&e->waitable, &g_ueventVt); e->signal = false; e->auto_clear = auto_clear; @@ -28,22 +38,29 @@ void ueventSignal(UEvent* e) mutexUnlock(&e->waitable.mutex); } -Result _ueventTryAutoClear(UEvent* e) +Result _ueventOnSignal(Waitable* ww) { + UEvent* e = (UEvent*)ww; Result rc = 0; mutexLock(&e->waitable.mutex); + + // Try to auto-clear the event. If auto-clear is enabled but + // the event is not signalled, that means the state of the + // event has changed and thus we need to retry the wait. if (e->auto_clear) { if (e->signal) e->signal = false; else rc = KERNELRESULT(Cancelled); } + mutexUnlock(&e->waitable.mutex); return rc; } -bool _ueventAddListener(UEvent* e, WaiterNode* w) +bool _ueventBeginWait(Waitable* ww, WaiterNode* w, u64 cur_tick, u64* next_tick) { + UEvent* e = (UEvent*)ww; mutexLock(&e->waitable.mutex); bool can_add = !e->signal; @@ -56,3 +73,9 @@ bool _ueventAddListener(UEvent* e, WaiterNode* w) mutexUnlock(&e->waitable.mutex); return can_add; } + +Result _ueventOnTimeout(Waitable* ww, u64 old_tick) +{ + // This is not supposed to happen. + return KERNELRESULT(Cancelled); +} diff --git a/nx/source/kernel/uevent.h b/nx/source/kernel/uevent.h deleted file mode 100644 index 7881f8e1..00000000 --- a/nx/source/kernel/uevent.h +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2018 plutoo -#pragma once -#include "kernel/uevent.h" -#include "wait.h" - -Result _ueventTryAutoClear(UEvent* e); -bool _ueventAddListener(UEvent* e, WaiterNode* w); diff --git a/nx/source/kernel/utimer.c b/nx/source/kernel/utimer.c index 95089d4e..06df8670 100644 --- a/nx/source/kernel/utimer.c +++ b/nx/source/kernel/utimer.c @@ -1,14 +1,25 @@ // Copyright 2018 plutoo +#include "result.h" +#include "arm/counter.h" #include "kernel/svc.h" #include "kernel/utimer.h" -#include "arm/counter.h" #include "wait.h" #define STOPPED 0 +static bool _utimerBeginWait(Waitable* ww, WaiterNode* w, u64 cur_tick, u64* next_tick); +static Result _utimerOnTimeout(Waitable* ww, u64 old_tick); +static Result _utimerOnSignal(Waitable* ww); + +static const WaitableMethods g_utimerVt = { + .beginWait = _utimerBeginWait, + .onTimeout = _utimerOnTimeout, + .onSignal = _utimerOnSignal, +}; + void utimerCreate(UTimer* t, u64 interval, TimerType type) { - _waitableInitialize(&t->waitable); + _waitableInitialize(&t->waitable, &g_utimerVt); t->next_tick = STOPPED; t->interval = armNsToTicks(interval); @@ -40,10 +51,8 @@ void utimerStop(UTimer* t) mutexUnlock(&t->waitable.mutex); } -void _utimerRecalculate(UTimer* t, u64 old_tick) +static void _utimerRecalculate(UTimer* t, u64 old_tick) { - mutexLock(&t->waitable.mutex); - if (t->next_tick == old_tick) { u64 interval = t->interval; u64 new_tick = 0; @@ -53,30 +62,54 @@ void _utimerRecalculate(UTimer* t, u64 old_tick) new_tick = STOPPED; break; case TimerType_Repeating: - new_tick = old_tick + ((svcGetSystemTick() - old_tick + interval - 1)/interval)*interval; + new_tick = old_tick + ((armGetSystemTick() - old_tick + interval - 1)/interval)*interval; break; } t->next_tick = new_tick; } - - mutexUnlock(&t->waitable.mutex); } -u64 _utimerGetNextTick(UTimer* t) +static bool _utimerBeginWait(Waitable* ww, WaiterNode* w, u64 cur_tick, u64* next_tick) { - u64 ret; - + UTimer* t = (UTimer*)ww; mutexLock(&t->waitable.mutex); - ret = t->next_tick; - mutexUnlock(&t->waitable.mutex); - return ret; + // By default we do want to perform a wait, because if the + // timer's start/stop state changes we want to detect that. + u64 timer_tick = t->next_tick; + bool do_wait = true; + + // Skip timer if stopped. + if (timer_tick != STOPPED) { + s64 diff = timer_tick - cur_tick; + + // If the timer is already signalled, we're done. + if (diff < 0) { + _utimerRecalculate(t, timer_tick); + do_wait = false; + } else + *next_tick = diff; + } + + if (do_wait) + _waiterNodeAdd(w); + + mutexUnlock(&t->waitable.mutex); + return do_wait; } -void _utimerAddListener(UTimer* t, WaiterNode* w) +static Result _utimerOnTimeout(Waitable* ww, u64 old_tick) { + UTimer* t = (UTimer*)ww; mutexLock(&t->waitable.mutex); - _waiterNodeAdd(w); + _utimerRecalculate(t, old_tick); mutexUnlock(&t->waitable.mutex); + return 0; +} + +static Result _utimerOnSignal(Waitable* ww) +{ + // Timer state changed, so we need to retry the wait. + return KERNELRESULT(Cancelled); } diff --git a/nx/source/kernel/utimer.h b/nx/source/kernel/utimer.h deleted file mode 100644 index c4497608..00000000 --- a/nx/source/kernel/utimer.h +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2018 plutoo -#pragma once -#include "kernel/utimer.h" -#include "wait.h" - -void _utimerRecalculate(UTimer* t, u64 old_tick); -u64 _utimerGetNextTick(UTimer* t); -void _utimerAddListener(UTimer* t, WaiterNode* w); diff --git a/nx/source/kernel/wait.c b/nx/source/kernel/wait.c index a61639e8..b6beee62 100644 --- a/nx/source/kernel/wait.c +++ b/nx/source/kernel/wait.c @@ -6,8 +6,6 @@ #include "kernel/uevent.h" #include "arm/counter.h" #include "wait.h" -#include "utimer.h" -#include "uevent.h" #include "../internal.h" #define MAX_WAIT 0x40 @@ -39,7 +37,7 @@ static Result waitImpl(s32* idx_out, Waiter* objects, size_t num_objects, u64 ti for (i = 0; i < num_objects; i ++) { Waiter* obj = &objects[i]; - u64 timer_tick; + u64 next_tick; bool added; switch (obj->type) { @@ -49,49 +47,25 @@ static Result waitImpl(s32* idx_out, Waiter* objects, size_t num_objects, u64 ti handles[i] = obj->handle; break; - case WaiterType_UTimer: - timer_tick = _utimerGetNextTick(obj->timer); - - // Skip timer if stopped. - if (timer_tick != 0) { - // If the timer already signalled, we're done. - if (timer_tick < cur_tick) { - _utimerRecalculate(obj->timer, timer_tick); - - *idx_out = i; - rc = 0; - goto clean_up; - } - - // Override the user-supplied timeout if timer would fire before that. - if ((timer_tick - cur_tick) < end_tick) { - end_tick = timer_tick - cur_tick; - end_tick_idx = i; - } - } - - // Always add a listener on the timer, - // If the timer is started/stopped we want to detect that. - _waiterNodeInitialize(&waiters[i], &obj->timer->waitable, own_thread_handle, i, &triggered_idx); - _utimerAddListener(obj->timer, &waiters[i]); - - waiters_added |= 1UL << i; - handles[i] = dummy_handle; - break; - - case WaiterType_UEvent: - // Try to add a listener to the event, if it hasn't already signalled. - _waiterNodeInitialize(&waiters[i], &obj->event->waitable, own_thread_handle, i, &triggered_idx); - added = _ueventAddListener(obj->event, &waiters[i]); - - // If the event already happened, we're done. + case WaiterType_Waitable: + // Try to wait on the object. If it doesn't add a listener for this thread then + // it means the object is signalled and we're already done. + next_tick = UINT64_MAX; + _waiterNodeInitialize(&waiters[i], obj->waitable, own_thread_handle, i, &triggered_idx); + added = obj->waitable->vt->beginWait(obj->waitable, &waiters[i], cur_tick, &next_tick); if (!added) { *idx_out = i; rc = 0; goto clean_up; } - // If the event hasn't signalled, we added a listener. + // Otherwise, override the user-supplied timeout if the object specified an earlier timeout. + if (next_tick < end_tick) { + end_tick = next_tick; + end_tick_idx = i; + } + + // Add (fake) handle to the array. waiters_added |= 1UL << i; handles[i] = dummy_handle; break; @@ -116,11 +90,11 @@ static Result waitImpl(s32* idx_out, Waiter* objects, size_t num_objects, u64 ti if (end_tick_idx == -1) goto clean_up; - // If not, it means a timer triggered the timeout. - _utimerRecalculate(objects[end_tick_idx].timer, end_tick + cur_tick); - - *idx_out = end_tick_idx; - rc = 0; + // If not, it means an object triggered the timeout; handle it. + Waitable* w = objects[end_tick_idx].waitable; + rc = w->vt->onTimeout(w, end_tick + cur_tick); + if (R_SUCCEEDED(rc)) + *idx_out = end_tick_idx; } else if (R_VALUE(rc) == KERNELRESULT(Cancelled)) { // If no listener filled in its own index, we return the cancelled error back to caller. // This only happens if user for some reason manually does a svcCancelSynchronization. @@ -128,26 +102,11 @@ static Result waitImpl(s32* idx_out, Waiter* objects, size_t num_objects, u64 ti if (triggered_idx == -1) goto clean_up; - // An event was signalled, or a timer was updated. - // So.. which is it? - switch (objects[triggered_idx].type) { - default: - break; - - case WaiterType_UEvent: - // Try to auto-clear the event. If auto-clear is enabled but - // the event is not signalled, that means the state of the - // event has changed and thus we need to retry the wait. - rc = _ueventTryAutoClear(objects[triggered_idx].event); - if (R_SUCCEEDED(rc)) - *idx_out = triggered_idx; - break; - - case WaiterType_UTimer: - // Timer state changed, so we need to retry the wait. - rc = KERNELRESULT(Cancelled); - break; - } + // An object was signalled, handle it. + Waitable* w = objects[triggered_idx].waitable; + rc = w->vt->onSignal(w); + if (R_SUCCEEDED(rc)) + *idx_out = triggered_idx; } clean_up: diff --git a/nx/source/kernel/wait.h b/nx/source/kernel/wait.h index 4c17586e..406d2814 100644 --- a/nx/source/kernel/wait.h +++ b/nx/source/kernel/wait.h @@ -14,9 +14,16 @@ struct WaiterNode { s32 idx; }; -static inline void _waitableInitialize(Waitable* ww) +struct WaitableMethods { + bool (* beginWait)(Waitable* ww, WaiterNode* w, u64 cur_tick, u64* next_tick); + Result (* onTimeout)(Waitable* ww, u64 old_tick); + Result (* onSignal)(Waitable* ww); +}; + +static inline void _waitableInitialize(Waitable* ww, const WaitableMethods* vt) { mutexInit(&ww->mutex); + ww->vt = vt; ww->list.next = &ww->list; ww->list.prev = &ww->list; }