/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.HLE.kernel.managers;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_MESSAGEBOX_NO_MESSAGE;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_MESSAGE_BOX;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_CANCELLED;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_DELETE;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_STATUS_RELEASED;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_TIMEOUT;
import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_THREAD_READY;
import static jpcsp.HLE.kernel.types.SceKernelThreadInfo.PSP_WAIT_MBX;
import java.util.HashMap;
import java.util.Iterator;
import jpcsp.Memory;
import jpcsp.HLE.Modules;
import jpcsp.HLE.SceKernelErrorException;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.TPointer32;
import jpcsp.HLE.kernel.types.IWaitStateChecker;
import jpcsp.HLE.kernel.types.SceKernelMbxInfo;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.ThreadWaitInfo;
import jpcsp.HLE.modules.ThreadManForUser;
import org.apache.log4j.Logger;
public class MbxManager {
protected static Logger log = Modules.getLogger("ThreadManForUser");
private HashMap<Integer, SceKernelMbxInfo> mbxMap;
private MbxWaitStateChecker mbxWaitStateChecker;
public final static int PSP_MBX_ATTR_FIFO = 0;
public final static int PSP_MBX_ATTR_PRIORITY = 0x100;
private final static int PSP_MBX_ATTR_MSG_FIFO = 0; // Add new messages by FIFO.
private final static int PSP_MBX_ATTR_MSG_PRIORITY = 0x400; // Add new messages by MsgPacket priority.
public void reset() {
mbxMap = new HashMap<Integer, SceKernelMbxInfo>();
mbxWaitStateChecker = new MbxWaitStateChecker();
}
private boolean removeWaitingThread(SceKernelThreadInfo thread) {
SceKernelMbxInfo info = mbxMap.get(thread.wait.Mbx_id);
if (info == null) {
return false;
}
info.threadWaitingList.removeWaitingThread(thread);
return true;
}
public void onThreadWaitTimeout(SceKernelThreadInfo thread) {
if (removeWaitingThread(thread)) {
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_TIMEOUT;
} else {
log.warn("Mbx deleted while we were waiting for it! (timeout expired)");
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE;
}
}
public void onThreadWaitReleased(SceKernelThreadInfo thread) {
// Untrack
if (removeWaitingThread(thread)) {
// Return ERROR_WAIT_STATUS_RELEASED
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_STATUS_RELEASED;
} else {
log.warn("EventFlag deleted while we were waiting for it!");
// Return WAIT_DELETE
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE;
}
}
public void onThreadDeleted(SceKernelThreadInfo thread) {
if (thread.isWaitingForType(PSP_WAIT_MBX)) {
removeWaitingThread(thread);
}
}
private void onMbxDeletedCancelled(int mbxid, int result) {
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
boolean reschedule = false;
for (Iterator<SceKernelThreadInfo> it = threadMan.iterator(); it.hasNext();) {
SceKernelThreadInfo thread = it.next();
if (thread.isWaitingFor(PSP_WAIT_MBX, mbxid)) {
thread.cpuContext._v0 = result;
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
reschedule = true;
}
}
// Reschedule only if threads waked up.
if (reschedule) {
threadMan.hleRescheduleCurrentThread();
}
}
private void onMbxDeleted(int mbxid) {
onMbxDeletedCancelled(mbxid, ERROR_KERNEL_WAIT_DELETE);
}
private void onMbxCancelled(int mbxid) {
onMbxDeletedCancelled(mbxid, ERROR_KERNEL_WAIT_CANCELLED);
}
public int checkMbxID(int uid) {
if (!mbxMap.containsKey(uid)) {
log.warn(String.format("checkMbxID unknown uid=0x%X", uid));
throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_MESSAGE_BOX);
}
return uid;
}
public int sceKernelCreateMbx(String name, int attr, TPointer option) {
if (option.isNotNull()) {
int optionSize = option.getValue32();
log.warn(String.format("sceKernelCreateMbx option at %s: size=%d", option, optionSize));
}
SceKernelMbxInfo info = new SceKernelMbxInfo(name, attr);
if (log.isDebugEnabled()) {
log.debug(String.format("sceKernelCreateMbx returning %s", info));
}
mbxMap.put(info.uid, info);
return info.uid;
}
public int sceKernelDeleteMbx(int uid) {
mbxMap.remove(uid);
onMbxDeleted(uid);
return 0;
}
public int sceKernelSendMbx(int uid, TPointer msgAddr) {
SceKernelMbxInfo info = mbxMap.get(uid);
boolean msgConsumed = false;
// If the Mbx is empty, check if some thread is already waiting.
// If a thread is already waiting, do not update the msg "nextMsgPacketAddr" field.
if (!info.hasMessage()) {
SceKernelThreadInfo thread = info.threadWaitingList.getFirstWaitingThread();
if (thread != null) {
if (log.isDebugEnabled()) {
log.debug(String.format("sceKernelSendMbx waking thread %s", thread));
}
thread.wait.Mbx_resultAddr.setValue(msgAddr.getAddress());
info.threadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
threadMan.hleRescheduleCurrentThread();
msgConsumed = true;
}
}
// Add the message if it has not yet been consumed by a waiting thread
if (!msgConsumed) {
if ((info.attr & PSP_MBX_ATTR_MSG_PRIORITY) == PSP_MBX_ATTR_MSG_FIFO) {
info.addMsg(msgAddr.getMemory(), msgAddr.getAddress());
} else if ((info.attr & PSP_MBX_ATTR_MSG_PRIORITY) == PSP_MBX_ATTR_MSG_PRIORITY) {
info.addMsgByPriority(msgAddr.getMemory(), msgAddr.getAddress());
}
}
return 0;
}
private int hleKernelReceiveMbx(int uid, TPointer32 addrMsgAddr, TPointer32 timeoutAddr, boolean doCallbacks, boolean poll) {
SceKernelMbxInfo info = mbxMap.get(uid);
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (!info.hasMessage()) {
if (!poll) {
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelReceiveMbx - %s (waiting)", info));
}
SceKernelThreadInfo currentThread = threadMan.getCurrentThread();
info.threadWaitingList.addWaitingThread(currentThread);
currentThread.wait.Mbx_id = uid;
currentThread.wait.Mbx_resultAddr = addrMsgAddr;
threadMan.hleKernelThreadEnterWaitState(PSP_WAIT_MBX, uid, mbxWaitStateChecker, timeoutAddr.getAddress(), doCallbacks);
} else {
if (log.isDebugEnabled()) {
log.debug("hleKernelReceiveMbx has no messages.");
}
return ERROR_KERNEL_MESSAGEBOX_NO_MESSAGE;
}
} else {
// Success, do not reschedule the current thread.
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelReceiveMbx - %s fast check succeeded", info));
}
int msgAddr = info.removeMsg(Memory.getInstance());
addrMsgAddr.setValue(msgAddr);
}
return 0;
}
public int sceKernelReceiveMbx(int uid, TPointer32 addrMsgAddr, TPointer32 timeoutAddr) {
return hleKernelReceiveMbx(uid, addrMsgAddr, timeoutAddr, false, false);
}
public int sceKernelReceiveMbxCB(int uid, TPointer32 addrMsgAddr, TPointer32 timeoutAddr) {
return hleKernelReceiveMbx(uid, addrMsgAddr, timeoutAddr, true, false);
}
public int sceKernelPollMbx(int uid, TPointer32 addrMsgAddr) {
return hleKernelReceiveMbx(uid, addrMsgAddr, TPointer32.NULL, false, true);
}
public int sceKernelCancelReceiveMbx(int uid, TPointer32 pnumAddr) {
SceKernelMbxInfo info = mbxMap.get(uid);
pnumAddr.setValue(info.getNumWaitThreads());
info.threadWaitingList.removeAllWaitingThreads();
onMbxCancelled(uid);
return 0;
}
public int sceKernelReferMbxStatus(int uid, TPointer infoAddr) {
SceKernelMbxInfo info = mbxMap.get(uid);
info.write(infoAddr);
return 0;
}
private class MbxWaitStateChecker implements IWaitStateChecker {
@Override
public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) {
// Check if the thread has to continue its wait state or if the mbx
// has received a new message during the callback execution.
SceKernelMbxInfo info = mbxMap.get(wait.Mbx_id);
if (info == null) {
thread.cpuContext._v0 = ERROR_KERNEL_NOT_FOUND_MESSAGE_BOX;
return false;
}
// Check the mbx for a new message.
if (info.hasMessage()) {
Memory mem = Memory.getInstance();
int msgAddr = info.removeMsg(mem);
wait.Mbx_resultAddr.setValue(msgAddr);
info.threadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
return false;
}
return true;
}
}
public static final MbxManager singleton = new MbxManager();
private MbxManager() {
}
}