From b706aa3463918587266eb4b8cae427586e310a60 Mon Sep 17 00:00:00 2001 From: PixelyIon Date: Sat, 9 Apr 2022 17:33:40 +0530 Subject: [PATCH] Implement SVC `SetThreadActivity` This SVC can pause/resume a thread, it is used by engines like Unity to pause a thread during a GC world stop. --- app/src/main/cpp/skyline/kernel/scheduler.cpp | 44 +++++++++++++++ app/src/main/cpp/skyline/kernel/scheduler.h | 12 ++++ app/src/main/cpp/skyline/kernel/svc.cpp | 55 +++++++++++++++++++ app/src/main/cpp/skyline/kernel/svc.h | 8 ++- .../main/cpp/skyline/kernel/types/KThread.h | 3 + 5 files changed, 121 insertions(+), 1 deletion(-) diff --git a/app/src/main/cpp/skyline/kernel/scheduler.cpp b/app/src/main/cpp/skyline/kernel/scheduler.cpp index 806f560d..d3652cb7 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.cpp +++ b/app/src/main/cpp/skyline/kernel/scheduler.cpp @@ -85,6 +85,13 @@ namespace skyline::kernel { void Scheduler::InsertThread(const std::shared_ptr &thread) { auto &core{cores.at(thread->coreId)}; std::unique_lock lock(core.mutex); + + if (thread->isPaused) { + // We cannot insert a thread that is paused, so we need to wait until it has been resumed + thread->insertThreadOnResume = false; + thread->scheduleCondition.wait(lock, [&]() { return !thread->isPaused; }); + } + auto nextThread{std::upper_bound(core.queue.begin(), core.queue.end(), thread->priority.load(), type::KThread::IsHigherPriority)}; if (nextThread == core.queue.begin()) { if (nextThread != core.queue.end()) { @@ -343,4 +350,41 @@ namespace skyline::kernel { } } } + + void Scheduler::PauseThread(const std::shared_ptr &thread) { + CoreContext *core{&cores.at(thread->coreId)}; + std::unique_lock lock{core->mutex}; + + thread->isPaused = true; + + auto it{std::find(core->queue.begin(), core->queue.end(), thread)}; + if (it != core->queue.end()) { + thread->insertThreadOnResume = true; // If we're handling removing the thread then we need to be responsible for inserting it back inside ResumeThread + + it = core->queue.erase(it); + if (it == core->queue.begin() && it != core->queue.end()) + (*it)->scheduleCondition.notify_one(); + + if (it == core->queue.begin() && !thread->pendingYield) { + // We need to send a yield signal to the thread if it's currently running + thread->SendSignal(YieldSignal); + thread->pendingYield = true; + thread->forceYield = true; + } + } else { + // If removal of the thread was performed by a lock/sleep/etc then we don't need to handle inserting it back ourselves inside ResumeThread + // It'll be automatically re-inserted when the lock/sleep is completed and InsertThread will block till the thread is resumed + thread->insertThreadOnResume = false; + } + } + + void Scheduler::ResumeThread(const std::shared_ptr &thread) { + thread->isPaused = false; + if (thread->insertThreadOnResume) + // If we handled removing the thread then we need to be responsible for inserting it back as well + InsertThread(thread); + else + // If we're not inserting the thread back into the queue ourselves then we need to notify the thread inserting it about the updated pause state + thread->scheduleCondition.notify_one(); + } } diff --git a/app/src/main/cpp/skyline/kernel/scheduler.h b/app/src/main/cpp/skyline/kernel/scheduler.h index 22f4407b..f45066d5 100644 --- a/app/src/main/cpp/skyline/kernel/scheduler.h +++ b/app/src/main/cpp/skyline/kernel/scheduler.h @@ -137,6 +137,18 @@ namespace skyline { * @note We will only wake a thread if it's determined to be a better pick than the thread which would be run on this core next */ void WakeParkedThread(); + + /** + * @brief Pauses the supplied thread till a corresponding call to ResumeThread has been made + * @note 'KThread::coreMigrationMutex' **must** be locked by the calling thread prior to calling this + */ + void PauseThread(const std::shared_ptr &thread); + + /** + * @brief Resumes a thread which was previously paused by a call to PauseThread + * @note 'KThread::coreMigrationMutex' **must** be locked by the calling thread prior to calling this + */ + void ResumeThread(const std::shared_ptr &thread); }; /** diff --git a/app/src/main/cpp/skyline/kernel/svc.cpp b/app/src/main/cpp/skyline/kernel/svc.cpp index 03ce18a9..5f04761c 100644 --- a/app/src/main/cpp/skyline/kernel/svc.cpp +++ b/app/src/main/cpp/skyline/kernel/svc.cpp @@ -1085,6 +1085,61 @@ namespace skyline::kernel::svc { state.ctx->gpr.w0 = Result{}; } + void SetThreadActivity(const DeviceState &state) { + u32 activityValue{static_cast(state.ctx->gpr.w1)}; + enum class ThreadActivity : u32 { + Runnable = 0, + Paused = 1, + } activity{static_cast(activityValue)}; + + switch (activity) { + case ThreadActivity::Runnable: + case ThreadActivity::Paused: + break; + + default: + Logger::Warn("Invalid thread activity: {}", static_cast(activity)); + state.ctx->gpr.w0 = result::InvalidEnumValue; + return; + } + + KHandle threadHandle{state.ctx->gpr.w0}; + try { + auto thread{state.process->GetHandle(threadHandle)}; + if (thread == state.thread) { + Logger::Warn("Thread setting own activity: {} (Thread: 0x{:X})", static_cast(activity), threadHandle); + state.ctx->gpr.w0 = result::Busy; + return; + } + + std::scoped_lock guard{thread->coreMigrationMutex}; + if (activity == ThreadActivity::Runnable) { + if (thread->running && thread->isPaused) { + Logger::Debug("Resuming Thread: 0x{:X}", threadHandle); + state.scheduler->ResumeThread(thread); + } else { + Logger::Warn("Attempting to resume thread which is already runnable (Thread: 0x{:X})", threadHandle); + state.ctx->gpr.w0 = result::InvalidState; + return; + } + } else if (activity == ThreadActivity::Paused) { + if (thread->running && !thread->isPaused) { + Logger::Debug("Pausing Thread: 0x{:X}", threadHandle); + state.scheduler->PauseThread(thread); + } else { + Logger::Warn("Attempting to pause thread which is already paused (Thread: 0x{:X})", threadHandle); + state.ctx->gpr.w0 = result::InvalidState; + return; + } + } + + state.ctx->gpr.w0 = Result{}; + } catch (const std::out_of_range &) { + Logger::Warn("'handle' invalid: 0x{:X}", static_cast(threadHandle)); + state.ctx->gpr.w0 = result::InvalidHandle; + } + } + void WaitForAddress(const DeviceState &state) { auto address{reinterpret_cast(state.ctx->gpr.x0)}; if (!util::IsWordAligned(address)) [[unlikely]] { diff --git a/app/src/main/cpp/skyline/kernel/svc.h b/app/src/main/cpp/skyline/kernel/svc.h index 001dbf26..7ed6af68 100644 --- a/app/src/main/cpp/skyline/kernel/svc.h +++ b/app/src/main/cpp/skyline/kernel/svc.h @@ -222,6 +222,12 @@ namespace skyline::kernel::svc { */ void UnmapPhysicalMemory(const DeviceState &state); + /** + * @brief Sets if a thread is runnable or paused + * @url https://switchbrew.org/wiki/SVC#SetThreadActivity + */ + void SetThreadActivity(const DeviceState &state); + /** * @brief Waits on an address based on the value of the address * @url https://switchbrew.org/wiki/SVC#WaitForAddress @@ -305,7 +311,7 @@ namespace skyline::kernel::svc { SVC_NONE, // 0x2F SVC_NONE, // 0x30 SVC_NONE, // 0x31 - SVC_NONE, // 0x32 + SVC_ENTRY(SetThreadActivity), // 0x32 SVC_NONE, // 0x33 SVC_ENTRY(WaitForAddress), // 0x34 SVC_ENTRY(SignalToAddress), // 0x35 diff --git a/app/src/main/cpp/skyline/kernel/types/KThread.h b/app/src/main/cpp/skyline/kernel/types/KThread.h index e9c36f06..3e79ad77 100644 --- a/app/src/main/cpp/skyline/kernel/types/KThread.h +++ b/app/src/main/cpp/skyline/kernel/types/KThread.h @@ -72,6 +72,9 @@ namespace skyline { bool cancelSync{false}; //!< Whether to cancel the SvcWaitSynchronization call this thread currently is in/the next one it joins type::KSyncObject *wakeObject{}; //!< A pointer to the synchronization object responsible for waking this thread up + bool isPaused{false}; //!< If the thread is currently paused and not runnable + bool insertThreadOnResume{false}; //!< If the thread should be inserted into the scheduler when it resumes (used for pausing threads during sleep/sync) + KThread(const DeviceState &state, KHandle handle, KProcess *parent, size_t id, void *entry, u64 argument, void *stackTop, i8 priority, u8 idealCore); ~KThread();