44 changed files with 1559 additions and 193 deletions
-
2src/citra_qt/main.cpp
-
1src/common/common.vcxproj
-
1src/common/common.vcxproj.filters
-
5src/common/common_funcs.h
-
4src/common/log.h
-
4src/common/log_manager.cpp
-
216src/common/thread_queue_list.h
-
5src/core/CMakeLists.txt
-
30src/core/arm/arm_interface.h
-
91src/core/arm/interpreter/arm_interpreter.cpp
-
20src/core/arm/interpreter/arm_interpreter.h
-
4src/core/arm/interpreter/armdefs.h
-
5src/core/arm/interpreter/armemu.cpp
-
3src/core/arm/interpreter/arminit.cpp
-
2src/core/arm/interpreter/vfp/vfp.h
-
7src/core/core.cpp
-
10src/core/core.vcxproj
-
39src/core/core.vcxproj.filters
-
14src/core/hle/function_wrappers.h
-
25src/core/hle/hle.cpp
-
6src/core/hle/hle.h
-
158src/core/hle/kernel/kernel.cpp
-
154src/core/hle/kernel/kernel.h
-
132src/core/hle/kernel/mutex.cpp
-
26src/core/hle/kernel/mutex.h
-
323src/core/hle/kernel/thread.cpp
-
74src/core/hle/kernel/thread.h
-
8src/core/hle/service/apt.cpp
-
2src/core/hle/service/apt.h
-
4src/core/hle/service/gsp.cpp
-
2src/core/hle/service/gsp.h
-
2src/core/hle/service/hid.h
-
28src/core/hle/service/service.cpp
-
72src/core/hle/service/service.h
-
14src/core/hle/service/srv.cpp
-
4src/core/hle/service/srv.h
-
140src/core/hle/svc.cpp
-
48src/core/hle/svc.h
-
19src/core/hle/syscall.h
-
4src/core/hw/lcd.cpp
-
20src/core/loader.cpp
-
3src/core/mem_map.cpp
-
7src/core/mem_map.h
-
14src/core/mem_map_funcs.cpp
@ -0,0 +1,216 @@ |
|||
// Copyright 2014 Citra Emulator Project / PPSSPP Project |
|||
// Licensed under GPLv2 |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common.h" |
|||
|
|||
namespace Common { |
|||
|
|||
template<class IdType> |
|||
struct ThreadQueueList { |
|||
// Number of queues (number of priority levels starting at 0.) |
|||
static const int NUM_QUEUES = 128; |
|||
|
|||
// Initial number of threads a single queue can handle. |
|||
static const int INITIAL_CAPACITY = 32; |
|||
|
|||
struct Queue { |
|||
// Next ever-been-used queue (worse priority.) |
|||
Queue *next; |
|||
// First valid item in data. |
|||
int first; |
|||
// One after last valid item in data. |
|||
int end; |
|||
// A too-large array with room on the front and end. |
|||
IdType *data; |
|||
// Size of data array. |
|||
int capacity; |
|||
}; |
|||
|
|||
ThreadQueueList() { |
|||
memset(queues, 0, sizeof(queues)); |
|||
first = invalid(); |
|||
} |
|||
|
|||
~ThreadQueueList() { |
|||
for (int i = 0; i < NUM_QUEUES; ++i) |
|||
{ |
|||
if (queues[i].data != NULL) |
|||
free(queues[i].data); |
|||
} |
|||
} |
|||
|
|||
// Only for debugging, returns priority level. |
|||
int contains(const IdType uid) { |
|||
for (int i = 0; i < NUM_QUEUES; ++i) |
|||
{ |
|||
if (queues[i].data == NULL) |
|||
continue; |
|||
|
|||
Queue *cur = &queues[i]; |
|||
for (int j = cur->first; j < cur->end; ++j) |
|||
{ |
|||
if (cur->data[j] == uid) |
|||
return i; |
|||
} |
|||
} |
|||
|
|||
return -1; |
|||
} |
|||
|
|||
inline IdType pop_first() { |
|||
Queue *cur = first; |
|||
while (cur != invalid()) |
|||
{ |
|||
if (cur->end - cur->first > 0) |
|||
return cur->data[cur->first++]; |
|||
cur = cur->next; |
|||
} |
|||
|
|||
//_dbg_assert_msg_(SCEKERNEL, false, "ThreadQueueList should not be empty."); |
|||
return 0; |
|||
} |
|||
|
|||
inline IdType pop_first_better(u32 priority) { |
|||
Queue *cur = first; |
|||
Queue *stop = &queues[priority]; |
|||
while (cur < stop) |
|||
{ |
|||
if (cur->end - cur->first > 0) |
|||
return cur->data[cur->first++]; |
|||
cur = cur->next; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
inline void push_front(u32 priority, const IdType threadID) { |
|||
Queue *cur = &queues[priority]; |
|||
cur->data[--cur->first] = threadID; |
|||
if (cur->first == 0) |
|||
rebalance(priority); |
|||
} |
|||
|
|||
inline void push_back(u32 priority, const IdType threadID) { |
|||
Queue *cur = &queues[priority]; |
|||
cur->data[cur->end++] = threadID; |
|||
if (cur->end == cur->capacity) |
|||
rebalance(priority); |
|||
} |
|||
|
|||
inline void remove(u32 priority, const IdType threadID) { |
|||
Queue *cur = &queues[priority]; |
|||
//_dbg_assert_msg_(SCEKERNEL, cur->next != NULL, "ThreadQueueList::Queue should already be linked up."); |
|||
|
|||
for (int i = cur->first; i < cur->end; ++i) |
|||
{ |
|||
if (cur->data[i] == threadID) |
|||
{ |
|||
int remaining = --cur->end - i; |
|||
if (remaining > 0) |
|||
memmove(&cur->data[i], &cur->data[i + 1], remaining * sizeof(IdType)); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
// Wasn't there. |
|||
} |
|||
|
|||
inline void rotate(u32 priority) { |
|||
Queue *cur = &queues[priority]; |
|||
//_dbg_assert_msg_(SCEKERNEL, cur->next != NULL, "ThreadQueueList::Queue should already be linked up."); |
|||
|
|||
if (cur->end - cur->first > 1) |
|||
{ |
|||
cur->data[cur->end++] = cur->data[cur->first++]; |
|||
if (cur->end == cur->capacity) |
|||
rebalance(priority); |
|||
} |
|||
} |
|||
|
|||
inline void clear() { |
|||
for (int i = 0; i < NUM_QUEUES; ++i) |
|||
{ |
|||
if (queues[i].data != NULL) |
|||
free(queues[i].data); |
|||
} |
|||
memset(queues, 0, sizeof(queues)); |
|||
first = invalid(); |
|||
} |
|||
|
|||
inline bool empty(u32 priority) const { |
|||
const Queue *cur = &queues[priority]; |
|||
return cur->first == cur->end; |
|||
} |
|||
|
|||
inline void prepare(u32 priority) { |
|||
Queue *cur = &queues[priority]; |
|||
if (cur->next == NULL) |
|||
link(priority, INITIAL_CAPACITY); |
|||
} |
|||
|
|||
private: |
|||
Queue *invalid() const { |
|||
return (Queue *) -1; |
|||
} |
|||
|
|||
void link(u32 priority, int size) { |
|||
//_dbg_assert_msg_(SCEKERNEL, queues[priority].data == NULL, "ThreadQueueList::Queue should only be initialized once."); |
|||
|
|||
if (size <= INITIAL_CAPACITY) |
|||
size = INITIAL_CAPACITY; |
|||
else |
|||
{ |
|||
int goal = size; |
|||
size = INITIAL_CAPACITY; |
|||
while (size < goal) |
|||
size *= 2; |
|||
} |
|||
Queue *cur = &queues[priority]; |
|||
cur->data = (IdType *) malloc(sizeof(IdType) * size); |
|||
cur->capacity = size; |
|||
cur->first = size / 2; |
|||
cur->end = size / 2; |
|||
|
|||
for (int i = (int) priority - 1; i >= 0; --i) |
|||
{ |
|||
if (queues[i].next != NULL) |
|||
{ |
|||
cur->next = queues[i].next; |
|||
queues[i].next = cur; |
|||
return; |
|||
} |
|||
} |
|||
|
|||
cur->next = first; |
|||
first = cur; |
|||
} |
|||
|
|||
void rebalance(u32 priority) { |
|||
Queue *cur = &queues[priority]; |
|||
int size = cur->end - cur->first; |
|||
if (size >= cur->capacity - 2) { |
|||
IdType *new_data = (IdType *)realloc(cur->data, cur->capacity * 2 * sizeof(IdType)); |
|||
if (new_data != NULL) { |
|||
cur->capacity *= 2; |
|||
cur->data = new_data; |
|||
} |
|||
} |
|||
|
|||
int newFirst = (cur->capacity - size) / 2; |
|||
if (newFirst != cur->first) { |
|||
memmove(&cur->data[newFirst], &cur->data[cur->first], size * sizeof(IdType)); |
|||
cur->first = newFirst; |
|||
cur->end = newFirst + size; |
|||
} |
|||
} |
|||
|
|||
// The first queue that's ever been used. |
|||
Queue *first; |
|||
// The priority level queues of thread ids. |
|||
Queue queues[NUM_QUEUES]; |
|||
}; |
|||
|
|||
} // namespace |
|||
@ -0,0 +1,158 @@ |
|||
// Copyright 2014 Citra Emulator Project / PPSSPP Project
|
|||
// Licensed under GPLv2
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#pragma once
|
|||
|
|||
#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 { |
|||
|
|||
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 NULL; |
|||
} |
|||
} |
|||
|
|||
void Init() { |
|||
Kernel::ThreadingInit(); |
|||
} |
|||
|
|||
void Shutdown() { |
|||
Kernel::ThreadingShutdown(); |
|||
} |
|||
|
|||
/**
|
|||
* 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
|
|||
Handle thread = Kernel::SetupMainThread(0x30); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
} // namespace
|
|||
@ -0,0 +1,154 @@ |
|||
// 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 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() { return "[BAD KERNEL OBJECT TYPE]"; } |
|||
virtual const char *GetName() { return "[UNKNOWN KERNEL OBJECT]"; } |
|||
virtual Kernel::HandleType GetHandleType() const = 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; |
|||
|
|||
/** |
|||
* 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 |
|||
@ -0,0 +1,132 @@ |
|||
// 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/thread.h"
|
|||
|
|||
namespace Kernel { |
|||
|
|||
class Mutex : public Object { |
|||
public: |
|||
const char* GetTypeName() { return "Mutex"; } |
|||
|
|||
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
|
|||
}; |
|||
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
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; |
|||
auto iter = mutex->waiting_threads.begin(); |
|||
|
|||
// Find the next waiting thread for the mutex...
|
|||
while (!woke_threads && !mutex->waiting_threads.empty()) { |
|||
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); |
|||
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 |
|||
*/ |
|||
Mutex* CreateMutex(Handle& handle, bool initial_locked) { |
|||
Mutex* mutex = new Mutex; |
|||
handle = Kernel::g_object_pool.Create(mutex); |
|||
|
|||
mutex->locked = mutex->initial_locked = initial_locked; |
|||
|
|||
// 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 |
|||
*/ |
|||
Handle CreateMutex(bool initial_locked) { |
|||
Handle handle; |
|||
Mutex* mutex = CreateMutex(handle, initial_locked); |
|||
return handle; |
|||
} |
|||
|
|||
} // namespace
|
|||
@ -0,0 +1,26 @@ |
|||
// 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 |
|||
*/ |
|||
Result ReleaseMutex(Handle handle); |
|||
|
|||
/** |
|||
* Creates a mutex |
|||
* @param handle Reference to handle for the newly created mutex |
|||
* @param initial_locked Specifies if the mutex should be locked initially |
|||
*/ |
|||
Handle CreateMutex(bool initial_locked); |
|||
|
|||
} // namespace |
|||
@ -0,0 +1,323 @@ |
|||
// Copyright 2014 Citra Emulator Project / PPSSPP Project
|
|||
// Licensed under GPLv2
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <stdio.h>
|
|||
|
|||
#include <list>
|
|||
#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() { return name; } |
|||
const char* GetTypeName() { 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; } |
|||
|
|||
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; |
|||
|
|||
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->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; |
|||
} |
|||
|
|||
/// 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; |
|||
} |
|||
} |
|||
|
|||
/// 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) { |
|||
printf("ERROR: Waittype none not allowed here\n"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// 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(NULL); |
|||
} |
|||
} |
|||
|
|||
/// 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 NULL; |
|||
} |
|||
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) { |
|||
Thread* t = GetCurrentThread(); |
|||
t->wait_type = wait_type; |
|||
ChangeThreadState(t, ThreadStatus(THREADSTATUS_WAIT | (t->status & THREADSTATUS_SUSPEND))); |
|||
} |
|||
|
|||
/// Resumes a thread from waiting by marking it as "ready"
|
|||
void ResumeThreadFromWait(Handle handle) { |
|||
u32 error; |
|||
Thread* t = Kernel::g_object_pool.Get<Thread>(handle, error); |
|||
if (t) { |
|||
t->status &= ~THREADSTATUS_WAIT; |
|||
if (!(t->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) { |
|||
ChangeReadyState(t, true); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// 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* 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; |
|||
t->stack_size = stack_size; |
|||
t->initial_priority = t->current_priority = priority; |
|||
t->processor_id = processor_id; |
|||
t->wait_type = WAITTYPE_NONE; |
|||
|
|||
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; |
|||
} |
|||
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* t = CreateThread(handle, name, entry_point, priority, processor_id, stack_top, |
|||
stack_size); |
|||
|
|||
ResetThread(t, arg, 0); |
|||
|
|||
HLE::EatCycles(32000); |
|||
|
|||
// 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"); |
|||
|
|||
CallThread(t); |
|||
|
|||
return handle; |
|||
} |
|||
|
|||
/// Sets up the primary application thread
|
|||
Handle SetupMainThread(s32 priority, int stack_size) { |
|||
Handle handle; |
|||
|
|||
// Initialize new "main" thread
|
|||
Thread* t = CreateThread(handle, "main", Core::g_app_core->GetPC(), priority, |
|||
THREADPROCESSORID_0, Memory::SCRATCHPAD_VADDR_END, stack_size); |
|||
|
|||
ResetThread(t, 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(t); |
|||
t->status = THREADSTATUS_RUNNING; |
|||
LoadContext(t->context); |
|||
|
|||
return handle; |
|||
} |
|||
|
|||
/// Reschedules to the next available thread (call after current thread is suspended)
|
|||
void Reschedule() { |
|||
Thread* prev = GetCurrentThread(); |
|||
Thread* next = NextThread(); |
|||
if (next > 0) { |
|||
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); |
|||
} |
|||
} |
|||
|
|||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|||
|
|||
void ThreadingInit() { |
|||
} |
|||
|
|||
void ThreadingShutdown() { |
|||
} |
|||
|
|||
} // namespace
|
|||
@ -0,0 +1,74 @@ |
|||
// 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_EVENTFLAG, |
|||
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(); |
|||
|
|||
/// Puts the current thread in the wait state for the given type |
|||
void WaitCurrentThread(WaitType wait_type); |
|||
|
|||
/// Resumes a thread from waiting by marking it as "ready" |
|||
void ResumeThreadFromWait(Handle handle); |
|||
|
|||
/// Gets the current thread handle |
|||
Handle GetCurrentThreadHandle(); |
|||
|
|||
/// Put current thread in a wait state - on WaitSynchronization |
|||
void WaitThread_Synchronization(); |
|||
|
|||
/// Initialize threading |
|||
void ThreadingInit(); |
|||
|
|||
/// Shutdown threading |
|||
void ThreadingShutdown(); |
|||
|
|||
} // namespace |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
// SVC types |
|||
|
|||
struct MemoryInfo { |
|||
u32 base_address; |
|||
u32 size; |
|||
u32 permission; |
|||
u32 state; |
|||
}; |
|||
|
|||
struct PageInfo { |
|||
u32 flags; |
|||
}; |
|||
|
|||
struct ThreadContext { |
|||
u32 cpu_registers[13]; |
|||
u32 sp; |
|||
u32 lr; |
|||
u32 pc; |
|||
u32 cpsr; |
|||
u32 fpu_registers[32]; |
|||
u32 fpscr; |
|||
u32 fpexc; |
|||
}; |
|||
|
|||
enum ResetType { |
|||
RESETTYPE_ONESHOT, |
|||
RESETTYPE_STICKY, |
|||
RESETTYPE_PULSE, |
|||
RESETTYPE_MAX_BIT = (1u << 31), |
|||
}; |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
// Namespace SVC |
|||
|
|||
namespace SVC { |
|||
|
|||
void Register(); |
|||
|
|||
} // namespace |
|||
@ -1,19 +0,0 @@ |
|||
// Copyright 2014 Citra Emulator Project |
|||
// Licensed under GPLv2 |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
//////////////////////////////////////////////////////////////////////////////////////////////////// |
|||
// Namespace Syscall |
|||
|
|||
namespace Syscall { |
|||
|
|||
typedef u32 Handle; |
|||
typedef s32 Result; |
|||
|
|||
void Register(); |
|||
|
|||
} // namespace |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue