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)
{
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;
}

View File

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

View File

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

View File

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

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
#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);
}

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 "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:

View File

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