Common: Move VirtualMemory related functionality to core

Also rewrites page fault handling to not use EventSource junk.
This commit is contained in:
Stenzek 2022-12-28 16:53:37 +10:00 committed by refractionpcsx2
parent 1b86a6e6f8
commit 4cf041f6cb
25 changed files with 763 additions and 813 deletions

View File

@ -10,7 +10,6 @@ add_library(common)
# x86emitter sources # x86emitter sources
target_sources(common PRIVATE target_sources(common PRIVATE
AlignedMalloc.cpp AlignedMalloc.cpp
VirtualMemory.cpp
EventSource.inl EventSource.inl
SafeArray.inl SafeArray.inl
Console.cpp Console.cpp
@ -20,6 +19,7 @@ target_sources(common PRIVATE
Exceptions.cpp Exceptions.cpp
FastJmp.cpp FastJmp.cpp
FileSystem.cpp FileSystem.cpp
General.cpp
Image.cpp Image.cpp
HTTPDownloader.cpp HTTPDownloader.cpp
MemorySettingsInterface.cpp MemorySettingsInterface.cpp
@ -88,7 +88,6 @@ target_sources(common PRIVATE
MD5Digest.h MD5Digest.h
MRCHelpers.h MRCHelpers.h
Path.h Path.h
PageFaultSource.h
PrecompiledHeader.h PrecompiledHeader.h
ProgressCallback.h ProgressCallback.h
ReadbackSpinManager.h ReadbackSpinManager.h

39
common/General.cpp Normal file
View File

@ -0,0 +1,39 @@
/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2010 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "General.h"
// --------------------------------------------------------------------------------------
// PageProtectionMode (implementations)
// --------------------------------------------------------------------------------------
std::string PageProtectionMode::ToString() const
{
std::string modeStr;
if (m_read)
modeStr += "Read";
if (m_write)
modeStr += "Write";
if (m_exec)
modeStr += "Exec";
if (modeStr.empty())
return "NoAccess";
if (modeStr.length() <= 5)
modeStr += "Only";
return modeStr;
}

View File

@ -116,6 +116,14 @@ static __fi PageProtectionMode PageAccess_Any()
return PageProtectionMode().All(); return PageProtectionMode().All();
} }
struct PageFaultInfo
{
uptr pc;
uptr addr;
};
using PageFaultHandler = bool(*)(const PageFaultInfo& info);
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// HostSys // HostSys
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
@ -141,6 +149,12 @@ namespace HostSys
extern void DestroySharedMemory(void* ptr); extern void DestroySharedMemory(void* ptr);
extern void* MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, const PageProtectionMode& mode); extern void* MapSharedMemory(void* handle, size_t offset, void* baseaddr, size_t size, const PageProtectionMode& mode);
extern void UnmapSharedMemory(void* baseaddr, size_t size); extern void UnmapSharedMemory(void* baseaddr, size_t size);
/// Installs the specified page fault handler. Only one handler can be active at once.
bool InstallPageFaultHandler(PageFaultHandler handler);
/// Removes the page fault handler. handler is only specified to check against the active callback.
void RemovePageFaultHandler(PageFaultHandler handler);
} }
class SharedMemoryMappingArea class SharedMemoryMappingArea

View File

@ -21,13 +21,15 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <mutex>
#include "fmt/core.h" #include "fmt/core.h"
#include "common/Align.h" #include "common/Align.h"
#include "common/PageFaultSource.h"
#include "common/Assertions.h" #include "common/Assertions.h"
#include "common/Console.h" #include "common/Console.h"
#include "common/Exceptions.h" #include "common/Exceptions.h"
#include "common/General.h"
// Apple uses the MAP_ANON define instead of MAP_ANONYMOUS, but they mean // Apple uses the MAP_ANON define instead of MAP_ANONYMOUS, but they mean
// the same thing. // the same thing.
@ -44,34 +46,57 @@
#include <ucontext.h> #include <ucontext.h>
#endif #endif
extern void SignalExit(int sig); static std::recursive_mutex s_exception_handler_mutex;
static PageFaultHandler s_exception_handler_callback;
static bool s_in_exception_handler;
static const uptr m_pagemask = getpagesize() - 1; #ifdef __APPLE__
#if defined(__APPLE__)
static struct sigaction s_old_sigbus_action; static struct sigaction s_old_sigbus_action;
#else #else
static struct sigaction s_old_sigsegv_action; static struct sigaction s_old_sigsegv_action;
#endif #endif
static void CallExistingSignalHandler(int signal, siginfo_t* siginfo, void* ctx)
{
#ifdef __APPLE__
const struct sigaction& sa = s_old_sigbus_action;
#else
const struct sigaction& sa = s_old_sigsegv_action;
#endif
if (sa.sa_flags & SA_SIGINFO)
{
sa.sa_sigaction(signal, siginfo, ctx);
}
else if (sa.sa_handler == SIG_DFL)
{
// Re-raising the signal would just queue it, and since we'd restore the handler back to us,
// we'd end up right back here again. So just abort, because that's probably what it'd do anyway.
abort();
}
else if (sa.sa_handler != SIG_IGN)
{
sa.sa_handler(signal);
}
}
// Linux implementation of SIGSEGV handler. Bind it using sigaction(). // Linux implementation of SIGSEGV handler. Bind it using sigaction().
static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx) static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx)
{ {
// [TODO] : Add a thread ID filter to the Linux Signal handler here. // Executing the handler concurrently from multiple threads wouldn't go down well.
// Rationale: On windows, the __try/__except model allows per-thread specific behavior std::unique_lock lock(s_exception_handler_mutex);
// for page fault handling. On linux, there is a single signal handler for the whole
// process, but the handler is executed by the thread that caused the exception.
// Prevent recursive exception filtering.
if (s_in_exception_handler)
{
CallExistingSignalHandler(signal, siginfo, ctx);
return;
}
// Stdio Usage note: SIGSEGV handling is a synchronous in-thread signal. It is done // Note: Use of stdio functions isn't safe here. Avoid console logs, assertions, file logs,
// from the context of the current thread and stackframe. So long as the thread is not // or just about anything else useful. However, that's really only a concern if the signal
// the main/ui thread, use of the px assertion system should be safe. Use of stdio should // occurred within those functions. The logging which we do only happens when the exception
// be safe even on the main thread. // occurred within JIT code.
// (in other words, stdio limitations only really apply to process-level asynchronous
// signals)
// Note: Use of stdio functions isn't safe here. Avoid console logs,
// assertions, file logs, or just about anything else useful.
#if defined(__APPLE__) && defined(__x86_64__) #if defined(__APPLE__) && defined(__x86_64__)
void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip); void* const exception_pc = reinterpret_cast<void*>(static_cast<ucontext_t*>(ctx)->uc_mcontext->__ss.__rip);
@ -81,35 +106,62 @@ static void SysPageFaultSignalFilter(int signal, siginfo_t* siginfo, void* ctx)
void* const exception_pc = nullptr; void* const exception_pc = nullptr;
#endif #endif
// Note: This signal can be accessed by the EE or MTVU thread const PageFaultInfo pfi{(uptr)exception_pc, (uptr)siginfo->si_addr & ~__pagemask};
// Source_PageFault is a global variable with its own state information
// so for now we lock this exception code unless someone can fix this better...
std::unique_lock lock(PageFault_Mutex);
Source_PageFault->Dispatch(PageFaultInfo((uptr)exception_pc, (uptr)siginfo->si_addr & ~m_pagemask)); s_in_exception_handler = true;
// resumes execution right where we left off (re-executes instruction that const bool handled = s_exception_handler_callback(pfi);
// caused the SIGSEGV).
if (Source_PageFault->WasHandled()) s_in_exception_handler = false;
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV).
if (handled)
return; return;
std::fprintf(stderr, "Unhandled page fault @ 0x%08x", siginfo->si_addr); // Call old signal handler, which will likely dump core.
pxFailRel("Unhandled page fault"); CallExistingSignalHandler(signal, siginfo, ctx);
} }
void _platform_InstallSignalHandler() bool HostSys::InstallPageFaultHandler(PageFaultHandler handler)
{ {
Console.WriteLn("Installing POSIX SIGSEGV handler..."); std::unique_lock lock(s_exception_handler_mutex);
pxAssertRel(!s_exception_handler_callback, "A page fault handler is already registered.");
if (!s_exception_handler_callback)
{
struct sigaction sa; struct sigaction sa;
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO; sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = SysPageFaultSignalFilter; sa.sa_sigaction = SysPageFaultSignalFilter;
#if defined(__APPLE__) #ifdef __APPLE__
// MacOS uses SIGBUS for memory permission violations // MacOS uses SIGBUS for memory permission violations
sigaction(SIGBUS, &sa, &s_old_sigbus_action); if (sigaction(SIGBUS, &sa, &s_old_sigbus_action) != 0)
return false;
#else #else
sigaction(SIGSEGV, &sa, &s_old_sigsegv_action); if (sigaction(SIGSEGV, &sa, &s_old_sigsegv_action) != 0)
return false;
#endif
}
s_exception_handler_callback = handler;
return true;
}
void HostSys::RemovePageFaultHandler(PageFaultHandler handler)
{
std::unique_lock lock(s_exception_handler_mutex);
pxAssertRel(!s_exception_handler_callback || s_exception_handler_callback == handler,
"Not removing the same handler previously registered.");
if (!s_exception_handler_callback)
return;
s_exception_handler_callback = nullptr;
struct sigaction sa;
#ifdef __APPLE__
sigaction(SIGBUS, &s_old_sigbus_action, &sa);
#else
sigaction(SIGSEGV, &s_old_sigsegv_action, &sa);
#endif #endif
} }

View File

