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.
This commit is contained in:
PixelyIon 2022-04-09 17:33:40 +05:30
parent 36a7ad06bd
commit b706aa3463
5 changed files with 121 additions and 1 deletions

View File

@ -85,6 +85,13 @@ namespace skyline::kernel {
void Scheduler::InsertThread(const std::shared_ptr<type::KThread> &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<type::KThread> &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<type::KThread> &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();
}
}

View File

@ -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<type::KThread> &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<type::KThread> &thread);
};
/**

View File

@ -1085,6 +1085,61 @@ namespace skyline::kernel::svc {
state.ctx->gpr.w0 = Result{};
}
void SetThreadActivity(const DeviceState &state) {
u32 activityValue{static_cast<u32>(state.ctx->gpr.w1)};
enum class ThreadActivity : u32 {
Runnable = 0,
Paused = 1,
} activity{static_cast<ThreadActivity>(activityValue)};
switch (activity) {
case ThreadActivity::Runnable:
case ThreadActivity::Paused:
break;
default:
Logger::Warn("Invalid thread activity: {}", static_cast<u32>(activity));
state.ctx->gpr.w0 = result::InvalidEnumValue;
return;
}
KHandle threadHandle{state.ctx->gpr.w0};
try {
auto thread{state.process->GetHandle<type::KThread>(threadHandle)};
if (thread == state.thread) {
Logger::Warn("Thread setting own activity: {} (Thread: 0x{:X})", static_cast<u32>(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<u32>(threadHandle));
state.ctx->gpr.w0 = result::InvalidHandle;
}
}
void WaitForAddress(const DeviceState &state) {
auto address{reinterpret_cast<u32 *>(state.ctx->gpr.x0)};
if (!util::IsWordAligned(address)) [[unlikely]] {

View File

@ -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

View File

@ -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();