/* This file is part of jpcsp. Jpcsp 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, either version 3 of the License, or (at your option) any later version. Jpcsp 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 for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.HLE.kernel.managers; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_MUTEX_NOT_FOUND; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_MUTEX_LOCKED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_MUTEX_RECURSIVE_NOT_ALLOWED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_MUTEX_UNLOCKED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_MUTEX_UNLOCK_UNDERFLOW; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_CANCELLED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_DELETE; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_STATUS_RELEASED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_TIMEOUT; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_READY; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_MUTEX; import java.util.HashMap; import java.util.Iterator; import jpcsp.HLE.Modules; import jpcsp.HLE.PspString; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import jpcsp.HLE.kernel.types.IWaitStateChecker; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.SceKernelMutexInfo; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.HLE.kernel.types.ThreadWaitInfo; import jpcsp.HLE.modules.ThreadManForUser; import org.apache.log4j.Logger; public class MutexManager { public static Logger log = ThreadManForUser.log; private HashMap<Integer, SceKernelMutexInfo> mutexMap; private MutexWaitStateChecker mutexWaitStateChecker; public final static int PSP_MUTEX_ATTR_FIFO = 0; public final static int PSP_MUTEX_ATTR_PRIORITY = 0x100; private final static int PSP_MUTEX_ATTR_ALLOW_RECURSIVE = 0x200; public void reset() { mutexMap = new HashMap<Integer, SceKernelMutexInfo>(); mutexWaitStateChecker = new MutexWaitStateChecker(); } /** Don't call this unless thread.waitType == PSP_WAIT_MUTEX * @return true if the thread was waiting on a valid mutex */ private boolean removeWaitingThread(SceKernelThreadInfo thread) { SceKernelMutexInfo info = mutexMap.get(thread.wait.Mutex_id); if (info == null) { return false; } info.threadWaitingList.removeWaitingThread(thread); return true; } /** Don't call this unless thread.wait.waitingOnMutex == true */ public void onThreadWaitTimeout(SceKernelThreadInfo thread) { // Untrack if (removeWaitingThread(thread)) { // Return WAIT_TIMEOUT thread.cpuContext._v0 = ERROR_KERNEL_WAIT_TIMEOUT; } else { log.warn("Mutex deleted while we were waiting for it! (timeout expired)"); // Return WAIT_DELETE thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE; } } public void onThreadWaitReleased(SceKernelThreadInfo thread) { // Untrack if (removeWaitingThread(thread)) { // Return ERROR_WAIT_STATUS_RELEASED thread.cpuContext._v0 = ERROR_KERNEL_WAIT_STATUS_RELEASED; } else { log.warn("EventFlag deleted while we were waiting for it!"); // Return WAIT_DELETE thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE; } } public void onThreadDeleted(SceKernelThreadInfo thread) { if (thread.isWaitingForType(PSP_WAIT_MUTEX)) { // decrement numWaitThreads removeWaitingThread(thread); } for (SceKernelMutexInfo info : mutexMap.values()) { if (info.threadid == thread.uid) { log.info(String.format("onThreadDeleted: thread %s owning mutex %s", thread, info)); } } } private void onMutexDeletedCancelled(int mid, int result) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; boolean reschedule = false; for (Iterator<SceKernelThreadInfo> it = threadMan.iterator(); it.hasNext(); ) { SceKernelThreadInfo thread = it.next(); if (thread.isWaitingFor(PSP_WAIT_MUTEX, mid)) { thread.cpuContext._v0 = result; threadMan.hleChangeThreadState(thread, PSP_THREAD_READY); reschedule = true; } } // Reschedule only if threads waked up. if (reschedule) { threadMan.hleRescheduleCurrentThread(); } } private void onMutexDeleted(int mid) { onMutexDeletedCancelled(mid, ERROR_KERNEL_WAIT_DELETE); } private void onMutexCancelled(int mid) { onMutexDeletedCancelled(mid, ERROR_KERNEL_WAIT_CANCELLED); } private void onMutexModified(SceKernelMutexInfo info) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; boolean reschedule = false; SceKernelThreadInfo checkedThread = null; while (true) { SceKernelThreadInfo thread = info.threadWaitingList.getNextWaitingThread(checkedThread); if (thread == null) { break; } if (tryLockMutex(info, thread.wait.Mutex_count, thread)) { if (log.isDebugEnabled()) { log.debug(String.format("onMutexModified waking thread %s", thread)); } info.threadWaitingList.removeWaitingThread(thread); thread.cpuContext._v0 = 0; threadMan.hleChangeThreadState(thread, PSP_THREAD_READY); reschedule = true; } else { checkedThread = thread; } } // Reschedule only if threads waked up. if (reschedule) { Modules.ThreadManForUserModule.hleRescheduleCurrentThread(); } } private boolean tryLockMutex(SceKernelMutexInfo info, int count, SceKernelThreadInfo thread) { if (info.lockedCount == 0) { // If the mutex is not locked, allow this thread to lock it. info.threadid = thread.uid; info.lockedCount += count; return true; } else if (info.threadid == thread.uid) { // If the mutex is already locked, but it's trying to be locked by the same thread // that acquired it initially, check if recursive locking is allowed. // If not, return an error. if (((info.attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) == PSP_MUTEX_ATTR_ALLOW_RECURSIVE)) { info.lockedCount += count; return true; } } return false; } public int sceKernelCreateMutex(PspString name, int attr, int count, int option_addr) { if (count < 0 || (count > 1 && (attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) == 0)) { return SceKernelErrors.ERROR_KERNEL_ILLEGAL_COUNT; } SceKernelMutexInfo info = new SceKernelMutexInfo(name.getString(), count, attr); mutexMap.put(info.uid, info); return info.uid; } public int sceKernelDeleteMutex(int uid) { SceKernelMutexInfo info = mutexMap.remove(uid); if (info == null) { log.warn("sceKernelDeleteMutex unknown UID " + Integer.toHexString(uid)); return ERROR_KERNEL_MUTEX_NOT_FOUND; } onMutexDeleted(uid); return 0; } private int hleKernelLockMutex(int uid, int count, int timeout_addr, boolean wait, boolean doCallbacks) { SceKernelMutexInfo info = mutexMap.get(uid); if (info == null) { log.warn(String.format("hleKernelLockMutex uid=%d, count=%d, timeout_addr=0x%08X, wait=%b, doCallbacks=%b - unknown UID", uid, count, timeout_addr, wait, doCallbacks)); return ERROR_KERNEL_MUTEX_NOT_FOUND; } if (count <= 0) { log.warn(String.format("hleKernelLockMutex uid=%d, count=%d, timeout_addr=0x%08X, wait=%b, doCallbacks=%b - illegal count", uid, count, timeout_addr, wait, doCallbacks)); return SceKernelErrors.ERROR_KERNEL_ILLEGAL_COUNT; } if (count > 1 && (info.attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) == 0) { log.warn(String.format("hleKernelLockMutex uid=%d, count=%d, timeout_addr=0x%08X, wait=%b, doCallbacks=%b - illegal count", uid, count, timeout_addr, wait, doCallbacks)); return SceKernelErrors.ERROR_KERNEL_ILLEGAL_COUNT; } ThreadManForUser threadMan = Modules.ThreadManForUserModule; SceKernelThreadInfo currentThread = threadMan.getCurrentThread(); if (!tryLockMutex(info, count, currentThread)) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelLockMutex %s, count=%d, timeout_addr=0x%08X, wait=%b, doCallbacks=%b - fast check failed", info.toString(), count, timeout_addr, wait, doCallbacks)); } if (wait && info.threadid != currentThread.uid) { // Failed, but it's ok, just wait a little info.threadWaitingList.addWaitingThread(currentThread); // Wait on a specific mutex currentThread.wait.Mutex_id = uid; currentThread.wait.Mutex_count = count; threadMan.hleKernelThreadEnterWaitState(PSP_WAIT_MUTEX, uid, mutexWaitStateChecker, timeout_addr, doCallbacks); } else { if ((info.attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) != PSP_MUTEX_ATTR_ALLOW_RECURSIVE) { return ERROR_KERNEL_MUTEX_RECURSIVE_NOT_ALLOWED; } return ERROR_KERNEL_MUTEX_LOCKED; } } else { // Success, do not reschedule the current thread. if (log.isDebugEnabled()) { log.debug(String.format("hleKernelLockMutex %s, count=%d, timeout_addr=0x%08X, wait=%b, doCallbacks=%b - fast check succeeded", info.toString(), count, timeout_addr, wait, doCallbacks)); } } return 0; } public int sceKernelLockMutex(int uid, int count, int timeout_addr) { return hleKernelLockMutex(uid, count, timeout_addr, true, false); } public int sceKernelLockMutexCB(int uid, int count, int timeout_addr) { return hleKernelLockMutex(uid, count, timeout_addr, true, true); } public int sceKernelTryLockMutex(int uid, int count) { return hleKernelLockMutex(uid, count, 0, false, false); } public int sceKernelUnlockMutex(int uid, int count) { SceKernelMutexInfo info = mutexMap.get(uid); if (info == null) { log.warn("sceKernelUnlockMutex unknown uid"); return ERROR_KERNEL_MUTEX_NOT_FOUND; } if (info.lockedCount == 0) { // log only as debug to avoid warning spams on some games log.debug("sceKernelUnlockMutex not locked"); return ERROR_KERNEL_MUTEX_UNLOCKED; } if ((info.lockedCount - count) < 0) { log.warn("sceKernelUnlockMutex underflow"); return ERROR_KERNEL_MUTEX_UNLOCK_UNDERFLOW; } info.lockedCount -= count; if (info.lockedCount == 0) { info.threadid = -1; onMutexModified(info); } return 0; } public int sceKernelCancelMutex(int uid, int newcount, TPointer32 numWaitThreadAddr) { SceKernelMutexInfo info = mutexMap.get(uid); if (info == null) { log.warn("sceKernelCancelMutex unknown UID " + Integer.toHexString(uid)); return ERROR_KERNEL_MUTEX_NOT_FOUND; } if (info.lockedCount == 0) { log.warn("sceKernelCancelMutex UID " + Integer.toHexString(uid) + " not locked"); return -1; } if (newcount < 0) { newcount = info.initCount; } if (newcount > 1 && (info.attr & PSP_MUTEX_ATTR_ALLOW_RECURSIVE) == 0) { log.warn(String.format("sceKernelCancelMutex uid=%d, newcount=%d - illegal count", uid, newcount)); return SceKernelErrors.ERROR_KERNEL_ILLEGAL_COUNT; } // Write previous numWaitThreads count. numWaitThreadAddr.setValue(info.getNumWaitingThreads()); info.threadWaitingList.removeAllWaitingThreads(); // Set new count. info.lockedCount = newcount; onMutexCancelled(uid); return 0; } public int sceKernelReferMutexStatus(int uid, TPointer addr) { SceKernelMutexInfo info = mutexMap.get(uid); if (info == null) { log.warn("sceKernelReferMutexStatus unknown UID " + Integer.toHexString(uid)); return ERROR_KERNEL_MUTEX_NOT_FOUND; } info.write(addr); return 0; } private class MutexWaitStateChecker implements IWaitStateChecker { @Override public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) { // Check if the thread has to continue its wait state or if the mutex // has been unlocked during the callback execution. SceKernelMutexInfo info = mutexMap.get(wait.Mutex_id); if (info == null) { thread.cpuContext._v0 = ERROR_KERNEL_MUTEX_NOT_FOUND; return false; } // Check the mutex. if (tryLockMutex(info, wait.Mutex_count, thread)) { info.threadWaitingList.removeWaitingThread(thread); thread.cpuContext._v0 = 0; return false; } return true; } } public static final MutexManager singleton = new MutexManager(); private MutexManager() { } }