Generalize Waitable, moving UEvent/UTimer specific code into a vtable.

This commit is contained in:
fincs 2018-12-15 14:43:03 +01:00 committed by fincs
parent 1d14cad1cf
commit 8d813ee666
9 changed files with 116 additions and 109 deletions

View File

@ -19,8 +19,8 @@ struct UEvent {
static inline Waiter waiterForUEvent(UEvent* e) static inline Waiter waiterForUEvent(UEvent* e)
{ {
Waiter wait_obj; Waiter wait_obj;
wait_obj.type = WaiterType_UEvent; wait_obj.type = WaiterType_Waitable;
wait_obj.event = e; wait_obj.waitable = &e->waitable;
return wait_obj; return wait_obj;
} }

View File

@ -25,8 +25,8 @@ struct UTimer {
static inline Waiter waiterForUTimer(UTimer* t) static inline Waiter waiterForUTimer(UTimer* t)
{ {
Waiter wait_obj; Waiter wait_obj;
wait_obj.type = WaiterType_UTimer; wait_obj.type = WaiterType_Waitable;
wait_obj.timer = t; wait_obj.waitable = &t->waitable;
return wait_obj; return wait_obj;
} }

View File

@ -10,6 +10,7 @@
// Implementation details. // Implementation details.
typedef struct Waitable Waitable; typedef struct Waitable Waitable;
typedef struct WaitableMethods WaitableMethods;
typedef struct WaitableNode WaitableNode; typedef struct WaitableNode WaitableNode;
struct WaitableNode { struct WaitableNode {
@ -18,6 +19,7 @@ struct WaitableNode {
}; };
struct Waitable { struct Waitable {
const WaitableMethods* vt;
WaitableNode list; WaitableNode list;
Mutex mutex; Mutex mutex;
}; };
@ -25,8 +27,7 @@ struct Waitable {
typedef enum { typedef enum {
WaiterType_Handle, WaiterType_Handle,
WaiterType_HandleWithClear, WaiterType_HandleWithClear,
WaiterType_UTimer, WaiterType_Waitable,
WaiterType_UEvent,
} WaiterType; } WaiterType;
// User-facing API starts here. // User-facing API starts here.
@ -37,8 +38,7 @@ typedef struct {
union { union {
Handle handle; Handle handle;
struct UTimer* timer; Waitable* waitable;
struct UEvent* event;
}; };
} Waiter; } Waiter;

View File

@ -3,11 +3,21 @@
#include "kernel/svc.h" #include "kernel/svc.h"
#include "kernel/mutex.h" #include "kernel/mutex.h"
#include "kernel/uevent.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) void ueventCreate(UEvent* e, bool auto_clear)
{ {
_waitableInitialize(&e->waitable); _waitableInitialize(&e->waitable, &g_ueventVt);
e->signal = false; e->signal = false;
e->auto_clear = auto_clear; e->auto_clear = auto_clear;
@ -28,22 +38,29 @@ void ueventSignal(UEvent* e)
mutexUnlock(&e->waitable.mutex); mutexUnlock(&e->waitable.mutex);
} }
Result _ueventTryAutoClear(UEvent* e) Result _ueventOnSignal(Waitable* ww)
{ {
UEvent* e = (UEvent*)ww;
Result rc = 0; Result rc = 0;
mutexLock(&e->waitable.mutex); 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->auto_clear) {
if (e->signal) if (e->signal)
e->signal = false; e->signal = false;
else else
rc = KERNELRESULT(Cancelled); rc = KERNELRESULT(Cancelled);
} }
mutexUnlock(&e->waitable.mutex); mutexUnlock(&e->waitable.mutex);
return rc; 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); mutexLock(&e->waitable.mutex);
bool can_add = !e->signal; bool can_add = !e->signal;
@ -56,3 +73,9 @@ bool _ueventAddListener(UEvent* e, WaiterNode* w)
mutexUnlock(&e->waitable.mutex); mutexUnlock(&e->waitable.mutex);
return can_add; return can_add;
} }
Result _ueventOnTimeout(Waitable* ww, u64 old_tick)
{
// This is not supposed to happen.
return KERNELRESULT(Cancelled);
}

View File

@ -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);

View File

