/* 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_ATTR; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ILLEGAL_MEMSIZE; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ILLEGAL_MEMBLOCK; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_FPOOL; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NO_MEMORY; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_TIMEOUT; 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_CANCELLED; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_CAN_NOT_WAIT; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_READY; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_FPL; import static jpcsp.HLE.modules.SysMemUserForUser.PSP_SMEM_High; import static jpcsp.HLE.modules.SysMemUserForUser.PSP_SMEM_Low; import java.util.HashMap; import java.util.Iterator; import jpcsp.HLE.Modules; import jpcsp.HLE.PspString; import jpcsp.HLE.SceKernelErrorException; 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.SceKernelFplInfo; import jpcsp.HLE.kernel.types.SceKernelFplOptParam; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.HLE.kernel.types.ThreadWaitInfo; import jpcsp.HLE.modules.ThreadManForUser; import jpcsp.util.Utilities; import org.apache.log4j.Logger; public class FplManager { protected static Logger log = Modules.getLogger("ThreadManForUser"); private HashMap<Integer, SceKernelFplInfo> fplMap; private FplWaitStateChecker fplWaitStateChecker; public final static int PSP_FPL_ATTR_FIFO = 0; public final static int PSP_FPL_ATTR_PRIORITY = 0x100; private final static int PSP_FPL_ATTR_MASK = 0x41FF; // Anything outside this mask is an illegal attr. private final static int PSP_FPL_ATTR_ADDR_HIGH = 0x4000; // Create the fpl in high memory. public void reset() { fplMap = new HashMap<Integer, SceKernelFplInfo>(); fplWaitStateChecker = new FplWaitStateChecker(); } private boolean removeWaitingThread(SceKernelThreadInfo thread) { SceKernelFplInfo fpl = fplMap.get(thread.wait.Fpl_id); if (fpl == null) { return false; } fpl.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("FPL 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_FPL)) { removeWaitingThread(thread); } } private void onFplDeletedCancelled(int fid, 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_FPL, fid)) { thread.cpuContext._v0 = result; threadMan.hleChangeThreadState(thread, PSP_THREAD_READY); reschedule = true; } } // Reschedule only if threads waked up. if (reschedule) { threadMan.hleRescheduleCurrentThread(); } } private void onFplDeleted(int fid) { onFplDeletedCancelled(fid, ERROR_KERNEL_WAIT_DELETE); } private void onFplCancelled(int fid) { onFplDeletedCancelled(fid, ERROR_KERNEL_WAIT_CANCELLED); } private void onFplFree(SceKernelFplInfo info) { ThreadManForUser threadMan = Modules.ThreadManForUserModule; boolean reschedule = false; SceKernelThreadInfo checkedThread = null; while (info.freeBlocks > 0) { SceKernelThreadInfo thread = info.threadWaitingList.getNextWaitingThread(checkedThread); if (thread == null) { break; } int addr = tryAllocateFpl(info); if (addr != 0) { if (log.isDebugEnabled()) { log.debug(String.format("onFplFree waking thread %s", thread)); } // Return the allocated address thread.wait.Fpl_dataAddr.setValue(addr); 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) { threadMan.hleRescheduleCurrentThread(); } } /** @return the address of the allocated block or 0 if failed. */ private int tryAllocateFpl(SceKernelFplInfo info) { int block; int addr = 0; if (info.freeBlocks == 0 || (block = info.findFreeBlock()) == -1) { log.warn("tryAllocateFpl no free blocks (numBlocks=" + info.numBlocks + ")"); return 0; } addr = info.allocateBlock(block); return addr; } public int checkFplID(int uid) { SceUidManager.checkUidPurpose(uid, "ThreadMan-Fpl", true); if (!fplMap.containsKey(uid)) { log.warn(String.format("checkFplID unknown uid=0x%X", uid)); throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_FPOOL); } return uid; } public int sceKernelCreateFpl(PspString name, int partitionid, int attr, int blocksize, int blocks, TPointer option) { if (name.isNull()) { // PSP is returning this error in case of NULL name return SceKernelErrors.ERROR_KERNEL_NO_MEMORY; } int memType = PSP_SMEM_Low; if ((attr & PSP_FPL_ATTR_ADDR_HIGH) == PSP_FPL_ATTR_ADDR_HIGH) { memType = PSP_SMEM_High; } int memAlign = 4; // 4-bytes is default. if (option.isNotNull()) { int optionSize = option.getValue32(); // Up to firmware 6.20 only two FplOptParam fields exist, being the // first one the struct size, the second is the memory alignment (0 is default, // which is 4-byte/32-bit). if ((optionSize >= 4) && (optionSize <= 8)) { SceKernelFplOptParam optParams = new SceKernelFplOptParam(); optParams.read(option); memAlign = optParams.align; if (!Utilities.isPower2(memAlign)) { // The alignment has to be a power of 2. return SceKernelErrors.ERROR_KERNEL_ILLEGAL_ARGUMENT; } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelCreateFpl options: struct size=%d, alignment=0x%X", optParams.sizeof(), optParams.align)); } } else { log.warn(String.format("sceKernelCreateFpl option at %s, size=%d", option, optionSize)); } } if ((attr & ~PSP_FPL_ATTR_MASK) != 0) { log.warn(String.format("sceKernelCreateFpl bad attr value 0x%X", attr)); return ERROR_KERNEL_ILLEGAL_ATTR; } if (blocksize <= 0) { log.warn(String.format("sceKernelCreateFpl bad blocksize %d", blocksize)); return ERROR_KERNEL_ILLEGAL_MEMSIZE; } if (blocks <= 0) { log.warn(String.format("sceKernelCreateFpl bad number of blocks %d", blocks)); return ERROR_KERNEL_ILLEGAL_MEMSIZE; } if (blocks * blocksize < 0) { return ERROR_KERNEL_NO_MEMORY; } if (blocks * blocksize != blocks * (long) blocksize) { return ERROR_KERNEL_ILLEGAL_MEMSIZE; } SceKernelFplInfo info = SceKernelFplInfo.tryCreateFpl(name.getString(), partitionid, attr, blocksize, blocks, memType, memAlign); if (info == null) { return ERROR_KERNEL_NO_MEMORY; } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelCreateFpl returning %s", info)); } fplMap.put(info.uid, info); return info.uid; } public int sceKernelDeleteFpl(int uid) { SceKernelFplInfo info = fplMap.remove(uid); if (info.freeBlocks < info.numBlocks) { log.warn(String.format("sceKernelDeleteFpl %s unfreed blocks, deleting", info.numBlocks - info.freeBlocks)); } info.deleteSysMemInfo(); onFplDeleted(uid); return 0; } private int hleKernelAllocateFpl(int uid, TPointer32 dataAddr, TPointer32 timeoutAddr, boolean wait, boolean doCallbacks) { SceKernelFplInfo fpl = fplMap.get(uid); int addr = tryAllocateFpl(fpl); ThreadManForUser threadMan = Modules.ThreadManForUserModule; if (addr == 0) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelAllocateFpl %s fast check failed", fpl)); } if (!wait) { return ERROR_KERNEL_WAIT_CAN_NOT_WAIT; } // Go to wait state SceKernelThreadInfo currentThread = threadMan.getCurrentThread(); fpl.threadWaitingList.addWaitingThread(currentThread); currentThread.wait.Fpl_id = uid; currentThread.wait.Fpl_dataAddr = dataAddr; threadMan.hleKernelThreadEnterWaitState(PSP_WAIT_FPL, uid, fplWaitStateChecker, timeoutAddr.getAddress(), doCallbacks); } else { // Success, do not reschedule the current thread. if (log.isDebugEnabled()) { log.debug(String.format("hleKernelAllocateFpl %s fast check succeeded", fpl)); } dataAddr.setValue(addr); } return 0; } public int sceKernelAllocateFpl(int uid, TPointer32 dataAddr, TPointer32 timeoutAddr) { return hleKernelAllocateFpl(uid, dataAddr, timeoutAddr, true, false); } public int sceKernelAllocateFplCB(int uid, TPointer32 dataAddr, TPointer32 timeoutAddr) { return hleKernelAllocateFpl(uid, dataAddr, timeoutAddr, true, true); } public int sceKernelTryAllocateFpl(int uid, TPointer32 dataAddr) { return hleKernelAllocateFpl(uid, dataAddr, TPointer32.NULL, false, false); } public int sceKernelFreeFpl(int uid, TPointer dataAddr) { SceKernelFplInfo info = fplMap.get(uid); int block = info.findBlockByAddress(dataAddr.getAddress()); if (block < 0) { log.warn(String.format("sceKernelFreeFpl unknown block address=%s", dataAddr)); return ERROR_KERNEL_ILLEGAL_MEMBLOCK; } info.freeBlock(block); onFplFree(info); return 0; } public int sceKernelCancelFpl(int uid, TPointer32 numWaitThreadAddr) { SceKernelFplInfo info = fplMap.get(uid); numWaitThreadAddr.setValue(info.getNumWaitThreads()); info.threadWaitingList.removeAllWaitingThreads(); onFplCancelled(uid); return 0; } public int sceKernelReferFplStatus(int uid, TPointer infoAddr) { SceKernelFplInfo info = fplMap.get(uid); info.write(infoAddr); return 0; } private class FplWaitStateChecker implements IWaitStateChecker { @Override public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) { // Check if the thread has to continue its wait state or if the fpl // has been allocated during the callback execution. SceKernelFplInfo fpl = fplMap.get(wait.Fpl_id); if (fpl == null) { thread.cpuContext._v0 = ERROR_KERNEL_NOT_FOUND_FPOOL; return false; } // Check fpl. int addr = tryAllocateFpl(fpl); if (addr != 0) { fpl.threadWaitingList.removeWaitingThread(thread); thread.wait.Fpl_dataAddr.setValue(addr); thread.cpuContext._v0 = 0; return false; } return true; } } public static final FplManager singleton = new FplManager(); private FplManager() { } }