mirror of https://github.com/PCSX2/pcsx2.git
886 lines
27 KiB
C++
886 lines
27 KiB
C++
/* 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/>.
|
|
*/
|
|
|
|
/*
|
|
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 "PrecompiledHeader.h"
|
|
|
|
#include "Common.h"
|
|
#include "vtlb.h"
|
|
#include "COP0.h"
|
|
#include "Cache.h"
|
|
#include "R5900Exceptions.h"
|
|
|
|
#include "common/MemsetFast.inl"
|
|
|
|
using namespace R5900;
|
|
using namespace vtlb_private;
|
|
|
|
#define verify pxAssert
|
|
|
|
namespace vtlb_private
|
|
{
|
|
alignas(64) MapData vtlbdata;
|
|
}
|
|
|
|
static vtlbHandler vtlbHandlerCount = 0;
|
|
|
|
static vtlbHandler DefaultPhyHandler;
|
|
static vtlbHandler UnmappedVirtHandler0;
|
|
static vtlbHandler UnmappedVirtHandler1;
|
|
static vtlbHandler UnmappedPhyHandler0;
|
|
static vtlbHandler UnmappedPhyHandler1;
|
|
|
|
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)
|
|
{
|
|
u32 mask;
|
|
|
|
if(((cpuRegs.CP0.n.Config >> 16) & 0x1) == 0)
|
|
{
|
|
//DevCon.Warning("Data Cache Disabled! %x", cpuRegs.CP0.n.Config);
|
|
return false;//
|
|
}
|
|
|
|
for(int i = 1; i < 48; i++)
|
|
{
|
|
if (((tlb[i].EntryLo1 & 0x38) >> 3) == 0x3) {
|
|
mask = tlb[i].PageMask;
|
|
|
|
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);
|
|
return true;
|
|
}
|
|
}
|
|
if (((tlb[i].EntryLo0 & 0x38) >> 3) == 0x3) {
|
|
mask = tlb[i].PageMask;
|
|
|
|
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);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
// --------------------------------------------------------------------------------------
|
|
// Interpreter Implementations of VTLB Memory Operations.
|
|
// --------------------------------------------------------------------------------------
|
|
// See recVTLB.cpp for the dynarec versions.
|
|
|
|
template< typename DataType >
|
|
DataType __fastcall 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;
|
|
|
|
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);
|
|
|
|
jNO_DEFAULT;
|
|
}
|
|
|
|
return 0; // technically unreachable, but suppresses warnings.
|
|
}
|
|
|
|
RETURNS_R64 vtlb_memRead64(u32 mem)
|
|
{
|
|
auto vmv = vtlbdata.vmap[mem>>VTLB_PAGE_BITS];
|
|
|
|
if (!vmv.isHandler(mem))
|
|
{
|
|
if (!CHECK_EEREC) {
|
|
if(CHECK_CACHE && CheckCache(mem))
|
|
{
|
|
return readCache64(mem);
|
|
}
|
|
}
|
|
|
|
return r64_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<64, false>()(paddr);
|
|
}
|
|
}
|
|
|
|
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 __fastcall 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
*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 __fastcall vtlb_memWrite64(u32 mem, const mem64_t* value)
|
|
{
|
|
auto vmv = vtlbdata.vmap[mem>>VTLB_PAGE_BITS];
|
|
|
|
if (!vmv.isHandler(mem))
|
|
{
|
|
if (!CHECK_EEREC)
|
|
{
|
|
if(CHECK_CACHE && CheckCache(mem))
|
|
{
|
|
writeCache64(mem, *value);
|
|
return;
|
|
}
|
|
}
|
|
|
|
*(mem64_t*)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<64, true>()(paddr, value);
|
|
}
|
|
}
|
|
|
|
void __fastcall vtlb_memWrite128(u32 mem, const mem128_t *value)
|
|
{
|
|
auto vmv = vtlbdata.vmap[mem>>VTLB_PAGE_BITS];
|
|
|
|
if (!vmv.isHandler(mem))
|
|
{
|
|
if (!CHECK_EEREC)
|
|
{
|
|
if(CHECK_CACHE && CheckCache(mem))
|
|
{
|
|
writeCache128(mem, value);
|
|
return;
|
|
}
|
|
}
|
|
|
|
CopyQWC((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 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);
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// 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 __fastcall 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 __fastcall 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();
|
|
|
|
// Hack to handle expected tlb miss by some games.
|
|
if (Cpu == &intCpu) {
|
|
if (mode)
|
|
cpuTlbMissW(addr, cpuRegs.branch);
|
|
else
|
|
cpuTlbMissR(addr, cpuRegs.branch);
|
|
|
|
// Exception handled. Current instruction need to be stopped
|
|
throw Exception::CancelInstruction();
|
|
}
|
|
|
|
if( IsDevBuild )
|
|
Cpu->ThrowCpuException( R5900Exception::TLBMiss( addr, !!mode ) );
|
|
else
|
|
{
|
|
static int spamStop = 0;
|
|
if ( spamStop++ < 50 )
|
|
Console.Error( R5900Exception::TLBMiss( addr, !!mode ).FormatMessage() );
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// The exception terminate the program on linux which is very annoying
|
|
// Just disable it for the moment
|
|
#ifdef __linux__
|
|
if (0)
|
|
#else
|
|
if( IsDevBuild )
|
|
#endif
|
|
Cpu->ThrowCpuException( R5900Exception::BusError( addr, !!mode ) );
|
|
else
|
|
Console.Error( R5900Exception::TLBMiss( addr, !!mode ).FormatMessage() );
|
|
}
|
|
|
|
template<typename OperandType, u32 saddr>
|
|
OperandType __fastcall vtlbUnmappedVReadSm(u32 addr) { vtlb_Miss(addr|saddr,0); return 0; }
|
|
|
|
template<typename OperandType, u32 saddr>
|
|
u_to_r<OperandType> __vectorcall vtlbUnmappedVReadLg(u32 addr) { vtlb_Miss(addr|saddr,0); return rhelper<OperandType>::zero(); }
|
|
|
|
template<typename OperandType, u32 saddr>
|
|
void __fastcall vtlbUnmappedVWriteSm(u32 addr,OperandType data) { vtlb_Miss(addr|saddr,1); }
|
|
|
|
template<typename OperandType, u32 saddr>
|
|
void __fastcall vtlbUnmappedVWriteLg(u32 addr,const OperandType* data) { vtlb_Miss(addr|saddr,1); }
|
|
|
|
template<typename OperandType, u32 saddr>
|
|
OperandType __fastcall vtlbUnmappedPReadSm(u32 addr) { vtlb_BusError(addr|saddr,0); return 0; }
|
|
|
|
template<typename OperandType, u32 saddr>
|
|
u_to_r<OperandType> __vectorcall vtlbUnmappedPReadLg(u32 addr) { vtlb_BusError(addr|saddr,0); return rhelper<OperandType>::zero(); }
|
|
|
|
template<typename OperandType, u32 saddr>
|
|
void __fastcall vtlbUnmappedPWriteSm(u32 addr,OperandType data) { vtlb_BusError(addr|saddr,1); }
|
|
|
|
template<typename OperandType, u32 saddr>
|
|
void __fastcall vtlbUnmappedPWriteLg(u32 addr,const OperandType* data) { vtlb_BusError(addr|saddr,1); }
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// 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 __fastcall vtlbDefaultPhyRead8(u32 addr)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted read8 from unmapped physical address @ 0x%08X.", addr));
|
|
return 0;
|
|
}
|
|
|
|
static mem16_t __fastcall vtlbDefaultPhyRead16(u32 addr)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted read16 from unmapped physical address @ 0x%08X.", addr));
|
|
return 0;
|
|
}
|
|
|
|
static mem32_t __fastcall vtlbDefaultPhyRead32(u32 addr)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted read32 from unmapped physical address @ 0x%08X.", addr));
|
|
return 0;
|
|
}
|
|
|
|
static __m128i __vectorcall vtlbDefaultPhyRead64(u32 addr)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted read64 from unmapped physical address @ 0x%08X.", addr));
|
|
return r64_zero();
|
|
}
|
|
|
|
static __m128i __vectorcall vtlbDefaultPhyRead128(u32 addr)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted read128 from unmapped physical address @ 0x%08X.", addr));
|
|
return r128_zero();
|
|
}
|
|
|
|
static void __fastcall vtlbDefaultPhyWrite8(u32 addr, mem8_t data)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted write8 to unmapped physical address @ 0x%08X.", addr));
|
|
}
|
|
|
|
static void __fastcall vtlbDefaultPhyWrite16(u32 addr, mem16_t data)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted write16 to unmapped physical address @ 0x%08X.", addr));
|
|
}
|
|
|
|
static void __fastcall vtlbDefaultPhyWrite32(u32 addr, mem32_t data)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted write32 to unmapped physical address @ 0x%08X.", addr));
|
|
}
|
|
|
|
static void __fastcall vtlbDefaultPhyWrite64(u32 addr,const mem64_t* data)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted write64 to unmapped physical address @ 0x%08X.", addr));
|
|
}
|
|
|
|
static void __fastcall vtlbDefaultPhyWrite128(u32 addr,const mem128_t* data)
|
|
{
|
|
pxFailDev(pxsFmt("(VTLB) Attempted write128 to unmapped physical address @ 0x%08X.", addr));
|
|
}
|
|
|
|
// ===========================================================================================
|
|
// 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()
|
|
{
|
|
pxAssertDev( 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;
|
|
}
|
|
|
|
|
|
// 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) < std::size(vtlbdata.pmap) );
|
|
|
|
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;
|
|
}
|
|
|
|
//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);
|
|
|
|
while (size > 0)
|
|
{
|
|
VTLBVirtual vmv;
|
|
if (paddr >= VTLB_PMAP_SZ) {
|
|
if ((s32)paddr >= 0) {
|
|
vmv = VTLBVirtual(VTLBPhysical::fromHandler(UnmappedPhyHandler0), paddr, vaddr);
|
|
} else {
|
|
vmv = VTLBVirtual(VTLBPhysical::fromHandler(UnmappedPhyHandler1), paddr & ~(1<<31), 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);
|
|
|
|
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);
|
|
|
|
while (size > 0)
|
|
{
|
|
|
|
VTLBVirtual handl;
|
|
if ((s32)vaddr >= 0) {
|
|
handl = VTLBVirtual(VTLBPhysical::fromHandler(UnmappedVirtHandler0), vaddr, vaddr);
|
|
} else {
|
|
handl = VTLBVirtual(VTLBPhysical::fromHandler(UnmappedVirtHandler1), vaddr & ~(1<<31), vaddr);
|
|
}
|
|
|
|
vtlbdata.vmap[vaddr>>VTLB_PAGE_BITS] = handl;
|
|
vaddr += VTLB_PAGE_SIZE;
|
|
size -= VTLB_PAGE_SIZE;
|
|
}
|
|
}
|
|
|
|
// vtlb_Init -- Clears vtlb handlers and memory mappings.
|
|
void vtlb_Init()
|
|
{
|
|
vtlbHandlerCount=0;
|
|
memzero(vtlbdata.RWFT);
|
|
|
|
#define VTLB_BuildUnmappedHandler(baseName, highBit) \
|
|
baseName##ReadSm<mem8_t,0>, baseName##ReadSm<mem16_t,0>, baseName##ReadSm<mem32_t,0>, \
|
|
baseName##ReadLg<mem64_t,0>, baseName##ReadLg<mem128_t,0>, \
|
|
baseName##WriteSm<mem8_t,0>, baseName##WriteSm<mem16_t,0>, baseName##WriteSm<mem32_t,0>, \
|
|
baseName##WriteLg<mem64_t,0>, baseName##WriteLg<mem128_t,0>
|
|
|
|
//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 :)
|
|
|
|
UnmappedVirtHandler0 = vtlb_RegisterHandler( VTLB_BuildUnmappedHandler(vtlbUnmappedV, 0) );
|
|
UnmappedVirtHandler1 = vtlb_RegisterHandler( VTLB_BuildUnmappedHandler(vtlbUnmappedV, 0x80000000) );
|
|
|
|
UnmappedPhyHandler0 = vtlb_RegisterHandler( VTLB_BuildUnmappedHandler(vtlbUnmappedP, 0) );
|
|
UnmappedPhyHandler1 = vtlb_RegisterHandler( VTLB_BuildUnmappedHandler(vtlbUnmappedP, 0x80000000) );
|
|
|
|
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();
|
|
|
|
extern void vtlb_dynarec_init();
|
|
vtlb_dynarec_init();
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
for(int i=0; i<48; i++) UnmapTLB(i);
|
|
}
|
|
|
|
void vtlb_Term()
|
|
{
|
|
//nothing to do for now
|
|
}
|
|
|
|
constexpr size_t VMAP_SIZE = sizeof(VTLBVirtual) * VTLB_VMAP_ITEMS;
|
|
|
|
// 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.
|
|
void vtlb_Core_Alloc()
|
|
{
|
|
// Can't return regions to the bump allocator
|
|
static VTLBVirtual* vmap = nullptr;
|
|
if (!vmap)
|
|
vmap = (VTLBVirtual*)GetVmMemory().BumpAllocator().Alloc(VMAP_SIZE);
|
|
if (!vtlbdata.vmap)
|
|
{
|
|
bool okay = HostSys::MmapCommitPtr(vmap, VMAP_SIZE, PageProtectionMode().Read().Write());
|
|
if (okay) {
|
|
vtlbdata.vmap = vmap;
|
|
} else {
|
|
throw Exception::OutOfMemory( L"VTLB Virtual Address Translation LUT" )
|
|
.SetDiagMsg(pxsFmt("(%u megs)", VTLB_VMAP_ITEMS * sizeof(*vtlbdata.vmap) / _1mb)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
if (vtlbdata.ppmap) return;
|
|
|
|
vtlbdata.ppmap = (u32*)_aligned_malloc( VTLB_VMAP_ITEMS * sizeof(*vtlbdata.ppmap), 16 );
|
|
if (!vtlbdata.ppmap)
|
|
throw Exception::OutOfMemory( L"VTLB PS2 Virtual Address Translation LUT" )
|
|
.SetDiagMsg(pxsFmt("(%u megs)", VTLB_VMAP_ITEMS * sizeof(*vtlbdata.ppmap) / _1mb));
|
|
|
|
// 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()
|
|
{
|
|
if (vtlbdata.vmap) {
|
|
HostSys::MmapResetPtr(vtlbdata.vmap, VMAP_SIZE);
|
|
vtlbdata.vmap = nullptr;
|
|
}
|
|
safe_aligned_free( vtlbdata.ppmap );
|
|
}
|
|
|
|
static wxString GetHostVmErrorMsg()
|
|
{
|
|
return pxE(
|
|
L"Your system is too low on virtual resources for PCSX2 to run. This can be caused by having a small or disabled swapfile, or by other programs that are hogging resources."
|
|
);
|
|
}
|
|
// --------------------------------------------------------------------------------------
|
|
// VtlbMemoryReserve (implementations)
|
|
// --------------------------------------------------------------------------------------
|
|
VtlbMemoryReserve::VtlbMemoryReserve( const wxString& name, size_t size )
|
|
: m_reserve( name, size )
|
|
{
|
|
m_reserve.SetPageAccessOnCommit( PageAccess_ReadWrite() );
|
|
}
|
|
|
|
void VtlbMemoryReserve::Reserve( VirtualMemoryManagerPtr allocator, sptr offset )
|
|
{
|
|
if (!m_reserve.Reserve( std::move(allocator), offset ))
|
|
{
|
|
throw Exception::OutOfMemory( m_reserve.GetName() )
|
|
.SetDiagMsg(L"Vtlb memory could not be reserved.")
|
|
.SetUserMsg(GetHostVmErrorMsg());
|
|
}
|
|
}
|
|
|
|
void VtlbMemoryReserve::Commit()
|
|
{
|
|
if (IsCommitted()) return;
|
|
if (!m_reserve.Commit())
|
|
{
|
|
throw Exception::OutOfMemory( m_reserve.GetName() )
|
|
.SetDiagMsg(L"Vtlb memory could not be committed.")
|
|
.SetUserMsg(GetHostVmErrorMsg());
|
|
}
|
|
}
|
|
|
|
void VtlbMemoryReserve::Reset()
|
|
{
|
|
Commit();
|
|
memzero_sse_a(m_reserve.GetPtr(), m_reserve.GetCommittedBytes());
|
|
}
|
|
|
|
void VtlbMemoryReserve::Decommit()
|
|
{
|
|
m_reserve.Reset();
|
|
}
|
|
|
|
bool VtlbMemoryReserve::IsCommitted() const
|
|
{
|
|
return !!m_reserve.GetCommittedPageCount();
|
|
}
|