From d5d133372ff11e28b6a2b8e9f17a056f48e92d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=97=B1=20PixelyIon?= Date: Sat, 16 Jan 2021 02:45:06 +0530 Subject: [PATCH] Fix Clean Exiting + Optimize Core Queues + Optimize Thread Insertion + Implement HID `SendVibrationValue` --- app/src/main/cpp/emu_jni.cpp | 1 + app/src/main/cpp/skyline/common/signal.cpp | 33 +++++++++--- app/src/main/cpp/skyline/common/signal.h | 23 ++++++++ app/src/main/cpp/skyline/gpu/gpfifo.cpp | 1 + app/src/main/cpp/skyline/kernel/scheduler.cpp | 52 ++++++++++++++++--- app/src/main/cpp/skyline/kernel/scheduler.h | 2 +- app/src/main/cpp/skyline/kernel/svc.cpp | 2 +- .../main/cpp/skyline/kernel/types/KThread.h | 4 +- .../cpp/skyline/services/hid/IHidServer.cpp | 12 +++++ .../cpp/skyline/services/hid/IHidServer.h | 9 +++- 10 files changed, 121 insertions(+), 18 deletions(-) diff --git a/app/src/main/cpp/emu_jni.cpp b/app/src/main/cpp/emu_jni.cpp index 5e08f723..a4107d93 100644 --- a/app/src/main/cpp/emu_jni.cpp +++ b/app/src/main/cpp/emu_jni.cpp @@ -22,6 +22,7 @@ std::weak_ptr GpuWeak; std::weak_ptr InputWeak; extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jstring appFilesPathJstring) { + skyline::signal::ScopedStackBlocker stackBlocker; Fps = FrameTime = 0; pthread_setname_np(pthread_self(), "EmuMain"); diff --git a/app/src/main/cpp/skyline/common/signal.cpp b/app/src/main/cpp/skyline/common/signal.cpp index 1a369147..7b05bf65 100644 --- a/app/src/main/cpp/skyline/common/signal.cpp +++ b/app/src/main/cpp/skyline/common/signal.cpp @@ -10,15 +10,19 @@ namespace skyline::signal { thread_local std::exception_ptr SignalExceptionPtr; void ExceptionThrow() { - std::rethrow_exception(SignalExceptionPtr); + // We need the compiler to not remove the asm at the end of 'std::rethrow_exception' which is a noreturn function + volatile bool alwaysTrue{true}; + if (alwaysTrue) + std::rethrow_exception(SignalExceptionPtr); } std::terminate_handler terminateHandler{}; + [[clang::optnone]] inline StackFrame *SafeFrameRecurse(size_t depth, StackFrame *frame) { if (frame) { for (size_t it{}; it < depth; it++) { - if (frame->next) + if (frame->lr && frame->next) frame = frame->next; else terminateHandler(); @@ -29,6 +33,7 @@ namespace skyline::signal { return frame; } + [[clang::optnone]] void TerminateHandler() { auto exception{std::current_exception()}; if (terminateHandler && exception && exception == SignalExceptionPtr) { @@ -36,11 +41,25 @@ namespace skyline::signal { asm("MOV %0, FP" : "=r"(frame)); frame = SafeFrameRecurse(2, frame); // We unroll past 'std::terminate' + static void *exceptionThrowEnd{}; + if (!exceptionThrowEnd) { + u32 *it{reinterpret_cast(&ExceptionThrow) + 1}; + while (_Unwind_FindEnclosingFunction(it) == &ExceptionThrow) + it++; + exceptionThrowEnd = it - 1; + } + auto lookupFrame{frame}; - while (lookupFrame) { - auto function{_Unwind_FindEnclosingFunction(frame->lr)}; - if (function == &ExceptionThrow) - terminateHandler(); // We have no handler to consume the exception, it's time to quit + bool hasAdvanced{}; + while (lookupFrame && lookupFrame->lr) { + if (lookupFrame->lr >= reinterpret_cast(&ExceptionThrow) && lookupFrame->lr < exceptionThrowEnd) { + if (!hasAdvanced) { + frame = SafeFrameRecurse(2, lookupFrame); + hasAdvanced = true; + } else { + terminateHandler(); // We have no handler to consume the exception, it's time to quit + } + } lookupFrame = lookupFrame->next; } @@ -68,7 +87,7 @@ namespace skyline::signal { signalException.frames.push_back(reinterpret_cast(context->uc_mcontext.pc)); StackFrame *frame{reinterpret_cast(context->uc_mcontext.regs[29])}; - while (frame) { + while (frame && frame->lr) { signalException.frames.push_back(frame->lr); frame = frame->next; } diff --git a/app/src/main/cpp/skyline/common/signal.h b/app/src/main/cpp/skyline/common/signal.h index 0d6e1551..e696663c 100644 --- a/app/src/main/cpp/skyline/common/signal.h +++ b/app/src/main/cpp/skyline/common/signal.h @@ -14,6 +14,29 @@ namespace skyline::signal { void *lr; }; + /** + * @brief A scoped way to block a stack trace beyond the scope of this object + * @note This is used for JNI functions where the stack trace will be determined as they often contain invalid stack frames which'd cause a SIGSEGV + */ + struct ScopedStackBlocker { + StackFrame realFrame; + + __attribute__((noinline)) ScopedStackBlocker() { + StackFrame *frame; + asm("MOV %0, FP" : "=r"(frame)); + realFrame = *frame; + frame->next = nullptr; + frame->lr = nullptr; + } + + __attribute__((noinline)) ~ScopedStackBlocker() { + StackFrame *frame; + asm("MOV %0, FP" : "=r"(frame)); + frame->next = realFrame.next; + frame->lr = realFrame.lr; + } + }; + /** * @brief An exception object that is designed specifically to hold Linux signals * @note This doesn't inherit std::exception as it shouldn't be caught as such diff --git a/app/src/main/cpp/skyline/gpu/gpfifo.cpp b/app/src/main/cpp/skyline/gpu/gpfifo.cpp index 4cfba45b..7e9f542e 100644 --- a/app/src/main/cpp/skyline/gpu/gpfifo.cpp +++ b/app/src/main/cpp/skyline/gpu/gpfifo.cpp @@ -105,6 +105,7 @@ namespace skyline::gpu::gpfifo { } } catch (const std::exception &e) { state.logger->Write(Logger::LogLevel::Error, e.what()); + signal::BlockSignal({SIGINT}); state.process->Kill(false); } } diff --git a/app/src/main/cpp/skyline/kernel/scheduler.cpp b/app/src/main/cpp/skyline/kernel/scheduler.cpp index 440ceaaf..a0f2b983 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.cpp +++ b/app/src/main/cpp/skyline/kernel/scheduler.cpp @@ -36,7 +36,7 @@ namespace skyline::kernel { u64 timeslice{}; if (!candidateCore.queue.empty()) { - std::shared_lock coreLock(candidateCore.mutex); + std::unique_lock coreLock(candidateCore.mutex); auto threadIterator{candidateCore.queue.cbegin()}; if (threadIterator != candidateCore.queue.cend()) { @@ -89,8 +89,20 @@ namespace skyline::kernel { if (nextThread == core.queue.begin()) { if (nextThread != core.queue.end()) { // If the inserted thread has a higher priority than the currently running thread (and the queue isn't empty) - // We need to interrupt the currently scheduled thread and put this thread at the front instead - core.queue.insert(std::next(core.queue.begin()), thread); + if (state.thread == thread) { + // If the current thread is inserting itself then we try to optimize by trying to by forcefully yielding it ourselves now + // We can avoid waiting on it to yield itself on receiving the signal which serializes the entire pipeline + // This isn't done in other cases as this optimization is unsafe when done where serialization is required (Eg: Mutexes) + core.queue.front()->forceYield = true; + core.queue.splice(std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority), core.queue, core.queue.begin()); + core.queue.push_front(thread); + } else { + // If we're inserting another thread then we just insert it after the thread in line + // It'll automatically be ready to be scheduled when the thread at the front yields + // This enforces strict synchronization for the thread to run and waits till the previous thread has yielded itself + core.queue.insert(std::next(core.queue.begin()), thread); + } + if (state.thread != core.queue.front()) core.queue.front()->SendSignal(YieldSignal); else @@ -109,7 +121,7 @@ namespace skyline::kernel { auto &thread{state.thread}; auto *core{&cores.at(thread->coreId)}; - std::shared_lock lock(core->mutex); + std::unique_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 (!thread->wakeCondition.wait_for(lock, loadBalanceThreshold, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) { @@ -119,7 +131,7 @@ namespace skyline::kernel { lock.lock(); } else { core = &cores.at(thread->coreId); - lock = std::shared_lock(core->mutex); + lock = std::unique_lock(core->mutex); } loadBalanceThreshold *= 2; // We double the duration required for future load balancing for this invocation to minimize pointless load balancing @@ -141,7 +153,7 @@ namespace skyline::kernel { auto &thread{state.thread}; auto *core{&cores.at(thread->coreId)}; - std::shared_lock lock(core->mutex); + std::unique_lock lock(core->mutex); 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()}}; @@ -177,10 +189,36 @@ namespace skyline::kernel { struct itimerspec spec{}; timer_settime(thread->preemptionTimer, 0, &spec, nullptr); } + thread->isPreempted = false; + } else if (thread->forceYield) { + // We need to check if this thread was yielded by another thread on behalf of this thread + // If it is then we just need to disarm the preemption timer and update the average timeslice + // If it isn't then we need to throw an exception as it's indicative of an invalid state + struct ThreadComparision { + constexpr bool operator()(const i8 priority, const std::shared_ptr &it) { return priority < it->priority; } + + constexpr bool operator()(const std::shared_ptr &it, const i8 priority) { return it->priority < priority; } + }; + auto bounds{std::equal_range(core.queue.begin(), core.queue.end(), thread->priority.load(), ThreadComparision{})}; + + auto iterator{std::find(bounds.first, bounds.second, thread)}; + if (iterator != bounds.second) { + thread->averageTimeslice = (thread->averageTimeslice / 4) + (3 * (util::GetTimeTicks() - thread->timesliceStart / 4)); + + if (cooperative && thread->isPreempted) { + struct itimerspec spec{}; + timer_settime(thread->preemptionTimer, 0, &spec, nullptr); + } + + thread->isPreempted = false; + } else { + throw exception("T{} called Rotate while not being in C{}'s queue after being forcefully yielded", thread->id, thread->coreId); + } } else { - throw exception("T{} called Rotate while not being at the front of C{}'s queue", thread->id, thread->coreId); + throw exception("T{} called Rotate while not being in C{}'s queue", thread->id, thread->coreId); } + thread->forceYield = false; } void Scheduler::UpdatePriority(const std::shared_ptr &thread) { diff --git a/app/src/main/cpp/skyline/kernel/scheduler.h b/app/src/main/cpp/skyline/kernel/scheduler.h index 4265bce6..8b40565e 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.h +++ b/app/src/main/cpp/skyline/kernel/scheduler.h @@ -46,7 +46,7 @@ namespace skyline { struct CoreContext { 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::mutex mutex; //!< Synchronizes all operations on the queue std::list> queue; //!< A queue of threads which are running or to be run on this core CoreContext(u8 id, u8 preemptionPriority); diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index ee82263e..a7615a00 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -647,7 +647,7 @@ namespace skyline::kernel::svc { } if (wakeObject) { - state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles[index]); + state.logger->Debug("svcWaitSynchronization: Signalled handle: 0x{:X}", waitHandles[wakeIndex]); state.ctx->gpr.w0 = Result{}; state.ctx->gpr.w1 = wakeIndex; } else if (state.thread->cancelSync) { diff --git a/app/src/main/cpp/skyline/kernel/types/KThread.h b/app/src/main/cpp/skyline/kernel/types/KThread.h index 11fc161a..ecd666d6 100644 --- a/app/src/main/cpp/skyline/kernel/types/KThread.h +++ b/app/src/main/cpp/skyline/kernel/types/KThread.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "KSyncObject.h" #include "KPrivateMemory.h" #include "KSharedMemory.h" @@ -40,7 +41,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::condition_variable 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 @@ -51,6 +52,7 @@ namespace skyline { u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread timer_t preemptionTimer{}; //!< A kernel timer used for preemption interrupts bool isPreempted{}; //!< If the preemption timer has been armed and will fire + bool forceYield{}; //!< If the thread has been forcefully yielded by another thread std::mutex waiterMutex; //!< Synchronizes operations on mutation of the waiter members u32* waitKey; //!< The key of the mutex which this thread is waiting on KHandle waitTag; //!< The handle of the thread which requested the mutex lock diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp index 91e84eed..e80a66b4 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.cpp +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.cpp @@ -142,6 +142,18 @@ namespace skyline::service::hid { return {}; } + Result IHidServer::SendVibrationValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { + const auto &handle{request.Pop()}; + auto &device{state.input->npad.at(handle.id)}; + if (device.type == handle.GetType()) { + const auto &value{request.Pop()}; + state.logger->Debug("Vibration - Handle: 0x{:02X} (0b{:05b}), Vibration: {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz", static_cast(handle.id), static_cast(handle.type), value.amplitudeLow, value.frequencyLow, value.amplitudeHigh, value.frequencyHigh); + device.Vibrate(handle.isRight, value); + } + + return {}; + } + Result IHidServer::SendVibrationValues(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) { request.Skip(); // appletResourceUserId diff --git a/app/src/main/cpp/skyline/services/hid/IHidServer.h b/app/src/main/cpp/skyline/services/hid/IHidServer.h index 981ef2c2..dac5570c 100644 --- a/app/src/main/cpp/skyline/services/hid/IHidServer.h +++ b/app/src/main/cpp/skyline/services/hid/IHidServer.h @@ -115,7 +115,13 @@ namespace skyline::service::hid { Result CreateActiveVibrationDeviceList(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); /** - * @brief Send vibration values to an NPad + * @brief Send a single vibration value to a HID device + * @url https://switchbrew.org/wiki/HID_services#SendVibrationValue + */ + Result SendVibrationValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); + + /** + * @brief Send vibration values to a HID device * @url https://switchbrew.org/wiki/HID_services#SendVibrationValues */ Result SendVibrationValues(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); @@ -138,6 +144,7 @@ namespace skyline::service::hid { SFUNC(0x7B, IHidServer, SetNpadJoyAssignmentModeSingle), SFUNC(0x7C, IHidServer, SetNpadJoyAssignmentModeDual), SFUNC(0xCB, IHidServer, CreateActiveVibrationDeviceList), + SFUNC(0xC9, IHidServer, SendVibrationValue), SFUNC(0xCE, IHidServer, SendVibrationValues) ) };