Merge pull request #9 from bunnei/master
Add initial kernel HLE, includes thread creation and context switchingmaster
commit
6448c2f300
@ -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
|
Loading…
Reference in New Issue