From 370d78453e9e7e9734e3ab71b19619e3f0d59d21 Mon Sep 17 00:00:00 2001 From: plutooo Date: Sat, 27 Oct 2018 04:29:51 +0200 Subject: [PATCH] kernel: Introduce utimer, uevent, and waitN --- nx/include/switch/arm/counter.h | 8 +++ nx/include/switch/kernel/uevent.h | 23 +++++++ nx/include/switch/kernel/utimer.h | 17 +++++ nx/include/switch/kernel/wait.h | 24 +++++++ nx/include/switch/kernel/waiter.h | 35 ++++++++++ nx/include/switch/result.h | 1 + nx/source/kernel/event.c | 4 +- nx/source/kernel/uevent.c | 74 +++++++++++++++++++++ nx/source/kernel/utimer.c | 22 +++++++ nx/source/kernel/wait.c | 104 ++++++++++++++++++++++++++++++ nx/source/kernel/waiter.c | 47 ++++++++++++++ 11 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 nx/include/switch/kernel/uevent.h create mode 100644 nx/include/switch/kernel/utimer.h create mode 100644 nx/include/switch/kernel/wait.h create mode 100644 nx/include/switch/kernel/waiter.h create mode 100644 nx/source/kernel/uevent.c create mode 100644 nx/source/kernel/utimer.c create mode 100644 nx/source/kernel/wait.c create mode 100644 nx/source/kernel/waiter.c diff --git a/nx/include/switch/arm/counter.h b/nx/include/switch/arm/counter.h index cdcf3186..c242866c 100644 --- a/nx/include/switch/arm/counter.h +++ b/nx/include/switch/arm/counter.h @@ -26,3 +26,11 @@ static inline u64 armGetSystemTickFreq(void) { __asm__ ("mrs %x[data], cntfrq_el0" : [data] "=r" (ret)); return ret; } + +static inline u64 armNsToTick(u64 ns) { + return (ns * 12) / 625; +} + +static inline u64 armTickToNs(u64 tick) { + return (tick * 625) / 12; +} diff --git a/nx/include/switch/kernel/uevent.h b/nx/include/switch/kernel/uevent.h new file mode 100644 index 00000000..e231cf22 --- /dev/null +++ b/nx/include/switch/kernel/uevent.h @@ -0,0 +1,23 @@ +// Copyright 2018 plutoo +#pragma once +#include "kernel/mutex.h" +#include "kernel/waiter.h" + +typedef struct UsermodeEvent UsermodeEvent; + +struct UsermodeEvent +{ + Mutex mutex; + WaiterNode waiter_list; + bool signal; + bool auto_clear; +}; + +void ueventCreate(UsermodeEvent* e, bool auto_clear); +void ueventClear(UsermodeEvent* e); +void ueventSignal(UsermodeEvent* e); + +// Internal methods (do not use!): +bool _ueventConsumeIfSignalled(UsermodeEvent* e); +void _ueventAddListener(UsermodeEvent* e, WaiterNode* w); +void _ueventRemoveListener(UsermodeEvent* e, WaiterNode* w); diff --git a/nx/include/switch/kernel/utimer.h b/nx/include/switch/kernel/utimer.h new file mode 100644 index 00000000..cbb02817 --- /dev/null +++ b/nx/include/switch/kernel/utimer.h @@ -0,0 +1,17 @@ +// Copyright 2018 plutoo +#pragma once +#include "kernel/svc.h" + +typedef struct UsermodeTimer UsermodeTimer; + +struct UsermodeTimer +{ + u64 next_time; + u64 interval; +}; + +void utimerCreate(UsermodeTimer* t, u64 interval); + +// Internal methods (do not use!): +void _utimerRecalculate(UsermodeTimer* t, u64 old_time); +u64 _utimerGetNextTime(UsermodeTimer* t); diff --git a/nx/include/switch/kernel/wait.h b/nx/include/switch/kernel/wait.h new file mode 100644 index 00000000..66a94e69 --- /dev/null +++ b/nx/include/switch/kernel/wait.h @@ -0,0 +1,24 @@ +// Copyright 2018 plutoo +#pragma once +#include "kernel/mutex.h" + +typedef enum { + WaitObjectType_Handle, + WaitObjectType_UsermodeTimer, + WaitObjectType_UsermodeEvent, +} WaitObjectType; + +typedef struct UsermodeTimer UsermodeTimer; +typedef struct UsermodeEvent UsermodeEvent; + +typedef struct { + WaitObjectType type; + + union { + Handle handle; + UsermodeTimer* timer; + UsermodeEvent* event; + }; +} WaitObject; + +Result waitN(s32* idx_out, WaitObject* objects, size_t num_objects, u64 timeout); diff --git a/nx/include/switch/kernel/waiter.h b/nx/include/switch/kernel/waiter.h new file mode 100644 index 00000000..ec67357a --- /dev/null +++ b/nx/include/switch/kernel/waiter.h @@ -0,0 +1,35 @@ +// Copyright 2018 plutoo +#pragma once +#include "kernel/svc.h" +#include "kernel/mutex.h" +#include "kernel/wait.h" + +#define MAX_WAIT 0x40 + +typedef struct WaiterNode WaiterNode; +typedef struct Waiter Waiter; + +struct WaiterNode +{ + Waiter* owner; + s32 idx; + WaiterNode* prev; + WaiterNode* next; +}; + +struct Waiter +{ + Mutex mutex; + Handle thread; + s32 signalled_idx; + + WaiterNode nodes[MAX_WAIT]; + size_t num_nodes; +}; + +// Internal methods (do not use!): +void _waiterCreate(Waiter* w); +void _waiterFree(Waiter* w, WaitObject* objects); +void _waiterSubscribe(Waiter* w, UsermodeEvent* e); +bool _waiterSignal(Waiter* w, s32 idx); +s32 _waiterGetSignalledIndex(Waiter* w); diff --git a/nx/include/switch/result.h b/nx/include/switch/result.h index 2fcdedef..f9304114 100644 --- a/nx/include/switch/result.h +++ b/nx/include/switch/result.h @@ -122,6 +122,7 @@ enum { LibnxError_ApmFailedToInitialize, LibnxError_NvinfoFailedToInitialize, LibnxError_NvbufFailedToInitialize, + LibnxError_TooManyWaitables, }; /// libnx binder error codes diff --git a/nx/source/kernel/event.c b/nx/source/kernel/event.c index 599e7fbb..73da3b9b 100644 --- a/nx/source/kernel/event.c +++ b/nx/source/kernel/event.c @@ -36,14 +36,14 @@ Result eventWait(Event* t, u64 timeout) return MAKERESULT(Module_Libnx, LibnxError_NotInitialized); if (timeout != U64_MAX) - deadline = armGetSystemTick() + timeout * 12 / 625; // timeout: ns->ticks + deadline = armGetSystemTick() + armNsToTick(timeout); // timeout: ns->ticks do { do { s64 this_timeout = -1; if (deadline) { this_timeout = deadline - armGetSystemTick(); - this_timeout = (this_timeout >= 0 ? this_timeout : 0) * 625 / 12; // ticks->ns + this_timeout = armTickToNs(this_timeout >= 0 ? this_timeout : 0); // ticks->ns } rc = svcWaitSynchronizationSingle(t->revent, this_timeout); diff --git a/nx/source/kernel/uevent.c b/nx/source/kernel/uevent.c new file mode 100644 index 00000000..e2c5f20f --- /dev/null +++ b/nx/source/kernel/uevent.c @@ -0,0 +1,74 @@ +// Copyright 2018 plutoo +#include "kernel/svc.h" +#include "kernel/mutex.h" +#include "kernel/waiter.h" +#include "kernel/uevent.h" + +void ueventCreate(UsermodeEvent* e, bool auto_clear) +{ + mutexInit(&e->mutex); + e->waiter_list.prev = &e->waiter_list; + e->waiter_list.next = &e->waiter_list; + e->signal = false; + e->auto_clear = auto_clear; +} + +void ueventClear(UsermodeEvent* e) +{ + mutexLock(&e->mutex); + e->signal = false; + mutexUnlock(&e->mutex); +} + +void ueventSignal(UsermodeEvent* e) +{ + mutexLock(&e->mutex); + e->signal = true; + + WaiterNode* end = &e->waiter_list; + WaiterNode* w = end; + + while (w->next != end) + { + w = w->next; + + bool signalled = _waiterSignal(w->owner, w->idx); + + if (signalled && e->auto_clear) + e->signal = false; + } + + mutexUnlock(&e->mutex); +} + +bool _ueventConsumeIfSignalled(UsermodeEvent* e) +{ + bool ret; + mutexLock(&e->mutex); + + ret = e->signal; + + if (ret && e->auto_clear) + e->signal = false; + + mutexUnlock(&e->mutex); + return ret; +} + +void _ueventAddListener(UsermodeEvent* e, WaiterNode* w) +{ + mutexLock(&e->mutex); + e->waiter_list.next->prev = w; + w->next = e->waiter_list.next; + w->prev = &e->waiter_list; + e->waiter_list.next = w; + mutexUnlock(&e->mutex); +} + +void _ueventRemoveListener(UsermodeEvent* e, WaiterNode* w) +{ + mutexLock(&e->mutex); + w->prev->next = w->next; + w->next->prev = w->prev; + mutexUnlock(&e->mutex); +} diff --git a/nx/source/kernel/utimer.c b/nx/source/kernel/utimer.c new file mode 100644 index 00000000..24a4534e --- /dev/null +++ b/nx/source/kernel/utimer.c @@ -0,0 +1,22 @@ +// Copyright 2018 plutoo +#include "kernel/svc.h" +#include "kernel/utimer.h" +#include "arm/counter.h" + +void utimerCreate(UsermodeTimer* t, u64 interval) +{ + t->next_time = armGetSystemTick() + armNsToTick(interval); + t->interval = armNsToTick(interval); +} + +void _utimerRecalculate(UsermodeTimer* t, u64 old_time) +{ + s64 interval = t->interval; + s64 new_time = ((armGetSystemTick() - old_time + interval - 1) / interval) * interval; + __sync_bool_compare_and_swap(&t->next_time, old_time, new_time); +} + +u64 _utimerGetNextTime(UsermodeTimer* t) +{ + return t->next_time; +} diff --git a/nx/source/kernel/wait.c b/nx/source/kernel/wait.c new file mode 100644 index 00000000..b9475d19 --- /dev/null +++ b/nx/source/kernel/wait.c @@ -0,0 +1,104 @@ +// Copyright 2018 plutoo +#include "result.h" +#include "kernel/svc.h" +#include "kernel/wait.h" +#include "kernel/waiter.h" +#include "kernel/utimer.h" +#include "kernel/uevent.h" +#include "arm/counter.h" +#include "../internal.h" + +Result waitN(s32* idx_out, WaitObject* objects, size_t num_objects, u64 timeout) +{ + if (num_objects > MAX_WAIT) + return MAKERESULT(Module_Libnx, LibnxError_TooManyWaitables); + + Handle dummy_handle = getThreadVars()->handle; + + Waiter waiter; + _waiterCreate(&waiter); + + Handle handles[MAX_WAIT]; + u64 cur_tick = armGetSystemTick(); + + u64 end_time = (timeout == UINT64_MAX) ? UINT64_MAX : cur_tick + timeout; + s32 end_time_idx = -1; + size_t i; + + for (i=0; itype) + { + case WaitObjectType_UsermodeTimer: + // Override the user-supplied timeout if timer would fire before that. + timer_tick = _utimerGetNextTime(obj->timer); + + if (timer_tick < end_time) + { + end_time = timer_tick; + end_time_idx = i; + } + break; + + case WaitObjectType_UsermodeEvent: + // If the event already happened, we're done. + if (_ueventConsumeIfSignalled(obj->event)) + { + *idx_out = i; + _waiterFree(&waiter, objects); + return 0; + } + + // If not, add a listener. + _waiterSubscribe(&waiter, obj->event); + break; + + case WaitObjectType_Handle: + break; + } + + handles[i] = (obj->type == WaitObjectType_Handle) ? obj->handle : dummy_handle; + } + + // If the timer already signalled, we're done. + if (end_time < cur_tick) + { + *idx_out = end_time_idx; + _utimerRecalculate(objects[end_time_idx].timer, end_time); + _waiterFree(&waiter, objects); + return 0; + } + + // Do the actual syscall. + Result rc; + rc = svcWaitSynchronization(idx_out, handles, num_objects, end_time - cur_tick); + + // Timeout-error? + if (rc == 0xEA01) + { + // If the user-supplied timeout, we return the error back to them. + if (end_time_idx == -1) + return rc; + + // If not, it means a timer was triggered. + *idx_out = end_time_idx; + _utimerRecalculate(objects[end_time_idx].timer, end_time); + _waiterFree(&waiter, objects); + return 0; + } + + // Interrupted-error? + if (rc == 0xEC01) + { + // An event was signalled. + *idx_out = _waiterGetSignalledIndex(&waiter); + _waiterFree(&waiter, objects); + return 0; + } + + _waiterFree(&waiter, objects); + return rc; +} diff --git a/nx/source/kernel/waiter.c b/nx/source/kernel/waiter.c new file mode 100644 index 00000000..ef678480 --- /dev/null +++ b/nx/source/kernel/waiter.c @@ -0,0 +1,47 @@ +// Copyright 2018 plutoo +#include "kernel/waiter.h" +#include "kernel/uevent.h" +#include "../internal.h" + +#define NOT_YET_SIGNALLED (-1) + +void _waiterCreate(Waiter* w) +{ + mutexInit(&w->mutex); + w->thread = getThreadVars()->handle; + w->signalled_idx = NOT_YET_SIGNALLED; + w->num_nodes = 0; +} + +void _waiterFree(Waiter* w, WaitObject* objects) +{ + size_t i; + for (i=0; inum_nodes; i++) + _ueventRemoveListener(objects[w->nodes[i].idx].event, &w->nodes[i]); +} + +bool _waiterSignal(Waiter* w, s32 idx) +{ + bool ret = false; + mutexLock(&w->mutex); + + if (w->signalled_idx == NOT_YET_SIGNALLED) + { + w->signalled_idx = idx; + svcCancelSynchronization(w->thread); + ret = true; + } + + mutexUnlock(&w->mutex); + return ret; +} + +void _waiterSubscribe(Waiter* w, UsermodeEvent* e) +{ + _ueventAddListener(e, &w->nodes[w->num_nodes++]); +} + +s32 _waiterGetSignalledIndex(Waiter* w) +{ + return w->signalled_idx; +}