@ -1,14 +1,25 @@
// Copyright 2018 plutoo // Copyright 2018 plutoo
#include "result.h"
#include "arm/counter.h"
#include "kernel/svc.h" #include "kernel/svc.h"
#include "kernel/utimer.h" #include "kernel/utimer.h"
#include "arm/counter.h"
#include "wait.h" #include "wait.h"
#define STOPPED 0 #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) void utimerCreate(UTimer* t, u64 interval, TimerType type)
{ {
_waitableInitialize(&t->waitable); _waitableInitialize(&t->waitable, &g_utimerVt);
t->next_tick = STOPPED; t->next_tick = STOPPED;
t->interval = armNsToTicks(interval); t->interval = armNsToTicks(interval);
@ -40,10 +51,8 @@ void utimerStop(UTimer* t)
mutexUnlock(&t->waitable.mutex); 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) { if (t->next_tick == old_tick) {
u64 interval = t->interval; u64 interval = t->interval;
u64 new_tick = 0; u64 new_tick = 0;
@ -53,30 +62,54 @@ void _utimerRecalculate(UTimer* t, u64 old_tick)
new_tick = STOPPED; new_tick = STOPPED;
break; break;
case TimerType_Repeating: 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; break;
} }
t->next_tick = new_tick; 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); 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); mutexLock(&t->waitable.mutex);
_waiterNodeAdd(w); _utimerRecalculate(t, old_tick);
mutexUnlock(&t->waitable.mutex); mutexUnlock(&t->waitable.mutex);
return 0;
}
static Result _utimerOnSignal(Waitable* ww)
{
// Timer state changed, so we need to retry the wait.
return KERNELRESULT(Cancelled);
} }

View File

@ -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);

View File

@ -6,8 +6,6 @@
#include "kernel/uevent.h" #include "kernel/uevent.h"
#include "arm/counter.h" #include "arm/counter.h"
#include "wait.h" #include "wait.h"
#include "utimer.h"
#include "uevent.h"
#include "../internal.h" #include "../internal.h"
#define MAX_WAIT 0x40 #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 ++) { for (i = 0; i < num_objects; i ++) {
Waiter* obj = &objects[i]; Waiter* obj = &objects[i];
u64 timer_tick; u64 next_tick;
bool added; bool added;
switch (obj->type) { 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; handles[i] = obj->handle;
break; break;
case WaiterType_UTimer: case WaiterType_Waitable:
timer_tick = _utimerGetNextTick(obj->timer); // 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.
// Skip timer if stopped. next_tick = UINT64_MAX;
if (timer_tick != 0) { _waiterNodeInitialize(&waiters[i], obj->waitable, own_thread_handle, i, &triggered_idx);
// If the timer already signalled, we're done. added = obj->waitable->vt->beginWait(obj->waitable, &waiters[i], cur_tick, &next_tick);
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.
if (!added) { if (!added) {
*idx_out = i; *idx_out = i;
rc = 0; rc = 0;
goto clean_up; 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; waiters_added |= 1UL << i;
handles[i] = dummy_handle; handles[i] = dummy_handle;
break; break;
@ -116,11 +90,11 @@ static Result waitImpl(s32* idx_out, Waiter* objects, size_t num_objects, u64 ti
if (end_tick_idx == -1) if (end_tick_idx == -1)
goto clean_up; goto clean_up;
// If not, it means a timer triggered the timeout. // If not, it means an object triggered the timeout; handle it.
_utimerRecalculate(objects[end_tick_idx].timer, end_tick + cur_tick); Waitable* w = objects[end_tick_idx].waitable;
rc = w->vt->onTimeout(w, end_tick + cur_tick);
*idx_out = end_tick_idx; if (R_SUCCEEDED(rc))
rc = 0; *idx_out = end_tick_idx;
} else if (R_VALUE(rc) == KERNELRESULT(Cancelled)) { } else if (R_VALUE(rc) == KERNELRESULT(Cancelled)) {
// If no listener filled in its own index, we return the cancelled error back to caller. // 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. // 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) if (triggered_idx == -1)
goto clean_up; goto clean_up;
// An event was signalled, or a timer was updated. // An object was signalled, handle it.
// So.. which is it? Waitable* w = objects[triggered_idx].waitable;
switch (objects[triggered_idx].type) { rc = w->vt->onSignal(w);
default: if (R_SUCCEEDED(rc))
break; *idx_out = triggered_idx;
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;
}
} }
clean_up: clean_up:

View File

@ -14,9 +14,16 @@ struct WaiterNode {
s32 idx; 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); mutexInit(&ww->mutex);
ww->vt = vt;
ww->list.next = &ww->list; ww->list.next = &ww->list;
ww->list.prev = &ww->list; ww->list.prev = &ww->list;
} }