Pure dynamic MMIO access. Prep for more complex GPU memory management.
This commit is contained in:
@@ -42,12 +42,13 @@ X_STATUS AudioSystem::Setup() {
|
||||
processor_ = emulator_->processor();
|
||||
|
||||
// Let the processor know we want register access callbacks.
|
||||
RegisterAccessCallbacks callbacks;
|
||||
callbacks.context = this;
|
||||
callbacks.handles = (RegisterHandlesCallback)HandlesRegisterThunk;
|
||||
callbacks.read = (RegisterReadCallback)ReadRegisterThunk;
|
||||
callbacks.write = (RegisterWriteCallback)WriteRegisterThunk;
|
||||
emulator_->processor()->AddRegisterAccessCallbacks(callbacks);
|
||||
emulator_->memory()->AddMappedRange(
|
||||
0x7FEA0000,
|
||||
0xFFFF0000,
|
||||
0x0000FFFF,
|
||||
this,
|
||||
reinterpret_cast<MMIOReadCallback>(MMIOReadRegisterThunk),
|
||||
reinterpret_cast<MMIOWriteCallback>(MMIOWriteRegisterThunk));
|
||||
|
||||
// Setup worker thread state. This lets us make calls into guest code.
|
||||
thread_state_ = new XenonThreadState(
|
||||
@@ -181,10 +182,6 @@ void AudioSystem::UnregisterClient(size_t index) {
|
||||
xe_mutex_unlock(lock_);
|
||||
}
|
||||
|
||||
bool AudioSystem::HandlesRegister(uint64_t addr) {
|
||||
return (addr & 0xFFFF0000) == 0x7FEA0000;
|
||||
}
|
||||
|
||||
// free60 may be useful here, however it looks like it's using a different
|
||||
// piece of hardware:
|
||||
// https://github.com/Free60Project/libxenon/blob/master/libxenon/drivers/xenon_sound/sound.c
|
||||
|
||||
@@ -42,7 +42,6 @@ public:
|
||||
virtual X_STATUS CreateDriver(size_t index, HANDLE wait_handle, AudioDriver** out_driver) = 0;
|
||||
virtual void DestroyDriver(AudioDriver* driver) = 0;
|
||||
|
||||
bool HandlesRegister(uint64_t addr);
|
||||
virtual uint64_t ReadRegister(uint64_t addr);
|
||||
virtual void WriteRegister(uint64_t addr, uint64_t value);
|
||||
|
||||
@@ -55,14 +54,11 @@ private:
|
||||
}
|
||||
void ThreadStart();
|
||||
|
||||
static bool HandlesRegisterThunk(AudioSystem* as, uint64_t addr) {
|
||||
return as->HandlesRegister(addr);
|
||||
}
|
||||
static uint64_t ReadRegisterThunk(AudioSystem* as, uint64_t addr) {
|
||||
static uint64_t MMIOReadRegisterThunk(AudioSystem* as, uint64_t addr) {
|
||||
return as->ReadRegister(addr);
|
||||
}
|
||||
static void WriteRegisterThunk(AudioSystem* as, uint64_t addr,
|
||||
uint64_t value) {
|
||||
static void MMIOWriteRegisterThunk(AudioSystem* as, uint64_t addr,
|
||||
uint64_t value) {
|
||||
as->WriteRegister(addr, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -141,11 +141,6 @@ int Processor::Setup() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Processor::AddRegisterAccessCallbacks(
|
||||
xe::cpu::RegisterAccessCallbacks callbacks) {
|
||||
runtime_->AddRegisterAccessCallbacks(callbacks);
|
||||
}
|
||||
|
||||
int Processor::Execute(XenonThreadState* thread_state, uint64_t address) {
|
||||
SCOPE_profile_cpu_f("cpu");
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#ifndef XENIA_CPU_PROCESSOR_H_
|
||||
#define XENIA_CPU_PROCESSOR_H_
|
||||
|
||||
#include <alloy/runtime/register_access.h>
|
||||
#include <xenia/core.h>
|
||||
#include <xenia/debug/debug_target.h>
|
||||
|
||||
@@ -28,11 +27,6 @@ XEDECLARECLASS2(xe, cpu, XexModule);
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
using RegisterAccessCallbacks = alloy::runtime::RegisterAccessCallbacks;
|
||||
using RegisterHandlesCallback = alloy::runtime::RegisterHandlesCallback;
|
||||
using RegisterReadCallback = alloy::runtime::RegisterReadCallback;
|
||||
using RegisterWriteCallback = alloy::runtime::RegisterWriteCallback;
|
||||
|
||||
|
||||
class Processor : public debug::DebugTarget {
|
||||
public:
|
||||
@@ -45,8 +39,6 @@ public:
|
||||
|
||||
int Setup();
|
||||
|
||||
void AddRegisterAccessCallbacks(RegisterAccessCallbacks callbacks);
|
||||
|
||||
int Execute(
|
||||
XenonThreadState* thread_state, uint64_t address);
|
||||
uint64_t Execute(
|
||||
|
||||
@@ -119,6 +119,111 @@ private:
|
||||
};
|
||||
uint32_t XenonMemoryHeap::next_heap_id_ = 1;
|
||||
|
||||
namespace {
|
||||
|
||||
namespace BE {
|
||||
#include <beaengine/BeaEngine.h>
|
||||
}
|
||||
|
||||
struct MMIORange {
|
||||
uint64_t address;
|
||||
uint64_t mask;
|
||||
uint64_t size;
|
||||
void* context;
|
||||
MMIOReadCallback read;
|
||||
MMIOWriteCallback write;
|
||||
};
|
||||
MMIORange g_mapped_ranges_[16] = { 0 };
|
||||
int g_mapped_range_count_ = 0;
|
||||
|
||||
uint64_t* GetContextRegPtr(BE::Int32 arg_type, PCONTEXT context) {
|
||||
DWORD index = 0;
|
||||
_BitScanForward(&index, arg_type);
|
||||
return &context->Rax + index;
|
||||
}
|
||||
|
||||
// Handles potential accesses to mmio. We look for access violations to
|
||||
// addresses in our range and call into the registered handlers, if any.
|
||||
// If there are none, we continue.
|
||||
LONG CALLBACK CheckMMIOHandler(PEXCEPTION_POINTERS ex_info) {
|
||||
// http://msdn.microsoft.com/en-us/library/ms679331(v=vs.85).aspx
|
||||
// http://msdn.microsoft.com/en-us/library/aa363082(v=vs.85).aspx
|
||||
auto code = ex_info->ExceptionRecord->ExceptionCode;
|
||||
if (code == STATUS_ACCESS_VIOLATION) {
|
||||
// Access violations are pretty rare, so we can do a linear search here.
|
||||
auto address = ex_info->ExceptionRecord->ExceptionInformation[1];
|
||||
for (int i = 0; i < g_mapped_range_count_; ++i) {
|
||||
const auto& range = g_mapped_ranges_[i];
|
||||
if ((address & range.mask) == range.address) {
|
||||
// Within our range.
|
||||
|
||||
// TODO(benvanik): replace with simple check of mov (that's all
|
||||
// we care about).
|
||||
BE::DISASM disasm = { 0 };
|
||||
disasm.Archi = 64;
|
||||
disasm.Options = BE::MasmSyntax + BE::PrefixedNumeral;
|
||||
disasm.EIP = (BE::UIntPtr)ex_info->ExceptionRecord->ExceptionAddress;
|
||||
BE::UIntPtr eip_end = disasm.EIP + 20;
|
||||
size_t len = BE::Disasm(&disasm);
|
||||
if (len == BE::UNKNOWN_OPCODE) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto action = ex_info->ExceptionRecord->ExceptionInformation[0];
|
||||
if (action == 0) {
|
||||
uint64_t value = range.read(range.context, address & 0xFFFFFFFF);
|
||||
XEASSERT((disasm.Argument1.ArgType & BE::REGISTER_TYPE) ==
|
||||
BE::REGISTER_TYPE);
|
||||
uint64_t* reg_ptr = GetContextRegPtr(disasm.Argument1.ArgType,
|
||||
ex_info->ContextRecord);
|
||||
switch (disasm.Argument1.ArgSize) {
|
||||
case 8:
|
||||
*reg_ptr = static_cast<uint8_t>(value);
|
||||
break;
|
||||
case 16:
|
||||
*reg_ptr = XESWAP16(static_cast<uint16_t>(value));
|
||||
break;
|
||||
case 32:
|
||||
*reg_ptr = XESWAP32(static_cast<uint32_t>(value));
|
||||
break;
|
||||
case 64:
|
||||
*reg_ptr = XESWAP64(static_cast<uint64_t>(value));
|
||||
break;
|
||||
}
|
||||
ex_info->ContextRecord->Rip += len;
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
} else if (action == 1) {
|
||||
XEASSERT((disasm.Argument2.ArgType & BE::REGISTER_TYPE) ==
|
||||
BE::REGISTER_TYPE);
|
||||
uint64_t* reg_ptr = GetContextRegPtr(disasm.Argument2.ArgType,
|
||||
ex_info->ContextRecord);
|
||||
uint64_t value = *reg_ptr;
|
||||
switch (disasm.Argument2.ArgSize) {
|
||||
case 8:
|
||||
value = static_cast<uint8_t>(value);
|
||||
break;
|
||||
case 16:
|
||||
value = XESWAP16(static_cast<uint16_t>(value));
|
||||
break;
|
||||
case 32:
|
||||
value = XESWAP32(static_cast<uint32_t>(value));
|
||||
break;
|
||||
case 64:
|
||||
value = XESWAP64(static_cast<uint64_t>(value));
|
||||
break;
|
||||
}
|
||||
range.write(range.context, address & 0xFFFFFFFF, value);
|
||||
ex_info->ContextRecord->Rip += len;
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
XenonMemory::XenonMemory() :
|
||||
mapping_(0), mapping_base_(0),
|
||||
@@ -204,6 +309,15 @@ int XenonMemory::Initialize() {
|
||||
0x00100000,
|
||||
MEM_COMMIT, PAGE_READWRITE);
|
||||
|
||||
// Add handlers for MMIO.
|
||||
// If there is a debugger attached the normal exception handler will not
|
||||
// fire and we must instead add the continue handler.
|
||||
AddVectoredExceptionHandler(1, CheckMMIOHandler);
|
||||
if (IsDebuggerPresent()) {
|
||||
// TODO(benvanik): is this really required?
|
||||
//AddVectoredContinueHandler(1, CheckMMIOHandler);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
XECLEANUP:
|
||||
@@ -248,6 +362,112 @@ void XenonMemory::UnmapViews() {
|
||||
}
|
||||
}
|
||||
|
||||
bool XenonMemory::AddMappedRange(uint64_t address, uint64_t mask,
|
||||
uint64_t size, void* context,
|
||||
MMIOReadCallback read_callback,
|
||||
MMIOWriteCallback write_callback) {
|
||||
DWORD protect = 0;
|
||||
if (read_callback && write_callback) {
|
||||
protect = PAGE_NOACCESS;
|
||||
} else if (write_callback) {
|
||||
protect = PAGE_READONLY;
|
||||
} else {
|
||||
// Write-only memory is not supported.
|
||||
XEASSERTALWAYS();
|
||||
}
|
||||
if (!VirtualAlloc(Translate(address),
|
||||
size,
|
||||
MEM_COMMIT, protect)) {
|
||||
return false;
|
||||
}
|
||||
XEASSERT(g_mapped_range_count_ + 1 < XECOUNT(g_mapped_ranges_));
|
||||
g_mapped_ranges_[g_mapped_range_count_++] = {
|
||||
reinterpret_cast<uint64_t>(mapping_base_) | address,
|
||||
0xFFFFFFFF00000000 | mask,
|
||||
size, context,
|
||||
read_callback, write_callback,
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XenonMemory::CheckMMIOLoad(uint64_t address, uint64_t* out_value) {
|
||||
for (int i = 0; i < g_mapped_range_count_; ++i) {
|
||||
const auto& range = g_mapped_ranges_[i];
|
||||
if (((address | (uint64_t)mapping_base_) & range.mask) == range.address) {
|
||||
*out_value = static_cast<uint32_t>(range.read(range.context, address));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t XenonMemory::LoadI8(uint64_t address) {
|
||||
uint64_t value;
|
||||
if (!CheckMMIOLoad(address, &value)) {
|
||||
value = *reinterpret_cast<uint8_t*>(Translate(address));
|
||||
}
|
||||
return static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
uint16_t XenonMemory::LoadI16(uint64_t address) {
|
||||
uint64_t value;
|
||||
if (!CheckMMIOLoad(address, &value)) {
|
||||
value = *reinterpret_cast<uint16_t*>(Translate(address));
|
||||
}
|
||||
return static_cast<uint16_t>(value);
|
||||
}
|
||||
|
||||
uint32_t XenonMemory::LoadI32(uint64_t address) {
|
||||
uint64_t value;
|
||||
if (!CheckMMIOLoad(address, &value)) {
|
||||
value = *reinterpret_cast<uint32_t*>(Translate(address));
|
||||
}
|
||||
return static_cast<uint32_t>(value);
|
||||
}
|
||||
|
||||
uint64_t XenonMemory::LoadI64(uint64_t address) {
|
||||
uint64_t value;
|
||||
if (!CheckMMIOLoad(address, &value)) {
|
||||
value = *reinterpret_cast<uint64_t*>(Translate(address));
|
||||
}
|
||||
return static_cast<uint64_t>(value);
|
||||
}
|
||||
|
||||
bool XenonMemory::CheckMMIOStore(uint64_t address, uint64_t value) {
|
||||
for (int i = 0; i < g_mapped_range_count_; ++i) {
|
||||
const auto& range = g_mapped_ranges_[i];
|
||||
if (((address | (uint64_t)mapping_base_) & range.mask) == range.address) {
|
||||
range.write(range.context, address, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void XenonMemory::StoreI8(uint64_t address, uint8_t value) {
|
||||
if (!CheckMMIOStore(address, value)) {
|
||||
*reinterpret_cast<uint8_t*>(Translate(address)) = value;
|
||||
}
|
||||
}
|
||||
|
||||
void XenonMemory::StoreI16(uint64_t address, uint16_t value) {
|
||||
if (!CheckMMIOStore(address, value)) {
|
||||
*reinterpret_cast<uint16_t*>(Translate(address)) = value;
|
||||
}
|
||||
}
|
||||
|
||||
void XenonMemory::StoreI32(uint64_t address, uint32_t value) {
|
||||
if (!CheckMMIOStore(address, value)) {
|
||||
*reinterpret_cast<uint32_t*>(Translate(address)) = value;
|
||||
}
|
||||
}
|
||||
|
||||
void XenonMemory::StoreI64(uint64_t address, uint64_t value) {
|
||||
if (!CheckMMIOStore(address, value)) {
|
||||
*reinterpret_cast<uint64_t*>(Translate(address)) = value;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t XenonMemory::HeapAlloc(
|
||||
uint64_t base_address, size_t size, uint32_t flags,
|
||||
uint32_t alignment) {
|
||||
|
||||
@@ -15,33 +15,56 @@
|
||||
#include <xenia/core.h>
|
||||
|
||||
|
||||
typedef struct xe_ppc_state xe_ppc_state_t;
|
||||
|
||||
namespace xe {
|
||||
namespace cpu {
|
||||
|
||||
class XenonMemoryHeap;
|
||||
|
||||
typedef uint64_t (*MMIOReadCallback)(void* context, uint64_t addr);
|
||||
typedef void (*MMIOWriteCallback)(void* context, uint64_t addr,
|
||||
uint64_t value);
|
||||
|
||||
class XenonMemory : public alloy::Memory {
|
||||
public:
|
||||
XenonMemory();
|
||||
virtual ~XenonMemory();
|
||||
|
||||
virtual int Initialize();
|
||||
int Initialize() override;
|
||||
|
||||
virtual uint64_t HeapAlloc(
|
||||
bool AddMappedRange(uint64_t address, uint64_t mask,
|
||||
uint64_t size,
|
||||
void* context,
|
||||
MMIOReadCallback read_callback = nullptr,
|
||||
MMIOWriteCallback write_callback = nullptr);
|
||||
|
||||
uint8_t LoadI8(uint64_t address) override;
|
||||
uint16_t LoadI16(uint64_t address) override;
|
||||
uint32_t LoadI32(uint64_t address) override;
|
||||
uint64_t LoadI64(uint64_t address) override;
|
||||
void StoreI8(uint64_t address, uint8_t value) override;
|
||||
void StoreI16(uint64_t address, uint16_t value) override;
|
||||
void StoreI32(uint64_t address, uint32_t value) override;
|
||||
void StoreI64(uint64_t address, uint64_t value) override;
|
||||
|
||||
uint64_t HeapAlloc(
|
||||
uint64_t base_address, size_t size, uint32_t flags,
|
||||
uint32_t alignment = 0x20);
|
||||
virtual int HeapFree(uint64_t address, size_t size);
|
||||
uint32_t alignment = 0x20) override;
|
||||
int HeapFree(uint64_t address, size_t size) override;
|
||||
|
||||
virtual size_t QuerySize(uint64_t base_address);
|
||||
size_t QuerySize(uint64_t base_address) override;
|
||||
|
||||
virtual int Protect(uint64_t address, size_t size, uint32_t access);
|
||||
virtual uint32_t QueryProtect(uint64_t address);
|
||||
int Protect(uint64_t address, size_t size, uint32_t access) override;
|
||||
uint32_t QueryProtect(uint64_t address) override;
|
||||
|
||||
private:
|
||||
int MapViews(uint8_t* mapping_base);
|
||||
void UnmapViews();
|
||||
|
||||
bool CheckMMIOLoad(uint64_t address, uint64_t* out_value);
|
||||
bool CheckMMIOStore(uint64_t address, uint64_t value);
|
||||
|
||||
private:
|
||||
HANDLE mapping_;
|
||||
uint8_t* mapping_base_;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <xenia/common.h>
|
||||
#include <xenia/core.h>
|
||||
#include <xenia/xbox.h>
|
||||
#include <xenia/cpu/xenon_memory.h>
|
||||
|
||||
|
||||
XEDECLARECLASS1(xe, ExportResolver);
|
||||
@@ -41,7 +42,7 @@ public:
|
||||
ui::Window* main_window() const { return main_window_; }
|
||||
void set_main_window(ui::Window* window);
|
||||
|
||||
Memory* memory() const { return memory_; }
|
||||
cpu::XenonMemory* memory() const { return memory_; }
|
||||
|
||||
debug::DebugServer* debug_server() const { return debug_server_; }
|
||||
|
||||
@@ -68,7 +69,7 @@ private:
|
||||
|
||||
ui::Window* main_window_;
|
||||
|
||||
Memory* memory_;
|
||||
cpu::XenonMemory* memory_;
|
||||
|
||||
debug::DebugServer* debug_server_;
|
||||
|
||||
|
||||
@@ -45,12 +45,13 @@ X_STATUS GraphicsSystem::Setup() {
|
||||
worker_ = new RingBufferWorker(this, memory_);
|
||||
|
||||
// Let the processor know we want register access callbacks.
|
||||
RegisterAccessCallbacks callbacks;
|
||||
callbacks.context = this;
|
||||
callbacks.handles = (RegisterHandlesCallback)HandlesRegisterThunk;
|
||||
callbacks.read = (RegisterReadCallback)ReadRegisterThunk;
|
||||
callbacks.write = (RegisterWriteCallback)WriteRegisterThunk;
|
||||
emulator_->processor()->AddRegisterAccessCallbacks(callbacks);
|
||||
emulator_->memory()->AddMappedRange(
|
||||
0x7FC80000,
|
||||
0xFFFF0000,
|
||||
0x0000FFFF,
|
||||
this,
|
||||
reinterpret_cast<MMIOReadCallback>(MMIOReadRegisterThunk),
|
||||
reinterpret_cast<MMIOWriteCallback>(MMIOWriteRegisterThunk));
|
||||
|
||||
// Create worker thread.
|
||||
// This will initialize the graphics system.
|
||||
@@ -132,10 +133,6 @@ void GraphicsSystem::EnableReadPointerWriteBack(uint32_t ptr,
|
||||
worker_->EnableReadPointerWriteBack(ptr, block_size);
|
||||
}
|
||||
|
||||
bool GraphicsSystem::HandlesRegister(uint64_t addr) {
|
||||
return (addr & 0xFFFF0000) == 0x7FC80000;
|
||||
}
|
||||
|
||||
uint64_t GraphicsSystem::ReadRegister(uint64_t addr) {
|
||||
uint32_t r = addr & 0xFFFF;
|
||||
XELOGGPU("ReadRegister(%.4X)", r);
|
||||
|
||||
@@ -40,7 +40,6 @@ public:
|
||||
void InitializeRingBuffer(uint32_t ptr, uint32_t page_count);
|
||||
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size);
|
||||
|
||||
bool HandlesRegister(uint64_t addr);
|
||||
virtual uint64_t ReadRegister(uint64_t addr);
|
||||
virtual void WriteRegister(uint64_t addr, uint64_t value);
|
||||
|
||||
@@ -59,14 +58,11 @@ private:
|
||||
}
|
||||
void ThreadStart();
|
||||
|
||||
static bool HandlesRegisterThunk(GraphicsSystem* gs, uint64_t addr) {
|
||||
return gs->HandlesRegister(addr);
|
||||
}
|
||||
static uint64_t ReadRegisterThunk(GraphicsSystem* gs, uint64_t addr) {
|
||||
static uint64_t MMIOReadRegisterThunk(GraphicsSystem* gs, uint64_t addr) {
|
||||
return gs->ReadRegister(addr);
|
||||
}
|
||||
static void WriteRegisterThunk(GraphicsSystem* gs, uint64_t addr,
|
||||
uint64_t value) {
|
||||
static void MMIOWriteRegisterThunk(GraphicsSystem* gs, uint64_t addr,
|
||||
uint64_t value) {
|
||||
gs->WriteRegister(addr, value);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user