mirror of
https://github.com/skyline-emu/skyline.git
synced 2024-11-04 23:55:08 +01:00
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:
parent
36a7ad06bd
commit
b706aa3463
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -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]] {
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user