Files
pstef c5ad9ddd4d EE rec: stop raising TLB misses, matching x86
vtlb_Miss raised the exception under the arm64 recompiler and returned,
on the assumption that the rec would pick cpuRegs.pc up at the next
dispatch. Nothing picks it up: the block runs on and its tail writes its
own branch target over the vector PC, while cpuException has already
latched Status.EXL. cpuException leaves EPC alone whenever EXL is
already set, so from the first swallowed miss onward every exception
keeps its predecessor's EPC, and the guest kernel's syscall epilogue
erets to an address belonging to the fault.

Report the miss and continue instead, as the x86 rec already does. The
guest's handler still does not run, so a title that needs demand paging
still cannot work under the rec; a stray miss now stays local to the
instruction. ee_rec_tlb_divert_tests.cpp describes the divert and stays
disabled.

With nothing left to set s_recTlbMissOccurred, the poll after every
interpreter call goes too. It could not have covered the raise anyway:
only the interpreter-call sites had it, so a flag set at an inline
access sat there until an unrelated later recCall consumed it and
diverted on a pc belonging to neither.

This gives up the one case the rec did handle, a miss in a branch delay
slot reaching the vector through the cpuRegs.branch bracket epilogue.
Its recompiler half moves to the disabled file; the interpreter half
stays live, beside a new test pinning the rec's behaviour. The bracket
itself stays: the tlb_fallback_* handlers raise from a delay slot on
their own.
2026-08-18 08:00:56 -07:00

