From 47b121a700fc240e786d268203f0b657153b8912 Mon Sep 17 00:00:00 2001 From: Robbie Date: Sun, 8 Oct 2017 15:37:54 -0500 Subject: [PATCH] PPU Page Faults (#3309) I guess I'll merge it and fix some issues later myself. --- Utilities/Thread.cpp | 55 +++++++++++++++++++- rpcs3/Emu/Cell/lv2/lv2.cpp | 4 +- rpcs3/Emu/Cell/lv2/sys_mmapper.cpp | 73 ++++++++++++++++++++++++++- rpcs3/Emu/Cell/lv2/sys_mmapper.h | 43 +++++++++++++++- rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp | 67 ++++++++++++++++++++++++ rpcs3/Emu/Cell/lv2/sys_ppu_thread.h | 13 +++++ 6 files changed, 249 insertions(+), 6 deletions(-) diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index b8a607361c..9a3034c49e 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -3,6 +3,8 @@ #include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/Cell/RawSPUThread.h" +#include "Emu/Cell/lv2/sys_mmapper.h" +#include "Emu/Cell/lv2/sys_event.h" #include "Thread.h" #include @@ -1259,9 +1261,60 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context) return true; } - // TODO: allow recovering from a page fault as a feature of PS3 virtual memory if (cpu) { + if (fxm::check()) + { + for (const auto& entry : fxm::get()->entries) + { + auto mem = vm::get(vm::any, entry.start_addr); + if (!mem) + { + continue; + } + if (entry.start_addr <= addr && addr <= addr + mem->size - 1) + { + // Place the page fault event onto table so that other functions [sys_mmapper_free_address and ppu pagefault funcs] + // know that this thread is page faulted and where. + + auto pf_entries = fxm::get_always(); + { + semaphore_lock pf_lock(pf_entries->pf_mutex); + page_fault_event pf_event{ cpu->id, addr }; + pf_entries->events.emplace_back(pf_event); + } + + // Now, we notify the game that a page fault occurred so it can rectify it. + // Note, for data3, were the memory readable AND we got a page fault, it must be due to a write violation since reads are allowed. + be_t data1 = addr; + be_t data2 = (SYS_MEMORY_PAGE_FAULT_TYPE_PPU_THREAD << 32) + cpu->id; // TODO: fix hack for now that assumes PPU thread always. + be_t data3 = vm::check_addr(addr, a_size, vm::page_readable) ? SYS_MEMORY_PAGE_FAULT_CAUSE_READ_ONLY : SYS_MEMORY_PAGE_FAULT_CAUSE_NON_MAPPED; + + LOG_ERROR(MEMORY, "Page_fault %s location 0x%x because of %s memory", is_writing ? "writing" : "reading", + addr, data3 == SYS_MEMORY_PAGE_FAULT_CAUSE_READ_ONLY ? "writing read-only" : "using unmapped"); + + error_code sending_error = sys_event_port_send(entry.port_id, data1, data2, data3); + + // If we fail due to being busy, wait a bit and try again. + while (sending_error == CELL_EBUSY) + { + lv2_obj::sleep(*cpu, 1000); + thread_ctrl::wait_for(1000); + sending_error = sys_event_port_send(entry.port_id, data1, data2, data3); + } + + if (sending_error) + { + fmt::throw_exception("Unknown error %x while trying to pass page fault.", sending_error.value); + } + + lv2_obj::sleep(*cpu); + thread_ctrl::wait(); + return true; + } + } + } + LOG_FATAL(MEMORY, "Access violation %s location 0x%x", is_writing ? "writing" : "reading", addr); cpu->state += cpu_flag::dbg_pause; cpu->check_state(); diff --git a/rpcs3/Emu/Cell/lv2/lv2.cpp b/rpcs3/Emu/Cell/lv2/lv2.cpp index 339f65b543..ffd4057609 100644 --- a/rpcs3/Emu/Cell/lv2/lv2.cpp +++ b/rpcs3/Emu/Cell/lv2/lv2.cpp @@ -106,8 +106,8 @@ const std::array s_ppu_syscall_table null_func,//BIND_FUNC(sys_ppu_...), //54 (0x036) ROOT null_func,//BIND_FUNC(sys_ppu_...), //55 (0x037) ROOT BIND_FUNC(sys_ppu_thread_rename), //56 (0x038) - null_func,//BIND_FUNC(sys_ppu_thread_recover_page_fault)//57 (0x039) - null_func,//BIND_FUNC(sys_ppu_thread_get_page_fault_context),//58 (0x03A) + BIND_FUNC(sys_ppu_thread_recover_page_fault), //57 (0x039) + BIND_FUNC(sys_ppu_thread_get_page_fault_context), //58 (0x03A) null_func, //59 (0x03B) UNS BIND_FUNC(sys_trace_create), //60 (0x03C) BIND_FUNC(sys_trace_start), //61 (0x03D) diff --git a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp index fbefd4570b..94061efa18 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp @@ -1,5 +1,8 @@ #include "stdafx.h" #include "sys_mmapper.h" +#include "Emu/Cell/PPUThread.h" +#include "sys_ppu_thread.h" +#include "Emu/Cell/lv2/sys_event.h" namespace vm { using namespace ps3; } @@ -179,6 +182,19 @@ error_code sys_mmapper_free_address(u32 addr) { sys_mmapper.error("sys_mmapper_free_address(addr=0x%x)", addr); + // If page fault notify exists and an address in this area is faulted, we can't free the memory. + auto pf_events = fxm::get_always(); + semaphore_lock pf_lock(pf_events->pf_mutex); + + for (const auto& ev : pf_events->events) + { + auto mem = vm::get(vm::any, addr); + if (mem && addr <= ev.fault_addr && ev.fault_addr <= addr + mem->size - 1) + { + return CELL_EBUSY; + } + } + // Try to unmap area const auto area = vm::unmap(addr, true); @@ -192,6 +208,21 @@ error_code sys_mmapper_free_address(u32 addr) return CELL_EBUSY; } + // If a memory block is freed, remove it from page notification table. + auto pf_entries = fxm::get_always(); + auto ind_to_remove = pf_entries->entries.begin(); + for (; ind_to_remove != pf_entries->entries.end(); ++ind_to_remove) + { + if (addr == ind_to_remove->start_addr) + { + break; + } + } + if (ind_to_remove != pf_entries->entries.end()) + { + pf_entries->entries.erase(ind_to_remove); + } + return CELL_OK; } @@ -332,9 +363,47 @@ error_code sys_mmapper_unmap_shared_memory(u32 addr, vm::ptr mem_id) return CELL_OK; } -error_code sys_mmapper_enable_page_fault_notification(u32 addr, u32 eq) +error_code sys_mmapper_enable_page_fault_notification(u32 start_addr, u32 event_queue_id) { - sys_mmapper.todo("sys_mmapper_enable_page_fault_notification(addr=0x%x, eq=0x%x)", addr, eq); + sys_mmapper.warning("sys_mmapper_enable_page_fault_notification(start_addr=0x%x, event_queue_id=0x%x)", start_addr, event_queue_id); + + auto mem = vm::get(vm::any, start_addr); + if (!mem) + { + return CELL_EINVAL; + } + + // TODO: Check memory region's flags to make sure the memory can be used for page faults. + + auto queue = idm::get(event_queue_id); + + if (!queue) + { // Can't connect the queue if it doesn't exist. + return CELL_ESRCH; + } + + auto pf_entries = fxm::get_always(); + + // We're not allowed to have the same queue registered more than once for page faults. + for (const auto& entry : pf_entries->entries) + { + if (entry.event_queue_id == event_queue_id) + { + return CELL_ESRCH; + } + } + + vm::ptr port_id = vm::make_var(0); + error_code res = sys_event_port_create(port_id, SYS_EVENT_PORT_LOCAL, SYS_MEMORY_PAGE_FAULT_EVENT_KEY); + sys_event_port_connect_local(port_id->value(), event_queue_id); + + if (res == CELL_EAGAIN) + { // Not enough system resources. + return CELL_EAGAIN; + } + + page_fault_notification_entry entry{ start_addr, event_queue_id, port_id->value() }; + pf_entries->entries.emplace_back(entry); return CELL_OK; } diff --git a/rpcs3/Emu/Cell/lv2/sys_mmapper.h b/rpcs3/Emu/Cell/lv2/sys_mmapper.h index b626fe1484..6ddb15e02e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mmapper.h +++ b/rpcs3/Emu/Cell/lv2/sys_mmapper.h @@ -3,6 +3,8 @@ #include "sys_sync.h" #include "sys_memory.h" +#include + struct lv2_memory : lv2_obj { static const u32 id_base = 0x08000000; @@ -26,6 +28,45 @@ struct lv2_memory : lv2_obj } }; +enum : u64 +{ + SYS_MEMORY_PAGE_FAULT_EVENT_KEY = 0xfffe000000000000ULL, +}; + +enum : u32 +{ + SYS_MEMORY_PAGE_FAULT_CAUSE_NON_MAPPED = 0x00000002U, + SYS_MEMORY_PAGE_FAULT_CAUSE_READ_ONLY = 0x00000001U, + SYS_MEMORY_PAGE_FAULT_TYPE_PPU_THREAD = 0x00000000U, + SYS_MEMORY_PAGE_FAULT_TYPE_SPU_THREAD = 0x00000001U, + SYS_MEMORY_PAGE_FAULT_TYPE_RAW_SPU = 0x00000002U, +}; + +struct page_fault_notification_entry +{ + u32 start_addr; // Starting address of region to monitor. + u32 event_queue_id; // Queue to be notified. + u32 port_id; // Port used to notify the queue. +}; + +// Used to hold list of queues to be notified on page fault event. +struct page_fault_notification_entries +{ + std::list entries; +}; + +struct page_fault_event +{ + u32 thread_id; + u32 fault_addr; +}; + +struct page_fault_event_entries +{ + std::list events; + semaphore<> pf_mutex; +}; + // SysCalls error_code sys_mmapper_allocate_address(u64 size, u64 flags, u64 alignment, vm::ps3::ptr alloc_addr); error_code sys_mmapper_allocate_fixed_address(); @@ -37,4 +78,4 @@ error_code sys_mmapper_free_shared_memory(u32 mem_id); error_code sys_mmapper_map_shared_memory(u32 addr, u32 mem_id, u64 flags); error_code sys_mmapper_search_and_map(u32 start_addr, u32 mem_id, u64 flags, vm::ps3::ptr alloc_addr); error_code sys_mmapper_unmap_shared_memory(u32 addr, vm::ps3::ptr mem_id); -error_code sys_mmapper_enable_page_fault_notification(u32 addr, u32 eq); +error_code sys_mmapper_enable_page_fault_notification(u32 start_addr, u32 event_queue_id); diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp index 02d7829d9c..db3c170ab9 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp @@ -7,6 +7,7 @@ #include "Emu/Cell/PPUThread.h" #include "sys_ppu_thread.h" #include "sys_event.h" +#include "sys_mmapper.h" namespace vm { using namespace ps3; } @@ -381,3 +382,69 @@ error_code sys_ppu_thread_rename(u32 thread_id, vm::cptr name) return CELL_OK; } + +error_code sys_ppu_thread_recover_page_fault(u32 thread_id) +{ + sys_ppu_thread.warning("sys_ppu_thread_recover_page_fault(thread_id=0x%x)", thread_id); + const auto thread = idm::get(thread_id); + if (!thread) + { + return CELL_ESRCH; + } + + // We can only wake a thread if it is being suspended for a page fault. + auto pf_events = fxm::get_always(); + auto pf_event_ind = pf_events->events.begin(); + + for (auto event_ind = pf_events->events.begin(); event_ind != pf_events->events.end(); ++event_ind) + { + if (event_ind->thread_id == thread_id) + { + pf_event_ind = event_ind; + break; + } + } + + if (pf_event_ind == pf_events->events.end()) + { // if not found... + return CELL_EINVAL; + } + + pf_events->events.erase(pf_event_ind); + + lv2_obj::awake(*thread); + return CELL_OK; +} + +error_code sys_ppu_thread_get_page_fault_context(u32 thread_id, vm::ptr ctxt) +{ + sys_ppu_thread.todo("sys_ppu_thread_get_page_fault_context(thread_id=0x%x, ctxt=*0x%x)", thread_id, ctxt); + + const auto thread = idm::get(thread_id); + if (!thread) + { + return CELL_ESRCH; + } + + // We can only get a context if the thread is being suspended for a page fault. + auto pf_events = fxm::get_always(); + + bool found = false; + for (const auto& ev : pf_events->events) + { + if (ev.thread_id == thread_id) + { + found = true; + break; + } + } + if (!found) + { + return CELL_EINVAL; + } + + // TODO: Fill ctxt with proper information. + + return CELL_OK; +} + diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h index 5a5501f2a3..df32f76f4d 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.h @@ -29,6 +29,17 @@ struct ppu_thread_param_t be_t tls; // vm::ps3::bptr }; +struct sys_ppu_thread_icontext_t +{ + be_t gpr[32]; + be_t cr; + be_t rsv1; + be_t xer; + be_t lr; + be_t ctr; + be_t pc; +}; + enum : u32 { PPU_THREAD_STATUS_IDLE, @@ -56,3 +67,5 @@ error_code sys_ppu_thread_restart(u32 thread_id); error_code _sys_ppu_thread_create(vm::ps3::ptr thread_id, vm::ps3::ptr param, u64 arg, u64 arg4, s32 prio, u32 stacksize, u64 flags, vm::ps3::cptr threadname); error_code sys_ppu_thread_start(ppu_thread& ppu, u32 thread_id); error_code sys_ppu_thread_rename(u32 thread_id, vm::ps3::cptr name); +error_code sys_ppu_thread_recover_page_fault(u32 thread_id); +error_code sys_ppu_thread_get_page_fault_context(u32 thread_id, vm::ps3::ptr ctxt);