|
|
@@ -5,6 +5,7 @@
|
|
|
#include <stdio.h>
|
|
|
|
|
|
#include <list>
|
|
|
+#include <algorithm>
|
|
|
#include <vector>
|
|
|
#include <map>
|
|
|
#include <string>
|
|
|
@@ -52,7 +53,14 @@ public:
|
|
|
* @return Result of operation, 0 on success, otherwise error code
|
|
|
*/
|
|
|
Result WaitSynchronization(bool* wait) {
|
|
|
- // TODO(bunnei): ImplementMe
|
|
|
+ if (status != THREADSTATUS_DORMANT) {
|
|
|
+ Handle thread = GetCurrentThreadHandle();
|
|
|
+ if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) {
|
|
|
+ waiting_threads.push_back(thread);
|
|
|
+ }
|
|
|
+ WaitCurrentThread(WAITTYPE_THREADEND, this->GetHandle());
|
|
|
+ *wait = true;
|
|
|
+ }
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
@@ -69,6 +77,9 @@ public:
|
|
|
s32 processor_id;
|
|
|
|
|
|
WaitType wait_type;
|
|
|
+ Handle wait_handle;
|
|
|
+
|
|
|
+ std::vector<Handle> waiting_threads;
|
|
|
|
|
|
char name[Kernel::MAX_NAME_LENGTH + 1];
|
|
|
};
|
|
|
@@ -82,7 +93,6 @@ Common::ThreadQueueList<Handle> g_thread_ready_queue;
|
|
|
Handle g_current_thread_handle;
|
|
|
Thread* g_current_thread;
|
|
|
|
|
|
-
|
|
|
/// Gets the current thread
|
|
|
inline Thread* GetCurrentThread() {
|
|
|
return g_current_thread;
|
|
|
@@ -114,15 +124,15 @@ void ResetThread(Thread* t, u32 arg, s32 lowest_priority) {
|
|
|
memset(&t->context, 0, sizeof(ThreadContext));
|
|
|
|
|
|
t->context.cpu_registers[0] = arg;
|
|
|
- t->context.pc = t->entry_point;
|
|
|
+ t->context.pc = t->context.cpu_registers[15] = t->entry_point;
|
|
|
t->context.sp = t->stack_top;
|
|
|
t->context.cpsr = 0x1F; // Usermode
|
|
|
|
|
|
if (t->current_priority < lowest_priority) {
|
|
|
t->current_priority = t->initial_priority;
|
|
|
}
|
|
|
-
|
|
|
t->wait_type = WAITTYPE_NONE;
|
|
|
+ t->wait_handle = 0;
|
|
|
}
|
|
|
|
|
|
/// Change a thread to "ready" state
|
|
|
@@ -142,6 +152,43 @@ void ChangeReadyState(Thread* t, bool ready) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Verify that a thread has not been released from waiting
|
|
|
+inline bool VerifyWait(const Handle& thread, WaitType type, Handle handle) {
|
|
|
+ Handle wait_id = 0;
|
|
|
+ Thread *t = g_object_pool.GetFast<Thread>(thread);
|
|
|
+ if (t) {
|
|
|
+ if (type == t->wait_type && handle == t->wait_handle) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ERROR_LOG(KERNEL, "thread 0x%08X does not exist", thread);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/// Stops the current thread
|
|
|
+void StopThread(Handle thread, const char* reason) {
|
|
|
+ u32 error;
|
|
|
+ Thread *t = g_object_pool.Get<Thread>(thread, error);
|
|
|
+ if (t) {
|
|
|
+ ChangeReadyState(t, false);
|
|
|
+ t->status = THREADSTATUS_DORMANT;
|
|
|
+ for (size_t i = 0; i < t->waiting_threads.size(); ++i) {
|
|
|
+ const Handle waiting_thread = t->waiting_threads[i];
|
|
|
+ if (VerifyWait(waiting_thread, WAITTYPE_THREADEND, thread)) {
|
|
|
+ ResumeThreadFromWait(waiting_thread);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ t->waiting_threads.clear();
|
|
|
+
|
|
|
+ // Stopped threads are never waiting.
|
|
|
+ t->wait_type = WAITTYPE_NONE;
|
|
|
+ t->wait_handle = 0;
|
|
|
+ } else {
|
|
|
+ ERROR_LOG(KERNEL, "thread 0x%08X does not exist", thread);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// Changes a threads state
|
|
|
void ChangeThreadState(Thread* t, ThreadStatus new_status) {
|
|
|
if (!t || t->status == new_status) {
|
|
|
@@ -152,7 +199,7 @@ void ChangeThreadState(Thread* t, ThreadStatus new_status) {
|
|
|
|
|
|
if (new_status == THREADSTATUS_WAIT) {
|
|
|
if (t->wait_type == WAITTYPE_NONE) {
|
|
|
- printf("ERROR: Waittype none not allowed here\n");
|
|
|
+ ERROR_LOG(KERNEL, "Waittype none not allowed");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -207,9 +254,10 @@ Thread* NextThread() {
|
|
|
}
|
|
|
|
|
|
/// Puts the current thread in the wait state for the given type
|
|
|
-void WaitCurrentThread(WaitType wait_type) {
|
|
|
+void WaitCurrentThread(WaitType wait_type, Handle wait_handle) {
|
|
|
Thread* t = GetCurrentThread();
|
|
|
t->wait_type = wait_type;
|
|
|
+ t->wait_handle = wait_handle;
|
|
|
ChangeThreadState(t, ThreadStatus(THREADSTATUS_WAIT | (t->status & THREADSTATUS_SUSPEND)));
|
|
|
}
|
|
|
|
|
|
@@ -225,6 +273,22 @@ void ResumeThreadFromWait(Handle handle) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+/// Prints the thread queue for debugging purposes
|
|
|
+void DebugThreadQueue() {
|
|
|
+ Thread* thread = GetCurrentThread();
|
|
|
+ if (!thread) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ INFO_LOG(KERNEL, "0x%02X 0x%08X (current)", thread->current_priority, GetCurrentThreadHandle());
|
|
|
+ for (u32 i = 0; i < g_thread_queue.size(); i++) {
|
|
|
+ Handle handle = g_thread_queue[i];
|
|
|
+ s32 priority = g_thread_ready_queue.contains(handle);
|
|
|
+ if (priority != -1) {
|
|
|
+ INFO_LOG(KERNEL, "0x%02X 0x%08X", priority, handle);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/// Creates a new thread
|
|
|
Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 priority,
|
|
|
s32 processor_id, u32 stack_top, int stack_size) {
|
|
|
@@ -233,12 +297,12 @@ Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 prio
|
|
|
"CreateThread priority=%d, outside of allowable range!", priority)
|
|
|
|
|
|
Thread* t = new Thread;
|
|
|
-
|
|
|
+
|
|
|
handle = Kernel::g_object_pool.Create(t);
|
|
|
-
|
|
|
+
|
|
|
g_thread_queue.push_back(handle);
|
|
|
g_thread_ready_queue.prepare(priority);
|
|
|
-
|
|
|
+
|
|
|
t->status = THREADSTATUS_DORMANT;
|
|
|
t->entry_point = entry_point;
|
|
|
t->stack_top = stack_top;
|
|
|
@@ -246,16 +310,18 @@ Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 prio
|
|
|
t->initial_priority = t->current_priority = priority;
|
|
|
t->processor_id = processor_id;
|
|
|
t->wait_type = WAITTYPE_NONE;
|
|
|
-
|
|
|
+ t->wait_handle = 0;
|
|
|
+
|
|
|
strncpy(t->name, name, Kernel::MAX_NAME_LENGTH);
|
|
|
t->name[Kernel::MAX_NAME_LENGTH] = '\0';
|
|
|
-
|
|
|
+
|
|
|
return t;
|
|
|
}
|
|
|
|
|
|
/// Creates a new thread - wrapper for external user
|
|
|
Handle CreateThread(const char* name, u32 entry_point, s32 priority, u32 arg, s32 processor_id,
|
|
|
u32 stack_top, int stack_size) {
|
|
|
+
|
|
|
if (name == NULL) {
|
|
|
ERROR_LOG(KERNEL, "CreateThread(): NULL name");
|
|
|
return -1;
|
|
|
@@ -289,7 +355,7 @@ Handle CreateThread(const char* name, u32 entry_point, s32 priority, u32 arg, s3
|
|
|
|
|
|
// This won't schedule to the new thread, but it may to one woken from eating cycles.
|
|
|
// Technically, this should not eat all at once, and reschedule in the middle, but that's hard.
|
|
|
- //HLE::Reschedule("thread created");
|
|
|
+ //HLE::Reschedule(__func__);
|
|
|
|
|
|
return handle;
|
|
|
}
|
|
|
@@ -363,35 +429,24 @@ Handle SetupMainThread(s32 priority, int stack_size) {
|
|
|
return handle;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/// Reschedules to the next available thread (call after current thread is suspended)
|
|
|
void Reschedule() {
|
|
|
Thread* prev = GetCurrentThread();
|
|
|
Thread* next = NextThread();
|
|
|
+ HLE::g_reschedule = false;
|
|
|
if (next > 0) {
|
|
|
INFO_LOG(KERNEL, "context switch 0x%08X -> 0x%08X", prev->GetHandle(), next->GetHandle());
|
|
|
-
|
|
|
+
|
|
|
SwitchContext(next);
|
|
|
|
|
|
- // Hack - automatically change previous thread (which would have been in "wait" state) to
|
|
|
- // "ready" state, so that we can immediately resume to it when new thread yields. FixMe to
|
|
|
- // actually wait for whatever event it is supposed to be waiting on.
|
|
|
-
|
|
|
- ChangeReadyState(prev, true);
|
|
|
- } else {
|
|
|
- INFO_LOG(KERNEL, "no ready threads, staying on 0x%08X", prev->GetHandle());
|
|
|
-
|
|
|
- // Hack - no other threads are available, so decrement current PC to the last instruction,
|
|
|
- // and then resume current thread. This should always be called on a blocking instruction
|
|
|
- // (e.g. svcWaitSynchronization), and the result should be that the instruction is repeated
|
|
|
- // until it no longer blocks.
|
|
|
-
|
|
|
- // TODO(bunnei): A better solution: Have the CPU switch to an idle thread
|
|
|
-
|
|
|
- ThreadContext ctx;
|
|
|
- SaveContext(ctx);
|
|
|
- ctx.pc -= 4;
|
|
|
- LoadContext(ctx);
|
|
|
- ChangeReadyState(prev, true);
|
|
|
+ // Hack - There is no mechanism yet to waken the primary thread if it has been put to sleep
|
|
|
+ // by a simulated VBLANK thread switch. So, we'll just immediately set it to "ready" again.
|
|
|
+ // This results in the current thread yielding on a VBLANK once, and then it will be
|
|
|
+ // immediately placed back in the queue for execution.
|
|
|
+ if (prev->wait_type == WAITTYPE_VBLANK) {
|
|
|
+ ResumeThreadFromWait(prev->GetHandle());
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|