Fix Clean Exiting + Optimize Core Queues + Optimize Thread Insertion + Implement HID SendVibrationValue

This commit is contained in:
◱ PixelyIon 2021-01-16 02:45:06 +05:30 committed by ◱ Mark
parent 98b1fd9056
commit d5d133372f
10 changed files with 121 additions and 18 deletions

View File

@ -22,6 +22,7 @@ std::weak_ptr<skyline::gpu::GPU> GpuWeak;
std::weak_ptr<skyline::input::Input> InputWeak; std::weak_ptr<skyline::input::Input> InputWeak;
extern "C" JNIEXPORT void Java_emu_skyline_EmulationActivity_executeApplication(JNIEnv *env, jobject instance, jstring romUriJstring, jint romType, jint romFd, jint preferenceFd, jstring appFilesPathJstring) { 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; Fps = FrameTime = 0;
pthread_setname_np(pthread_self(), "EmuMain"); pthread_setname_np(pthread_self(), "EmuMain");

View File

@ -10,15 +10,19 @@ namespace skyline::signal {
thread_local std::exception_ptr SignalExceptionPtr; thread_local std::exception_ptr SignalExceptionPtr;
void ExceptionThrow() { 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{}; std::terminate_handler terminateHandler{};
[[clang::optnone]]
inline StackFrame *SafeFrameRecurse(size_t depth, StackFrame *frame) { inline StackFrame *SafeFrameRecurse(size_t depth, StackFrame *frame) {
if (frame) { if (frame) {
for (size_t it{}; it < depth; it++) { for (size_t it{}; it < depth; it++) {
if (frame->next) if (frame->lr && frame->next)
frame = frame->next; frame = frame->next;
else else
terminateHandler(); terminateHandler();
@ -29,6 +33,7 @@ namespace skyline::signal {
return frame; return frame;
} }
[[clang::optnone]]
void TerminateHandler() { void TerminateHandler() {
auto exception{std::current_exception()}; auto exception{std::current_exception()};
if (terminateHandler && exception && exception == SignalExceptionPtr) { if (terminateHandler && exception && exception == SignalExceptionPtr) {
@ -36,11 +41,25 @@ namespace skyline::signal {
asm("MOV %0, FP" : "=r"(frame)); asm("MOV %0, FP" : "=r"(frame));
frame = SafeFrameRecurse(2, frame); // We unroll past 'std::terminate' frame = SafeFrameRecurse(2, frame); // We unroll past 'std::terminate'
static void *exceptionThrowEnd{};
if (!exceptionThrowEnd) {
u32 *it{reinterpret_cast<u32 *>(&ExceptionThrow) + 1};
while (_Unwind_FindEnclosingFunction(it) == &ExceptionThrow)
it++;
exceptionThrowEnd = it - 1;
}
auto lookupFrame{frame}; auto lookupFrame{frame};
while (lookupFrame) { bool hasAdvanced{};
auto function{_Unwind_FindEnclosingFunction(frame->lr)}; while (lookupFrame && lookupFrame->lr) {
if (function == &ExceptionThrow) if (lookupFrame->lr >= reinterpret_cast<void *>(&ExceptionThrow) && lookupFrame->lr < exceptionThrowEnd) {
terminateHandler(); // We have no handler to consume the exception, it's time to quit 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; lookupFrame = lookupFrame->next;
} }
@ -68,7 +87,7 @@ namespace skyline::signal {
signalException.frames.push_back(reinterpret_cast<void *>(context->uc_mcontext.pc)); signalException.frames.push_back(reinterpret_cast<void *>(context->uc_mcontext.pc));
StackFrame *frame{reinterpret_cast<StackFrame *>(context->uc_mcontext.regs[29])}; StackFrame *frame{reinterpret_cast<StackFrame *>(context->uc_mcontext.regs[29])};
while (frame) { while (frame && frame->lr) {
signalException.frames.push_back(frame->lr); signalException.frames.push_back(frame->lr);
frame = frame->next; frame = frame->next;
} }

View File

@ -14,6 +14,29 @@ namespace skyline::signal {
void *lr; 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 * @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 * @note This doesn't inherit std::exception as it shouldn't be caught as such

View File

@ -105,6 +105,7 @@ namespace skyline::gpu::gpfifo {
} }
} catch (const std::exception &e) { } catch (const std::exception &e) {
state.logger->Write(Logger::LogLevel::Error, e.what()); state.logger->Write(Logger::LogLevel::Error, e.what());
signal::BlockSignal({SIGINT});
state.process->Kill(false); state.process->Kill(false);
} }
} }

View File

@ -36,7 +36,7 @@ namespace skyline::kernel {
u64 timeslice{}; u64 timeslice{};
if (!candidateCore.queue.empty()) { if (!candidateCore.queue.empty()) {
std::shared_lock coreLock(candidateCore.mutex); std::unique_lock coreLock(candidateCore.mutex);
auto threadIterator{candidateCore.queue.cbegin()}; auto threadIterator{candidateCore.queue.cbegin()};
if (threadIterator != candidateCore.queue.cend()) { if (threadIterator != candidateCore.queue.cend()) {
@ -89,8 +89,20 @@ namespace skyline::kernel {
if (nextThread == core.queue.begin()) { if (nextThread == core.queue.begin()) {
if (nextThread != core.queue.end()) { if (nextThread != core.queue.end()) {
// If the inserted thread has a higher priority than the currently running thread (and the queue isn't empty) // 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 if (state.thread == thread) {
core.queue.insert(std::next(core.queue.begin()), 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()) if (state.thread != core.queue.front())
core.queue.front()->SendSignal(YieldSignal); core.queue.front()->SendSignal(YieldSignal);
else else
@ -109,7 +121,7 @@ namespace skyline::kernel {
auto &thread{state.thread}; auto &thread{state.thread};
auto *core{&cores.at(thread->coreId)}; auto *core{&cores.at(thread->coreId)};
std::shared_lock lock(core->mutex); std::unique_lock lock(core->mutex);
if (loadBalance && thread->affinityMask.count() > 1) { 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 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; })) { while (!thread->wakeCondition.wait_for(lock, loadBalanceThreshold, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) {
@ -119,7 +131,7 @@ namespace skyline::kernel {
lock.lock(); lock.lock();
} else { } else {
core = &cores.at(thread->coreId); 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 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 &thread{state.thread};
auto *core{&cores.at(thread->coreId)}; 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->wakeCondition.wait_for(lock, timeout, [&]() { return !core->queue.empty() && core->queue.front() == thread; })) {
if (thread->priority == core->preemptionPriority) { if (thread->priority == core->preemptionPriority) {
struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}}; struct itimerspec spec{.it_value = {.tv_nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(PreemptiveTimeslice).count()}};
@ -177,10 +189,36 @@ namespace skyline::kernel {
struct itimerspec spec{}; struct itimerspec spec{};
timer_settime(thread->preemptionTimer, 0, &spec, nullptr); timer_settime(thread->preemptionTimer, 0, &spec, nullptr);
} }
thread->isPreempted = false; 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<type::KThread> &it) { return priority < it->priority; }
constexpr bool operator()(const std::shared_ptr<type::KThread> &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 { } 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<type::KThread> &thread) { void Scheduler::UpdatePriority(const std::shared_ptr<type::KThread> &thread) {

View File

@ -46,7 +46,7 @@ namespace skyline {
struct CoreContext { struct CoreContext {
u8 id; u8 id;
u8 preemptionPriority; //!< The priority at which this core becomes preemptive as opposed to cooperative 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<std::shared_ptr<type::KThread>> queue; //!< A queue of threads which are running or to be run on this core std::list<std::shared_ptr<type::KThread>> queue; //!< A queue of threads which are running or to be run on this core
CoreContext(u8 id, u8 preemptionPriority); CoreContext(u8 id, u8 preemptionPriority);

View File

@ -647,7 +647,7 @@ namespace skyline::kernel::svc {
} }
if (wakeObject) { 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.w0 = Result{};
state.ctx->gpr.w1 = wakeIndex; state.ctx->gpr.w1 = wakeIndex;
} else if (state.thread->cancelSync) { } else if (state.thread->cancelSync) {

View File

@ -6,6 +6,7 @@
#include <csetjmp> #include <csetjmp>
#include <nce/guest.h> #include <nce/guest.h>
#include <kernel/scheduler.h> #include <kernel/scheduler.h>
#include <common/signal.h>
#include "KSyncObject.h" #include "KSyncObject.h"
#include "KPrivateMemory.h" #include "KPrivateMemory.h"
#include "KSharedMemory.h" #include "KSharedMemory.h"
@ -40,7 +41,7 @@ namespace skyline {
u64 entryArgument; u64 entryArgument;
void *stackTop; 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<u8> basePriority; //!< The priority of the thread for the scheduler without any priority-inheritance std::atomic<u8> basePriority; //!< The priority of the thread for the scheduler without any priority-inheritance
std::atomic<u8> priority; //!< The priority of the thread for the scheduler std::atomic<u8> priority; //!< The priority of the thread for the scheduler
i8 idealCore; //!< The ideal CPU core for this thread to run on 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 u64 averageTimeslice{}; //!< A weighted average of the timeslice duration for this thread
timer_t 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 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 std::mutex waiterMutex; //!< Synchronizes operations on mutation of the waiter members
u32* waitKey; //!< The key of the mutex which this thread is waiting on 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 KHandle waitTag; //!< The handle of the thread which requested the mutex lock

View File

@ -142,6 +142,18 @@ namespace skyline::service::hid {
return {}; return {};
} }
Result IHidServer::SendVibrationValue(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
const auto &handle{request.Pop<NpadDeviceHandle>()};
auto &device{state.input->npad.at(handle.id)};
if (device.type == handle.GetType()) {
const auto &value{request.Pop<NpadVibrationValue>()};
state.logger->Debug("Vibration - Handle: 0x{:02X} (0b{:05b}), Vibration: {:.2f}@{:.2f}Hz, {:.2f}@{:.2f}Hz", static_cast<u8>(handle.id), static_cast<u8>(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) { Result IHidServer::SendVibrationValues(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
request.Skip<u64>(); // appletResourceUserId request.Skip<u64>(); // appletResourceUserId

View File

@ -115,7 +115,13 @@ namespace skyline::service::hid {
Result CreateActiveVibrationDeviceList(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); 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 * @url https://switchbrew.org/wiki/HID_services#SendVibrationValues
*/ */
Result SendVibrationValues(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); Result SendVibrationValues(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
@ -138,6 +144,7 @@ namespace skyline::service::hid {
SFUNC(0x7B, IHidServer, SetNpadJoyAssignmentModeSingle), SFUNC(0x7B, IHidServer, SetNpadJoyAssignmentModeSingle),
SFUNC(0x7C, IHidServer, SetNpadJoyAssignmentModeDual), SFUNC(0x7C, IHidServer, SetNpadJoyAssignmentModeDual),
SFUNC(0xCB, IHidServer, CreateActiveVibrationDeviceList), SFUNC(0xCB, IHidServer, CreateActiveVibrationDeviceList),
SFUNC(0xC9, IHidServer, SendVibrationValue),
SFUNC(0xCE, IHidServer, SendVibrationValues) SFUNC(0xCE, IHidServer, SendVibrationValues)
) )
}; };