[Memory/Vulkan] Move old memory watches to the Vulkan backend

This commit is contained in:
Triang3l
2019-08-03 21:06:59 +03:00
parent 0370f8bbd9
commit d20c2fa9da
7 changed files with 192 additions and 355 deletions

View File

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

View File

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