diff options
Diffstat (limited to 'src/core/hle/kernel')
| -rw-r--r-- | src/core/hle/kernel/kernel.cpp | 158 | ||||
| -rw-r--r-- | src/core/hle/kernel/kernel.h | 154 | ||||
| -rw-r--r-- | src/core/hle/kernel/mutex.cpp | 132 | ||||
| -rw-r--r-- | src/core/hle/kernel/mutex.h | 26 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.cpp | 323 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.h | 74 |
6 files changed, 867 insertions, 0 deletions
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp new file mode 100644 index 000000000..de80de893 --- /dev/null +++ b/src/core/hle/kernel/kernel.cpp | |||
| @@ -0,0 +1,158 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project / PPSSPP Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <string.h> | ||
| 8 | |||
| 9 | #include "common/common.h" | ||
| 10 | |||
| 11 | #include "core/core.h" | ||
| 12 | #include "core/hle/kernel/kernel.h" | ||
| 13 | #include "core/hle/kernel/thread.h" | ||
| 14 | |||
| 15 | namespace Kernel { | ||
| 16 | |||
| 17 | ObjectPool g_object_pool; | ||
| 18 | |||
| 19 | ObjectPool::ObjectPool() { | ||
| 20 | memset(occupied, 0, sizeof(bool) * MAX_COUNT); | ||
| 21 | next_id = INITIAL_NEXT_ID; | ||
| 22 | } | ||
| 23 | |||
| 24 | Handle ObjectPool::Create(Object* obj, int range_bottom, int range_top) { | ||
| 25 | if (range_top > MAX_COUNT) { | ||
| 26 | range_top = MAX_COUNT; | ||
| 27 | } | ||
| 28 | if (next_id >= range_bottom && next_id < range_top) { | ||
| 29 | range_bottom = next_id++; | ||
| 30 | } | ||
| 31 | for (int i = range_bottom; i < range_top; i++) { | ||
| 32 | if (!occupied[i]) { | ||
| 33 | occupied[i] = true; | ||
| 34 | pool[i] = obj; | ||
| 35 | pool[i]->handle = i + HANDLE_OFFSET; | ||
| 36 | return i + HANDLE_OFFSET; | ||
| 37 | } | ||
| 38 | } | ||
| 39 | ERROR_LOG(HLE, "Unable to allocate kernel object, too many objects slots in use."); | ||
| 40 | return 0; | ||
| 41 | } | ||
| 42 | |||
| 43 | bool ObjectPool::IsValid(Handle handle) { | ||
| 44 | int index = handle - HANDLE_OFFSET; | ||
| 45 | if (index < 0) | ||
| 46 | return false; | ||
| 47 | if (index >= MAX_COUNT) | ||
| 48 | return false; | ||
| 49 | |||
| 50 | return occupied[index]; | ||
| 51 | } | ||
| 52 | |||
| 53 | void ObjectPool::Clear() { | ||
| 54 | for (int i = 0; i < MAX_COUNT; i++) { | ||
| 55 | //brutally clear everything, no validation | ||
| 56 | if (occupied[i]) | ||
| 57 | delete pool[i]; | ||
| 58 | occupied[i] = false; | ||
| 59 | } | ||
| 60 | memset(pool, 0, sizeof(Object*)*MAX_COUNT); | ||
| 61 | next_id = INITIAL_NEXT_ID; | ||
| 62 | } | ||
| 63 | |||
| 64 | Object* &ObjectPool::operator [](Handle handle) | ||
| 65 | { | ||
| 66 | _dbg_assert_msg_(KERNEL, IsValid(handle), "GRABBING UNALLOCED KERNEL OBJ"); | ||
| 67 | return pool[handle - HANDLE_OFFSET]; | ||
| 68 | } | ||
| 69 | |||
| 70 | void ObjectPool::List() { | ||
| 71 | for (int i = 0; i < MAX_COUNT; i++) { | ||
| 72 | if (occupied[i]) { | ||
| 73 | if (pool[i]) { | ||
| 74 | INFO_LOG(KERNEL, "KO %i: %s \"%s\"", i + HANDLE_OFFSET, pool[i]->GetTypeName(), | ||
| 75 | pool[i]->GetName()); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | int ObjectPool::GetCount() { | ||
| 82 | int count = 0; | ||
| 83 | for (int i = 0; i < MAX_COUNT; i++) { | ||
| 84 | if (occupied[i]) | ||
| 85 | count++; | ||
| 86 | } | ||
| 87 | return count; | ||
| 88 | } | ||
| 89 | |||
| 90 | Object* ObjectPool::CreateByIDType(int type) { | ||
| 91 | // Used for save states. This is ugly, but what other way is there? | ||
| 92 | switch (type) { | ||
| 93 | //case SCE_KERNEL_TMID_Alarm: | ||
| 94 | // return __KernelAlarmObject(); | ||
| 95 | //case SCE_KERNEL_TMID_EventFlag: | ||
| 96 | // return __KernelEventFlagObject(); | ||
| 97 | //case SCE_KERNEL_TMID_Mbox: | ||
| 98 | // return __KernelMbxObject(); | ||
| 99 | //case SCE_KERNEL_TMID_Fpl: | ||
| 100 | // return __KernelMemoryFPLObject(); | ||
| 101 | //case SCE_KERNEL_TMID_Vpl: | ||
| 102 | // return __KernelMemoryVPLObject(); | ||
| 103 | //case PPSSPP_KERNEL_TMID_PMB: | ||
| 104 | // return __KernelMemoryPMBObject(); | ||
| 105 | //case PPSSPP_KERNEL_TMID_Module: | ||
| 106 | // return __KernelModuleObject(); | ||
| 107 | //case SCE_KERNEL_TMID_Mpipe: | ||
| 108 | // return __KernelMsgPipeObject(); | ||
| 109 | //case SCE_KERNEL_TMID_Mutex: | ||
| 110 | // return __KernelMutexObject(); | ||
| 111 | //case SCE_KERNEL_TMID_LwMutex: | ||
| 112 | // return __KernelLwMutexObject(); | ||
| 113 | //case SCE_KERNEL_TMID_Semaphore: | ||
| 114 | // return __KernelSemaphoreObject(); | ||
| 115 | //case SCE_KERNEL_TMID_Callback: | ||
| 116 | // return __KernelCallbackObject(); | ||
| 117 | //case SCE_KERNEL_TMID_Thread: | ||
| 118 | // return __KernelThreadObject(); | ||
| 119 | //case SCE_KERNEL_TMID_VTimer: | ||
| 120 | // return __KernelVTimerObject(); | ||
| 121 | //case SCE_KERNEL_TMID_Tlspl: | ||
| 122 | // return __KernelTlsplObject(); | ||
| 123 | //case PPSSPP_KERNEL_TMID_File: | ||
| 124 | // return __KernelFileNodeObject(); | ||
| 125 | //case PPSSPP_KERNEL_TMID_DirList: | ||
| 126 | // return __KernelDirListingObject(); | ||
| 127 | |||
| 128 | default: | ||
| 129 | ERROR_LOG(COMMON, "Unable to load state: could not find object type %d.", type); | ||
| 130 | return NULL; | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | void Init() { | ||
| 135 | Kernel::ThreadingInit(); | ||
| 136 | } | ||
| 137 | |||
| 138 | void Shutdown() { | ||
| 139 | Kernel::ThreadingShutdown(); | ||
| 140 | } | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Loads executable stored at specified address | ||
| 144 | * @entry_point Entry point in memory of loaded executable | ||
| 145 | * @return True on success, otherwise false | ||
| 146 | */ | ||
| 147 | bool LoadExec(u32 entry_point) { | ||
| 148 | Init(); | ||
| 149 | |||
| 150 | Core::g_app_core->SetPC(entry_point); | ||
| 151 | |||
| 152 | // 0x30 is the typical main thread priority I've seen used so far | ||
| 153 | Handle thread = Kernel::SetupMainThread(0x30); | ||
| 154 | |||
| 155 | return true; | ||
| 156 | } | ||
| 157 | |||
| 158 | } // namespace | ||
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h new file mode 100644 index 000000000..7cd79c2c4 --- /dev/null +++ b/src/core/hle/kernel/kernel.h | |||
| @@ -0,0 +1,154 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project / PPSSPP Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "common/common.h" | ||
| 8 | |||
| 9 | typedef u32 Handle; | ||
| 10 | typedef s32 Result; | ||
| 11 | |||
| 12 | namespace Kernel { | ||
| 13 | |||
| 14 | enum class HandleType : u32 { | ||
| 15 | Unknown = 0, | ||
| 16 | Port = 1, | ||
| 17 | Service = 2, | ||
| 18 | Event = 3, | ||
| 19 | Mutex = 4, | ||
| 20 | SharedMemory = 5, | ||
| 21 | Redirection = 6, | ||
| 22 | Thread = 7, | ||
| 23 | Process = 8, | ||
| 24 | Arbiter = 9, | ||
| 25 | File = 10, | ||
| 26 | Semaphore = 11, | ||
| 27 | }; | ||
| 28 | |||
| 29 | enum { | ||
| 30 | MAX_NAME_LENGTH = 0x100, | ||
| 31 | DEFAULT_STACK_SIZE = 0x4000, | ||
| 32 | }; | ||
| 33 | |||
| 34 | class ObjectPool; | ||
| 35 | |||
| 36 | class Object : NonCopyable { | ||
| 37 | friend class ObjectPool; | ||
| 38 | u32 handle; | ||
| 39 | public: | ||
| 40 | virtual ~Object() {} | ||
| 41 | Handle GetHandle() const { return handle; } | ||
| 42 | virtual const char *GetTypeName() { return "[BAD KERNEL OBJECT TYPE]"; } | ||
| 43 | virtual const char *GetName() { return "[UNKNOWN KERNEL OBJECT]"; } | ||
| 44 | virtual Kernel::HandleType GetHandleType() const = 0; | ||
| 45 | }; | ||
| 46 | |||
| 47 | class ObjectPool : NonCopyable { | ||
| 48 | public: | ||
| 49 | ObjectPool(); | ||
| 50 | ~ObjectPool() {} | ||
| 51 | |||
| 52 | // Allocates a handle within the range and inserts the object into the map. | ||
| 53 | Handle Create(Object* obj, int range_bottom=INITIAL_NEXT_ID, int range_top=0x7FFFFFFF); | ||
| 54 | |||
| 55 | static Object* CreateByIDType(int type); | ||
| 56 | |||
| 57 | template <class T> | ||
| 58 | u32 Destroy(Handle handle) { | ||
| 59 | u32 error; | ||
| 60 | if (Get<T>(handle, error)) { | ||
| 61 | occupied[handle - HANDLE_OFFSET] = false; | ||
| 62 | delete pool[handle - HANDLE_OFFSET]; | ||
| 63 | } | ||
| 64 | return error; | ||
| 65 | }; | ||
| 66 | |||
| 67 | bool IsValid(Handle handle); | ||
| 68 | |||
| 69 | template <class T> | ||
| 70 | T* Get(Handle handle, u32& outError) { | ||
| 71 | if (handle < HANDLE_OFFSET || handle >= HANDLE_OFFSET + MAX_COUNT || !occupied[handle - HANDLE_OFFSET]) { | ||
| 72 | // Tekken 6 spams 0x80020001 gets wrong with no ill effects, also on the real PSP | ||
| 73 | if (handle != 0 && (u32)handle != 0x80020001) { | ||
| 74 | WARN_LOG(KERNEL, "Kernel: Bad object handle %i (%08x)", handle, handle); | ||
| 75 | } | ||
| 76 | outError = 0;//T::GetMissingErrorCode(); | ||
| 77 | return 0; | ||
| 78 | } else { | ||
| 79 | // Previously we had a dynamic_cast here, but since RTTI was disabled traditionally, | ||
| 80 | // it just acted as a static case and everything worked. This means that we will never | ||
| 81 | // see the Wrong type object error below, but we'll just have to live with that danger. | ||
| 82 | T* t = static_cast<T*>(pool[handle - HANDLE_OFFSET]); | ||
| 83 | if (t == 0 || t->GetHandleType() != T::GetStaticHandleType()) { | ||
| 84 | WARN_LOG(KERNEL, "Kernel: Wrong object type for %i (%08x)", handle, handle); | ||
| 85 | outError = 0;//T::GetMissingErrorCode(); | ||
| 86 | return 0; | ||
| 87 | } | ||
| 88 | outError = 0;//SCE_KERNEL_ERROR_OK; | ||
| 89 | return t; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | // ONLY use this when you know the handle is valid. | ||
| 94 | template <class T> | ||
| 95 | T *GetFast(Handle handle) { | ||
| 96 | const Handle realHandle = handle - HANDLE_OFFSET; | ||
| 97 | _dbg_assert_(KERNEL, realHandle >= 0 && realHandle < MAX_COUNT && occupied[realHandle]); | ||
| 98 | return static_cast<T*>(pool[realHandle]); | ||
| 99 | } | ||
| 100 | |||
| 101 | template <class T, typename ArgT> | ||
| 102 | void Iterate(bool func(T*, ArgT), ArgT arg) { | ||
| 103 | int type = T::GetStaticIDType(); | ||
| 104 | for (int i = 0; i < MAX_COUNT; i++) | ||
| 105 | { | ||
| 106 | if (!occupied[i]) | ||
| 107 | continue; | ||
| 108 | T* t = static_cast<T*>(pool[i]); | ||
| 109 | if (t->GetIDType() == type) { | ||
| 110 | if (!func(t, arg)) | ||
| 111 | break; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | bool GetIDType(Handle handle, HandleType* type) const { | ||
| 117 | if ((handle < HANDLE_OFFSET) || (handle >= HANDLE_OFFSET + MAX_COUNT) || | ||
| 118 | !occupied[handle - HANDLE_OFFSET]) { | ||
| 119 | ERROR_LOG(KERNEL, "Kernel: Bad object handle %i (%08x)", handle, handle); | ||
| 120 | return false; | ||
| 121 | } | ||
| 122 | Object* t = pool[handle - HANDLE_OFFSET]; | ||
| 123 | *type = t->GetHandleType(); | ||
| 124 | return true; | ||
| 125 | } | ||
| 126 | |||
| 127 | Object* &operator [](Handle handle); | ||
| 128 | void List(); | ||
| 129 | void Clear(); | ||
| 130 | int GetCount(); | ||
| 131 | |||
| 132 | private: | ||
| 133 | |||
| 134 | enum { | ||
| 135 | MAX_COUNT = 0x1000, | ||
| 136 | HANDLE_OFFSET = 0x100, | ||
| 137 | INITIAL_NEXT_ID = 0x10, | ||
| 138 | }; | ||
| 139 | |||
| 140 | Object* pool[MAX_COUNT]; | ||
| 141 | bool occupied[MAX_COUNT]; | ||
| 142 | int next_id; | ||
| 143 | }; | ||
| 144 | |||
| 145 | extern ObjectPool g_object_pool; | ||
| 146 | |||
| 147 | /** | ||
| 148 | * Loads executable stored at specified address | ||
| 149 | * @entry_point Entry point in memory of loaded executable | ||
| 150 | * @return True on success, otherwise false | ||
| 151 | */ | ||
| 152 | bool LoadExec(u32 entry_point); | ||
| 153 | |||
| 154 | } // namespace | ||
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp new file mode 100644 index 000000000..019efbc78 --- /dev/null +++ b/src/core/hle/kernel/mutex.cpp | |||
| @@ -0,0 +1,132 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <map> | ||
| 6 | #include <vector> | ||
| 7 | |||
| 8 | #include "common/common.h" | ||
| 9 | |||
| 10 | #include "core/hle/kernel/kernel.h" | ||
| 11 | #include "core/hle/kernel/thread.h" | ||
| 12 | |||
| 13 | namespace Kernel { | ||
| 14 | |||
| 15 | class Mutex : public Object { | ||
| 16 | public: | ||
| 17 | const char* GetTypeName() { return "Mutex"; } | ||
| 18 | |||
| 19 | static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Mutex; } | ||
| 20 | Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Mutex; } | ||
| 21 | |||
| 22 | bool initial_locked; ///< Initial lock state when mutex was created | ||
| 23 | bool locked; ///< Current locked state | ||
| 24 | Handle lock_thread; ///< Handle to thread that currently has mutex | ||
| 25 | std::vector<Handle> waiting_threads; ///< Threads that are waiting for the mutex | ||
| 26 | }; | ||
| 27 | |||
| 28 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 29 | |||
| 30 | typedef std::multimap<Handle, Handle> MutexMap; | ||
| 31 | static MutexMap g_mutex_held_locks; | ||
| 32 | |||
| 33 | void MutexAcquireLock(Mutex* mutex, Handle thread) { | ||
| 34 | g_mutex_held_locks.insert(std::make_pair(thread, mutex->GetHandle())); | ||
| 35 | mutex->lock_thread = thread; | ||
| 36 | } | ||
| 37 | |||
| 38 | void MutexAcquireLock(Mutex* mutex) { | ||
| 39 | Handle thread = GetCurrentThreadHandle(); | ||
| 40 | MutexAcquireLock(mutex, thread); | ||
| 41 | } | ||
| 42 | |||
| 43 | void MutexEraseLock(Mutex* mutex) { | ||
| 44 | Handle handle = mutex->GetHandle(); | ||
| 45 | auto locked = g_mutex_held_locks.equal_range(mutex->lock_thread); | ||
| 46 | for (MutexMap::iterator iter = locked.first; iter != locked.second; ++iter) { | ||
| 47 | if ((*iter).second == handle) { | ||
| 48 | g_mutex_held_locks.erase(iter); | ||
| 49 | break; | ||
| 50 | } | ||
| 51 | } | ||
| 52 | mutex->lock_thread = -1; | ||
| 53 | } | ||
| 54 | |||
| 55 | bool LockMutex(Mutex* mutex) { | ||
| 56 | // Mutex alread locked? | ||
| 57 | if (mutex->locked) { | ||
| 58 | return false; | ||
| 59 | } | ||
| 60 | MutexAcquireLock(mutex); | ||
| 61 | return true; | ||
| 62 | } | ||
| 63 | |||
| 64 | bool ReleaseMutexForThread(Mutex* mutex, Handle thread) { | ||
| 65 | MutexAcquireLock(mutex, thread); | ||
| 66 | Kernel::ResumeThreadFromWait(thread); | ||
| 67 | return true; | ||
| 68 | } | ||
| 69 | |||
| 70 | bool ReleaseMutex(Mutex* mutex) { | ||
| 71 | MutexEraseLock(mutex); | ||
| 72 | bool woke_threads = false; | ||
| 73 | auto iter = mutex->waiting_threads.begin(); | ||
| 74 | |||
| 75 | // Find the next waiting thread for the mutex... | ||
| 76 | while (!woke_threads && !mutex->waiting_threads.empty()) { | ||
| 77 | woke_threads |= ReleaseMutexForThread(mutex, *iter); | ||
| 78 | mutex->waiting_threads.erase(iter); | ||
| 79 | } | ||
| 80 | // Reset mutex lock thread handle, nothing is waiting | ||
| 81 | if (!woke_threads) { | ||
| 82 | mutex->locked = false; | ||
| 83 | mutex->lock_thread = -1; | ||
| 84 | } | ||
| 85 | return woke_threads; | ||
| 86 | } | ||
| 87 | |||
| 88 | /** | ||
| 89 | * Releases a mutex | ||
| 90 | * @param handle Handle to mutex to release | ||
| 91 | */ | ||
| 92 | Result ReleaseMutex(Handle handle) { | ||
| 93 | Mutex* mutex = Kernel::g_object_pool.GetFast<Mutex>(handle); | ||
| 94 | if (!ReleaseMutex(mutex)) { | ||
| 95 | return -1; | ||
| 96 | } | ||
| 97 | return 0; | ||
| 98 | } | ||
| 99 | |||
| 100 | /** | ||
| 101 | * Creates a mutex | ||
| 102 | * @param handle Reference to handle for the newly created mutex | ||
| 103 | * @param initial_locked Specifies if the mutex should be locked initially | ||
| 104 | */ | ||
| 105 | Mutex* CreateMutex(Handle& handle, bool initial_locked) { | ||
| 106 | Mutex* mutex = new Mutex; | ||
| 107 | handle = Kernel::g_object_pool.Create(mutex); | ||
| 108 | |||
| 109 | mutex->locked = mutex->initial_locked = initial_locked; | ||
| 110 | |||
| 111 | // Acquire mutex with current thread if initialized as locked... | ||
| 112 | if (mutex->locked) { | ||
| 113 | MutexAcquireLock(mutex); | ||
| 114 | |||
| 115 | // Otherwise, reset lock thread handle | ||
| 116 | } else { | ||
| 117 | mutex->lock_thread = -1; | ||
| 118 | } | ||
| 119 | return mutex; | ||
| 120 | } | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Creates a mutex | ||
| 124 | * @param initial_locked Specifies if the mutex should be locked initially | ||
| 125 | */ | ||
| 126 | Handle CreateMutex(bool initial_locked) { | ||
| 127 | Handle handle; | ||
| 128 | Mutex* mutex = CreateMutex(handle, initial_locked); | ||
| 129 | return handle; | ||
| 130 | } | ||
| 131 | |||
| 132 | } // namespace | ||
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h new file mode 100644 index 000000000..871e2e562 --- /dev/null +++ b/src/core/hle/kernel/mutex.h | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | #include "core/hle/kernel/kernel.h" | ||
| 10 | |||
| 11 | namespace Kernel { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * Releases a mutex | ||
| 15 | * @param handle Handle to mutex to release | ||
| 16 | */ | ||
| 17 | Result ReleaseMutex(Handle handle); | ||
| 18 | |||
| 19 | /** | ||
| 20 | * Creates a mutex | ||
| 21 | * @param handle Reference to handle for the newly created mutex | ||
| 22 | * @param initial_locked Specifies if the mutex should be locked initially | ||
| 23 | */ | ||
| 24 | Handle CreateMutex(bool initial_locked); | ||
| 25 | |||
| 26 | } // namespace | ||
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 | ||
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h new file mode 100644 index 000000000..9628f165d --- /dev/null +++ b/src/core/hle/kernel/thread.h | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project / PPSSPP Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include "common/common_types.h" | ||
| 8 | #include "core/hle/kernel/kernel.h" | ||
| 9 | |||
| 10 | enum ThreadPriority { | ||
| 11 | THREADPRIO_HIGHEST = 0, ///< Highest thread priority | ||
| 12 | THREADPRIO_DEFAULT = 16, ///< Default thread priority for userland apps | ||
| 13 | THREADPRIO_LOW = 31, ///< Low range of thread priority for userland apps | ||
| 14 | THREADPRIO_LOWEST = 63, ///< Thread priority max checked by svcCreateThread | ||
| 15 | }; | ||
| 16 | |||
| 17 | enum ThreadProcessorId { | ||
| 18 | THREADPROCESSORID_0 = 0xFFFFFFFE, ///< Enables core appcode | ||
| 19 | THREADPROCESSORID_1 = 0xFFFFFFFD, ///< Enables core syscore | ||
| 20 | THREADPROCESSORID_ALL = 0xFFFFFFFC, ///< Enables both cores | ||
| 21 | }; | ||
| 22 | |||
| 23 | enum ThreadStatus { | ||
| 24 | THREADSTATUS_RUNNING = 1, | ||
| 25 | THREADSTATUS_READY = 2, | ||
| 26 | THREADSTATUS_WAIT = 4, | ||
| 27 | THREADSTATUS_SUSPEND = 8, | ||
| 28 | THREADSTATUS_DORMANT = 16, | ||
| 29 | THREADSTATUS_DEAD = 32, | ||
| 30 | THREADSTATUS_WAITSUSPEND = THREADSTATUS_WAIT | THREADSTATUS_SUSPEND | ||
| 31 | }; | ||
| 32 | |||
| 33 | enum WaitType { | ||
| 34 | WAITTYPE_NONE, | ||
| 35 | WAITTYPE_SLEEP, | ||
| 36 | WAITTYPE_SEMA, | ||
| 37 | WAITTYPE_EVENTFLAG, | ||
| 38 | WAITTYPE_THREADEND, | ||
| 39 | WAITTYPE_VBLANK, | ||
| 40 | WAITTYPE_MUTEX, | ||
| 41 | WAITTYPE_SYNCH, | ||
| 42 | }; | ||
| 43 | |||
| 44 | namespace Kernel { | ||
| 45 | |||
| 46 | /// Creates a new thread - wrapper for external user | ||
| 47 | Handle CreateThread(const char* name, u32 entry_point, s32 priority, u32 arg, s32 processor_id, | ||
| 48 | u32 stack_top, int stack_size=Kernel::DEFAULT_STACK_SIZE); | ||
| 49 | |||
| 50 | /// Sets up the primary application thread | ||
| 51 | Handle SetupMainThread(s32 priority, int stack_size=Kernel::DEFAULT_STACK_SIZE); | ||
| 52 | |||
| 53 | /// Reschedules to the next available thread (call after current thread is suspended) | ||
| 54 | void Reschedule(); | ||
| 55 | |||
| 56 | /// Puts the current thread in the wait state for the given type | ||
| 57 | void WaitCurrentThread(WaitType wait_type); | ||
| 58 | |||
| 59 | /// Resumes a thread from waiting by marking it as "ready" | ||
| 60 | void ResumeThreadFromWait(Handle handle); | ||
| 61 | |||
| 62 | /// Gets the current thread handle | ||
| 63 | Handle GetCurrentThreadHandle(); | ||
| 64 | |||
| 65 | /// Put current thread in a wait state - on WaitSynchronization | ||
| 66 | void WaitThread_Synchronization(); | ||
| 67 | |||
| 68 | /// Initialize threading | ||
| 69 | void ThreadingInit(); | ||
| 70 | |||
| 71 | /// Shutdown threading | ||
| 72 | void ThreadingShutdown(); | ||
| 73 | |||
| 74 | } // namespace | ||