Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PPU Page Faults #3309

Merged
merged 7 commits into from
Oct 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion Utilities/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions rpcs3/Emu/Cell/lv2/lv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
73 changes: 71 additions & 2 deletions rpcs3/Emu/Cell/lv2/sys_mmapper.cpp
Original file line number Diff line number Diff line change
@@ -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; }

Expand Down Expand Up @@ -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);

Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
43 changes: 42 additions & 1 deletion rpcs3/Emu/Cell/lv2/sys_mmapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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);
67 changes: 67 additions & 0 deletions rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -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;
}

13 changes: 13 additions & 0 deletions rpcs3/Emu/Cell/lv2/sys_ppu_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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);