1660 lines
54 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
/*
EE physical map :
[0000 0000,1000 0000) -> Ram (mirrored ?)
[1000 0000,1400 0000) -> Registers
[1400 0000,1fc0 0000) -> Reserved (ingored writes, 'random' reads)
[1fc0 0000,2000 0000) -> Boot ROM
[2000 0000,4000 0000) -> Unmapped (BUS ERROR)
[4000 0000,8000 0000) -> "Extended memory", probably unmapped (BUS ERROR) on retail ps2's :)
[8000 0000,FFFF FFFF] -> Unmapped (BUS ERROR)
vtlb/phy only supports the [0000 0000,2000 0000) region, with 4k pages.
vtlb/vmap supports mapping to either of these locations, or some other (externaly) specified address.
*/
#include "Common.h"
#include "vtlb.h"
#include "COP0.h"
#include "Cache.h"
#include "IopMem.h"
#include "Host.h"
#include "VMManager.h"
#if defined(__APPLE__)
#include <TargetConditionals.h>
#endif
#include "common/BitUtils.h"
#include "common/Error.h"
#include "fmt/format.h"
#include "GS/GSVector.h"
#include <algorithm>
#include <bit>
#include <cstdlib>
#include <map>
#include <unordered_map>
#define FASTMEM_LOG(...)
//#define FASTMEM_LOG(...) Console.WriteLn(__VA_ARGS__)
using namespace R5900;
using namespace vtlb_private;
#define verify pxAssert
namespace vtlb_private
{
alignas(64) MapData vtlbdata;
} // namespace vtlb_private
static vtlbHandler vtlbHandlerCount = 0;
static vtlbHandler DefaultPhyHandler;
static vtlbHandler UnmappedVirtHandler;
static vtlbHandler UnmappedPhyHandler;
struct FastmemVirtualMapping
{
u32 offset;
u32 size;
};
struct LoadstoreBackpatchInfo
{
u32 guest_pc;
u32 gpr_bitmask;
u32 fpr_bitmask;
u8 code_size;
u8 address_register;
u8 data_register;
u8 size_in_bits;
bool is_signed;
bool is_load;
bool is_fpr;
};
static constexpr size_t FASTMEM_AREA_SIZE = 0x100000000ULL;
static constexpr u32 FASTMEM_PAGE_COUNT = FASTMEM_AREA_SIZE / VTLB_PAGE_SIZE;
static constexpr u32 NO_FASTMEM_MAPPING = 0xFFFFFFFFu;
static std::unique_ptr<SharedMemoryMappingArea> s_fastmem_area;
static std::vector<u32> s_fastmem_virtual_mapping; // maps vaddr -> mainmem offset
static std::unordered_multimap<u32, u32> s_fastmem_physical_mapping; // maps mainmem offset -> vaddr
static std::unordered_map<uptr, LoadstoreBackpatchInfo> s_fastmem_backpatch_info;
static std::vector<u32> s_fastmem_faulting_pcs; // sorted; lookups via binary search
// Sticky: the 4 GB fastmem area allocation failed on this device at least once.
// Process-lifetime flag so LoadSettings's config reload can't silently re-enable
// fastmem against a null base and crash the EE rec on its first load/store.
static bool s_fastmem_area_unavailable = false;
bool vtlb_FastmemAreaUnavailable()
{
// Settings can be reloaded after allocation. Treat any allocated VM without
// a reservation as unavailable so fastmem cannot be re-enabled against a
// null base, whether allocation failed or the area was intentionally omitted.
return s_fastmem_area_unavailable || (SysMemory::IsAllocated() && !s_fastmem_area);
}
vtlb_private::VTLBPhysical vtlb_private::VTLBPhysical::fromPointer(sptr ptr)
{
pxAssertMsg(ptr >= 0, "Address too high");
return VTLBPhysical(ptr);
}
vtlb_private::VTLBPhysical vtlb_private::VTLBPhysical::fromHandler(vtlbHandler handler)
{
return VTLBPhysical(handler | POINTER_SIGN_BIT);
}
vtlb_private::VTLBVirtual::VTLBVirtual(VTLBPhysical phys, u32 paddr, u32 vaddr)
{
pxAssertMsg(0 == (paddr & 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");
if (phys.isHandler())
{
value = phys.raw() + paddr - vaddr;
}
else
{
value = phys.raw() - vaddr;
}
}
__inline int CheckCache(u32 addr)
{
// Check if the cache is enabled
if (((cpuRegs.CP0.n.Config >> 16) & 0x1) == 0)
{
return false;
}
size_t i = 0;
const size_t size = cachedTlbs.count;
const int stride = 4;
const GSVector4i addr_vec = GSVector4i::load(addr);
for (; i + stride <= size; i += stride)
{
const GSVector4i pfn1_vec = GSVector4i::load<true>(&cachedTlbs.PFN1s[i]);
const GSVector4i pfn0_vec = GSVector4i::load<true>(&cachedTlbs.PFN0s[i]);
const GSVector4i mask_vec = GSVector4i::load<true>(&cachedTlbs.PageMasks[i]);
const GSVector4i cached1_enable_vec = GSVector4i::load<true>(&cachedTlbs.CacheEnabled1[i]);
const GSVector4i cached0_enable_vec = GSVector4i::load<true>(&cachedTlbs.CacheEnabled0[i]);
const GSVector4i cmp1 = addr_vec.ge32(pfn1_vec) & addr_vec.le32(pfn1_vec + mask_vec);
const GSVector4i cmp0 = addr_vec.ge32(pfn0_vec) & addr_vec.le32(pfn1_vec + mask_vec);
const GSVector4i lanes_enabled = (cmp1 & cached1_enable_vec) | (cmp0 & cached0_enable_vec);
if (!lanes_enabled.allfalse())
return true;
}
for (; i < size; i++)
{
const u32 mask = cachedTlbs.PageMasks[i];
if ((cachedTlbs.CacheEnabled1[i] && addr >= cachedTlbs.PFN1s[i] && addr <= cachedTlbs.PFN1s[i] + mask) ||
(cachedTlbs.CacheEnabled0[i] && addr >= cachedTlbs.PFN0s[i] && addr <= cachedTlbs.PFN0s[i] + mask))
{
return true;
}
}
return false;
}
// --------------------------------------------------------------------------------------
// Interpreter Implementations of VTLB Memory Operations.
// --------------------------------------------------------------------------------------
// See recVTLB.cpp for the dynarec versions.
template <typename DataType>
DataType vtlb_memRead(u32 addr)
{
static const uint DataSize = sizeof(DataType) * 8;
auto vmv = vtlbdata.vmap[addr >> VTLB_PAGE_BITS];
if (!vmv.isHandler(addr))
{
if (!CHECK_EEREC)
{
if (CHECK_CACHE && CheckCache(addr))
{
switch (DataSize)
{
case 8:
return readCache8(addr);
break;
case 16:
return readCache16(addr);
break;
case 32:
return readCache32(addr);
break;
case 64:
return readCache64(addr);
break;
jNO_DEFAULT;
}
}
}
return *reinterpret_cast<DataType*>(vmv.assumePtr(addr));
}
//has to: translate, find function, call function
u32 paddr = vmv.assumeHandlerGetPAddr(addr);
//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);
switch (DataSize)
{
case 8:
return vmv.assumeHandler<8, false>()(paddr);
case 16:
return vmv.assumeHandler<16, false>()(paddr);
case 32:
return vmv.assumeHandler<32, false>()(paddr);
case 64:
return vmv.assumeHandler<64, false>()(paddr);
jNO_DEFAULT;
}
return 0; // technically unreachable, but suppresses warnings.
}
RETURNS_R128 vtlb_memRead128(u32 mem)
{
auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (!vmv.isHandler(mem))
{
if (!CHECK_EEREC)
{
if (CHECK_CACHE && CheckCache(mem))
{
return readCache128(mem);
}
}
return r128_load(reinterpret_cast<const void*>(vmv.assumePtr(mem)));
}
else
{
//has to: translate, find function, call function
u32 paddr = vmv.assumeHandlerGetPAddr(mem);
//Console.WriteLn("Translated 0x%08X to 0x%08X", addr,paddr);
return vmv.assumeHandler<128, false>()(paddr);
}
}
template <typename DataType>
void vtlb_memWrite(u32 addr, DataType data)
{
static const uint DataSize = sizeof(DataType) * 8;
auto vmv = vtlbdata.vmap[addr >> VTLB_PAGE_BITS];
if (!vmv.isHandler(addr))
{
if (!CHECK_EEREC)
{
if (CHECK_CACHE && CheckCache(addr))
{
switch (DataSize)
{
case 8:
writeCache8(addr, data);
return;
case 16:
writeCache16(addr, data);
return;
case 32:
writeCache32(addr, data);
return;
case 64:
writeCache64(addr, data);
return;
}
}
}
*reinterpret_cast<DataType*>(vmv.assumePtr(addr)) = data;
}
else
{
//has to: translate, find function, call function
u32 paddr = vmv.assumeHandlerGetPAddr(addr);
//Console.WriteLn("Translated 0x%08X to 0x%08X", addr,paddr);
return vmv.assumeHandler<sizeof(DataType) * 8, true>()(paddr, data);
}
}
void TAKES_R128 vtlb_memWrite128(u32 mem, r128 value)
{
auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (!vmv.isHandler(mem))
{
if (!CHECK_EEREC)
{
if (CHECK_CACHE && CheckCache(mem))
{
alignas(16) const u128 r = r128_to_u128(value);
writeCache128(mem, &r);
return;
}
}
r128_store_unaligned((void*)vmv.assumePtr(mem), value);
}
else
{
//has to: translate, find function, call function
u32 paddr = vmv.assumeHandlerGetPAddr(mem);
//Console.WriteLn("Translated 0x%08X to 0x%08X", addr,paddr);
vmv.assumeHandler<128, true>()(paddr, value);
}
}
template mem8_t vtlb_memRead<mem8_t>(u32 mem);
template mem16_t vtlb_memRead<mem16_t>(u32 mem);
template mem32_t vtlb_memRead<mem32_t>(u32 mem);
template mem64_t vtlb_memRead<mem64_t>(u32 mem);
template void vtlb_memWrite<mem8_t>(u32 mem, mem8_t data);
template void vtlb_memWrite<mem16_t>(u32 mem, mem16_t data);
template void vtlb_memWrite<mem32_t>(u32 mem, mem32_t data);
template void vtlb_memWrite<mem64_t>(u32 mem, mem64_t data);
template <typename DataType>
DataType vtlb_ramRead(u32 addr, bool* result)
{
const auto vmv = vtlbdata.vmap[addr >> VTLB_PAGE_BITS];
if (vmv.isHandler(addr)) [[unlikely]]
{
if (result)
*result = false;
return {};
}
DataType value = {};
std::memcpy(&value, reinterpret_cast<DataType*>(vmv.assumePtr(addr)), sizeof(DataType));
if (result)
*result = true;
return value;
}
template <typename DataType>
bool vtlb_ramWrite(u32 addr, const DataType& data)
{
const auto vmv = vtlbdata.vmap[addr >> VTLB_PAGE_BITS];
if (vmv.isHandler(addr)) [[unlikely]]
return false;
std::memcpy(reinterpret_cast<DataType*>(vmv.assumePtr(addr)), &data, sizeof(DataType));
return true;
}
template mem8_t vtlb_ramRead<mem8_t>(u32 mem, bool* result);
template mem16_t vtlb_ramRead<mem16_t>(u32 mem, bool* result);
template mem32_t vtlb_ramRead<mem32_t>(u32 mem, bool* result);
template mem64_t vtlb_ramRead<mem64_t>(u32 mem, bool* result);
template mem128_t vtlb_ramRead<mem128_t>(u32 mem, bool* result);
template bool vtlb_ramWrite<mem8_t>(u32 mem, const mem8_t& data);
template bool vtlb_ramWrite<mem16_t>(u32 mem, const mem16_t& data);
template bool vtlb_ramWrite<mem32_t>(u32 mem, const mem32_t& data);
template bool vtlb_ramWrite<mem64_t>(u32 mem, const mem64_t& data);
template bool vtlb_ramWrite<mem128_t>(u32 mem, const mem128_t& data);
int vtlb_memSafeCmpBytes(u32 mem, const void* src, u32 size)
{
// can memcpy so long as pages aren't crossed
const u8* sptr = static_cast<const u8*>(src);
const u8* const sptr_end = sptr + size;
while (sptr != sptr_end)
{
auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (vmv.isHandler(mem))
return -1;
const size_t remaining_in_page =
std::min(VTLB_PAGE_SIZE - (mem & VTLB_PAGE_MASK), static_cast<u32>(sptr_end - sptr));
const int res = std::memcmp(sptr, reinterpret_cast<void*>(vmv.assumePtr(mem)), remaining_in_page);
if (res != 0)
return res;
sptr += remaining_in_page;
mem += remaining_in_page;
}
return 0;
}
bool vtlb_memSafeReadBytes(u32 mem, void* dst, u32 size)
{
// can memcpy so long as pages aren't crossed
u8* dptr = static_cast<u8*>(dst);
u8* const dptr_end = dptr + size;
while (dptr != dptr_end)
{
auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (vmv.isHandler(mem))
return false;
const u32 remaining_in_page =
std::min(VTLB_PAGE_SIZE - (mem & VTLB_PAGE_MASK), static_cast<u32>(dptr_end - dptr));
std::memcpy(dptr, reinterpret_cast<void*>(vmv.assumePtr(mem)), remaining_in_page);
dptr += remaining_in_page;
mem += remaining_in_page;
}
return true;
}
bool vtlb_memSafeWriteBytes(u32 mem, const void* src, u32 size)
{
// can memcpy so long as pages aren't crossed
const u8* sptr = static_cast<const u8*>(src);
const u8* const sptr_end = sptr + size;
while (sptr != sptr_end)
{
auto vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
if (vmv.isHandler(mem))
return false;
const size_t remaining_in_page =
std::min(VTLB_PAGE_SIZE - (mem & VTLB_PAGE_MASK), static_cast<u32>(sptr_end - sptr));
std::memcpy(reinterpret_cast<void*>(vmv.assumePtr(mem)), sptr, remaining_in_page);
sptr += remaining_in_page;
mem += remaining_in_page;
}
return true;
}
// --------------------------------------------------------------------------------------
// TLB Miss / BusError Handlers
// --------------------------------------------------------------------------------------
// These are valid VM memory errors that should typically be handled by the VM itself via
// its own cpu exception system.
//
// [TODO] Add first-chance debugging hooks to these exceptions!
//
// Important recompiler note: Mid-block Exception handling isn't reliable *yet* because
// memory ops don't flush the PC prior to invoking the indirect handlers.
static void GoemonTlbMissDebug()
{
// 0x3d5580 is the address of the TLB cache
GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580];
for (u32 i = 0; i < 150; i++)
{
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);
else if (tlb[i].low_add != tlb[i].high_add)
DevCon.WriteLn("GoemonTlbMissDebug: Entry %d is invalid. 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);
}
}
void GoemonPreloadTlb()
{
// 0x3d5580 is the address of the TLB cache table
GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580];
for (u32 i = 0; i < 150; i++)
{
if (tlb[i].valid == 0x1 && tlb[i].low_add != tlb[i].high_add)
{
u32 size = tlb[i].high_add - tlb[i].low_add;
u32 vaddr = tlb[i].low_add;
u32 paddr = tlb[i].physical_add;
// 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) {
auto vmv = vtlbdata.vmap[vaddr >> VTLB_PAGE_BITS];
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);
vtlb_VMap(vaddr, paddr, size);
vtlb_VMap(0x20000000 | vaddr, paddr, size);
}
}
}
}
void GoemonUnloadTlb(u32 key)
{
// 0x3d5580 is the address of the TLB cache table
GoemonTlb* tlb = (GoemonTlb*)&eeMem->Main[0x3d5580];
for (u32 i = 0; i < 150; i++)
{
if (tlb[i].key == key)
{
if (tlb[i].valid == 0x1)
{
u32 size = tlb[i].high_add - 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);
vtlb_VMapUnmap(vaddr, size);
vtlb_VMapUnmap(0x20000000 | vaddr, size);
// Unmap the tlb in game cache table
// Note: Game copy FEFEFEFE for others data
tlb[i].valid = 0;
tlb[i].key = 0xFEFEFEFE;
tlb[i].low_add = 0xFEFEFEFE;
tlb[i].high_add = 0xFEFEFEFE;
}
else
{
DevCon.Error("GoemonUnloadTlb: Entry %d is not valid. Key %x", i, tlb[i].key);
}
}
}
}
// Generates a tlbMiss Exception
static __ri void vtlb_Miss(u32 addr, u32 mode)
{
if (EmuConfig.Gamefixes.GoemonTlbHack)
GoemonTlbMissDebug();
// Interpreter: raise the exception, then CancelInstruction stops the current
// instruction so the exception vector is dispatched immediately.
if (Cpu == &intCpu)
{
if (mode)
cpuTlbMissW(addr, cpuRegs.branch);
else
cpuTlbMissR(addr, cpuRegs.branch);
Cpu->CancelInstruction();
return;
}
const std::string message(fmt::format("TLB Miss, pc=0x{:x} addr=0x{:x} [{}]", cpuRegs.pc, addr, mode ? "store" : "load"));
if (EmuConfig.Cpu.Recompiler.PauseOnTLBMiss)
{
// Pause, let the user try to figure out what went wrong in the debugger.
Host::ReportErrorAsync("R5900 Exception", message);
VMManager::SetPaused(true);
Cpu->ExitExecution();
return;
}
// Recompilers: log and continue. Nothing diverts a block to the vector, so
// raising here would only latch Status.EXL, and cpuException skips the EPC
// write while EXL is set — every later exception would inherit this one's
// EPC.
static int spamStop = 0;
if (spamStop++ < 50 || IsDevBuild)
Console.Error(message);
}
// 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
// time of the exception.
static __ri void vtlb_BusError(u32 addr, u32 mode)
{
const std::string message(fmt::format("Bus Error, addr=0x{:x} [{}]", addr, mode ? "store" : "load"));
if (EmuConfig.Cpu.Recompiler.PauseOnTLBMiss)
{
// Pause, let the user try to figure out what went wrong in the debugger.
Host::ReportErrorAsync("R5900 Exception", message);
VMManager::SetPaused(true);
Cpu->ExitExecution();
return;
}
Console.Error(message);
}
// clang-format off
template <typename OperandType>
static OperandType vtlbUnmappedVReadSm(u32 addr) { vtlb_Miss(addr, 0); return 0; }
static RETURNS_R128 vtlbUnmappedVReadLg(u32 addr) { vtlb_Miss(addr, 0); return r128_zero(); }
template <typename OperandType>
static void vtlbUnmappedVWriteSm(u32 addr, OperandType data) { vtlb_Miss(addr, 1); }
static void TAKES_R128 vtlbUnmappedVWriteLg(u32 addr, r128 data) { vtlb_Miss(addr, 1); }
template <typename OperandType>
static OperandType vtlbUnmappedPReadSm(u32 addr) {
vtlb_BusError(addr, 0);
if (!CHECK_EEREC && CHECK_CACHE && CheckCache(addr)){
switch (sizeof(OperandType)) {
case 1: return readCache8(addr, false);
case 2: return readCache16(addr, false);
case 4: return readCache32(addr, false);
case 8: return readCache64(addr, false);
default: pxFail("Invalid data size for unmapped physical cache load");
}
}
return 0;
}
static RETURNS_R128 vtlbUnmappedPReadLg(u32 addr) { vtlb_BusError(addr, 0); if (!CHECK_EEREC && CHECK_CACHE && CheckCache(addr)){ return readCache128(addr, false); } return r128_zero(); }
template <typename OperandType>
static void vtlbUnmappedPWriteSm(u32 addr, OperandType data) {
vtlb_BusError(addr, 1);
if (!CHECK_EEREC && CHECK_CACHE && CheckCache(addr)) {
switch (sizeof(OperandType)) {
case 1: writeCache8(addr, data, false); break;
case 2: writeCache16(addr, data, false); break;
case 4: writeCache32(addr, data, false); break;
case 8: writeCache64(addr, data, false); break;
default: pxFail("Invalid data size for unmapped physical cache store");
}
}
}
static void TAKES_R128 vtlbUnmappedPWriteLg(u32 addr, r128 data) { vtlb_BusError(addr, 1); if (!CHECK_EEREC && CHECK_CACHE && CheckCache(addr)) { writeCache128(addr, reinterpret_cast<mem128_t*>(&data) /*Safe??*/, false); }}
// clang-format on
// --------------------------------------------------------------------------------------
// VTLB mapping errors
// --------------------------------------------------------------------------------------
// These errors are assertion/logic errors that should never occur if PCSX2 has been initialized
// properly. All addressable physical memory should be configured as TLBMiss or Bus Error.
//
static mem8_t vtlbDefaultPhyRead8(u32 addr)
{
pxFail(fmt::format("(VTLB) Attempted read8 from unmapped physical address @ 0x{:08X}.", addr).c_str());
return 0;
}
static mem16_t vtlbDefaultPhyRead16(u32 addr)
{
pxFail(fmt::format("(VTLB) Attempted read16 from unmapped physical address @ 0x{:08X}.", addr).c_str());
return 0;
}
static mem32_t vtlbDefaultPhyRead32(u32 addr)
{
pxFail(fmt::format("(VTLB) Attempted read32 from unmapped physical address @ 0x{:08X}.", addr).c_str());
return 0;
}
static mem64_t vtlbDefaultPhyRead64(u32 addr)
{
pxFail(fmt::format("(VTLB) Attempted read64 from unmapped physical address @ 0x{:08X}.", addr).c_str());
return 0;
}
static RETURNS_R128 vtlbDefaultPhyRead128(u32 addr)
{
pxFail(fmt::format("(VTLB) Attempted read128 from unmapped physical address @ 0x{:08X}.", addr).c_str());
return r128_zero();
}
static void vtlbDefaultPhyWrite8(u32 addr, mem8_t data)
{
pxFail(fmt::format("(VTLB) Attempted write8 to unmapped physical address @ 0x{:08X}.", addr).c_str());
}
static void vtlbDefaultPhyWrite16(u32 addr, mem16_t data)
{
pxFail(fmt::format("(VTLB) Attempted write16 to unmapped physical address @ 0x{:08X}.", addr).c_str());
}
static void vtlbDefaultPhyWrite32(u32 addr, mem32_t data)
{
pxFail(fmt::format("(VTLB) Attempted write32 to unmapped physical address @ 0x{:08X}.", addr).c_str());
}
static void vtlbDefaultPhyWrite64(u32 addr, mem64_t data)
{
pxFail(fmt::format("(VTLB) Attempted write64 to unmapped physical address @ 0x{:08X}.", addr).c_str());
}
static void TAKES_R128 vtlbDefaultPhyWrite128(u32 addr, r128 data)
{
pxFail(fmt::format("(VTLB) Attempted write128 to unmapped physical address @ 0x{:08X}.", addr).c_str());
}
// ===========================================================================================
// VTLB Public API -- Init/Term/RegisterHandler stuff
// ===========================================================================================
//
// Assigns or re-assigns the callbacks for a VTLB memory handler. The handler defines specific behavior
// for how memory pages bound to the handler are read from / written to. If any of the handler pointers
// are NULL, the memory operations will be mapped to the BusError handler (thus generating BusError
// exceptions if the emulated app attempts to access them).
//
// Note: All handlers persist across calls to vtlb_Reset(), but are wiped/invalidated by calls to vtlb_Init()
//
__ri void vtlb_ReassignHandler(vtlbHandler rv,
vtlbMemR8FP* r8, vtlbMemR16FP* r16, vtlbMemR32FP* r32, vtlbMemR64FP* r64, vtlbMemR128FP* r128,
vtlbMemW8FP* w8, vtlbMemW16FP* w16, vtlbMemW32FP* w32, vtlbMemW64FP* w64, vtlbMemW128FP* w128)
{
pxAssume(rv < VTLB_HANDLER_ITEMS);
vtlbdata.RWFT[0][0][rv] = (void*)((r8 != 0) ? r8 : vtlbDefaultPhyRead8);
vtlbdata.RWFT[1][0][rv] = (void*)((r16 != 0) ? r16 : vtlbDefaultPhyRead16);
vtlbdata.RWFT[2][0][rv] = (void*)((r32 != 0) ? r32 : vtlbDefaultPhyRead32);
vtlbdata.RWFT[3][0][rv] = (void*)((r64 != 0) ? r64 : vtlbDefaultPhyRead64);
vtlbdata.RWFT[4][0][rv] = (void*)((r128 != 0) ? r128 : vtlbDefaultPhyRead128);
vtlbdata.RWFT[0][1][rv] = (void*)((w8 != 0) ? w8 : vtlbDefaultPhyWrite8);
vtlbdata.RWFT[1][1][rv] = (void*)((w16 != 0) ? w16 : vtlbDefaultPhyWrite16);
vtlbdata.RWFT[2][1][rv] = (void*)((w32 != 0) ? w32 : vtlbDefaultPhyWrite32);
vtlbdata.RWFT[3][1][rv] = (void*)((w64 != 0) ? w64 : vtlbDefaultPhyWrite64);
vtlbdata.RWFT[4][1][rv] = (void*)((w128 != 0) ? w128 : vtlbDefaultPhyWrite128);
}
vtlbHandler vtlb_NewHandler()
{
pxAssertMsg(vtlbHandlerCount < VTLB_HANDLER_ITEMS, "VTLB handler count overflow!");
return vtlbHandlerCount++;
}
// Registers a handler into the VTLB's internal handler array. The handler defines specific behavior
// for how memory pages bound to the handler are read from / written to. If any of the handler pointers
// are NULL, the memory operations will be mapped to the BusError handler (thus generating BusError
// exceptions if the emulated app attempts to access them).
//
// Note: All handlers persist across calls to vtlb_Reset(), but are wiped/invalidated by calls to vtlb_Init()
//
// 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,
vtlbMemW8FP* w8, vtlbMemW16FP* w16, vtlbMemW32FP* w32, vtlbMemW64FP* w64, vtlbMemW128FP* w128)
{
vtlbHandler rv = vtlb_NewHandler();
vtlb_ReassignHandler(rv, r8, r16, r32, r64, r128, w8, w16, w32, w64, w128);
return rv;
}
bool vtlb_IsUnmappedHandlerID(vtlbHandler id)
{
return id == UnmappedVirtHandler || id == UnmappedPhyHandler;
}
// Maps the given hander (created with vtlb_RegisterHandler) to the specified memory region.
// New mappings always assume priority over previous mappings, so place "generic" mappings for
// large areas of memory first, and then specialize specific small regions of memory afterward.
// A single handler can be mapped to many different regions by using multiple calls to this
// function.
//
// The memory region start and size parameters must be pagesize aligned.
void vtlb_MapHandler(vtlbHandler handler, u32 start, u32 size)
{
verify(0 == (start & VTLB_PAGE_MASK));
verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
u32 end = start + (size - VTLB_PAGE_SIZE);
pxAssume((end >> VTLB_PAGE_BITS) < (sizeof(vtlbdata.pmap) / sizeof(vtlbdata.pmap[0])));
while (start <= end)
{
vtlbdata.pmap[start >> VTLB_PAGE_BITS] = VTLBPhysical::fromHandler(handler);
start += VTLB_PAGE_SIZE;
}
}
void vtlb_MapBlock(void* base, u32 start, u32 size, u32 blocksize)
{
verify(0 == (start & VTLB_PAGE_MASK));
verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
if (!blocksize)
blocksize = size;
verify(0 == (blocksize & VTLB_PAGE_MASK) && blocksize > 0);
verify(0 == (size % blocksize));
sptr baseint = (sptr)base;
u32 end = start + (size - VTLB_PAGE_SIZE);
verify((end >> VTLB_PAGE_BITS) < std::size(vtlbdata.pmap));
while (start <= end)
{
u32 loopsz = blocksize;
sptr ptr = baseint;
while (loopsz > 0)
{
vtlbdata.pmap[start >> VTLB_PAGE_BITS] = VTLBPhysical::fromPointer(ptr);
start += VTLB_PAGE_SIZE;
ptr += VTLB_PAGE_SIZE;
loopsz -= VTLB_PAGE_SIZE;
}
}
}
void vtlb_Mirror(u32 new_region, u32 start, u32 size)
{
verify(0 == (new_region & VTLB_PAGE_MASK));
verify(0 == (start & VTLB_PAGE_MASK));
verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
u32 end = start + (size - VTLB_PAGE_SIZE);
verify((end >> VTLB_PAGE_BITS) < std::size(vtlbdata.pmap));
while (start <= end)
{
vtlbdata.pmap[start >> VTLB_PAGE_BITS] = vtlbdata.pmap[new_region >> VTLB_PAGE_BITS];
start += VTLB_PAGE_SIZE;
new_region += VTLB_PAGE_SIZE;
}
}
__fi void* vtlb_GetPhyPtr(u32 paddr)
{
if (paddr >= VTLB_PMAP_SZ || vtlbdata.pmap[paddr >> VTLB_PAGE_BITS].isHandler())
return NULL;
else
return reinterpret_cast<void*>(vtlbdata.pmap[paddr >> VTLB_PAGE_BITS].assumePtr() + (paddr & VTLB_PAGE_MASK));
}
__fi u32 vtlb_V2P(u32 vaddr)
{
u32 paddr = vtlbdata.ppmap[vaddr >> VTLB_PAGE_BITS];
paddr |= vaddr & VTLB_PAGE_MASK;
return paddr;
}
static constexpr bool vtlb_MismatchedHostPageSize()
{
return (__pagesize != VTLB_PAGE_SIZE);
}
static bool vtlb_IsHostAligned(u32 paddr)
{
if constexpr (!vtlb_MismatchedHostPageSize())
return true;
return ((paddr & __pagemask) == 0);
}
static u32 vtlb_HostPage(u32 page)
{
if constexpr (!vtlb_MismatchedHostPageSize())
return page;
return page >> (__pageshift - VTLB_PAGE_BITS);
}
static u32 vtlb_HostAlignOffset(u32 offset)
{
if constexpr (!vtlb_MismatchedHostPageSize())
return offset;
return offset & ~__pagemask;
}
static bool vtlb_IsHostCoalesced(u32 page)
{
if constexpr (__pagesize == VTLB_PAGE_SIZE)
{
return true;
}
else
{
static constexpr u32 shift = __pageshift - VTLB_PAGE_BITS;
static constexpr u32 count = (1u << shift);
static constexpr u32 mask = count - 1;
const u32 base = page & ~mask;
const u32 base_offset = s_fastmem_virtual_mapping[base];
if ((base_offset & __pagemask) != 0)
return false;
for (u32 i = 0, expected_offset = base_offset; i < count; i++, expected_offset += VTLB_PAGE_SIZE)
{
if (s_fastmem_virtual_mapping[base + i] != expected_offset)
return false;
}
return true;
}
}
static bool vtlb_GetMainMemoryOffsetFromPtr(uptr ptr, u32* mainmem_offset, u32* mainmem_size, PageProtectionMode* prot)
{
const uptr page_end = ptr + VTLB_PAGE_SIZE;
// EE memory and ROMs.
if (ptr >= (uptr)eeMem->Main && page_end <= (uptr)eeMem->ZeroRead)
{
const u32 eemem_offset = static_cast<u32>(ptr - (uptr)eeMem->Main);
const bool writeable = ((eemem_offset < Ps2MemSize::ExposedRam) ? (mmap_GetRamPageInfo(eemem_offset) != ProtMode_Write) : true);
*mainmem_offset = (eemem_offset + HostMemoryMap::EEmemOffset);
*mainmem_size = (offsetof(EEVM_MemoryAllocMess, ZeroRead) - eemem_offset);
*prot = PageProtectionMode().Read().Write(writeable);
return true;
}
// IOP memory.
if (ptr >= (uptr)iopMem->Main && page_end <= (uptr)iopMem->P)
{
const u32 iopmem_offset = static_cast<u32>(ptr - (uptr)iopMem->Main);
*mainmem_offset = iopmem_offset + HostMemoryMap::IOPmemOffset;
*mainmem_size = (offsetof(IopVM_MemoryAllocMess, P) - iopmem_offset);
*prot = PageProtectionMode().Read().Write();
return true;
}
// VU memory - this includes both data and code for VU0/VU1.
// Practically speaking, this is only data, because the code goes through a handler.
if (ptr >= (uptr)SysMemory::GetVUMem() && page_end <= (uptr)SysMemory::GetVUMemEnd())
{
const u32 vumem_offset = static_cast<u32>(ptr - (uptr)SysMemory::GetVUMem());
*mainmem_offset = vumem_offset + HostMemoryMap::VUmemOffset;
*mainmem_size = HostMemoryMap::VUmemSize - vumem_offset;
*prot = PageProtectionMode().Read().Write();
return true;
}
// We end up with some unknown mappings here; currently the IOP memory, instead of being physically mapped
// as 2MB, ends up being mapped as 8MB. But this shouldn't be virtual mapped anyway, so fallback to slowmem
// in such cases.
return false;
}
static bool vtlb_GetMainMemoryOffset(u32 paddr, u32* mainmem_offset, u32* mainmem_size, PageProtectionMode* prot)
{
if (paddr >= VTLB_PMAP_SZ)
return false;
// Handlers aren't in our shared memory, obviously.
const VTLBPhysical& vm = vtlbdata.pmap[paddr >> VTLB_PAGE_BITS];
if (vm.isHandler())
return false;
return vtlb_GetMainMemoryOffsetFromPtr(vm.raw(), mainmem_offset, mainmem_size, prot);
}
static void vtlb_CreateFastmemMapping(u32 vaddr, u32 mainmem_offset, const PageProtectionMode& mode)
{
// Bail if the fastmem area was never allocated (e.g. 4 GB reservation
// failed on a low-memory iOS device). Mirrors vtlb_RemoveFastmemMappings.
if (s_fastmem_virtual_mapping.empty())
return;
FASTMEM_LOG("Create fastmem mapping @ vaddr %08X mainmem %08X", vaddr, mainmem_offset);
const u32 page = vaddr / VTLB_PAGE_SIZE;
if (s_fastmem_virtual_mapping[page] == mainmem_offset)
{
// current mapping is fine
return;
}
if (s_fastmem_virtual_mapping[page] != NO_FASTMEM_MAPPING)
{
// current mapping needs to be removed
const bool was_coalesced = vtlb_IsHostCoalesced(page);
s_fastmem_virtual_mapping[page] = NO_FASTMEM_MAPPING;
if (was_coalesced && !s_fastmem_area->Unmap(s_fastmem_area->PagePointer(vtlb_HostPage(page)), __pagesize))
Console.Error("Failed to unmap vaddr %08X", vaddr);
// remove reverse mapping
auto range = s_fastmem_physical_mapping.equal_range(mainmem_offset);
for (auto it = range.first; it != range.second;)
{
auto this_it = it++;
if (this_it->second == vaddr)
s_fastmem_physical_mapping.erase(this_it);
}
}
s_fastmem_virtual_mapping[page] = mainmem_offset;
if (vtlb_IsHostCoalesced(page))
{
const u32 host_page = vtlb_HostPage(page);
const u32 host_offset = vtlb_HostAlignOffset(mainmem_offset);
if (!s_fastmem_area->Map(SysMemory::GetDataFileHandle(), host_offset,
s_fastmem_area->PagePointer(host_page), __pagesize, mode))
{
Console.Error("Failed to map vaddr %08X to mainmem offset %08X", vtlb_HostAlignOffset(vaddr), host_offset);
s_fastmem_virtual_mapping[page] = NO_FASTMEM_MAPPING;
return;
}
}
s_fastmem_physical_mapping.emplace(mainmem_offset, vaddr);
}
static void vtlb_RemoveFastmemMapping(u32 vaddr)
{
const u32 page = vaddr / VTLB_PAGE_SIZE;
if (s_fastmem_virtual_mapping[page] == NO_FASTMEM_MAPPING)
return;
const u32 mainmem_offset = s_fastmem_virtual_mapping[page];
const bool was_coalesced = vtlb_IsHostCoalesced(page);
FASTMEM_LOG("Remove fastmem mapping @ vaddr %08X mainmem %08X", vaddr, mainmem_offset);
s_fastmem_virtual_mapping[page] = NO_FASTMEM_MAPPING;
if (was_coalesced && !s_fastmem_area->Unmap(s_fastmem_area->PagePointer(vtlb_HostPage(page)), __pagesize))
Console.Error("Failed to unmap vaddr %08X", vtlb_HostAlignOffset(vaddr));
// remove from reverse map
auto range = s_fastmem_physical_mapping.equal_range(mainmem_offset);
for (auto it = range.first; it != range.second;)
{
auto this_it = it++;
if (this_it->second == vaddr)
s_fastmem_physical_mapping.erase(this_it);
}
}
static void vtlb_RemoveFastmemMappings(u32 vaddr, u32 size)
{
// When the 4 GB fastmem area reservation fails (e.g. on low-memory iOS
// devices like the iPhone SE 2), s_fastmem_virtual_mapping is never
// resized and remains empty. Indexing it would dereference NULL, so bail
// out early — there are no mappings to remove.
if (s_fastmem_virtual_mapping.empty())
return;
pxAssert((vaddr & VTLB_PAGE_MASK) == 0);
pxAssert(size > 0 && (size & VTLB_PAGE_MASK) == 0);
const u32 num_pages = size / VTLB_PAGE_SIZE;
for (u32 i = 0; i < num_pages; i++, vaddr += VTLB_PAGE_SIZE)
vtlb_RemoveFastmemMapping(vaddr);
}
static void vtlb_RemoveFastmemMappings()
{
if (s_fastmem_virtual_mapping.empty())
{
// not initialized yet
return;
}
for (u32 page = 0; page < FASTMEM_PAGE_COUNT; page++)
{
if (s_fastmem_virtual_mapping[page] == NO_FASTMEM_MAPPING)
continue;
if (vtlb_IsHostCoalesced(page))
{
if (!s_fastmem_area->Unmap(s_fastmem_area->PagePointer(vtlb_HostPage(page)), __pagesize))
Console.Error("Failed to unmap vaddr %08X", page * __pagesize);
}
s_fastmem_virtual_mapping[page] = NO_FASTMEM_MAPPING;
}
s_fastmem_physical_mapping.clear();
}
bool vtlb_ResolveFastmemMapping(uptr* addr)
{
uptr uaddr = *addr;
uptr fastmem_start = (uptr)vtlbdata.fastmem_base;
uptr fastmem_end = fastmem_start + 0xFFFFFFFFu;
if (uaddr < fastmem_start || uaddr > fastmem_end)
return false;
const u32 vaddr = static_cast<u32>(uaddr - fastmem_start);
FASTMEM_LOG("Trying to resolve %p (vaddr %08X)", (void*)uaddr, vaddr);
const u32 vpage = vaddr / VTLB_PAGE_SIZE;
if (s_fastmem_virtual_mapping[vpage] == NO_FASTMEM_MAPPING)
{
FASTMEM_LOG("%08X is not virtual mapped", vaddr);
return false;
}
const u32 mainmem_offset = s_fastmem_virtual_mapping[vpage] + (vaddr & VTLB_PAGE_MASK);
FASTMEM_LOG("Resolved %p (vaddr %08X) to mainmem offset %08X", uaddr, vaddr, mainmem_offset);
*addr = ((uptr)SysMemory::GetDataPtr(0)) + mainmem_offset;
return true;
}
bool vtlb_GetGuestAddress(uptr host_addr, u32* guest_addr)
{
// Explicit unsigned bound rather than `fastmem_start + 0xFFFFFFFF` + a
// two-sided compare: that addition overflows a 64-bit uptr when the fastmem
// mapping lands within 4 GB of the top of the address space (exotic kernels /
// high-mmap allocators), wrapping fastmem_end below fastmem_start and silently
// rejecting every valid in-range address. Subtraction-first wraps a below-base
// host to a huge offset, so a single `offset >= FASTMEM_AREA_SIZE` check is
// overflow-proof regardless of base.
const uptr fastmem_start = (uptr)vtlbdata.fastmem_base;
const uptr offset = host_addr - fastmem_start;
if (offset >= FASTMEM_AREA_SIZE)
return false;
*guest_addr = static_cast<u32>(offset);
return true;
}
void vtlb_UpdateFastmemProtection(u32 paddr, u32 size, PageProtectionMode prot)
{
if (!CHECK_FASTMEM)
return;
pxAssert((paddr & VTLB_PAGE_MASK) == 0);
pxAssert(size > 0 && (size & VTLB_PAGE_MASK) == 0);
u32 mainmem_start, mainmem_size;
PageProtectionMode old_prot;
if (!vtlb_GetMainMemoryOffset(paddr, &mainmem_start, &mainmem_size, &old_prot))
return;
FASTMEM_LOG("UpdateFastmemProtection %08X mmoffset %08X %08X", paddr, mainmem_start, size);
u32 current_mainmem = mainmem_start;
const u32 num_pages = std::min(size, mainmem_size) / VTLB_PAGE_SIZE;
for (u32 i = 0; i < num_pages; i++, current_mainmem += VTLB_PAGE_SIZE)
{
// update virtual mapping mapping
auto range = s_fastmem_physical_mapping.equal_range(current_mainmem);
for (auto it = range.first; it != range.second; ++it)
{
FASTMEM_LOG(" valias %08X (size %u)", it->second, VTLB_PAGE_SIZE);
if (vtlb_IsHostAligned(it->second))
HostSys::MemProtect(s_fastmem_area->OffsetPointer(it->second), __pagesize, prot);
}
}
}
void vtlb_ClearLoadStoreInfo()
{
s_fastmem_backpatch_info.clear();
s_fastmem_faulting_pcs.clear();
}
void vtlb_AddLoadStoreInfo(uptr code_address, u32 code_size, u32 guest_pc, u32 gpr_bitmask, u32 fpr_bitmask, u8 address_register, u8 data_register, u8 size_in_bits, bool is_signed, bool is_load, bool is_fpr)
{
pxAssert(code_size < std::numeric_limits<u8>::max());
auto iter = s_fastmem_backpatch_info.find(code_address);
if (iter != s_fastmem_backpatch_info.end())
s_fastmem_backpatch_info.erase(iter);
LoadstoreBackpatchInfo info{guest_pc, gpr_bitmask, fpr_bitmask, static_cast<u8>(code_size), address_register, data_register, size_in_bits, is_signed, is_load, is_fpr};
s_fastmem_backpatch_info.emplace(code_address, info);
}
bool vtlb_BackpatchLoadStore(uptr code_address, uptr fault_address)
{
uptr fastmem_start = (uptr)vtlbdata.fastmem_base;
uptr fastmem_end = fastmem_start + 0xFFFFFFFFu;
if (fault_address < fastmem_start || fault_address > fastmem_end)
return false;
auto iter = s_fastmem_backpatch_info.find(code_address);
if (iter == s_fastmem_backpatch_info.end())
return false;
const LoadstoreBackpatchInfo& info = iter->second;
const u32 guest_addr = static_cast<u32>(fault_address - fastmem_start);
vtlb_DynBackpatchLoadStore(code_address, info.code_size, info.guest_pc, guest_addr,
info.gpr_bitmask, info.fpr_bitmask, info.address_register, info.data_register,
info.size_in_bits, info.is_signed, info.is_load, info.is_fpr);
// queue block for recompilation later
Cpu->Clear(info.guest_pc, 1);
// and store the pc in the faulting list, so that we don't emit another fastmem loadstore
auto it = std::lower_bound(s_fastmem_faulting_pcs.begin(), s_fastmem_faulting_pcs.end(), info.guest_pc);
if (it == s_fastmem_faulting_pcs.end() || *it != info.guest_pc)
s_fastmem_faulting_pcs.insert(it, info.guest_pc);
s_fastmem_backpatch_info.erase(iter);
return true;
}
bool vtlb_IsFaultingPC(u32 guest_pc)
{
return std::binary_search(s_fastmem_faulting_pcs.begin(), s_fastmem_faulting_pcs.end(), guest_pc);
}
//virtual mappings
//TODO: Add invalid paddr checks
void vtlb_VMap(u32 vaddr, u32 paddr, u32 size)
{
verify(0 == (vaddr & VTLB_PAGE_MASK));
verify(0 == (paddr & VTLB_PAGE_MASK));
verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
if (CHECK_FASTMEM)
{
const u32 num_pages = size / VTLB_PAGE_SIZE;
u32 current_vaddr = vaddr;
u32 current_paddr = paddr;
for (u32 i = 0; i < num_pages; i++, current_vaddr += VTLB_PAGE_SIZE, current_paddr += VTLB_PAGE_SIZE)
{
u32 hoffset, hsize;
PageProtectionMode mode;
if (vtlb_GetMainMemoryOffset(current_paddr, &hoffset, &hsize, &mode))
vtlb_CreateFastmemMapping(current_vaddr, hoffset, mode);
else
vtlb_RemoveFastmemMapping(current_vaddr);
}
}
while (size > 0)
{
VTLBVirtual vmv;
if (paddr >= VTLB_PMAP_SZ)
vmv = VTLBVirtual(VTLBPhysical::fromHandler(UnmappedPhyHandler), paddr, vaddr);
else
vmv = VTLBVirtual(vtlbdata.pmap[paddr >> VTLB_PAGE_BITS], paddr, vaddr);
vtlbdata.vmap[vaddr >> VTLB_PAGE_BITS] = vmv;
if (vtlbdata.ppmap)
{
if (!(vaddr & 0x80000000)) // those address are already physical don't change them
vtlbdata.ppmap[vaddr >> VTLB_PAGE_BITS] = paddr & ~VTLB_PAGE_MASK;
}
vaddr += VTLB_PAGE_SIZE;
paddr += VTLB_PAGE_SIZE;
size -= VTLB_PAGE_SIZE;
}
}
void vtlb_VMapBuffer(u32 vaddr, void* buffer, u32 size)
{
verify(0 == (vaddr & VTLB_PAGE_MASK));
verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
if (CHECK_FASTMEM)
{
if (buffer == eeMem->Scratch && size == Ps2MemSize::Scratch)
{
u32 fm_vaddr = vaddr;
u32 fm_hostoffset = HostMemoryMap::EEmemOffset + offsetof(EEVM_MemoryAllocMess, Scratch);
PageProtectionMode mode = PageProtectionMode().Read().Write();
for (u32 i = 0; i < (Ps2MemSize::Scratch / VTLB_PAGE_SIZE); i++, fm_vaddr += VTLB_PAGE_SIZE, fm_hostoffset += VTLB_PAGE_SIZE)
vtlb_CreateFastmemMapping(fm_vaddr, fm_hostoffset, mode);
}
else
{
vtlb_RemoveFastmemMappings(vaddr, size);
}
}
uptr bu8 = (uptr)buffer;
while (size > 0)
{
vtlbdata.vmap[vaddr >> VTLB_PAGE_BITS] = VTLBVirtual::fromPointer(bu8, vaddr);
vaddr += VTLB_PAGE_SIZE;
bu8 += VTLB_PAGE_SIZE;
size -= VTLB_PAGE_SIZE;
}
}
void vtlb_VMapUnmap(u32 vaddr, u32 size)
{
verify(0 == (vaddr & VTLB_PAGE_MASK));
verify(0 == (size & VTLB_PAGE_MASK) && size > 0);
vtlb_RemoveFastmemMappings(vaddr, size);
while (size > 0)
{
vtlbdata.vmap[vaddr >> VTLB_PAGE_BITS] = VTLBVirtual(VTLBPhysical::fromHandler(UnmappedVirtHandler), vaddr, vaddr);
vaddr += VTLB_PAGE_SIZE;
size -= VTLB_PAGE_SIZE;
}
}
// vtlb_Init -- Clears vtlb handlers and memory mappings.
void vtlb_Init()
{
vtlbHandlerCount = 0;
std::memset(vtlbdata.RWFT, 0, sizeof(vtlbdata.RWFT));
#define VTLB_BuildUnmappedHandler(baseName) \
baseName##ReadSm<mem8_t>, baseName##ReadSm<mem16_t>, baseName##ReadSm<mem32_t>, \
baseName##ReadSm<mem64_t>, baseName##ReadLg, \
baseName##WriteSm<mem8_t>, baseName##WriteSm<mem16_t>, baseName##WriteSm<mem32_t>, \
baseName##WriteSm<mem64_t>, baseName##WriteLg
//Register default handlers
//Unmapped Virt handlers _MUST_ be registered first.
//On address translation the top bit cannot be preserved.This is not normaly a problem since
//the physical address space can be 'compressed' to just 29 bits.However, to properly handle exceptions
//there must be a way to get the full address back.Thats why i use these 2 functions and encode the hi bit directly into em :)
UnmappedVirtHandler = vtlb_RegisterHandler(VTLB_BuildUnmappedHandler(vtlbUnmappedV));
UnmappedPhyHandler = vtlb_RegisterHandler(VTLB_BuildUnmappedHandler(vtlbUnmappedP));
DefaultPhyHandler = vtlb_RegisterHandler(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
//done !
//Setup the initial mappings
vtlb_MapHandler(DefaultPhyHandler, 0, VTLB_PMAP_SZ);
//Set the V space as unmapped
vtlb_VMapUnmap(0, (VTLB_VMAP_ITEMS - 1) * VTLB_PAGE_SIZE);
//yeah i know, its stupid .. but this code has to be here for now ;p
vtlb_VMapUnmap((VTLB_VMAP_ITEMS - 1) * VTLB_PAGE_SIZE, VTLB_PAGE_SIZE);
// The LUT is only used for 1 game so we allocate it only when the gamefix is enabled (save 4MB)
if (EmuConfig.Gamefixes.GoemonTlbHack)
vtlb_Alloc_Ppmap();
}
// vtlb_Reset -- Performs a COP0-level reset of the PS2's TLB.
// This function should probably be part of the COP0 rather than here in VTLB.
void vtlb_Reset()
{
vtlb_RemoveFastmemMappings();
for (int i = 0; i < 48; i++)
UnmapTLB(tlb[i], i);
}
void vtlb_Shutdown()
{
vtlb_RemoveFastmemMappings();
s_fastmem_backpatch_info.clear();
s_fastmem_faulting_pcs.clear();
}
void vtlb_ResetFastmem()
{
DevCon.WriteLn("Resetting fastmem mappings...");
vtlb_RemoveFastmemMappings();
s_fastmem_backpatch_info.clear();
s_fastmem_faulting_pcs.clear();
if (!CHECK_FASTMEM || !CHECK_EEREC || !vtlbdata.vmap)
return;
// If the reservation failed at boot, fastmem is permanently unavailable --
// don't let a config change resurrect it.
if (s_fastmem_area_unavailable)
return;
// we need to go through and look at the vtlb pointers, to remap the host area
for (size_t i = 0; i < VTLB_VMAP_ITEMS; i++)
{
const VTLBVirtual& vm = vtlbdata.vmap[i];
const u32 vaddr = static_cast<u32>(i) << VTLB_PAGE_BITS;
if (vm.isHandler(vaddr))
{
// Handlers should be unmapped.
continue;
}
// Check if it's a physical mapping to our main memory area.
u32 mainmem_offset, mainmem_size;
PageProtectionMode prot;
if (vtlb_GetMainMemoryOffsetFromPtr(vm.assumePtr(vaddr), &mainmem_offset, &mainmem_size, &prot))
vtlb_CreateFastmemMapping(vaddr, mainmem_offset, prot);
}
}
// Reserves the vtlb core allocation used by various emulation components!
// [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
// default is used.
bool vtlb_Core_Alloc()
{
static constexpr size_t VMAP_SIZE = sizeof(VTLBVirtual) * VTLB_VMAP_ITEMS;
static_assert(HostMemoryMap::VTLBVirtualMapSize == VMAP_SIZE);
pxAssert(!vtlbdata.vmap && !vtlbdata.fastmem_base && !s_fastmem_area);
vtlbdata.vmap = reinterpret_cast<VTLBVirtual*>(SysMemory::GetVTLBVirtualMap());
pxAssert(!s_fastmem_area);
if (!SysMemory::HasCodeMemory())
{
// Interpreter execution never emits fastmem accesses. Avoid reserving a
// 4 GB virtual address range that cannot improve the no-JIT backend.
vtlbdata.fastmem_base = 0;
EmuConfig.Cpu.Recompiler.EnableFastmem = false;
Console.WriteLn("Fastmem disabled: executable code memory is unavailable");
}
else
{
s_fastmem_area = SharedMemoryMappingArea::Create(FASTMEM_AREA_SIZE);
if (!s_fastmem_area)
{
// 4 GB virtual reservation can fail on devices with limited VA space
// (e.g. iPhone SE 2 with 4 GB RAM under LiveContainer). On iOS we
// continue without fastmem instead of aborting boot.
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
Console.Warning("Fastmem disabled: 4 GB virtual-address reservation failed; continuing without fastmem");
vtlbdata.fastmem_base = 0;
EmuConfig.Cpu.Recompiler.EnableFastmem = false;
s_fastmem_area_unavailable = true;
#else
Host::ReportErrorAsync("Error", "Failed to allocate fastmem area");
return false;
#endif
}
}
// Force-disable fastmem if explicitly requested via env var or INI.
if (s_fastmem_area)
{
const char* env = std::getenv("iPSX2_FORCE_NO_FASTMEM");
const bool env_force = env && std::atoi(env) == 1;
const bool ini_disabled = !EmuConfig.Cpu.Recompiler.EnableFastmem;
if (env_force || ini_disabled)
{
Console.Warning("Fastmem disabled (env=%d ini=%d); releasing fastmem area",
(int)env_force, (int)ini_disabled);
s_fastmem_area.reset();
vtlbdata.fastmem_base = 0;
EmuConfig.Cpu.Recompiler.EnableFastmem = false;
}
}
if (s_fastmem_area)
{
s_fastmem_virtual_mapping.resize(FASTMEM_PAGE_COUNT, NO_FASTMEM_MAPPING);
vtlbdata.fastmem_base = (uptr)s_fastmem_area->BasePointer();
DevCon.WriteLn(Color_StrongGreen, "Fastmem area: %p - %p",
vtlbdata.fastmem_base, vtlbdata.fastmem_base + (FASTMEM_AREA_SIZE - 1));
}
Error error;
if (!PageFaultHandler::Install(&error))
{
Host::ReportErrorAsync("Failed to install page fault handler.", error.GetDescription());
return false;
}
return true;
}
// The LUT is only used for 1 game so we allocate it only when the gamefix is enabled (save 4MB)
// However automatic gamefix is done after the standard init so a new init function was done.
void vtlb_Alloc_Ppmap()
{
static constexpr size_t PPMAP_SIZE = sizeof(*vtlbdata.ppmap) * VTLB_VMAP_ITEMS;
static_assert(HostMemoryMap::VTLBAddressMapSize == PPMAP_SIZE);
if (vtlbdata.ppmap)
return;
vtlbdata.ppmap = reinterpret_cast<u32*>(SysMemory::GetVTLBAddressMap());
// By default a 1:1 virtual to physical mapping
for (u32 i = 0; i < VTLB_VMAP_ITEMS; i++)
vtlbdata.ppmap[i] = i << VTLB_PAGE_BITS;
}
void vtlb_Core_Free()
{
vtlbdata.vmap = nullptr;
vtlbdata.ppmap = nullptr;
vtlb_RemoveFastmemMappings();
vtlb_ClearLoadStoreInfo();
vtlbdata.fastmem_base = 0;
decltype(s_fastmem_physical_mapping)().swap(s_fastmem_physical_mapping);
decltype(s_fastmem_virtual_mapping)().swap(s_fastmem_virtual_mapping);
s_fastmem_area.reset();
}
// ===========================================================================================
// 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::TotalRam >> __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::ExposedRam)
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;
// Same story as the fault handler: anything PSM resolves outside main RAM is
// ROM or VU memory, which is never under EE write protection, so there is
// nothing here to mark. Bounded the way mmap_GetRamPageInfo already does it.
// The old int also went negative for a pointer below Main and indexed
// backwards out of the array. No caller reaches either case today, they all
// come through mmap_GetRamPageInfo first, but it is a nasty thing to leave
// lying around for the next one.
uptr ptr = (uptr)PSM(paddr);
uptr rampage = ptr - (uptr)eeMem->Main;
if (!ptr || rampage >= Ps2MemSize::ExposedRam)
return;
rampage >>= __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());
// Narrowing is safe, the bound above keeps this under ExposedRam.
vtlb_UpdateFastmemProtection(static_cast<u32>(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);
}
PageFaultHandler::HandlerResult PageFaultHandler::HandlePageFault(void* exception_pc, void* fault_address, bool is_write)
{
pxAssert(eeMem);
u32 vaddr;
if (CHECK_FASTMEM && vtlb_GetGuestAddress(reinterpret_cast<uptr>(fault_address), &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);
// PSM resolves the whole physical map, not just main RAM, so a fault on
// VU memory or ROM arrives here with an offset way past the end of the
// table. Reading it is out of bounds, and if the aliased value happens to
// match ProtMode_Write we walk into mmap_ClearCpuBlock and write a
// ProtMode over whatever follows the array. The branch below has always
// had this check; this one never did.
uptr ptr = (uptr)PSM(vaddr);
uptr offset = (ptr - (uptr)eeMem->Main);
if (ptr && offset < Ps2MemSize::ExposedRam &&
m_PageProtectInfo[offset >> __pageshift].Mode == ProtMode_Write)
{
// fprintf(stderr, "Not backpatching code write at %08X\n", vaddr);
mmap_ClearCpuBlock(offset);
return HandlerResult::ContinueExecution;
}
else
{
// fprintf(stderr, "Trying backpatching vaddr %08X\n", vaddr);
return vtlb_BackpatchLoadStore(reinterpret_cast<uptr>(exception_pc),
reinterpret_cast<uptr>(fault_address)) ?
HandlerResult::ContinueExecution :
HandlerResult::ExecuteNextHandler;
}
}
else
{
// get bad virtual address
uptr offset = reinterpret_cast<uptr>(fault_address) - reinterpret_cast<uptr>(eeMem->Main);
if (offset >= Ps2MemSize::ExposedRam)
return HandlerResult::ExecuteNextHandler;
mmap_ClearCpuBlock(offset);
return HandlerResult::ContinueExecution;
}
}
// 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..." );
std::memset(m_PageProtectInfo, 0, sizeof(m_PageProtectInfo));
if (eeMem)
HostSys::MemProtect(eeMem->Main, Ps2MemSize::ExposedRam, PageAccess_ReadWrite());
vtlb_UpdateFastmemProtection(0, Ps2MemSize::ExposedRam, PageAccess_ReadWrite());
}