diff options
Diffstat (limited to 'src/core/hle/kernel/thread.cpp')
| -rw-r--r-- | src/core/hle/kernel/thread.cpp | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp new file mode 100644 index 000000000..bf4c8353c --- /dev/null +++ b/src/core/hle/kernel/thread.cpp | |||
| @@ -0,0 +1,323 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project / PPSSPP Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <stdio.h> | ||
| 6 | |||
| 7 | #include <list> | ||
| 8 | #include <vector> | ||
| 9 | #include <map> | ||
| 10 | #include <string> | ||
| 11 | |||
| 12 | #include "common/common.h" | ||
| 13 | #include "common/thread_queue_list.h" | ||
| 14 | |||
| 15 | #include "core/core.h" | ||
| 16 | #include "core/mem_map.h" | ||
| 17 | #include "core/hle/hle.h" | ||
| 18 | #include "core/hle/svc.h" | ||
| 19 | #include "core/hle/kernel/kernel.h" | ||
| 20 | #include "core/hle/kernel/thread.h" | ||
| 21 | |||
| 22 | namespace Kernel { | ||
| 23 | |||
| 24 | class Thread : public Kernel::Object { | ||
| 25 | public: | ||
| 26 | |||
| 27 | const char* GetName() { return name; } | ||
| 28 | const char* GetTypeName() { return "Thread"; } | ||
| 29 | |||
| 30 | static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Thread; } | ||
| 31 | Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Thread; } | ||
| 32 | |||
| 33 | inline bool IsRunning() const { return (status & THREADSTATUS_RUNNING) != 0; } | ||
| 34 | inline bool IsStopped() const { return (status & THREADSTATUS_DORMANT) != 0; } | ||
| 35 | inline bool IsReady() const { return (status & THREADSTATUS_READY) != 0; } | ||
| 36 | inline bool IsWaiting() const { return (status & THREADSTATUS_WAIT) != 0; } | ||
| 37 | inline bool IsSuspended() const { return (status & THREADSTATUS_SUSPEND) != 0; } | ||
| 38 | |||
| 39 | ThreadContext context; | ||
| 40 | |||
| 41 | u32 status; | ||
| 42 | u32 entry_point; | ||
| 43 | u32 stack_top; | ||
| 44 | u32 stack_size; | ||
| 45 | |||
| 46 | s32 initial_priority; | ||
| 47 | s32 current_priority; | ||
| 48 | |||
| 49 | s32 processor_id; | ||
| 50 | |||
| 51 | WaitType wait_type; | ||
| 52 | |||
| 53 | char name[Kernel::MAX_NAME_LENGTH + 1]; | ||
| 54 | }; | ||
| 55 | |||
| 56 | // Lists all thread ids that aren't deleted/etc. | ||
| 57 | std::vector<Handle> g_thread_queue; | ||
| 58 | |||
| 59 | // Lists only ready thread ids. | ||
| 60 | Common::ThreadQueueList<Handle> g_thread_ready_queue; | ||
| 61 | |||
| 62 | Handle g_current_thread_handle; | ||
| 63 | Thread* g_current_thread; | ||
| 64 | |||
| 65 | |||
| 66 | /// Gets the current thread | ||
| 67 | inline Thread* GetCurrentThread() { | ||
| 68 | return g_current_thread; | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Gets the current thread handle | ||
| 72 | Handle GetCurrentThreadHandle() { | ||
| 73 | return GetCurrentThread()->GetHandle(); | ||
| 74 | } | ||
| 75 | |||
| 76 | /// Sets the current thread | ||
| 77 | inline void SetCurrentThread(Thread* t) { | ||
| 78 | g_current_thread = t; | ||
| 79 | g_current_thread_handle = t->GetHandle(); | ||
| 80 | } | ||
| 81 | |||
| 82 | /// Saves the current CPU context | ||
| 83 | void SaveContext(ThreadContext& ctx) { | ||
| 84 | Core::g_app_core->SaveContext(ctx); | ||
| 85 | } | ||
| 86 | |||
| 87 | /// Loads a CPU context | ||
| 88 | void LoadContext(ThreadContext& ctx) { | ||
| 89 | Core::g_app_core->LoadContext(ctx); | ||
| 90 | } | ||
| 91 | |||
| 92 | /// Resets a thread | ||
| 93 | void ResetThread(Thread* t, u32 arg, s32 lowest_priority) { | ||
| 94 | memset(&t->context, 0, sizeof(ThreadContext)); | ||
| 95 | |||
| 96 | t->context.cpu_registers[0] = arg; | ||
| 97 | t->context.pc = t->entry_point; | ||
| 98 | t->context.sp = t->stack_top; | ||
| 99 | t->context.cpsr = 0x1F; // Usermode | ||
| 100 | |||
| 101 | if (t->current_priority < lowest_priority) { | ||
| 102 | t->current_priority = t->initial_priority; | ||
| 103 | } | ||
| 104 | |||
| 105 | t->wait_type = WAITTYPE_NONE; | ||
| 106 | } | ||
| 107 | |||
| 108 | /// Change a thread to "ready" state | ||
| 109 | void ChangeReadyState(Thread* t, bool ready) { | ||
| 110 | Handle handle = t->GetHandle(); | ||
| 111 | if (t->IsReady()) { | ||
| 112 | if (!ready) { | ||
| 113 | g_thread_ready_queue.remove(t->current_priority, handle); | ||
| 114 | } | ||
| 115 | } else if (ready) { | ||
| 116 | if (t->IsRunning()) { | ||
| 117 | g_thread_ready_queue.push_front(t->current_priority, handle); | ||
| 118 | } else { | ||
| 119 | g_thread_ready_queue.push_back(t->current_priority, handle); | ||
| 120 | } | ||
| 121 | t->status = THREADSTATUS_READY; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | /// Changes a threads state | ||
| 126 | void ChangeThreadState(Thread* t, ThreadStatus new_status) { | ||
| 127 | if (!t || t->status == new_status) { | ||
| 128 | return; | ||
| 129 | } | ||
| 130 | ChangeReadyState(t, (new_status & THREADSTATUS_READY) != 0); | ||
| 131 | t->status = new_status; | ||
| 132 | |||
| 133 | if (new_status == THREADSTATUS_WAIT) { | ||
| 134 | if (t->wait_type == WAITTYPE_NONE) { | ||
| 135 | printf("ERROR: Waittype none not allowed here\n"); | ||
| 136 | } | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | /// Calls a thread by marking it as "ready" (note: will not actually execute until current thread yields) | ||
| 141 | void CallThread(Thread* t) { | ||
| 142 | // Stop waiting | ||
| 143 | if (t->wait_type != WAITTYPE_NONE) { | ||
| 144 | t->wait_type = WAITTYPE_NONE; | ||
| 145 | } | ||
| 146 | ChangeThreadState(t, THREADSTATUS_READY); | ||
| 147 | } | ||
| 148 | |||
| 149 | /// Switches CPU context to that of the specified thread | ||
| 150 | void SwitchContext(Thread* t) { | ||
| 151 | Thread* cur = GetCurrentThread(); | ||
| 152 | |||
| 153 | // Save context for current thread | ||
| 154 | if (cur) { | ||
| 155 | SaveContext(cur->context); | ||
| 156 | |||
| 157 | if (cur->IsRunning()) { | ||
| 158 | ChangeReadyState(cur, true); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | // Load context of new thread | ||
| 162 | if (t) { | ||
| 163 | SetCurrentThread(t); | ||
| 164 | ChangeReadyState(t, false); | ||
| 165 | t->status = (t->status | THREADSTATUS_RUNNING) & ~THREADSTATUS_READY; | ||
| 166 | t->wait_type = WAITTYPE_NONE; | ||
| 167 | LoadContext(t->context); | ||
| 168 | } else { | ||
| 169 | SetCurrentThread(NULL); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | /// Gets the next thread that is ready to be run by priority | ||
| 174 | Thread* NextThread() { | ||
| 175 | Handle next; | ||
| 176 | Thread* cur = GetCurrentThread(); | ||
| 177 | |||
| 178 | if (cur && cur->IsRunning()) { | ||
| 179 | next = g_thread_ready_queue.pop_first_better(cur->current_priority); | ||
| 180 | } else { | ||
| 181 | next = g_thread_ready_queue.pop_first(); | ||
| 182 | } | ||
| 183 | if (next == 0) { | ||
| 184 | return NULL; | ||
| 185 | } | ||
| 186 | return Kernel::g_object_pool.GetFast<Thread>(next); | ||
| 187 | } | ||
| 188 | |||
| 189 | /// Puts the current thread in the wait state for the given type | ||
| 190 | void WaitCurrentThread(WaitType wait_type) { | ||
| 191 | Thread* t = GetCurrentThread(); | ||
| 192 | t->wait_type = wait_type; | ||
| 193 | ChangeThreadState(t, ThreadStatus(THREADSTATUS_WAIT | (t->status & THREADSTATUS_SUSPEND))); | ||
| 194 | } | ||
| 195 | |||
| 196 | /// Resumes a thread from waiting by marking it as "ready" | ||
| 197 | void ResumeThreadFromWait(Handle handle) { | ||
| 198 | u32 error; | ||
| 199 | Thread* t = Kernel::g_object_pool.Get<Thread>(handle, error); | ||
| 200 | if (t) { | ||
| 201 | t->status &= ~THREADSTATUS_WAIT; | ||
| 202 | if (!(t->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) { | ||
| 203 | ChangeReadyState(t, true); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | /// Creates a new thread | ||
| 209 | Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 priority, | ||
| 210 | s32 processor_id, u32 stack_top, int stack_size) { | ||
| 211 | |||
| 212 | _assert_msg_(KERNEL, (priority >= THREADPRIO_HIGHEST && priority <= THREADPRIO_LOWEST), | ||
| 213 | "CreateThread priority=%d, outside of allowable range!", priority) | ||
| 214 | |||
| 215 | Thread* t = new Thread; | ||
| 216 | |||
| 217 | handle = Kernel::g_object_pool.Create(t); | ||
| 218 | |||
| 219 | g_thread_queue.push_back(handle); | ||
| 220 | g_thread_ready_queue.prepare(priority); | ||
| 221 | |||
| 222 | t->status = THREADSTATUS_DORMANT; | ||
| 223 | t->entry_point = entry_point; | ||
| 224 | t->stack_top = stack_top; | ||
| 225 | t->stack_size = stack_size; | ||
| 226 | t->initial_priority = t->current_priority = priority; | ||
| 227 | t->processor_id = processor_id; | ||
| 228 | t->wait_type = WAITTYPE_NONE; | ||
| 229 | |||
| 230 | strncpy(t->name, name, Kernel::MAX_NAME_LENGTH); | ||
| 231 | t->name[Kernel::MAX_NAME_LENGTH] = '\0'; | ||
| 232 | |||
| 233 | return t; | ||
| 234 | } | ||
| 235 | |||
| 236 | /// Creates a new thread - wrapper for external user | ||
| 237 | Handle CreateThread(const char* name, u32 entry_point, s32 priority, u32 arg, s32 processor_id, | ||
| 238 | u32 stack_top, int stack_size) { | ||
| 239 | if (name == NULL) { | ||
| 240 | ERROR_LOG(KERNEL, "CreateThread(): NULL name"); | ||
| 241 | return -1; | ||
| 242 | } | ||
| 243 | if ((u32)stack_size < 0x200) { | ||
| 244 | ERROR_LOG(KERNEL, "CreateThread(name=%s): invalid stack_size=0x%08X", name, | ||
| 245 | stack_size); | ||
| 246 | return -1; | ||
| 247 | } | ||
| 248 | if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { | ||
| 249 | s32 new_priority = CLAMP(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); | ||
| 250 | WARN_LOG(KERNEL, "CreateThread(name=%s): invalid priority=0x%08X, clamping to %08X", | ||
| 251 | name, priority, new_priority); | ||
| 252 | // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm | ||
| 253 | // validity of this | ||
| 254 | priority = new_priority; | ||
| 255 | } | ||
| 256 | if (!Memory::GetPointer(entry_point)) { | ||
| 257 | ERROR_LOG(KERNEL, "CreateThread(name=%s): invalid entry %08x", name, entry_point); | ||
| 258 | return -1; | ||
| 259 | } | ||
| 260 | Handle handle; | ||
| 261 | Thread* t = CreateThread(handle, name, entry_point, priority, processor_id, stack_top, | ||
| 262 | stack_size); | ||
| 263 | |||
| 264 | ResetThread(t, arg, 0); | ||
| 265 | |||
| 266 | HLE::EatCycles(32000); | ||
| 267 | |||
| 268 | // This won't schedule to the new thread, but it may to one woken from eating cycles. | ||
| 269 | // Technically, this should not eat all at once, and reschedule in the middle, but that's hard. | ||
| 270 | HLE::ReSchedule("thread created"); | ||
| 271 | |||
| 272 | CallThread(t); | ||
| 273 | |||
| 274 | return handle; | ||
| 275 | } | ||
| 276 | |||
| 277 | /// Sets up the primary application thread | ||
| 278 | Handle SetupMainThread(s32 priority, int stack_size) { | ||
| 279 | Handle handle; | ||
| 280 | |||
| 281 | // Initialize new "main" thread | ||
| 282 | Thread* t = CreateThread(handle, "main", Core::g_app_core->GetPC(), priority, | ||
| 283 | THREADPROCESSORID_0, Memory::SCRATCHPAD_VADDR_END, stack_size); | ||
| 284 | |||
| 285 | ResetThread(t, 0, 0); | ||
| 286 | |||
| 287 | // If running another thread already, set it to "ready" state | ||
| 288 | Thread* cur = GetCurrentThread(); | ||
| 289 | if (cur && cur->IsRunning()) { | ||
| 290 | ChangeReadyState(cur, true); | ||
| 291 | } | ||
| 292 | |||
| 293 | // Run new "main" thread | ||
| 294 | SetCurrentThread(t); | ||
| 295 | t->status = THREADSTATUS_RUNNING; | ||
| 296 | LoadContext(t->context); | ||
| 297 | |||
| 298 | return handle; | ||
| 299 | } | ||
| 300 | |||
| 301 | /// Reschedules to the next available thread (call after current thread is suspended) | ||
| 302 | void Reschedule() { | ||
| 303 | Thread* prev = GetCurrentThread(); | ||
| 304 | Thread* next = NextThread(); | ||
| 305 | if (next > 0) { | ||
| 306 | SwitchContext(next); | ||
| 307 | |||
| 308 | // Hack - automatically change previous thread (which would have been in "wait" state) to | ||
| 309 | // "ready" state, so that we can immediately resume to it when new thread yields. FixMe to | ||
| 310 | // actually wait for whatever event it is supposed to be waiting on. | ||
| 311 | ChangeReadyState(prev, true); | ||
| 312 | } | ||
| 313 | } | ||
| 314 | |||
| 315 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 316 | |||
| 317 | void ThreadingInit() { | ||
| 318 | } | ||
| 319 | |||
| 320 | void ThreadingShutdown() { | ||
| 321 | } | ||
| 322 | |||
| 323 | } // namespace | ||