/* 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.modules; import static jpcsp.Allegrex.Common._a0; import static jpcsp.Allegrex.Common._ra; import static jpcsp.Allegrex.Common._s0; import static jpcsp.Allegrex.Common._v0; import static jpcsp.Allegrex.Common._zr; import static jpcsp.HLE.HLEModuleManager.HLESyscallNid; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ILLEGAL_ARGUMENT; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ILLEGAL_PRIORITY; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ILLEGAL_THREAD; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_ALARM; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_THREAD; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_THREAD_EVENT_HANDLER; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_VTIMER; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_THREAD_ALREADY_DORMANT; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_THREAD_ALREADY_SUSPEND; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_THREAD_IS_NOT_DORMANT; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_THREAD_IS_NOT_SUSPEND; import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_THREAD_IS_TERMINATED; 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.SceKernelThreadEventHandlerInfo.THREAD_EVENT_CREATE; import static jpcsp.HLE.kernel.types.SceKernelThreadEventHandlerInfo.THREAD_EVENT_DELETE; import static jpcsp.HLE.kernel.types.SceKernelThreadEventHandlerInfo.THREAD_EVENT_EXIT; import static jpcsp.HLE.kernel.types.SceKernelThreadEventHandlerInfo.THREAD_EVENT_START; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_AUDIO; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_CTRL; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_DISPLAY_VBLANK; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_GE_LIST; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_NET; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_UMD; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.JPCSP_WAIT_USB; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_ATTR_KERNEL; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_ATTR_USER; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_READY; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_RUNNING; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_STOPPED; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_SUSPEND; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_WAITING; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_WAITING_SUSPEND; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_DELAY; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_EVENTFLAG; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_MSGPIPE; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_MUTEX; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_NONE; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_SLEEP; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_THREAD_END; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_FPL; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_LWMUTEX; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_MBX; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_SEMA; import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_VPL; import static jpcsp.HLE.modules.SysMemUserForUser.KERNEL_PARTITION_ID; import static jpcsp.HLE.modules.SysMemUserForUser.USER_PARTITION_ID; import static jpcsp.util.Utilities.alignUp; import static jpcsp.util.Utilities.writeStringZ; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import jpcsp.AllegrexOpcodes; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.MemoryMap; import jpcsp.Processor; import jpcsp.Allegrex.CpuState; import jpcsp.Allegrex.Decoder; import jpcsp.Allegrex.compiler.RuntimeContext; import jpcsp.Debugger.DumpDebugState; import jpcsp.HLE.BufferInfo; import jpcsp.HLE.BufferInfo.LengthInfo; import jpcsp.HLE.BufferInfo.Usage; import jpcsp.HLE.CanBeNull; import jpcsp.HLE.HLEFunction; import jpcsp.HLE.HLEModule; import jpcsp.HLE.HLEUnimplemented; import jpcsp.HLE.Modules; import jpcsp.HLE.PspString; import jpcsp.HLE.SceKernelErrorException; import jpcsp.HLE.StringInfo; import jpcsp.HLE.TPointer; import jpcsp.HLE.TPointer32; import jpcsp.HLE.TPointer64; import jpcsp.HLE.kernel.Managers; import jpcsp.HLE.kernel.managers.IntrManager; import jpcsp.HLE.kernel.managers.SceUidManager; import jpcsp.HLE.kernel.managers.SystemTimeManager; import jpcsp.HLE.kernel.types.IAction; import jpcsp.HLE.kernel.types.IWaitStateChecker; import jpcsp.HLE.kernel.types.SceKernelAlarmInfo; import jpcsp.HLE.kernel.types.SceKernelCallbackInfo; import jpcsp.HLE.kernel.types.SceKernelErrors; import jpcsp.HLE.kernel.types.SceKernelSystemStatus; import jpcsp.HLE.kernel.types.SceKernelThreadEventHandlerInfo; import jpcsp.HLE.kernel.types.SceKernelThreadInfo; import jpcsp.HLE.kernel.types.SceKernelThreadOptParam; import jpcsp.HLE.kernel.types.SceKernelTls; import jpcsp.HLE.kernel.types.SceKernelVTimerInfo; import jpcsp.HLE.kernel.types.SceModule; import jpcsp.HLE.kernel.types.ThreadWaitInfo; import jpcsp.HLE.kernel.types.pspBaseCallback; import jpcsp.HLE.kernel.types.SceKernelThreadInfo.RegisteredCallbacks; import jpcsp.HLE.modules.SysMemUserForUser.SysMemInfo; import jpcsp.hardware.Interrupts; import jpcsp.memory.IMemoryReader; import jpcsp.memory.IMemoryWriter; import jpcsp.memory.MemoryReader; import jpcsp.memory.MemoryWriter; import jpcsp.scheduler.Scheduler; import jpcsp.util.DurationStatistics; import org.apache.log4j.Logger; import jpcsp.HLE.CheckArgument;; /* * Thread scheduling on PSP: * - when a thread having a higher priority than the current thread, switches to the * READY state, the current thread is interrupted immediately and is loosing the * RUNNING state. The new thread then moves to the RUNNING state. * - when a thread having the same or a lower priority than the current thread, * switches to the READY state, the current thread is not interrupted and is keeping * the RUNNING state. * - a RUNNING thread can only yield to a thread having the same priority by calling * sceKernelRotateThreadReadyQueue(0). * - the clock precision when interrupting a RUNNING thread is about 200 microseconds. * i.e., it can take up to 200us when a high priority thread moves to the READY * state before it changes to the RUNNING state. * - sceKernelStartThread is always resuming the thread dispatching. * * Thread scheduling on Jpcsp: * - the rules for moving between states are implemented in hleChangeThreadState() * - the rules for choosing the thread in the RUNNING state are implemented in * hleRescheduleCurrentThread() * - the clock precision for interrupting a RUNNING thread is about 1000 microseconds. * This is due to a restriction of the Java timers used by the Thread.sleep() methods. * Even the Thread.sleep(millis, nanos) seems to have the same restriction * (at least on windows). * - preemptive scheduling is implemented in RuntimeContext by a separate * Java thread (RuntimeSyncThread). This thread sets the flag RuntimeContext.wantSync * when a scheduler action has to be executed. This flag is checked by the compiled * code at each back branch (i.e. a branch to a lower address, usually a loop). * * Test application: * - taskScheduler.prx: testing the scheduler rules between threads having higher, * lower or the same priority. * The clock precision of 200us on the PSP can be observed here. */ public class ThreadManForUser extends HLEModule { public static Logger log = Modules.getLogger("ThreadManForUser"); private HashMap<Integer, SceKernelThreadInfo> threadMap; private HashMap<Integer, SceKernelThreadEventHandlerInfo> threadEventHandlers; private LinkedList<SceKernelThreadInfo> readyThreads; private SceKernelThreadInfo currentThread; private SceKernelThreadInfo idle0, idle1; public Statistics statistics; private boolean dispatchThreadEnabled; private static final int SCE_KERNEL_DISPATCHTHREAD_STATE_DISABLED = 0; private static final int SCE_KERNEL_DISPATCHTHREAD_STATE_ENABLED = 1; private static final String rootThreadName = "root"; // The PSP seems to have a resolution of 200us protected static final int THREAD_DELAY_MINIMUM_MICROS = 200; protected static final int CALLBACKID_REGISTER = _s0; protected CallbackManager callbackManager = new CallbackManager(); public static final int INTERNAL_THREAD_ADDRESS_START = MemoryMap.START_RAM; protected static final int IDLE_THREAD_ADDRESS = INTERNAL_THREAD_ADDRESS_START; public static final int THREAD_EXIT_HANDLER_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x10; public static final int CALLBACK_EXIT_HANDLER_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x20; public static final int ASYNC_LOOP_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x30; public static final int NET_APCTL_LOOP_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x40; public static final int NET_ADHOC_MATCHING_EVENT_LOOP_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x50; public static final int NET_ADHOC_MATCHING_INPUT_LOOP_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x60; public static final int NET_ADHOC_CTL_LOOP_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x70; public static final int UTILITY_LOOP_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x80; public static final int WLAN_SEND_CALLBACK_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0x90; public static final int WLAN_UP_CALLBACK_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0xA0; public static final int WLAN_DOWN_CALLBACK_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0xB0; public static final int WLAN_IOCTL_CALLBACK_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0xC0; public static final int WLAN_LOOP_ADDRESS = INTERNAL_THREAD_ADDRESS_START + 0xD0; public static final int INTERNAL_THREAD_ADDRESS_END = INTERNAL_THREAD_ADDRESS_START + 0xE0; public static final int INTERNAL_THREAD_ADDRESS_SIZE = INTERNAL_THREAD_ADDRESS_END - INTERNAL_THREAD_ADDRESS_START; private HashMap<Integer, pspBaseCallback> callbackMap; private static final boolean LOG_CONTEXT_SWITCHING = true; private static final boolean LOG_INSTRUCTIONS = false; public boolean exitCalled = false; // see sceKernelGetThreadmanIdList public final static int SCE_KERNEL_TMID_Thread = 1; public final static int SCE_KERNEL_TMID_Semaphore = 2; public final static int SCE_KERNEL_TMID_EventFlag = 3; public final static int SCE_KERNEL_TMID_Mbox = 4; public final static int SCE_KERNEL_TMID_Vpl = 5; public final static int SCE_KERNEL_TMID_Fpl = 6; public final static int SCE_KERNEL_TMID_Mpipe = 7; public final static int SCE_KERNEL_TMID_Callback = 8; public final static int SCE_KERNEL_TMID_ThreadEventHandler = 9; public final static int SCE_KERNEL_TMID_Alarm = 10; public final static int SCE_KERNEL_TMID_VTimer = 11; public final static int SCE_KERNEL_TMID_Mutex = 12; public final static int SCE_KERNEL_TMID_LwMutex = 13; public final static int SCE_KERNEL_TMID_SleepThread = 64; public final static int SCE_KERNEL_TMID_DelayThread = 65; public final static int SCE_KERNEL_TMID_SuspendThread = 66; public final static int SCE_KERNEL_TMID_DormantThread = 67; protected static final int INTR_NUMBER = IntrManager.PSP_SYSTIMER0_INTR; protected Map<Integer, SceKernelAlarmInfo> alarms; protected Map<Integer, SceKernelVTimerInfo> vtimers; protected boolean needThreadReschedule; protected WaitThreadEndWaitStateChecker waitThreadEndWaitStateChecker; protected TimeoutThreadWaitStateChecker timeoutThreadWaitStateChecker; protected SleepThreadWaitStateChecker sleepThreadWaitStateChecker; public final static int PSP_ATTR_ADDR_HIGH = 0x4000; protected HashMap<Integer, SceKernelTls> tlsMap; public static class Statistics { private ArrayList<ThreadStatistics> threads = new ArrayList<ThreadStatistics>(); public long allCycles = 0; public long startTimeMillis; public long endTimeMillis; public long allCpuMillis = 0; public Statistics() { startTimeMillis = System.currentTimeMillis(); } public void exit() { endTimeMillis = System.currentTimeMillis(); } public long getDurationMillis() { return endTimeMillis - startTimeMillis; } private void addThreadStatistics(SceKernelThreadInfo thread) { if (!DurationStatistics.collectStatistics) { return; } ThreadStatistics threadStatistics = new ThreadStatistics(); threadStatistics.name = thread.name; threadStatistics.runClocks = thread.runClocks; threads.add(threadStatistics); allCycles += thread.runClocks; if (thread.javaThreadId > 0) { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); if (threadMXBean.isThreadCpuTimeEnabled()) { long threadCpuTimeNanos = thread.javaThreadCpuTimeNanos; if (threadCpuTimeNanos < 0) { threadCpuTimeNanos = threadMXBean.getThreadCpuTime(thread.javaThreadId); } if (threadCpuTimeNanos > 0) { allCpuMillis += threadCpuTimeNanos / 1000000L; } } } } private static class ThreadStatistics implements Comparable<ThreadStatistics> { public String name; public long runClocks; @Override public int compareTo(ThreadStatistics o) { return -(new Long(runClocks).compareTo(o.runClocks)); } public String getQuotedName() { return "'" + name + "'"; } } } public static class CallbackManager { private Map<Integer, Callback> callbacks; private int currentCallbackId; public void Initialize() { callbacks = new HashMap<Integer, Callback>(); currentCallbackId = 1; } public void addCallback(Callback callback) { callbacks.put(callback.getId(), callback); } public Callback remove(int id) { Callback callback = callbacks.remove(id); return callback; } public int getNewCallbackId() { return currentCallbackId++; } } public static class Callback { private int id; private int address; private int[] parameters; private int savedIdRegister; private int savedRa; private int savedPc; private int savedV0; private int savedV1; private IAction afterAction; private boolean returnVoid; private boolean preserveCpuState; private CpuState savedCpuState; public Callback(int id, int address, int[] parameters, IAction afterAction, boolean returnVoid, boolean preserveCpuState) { this.id = id; this.address = address; this.parameters = parameters; this.afterAction = afterAction; this.returnVoid = returnVoid; this.preserveCpuState = preserveCpuState; } public int getId() { return id; } public void execute(SceKernelThreadInfo thread) { CpuState cpu = thread.cpuContext; savedIdRegister = cpu.getRegister(CALLBACKID_REGISTER); savedRa = cpu._ra; savedPc = cpu.pc; savedV0 = cpu._v0; savedV1 = cpu._v1; if (preserveCpuState) { savedCpuState = new CpuState(cpu); } // Copy parameters ($a0, $a1, ...) to the cpu if (parameters != null) { for (int i = 0; i < parameters.length; i++) { cpu.setRegister(_a0 + i, parameters[i]); } } cpu.setRegister(CALLBACKID_REGISTER, id); cpu._ra = CALLBACK_EXIT_HANDLER_ADDRESS; cpu.pc = address; RuntimeContext.executeCallback(); } public void executeExit(CpuState cpu) { cpu.setRegister(CALLBACKID_REGISTER, savedIdRegister); cpu._ra = savedRa; cpu.pc = savedPc; if (afterAction != null) { afterAction.execute(); } if (preserveCpuState) { cpu.copy(savedCpuState); } else { // Do we need to restore $v0/$v1? if (returnVoid) { cpu._v0 = savedV0; cpu._v1 = savedV1; } } } public void setAfterAction(IAction afterAction) { this.afterAction = afterAction; } @Override public String toString() { return String.format("Callback address=0x%08X, id=%d, returnVoid=%b, preserveCpuState=%b", address, getId(), returnVoid, preserveCpuState); } } private class AfterCallAction implements IAction { private SceKernelThreadInfo thread; private int status; private int waitType; private int waitId; private ThreadWaitInfo threadWaitInfo; private boolean doCallbacks; private IAction afterAction; public AfterCallAction(SceKernelThreadInfo thread, IAction afterAction) { this.thread = thread; status = thread.status; waitType = thread.waitType; waitId = thread.waitId; threadWaitInfo = new ThreadWaitInfo(thread.wait); doCallbacks = thread.doCallbacks; this.afterAction = afterAction; } @Override public void execute() { boolean restoreWaitState = true; // After calling a callback, check if the waiting state of the thread // is still valid, i.e. if the thread must continue to wait or if the // wait condition has been reached. if (threadWaitInfo.waitStateChecker != null) { if (!threadWaitInfo.waitStateChecker.continueWaitState(thread, threadWaitInfo)) { restoreWaitState = false; } } if (restoreWaitState) { if (status == PSP_THREAD_RUNNING) { doCallbacks = false; } if (log.isDebugEnabled()) { log.debug(String.format("AfterCallAction: restoring wait state for thread '%s' to %s, %s, doCallbacks %b", thread.toString(), SceKernelThreadInfo.getStatusName(status), SceKernelThreadInfo.getWaitName(waitType, waitId, threadWaitInfo, status), doCallbacks)); } // Restore the wait state of the thread thread.waitType = waitType; thread.waitId = waitId; thread.wait.copy(threadWaitInfo); hleChangeThreadState(thread, status); } else if (thread.isRunning()) { if (log.isDebugEnabled()) { log.debug(String.format("AfterCallAction: leaving thread in RUNNING state: %s", thread)); } doCallbacks = false; } else if (!thread.isReady()) { if (log.isDebugEnabled()) { log.debug(String.format("AfterCallAction: set thread to READY state: %s", thread)); } hleChangeThreadState(thread, PSP_THREAD_READY); doCallbacks = false; } else { if (log.isDebugEnabled()) { log.debug(String.format("AfterCallAction: leaving thread in READY state: %s", thread)); } doCallbacks = false; } thread.doCallbacks = doCallbacks; hleRescheduleCurrentThread(); if (afterAction != null) { afterAction.execute(); } } } public class TimeoutThreadAction implements IAction { private SceKernelThreadInfo thread; public TimeoutThreadAction(SceKernelThreadInfo thread) { this.thread = thread; } @Override public void execute() { hleThreadWaitTimeout(thread); } } public class TimeoutThreadWaitStateChecker implements IWaitStateChecker { @Override public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) { // Waiting forever? if (wait.forever) { return true; } if (wait.microTimeTimeout <= Emulator.getClock().microTime()) { hleThreadWaitTimeout(thread); return false; } // The waitTimeoutAction has been deleted by hleChangeThreadState while // leaving the WAIT state. It has to be restored. if (wait.waitTimeoutAction == null) { wait.waitTimeoutAction = new TimeoutThreadAction(thread); } return true; } } public class DeleteThreadAction implements IAction { private SceKernelThreadInfo thread; public DeleteThreadAction(SceKernelThreadInfo thread) { this.thread = thread; } @Override public void execute() { hleDeleteThread(thread); } } public class WaitThreadEndWaitStateChecker implements IWaitStateChecker { @Override public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) { // Check if the thread has to continue its wait state or if the thread // has exited during the callback execution. SceKernelThreadInfo threadEnd = getThreadById(wait.ThreadEnd_id); if (threadEnd == null) { // The thread has completely disappeared during the callback execution... thread.cpuContext._v0 = ERROR_KERNEL_NOT_FOUND_THREAD; return false; } if (threadEnd.isStopped()) { // Return exit status of stopped thread thread.cpuContext._v0 = threadEnd.exitStatus; return false; } return true; } } public static class SleepThreadWaitStateChecker implements IWaitStateChecker { @Override public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) { if (thread.wakeupCount > 0) { // sceKernelWakeupThread() has been called while the thread was waiting thread.wakeupCount--; // Return 0 thread.cpuContext._v0 = 0; return false; } return true; } } /** * A callback is deleted when its return value is non-zero. */ private class CheckCallbackReturnValue implements IAction { private SceKernelThreadInfo thread; private int callbackUid; public CheckCallbackReturnValue(SceKernelThreadInfo thread, int callbackUid) { this.thread = thread; this.callbackUid = callbackUid; } @Override public void execute() { int callbackReturnValue = thread.cpuContext._v0; if (callbackReturnValue != 0) { if (log.isDebugEnabled()) { log.debug(String.format("Callback uid=0x%X has returned value 0x%08X: deleting the callback", callbackUid, callbackReturnValue)); } hleKernelDeleteCallback(callbackUid); } } } private static class AfterSceKernelExtendThreadStackAction implements IAction { private SceKernelThreadInfo thread; private int savedPc; private int savedSp; private int savedRa; public AfterSceKernelExtendThreadStackAction(SceKernelThreadInfo thread, int savedPc, int savedSp, int savedRa) { this.thread = thread; this.savedPc = savedPc; this.savedSp = savedSp; this.savedRa = savedRa; } @Override public void execute() { CpuState cpu = Emulator.getProcessor().cpu; if (log.isDebugEnabled()) { log.debug(String.format("AfterSceKernelExtendThreadStackAction savedSp=0x%08X, savedRa=0x%08X", savedSp, savedRa)); } cpu.pc = savedPc; cpu._sp = savedSp; cpu._ra = savedRa; // The return value in $v0 of the entryAdd is passed back as return value // of sceKernelExtendThreadStack. thread.freeExtendedStack(); } } public ThreadManForUser() { } @Override public void start() { currentThread = null; threadMap = new HashMap<Integer, SceKernelThreadInfo>(); threadEventHandlers = new HashMap<Integer, SceKernelThreadEventHandlerInfo>(); readyThreads = new LinkedList<SceKernelThreadInfo>(); statistics = new Statistics(); callbackMap = new HashMap<Integer, pspBaseCallback>(); callbackManager.Initialize(); // Reserve the memory used by the internal handlers Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.KERNEL_PARTITION_ID, "ThreadMan-InternalHandlers", SysMemUserForUser.PSP_SMEM_Addr, INTERNAL_THREAD_ADDRESS_SIZE, INTERNAL_THREAD_ADDRESS_START); installIdleThreads(); installThreadExitHandler(); installCallbackExitHandler(); installAsyncLoopHandler(); installNetApctlLoopHandler(); installNetAdhocMatchingEventLoopHandler(); installNetAdhocMatchingInputLoopHandler(); installNetAdhocCtlLoopHandler(); installUtilityLoopHandler(); installWlanSendCallback(); installWlanUpCallback(); installWlanDownCallback(); installWlanIoctlCallback(); installWlanLoopHandler(); alarms = new HashMap<Integer, SceKernelAlarmInfo>(); vtimers = new HashMap<Integer, SceKernelVTimerInfo>(); dispatchThreadEnabled = true; needThreadReschedule = true; ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); if (threadMXBean.isThreadCpuTimeSupported()) { threadMXBean.setThreadCpuTimeEnabled(true); } waitThreadEndWaitStateChecker = new WaitThreadEndWaitStateChecker(); timeoutThreadWaitStateChecker = new TimeoutThreadWaitStateChecker(); sleepThreadWaitStateChecker = new SleepThreadWaitStateChecker(); tlsMap = new HashMap<Integer, SceKernelTls>(); super.start(); } @Override public void stop() { alarms = null; vtimers = null; for (SceKernelThreadInfo thread : threadMap.values()) { terminateThread(thread); } super.stop(); } public Iterator<SceKernelThreadInfo> iterator() { return threadMap.values().iterator(); } public Iterator<SceKernelThreadInfo> iteratorByPriority() { Collection<SceKernelThreadInfo> c = threadMap.values(); List<SceKernelThreadInfo> list = new LinkedList<SceKernelThreadInfo>(c); Collections.sort(list, idle0); // We need an instance of SceKernelThreadInfo for the comparator, so we use idle0 return list.iterator(); } public SceKernelThreadInfo getRootThread(SceModule module) { for (SceKernelThreadInfo thread : threadMap.values()) { if (rootThreadName.equals(thread.name) && thread.moduleid == module.modid) { return thread; } } return null; } /** call this when resetting the emulator * @param entry_addr entry from ELF header * @param attr from sceModuleInfo ELF section header */ public void Initialise(SceModule module, int entry_addr, int attr, String pspfilename, int moduleid, int gp, boolean fromSyscall) { // Create a thread the program will run inside // The stack size seems to be 0x40000 when starting the application from the VSH // and smaller when starting the application with sceKernelLoadExec() - guess: 0x8000. // This could not be reproduced on a PSP. int rootStackSize = (fromSyscall ? 0x8000 : 0x40000); // Use the module_start_thread_stacksize when this information was present in the ELF file if (module != null && module.module_start_thread_stacksize > 0) { rootStackSize = module.module_start_thread_stacksize; } // For a kernel module, the stack is allocated in the kernel partition int rootMpidStack = module.mpiddata > 0 ? module.mpiddata : USER_PARTITION_ID; int rootInitPriority = 0x20; // Use the module_start_thread_priority when this information was present in the ELF file if (module != null && module.module_start_thread_priority > 0) { rootInitPriority = module.module_start_thread_priority; } SceKernelThreadInfo rootThread = new SceKernelThreadInfo(rootThreadName, entry_addr, rootInitPriority, rootStackSize, attr, rootMpidStack); if (log.isDebugEnabled()) { log.debug(String.format("Creating root thread: uid=0x%X, entry=0x%08X, priority=%d, stackSize=0x%X, attr=0x%X", rootThread.uid, entry_addr, rootInitPriority, rootStackSize, attr)); } rootThread.moduleid = moduleid; threadMap.put(rootThread.uid, rootThread); // Set user mode bit if kernel mode bit is not present if (!rootThread.isKernelMode()) { rootThread.attr |= PSP_THREAD_ATTR_USER; } // Setup args by copying them onto the stack hleKernelSetThreadArguments(rootThread, pspfilename); // Setup threads $gp rootThread.cpuContext._gp = gp; idle0.cpuContext._gp = gp; idle1.cpuContext._gp = gp; hleChangeThreadState(rootThread, PSP_THREAD_READY); hleRescheduleCurrentThread(); } public void hleKernelSetThreadArguments(SceKernelThreadInfo thread, String argument) { int address = prepareThreadArguments(thread, argument.length() + 1); writeStringZ(Memory.getInstance(), address, argument); } public void hleKernelSetThreadArguments(SceKernelThreadInfo thread, byte[] argument, int argumentSize) { int address = prepareThreadArguments(thread, argumentSize); IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(address, argumentSize, 1); for (int i = 0; i < argumentSize; i++) { memoryWriter.writeNext(argument[i] & 0xFF); } memoryWriter.flush(); } public void hleKernelSetThreadArguments(SceKernelThreadInfo thread, int argumentAddr, int argumentSize) { int address = prepareThreadArguments(thread, argumentAddr == 0 ? -1 : argumentSize); if (argumentAddr != 0) { Memory.getInstance().memcpy(address, argumentAddr, argumentSize); } } private int prepareThreadArguments(SceKernelThreadInfo thread, int argumentSize) { // 256 bytes padding between user data top and real stack top int address = (thread.getStackAddr() + thread.stackSize - 0x100) - ((argumentSize + 0xF) & ~0xF); if (argumentSize < 0) { // Set the pointer to NULL when none is provided thread.cpuContext._a0 = 0; // a0 = user data len thread.cpuContext._a1 = 0; // a1 = pointer to arg data in stack } else { thread.cpuContext._a0 = argumentSize; // a0 = user data len thread.cpuContext._a1 = address; // a1 = pointer to arg data in stack } // 64 bytes padding between program stack top and user data thread.cpuContext._sp = address - 0x40; return address; } public static int NOP() { // sll $zr, $zr, 0 <=> nop return (AllegrexOpcodes.SLL << 26) | (_zr << 16) | (_zr << 11) | (0 << 6); } public static int MOVE(int rd, int rs) { // addu rd, rs, $zr <=> move rd, rs return AllegrexOpcodes.ADDU | (rd << 11) | (_zr << 16) | (rs << 21); } public static int LUI(int rd, int imm16) { return (AllegrexOpcodes.LUI << 26) | (rd << 16) | (imm16 & 0xFFFF); } public static int ADDIU(int rs, int rt, int imm16) { return (AllegrexOpcodes.ADDIU << 26) | (rs << 21) | (rt << 16) | (imm16 & 0xFFFF); } public static int SW(int rt, int base, int imm16) { return (AllegrexOpcodes.SW << 26) | (base << 21) | (rt << 16) | (imm16 & 0xFFFF); } public static int LW(int rt, int base, int imm16) { return (AllegrexOpcodes.LW << 26) | (base << 21) | (rt << 16) | (imm16 & 0xFFFF); } public static int JAL(int address) { return (AllegrexOpcodes.JAL << 26) | ((address >> 2) & 0x03FFFFFF); } public static int J(int address) { return (AllegrexOpcodes.J << 26) | ((address >> 2) & 0x03FFFFFF); } private int SYSCALL(String functionName) { // syscall [functionName] return (AllegrexOpcodes.SPECIAL << 26) | AllegrexOpcodes.SYSCALL | (getHleFunctionByName(functionName).getSyscallCode() << 6); } public static int JR() { // jr $ra return (AllegrexOpcodes.SPECIAL << 26) | AllegrexOpcodes.JR | (_ra << 21); } public static int B(int destination) { // beq $zr, $zr, destination <=> b destination return (AllegrexOpcodes.BEQ << 26) | (_zr << 21) | (_zr << 16) | (destination & 0x0000FFFF); } /** * Generate 2 idle threads which can toggle between each other when there are no ready threads */ private void installIdleThreads() { // This memory is always reserved on a real PSP SysMemInfo info = Modules.SysMemUserForUserModule.malloc(SysMemUserForUser.USER_PARTITION_ID, "ThreadMan-RootMem", SysMemUserForUser.PSP_SMEM_Addr, 0x4000, MemoryMap.START_USERSPACE); int reservedMem = info.addr; IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(IDLE_THREAD_ADDRESS, 0x20, 4); memoryWriter.writeNext(MOVE(_a0, _zr)); memoryWriter.writeNext(SYSCALL("sceKernelDelayThread")); memoryWriter.writeNext(B(-3)); memoryWriter.writeNext(NOP()); memoryWriter.flush(); // lowest allowed priority is 0x77, so we are ok at 0x7f // Allocate a stack because interrupts can be processed by the // idle thread, using its stack. // The stack is allocated into the reservedMem area. idle0 = new SceKernelThreadInfo("idle0", IDLE_THREAD_ADDRESS | 0x80000000, 0x7f, 0, PSP_THREAD_ATTR_KERNEL, KERNEL_PARTITION_ID); idle0.setSystemStack(reservedMem, 0x2000); idle0.reset(); idle0.exitStatus = ERROR_KERNEL_THREAD_IS_NOT_DORMANT; threadMap.put(idle0.uid, idle0); hleChangeThreadState(idle0, PSP_THREAD_READY); idle1 = new SceKernelThreadInfo("idle1", IDLE_THREAD_ADDRESS | 0x80000000, 0x7f, 0, PSP_THREAD_ATTR_KERNEL, KERNEL_PARTITION_ID); idle1.setSystemStack(reservedMem + 0x2000, 0x2000); idle1.reset(); idle1.exitStatus = ERROR_KERNEL_THREAD_IS_NOT_DORMANT; threadMap.put(idle1.uid, idle1); hleChangeThreadState(idle1, PSP_THREAD_READY); } private void installThreadExitHandler() { IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(THREAD_EXIT_HANDLER_ADDRESS, 0x10, 4); memoryWriter.writeNext(MOVE(_a0, _v0)); memoryWriter.writeNext(SYSCALL("hleKernelExitThread")); memoryWriter.writeNext(JR()); memoryWriter.writeNext(NOP()); memoryWriter.flush(); } private void installCallbackExitHandler() { IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(CALLBACK_EXIT_HANDLER_ADDRESS, 0x10, 4); memoryWriter.writeNext(SYSCALL("hleKernelExitCallback")); memoryWriter.writeNext(JR()); memoryWriter.writeNext(NOP()); memoryWriter.flush(); } private void installLoopHandler(String hleFunctionName, int address) { IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(address, 0x20, 4); memoryWriter.writeNext(SYSCALL(hleFunctionName)); memoryWriter.writeNext(B(-2)); memoryWriter.writeNext(NOP()); memoryWriter.flush(); } private void installAsyncLoopHandler() { installLoopHandler("hleKernelAsyncLoop", ASYNC_LOOP_ADDRESS); } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleKernelAsyncLoop(Processor processor) { Modules.IoFileMgrForUserModule.hleAsyncThread(processor); } private void installNetApctlLoopHandler() { installLoopHandler("hleKernelNetApctlLoop", NET_APCTL_LOOP_ADDRESS); } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleKernelNetApctlLoop(Processor processor) { Modules.sceNetApctlModule.hleNetApctlThread(processor); } private void installNetAdhocMatchingEventLoopHandler() { installLoopHandler("hleKernelNetAdhocMatchingEventLoop", NET_ADHOC_MATCHING_EVENT_LOOP_ADDRESS); } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleKernelNetAdhocMatchingEventLoop(Processor processor) { Modules.sceNetAdhocMatchingModule.hleNetAdhocMatchingEventThread(processor); } private void installNetAdhocMatchingInputLoopHandler() { installLoopHandler("hleKernelNetAdhocMatchingInputLoop", NET_ADHOC_MATCHING_INPUT_LOOP_ADDRESS); } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleKernelNetAdhocMatchingInputLoop(Processor processor) { Modules.sceNetAdhocMatchingModule.hleNetAdhocMatchingInputThread(processor); } private void installNetAdhocCtlLoopHandler() { installLoopHandler("hleKernelNetAdhocctlLoop", NET_ADHOC_CTL_LOOP_ADDRESS); } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleUtilityLoop(Processor processor) { Modules.sceUtilityModule.hleUtilityThread(processor); } private void installUtilityLoopHandler() { installLoopHandler("hleUtilityLoop", UTILITY_LOOP_ADDRESS); } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleKernelNetAdhocctlLoop(Processor processor) { Modules.sceNetAdhocctlModule.hleNetAdhocctlThread(processor); } private void installWlanSendCallback() { IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(WLAN_SEND_CALLBACK_ADDRESS, 0x10, 4); memoryWriter.writeNext(SYSCALL("hleWlanSendCallback")); memoryWriter.writeNext(JR()); memoryWriter.writeNext(NOP()); memoryWriter.flush(); } @HLEFunction(nid = HLESyscallNid, version = 150) public int hleWlanSendCallback(TPointer handleAddr) { return Modules.sceWlanModule.hleWlanSendCallback(handleAddr); } private void installWlanUpCallback() { IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(WLAN_UP_CALLBACK_ADDRESS, 0x10, 4); memoryWriter.writeNext(SYSCALL("hleWlanUpCallback")); memoryWriter.writeNext(JR()); memoryWriter.writeNext(NOP()); memoryWriter.flush(); } @HLEFunction(nid = HLESyscallNid, version = 150) public int hleWlanUpCallback(TPointer handleAddr) { return Modules.sceWlanModule.hleWlanUpCallback(handleAddr); } private void installWlanDownCallback() { IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(WLAN_DOWN_CALLBACK_ADDRESS, 0x10, 4); memoryWriter.writeNext(SYSCALL("hleWlanDownCallback")); memoryWriter.writeNext(JR()); memoryWriter.writeNext(NOP()); memoryWriter.flush(); } @HLEFunction(nid = HLESyscallNid, version = 150) public int hleWlanDownCallback(TPointer handleAddr) { return Modules.sceWlanModule.hleWlanDownCallback(handleAddr); } private void installWlanIoctlCallback() { IMemoryWriter memoryWriter = MemoryWriter.getMemoryWriter(WLAN_IOCTL_CALLBACK_ADDRESS, 0x10, 4); memoryWriter.writeNext(SYSCALL("hleWlanIoctlCallback")); memoryWriter.writeNext(JR()); memoryWriter.writeNext(NOP()); memoryWriter.flush(); } @HLEFunction(nid = HLESyscallNid, version = 150) public int hleWlanIoctlCallback(TPointer handleAddr, int cmd, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=32, usage=Usage.in) TPointer unknown, @BufferInfo(lengthInfo=LengthInfo.fixedLength, length=8, usage=Usage.in) TPointer32 buffersAddr) { return Modules.sceWlanModule.hleWlanIoctlCallback(handleAddr, cmd, unknown, buffersAddr); } private void installWlanLoopHandler() { installLoopHandler("hleKernelWlanLoop", WLAN_LOOP_ADDRESS); } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleKernelWlanLoop() { Modules.sceWlanModule.hleWlanThread(); } /** to be called when exiting the emulation */ public void exit() { exitCalled = true; if (threadMap != null) { // Delete all the threads to collect statistics deleteAllThreads(); log.info("----------------------------- ThreadMan exit -----------------------------"); if (DurationStatistics.collectStatistics) { statistics.exit(); log.info(String.format("ThreadMan Statistics (%,d cycles in %.3fs):", statistics.allCycles, statistics.getDurationMillis() / 1000.0)); Collections.sort(statistics.threads); for (Statistics.ThreadStatistics threadStatistics : statistics.threads) { double percentage = 0; if (statistics.allCycles != 0) { percentage = (threadStatistics.runClocks / (double) statistics.allCycles) * 100; } log.info(String.format(" Thread %-30s %,12d cycles (%5.2f%%)", threadStatistics.getQuotedName(), threadStatistics.runClocks, percentage)); } } } } /** To be called from the main emulation loop * This is only used when running in interpreter mode, * i.e. it is no longer used when the Compiler is enabled. */ public void step() { if (LOG_INSTRUCTIONS) { if (log.isTraceEnabled()) { CpuState cpu = Emulator.getProcessor().cpu; if (!isIdleThread(currentThread) && cpu.pc != 0) { int address = cpu.pc - 4; int opcode = Memory.getInstance().read32(address); log.trace(String.format("Executing %08X %s", address, Decoder.instruction(opcode).disasm(address, opcode))); } } } if (currentThread != null) { currentThread.runClocks++; } else if (!exitCalled) { // We always need to be in a thread! we shouldn't get here. log.error("No ready threads!"); } } private void internalContextSwitch(SceKernelThreadInfo newThread) { if (currentThread != null) { // Switch out old thread if (currentThread.status == PSP_THREAD_RUNNING) { hleChangeThreadState(currentThread, PSP_THREAD_READY); } // save registers currentThread.saveContext(); } if (newThread != null) { // Switch in new thread hleChangeThreadState(newThread, PSP_THREAD_RUNNING); // restore registers newThread.restoreContext(); if (LOG_CONTEXT_SWITCHING && log.isDebugEnabled() && !isIdleThread(newThread)) { log.debug(String.format("----- %s, now=%d", newThread, Emulator.getClock().microTime())); } } else { // When running under compiler mode this gets triggered by exit() if (!exitCalled) { DumpDebugState.dumpDebugState(); log.info("No ready threads - pausing emulator. caller:" + getCallingFunction()); Emulator.PauseEmuWithStatus(Emulator.EMU_STATUS_UNKNOWN); } } currentThread = newThread; RuntimeContext.update(); } /** @param newThread The thread to switch in. */ private boolean contextSwitch(SceKernelThreadInfo newThread) { if (IntrManager.getInstance().isInsideInterrupt()) { // No context switching inside an interrupt if (log.isDebugEnabled()) { log.debug("Inside an interrupt, not context switching to " + newThread); } return false; } if (Interrupts.isInterruptsDisabled()) { // No context switching when interrupts are disabled if (log.isDebugEnabled()) { log.debug("Interrupts are disabled, not context switching to " + newThread); } return false; } if (!dispatchThreadEnabled) { log.info("DispatchThread disabled, not context switching to " + newThread); return false; } internalContextSwitch(newThread); checkThreadCallbacks(currentThread); executePendingCallbacks(currentThread); return true; } private void executePendingCallbacks(SceKernelThreadInfo thread) { if (!thread.pendingCallbacks.isEmpty()) { if (RuntimeContext.canExecuteCallback(thread)) { Callback callback = thread.pendingCallbacks.poll(); if (log.isDebugEnabled()) { log.debug(String.format("Executing pending callback '%s' for thread '%s'", callback, thread)); } callback.execute(thread); } } } public void checkPendingCallbacks() { executePendingCallbacks(currentThread); } private boolean executePendingActions(SceKernelThreadInfo thread) { boolean actionExecuted = false; if (!thread.pendingActions.isEmpty()) { if (currentThread == thread) { IAction action = thread.pendingActions.poll(); if (log.isDebugEnabled()) { log.debug(String.format("Executing pending action '%s' for thread '%s'", action, thread)); } action.execute(); actionExecuted = true; } } return actionExecuted; } public boolean checkPendingActions() { return executePendingActions(currentThread); } public void pushActionForThread(SceKernelThreadInfo thread, IAction action) { thread.pendingActions.add(action); } /** This function must have the property of never returning currentThread, * unless currentThread is already null. * @return The next thread to schedule (based on thread priorities). */ private SceKernelThreadInfo nextThread() { // Find the thread with status PSP_THREAD_READY and the highest priority. // In this implementation low priority threads can get starved. // Remark: the currentThread is not present in the readyThreads List. SceKernelThreadInfo found = null; synchronized (readyThreads) { for (SceKernelThreadInfo thread : readyThreads) { if (found == null || thread.currentPriority < found.currentPriority) { found = thread; } } } return found; } /** * Switch to the thread with status PSP_THREAD_READY and having the highest priority. * If the current thread is in status PSP_THREAD_READY and * still having the highest priority, nothing is changed. * If the current thread is having the same priority as the highest priority, * nothing is changed (no yielding to threads having the same priority). */ public void hleRescheduleCurrentThread() { if (needThreadReschedule) { SceKernelThreadInfo newThread = nextThread(); if (newThread != null && (currentThread == null || currentThread.status != PSP_THREAD_RUNNING || currentThread.currentPriority > newThread.currentPriority)) { if (LOG_CONTEXT_SWITCHING && Modules.log.isDebugEnabled()) { log.debug("Context switching to '" + newThread + "' after reschedule"); } if (contextSwitch(newThread)) { needThreadReschedule = false; } } else { needThreadReschedule = false; } } } /** * Same behavior as hleRescheduleCurrentThread() * excepted that it executes callbacks when doCallbacks == true */ public void hleRescheduleCurrentThread(boolean doCallbacks) { SceKernelThreadInfo thread = currentThread; if (doCallbacks) { if (thread != null) { thread.doCallbacks = doCallbacks; } checkCallbacks(); } hleRescheduleCurrentThread(); if (currentThread == thread && doCallbacks) { if (thread.isRunning()) { thread.doCallbacks = false; } } } public void hleYieldCurrentThread() { hleKernelDelayThread(100, false); } public int getCurrentThreadID() { if (currentThread == null) { return -1; } return currentThread.uid; } public SceKernelThreadInfo getCurrentThread() { return currentThread; } public boolean isIdleThread(SceKernelThreadInfo thread) { return (thread == idle0 || thread == idle1); } public boolean isIdleThread(int uid) { return uid == idle0.uid || uid == idle1.uid; } public boolean isKernelMode() { return currentThread.isKernelMode(); } public String getThreadName(int uid) { SceKernelThreadInfo thread = threadMap.get(uid); if (thread == null) { return "NOT A THREAD"; } return thread.name; } public boolean isDispatchThreadEnabled() { return dispatchThreadEnabled; } public SceKernelCallbackInfo getCallbackInfo(int uid) { pspBaseCallback callback = callbackMap.get(uid); if (callback != null && callback instanceof SceKernelCallbackInfo) { return (SceKernelCallbackInfo) callback; } return null; } public boolean isCurrentThreadStackAddress(int address) { return currentThread.isStackAddress(address & Memory.addressMask); } /** * Enter the current thread in a wait state. * * @param waitType the wait type (one of SceKernelThreadInfo.PSP_WAIT_xxx) * @param waitId the uid of the wait object * @param waitStateChecker this wait state checked will be called after the * execution of a callback in the waiting thread to check * if the thread has to return to its wait state (i.e. if * the wait condition is still valid). * @param timeoutAddr 0 when the thread is waiting forever * otherwise, a valid address containing a timeout value * in microseconds. * @param callbacks true if callback can be executed while waiting. * false if callback cannot be execute while waiting. */ public void hleKernelThreadEnterWaitState(int waitType, int waitId, IWaitStateChecker waitStateChecker, int timeoutAddr, boolean callbacks) { hleKernelThreadEnterWaitState(currentThread, waitType, waitId, waitStateChecker, timeoutAddr, callbacks); } /** * Enter a thread in a wait state. * * @param thread the thread entering the wait state * @param waitType the wait type (one of SceKernelThreadInfo.PSP_WAIT_xxx) * @param waitId the uid of the wait object * @param waitStateChecker this wait state checked will be called after the * execution of a callback in the waiting thread to check * if the thread has to return to its wait state (i.e. if * the wait condition is still valid). * @param timeoutAddr 0 when the thread is waiting forever * otherwise, a valid address containing a timeout value * in microseconds. * @param callbacks true if callback can be executed while waiting. * false if callback cannot be execute while waiting. */ public void hleKernelThreadEnterWaitState(SceKernelThreadInfo thread, int waitType, int waitId, IWaitStateChecker waitStateChecker, int timeoutAddr, boolean callbacks) { int micros = 0; boolean forever = true; if (Memory.isAddressGood(timeoutAddr)) { micros = Memory.getInstance().read32(timeoutAddr); forever = false; } hleKernelThreadEnterWaitState(thread, waitType, waitId, waitStateChecker, micros, forever, callbacks); } /** * Enter the current thread in a wait state. * The thread will wait without timeout, i.e. forever. * * @param waitType the wait type (one of SceKernelThreadInfo.PSP_WAIT_xxx) * @param waitId the uid of the wait object * @param waitStateChecker this wait state checked will be called after the * execution of a callback in the waiting thread to check * if the thread has to return to its wait state (i.e. if * the wait condition is still valid). * @param callbacks true if callback can be executed while waiting. * false if callback cannot be execute while waiting. */ public void hleKernelThreadEnterWaitState(int waitType, int waitId, IWaitStateChecker waitStateChecker, boolean callbacks) { hleKernelThreadEnterWaitState(currentThread, waitType, waitId, waitStateChecker, 0, true, callbacks); } /** * Enter a thread in a wait state. * * @param thread the thread entering the wait state * @param waitType the wait type (one of SceKernelThreadInfo.PSP_WAIT_xxx) * @param waitId the uid of the wait object * @param waitStateChecker this wait state checked will be called after the * execution of a callback in the waiting thread to check * if the thread has to return to its wait state (i.e. if * the wait condition is still valid). * @param micros a timeout value in microseconds, only relevant * when the "forever" parameter is false. * @param forever true when the thread is waiting without a timeout, * false when the thread is waiting with a timeout * (see the "micros" parameter). * @param callbacks true if callback can be executed while waiting. * false if callback cannot be execute while waiting. */ public void hleKernelThreadEnterWaitState(SceKernelThreadInfo thread, int waitType, int waitId, IWaitStateChecker waitStateChecker, int micros, boolean forever, boolean callbacks) { // wait state thread.waitType = waitType; thread.waitId = waitId; thread.wait.waitStateChecker = waitStateChecker; // Go to wait state hleKernelThreadWait(thread, micros, forever); hleChangeThreadState(thread, PSP_THREAD_WAITING); hleRescheduleCurrentThread(callbacks); } public void hleBlockThread(SceKernelThreadInfo thread, int waitType, int waitId, boolean doCallbacks, IAction onUnblockAction, IWaitStateChecker waitStateChecker) { if (!thread.isWaiting()) { thread.doCallbacks = doCallbacks; thread.wait.onUnblockAction = onUnblockAction; thread.waitType = waitType; thread.waitId = waitId; thread.wait.waitStateChecker = waitStateChecker; thread.wait.forever = true; hleChangeThreadState(thread, thread.isSuspended() ? PSP_THREAD_WAITING_SUSPEND : PSP_THREAD_WAITING); } hleRescheduleCurrentThread(doCallbacks); } public void hleBlockCurrentThread(int waitType, int waitId, boolean doCallbacks, IAction onUnblockAction, IWaitStateChecker waitStateChecker) { if (LOG_CONTEXT_SWITCHING && Modules.log.isDebugEnabled()) { log.debug("-------------------- block SceUID=" + Integer.toHexString(currentThread.uid) + " name:'" + currentThread.name + "' caller:" + getCallingFunction()); } hleBlockThread(currentThread, waitType, waitId, doCallbacks, onUnblockAction, waitStateChecker); } public void hleBlockCurrentThread(int waitType) { hleBlockCurrentThread(waitType, 0, false, null, null); } public void hleBlockCurrentThread(int waitType, IAction onUnblockAction) { hleBlockCurrentThread(waitType, 0, false, onUnblockAction, null); } public SceKernelThreadInfo getThreadById(int uid) { return threadMap.get(uid); } public SceKernelThreadInfo getThreadByName(String name) { for (SceKernelThreadInfo thread : threadMap.values()) { if (name.equals(thread.name)) { return thread; } } return null; } public void hleUnblockThread(int uid) { if (SceUidManager.checkUidPurpose(uid, "ThreadMan-thread", false)) { SceKernelThreadInfo thread = threadMap.get(uid); // Remove PSP_THREAD_WAITING from the thread state, // i.e. change the thread state // - from PSP_THREAD_WAITING_SUSPEND to PSP_THREAD_SUSPEND // - from PSP_THREAD_WAITING to PSP_THREAD_READY hleChangeThreadState(thread, thread.isSuspended() ? PSP_THREAD_SUSPEND : PSP_THREAD_READY); if (LOG_CONTEXT_SWITCHING && thread != null && Modules.log.isDebugEnabled()) { log.debug("-------------------- unblock SceUID=" + Integer.toHexString(thread.uid) + " name:'" + thread.name + "' caller:" + getCallingFunction()); } } } private String getCallingFunction() { String msg = ""; StackTraceElement[] lines = new Exception().getStackTrace(); if (lines.length >= 3) { msg = lines[2].toString(); msg = msg.substring(0, msg.indexOf("(")); //msg = "'" + msg.substring(msg.lastIndexOf(".") + 1, msg.length()) + "'"; String[] parts = msg.split("\\."); msg = "'" + parts[parts.length - 2] + "." + parts[parts.length - 1] + "'"; } else { for (StackTraceElement e : lines) { String line = e.toString(); if (line.startsWith("jpcsp.Allegrex") || line.startsWith("jpcsp.Processor")) { break; } msg += "\n" + line; } } return msg; } public void hleThreadWaitTimeout(SceKernelThreadInfo thread) { if (thread.waitType == PSP_WAIT_NONE) { // The thread is no longer waiting... } else { onWaitTimeout(thread); // Remove PSP_THREAD_WAITING from the thread state, // i.e. change the thread state // - from PSP_THREAD_WAITING_SUSPEND to PSP_THREAD_SUSPEND // - from PSP_THREAD_WAITING to PSP_THREAD_READY hleChangeThreadState(thread, thread.isSuspended() ? PSP_THREAD_SUSPEND : PSP_THREAD_READY); } } /** Call this when a thread's wait timeout has expired. * You can assume the calling function will set thread.status = ready. */ private void onWaitTimeout(SceKernelThreadInfo thread) { switch (thread.waitType) { case PSP_WAIT_THREAD_END: // Return WAIT_TIMEOUT if (thread.wait.ThreadEnd_returnExitStatus) { thread.cpuContext._v0 = ERROR_KERNEL_WAIT_TIMEOUT; } break; case PSP_WAIT_EVENTFLAG: Managers.eventFlags.onThreadWaitTimeout(thread); break; case PSP_WAIT_SEMA: Managers.semas.onThreadWaitTimeout(thread); break; case JPCSP_WAIT_UMD: Modules.sceUmdUserModule.onThreadWaitTimeout(thread); break; case PSP_WAIT_MUTEX: Managers.mutex.onThreadWaitTimeout(thread); break; case PSP_WAIT_LWMUTEX: Managers.lwmutex.onThreadWaitTimeout(thread); break; case PSP_WAIT_MSGPIPE: Managers.msgPipes.onThreadWaitTimeout(thread); break; case PSP_WAIT_MBX: Managers.mbx.onThreadWaitTimeout(thread); break; case PSP_WAIT_FPL: Managers.fpl.onThreadWaitTimeout(thread); break; case PSP_WAIT_VPL: Managers.vpl.onThreadWaitTimeout(thread); break; } } private void hleThreadWaitRelease(SceKernelThreadInfo thread) { // Thread was in a WAITING SUSPEND state? if (thread.isSuspended()) { // Go back to the SUSPEND state hleChangeThreadState(thread, PSP_THREAD_SUSPEND); } else if (thread.waitType != PSP_WAIT_NONE) { onWaitReleased(thread); hleChangeThreadState(thread, PSP_THREAD_READY); } } /** Call this when a thread's wait has been released. */ private void onWaitReleased(SceKernelThreadInfo thread) { switch (thread.waitType) { case PSP_WAIT_THREAD_END: // Return ERROR_WAIT_STATUS_RELEASED if (thread.wait.ThreadEnd_returnExitStatus) { thread.cpuContext._v0 = ERROR_KERNEL_WAIT_STATUS_RELEASED; } break; case PSP_WAIT_EVENTFLAG: Managers.eventFlags.onThreadWaitReleased(thread); break; case PSP_WAIT_SEMA: Managers.semas.onThreadWaitReleased(thread); break; case JPCSP_WAIT_UMD: Modules.sceUmdUserModule.onThreadWaitReleased(thread); break; case PSP_WAIT_MUTEX: Managers.mutex.onThreadWaitReleased(thread); break; case PSP_WAIT_LWMUTEX: Managers.lwmutex.onThreadWaitReleased(thread); break; case PSP_WAIT_MSGPIPE: Managers.msgPipes.onThreadWaitReleased(thread); break; case PSP_WAIT_MBX: Managers.mbx.onThreadWaitReleased(thread); break; case PSP_WAIT_FPL: Managers.fpl.onThreadWaitReleased(thread); break; case PSP_WAIT_VPL: Managers.vpl.onThreadWaitReleased(thread); break; case JPCSP_WAIT_GE_LIST: case JPCSP_WAIT_NET: case JPCSP_WAIT_AUDIO: case JPCSP_WAIT_DISPLAY_VBLANK: case JPCSP_WAIT_CTRL: case JPCSP_WAIT_USB: thread.cpuContext._v0 = ERROR_KERNEL_WAIT_STATUS_RELEASED; break; } } private void deleteAllThreads() { // Copy the list of threads into a new list to avoid ConcurrentModificationException List<SceKernelThreadInfo> threadsToBeDeleted = null; do { try { threadsToBeDeleted = new LinkedList<SceKernelThreadInfo>(threadMap.values()); } catch (ConcurrentModificationException e) { // Exception occurred in LinkedList.addAll() method, retry threadsToBeDeleted = null; } } while (threadsToBeDeleted == null); for (SceKernelThreadInfo thread : threadsToBeDeleted) { hleDeleteThread(thread); } } public void hleDeleteThread(SceKernelThreadInfo thread) { if (!threadMap.containsKey(thread.uid)) { log.debug(String.format("Thread %s already deleted", thread.toString())); return; } if (log.isDebugEnabled()) { log.debug(String.format("really deleting thread:'%s'", thread.name)); } // cleanup thread - free the stack if (log.isDebugEnabled()) { log.debug(String.format("thread:'%s' freeing stack 0x%08X", thread.name, thread.getStackAddr())); } thread.freeStack(); Managers.eventFlags.onThreadDeleted(thread); Managers.semas.onThreadDeleted(thread); Managers.mutex.onThreadDeleted(thread); Managers.lwmutex.onThreadDeleted(thread); Managers.msgPipes.onThreadDeleted(thread); Managers.mbx.onThreadDeleted(thread); Managers.fpl.onThreadDeleted(thread); Managers.vpl.onThreadDeleted(thread); Modules.sceUmdUserModule.onThreadDeleted(thread); RuntimeContext.onThreadDeleted(thread); cancelThreadWait(thread); threadMap.remove(thread.uid); if (thread.unloadModuleAtDeletion) { SceModule module = Managers.modules.getModuleByUID(thread.moduleid); if (module != null) { module.stop(); module.unload(); } } SceUidManager.releaseUid(thread.uid, "ThreadMan-thread"); statistics.addThreadStatistics(thread); } private void removeFromReadyThreads(SceKernelThreadInfo thread) { synchronized (readyThreads) { readyThreads.remove(thread); needThreadReschedule = true; } } private void addToReadyThreads(SceKernelThreadInfo thread, boolean addFirst) { synchronized (readyThreads) { if (addFirst) { readyThreads.addFirst(thread); } else { readyThreads.addLast(thread); } needThreadReschedule = true; } } private void setToBeDeletedThread(SceKernelThreadInfo thread) { thread.doDelete = true; if (thread.isStopped()) { // It's possible for a game to request the same thread to be deleted multiple times. // We only mark for deferred deletion. // Example: // - main thread calls sceKernelDeleteThread on child thread // - child thread calls sceKernelExitDeleteThread if (thread.doDeleteAction == null) { thread.doDeleteAction = new DeleteThreadAction(thread); Scheduler.getInstance().addAction(thread.doDeleteAction); } } } private void triggerThreadEvent(SceKernelThreadInfo thread, SceKernelThreadInfo contextThread, int event) { for (SceKernelThreadEventHandlerInfo handler : threadEventHandlers.values()) { if (handler.appliesFor(getCurrentThread(), thread, event)) { handler.triggerThreadEventHandler(contextThread, event); } } } public void hleKernelChangeThreadPriority(SceKernelThreadInfo thread, int newPriority) { if (thread == null) { return; } thread.currentPriority = newPriority; if (thread.isRunning()) { // The current thread will be moved to the front of the ready queue hleChangeThreadState(thread, PSP_THREAD_READY); } if (thread.isReady()) { // A ready thread is yielding when changing priority and moved to the end of the ready thread list. if (log.isDebugEnabled()) { log.debug("hleKernelChangeThreadPriority rescheduling ready thread"); } removeFromReadyThreads(thread); addToReadyThreads(thread, false); needThreadReschedule = true; hleRescheduleCurrentThread(); } } /** * Change to state of a thread. * This function must be used when changing the state of a thread as * it updates the ThreadMan internal data structures and implements * the PSP behavior on status change. * * @param thread the thread to be updated * @param newStatus the new thread status */ public void hleChangeThreadState(SceKernelThreadInfo thread, int newStatus) { if (thread == null) { return; } if (thread.status == newStatus) { // Thread status not changed, nothing to do return; } if (!dispatchThreadEnabled && thread == currentThread && newStatus != PSP_THREAD_RUNNING) { log.info("DispatchThread disabled, not changing thread state of " + thread + " to " + newStatus); return; } boolean addReadyThreadsFirst = false; // Moving out of the following states... if (thread.status == PSP_THREAD_WAITING && newStatus != PSP_THREAD_WAITING_SUSPEND) { if (thread.wait.waitTimeoutAction != null) { Scheduler.getInstance().removeAction(thread.wait.microTimeTimeout, thread.wait.waitTimeoutAction); thread.wait.waitTimeoutAction = null; } if (thread.wait.onUnblockAction != null) { thread.wait.onUnblockAction.execute(); thread.wait.onUnblockAction = null; } thread.doCallbacks = false; } else if (thread.isStopped()) { if (thread.doDeleteAction != null) { Scheduler.getInstance().removeAction(0, thread.doDeleteAction); thread.doDeleteAction = null; } } else if (thread.isReady()) { removeFromReadyThreads(thread); } else if (thread.isSuspended()) { thread.doCallbacks = false; } else if (thread.isRunning()) { needThreadReschedule = true; // When a running thread has to yield to a thread having a higher // priority, the thread stays in front of the ready threads having // the same priority (no yielding to threads having the same priority). addReadyThreadsFirst = true; } thread.status = newStatus; // Moving to the following states... if (thread.status == PSP_THREAD_WAITING) { if (thread.wait.waitTimeoutAction != null) { Scheduler.getInstance().addAction(thread.wait.microTimeTimeout, thread.wait.waitTimeoutAction); } // debug if (thread.waitType == PSP_WAIT_NONE) { log.warn("changeThreadState thread '" + thread.name + "' => PSP_THREAD_WAITING. waitType should NOT be PSP_WAIT_NONE. caller:" + getCallingFunction()); } } else if (thread.isStopped()) { // HACK auto delete module mgr threads if (thread.name.equals(rootThreadName) || // should probably find the real name and change it thread.name.equals("SceModmgrStart") || thread.name.equals("SceModmgrStop")) { thread.doDelete = true; } if (thread.doDelete) { if (thread.doDeleteAction == null) { thread.doDeleteAction = new DeleteThreadAction(thread); Scheduler.getInstance().addAction(0, thread.doDeleteAction); } } onThreadStopped(thread); } else if (thread.isReady()) { addToReadyThreads(thread, addReadyThreadsFirst); thread.waitType = PSP_WAIT_NONE; thread.wait.waitTimeoutAction = null; thread.wait.waitStateChecker = null; thread.wait.onUnblockAction = null; thread.doCallbacks = false; } else if (thread.isRunning()) { // debug if (thread.waitType != PSP_WAIT_NONE && !isIdleThread(thread)) { log.error(String.format("changeThreadState thread %s => PSP_THREAD_RUNNING. waitType should be PSP_WAIT_NONE. caller: %s", thread, getCallingFunction())); } } } private void cancelThreadWait(SceKernelThreadInfo thread) { // Cancel all waiting actions thread.wait.onUnblockAction = null; thread.wait.waitStateChecker = null; thread.waitType = PSP_WAIT_NONE; if (thread.wait.waitTimeoutAction != null) { Scheduler.getInstance().removeAction(thread.wait.microTimeTimeout, thread.wait.waitTimeoutAction); thread.wait.waitTimeoutAction = null; } } private void terminateThread(SceKernelThreadInfo thread) { hleChangeThreadState(thread, PSP_THREAD_STOPPED); // PSP_THREAD_STOPPED (checked) cancelThreadWait(thread); RuntimeContext.onThreadExit(thread); if (thread == currentThread) { hleRescheduleCurrentThread(); } } private void onThreadStopped(SceKernelThreadInfo stoppedThread) { for (SceKernelThreadInfo thread : threadMap.values()) { // Wakeup threads that are in sceKernelWaitThreadEnd // We're assuming if waitingOnThreadEnd is set then thread.status = waiting if (thread.isWaitingForType(PSP_WAIT_THREAD_END) && thread.wait.ThreadEnd_id == stoppedThread.uid) { hleThreadWaitRelease(thread); if (thread.wait.ThreadEnd_returnExitStatus) { // Return exit status of stopped thread thread.cpuContext._v0 = stoppedThread.exitStatus; } } } } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleKernelExitCallback(Processor processor) { CpuState cpu = processor.cpu; int callbackId = cpu.getRegister(CALLBACKID_REGISTER); Callback callback = callbackManager.remove(callbackId); if (callback != null) { if (log.isTraceEnabled()) { log.trace("End of callback " + callback); } callback.executeExit(cpu); } } /** * Execute the code at the given address. * The code is executed in the context of the currentThread. * Parameters ($a0, $a1, ...) may have been copied to the current CpuState * before calling this method. * This call can return before the completion of the code. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the code (e.g. to evaluate a return value in $v0). * * @param address the address to be called * @param afterAction the action to be executed after the completion of the code * @param returnVoid the code has a void return value, i.e. $v0/$v1 have to be restored */ public void callAddress(int address, IAction afterAction, boolean returnVoid) { callAddress(null, address, afterAction, returnVoid, false, null); } private void callAddress(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid, boolean preserveCpuState, int[] parameters) { if (thread != null) { // Save the wait state of the thread to restore it after the call afterAction = new AfterCallAction(thread, afterAction); // Terminate the thread wait state thread.waitType = PSP_WAIT_NONE; thread.wait.onUnblockAction = null; hleChangeThreadState(thread, PSP_THREAD_READY); } int callbackId = callbackManager.getNewCallbackId(); Callback callback = new Callback(callbackId, address, parameters, afterAction, returnVoid, preserveCpuState); callbackManager.addCallback(callback); boolean callbackCalled = false; if (thread == null || thread == currentThread) { if (RuntimeContext.canExecuteCallback(thread)) { thread = currentThread; if (thread.waitType != PSP_WAIT_NONE) { afterAction = new AfterCallAction(thread, afterAction); callback.setAfterAction(afterAction); // Terminate the thread wait state thread.waitType = PSP_WAIT_NONE; } hleChangeThreadState(thread, PSP_THREAD_RUNNING); callback.execute(thread); callbackCalled = true; } } if (!callbackCalled) { if (log.isDebugEnabled()) { log.debug(String.format("Pushing pending callback '%s' for thread '%s'", callback, thread)); } thread.pendingCallbacks.add(callback); } } /** * Trigger a call to a callback in the context of a thread. * This call can return before the completion of the callback. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the callback (e.g. to evaluate a return value in $v0). * * @param thread the callback has to be executed by this thread (null means the currentThread) * @param address address of the callback * @param afterAction action to be executed after the completion of the callback * @param returnVoid the callback has a void return value, i.e. $v0/$v1 have to be restored */ public void executeCallback(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid) { if (log.isDebugEnabled()) { log.debug(String.format("Execute callback 0x%08X, afterAction=%s, returnVoid=%b", address, afterAction, returnVoid)); } callAddress(thread, address, afterAction, returnVoid, false, null); } /** * Trigger a call to a callback in the context of a thread. * This call can return before the completion of the callback. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the callback (e.g. to evaluate a return value in cpu.gpr[2]). * * @param thread the callback has to be executed by this thread (null means the currentThread) * @param address address of the callback * @param afterAction action to be executed after the completion of the callback * @param returnVoid the callback has a void return value, i.e. $v0/$v1 have to be restored * @param registerA0 first parameter of the callback ($a0) */ public void executeCallback(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid, int registerA0) { if (log.isDebugEnabled()) { log.debug(String.format("Execute callback 0x%08X($a0=0x%08X), afterAction=%s, returnVoid=%b", address, registerA0, afterAction, returnVoid)); } callAddress(thread, address, afterAction, returnVoid, false, new int[]{registerA0}); } /** * Trigger a call to a callback in the context of a thread. * This call can return before the completion of the callback. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the callback (e.g. to evaluate a return value in cpu.gpr[2]). * * @param thread the callback has to be executed by this thread (null means the currentThread) * @param address address of the callback * @param afterAction action to be executed after the completion of the callback * @param returnVoid the callback has a void return value, i.e. $v0/$v1 have to be restored * @param registerA0 first parameter of the callback ($a0) * @param registerA1 second parameter of the callback ($a1) */ public void executeCallback(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid, int registerA0, int registerA1) { if (log.isDebugEnabled()) { log.debug(String.format("Execute callback 0x%08X($a0=0x%08X, $a1=0x%08X), afterAction=%s, returnVoid=%b", address, registerA0, registerA1, afterAction, returnVoid)); } callAddress(thread, address, afterAction, returnVoid, false, new int[]{registerA0, registerA1}); } /** * Trigger a call to a callback in the context of a thread. * This call can return before the completion of the callback. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the callback (e.g. to evaluate a return value in cpu.gpr[2]). * * @param thread the callback has to be executed by this thread (null means the currentThread) * @param address address of the callback * @param afterAction action to be executed after the completion of the callback * @param returnVoid the callback has a void return value, i.e. $v0/$v1 have to be restored * @param registerA0 first parameter of the callback ($a0) * @param registerA1 second parameter of the callback ($a1) * @param registerA2 third parameter of the callback ($a2) */ public void executeCallback(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid, int registerA0, int registerA1, int registerA2) { if (log.isDebugEnabled()) { log.debug(String.format("Execute callback 0x%08X($a0=0x%08X, $a1=0x%08X, $a2=0x%08X), afterAction=%s, returnVoid=%b", address, registerA0, registerA1, registerA2, afterAction, returnVoid)); } callAddress(thread, address, afterAction, returnVoid, false, new int[]{registerA0, registerA1, registerA2}); } /** * Trigger a call to a callback in the context of a thread. * This call can return before the completion of the callback. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the callback (e.g. to evaluate a return value in cpu.gpr[2]). * * @param thread the callback has to be executed by this thread (null means the currentThread) * @param address address of the callback * @param afterAction action to be executed after the completion of the callback * @param returnVoid the callback has a void return value, i.e. $v0/$v1 have to be restored * @param preserverCpuState preserve the complete CpuState while executing the callback. * All the registers will be restored after the callback execution. * @param registerA0 first parameter of the callback ($a0) * @param registerA1 second parameter of the callback ($a1) * @param registerA2 third parameter of the callback ($a2) */ public void executeCallback(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid, boolean preserverCpuState, int registerA0, int registerA1, int registerA2) { if (log.isDebugEnabled()) { log.debug(String.format("Execute callback 0x%08X($a0=0x%08X, $a1=0x%08X, $a2=0x%08X), afterAction=%s, returnVoid=%b, preserverCpuState=%b", address, registerA0, registerA1, registerA2, afterAction, returnVoid, preserverCpuState)); } callAddress(thread, address, afterAction, returnVoid, preserverCpuState, new int[]{registerA0, registerA1, registerA2}); } /** * Trigger a call to a callback in the context of a thread. * This call can return before the completion of the callback. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the callback (e.g. to evaluate a return value in cpu.gpr[2]). * * @param thread the callback has to be executed by this thread (null means the currentThread) * @param address address of the callback * @param afterAction action to be executed after the completion of the callback * @param returnVoid the callback has a void return value, i.e. $v0/$v1 have to be restored * @param registerA0 first parameter of the callback ($a0) * @param registerA1 second parameter of the callback ($a1) * @param registerA2 third parameter of the callback ($a2) * @param registerA3 fourth parameter of the callback ($a3) */ public void executeCallback(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid, int registerA0, int registerA1, int registerA2, int registerA3) { if (log.isDebugEnabled()) { log.debug(String.format("Execute callback 0x%08X($a0=0x%08X, $a1=0x%08X, $a2=0x%08X, $a3=0x%08X), afterAction=%s, returnVoid=%b", address, registerA0, registerA1, registerA2, registerA3, afterAction, returnVoid)); } callAddress(thread, address, afterAction, returnVoid, false, new int[]{registerA0, registerA1, registerA2, registerA3}); } /** * Trigger a call to a callback in the context of a thread. * This call can return before the completion of the callback. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the callback (e.g. to evaluate a return value in cpu.gpr[2]). * * @param thread the callback has to be executed by this thread (null means the currentThread) * @param address address of the callback * @param afterAction action to be executed after the completion of the callback * @param returnVoid the callback has a void return value, i.e. $v0/$v1 have to be restored * @param registerA0 first parameter of the callback ($a0) * @param registerA1 second parameter of the callback ($a1) * @param registerA2 third parameter of the callback ($a2) * @param registerT0 fifth parameter of the callback ($t0) */ public void executeCallback(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid, int registerA0, int registerA1, int registerA2, int registerA3, int registerT0) { if (log.isDebugEnabled()) { log.debug(String.format("Execute callback 0x%08X($a0=0x%08X, $a1=0x%08X, $a2=0x%08X, $a3=0x%08X, $t0=0x%08X), afterAction=%s, returnVoid=%b", address, registerA0, registerA1, registerA2, registerA3, registerT0, afterAction, returnVoid)); } callAddress(thread, address, afterAction, returnVoid, false, new int[]{registerA0, registerA1, registerA2, registerA3, registerT0}); } /** * Trigger a call to a callback in the context of a thread. * This call can return before the completion of the callback. Use the * "afterAction" parameter to trigger some actions that need to be executed * after the callback (e.g. to evaluate a return value in cpu.gpr[2]). * * @param thread the callback has to be executed by this thread (null means the currentThread) * @param address address of the callback * @param afterAction action to be executed after the completion of the callback * @param returnVoid the callback has a void return value, i.e. $v0/$v1 have to be restored * @param registers parameters of the callback */ public void executeCallback(SceKernelThreadInfo thread, int address, IAction afterAction, boolean returnVoid, int[] registers) { if (log.isDebugEnabled()) { log.debug(String.format("Execute callback 0x%08X, afterAction=%s, returnVoid=%b", address, afterAction, returnVoid)); } callAddress(thread, address, afterAction, returnVoid, false, registers); } @HLEFunction(nid = HLESyscallNid, version = 150) public void hleKernelExitThread(int exitStatus) { if (log.isDebugEnabled()) { log.debug(String.format("Thread exit detected SceUID=%x name='%s' return:0x%08X", currentThread.uid, currentThread.name, exitStatus)); } sceKernelExitThread(exitStatus); } public int hleKernelExitDeleteThread(int exitStatus) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelExitDeleteThread SceUID=%x name='%s' return:0x%08X", currentThread.uid, currentThread.name, exitStatus)); } return sceKernelExitDeleteThread(exitStatus); } public int hleKernelExitDeleteThread() { int exitStatus = Emulator.getProcessor().cpu._v0; return hleKernelExitDeleteThread(exitStatus); } /** * Check the validity of the thread UID. * Do not allow uid=0. * * @param uid thread UID to be checked * @return valid thread UID */ public int checkThreadID(int uid) { if (uid == 0) { log.warn("checkThreadID illegal thread uid=0"); throw new SceKernelErrorException(ERROR_KERNEL_ILLEGAL_THREAD); } return checkThreadIDAllow0(uid); } /** * Check the validity of the thread UID. * Allow uid=0. * * @param uid thread UID to be checked * @return valid thread UID (0 has been replaced by the UID of the currentThread) */ public int checkThreadIDAllow0(int uid) { if (uid == 0) { uid = currentThread.uid; } if (!threadMap.containsKey(uid)) { log.warn(String.format("checkThreadID not found thread 0x%08X", uid)); throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_THREAD); } if (!SceUidManager.checkUidPurpose(uid, "ThreadMan-thread", true)) { throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_THREAD); } return uid; } /** * Check the validity of the thread UID. * No special check on uid=0, i.e. return ERROR_KERNEL_NOT_FOUND_THREAD for uid=0. * * @param uid thread UID to be checked * @return valid thread UID */ public int checkThreadIDNoCheck0(int uid) { if (uid == 0) { log.warn(String.format("checkThreadID not found thread 0x%08X", uid)); throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_THREAD); } return checkThreadIDAllow0(uid); } /** * Check the validity of the VTimer UID. * * @param uid VTimer UID to be checked * @return valid VTimer UID */ public int checkVTimerID(int uid) { if (!vtimers.containsKey(uid)) { throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_VTIMER); } return uid; } public int checkSemaID(int uid) { return Managers.semas.checkSemaID(uid); } public int checkEventFlagID(int uid) { return Managers.eventFlags.checkEventFlagID(uid); } public int checkMbxID(int uid) { return Managers.mbx.checkMbxID(uid); } public int checkMsgPipeID(int uid) { return Managers.msgPipes.checkMsgPipeID(uid); } public int checkVplID(int uid) { return Managers.vpl.checkVplID(uid); } public int checkFplID(int uid) { return Managers.fpl.checkFplID(uid); } public int checkAlarmID(int uid) { if (!alarms.containsKey(uid)) { log.warn(String.format("checkAlarmID unknown uid=0x%x", uid)); throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_ALARM); } return uid; } public int checkCallbackID(int uid) { if (!callbackMap.containsKey(uid)) { throw new SceKernelErrorException(SceKernelErrors.ERROR_KERNEL_NOT_FOUND_CALLBACK); } return uid; } public int checkPartitionID(int id) { if (id < 1 || id > 9 || id == 7) { throw new SceKernelErrorException(SceKernelErrors.ERROR_KERNEL_ILLEGAL_ARGUMENT); } if (id == 6) { // Partition ID 6 is accepted by the PSP... id = SysMemUserForUser.USER_PARTITION_ID; } if (id != SysMemUserForUser.USER_PARTITION_ID && id != SysMemUserForUser.VSHELL_PARTITION_ID) { // Accept KERNEL_PARTITION_ID for threads running in kernel mode. if (id != SysMemUserForUser.KERNEL_PARTITION_ID || !currentThread.isKernelMode()) { throw new SceKernelErrorException(SceKernelErrors.ERROR_KERNEL_ILLEGAL_PERMISSION); } } return id; } public SceKernelThreadInfo hleKernelCreateThread(String name, int entry_addr, int initPriority, int stackSize, int attr, int option_addr, int mpidStack) { if (option_addr != 0) { SceKernelThreadOptParam sceKernelThreadOptParam = new SceKernelThreadOptParam(); sceKernelThreadOptParam.read(Emulator.getMemory(), option_addr); if (sceKernelThreadOptParam.sizeof() >= 8) { mpidStack = sceKernelThreadOptParam.stackMpid; } } SceKernelThreadInfo thread = new SceKernelThreadInfo(name, entry_addr, initPriority, stackSize, attr, mpidStack); threadMap.put(thread.uid, thread); // inherit module id if (currentThread != null) { thread.moduleid = currentThread.moduleid; } if (log.isDebugEnabled()) { log.debug(String.format("hleKernelCreateThread SceUID=0x%X, name='%s', PC=0x%08X, attr=0x%X, priority=0x%X, stackSize=0x%X", thread.uid, thread.name, thread.cpuContext.pc, attr, initPriority, stackSize)); } return thread; } public void hleKernelStartThread(SceKernelThreadInfo thread, int userDataLength, int userDataAddr, int gp) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelStartThread SceUID=0x%X, name='%s', dataLen=0x%X, data=0x%08X, gp=0x%08X", thread.uid, thread.name, userDataLength, userDataAddr, gp)); } // Reset all thread parameters: a thread can be restarted when it has exited. thread.reset(); // Setup args by copying them onto the stack hleKernelSetThreadArguments(thread, userDataAddr, userDataLength); // Set thread $gp thread.cpuContext._gp = gp; // Update the exit status. thread.exitStatus = ERROR_KERNEL_THREAD_IS_NOT_DORMANT; // switch in the target thread if it's higher priority hleChangeThreadState(thread, PSP_THREAD_READY); // Execute the event in the context of the starting thread triggerThreadEvent(thread, thread, THREAD_EVENT_START); // sceKernelStartThread is always resuming the thread dispatching (tested on PSP using taskScheduler.prx). // Assuming here that the other syscalls starting a thread // (sceKernelStartModule, sceKernelStopModule, sceNetAdhocctlInit...) // have the same behavior. hleKernelResumeDispatchThread(); RuntimeContext.onThreadStart(thread); if (currentThread == null || thread.currentPriority < currentThread.currentPriority) { if (log.isDebugEnabled()) { log.debug("hleKernelStartThread switching in thread immediately"); } hleRescheduleCurrentThread(); } } public int hleKernelSleepThread(boolean doCallbacks) { if (currentThread.wakeupCount > 0) { // sceKernelWakeupThread() has been called before, do not sleep currentThread.wakeupCount--; } else { // Go to wait state and wait forever (another thread will call sceKernelWakeupThread) hleKernelThreadEnterWaitState(PSP_WAIT_SLEEP, 0, sleepThreadWaitStateChecker, doCallbacks); } return 0; } public void hleKernelWakeupThread(SceKernelThreadInfo thread) { if (!thread.isWaiting() || thread.waitType != PSP_WAIT_SLEEP) { thread.wakeupCount++; if (log.isDebugEnabled()) { log.debug("sceKernelWakeupThread SceUID=" + Integer.toHexString(thread.uid) + " name:'" + thread.name + "' not sleeping/waiting (status=0x" + Integer.toHexString(thread.status) + "), incrementing wakeupCount to " + thread.wakeupCount); } } else { if (log.isDebugEnabled()) { log.debug("sceKernelWakeupThread SceUID=" + Integer.toHexString(thread.uid) + " name:'" + thread.name + "'"); } hleThreadWaitRelease(thread); // Check if we have to switch in the target thread // e.g. if if has a higher priority hleRescheduleCurrentThread(); } } public int hleKernelWaitThreadEnd(SceKernelThreadInfo waitingThread, int uid, TPointer32 timeoutAddr, boolean callbacks, boolean returnExitStatus) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelWaitThreadEnd SceUID=0x%X, callbacks=%b", uid, callbacks)); } SceKernelThreadInfo thread = threadMap.get(uid); if (thread == null) { log.warn(String.format("hleKernelWaitThreadEnd unknown thread 0x%X", uid)); return ERROR_KERNEL_NOT_FOUND_THREAD; } int result = 0; if (thread.isStopped()) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelWaitThreadEnd %s thread already stopped, not waiting, exitStatus=0x%08X", thread, thread.exitStatus)); } if (returnExitStatus) { // Return the thread exit status result = thread.exitStatus; } hleRescheduleCurrentThread(); } else { // Wait on a specific thread end waitingThread.wait.ThreadEnd_id = uid; waitingThread.wait.ThreadEnd_returnExitStatus = returnExitStatus; hleKernelThreadEnterWaitState(waitingThread, PSP_WAIT_THREAD_END, uid, waitThreadEndWaitStateChecker, timeoutAddr.getAddress(), callbacks); } return result; } /** * Set the wait timeout for a thread. The state of the thread is not changed. * * @param thread the thread * @param wait the same as thread.wait * @param micros the timeout in microseconds (this is an unsigned value: SceUInt32) * @param forever true if the thread has to wait forever (micros in then ignored) */ public void hleKernelThreadWait(SceKernelThreadInfo thread, int micros, boolean forever) { thread.wait.forever = forever; thread.wait.micros = micros; // for debugging if (forever) { thread.wait.microTimeTimeout = 0; thread.wait.waitTimeoutAction = null; } else { if (micros < THREAD_DELAY_MINIMUM_MICROS && !isIdleThread(thread)) { micros = THREAD_DELAY_MINIMUM_MICROS * 2; } long longMicros = ((long) micros) & 0xFFFFFFFFL; thread.wait.microTimeTimeout = Emulator.getClock().microTime() + longMicros; thread.wait.waitTimeoutAction = new TimeoutThreadAction(thread); thread.wait.waitStateChecker = timeoutThreadWaitStateChecker; } if (LOG_CONTEXT_SWITCHING && log.isDebugEnabled() && !isIdleThread(thread)) { log.debug("-------------------- hleKernelThreadWait micros=" + micros + " forever:" + forever + " thread:'" + thread.name + "' caller:" + getCallingFunction()); } } public void hleKernelDelayThread(int micros, boolean doCallbacks) { hleKernelDelayThread(currentThread.uid, micros, doCallbacks); } public void hleKernelDelayThread(int uid, int micros, boolean doCallbacks) { if (micros < THREAD_DELAY_MINIMUM_MICROS && !isIdleThread(uid)) { micros = THREAD_DELAY_MINIMUM_MICROS; } if (log.isDebugEnabled()) { log.debug(String.format("hleKernelDelayThread micros=%d, callbacks=%b", micros, doCallbacks)); } SceKernelThreadInfo thread = getThreadById(uid); if (thread != null) { hleKernelThreadEnterWaitState(thread, PSP_WAIT_DELAY, 0, null, micros, false, doCallbacks); } } public SceKernelCallbackInfo hleKernelCreateCallback(String name, int func_addr, int user_arg_addr) { SceKernelCallbackInfo callback = new SceKernelCallbackInfo(name, currentThread.uid, func_addr, user_arg_addr); if (log.isDebugEnabled()) { log.debug(String.format("hleKernelCreateCallback %s", callback)); } callbackMap.put(callback.getUid(), callback); return callback; } public pspBaseCallback hleKernelCreateCallback(int callbackFunction, int numberArguments) { pspBaseCallback callback = new pspBaseCallback(callbackFunction, numberArguments); if (log.isDebugEnabled()) { log.debug(String.format("hleKernelCreateCallback %s", callback)); } callbackMap.put(callback.getUid(), callback); return callback; } /** @return true if successful. */ public void hleKernelDeleteCallback(int uid) { SceKernelCallbackInfo callback = getCallbackInfo(uid); if (callback != null) { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelDeleteCallback %s", callback)); } callbackMap.remove(uid); SceKernelThreadInfo thread = getThreadById(callback.getThreadId()); if (thread != null) { thread.deleteCallback(callback); } } else { log.warn(String.format("hleKernelDeleteCallback not a callback uid 0x%X", uid)); } } protected int getThreadCurrentStackSize(Processor processor) { int size = processor.cpu._sp - currentThread.getStackAddr(); if (size < 0) { size = 0; } return size; } private boolean userCurrentThreadTryingToSwitchToKernelMode(int newAttr) { return currentThread.isUserMode() && !SceKernelThreadInfo.isUserMode(newAttr); } private boolean userThreadCalledKernelCurrentThread(SceKernelThreadInfo thread) { return !isIdleThread(thread) && (!thread.isKernelMode() || currentThread.isKernelMode()); } private int getDispatchThreadState() { return dispatchThreadEnabled ? SCE_KERNEL_DISPATCHTHREAD_STATE_ENABLED : SCE_KERNEL_DISPATCHTHREAD_STATE_DISABLED; } private void hleKernelResumeDispatchThread() { if (!dispatchThreadEnabled) { dispatchThreadEnabled = true; hleRescheduleCurrentThread(); } } public boolean hleKernelRegisterCallback(int callbackType, pspBaseCallback callback) { return hleKernelRegisterCallback(getCurrentThread(), callbackType, callback); } public boolean hleKernelRegisterCallback(SceKernelThreadInfo thread, int callbackType, pspBaseCallback callback) { RegisteredCallbacks registeredCallbacks = thread.getRegisteredCallbacks(callbackType); return registeredCallbacks.addCallback(callback); } /** Registers a callback on the thread that created the callback. * @return true on success (the cbid was a valid callback uid) */ public boolean hleKernelRegisterCallback(int callbackType, int cbid) { SceKernelCallbackInfo callback = getCallbackInfo(cbid); if (callback == null) { log.warn("hleKernelRegisterCallback(type=" + callbackType + ") unknown uid " + Integer.toHexString(cbid)); return false; } SceKernelThreadInfo thread = getThreadById(callback.getThreadId()); if (thread == null) { log.warn("hleKernelRegisterCallback(type=" + callbackType + ") unknown thread uid " + Integer.toHexString(callback.getThreadId())); return false; } RegisteredCallbacks registeredCallbacks = thread.getRegisteredCallbacks(callbackType); if (!registeredCallbacks.addCallback(callback)) { return false; } return true; } /** Unregisters a callback by type and cbid. May not be on the current thread. * @param callbackType See SceKernelThreadInfo. * @param cbid The UID of the callback to unregister. * @return true if the callback has been removed, or false if it couldn't be found. **/ public boolean hleKernelUnRegisterCallback(int callbackType, int cbid) { boolean found = false; for (SceKernelThreadInfo thread : threadMap.values()) { RegisteredCallbacks registeredCallbacks = thread.getRegisteredCallbacks(callbackType); pspBaseCallback callback = registeredCallbacks.getCallbackInfoByUid(cbid); if (callback != null) { found = true; // Warn if we are removing a pending callback, this a callback // that has been pushed but not yet executed. if (registeredCallbacks.isCallbackReady(callback)) { log.warn("hleKernelUnRegisterCallback(type=" + callbackType + ") removing pending callback"); } registeredCallbacks.removeCallback(callback); break; } } if (!found) { log.warn("hleKernelUnRegisterCallback(type=" + callbackType + ") cbid=" + Integer.toHexString(cbid) + " no matching callbacks found"); } return found; } public void hleKernelNotifyCallback(int callbackType, pspBaseCallback callback) { hleKernelNotifyCallback(callbackType, callback, getCurrentThread()); } public void hleKernelNotifyCallback(int callbackType, pspBaseCallback callback, SceKernelThreadInfo thread) { RegisteredCallbacks registeredCallbacks = thread.getRegisteredCallbacks(callbackType); if (registeredCallbacks.hasCallback(callback)) { registeredCallbacks.setCallbackReady(callback); } } /** push callback to all threads */ public void hleKernelNotifyCallback(int callbackType, int notifyArg) { hleKernelNotifyCallback(callbackType, -1, notifyArg); } private void notifyCallback(SceKernelThreadInfo thread, SceKernelCallbackInfo callback, int callbackType, int notifyArg) { if (callback.getNotifyCount() > 0) { log.warn("hleKernelNotifyCallback(type=" + callbackType + ") thread:'" + thread.name + "' overwriting previous notifyArg 0x" + Integer.toHexString(callback.getNotifyArg()) + " -> 0x" + Integer.toHexString(notifyArg) + ", newCount=" + (callback.getNotifyCount() + 1)); } callback.setNotifyArg(notifyArg); thread.getRegisteredCallbacks(callbackType).setCallbackReady(callback); } /** @param cbid If cbid is -1, then push callback to all threads * if cbid is not -1 then only trigger that specific cbid provided it is * also of type callbackType. */ public void hleKernelNotifyCallback(int callbackType, int cbid, int notifyArg) { boolean pushed = false; for (SceKernelThreadInfo thread : threadMap.values()) { RegisteredCallbacks registeredCallbacks = thread.getRegisteredCallbacks(callbackType); if (!registeredCallbacks.hasCallbacks()) { continue; } if (cbid != -1) { pspBaseCallback callback = registeredCallbacks.getCallbackInfoByUid(cbid); if (callback == null || !(callback instanceof SceKernelCallbackInfo)) { continue; } notifyCallback(thread, (SceKernelCallbackInfo) callback, callbackType, notifyArg); } else { int numberOfCallbacks = registeredCallbacks.getNumberOfCallbacks(); for (int i = 0; i < numberOfCallbacks; i++) { pspBaseCallback callback = registeredCallbacks.getCallbackByIndex(i); if (callback instanceof SceKernelCallbackInfo) { notifyCallback(thread, (SceKernelCallbackInfo) callback, callbackType, notifyArg); } } } pushed = true; } if (pushed) { // Enter callbacks immediately, // except those registered to the current thread. The app must explictly // call sceKernelCheckCallback or a waitCB function to do that. if (log.isDebugEnabled()) { log.debug("hleKernelNotifyCallback(type=" + callbackType + ") calling checkCallbacks"); } checkCallbacks(); } else { if (log.isDebugEnabled()) { log.debug(String.format("hleKernelNotifyCallback(type=%d) no registered callbacks to push", callbackType)); } } } /** runs callbacks. Check the thread doCallbacks flag. * @return true if we switched into a callback. */ private boolean checkThreadCallbacks(SceKernelThreadInfo thread) { boolean handled = false; if (thread == null || !thread.doCallbacks) { return handled; } for (int callbackType = 0; callbackType < SceKernelThreadInfo.THREAD_CALLBACK_SIZE; callbackType++) { RegisteredCallbacks registeredCallbacks = thread.getRegisteredCallbacks(callbackType); pspBaseCallback callback = registeredCallbacks.getNextReadyCallback(); if (callback != null) { if (log.isDebugEnabled()) { log.debug(String.format("Entering callback type %d %s for thread %s (current thread is %s)", callbackType, callback.toString(), thread.toString(), currentThread.toString())); } CheckCallbackReturnValue checkCallbackReturnValue = new CheckCallbackReturnValue(thread, callback.getUid()); callback.call(thread, checkCallbackReturnValue); handled = true; break; } } return handled; } public void cancelAlarm(SceKernelAlarmInfo sceKernelAlarmInfo) { Scheduler.getInstance().removeAction(sceKernelAlarmInfo.schedule, sceKernelAlarmInfo.alarmInterruptAction); sceKernelAlarmInfo.schedule = 0; sceKernelAlarmInfo.delete(); alarms.remove(sceKernelAlarmInfo.uid); } public void rescheduleAlarm(SceKernelAlarmInfo sceKernelAlarmInfo, int delay) { if (delay < 0) { delay = 100; } sceKernelAlarmInfo.schedule += delay; scheduleAlarm(sceKernelAlarmInfo); if (log.isDebugEnabled()) { log.debug(String.format("New Schedule for Alarm uid=%x: %d", sceKernelAlarmInfo.uid, sceKernelAlarmInfo.schedule)); } } private void scheduleAlarm(SceKernelAlarmInfo sceKernelAlarmInfo) { Scheduler.getInstance().addAction(sceKernelAlarmInfo.schedule, sceKernelAlarmInfo.alarmInterruptAction); } protected int hleKernelSetAlarm(long delayUsec, TPointer handlerAddress, int handlerArgument) { long now = Scheduler.getNow(); long schedule = now + delayUsec; SceKernelAlarmInfo sceKernelAlarmInfo = new SceKernelAlarmInfo(schedule, handlerAddress.getAddress(), handlerArgument); alarms.put(sceKernelAlarmInfo.uid, sceKernelAlarmInfo); scheduleAlarm(sceKernelAlarmInfo); return sceKernelAlarmInfo.uid; } protected long getSystemTime() { return SystemTimeManager.getSystemTime(); } protected long getVTimerScheduleForScheduler(SceKernelVTimerInfo sceKernelVTimerInfo) { return sceKernelVTimerInfo.base + sceKernelVTimerInfo.schedule; } protected long setVTimer(SceKernelVTimerInfo sceKernelVTimerInfo, long time) { long current = sceKernelVTimerInfo.getCurrentTime(); sceKernelVTimerInfo.base = sceKernelVTimerInfo.base + sceKernelVTimerInfo.getCurrentTime() - time; sceKernelVTimerInfo.current = 0; return current; } protected void startVTimer(SceKernelVTimerInfo sceKernelVTimerInfo) { sceKernelVTimerInfo.active = SceKernelVTimerInfo.ACTIVE_RUNNING; sceKernelVTimerInfo.base = getSystemTime(); if (sceKernelVTimerInfo.handlerAddress != 0) { scheduleVTimer(sceKernelVTimerInfo, sceKernelVTimerInfo.schedule); } } protected void stopVTimer(SceKernelVTimerInfo sceKernelVTimerInfo) { Scheduler.getInstance().removeAction(getVTimerScheduleForScheduler(sceKernelVTimerInfo), sceKernelVTimerInfo.vtimerInterruptAction); // Sum the elapsed time (multiple Start/Stop sequences are added) sceKernelVTimerInfo.current = sceKernelVTimerInfo.getCurrentTime(); sceKernelVTimerInfo.active = SceKernelVTimerInfo.ACTIVE_STOPPED; sceKernelVTimerInfo.base = 0; } protected void scheduleVTimer(SceKernelVTimerInfo sceKernelVTimerInfo, long schedule) { // Remove any previous schedule Scheduler.getInstance().removeAction(getVTimerScheduleForScheduler(sceKernelVTimerInfo), sceKernelVTimerInfo.vtimerInterruptAction); sceKernelVTimerInfo.schedule = schedule; if (sceKernelVTimerInfo.active == SceKernelVTimerInfo.ACTIVE_RUNNING && sceKernelVTimerInfo.handlerAddress != 0) { Scheduler scheduler = Scheduler.getInstance(); long schedulerSchedule = getVTimerScheduleForScheduler(sceKernelVTimerInfo); scheduler.addAction(schedulerSchedule, sceKernelVTimerInfo.vtimerInterruptAction); if (log.isDebugEnabled()) { log.debug(String.format("Scheduling VTimer %s at %d(now=%d)", sceKernelVTimerInfo, schedulerSchedule, Scheduler.getNow())); } } } public void cancelVTimer(SceKernelVTimerInfo sceKernelVTimerInfo) { Scheduler.getInstance().removeAction(getVTimerScheduleForScheduler(sceKernelVTimerInfo), sceKernelVTimerInfo.vtimerInterruptAction); sceKernelVTimerInfo.schedule = 0; sceKernelVTimerInfo.handlerAddress = 0; sceKernelVTimerInfo.handlerArgument = 0; } public void rescheduleVTimer(SceKernelVTimerInfo sceKernelVTimerInfo, int delay) { if (delay < 0) { delay = 100; } long schedule = sceKernelVTimerInfo.schedule + delay; scheduleVTimer(sceKernelVTimerInfo, schedule); if (log.isDebugEnabled()) { log.debug(String.format("New Schedule for VTimer uid=%x: %d", sceKernelVTimerInfo.uid, sceKernelVTimerInfo.schedule)); } } /** * Iterates waiting threads, making sure doCallbacks is set before * checking for pending callbacks. * Handles sceKernelCheckCallback when doCallbacks is set on currentThread. * Handles redirects to yieldCB (from fake waitCB) on the thread that called waitCB. * * We currently call checkCallbacks() at the end of each waitCB function * since this has less overhead than checking on every step. * * Some trickery is used in yieldCurrentThreadCB(). By the time we get * inside the checkCallbacks() function the thread that called yieldCB is * no longer the current thread. Also the thread that called yieldCB is * not in the wait state (it's in the ready state). so what we do is check * every thread, not just the waiting threads for the doCallbacks flag. * Also the waitingThreads list only contains waiting threads that have a * finite wait period, so we have to iterate on all threads anyway. * * It is probably unsafe to call contextSwitch() when insideCallback is true. * insideCallback may become true after a call to checkCallbacks(). */ public void checkCallbacks() { if (log.isTraceEnabled()) { log.trace("checkCallbacks current thread is '" + currentThread.name + "' doCallbacks:" + currentThread.doCallbacks + " caller:" + getCallingFunction()); } boolean handled; SceKernelThreadInfo checkCurrentThread = currentThread; do { handled = false; for (SceKernelThreadInfo thread : threadMap.values()) { if (thread.doCallbacks && checkThreadCallbacks(thread)) { handled = true; break; } } // Continue until there is no more callback to be executed or // we have switched to another thread. } while (handled && checkCurrentThread == currentThread); } public int checkStackSize(int size) { if (size < 0x200) { throw new SceKernelErrorException(SceKernelErrors.ERROR_KERNEL_ILLEGAL_STACK_SIZE); } // Size is rounded up to a multiple of 256 return (size + 0xFF) & ~0xFF; } public SceKernelTls getKernelTls(int uid) { return tlsMap.get(uid); } @HLEUnimplemented @HLEFunction(nid = 0x6E9EA350, version = 150) public int _sceKernelReturnFromCallback() { return 0; } @HLEFunction(nid = 0x0C106E53, version = 150, checkInsideInterrupt = true) public int sceKernelRegisterThreadEventHandler(@StringInfo(maxLength = 32) String name, int thid, int mask, TPointer handlerFunc, int commonAddr) { switch (thid) { case SceKernelThreadEventHandlerInfo.THREAD_EVENT_ID_CURRENT: // Only allowed for THREAD_EVENT_EXIT (doesn't make sense for the other events). if (mask != SceKernelThreadEventHandlerInfo.THREAD_EVENT_EXIT) { return SceKernelErrors.ERROR_KERNEL_OUT_OF_RANGE; } thid = getCurrentThreadID(); break; case SceKernelThreadEventHandlerInfo.THREAD_EVENT_ID_USER: // Always allowed break; case SceKernelThreadEventHandlerInfo.THREAD_EVENT_ID_KERN: case SceKernelThreadEventHandlerInfo.THREAD_EVENT_ID_ALL: // Only allowed in kernel mode if (!isKernelMode()) { return ERROR_KERNEL_NOT_FOUND_THREAD; } break; default: SceKernelThreadInfo thread = getThreadById(thid); if (thread == null) { return ERROR_KERNEL_NOT_FOUND_THREAD; } break; } SceKernelThreadEventHandlerInfo handler = new SceKernelThreadEventHandlerInfo(name, thid, mask, handlerFunc.getAddress(), commonAddr); threadEventHandlers.put(handler.uid, handler); return handler.uid; } @HLEFunction(nid = 0x72F3C145, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelReleaseThreadEventHandler(int uid) { if (!threadEventHandlers.containsKey(uid)) { return ERROR_KERNEL_NOT_FOUND_THREAD_EVENT_HANDLER; } SceKernelThreadEventHandlerInfo handler = threadEventHandlers.remove(uid); handler.release(); return 0; } @HLEFunction(nid = 0x369EEB6B, version = 150) public int sceKernelReferThreadEventHandlerStatus(int uid, TPointer statusPointer) { if (!threadEventHandlers.containsKey(uid)) { return ERROR_KERNEL_NOT_FOUND_THREAD_EVENT_HANDLER; } threadEventHandlers.get(uid).write(statusPointer); return 0; } @HLEFunction(nid = 0xE81CAF8F, version = 150, checkInsideInterrupt = true) public int sceKernelCreateCallback(@StringInfo(maxLength = 32) String name, int func_addr, int user_arg_addr) { SceKernelCallbackInfo callback = hleKernelCreateCallback(name, func_addr, user_arg_addr); return callback.getUid(); } @HLEFunction(nid = 0xEDBA5844, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelDeleteCallback(@CheckArgument("checkCallbackID") int uid) { hleKernelDeleteCallback(uid); return 0; } /** * Manually notifies a callback. Mostly used for exit callbacks, * and shouldn't be used at all (only some old homebrews use this, anyway). */ @HLEFunction(nid = 0xC11BA8C4, version = 150) public int sceKernelNotifyCallback(@CheckArgument("checkCallbackID") int uid, int arg) { SceKernelCallbackInfo callback = getCallbackInfo(uid); boolean foundCallback = false; for (int i = 0; i < SceKernelThreadInfo.THREAD_CALLBACK_SIZE; i++) { RegisteredCallbacks registeredCallbacks = getCurrentThread().getRegisteredCallbacks(i); if (registeredCallbacks.hasCallback(callback)) { hleKernelNotifyCallback(i, uid, arg); foundCallback = true; break; } } if (!foundCallback) { // Register the callback as a temporary THREAD_CALLBACK_USER_DEFINED if (hleKernelRegisterCallback(SceKernelThreadInfo.THREAD_CALLBACK_USER_DEFINED, uid)) { hleKernelNotifyCallback(SceKernelThreadInfo.THREAD_CALLBACK_USER_DEFINED, uid, arg); } } return 0; } @HLEFunction(nid = 0xBA4051D6, version = 150) public int sceKernelCancelCallback(@CheckArgument("checkCallbackID") int uid) { SceKernelCallbackInfo callback = getCallbackInfo(uid); callback.cancel(); return 0; } /** Return the current notifyCount for a specific callback */ @HLEFunction(nid = 0x2A3D44FF, version = 150) public int sceKernelGetCallbackCount(@CheckArgument("checkCallbackID") int uid) { SceKernelCallbackInfo callback = getCallbackInfo(uid); if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetCallbackCount returning count=%d", callback.getNotifyCount())); } return callback.getNotifyCount(); } /** Check callbacks, only on the current thread. */ @HLEFunction(nid = 0x349D6D6C, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelCheckCallback() { // Remember the currentThread, as it might have changed after // the execution of a callback. SceKernelThreadInfo thread = currentThread; boolean doCallbacks = thread.doCallbacks; thread.doCallbacks = true; // Force callbacks execution. // 0 - The calling thread has no reported callbacks. // 1 - The calling thread has reported callbacks which were executed successfully. int result = checkThreadCallbacks(thread) ? 1 : 0; thread.doCallbacks = doCallbacks; // Reset to the previous value. return result; } @HLEFunction(nid = 0x730ED8BC, version = 150) public int sceKernelReferCallbackStatus(@CheckArgument("checkCallbackID") int uid, TPointer infoAddr) { SceKernelCallbackInfo info = getCallbackInfo(uid); info.write(infoAddr); return 0; } /** sleep the current thread (using wait) */ @HLEFunction(nid = 0x9ACE131E, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelSleepThread() { return hleKernelSleepThread(false); } /** sleep the current thread and handle callbacks (using wait) * in our implementation we have to use wait, not suspend otherwise we don't handle callbacks. */ @HLEFunction(nid = 0x82826F70, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelSleepThreadCB() { int result = hleKernelSleepThread(true); checkCallbacks(); return result; } @HLEFunction(nid = 0xD59EAD2F, version = 150) public int sceKernelWakeupThread(@CheckArgument("checkThreadID") int uid) { SceKernelThreadInfo thread = threadMap.get(uid); hleKernelWakeupThread(thread); return 0; } @HLEFunction(nid = 0xFCCFAD26, version = 150) public int sceKernelCancelWakeupThread(@CheckArgument("checkThreadIDAllow0") int uid) { SceKernelThreadInfo thread = getThreadById(uid); int result = thread.wakeupCount; if (log.isDebugEnabled()) { log.debug(String.format("sceKernelCancelWakeupThread thread=%s returning %d", thread, result)); } thread.wakeupCount = 0; return result; } @HLEFunction(nid = 0x9944F31F, version = 150) public int sceKernelSuspendThread(@CheckArgument("checkThreadID") int uid) { SceKernelThreadInfo thread = getThreadCurrentIsInvalid(uid); if (thread.isSuspended()) { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelSuspendThread thread already suspended: thread=%s", thread.toString())); } return ERROR_KERNEL_THREAD_ALREADY_SUSPEND; } if (thread.isStopped()) { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelSuspendThread thread already stopped: thread=%s", thread.toString())); } return ERROR_KERNEL_THREAD_ALREADY_DORMANT; } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelSuspendThread thread before suspend: %s", thread)); } if (thread.isWaiting()) { hleChangeThreadState(thread, PSP_THREAD_WAITING_SUSPEND); } else { hleChangeThreadState(thread, PSP_THREAD_SUSPEND); } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelSuspendThread thread after suspend: %s", thread)); } return 0; } @HLEFunction(nid = 0x75156E8F, version = 150) public int sceKernelResumeThread(@CheckArgument("checkThreadID") int uid) { SceKernelThreadInfo thread = getThreadById(uid); if (!thread.isSuspended()) { log.warn("sceKernelResumeThread SceUID=" + Integer.toHexString(uid) + " not suspended (status=" + thread.status + ")"); return ERROR_KERNEL_THREAD_IS_NOT_SUSPEND; } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelResumeThread thread before resume: %s", thread)); } if (thread.isWaiting()) { hleChangeThreadState(thread, PSP_THREAD_WAITING); } else { hleChangeThreadState(thread, PSP_THREAD_READY); } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelResumeThread thread after resume: %s", thread)); } return 0; } @HLEFunction(nid = 0x278C0DF5, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelWaitThreadEnd(@CheckArgument("checkThreadID") int uid, @CanBeNull TPointer32 timeoutAddr) { return hleKernelWaitThreadEnd(currentThread, uid, timeoutAddr, false, true); } @HLEFunction(nid = 0x840E8133, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelWaitThreadEndCB(@CheckArgument("checkThreadID") int uid, @CanBeNull TPointer32 timeoutAddr) { int result = hleKernelWaitThreadEnd(currentThread, uid, timeoutAddr, true, true); checkCallbacks(); return result; } /** wait the current thread for a certain number of microseconds */ @HLEFunction(nid = 0xCEADEB47, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelDelayThread(int micros) { hleKernelDelayThread(micros, /* doCallbacks = */ false); return 0; } /** wait the current thread for a certain number of microseconds */ @HLEFunction(nid = 0x68DA9E36, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelDelayThreadCB(int micros) { hleKernelDelayThread(micros, /* doCallbacks = */ true); return 0; } /** * Delay the current thread by a specified number of sysclocks * * @param sysclocksPointer - Address of delay in sysclocks * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xBD123D9E, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelDelaySysClockThread(TPointer64 sysclocksPointer) { long sysclocks = sysclocksPointer.getValue(); int micros = SystemTimeManager.hleSysClock2USec32(sysclocks); hleKernelDelayThread(micros, false); return 0; } /** * Delay the current thread by a specified number of sysclocks handling callbacks * * @param sysclocks_addr - Address of delay in sysclocks * * @return 0 on success, < 0 on error * */ @HLEFunction(nid = 0x1181E963, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelDelaySysClockThreadCB(TPointer64 sysclocksAddr) { long sysclocks = sysclocksAddr.getValue(); int micros = SystemTimeManager.hleSysClock2USec32(sysclocks); hleKernelDelayThread(micros, true); return 0; } @HLEFunction(nid = 0xD6DA4BA1, version = 150, checkInsideInterrupt = true) public int sceKernelCreateSema(String name, int attr, int initVal, int maxVal, @CanBeNull TPointer option) { return Managers.semas.sceKernelCreateSema(name, attr, initVal, maxVal, option); } @HLEFunction(nid = 0x28B6489C, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteSema(@CheckArgument("checkSemaID") int semaid) { return Managers.semas.sceKernelDeleteSema(semaid); } @HLEFunction(nid = 0x3F53E640, version = 150) public int sceKernelSignalSema(@CheckArgument("checkSemaID") int semaid, int signal) { return Managers.semas.sceKernelSignalSema(semaid, signal); } @HLEFunction(nid = 0x4E3A1105, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelWaitSema(@CheckArgument("checkSemaID") int semaid, int signal, @CanBeNull TPointer32 timeoutAddr) { return Managers.semas.sceKernelWaitSema(semaid, signal, timeoutAddr); } @HLEFunction(nid = 0x6D212BAC, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelWaitSemaCB(@CheckArgument("checkSemaID") int semaid, int signal, @CanBeNull TPointer32 timeoutAddr) { return Managers.semas.sceKernelWaitSemaCB(semaid, signal, timeoutAddr); } @HLEFunction(nid = 0x58B1F937, version = 150) public int sceKernelPollSema(@CheckArgument("checkSemaID") int semaid, int signal) { return Managers.semas.sceKernelPollSema(semaid, signal); } @HLEFunction(nid = 0x8FFDF9A2, version = 150) public int sceKernelCancelSema(@CheckArgument("checkSemaID") int semaid, int newcount, @CanBeNull TPointer32 numWaitThreadAddr) { return Managers.semas.sceKernelCancelSema(semaid, newcount, numWaitThreadAddr); } @HLEFunction(nid = 0xBC6FEBC5, version = 150) public int sceKernelReferSemaStatus(@CheckArgument("checkSemaID") int semaid, TPointer addr) { return Managers.semas.sceKernelReferSemaStatus(semaid, addr); } @HLEFunction(nid = 0x55C20A00, version = 150, checkInsideInterrupt = true) public int sceKernelCreateEventFlag(String name, int attr, int initPattern, @CanBeNull TPointer option) { return Managers.eventFlags.sceKernelCreateEventFlag(name, attr, initPattern, option); } @HLEFunction(nid = 0xEF9E4C70, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteEventFlag(@CheckArgument("checkEventFlagID") int uid) { return Managers.eventFlags.sceKernelDeleteEventFlag(uid); } @HLEFunction(nid = 0x1FB15A32, version = 150) public int sceKernelSetEventFlag(@CheckArgument("checkEventFlagID") int uid, int bitsToSet) { return Managers.eventFlags.sceKernelSetEventFlag(uid, bitsToSet); } @HLEFunction(nid = 0x812346E4, version = 150) public int sceKernelClearEventFlag(@CheckArgument("checkEventFlagID") int uid, int bitsToKeep) { return Managers.eventFlags.sceKernelClearEventFlag(uid, bitsToKeep); } @HLEFunction(nid = 0x402FCF22, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelWaitEventFlag(@CheckArgument("checkEventFlagID") int uid, int bits, int wait, @CanBeNull TPointer32 outBitsAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.eventFlags.sceKernelWaitEventFlag(uid, bits, wait, outBitsAddr, timeoutAddr); } @HLEFunction(nid = 0x328C546A, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelWaitEventFlagCB(@CheckArgument("checkEventFlagID") int uid, int bits, int wait, @CanBeNull TPointer32 outBitsAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.eventFlags.sceKernelWaitEventFlagCB(uid, bits, wait, outBitsAddr, timeoutAddr); } @HLEFunction(nid = 0x30FD48F0, version = 150) public int sceKernelPollEventFlag(@CheckArgument("checkEventFlagID") int uid, int bits, int wait, @CanBeNull TPointer32 outBitsAddr) { return Managers.eventFlags.sceKernelPollEventFlag(uid, bits, wait, outBitsAddr); } @HLEFunction(nid = 0xCD203292, version = 150) public int sceKernelCancelEventFlag(@CheckArgument("checkEventFlagID") int uid, int newPattern, @CanBeNull TPointer32 numWaitThreadAddr) { return Managers.eventFlags.sceKernelCancelEventFlag(uid, newPattern, numWaitThreadAddr); } @HLEFunction(nid = 0xA66B0120, version = 150) public int sceKernelReferEventFlagStatus(@CheckArgument("checkEventFlagID") int uid, TPointer addr) { return Managers.eventFlags.sceKernelReferEventFlagStatus(uid, addr); } @HLEFunction(nid = 0x8125221D, version = 150, checkInsideInterrupt = true) public int sceKernelCreateMbx(String name, int attr, @CanBeNull TPointer option) { return Managers.mbx.sceKernelCreateMbx(name, attr, option); } @HLEFunction(nid = 0x86255ADA, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteMbx(@CheckArgument("checkMbxID") int uid) { return Managers.mbx.sceKernelDeleteMbx(uid); } @HLEFunction(nid = 0xE9B3061E, version = 150) public int sceKernelSendMbx(@CheckArgument("checkMbxID") int uid, TPointer msgAddr) { return Managers.mbx.sceKernelSendMbx(uid, msgAddr); } @HLEFunction(nid = 0x18260574, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelReceiveMbx(@CheckArgument("checkMbxID") int uid, TPointer32 addrMsgAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.mbx.sceKernelReceiveMbx(uid, addrMsgAddr, timeoutAddr); } @HLEFunction(nid = 0xF3986382, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelReceiveMbxCB(@CheckArgument("checkMbxID") int uid, TPointer32 addrMsgAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.mbx.sceKernelReceiveMbxCB(uid, addrMsgAddr, timeoutAddr); } @HLEFunction(nid = 0x0D81716A, version = 150) public int sceKernelPollMbx(@CheckArgument("checkMbxID") int uid, TPointer32 addrMsgAddr) { return Managers.mbx.sceKernelPollMbx(uid, addrMsgAddr); } @HLEFunction(nid = 0x87D4DD36, version = 150) public int sceKernelCancelReceiveMbx(@CheckArgument("checkMbxID") int uid, @CanBeNull TPointer32 pnumAddr) { return Managers.mbx.sceKernelCancelReceiveMbx(uid, pnumAddr); } @HLEFunction(nid = 0xA8E8C846, version = 150) public int sceKernelReferMbxStatus(@CheckArgument("checkMbxID") int uid, TPointer infoAddr) { return Managers.mbx.sceKernelReferMbxStatus(uid, infoAddr); } @HLEFunction(nid = 0x7C0DC2A0, version = 150, checkInsideInterrupt = true) public int sceKernelCreateMsgPipe(String name, int partitionid, int attr, int size, @CanBeNull TPointer option) { return Managers.msgPipes.sceKernelCreateMsgPipe(name, partitionid, attr, size, option); } @HLEFunction(nid = 0xF0B7DA1C, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteMsgPipe(@CheckArgument("checkMsgPipeID") int uid) { return Managers.msgPipes.sceKernelDeleteMsgPipe(uid); } @HLEFunction(nid = 0x876DBFAD, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelSendMsgPipe(@CheckArgument("checkMsgPipeID") int uid, TPointer msgAddr, int size, int waitMode, @CanBeNull TPointer32 resultSizeAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.msgPipes.sceKernelSendMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr, timeoutAddr); } @HLEFunction(nid = 0x7C41F2C2, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelSendMsgPipeCB(@CheckArgument("checkMsgPipeID") int uid, TPointer msgAddr, int size, int waitMode, @CanBeNull TPointer32 resultSizeAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.msgPipes.sceKernelSendMsgPipeCB(uid, msgAddr, size, waitMode, resultSizeAddr, timeoutAddr); } @HLEFunction(nid = 0x884C9F90, version = 150) public int sceKernelTrySendMsgPipe(@CheckArgument("checkMsgPipeID") int uid, TPointer msgAddr, int size, int waitMode, @CanBeNull TPointer32 resultSizeAddr) { return Managers.msgPipes.sceKernelTrySendMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr); } @HLEFunction(nid = 0x74829B76, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelReceiveMsgPipe(@CheckArgument("checkMsgPipeID") int uid, TPointer msgAddr, int size, int waitMode, @CanBeNull TPointer32 resultSizeAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.msgPipes.sceKernelReceiveMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr, timeoutAddr); } @HLEFunction(nid = 0xFBFA697D, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelReceiveMsgPipeCB(@CheckArgument("checkMsgPipeID") int uid, TPointer msgAddr, int size, int waitMode, @CanBeNull TPointer32 resultSizeAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.msgPipes.sceKernelReceiveMsgPipeCB(uid, msgAddr, size, waitMode, resultSizeAddr, timeoutAddr); } @HLEFunction(nid = 0xDF52098F, version = 150) public int sceKernelTryReceiveMsgPipe(@CheckArgument("checkMsgPipeID") int uid, TPointer msgAddr, int size, int waitMode, @CanBeNull TPointer32 resultSizeAddr) { return Managers.msgPipes.sceKernelTryReceiveMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr); } @HLEFunction(nid = 0x349B864D, version = 150) public int sceKernelCancelMsgPipe(@CheckArgument("checkMsgPipeID") int uid, @CanBeNull TPointer32 sendAddr, @CanBeNull TPointer32 recvAddr) { return Managers.msgPipes.sceKernelCancelMsgPipe(uid, sendAddr, recvAddr); } @HLEFunction(nid = 0x33BE4024, version = 150) public int sceKernelReferMsgPipeStatus(@CheckArgument("checkMsgPipeID") int uid, TPointer infoAddr) { return Managers.msgPipes.sceKernelReferMsgPipeStatus(uid, infoAddr); } @HLEFunction(nid = 0x56C039B5, version = 150, checkInsideInterrupt = true) public int sceKernelCreateVpl(PspString name, @CheckArgument("checkPartitionID") int partitionid, int attr, int size, @CanBeNull TPointer option) { return Managers.vpl.sceKernelCreateVpl(name, partitionid, attr, size, option); } @HLEFunction(nid = 0x89B3D48C, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteVpl(@CheckArgument("checkVplID") int uid) { return Managers.vpl.sceKernelDeleteVpl(uid); } @HLEFunction(nid = 0xBED27435, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelAllocateVpl(@CheckArgument("checkVplID") int uid, int size, TPointer32 dataAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.vpl.sceKernelAllocateVpl(uid, size, dataAddr, timeoutAddr); } @HLEFunction(nid = 0xEC0A693F, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelAllocateVplCB(@CheckArgument("checkVplID") int uid, int size, TPointer32 dataAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.vpl.sceKernelAllocateVplCB(uid, size, dataAddr, timeoutAddr); } @HLEFunction(nid = 0xAF36D708, version = 150) public int sceKernelTryAllocateVpl(@CheckArgument("checkVplID") int uid, int size, TPointer32 dataAddr) { return Managers.vpl.sceKernelTryAllocateVpl(uid, size, dataAddr); } @HLEFunction(nid = 0xB736E9FF, version = 150, checkInsideInterrupt = true) public int sceKernelFreeVpl(@CheckArgument("checkVplID") int uid, TPointer dataAddr) { return Managers.vpl.sceKernelFreeVpl(uid, dataAddr); } @HLEFunction(nid = 0x1D371B8A, version = 150) public int sceKernelCancelVpl(@CheckArgument("checkVplID") int uid, @CanBeNull TPointer32 numWaitThreadAddr) { return Managers.vpl.sceKernelCancelVpl(uid, numWaitThreadAddr); } @HLEFunction(nid = 0x39810265, version = 150) public int sceKernelReferVplStatus(@CheckArgument("checkVplID") int uid, TPointer infoAddr) { return Managers.vpl.sceKernelReferVplStatus(uid, infoAddr); } @HLEFunction(nid = 0xC07BB470, version = 150, checkInsideInterrupt = true) public int sceKernelCreateFpl(@CanBeNull PspString name, @CheckArgument("checkPartitionID") int partitionid, int attr, int blocksize, int blocks, @CanBeNull TPointer option) { return Managers.fpl.sceKernelCreateFpl(name, partitionid, attr, blocksize, blocks, option); } @HLEFunction(nid = 0xED1410E0, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteFpl(@CheckArgument("checkFplID") int uid) { return Managers.fpl.sceKernelDeleteFpl(uid); } @HLEFunction(nid = 0xD979E9BF, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelAllocateFpl(@CheckArgument("checkFplID") int uid, TPointer32 dataAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.fpl.sceKernelAllocateFpl(uid, dataAddr, timeoutAddr); } @HLEFunction(nid = 0xE7282CB6, version = 150, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelAllocateFplCB(@CheckArgument("checkFplID") int uid, TPointer32 dataAddr, @CanBeNull TPointer32 timeoutAddr) { return Managers.fpl.sceKernelAllocateFplCB(uid, dataAddr, timeoutAddr); } @HLEFunction(nid = 0x623AE665, version = 150) public int sceKernelTryAllocateFpl(@CheckArgument("checkFplID") int uid, TPointer32 dataAddr) { return Managers.fpl.sceKernelTryAllocateFpl(uid, dataAddr); } @HLEFunction(nid = 0xF6414A71, version = 150, checkInsideInterrupt = true) public int sceKernelFreeFpl(@CheckArgument("checkFplID") int uid, TPointer dataAddr) { return Managers.fpl.sceKernelFreeFpl(uid, dataAddr); } @HLEFunction(nid = 0xA8AA591F, version = 150) public int sceKernelCancelFpl(@CheckArgument("checkFplID") int uid, @CanBeNull TPointer32 numWaitThreadAddr) { return Managers.fpl.sceKernelCancelFpl(uid, numWaitThreadAddr); } @HLEFunction(nid = 0xD8199E4C, version = 150) public int sceKernelReferFplStatus(@CheckArgument("checkFplID") int uid, TPointer infoAddr) { return Managers.fpl.sceKernelReferFplStatus(uid, infoAddr); } @HLEUnimplemented @HLEFunction(nid = 0x0E927AED, version = 150) public int _sceKernelReturnFromTimerHandler() { return 0; } @HLEFunction(nid = 0x110DEC9A, version = 150) public int sceKernelUSec2SysClock(int usec, TPointer64 sysClockAddr) { return Managers.systime.sceKernelUSec2SysClock(usec, sysClockAddr); } @HLEFunction(nid = 0xC8CD158C, version = 150) public long sceKernelUSec2SysClockWide(int usec) { return Managers.systime.sceKernelUSec2SysClockWide(usec); } @HLEFunction(nid = 0xBA6B92E2, version = 150) public int sceKernelSysClock2USec(TPointer64 sysClockAddr, @CanBeNull TPointer32 secAddr, @CanBeNull TPointer32 microSecAddr) { return Managers.systime.sceKernelSysClock2USec(sysClockAddr, secAddr, microSecAddr); } @HLEFunction(nid = 0xE1619D7C, version = 150) public int sceKernelSysClock2USecWide(long sysClock, @CanBeNull TPointer32 secAddr, @CanBeNull TPointer32 microSecAddr) { return Managers.systime.sceKernelSysClock2USecWide(sysClock, secAddr, microSecAddr); } @HLEFunction(nid = 0xDB738F35, version = 150) public int sceKernelGetSystemTime(TPointer64 timeAddr) { return Managers.systime.sceKernelGetSystemTime(timeAddr); } @HLEFunction(nid = 0x82BC5777, version = 150) public long sceKernelGetSystemTimeWide() { return Managers.systime.sceKernelGetSystemTimeWide(); } @HLEFunction(nid = 0x369ED59D, version = 150) public int sceKernelGetSystemTimeLow() { return Managers.systime.sceKernelGetSystemTimeLow(); } /** * Set an alarm. * @param delayUsec - The number of micro seconds till the alarm occurs. * @param handlerAddress - Pointer to a ::SceKernelAlarmHandler * @param handlerArgument - Common pointer for the alarm handler * * @return A UID representing the created alarm, < 0 on error. */ @HLEFunction(nid = 0x6652B8CA, version = 150) public int sceKernelSetAlarm(int delayUsec, TPointer handlerAddress, int handlerArgument) { // delayUsec is an unsigned 32-bit value return hleKernelSetAlarm(delayUsec & 0xFFFFFFFFL, handlerAddress, handlerArgument); } /** * Set an alarm using a ::SceKernelSysClock structure for the time * * @param delaySysclockAddr - Pointer to a ::SceKernelSysClock structure * @param handlerAddress - Pointer to a ::SceKernelAlarmHandler * @param handlerArgument - Common pointer for the alarm handler. * * @return A UID representing the created alarm, < 0 on error. */ @HLEFunction(nid = 0xB2C25152, version = 150) public int sceKernelSetSysClockAlarm(TPointer64 delaySysclockAddr, TPointer handlerAddress, int handlerArgument) { long delaySysclock = delaySysclockAddr.getValue(); long delayUsec = SystemTimeManager.hleSysClock2USec(delaySysclock); return hleKernelSetAlarm(delayUsec, handlerAddress, handlerArgument); } /** * Cancel a pending alarm. * * @param alarmUid - UID of the alarm to cancel. * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x7E65B999, version = 150) public int sceKernelCancelAlarm(@CheckArgument("checkAlarmID") int alarmUid) { SceKernelAlarmInfo sceKernelAlarmInfo = alarms.get(alarmUid); cancelAlarm(sceKernelAlarmInfo); return 0; } /** * Refer the status of a created alarm. * * @param alarmUid - UID of the alarm to get the info of * @param infoAddr - Pointer to a ::SceKernelAlarmInfo structure * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0xDAA3F564, version = 150) public int sceKernelReferAlarmStatus(@CheckArgument("checkAlarmID") int alarmUid, TPointer infoAddr) { SceKernelAlarmInfo sceKernelAlarmInfo = alarms.get(alarmUid); sceKernelAlarmInfo.write(infoAddr); return 0; } /** * Create a virtual timer * * @param nameAddr - Name for the timer. * @param optAddr - Pointer to an ::SceKernelVTimerOptParam (pass NULL) * * @return The VTimer's UID or < 0 on error. */ @HLEFunction(nid = 0x20FFF560, version = 150, checkInsideInterrupt = true) public int sceKernelCreateVTimer(String name, @CanBeNull TPointer optAddr) { SceKernelVTimerInfo sceKernelVTimerInfo = new SceKernelVTimerInfo(name); vtimers.put(sceKernelVTimerInfo.uid, sceKernelVTimerInfo); return sceKernelVTimerInfo.uid; } /** * Delete a virtual timer * * @param vtimerUid - The UID of the timer * * @return < 0 on error. */ @HLEFunction(nid = 0x328F9E52, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteVTimer(@CheckArgument("checkVTimerID") int vtimerUid) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.remove(vtimerUid); sceKernelVTimerInfo.delete(); return 0; } /** * Get the timer base * * @param vtimerUid - UID of the vtimer * @param baseAddr - Pointer to a ::SceKernelSysClock structure * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xB3A59970, version = 150) public int sceKernelGetVTimerBase(@CheckArgument("checkVTimerID") int vtimerUid, TPointer64 baseAddr) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); baseAddr.setValue(sceKernelVTimerInfo.base); return 0; } /** * Get the timer base (wide format) * * @param vtimerUid - UID of the vtimer * * @return The 64bit timer base */ @HLEFunction(nid = 0xB7C18B77, version = 150) public long sceKernelGetVTimerBaseWide(@CheckArgument("checkVTimerID") int vtimerUid) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); return sceKernelVTimerInfo.base; } /** * Get the timer time * * @param vtimerUid - UID of the vtimer * @param timeAddr - Pointer to a ::SceKernelSysClock structure * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0x034A921F, version = 150) public int sceKernelGetVTimerTime(@CheckArgument("checkVTimerID") int vtimerUid, TPointer64 timeAddr) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); long time = sceKernelVTimerInfo.getCurrentTime(); if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetVTimerTime returning %d", time)); } timeAddr.setValue(time); return 0; } /** * Get the timer time (wide format) * * @param vtimerUid - UID of the vtimer * * @return The 64bit timer time */ @HLEFunction(nid = 0xC0B3FFD2, version = 150) public long sceKernelGetVTimerTimeWide(@CheckArgument("checkVTimerID") int vtimerUid) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); long time = sceKernelVTimerInfo.getCurrentTime(); if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetVTimerTimeWide returning %d", time)); } return time; } /** * Set the timer time * * @param vtimerUid - UID of the vtimer * @param timeAddr - Pointer to a ::SceKernelSysClock structure * The previous value of the vtimer is returned back in this structure. * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0x542AD630, version = 150, checkInsideInterrupt = true) public int sceKernelSetVTimerTime(@CheckArgument("checkVTimerID") int vtimerUid, TPointer64 timeAddr) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); long time = timeAddr.getValue(); timeAddr.setValue(setVTimer(sceKernelVTimerInfo, time)); return 0; } /** * Set the timer time (wide format) * * @param vtimerUid - UID of the vtimer * @param time - a ::SceKernelSysClock structure * * @return the last time of the vtimer or -1 if the vtimerUid is invalid */ @HLEFunction(nid = 0xFB6425C3, version = 150, checkInsideInterrupt = true) public long sceKernelSetVTimerTimeWide(int vtimerUid, long time) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); if (sceKernelVTimerInfo == null) { // sceKernelSetVTimerTimeWide returns -1 instead of ERROR_KERNEL_NOT_FOUND_VTIMER // when the vtimerUid is invalid. return -1; } return setVTimer(sceKernelVTimerInfo, time); } /** * Start a virtual timer * * @param vtimerUid - The UID of the timer * * @return < 0 on error */ @HLEFunction(nid = 0xC68D9437, version = 150) public int sceKernelStartVTimer(@CheckArgument("checkVTimerID") int vtimerUid) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); if (sceKernelVTimerInfo.active == SceKernelVTimerInfo.ACTIVE_RUNNING) { return 1; // already started } startVTimer(sceKernelVTimerInfo); return 0; } /** * Stop a virtual timer * * @param vtimerUid - The UID of the timer * * @return < 0 on error */ @HLEFunction(nid = 0xD0AEEE87, version = 150) public int sceKernelStopVTimer(@CheckArgument("checkVTimerID") int vtimerUid) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); if (sceKernelVTimerInfo.active == SceKernelVTimerInfo.ACTIVE_STOPPED) { return 0; // already stopped } stopVTimer(sceKernelVTimerInfo); return 1; } /** * Set the timer handler * * @param vtimerUid - UID of the vtimer * @param scheduleAddr - Time to call the handler * @param handlerAddress - The timer handler * @param handlerArgument - Common pointer * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xD8B299AE, version = 150) public int sceKernelSetVTimerHandler(@CheckArgument("checkVTimerID") int vtimerUid, TPointer64 scheduleAddr, @CanBeNull TPointer handlerAddress, int handlerArgument) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); long schedule = scheduleAddr.getValue(); sceKernelVTimerInfo.handlerAddress = handlerAddress.getAddress(); sceKernelVTimerInfo.handlerArgument = handlerArgument; scheduleVTimer(sceKernelVTimerInfo, schedule); return 0; } /** * Set the timer handler (wide mode) * * @param vtimerUid - UID of the vtimer * @param schedule - Time to call the handler * @param handlerAddress - The timer handler * @param handlerArgument - Common pointer * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0x53B00E9A, version = 150) public int sceKernelSetVTimerHandlerWide(@CheckArgument("checkVTimerID") int vtimerUid, long schedule, TPointer handlerAddress, int handlerArgument) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); sceKernelVTimerInfo.handlerAddress = handlerAddress.getAddress(); sceKernelVTimerInfo.handlerArgument = handlerArgument; scheduleVTimer(sceKernelVTimerInfo, schedule); return 0; } /** * Cancel the timer handler * * @param vtimerUid - The UID of the vtimer * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0xD2D615EF, version = 150) public int sceKernelCancelVTimerHandler(@CheckArgument("checkVTimerID") int vtimerUid) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); cancelVTimer(sceKernelVTimerInfo); return 0; } /** * Get the status of a VTimer * * @param vtimerUid - The uid of the VTimer * @param infoAddr - Pointer to a ::SceKernelVTimerInfo structure * * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0x5F32BEAA, version = 150) public int sceKernelReferVTimerStatus(@CheckArgument("checkVTimerID") int vtimerUid, TPointer infoAddr) { SceKernelVTimerInfo sceKernelVTimerInfo = vtimers.get(vtimerUid); sceKernelVTimerInfo.write(infoAddr); return 0; } @HLEFunction(nid = 0x446D8DE6, version = 150) public int sceKernelCreateThread(@StringInfo(maxLength = 32) String name, int entry_addr, int initPriority, int stackSize, int attr, int option_addr) { int mpidStack = USER_PARTITION_ID; // Inherit kernel mode if user mode bit is not set if (currentThread.isKernelMode() && !SceKernelThreadInfo.isUserMode(attr)) { log.debug("sceKernelCreateThread inheriting kernel mode"); attr |= PSP_THREAD_ATTR_KERNEL; mpidStack = KERNEL_PARTITION_ID; } SceKernelThreadInfo thread = hleKernelCreateThread(name, entry_addr, initPriority, stackSize, attr, option_addr, mpidStack); if (thread.stackSize > 0 && thread.getStackAddr() == 0) { log.warn("sceKernelCreateThread not enough memory to create the stack"); hleDeleteThread(thread); return SceKernelErrors.ERROR_KERNEL_NO_MEMORY; } // Inherit user mode if (currentThread.isUserMode()) { if (!SceKernelThreadInfo.isUserMode(thread.attr)) { log.debug("sceKernelCreateThread inheriting user mode"); } thread.attr |= PSP_THREAD_ATTR_USER; // Always remove kernel mode bit thread.attr &= ~PSP_THREAD_ATTR_KERNEL; } triggerThreadEvent(thread, currentThread, THREAD_EVENT_CREATE); return thread.uid; } /** mark a thread for deletion. */ @HLEFunction(nid = 0x9FA03CD3, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteThread(@CheckArgument("checkThreadIDAllow0") int uid) { SceKernelThreadInfo thread = threadMap.get(uid); if (!thread.isStopped()) { // This error is also returned for the current thread or thread id 0 (the current thread isn't stopped). return ERROR_KERNEL_THREAD_IS_NOT_DORMANT; } // Mark thread for deletion setToBeDeletedThread(thread); triggerThreadEvent(thread, currentThread, THREAD_EVENT_DELETE); return 0; } @HLEFunction(nid = 0xF475845D, version = 150, checkInsideInterrupt = true) public int sceKernelStartThread(@CheckArgument("checkThreadID") int uid, int len, int data_addr) { SceKernelThreadInfo thread = threadMap.get(uid); if (!thread.isStopped()) { return ERROR_KERNEL_THREAD_IS_NOT_DORMANT; } hleKernelStartThread(thread, len, data_addr, thread.gpReg_addr); return 0; } @HLEFunction(nid = 0x532A522E, version = 150) public int _sceKernelExitThread(int exitStatus) { // _sceKernelExitThread is equivalent to sceKernelExitThread return sceKernelExitThread(exitStatus); } /** exit the current thread */ @HLEFunction(nid = 0xAA73C935, version = 150, checkInsideInterrupt = true) public int sceKernelExitThread(int exitStatus) { // PSP is only returning an error for a SDK after 3.07 if (!isDispatchThreadEnabled() && Modules.SysMemUserForUserModule.hleKernelGetCompiledSdkVersion() > 0x0307FFFF) { return SceKernelErrors.ERROR_KERNEL_WAIT_CAN_NOT_WAIT; } SceKernelThreadInfo thread = currentThread; if (exitStatus < 0) { thread.setExitStatus(ERROR_KERNEL_ILLEGAL_ARGUMENT); } else { thread.setExitStatus(exitStatus); } triggerThreadEvent(thread, currentThread, THREAD_EVENT_EXIT); hleChangeThreadState(thread, PSP_THREAD_STOPPED); RuntimeContext.onThreadExit(thread); hleRescheduleCurrentThread(); return 0; } /** exit the current thread, then delete it */ @HLEFunction(nid = 0x809CE29B, version = 150, checkInsideInterrupt = true) public int sceKernelExitDeleteThread(int exitStatus) { // PSP is only returning an error for a SDK after 3.07 if (!isDispatchThreadEnabled() && Modules.SysMemUserForUserModule.hleKernelGetCompiledSdkVersion() > 0x0307FFFF) { return SceKernelErrors.ERROR_KERNEL_WAIT_CAN_NOT_WAIT; } SceKernelThreadInfo thread = currentThread; thread.setExitStatus(exitStatus); triggerThreadEvent(thread, currentThread, THREAD_EVENT_EXIT); triggerThreadEvent(thread, currentThread, THREAD_EVENT_DELETE); hleChangeThreadState(thread, PSP_THREAD_STOPPED); RuntimeContext.onThreadExit(thread); setToBeDeletedThread(thread); hleRescheduleCurrentThread(); return 0; } /** terminate thread */ @HLEFunction(nid = 0x616403BA, version = 150) public int sceKernelTerminateThread(@CheckArgument("checkThreadID") int uid) { // PSP is only returning an error for a SDK after 3.07 if (IntrManager.getInstance().isInsideInterrupt() && Modules.SysMemUserForUserModule.hleKernelGetCompiledSdkVersion() > 0x0307FFFF) { return SceKernelErrors.ERROR_KERNEL_CANNOT_BE_CALLED_FROM_INTERRUPT; } // PSP is only returning an error for a SDK after 3.07 if (!isDispatchThreadEnabled() && Modules.SysMemUserForUserModule.hleKernelGetCompiledSdkVersion() > 0x0307FFFF) { return SceKernelErrors.ERROR_KERNEL_WAIT_CAN_NOT_WAIT; } SceKernelThreadInfo thread = getThreadCurrentIsInvalid(uid); triggerThreadEvent(thread, currentThread, THREAD_EVENT_EXIT); // Return this exit status to threads currently waiting on the thread end thread.setExitStatus(ERROR_KERNEL_THREAD_IS_TERMINATED); terminateThread(thread); // Return this exit status to threads that will wait on this thread end later on thread.setExitStatus(ERROR_KERNEL_THREAD_ALREADY_DORMANT); return 0; } /** terminate thread, then mark it for deletion */ @HLEFunction(nid = 0x383F7BCC, version = 150, checkInsideInterrupt = true) public int sceKernelTerminateDeleteThread(@CheckArgument("checkThreadID") int uid) { // PSP is only returning an error for a SDK after 3.07 if (!isDispatchThreadEnabled() && Modules.SysMemUserForUserModule.hleKernelGetCompiledSdkVersion() > 0x0307FFFF) { return SceKernelErrors.ERROR_KERNEL_WAIT_CAN_NOT_WAIT; } SceKernelThreadInfo thread = getThreadCurrentIsInvalid(uid); triggerThreadEvent(thread, currentThread, THREAD_EVENT_EXIT); triggerThreadEvent(thread, currentThread, THREAD_EVENT_DELETE); terminateThread(thread); setToBeDeletedThread(thread); return 0; } /** * Suspend the dispatch thread * * @return The current state of the dispatch thread, < 0 on error */ @HLEFunction(nid = 0x3AD58B8C, version = 150, checkInsideInterrupt = true) public int sceKernelSuspendDispatchThread() { int state = getDispatchThreadState(); if (log.isDebugEnabled()) { log.debug("sceKernelSuspendDispatchThread() state=" + state); } if (Interrupts.isInterruptsDisabled()) { return SceKernelErrors.ERROR_KERNEL_INTERRUPTS_ALREADY_DISABLED; } dispatchThreadEnabled = false; return state; } /** * Resume the dispatch thread * * @param state - The state of the dispatch thread * (from sceKernelSuspendDispatchThread) * @return 0 on success, < 0 on error */ @HLEFunction(nid = 0x27E22EC2, version = 150, checkInsideInterrupt = true) public int sceKernelResumeDispatchThread(int state) { boolean isInterruptsDisabled = Interrupts.isInterruptsDisabled(); if (state == SCE_KERNEL_DISPATCHTHREAD_STATE_ENABLED) { hleKernelResumeDispatchThread(); } if (isInterruptsDisabled) { return SceKernelErrors.ERROR_KERNEL_INTERRUPTS_ALREADY_DISABLED; } return 0; } @HLEFunction(nid = 0xEA748E31, version = 150) public int sceKernelChangeCurrentThreadAttr(int removeAttr, int addAttr) { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelChangeCurrentThreadAttr removeAttr=0x%X, addAttr=0x%X, currentAttr=0x%X", removeAttr, addAttr, currentThread.attr)); } int newAttr = (currentThread.attr & ~removeAttr) | addAttr; // Don't allow switching into kernel mode! if (userCurrentThreadTryingToSwitchToKernelMode(newAttr)) { log.debug("sceKernelChangeCurrentThreadAttr forcing user mode"); newAttr |= PSP_THREAD_ATTR_USER; newAttr &= ~PSP_THREAD_ATTR_KERNEL; } currentThread.attr = newAttr; return 0; } @HLEFunction(nid = 0x71BC9871, version = 150) public int sceKernelChangeThreadPriority(@CheckArgument("checkThreadIDAllow0") int uid, @CheckArgument("checkThreadPriority") int priority) { SceKernelThreadInfo thread = getThreadById(uid); if (thread.isStopped()) { // Tested on PSP: // If the thread is stopped, it's current priority is replaced by it's initial priority. thread.currentPriority = thread.initPriority; return ERROR_KERNEL_THREAD_ALREADY_DORMANT; } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelChangeThreadPriority thread=%s, newPriority=0x%X, oldPriority=0x%X", thread, priority, thread.currentPriority)); } hleKernelChangeThreadPriority(thread, priority); return 0; } public int checkThreadPriority(int priority) { // Priority 0 means priority of the calling thread if (priority == 0) { priority = currentThread.currentPriority; } if (currentThread.isUserMode()) { // Value priority range in user mode: [8..119] if (priority < 8 || priority >= 120) { if (log.isDebugEnabled()) { log.debug(String.format("checkThreadPriority priority:0x%x is outside of valid range in user mode", priority)); } throw(new SceKernelErrorException(ERROR_KERNEL_ILLEGAL_PRIORITY)); } } if (currentThread.isKernelMode()) { // Value priority range in user mode: [1..126] if (priority < 1 || priority >= 127) { if (log.isDebugEnabled()) { log.debug(String.format("checkThreadPriority priority:0x%x is outside of valid range in kernel mode", priority)); } throw(new SceKernelErrorException(ERROR_KERNEL_ILLEGAL_PRIORITY)); } } return priority; } protected SceKernelThreadInfo getThreadCurrentIsInvalid(int uid) { SceKernelThreadInfo thread = getThreadById(uid); if (thread == currentThread) { throw(new SceKernelErrorException(ERROR_KERNEL_ILLEGAL_THREAD)); } return thread; } /** * Rotate thread ready queue at a set priority * * @param priority - The priority of the queue * * @return 0 on success, < 0 on error. */ @HLEFunction(nid = 0x912354A7, version = 150) public int sceKernelRotateThreadReadyQueue(@CheckArgument("checkThreadPriority") int priority) { synchronized (readyThreads) { for (SceKernelThreadInfo thread : readyThreads) { if (thread.currentPriority == priority) { // When rotating the ready queue of the current thread, // the current thread yields and is moved to the end of its // ready queue. if (priority == currentThread.currentPriority) { thread = currentThread; // The current thread will be moved to the front of the ready queue hleChangeThreadState(thread, PSP_THREAD_READY); } // Move the thread to the end of the ready queue removeFromReadyThreads(thread); addToReadyThreads(thread, false); hleRescheduleCurrentThread(); break; } } } return 0; } @HLEFunction(nid = 0x2C34E053, version = 150) public int sceKernelReleaseWaitThread(@CheckArgument("checkThreadID") int uid) { SceKernelThreadInfo thread = getThreadCurrentIsInvalid(uid); if (!thread.isWaiting()) { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelReleaseWaitThread(0x%X): thread not waiting: %s", uid, thread)); } return SceKernelErrors.ERROR_KERNEL_THREAD_IS_NOT_WAIT; } // If the application is waiting on a internal condition, // return an illegal permission // (e.g. on a real PSP, it would be the case for a // sceKernelWaitEventFlag issued internally by a syscall). if (thread.waitType >= SceKernelThreadInfo.JPCSP_FIRST_INTERNAL_WAIT_TYPE) { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelReleaseWaitThread(0x%X): thread waiting in privileged status: waitType=0x%X", uid, thread.waitType)); } return SceKernelErrors.ERROR_KERNEL_ILLEGAL_PERMISSION; } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelReleaseWaitThread(0x%X): releasing waiting thread: %s", uid, thread)); } hleThreadWaitRelease(thread); // Check if we need to switch to the released thread // (e.g. has a higher priority) hleRescheduleCurrentThread(); return 0; } /** Get the current thread Id */ @HLEFunction(nid = 0x293B45B8, version = 150, checkInsideInterrupt = true) public int sceKernelGetThreadId() { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetThreadId returning uid=0x%X", currentThread.uid)); } return currentThread.uid; } @HLEFunction(nid = 0x94AA61EE, version = 150, checkInsideInterrupt = true) public int sceKernelGetThreadCurrentPriority() { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetThreadCurrentPriority returning currentPriority=%d", currentThread.currentPriority)); } return currentThread.currentPriority; } /** @return ERROR_NOT_FOUND_THREAD on uid < 0, uid == 0 and thread not found */ @HLEFunction(nid = 0x3B183E26, version = 150) public int sceKernelGetThreadExitStatus(@CheckArgument("checkThreadIDNoCheck0") int uid) { SceKernelThreadInfo thread = getThreadById(uid); if (!thread.isStopped()) { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetThreadExitStatus not stopped uid=0x%x", uid)); } return ERROR_KERNEL_THREAD_IS_NOT_DORMANT; } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetThreadExitStatus thread=%s returning exitStatus=0x%08X", thread, thread.exitStatus)); } return thread.exitStatus; } /** @return amount of free stack space.*/ @HLEFunction(nid = 0xD13BDE95, version = 150, checkInsideInterrupt = true) public int sceKernelCheckThreadStack(Processor processor) { int size = getThreadCurrentStackSize(processor); if (log.isDebugEnabled()) { log.debug(String.format("sceKernelCheckThreadStack returning size=0x%X", size)); } return size; } /** * @return amount of unused stack space of a thread. * */ @HLEFunction(nid = 0x52089CA1, version = 150, checkInsideInterrupt = true) public int sceKernelGetThreadStackFreeSize(@CheckArgument("checkThreadIDAllow0") int uid) { SceKernelThreadInfo thread = getThreadById(uid); // The stack is filled with 0xFF when the thread starts. // Scan for the unused stack space by looking for the first 32-bit value // differing from 0xFFFFFFFF. IMemoryReader memoryReader = MemoryReader.getMemoryReader(thread.getStackAddr(), thread.stackSize, 4); int unusedStackSize = thread.stackSize; for (int i = 0; i < thread.stackSize; i += 4) { int stackValue = memoryReader.readNext(); if (stackValue != -1) { unusedStackSize = i; break; } } if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetThreadStackFreeSize returning size=0x%X", unusedStackSize)); } return unusedStackSize; } /** Get the status information for the specified thread **/ @HLEFunction(nid = 0x17C1684E, version = 150) public int sceKernelReferThreadStatus(@CheckArgument("checkThreadIDAllow0") int uid, TPointer addr) { SceKernelThreadInfo thread = getThreadById(uid); thread.write(addr); return 0; } @HLEFunction(nid = 0xFFC36A14, version = 150, checkInsideInterrupt = true) public int sceKernelReferThreadRunStatus(@CheckArgument("checkThreadIDAllow0") int uid, TPointer addr) { SceKernelThreadInfo thread = getThreadById(uid); thread.writeRunStatus(addr); return 0; } /** * Get the current system status. * * @param status - Pointer to a ::SceKernelSystemStatus structure. * * @return < 0 on error. */ @HLEFunction(nid = 0x627E6F3A, version = 150) public int sceKernelReferSystemStatus(TPointer statusPtr) { SceKernelSystemStatus status = new SceKernelSystemStatus(); status.read(statusPtr); status.status = 0; status.write(statusPtr); return 0; } /** Write uid's to buffer * return written count * save full count to idcount_addr */ @HLEFunction(nid = 0x94416130, version = 150, checkInsideInterrupt = true) public int sceKernelGetThreadmanIdList(int type, TPointer32 readBufPtr, int readBufSize, TPointer32 idCountPtr) { if (type != SCE_KERNEL_TMID_Thread) { log.warn(String.format("UNIMPLEMENTED:sceKernelGetThreadmanIdList type=%d", type)); idCountPtr.setValue(0); return 0; } int saveCount = 0; int fullCount = 0; for (SceKernelThreadInfo thread : threadMap.values()) { // Hide kernel mode threads when called from a user mode thread if (userThreadCalledKernelCurrentThread(thread)) { if (saveCount < readBufSize) { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelGetThreadmanIdList adding thread %s", thread)); } readBufPtr.setValue(saveCount << 2, thread.uid); saveCount++; } else { log.warn(String.format("sceKernelGetThreadmanIdList NOT adding thread %s (no more space)", thread)); } fullCount++; } } idCountPtr.setValue(fullCount); return 0; } @HLEFunction(nid = 0x57CF62DD, version = 150) public int sceKernelGetThreadmanIdType(int uid) { if (SceUidManager.checkUidPurpose(uid, "ThreadMan-thread", false)) { return SCE_KERNEL_TMID_Thread; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-sema", false)) { return SCE_KERNEL_TMID_Semaphore; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-eventflag", false)) { return SCE_KERNEL_TMID_EventFlag; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-Mbx", false)) { return SCE_KERNEL_TMID_Mbox; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-Vpl", false)) { return SCE_KERNEL_TMID_Vpl; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-Fpl", false)) { return SCE_KERNEL_TMID_Fpl; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-MsgPipe", false)) { return SCE_KERNEL_TMID_Mpipe; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-callback", false)) { return SCE_KERNEL_TMID_Callback; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-ThreadEventHandler", false)) { return SCE_KERNEL_TMID_ThreadEventHandler; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-Alarm", false)) { return SCE_KERNEL_TMID_Alarm; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-VTimer", false)) { return SCE_KERNEL_TMID_VTimer; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-Mutex", false)) { return SCE_KERNEL_TMID_Mutex; } if (SceUidManager.checkUidPurpose(uid, "ThreadMan-LwMutex", false)) { return SCE_KERNEL_TMID_LwMutex; } return SceKernelErrors.ERROR_KERNEL_ILLEGAL_ARGUMENT; } @HLEFunction(nid = 0x64D4540E, version = 150) public int sceKernelReferThreadProfiler() { // Can be safely ignored. Only valid in debug mode on a real PSP. return 0; } @HLEFunction(nid = 0x8218B4DD, version = 150) public int sceKernelReferGlobalProfiler() { // Can be safely ignored. Only valid in debug mode on a real PSP. return 0; } @HLEFunction(nid = 0x0DDCD2C9, version = 271, checkInsideInterrupt = true) public int sceKernelTryLockMutex(int uid, int count) { return Managers.mutex.sceKernelTryLockMutex(uid, count); } @HLEFunction(nid = 0x5BF4DD27, version = 271, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelLockMutexCB(int uid, int count, int timeout_addr) { return Managers.mutex.sceKernelLockMutexCB(uid, count, timeout_addr); } @HLEFunction(nid = 0x6B30100F, version = 271, checkInsideInterrupt = true) public int sceKernelUnlockMutex(int uid, int count) { return Managers.mutex.sceKernelUnlockMutex(uid, count); } @HLEFunction(nid = 0x87D9223C, version = 271) public int sceKernelCancelMutex(int uid, int newcount, @CanBeNull TPointer32 numWaitThreadAddr) { return Managers.mutex.sceKernelCancelMutex(uid, newcount, numWaitThreadAddr); } @HLEFunction(nid = 0xA9C2CB9A, version = 271) public int sceKernelReferMutexStatus(int uid, TPointer addr) { return Managers.mutex.sceKernelReferMutexStatus(uid, addr); } @HLEFunction(nid = 0xB011B11F, version = 271, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelLockMutex(int uid, int count, int timeout_addr) { return Managers.mutex.sceKernelLockMutex(uid, count, timeout_addr); } @HLEFunction(nid = 0xB7D098C6, version = 271, checkInsideInterrupt = true) public int sceKernelCreateMutex(@CanBeNull PspString name, int attr, int count, int option_addr) { return Managers.mutex.sceKernelCreateMutex(name, attr, count, option_addr); } @HLEFunction(nid = 0xF8170FBE, version = 271, checkInsideInterrupt = true) public int sceKernelDeleteMutex(int uid) { return Managers.mutex.sceKernelDeleteMutex(uid); } @HLEFunction(nid = 0x19CFF145, version = 150, checkInsideInterrupt = true) public int sceKernelCreateLwMutex(TPointer workAreaAddr, String name, int attr, int count, @CanBeNull TPointer option) { return Managers.lwmutex.sceKernelCreateLwMutex(workAreaAddr, name, attr, count, option); } @HLEUnimplemented @HLEFunction(nid = 0x1AF94D03, version = 380, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int sceKernelDonateWakeupThread() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x31327F19, version = 380, checkInsideInterrupt = true, checkDispatchThreadEnabled = true) public int ThreadManForUser_31327F19(int unkown1, int unknown2, int unknown3) { return 0; } @HLEFunction(nid = 0x4C145944, version = 380) public int sceKernelReferLwMutexStatusByID(int uid, TPointer addr) { return Managers.lwmutex.sceKernelReferLwMutexStatusByID(uid, addr); } @HLEFunction(nid = 0x60107536, version = 150, checkInsideInterrupt = true) public int sceKernelDeleteLwMutex(TPointer workAreaAddr) { return Managers.lwmutex.sceKernelDeleteLwMutex(workAreaAddr); } @HLEUnimplemented @HLEFunction(nid = 0x71040D5C, version = 380) public int ThreadManForUser_71040D5C() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x7CFF8CF3, version = 380) public int ThreadManForUser_7CFF8CF3() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0xBEED3A47, version = 380) public int ThreadManForUser_BEED3A47() { return 0; } @HLEFunction(nid = 0xBC80EC7C, version = 620, checkInsideInterrupt = true) public int sceKernelExtendThreadStack(CpuState cpu, @CheckArgument("checkStackSize") int size, TPointer entryAddr, int entryParameter) { // sceKernelExtendThreadStack executes the code at entryAddr using a larger // stack. The entryParameter is passed as the only parameter ($a0) to // the code at entryAddr. // When the code at entryAddr returns, sceKernelExtendThreadStack also returns // with the return value of entryAddr. SceKernelThreadInfo thread = getCurrentThread(); int extendedStackAddr = thread.extendStack(size); IAction afterAction = new AfterSceKernelExtendThreadStackAction(thread, cpu.pc, cpu._sp, cpu._ra); cpu._a0 = entryParameter; cpu._sp = extendedStackAddr + size; callAddress(entryAddr.getAddress(), afterAction, false); return 0; } @HLEFunction(nid = 0xBC31C1B9, version = 150, checkInsideInterrupt = true) public int sceKernelExtendKernelStack(CpuState cpu, @CheckArgument("checkStackSize") int size, TPointer entryAddr, int entryParameter) { // sceKernelExtendKernelStack executes the code at entryAddr using a larger // stack. The entryParameter is passed as the only parameter ($a0) to // the code at entryAddr. // When the code at entryAddr returns, sceKernelExtendKernelStack also returns // with the return value of entryAddr. SceKernelThreadInfo thread = getCurrentThread(); int extendedStackAddr = thread.extendStack(size); IAction afterAction = new AfterSceKernelExtendThreadStackAction(thread, cpu.pc, cpu._sp, cpu._ra); cpu._a0 = entryParameter; cpu._sp = extendedStackAddr + size; callAddress(entryAddr.getAddress(), afterAction, false); return 0; } @HLEFunction(nid = 0x8DAFF657, version = 620) public int sceKernelCreateTlspl(PspString name, int partitionId, int attr, int blockSize, int numberBlocks, @CanBeNull TPointer optionsAddr) { int alignment = 0; if (optionsAddr.isNotNull()) { int length = optionsAddr.getValue32(0); if (length >= 8) { alignment = optionsAddr.getValue32(4); } } int alignedBlockSize = alignUp(blockSize, 3); if (alignment != 0) { if ((alignment & (alignment - 1)) != 0) { return SceKernelErrors.ERROR_KERNEL_ILLEGAL_ARGUMENT; } alignment = Math.max(alignment, 4); alignedBlockSize = alignUp(alignedBlockSize, alignment - 1); } SceKernelTls tls = new SceKernelTls(name.getString(), partitionId, attr, blockSize, alignedBlockSize, numberBlocks, alignment); if (tls.getBaseAddress() == 0) { return SceKernelErrors.ERROR_OUT_OF_MEMORY; } tlsMap.put(tls.uid, tls); if (log.isDebugEnabled()) { log.debug(String.format("sceKernelCreateTlspl returning 0x%X, baseAddress=0x%08X", tls.uid, tls.getBaseAddress())); } return tls.uid; } @HLEFunction(nid = 0x32BF938E, version = 620) public int sceKernelDeleteTlspl(int uid) { SceKernelTls tls = tlsMap.remove(uid); if (tls == null) { return -1; } tls.free(); return 0; } @HLEUnimplemented @HLEFunction(nid = 0x4A719FB2, version = 620) public int sceKernelFreeTlspl() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x65F54FFB, version = 620) public int _sceKernelAllocateTlspl() { return 0; } @HLEUnimplemented @HLEFunction(nid = 0x721067F3, version = 620) public int sceKernelReferTlsplStatus() { return 0; } /* * Suspend all user mode threads in the system. */ @HLEFunction(nid = 0x8FD9F70C, version = 150) public int sceKernelSuspendAllUserThreads() { for (SceKernelThreadInfo thread : threadMap.values()) { if (thread != currentThread && thread.isUserMode()) { if (log.isDebugEnabled()) { log.debug(String.format("sceKernelSuspendAllUserThreads suspending %s", thread)); } if (thread.isWaiting()) { hleChangeThreadState(thread, PSP_THREAD_WAITING_SUSPEND); } else { hleChangeThreadState(thread, PSP_THREAD_SUSPEND); } } } return 0; } @HLEFunction(nid = 0xF6427665, version = 150) public int sceKernelGetUserLevel() { return 4; } }