1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 02:32:36 +01:00

PPU Page Faults (#3309)

I guess I'll merge it and fix some issues later myself.
This commit is contained in:
Robbie 2017-10-08 15:37:54 -05:00 committed by Ivan
parent d53b640bcf
commit 47b121a700
6 changed files with 249 additions and 6 deletions

View File

@ -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 <typeinfo>
@ -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<page_fault_notification_entries>())
{
for (const auto& entry : fxm::get<page_fault_notification_entries>()->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<page_fault_event_entries>();
{
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<u64> data1 = addr;
be_t<u64> data2 = (SYS_MEMORY_PAGE_FAULT_TYPE_PPU_THREAD << 32) + cpu->id; // TODO: fix hack for now that assumes PPU thread always.
be_t<u64> 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();

View File

@ -106,8 +106,8 @@ const std::array<ppu_function_t, 1024> 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)

View File

@ -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<page_fault_event_entries>();
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<page_fault_notification_entries>();
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<u32> 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<lv2_obj, lv2_event_queue>(event_queue_id);
if (!queue)
{ // Can't connect the queue if it doesn't exist.
return CELL_ESRCH;
}
auto pf_entries = fxm::get_always<page_fault_notification_entries>();
// 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<u32> port_id = vm::make_var<u32>(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;
}

View File

@ -3,6 +3,8 @@
#include "sys_sync.h"
#include "sys_memory.h"
#include <list>
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<page_fault_notification_entry> entries;
};
struct page_fault_event
{
u32 thread_id;
u32 fault_addr;
};
struct page_fault_event_entries
{
std::list<page_fault_event> events;
semaphore<> pf_mutex;
};
// SysCalls
error_code sys_mmapper_allocate_address(u64 size, u64 flags, u64 alignment, vm::ps3::ptr<u32> 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<u32> alloc_addr);
error_code sys_mmapper_unmap_shared_memory(u32 addr, vm::ps3::ptr<u32> 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);

View File

@ -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<char> 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<ppu_thread>(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<page_fault_event_entries>();
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<sys_ppu_thread_icontext_t> 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<ppu_thread>(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<page_fault_event_entries>();
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;
}

View File

@ -29,6 +29,17 @@ struct ppu_thread_param_t
be_t<u32> tls; // vm::ps3::bptr<void>
};
struct sys_ppu_thread_icontext_t
{
be_t<u64> gpr[32];
be_t<u32> cr;
be_t<u32> rsv1;
be_t<u64> xer;
be_t<u64> lr;
be_t<u64> ctr;
be_t<u64> 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<u64> thread_id, vm::ps3::ptr<ppu_thread_param_t> param, u64 arg, u64 arg4, s32 prio, u32 stacksize, u64 flags, vm::ps3::cptr<char> 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<char> 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<sys_ppu_thread_icontext_t> ctxt);