mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-23 00:39:17 +01:00
Fix Clean Exiting + Optimize Core Queues + Optimize Thread Insertion + Implement HID SendVibrationValue
This commit is contained in:
parent
98b1fd9056
commit
d5d133372f
@ -22,6 +22,7 @@ std::weak_ptr<skyline::gpu::GPU> GpuWeak;
|
||||
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) {
|
||||
skyline::signal::ScopedStackBlocker stackBlocker;
|
||||
Fps = FrameTime = 0;
|
||||
|
||||
pthread_setname_np(pthread_self(), "EmuMain");
|
||||
|
@ -10,15 +10,19 @@ namespace skyline::signal {
|
||||
thread_local std::exception_ptr SignalExceptionPtr;
|
||||
|
||||
void ExceptionThrow() {
|
||||
// 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<u32 *>(&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)
|
||||
bool hasAdvanced{};
|
||||
while (lookupFrame && lookupFrame->lr) {
|
||||
if (lookupFrame->lr >= reinterpret_cast<void *>(&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<void *>(context->uc_mcontext.pc));
|
||||
StackFrame *frame{reinterpret_cast<StackFrame *>(context->uc_mcontext.regs[29])};
|
||||
while (frame) {
|
||||
while (frame && frame->lr) {
|
||||
signalException.frames.push_back(frame->lr);
|
||||
frame = frame->next;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
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<std::chrono::nanoseconds>(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<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 at the front of C{}'s queue", thread->id, thread->coreId);
|
||||
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 in C{}'s queue", thread->id, thread->coreId);
|
||||
}
|
||||
thread->forceYield = false;
|
||||
}
|
||||
|
||||
void Scheduler::UpdatePriority(const std::shared_ptr<type::KThread> &thread) {
|
||||
|
@ -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<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);
|
||||
|
@ -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) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <csetjmp>
|
||||
#include <nce/guest.h>
|
||||
#include <kernel/scheduler.h>
|
||||
#include <common/signal.h>
|
||||
#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<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
|
||||
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
|
||||
|
@ -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<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) {
|
||||
request.Skip<u64>(); // appletResourceUserId
|
||||
|
||||
|
@ -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)
|
||||
)
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user