diff options
Diffstat (limited to 'src/core/hle/kernel')
| -rw-r--r-- | src/core/hle/kernel/event.cpp | 159 | ||||
| -rw-r--r-- | src/core/hle/kernel/event.h | 52 | ||||
| -rw-r--r-- | src/core/hle/kernel/kernel.cpp | 161 | ||||
| -rw-r--r-- | src/core/hle/kernel/kernel.h | 183 | ||||
| -rw-r--r-- | src/core/hle/kernel/mutex.cpp | 170 | ||||
| -rw-r--r-- | src/core/hle/kernel/mutex.h | 28 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.cpp | 436 | ||||
| -rw-r--r-- | src/core/hle/kernel/thread.h | 83 |
8 files changed, 1272 insertions, 0 deletions
diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp new file mode 100644 index 000000000..127c0cfc6 --- /dev/null +++ b/src/core/hle/kernel/event.cpp @@ -0,0 +1,159 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <map> +#include <algorithm> +#include <vector> + +#include "common/common.h" + +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +class Event : public Object { +public: + const char* GetTypeName() const { return "Event"; } + const char* GetName() const { return name.c_str(); } + + static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Event; } + Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Event; } + + ResetType intitial_reset_type; ///< ResetType specified at Event initialization + ResetType reset_type; ///< Current ResetType + + bool locked; ///< Event signal wait + bool permanent_locked; ///< Hack - to set event permanent state (for easy passthrough) + std::vector<Handle> waiting_threads; ///< Threads that are waiting for the event + std::string name; ///< Name of event (optional) + + /** + * Wait for kernel object to synchronize + * @param wait Boolean wait set if current thread should wait as a result of sync operation + * @return Result of operation, 0 on success, otherwise error code + */ + Result WaitSynchronization(bool* wait) { + *wait = locked; + if (locked) { + Handle thread = GetCurrentThreadHandle(); + if (std::find(waiting_threads.begin(), waiting_threads.end(), thread) == waiting_threads.end()) { + waiting_threads.push_back(thread); + } + Kernel::WaitCurrentThread(WAITTYPE_EVENT); + } + if (reset_type != RESETTYPE_STICKY && !permanent_locked) { + locked = true; + } + return 0; + } +}; + +/** + * Hackish function to set an events permanent lock state, used to pass through synch blocks + * @param handle Handle to event to change + * @param permanent_locked Boolean permanent locked value to set event + * @return Result of operation, 0 on success, otherwise error code + */ +Result SetPermanentLock(Handle handle, const bool permanent_locked) { + Event* evt = g_object_pool.GetFast<Event>(handle); + _assert_msg_(KERNEL, (evt != nullptr), "called, but event is nullptr!"); + + evt->permanent_locked = permanent_locked; + return 0; +} + +/** + * Changes whether an event is locked or not + * @param handle Handle to event to change + * @param locked Boolean locked value to set event + * @return Result of operation, 0 on success, otherwise error code + */ +Result SetEventLocked(const Handle handle, const bool locked) { + Event* evt = g_object_pool.GetFast<Event>(handle); + _assert_msg_(KERNEL, (evt != nullptr), "called, but event is nullptr!"); + + if (!evt->permanent_locked) { + evt->locked = locked; + } + return 0; +} + +/** + * Signals an event + * @param handle Handle to event to signal + * @return Result of operation, 0 on success, otherwise error code + */ +Result SignalEvent(const Handle handle) { + Event* evt = g_object_pool.GetFast<Event>(handle); + _assert_msg_(KERNEL, (evt != nullptr), "called, but event is nullptr!"); + + // Resume threads waiting for event to signal + bool event_caught = false; + for (size_t i = 0; i < evt->waiting_threads.size(); ++i) { + ResumeThreadFromWait( evt->waiting_threads[i]); + + // If any thread is signalled awake by this event, assume the event was "caught" and reset + // the event. This will result in the next thread waiting on the event to block. Otherwise, + // the event will not be reset, and the next thread to call WaitSynchronization on it will + // not block. Not sure if this is correct behavior, but it seems to work. + event_caught = true; + } + evt->waiting_threads.clear(); + + if (!evt->permanent_locked) { + evt->locked = event_caught; + } + return 0; +} + +/** + * Clears an event + * @param handle Handle to event to clear + * @return Result of operation, 0 on success, otherwise error code + */ +Result ClearEvent(Handle handle) { + Event* evt = g_object_pool.GetFast<Event>(handle); + _assert_msg_(KERNEL, (evt != nullptr), "called, but event is nullptr!"); + + if (!evt->permanent_locked) { + evt->locked = true; + } + return 0; +} + +/** + * Creates an event + * @param handle Reference to handle for the newly created mutex + * @param reset_type ResetType describing how to create event + * @param name Optional name of event + * @return Newly created Event object + */ +Event* CreateEvent(Handle& handle, const ResetType reset_type, const std::string& name) { + Event* evt = new Event; + + handle = Kernel::g_object_pool.Create(evt); + + evt->locked = true; + evt->permanent_locked = false; + evt->reset_type = evt->intitial_reset_type = reset_type; + evt->name = name; + + return evt; +} + +/** + * Creates an event + * @param reset_type ResetType describing how to create event + * @param name Optional name of event + * @return Handle to newly created Event object + */ +Handle CreateEvent(const ResetType reset_type, const std::string& name) { + Handle handle; + Event* evt = CreateEvent(handle, reset_type, name); + return handle; +} + +} // namespace diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h new file mode 100644 index 000000000..c39b33180 --- /dev/null +++ b/src/core/hle/kernel/event.h @@ -0,0 +1,52 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +#include "core/hle/kernel/kernel.h" +#include "core/hle/svc.h" + +namespace Kernel { + +/** + * Changes whether an event is locked or not + * @param handle Handle to event to change + * @param locked Boolean locked value to set event + * @return Result of operation, 0 on success, otherwise error code + */ +Result SetEventLocked(const Handle handle, const bool locked); + +/** + * Hackish function to set an events permanent lock state, used to pass through synch blocks + * @param handle Handle to event to change + * @param permanent_locked Boolean permanent locked value to set event + * @return Result of operation, 0 on success, otherwise error code + */ +Result SetPermanentLock(Handle handle, const bool permanent_locked); + +/** + * Signals an event + * @param handle Handle to event to signal + * @return Result of operation, 0 on success, otherwise error code + */ +Result SignalEvent(const Handle handle); + +/** + * Clears an event + * @param handle Handle to event to clear + * @return Result of operation, 0 on success, otherwise error code + */ +Result ClearEvent(Handle handle); + +/** + * Creates an event + * @param reset_type ResetType describing how to create event + * @param name Optional name of event + * @return Handle to newly created Event object + */ +Handle CreateEvent(const ResetType reset_type, const std::string& name="Unknown"); + +} // namespace diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp new file mode 100644 index 000000000..cda183add --- /dev/null +++ b/src/core/hle/kernel/kernel.cpp @@ -0,0 +1,161 @@ +// Copyright 2014 Citra Emulator Project / PPSSPP Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <string.h> + +#include "common/common.h" + +#include "core/core.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +Handle g_main_thread = 0; +ObjectPool g_object_pool; + +ObjectPool::ObjectPool() { + memset(occupied, 0, sizeof(bool) * MAX_COUNT); + next_id = INITIAL_NEXT_ID; +} + +Handle ObjectPool::Create(Object* obj, int range_bottom, int range_top) { + if (range_top > MAX_COUNT) { + range_top = MAX_COUNT; + } + if (next_id >= range_bottom && next_id < range_top) { + range_bottom = next_id++; + } + for (int i = range_bottom; i < range_top; i++) { + if (!occupied[i]) { + occupied[i] = true; + pool[i] = obj; + pool[i]->handle = i + HANDLE_OFFSET; + return i + HANDLE_OFFSET; + } + } + ERROR_LOG(HLE, "Unable to allocate kernel object, too many objects slots in use."); + return 0; +} + +bool ObjectPool::IsValid(Handle handle) { + int index = handle - HANDLE_OFFSET; + if (index < 0) + return false; + if (index >= MAX_COUNT) + return false; + + return occupied[index]; +} + +void ObjectPool::Clear() { + for (int i = 0; i < MAX_COUNT; i++) { + //brutally clear everything, no validation + if (occupied[i]) + delete pool[i]; + occupied[i] = false; + } + memset(pool, 0, sizeof(Object*)*MAX_COUNT); + next_id = INITIAL_NEXT_ID; +} + +Object* &ObjectPool::operator [](Handle handle) +{ + _dbg_assert_msg_(KERNEL, IsValid(handle), "GRABBING UNALLOCED KERNEL OBJ"); + return pool[handle - HANDLE_OFFSET]; +} + +void ObjectPool::List() { + for (int i = 0; i < MAX_COUNT; i++) { + if (occupied[i]) { + if (pool[i]) { + INFO_LOG(KERNEL, "KO %i: %s \"%s\"", i + HANDLE_OFFSET, pool[i]->GetTypeName(), + pool[i]->GetName()); + } + } + } +} + +int ObjectPool::GetCount() { + int count = 0; + for (int i = 0; i < MAX_COUNT; i++) { + if (occupied[i]) + count++; + } + return count; +} + +Object* ObjectPool::CreateByIDType(int type) { + // Used for save states. This is ugly, but what other way is there? + switch (type) { + //case SCE_KERNEL_TMID_Alarm: + // return __KernelAlarmObject(); + //case SCE_KERNEL_TMID_EventFlag: + // return __KernelEventFlagObject(); + //case SCE_KERNEL_TMID_Mbox: + // return __KernelMbxObject(); + //case SCE_KERNEL_TMID_Fpl: + // return __KernelMemoryFPLObject(); + //case SCE_KERNEL_TMID_Vpl: + // return __KernelMemoryVPLObject(); + //case PPSSPP_KERNEL_TMID_PMB: + // return __KernelMemoryPMBObject(); + //case PPSSPP_KERNEL_TMID_Module: + // return __KernelModuleObject(); + //case SCE_KERNEL_TMID_Mpipe: + // return __KernelMsgPipeObject(); + //case SCE_KERNEL_TMID_Mutex: + // return __KernelMutexObject(); + //case SCE_KERNEL_TMID_LwMutex: + // return __KernelLwMutexObject(); + //case SCE_KERNEL_TMID_Semaphore: + // return __KernelSemaphoreObject(); + //case SCE_KERNEL_TMID_Callback: + // return __KernelCallbackObject(); + //case SCE_KERNEL_TMID_Thread: + // return __KernelThreadObject(); + //case SCE_KERNEL_TMID_VTimer: + // return __KernelVTimerObject(); + //case SCE_KERNEL_TMID_Tlspl: + // return __KernelTlsplObject(); + //case PPSSPP_KERNEL_TMID_File: + // return __KernelFileNodeObject(); + //case PPSSPP_KERNEL_TMID_DirList: + // return __KernelDirListingObject(); + + default: + ERROR_LOG(COMMON, "Unable to load state: could not find object type %d.", type); + return nullptr; + } +} + +/// Initialize the kernel +void Init() { + Kernel::ThreadingInit(); +} + +/// Shutdown the kernel +void Shutdown() { + Kernel::ThreadingShutdown(); + + g_object_pool.Clear(); // Free all kernel objects +} + +/** + * Loads executable stored at specified address + * @entry_point Entry point in memory of loaded executable + * @return True on success, otherwise false + */ +bool LoadExec(u32 entry_point) { + Init(); + + Core::g_app_core->SetPC(entry_point); + + // 0x30 is the typical main thread priority I've seen used so far + g_main_thread = Kernel::SetupMainThread(0x30); + + return true; +} + +} // namespace diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h new file mode 100644 index 000000000..3f15da0ac --- /dev/null +++ b/src/core/hle/kernel/kernel.h @@ -0,0 +1,183 @@ +// Copyright 2014 Citra Emulator Project / PPSSPP Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include "common/common.h" + +typedef u32 Handle; +typedef s32 Result; + +namespace Kernel { + +enum KernelHandle { + CurrentThread = 0xFFFF8000, + CurrentProcess = 0xFFFF8001, +}; + +enum class HandleType : u32 { + Unknown = 0, + Port = 1, + Service = 2, + Event = 3, + Mutex = 4, + SharedMemory = 5, + Redirection = 6, + Thread = 7, + Process = 8, + Arbiter = 9, + File = 10, + Semaphore = 11, +}; + +enum { + MAX_NAME_LENGTH = 0x100, + DEFAULT_STACK_SIZE = 0x4000, +}; + +class ObjectPool; + +class Object : NonCopyable { + friend class ObjectPool; + u32 handle; +public: + virtual ~Object() {} + Handle GetHandle() const { return handle; } + virtual const char* GetTypeName() const { return "[BAD KERNEL OBJECT TYPE]"; } + virtual const char* GetName() const { return "[UNKNOWN KERNEL OBJECT]"; } + virtual Kernel::HandleType GetHandleType() const = 0; + + /** + * Synchronize kernel object + * @param wait Boolean wait set if current thread should wait as a result of sync operation + * @return Result of operation, 0 on success, otherwise error code + */ + virtual Result SyncRequest(bool* wait) { + ERROR_LOG(KERNEL, "(UNIMPLEMENTED)"); + return -1; + } + + /** + * Wait for kernel object to synchronize + * @param wait Boolean wait set if current thread should wait as a result of sync operation + * @return Result of operation, 0 on success, otherwise error code + */ + virtual Result WaitSynchronization(bool* wait) = 0; +}; + +class ObjectPool : NonCopyable { +public: + ObjectPool(); + ~ObjectPool() {} + + // Allocates a handle within the range and inserts the object into the map. + Handle Create(Object* obj, int range_bottom=INITIAL_NEXT_ID, int range_top=0x7FFFFFFF); + + static Object* CreateByIDType(int type); + + template <class T> + u32 Destroy(Handle handle) { + u32 error; + if (Get<T>(handle, error)) { + occupied[handle - HANDLE_OFFSET] = false; + delete pool[handle - HANDLE_OFFSET]; + } + return error; + }; + + bool IsValid(Handle handle); + + template <class T> + T* Get(Handle handle, u32& outError) { + if (handle < HANDLE_OFFSET || handle >= HANDLE_OFFSET + MAX_COUNT || !occupied[handle - HANDLE_OFFSET]) { + // Tekken 6 spams 0x80020001 gets wrong with no ill effects, also on the real PSP + if (handle != 0 && (u32)handle != 0x80020001) { + WARN_LOG(KERNEL, "Kernel: Bad object handle %i (%08x)", handle, handle); + } + outError = 0;//T::GetMissingErrorCode(); + return 0; + } else { + // Previously we had a dynamic_cast here, but since RTTI was disabled traditionally, + // it just acted as a static case and everything worked. This means that we will never + // see the Wrong type object error below, but we'll just have to live with that danger. + T* t = static_cast<T*>(pool[handle - HANDLE_OFFSET]); + if (t == 0 || t->GetHandleType() != T::GetStaticHandleType()) { + WARN_LOG(KERNEL, "Kernel: Wrong object type for %i (%08x)", handle, handle); + outError = 0;//T::GetMissingErrorCode(); + return 0; + } + outError = 0;//SCE_KERNEL_ERROR_OK; + return t; + } + } + + // ONLY use this when you know the handle is valid. + template <class T> + T *GetFast(Handle handle) { + const Handle realHandle = handle - HANDLE_OFFSET; + _dbg_assert_(KERNEL, realHandle >= 0 && realHandle < MAX_COUNT && occupied[realHandle]); + return static_cast<T*>(pool[realHandle]); + } + + template <class T, typename ArgT> + void Iterate(bool func(T*, ArgT), ArgT arg) { + int type = T::GetStaticIDType(); + for (int i = 0; i < MAX_COUNT; i++) + { + if (!occupied[i]) + continue; + T* t = static_cast<T*>(pool[i]); + if (t->GetIDType() == type) { + if (!func(t, arg)) + break; + } + } + } + + bool GetIDType(Handle handle, HandleType* type) const { + if ((handle < HANDLE_OFFSET) || (handle >= HANDLE_OFFSET + MAX_COUNT) || + !occupied[handle - HANDLE_OFFSET]) { + ERROR_LOG(KERNEL, "Kernel: Bad object handle %i (%08x)", handle, handle); + return false; + } + Object* t = pool[handle - HANDLE_OFFSET]; + *type = t->GetHandleType(); + return true; + } + + Object* &operator [](Handle handle); + void List(); + void Clear(); + int GetCount(); + +private: + + enum { + MAX_COUNT = 0x1000, + HANDLE_OFFSET = 0x100, + INITIAL_NEXT_ID = 0x10, + }; + + Object* pool[MAX_COUNT]; + bool occupied[MAX_COUNT]; + int next_id; +}; + +extern ObjectPool g_object_pool; +extern Handle g_main_thread; + +/// Initialize the kernel +void Init(); + +/// Shutdown the kernel +void Shutdown(); + +/** + * Loads executable stored at specified address + * @entry_point Entry point in memory of loaded executable + * @return True on success, otherwise false + */ +bool LoadExec(u32 entry_point); + +} // namespace diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp new file mode 100644 index 000000000..1ccf1eb73 --- /dev/null +++ b/src/core/hle/kernel/mutex.cpp @@ -0,0 +1,170 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <map> +#include <vector> + +#include "common/common.h" + +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/mutex.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +class Mutex : public Object { +public: + const char* GetTypeName() const { return "Mutex"; } + const char* GetName() const { return name.c_str(); } + + static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Mutex; } + Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Mutex; } + + bool initial_locked; ///< Initial lock state when mutex was created + bool locked; ///< Current locked state + Handle lock_thread; ///< Handle to thread that currently has mutex + std::vector<Handle> waiting_threads; ///< Threads that are waiting for the mutex + std::string name; ///< Name of mutex (optional) + + /** + * Synchronize kernel object + * @param wait Boolean wait set if current thread should wait as a result of sync operation + * @return Result of operation, 0 on success, otherwise error code + */ + Result SyncRequest(bool* wait) { + // TODO(bunnei): ImplementMe + locked = true; + return 0; + } + + /** + * Wait for kernel object to synchronize + * @param wait Boolean wait set if current thread should wait as a result of sync operation + * @return Result of operation, 0 on success, otherwise error code + */ + Result WaitSynchronization(bool* wait) { + // TODO(bunnei): ImplementMe + *wait = locked; + + if (locked) { + Kernel::WaitCurrentThread(WAITTYPE_MUTEX); + } + + return 0; + } +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +typedef std::multimap<Handle, Handle> MutexMap; +static MutexMap g_mutex_held_locks; + +void MutexAcquireLock(Mutex* mutex, Handle thread) { + g_mutex_held_locks.insert(std::make_pair(thread, mutex->GetHandle())); + mutex->lock_thread = thread; +} + +void MutexAcquireLock(Mutex* mutex) { + Handle thread = GetCurrentThreadHandle(); + MutexAcquireLock(mutex, thread); +} + +void MutexEraseLock(Mutex* mutex) { + Handle handle = mutex->GetHandle(); + auto locked = g_mutex_held_locks.equal_range(mutex->lock_thread); + for (MutexMap::iterator iter = locked.first; iter != locked.second; ++iter) { + if ((*iter).second == handle) { + g_mutex_held_locks.erase(iter); + break; + } + } + mutex->lock_thread = -1; +} + +bool LockMutex(Mutex* mutex) { + // Mutex alread locked? + if (mutex->locked) { + return false; + } + MutexAcquireLock(mutex); + return true; +} + +bool ReleaseMutexForThread(Mutex* mutex, Handle thread) { + MutexAcquireLock(mutex, thread); + Kernel::ResumeThreadFromWait(thread); + return true; +} + +bool ReleaseMutex(Mutex* mutex) { + MutexEraseLock(mutex); + bool woke_threads = false; + + // Find the next waiting thread for the mutex... + while (!woke_threads && !mutex->waiting_threads.empty()) { + std::vector<Handle>::iterator iter = mutex->waiting_threads.begin(); + woke_threads |= ReleaseMutexForThread(mutex, *iter); + mutex->waiting_threads.erase(iter); + } + // Reset mutex lock thread handle, nothing is waiting + if (!woke_threads) { + mutex->locked = false; + mutex->lock_thread = -1; + } + return woke_threads; +} + +/** + * Releases a mutex + * @param handle Handle to mutex to release + */ +Result ReleaseMutex(Handle handle) { + Mutex* mutex = Kernel::g_object_pool.GetFast<Mutex>(handle); + + _assert_msg_(KERNEL, (mutex != nullptr), "ReleaseMutex tried to release a nullptr mutex!"); + + if (!ReleaseMutex(mutex)) { + return -1; + } + return 0; +} + +/** + * Creates a mutex + * @param handle Reference to handle for the newly created mutex + * @param initial_locked Specifies if the mutex should be locked initially + * @param name Optional name of mutex + * @return Pointer to new Mutex object + */ +Mutex* CreateMutex(Handle& handle, bool initial_locked, const std::string& name) { + Mutex* mutex = new Mutex; + handle = Kernel::g_object_pool.Create(mutex); + + mutex->locked = mutex->initial_locked = initial_locked; + mutex->name = name; + + // Acquire mutex with current thread if initialized as locked... + if (mutex->locked) { + MutexAcquireLock(mutex); + + // Otherwise, reset lock thread handle + } else { + mutex->lock_thread = -1; + } + return mutex; +} + +/** + * Creates a mutex + * @param initial_locked Specifies if the mutex should be locked initially + * @param name Optional name of mutex + * @return Handle to newly created object + */ +Handle CreateMutex(bool initial_locked, const std::string& name) { + Handle handle; + Mutex* mutex = CreateMutex(handle, initial_locked, name); + return handle; +} + +} // namespace diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h new file mode 100644 index 000000000..7d7b5137e --- /dev/null +++ b/src/core/hle/kernel/mutex.h @@ -0,0 +1,28 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" + +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +/** + * Releases a mutex + * @param handle Handle to mutex to release + * @return Result of operation, 0 on success, otherwise error code + */ +Result ReleaseMutex(Handle handle); + +/** + * Creates a mutex + * @param initial_locked Specifies if the mutex should be locked initially + * @param name Optional name of mutex + * @return Handle to newly created object + */ +Handle CreateMutex(bool initial_locked, const std::string& name="Unknown"); + +} // namespace diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp new file mode 100644 index 000000000..ab5a5559e --- /dev/null +++ b/src/core/hle/kernel/thread.cpp @@ -0,0 +1,436 @@ +// Copyright 2014 Citra Emulator Project / PPSSPP Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <stdio.h> + +#include <list> +#include <algorithm> +#include <vector> +#include <map> +#include <string> + +#include "common/common.h" +#include "common/thread_queue_list.h" + +#include "core/core.h" +#include "core/mem_map.h" +#include "core/hle/hle.h" +#include "core/hle/svc.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" + +namespace Kernel { + +class Thread : public Kernel::Object { +public: + + const char* GetName() const { return name; } + const char* GetTypeName() const { return "Thread"; } + + static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Thread; } + Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Thread; } + + inline bool IsRunning() const { return (status & THREADSTATUS_RUNNING) != 0; } + inline bool IsStopped() const { return (status & THREADSTATUS_DORMANT) != 0; } + inline bool IsReady() const { return (status & THREADSTATUS_READY) != 0; } + inline bool IsWaiting() const { return (status & THREADSTATUS_WAIT) != 0; } + inline bool IsSuspended() const { return (status & THREADSTATUS_SUSPEND) != 0; } + + /** + * Wait for kernel object to synchronize + * @param wait Boolean wait set if current thread should wait as a result of sync operation + * @return Result of operation, 0 on success, otherwise error code + */ + Result WaitSynchronization(bool* wait) { + 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; + } + + ThreadContext context; + + u32 status; + u32 entry_point; + u32 stack_top; + u32 stack_size; + + s32 initial_priority; + s32 current_priority; + + s32 processor_id; + + WaitType wait_type; + Handle wait_handle; + + std::vector<Handle> waiting_threads; + + char name[Kernel::MAX_NAME_LENGTH + 1]; +}; + +// Lists all thread ids that aren't deleted/etc. +std::vector<Handle> g_thread_queue; + +// Lists only ready thread ids. +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; +} + +/// Gets the current thread handle +Handle GetCurrentThreadHandle() { + return GetCurrentThread()->GetHandle(); +} + +/// Sets the current thread +inline void SetCurrentThread(Thread* t) { + g_current_thread = t; + g_current_thread_handle = t->GetHandle(); +} + +/// Saves the current CPU context +void SaveContext(ThreadContext& ctx) { + Core::g_app_core->SaveContext(ctx); +} + +/// Loads a CPU context +void LoadContext(ThreadContext& ctx) { + Core::g_app_core->LoadContext(ctx); +} + +/// Resets a thread +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->context.reg_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 +void ChangeReadyState(Thread* t, bool ready) { + Handle handle = t->GetHandle(); + if (t->IsReady()) { + if (!ready) { + g_thread_ready_queue.remove(t->current_priority, handle); + } + } else if (ready) { + if (t->IsRunning()) { + g_thread_ready_queue.push_front(t->current_priority, handle); + } else { + g_thread_ready_queue.push_back(t->current_priority, handle); + } + t->status = THREADSTATUS_READY; + } +} + +/// Verify that a thread has not been released from waiting +inline bool VerifyWait(const Handle& handle, WaitType type, Handle wait_handle) { + Thread* thread = g_object_pool.GetFast<Thread>(handle); + _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); + + if (type != thread->wait_type || wait_handle != thread->wait_handle) + return false; + + return true; +} + +/// Stops the current thread +void StopThread(Handle handle, const char* reason) { + Thread* thread = g_object_pool.GetFast<Thread>(handle); + _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); + + ChangeReadyState(thread, false); + thread->status = THREADSTATUS_DORMANT; + for (size_t i = 0; i < thread->waiting_threads.size(); ++i) { + const Handle waiting_thread = thread->waiting_threads[i]; + if (VerifyWait(waiting_thread, WAITTYPE_THREADEND, handle)) { + ResumeThreadFromWait(waiting_thread); + } + } + thread->waiting_threads.clear(); + + // Stopped threads are never waiting. + thread->wait_type = WAITTYPE_NONE; + thread->wait_handle = 0; +} + +/// Changes a threads state +void ChangeThreadState(Thread* t, ThreadStatus new_status) { + if (!t || t->status == new_status) { + return; + } + ChangeReadyState(t, (new_status & THREADSTATUS_READY) != 0); + t->status = new_status; + + if (new_status == THREADSTATUS_WAIT) { + if (t->wait_type == WAITTYPE_NONE) { + ERROR_LOG(KERNEL, "Waittype none not allowed"); + } + } +} + +/// Calls a thread by marking it as "ready" (note: will not actually execute until current thread yields) +void CallThread(Thread* t) { + // Stop waiting + if (t->wait_type != WAITTYPE_NONE) { + t->wait_type = WAITTYPE_NONE; + } + ChangeThreadState(t, THREADSTATUS_READY); +} + +/// Switches CPU context to that of the specified thread +void SwitchContext(Thread* t) { + Thread* cur = GetCurrentThread(); + + // Save context for current thread + if (cur) { + SaveContext(cur->context); + + if (cur->IsRunning()) { + ChangeReadyState(cur, true); + } + } + // Load context of new thread + if (t) { + SetCurrentThread(t); + ChangeReadyState(t, false); + t->status = (t->status | THREADSTATUS_RUNNING) & ~THREADSTATUS_READY; + t->wait_type = WAITTYPE_NONE; + LoadContext(t->context); + } else { + SetCurrentThread(nullptr); + } +} + +/// Gets the next thread that is ready to be run by priority +Thread* NextThread() { + Handle next; + Thread* cur = GetCurrentThread(); + + if (cur && cur->IsRunning()) { + next = g_thread_ready_queue.pop_first_better(cur->current_priority); + } else { + next = g_thread_ready_queue.pop_first(); + } + if (next == 0) { + return nullptr; + } + return Kernel::g_object_pool.GetFast<Thread>(next); +} + +/// Puts the current thread in the wait state for the given type +void WaitCurrentThread(WaitType wait_type, Handle wait_handle) { + Thread* thread = GetCurrentThread(); + thread->wait_type = wait_type; + thread->wait_handle = wait_handle; + ChangeThreadState(thread, ThreadStatus(THREADSTATUS_WAIT | (thread->status & THREADSTATUS_SUSPEND))); +} + +/// Resumes a thread from waiting by marking it as "ready" +void ResumeThreadFromWait(Handle handle) { + u32 error; + Thread* thread = Kernel::g_object_pool.Get<Thread>(handle, error); + if (thread) { + thread->status &= ~THREADSTATUS_WAIT; + if (!(thread->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) { + ChangeReadyState(thread, true); + } + } +} + +/// 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) { + + _assert_msg_(KERNEL, (priority >= THREADPRIO_HIGHEST && priority <= THREADPRIO_LOWEST), + "CreateThread priority=%d, outside of allowable range!", priority) + + Thread* thread = new Thread; + + handle = Kernel::g_object_pool.Create(thread); + + g_thread_queue.push_back(handle); + g_thread_ready_queue.prepare(priority); + + thread->status = THREADSTATUS_DORMANT; + thread->entry_point = entry_point; + thread->stack_top = stack_top; + thread->stack_size = stack_size; + thread->initial_priority = thread->current_priority = priority; + thread->processor_id = processor_id; + thread->wait_type = WAITTYPE_NONE; + thread->wait_handle = 0; + + strncpy(thread->name, name, Kernel::MAX_NAME_LENGTH); + thread->name[Kernel::MAX_NAME_LENGTH] = '\0'; + + return thread; +} + +/// 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 == nullptr) { + ERROR_LOG(KERNEL, "CreateThread(): nullptr name"); + return -1; + } + if ((u32)stack_size < 0x200) { + ERROR_LOG(KERNEL, "CreateThread(name=%s): invalid stack_size=0x%08X", name, + stack_size); + return -1; + } + if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { + s32 new_priority = CLAMP(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); + WARN_LOG(KERNEL, "CreateThread(name=%s): invalid priority=0x%08X, clamping to %08X", + name, priority, new_priority); + // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm + // validity of this + priority = new_priority; + } + if (!Memory::GetPointer(entry_point)) { + ERROR_LOG(KERNEL, "CreateThread(name=%s): invalid entry %08x", name, entry_point); + return -1; + } + Handle handle; + Thread* thread = CreateThread(handle, name, entry_point, priority, processor_id, stack_top, + stack_size); + + ResetThread(thread, arg, 0); + CallThread(thread); + + return handle; +} + +/// Get the priority of the thread specified by handle +u32 GetThreadPriority(const Handle handle) { + Thread* thread = g_object_pool.GetFast<Thread>(handle); + _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); + return thread->current_priority; +} + +/// Set the priority of the thread specified by handle +Result SetThreadPriority(Handle handle, s32 priority) { + Thread* thread = nullptr; + if (!handle) { + thread = GetCurrentThread(); // TODO(bunnei): Is this correct behavior? + } else { + thread = g_object_pool.GetFast<Thread>(handle); + } + _assert_msg_(KERNEL, (thread != nullptr), "called, but thread is nullptr!"); + + // If priority is invalid, clamp to valid range + if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) { + s32 new_priority = CLAMP(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST); + WARN_LOG(KERNEL, "invalid priority=0x%08X, clamping to %08X", priority, new_priority); + // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm + // validity of this + priority = new_priority; + } + + // Change thread priority + s32 old = thread->current_priority; + g_thread_ready_queue.remove(old, handle); + thread->current_priority = priority; + g_thread_ready_queue.prepare(thread->current_priority); + + // Change thread status to "ready" and push to ready queue + if (thread->IsRunning()) { + thread->status = (thread->status & ~THREADSTATUS_RUNNING) | THREADSTATUS_READY; + } + if (thread->IsReady()) { + g_thread_ready_queue.push_back(thread->current_priority, handle); + } + + return 0; +} + +/// Sets up the primary application thread +Handle SetupMainThread(s32 priority, int stack_size) { + Handle handle; + + // Initialize new "main" thread + Thread* thread = CreateThread(handle, "main", Core::g_app_core->GetPC(), priority, + THREADPROCESSORID_0, Memory::SCRATCHPAD_VADDR_END, stack_size); + + ResetThread(thread, 0, 0); + + // If running another thread already, set it to "ready" state + Thread* cur = GetCurrentThread(); + if (cur && cur->IsRunning()) { + ChangeReadyState(cur, true); + } + + // Run new "main" thread + SetCurrentThread(thread); + thread->status = THREADSTATUS_RUNNING; + LoadContext(thread->context); + + 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 - 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()); + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +void ThreadingInit() { +} + +void ThreadingShutdown() { +} + +} // namespace diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h new file mode 100644 index 000000000..04914ba90 --- /dev/null +++ b/src/core/hle/kernel/thread.h @@ -0,0 +1,83 @@ +// Copyright 2014 Citra Emulator Project / PPSSPP Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hle/kernel/kernel.h" + +enum ThreadPriority { + THREADPRIO_HIGHEST = 0, ///< Highest thread priority + THREADPRIO_DEFAULT = 16, ///< Default thread priority for userland apps + THREADPRIO_LOW = 31, ///< Low range of thread priority for userland apps + THREADPRIO_LOWEST = 63, ///< Thread priority max checked by svcCreateThread +}; + +enum ThreadProcessorId { + THREADPROCESSORID_0 = 0xFFFFFFFE, ///< Enables core appcode + THREADPROCESSORID_1 = 0xFFFFFFFD, ///< Enables core syscore + THREADPROCESSORID_ALL = 0xFFFFFFFC, ///< Enables both cores +}; + +enum ThreadStatus { + THREADSTATUS_RUNNING = 1, + THREADSTATUS_READY = 2, + THREADSTATUS_WAIT = 4, + THREADSTATUS_SUSPEND = 8, + THREADSTATUS_DORMANT = 16, + THREADSTATUS_DEAD = 32, + THREADSTATUS_WAITSUSPEND = THREADSTATUS_WAIT | THREADSTATUS_SUSPEND +}; + +enum WaitType { + WAITTYPE_NONE, + WAITTYPE_SLEEP, + WAITTYPE_SEMA, + WAITTYPE_EVENT, + WAITTYPE_THREADEND, + WAITTYPE_VBLANK, + WAITTYPE_MUTEX, + WAITTYPE_SYNCH, +}; + +namespace Kernel { + +/// 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=Kernel::DEFAULT_STACK_SIZE); + +/// Sets up the primary application thread +Handle SetupMainThread(s32 priority, int stack_size=Kernel::DEFAULT_STACK_SIZE); + +/// Reschedules to the next available thread (call after current thread is suspended) +void Reschedule(); + +/// Stops the current thread +void StopThread(Handle thread, const char* reason); + +/// Resumes a thread from waiting by marking it as "ready" +void ResumeThreadFromWait(Handle handle); + +/// Gets the current thread handle +Handle GetCurrentThreadHandle(); + +/// Puts the current thread in the wait state for the given type +void WaitCurrentThread(WaitType wait_type, Handle wait_handle=GetCurrentThreadHandle()); + +/// Put current thread in a wait state - on WaitSynchronization +void WaitThread_Synchronization(); + +/// Get the priority of the thread specified by handle +u32 GetThreadPriority(const Handle handle); + +/// Set the priority of the thread specified by handle +Result SetThreadPriority(Handle handle, s32 priority); + +/// Initialize threading +void ThreadingInit(); + +/// Shutdown threading +void ThreadingShutdown(); + +} // namespace |
