/* 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_ILLEGAL_COUNT; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_SEMAPHORE; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_SEMA_OVERFLOW; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_SEMA_ZERO; 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_SEMA; import java.util.HashMap; import java.util.Iterator; import jpcsp.HLE.Modules; import jpcsp.HLE.SceKernelErrorException; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import jpcsp.HLE.kernel.types.IWaitStateChecker; import jpcsp.HLE.kernel.types.SceKernelSemaInfo; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.HLE.kernel.types.ThreadWaitInfo; import jpcsp.HLE.modules.ThreadManForUser; import org.apache.log4j.Logger; public class SemaManager { protected static Logger log = Modules.getLogger("ThreadManForUser"); private HashMap<Integer, SceKernelSemaInfo> semaMap; private SemaWaitStateChecker semaWaitStateChecker; public final static int PSP_SEMA_ATTR_FIFO = 0; // Signal waiting threads with a FIFO iterator. public final static int PSP_SEMA_ATTR_PRIORITY = 0x100; // Signal waiting threads with a priority based iterator. public void reset() { semaMap = new HashMap<Integer, SceKernelSemaInfo>(); semaWaitStateChecker = new SemaWaitStateChecker(); } /** Don't call this unless thread.wait.waitingOnSemaphore == true * @return true if the thread was waiting on a valid sema */ private boolean removeWaitingThread(SceKernelThreadInfo thread) { SceKernelSemaInfo sema = semaMap.get(thread.wait.Semaphore_id); if (sema == null) { return false; } sema.threadWaitingList.removeWaitingThread(thread); return true; } /** Don't call this unless thread.wait.waitingOnSemaphore == true */ public void onThreadWaitTimeout(SceKernelThreadInfo thread) { // Untrack if (removeWaitingThread(thread)) { // Return WAIT_TIMEOUT thread.cpuContext._v0 = ERROR_KERNEL_WAIT_TIMEOUT; } else { log.warn("Sema 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_SEMA)) { // decrement numWaitThreads removeWaitingThread(thread); } } private void onSemaphoreDeletedCancelled(int semaid, 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_SEMA, semaid)) { thread.cpuContext._v0 = result; threadMan.hleChangeThreadState(thread, PSP_THREAD_READY); reschedule = true; } } // Reschedule only if threads waked up. if (reschedule) { threadMan.hleRescheduleCurrentThread(); } } private void onSemaphoreDeleted(int semaid) { onSemaphoreDeletedCancelled(semaid, ERROR_KERNEL_WAIT_DELETE); } private void onSemaphoreCancelled(int semaid) { onSemaphoreDeletedCancelled(semaid, ERROR_KERNEL_WAIT_CANCELLED); } private void onSemaphoreModified(SceKernelSemaInfo sema) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; boolean reschedule = false; SceKernelThreadInfo checkedThread = null; while (sema.currentCount > 0) { SceKernelThreadInfo thread = sema.threadWaitingList.getNextWaitingThread(checkedThread); if (thread == null) { break; } if (tryWaitSemaphore(sema, thread.wait.Semaphore_signal)) { if (log.isDebugEnabled()) { log.debug(String.format("onSemaphoreModified waking thread %s", thread)); } sema.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) { threadMan.hleRescheduleCurrentThread(); } } private boolean tryWaitSemaphore(SceKernelSemaInfo sema, int signal) { boolean success = false; if (sema.currentCount >= signal) { sema.currentCount -= signal; success = true; } return success; } public int checkSemaID(int semaid) { SceUidManager.checkUidPurpose(semaid, "ThreadMan-sema", true); if (!semaMap.containsKey(semaid)) { if (semaid == 0) { // Some applications systematically try to signal a semaid=0. // Do not spam WARNings for this case. log.debug(String.format("checkSemaID - unknown uid 0x%X", semaid)); } else { log.warn(String.format("checkSemaID - unknown uid 0x%X", semaid)); } throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_SEMAPHORE); } return semaid; } public SceKernelSemaInfo hleKernelCreateSema(String name, int attr, int initVal, int maxVal, TPointer option) { if (option.isNotNull()) { // The first int does not seem to be the size of the struct, found values: // SSX On Tour: 0, 0x08B0F9E4, 0x0892E664, 0x08AF7257 (some values are used in more than one semaphore) int optionSize = option.getValue32(); log.warn(String.format("sceKernelCreateSema option at %s, size=%d", option, optionSize)); } SceKernelSemaInfo sema = new SceKernelSemaInfo(name, attr, initVal, maxVal); semaMap.put(sema.uid, sema); return sema; } public int hleKernelWaitSema(SceKernelSemaInfo sema, int signal, TPointer32 timeoutAddr, boolean doCallbacks) { if (!tryWaitSemaphore(sema, signal)) { // Failed, but it's ok, just wait a little if (log.isDebugEnabled()) { log.debug(String.format("hleKernelWaitSema %s fast check failed", sema)); } ThreadManForUser threadMan = Modules.ThreadManForUserModule; SceKernelThreadInfo currentThread = threadMan.getCurrentThread(); sema.threadWaitingList.addWaitingThread(currentThread); // Wait on a specific semaphore currentThread.wait.Semaphore_id = sema.uid; currentThread.wait.Semaphore_signal = signal; threadMan.hleKernelThreadEnterWaitState(PSP_WAIT_SEMA, sema.uid, semaWaitStateChecker, timeoutAddr.getAddress(), doCallbacks); } else { // Success, do not reschedule the current thread. if (log.isDebugEnabled()) { log.debug(String.format("hleKernelWaitSema %s fast check succeeded", sema)); } } return 0; } private int hleKernelWaitSema(int semaid, int signal, TPointer32 timeoutAddr, boolean doCallbacks) { if (signal <= 0) { log.warn(String.format("hleKernelWaitSema - bad signal %d", signal)); return ERROR_KERNEL_ILLEGAL_COUNT; } SceKernelSemaInfo sema = semaMap.get(semaid); if (signal > sema.maxCount) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelWaitSema returning 0x%08X(ERROR_KERNEL_ILLEGAL_COUNT)", ERROR_KERNEL_ILLEGAL_COUNT)); } return ERROR_KERNEL_ILLEGAL_COUNT; } return hleKernelWaitSema(sema, signal, timeoutAddr, doCallbacks); } public int hleKernelPollSema(SceKernelSemaInfo sema, int signal) { if (signal > sema.currentCount) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelPollSema returning 0x%08X(ERROR_KERNEL_SEMA_ZERO)", ERROR_KERNEL_SEMA_ZERO)); } return ERROR_KERNEL_SEMA_ZERO; } sema.currentCount -= signal; if (log.isDebugEnabled()) { log.debug(String.format("hleKernelPollSema returning 0, %s", sema)); } return 0; } public int hleKernelSignalSema(SceKernelSemaInfo sema, int signal) { // Check that currentCount will not exceed the maxCount // after releasing all the threads waiting on this sema. int newCount = sema.currentCount + signal; if (newCount > sema.maxCount) { for (Iterator<SceKernelThreadInfo> it = Modules.ThreadManForUserModule.iterator(); it.hasNext();) { SceKernelThreadInfo thread = it.next(); if (thread.isWaitingForType(PSP_WAIT_SEMA) && thread.wait.Semaphore_id == sema.uid) { newCount -= thread.wait.Semaphore_signal; } } if (newCount > sema.maxCount) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelSignalSema returning 0x%08X(ERROR_KERNEL_SEMA_OVERFLOW)", ERROR_KERNEL_SEMA_OVERFLOW)); } return ERROR_KERNEL_SEMA_OVERFLOW; } } sema.currentCount += signal; onSemaphoreModified(sema); // Sanity check... if (sema.currentCount > sema.maxCount) { // This situation should never happen, otherwise something went wrong // in the overflow check above. log.error(String.format("hleKernelSignalSema currentCount %d exceeding maxCount %d", sema.currentCount, sema.maxCount)); } if (log.isDebugEnabled()) { log.debug(String.format("hleKernelSignalSema returning 0, %s", sema)); } return 0; } public int sceKernelCreateSema(String name, int attr, int initVal, int maxVal, TPointer option) { SceKernelSemaInfo sema = hleKernelCreateSema(name, attr, initVal, maxVal, option); if (log.isDebugEnabled()) { log.debug(String.format("sceKernelCreateSema %s", sema)); } return sema.uid; } public int sceKernelDeleteSema(int semaid) { semaMap.remove(semaid); onSemaphoreDeleted(semaid); return 0; } public int sceKernelWaitSema(int semaid, int signal, TPointer32 timeoutAddr) { return hleKernelWaitSema(semaid, signal, timeoutAddr, false); } public int sceKernelWaitSemaCB(int semaid, int signal, TPointer32 timeoutAddr) { return hleKernelWaitSema(semaid, signal, timeoutAddr, true); } public int sceKernelSignalSema(int semaid, int signal) { SceKernelSemaInfo sema = semaMap.get(semaid); return hleKernelSignalSema(sema, signal); } /** This is attempt to signal the sema and always return immediately */ public int sceKernelPollSema(int semaid, int signal) { if (signal <= 0) { log.warn(String.format("sceKernelPollSema id=0x%X, signal=%d: bad signal", semaid, signal)); return ERROR_KERNEL_ILLEGAL_COUNT; } SceKernelSemaInfo sema = semaMap.get(semaid); return hleKernelPollSema(sema, signal); } public int sceKernelCancelSema(int semaid, int newcount, TPointer32 numWaitThreadAddr) { SceKernelSemaInfo sema = semaMap.get(semaid); if (newcount > sema.maxCount) { return ERROR_KERNEL_ILLEGAL_COUNT; } // Write previous numWaitThreads count. numWaitThreadAddr.setValue(sema.getNumWaitThreads()); sema.threadWaitingList.removeAllWaitingThreads(); // Reset this semaphore's count based on newcount. // Note: If newcount is negative, the count becomes this semaphore's initCount. if (newcount < 0) { sema.currentCount = sema.initCount; } else { sema.currentCount = newcount; } onSemaphoreCancelled(semaid); return 0; } public int sceKernelReferSemaStatus(int semaid, TPointer addr) { SceKernelSemaInfo sema = semaMap.get(semaid); sema.write(addr); if (log.isDebugEnabled()) { log.debug(String.format("sceKernelReferSemaStatus returning %s", sema)); } return 0; } private class SemaWaitStateChecker implements IWaitStateChecker { @Override public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) { // Check if the thread has to continue its wait state or if the sema // has been signaled during the callback execution. SceKernelSemaInfo sema = semaMap.get(wait.Semaphore_id); if (sema == null) { thread.cpuContext._v0 = ERROR_KERNEL_NOT_FOUND_SEMAPHORE; return false; } // Check the sema. if (tryWaitSemaphore(sema, wait.Semaphore_signal)) { sema.threadWaitingList.removeWaitingThread(thread); thread.cpuContext._v0 = 0; return false; } return true; } } public static final SemaManager singleton = new SemaManager(); private SemaManager() { } }