@ -17,60 +17,78 @@
#include "common/Align.h" #include "common/Align.h"
#include "common/RedtapeWindows.h" #include "common/RedtapeWindows.h"
#include "common/PageFaultSource.h"
#include "common/Console.h" #include "common/Console.h"
#include "common/General.h"
#include "common/Exceptions.h" #include "common/Exceptions.h"
#include "common/StringUtil.h" #include "common/StringUtil.h"
#include "common/AlignedMalloc.h" #include "common/AlignedMalloc.h"
#include "fmt/core.h" #include "common/Assertions.h"
#include "fmt/core.h"
#include "fmt/format.h" #include "fmt/format.h"
static long DoSysPageFaultExceptionFilter(EXCEPTION_POINTERS* eps) #include <mutex>
{
if (eps->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
return EXCEPTION_CONTINUE_SEARCH;
#if defined(_M_AMD64) static std::recursive_mutex s_exception_handler_mutex;
void* const exception_pc = reinterpret_cast<void*>(eps->ContextRecord->Rip); static PageFaultHandler s_exception_handler_callback;
#else static void* s_exception_handler_handle;
void* const exception_pc = nullptr; static bool s_in_exception_handler;
#endif
// Note: This exception can be accessed by the EE or MTVU thread
// Source_PageFault is a global variable with its own state information
// so for now we lock this exception code unless someone can fix this better...
std::unique_lock lock(PageFault_Mutex);
Source_PageFault->Dispatch(PageFaultInfo((uptr)exception_pc, (uptr)eps->ExceptionRecord->ExceptionInformation[1]));
return Source_PageFault->WasHandled() ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
}
long __stdcall SysPageFaultExceptionFilter(EXCEPTION_POINTERS* eps) long __stdcall SysPageFaultExceptionFilter(EXCEPTION_POINTERS* eps)
{ {
// Prevent recursive exception filtering by catching the exception from the filter here. // Executing the handler concurrently from multiple threads wouldn't go down well.
// In the event that the filter causes an access violation (happened during shutdown std::unique_lock lock(s_exception_handler_mutex);
// because Source_PageFault was deallocated), this will allow the debugger to catch the
// exception. // Prevent recursive exception filtering.
// TODO: find a reliable way to debug the filter itself, I've come up with a few ways that if (s_in_exception_handler)
// work but I don't fully understand why some do and some don't.
__try
{
return DoSysPageFaultExceptionFilter(eps);
}
__except (GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
// Only interested in page faults.
if (eps->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION)
return EXCEPTION_CONTINUE_SEARCH;
void* const exception_pc = reinterpret_cast<void*>(eps->ContextRecord->Rip);
const PageFaultInfo pfi{(uptr)exception_pc, (uptr)eps->ExceptionRecord->ExceptionInformation[1]};
s_in_exception_handler = true;
const bool handled = s_exception_handler_callback(pfi);
s_in_exception_handler = false;
return handled ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
}
bool HostSys::InstallPageFaultHandler(PageFaultHandler handler)
{
std::unique_lock lock(s_exception_handler_mutex);
pxAssertRel(!s_exception_handler_callback, "A page fault handler is already registered.");
if (!s_exception_handler_handle)
{
s_exception_handler_handle = AddVectoredExceptionHandler(TRUE, SysPageFaultExceptionFilter);
if (!s_exception_handler_handle)
return false;
}
s_exception_handler_callback = handler;
return true;
}
void HostSys::RemovePageFaultHandler(PageFaultHandler handler)
{
std::unique_lock lock(s_exception_handler_mutex);
pxAssertRel(!s_exception_handler_callback || s_exception_handler_callback == handler,
"Not removing the same handler previously registered.");
s_exception_handler_callback = nullptr;
if (s_exception_handler_handle)
{
RemoveVectoredExceptionHandler(s_exception_handler_handle);
s_exception_handler_handle = {};
} }
} }
void _platform_InstallSignalHandler()
{
#ifdef _WIN64 // We don't handle SEH properly on Win64 so use a vectored exception handler instead
AddVectoredExceptionHandler(true, SysPageFaultExceptionFilter);
#endif
}
static DWORD ConvertToWinApi(const PageProtectionMode& mode) static DWORD ConvertToWinApi(const PageProtectionMode& mode)
{ {
DWORD winmode = PAGE_NOACCESS; DWORD winmode = PAGE_NOACCESS;

View File

@ -70,6 +70,7 @@
<ClCompile Include="FastJmp.cpp"> <ClCompile Include="FastJmp.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile> </ClCompile>
<ClCompile Include="General.cpp" />
<ClCompile Include="GL\Context.cpp" /> <ClCompile Include="GL\Context.cpp" />
<ClCompile Include="GL\ContextWGL.cpp" /> <ClCompile Include="GL\ContextWGL.cpp" />
<ClCompile Include="GL\Program.cpp" /> <ClCompile Include="GL\Program.cpp" />
@ -90,7 +91,6 @@
<ClCompile Include="StringUtil.cpp" /> <ClCompile Include="StringUtil.cpp" />
<ClCompile Include="SettingsWrapper.cpp" /> <ClCompile Include="SettingsWrapper.cpp" />
<ClCompile Include="Timer.cpp" /> <ClCompile Include="Timer.cpp" />
<ClCompile Include="VirtualMemory.cpp" />
<ClCompile Include="Vulkan\vk_mem_alloc.cpp" /> <ClCompile Include="Vulkan\vk_mem_alloc.cpp" />
<ClCompile Include="Vulkan\Builders.cpp" /> <ClCompile Include="Vulkan\Builders.cpp" />
<ClCompile Include="Vulkan\Context.cpp" /> <ClCompile Include="Vulkan\Context.cpp" />

View File

@ -67,9 +67,6 @@
<ClCompile Include="emitter\simd.cpp"> <ClCompile Include="emitter\simd.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="VirtualMemory.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="emitter\WinCpuDetect.cpp"> <ClCompile Include="emitter\WinCpuDetect.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
@ -211,6 +208,7 @@
<ClCompile Include="WAVWriter.cpp"> <ClCompile Include="WAVWriter.cpp">
<Filter>Source Files</Filter> <Filter>Source Files</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="General.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="AlignedMalloc.h"> <ClInclude Include="AlignedMalloc.h">

View File

@ -150,6 +150,7 @@ set(pcsx2Sources
Vif_Codes.cpp Vif_Codes.cpp
Vif_Transfer.cpp Vif_Transfer.cpp
Vif_Unpack.cpp Vif_Unpack.cpp
VirtualMemory.cpp
vtlb.cpp vtlb.cpp
VU0.cpp VU0.cpp
VUmicro.cpp VUmicro.cpp
@ -217,6 +218,7 @@ set(pcsx2Headers
Vif_Dma.h Vif_Dma.h
Vif.h Vif.h
Vif_Unpack.h Vif_Unpack.h
VirtualMemory.h
vtlb.h vtlb.h
VUflags.h VUflags.h
VUmicro.h VUmicro.h

View File

@ -14,4 +14,42 @@
*/ */
#include "PrecompiledHeader.h" #include "PrecompiledHeader.h"
#include "GSFunctionMap.h" #include "GS/Renderers/Common/GSFunctionMap.h"
#include "System.h"
static GSCodeReserve s_instance;
GSCodeReserve::GSCodeReserve()
: RecompiledCodeReserve("GS Software Renderer")
{
}
GSCodeReserve::~GSCodeReserve() = default;
GSCodeReserve& GSCodeReserve::GetInstance()
{
return s_instance;
}
void GSCodeReserve::Assign(VirtualMemoryManagerPtr allocator)
{
RecompiledCodeReserve::Assign(std::move(allocator), HostMemoryMap::SWrecOffset, HostMemoryMap::SWrecSize);
}
void GSCodeReserve::Reset()
{
RecompiledCodeReserve::Reset();
m_memory_used = 0;
}
u8* GSCodeReserve::Reserve(size_t size)
{
pxAssert((m_memory_used + size) <= m_size);
return m_baseptr + m_memory_used;
}
void GSCodeReserve::Commit(size_t size)
{
pxAssert((m_memory_used + size) <= m_size);
m_memory_used += size;
}

View File

@ -17,7 +17,7 @@
#include "GS/GSExtra.h" #include "GS/GSExtra.h"
#include "GS/Renderers/SW/GSScanlineEnvironment.h" #include "GS/Renderers/SW/GSScanlineEnvironment.h"
#include "System.h" #include "VirtualMemory.h"
#include "common/emitter/tools.h" #include "common/emitter/tools.h"
template <class KEY, class VALUE> template <class KEY, class VALUE>
@ -142,6 +142,31 @@ public:
} }
}; };
// --------------------------------------------------------------------------------------
// GSCodeReserve
// --------------------------------------------------------------------------------------
// Stores code buffers for the GS software JIT.
//
class GSCodeReserve : public RecompiledCodeReserve
{
public:
GSCodeReserve();
~GSCodeReserve();
static GSCodeReserve& GetInstance();
size_t GetMemoryUsed() const { return m_memory_used; }
void Assign(VirtualMemoryManagerPtr allocator);
void Reset();
u8* Reserve(size_t size);
void Commit(size_t size);
private:
size_t m_memory_used = 0;
};
template <class CG, class KEY, class VALUE> template <class CG, class KEY, class VALUE>
class GSCodeGeneratorFunctionMap : public GSFunctionMap<KEY, VALUE> class GSCodeGeneratorFunctionMap : public GSFunctionMap<KEY, VALUE>
{ {
@ -175,7 +200,7 @@ public:
} }
else else
{ {
u8* code_ptr = GetVmMemory().GSCode().Reserve(MAX_SIZE); u8* code_ptr = GSCodeReserve::GetInstance().Reserve(MAX_SIZE);
CG cg(key, code_ptr, MAX_SIZE); CG cg(key, code_ptr, MAX_SIZE);
ASSERT(cg.getSize() < MAX_SIZE); ASSERT(cg.getSize() < MAX_SIZE);
@ -185,7 +210,7 @@ public:
sel.Print(); sel.Print();
#endif #endif
GetVmMemory().GSCode().Commit(cg.getSize()); GSCodeReserve::GetInstance().Commit(cg.getSize());
ret = (VALUE)cg.getCode(); ret = (VALUE)cg.getCode();

View File

@ -38,16 +38,16 @@ GSDrawScanline::GSDrawScanline()
: m_sp_map("GSSetupPrim") : m_sp_map("GSSetupPrim")
, m_ds_map("GSDrawScanline") , m_ds_map("GSDrawScanline")
{ {
GetVmMemory().GSCode().AllowModification(); GSCodeReserve::GetInstance().AllowModification();
GetVmMemory().GSCode().Reset(); GSCodeReserve::GetInstance().Reset();
} }
GSDrawScanline::~GSDrawScanline() GSDrawScanline::~GSDrawScanline()
{ {
if (const size_t used = GetVmMemory().GSCode().GetMemoryUsed(); used > 0) if (const size_t used = GSCodeReserve::GetInstance().GetMemoryUsed(); used > 0)
DevCon.WriteLn("SW JIT generated %zu bytes of code", used); DevCon.WriteLn("SW JIT generated %zu bytes of code", used);
GetVmMemory().GSCode().ForbidModification(); GSCodeReserve::GetInstance().ForbidModification();
} }
void GSDrawScanline::BeginDraw(const GSRasterizerData& data, GSScanlineLocalData& local) void GSDrawScanline::BeginDraw(const GSRasterizerData& data, GSScanlineLocalData& local)
@ -85,7 +85,7 @@ void GSDrawScanline::ResetCodeCache()
Console.Warning("GS Software JIT cache overflow, resetting."); Console.Warning("GS Software JIT cache overflow, resetting.");
m_sp_map.Clear(); m_sp_map.Clear();
m_ds_map.Clear(); m_ds_map.Clear();
GetVmMemory().GSCode().Reset(); GSCodeReserve::GetInstance().Reset();
} }
bool GSDrawScanline::SetupDraw(GSRasterizerData& data) bool GSDrawScanline::SetupDraw(GSRasterizerData& data)

View File

@ -47,7 +47,6 @@ BIOS
#include "SPU2/spu2.h" #include "SPU2/spu2.h"
#include "common/AlignedMalloc.h" #include "common/AlignedMalloc.h"
#include "common/PageFaultSource.h"
#include "GSDumpReplayer.h" #include "GSDumpReplayer.h"
@ -668,14 +667,6 @@ void memClearPageAddr(u32 vaddr)
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
// PS2 Memory Init / Reset / Shutdown // PS2 Memory Init / Reset / Shutdown
class mmap_PageFaultHandler : public EventListener_PageFault
{
public:
void OnPageFaultEvent( const PageFaultInfo& info, bool& handled );
};
static mmap_PageFaultHandler* mmap_faultHandler = NULL;
EEVM_MemoryAllocMess* eeMem = NULL; EEVM_MemoryAllocMess* eeMem = NULL;
alignas(__pagesize) u8 eeHw[Ps2MemSize::Hardware]; alignas(__pagesize) u8 eeHw[Ps2MemSize::Hardware];
@ -726,12 +717,6 @@ void eeMemoryReserve::Assign(VirtualMemoryManagerPtr allocator)
{ {
_parent::Assign(std::move(allocator), HostMemoryMap::EEmemOffset, sizeof(*eeMem)); _parent::Assign(std::move(allocator), HostMemoryMap::EEmemOffset, sizeof(*eeMem));
eeMem = reinterpret_cast<EEVM_MemoryAllocMess*>(GetPtr()); eeMem = reinterpret_cast<EEVM_MemoryAllocMess*>(GetPtr());
if (!mmap_faultHandler)
{
pxAssert(Source_PageFault);
mmap_faultHandler = new mmap_PageFaultHandler();
}
} }
@ -865,166 +850,6 @@ void eeMemoryReserve::Reset()
void eeMemoryReserve::Release() void eeMemoryReserve::Release()
{ {
safe_delete(mmap_faultHandler);
eeMem = nullptr; eeMem = nullptr;
_parent::Release(); _parent::Release();
} }
// ===========================================================================================
// Memory Protection and Block Checking, vtlb Style!
// ===========================================================================================
// For the first time code is recompiled (executed), the PS2 ram page for that code is
// protected using Virtual Memory (mprotect). If the game modifies its own code then this
// protection causes an *exception* to be raised (signal in Linux), which is handled by
// unprotecting the page and switching the recompiled block to "manual" protection.
//
// Manual protection uses a simple brute-force memcmp of the recompiled code to the code
// currently in RAM for *each time* the block is executed. Fool-proof, but slow, which
// is why we default to using the exception-based protection scheme described above.
//
// Why manual blocks? Because many games contain code and data in the same 4k page, so
// we *cannot* automatically recompile and reprotect pages, lest we end up recompiling and
// reprotecting them constantly (Which would be very slow). As a counter, the R5900 side
// of the block checking code does try to periodically re-protect blocks [going from manual
// back to protected], so that blocks which underwent a single invalidation don't need to
// incur a permanent performance penalty.
//
// Page Granularity:
// Fortunately for us MIPS and x86 use the same page granularity for TLB and memory
// protection, so we can use a 1:1 correspondence when protecting pages. Page granularity
// is 4096 (4k), which is why you'll see a lot of 0xfff's, >><< 12's, and 0x1000's in the
// code below.
//
struct vtlb_PageProtectionInfo
{
// Ram De-mapping -- used to convert fully translated/mapped offsets (which reside with
// in the eeMem->Main block) back into their originating ps2 physical ram address.
// Values are assigned when pages are marked for protection. since pages are automatically
// cleared and reset when TLB-remapped, stale values in this table (due to on-the-fly TLB
// changes) will be re-assigned the next time the page is accessed.
u32 ReverseRamMap;
vtlb_ProtectionMode Mode;
};
alignas(16) static vtlb_PageProtectionInfo m_PageProtectInfo[Ps2MemSize::MainRam >> __pageshift];
// returns:
// ProtMode_NotRequired - unchecked block (resides in ROM, thus is integrity is constant)
// Or the current mode
//
vtlb_ProtectionMode mmap_GetRamPageInfo( u32 paddr )
{
pxAssert( eeMem );
paddr &= ~0xfff;
uptr ptr = (uptr)PSM( paddr );
uptr rampage = ptr - (uptr)eeMem->Main;
if (!ptr || rampage >= Ps2MemSize::MainRam)
return ProtMode_NotRequired; //not in ram, no tracking done ...
rampage >>= __pageshift;
return m_PageProtectInfo[rampage].Mode;
}
// paddr - physically mapped PS2 address
void mmap_MarkCountedRamPage( u32 paddr )
{
pxAssert( eeMem );
paddr &= ~__pagemask;
uptr ptr = (uptr)PSM( paddr );
int rampage = (ptr - (uptr)eeMem->Main) >> __pageshift;
// Important: Update the ReverseRamMap here because TLB changes could alter the paddr
// mapping into eeMem->Main.
m_PageProtectInfo[rampage].ReverseRamMap = paddr;
if( m_PageProtectInfo[rampage].Mode == ProtMode_Write )
return; // skip town if we're already protected.
eeRecPerfLog.Write( (m_PageProtectInfo[rampage].Mode == ProtMode_Manual) ?
"Re-protecting page @ 0x%05x" : "Protected page @ 0x%05x",
paddr>>__pageshift
);
m_PageProtectInfo[rampage].Mode = ProtMode_Write;
HostSys::MemProtect( &eeMem->Main[rampage<<__pageshift], __pagesize, PageAccess_ReadOnly() );
vtlb_UpdateFastmemProtection(rampage << __pageshift, __pagesize, PageAccess_ReadOnly());
}
// offset - offset of address relative to psM.
// All recompiled blocks belonging to the page are cleared, and any new blocks recompiled
// from code residing in this page will use manual protection.
static __fi void mmap_ClearCpuBlock( uint offset )
{
pxAssert( eeMem );
int rampage = offset >> __pageshift;
// Assertion: This function should never be run on a block that's already under
// manual protection. Indicates a logic error in the recompiler or protection code.
pxAssertMsg( m_PageProtectInfo[rampage].Mode != ProtMode_Manual,
"Attempted to clear a block that is already under manual protection." );
HostSys::MemProtect( &eeMem->Main[rampage<<__pageshift], __pagesize, PageAccess_ReadWrite() );
vtlb_UpdateFastmemProtection(rampage << __pageshift, __pagesize, PageAccess_ReadWrite());
m_PageProtectInfo[rampage].Mode = ProtMode_Manual;
Cpu->Clear( m_PageProtectInfo[rampage].ReverseRamMap, __pagesize );
}
void mmap_PageFaultHandler::OnPageFaultEvent( const PageFaultInfo& info, bool& handled )
{
pxAssert( eeMem );
u32 vaddr;
if (CHECK_FASTMEM && vtlb_GetGuestAddress(info.addr, &vaddr))
{
// this was inside the fastmem area. check if it's a code page
// fprintf(stderr, "Fault on fastmem %p vaddr %08X\n", info.addr, vaddr);
uptr ptr = (uptr)PSM(vaddr);
uptr offset = (ptr - (uptr)eeMem->Main);
if (ptr && m_PageProtectInfo[offset >> __pageshift].Mode == ProtMode_Write)
{
// fprintf(stderr, "Not backpatching code write at %08X\n", vaddr);
mmap_ClearCpuBlock(offset);
handled = true;
}
else
{
// fprintf(stderr, "Trying backpatching vaddr %08X\n", vaddr);
if (vtlb_BackpatchLoadStore(info.pc, info.addr))
handled = true;
}
}
else
{
// get bad virtual address
uptr offset = info.addr - (uptr)eeMem->Main;
if (offset >= Ps2MemSize::MainRam)
return;
mmap_ClearCpuBlock(offset);
handled = true;
}
}
// Clears all block tracking statuses, manual protection flags, and write protection.
// This does not clear any recompiler blocks. It is assumed (and necessary) for the caller
// to ensure the EErec is also reset in conjunction with calling this function.
// (this function is called by default from the eerecReset).
void mmap_ResetBlockTracking()
{
//DbgCon.WriteLn( "vtlb/mmap: Block Tracking reset..." );
memzero( m_PageProtectInfo );
if (eeMem) HostSys::MemProtect( eeMem->Main, Ps2MemSize::MainRam, PageAccess_ReadWrite() );
vtlb_UpdateFastmemProtection(0, Ps2MemSize::MainRam, PageAccess_ReadWrite());
}

View File

@ -107,18 +107,6 @@ extern void memBindConditionalHandlers();
extern void memMapVUmicro(); extern void memMapVUmicro();
enum vtlb_ProtectionMode
{
ProtMode_None = 0, // page is 'unaccounted' -- neither protected nor unprotected
ProtMode_Write, // page is under write protection (exception handler)
ProtMode_Manual, // page is under manual protection (self-checked at execution)
ProtMode_NotRequired // page doesn't require any protection
};
extern vtlb_ProtectionMode mmap_GetRamPageInfo( u32 paddr );
extern void mmap_MarkCountedRamPage( u32 paddr );
extern void mmap_ResetBlockTracking();
#define memRead8 vtlb_memRead<mem8_t> #define memRead8 vtlb_memRead<mem8_t>
#define memRead16 vtlb_memRead<mem16_t> #define memRead16 vtlb_memRead<mem16_t>
#define memRead32 vtlb_memRead<mem32_t> #define memRead32 vtlb_memRead<mem32_t>

View File

@ -27,13 +27,18 @@
#include "common/Perf.h" #include "common/Perf.h"
#include "common/StringUtil.h" #include "common/StringUtil.h"
#include "CDVD/CDVD.h" #include "CDVD/CDVD.h"
#include "GS/Renderers/Common/GSFunctionMap.h"
#include "common/emitter/x86_intrin.h" #include "common/emitter/x86_intrin.h"
#include "GSDumpReplayer.h" #include "GSDumpReplayer.h"
#include "svnrev.h"
extern R5900cpu GSDumpReplayerCpu; extern R5900cpu GSDumpReplayerCpu;
Pcsx2Config EmuConfig;
SSE_MXCSR g_sseMXCSR = {DEFAULT_sseMXCSR}; SSE_MXCSR g_sseMXCSR = {DEFAULT_sseMXCSR};
SSE_MXCSR g_sseVU0MXCSR = {DEFAULT_sseVUMXCSR}; SSE_MXCSR g_sseVU0MXCSR = {DEFAULT_sseVUMXCSR};
SSE_MXCSR g_sseVU1MXCSR = {DEFAULT_sseVUMXCSR}; SSE_MXCSR g_sseVU1MXCSR = {DEFAULT_sseVUMXCSR};
@ -51,121 +56,6 @@ void SetCPUState(SSE_MXCSR sseMXCSR, SSE_MXCSR sseVU0MXCSR, SSE_MXCSR sseVU1MXCS
_mm_setcsr(g_sseMXCSR.bitmask); _mm_setcsr(g_sseMXCSR.bitmask);
} }
// --------------------------------------------------------------------------------------
// RecompiledCodeReserve (implementations)
// --------------------------------------------------------------------------------------
// Constructor!
// Parameters:
// name - a nice long name that accurately describes the contents of this reserve.
RecompiledCodeReserve::RecompiledCodeReserve(std::string name)
: VirtualMemoryReserve(std::move(name))
{
}
RecompiledCodeReserve::~RecompiledCodeReserve()
{
Release();
}
void RecompiledCodeReserve::_registerProfiler()
{
if (m_profiler_name.empty() || !IsOk())
return;
Perf::any.map((uptr)m_baseptr, m_size, m_profiler_name.c_str());
}
void RecompiledCodeReserve::Assign(VirtualMemoryManagerPtr allocator, size_t offset, size_t size)
{
// Anything passed to the memory allocator must be page aligned.
size = Common::PageAlign(size);
// Since the memory has already been allocated as part of the main memory map, this should never fail.
u8* base = allocator->Alloc(offset, size);
if (!base)
{
Console.WriteLn("(RecompiledCodeReserve) Failed to allocate %zu bytes for %s at offset %zu", size, m_name.c_str(), offset);
pxFailRel("RecompiledCodeReserve allocation failed.");
}
VirtualMemoryReserve::Assign(std::move(allocator), base, size);
_registerProfiler();
}
void RecompiledCodeReserve::Reset()
{
if (IsDevBuild && m_baseptr)
{
// Clear the recompiled code block to 0xcc (INT3) -- this helps disasm tools show
// the assembly dump more cleanly. We don't clear the block on Release builds since
// it can add a noticeable amount of overhead to large block recompilations.
std::memset(m_baseptr, 0xCC, m_size);
}
}
void RecompiledCodeReserve::AllowModification()
{
// Apple Silicon enforces write protection in hardware.
#if !defined(__APPLE__) || !defined(_M_ARM64)
HostSys::MemProtect(m_baseptr, m_size, PageAccess_Any());
#endif
}
void RecompiledCodeReserve::ForbidModification()
{
// Apple Silicon enforces write protection in hardware.
#if !defined(__APPLE__) || !defined(_M_ARM64)
HostSys::MemProtect(m_baseptr, m_size, PageProtectionMode().Read().Execute());
#endif
}
// Sets the abbreviated name used by the profiler. Name should be under 10 characters long.
// After a name has been set, a profiler source will be automatically registered and cleared
// in accordance with changes in the reserve area.
RecompiledCodeReserve& RecompiledCodeReserve::SetProfilerName(std::string name)
{
m_profiler_name = std::move(name);
_registerProfiler();
return *this;
}
GSCodeReserve::GSCodeReserve()
: RecompiledCodeReserve("GS Software Renderer")
{
}
GSCodeReserve::~GSCodeReserve() = default;
void GSCodeReserve::Assign(VirtualMemoryManagerPtr allocator)
{
RecompiledCodeReserve::Assign(std::move(allocator), HostMemoryMap::SWrecOffset, HostMemoryMap::SWrecSize);
}
void GSCodeReserve::Reset()
{
RecompiledCodeReserve::Reset();
m_memory_used = 0;
}
u8* GSCodeReserve::Reserve(size_t size)
{
pxAssert((m_memory_used + size) <= m_size);
return m_baseptr + m_memory_used;
}
void GSCodeReserve::Commit(size_t size)
{
pxAssert((m_memory_used + size) <= m_size);
m_memory_used += size;
}
#include "svnrev.h"
Pcsx2Config EmuConfig;
// This function should be called once during program execution. // This function should be called once during program execution.
void SysLogMachineCaps() void SysLogMachineCaps()
{ {
@ -322,7 +212,6 @@ SysMainMemory::~SysMainMemory()
bool SysMainMemory::Allocate() bool SysMainMemory::Allocate()
{ {
DevCon.WriteLn(Color_StrongBlue, "Allocating host memory for virtual systems..."); DevCon.WriteLn(Color_StrongBlue, "Allocating host memory for virtual systems...");
pxInstallSignalHandler();
ConsoleIndentScope indent(1); ConsoleIndentScope indent(1);
@ -363,8 +252,6 @@ void SysMainMemory::Release()
m_ee.Release(); m_ee.Release();
m_iop.Release(); m_iop.Release();
m_vu.Release(); m_vu.Release();
safe_delete(Source_PageFault);
} }
@ -388,12 +275,12 @@ SysCpuProviderPack::SysCpuProviderPack()
dVifReserve(1); dVifReserve(1);
} }
GetVmMemory().GSCode().Assign(GetVmMemory().CodeMemory()); GSCodeReserve::GetInstance().Assign(GetVmMemory().CodeMemory());
} }
SysCpuProviderPack::~SysCpuProviderPack() SysCpuProviderPack::~SysCpuProviderPack()
{ {
GetVmMemory().GSCode().Release(); GSCodeReserve::GetInstance().Release();
if (newVifDynaRec) if (newVifDynaRec)
{ {

View File

@ -19,11 +19,11 @@
#include "common/Exceptions.h" #include "common/Exceptions.h"
#include "common/SafeArray.h" #include "common/SafeArray.h"
#include "common/Threading.h" // to use threading stuff, include the Threading namespace in your file. #include "common/Threading.h"
#include "vtlb.h"
#include "Config.h" #include "Config.h"
#include "VirtualMemory.h"
#include "vtlb.h"
typedef SafeArray<u8> VmStateBuffer; typedef SafeArray<u8> VmStateBuffer;
@ -91,61 +91,6 @@ namespace HostMemoryMap
static const u32 SWrecSize = 0x04000000; static const u32 SWrecSize = 0x04000000;
} }
// --------------------------------------------------------------------------------------
// RecompiledCodeReserve
// --------------------------------------------------------------------------------------
// A recompiled code reserve is a simple sequential-growth block of memory which is auto-
// cleared to INT 3 (0xcc) as needed.
//
class RecompiledCodeReserve : public VirtualMemoryReserve
{
typedef VirtualMemoryReserve _parent;
protected:
std::string m_profiler_name;
public:
RecompiledCodeReserve(std::string name);
~RecompiledCodeReserve();
void Assign(VirtualMemoryManagerPtr allocator, size_t offset, size_t size);
void Reset();
RecompiledCodeReserve& SetProfilerName(std::string name);
void ForbidModification();
void AllowModification();
operator u8*() { return m_baseptr; }
operator const u8*() const { return m_baseptr; }
protected:
void _registerProfiler();
};
// --------------------------------------------------------------------------------------
// GSCodeReserve
// --------------------------------------------------------------------------------------
// Stores code buffers for the GS software JIT.
class GSCodeReserve : public RecompiledCodeReserve
{
public:
GSCodeReserve();
~GSCodeReserve();
size_t GetMemoryUsed() const { return m_memory_used; }
void Assign(VirtualMemoryManagerPtr allocator);
void Reset();
u8* Reserve(size_t size);
void Commit(size_t size);
private:
size_t m_memory_used = 0;
};
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// SysMainMemory // SysMainMemory
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
@ -162,8 +107,6 @@ protected:
iopMemoryReserve m_iop; iopMemoryReserve m_iop;
vuMemoryReserve m_vu; vuMemoryReserve m_vu;
GSCodeReserve m_gs_code;
public: public:
SysMainMemory(); SysMainMemory();
~SysMainMemory(); ~SysMainMemory();
@ -177,8 +120,6 @@ public:
const iopMemoryReserve& IOPMemory() const { return m_iop; } const iopMemoryReserve& IOPMemory() const { return m_iop; }
const vuMemoryReserve& VUMemory() const { return m_vu; } const vuMemoryReserve& VUMemory() const { return m_vu; }
GSCodeReserve& GSCode() { return m_gs_code; }
bool Allocate(); bool Allocate();
void Reset(); void Reset();
void Release(); void Release();

View File

@ -13,62 +13,18 @@
* If not, see <http://www.gnu.org/licenses/>. * If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "PrecompiledHeader.h"
#include "VirtualMemory.h"
#include "common/Align.h" #include "common/Align.h"
#include "common/PageFaultSource.h"
#include "common/EventSource.inl"
#include "common/MemsetFast.inl"
#include "common/Console.h" #include "common/Console.h"
#include "common/Perf.h"
#include "fmt/core.h" #include "fmt/core.h"
#include <cinttypes> #include <cinttypes>
template class EventSource<IEventListener_PageFault>;
SrcType_PageFault* Source_PageFault = NULL;
std::mutex PageFault_Mutex;
void pxInstallSignalHandler()
{
if (!Source_PageFault)
{
Source_PageFault = new SrcType_PageFault();
}
_platform_InstallSignalHandler();
// NOP on Win32 systems -- we use __try{} __except{} instead.
}
// --------------------------------------------------------------------------------------
// EventListener_PageFault (implementations)
// --------------------------------------------------------------------------------------
EventListener_PageFault::EventListener_PageFault()
{
pxAssert(Source_PageFault);
Source_PageFault->Add(*this);
}
EventListener_PageFault::~EventListener_PageFault()
{
if (Source_PageFault)
Source_PageFault->Remove(*this);
}
void SrcType_PageFault::Dispatch(const PageFaultInfo& params)
{
m_handled = false;
_parent::Dispatch(params);
}
void SrcType_PageFault::_DispatchRaw(ListenerIterator iter, const ListenerIterator& iend, const PageFaultInfo& evt)
{
do
{
(*iter)->DispatchEvent(evt, m_handled);
} while ((++iter != iend) && !m_handled);
}
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// VirtualMemoryManager (implementations) // VirtualMemoryManager (implementations)
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
@ -335,23 +291,75 @@ void VirtualMemoryReserve::Release()
} }
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// PageProtectionMode (implementations) // RecompiledCodeReserve (implementations)
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
std::string PageProtectionMode::ToString() const
// Constructor!
// Parameters:
// name - a nice long name that accurately describes the contents of this reserve.
RecompiledCodeReserve::RecompiledCodeReserve(std::string name)
: VirtualMemoryReserve(std::move(name))
{ {
std::string modeStr; }
if (m_read) RecompiledCodeReserve::~RecompiledCodeReserve()
modeStr += "Read"; {
if (m_write) Release();
modeStr += "Write"; }
if (m_exec)
modeStr += "Exec"; void RecompiledCodeReserve::_registerProfiler()
{
if (modeStr.empty()) if (m_profiler_name.empty() || !IsOk())
return "NoAccess"; return;
if (modeStr.length() <= 5)
modeStr += "Only"; Perf::any.map((uptr)m_baseptr, m_size, m_profiler_name.c_str());
}
return modeStr;
void RecompiledCodeReserve::Assign(VirtualMemoryManagerPtr allocator, size_t offset, size_t size)
{
// Anything passed to the memory allocator must be page aligned.
size = Common::PageAlign(size);
// Since the memory has already been allocated as part of the main memory map, this should never fail.
u8* base = allocator->Alloc(offset, size);
if (!base)
{
Console.WriteLn("(RecompiledCodeReserve) Failed to allocate %zu bytes for %s at offset %zu", size, m_name.c_str(), offset);
pxFailRel("RecompiledCodeReserve allocation failed.");
}
VirtualMemoryReserve::Assign(std::move(allocator), base, size);
_registerProfiler();
}
void RecompiledCodeReserve::Reset()
{
if (IsDevBuild && m_baseptr)
{
// Clear the recompiled code block to 0xcc (INT3) -- this helps disasm tools show
// the assembly dump more cleanly. We don't clear the block on Release builds since
// it can add a noticeable amount of overhead to large block recompilations.
std::memset(m_baseptr, 0xCC, m_size);
}
}
void RecompiledCodeReserve::AllowModification()
{
HostSys::MemProtect(m_baseptr, m_size, PageAccess_Any());
}
void RecompiledCodeReserve::ForbidModification()
{
HostSys::MemProtect(m_baseptr, m_size, PageProtectionMode().Read().Execute());
}
// Sets the abbreviated name used by the profiler. Name should be under 10 characters long.
// After a name has been set, a profiler source will be automatically registered and cleared
// in accordance with changes in the reserve area.
RecompiledCodeReserve& RecompiledCodeReserve::SetProfilerName(std::string name)
{
m_profiler_name = std::move(name);
_registerProfiler();
return *this;
} }

