[Memory/Vulkan] Move old memory watches to the Vulkan backend
This commit is contained in:
@@ -16,7 +16,6 @@
|
||||
#include "xenia/base/byte_order.h"
|
||||
#include "xenia/base/exception_handler.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
|
||||
namespace xe {
|
||||
@@ -107,211 +106,6 @@ bool MMIOHandler::CheckStore(uint32_t virtual_address, uint32_t value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uintptr_t MMIOHandler::AddPhysicalAccessWatch(uint32_t guest_address,
|
||||
size_t length, WatchType type,
|
||||
AccessWatchCallback callback,
|
||||
void* callback_context,
|
||||
void* callback_data) {
|
||||
uint32_t base_address = guest_address & 0x1FFFFFFF;
|
||||
|
||||
// Can only protect sizes matching system page size.
|
||||
// This means we need to round up, which will cause spurious access
|
||||
// violations and invalidations.
|
||||
// TODO(benvanik): only invalidate if actually within the region?
|
||||
length = xe::round_up(length + (base_address % xe::memory::page_size()),
|
||||
xe::memory::page_size());
|
||||
base_address = base_address - (base_address % xe::memory::page_size());
|
||||
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
// Fire any access watches that overlap this region.
|
||||
for (auto it = access_watches_.begin(); it != access_watches_.end();) {
|
||||
// Case 1: 2222222|222|11111111
|
||||
// Case 2: 1111111|222|22222222
|
||||
// Case 3: 1111111|222|11111111 (fragmentation)
|
||||
// Case 4: 2222222|222|22222222 (complete overlap)
|
||||
bool hit = false;
|
||||
auto entry = *it;
|
||||
|
||||
if (base_address <= (*it)->address &&
|
||||
base_address + length > (*it)->address) {
|
||||
hit = true;
|
||||
} else if ((*it)->address <= base_address &&
|
||||
(*it)->address + (*it)->length > base_address) {
|
||||
hit = true;
|
||||
} else if ((*it)->address <= base_address &&
|
||||
(*it)->address + (*it)->length > base_address + length) {
|
||||
hit = true;
|
||||
} else if ((*it)->address >= base_address &&
|
||||
(*it)->address + (*it)->length < base_address + length) {
|
||||
hit = true;
|
||||
}
|
||||
|
||||
if (hit) {
|
||||
FireAccessWatch(*it);
|
||||
it = access_watches_.erase(it);
|
||||
delete entry;
|
||||
continue;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
// Add to table. The slot reservation may evict a previous watch, which
|
||||
// could include our target, so we do it first.
|
||||
auto entry = new AccessWatchEntry();
|
||||
entry->address = base_address;
|
||||
entry->length = uint32_t(length);
|
||||
entry->type = type;
|
||||
entry->callback = callback;
|
||||
entry->callback_context = callback_context;
|
||||
entry->callback_data = callback_data;
|
||||
access_watches_.push_back(entry);
|
||||
|
||||
auto page_access = memory::PageAccess::kNoAccess;
|
||||
switch (type) {
|
||||
case kWatchWrite:
|
||||
page_access = memory::PageAccess::kReadOnly;
|
||||
break;
|
||||
case kWatchReadWrite:
|
||||
page_access = memory::PageAccess::kNoAccess;
|
||||
break;
|
||||
default:
|
||||
assert_unhandled_case(type);
|
||||
break;
|
||||
}
|
||||
|
||||
// Protect the range under all address spaces
|
||||
memory::Protect(physical_membase_ + entry->address, entry->length,
|
||||
page_access, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xA0000000 + entry->address, entry->length,
|
||||
page_access, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xC0000000 + entry->address, entry->length,
|
||||
page_access, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xE0000000 + entry->address, entry->length,
|
||||
page_access, nullptr);
|
||||
|
||||
return reinterpret_cast<uintptr_t>(entry);
|
||||
}
|
||||
|
||||
void MMIOHandler::FireAccessWatch(AccessWatchEntry* entry) {
|
||||
ClearAccessWatch(entry);
|
||||
entry->callback(entry->callback_context, entry->callback_data,
|
||||
entry->address);
|
||||
}
|
||||
|
||||
void MMIOHandler::ClearAccessWatch(AccessWatchEntry* entry) {
|
||||
memory::Protect(physical_membase_ + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xA0000000 + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xC0000000 + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
||||
memory::Protect(virtual_membase_ + 0xE0000000 + entry->address, entry->length,
|
||||
xe::memory::PageAccess::kReadWrite, nullptr);
|
||||
}
|
||||
|
||||
void MMIOHandler::CancelAccessWatch(uintptr_t watch_handle) {
|
||||
auto entry = reinterpret_cast<AccessWatchEntry*>(watch_handle);
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
// Allow access to the range again.
|
||||
ClearAccessWatch(entry);
|
||||
|
||||
// Remove from table.
|
||||
auto it = std::find(access_watches_.begin(), access_watches_.end(), entry);
|
||||
assert_false(it == access_watches_.end());
|
||||
|
||||
if (it != access_watches_.end()) {
|
||||
access_watches_.erase(it);
|
||||
}
|
||||
|
||||
delete entry;
|
||||
}
|
||||
|
||||
void MMIOHandler::InvalidateRange(uint32_t physical_address_and_heap,
|
||||
uint32_t length) {
|
||||
uint32_t heap_relative_address = physical_address_and_heap & 0x1FFFFFFF;
|
||||
length = std::min(length, 0x20000000u - heap_relative_address);
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
// Trigger the legacy (per-range) watches.
|
||||
for (auto it = access_watches_.begin(); it != access_watches_.end();) {
|
||||
auto entry = *it;
|
||||
if ((entry->address <= heap_relative_address &&
|
||||
entry->address + entry->length > heap_relative_address) ||
|
||||
(entry->address >= heap_relative_address &&
|
||||
entry->address < heap_relative_address + length)) {
|
||||
// This watch lies within the range. End it.
|
||||
FireAccessWatch(entry);
|
||||
it = access_watches_.erase(it);
|
||||
delete entry;
|
||||
continue;
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
bool MMIOHandler::IsRangeWatched(uint32_t physical_address, size_t length) {
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
|
||||
for (auto it = access_watches_.begin(); it != access_watches_.end(); ++it) {
|
||||
auto entry = *it;
|
||||
if ((entry->address <= physical_address &&
|
||||
entry->address + entry->length > physical_address + length)) {
|
||||
// This range lies entirely within this watch.
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(DrChat): Check if the range is partially covered, and subtract the
|
||||
// covered portion if it is.
|
||||
if ((entry->address <= physical_address &&
|
||||
entry->address + entry->length > physical_address)) {
|
||||
// The beginning of range lies partially within this watch.
|
||||
} else if ((entry->address < physical_address + length &&
|
||||
entry->address + entry->length > physical_address + length)) {
|
||||
// The ending of this range lies partially within this watch.
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MMIOHandler::CheckAccessWatch(uint32_t physical_address,
|
||||
uint32_t heap_address) {
|
||||
bool hit = false;
|
||||
|
||||
// Trigger legacy (per-range) access watches.
|
||||
// TODO(Triang3l): Remove when legacy watches are deleted.
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
for (auto it = access_watches_.begin(); it != access_watches_.end();) {
|
||||
auto entry = *it;
|
||||
if (entry->address <= physical_address &&
|
||||
entry->address + entry->length > physical_address) {
|
||||
// Hit! Remove the watch.
|
||||
hit = true;
|
||||
FireAccessWatch(entry);
|
||||
it = access_watches_.erase(it);
|
||||
delete entry;
|
||||
continue;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
|
||||
if (!hit) {
|
||||
// Rethrow access violation - range was not being watched.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Range was watched, so lets eat this access violation.
|
||||
return true;
|
||||
}
|
||||
|
||||
struct DecodedMov {
|
||||
size_t length;
|
||||
// Inidicates this is a load (or conversely a store).
|
||||
@@ -514,8 +308,8 @@ bool MMIOHandler::ExceptionCallback(Exception* ex) {
|
||||
guest_heap_address = 0;
|
||||
}
|
||||
|
||||
// HACK: Recheck if the pages are still protected (race condition - another
|
||||
// thread clears the writewatch we just hit)
|
||||
// Recheck if the pages are still protected (race condition - another thread
|
||||
// clears the writewatch we just hit).
|
||||
// Do this under the lock so we don't introduce another race condition.
|
||||
auto lock = global_critical_region_.Acquire();
|
||||
memory::PageAccess cur_access;
|
||||
@@ -527,26 +321,22 @@ bool MMIOHandler::ExceptionCallback(Exception* ex) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Access is not found within any range, so fail and let the caller handle
|
||||
// it (likely by aborting).
|
||||
// TODO(Triang3l): Remove legacy CheckAccessWatch, only call the callback.
|
||||
bool hit = CheckAccessWatch(guest_address, guest_heap_address);
|
||||
// The address is not found within any range, so either a write watch or an
|
||||
// actual access violation.
|
||||
if (access_violation_callback_) {
|
||||
switch (ex->access_violation_operation()) {
|
||||
case Exception::AccessViolationOperation::kRead:
|
||||
hit |= access_violation_callback_(access_violation_callback_context_,
|
||||
return access_violation_callback_(access_violation_callback_context_,
|
||||
size_t(ex->fault_address()), false);
|
||||
break;
|
||||
case Exception::AccessViolationOperation::kWrite:
|
||||
hit |= access_violation_callback_(access_violation_callback_context_,
|
||||
return access_violation_callback_(access_violation_callback_context_,
|
||||
size_t(ex->fault_address()), true);
|
||||
break;
|
||||
default:
|
||||
// Data Execution Prevention or something else uninteresting.
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hit;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto rip = ex->pc();
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#ifndef XENIA_CPU_MMIO_HANDLER_H_
|
||||
#define XENIA_CPU_MMIO_HANDLER_H_
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
@@ -48,12 +47,6 @@ class MMIOHandler {
|
||||
typedef bool (*AccessViolationCallback)(void* context, size_t host_address,
|
||||
bool is_write);
|
||||
|
||||
enum WatchType {
|
||||
kWatchInvalid = 0,
|
||||
kWatchWrite = 1,
|
||||
kWatchReadWrite = 2,
|
||||
};
|
||||
|
||||
// access_violation_callback is called in global_critical_region, so if
|
||||
// multiple threads trigger an access violation in the same page, the callback
|
||||
// will be called only once.
|
||||
@@ -71,37 +64,7 @@ class MMIOHandler {
|
||||
bool CheckLoad(uint32_t virtual_address, uint32_t* out_value);
|
||||
bool CheckStore(uint32_t virtual_address, uint32_t value);
|
||||
|
||||
// Memory watches: These are one-shot alarms that fire a callback (in the
|
||||
// context of the thread that caused the callback) when a memory range is
|
||||
// either written to or read from, depending on the watch type. These fire as
|
||||
// soon as a read/write happens, and only fire once.
|
||||
// These watches may be spuriously fired if memory is accessed nearby.
|
||||
// TODO(Triang3l): This is legacy currently used only to support the old
|
||||
// Vulkan graphics layer. Remove and use WatchPhysicalMemoryWrite instead.
|
||||
uintptr_t AddPhysicalAccessWatch(uint32_t guest_address, size_t length,
|
||||
WatchType type, AccessWatchCallback callback,
|
||||
void* callback_context, void* callback_data);
|
||||
void CancelAccessWatch(uintptr_t watch_handle);
|
||||
|
||||
// Fires and clears any write watches that overlap this range in one heap.
|
||||
// Unprotecting can be inhibited if this is called right before applying
|
||||
// different protection to the same range.
|
||||
void InvalidateRange(uint32_t physical_address_and_heap, uint32_t length);
|
||||
|
||||
// Returns true if /all/ of this range is watched.
|
||||
// TODO(Triang3l): Remove when legacy watches are removed.
|
||||
bool IsRangeWatched(uint32_t physical_address, size_t length);
|
||||
|
||||
protected:
|
||||
struct AccessWatchEntry {
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
WatchType type;
|
||||
AccessWatchCallback callback;
|
||||
void* callback_context;
|
||||
void* callback_data;
|
||||
};
|
||||
|
||||
MMIOHandler(uint8_t* virtual_membase, uint8_t* physical_membase,
|
||||
uint8_t* membase_end,
|
||||
AccessViolationCallback access_violation_callback,
|
||||
@@ -110,10 +73,6 @@ class MMIOHandler {
|
||||
static bool ExceptionCallbackThunk(Exception* ex, void* data);
|
||||
bool ExceptionCallback(Exception* ex);
|
||||
|
||||
void FireAccessWatch(AccessWatchEntry* entry);
|
||||
void ClearAccessWatch(AccessWatchEntry* entry);
|
||||
bool CheckAccessWatch(uint32_t guest_address, uint32_t guest_heap_address);
|
||||
|
||||
uint8_t* virtual_membase_;
|
||||
uint8_t* physical_membase_;
|
||||
uint8_t* memory_end_;
|
||||
@@ -123,11 +82,9 @@ class MMIOHandler {
|
||||
AccessViolationCallback access_violation_callback_;
|
||||
void* access_violation_callback_context_;
|
||||
|
||||
xe::global_critical_region global_critical_region_;
|
||||
// TODO(benvanik): data structure magic.
|
||||
std::list<AccessWatchEntry*> access_watches_;
|
||||
|
||||
static MMIOHandler* global_handler_;
|
||||
|
||||
xe::global_critical_region global_critical_region_;
|
||||
};
|
||||
|
||||
} // namespace cpu
|
||||
|
||||
Reference in New Issue
Block a user