/* 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_LWMUTEX_NOT_FOUND; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_LWMUTEX_LOCKED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_LWMUTEX_RECURSIVE_NOT_ALLOWED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_LWMUTEX_UNLOCKED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_LWMUTEX_UNLOCK_UNDERFLOW; 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_LWMUTEX; import java.util.HashMap; import java.util.Iterator; import jpcsp.HLE.Modules; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import jpcsp.HLE.kernel.types.IWaitStateChecker; import jpcsp.HLE.kernel.types.SceKernelLwMutexInfo; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.HLE.kernel.types.ThreadWaitInfo; import jpcsp.HLE.modules.ThreadManForUser; import org.apache.log4j.Logger; public class LwMutexManager { protected static Logger log = Modules.getLogger("ThreadManForUser"); private HashMap<Integer, SceKernelLwMutexInfo> lwMutexMap; private LwMutexWaitStateChecker lwMutexWaitStateChecker; public final static int PSP_LWMUTEX_ATTR_FIFO = 0; public final static int PSP_LWMUTEX_ATTR_PRIORITY = 0x100; private final static int PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE = 0x200; public void reset() { lwMutexMap = new HashMap<Integer, SceKernelLwMutexInfo>(); lwMutexWaitStateChecker = new LwMutexWaitStateChecker(); } private boolean removeWaitingThread(SceKernelThreadInfo thread) { SceKernelLwMutexInfo info = lwMutexMap.get(thread.wait.LwMutex_id); if (info == null) { return false; } info.threadWaitingList.removeWaitingThread(thread); return true; } public void onThreadWaitTimeout(SceKernelThreadInfo thread) { // Untrack if (removeWaitingThread(thread)) { // Return WAIT_TIMEOUT thread.cpuContext._v0 = ERROR_KERNEL_WAIT_TIMEOUT; } else { log.warn("LwMutex 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_LWMUTEX)) { // decrement numWaitThreads removeWaitingThread(thread); } } private void onLwMutexDeleted(int lwmid) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; boolean reschedule = false; for (Iterator<SceKernelThreadInfo> it = threadMan.iterator(); it.hasNext();) { SceKernelThreadInfo thread = it.next(); if (thread.isWaitingFor(PSP_WAIT_LWMUTEX, lwmid)) { thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE; threadMan.hleChangeThreadState(thread, PSP_THREAD_READY); reschedule = true; } } // Reschedule only if threads waked up. if (reschedule) { threadMan.hleRescheduleCurrentThread(); } } private void onLwMutexModified(SceKernelLwMutexInfo info) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; boolean reschedule = false; SceKernelThreadInfo checkedThread = null; while (true) { SceKernelThreadInfo thread = info.threadWaitingList.getNextWaitingThread(checkedThread); if (thread == null) { break; } if (tryLockLwMutex(info, thread.wait.LwMutex_count, thread)) { if (log.isDebugEnabled()) { log.debug(String.format("onLwMutexModified waking thread %s", thread)); } // New thread is taking control of LwMutex. info.threadid = thread.uid; 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 tryLockLwMutex(SceKernelLwMutexInfo info, int count, SceKernelThreadInfo thread) { if (info.lockedCount == 0) { // If the lwmutex 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 lwmutex 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_LWMUTEX_ATTR_ALLOW_RECURSIVE) == PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE)) { info.lockedCount += count; return true; } } return false; } private int hleKernelLockLwMutex(int uid, int count, TPointer32 timeoutAddr, boolean wait, boolean doCallbacks) { SceKernelLwMutexInfo info = lwMutexMap.get(uid); if (info == null) { if (uid == 0) { // Avoid spamming messages for uid==0 if (log.isDebugEnabled()) { log.debug(String.format("hleKernelLockLwMutex uid=%d, count=%d, timeout_addr=%s, wait=%b, doCallbacks=%b - - unknown UID", uid, count, timeoutAddr, wait, doCallbacks)); } } else { log.warn(String.format("hleKernelLockLwMutex uid=%d, count=%d, timeout_addr=%s, wait=%b, doCallbacks=%b - - unknown UID", uid, count, timeoutAddr, wait, doCallbacks)); } return ERROR_KERNEL_LWMUTEX_NOT_FOUND; } ThreadManForUser threadMan = Modules.ThreadManForUserModule; SceKernelThreadInfo currentThread = threadMan.getCurrentThread(); if (!tryLockLwMutex(info, count, currentThread)) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelLockLwMutex %s, count=%d, timeout_addr=%s, wait=%b, doCallbacks=%b - fast check failed", info.toString(), count, timeoutAddr, 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 lwmutex currentThread.wait.LwMutex_id = uid; currentThread.wait.LwMutex_count = count; threadMan.hleKernelThreadEnterWaitState(PSP_WAIT_LWMUTEX, uid, lwMutexWaitStateChecker, timeoutAddr.getAddress(), doCallbacks); } else { if ((info.attr & PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE) != PSP_LWMUTEX_ATTR_ALLOW_RECURSIVE) { return ERROR_KERNEL_LWMUTEX_RECURSIVE_NOT_ALLOWED; } return ERROR_KERNEL_LWMUTEX_LOCKED; } } else { // Success, do not reschedule the current thread. if (log.isDebugEnabled()) { log.debug(String.format("hleKernelLockLwMutex %s, count=%d, timeout_addr=%s, wait=%b, doCallbacks=%b - fast check succeeded", info.toString(), count, timeoutAddr, wait, doCallbacks)); } } return 0; } public int sceKernelCreateLwMutex(TPointer workAreaAddr, String name, int attr, int count, TPointer option) { SceKernelLwMutexInfo info = new SceKernelLwMutexInfo(workAreaAddr, name, count, attr); lwMutexMap.put(info.uid, info); // Return 0 in case of no error, do not return the UID of the created mutex return 0; } public int sceKernelDeleteLwMutex(TPointer workAreaAddr) { int uid = workAreaAddr.getValue32(); SceKernelLwMutexInfo info = lwMutexMap.remove(uid); if (info == null) { log.warn("sceKernelDeleteLwMutex unknown UID " + Integer.toHexString(uid)); return ERROR_KERNEL_LWMUTEX_NOT_FOUND; } workAreaAddr.setValue32(0); // Clear uid. onLwMutexDeleted(uid); return 0; } public int sceKernelLockLwMutex(TPointer workAreaAddr, int count, TPointer32 timeoutAddr) { int uid = workAreaAddr.getValue32(); return hleKernelLockLwMutex(uid, count, timeoutAddr, true, false); } public int sceKernelLockLwMutexCB(TPointer workAreaAddr, int count, TPointer32 timeoutAddr) { int uid = workAreaAddr.getValue32(); return hleKernelLockLwMutex(uid, count, timeoutAddr, true, true); } public int sceKernelTryLockLwMutex(TPointer workAreaAddr, int count) { int uid = workAreaAddr.getValue32(); return hleKernelLockLwMutex(uid, count, TPointer32.NULL, false, false); } public int sceKernelUnlockLwMutex(TPointer workAreaAddr, int count) { int uid = workAreaAddr.getValue32(); SceKernelLwMutexInfo info = lwMutexMap.get(uid); if (info == null) { if (uid == 0) { // Avoid spamming messages for uid==0 if (log.isDebugEnabled()) { log.debug(String.format("sceKernelUnlockLwMutex unknown uid=0x%X", uid)); } } else { log.warn(String.format("sceKernelUnlockLwMutex unknown uid=0x%X", uid)); } return ERROR_KERNEL_LWMUTEX_NOT_FOUND; } if (info.lockedCount == 0) { log.debug("sceKernelUnlockLwMutex not locked"); return ERROR_KERNEL_LWMUTEX_UNLOCKED; } if (info.lockedCount < 0) { log.warn("sceKernelUnlockLwMutex underflow"); return ERROR_KERNEL_LWMUTEX_UNLOCK_UNDERFLOW; } info.lockedCount -= count; if (info.lockedCount == 0) { info.threadid = -1; onLwMutexModified(info); } return 0; } public int sceKernelReferLwMutexStatus(TPointer workAreaAddr, TPointer addr) { int uid = workAreaAddr.getValue32(); SceKernelLwMutexInfo info = lwMutexMap.get(uid); if (info == null) { log.warn("sceKernelReferLwMutexStatus unknown UID " + Integer.toHexString(uid)); return ERROR_KERNEL_LWMUTEX_NOT_FOUND; } info.write(addr); return 0; } public int sceKernelReferLwMutexStatusByID(int uid, TPointer addr) { SceKernelLwMutexInfo info = lwMutexMap.get(uid); if (info == null) { log.warn("sceKernelReferLwMutexStatus unknown UID " + Integer.toHexString(uid)); return ERROR_KERNEL_LWMUTEX_NOT_FOUND; } info.write(addr); return 0; } private class LwMutexWaitStateChecker implements IWaitStateChecker { @Override public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) { // Check if the thread has to continue its wait state or if the lwmutex // has been unlocked during the callback execution. SceKernelLwMutexInfo info = lwMutexMap.get(wait.LwMutex_id); if (info == null) { thread.cpuContext._v0 = ERROR_KERNEL_LWMUTEX_NOT_FOUND; return false; } // Check the lwmutex. if (tryLockLwMutex(info, wait.LwMutex_count, thread)) { info.threadWaitingList.removeWaitingThread(thread); thread.cpuContext._v0 = 0; return false; } return true; } } public static final LwMutexManager singleton = new LwMutexManager(); private LwMutexManager() { } }