View File

@ -13,123 +13,15 @@
* If not, see <http://www.gnu.org/licenses/>. * If not, see <http://www.gnu.org/licenses/>.
*/ */
// [TODO] Rename this file to VirtualMemory.h !!
#pragma once #pragma once
// ===================================================================================================== #include "common/General.h"
// Cross-Platform Memory Protection (Used by VTLB, Recompilers and Texture caches) #include "common/Assertions.h"
// =====================================================================================================
// Win32 platforms use the SEH model: __try {} __except {}
// Linux platforms use the POSIX Signals model: sigaction()
// [TODO] OS-X (Darwin) platforms should use the Mach exception model (not implemented)
#include "EventSource.h"
#include "General.h"
#include "Assertions.h"
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
struct PageFaultInfo
{
uptr pc;
uptr addr;
PageFaultInfo(uptr pc_, uptr address)
{
pc = pc_;
addr = address;
}
};
// --------------------------------------------------------------------------------------
// IEventListener_PageFault
// --------------------------------------------------------------------------------------
class IEventListener_PageFault : public IEventDispatcher<PageFaultInfo>
{
public:
typedef PageFaultInfo EvtParams;
public:
virtual ~IEventListener_PageFault() = default;
virtual void DispatchEvent(const PageFaultInfo& evtinfo, bool& handled)
{
OnPageFaultEvent(evtinfo, handled);
}
virtual void DispatchEvent(const PageFaultInfo& evtinfo)
{
pxFailRel("Don't call me, damnit. Use DispatchException instead.");
}
virtual void OnPageFaultEvent(const PageFaultInfo& evtinfo, bool& handled) {}
};
// --------------------------------------------------------------------------------------
// EventListener_PageFault / EventListenerHelper_PageFault
// --------------------------------------------------------------------------------------
class EventListener_PageFault : public IEventListener_PageFault
{
public:
EventListener_PageFault();
virtual ~EventListener_PageFault();
};
template <typename TypeToDispatchTo>
class EventListenerHelper_PageFault : public EventListener_PageFault
{
public:
TypeToDispatchTo* Owner;
public:
EventListenerHelper_PageFault(TypeToDispatchTo& dispatchTo)
{
Owner = &dispatchTo;
}
EventListenerHelper_PageFault(TypeToDispatchTo* dispatchTo)
{
Owner = dispatchTo;
}
virtual ~EventListenerHelper_PageFault() = default;
protected:
virtual void OnPageFaultEvent(const PageFaultInfo& info, bool& handled)
{
Owner->OnPageFaultEvent(info, handled);
}
};
// --------------------------------------------------------------------------------------
// SrcType_PageFault
// --------------------------------------------------------------------------------------
class SrcType_PageFault : public EventSource<IEventListener_PageFault>
{
protected:
typedef EventSource<IEventListener_PageFault> _parent;
protected:
bool m_handled;
public:
SrcType_PageFault()
: m_handled(false)
{
}
virtual ~SrcType_PageFault() = default;
bool WasHandled() const { return m_handled; }
virtual void Dispatch(const PageFaultInfo& params);
protected:
virtual void _DispatchRaw(ListenerIterator iter, const ListenerIterator& iend, const PageFaultInfo& evt);
};
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// VirtualMemoryManager: Manages the allocation of PCSX2 VM // VirtualMemoryManager: Manages the allocation of PCSX2 VM
// Ensures that all memory is close enough together for rip-relative addressing // Ensures that all memory is close enough together for rip-relative addressing
@ -252,26 +144,34 @@ public:
} }
}; };
#ifdef __POSIX__ // --------------------------------------------------------------------------------------
// RecompiledCodeReserve
// --------------------------------------------------------------------------------------
// A recompiled code reserve is a simple sequential-growth block of memory which is auto-
// cleared to INT 3 (0xcc) as needed.
//
class RecompiledCodeReserve : public VirtualMemoryReserve
{
typedef VirtualMemoryReserve _parent;
#define PCSX2_PAGEFAULT_PROTECT protected:
#define PCSX2_PAGEFAULT_EXCEPT std::string m_profiler_name;
#elif defined(_WIN32) public:
RecompiledCodeReserve(std::string name);
~RecompiledCodeReserve();
struct _EXCEPTION_POINTERS; void Assign(VirtualMemoryManagerPtr allocator, size_t offset, size_t size);
extern long __stdcall SysPageFaultExceptionFilter(struct _EXCEPTION_POINTERS* eps); void Reset();
#define PCSX2_PAGEFAULT_PROTECT __try RecompiledCodeReserve& SetProfilerName(std::string name);
#define PCSX2_PAGEFAULT_EXCEPT \
__except (SysPageFaultExceptionFilter(GetExceptionInformation())) {}
#else void ForbidModification();
#error PCSX2 - Unsupported operating system platform. void AllowModification();
#endif
extern void pxInstallSignalHandler(); operator u8*() { return m_baseptr; }
extern void _platform_InstallSignalHandler(); operator const u8*() const { return m_baseptr; }
extern SrcType_PageFault* Source_PageFault; protected:
extern std::mutex PageFault_Mutex; void _registerProfiler();
};

