// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #pragma once #include #include #include #include "Core/HLE/sceKernelThread.h" namespace HLEKernel { // Should be called from the CoreTiming handler for the wait func. template inline void WaitExecTimeout(SceUID threadID) { u32 error; SceUID uid = __KernelGetWaitID(threadID, waitType, error); u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); KO *ko = uid == 0 ? NULL : kernelObjects.Get(uid, error); if (ko) { if (timeoutPtr != 0) Memory::Write_U32(0, timeoutPtr); // This thread isn't waiting anymore, but we'll remove it from waitingThreads later. // The reason is, if it times out, but what it was waiting on is DELETED prior to it // actually running, it will get a DELETE result instead of a TIMEOUT. // So, we need to remember it or we won't be able to mark it DELETE instead later. __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT); __KernelReSchedule("wait timed out"); } } // Move a thread from the waiting thread list to the paused thread list. // This version is for vectors which contain structs, which must have SceUID threadID and u64 pausedTimeout. // Should not be called directly. template inline bool WaitPauseHelperUpdate(SceUID pauseKey, SceUID threadID, std::vector &waitingThreads, std::map &pausedWaits, u64 pauseTimeout) { WaitInfoType waitData = {0}; for (size_t i = 0; i < waitingThreads.size(); i++) { WaitInfoType *t = &waitingThreads[i]; if (t->threadID == threadID) { waitData = *t; // TODO: Hmm, what about priority/fifo order? Does it lose its place in line? waitingThreads.erase(waitingThreads.begin() + i); break; } } if (waitData.threadID != threadID) return false; waitData.pausedTimeout = pauseTimeout; pausedWaits[pauseKey] = waitData; return true; } // Move a thread from the waiting thread list to the paused thread list. // This version is for a simpler list of SceUIDs. The paused list is a std::map. // Should not be called directly. template <> inline bool WaitPauseHelperUpdate(SceUID pauseKey, SceUID threadID, std::vector &waitingThreads, std::map &pausedWaits, u64 pauseTimeout) { // TODO: Hmm, what about priority/fifo order? Does it lose its place in line? waitingThreads.erase(std::remove(waitingThreads.begin(), waitingThreads.end(), threadID), waitingThreads.end()); pausedWaits[pauseKey] = pauseTimeout; return true; } // Retrieve the paused wait info from the list, and pop it. // Returns the pausedTimeout value. // Should not be called directly. template inline u64 WaitPauseHelperGet(SceUID pauseKey, SceUID threadID, std::map &pausedWaits, WaitInfoType &waitData) { waitData = pausedWaits[pauseKey]; u64 waitDeadline = waitData.pausedTimeout; pausedWaits.erase(pauseKey); return waitDeadline; } // Retrieve the paused wait info from the list, and pop it. // This version is for a simple std::map paused list. // Should not be called directly. template <> inline u64 WaitPauseHelperGet(SceUID pauseKey, SceUID threadID, std::map &pausedWaits, SceUID &waitData) { waitData = threadID; u64 waitDeadline = pausedWaits[pauseKey]; pausedWaits.erase(pauseKey); return waitDeadline; } enum WaitBeginEndCallbackResult { // Returned when the thread cannot be found in the waiting threads list. // Only returned for struct types, which have other data than the threadID. WAIT_CB_BAD_WAIT_DATA = -2, // Returned when the wait ID of the thread no longer matches the kernel object. WAIT_CB_BAD_WAIT_ID = -1, // Success, whether that means the wait was paused, deleted, etc. WAIT_CB_SUCCESS = 0, // Success, and resumed waiting. Useful for logging. WAIT_CB_RESUMED_WAIT = 1, // Success, but the wait timed out. Useful for logging. WAIT_CB_TIMED_OUT = 2, }; // Meant to be called in a registered begin callback function for a wait type. // // The goal of this function is to pause the wait. While inside a callback, waits are released. // Once the callback returns, the wait should be resumed (see WaitEndCallback.) // // This assumes the object has been validated already. The primary purpose is if you need // to use a specific pausedWaits list (for example, sceMsgPipe has two types of waiting per object.) // // In most cases, use the other, simpler version of WaitBeginCallback(). template WaitBeginEndCallbackResult WaitBeginCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer, std::vector &waitingThreads, std::map &pausedWaits, bool doTimeout = true) { SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId; // This means two callbacks in a row. PSP crashes if the same callback waits inside itself (may need more testing.) // TODO: Handle this better? if (pausedWaits.find(pauseKey) != pausedWaits.end()) { return WAIT_CB_SUCCESS; } u64 pausedTimeout = 0; if (doTimeout && waitTimer != -1) { s64 cyclesLeft = CoreTiming::UnscheduleEvent(waitTimer, threadID); pausedTimeout = CoreTiming::GetTicks() + cyclesLeft; } if (!WaitPauseHelperUpdate(pauseKey, threadID, waitingThreads, pausedWaits, pausedTimeout)) { return WAIT_CB_BAD_WAIT_DATA; } return WAIT_CB_SUCCESS; } // Meant to be called in a registered begin callback function for a wait type. // // The goal of this function is to pause the wait. While inside a callback, waits are released. // Once the callback returns, the wait should be resumed (see WaitEndCallback.) // // In the majority of cases, calling this function is sufficient for the BeginCallback handler. template WaitBeginEndCallbackResult WaitBeginCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer) { u32 error; SceUID uid = __KernelGetWaitID(threadID, waitType, error); u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); KO *ko = uid == 0 ? NULL : kernelObjects.Get(uid, error); if (ko) { return WaitBeginCallback(threadID, prevCallbackId, waitTimer, ko->waitingThreads, ko->pausedWaits, timeoutPtr != 0); } else { return WAIT_CB_BAD_WAIT_ID; } } // Meant to be called in a registered end callback function for a wait type. // // The goal of this function is to resume the wait, or to complete it if a wait is no longer needed. // // This version allows you to specify the pausedWaits and waitingThreads vectors, primarily for // MsgPipes which have two waiting thread lists. Unlike the matching WaitBeginCallback() function, // this still validates the wait (since it needs other data from the object.) // // In most cases, use the other, simpler version of WaitEndCallback(). template WaitBeginEndCallbackResult WaitEndCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer, TryUnlockFunc TryUnlock, WaitInfoType &waitData, std::vector &waitingThreads, std::map &pausedWaits) { SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId; // Note: Cancel does not affect suspended semaphore waits, probably same for others. u32 error; SceUID uid = __KernelGetWaitID(threadID, waitType, error); u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); KO *ko = uid == 0 ? NULL : kernelObjects.Get(uid, error); if (!ko || pausedWaits.find(pauseKey) == 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 && waitTimer != -1) { Memory::Write_U32(0, timeoutPtr); } __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_DELETE); return WAIT_CB_SUCCESS; } u64 waitDeadline = WaitPauseHelperGet(pauseKey, threadID, pausedWaits, waitData); // TODO: Don't wake up if __KernelCurHasReadyCallbacks()? bool wokeThreads; // Attempt to unlock. if (TryUnlock(ko, waitData, error, 0, wokeThreads)) { return WAIT_CB_SUCCESS; } // 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 && waitTimer != -1) { Memory::Write_U32(0, timeoutPtr); } __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_TIMEOUT); return WAIT_CB_TIMED_OUT; } else { if (timeoutPtr != 0 && waitTimer != -1) { CoreTiming::ScheduleEvent(cyclesLeft, waitTimer, __KernelGetCurThread()); } return WAIT_CB_RESUMED_WAIT; } } // Meant to be called in a registered end callback function for a wait type. // // The goal of this function is to resume the wait, or to complete it if a wait is no longer needed. // // The TryUnlockFunc signature should be (choosen due to similarity to existing funcitons): // bool TryUnlock(KO *ko, WaitInfoType waitingThreadInfo, u32 &error, int result, bool &wokeThreads) template WaitBeginEndCallbackResult WaitEndCallback(SceUID threadID, SceUID prevCallbackId, int waitTimer, TryUnlockFunc TryUnlock) { u32 error; SceUID uid = __KernelGetWaitID(threadID, waitType, error); u32 timeoutPtr = __KernelGetWaitTimeoutPtr(threadID, error); KO *ko = uid == 0 ? NULL : kernelObjects.Get(uid, error); // We need the ko for the vectors, but to avoid a null check we validate it here too. if (!ko) { // 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 && waitTimer != -1) { Memory::Write_U32(0, timeoutPtr); } __KernelResumeThreadFromWait(threadID, SCE_KERNEL_ERROR_WAIT_DELETE); return WAIT_CB_SUCCESS; } WaitInfoType waitData; auto result = WaitEndCallback(threadID, prevCallbackId, waitTimer, TryUnlock, waitData, ko->waitingThreads, ko->pausedWaits); if (result == WAIT_CB_RESUMED_WAIT) { // TODO: Should this not go at the end? ko->waitingThreads.push_back(waitData); } return result; } // Verify that a thread has not been released from waiting, e.g. by sceKernelReleaseWaitThread(). // For a waiting thread info struct. template inline bool VerifyWait(const T &waitInfo, WaitType waitType, SceUID uid) { u32 error; SceUID waitID = __KernelGetWaitID(waitInfo.threadID, waitType, error); return waitID == uid && error == 0; } // Verify that a thread has not been released from waiting, e.g. by sceKernelReleaseWaitThread(). template <> inline bool VerifyWait(const SceUID &threadID, WaitType waitType, SceUID uid) { u32 error; SceUID waitID = __KernelGetWaitID(threadID, waitType, error); return waitID == uid && error == 0; } // Resume a thread from waiting for a particular object. template inline bool ResumeFromWait(SceUID threadID, WaitType waitType, SceUID uid, T result) { if (VerifyWait(threadID, waitType, uid)) { __KernelResumeThreadFromWait(threadID, result); return true; } return false; } // Removes threads that are not waiting anymore from a waitingThreads list. template inline void CleanupWaitingThreads(WaitType waitType, SceUID uid, std::vector &waitingThreads) { size_t size = waitingThreads.size(); for (size_t i = 0; i < size; ++i) { if (!VerifyWait(waitingThreads[i], waitType, uid)) { // Decrement size and swap what was there with i. if (--size != i) { std::swap(waitingThreads[i], waitingThreads[size]); } // Now we haven't checked the new i, so go back and do i again. --i; } } waitingThreads.resize(size); } template inline void RemoveWaitingThread(std::vector &waitingThreads, const SceUID threadID) { waitingThreads.erase(std::remove(waitingThreads.begin(), waitingThreads.end(), threadID), waitingThreads.end()); } };