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:
parent
d53b640bcf
commit
47b121a700
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user