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