/*
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_EVENT_FLAG_ILLEGAL_WAIT_PATTERN;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_EVENT_FLAG_NO_MULTI_PERM;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_EVENT_FLAG_POLL_FAILED;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_ILLEGAL_MODE;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_NOT_FOUND_EVENT_FLAG;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_CANCELLED;
import static jpcsp.HLE.kernel.types.SceKernelErrors.ERROR_KERNEL_WAIT_CAN_NOT_WAIT;
import static jpcsp.HLE.kernel.types.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_EVENTFLAG;
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.SceKernelEventFlagInfo;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.kernel.types.ThreadWaitInfo;
import jpcsp.HLE.modules.ThreadManForUser;
import org.apache.log4j.Logger;
public class EventFlagManager {
protected static Logger log = Modules.getLogger("ThreadManForUser");
private static HashMap<Integer, SceKernelEventFlagInfo> eventMap;
private EventFlagWaitStateChecker eventFlagWaitStateChecker;
protected final static int PSP_EVENT_WAITSINGLE = 0;
protected final static int PSP_EVENT_WAITMULTIPLE = 0x200;
protected final static int PSP_EVENT_WAITANDOR_MASK = 0x01;
protected final static int PSP_EVENT_WAITAND = 0x00;
protected final static int PSP_EVENT_WAITOR = 0x01;
protected final static int PSP_EVENT_WAITCLEARALL = 0x10;
protected final static int PSP_EVENT_WAITCLEAR = 0x20;
public void reset() {
eventMap = new HashMap<Integer, SceKernelEventFlagInfo>();
eventFlagWaitStateChecker = new EventFlagWaitStateChecker();
}
/** Don't call this unless thread.waitType == PSP_WAIT_EVENTFLAG
* @return true if the thread was waiting on a valid event flag */
private boolean removeWaitingThread(SceKernelThreadInfo thread) {
SceKernelEventFlagInfo event = eventMap.get(thread.wait.EventFlag_id);
if (event == null) {
return false;
}
event.threadWaitingList.removeWaitingThread(thread);
// Store the currentPattern at the outBits address, even in case of error
thread.wait.EventFlag_outBits_addr.setValue(event.currentPattern);
return true;
}
/** Don't call this unless thread.wait.waitingOnEventFlag == true */
public void onThreadWaitTimeout(SceKernelThreadInfo thread) {
// Untrack
if (removeWaitingThread(thread)) {
// Return WAIT_TIMEOUT
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_TIMEOUT;
} else {
log.warn("EventFlag deleted while we were waiting for it! (timeout expired)");
// Return WAIT_DELETE
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE;
}
}
public void onThreadWaitReleased(SceKernelThreadInfo thread) {
// Untrack
if (removeWaitingThread(thread)) {
// Return ERROR_WAIT_STATUS_RELEASED
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_STATUS_RELEASED;
} else {
log.warn("EventFlag deleted while we were waiting for it!");
// Return WAIT_DELETE
thread.cpuContext._v0 = ERROR_KERNEL_WAIT_DELETE;
}
}
public void onThreadDeleted(SceKernelThreadInfo thread) {
if (thread.isWaitingForType(PSP_WAIT_EVENTFLAG)) {
// decrement numWaitThreads
removeWaitingThread(thread);
}
}
private void onEventFlagDeletedCancelled(int evid, 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_EVENTFLAG, evid)) {
thread.cpuContext._v0 = result;
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
reschedule = true;
}
}
// Reschedule only if threads waked up.
if (reschedule) {
threadMan.hleRescheduleCurrentThread();
}
}
private void onEventFlagDeleted(int evid) {
onEventFlagDeletedCancelled(evid, ERROR_KERNEL_WAIT_DELETE);
}
private void onEventFlagCancelled(int evid) {
onEventFlagDeletedCancelled(evid, ERROR_KERNEL_WAIT_CANCELLED);
}
private void onEventFlagModified(SceKernelEventFlagInfo event) {
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
boolean reschedule = false;
SceKernelThreadInfo checkedThread = null;
while (event.currentPattern != 0) {
SceKernelThreadInfo thread = event.threadWaitingList.getNextWaitingThread(checkedThread);
if (thread == null) {
break;
}
if (checkEventFlag(event, thread.wait.EventFlag_bits, thread.wait.EventFlag_wait, thread.wait.EventFlag_outBits_addr)) {
if (log.isDebugEnabled()) {
log.debug(String.format("onEventFlagModified waking thread %s", thread));
}
event.threadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
threadMan.hleChangeThreadState(thread, PSP_THREAD_READY);
reschedule = true;
} else {
checkedThread = thread;
}
}
// Reschedule only if threads waked up.
if (reschedule) {
threadMan.hleRescheduleCurrentThread();
}
}
private boolean checkEventFlag(SceKernelEventFlagInfo event, int bits, int wait, TPointer32 outBitsAddr) {
boolean matched = false;
if (((wait & PSP_EVENT_WAITANDOR_MASK) == PSP_EVENT_WAITAND) &&
((event.currentPattern & bits) == bits)) {
matched = true;
} else if (((wait & PSP_EVENT_WAITANDOR_MASK) == PSP_EVENT_WAITOR) &&
((event.currentPattern & bits) != 0)) {
matched = true;
}
if (matched) {
// Write current pattern.
outBitsAddr.setValue(event.currentPattern);
if (log.isDebugEnabled() && outBitsAddr.isNotNull()) {
log.debug(String.format("checkEventFlag returning outBits=0x%X at %s", outBitsAddr.getValue(), outBitsAddr));
}
if ((wait & PSP_EVENT_WAITCLEARALL) == PSP_EVENT_WAITCLEARALL) {
event.currentPattern = 0;
}
if ((wait & PSP_EVENT_WAITCLEAR) == PSP_EVENT_WAITCLEAR) {
event.currentPattern &= ~bits;
}
}
return matched;
}
public int checkEventFlagID(int uid) {
SceUidManager.checkUidPurpose(uid, "ThreadMan-eventflag", true);
if (!eventMap.containsKey(uid)) {
if (uid != 0) {
log.warn(String.format("checkEventFlagID unknown uid=0x%X", uid));
}
throw new SceKernelErrorException(ERROR_KERNEL_NOT_FOUND_EVENT_FLAG);
}
return uid;
}
public int sceKernelCreateEventFlag(String name, int attr, int initPattern, TPointer option) {
SceKernelEventFlagInfo event = new SceKernelEventFlagInfo(name, attr, initPattern, initPattern);
eventMap.put(event.uid, event);
return event.uid;
}
public int sceKernelDeleteEventFlag(int uid) {
SceKernelEventFlagInfo event = eventMap.remove(uid);
if (event.getNumWaitThreads() > 0) {
log.warn(String.format("sceKernelDeleteEventFlag numWaitThreads %d", event.getNumWaitThreads()));
}
onEventFlagDeleted(uid);
return 0;
}
public int sceKernelSetEventFlag(int uid, int bitsToSet) {
SceKernelEventFlagInfo event = eventMap.get(uid);
event.currentPattern |= bitsToSet;
onEventFlagModified(event);
return 0;
}
public int sceKernelClearEventFlag(int uid, int bitsToKeep) {
SceKernelEventFlagInfo event = eventMap.get(uid);
event.currentPattern &= bitsToKeep;
return 0;
}
public int hleKernelWaitEventFlag(int uid, int bits, int wait, TPointer32 outBitsAddr, TPointer32 timeoutAddr, boolean doCallbacks) {
if ((wait & ~(PSP_EVENT_WAITOR | PSP_EVENT_WAITCLEAR | PSP_EVENT_WAITCLEARALL)) != 0 ||
(wait & (PSP_EVENT_WAITCLEAR | PSP_EVENT_WAITCLEARALL)) == (PSP_EVENT_WAITCLEAR | PSP_EVENT_WAITCLEARALL)) {
return ERROR_KERNEL_ILLEGAL_MODE;
}
if (bits == 0) {
return ERROR_KERNEL_EVENT_FLAG_ILLEGAL_WAIT_PATTERN;
}
if (!Modules.ThreadManForUserModule.isDispatchThreadEnabled()) {
return ERROR_KERNEL_WAIT_CAN_NOT_WAIT;
}
SceKernelEventFlagInfo event = eventMap.get(uid);
if (event.getNumWaitThreads() >= 1 && (event.attr & PSP_EVENT_WAITMULTIPLE) != PSP_EVENT_WAITMULTIPLE) {
log.warn("hleKernelWaitEventFlag already another thread waiting on it");
return ERROR_KERNEL_EVENT_FLAG_NO_MULTI_PERM;
}
if (!checkEventFlag(event, bits, wait, outBitsAddr)) {
// Failed, but it's ok, just wait a little
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelWaitEventFlag - %s fast check failed", event));
}
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
SceKernelThreadInfo currentThread = threadMan.getCurrentThread();
event.threadWaitingList.addWaitingThread(currentThread);
// Wait on a specific event flag
currentThread.wait.EventFlag_id = uid;
currentThread.wait.EventFlag_bits = bits;
currentThread.wait.EventFlag_wait = wait;
currentThread.wait.EventFlag_outBits_addr = outBitsAddr;
threadMan.hleKernelThreadEnterWaitState(PSP_WAIT_EVENTFLAG, uid, eventFlagWaitStateChecker, timeoutAddr.getAddress(), doCallbacks);
} else {
// Success, do not reschedule the current thread.
if (log.isDebugEnabled()) {
log.debug(String.format("hleKernelWaitEventFlag - %s fast check succeeded", event));
}
}
return 0;
}
public int sceKernelWaitEventFlag(int uid, int bits, int wait, TPointer32 outBitsAddr, TPointer32 timeoutAddr) {
return hleKernelWaitEventFlag(uid, bits, wait, outBitsAddr, timeoutAddr, false);
}
public int sceKernelWaitEventFlagCB(int uid, int bits, int wait, TPointer32 outBitsAddr, TPointer32 timeoutAddr) {
return hleKernelWaitEventFlag(uid, bits, wait, outBitsAddr, timeoutAddr, true);
}
public int sceKernelPollEventFlag(int uid, int bits, int wait, TPointer32 outBitsAddr) {
if (bits == 0) {
return ERROR_KERNEL_EVENT_FLAG_ILLEGAL_WAIT_PATTERN;
}
SceKernelEventFlagInfo event = eventMap.get(uid);
if (!checkEventFlag(event, bits, wait, outBitsAddr)) {
// Write the outBits, even if the poll failed
outBitsAddr.setValue(event.currentPattern);
return ERROR_KERNEL_EVENT_FLAG_POLL_FAILED;
}
return 0;
}
public int sceKernelCancelEventFlag(int uid, int newPattern, TPointer32 numWaitThreadAddr) {
SceKernelEventFlagInfo event = eventMap.get(uid);
numWaitThreadAddr.setValue(event.getNumWaitThreads());
event.threadWaitingList.removeAllWaitingThreads();
event.currentPattern = newPattern;
onEventFlagCancelled(uid);
return 0;
}
public int sceKernelReferEventFlagStatus(int uid, TPointer addr) {
SceKernelEventFlagInfo event = eventMap.get(uid);
if (log.isDebugEnabled()) {
log.debug(String.format("sceKernelReferEventFlagStatus event=%s", event));
}
event.write(addr);
return 0;
}
private class EventFlagWaitStateChecker implements IWaitStateChecker {
@Override
public boolean continueWaitState(SceKernelThreadInfo thread, ThreadWaitInfo wait) {
// Check if the thread has to continue its wait state or if the event flag
// has been set during the callback execution.
SceKernelEventFlagInfo event = eventMap.get(wait.EventFlag_id);
if (event == null) {
thread.cpuContext._v0 = ERROR_KERNEL_NOT_FOUND_EVENT_FLAG;
return false;
}
// Check EventFlag.
if (checkEventFlag(event, wait.EventFlag_bits, wait.EventFlag_wait, wait.EventFlag_outBits_addr)) {
event.threadWaitingList.removeWaitingThread(thread);
thread.cpuContext._v0 = 0;
return false;
}
return true;
}
}
public static final EventFlagManager singleton = new EventFlagManager();
private EventFlagManager() {
}
}