View File

@ -352,6 +352,7 @@
<ClCompile Include="USB\usb-pad\usb-seamic.cpp" /> <ClCompile Include="USB\usb-pad\usb-seamic.cpp" />
<ClCompile Include="USB\usb-printer\usb-printer.cpp" /> <ClCompile Include="USB\usb-printer\usb-printer.cpp" />
<ClCompile Include="USB\USB.cpp" /> <ClCompile Include="USB\USB.cpp" />
<ClCompile Include="VirtualMemory.cpp" />
<ClCompile Include="VMManager.cpp" /> <ClCompile Include="VMManager.cpp" />
<ClCompile Include="windows\Optimus.cpp" /> <ClCompile Include="windows\Optimus.cpp" />
<ClCompile Include="Pcsx2Config.cpp" /> <ClCompile Include="Pcsx2Config.cpp" />
@ -711,7 +712,6 @@
<ClInclude Include="SaveState.h" /> <ClInclude Include="SaveState.h" />
<ClInclude Include="SingleRegisterTypes.h" /> <ClInclude Include="SingleRegisterTypes.h" />
<ClInclude Include="System.h" /> <ClInclude Include="System.h" />
<ClInclude Include="System\SysThreads.h" />
<ClInclude Include="Counters.h" /> <ClInclude Include="Counters.h" />
<ClInclude Include="Dmac.h" /> <ClInclude Include="Dmac.h" />
<ClInclude Include="Hardware.h" /> <ClInclude Include="Hardware.h" />
@ -719,6 +719,7 @@
<ClInclude Include="ps2\HwInternal.h" /> <ClInclude Include="ps2\HwInternal.h" />
<ClInclude Include="Cache.h" /> <ClInclude Include="Cache.h" />
<ClInclude Include="Memory.h" /> <ClInclude Include="Memory.h" />
<ClInclude Include="VirtualMemory.h" />
<ClInclude Include="VMManager.h" /> <ClInclude Include="VMManager.h" />
<ClInclude Include="vtlb.h" /> <ClInclude Include="vtlb.h" />
<ClInclude Include="MTVU.h" /> <ClInclude Include="MTVU.h" />

View File

@ -1385,6 +1385,9 @@
<ClCompile Include="GSDumpReplayer.cpp"> <ClCompile Include="GSDumpReplayer.cpp">
<Filter>Tools</Filter> <Filter>Tools</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="VirtualMemory.cpp">
<Filter>System</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="Patch.h"> <ClInclude Include="Patch.h">
@ -1420,9 +1423,6 @@
<ClInclude Include="System.h"> <ClInclude Include="System.h">
<Filter>System\Include</Filter> <Filter>System\Include</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="System\SysThreads.h">
<Filter>System\Include</Filter>
</ClInclude>
<ClInclude Include="Counters.h"> <ClInclude Include="Counters.h">
<Filter>System\Ps2\EmotionEngine</Filter> <Filter>System\Ps2\EmotionEngine</Filter>
</ClInclude> </ClInclude>
@ -2318,6 +2318,9 @@
<ClInclude Include="GS.h"> <ClInclude Include="GS.h">
<Filter>System\Ps2\GS\GIF</Filter> <Filter>System\Ps2\GS\GIF</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="VirtualMemory.h">
<Filter>System</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<CustomBuildStep Include="rdebug\deci2.h"> <CustomBuildStep Include="rdebug\deci2.h">

