Pure dynamic MMIO access. Prep for more complex GPU memory management.

This commit is contained in:
Ben Vanik
2014-06-01 23:36:18 -07:00
parent 3a8065b7b1
commit 0e3854555d
19 changed files with 335 additions and 586 deletions

View File

@@ -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");

View File

@@ -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(

View File

@@ -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) {

View File

@@ -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_;