/* 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.types; import static jpcsp.Allegrex.Common._ra; import static jpcsp.Allegrex.Common._zr; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_THREAD_ALREADY_DORMANT; import java.util.Comparator; import java.util.LinkedList; import java.util.List; import java.util.Queue; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.Allegrex.Common; import jpcsp.Allegrex.CpuState; import jpcsp.Allegrex.compiler.RuntimeContext; import jpcsp.HLE.Modules; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import jpcsp.HLE.kernel.managers.SceUidManager; import jpcsp.HLE.modules.SysMemUserForUser; import jpcsp.HLE.modules.SysMemUserForUser.SysMemInfo; import jpcsp.HLE.modules.ThreadManForUser.Callback; public class SceKernelThreadInfo extends pspAbstractMemoryMappedStructureVariableLength implements Comparator<SceKernelThreadInfo> { public static final int PSP_MODULE_USER = 0; public static final int PSP_MODULE_NO_STOP = 0x00000001; public static final int PSP_MODULE_SINGLE_LOAD = 0x00000002; public static final int PSP_MODULE_SINGLE_START = 0x00000004; public static final int PSP_MODULE_POPS = 0x00000200; public static final int PSP_MODULE_DEMO = 0x00000200; // same as PSP_MODULE_POPS public static final int PSP_MODULE_GAMESHARING = 0x00000400; public static final int PSP_MODULE_VSH = 0x00000800; // can only be loaded from kernel mode? public static final int PSP_MODULE_KERNEL = 0x00001000; public static final int PSP_MODULE_USE_MEMLMD_LIB = 0x00002000; public static final int PSP_MODULE_USE_SEMAPHORE_LIB = 0x00004000; // not kernel semaphores, but a fake name (actually security stuff) public static final int PSP_THREAD_ATTR_USER = 0x80000000; // module attr 0, thread attr: 0x800000FF? public static final int PSP_THREAD_ATTR_USBWLAN = 0xa0000000; public static final int PSP_THREAD_ATTR_VSH = 0xc0000000; public static final int PSP_THREAD_ATTR_KERNEL = 0x00001000; // module attr 0x1000, thread attr: 0? public static final int PSP_THREAD_ATTR_VFPU = 0x00004000; public static final int PSP_THREAD_ATTR_SCRATCH_SRAM = 0x00008000; public static final int PSP_THREAD_ATTR_NO_FILLSTACK = 0x00100000; // Disables filling the stack with 0xFF on creation. public static final int PSP_THREAD_ATTR_CLEAR_STACK = 0x00200000; // Clear the stack when the thread is deleted. public static final int PSP_THREAD_ATTR_LOW_MEM_STACK = 0x00400000; // Allocate the stack in low memory instead of high memory // PspThreadStatus public static final int PSP_THREAD_RUNNING = 0x00000001; public static final int PSP_THREAD_READY = 0x00000002; public static final int PSP_THREAD_WAITING = 0x00000004; public static final int PSP_THREAD_SUSPEND = 0x00000008; public static final int PSP_THREAD_WAITING_SUSPEND = PSP_THREAD_WAITING | PSP_THREAD_SUSPEND; public static final int PSP_THREAD_STOPPED = 0x00000010; public static final int PSP_THREAD_KILLED = 0x00000020; // Wait types public static final int PSP_WAIT_NONE = 0x00; public static final int PSP_WAIT_SLEEP = 0x01; // Wait on sleep thread. public static final int PSP_WAIT_DELAY = 0x02; // Wait on delay thread. public static final int PSP_WAIT_SEMA = 0x03; // Wait on sema. public static final int PSP_WAIT_EVENTFLAG = 0x04; // Wait on event flag. public static final int PSP_WAIT_MBX = 0x05; // Wait on mbx. public static final int PSP_WAIT_VPL = 0x06; // Wait on vpl. public static final int PSP_WAIT_FPL = 0x07; // Wait on fpl. public static final int PSP_WAIT_MSGPIPE = 0x08; // Wait on msg pipe (send and receive). public static final int PSP_WAIT_THREAD_END = 0x09; // Wait on thread end. public static final int PSP_WAIT_EVENTHANDLER = 0x0a; // Wait on event handler release. public static final int PSP_WAIT_CALLBACK_DELETE = 0x0b; // Wait on callback delete. public static final int PSP_WAIT_MUTEX = 0x0c; // Wait on mutex. public static final int PSP_WAIT_LWMUTEX = 0x0d; // Wait on lwmutex. // These wait types are only used internally in Jpcsp and are not real PSP wait types. public static final int JPCSP_FIRST_INTERNAL_WAIT_TYPE = 0x100; public static final int JPCSP_WAIT_IO = JPCSP_FIRST_INTERNAL_WAIT_TYPE; // Wait on IO. public static final int JPCSP_WAIT_UMD = JPCSP_WAIT_IO + 1; // Wait on UMD. public static final int JPCSP_WAIT_GE_LIST = JPCSP_WAIT_UMD + 1; // Wait on GE list. public static final int JPCSP_WAIT_NET = JPCSP_WAIT_GE_LIST + 1; // Wait on Network. public static final int JPCSP_WAIT_AUDIO = JPCSP_WAIT_NET + 1; // Wait on Audio. public static final int JPCSP_WAIT_DISPLAY_VBLANK = JPCSP_WAIT_AUDIO + 1; // Wait on Display Vblank. public static final int JPCSP_WAIT_CTRL = JPCSP_WAIT_DISPLAY_VBLANK + 1; // Wait on Control public static final int JPCSP_WAIT_USB = JPCSP_WAIT_CTRL + 1; // Wait on USB public static final int JPCSP_WAIT_VIDEO_DECODER = JPCSP_WAIT_USB + 1; // Wait for sceMpeg video decoder // SceKernelThreadInfo. public final String name; public int attr; public int status; // it's a bitfield but I don't think we ever use more than 1 bit at once public int entry_addr; private int stackAddr; // using low address, no need to add stackSize to the pointer returned by malloc public int stackSize; public int gpReg_addr; public final int initPriority; // lower numbers mean higher priority public int currentPriority; public int waitType; public int waitId; // the uid of the wait object public int wakeupCount; // number of sceKernelWakeupThread() calls pending public int exitStatus; public TPointer32 exitStatusAddr; // Store the exitStatus at this address if specified public long runClocks; public int intrPreemptCount; public int threadPreemptCount; public int releaseCount; public int notifyCallback; // Used by sceKernelNotifyCallback to check if a callback has been called or not. public int errno; // used by sceNetInet private SysMemInfo stackSysMemInfo; // internal variables public final int uid; public int moduleid; public CpuState cpuContext; public boolean doDelete; public IAction doDeleteAction; public boolean unloadModuleAtDeletion; public boolean doCallbacks; public final ThreadWaitInfo wait; public int displayLastWaitVcount; public long javaThreadId = -1; public long javaThreadCpuTimeNanos = -1; // Callbacks, only 1 of each type can be registered per thread. public final static int THREAD_CALLBACK_UMD = 0; public final static int THREAD_CALLBACK_IO = 1; public final static int THREAD_CALLBACK_MEMORYSTICK = 2; public final static int THREAD_CALLBACK_MEMORYSTICK_FAT = 3; public final static int THREAD_CALLBACK_POWER = 4; public final static int THREAD_CALLBACK_EXIT = 5; public final static int THREAD_CALLBACK_USB = 6; public final static int THREAD_CALLBACK_USER_DEFINED = 7; public final static int THREAD_CALLBACK_SIZE = 8; private RegisteredCallbacks[] registeredCallbacks; public Queue<Callback> pendingCallbacks = new LinkedList<Callback>(); public Queue<IAction> pendingActions = new LinkedList<IAction>(); // Used by sceKernelExtendThreadStack private SysMemInfo extendedStackSysMemInfo; public boolean preserveStack; public static class RegisteredCallbacks { private int type; private List<pspBaseCallback> callbacks; private List<pspBaseCallback> readyCallbacks; // THREAD_CALLBACK_MEMORYSTICK and THREAD_CALLBACK_MEMORYSTICK_FAT have // a maximum of 32 registered callbacks each. // Don't know yet for the other types, assuming also 32. private int maxNumberOfCallbacks = 32; // Registering a new callback overwrites the previous one. private boolean registerOnlyLastCallback = false; public RegisteredCallbacks(int type) { this.type = type; callbacks = new LinkedList<pspBaseCallback>(); readyCallbacks = new LinkedList<pspBaseCallback>(); } public boolean hasCallbacks() { return !callbacks.isEmpty(); } public pspBaseCallback getCallbackInfoByUid(int cbid) { for (pspBaseCallback callback : callbacks) { if (callback.getUid() == cbid) { return callback; } } return null; } public boolean hasCallback(int cbid) { return getCallbackInfoByUid(cbid) != null; } public boolean hasCallback(pspBaseCallback callback) { return callbacks.contains(callback); } public boolean addCallback(pspBaseCallback callback) { if (hasCallback(callback)) { return true; } if (getNumberOfCallbacks() >= maxNumberOfCallbacks) { return false; } if (registerOnlyLastCallback) { callbacks.clear(); } callbacks.add(callback); return true; } public void setCallbackReady(pspBaseCallback callback) { if (hasCallback(callback) && !isCallbackReady(callback)) { readyCallbacks.add(callback); } } public boolean isCallbackReady(pspBaseCallback callback) { return readyCallbacks.contains(callback); } public pspBaseCallback removeCallback(pspBaseCallback callback) { if (!callbacks.remove(callback)) { return null; } readyCallbacks.remove(callback); return callback; } public pspBaseCallback getNextReadyCallback() { if (readyCallbacks.isEmpty()) { return null; } return readyCallbacks.remove(0); } public int getNumberOfCallbacks() { return callbacks.size(); } public pspBaseCallback getCallbackByIndex(int index) { return callbacks.get(index); } public void setRegisterOnlyLastCallback() { registerOnlyLastCallback = true; } @Override public String toString() { return String.format("RegisteredCallbacks[type %d, count %d, ready %d]", type, callbacks.size(), readyCallbacks.size()); } } public SceKernelThreadInfo(String name, int entry_addr, int initPriority, int stackSize, int attr, int mpidStack) { if (stackSize < 512) { // 512 byte min. (required for interrupts) stackSize = 512; } else { // 256 byte size alignment. stackSize = (stackSize + 0xFF) & ~0xFF; } if (mpidStack == 0) { mpidStack = SysMemUserForUser.USER_PARTITION_ID; } this.name = name; this.entry_addr = entry_addr; this.initPriority = initPriority; this.stackSize = stackSize; this.attr = attr; uid = SceUidManager.getNewUid("ThreadMan-thread"); // Setup the stack. int stackMemoryType = (attr & PSP_THREAD_ATTR_LOW_MEM_STACK) != 0 ? SysMemUserForUser.PSP_SMEM_Low : SysMemUserForUser.PSP_SMEM_High; stackSysMemInfo = Modules.SysMemUserForUserModule.malloc(mpidStack, String.format("ThreadMan-Stack-0x%x-%s", uid, name), stackMemoryType, stackSize, 0); if (stackSysMemInfo == null) { stackAddr = 0; } else { stackAddr = stackSysMemInfo.addr; } // Inherit gpReg. gpReg_addr = Emulator.getProcessor().cpu._gp; // Inherit context. cpuContext = new CpuState(Emulator.getProcessor().cpu); wait = new ThreadWaitInfo(); reset(); } public void reset() { status = PSP_THREAD_STOPPED; int k0 = stackAddr + stackSize - 0x100; // setup k0 Memory mem = Memory.getInstance(); if (stackAddr != 0 && stackSize > 0 && !preserveStack) { // set stack to 0xFF if ((attr & PSP_THREAD_ATTR_NO_FILLSTACK) != PSP_THREAD_ATTR_NO_FILLSTACK) { mem.memset(stackAddr, (byte) 0xFF, stackSize); } // setup k0 mem.memset(k0, (byte) 0x0, 0x100); mem.write32(k0 + 0xc0, stackAddr); mem.write32(k0 + 0xca, uid); mem.write32(k0 + 0xf8, 0xffffffff); mem.write32(k0 + 0xfc, 0xffffffff); mem.write32(stackAddr, uid); } currentPriority = initPriority; waitType = PSP_WAIT_NONE; waitId = 0; wakeupCount = 0; exitStatus = ERROR_KERNEL_THREAD_ALREADY_DORMANT; // Threads start with DORMANT and not NOT_DORMANT (tested and checked). exitStatusAddr = null; runClocks = 0; intrPreemptCount = 0; threadPreemptCount = 0; releaseCount = 0; notifyCallback = 0; // Thread specific registers cpuContext.pc = entry_addr; cpuContext.npc = entry_addr; // + 4; // Reset all the registers to DEADBEEF value for (int i = _ra; i > _zr; i--) { cpuContext.setRegister(i, 0xDEADBEEF); } cpuContext._k0 = 0; cpuContext._k1 = 0; int intNanValue = 0x7F800001; float nanValue = Float.intBitsToFloat(intNanValue); for (int i = Common._f31; i >= Common._f0; i--) { cpuContext.fpr[i] = nanValue; } cpuContext.hilo = 0xDEADBEEFDEADBEEFL; if ((attr & PSP_THREAD_ATTR_VFPU) != 0) { // Reset the VFPU context for (int m = 0; m < 8; m++) { for (int c = 0; c < 4; c++) { for (int r = 0; r < 4; r++) { cpuContext.setVprInt(m, c, r, intNanValue); } } } for (int i = 0; i < cpuContext.vcr.cc.length; i++) { cpuContext.vcr.cc[i] = true; } cpuContext.vcr.pfxs.reset(); cpuContext.vcr.pfxt.reset(); cpuContext.vcr.pfxd.reset(); } // sp, 512 byte padding at the top for user data, this will get re-jigged when we call start thread cpuContext._sp = stackAddr + stackSize - 512; cpuContext._k0 = k0; // We'll hook "jr $ra" where $ra == address of HLE syscall hleKernelExitThread // when the thread is exiting cpuContext._ra = jpcsp.HLE.modules.ThreadManForUser.THREAD_EXIT_HANDLER_ADDRESS; doDelete = false; doCallbacks = false; registeredCallbacks = new RegisteredCallbacks[THREAD_CALLBACK_SIZE]; for (int i = 0; i < registeredCallbacks.length; i++) { registeredCallbacks[i] = new RegisteredCallbacks(i); } // The UMD callback registers only the last callback. registeredCallbacks[THREAD_CALLBACK_UMD].setRegisterOnlyLastCallback(); } public void saveContext() { cpuContext = Emulator.getProcessor().cpu; } public void restoreContext() { // Assuming context switching only happens on syscall, // we always execute npc after a syscall, // so we can set pc = npc regardless of cop0.status.bd. cpuContext.pc = cpuContext.npc; Emulator.getProcessor().setCpu(cpuContext); RuntimeContext.update(); } /** For use in the scheduler */ @Override public int compare(SceKernelThreadInfo o1, SceKernelThreadInfo o2) { return o1.currentPriority - o2.currentPriority; } private int getPSPWaitType() { if (waitType >= 0x100) { // A blocked thread (e.g. a thread blocked due to audio output or // wait for vblank or sceCtrl sample reading) is implemented like // a "wait for Event Flag". This is the closest implementation to a real PSP, // as event flags are usually used by a PSP to implement these wait // functions. // Jpcsp internal wait types are best matched to PSP_WAIT_EVENTFLAG. return PSP_WAIT_EVENTFLAG; } return waitType; } @Override protected void write() { super.write(); writeStringNZ(32, name); write32(attr); write32(status); write32(entry_addr); write32(stackAddr); write32(stackSize); write32(gpReg_addr); write32(initPriority); write32(currentPriority); write32(getPSPWaitType()); write32(waitId); write32(wakeupCount); write32(exitStatus); write64(runClocks); write32(intrPreemptCount); write32(threadPreemptCount); write32(releaseCount); } // SceKernelThreadRunStatus. // Represents a smaller subset of SceKernelThreadInfo containing only the most volatile parts // of the thread (mostly used for debugging). public void writeRunStatus(TPointer pointer) { start(pointer.getMemory(), pointer.getAddress()); super.write(); write32(status); write32(currentPriority); write32(waitType); write32(waitId); write32(wakeupCount); write64(runClocks); write32(intrPreemptCount); write32(threadPreemptCount); write32(releaseCount); } public void setSystemStack(int stackAddr, int stackSize) { freeStack(); this.stackAddr = stackAddr; this.stackSize = stackSize; } public void freeStack() { if (stackSysMemInfo != null) { Modules.SysMemUserForUserModule.free(stackSysMemInfo); stackSysMemInfo = null; stackAddr = 0; } freeExtendedStack(); } public void freeExtendedStack() { if (extendedStackSysMemInfo != null) { Modules.SysMemUserForUserModule.free(extendedStackSysMemInfo); extendedStackSysMemInfo = null; } } public int extendStack(int size) { extendedStackSysMemInfo = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.USER_PARTITION_ID, String.format("ThreadMan-ExtendedStack-0x%x-%s", uid, name), SysMemUserForUser.PSP_SMEM_High, size, 0); return extendedStackSysMemInfo.addr; } public int getStackAddr() { if (extendedStackSysMemInfo != null) { return extendedStackSysMemInfo.addr; } return stackAddr; } public static String getStatusName(int status) { StringBuilder s = new StringBuilder(); // A thread status is a bitfield so it could be in multiple states if ((status & PSP_THREAD_RUNNING) == PSP_THREAD_RUNNING) { s.append(" | PSP_THREAD_RUNNING"); } if ((status & PSP_THREAD_READY) == PSP_THREAD_READY) { s.append(" | PSP_THREAD_READY"); } if ((status & PSP_THREAD_WAITING) == PSP_THREAD_WAITING) { s.append(" | PSP_THREAD_WAITING"); } if ((status & PSP_THREAD_SUSPEND) == PSP_THREAD_SUSPEND) { s.append(" | PSP_THREAD_SUSPEND"); } if ((status & PSP_THREAD_STOPPED) == PSP_THREAD_STOPPED) { s.append(" | PSP_THREAD_STOPPED"); } if ((status & PSP_THREAD_KILLED) == PSP_THREAD_KILLED) { s.append(" | PSP_THREAD_KILLED"); } // Strip off leading " | " if (s.length() > 0) { s.delete(0, 3); } else { s.append("UNKNOWN"); } return s.toString(); } public String getStatusName() { return getStatusName(status); } public static String getWaitName(int waitType, int waitId, ThreadWaitInfo wait, int status) { StringBuilder s = new StringBuilder(); switch (waitType) { case PSP_WAIT_NONE: s.append(String.format("None")); break; case PSP_WAIT_SLEEP: s.append(String.format("Sleep")); break; case PSP_WAIT_DELAY: s.append(String.format("Delay")); break; case PSP_WAIT_THREAD_END: s.append(String.format("ThreadEnd (0x%04X)", wait.ThreadEnd_id)); break; case PSP_WAIT_EVENTFLAG: s.append(String.format("EventFlag (0x%04X)", wait.EventFlag_id)); break; case PSP_WAIT_SEMA: s.append(String.format("Semaphore (0x%04X)", wait.Semaphore_id)); break; case PSP_WAIT_MUTEX: s.append(String.format("Mutex (0x%04X)", wait.Mutex_id)); break; case PSP_WAIT_LWMUTEX: s.append(String.format("LwMutex (0x%04X)", wait.LwMutex_id)); break; case PSP_WAIT_MBX: s.append(String.format("Mbx (0x%04X)", wait.Mbx_id)); break; case PSP_WAIT_VPL: s.append(String.format("Vpl (0x%04X)", wait.Vpl_id)); break; case PSP_WAIT_FPL: s.append(String.format("Fpl (0x%04X)", wait.Fpl_id)); break; case PSP_WAIT_MSGPIPE: s.append(String.format("MsgPipe (0x%04X)", wait.MsgPipe_id)); break; case PSP_WAIT_EVENTHANDLER: s.append(String.format("EventHandler")); break; case PSP_WAIT_CALLBACK_DELETE: s.append(String.format("CallBackDelete")); break; case JPCSP_WAIT_IO: s.append(String.format("Io (0x%04X)", wait.Io_id)); break; case JPCSP_WAIT_UMD: s.append(String.format("Umd (0x%02X)", wait.wantedUmdStat)); break; case JPCSP_WAIT_GE_LIST: s.append(String.format("Ge List (%s)", Modules.sceGe_userModule.getGeList(waitId))); break; case JPCSP_WAIT_NET: s.append(String.format("Network")); break; case JPCSP_WAIT_AUDIO: s.append(String.format("Audio")); break; case JPCSP_WAIT_DISPLAY_VBLANK: s.append(String.format("Display Vblank (vcount=%d, current=%d)", waitId, Modules.sceDisplayModule.getVcount())); break; case JPCSP_WAIT_CTRL: s.append(String.format("Ctrl")); break; case JPCSP_WAIT_USB: s.append(String.format("Usb")); break; case JPCSP_WAIT_VIDEO_DECODER: s.append(String.format("VideoDecoder")); break; default: s.append(String.format("Unknown waitType=%d", waitType)); break; } if ((status & PSP_THREAD_WAITING) != 0) { if (wait.forever) { s.append(" (forever)"); } else { int restDelay = (int) (wait.microTimeTimeout - Emulator.getClock().microTime()); if (restDelay < 0) { restDelay = 0; } s.append(String.format(" (delay %d us, rest %d us)", wait.micros, restDelay)); } } return s.toString(); } public String getWaitName() { return getWaitName(waitType, waitId, wait, status); } public boolean isSuspended() { return (status & PSP_THREAD_SUSPEND) != 0; } public boolean isWaiting() { return (status & PSP_THREAD_WAITING) != 0; } public boolean isWaitingForType(int waitType) { // Check if the thread is still in a WAITING state, but not WAITING_SUSPEND if (!isWaiting() || isSuspended()) { return false; } return this.waitType == waitType; } public boolean isWaitingFor(int waitType, int waitId) { if (!isWaitingForType(waitType)) { return false; } return this.waitId == waitId; } public boolean isRunning() { return (status & PSP_THREAD_RUNNING) != 0; } public boolean isReady() { return (status & PSP_THREAD_READY) != 0; } public boolean isStopped() { return (status & PSP_THREAD_STOPPED) != 0; } public static boolean isKernelMode(int attr) { return (attr & PSP_THREAD_ATTR_KERNEL) != 0; } public static boolean isUserMode(int attr) { return (attr & PSP_THREAD_ATTR_USER) != 0; } public boolean isKernelMode() { return isKernelMode(attr); } public boolean isUserMode() { return isUserMode(attr); } public RegisteredCallbacks getRegisteredCallbacks(int type) { return registeredCallbacks[type]; } public boolean deleteCallback(SceKernelCallbackInfo callback) { boolean deleted = false; for (int i = 0; i < registeredCallbacks.length; i++) { if (registeredCallbacks[i].removeCallback(callback) != null) { deleted = true; } } return deleted; } public void setExitStatus(int exitStatus) { this.exitStatus = exitStatus; if (exitStatusAddr != null) { exitStatusAddr.setValue(exitStatus); } } public boolean isStackAddress(int address) { if (stackAddr == 0 || stackSize <= 0) { return false; } return address >= stackAddr && address < (stackAddr + stackSize); } @Override public String toString() { return String.format("%s(uid=0x%X, Status=%s, Priority=0x%X, Wait=%s, doCallbacks=%b)", name, uid, getStatusName(), currentPriority, getWaitName(), doCallbacks); } }