diff --git a/Core/HLE/sceKernelEventFlag.cpp b/Core/HLE/sceKernelEventFlag.cpp index 1e8a41ed97..cd04d1e0ab 100644 --- a/Core/HLE/sceKernelEventFlag.cpp +++ b/Core/HLE/sceKernelEventFlag.cpp @@ -45,6 +45,7 @@ struct EventFlagTh u32 bits; u32 wait; u32 outAddr; + u64 pausedTimeout; }; class EventFlag : public KernelObject @@ -70,11 +71,14 @@ public: p.Do(nef); EventFlagTh eft = {0}; p.Do(waitingThreads, eft); + p.Do(pausedWaits); p.DoMarker("EventFlag"); } NativeEventFlag nef; std::vector waitingThreads; + // Key is the callback id it was for, or if no callback, the thread id. + std::map pausedWaits; }; @@ -102,9 +106,13 @@ enum PspEventFlagWaitTypes int eventFlagWaitTimer = -1; +void __KernelEventFlagBeginCallback(SceUID threadID, SceUID prevCallbackId); +void __KernelEventFlagEndCallback(SceUID threadID, SceUID prevCallbackId, u32 &returnValue); + void __KernelEventFlagInit() { eventFlagWaitTimer = CoreTiming::RegisterEvent("EventFlagTimeout", __KernelEventFlagTimeout); + __KernelRegisterWaitTypeFuncs(WAITTYPE_EVENTFLAG, __KernelEventFlagBeginCallback, __KernelEventFlagEndCallback); } void __KernelEventFlagDoState(PointerWrap &p) @@ -186,6 +194,106 @@ bool __KernelClearEventFlagThreads(EventFlag *e, int reason) return wokeThreads; } +void __KernelEventFlagBeginCallback(SceUID threadID, SceUID prevCallbackId) +{ + SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId; + + u32 error; + SceUID flagID = __KernelGetWaitID(threadID, WAITTYPE_EVENTFLAG, error); + u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); + EventFlag *flag = flagID == 0 ? NULL : kernelObjects.Get(flagID, error); + if (flag) + { + + // This means two callbacks in a row. PSP crashes if the same callback runs inside itself. + // TODO: Handle this better? + if (flag->pausedWaits.find(pauseKey) != flag->pausedWaits.end()) + return; + + EventFlagTh waitData = {0}; + for (size_t i = 0; i < flag->waitingThreads.size(); i++) + { + EventFlagTh *t = &flag->waitingThreads[i]; + if (t->tid == threadID) + { + waitData = *t; + // TODO: Hmm, what about priority/fifo order? Does it lose its place in line? + flag->waitingThreads.erase(flag->waitingThreads.begin() + i); + flag->nef.numWaitThreads--; + break; + } + } + + if (waitData.tid != threadID) + { + ERROR_LOG_REPORT(HLE, "sceKernelWaitEventFlagCB: wait not found to pause for callback"); + return; + } + + if (timeoutPtr != 0 && eventFlagWaitTimer != -1) + { + s64 cyclesLeft = CoreTiming::UnscheduleEvent(eventFlagWaitTimer, threadID); + waitData.pausedTimeout = CoreTiming::GetTicks() + cyclesLeft; + } + else + waitData.pausedTimeout = 0; + + flag->pausedWaits[pauseKey] = waitData; + DEBUG_LOG(HLE, "sceKernelWaitEventFlagCB: Suspending lock wait for callback"); + } + else + WARN_LOG_REPORT(HLE, "sceKernelWaitEventFlagCB: beginning callback with bad wait id?"); +} + +void __KernelEventFlagEndCallback(SceUID threadID, SceUID prevCallbackId, u32 &returnValue) +{ + SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId; + + u32 error; + SceUID flagID = __KernelGetWaitID(threadID, WAITTYPE_EVENTFLAG, error); + u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); + EventFlag *flag = flagID == 0 ? NULL : kernelObjects.Get(flagID, error); + if (!flag || flag->pausedWaits.find(pauseKey) == flag->pausedWaits.end()) + { + // TODO: Since it was deleted, we don't know how long was actually left. + // For now, we just say the full time was taken. + if (timeoutPtr != 0 && eventFlagWaitTimer != -1) + Memory::Write_U32(0, timeoutPtr); + + __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_DELETE); + return; + } + + EventFlagTh waitData = flag->pausedWaits[pauseKey]; + u64 waitDeadline = waitData.pausedTimeout; + flag->pausedWaits.erase(pauseKey); + flag->nef.numWaitThreads++; + + // TODO: Don't wake up if __KernelCurHasReadyCallbacks()? + + bool wokeThreads; + // Attempt to unlock. + if (__KernelUnlockEventFlagForThread(flag, waitData, error, 0, wokeThreads)) + return; + + // We only check if it timed out if it couldn't unlock. + s64 cyclesLeft = waitDeadline - CoreTiming::GetTicks(); + if (cyclesLeft < 0 && waitDeadline != 0) + { + if (timeoutPtr != 0 && eventFlagWaitTimer != -1) + Memory::Write_U32(0, timeoutPtr); + + __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT); + } + else + { + // TODO: Should this not go at the end? + flag->waitingThreads.push_back(waitData); + + DEBUG_LOG(HLE, "sceKernelWaitEventFlagCB: Resuming lock wait for callback"); + } +} + //SceUID sceKernelCreateEventFlag(const char *name, int attr, int bits, SceKernelEventFlagOptParam *opt); int sceKernelCreateEventFlag(const char *name, u32 flag_attr, u32 flag_initPattern, u32 optPtr) {