[Memory] Move new watches to heap-aware Memory from MMIOHandler
This commit is contained in:
@@ -25,6 +25,8 @@ class ByteStream;
|
||||
|
||||
namespace xe {
|
||||
|
||||
class Memory;
|
||||
|
||||
enum SystemHeapFlag : uint32_t {
|
||||
kSystemHeapVirtual = 1 << 0,
|
||||
kSystemHeapPhysical = 1 << 1,
|
||||
@@ -163,9 +165,10 @@ class BaseHeap {
|
||||
protected:
|
||||
BaseHeap();
|
||||
|
||||
void Initialize(uint8_t* membase, uint32_t heap_base, uint32_t heap_size,
|
||||
uint32_t page_size);
|
||||
void Initialize(Memory* memory, uint8_t* membase, uint32_t heap_base,
|
||||
uint32_t heap_size, uint32_t page_size);
|
||||
|
||||
Memory* memory_;
|
||||
uint8_t* membase_;
|
||||
uint32_t heap_base_;
|
||||
uint32_t heap_size_;
|
||||
@@ -181,8 +184,8 @@ class VirtualHeap : public BaseHeap {
|
||||
~VirtualHeap() override;
|
||||
|
||||
// Initializes the heap properties and allocates the page table.
|
||||
void Initialize(uint8_t* membase, uint32_t heap_base, uint32_t heap_size,
|
||||
uint32_t page_size);
|
||||
void Initialize(Memory* memory, uint8_t* membase, uint32_t heap_base,
|
||||
uint32_t heap_size, uint32_t page_size);
|
||||
};
|
||||
|
||||
// A heap for ranges of memory that are mapped to physical ranges.
|
||||
@@ -198,8 +201,9 @@ class PhysicalHeap : public BaseHeap {
|
||||
~PhysicalHeap() override;
|
||||
|
||||
// Initializes the heap properties and allocates the page table.
|
||||
void Initialize(uint8_t* membase, uint32_t heap_base, uint32_t heap_size,
|
||||
uint32_t page_size, VirtualHeap* parent_heap);
|
||||
void Initialize(Memory* memory, uint8_t* membase, uint32_t heap_base,
|
||||
uint32_t heap_size, uint32_t page_size,
|
||||
VirtualHeap* parent_heap);
|
||||
|
||||
bool Alloc(uint32_t size, uint32_t alignment, uint32_t allocation_type,
|
||||
uint32_t protect, bool top_down, uint32_t* out_address) override;
|
||||
@@ -215,11 +219,18 @@ class PhysicalHeap : public BaseHeap {
|
||||
bool Protect(uint32_t address, uint32_t size, uint32_t protect,
|
||||
uint32_t* old_protect = nullptr) override;
|
||||
|
||||
void WatchWrite(uint32_t address, uint32_t size,
|
||||
cpu::MMIOHandler* mmio_handler);
|
||||
void WatchPhysicalWrite(uint32_t physical_address, uint32_t length);
|
||||
// Returns true if any page in the range was watched.
|
||||
bool TriggerWatches(uint32_t virtual_address, uint32_t length, bool is_write,
|
||||
bool unprotect = true);
|
||||
|
||||
protected:
|
||||
VirtualHeap* parent_heap_;
|
||||
|
||||
uint32_t system_page_size_;
|
||||
uint32_t system_page_count_;
|
||||
// Protected by global_critical_region.
|
||||
std::vector<uint64_t> system_pages_watched_write_;
|
||||
};
|
||||
|
||||
// Models the entire guest memory system on the console.
|
||||
@@ -322,9 +333,46 @@ class Memory {
|
||||
// Cancels a write watch requested with AddPhysicalAccessWatch.
|
||||
void CancelAccessWatch(uintptr_t watch_handle);
|
||||
|
||||
// Registers a global callback for physical memory writes. See
|
||||
// cpu/mmio_handler.h for more details about physical memory write watches.
|
||||
void* RegisterPhysicalWriteWatch(cpu::PhysicalWriteWatchCallback callback,
|
||||
typedef void (*PhysicalWriteWatchCallback)(void* context_ptr,
|
||||
uint32_t physical_address_start,
|
||||
uint32_t length);
|
||||
|
||||
// Physical memory write watching, allowing subsystems to invalidate cached
|
||||
// data that depends on memory contents.
|
||||
//
|
||||
// Placing a watch simply marks the pages (of the system page size) as
|
||||
// watched, individual watched ranges (or which specific subscribers are
|
||||
// watching specific pages) are not stored. Because of this, callbacks may be
|
||||
// triggered multiple times for a single range, and for any watched page every
|
||||
// registered callbacks is triggered. This is a very simple one-shot method
|
||||
// for use primarily for cache invalidation - there may be spurious firing,
|
||||
// for example, if the game only changes the protection level without writing
|
||||
// anything.
|
||||
//
|
||||
// A range of pages can be watched at any time, but pages are only unwatched
|
||||
// when watches are triggered (since multiple subscribers can depend on the
|
||||
// same memory, and one subscriber shouldn't interfere with another).
|
||||
//
|
||||
// Callbacks can be triggered for one page (if the guest just stores words) or
|
||||
// for multiple pages (for file reading, protection level changes).
|
||||
//
|
||||
// Only guest physical memory mappings are watched - the host-only mapping is
|
||||
// not protected so it can be used to bypass the write protection (for file
|
||||
// reads, for example - in this case, watches are triggered manually).
|
||||
//
|
||||
// Note that when a watch is triggered, the watched page is unprotected only
|
||||
// in the heap where the address is located. Since different virtual memory
|
||||
// mappings of physical memory can have different protection levels for the
|
||||
// same pages, and watches must not be placed on read-only or totally
|
||||
// inaccessible pages, there are significant difficulties with synchronizing
|
||||
// all the three ranges, but it's generally not needed.
|
||||
//
|
||||
// TODO(Triang3l): Allow the callbacks to unwatch regions larger than one page
|
||||
// (for instance, 64 KB) so there are less access violations. All callbacks
|
||||
// must agree to unwatch larger ranges because in some cases (like regions
|
||||
// near the locations that render targets have been resolved to) it is
|
||||
// necessary to invalidate only a single page and none more.
|
||||
void* RegisterPhysicalWriteWatch(PhysicalWriteWatchCallback callback,
|
||||
void* callback_context);
|
||||
|
||||
// Unregisters a physical memory write watch previously added with
|
||||
@@ -336,11 +384,8 @@ class Memory {
|
||||
// protection of it changes), the registered watch callbacks are triggered for
|
||||
// the page (or pages, for file reads and protection changes) where something
|
||||
// has been written to. This protects physical memory only under
|
||||
// virtual_membase_, so writing to physical_membase_ can be written to bypass
|
||||
// the protection placed by the watches. Read-only and inaccessible pages are
|
||||
// silently ignored, only attempts to write to read-write pages will trigger
|
||||
// watch callbacks.
|
||||
// AVOID CALLING WITH MUTEXES LOCKED!!!
|
||||
// virtual_membase_, so writing to physical_membase_ can be done to bypass the
|
||||
// protection placed by the watches.
|
||||
void WatchPhysicalMemoryWrite(uint32_t physical_address, uint32_t length);
|
||||
|
||||
// Allocates virtual memory from the 'system' heap.
|
||||
@@ -372,6 +417,10 @@ class Memory {
|
||||
int MapViews(uint8_t* mapping_base);
|
||||
void UnmapViews();
|
||||
|
||||
bool AccessViolationCallback(size_t host_address, bool is_write);
|
||||
static bool AccessViolationCallbackThunk(void* context, size_t host_address,
|
||||
bool is_write);
|
||||
|
||||
private:
|
||||
std::wstring file_name_;
|
||||
uint32_t system_page_size_ = 0;
|
||||
@@ -410,6 +459,14 @@ class Memory {
|
||||
} heaps_;
|
||||
|
||||
friend class BaseHeap;
|
||||
|
||||
friend class PhysicalHeap;
|
||||
struct PhysicalWriteWatchEntry {
|
||||
PhysicalWriteWatchCallback callback;
|
||||
void* callback_context;
|
||||
};
|
||||
xe::global_critical_region global_critical_region_;
|
||||
std::vector<PhysicalWriteWatchEntry*> physical_write_watches_;
|
||||
};
|
||||
|
||||
} // namespace xe
|
||||
|
||||
Reference in New Issue
Block a user