/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.HLE.kernel.managers;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ILLEGAL_SIZE;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_MESSAGE_PIPE_EMPTY;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_MESSAGE_PIPE_FULL;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_MESSAGE_PIPE;
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_MSGPIPE;
import static jpcsp.HLE.modules.SysMemUserForUser.PSP_SMEM_High;
import static jpcsp.HLE.modules.SysMemUserForUser.PSP_SMEM_Low;
import java.util.HashMap;
import java.util.Iterator;
import jpcsp.HLE.Modules;
import jpcsp.HLE.SceKernelErrorException;
import jpcsp.HLE.TPointer;
import jpcsp.HLE.TPointer32;
import jpcsp.HLE.kernel.types.IWaitStateChecker;
import jpcsp.HLE.kernel.types.SceKernelErrors;
import jpcsp.HLE.kernel.types.SceKernelMppInfo;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.ThreadWaitInfo;
import jpcsp.HLE.modules.ThreadManForUser;
import org.apache.log4j.Logger;
public class MsgPipeManager {
protected static Logger log = Modules.getLogger("ThreadManForUser");
private HashMap<Integer, SceKernelMppInfo> msgMap;
private MsgPipeSendWaitStateChecker msgPipeSendWaitStateChecker;
private MsgPipeReceiveWaitStateChecker msgPipeReceiveWaitStateChecker;
public static final int PSP_MPP_ATTR_SEND_FIFO = 0;
public static final int PSP_MPP_ATTR_SEND_PRIORITY = 0x100;
public static final int PSP_MPP_ATTR_RECEIVE_FIFO = 0;
public static final int PSP_MPP_ATTR_RECEIVE_PRIORITY = 0x1000;
private final static int PSP_MPP_ATTR_ADDR_HIGH = 0x4000;
public static final int PSP_MPP_WAIT_MODE_COMPLETE = 0; // receive always a complete buffer
public static final int PSP_MPP_WAIT_MODE_PARTIAL = 1; // can receive a partial buffer
public void reset() {
msgMap = new HashMap<Integer, SceKernelMppInfo>();
msgPipeSendWaitStateChecker = new MsgPipeSendWaitStateChecker();
msgPipeReceiveWaitStateChecker = new MsgPipeReceiveWaitStateChecker();
}
private boolean removeWaitingThread(SceKernelThreadInfo thread) {
SceKernelMppInfo info = msgMap.get(thread.wait.MsgPipe_id);
if (info == null) {
return false;
}
if (thread.wait.MsgPipe_isSend) {
info.sendThreadWaitingList.removeWaitingThread(thread);
} else {
info.receiveThreadWaitingList.removeWaitingThread(thread);
}
return true;
}
public void onThreadWaitTimeout(SceKernelThreadInfo thread) {
// Untrack
if (removeWaitingThread(thread)) {
// Return WAIT_TIMEOUT
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_TIMEOUT;
} else {
log.warn("MsgPipe deleted while we were waiting for it! (timeout expired)");
// Return WAIT_DELETE
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE;
}
}
public void onThreadWaitReleased(SceKernelThreadInfo thread) {
// Untrack
if (removeWaitingThread(thread)) {
// Return ERROR_WAIT_STATUS_RELEASED
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_STATUS_RELEASED;
} else {
log.warn("EventFlag deleted while we were waiting for it!");
// Return WAIT_DELETE
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE;
}
}
public void onThreadDeleted(SceKernelThreadInfo thread) {
removeWaitingThread(thread);
}
private void onMsgPipeDeletedCancelled(int msgpid, 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_MSGPIPE, msgpid)) {
thread.cpuContext._v0 = result;
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
reschedule = true;
}
}
// Reschedule only if threads waked up.
if (reschedule) {
threadMan.hleRescheduleCurrentThread();
}
}
private void onMsgPipeDeleted(int msgpid) {
onMsgPipeDeletedCancelled(msgpid, ERROR_KERNEL_WAIT_DELETE);
}
private void onMsgPipeCancelled(int msgpid) {
onMsgPipeDeletedCancelled(msgpid, ERROR_KERNEL_WAIT_CANCELLED);
}
private void onMsgPipeSendModified(SceKernelMppInfo info) {
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
boolean reschedule = false;
SceKernelThreadInfo checkedThread = null;
while (true) {
SceKernelThreadInfo thread = info.sendThreadWaitingList.getNextWaitingThread(checkedThread);
if (thread == null) {
break;
}
if (thread.wait.MsgPipe_isSend && trySendMsgPipe(info, thread.wait.MsgPipe_address, thread.wait.MsgPipe_size, thread.wait.MsgPipe_waitMode, thread.wait.MsgPipe_resultSize_addr)) {
if (log.isDebugEnabled()) {
log.debug(String.format("onMsgPipeSendModified waking thread %s", thread));
}
info.sendThreadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
reschedule = true;
} else {
checkedThread = thread;
}
}
// Reschedule only if threads waked up.
if (reschedule) {
threadMan.hleRescheduleCurrentThread();
}
}
private void onMsgPipeReceiveModified(SceKernelMppInfo info) {
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
boolean reschedule = false;
SceKernelThreadInfo checkedThread = null;
while (true) {
SceKernelThreadInfo thread = info.receiveThreadWaitingList.getNextWaitingThread(checkedThread);
if (thread == null) {
break;
}
if (!thread.wait.MsgPipe_isSend && tryReceiveMsgPipe(info, thread.wait.MsgPipe_address, thread.wait.MsgPipe_size, thread.wait.MsgPipe_waitMode, thread.wait.MsgPipe_resultSize_addr)) {
if (log.isDebugEnabled()) {
log.debug(String.format("onMsgPipeReceiveModified waking thread %s", thread));
}
info.receiveThreadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
reschedule = true;
} else {
checkedThread = thread;
}
}
// Reschedule only if threads waked up.
if (reschedule) {
threadMan.hleRescheduleCurrentThread();
}
}
private boolean trySendMsgPipe(SceKernelMppInfo info, TPointer addr, int size, int waitMode, TPointer32 resultSizeAddr) {
if (size > 0) {
// When the bufSize is 0, the data is transfered directly
// from the sender to the receiver without being buffered.
if (info.bufSize == 0) {
info.setUserData(addr.getAddress(), size);
onMsgPipeReceiveModified(info);
if (info.getUserSize() > 0) {
// wait if nothing has been sent or
// if we have to wait to send everything
if (size == info.getUserSize() || waitMode == PSP_MPP_WAIT_MODE_COMPLETE) {
return false;
}
}
} else {
int availableSize = info.availableWriteSize();
if (availableSize == 0) {
return false;
}
// Trying to send more than available?
if (size > availableSize) {
// Do we need to send the complete size?
if (waitMode == PSP_MPP_WAIT_MODE_COMPLETE) {
return false;
}
// We can just send the available size.
size = availableSize;
}
info.append(addr.getMemory(), addr.getAddress(), size);
}
}
resultSizeAddr.setValue(size);
return true;
}
private boolean tryReceiveMsgPipe(SceKernelMppInfo info, TPointer addr, int size, int waitMode, TPointer32 resultSizeAddr) {
if (size > 0) {
int availableSize = info.availableReadSize();
if (availableSize == 0) {
return false;
}
// Trying to receive more than available?
if (size > availableSize) {
// Do we need to receive the complete size?
if (waitMode == PSP_MPP_WAIT_MODE_COMPLETE) {
return false;
}
// We can just receive the available size.
size = availableSize;
}
info.consume(addr.getMemory(), addr.getAddress(), size);
if (info.bufSize == 0 && info.availableReadSize() == 0 && info.getNumSendWaitThreads() > 0) {
SceKernelThreadInfo thread = info.sendThreadWaitingList.getFirstWaitingThread();
if (thread.wait.MsgPipe_isSend) {
if (log.isDebugEnabled()) {
log.debug(String.format("tryReceiveMsgPipe waking thread %s", thread));
}
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
info.sendThreadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
threadMan.hleRescheduleCurrentThread();
}
}
}
resultSizeAddr.setValue(size);
return true;
}
public int checkMsgPipeID(int uid) {
if (!msgMap.containsKey(uid)) {
log.warn(String.format("checkMsgPipeID unknown uid=0x%X", uid));
throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_MESSAGE_PIPE);
}
return uid;
}
public SceKernelMppInfo getMsgPipeInfo(int uid) {
return msgMap.get(uid);
}
public int sceKernelCreateMsgPipe(String name, int partitionid, int attr, int size, TPointer option) {
if (option.isNotNull()) {
int optionSize = option.getValue32();
log.warn(String.format("sceKernelCreateMsgPipe option at %s, size=%d", option, optionSize));
}
int memType = PSP_SMEM_Low;
if ((attr & PSP_MPP_ATTR_ADDR_HIGH) == PSP_MPP_ATTR_ADDR_HIGH) {
memType = PSP_SMEM_High;
}
SceKernelMppInfo info = new SceKernelMppInfo(name, partitionid, attr, size, memType);
if (!info.isMemoryAllocated()) {
log.warn(String.format("sceKernelCreateMsgPipe name='%s', partitionId=%d, attr=0x%X, size=0x%X, option=%s not enough memory", name, partitionid, attr, size, option));
info.delete();
return SceKernelErrors.ERROR_KERNEL_NO_MEMORY;
}
if (log.isDebugEnabled()) {
log.debug(String.format("sceKernelCreateMsgPipe returning %s", info));
}
msgMap.put(info.uid, info);
return info.uid;
}
public int sceKernelDeleteMsgPipe(int uid) {
SceKernelMppInfo info = msgMap.remove(uid);
info.delete();
onMsgPipeDeleted(uid);
return 0;
}
public int hleKernelSendMsgPipe(int uid, TPointer msgAddr, int size, int waitMode, TPointer32 resultSizeAddr, TPointer32 timeoutAddr, boolean doCallbacks, boolean poll) {
SceKernelMppInfo info = msgMap.get(uid);
if (info.bufSize != 0 && size > info.bufSize) {
log.warn(String.format("hleKernelSendMsgPipe illegal size 0x%X max 0x%X", size, info.bufSize));
return ERROR_KERNEL_ILLEGAL_SIZE;
}
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (!trySendMsgPipe(info, msgAddr, size, waitMode, resultSizeAddr)) {
if (!poll) {
// Failed, but it's ok, just wait a little
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelSendMsgPipe %s waiting for 0x%X bytes to become available", info, size));
}
SceKernelThreadInfo currentThread = threadMan.getCurrentThread();
info.sendThreadWaitingList.addWaitingThread(currentThread);
// Wait on a specific MsgPipe.
currentThread.wait.MsgPipe_isSend = true;
currentThread.wait.MsgPipe_id = uid;
currentThread.wait.MsgPipe_address = msgAddr;
currentThread.wait.MsgPipe_size = size;
currentThread.wait.MsgPipe_resultSize_addr = resultSizeAddr;
threadMan.hleKernelThreadEnterWaitState(PSP_WAIT_MSGPIPE, uid, msgPipeSendWaitStateChecker, timeoutAddr.getAddress(), doCallbacks);
} else {
log.warn(String.format("hleKernelSendMsgPipe illegal size 0x%X, max 0x%X (pipe needs consuming)", size, info.freeSize));
return ERROR_KERNEL_MESSAGE_PIPE_FULL;
}
} else {
// Success, do not reschedule the current thread.
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelSendMsgPipe %s fast check succeeded", info));
}
onMsgPipeReceiveModified(info);
}
return 0;
}
public int sceKernelSendMsgPipe(int uid, TPointer msgAddr, int size, int waitMode, TPointer32 resultSizeAddr, TPointer32 timeoutAddr) {
return hleKernelSendMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr, timeoutAddr, false, false);
}
public int sceKernelSendMsgPipeCB(int uid, TPointer msgAddr, int size, int waitMode, TPointer32 resultSizeAddr, TPointer32 timeoutAddr) {
return hleKernelSendMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr, timeoutAddr, true, false);
}
public int sceKernelTrySendMsgPipe(int uid, TPointer msgAddr, int size, int waitMode, TPointer32 resultSizeAddr) {
return hleKernelSendMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr, TPointer32.NULL, false, true);
}
private int hleKernelReceiveMsgPipe(int uid, TPointer msgAddr, int size, int waitMode, TPointer32 resultSizeAddr, TPointer32 timeoutAddr, boolean doCallbacks, boolean poll) {
SceKernelMppInfo info = msgMap.get(uid);
if (info.bufSize != 0 && size > info.bufSize) {
log.warn(String.format("hleKernelReceiveMsgPipe illegal size 0x%X, max 0x%X", size, info.bufSize));
return ERROR_KERNEL_ILLEGAL_SIZE;
}
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (!tryReceiveMsgPipe(info, msgAddr, size, waitMode, resultSizeAddr)) {
if (!poll) {
// Failed, but it's ok, just wait a little
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelReceiveMsgPipe %s waiting for 0x%X bytes to become available", info, size));
}
SceKernelThreadInfo currentThread = threadMan.getCurrentThread();
info.receiveThreadWaitingList.addWaitingThread(currentThread);
// Wait on a specific MsgPipe.
currentThread.wait.MsgPipe_isSend = false;
currentThread.wait.MsgPipe_id = uid;
currentThread.wait.MsgPipe_address = msgAddr;
currentThread.wait.MsgPipe_size = size;
currentThread.wait.MsgPipe_resultSize_addr = resultSizeAddr;
threadMan.hleKernelThreadEnterWaitState(PSP_WAIT_MSGPIPE, uid, msgPipeReceiveWaitStateChecker, timeoutAddr.getAddress(), doCallbacks);
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelReceiveMsgPipe trying to read more than is available size 0x%X, available 0x%X", size, info.bufSize - info.freeSize));
}
return ERROR_KERNEL_MESSAGE_PIPE_EMPTY;
}
} else {
// Success, do not reschedule the current thread.
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelReceiveMsgPipe %s fast check succeeded", info));
}
onMsgPipeSendModified(info);
}
return 0;
}
public int sceKernelReceiveMsgPipe(int uid, TPointer msgAddr, int size, int waitMode, TPointer32 resultSizeAddr, TPointer32 timeoutAddr) {
return hleKernelReceiveMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr, timeoutAddr, false, false);
}
public int sceKernelReceiveMsgPipeCB(int uid, TPointer msgAddr, int size, int waitMode, TPointer32 resultSizeAddr, TPointer32 timeoutAddr) {
return hleKernelReceiveMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr, timeoutAddr, true, false);
}
public int sceKernelTryReceiveMsgPipe(int uid, TPointer msgAddr, int size, int waitMode, TPointer32 resultSizeAddr) {
return hleKernelReceiveMsgPipe(uid, msgAddr, size, waitMode, resultSizeAddr, TPointer32.NULL, false, true);
}
public int sceKernelCancelMsgPipe(int uid, TPointer32 sendAddr, TPointer32 recvAddr) {
SceKernelMppInfo info = msgMap.get(uid);
sendAddr.setValue(info.getNumSendWaitThreads());
recvAddr.setValue(info.getNumReceiveWaitThreads());
info.sendThreadWaitingList.removeAllWaitingThreads();
info.receiveThreadWaitingList.removeAllWaitingThreads();
onMsgPipeCancelled(uid);
return 0;
}
public int sceKernelReferMsgPipeStatus(int uid, TPointer infoAddr) {
SceKernelMppInfo info = msgMap.get(uid);
info.write(infoAddr);
return 0;
}
private class MsgPipeSendWaitStateChecker implements IWaitStateChecker {
@Override
public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) {
// Check if the thread has to continue its wait state or if the msgpipe
// has received a new message during the callback execution.
SceKernelMppInfo info = msgMap.get(wait.MsgPipe_id);
if (info == null) {
thread.cpuContext._v0 = ERROR_KERNEL_NOT_FOUND_MESSAGE_PIPE;
return false;
}
if (trySendMsgPipe(info, thread.wait.MsgPipe_address, thread.wait.MsgPipe_size, thread.wait.MsgPipe_waitMode, thread.wait.MsgPipe_resultSize_addr)) {
info.sendThreadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
return false;
}
return true;
}
}
private class MsgPipeReceiveWaitStateChecker implements IWaitStateChecker {
@Override
public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) {
// Check if the thread has to continue its wait state or if the msgpipe
// has been sent a new message during the callback execution.
SceKernelMppInfo info = msgMap.get(wait.MsgPipe_id);
if (info == null) {
thread.cpuContext._v0 = ERROR_KERNEL_NOT_FOUND_MESSAGE_PIPE;
return false;
}
if (tryReceiveMsgPipe(info, wait.MsgPipe_address, wait.MsgPipe_size, wait.MsgPipe_waitMode, wait.MsgPipe_resultSize_addr)) {
info.receiveThreadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
return false;
}
return true;
}
}
public static final MsgPipeManager singleton = new MsgPipeManager();
private MsgPipeManager() {
}
}