mirror of
https://github.com/xenia-project/xenia.git
synced 2026-01-02 14:50:09 +01:00
302 lines
8.6 KiB
C++
302 lines
8.6 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <xenia/kernel/modules/xboxkrnl/objects/xthread.h>
|
|
|
|
#include <xenia/cpu/cpu.h>
|
|
#include <xenia/kernel/modules/xboxkrnl/xboxkrnl_threading.h>
|
|
#include <xenia/kernel/modules/xboxkrnl/objects/xevent.h>
|
|
#include <xenia/kernel/modules/xboxkrnl/objects/xmodule.h>
|
|
|
|
|
|
using namespace xe;
|
|
using namespace xe::kernel;
|
|
using namespace xe::kernel::xboxkrnl;
|
|
|
|
|
|
namespace {
|
|
static uint32_t next_xthread_id = 0;
|
|
static uint32_t current_thread_tls = xeKeTlsAlloc();
|
|
}
|
|
|
|
|
|
XThread::XThread(KernelState* kernel_state,
|
|
uint32_t stack_size,
|
|
uint32_t xapi_thread_startup,
|
|
uint32_t start_address, uint32_t start_context,
|
|
uint32_t creation_flags) :
|
|
XObject(kernel_state, kTypeThread),
|
|
thread_id_(++next_xthread_id),
|
|
thread_handle_(0),
|
|
thread_state_address_(0),
|
|
thread_state_(0),
|
|
event_(NULL) {
|
|
creation_params_.stack_size = stack_size;
|
|
creation_params_.xapi_thread_startup = xapi_thread_startup;
|
|
creation_params_.start_address = start_address;
|
|
creation_params_.start_context = start_context;
|
|
creation_params_.creation_flags = creation_flags;
|
|
|
|
// Adjust stack size - min of 16k.
|
|
if (creation_params_.stack_size < 16 * 1024 * 1024) {
|
|
creation_params_.stack_size = 16 * 1024 * 1024;
|
|
}
|
|
|
|
event_ = new XEvent(kernel_state);
|
|
event_->Initialize(true, false);
|
|
}
|
|
|
|
XThread::~XThread() {
|
|
event_->Release();
|
|
|
|
PlatformDestroy();
|
|
|
|
if (thread_state_) {
|
|
kernel_state()->processor()->DeallocThread(thread_state_);
|
|
}
|
|
if (tls_address_) {
|
|
xe_memory_heap_free(kernel_state()->memory(), tls_address_, 0);
|
|
}
|
|
if (thread_state_address_) {
|
|
xe_memory_heap_free(kernel_state()->memory(), thread_state_address_, 0);
|
|
}
|
|
|
|
if (thread_handle_) {
|
|
// TODO(benvanik): platform kill
|
|
XELOGE("Thread disposed without exiting");
|
|
}
|
|
}
|
|
|
|
uint32_t XThread::GetCurrentThreadHandle() {
|
|
return xeKeTlsGetValue(current_thread_tls);
|
|
}
|
|
|
|
uint32_t XThread::GetCurrentThreadId(const uint8_t* thread_state_block) {
|
|
return XEGETUINT32BE(thread_state_block + 0x14C);
|
|
}
|
|
|
|
uint32_t XThread::thread_id() {
|
|
return thread_id_;
|
|
}
|
|
|
|
uint32_t XThread::last_error() {
|
|
uint8_t *p = xe_memory_addr(memory(), thread_state_address_);
|
|
return XEGETUINT32BE(p + 0x160);
|
|
}
|
|
|
|
void XThread::set_last_error(uint32_t error_code) {
|
|
uint8_t *p = xe_memory_addr(memory(), thread_state_address_);
|
|
XESETUINT32BE(p + 0x160, error_code);
|
|
}
|
|
|
|
X_STATUS XThread::Create() {
|
|
// Allocate thread state block from heap.
|
|
// This is set as r13 for user code and some special inlined Win32 calls
|
|
// (like GetLastError/etc) will poke it directly.
|
|
// We try to use it as our primary store of data just to keep things all
|
|
// consistent.
|
|
// 0x000: pointer to tls data
|
|
// 0x100: pointer to self?
|
|
// 0x14C: thread id
|
|
// 0x150: if >0 then error states don't get set
|
|
// 0x160: last error
|
|
// So, at offset 0x100 we have a 4b pointer to offset 200, then have the
|
|
// structure.
|
|
thread_state_address_ = xe_memory_heap_alloc(memory(), 0, 2048, 0);
|
|
if (!thread_state_address_) {
|
|
XELOGW("Unable to allocate thread state block");
|
|
return X_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
XModule* module = kernel_state()->GetExecutableModule();
|
|
|
|
// Allocate TLS block.
|
|
const xe_xex2_header_t* header = module->xex_header();
|
|
uint32_t tls_size = header->tls_info.slot_count * header->tls_info.data_size;
|
|
tls_address_ = xe_memory_heap_alloc(memory(), 0, tls_size, 0);
|
|
if (!tls_address_) {
|
|
XELOGW("Unable to allocate thread local storage block");
|
|
module->Release();
|
|
return X_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
// Copy in default TLS info.
|
|
// TODO(benvanik): is this correct?
|
|
xe_memory_copy(memory(),
|
|
tls_address_, header->tls_info.raw_data_address, tls_size);
|
|
|
|
// Setup the thread state block (last error/etc).
|
|
uint8_t *p = xe_memory_addr(memory(), thread_state_address_);
|
|
XESETUINT32BE(p + 0x000, tls_address_);
|
|
XESETUINT32BE(p + 0x100, thread_state_address_);
|
|
XESETUINT32BE(p + 0x14C, thread_id_);
|
|
XESETUINT32BE(p + 0x150, 0); // ?
|
|
XESETUINT32BE(p + 0x160, 0); // last error
|
|
|
|
// Allocate processor thread state.
|
|
// This is thread safe.
|
|
thread_state_ = kernel_state()->processor()->AllocThread(
|
|
creation_params_.stack_size, thread_state_address_, thread_id_);
|
|
if (!thread_state_) {
|
|
XELOGW("Unable to allocate processor thread state");
|
|
module->Release();
|
|
return X_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
X_STATUS return_code = PlatformCreate();
|
|
if (XFAILED(return_code)) {
|
|
XELOGW("Unable to create platform thread (%.8X)", return_code);
|
|
module->Release();
|
|
return return_code;
|
|
}
|
|
|
|
module->Release();
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
X_STATUS XThread::Exit(int exit_code) {
|
|
// TODO(benvanik): set exit code in thread state block
|
|
|
|
// TODO(benvanik); dispatch events? waiters? etc?
|
|
event_->Set(0, false);
|
|
|
|
// NOTE: unless PlatformExit fails, expect it to never return!
|
|
X_STATUS return_code = PlatformExit(exit_code);
|
|
if (XFAILED(return_code)) {
|
|
return return_code;
|
|
}
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
#if XE_PLATFORM(WIN32)
|
|
|
|
static uint32_t __stdcall XThreadStartCallbackWin32(void* param) {
|
|
XThread* thread = reinterpret_cast<XThread*>(param);
|
|
xeKeTlsSetValue(current_thread_tls, thread->handle());
|
|
thread->Execute();
|
|
thread->Release();
|
|
return 0;
|
|
}
|
|
|
|
X_STATUS XThread::PlatformCreate() {
|
|
thread_handle_ = CreateThread(
|
|
NULL,
|
|
creation_params_.stack_size,
|
|
(LPTHREAD_START_ROUTINE)XThreadStartCallbackWin32,
|
|
this,
|
|
creation_params_.creation_flags,
|
|
NULL);
|
|
if (!thread_handle_) {
|
|
uint32_t last_error = GetLastError();
|
|
// TODO(benvanik): translate?
|
|
XELOGE("CreateThread failed with %d", last_error);
|
|
return last_error;
|
|
}
|
|
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
void XThread::PlatformDestroy() {
|
|
CloseHandle(reinterpret_cast<HANDLE>(thread_handle_));
|
|
thread_handle_ = NULL;
|
|
}
|
|
|
|
X_STATUS XThread::PlatformExit(int exit_code) {
|
|
// NOTE: does not return.
|
|
ExitThread(exit_code);
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
#else
|
|
|
|
static void* XThreadStartCallbackPthreads(void* param) {
|
|
XThread* thread = reinterpret_cast<XThread*>(param);
|
|
xeKeTlsSetValue(current_thread_tls, thread->handle());
|
|
thread->Execute();
|
|
thread->Release();
|
|
return 0;
|
|
}
|
|
|
|
X_STATUS XThread::PlatformCreate() {
|
|
pthread_attr_t attr;
|
|
|
|
pthread_attr_init(&attr);
|
|
pthread_attr_setstacksize(&attr, creation_params_.stack_size);
|
|
|
|
int result_code;
|
|
if (creation_params_.creation_flags & X_CREATE_SUSPENDED) {
|
|
#if XE_PLATFORM(OSX)
|
|
result_code = pthread_create_suspended_np(
|
|
reinterpret_cast<pthread_t*>(&thread_handle_),
|
|
&attr,
|
|
&XThreadStartCallbackPthreads,
|
|
this);
|
|
#else
|
|
// TODO(benvanik): pthread_create_suspended_np on linux
|
|
XEASSERTALWAYS();
|
|
#endif // OSX
|
|
} else {
|
|
result_code = pthread_create(
|
|
reinterpret_cast<pthread_t*>(&thread_handle_),
|
|
&attr,
|
|
&XThreadStartCallbackPthreads,
|
|
this);
|
|
}
|
|
|
|
pthread_attr_destroy(&attr);
|
|
|
|
switch (result_code) {
|
|
case 0:
|
|
// Succeeded!
|
|
return X_STATUS_SUCCESS;
|
|
default:
|
|
case EAGAIN:
|
|
return X_STATUS_NO_MEMORY;
|
|
case EINVAL:
|
|
case EPERM:
|
|
return X_STATUS_INVALID_PARAMETER;
|
|
}
|
|
}
|
|
|
|
void XThread::PlatformDestroy() {
|
|
// No-op?
|
|
}
|
|
|
|
X_STATUS XThread::PlatformExit(int exit_code) {
|
|
// NOTE: does not return.
|
|
pthread_exit((void*)exit_code);
|
|
return X_STATUS_SUCCESS;
|
|
}
|
|
|
|
#endif // WIN32
|
|
|
|
void XThread::Execute() {
|
|
// If a XapiThreadStartup value is present, we use that as a trampoline.
|
|
// Otherwise, we are a raw thread.
|
|
if (creation_params_.xapi_thread_startup) {
|
|
kernel_state()->processor()->Execute(
|
|
thread_state_,
|
|
creation_params_.xapi_thread_startup,
|
|
creation_params_.start_address, creation_params_.start_context);
|
|
} else {
|
|
// Run user code.
|
|
int exit_code = (int)kernel_state()->processor()->Execute(
|
|
thread_state_,
|
|
creation_params_.start_address, creation_params_.start_context);
|
|
// If we got here it means the execute completed without an exit being called.
|
|
// Treat the return code as an implicit exit code.
|
|
Exit(exit_code);
|
|
}
|
|
}
|
|
|
|
X_STATUS XThread::Wait(uint32_t wait_reason, uint32_t processor_mode,
|
|
uint32_t alertable, uint64_t* opt_timeout) {
|
|
return event_->Wait(wait_reason, processor_mode, alertable, opt_timeout);
|
|
}
|