323 lines
9.1 KiB
C++
323 lines
9.1 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/cpu/processor.h>
|
|
|
|
#include <xenia/cpu/jit.h>
|
|
#include <xenia/cpu/ppc/disasm.h>
|
|
#include <xenia/cpu/ppc/state.h>
|
|
#include <xenia/gpu/graphics_system.h>
|
|
|
|
|
|
using namespace xe;
|
|
using namespace xe::cpu;
|
|
using namespace xe::cpu::sdb;
|
|
using namespace xe::kernel;
|
|
|
|
|
|
namespace {
|
|
void InitializeIfNeeded();
|
|
void CleanupOnShutdown();
|
|
|
|
void InitializeIfNeeded() {
|
|
static bool has_initialized = false;
|
|
if (has_initialized) {
|
|
return;
|
|
}
|
|
has_initialized = true;
|
|
|
|
ppc::RegisterDisasmCategoryAltivec();
|
|
ppc::RegisterDisasmCategoryALU();
|
|
ppc::RegisterDisasmCategoryControl();
|
|
ppc::RegisterDisasmCategoryFPU();
|
|
ppc::RegisterDisasmCategoryMemory();
|
|
|
|
atexit(CleanupOnShutdown);
|
|
}
|
|
|
|
void CleanupOnShutdown() {
|
|
}
|
|
}
|
|
|
|
|
|
Processor::Processor(xe_memory_ref memory, shared_ptr<Backend> backend) :
|
|
sym_table_(NULL), jit_(NULL),
|
|
interrupt_thread_lock_(NULL), interrupt_thread_state_(NULL) {
|
|
memory_ = xe_memory_retain(memory);
|
|
backend_ = backend;
|
|
sym_lock_ = xe_mutex_alloc(10000);
|
|
|
|
InitializeIfNeeded();
|
|
}
|
|
|
|
Processor::~Processor() {
|
|
// Cleanup all modules.
|
|
for (std::vector<ExecModule*>::iterator it = modules_.begin();
|
|
it != modules_.end(); ++it) {
|
|
ExecModule* exec_module = *it;
|
|
if (jit_) {
|
|
jit_->UninitModule(exec_module);
|
|
}
|
|
delete exec_module;
|
|
}
|
|
modules_.clear();
|
|
|
|
xe_memory_heap_free(memory_, interrupt_thread_block_, 2048);
|
|
DeallocThread(interrupt_thread_state_);
|
|
xe_mutex_free(interrupt_thread_lock_);
|
|
|
|
delete jit_;
|
|
delete sym_table_;
|
|
xe_mutex_free(sym_lock_);
|
|
|
|
graphics_system_.reset();
|
|
export_resolver_.reset();
|
|
backend_.reset();
|
|
xe_memory_release(memory_);
|
|
}
|
|
|
|
xe_memory_ref Processor::memory() {
|
|
return xe_memory_retain(memory_);
|
|
}
|
|
|
|
shared_ptr<gpu::GraphicsSystem> Processor::graphics_system() {
|
|
return graphics_system_;
|
|
}
|
|
|
|
void Processor::set_graphics_system(
|
|
shared_ptr<gpu::GraphicsSystem> graphics_system) {
|
|
graphics_system_ = graphics_system;
|
|
}
|
|
|
|
shared_ptr<ExportResolver> Processor::export_resolver() {
|
|
return export_resolver_;
|
|
}
|
|
|
|
void Processor::set_export_resolver(
|
|
shared_ptr<ExportResolver> export_resolver) {
|
|
export_resolver_ = export_resolver;
|
|
}
|
|
|
|
int Processor::Setup() {
|
|
XEASSERTNULL(jit_);
|
|
|
|
interrupt_thread_lock_ = xe_mutex_alloc(10000);
|
|
interrupt_thread_state_ = AllocThread(16 * 1024, 0, 0);
|
|
interrupt_thread_block_ = xe_memory_heap_alloc(
|
|
memory_, 0, 2048, 0);
|
|
interrupt_thread_state_->ppc_state()->r[13] = interrupt_thread_block_;
|
|
|
|
sym_table_ = new SymbolTable();
|
|
|
|
jit_ = backend_->CreateJIT(memory_, sym_table_);
|
|
if (jit_->Setup()) {
|
|
XELOGE("Unable to create JIT");
|
|
return 1;
|
|
}
|
|
|
|
XEASSERTNOTNULL(graphics_system_.get());
|
|
jit_->SetupGpuPointers(
|
|
graphics_system_.get(),
|
|
(void*)&xe::gpu::GraphicsSystem::ReadRegisterThunk,
|
|
(void*)&xe::gpu::GraphicsSystem::WriteRegisterThunk);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int Processor::LoadRawBinary(const xechar_t* path, uint32_t start_address) {
|
|
ExecModule* exec_module = NULL;
|
|
const xechar_t* name = xestrrchr(path, XE_PATH_SEPARATOR) + 1;
|
|
|
|
// TODO(benvanik): map file from filesystem API, not via platform API.
|
|
xe_mmap_ref mmap = xe_mmap_open(kXEFileModeRead, path, 0, 0);
|
|
if (!mmap) {
|
|
return NULL;
|
|
}
|
|
void* addr = xe_mmap_get_addr(mmap);
|
|
size_t length = xe_mmap_get_length(mmap);
|
|
|
|
int result_code = 1;
|
|
|
|
// Place the data into memory at the desired address.
|
|
XEEXPECTZERO(xe_copy_memory(xe_memory_addr(memory_, start_address),
|
|
xe_memory_get_length(memory_),
|
|
addr, length));
|
|
|
|
char name_a[XE_MAX_PATH];
|
|
XEEXPECTTRUE(xestrnarrow(name_a, XECOUNT(name_a), name));
|
|
char path_a[XE_MAX_PATH];
|
|
XEEXPECTTRUE(xestrnarrow(path_a, XECOUNT(path_a), path));
|
|
|
|
// Prepare the module.
|
|
// This will analyze it, generate code (if needed), and adds methods to
|
|
// the function table.
|
|
exec_module = new ExecModule(
|
|
memory_, export_resolver_, sym_table_, name_a, path_a);
|
|
XEEXPECTZERO(exec_module->PrepareRawBinary(
|
|
start_address, start_address + (uint32_t)length));
|
|
|
|
// Initialize the module and prepare it for execution.
|
|
XEEXPECTZERO(jit_->InitModule(exec_module));
|
|
|
|
xe_mutex_lock(sym_lock_);
|
|
modules_.push_back(exec_module);
|
|
xe_mutex_unlock(sym_lock_);
|
|
|
|
result_code = 0;
|
|
XECLEANUP:
|
|
if (result_code) {
|
|
delete exec_module;
|
|
}
|
|
xe_mmap_release(mmap);
|
|
return result_code;
|
|
}
|
|
|
|
int Processor::LoadXexModule(const char* name, const char* path,
|
|
xe_xex2_ref xex) {
|
|
int result_code = 1;
|
|
|
|
// Prepare the module.
|
|
// This will analyze it, generate code (if needed), and adds methods to
|
|
// the function table.
|
|
ExecModule* exec_module = new ExecModule(
|
|
memory_, export_resolver_, sym_table_, name, path);
|
|
XEEXPECTZERO(exec_module->PrepareXexModule(xex));
|
|
|
|
// Initialize the module and prepare it for execution.
|
|
XEEXPECTZERO(jit_->InitModule(exec_module));
|
|
|
|
xe_mutex_lock(sym_lock_);
|
|
modules_.push_back(exec_module);
|
|
xe_mutex_unlock(sym_lock_);
|
|
|
|
result_code = 0;
|
|
XECLEANUP:
|
|
if (result_code) {
|
|
delete exec_module;
|
|
}
|
|
return result_code;
|
|
}
|
|
|
|
uint32_t Processor::CreateCallback(void (*callback)(void* data), void* data) {
|
|
// TODO(benvanik): implement callback creation.
|
|
return 0;
|
|
}
|
|
|
|
ThreadState* Processor::AllocThread(uint32_t stack_size,
|
|
uint32_t thread_state_address,
|
|
uint32_t thread_id) {
|
|
ThreadState* thread_state = new ThreadState(
|
|
this, stack_size, thread_state_address, thread_id);
|
|
return thread_state;
|
|
}
|
|
|
|
void Processor::DeallocThread(ThreadState* thread_state) {
|
|
delete thread_state;
|
|
}
|
|
|
|
int Processor::Execute(ThreadState* thread_state, uint32_t address) {
|
|
// Attempt to get the function.
|
|
FunctionSymbol* fn_symbol = GetFunction(address);
|
|
if (!fn_symbol) {
|
|
// Symbol not found in any module.
|
|
XELOGCPU("Execute(%.8X): failed to find function", address);
|
|
return 1;
|
|
}
|
|
|
|
xe_ppc_state_t* ppc_state = thread_state->ppc_state();
|
|
|
|
// This could be set to anything to give us a unique identifier to track
|
|
// re-entrancy/etc.
|
|
uint32_t lr = 0xBEBEBEBE;
|
|
|
|
// Setup registers.
|
|
ppc_state->lr = lr;
|
|
|
|
// Execute the function.
|
|
return jit_->Execute(ppc_state, fn_symbol);
|
|
}
|
|
|
|
uint64_t Processor::Execute(ThreadState* thread_state, uint32_t address,
|
|
uint64_t arg0) {
|
|
xe_ppc_state_t* ppc_state = thread_state->ppc_state();
|
|
ppc_state->r[3] = arg0;
|
|
if (Execute(thread_state, address)) {
|
|
return 0xDEADBABE;
|
|
}
|
|
return ppc_state->r[3];
|
|
}
|
|
|
|
uint64_t Processor::Execute(ThreadState* thread_state, uint32_t address,
|
|
uint64_t arg0, uint64_t arg1) {
|
|
xe_ppc_state_t* ppc_state = thread_state->ppc_state();
|
|
ppc_state->r[3] = arg0;
|
|
ppc_state->r[4] = arg1;
|
|
if (Execute(thread_state, address)) {
|
|
return 0xDEADBABE;
|
|
}
|
|
return ppc_state->r[3];
|
|
}
|
|
|
|
uint64_t Processor::ExecuteInterrupt(uint32_t cpu,
|
|
uint32_t address,
|
|
uint64_t arg0, uint64_t arg1) {
|
|
// Acquire lock on interrupt thread (we can only dispatch one at a time).
|
|
xe_mutex_lock(interrupt_thread_lock_);
|
|
|
|
// Set 0x10C(r13) to the current CPU ID.
|
|
uint8_t* p = xe_memory_addr(memory_, 0);
|
|
XESETUINT8BE(p + interrupt_thread_block_ + 0x10C, cpu);
|
|
|
|
// Execute interrupt.
|
|
uint64_t result = Execute(interrupt_thread_state_, address, arg0, arg1);
|
|
|
|
xe_mutex_unlock(interrupt_thread_lock_);
|
|
return result;
|
|
}
|
|
|
|
FunctionSymbol* Processor::GetFunction(uint32_t address) {
|
|
// Attempt to grab the function symbol from the global lookup table.
|
|
// The symbol table takes a lock so it should be safe.
|
|
FunctionSymbol* fn_symbol = sym_table_->GetFunction(address);
|
|
if (fn_symbol) {
|
|
return fn_symbol;
|
|
}
|
|
|
|
// Search all modules for the function symbol.
|
|
// Each module will see if the address is within its code range and if the
|
|
// symbol is not found (likely) it will do analysis on it.
|
|
// TODO(benvanik): make this more efficient. Could use a binary search or
|
|
// something more clever.
|
|
xe_mutex_lock(sym_lock_);
|
|
for (std::vector<ExecModule*>::iterator it = modules_.begin();
|
|
it != modules_.end(); ++it) {
|
|
fn_symbol = (*it)->FindFunctionSymbol(address);
|
|
if (fn_symbol) {
|
|
xe_mutex_unlock(sym_lock_);
|
|
return fn_symbol;
|
|
}
|
|
}
|
|
xe_mutex_unlock(sym_lock_);
|
|
|
|
// Not found at all? That seems wrong...
|
|
XEASSERTALWAYS();
|
|
return NULL;
|
|
}
|
|
|
|
void* Processor::GetFunctionPointer(uint32_t address) {
|
|
// Attempt to get the function.
|
|
FunctionSymbol* fn_symbol = GetFunction(address);
|
|
if (!fn_symbol || fn_symbol->type == FunctionSymbol::Unknown) {
|
|
return NULL;
|
|
}
|
|
|
|
// Grab the pointer.
|
|
return jit_->GetFunctionPointer(fn_symbol);
|
|
}
|