diff --git a/Core/HLE/sceKernelInterrupt.cpp b/Core/HLE/sceKernelInterrupt.cpp index e515cc9f0a..83f69f81b4 100644 --- a/Core/HLE/sceKernelInterrupt.cpp +++ b/Core/HLE/sceKernelInterrupt.cpp @@ -62,6 +62,7 @@ std::list pendingInterrupts; // Yeah, this bit is a bit silly. static int interruptsEnabled = 1; static bool inInterrupt; +static SceUID threadBeforeInterrupt; void sceKernelCpuSuspendIntr() @@ -216,6 +217,7 @@ void __InterruptsInit() for (int i = 0; i < (int)ARRAY_SIZE(intrHandlers); ++i) intrHandlers[i] = new IntrHandler(i); intState.clear(); + threadBeforeInterrupt = 0; } void __InterruptsDoState(PointerWrap &p) @@ -233,6 +235,7 @@ void __InterruptsDoState(PointerWrap &p) p.Do(pendingInterrupts, pi); p.Do(interruptsEnabled); p.Do(inInterrupt); + p.Do(threadBeforeInterrupt); p.DoMarker("sceKernelInterrupt"); } @@ -317,7 +320,9 @@ retry: { // If we came from CoreTiming::Advance(), we might've come from a waiting thread's callback. // To avoid "injecting" return values into our saved state, we context switch here. - __KernelSwitchOffThread("interrupt"); + SceUID savedThread = __KernelGetCurThread(); + if (__KernelSwitchOffThread("interrupt")) + threadBeforeInterrupt = savedThread; PendingInterrupt pend = pendingInterrupts.front(); @@ -357,8 +362,13 @@ void __TriggerRunInterrupts(int type) hleRunInterrupts(); else if ((type & PSP_INTR_ALWAYS_RESCHED) != 0) { - if (!__RunOnePendingInterrupt()) - __KernelSwitchOffThread("interrupt"); + // "Always" only means if dispatch is enabled. + if (!__RunOnePendingInterrupt() && __KernelIsDispatchEnabled()) + { + SceUID savedThread = __KernelGetCurThread(); + if (__KernelSwitchOffThread("interrupt")) + threadBeforeInterrupt = savedThread; + } } else __RunOnePendingInterrupt(); @@ -392,7 +402,13 @@ void __KernelReturnFromInterrupt() // Alright, let's see if there's any more interrupts queued... if (!__RunOnePendingInterrupt()) - __KernelReSchedule("return from interrupt"); + { + // Otherwise, we reschedule when dispatch was enabled, or switch back otherwise. + if (__KernelIsDispatchEnabled()) + __KernelReSchedule("return from interrupt"); + else + __KernelSwitchToThread(threadBeforeInterrupt, "return from interrupt"); + } } void __RegisterIntrHandler(u32 intrNumber, IntrHandler* handler) diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 186b561241..71a802c38d 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -812,6 +812,37 @@ bool __KernelSwitchOffThread(const char *reason) return false; } +bool __KernelSwitchToThread(SceUID threadID, const char *reason) +{ + if (!reason) + reason = "switch to thread"; + + if (currentThread != threadIdleID[0] && currentThread != threadIdleID[1]) + { + ERROR_LOG(HLE, "__KernelSwitchToThread used when already on a thread."); + return false; + } + + if (currentThread == threadID) + return false; + + u32 error; + Thread *t = kernelObjects.Get(threadID, error); + if (!t) + ERROR_LOG(HLE, "__KernelSwitchToThread: %x doesn't exist", threadID) + else + { + Thread *current = __GetCurrentThread(); + if (current && current->isRunning()) + __KernelChangeReadyState(current, threadID, true); + + __KernelSwitchContext(t, reason); + return true; + } + + return false; +} + void __KernelIdle() { CoreTiming::Idle(); diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index bd9a5e235e..324390e2e8 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -228,6 +228,7 @@ void __KernelNotifyCallback(RegisteredCallbackType type, SceUID cbId, int notify // Switch to an idle / non-user thread, if not already on one. // Returns whether a switch occurred. bool __KernelSwitchOffThread(const char *reason); +bool __KernelSwitchToThread(SceUID threadID, const char *reason); // A call into game code. These can be pending on a thread. // Similar to Callback-s (NOT CallbackInfos) in JPCSP.