From 98b1fd9056b9a8398102a0e7439a60224eeb9d64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=97=B1=20PixelyIon?= Date: Tue, 12 Jan 2021 01:11:21 +0530 Subject: [PATCH] Optimize Scheduler/IPC/HID + Fix Various Bugs * Optimize Scheduler With Per-Thread Scheduler Conditions * Optimize IPC by yielding * Optimize HID Vibration * Fix Priority Inheritance * Fix `KThread` Start/Kill/Signal Races * Fix `YieldPending` Races in `StartThread` & `SvcHandler` * Fix POSIX Time -> NN CalendarTime Conversion * Fix HID `TouchScreen`/`NPad` Activation --- app/src/main/cpp/skyline/input/npad.cpp | 30 ++++--- app/src/main/cpp/skyline/input/npad.h | 2 +- .../main/cpp/skyline/input/npad_device.cpp | 15 +++- app/src/main/cpp/skyline/input/npad_device.h | 7 ++ app/src/main/cpp/skyline/input/touch.cpp | 10 ++- app/src/main/cpp/skyline/kernel/scheduler.cpp | 90 +++++++++---------- app/src/main/cpp/skyline/kernel/scheduler.h | 6 +- app/src/main/cpp/skyline/kernel/svc.cpp | 10 ++- .../cpp/skyline/kernel/types/KProcess.cpp | 20 +++-- .../main/cpp/skyline/kernel/types/KThread.cpp | 74 ++++++++++----- .../main/cpp/skyline/kernel/types/KThread.h | 7 +- app/src/main/cpp/skyline/nce.cpp | 4 +- .../services/timesrv/ITimeZoneService.cpp | 8 +- .../services/timesrv/ITimeZoneService.h | 24 ++--- 14 files changed, 181 insertions(+), 126 deletions(-) diff --git a/app/src/main/cpp/skyline/input/npad.cpp b/app/src/main/cpp/skyline/input/npad.cpp index 56df6607..02bbb052 100644 --- a/app/src/main/cpp/skyline/input/npad.cpp +++ b/app/src/main/cpp/skyline/input/npad.cpp @@ -11,7 +11,7 @@ namespace skyline::input { {*this, hid->npad[4], NpadId::Player5}, {*this, hid->npad[5], NpadId::Player6}, {*this, hid->npad[6], NpadId::Player7}, {*this, hid->npad[7], NpadId::Player8}, {*this, hid->npad[8], NpadId::Handheld}, {*this, hid->npad[9], NpadId::Unknown}, - } {} + } { Activate(); /* NPads are activated by default, certain homebrew is reliant on this behavior */ } void NpadManager::Update() { std::lock_guard guard(mutex); @@ -83,25 +83,27 @@ namespace skyline::input { void NpadManager::Activate() { std::lock_guard guard(mutex); + if (!activated) { + supportedIds = {NpadId::Handheld, NpadId::Player1, NpadId::Player2, NpadId::Player3, NpadId::Player4, NpadId::Player5, NpadId::Player6, NpadId::Player7, NpadId::Player8}; + styles = {.proController = true, .joyconHandheld = true, .joyconDual = true, .joyconLeft = true, .joyconRight = true}; + activated = true; - supportedIds = {NpadId::Handheld, NpadId::Player1, NpadId::Player2, NpadId::Player3, NpadId::Player4, NpadId::Player5, NpadId::Player6, NpadId::Player7, NpadId::Player8}; - styles = {.proController = true, .joyconHandheld = true, .joyconDual = true, .joyconLeft = true, .joyconRight = true}; - activated = true; - - Update(); + Update(); + } } void NpadManager::Deactivate() { std::lock_guard guard(mutex); + if (activated) { + supportedIds = {}; + styles = {}; + activated = false; - supportedIds = {}; - styles = {}; - activated = false; + for (auto &npad : npads) + npad.Disconnect(); - for (auto &npad : npads) - npad.Disconnect(); - - for (auto &controller : controllers) - controller.device = nullptr; + for (auto &controller : controllers) + controller.device = nullptr; + } } } diff --git a/app/src/main/cpp/skyline/input/npad.h b/app/src/main/cpp/skyline/input/npad.h index c8477db9..cf1d1736 100644 --- a/app/src/main/cpp/skyline/input/npad.h +++ b/app/src/main/cpp/skyline/input/npad.h @@ -21,7 +21,7 @@ namespace skyline::input { class NpadManager { private: const DeviceState &state; - bool activated{false}; //!< If this NpadManager is activated or not + bool activated{}; friend NpadDevice; diff --git a/app/src/main/cpp/skyline/input/npad_device.cpp b/app/src/main/cpp/skyline/input/npad_device.cpp index 4b217872..dee5a8de 100644 --- a/app/src/main/cpp/skyline/input/npad_device.cpp +++ b/app/src/main/cpp/skyline/input/npad_device.cpp @@ -425,10 +425,15 @@ namespace skyline::input { } void NpadDevice::Vibrate(bool isRight, const NpadVibrationValue &value) { - if (isRight) + if (isRight) { + if (vibrationRight && (*vibrationRight) == value) + return; vibrationRight = value; - else + } else { + if (vibrationLeft == value) + return; vibrationLeft = value; + } if (vibrationRight) Vibrate(vibrationLeft, *vibrationRight); @@ -437,6 +442,12 @@ namespace skyline::input { } void NpadDevice::Vibrate(const NpadVibrationValue &left, const NpadVibrationValue &right) { + if (vibrationLeft == left && vibrationRight && (*vibrationRight) == right) + return; + + vibrationLeft = left; + vibrationRight = right; + if (partnerIndex == constant::NullIndex) { std::array vibrations{ VibrationInfo{left.frequencyLow, left.amplitudeLow * (constant::AmplitudeMax / 4)}, diff --git a/app/src/main/cpp/skyline/input/npad_device.h b/app/src/main/cpp/skyline/input/npad_device.h index b4614c55..27e2d398 100644 --- a/app/src/main/cpp/skyline/input/npad_device.h +++ b/app/src/main/cpp/skyline/input/npad_device.h @@ -105,6 +105,13 @@ namespace skyline::input { float frequencyLow; float amplitudeHigh; float frequencyHigh; + + bool operator==(const NpadVibrationValue &other) const { + return (amplitudeLow == other.amplitudeLow) && + (frequencyLow == other.frequencyLow) && + (amplitudeHigh == other.amplitudeHigh) && + (frequencyHigh == other.frequencyHigh); + } }; static_assert(sizeof(NpadVibrationValue) == 0x10); diff --git a/app/src/main/cpp/skyline/input/touch.cpp b/app/src/main/cpp/skyline/input/touch.cpp index 17222a20..3247ccaf 100644 --- a/app/src/main/cpp/skyline/input/touch.cpp +++ b/app/src/main/cpp/skyline/input/touch.cpp @@ -5,12 +5,14 @@ namespace skyline::input { TouchManager::TouchManager(const DeviceState &state, input::HidSharedMemory *hid) : state(state), section(hid->touchScreen) { - Activate(); + Activate(); // The touch screen is expected to be activated by default, commercial games are reliant on this behavior } void TouchManager::Activate() { - activated = true; - SetState({}); + if (!activated) { + activated = true; + SetState({}); + } } void TouchManager::SetState(const span &points) { @@ -37,7 +39,7 @@ namespace skyline::input { section.header.timestamp = util::GetTimeTicks(); section.header.entryCount = std::min(static_cast(section.header.entryCount + 1), constant::HidEntryCount); - section.header.maxEntry = constant::HidEntryCount - 1; + section.header.maxEntry = section.header.entryCount; section.header.currentEntry = entryIndex; } } diff --git a/app/src/main/cpp/skyline/kernel/scheduler.cpp b/app/src/main/cpp/skyline/kernel/scheduler.cpp index 6cc57dc4..440ceaaf 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.cpp +++ b/app/src/main/cpp/skyline/kernel/scheduler.cpp @@ -15,8 +15,8 @@ namespace skyline::kernel { if (*tls) { const auto &state{*reinterpret_cast(*tls)->state}; state.scheduler->Rotate(false); - state.scheduler->WaitSchedule(); YieldPending = false; + state.scheduler->WaitSchedule(); } else { YieldPending = true; } @@ -59,7 +59,10 @@ namespace skyline::kernel { } if (optimalCore != currentCore) { - RemoveThread(); + if (!alwaysInsert && thread == state.thread) + RemoveThread(); + else if (!alwaysInsert && thread != state.thread) + throw exception("Migrating an external thread (T{}) without 'alwaysInsert' isn't supported", thread->id); thread->coreId = optimalCore->id; InsertThread(thread); state.logger->Debug("Load Balancing T{}: C{} -> C{}", thread->id, currentCore->id, optimalCore->id); @@ -81,27 +84,6 @@ namespace skyline::kernel { void Scheduler::InsertThread(const std::shared_ptr &thread) { auto &core{cores.at(thread->coreId)}; - - thread_local bool signalHandlerSetup{}; - if (!signalHandlerSetup) { - signal::SetSignalHandler({YieldSignal}, SignalHandler); - signalHandlerSetup = true; - } - - if (!thread->preemptionTimer) { - struct sigevent event{ - .sigev_signo = YieldSignal, - .sigev_notify = SIGEV_THREAD_ID, - ._sigev_un = { - ._tid = gettid(), - }, - }; - timer_t timer; - if (timer_create(CLOCK_THREAD_CPUTIME_ID, &event, &timer)) - throw exception("timer_create has failed with '{}'", strerror(errno)); - thread->preemptionTimer.emplace(timer); - } - std::unique_lock lock(core.mutex); auto nextThread{std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority)}; if (nextThread == core.queue.begin()) { @@ -116,10 +98,8 @@ namespace skyline::kernel { } else { core.queue.push_front(thread); } - if (thread != state.thread) { - lock.unlock(); // We should unlock this prior to waking all threads to not cause contention on the core lock - core.frontCondition.notify_all(); // We only want to trigger the conditional variable if the current thread isn't inserting itself - } + if (thread != state.thread) + thread->wakeCondition.notify_one(); // We only want to trigger the conditional variable if the current thread isn't inserting itself } else { core.queue.insert(nextThread, thread); } @@ -132,7 +112,7 @@ namespace skyline::kernel { std::shared_lock lock(core->mutex); if (loadBalance && thread->affinityMask.count() > 1) { std::chrono::milliseconds loadBalanceThreshold{PreemptiveTimeslice * 2}; //!< The amount of time that needs to pass unscheduled for a thread to attempt load balancing - while (!core->frontCondition.wait_for(lock, loadBalanceThreshold, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) { + while (!thread->wakeCondition.wait_for(lock, loadBalanceThreshold, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) { lock.unlock(); LoadBalance(state.thread); if (thread->coreId == core->id) { @@ -145,12 +125,12 @@ namespace skyline::kernel { loadBalanceThreshold *= 2; // We double the duration required for future load balancing for this invocation to minimize pointless load balancing } } else { - core->frontCondition.wait(lock, [&]() { return !core->queue.empty() && core->queue.front() == thread; }); + thread->wakeCondition.wait(lock, [&]() { return !core->queue.empty() && core->queue.front() == thread; }); } if (thread->priority == core->preemptionPriority) { struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast(PreemptiveTimeslice).count()}}; - timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + timer_settime(thread->preemptionTimer, 0, &spec, nullptr); thread->isPreempted = true; } @@ -162,10 +142,10 @@ namespace skyline::kernel { auto *core{&cores.at(thread->coreId)}; std::shared_lock lock(core->mutex); - if (core->frontCondition.wait_for(lock, timeout, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) { + if (thread->wakeCondition.wait_for(lock, timeout, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) { if (thread->priority == core->preemptionPriority) { struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast(PreemptiveTimeslice).count()}}; - timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + timer_settime(thread->preemptionTimer, 0, &spec, nullptr); thread->isPreempted = true; } @@ -188,18 +168,18 @@ namespace skyline::kernel { // Splice the linked element from the beginning of the queue to where it's priority is present core.queue.splice(std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority), core.queue, core.queue.begin()); - if (core.queue.front() != thread) { - // If we aren't at the front of the queue, only then should we notify other threads that the front of the queue has changed - lock.unlock(); - core.frontCondition.notify_all(); - } + auto front{core.queue.front()}; + if (front != thread) + front->wakeCondition.notify_one(); // If we aren't at the front of the queue, only then should we wake the thread at the front up if (cooperative && thread->isPreempted) { // If a preemptive thread did a cooperative yield then we need to disarm the preemptive timer struct itimerspec spec{}; - timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + timer_settime(thread->preemptionTimer, 0, &spec, nullptr); } thread->isPreempted = false; + } else { + throw exception("T{} called Rotate while not being at the front of C{}'s queue", thread->id, thread->coreId); } } @@ -213,11 +193,14 @@ namespace skyline::kernel { // If the thread isn't in the queue then the new priority will be handled automatically on insertion return; if (currentIt == core->queue.begin()) { - // Alternatively, if it's currently running then we'd just want to reorder after it rotates - if (!thread->isPreempted && thread->priority == core->preemptionPriority) { + // Alternatively, if it's currently running then we'd just want to cause it to yield if it's priority is lower than the the thread behind it + auto nextIt{std::next(currentIt)}; + if (nextIt != core->queue.end() && (*nextIt)->priority < thread->priority) { + thread->SendSignal(YieldSignal); + } else if (!thread->isPreempted && thread->priority == core->preemptionPriority) { // If the thread needs to be preempted due to the new priority then arm it's preemption timer struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast(PreemptiveTimeslice).count()}}; - timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + timer_settime(thread->preemptionTimer, 0, &spec, nullptr); thread->isPreempted = true; } return; @@ -232,7 +215,7 @@ namespace skyline::kernel { if (thread->isPreempted && thread->priority != core->preemptionPriority) { struct itimerspec spec{}; - timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + timer_settime(thread->preemptionTimer, 0, &spec, nullptr); thread->isPreempted = false; } @@ -247,13 +230,21 @@ namespace skyline::kernel { void Scheduler::ParkThread() { auto &thread{state.thread}; + std::lock_guard migrationLock(thread->coreMigrationMutex); RemoveThread(); - { + + auto originalCoreId{thread->coreId}; + thread->coreId = constant::ParkedCoreId; + for (auto &core : cores) + if (originalCoreId != core.id && thread->affinityMask.test(core.id) && (core.queue.empty() || core.queue.front()->priority > thread->priority)) + thread->coreId = core.id; + + if (thread->coreId == constant::ParkedCoreId) { std::unique_lock lock(parkedMutex); parkedQueue.insert(std::upper_bound(parkedQueue.begin(), parkedQueue.end(), thread->priority.load(), type::KThread::IsHigherPriority), thread); - thread->coreId = constant::ParkedCoreId; - parkedFrontCondition.wait(lock, [&]() { return parkedCore.queue.front() == thread && thread->coreId != constant::ParkedCoreId; }); + thread->wakeCondition.wait(lock, [&]() { return parkedQueue.front() == thread && thread->coreId != constant::ParkedCoreId; }); } + InsertThread(thread); } @@ -272,7 +263,7 @@ namespace skyline::kernel { if (parkedThread->priority < thread->priority || (parkedThread->priority == thread->priority && (!nextThread || parkedThread->timesliceStart < nextThread->timesliceStart))) { parkedThread->coreId = thread->coreId; parkedLock.unlock(); - parkedFrontCondition.notify_all(); + thread->wakeCondition.notify_one(); } } } @@ -290,16 +281,15 @@ namespace skyline::kernel { if (thread->timesliceStart) thread->averageTimeslice = (thread->averageTimeslice / 4) + (3 * (util::GetTimeTicks() - thread->timesliceStart / 4)); - // We need to notify that the front was changed, if we were at the front previously - lock.unlock(); - core.frontCondition.notify_all(); + if (it != core.queue.end()) + (*it)->wakeCondition.notify_one(); // We need to wake the thread at the front of the queue, if we were at the front previously } } } if (thread->isPreempted) { struct itimerspec spec{}; - timer_settime(*thread->preemptionTimer, 0, &spec, nullptr); + timer_settime(thread->preemptionTimer, 0, &spec, nullptr); thread->isPreempted = false; } diff --git a/app/src/main/cpp/skyline/kernel/scheduler.h b/app/src/main/cpp/skyline/kernel/scheduler.h index 6d868d33..4265bce6 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.h +++ b/app/src/main/cpp/skyline/kernel/scheduler.h @@ -30,7 +30,7 @@ namespace skyline { return (std::numeric_limits::max() >> ((std::numeric_limits::digits - 1 + min) - max)) << min; } - constexpr bool Valid(i8 value) const { + constexpr bool Valid(u8 value) const { return (value >= min) && (value <= max); } }; @@ -47,7 +47,6 @@ namespace skyline { u8 id; u8 preemptionPriority; //!< The priority at which this core becomes preemptive as opposed to cooperative std::shared_mutex mutex; //!< Synchronizes all operations on the queue - std::condition_variable_any frontCondition; //!< A conditional variable which is signalled when the front of the queue has changed std::list> queue; //!< A queue of threads which are running or to be run on this core CoreContext(u8 id, u8 preemptionPriority); @@ -59,8 +58,6 @@ namespace skyline { std::condition_variable parkedFrontCondition; //!< A conditional variable which is signalled when the front of the parked queue has changed std::list> parkedQueue; //!< A queue of threads which are parked and waiting on core migration - CoreContext parkedCore{constant::CoreCount, 64}; //!< A psuedo-core which all parked threads are moved onto - public: static constexpr std::chrono::milliseconds PreemptiveTimeslice{10}; //!< The duration of time a preemptive thread can run before yielding inline static int YieldSignal{SIGRTMIN}; //!< The signal used to cause a yield in running threads @@ -78,6 +75,7 @@ namespace skyline { * @param alwaysInsert If to insert the thread even if it hasn't migrated cores, this is used during thread creation * @return A reference to the CoreContext of the core which the calling thread is running on after load balancing * @note This inserts the thread into the migrated process's queue after load balancing, there is no need to call it redundantly + * @note alwaysInsert makes the assumption that the thread isn't inserted in any core's queue currently */ CoreContext& LoadBalance(const std::shared_ptr &thread, bool alwaysInsert = false); diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index 42dd56bb..ee82263e 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -347,7 +347,14 @@ namespace skyline::kernel::svc { auto thread{state.process->GetHandle(handle)}; state.logger->Debug("svcSetThreadPriority: Setting thread #{}'s priority to {}", thread->id, priority); if (thread->priority != priority) { - thread->priority = priority; + thread->basePriority = priority; + u8 newPriority{}; + do { + // Try to CAS the priority of the thread with it's new base priority + // If the new priority is equivalent to the current priority then we don't need to CAS + newPriority = thread->priority.load(); + newPriority = std::min(newPriority, priority); + } while (newPriority != priority && thread->priority.compare_exchange_strong(newPriority, priority)); state.scheduler->UpdatePriority(thread); } state.ctx->gpr.w0 = Result{}; @@ -773,6 +780,7 @@ namespace skyline::kernel::svc { } void SendSyncRequest(const DeviceState &state) { + SchedulerScopedLock schedulerLock(state); state.os->serviceManager.SyncRequestHandler(static_cast(state.ctx->gpr.x0)); state.ctx->gpr.w0 = Result{}; } diff --git a/app/src/main/cpp/skyline/kernel/types/KProcess.cpp b/app/src/main/cpp/skyline/kernel/types/KProcess.cpp index 551e97fc..b6c646fb 100644 --- a/app/src/main/cpp/skyline/kernel/types/KProcess.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KProcess.cpp @@ -137,7 +137,7 @@ namespace skyline::kernel::type { u8 priority, ownerPriority; do { // Try to CAS the priority of the owner with the current thread - // If they're equivalent then we don't need to CAS as the priority won't be inherited + // If the new priority is equivalent to the current priority then we don't need to CAS ownerPriority = owner->priority.load(); priority = std::min(ownerPriority, state.thread->priority.load()); } while (ownerPriority != priority && owner->priority.compare_exchange_strong(ownerPriority, priority)); @@ -198,13 +198,21 @@ namespace skyline::kernel::type { if (!waiters.empty()) { // If there are threads still waiting on us then try to inherit their priority - auto highestPriority{waiters.front()}; - u8 priority, ownerPriority; + auto highestPriorityThread{waiters.front()}; + u8 newPriority, basePriority; do { - ownerPriority = state.thread->priority.load(); - priority = std::min(ownerPriority, highestPriority->priority.load()); - } while (ownerPriority != priority && nextOwner->priority.compare_exchange_strong(ownerPriority, priority)); + basePriority = state.thread->basePriority.load(); + newPriority = std::min(basePriority, highestPriorityThread->priority.load()); + } while (basePriority != newPriority && state.thread->priority.compare_exchange_strong(basePriority, newPriority)); state.scheduler->UpdatePriority(state.thread); + } else { + u8 priority, basePriority; + do { + basePriority = state.thread->basePriority.load(); + priority = state.thread->priority.load(); + } while (priority != basePriority && !state.thread->priority.compare_exchange_strong(priority, basePriority)); + if (priority != basePriority) + state.scheduler->UpdatePriority(state.thread); } if (nextWaiter) { diff --git a/app/src/main/cpp/skyline/kernel/types/KThread.cpp b/app/src/main/cpp/skyline/kernel/types/KThread.cpp index 4e416d75..2e3d8578 100644 --- a/app/src/main/cpp/skyline/kernel/types/KThread.cpp +++ b/app/src/main/cpp/skyline/kernel/types/KThread.cpp @@ -10,22 +10,16 @@ #include "KThread.h" namespace skyline::kernel::type { - KThread::KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, u8 priority, i8 idealCore) : handle(handle), parent(parent), id(id), entry(entry), entryArgument(argument), stackTop(stackTop), priority(priority), idealCore(idealCore), coreId(idealCore), KSyncObject(state, KType::KThread) { + KThread::KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, u8 priority, i8 idealCore) : handle(handle), parent(parent), id(id), entry(entry), entryArgument(argument), stackTop(stackTop), priority(priority), basePriority(priority), idealCore(idealCore), coreId(idealCore), KSyncObject(state, KType::KThread) { affinityMask.set(coreId); } KThread::~KThread() { - std::unique_lock lock(mutex); - if (running && pthread != pthread_self()) { - pthread_kill(pthread, SIGINT); - if (!thread.joinable()) - pthread_join(pthread, nullptr); - } + Kill(true); if (thread.joinable()) thread.join(); - if (preemptionTimer) - timer_delete(*preemptionTimer); + timer_delete(preemptionTimer); } void KThread::StartThread() { @@ -44,7 +38,13 @@ namespace skyline::kernel::type { if (setjmp(originalCtx)) { // Returns 1 if it's returning from guest, 0 otherwise state.scheduler->RemoveThread(); - running = false; + { + std::unique_lock lock(statusMutex); + running = false; + ready = false; + statusCondition.notify_all(); + } + Signal(); if (threadName[0] != 'H' || threadName[1] != 'O' || threadName[2] != 'S' || threadName[3] != '-') { @@ -55,10 +55,32 @@ namespace skyline::kernel::type { return; } + struct sigevent event{ + .sigev_signo = Scheduler::YieldSignal, + .sigev_notify = SIGEV_THREAD_ID, + .sigev_notify_thread_id = gettid(), + }; + if (timer_create(CLOCK_THREAD_CPUTIME_ID, &event, &preemptionTimer)) + throw exception("timer_create has failed with '{}'", strerror(errno)); + signal::SetSignalHandler({SIGINT, SIGILL, SIGTRAP, SIGBUS, SIGFPE, SIGSEGV}, nce::NCE::SignalHandler); + signal::SetSignalHandler({Scheduler::YieldSignal}, Scheduler::SignalHandler); + + { + std::lock_guard lock(statusMutex); + ready = true; + statusCondition.notify_all(); + } try { - state.scheduler->WaitSchedule(); + if (!Scheduler::YieldPending) + state.scheduler->WaitSchedule(); + while (Scheduler::YieldPending) { + // If there is a yield pending on us after thread creation + state.scheduler->Rotate(); + Scheduler::YieldPending = false; + state.scheduler->WaitSchedule(); + } asm volatile( "MRS X0, TPIDR_EL0\n\t" @@ -161,10 +183,13 @@ namespace skyline::kernel::type { } void KThread::Start(bool self) { - std::unique_lock lock(mutex); + std::unique_lock lock(statusMutex); if (!running) { - running = true; state.scheduler->LoadBalance(shared_from_this(), true); // This will automatically insert the thread into the core queue after load balancing + + running = true; + killed = false; + statusCondition.notify_all(); if (self) { pthread = pthread_self(); lock.unlock(); @@ -177,22 +202,23 @@ namespace skyline::kernel::type { } void KThread::Kill(bool join) { - std::lock_guard lock(mutex); - if (running) { - pthread_kill(pthread, SIGINT); - if (join) { - if (thread.joinable()) - thread.join(); - else - pthread_join(pthread, nullptr); + std::unique_lock lock(statusMutex); + if (!killed && running) { + statusCondition.wait(lock, [this]() { return ready || killed; }); + if (!killed) { + pthread_kill(pthread, SIGINT); + killed = true; + statusCondition.notify_all(); } - running = false; } + if (join) + statusCondition.wait(lock, [this]() { return !running; }); } void KThread::SendSignal(int signal) { - std::lock_guard lock(mutex); - if (running) + std::unique_lock lock(statusMutex); + statusCondition.wait(lock, [this]() { return ready || killed; }); + if (!killed && running) pthread_kill(pthread, signal); } } diff --git a/app/src/main/cpp/skyline/kernel/types/KThread.h b/app/src/main/cpp/skyline/kernel/types/KThread.h index 847e0c87..11fc161a 100644 --- a/app/src/main/cpp/skyline/kernel/types/KThread.h +++ b/app/src/main/cpp/skyline/kernel/types/KThread.h @@ -24,9 +24,11 @@ namespace skyline { void StartThread(); public: - std::mutex mutex; //!< Synchronizes all thread state changes + std::mutex statusMutex; //!< Synchronizes all thread state changes, running/ready + std::condition_variable statusCondition; //!< A conditional variable signalled on the status of the thread changing bool running{false}; //!< If the host thread that corresponds to this thread is running, this doesn't reflect guest scheduling changes bool killed{false}; //!< If this thread was previously running and has been killed + bool ready{false}; //!< If this thread is ready to recieve signals or not KHandle handle; size_t id; //!< Index of thread in parent process's KThread vector @@ -38,6 +40,7 @@ namespace skyline { u64 entryArgument; void *stackTop; + std::condition_variable_any wakeCondition; //!< A conditional variable which is signalled to wake the current thread while it's sleeping std::atomic basePriority; //!< The priority of the thread for the scheduler without any priority-inheritance std::atomic priority; //!< The priority of the thread for the scheduler i8 idealCore; //!< The ideal CPU core for this thread to run on @@ -46,7 +49,7 @@ namespace skyline { std::mutex coreMigrationMutex; //!< Synchronizes operations which depend on which core the thread is running on u64 timesliceStart{}; //!< Start of the scheduler timeslice u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread - std::optional preemptionTimer{}; //!< A kernel timer used for preemption interrupts + timer_t preemptionTimer{}; //!< A kernel timer used for preemption interrupts bool isPreempted{}; //!< If the preemption timer has been armed and will fire std::mutex waiterMutex; //!< Synchronizes operations on mutation of the waiter members u32* waitKey; //!< The key of the mutex which this thread is waiting on diff --git a/app/src/main/cpp/skyline/nce.cpp b/app/src/main/cpp/skyline/nce.cpp index 44f1ee26..46912ca1 100644 --- a/app/src/main/cpp/skyline/nce.cpp +++ b/app/src/main/cpp/skyline/nce.cpp @@ -25,10 +25,10 @@ namespace skyline::nce { throw exception("Unimplemented SVC 0x{:X}", svc); } - if (kernel::Scheduler::YieldPending) { + while (kernel::Scheduler::YieldPending) { state.scheduler->Rotate(false); - state.scheduler->WaitSchedule(); kernel::Scheduler::YieldPending = false; + state.scheduler->WaitSchedule(); } } catch (const signal::SignalException &e) { if (e.signal != SIGINT) { diff --git a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp index c28eef45..0b6788ca 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp +++ b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.cpp @@ -7,12 +7,12 @@ namespace skyline::service::timesrv { ITimeZoneService::ITimeZoneService(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager) {} Result ITimeZoneService::ToCalendarTimeWithMyRule(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { - auto curTime{std::time(nullptr)}; - auto calender{*std::gmtime(&curTime)}; + auto& time{request.Pop()}; + auto calender{*std::gmtime(reinterpret_cast(&time))}; CalendarTime calendarTime{ .year = static_cast(calender.tm_year), - .month = static_cast(calender.tm_mon), + .month = static_cast(calender.tm_mon + 1), .day = static_cast(calender.tm_hour), .minute = static_cast(calender.tm_min), .second = static_cast(calender.tm_sec), @@ -22,7 +22,7 @@ namespace skyline::service::timesrv { CalendarAdditionalInfo calendarInfo{ .dayWeek = static_cast(calender.tm_wday), .dayMonth = static_cast(calender.tm_mday), - .name = *reinterpret_cast(calender.tm_zone), + .tzName = *reinterpret_cast(calender.tm_zone), .dst = static_cast(calender.tm_isdst), .utcRel = static_cast(calender.tm_gmtoff), }; diff --git a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h index 2afdc075..7fa28320 100644 --- a/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h +++ b/app/src/main/cpp/skyline/services/timesrv/ITimeZoneService.h @@ -13,28 +13,28 @@ namespace skyline::service::timesrv { class ITimeZoneService : public BaseService { private: /** - * @brief A particular time point in calendar format + * @brief A particular time point in Nintendo's calendar format */ struct CalendarTime { - u16 year; //!< The Year component of the date - u8 month; //!< The Month component of the date - u8 day; //!< The Day component of the date - u8 hour; //!< The Hour component of the date - u8 minute; //!< The Minute component of the date - u8 second; //!< The Second component of the date + u16 year; //!< Amount of years that have passed since 1900 + u8 month; //!< Month of the year (1-12) [POSIX time uses 0-11] + u8 day; //!< Day of the month (1-31) + u8 hour; //!< Hour of the day (0-23) + u8 minute; //!< Minute of the hour (0-59) + u8 second; //!< Second of the minute (0-60) u8 _pad_; }; static_assert(sizeof(CalendarTime) == 0x8); /** - * @brief Information that is packaged along with CalendarTime + * @brief Additional metadata about the time alongside CalendarTime */ struct CalendarAdditionalInfo { - u32 dayWeek; //!< The amount of days since Sunday - u32 dayMonth; //!< The amount of days since the start of the month - u64 name; //!< The name of the time zone + u32 dayWeek; //!< Amount of days since Sunday + u32 dayMonth; //!< Amount of days since the start of the month + u64 tzName; //!< The name of the time zone i32 dst; //!< If DST is in effect or not - u32 utcRel; //!< The offset of the time from GMT in seconds + u32 utcRel; //!< Offset of the time from GMT in seconds }; static_assert(sizeof(CalendarAdditionalInfo) == 0x18);