View File

@ -36,6 +36,7 @@
#include "Cache.h" #include "Cache.h"
#include "R5900Exceptions.h" #include "R5900Exceptions.h"
#include "IopMem.h" #include "IopMem.h"
#include "Host.h"
#include "common/Align.h" #include "common/Align.h"
#include "common/MemsetFast.inl" #include "common/MemsetFast.inl"
@ -57,7 +58,9 @@ using namespace vtlb_private;
namespace vtlb_private namespace vtlb_private
{ {
alignas(64) MapData vtlbdata; alignas(64) MapData vtlbdata;
}
static bool PageFaultHandler(const PageFaultInfo& info);
} // namespace vtlb_private
static vtlbHandler vtlbHandlerCount = 0; static vtlbHandler vtlbHandlerCount = 0;
@ -95,22 +98,28 @@ static std::unordered_multimap<u32, u32> s_fastmem_physical_mapping; // maps mai
static std::unordered_map<uptr, LoadstoreBackpatchInfo> s_fastmem_backpatch_info; static std::unordered_map<uptr, LoadstoreBackpatchInfo> s_fastmem_backpatch_info;
static std::unordered_set<u32> s_fastmem_faulting_pcs; static std::unordered_set<u32> s_fastmem_faulting_pcs;
vtlb_private::VTLBPhysical vtlb_private::VTLBPhysical::fromPointer(sptr ptr) { vtlb_private::VTLBPhysical vtlb_private::VTLBPhysical::fromPointer(sptr ptr)
{
pxAssertMsg(ptr >= 0, "Address too high"); pxAssertMsg(ptr >= 0, "Address too high");
return VTLBPhysical(ptr); return VTLBPhysical(ptr);
} }
vtlb_private::VTLBPhysical vtlb_private::VTLBPhysical::fromHandler(vtlbHandler handler) { vtlb_private::VTLBPhysical vtlb_private::VTLBPhysical::fromHandler(vtlbHandler handler)
{
return VTLBPhysical(handler | POINTER_SIGN_BIT); return VTLBPhysical(handler | POINTER_SIGN_BIT);
} }
vtlb_private::VTLBVirtual::VTLBVirtual(VTLBPhysical phys, u32 paddr, u32 vaddr) { vtlb_private::VTLBVirtual::VTLBVirtual(VTLBPhysical phys, u32 paddr, u32 vaddr)
{
pxAssertMsg(0 == (paddr & VTLB_PAGE_MASK), "Should be page aligned"); pxAssertMsg(0 == (paddr & VTLB_PAGE_MASK), "Should be page aligned");
pxAssertMsg(0 == (vaddr & VTLB_PAGE_MASK), "Should be page aligned"); pxAssertMsg(0 == (vaddr & VTLB_PAGE_MASK), "Should be page aligned");
pxAssertMsg((uptr)paddr < POINTER_SIGN_BIT, "Address too high"); pxAssertMsg((uptr)paddr < POINTER_SIGN_BIT, "Address too high");
if (phys.isHandler()) { if (phys.isHandler())
{
value = phys.raw() + paddr - vaddr; value = phys.raw() + paddr - vaddr;
} else { }
else
{
value = phys.raw() - vaddr; value = phys.raw() - vaddr;
} }
} }
@ -119,26 +128,30 @@ __inline int CheckCache(u32 addr)
{ {
u32 mask; u32 mask;
if(((cpuRegs.CP0.n.Config >> 16) & 0x1) == 0) if (((cpuRegs.CP0.n.Config >> 16) & 0x1) == 0)
{ {
//DevCon.Warning("Data Cache Disabled! %x", cpuRegs.CP0.n.Config); //DevCon.Warning("Data Cache Disabled! %x", cpuRegs.CP0.n.Config);
return false;// return false; //
} }
for(int i = 1; i < 48; i++) for (int i = 1; i < 48; i++)
{
if (((tlb[i].EntryLo1 & 0x38) >> 3) == 0x3)
{ {
if (((tlb[i].EntryLo1 & 0x38) >> 3) == 0x3) {
mask = tlb[i].PageMask; mask = tlb[i].PageMask;
if ((addr >= tlb[i].PFN1) && (addr <= tlb[i].PFN1 + mask)) { if ((addr >= tlb[i].PFN1) && (addr <= tlb[i].PFN1 + mask))
{
//DevCon.Warning("Yay! Cache check cache addr=%x, mask=%x, addr+mask=%x, VPN2=%x PFN0=%x", addr, mask, (addr & mask), tlb[i].VPN2, tlb[i].PFN0); //DevCon.Warning("Yay! Cache check cache addr=%x, mask=%x, addr+mask=%x, VPN2=%x PFN0=%x", addr, mask, (addr & mask), tlb[i].VPN2, tlb[i].PFN0);
return true; return true;
} }
} }
if (((tlb[i].EntryLo0 & 0x38) >> 3) == 0x3) { if (((tlb[i].EntryLo0 & 0x38) >> 3) == 0x3)
{
mask = tlb[i].PageMask; mask = tlb[i].PageMask;
if ((addr >= tlb[i].PFN0) && (addr <= tlb[i].PFN0 + mask)) { if ((addr >= tlb[i].PFN0) && (addr <= tlb[i].PFN0 + mask))
{
//DevCon.Warning("Yay! Cache check cache addr=%x, mask=%x, addr+mask=%x, VPN2=%x PFN0=%x", addr, mask, (addr & mask), tlb[i].VPN2, tlb[i].PFN0); //DevCon.Warning("Yay! Cache check cache addr=%x, mask=%x, addr+mask=%x, VPN2=%x PFN0=%x", addr, mask, (addr & mask), tlb[i].VPN2, tlb[i].PFN0);
return true; return true;
} }
@ -151,19 +164,19 @@ __inline int CheckCache(u32 addr)
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// See recVTLB.cpp for the dynarec versions. // See recVTLB.cpp for the dynarec versions.
template< typename DataType > template <typename DataType>
DataType vtlb_memRead(u32 addr) DataType vtlb_memRead(u32 addr)
{ {
static const uint DataSize = sizeof(DataType) * 8; static const uint DataSize = sizeof(DataType) * 8;
auto vmv = vtlbdata.vmap[addr>>VTLB_PAGE_BITS]; auto vmv = vtlbdata.vmap[addr >> VTLB_PAGE_BITS];
if (!vmv.isHandler(addr)) if (!vmv.isHandler(addr))
{ {
if (!CHECK_EEREC) if (!CHECK_EEREC)
{ {
if(CHECK_CACHE && CheckCache(addr)) if (CHECK_CACHE && CheckCache(addr))
{ {
switch( DataSize ) switch (DataSize)
{ {
case 8: case 8:
return readCache8(addr); return readCache8(addr);
@ -187,14 +200,14 @@ DataType vtlb_memRead(u32 addr)
} }
//has to: translate, find function, call function //has to: translate, find function, call function
u32 paddr=vmv.assumeHandlerGetPAddr(addr); u32 paddr = vmv.assumeHandlerGetPAddr(addr);
//Console.WriteLn("Translated 0x%08X to 0x%08X", addr,paddr); //Console.WriteLn("Translated 0x%08X to 0x%08X", addr,paddr);
//return reinterpret_cast<TemplateHelper<DataSize,false>::HandlerType*>(vtlbdata.RWFT[TemplateHelper<DataSize,false>::sidx][0][hand])(paddr,data); //return reinterpret_cast<TemplateHelper<DataSize,false>::HandlerType*>(vtlbdata.RWFT[TemplateHelper<DataSize,false>::sidx][0][hand])(paddr,data);
switch( DataSize ) switch (DataSize)
{ {
case 8: case 8:
return vmv.assumeHandler< 8, false>()(paddr); return vmv.assumeHandler<8, false>()(paddr);
case 16: case 16:
return vmv.assumeHandler<16, false>()(paddr); return vmv.assumeHandler<16, false>()(paddr);
case 32: case 32:
@ -210,13 +223,13 @@ DataType vtlb_memRead(u32 addr)
RETURNS_R128 vtlb_memRead128(u32 mem) RETURNS_R128 vtlb_memRead128(u32 mem)
{ {
auto vmv = vtlbdata.vmap[mem>>VTLB_PAGE_BITS]; auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (!vmv.isHandler(mem)) if (!vmv.isHandler(mem))
{ {
if (!CHECK_EEREC) if (!CHECK_EEREC)
{ {
if(CHECK_CACHE && CheckCache(mem)) if (CHECK_CACHE && CheckCache(mem))
{ {
return readCache128(mem); return readCache128(mem);
} }
@ -233,20 +246,20 @@ RETURNS_R128 vtlb_memRead128(u32 mem)
} }
} }
template< typename DataType > template <typename DataType>
void vtlb_memWrite(u32 addr, DataType data) void vtlb_memWrite(u32 addr, DataType data)
{ {
static const uint DataSize = sizeof(DataType) * 8; static const uint DataSize = sizeof(DataType) * 8;
auto vmv = vtlbdata.vmap[addr>>VTLB_PAGE_BITS]; auto vmv = vtlbdata.vmap[addr >> VTLB_PAGE_BITS];
if (!vmv.isHandler(addr)) if (!vmv.isHandler(addr))
{ {
if (!CHECK_EEREC) if (!CHECK_EEREC)
{ {
if(CHECK_CACHE && CheckCache(addr)) if (CHECK_CACHE && CheckCache(addr))
{ {
switch( DataSize ) switch (DataSize)
{ {
case 8: case 8:
writeCache8(addr, data); writeCache8(addr, data);
@ -264,26 +277,26 @@ void vtlb_memWrite(u32 addr, DataType data)
} }
} }
*reinterpret_cast<DataType*>(vmv.assumePtr(addr))=data; *reinterpret_cast<DataType*>(vmv.assumePtr(addr)) = data;
} }
else else
{ {
//has to: translate, find function, call function //has to: translate, find function, call function
u32 paddr = vmv.assumeHandlerGetPAddr(addr); u32 paddr = vmv.assumeHandlerGetPAddr(addr);
//Console.WriteLn("Translated 0x%08X to 0x%08X", addr,paddr); //Console.WriteLn("Translated 0x%08X to 0x%08X", addr,paddr);
return vmv.assumeHandler<sizeof(DataType)*8, true>()(paddr, data); return vmv.assumeHandler<sizeof(DataType) * 8, true>()(paddr, data);
} }
} }
void TAKES_R128 vtlb_memWrite128(u32 mem, r128 value) void TAKES_R128 vtlb_memWrite128(u32 mem, r128 value)
{ {
auto vmv = vtlbdata.vmap[mem>>VTLB_PAGE_BITS]; auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (!vmv.isHandler(mem)) if (!vmv.isHandler(mem))
{ {
if (!CHECK_EEREC) if (!CHECK_EEREC)
{ {
if(CHECK_CACHE && CheckCache(mem)) if (CHECK_CACHE && CheckCache(mem))
{ {
alignas(16) const u128 r = r128_to_u128(value); alignas(16) const u128 r = r128_to_u128(value);
writeCache128(mem, &r); writeCache128(mem, &r);
@ -366,7 +379,8 @@ static void GoemonTlbMissDebug()
// 0x3d5580 is the address of the TLB cache // 0x3d5580 is the address of the TLB cache
GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580]; GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580];
for (u32 i = 0; i < 150; i++) { for (u32 i = 0; i < 150; i++)
{
if (tlb[i].valid == 0x1 && tlb[i].low_add != tlb[i].high_add) if (tlb[i].valid == 0x1 && tlb[i].low_add != tlb[i].high_add)
DevCon.WriteLn("GoemonTlbMissDebug: Entry %d is valid. Key %x. From V:0x%8.8x to V:0x%8.8x (P:0x%8.8x)", i, tlb[i].key, tlb[i].low_add, tlb[i].high_add, tlb[i].physical_add); DevCon.WriteLn("GoemonTlbMissDebug: Entry %d is valid. Key %x. From V:0x%8.8x to V:0x%8.8x (P:0x%8.8x)", i, tlb[i].key, tlb[i].low_add, tlb[i].high_add, tlb[i].physical_add);
else if (tlb[i].low_add != tlb[i].high_add) else if (tlb[i].low_add != tlb[i].high_add)
@ -379,8 +393,10 @@ void GoemonPreloadTlb()
// 0x3d5580 is the address of the TLB cache table // 0x3d5580 is the address of the TLB cache table
GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580]; GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580];
for (u32 i = 0; i < 150; i++) { for (u32 i = 0; i < 150; i++)
if (tlb[i].valid == 0x1 && tlb[i].low_add != tlb[i].high_add) { {
if (tlb[i].valid == 0x1 && tlb[i].low_add != tlb[i].high_add)
{
u32 size = tlb[i].high_add - tlb[i].low_add; u32 size = tlb[i].high_add - tlb[i].low_add;
u32 vaddr = tlb[i].low_add; u32 vaddr = tlb[i].low_add;
@ -388,11 +404,12 @@ void GoemonPreloadTlb()
// TODO: The old code (commented below) seems to check specifically for handler 0. Is this really correct? // TODO: The old code (commented below) seems to check specifically for handler 0. Is this really correct?
//if ((uptr)vtlbdata.vmap[vaddr>>VTLB_PAGE_BITS] == POINTER_SIGN_BIT) { //if ((uptr)vtlbdata.vmap[vaddr>>VTLB_PAGE_BITS] == POINTER_SIGN_BIT) {
auto vmv = vtlbdata.vmap[vaddr>>VTLB_PAGE_BITS]; auto vmv = vtlbdata.vmap[vaddr >> VTLB_PAGE_BITS];
if (vmv.isHandler(vaddr) && vmv.assumeHandlerGetID() == 0) { if (vmv.isHandler(vaddr) && vmv.assumeHandlerGetID() == 0)
{
DevCon.WriteLn("GoemonPreloadTlb: Entry %d. Key %x. From V:0x%8.8x to P:0x%8.8x (%d pages)", i, tlb[i].key, vaddr, paddr, size >> VTLB_PAGE_BITS); DevCon.WriteLn("GoemonPreloadTlb: Entry %d. Key %x. From V:0x%8.8x to P:0x%8.8x (%d pages)", i, tlb[i].key, vaddr, paddr, size >> VTLB_PAGE_BITS);
vtlb_VMap( vaddr , paddr, size); vtlb_VMap(vaddr, paddr, size);
vtlb_VMap(0x20000000|vaddr , paddr, size); vtlb_VMap(0x20000000 | vaddr, paddr, size);
} }
} }
} }
@ -402,15 +419,18 @@ void GoemonUnloadTlb(u32 key)
{ {
// 0x3d5580 is the address of the TLB cache table // 0x3d5580 is the address of the TLB cache table
GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580]; GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580];
for (u32 i = 0; i < 150; i++) { for (u32 i = 0; i < 150; i++)
if (tlb[i].key == key) { {
if (tlb[i].valid == 0x1) { if (tlb[i].key == key)
{
if (tlb[i].valid == 0x1)
{
u32 size = tlb[i].high_add - tlb[i].low_add; u32 size = tlb[i].high_add - tlb[i].low_add;
u32 vaddr = tlb[i].low_add; u32 vaddr = tlb[i].low_add;
DevCon.WriteLn("GoemonUnloadTlb: Entry %d. Key %x. From V:0x%8.8x to V:0x%8.8x (%d pages)", i, tlb[i].key, vaddr, vaddr+size, size >> VTLB_PAGE_BITS); DevCon.WriteLn("GoemonUnloadTlb: Entry %d. Key %x. From V:0x%8.8x to V:0x%8.8x (%d pages)", i, tlb[i].key, vaddr, vaddr + size, size >> VTLB_PAGE_BITS);
vtlb_VMapUnmap( vaddr , size); vtlb_VMapUnmap(vaddr, size);
vtlb_VMapUnmap(0x20000000|vaddr , size); vtlb_VMapUnmap(0x20000000 | vaddr, size);
// Unmap the tlb in game cache table // Unmap the tlb in game cache table
// Note: Game copy FEFEFEFE for others data // Note: Game copy FEFEFEFE for others data
@ -418,7 +438,9 @@ void GoemonUnloadTlb(u32 key)
tlb[i].key = 0xFEFEFEFE; tlb[i].key = 0xFEFEFEFE;
tlb[i].low_add = 0xFEFEFEFE; tlb[i].low_add = 0xFEFEFEFE;
tlb[i].high_add = 0xFEFEFEFE; tlb[i].high_add = 0xFEFEFEFE;
} else { }
else
{
DevCon.Error("GoemonUnloadTlb: Entry %d is not valid. Key %x", i, tlb[i].key); DevCon.Error("GoemonUnloadTlb: Entry %d is not valid. Key %x", i, tlb[i].key);
} }
} }
@ -426,13 +448,14 @@ void GoemonUnloadTlb(u32 key)
} }
// Generates a tlbMiss Exception // Generates a tlbMiss Exception
static __ri void vtlb_Miss(u32 addr,u32 mode) static __ri void vtlb_Miss(u32 addr, u32 mode)
{ {
if (EmuConfig.Gamefixes.GoemonTlbHack) if (EmuConfig.Gamefixes.GoemonTlbHack)
GoemonTlbMissDebug(); GoemonTlbMissDebug();
// Hack to handle expected tlb miss by some games. // Hack to handle expected tlb miss by some games.
if (Cpu == &intCpu) { if (Cpu == &intCpu)
{
if (mode) if (mode)
cpuTlbMissW(addr, cpuRegs.branch); cpuTlbMissW(addr, cpuRegs.branch);
else else
@ -442,33 +465,34 @@ static __ri void vtlb_Miss(u32 addr,u32 mode)
throw Exception::CancelInstruction(); throw Exception::CancelInstruction();
} }
if( IsDevBuild ) if (IsDevBuild)
Cpu->ThrowCpuException( R5900Exception::TLBMiss( addr, !!mode ) ); Cpu->ThrowCpuException(R5900Exception::TLBMiss(addr, !!mode));
else else
{ {
static int spamStop = 0; static int spamStop = 0;
if ( spamStop++ < 50 ) if (spamStop++ < 50)
Console.Error( R5900Exception::TLBMiss( addr, !!mode ).FormatMessage() ); Console.Error(R5900Exception::TLBMiss(addr, !!mode).FormatMessage());
} }
} }
// BusError exception: more serious than a TLB miss. If properly emulated the PS2 kernel // BusError exception: more serious than a TLB miss. If properly emulated the PS2 kernel
// itself would invoke a diagnostic/assertion screen that displays the cpu state at the // itself would invoke a diagnostic/assertion screen that displays the cpu state at the
// time of the exception. // time of the exception.
static __ri void vtlb_BusError(u32 addr,u32 mode) static __ri void vtlb_BusError(u32 addr, u32 mode)
{ {
// The exception terminate the program on linux which is very annoying // The exception terminate the program on linux which is very annoying
// Just disable it for the moment // Just disable it for the moment
#ifdef __linux__ #ifdef __linux__
if (0) if (0)
#else #else
if( IsDevBuild ) if (IsDevBuild)
#endif #endif
Cpu->ThrowCpuException( R5900Exception::BusError( addr, !!mode ) ); Cpu->ThrowCpuException(R5900Exception::BusError(addr, !!mode));
else else
Console.Error( R5900Exception::TLBMiss( addr, !!mode ).FormatMessage() ); Console.Error(R5900Exception::TLBMiss(addr, !!mode).FormatMessage());
} }
// clang-format off
template <typename OperandType> template <typename OperandType>
static OperandType vtlbUnmappedVReadSm(u32 addr) { vtlb_Miss(addr, 0); return 0; } static OperandType vtlbUnmappedVReadSm(u32 addr) { vtlb_Miss(addr, 0); return 0; }
static RETURNS_R128 vtlbUnmappedVReadLg(u32 addr) { vtlb_Miss(addr, 0); return r128_zero(); } static RETURNS_R128 vtlbUnmappedVReadLg(u32 addr) { vtlb_Miss(addr, 0); return r128_zero(); }
@ -484,6 +508,7 @@ static RETURNS_R128 vtlbUnmappedPReadLg(u32 addr) { vtlb_BusError(addr, 0); retu
template <typename OperandType> template <typename OperandType>
static void vtlbUnmappedPWriteSm(u32 addr, OperandType data) { vtlb_BusError(addr, 1); } static void vtlbUnmappedPWriteSm(u32 addr, OperandType data) { vtlb_BusError(addr, 1); }
static void TAKES_R128 vtlbUnmappedPWriteLg(u32 addr, r128 data) { vtlb_BusError(addr, 1); } static void TAKES_R128 vtlbUnmappedPWriteLg(u32 addr, r128 data) { vtlb_BusError(addr, 1); }
// clang-format on
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// VTLB mapping errors // VTLB mapping errors
@ -537,12 +562,12 @@ static void vtlbDefaultPhyWrite32(u32 addr, mem32_t data)
pxFailDev(fmt::format("(VTLB) Attempted write32 to unmapped physical address @ 0x{:08X}.", addr).c_str()); pxFailDev(fmt::format("(VTLB) Attempted write32 to unmapped physical address @ 0x{:08X}.", addr).c_str());
} }
static void vtlbDefaultPhyWrite64(u32 addr,mem64_t data) static void vtlbDefaultPhyWrite64(u32 addr, mem64_t data)
{ {
pxFailDev(fmt::format("(VTLB) Attempted write64 to unmapped physical address @ 0x{:08X}.", addr).c_str()); pxFailDev(fmt::format("(VTLB) Attempted write64 to unmapped physical address @ 0x{:08X}.", addr).c_str());
} }
static void TAKES_R128 vtlbDefaultPhyWrite128(u32 addr,r128 data) static void TAKES_R128 vtlbDefaultPhyWrite128(u32 addr, r128 data)
{ {
pxFailDev(fmt::format("(VTLB) Attempted write128 to unmapped physical address @ 0x{:08X}.", addr).c_str()); pxFailDev(fmt::format("(VTLB) Attempted write128 to unmapped physical address @ 0x{:08X}.", addr).c_str());
} }
@ -559,28 +584,28 @@ static void TAKES_R128 vtlbDefaultPhyWrite128(u32 addr,r128 data)
// //
// Note: All handlers persist across calls to vtlb_Reset(), but are wiped/invalidated by calls to vtlb_Init() // Note: All handlers persist across calls to vtlb_Reset(), but are wiped/invalidated by calls to vtlb_Init()
// //
__ri void vtlb_ReassignHandler( vtlbHandler rv, __ri void vtlb_ReassignHandler(vtlbHandler rv,
vtlbMemR8FP* r8,vtlbMemR16FP* r16,vtlbMemR32FP* r32,vtlbMemR64FP* r64,vtlbMemR128FP* r128, vtlbMemR8FP* r8, vtlbMemR16FP* r16, vtlbMemR32FP* r32, vtlbMemR64FP* r64, vtlbMemR128FP* r128,
vtlbMemW8FP* w8,vtlbMemW16FP* w16,vtlbMemW32FP* w32,vtlbMemW64FP* w64,vtlbMemW128FP* w128 ) vtlbMemW8FP* w8, vtlbMemW16FP* w16, vtlbMemW32FP* w32, vtlbMemW64FP* w64, vtlbMemW128FP* w128)
{ {
pxAssume(rv < VTLB_HANDLER_ITEMS); pxAssume(rv < VTLB_HANDLER_ITEMS);
vtlbdata.RWFT[0][0][rv] = (void*)((r8!=0) ? r8 : vtlbDefaultPhyRead8); vtlbdata.RWFT[0][0][rv] = (void*)((r8 != 0) ? r8 : vtlbDefaultPhyRead8);
vtlbdata.RWFT[1][0][rv] = (void*)((r16!=0) ? r16 : vtlbDefaultPhyRead16); vtlbdata.RWFT[1][0][rv] = (void*)((r16 != 0) ? r16 : vtlbDefaultPhyRead16);
vtlbdata.RWFT[2][0][rv] = (void*)((r32!=0) ? r32 : vtlbDefaultPhyRead32); vtlbdata.RWFT[2][0][rv] = (void*)((r32 != 0) ? r32 : vtlbDefaultPhyRead32);
vtlbdata.RWFT[3][0][rv] = (void*)((r64!=0) ? r64 : vtlbDefaultPhyRead64); vtlbdata.RWFT[3][0][rv] = (void*)((r64 != 0) ? r64 : vtlbDefaultPhyRead64);
vtlbdata.RWFT[4][0][rv] = (void*)((r128!=0) ? r128 : vtlbDefaultPhyRead128); vtlbdata.RWFT[4][0][rv] = (void*)((r128 != 0) ? r128 : vtlbDefaultPhyRead128);
vtlbdata.RWFT[0][1][rv] = (void*)((w8!=0) ? w8 : vtlbDefaultPhyWrite8); vtlbdata.RWFT[0][1][rv] = (void*)((w8 != 0) ? w8 : vtlbDefaultPhyWrite8);
vtlbdata.RWFT[1][1][rv] = (void*)((w16!=0) ? w16 : vtlbDefaultPhyWrite16); vtlbdata.RWFT[1][1][rv] = (void*)((w16 != 0) ? w16 : vtlbDefaultPhyWrite16);
vtlbdata.RWFT[2][1][rv] = (void*)((w32!=0) ? w32 : vtlbDefaultPhyWrite32); vtlbdata.RWFT[2][1][rv] = (void*)((w32 != 0) ? w32 : vtlbDefaultPhyWrite32);
vtlbdata.RWFT[3][1][rv] = (void*)((w64!=0) ? w64 : vtlbDefaultPhyWrite64); vtlbdata.RWFT[3][1][rv] = (void*)((w64 != 0) ? w64 : vtlbDefaultPhyWrite64);
vtlbdata.RWFT[4][1][rv] = (void*)((w128!=0) ? w128 : vtlbDefaultPhyWrite128); vtlbdata.RWFT[4][1][rv] = (void*)((w128 != 0) ? w128 : vtlbDefaultPhyWrite128);
} }
vtlbHandler vtlb_NewHandler() vtlbHandler vtlb_NewHandler()
{ {
pxAssertDev( vtlbHandlerCount < VTLB_HANDLER_ITEMS, "VTLB handler count overflow!" ); pxAssertDev(vtlbHandlerCount < VTLB_HANDLER_ITEMS, "VTLB handler count overflow!");
return vtlbHandlerCount++; return vtlbHandlerCount++;
} }
@ -593,11 +618,11 @@ vtlbHandler vtlb_NewHandler()
// //
// Returns a handle for the newly created handler See vtlb_MapHandler for use of the return value. // Returns a handle for the newly created handler See vtlb_MapHandler for use of the return value.
// //
__ri vtlbHandler vtlb_RegisterHandler( vtlbMemR8FP* r8,vtlbMemR16FP* r16,vtlbMemR32FP* r32,vtlbMemR64FP* r64,vtlbMemR128FP* r128, __ri vtlbHandler vtlb_RegisterHandler(vtlbMemR8FP* r8, vtlbMemR16FP* r16, vtlbMemR32FP* r32, vtlbMemR64FP* r64, vtlbMemR128FP* r128,
vtlbMemW8FP* w8,vtlbMemW16FP* w16,vtlbMemW32FP* w32,vtlbMemW64FP* w64,vtlbMemW128FP* w128) vtlbMemW8FP* w8, vtlbMemW16FP* w16, vtlbMemW32FP* w32, vtlbMemW64FP* w64, vtlbMemW128FP* w128)
{ {
vtlbHandler rv = vtlb_NewHandler(); vtlbHandler rv = vtlb_NewHandler();
vtlb_ReassignHandler( rv, r8, r16, r32, r64, r128, w8, w16, w32, w64, w128 ); vtlb_ReassignHandler(rv, r8, r16, r32, r64, r128, w8, w16, w32, w64, w128);
return rv; return rv;
} }
@ -611,31 +636,31 @@ __ri vtlbHandler vtlb_RegisterHandler( vtlbMemR8FP* r8,vtlbMemR16FP* r16,vtlbMem
// The memory region start and size parameters must be pagesize aligned. // The memory region start and size parameters must be pagesize aligned.
void vtlb_MapHandler(vtlbHandler handler, u32 start, u32 size) void vtlb_MapHandler(vtlbHandler handler, u32 start, u32 size)
{ {
verify(0==(start&VTLB_PAGE_MASK)); verify(0 == (start & VTLB_PAGE_MASK));
verify(0==(size&VTLB_PAGE_MASK) && size>0); verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
u32 end = start + (size - VTLB_PAGE_SIZE); u32 end = start + (size - VTLB_PAGE_SIZE);
pxAssume( (end>>VTLB_PAGE_BITS) < std::size(vtlbdata.pmap) ); pxAssume((end >> VTLB_PAGE_BITS) < std::size(vtlbdata.pmap));
while (start <= end) while (start <= end)
{ {
vtlbdata.pmap[start>>VTLB_PAGE_BITS] = VTLBPhysical::fromHandler(handler); vtlbdata.pmap[start >> VTLB_PAGE_BITS] = VTLBPhysical::fromHandler(handler);
start += VTLB_PAGE_SIZE; start += VTLB_PAGE_SIZE;
} }
} }
void vtlb_MapBlock(void* base, u32 start, u32 size, u32 blocksize) void vtlb_MapBlock(void* base, u32 start, u32 size, u32 blocksize)
{ {
verify(0==(start&VTLB_PAGE_MASK)); verify(0 == (start & VTLB_PAGE_MASK));
verify(0==(size&VTLB_PAGE_MASK) && size>0); verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
if(!blocksize) if (!blocksize)
blocksize = size; blocksize = size;
verify(0==(blocksize&VTLB_PAGE_MASK) && blocksize>0); verify(0 == (blocksize & VTLB_PAGE_MASK) && blocksize > 0);
verify(0==(size%blocksize)); verify(0 == (size % blocksize));
sptr baseint = (sptr)base; sptr baseint = (sptr)base;
u32 end = start + (size - VTLB_PAGE_SIZE); u32 end = start + (size - VTLB_PAGE_SIZE);
verify((end>>VTLB_PAGE_BITS) < std::size(vtlbdata.pmap)); verify((end >> VTLB_PAGE_BITS) < std::size(vtlbdata.pmap));
while (start <= end) while (start <= end)
{ {
@ -644,7 +669,7 @@ void vtlb_MapBlock(void* base, u32 start, u32 size, u32 blocksize)
while (loopsz > 0) while (loopsz > 0)
{ {
vtlbdata.pmap[start>>VTLB_PAGE_BITS] = VTLBPhysical::fromPointer(ptr); vtlbdata.pmap[start >> VTLB_PAGE_BITS] = VTLBPhysical::fromPointer(ptr);
start += VTLB_PAGE_SIZE; start += VTLB_PAGE_SIZE;
ptr += VTLB_PAGE_SIZE; ptr += VTLB_PAGE_SIZE;
@ -653,18 +678,18 @@ void vtlb_MapBlock(void* base, u32 start, u32 size, u32 blocksize)
} }
} }
void vtlb_Mirror(u32 new_region,u32 start,u32 size) void vtlb_Mirror(u32 new_region, u32 start, u32 size)
{ {
verify(0==(new_region&VTLB_PAGE_MASK)); verify(0 == (new_region & VTLB_PAGE_MASK));
verify(0==(start&VTLB_PAGE_MASK)); verify(0 == (start & VTLB_PAGE_MASK));
verify(0==(size&VTLB_PAGE_MASK) && size>0); verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
u32 end = start + (size-VTLB_PAGE_SIZE); u32 end = start + (size - VTLB_PAGE_SIZE);
verify((end>>VTLB_PAGE_BITS) < std::size(vtlbdata.pmap)); verify((end >> VTLB_PAGE_BITS) < std::size(vtlbdata.pmap));
while(start <= end) while (start <= end)
{ {
vtlbdata.pmap[start>>VTLB_PAGE_BITS] = vtlbdata.pmap[new_region>>VTLB_PAGE_BITS]; vtlbdata.pmap[start >> VTLB_PAGE_BITS] = vtlbdata.pmap[new_region >> VTLB_PAGE_BITS];
start += VTLB_PAGE_SIZE; start += VTLB_PAGE_SIZE;
new_region += VTLB_PAGE_SIZE; new_region += VTLB_PAGE_SIZE;
@ -673,15 +698,15 @@ void vtlb_Mirror(u32 new_region,u32 start,u32 size)
__fi void* vtlb_GetPhyPtr(u32 paddr) __fi void* vtlb_GetPhyPtr(u32 paddr)
{ {
if (paddr>=VTLB_PMAP_SZ || vtlbdata.pmap[paddr>>VTLB_PAGE_BITS].isHandler()) if (paddr >= VTLB_PMAP_SZ || vtlbdata.pmap[paddr >> VTLB_PAGE_BITS].isHandler())
return NULL; return NULL;
else else
return reinterpret_cast<void*>(vtlbdata.pmap[paddr>>VTLB_PAGE_BITS].assumePtr()+(paddr&VTLB_PAGE_MASK)); return reinterpret_cast<void*>(vtlbdata.pmap[paddr >> VTLB_PAGE_BITS].assumePtr() + (paddr & VTLB_PAGE_MASK));
} }
__fi u32 vtlb_V2P(u32 vaddr) __fi u32 vtlb_V2P(u32 vaddr)
{ {
u32 paddr = vtlbdata.ppmap[vaddr>>VTLB_PAGE_BITS]; u32 paddr = vtlbdata.ppmap[vaddr >> VTLB_PAGE_BITS];
paddr |= vaddr & VTLB_PAGE_MASK; paddr |= vaddr & VTLB_PAGE_MASK;
return paddr; return paddr;
} }
@ -821,7 +846,7 @@ static void vtlb_CreateFastmemMapping(u32 vaddr, u32 mainmem_offset, const PageP
// remove reverse mapping // remove reverse mapping
auto range = s_fastmem_physical_mapping.equal_range(mainmem_offset); auto range = s_fastmem_physical_mapping.equal_range(mainmem_offset);
for (auto it = range.first; it != range.second; ) for (auto it = range.first; it != range.second;)
{ {
auto this_it = it++; auto this_it = it++;
if (this_it->second == vaddr) if (this_it->second == vaddr)
@ -1023,11 +1048,11 @@ bool vtlb_IsFaultingPC(u32 guest_pc)
//virtual mappings //virtual mappings
//TODO: Add invalid paddr checks //TODO: Add invalid paddr checks
void vtlb_VMap(u32 vaddr,u32 paddr,u32 size) void vtlb_VMap(u32 vaddr, u32 paddr, u32 size)
{ {
verify(0==(vaddr&VTLB_PAGE_MASK)); verify(0 == (vaddr & VTLB_PAGE_MASK));
verify(0==(paddr&VTLB_PAGE_MASK)); verify(0 == (paddr & VTLB_PAGE_MASK));
verify(0==(size&VTLB_PAGE_MASK) && size>0); verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
if (CHECK_FASTMEM) if (CHECK_FASTMEM)
{ {
@ -1067,10 +1092,10 @@ void vtlb_VMap(u32 vaddr,u32 paddr,u32 size)
} }
} }
void vtlb_VMapBuffer(u32 vaddr,void* buffer,u32 size) void vtlb_VMapBuffer(u32 vaddr, void* buffer, u32 size)
{ {
verify(0==(vaddr&VTLB_PAGE_MASK)); verify(0 == (vaddr & VTLB_PAGE_MASK));
verify(0==(size&VTLB_PAGE_MASK) && size>0); verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
if (CHECK_FASTMEM) if (CHECK_FASTMEM)
{ {
@ -1091,23 +1116,23 @@ void vtlb_VMapBuffer(u32 vaddr,void* buffer,u32 size)
uptr bu8 = (uptr)buffer; uptr bu8 = (uptr)buffer;
while (size > 0) while (size > 0)
{ {
vtlbdata.vmap[vaddr>>VTLB_PAGE_BITS] = VTLBVirtual::fromPointer(bu8, vaddr); vtlbdata.vmap[vaddr >> VTLB_PAGE_BITS] = VTLBVirtual::fromPointer(bu8, vaddr);
vaddr += VTLB_PAGE_SIZE; vaddr += VTLB_PAGE_SIZE;
bu8 += VTLB_PAGE_SIZE; bu8 += VTLB_PAGE_SIZE;
size -= VTLB_PAGE_SIZE; size -= VTLB_PAGE_SIZE;
} }
} }
void vtlb_VMapUnmap(u32 vaddr,u32 size) void vtlb_VMapUnmap(u32 vaddr, u32 size)
{ {
verify(0==(vaddr&VTLB_PAGE_MASK)); verify(0 == (vaddr & VTLB_PAGE_MASK));
verify(0==(size&VTLB_PAGE_MASK) && size>0); verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
vtlb_RemoveFastmemMappings(vaddr, size); vtlb_RemoveFastmemMappings(vaddr, size);
while (size > 0) while (size > 0)
{ {
vtlbdata.vmap[vaddr>>VTLB_PAGE_BITS] = VTLBVirtual(VTLBPhysical::fromHandler(UnmappedVirtHandler), vaddr, vaddr); vtlbdata.vmap[vaddr >> VTLB_PAGE_BITS] = VTLBVirtual(VTLBPhysical::fromHandler(UnmappedVirtHandler), vaddr, vaddr);
vaddr += VTLB_PAGE_SIZE; vaddr += VTLB_PAGE_SIZE;
size -= VTLB_PAGE_SIZE; size -= VTLB_PAGE_SIZE;
} }
@ -1116,7 +1141,7 @@ void vtlb_VMapUnmap(u32 vaddr,u32 size)
// vtlb_Init -- Clears vtlb handlers and memory mappings. // vtlb_Init -- Clears vtlb handlers and memory mappings.
void vtlb_Init() void vtlb_Init()
{ {
vtlbHandlerCount=0; vtlbHandlerCount = 0;
memzero(vtlbdata.RWFT); memzero(vtlbdata.RWFT);
#define VTLB_BuildUnmappedHandler(baseName) \ #define VTLB_BuildUnmappedHandler(baseName) \
@ -1158,7 +1183,8 @@ void vtlb_Init()
void vtlb_Reset() void vtlb_Reset()
{ {
vtlb_RemoveFastmemMappings(); vtlb_RemoveFastmemMappings();
for(int i=0; i<48; i++) UnmapTLB(tlb[i], i); for (int i = 0; i < 48; i++)
UnmapTLB(tlb[i], i);
} }
void vtlb_Shutdown() void vtlb_Shutdown()
@ -1204,7 +1230,7 @@ static constexpr size_t VMAP_SIZE = sizeof(VTLBVirtual) * VTLB_VMAP_ITEMS;
// [TODO] basemem - request allocating memory at the specified virtual location, which can allow // [TODO] basemem - request allocating memory at the specified virtual location, which can allow
// for easier debugging and/or 3rd party cheat programs. If 0, the operating system // for easier debugging and/or 3rd party cheat programs. If 0, the operating system
// default is used. // default is used.
void vtlb_Core_Alloc() bool vtlb_Core_Alloc()
{ {
// Can't return regions to the bump allocator // Can't return regions to the bump allocator
static VTLBVirtual* vmap = nullptr; static VTLBVirtual* vmap = nullptr;
@ -1212,7 +1238,10 @@ void vtlb_Core_Alloc()
{ {
vmap = (VTLBVirtual*)GetVmMemory().BumpAllocator().Alloc(VMAP_SIZE); vmap = (VTLBVirtual*)GetVmMemory().BumpAllocator().Alloc(VMAP_SIZE);
if (!vmap) if (!vmap)
pxFailRel("Failed to allocate vtlb vmap"); {
Host::ReportErrorAsync("Error", "Failed to allocate vtlb vmap");
return false;
}
} }
if (!vtlbdata.vmap) if (!vtlbdata.vmap)
@ -1226,13 +1255,24 @@ void vtlb_Core_Alloc()
pxAssert(!s_fastmem_area); pxAssert(!s_fastmem_area);
s_fastmem_area = SharedMemoryMappingArea::Create(FASTMEM_AREA_SIZE); s_fastmem_area = SharedMemoryMappingArea::Create(FASTMEM_AREA_SIZE);
if (!s_fastmem_area) if (!s_fastmem_area)
pxFailRel("Failed to allocate fastmem area"); {
Host::ReportErrorAsync("Error", "Failed to allocate fastmem area");
return false;
}
s_fastmem_virtual_mapping.resize(FASTMEM_PAGE_COUNT, NO_FASTMEM_MAPPING); s_fastmem_virtual_mapping.resize(FASTMEM_PAGE_COUNT, NO_FASTMEM_MAPPING);
vtlbdata.fastmem_base = (uptr)s_fastmem_area->BasePointer(); vtlbdata.fastmem_base = (uptr)s_fastmem_area->BasePointer();
Console.WriteLn(Color_StrongGreen, "Fastmem area: %p - %p", Console.WriteLn(Color_StrongGreen, "Fastmem area: %p - %p",
vtlbdata.fastmem_base, vtlbdata.fastmem_base + (FASTMEM_AREA_SIZE - 1)); vtlbdata.fastmem_base, vtlbdata.fastmem_base + (FASTMEM_AREA_SIZE - 1));
} }
if (!HostSys::InstallPageFaultHandler(&vtlb_private::PageFaultHandler))
{
Host::ReportErrorAsync("Error", "Failed to install page fault handler.");
return false;
}
return true;
} }
static constexpr size_t PPMAP_SIZE = sizeof(*vtlbdata.ppmap) * VTLB_VMAP_ITEMS; static constexpr size_t PPMAP_SIZE = sizeof(*vtlbdata.ppmap) * VTLB_VMAP_ITEMS;
@ -1254,11 +1294,13 @@ void vtlb_Alloc_Ppmap()
// By default a 1:1 virtual to physical mapping // By default a 1:1 virtual to physical mapping
for (u32 i = 0; i < VTLB_VMAP_ITEMS; i++) for (u32 i = 0; i < VTLB_VMAP_ITEMS; i++)
vtlbdata.ppmap[i] = i<<VTLB_PAGE_BITS; vtlbdata.ppmap[i] = i << VTLB_PAGE_BITS;
} }
void vtlb_Core_Free() void vtlb_Core_Free()
{ {
HostSys::RemovePageFaultHandler(&vtlb_private::PageFaultHandler);
if (vtlbdata.vmap) if (vtlbdata.vmap)
{ {
HostSys::MemProtect(vtlbdata.vmap, VMAP_SIZE, PageProtectionMode()); HostSys::MemProtect(vtlbdata.vmap, VMAP_SIZE, PageProtectionMode());
@ -1307,3 +1349,163 @@ void VtlbMemoryReserve::Reset()
{ {
memzero_sse_a(GetPtr(), GetSize()); memzero_sse_a(GetPtr(), GetSize());
} }
// ===========================================================================================
// Memory Protection and Block Checking, vtlb Style!
// ===========================================================================================
// For the first time code is recompiled (executed), the PS2 ram page for that code is
// protected using Virtual Memory (mprotect). If the game modifies its own code then this
// protection causes an *exception* to be raised (signal in Linux), which is handled by
// unprotecting the page and switching the recompiled block to "manual" protection.
//
// Manual protection uses a simple brute-force memcmp of the recompiled code to the code
// currently in RAM for *each time* the block is executed. Fool-proof, but slow, which
// is why we default to using the exception-based protection scheme described above.
//
// Why manual blocks? Because many games contain code and data in the same 4k page, so
// we *cannot* automatically recompile and reprotect pages, lest we end up recompiling and
// reprotecting them constantly (Which would be very slow). As a counter, the R5900 side
// of the block checking code does try to periodically re-protect blocks [going from manual
// back to protected], so that blocks which underwent a single invalidation don't need to
// incur a permanent performance penalty.
//
// Page Granularity:
// Fortunately for us MIPS and x86 use the same page granularity for TLB and memory
// protection, so we can use a 1:1 correspondence when protecting pages. Page granularity
// is 4096 (4k), which is why you'll see a lot of 0xfff's, >><< 12's, and 0x1000's in the
// code below.
//
struct vtlb_PageProtectionInfo
{
// Ram De-mapping -- used to convert fully translated/mapped offsets (which reside with
// in the eeMem->Main block) back into their originating ps2 physical ram address.
// Values are assigned when pages are marked for protection. since pages are automatically
// cleared and reset when TLB-remapped, stale values in this table (due to on-the-fly TLB
// changes) will be re-assigned the next time the page is accessed.
u32 ReverseRamMap;
vtlb_ProtectionMode Mode;
};
alignas(16) static vtlb_PageProtectionInfo m_PageProtectInfo[Ps2MemSize::MainRam >> __pageshift];
// returns:
// ProtMode_NotRequired - unchecked block (resides in ROM, thus is integrity is constant)
// Or the current mode
//
vtlb_ProtectionMode mmap_GetRamPageInfo(u32 paddr)
{
pxAssert(eeMem);
paddr &= ~0xfff;
uptr ptr = (uptr)PSM(paddr);
uptr rampage = ptr - (uptr)eeMem->Main;
if (!ptr || rampage >= Ps2MemSize::MainRam)
return ProtMode_NotRequired; //not in ram, no tracking done ...
rampage >>= __pageshift;
return m_PageProtectInfo[rampage].Mode;
}
// paddr - physically mapped PS2 address
void mmap_MarkCountedRamPage(u32 paddr)
{
pxAssert(eeMem);
paddr &= ~__pagemask;
uptr ptr = (uptr)PSM(paddr);
int rampage = (ptr - (uptr)eeMem->Main) >> __pageshift;
// Important: Update the ReverseRamMap here because TLB changes could alter the paddr
// mapping into eeMem->Main.
m_PageProtectInfo[rampage].ReverseRamMap = paddr;
if (m_PageProtectInfo[rampage].Mode == ProtMode_Write)
return; // skip town if we're already protected.
eeRecPerfLog.Write((m_PageProtectInfo[rampage].Mode == ProtMode_Manual) ?
"Re-protecting page @ 0x%05x" :
"Protected page @ 0x%05x",
paddr >> __pageshift);
m_PageProtectInfo[rampage].Mode = ProtMode_Write;
HostSys::MemProtect(&eeMem->Main[rampage << __pageshift], __pagesize, PageAccess_ReadOnly());
vtlb_UpdateFastmemProtection(rampage << __pageshift, __pagesize, PageAccess_ReadOnly());
}
// offset - offset of address relative to psM.
// All recompiled blocks belonging to the page are cleared, and any new blocks recompiled
// from code residing in this page will use manual protection.
static __fi void mmap_ClearCpuBlock(uint offset)
{
pxAssert(eeMem);
int rampage = offset >> __pageshift;
// Assertion: This function should never be run on a block that's already under
// manual protection. Indicates a logic error in the recompiler or protection code.
pxAssertMsg(m_PageProtectInfo[rampage].Mode != ProtMode_Manual,
"Attempted to clear a block that is already under manual protection.");
HostSys::MemProtect(&eeMem->Main[rampage << __pageshift], __pagesize, PageAccess_ReadWrite());
vtlb_UpdateFastmemProtection(rampage << __pageshift, __pagesize, PageAccess_ReadWrite());
m_PageProtectInfo[rampage].Mode = ProtMode_Manual;
Cpu->Clear(m_PageProtectInfo[rampage].ReverseRamMap, __pagesize);
}
bool vtlb_private::PageFaultHandler(const PageFaultInfo& info)
{
pxAssert(eeMem);
u32 vaddr;
if (CHECK_FASTMEM && vtlb_GetGuestAddress(info.addr, &vaddr))
{
// this was inside the fastmem area. check if it's a code page
// fprintf(stderr, "Fault on fastmem %p vaddr %08X\n", info.addr, vaddr);
uptr ptr = (uptr)PSM(vaddr);
uptr offset = (ptr - (uptr)eeMem->Main);
if (ptr && m_PageProtectInfo[offset >> __pageshift].Mode == ProtMode_Write)
{
// fprintf(stderr, "Not backpatching code write at %08X\n", vaddr);
mmap_ClearCpuBlock(offset);
return true;
}
else
{
// fprintf(stderr, "Trying backpatching vaddr %08X\n", vaddr);
return vtlb_BackpatchLoadStore(info.pc, info.addr);
}
}
else
{
// get bad virtual address
uptr offset = info.addr - (uptr)eeMem->Main;
if (offset >= Ps2MemSize::MainRam)
return false;
mmap_ClearCpuBlock(offset);
return true;
}
}
// Clears all block tracking statuses, manual protection flags, and write protection.
// This does not clear any recompiler blocks. It is assumed (and necessary) for the caller
// to ensure the EErec is also reset in conjunction with calling this function.
// (this function is called by default from the eerecReset).
void mmap_ResetBlockTracking()
{
//DbgCon.WriteLn( "vtlb/mmap: Block Tracking reset..." );
memzero(m_PageProtectInfo);
if (eeMem)
HostSys::MemProtect(eeMem->Main, Ps2MemSize::MainRam, PageAccess_ReadWrite());
vtlb_UpdateFastmemProtection(0, Ps2MemSize::MainRam, PageAccess_ReadWrite());
}

View File

@ -17,8 +17,7 @@
#include "MemoryTypes.h" #include "MemoryTypes.h"
#include "SingleRegisterTypes.h" #include "SingleRegisterTypes.h"
#include "VirtualMemory.h"
#include "common/PageFaultSource.h"
static const uptr VTLB_AllocUpperBounds = _1gb * 2; static const uptr VTLB_AllocUpperBounds = _1gb * 2;
@ -51,7 +50,7 @@ template<> struct vtlbMemFP<128, true> { typedef vtlbMemW128FP fn; static const
typedef u32 vtlbHandler; typedef u32 vtlbHandler;
extern void vtlb_Core_Alloc(); extern bool vtlb_Core_Alloc();
extern void vtlb_Core_Free(); extern void vtlb_Core_Free();
extern void vtlb_Alloc_Ppmap(); extern void vtlb_Alloc_Ppmap();
extern void vtlb_Init(); extern void vtlb_Init();
@ -289,6 +288,18 @@ namespace vtlb_private
} }
} }
enum vtlb_ProtectionMode
{
ProtMode_None = 0, // page is 'unaccounted' -- neither protected nor unprotected
ProtMode_Write, // page is under write protection (exception handler)
ProtMode_Manual, // page is under manual protection (self-checked at execution)
ProtMode_NotRequired // page doesn't require any protection
};
extern vtlb_ProtectionMode mmap_GetRamPageInfo(u32 paddr);
extern void mmap_MarkCountedRamPage(u32 paddr);
extern void mmap_ResetBlockTracking();
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------
// Goemon game fix // Goemon game fix
// -------------------------------------------------------------------------------------- // --------------------------------------------------------------------------------------

View File

@ -23,11 +23,11 @@
#include "iR3000A.h" #include "iR3000A.h"
#include "R3000A.h" #include "R3000A.h"
#include "BaseblockEx.h" #include "BaseblockEx.h"
#include "System.h"
#include "R5900OpcodeTables.h" #include "R5900OpcodeTables.h"
#include "IopBios.h" #include "IopBios.h"
#include "IopHw.h" #include "IopHw.h"
#include "Common.h" #include "Common.h"
#include "VirtualMemory.h"
#include "VMManager.h" #include "VMManager.h"
#include <time.h> #include <time.h>

View File

@ -24,8 +24,7 @@
#include "iR5900.h" #include "iR5900.h"
#include "iR5900Analysis.h" #include "iR5900Analysis.h"
#include "BaseblockEx.h" #include "BaseblockEx.h"
#include "System.h" #include "VirtualMemory.h"
#include "vtlb.h" #include "vtlb.h"
#include "VMManager.h" #include "VMManager.h"

View File

@ -30,7 +30,7 @@ using namespace x86Emitter;
#include "Gif_Unit.h" #include "Gif_Unit.h"
#include "iR5900.h" #include "iR5900.h"
#include "R5900OpcodeTables.h" #include "R5900OpcodeTables.h"
#include "System.h" #include "VirtualMemory.h"
#include "common/emitter/x86emitter.h" #include "common/emitter/x86emitter.h"
#include "microVU_Misc.h" #include "microVU_Misc.h"
#include "microVU_IR.h" #include "microVU_IR.h"

View File

@ -17,9 +17,9 @@
#include "Vif.h" #include "Vif.h"
#include "VU.h" #include "VU.h"
#include "VirtualMemory.h"
#include "common/emitter/x86emitter.h" #include "common/emitter/x86emitter.h"
#include "System.h"
using namespace x86Emitter; using namespace x86Emitter;