7 changed files with 548 additions and 0 deletions
-
2src/core/CMakeLists.txt
-
4src/core/core.vcxproj
-
15src/core/core.vcxproj.filters
-
142src/core/hle/kernel/kernel.cpp
-
121src/core/hle/kernel/kernel.h
-
228src/core/hle/kernel/thread.cpp
-
36src/core/hle/kernel/thread.h
@ -0,0 +1,142 @@ |
|||
// 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/hle/kernel/kernel.h"
|
|||
#include "core/hle/kernel/thread.h"
|
|||
|
|||
KernelObjectPool g_kernel_objects; |
|||
|
|||
void __KernelInit() { |
|||
__KernelThreadingInit(); |
|||
} |
|||
|
|||
void __KernelShutdown() { |
|||
__KernelThreadingShutdown(); |
|||
} |
|||
|
|||
KernelObjectPool::KernelObjectPool() { |
|||
memset(occupied, 0, sizeof(bool) * MAX_COUNT); |
|||
next_id = INITIAL_NEXT_ID; |
|||
} |
|||
|
|||
UID KernelObjectPool::Create(KernelObject *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]->uid = i + HANDLE_OFFSET; |
|||
return i + HANDLE_OFFSET; |
|||
} |
|||
} |
|||
ERROR_LOG(HLE, "Unable to allocate kernel object, too many objects slots in use."); |
|||
return 0; |
|||
} |
|||
|
|||
bool KernelObjectPool::IsValid(UID handle) |
|||
{ |
|||
int index = handle - HANDLE_OFFSET; |
|||
if (index < 0) |
|||
return false; |
|||
if (index >= MAX_COUNT) |
|||
return false; |
|||
|
|||
return occupied[index]; |
|||
} |
|||
|
|||
void KernelObjectPool::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(KernelObject*)*MAX_COUNT); |
|||
next_id = INITIAL_NEXT_ID; |
|||
} |
|||
|
|||
KernelObject *&KernelObjectPool::operator [](UID handle) |
|||
{ |
|||
_dbg_assert_msg_(KERNEL, IsValid(handle), "GRABBING UNALLOCED KERNEL OBJ"); |
|||
return pool[handle - HANDLE_OFFSET]; |
|||
} |
|||
|
|||
void KernelObjectPool::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 KernelObjectPool::GetCount() |
|||
{ |
|||
int count = 0; |
|||
for (int i = 0; i < MAX_COUNT; i++) |
|||
{ |
|||
if (occupied[i]) |
|||
count++; |
|||
} |
|||
return count; |
|||
} |
|||
|
|||
KernelObject *KernelObjectPool::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; |
|||
} |
|||
} |
|||
@ -0,0 +1,121 @@ |
|||
// Copyright 2014 Citra Emulator Project / PPSSPP Project |
|||
// Licensed under GPLv2 |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
typedef u32 UID; |
|||
|
|||
class KernelObjectPool; |
|||
|
|||
class KernelObject { |
|||
friend class KernelObjectPool; |
|||
u32 uid; |
|||
public: |
|||
virtual ~KernelObject() {} |
|||
UID GetUID() const { return uid; } |
|||
virtual const char *GetTypeName() { return "[BAD KERNEL OBJECT TYPE]"; } |
|||
virtual const char *GetName() { return "[UNKNOWN KERNEL OBJECT]"; } |
|||
virtual int GetIDType() const = 0; |
|||
//virtual void GetQuickInfo(char *ptr, int size); |
|||
}; |
|||
|
|||
class KernelObjectPool { |
|||
public: |
|||
KernelObjectPool(); |
|||
~KernelObjectPool() {} |
|||
|
|||
// Allocates a UID within the range and inserts the object into the map. |
|||
UID Create(KernelObject *obj, int range_bottom=INITIAL_NEXT_ID, int range_top=0x7FFFFFFF); |
|||
|
|||
static KernelObject *CreateByIDType(int type); |
|||
|
|||
template <class T> |
|||
u32 Destroy(UID handle) { |
|||
u32 error; |
|||
if (Get<T>(handle, error)) { |
|||
occupied[handle - handleOffset] = false; |
|||
delete pool[handle - handleOffset]; |
|||
} |
|||
return error; |
|||
}; |
|||
|
|||
bool IsValid(UID handle); |
|||
|
|||
template <class T> |
|||
T* Get(UID handle, u32& outError) { |
|||
if (handle < handleOffset || handle >= handleOffset + maxCount || !occupied[handle - handleOffset]) { |
|||
// Tekken 6 spams 0x80020001 gets wrong with no ill effects, also on the real PSP |
|||
if (handle != 0 && (u32)handle != 0x80020001) { |
|||
WARN_LOG(SCEKERNEL, "Kernel: Bad object handle %i (%08x)", handle, handle); |
|||
} |
|||
outError = 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 - handleOffset]); |
|||
if (t == 0 || t->GetIDType() != T::GetStaticIDType()) { |
|||
WARN_LOG(SCEKERNEL, "Kernel: Wrong object type for %i (%08x)", handle, handle); |
|||
outError = T::GetMissingErrorCode(); |
|||
return 0; |
|||
} |
|||
outError = SCE_KERNEL_ERROR_OK; |
|||
return t; |
|||
} |
|||
} |
|||
|
|||
// ONLY use this when you know the handle is valid. |
|||
template <class T> |
|||
T *GetFast(UID handle) { |
|||
const UID realHandle = handle - handleOffset; |
|||
_dbg_assert_(SCEKERNEL, realHandle >= 0 && realHandle < maxCount && 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 < maxCount; i++) |
|||
{ |
|||
if (!occupied[i]) |
|||
continue; |
|||
T *t = static_cast<T *>(pool[i]); |
|||
if (t->GetIDType() == type) { |
|||
if (!func(t, arg)) |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
bool GetIDType(UID handle, int *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; |
|||
} |
|||
KernelObject *t = pool[handle - HANDLE_OFFSET]; |
|||
*type = t->GetIDType(); |
|||
return true; |
|||
} |
|||
|
|||
KernelObject *&operator [](UID handle); |
|||
void List(); |
|||
void Clear(); |
|||
int GetCount(); |
|||
|
|||
private: |
|||
enum { |
|||
MAX_COUNT = 0x1000, |
|||
HANDLE_OFFSET = 0x100, |
|||
INITIAL_NEXT_ID = 0x10, |
|||
}; |
|||
KernelObject *pool[MAX_COUNT]; |
|||
bool occupied[MAX_COUNT]; |
|||
int next_id; |
|||
}; |
|||
|
|||
extern KernelObjectPool g_kernel_objects; |
|||
@ -0,0 +1,228 @@ |
|||
// 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 "core/hle/kernel/kernel.h"
|
|||
#include "core/hle/kernel/thread.h"
|
|||
|
|||
// Real CTR struct, don't change the fields.
|
|||
struct NativeThread { |
|||
//u32 Pointer to vtable
|
|||
//u32 Reference count
|
|||
//KProcess* Process the thread belongs to (virtual address)
|
|||
//u32 Thread id
|
|||
//u32* ptr = *(KThread+0x8C) - 0xB0
|
|||
//u32* End-address of the page for this thread allocated in the 0xFF4XX000 region. Thus,
|
|||
// if the beginning of this mapped page is 0xFF401000, this ptr would be 0xFF402000.
|
|||
//KThread* Previous ? (virtual address)
|
|||
//KThread* Next ? (virtual address)
|
|||
}; |
|||
|
|||
struct ThreadWaitInfo { |
|||
u32 wait_value; |
|||
u32 timeout_ptr; |
|||
}; |
|||
|
|||
class Thread : public KernelObject { |
|||
public: |
|||
/*const char *GetName() { return nt.name; }*/ |
|||
const char *GetTypeName() { return "Thread"; } |
|||
//void GetQuickInfo(char *ptr, int size)
|
|||
//{
|
|||
// sprintf(ptr, "pc= %08x sp= %08x %s %s %s %s %s %s (wt=%i wid=%i wv= %08x )",
|
|||
// context.pc, context.r[13], // 13 is stack pointer
|
|||
// (nt.status & THREADSTATUS_RUNNING) ? "RUN" : "",
|
|||
// (nt.status & THREADSTATUS_READY) ? "READY" : "",
|
|||
// (nt.status & THREADSTATUS_WAIT) ? "WAIT" : "",
|
|||
// (nt.status & THREADSTATUS_SUSPEND) ? "SUSPEND" : "",
|
|||
// (nt.status & THREADSTATUS_DORMANT) ? "DORMANT" : "",
|
|||
// (nt.status & THREADSTATUS_DEAD) ? "DEAD" : "",
|
|||
// nt.waitType,
|
|||
// nt.waitID,
|
|||
// waitInfo.waitValue);
|
|||
//}
|
|||
|
|||
//static u32 GetMissingErrorCode() { return SCE_KERNEL_ERROR_UNKNOWN_THID; }
|
|||
//static int GetStaticIDType() { return SCE_KERNEL_TMID_Thread; }
|
|||
//int GetIDType() const { return SCE_KERNEL_TMID_Thread; }
|
|||
|
|||
//bool AllocateStack(u32 &stack_size) {
|
|||
// FreeStack();
|
|||
|
|||
// bool fromTop = (nt.attr & PSP_THREAD_ATTR_LOW_STACK) == 0;
|
|||
// if (nt.attr & PSP_THREAD_ATTR_KERNEL)
|
|||
// {
|
|||
// // Allocate stacks for kernel threads (idle) in kernel RAM
|
|||
// currentStack.start = kernelMemory.Alloc(stack_size, fromTop, (std::string("stack/") + nt.name).c_str());
|
|||
// }
|
|||
// else
|
|||
// {
|
|||
// currentStack.start = userMemory.Alloc(stack_size, fromTop, (std::string("stack/") + nt.name).c_str());
|
|||
// }
|
|||
// if (currentStack.start == (u32)-1)
|
|||
// {
|
|||
// currentStack.start = 0;
|
|||
// nt.initialStack = 0;
|
|||
// ERROR_LOG(KERNEL, "Failed to allocate stack for thread");
|
|||
// return false;
|
|||
// }
|
|||
|
|||
// nt.initialStack = currentStack.start;
|
|||
// nt.stack_size = stack_size;
|
|||
// return true;
|
|||
//}
|
|||
|
|||
//bool FillStack() {
|
|||
// // Fill the stack.
|
|||
// if ((nt.attr & PSP_THREAD_ATTR_NO_FILLSTACK) == 0) {
|
|||
// Memory::Memset(currentStack.start, 0xFF, nt.stack_size);
|
|||
// }
|
|||
// context.r[MIPS_REG_SP] = currentStack.start + nt.stack_size;
|
|||
// currentStack.end = context.r[MIPS_REG_SP];
|
|||
// // The k0 section is 256 bytes at the top of the stack.
|
|||
// context.r[MIPS_REG_SP] -= 256;
|
|||
// context.r[MIPS_REG_K0] = context.r[MIPS_REG_SP];
|
|||
// u32 k0 = context.r[MIPS_REG_K0];
|
|||
// Memory::Memset(k0, 0, 0x100);
|
|||
// Memory::Write_U32(GetUID(), k0 + 0xc0);
|
|||
// Memory::Write_U32(nt.initialStack, k0 + 0xc8);
|
|||
// Memory::Write_U32(0xffffffff, k0 + 0xf8);
|
|||
// Memory::Write_U32(0xffffffff, k0 + 0xfc);
|
|||
// // After k0 comes the arguments, which is done by sceKernelStartThread().
|
|||
|
|||
// Memory::Write_U32(GetUID(), nt.initialStack);
|
|||
// return true;
|
|||
//}
|
|||
|
|||
//void FreeStack() {
|
|||
// if (currentStack.start != 0) {
|
|||
// DEBUG_LOG(KERNEL, "Freeing thread stack %s", nt.name);
|
|||
|
|||
// if ((nt.attr & PSP_THREAD_ATTR_CLEAR_STACK) != 0 && nt.initialStack != 0) {
|
|||
// Memory::Memset(nt.initialStack, 0, nt.stack_size);
|
|||
// }
|
|||
|
|||
// if (nt.attr & PSP_THREAD_ATTR_KERNEL) {
|
|||
// kernelMemory.Free(currentStack.start);
|
|||
// }
|
|||
// else {
|
|||
// userMemory.Free(currentStack.start);
|
|||
// }
|
|||
// currentStack.start = 0;
|
|||
// }
|
|||
//}
|
|||
|
|||
//bool PushExtendedStack(u32 size) {
|
|||
// u32 stack = userMemory.Alloc(size, true, (std::string("extended/") + nt.name).c_str());
|
|||
// if (stack == (u32)-1)
|
|||
// return false;
|
|||
|
|||
// pushed_stacks.push_back(currentStack);
|
|||
// currentStack.start = stack;
|
|||
// currentStack.end = stack + size;
|
|||
// nt.initialStack = currentStack.start;
|
|||
// nt.stack_size = currentStack.end - currentStack.start;
|
|||
|
|||
// // We still drop the threadID at the bottom and fill it, but there's no k0.
|
|||
// Memory::Memset(currentStack.start, 0xFF, nt.stack_size);
|
|||
// Memory::Write_U32(GetUID(), nt.initialStack);
|
|||
// return true;
|
|||
//}
|
|||
|
|||
//bool PopExtendedStack() {
|
|||
// if (pushed_stacks.size() == 0) {
|
|||
// return false;
|
|||
// }
|
|||
// userMemory.Free(currentStack.start);
|
|||
// currentStack = pushed_stacks.back();
|
|||
// pushed_stacks.pop_back();
|
|||
// nt.initialStack = currentStack.start;
|
|||
// nt.stack_size = currentStack.end - currentStack.start;
|
|||
// return true;
|
|||
//}
|
|||
|
|||
Thread() { |
|||
currentStack.start = 0; |
|||
} |
|||
|
|||
// Can't use a destructor since savestates will call that too.
|
|||
//void Cleanup() {
|
|||
// // Callbacks are automatically deleted when their owning thread is deleted.
|
|||
// for (auto it = callbacks.begin(), end = callbacks.end(); it != end; ++it)
|
|||
// kernelObjects.Destroy<Callback>(*it);
|
|||
|
|||
// if (pushed_stacks.size() != 0)
|
|||
// {
|
|||
// WARN_LOG(KERNEL, "Thread ended within an extended stack");
|
|||
// for (size_t i = 0; i < pushed_stacks.size(); ++i)
|
|||
// userMemory.Free(pushed_stacks[i].start);
|
|||
// }
|
|||
// FreeStack();
|
|||
//}
|
|||
|
|||
void setReturnValue(u32 retval); |
|||
void setReturnValue(u64 retval); |
|||
void resumeFromWait(); |
|||
//bool isWaitingFor(WaitType type, int id);
|
|||
//int getWaitID(WaitType type);
|
|||
ThreadWaitInfo getWaitInfo(); |
|||
|
|||
// Utils
|
|||
//inline bool isRunning() const { return (nt.status & THREADSTATUS_RUNNING) != 0; }
|
|||
//inline bool isStopped() const { return (nt.status & THREADSTATUS_DORMANT) != 0; }
|
|||
//inline bool isReady() const { return (nt.status & THREADSTATUS_READY) != 0; }
|
|||
//inline bool isWaiting() const { return (nt.status & THREADSTATUS_WAIT) != 0; }
|
|||
//inline bool isSuspended() const { return (nt.status & THREADSTATUS_SUSPEND) != 0; }
|
|||
|
|||
NativeThread nt; |
|||
|
|||
ThreadWaitInfo waitInfo; |
|||
UID moduleId; |
|||
|
|||
bool isProcessingCallbacks; |
|||
u32 currentMipscallId; |
|||
UID currentCallbackId; |
|||
|
|||
ThreadContext context; |
|||
|
|||
std::vector<UID> callbacks; |
|||
|
|||
std::list<u32> pending_calls; |
|||
|
|||
struct StackInfo { |
|||
u32 start; |
|||
u32 end; |
|||
}; |
|||
// This is a stack of... stacks, since sceKernelExtendThreadStack() can recurse.
|
|||
// These are stacks that aren't "active" right now, but will pop off once the func returns.
|
|||
std::vector<StackInfo> pushed_stacks; |
|||
|
|||
StackInfo currentStack; |
|||
|
|||
// For thread end.
|
|||
std::vector<UID> waiting_threads; |
|||
// Key is the callback id it was for, or if no callback, the thread id.
|
|||
std::map<UID, u64> paused_waits; |
|||
}; |
|||
|
|||
void __KernelThreadingInit() { |
|||
} |
|||
|
|||
void __KernelThreadingShutdown() { |
|||
} |
|||
|
|||
//const char *__KernelGetThreadName(UID threadID);
|
|||
//
|
|||
//void __KernelSaveContext(ThreadContext *ctx);
|
|||
//void __KernelLoadContext(ThreadContext *ctx);
|
|||
|
|||
//void __KernelSwitchContext(Thread *target, const char *reason);
|
|||
@ -0,0 +1,36 @@ |
|||
// Copyright 2014 Citra Emulator Project / PPSSPP Project |
|||
// Licensed under GPLv2 |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_types.h" |
|||
|
|||
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 |
|||
}; |
|||
|
|||
struct ThreadContext { |
|||
void reset(); |
|||
|
|||
u32 reg[16]; |
|||
u32 cpsr; |
|||
u32 pc; |
|||
}; |
|||
|
|||
void __KernelThreadingInit(); |
|||
void __KernelThreadingShutdown(); |
|||
|
|||
//const char *__KernelGetThreadName(SceUID threadID); |
|||
// |
|||
//void __KernelSaveContext(ThreadContext *ctx); |
|||
//void __KernelLoadContext(ThreadContext *ctx); |
|||
|
|||
//void __KernelSwitchContext(Thread *target, const char *reason); |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue