Files

541 lines
14 KiB
C++
Raw Permalink Normal View History

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
2024-07-30 13:42:36 +02:00
// SPDX-License-Identifier: GPL-3.0+
#include "Common.h"
#include "Cache.h"
#include "vtlb.h"
2009-07-30 23:50:39 +00:00
using namespace R5900;
using namespace vtlb_private;
2020-10-17 04:31:26 -05:00
namespace
2019-07-13 17:49:51 -07:00
{
2020-10-17 04:31:26 -05:00
union alignas(64) CacheData
{
u8 bytes[64];
};
struct CacheTag
{
2024-01-08 22:31:11 +10:00
uptr rawValue;
// You are able to configure a TLB entry with non-existant physical address without causing a bus error.
// When this happens, the cache still fills with the data and when it gets evicted the data is lost.
// We don't emulate memory access on a logic level, so we need to ensure that we don't try to load/store to a non-existant physical address.
// This fixes the Find My Own Way demo.
2025-06-12 23:16:13 -05:00
2020-10-17 04:31:26 -05:00
// The lower parts of a cache tags structure is as follows:
// 31 - 12: The physical address cache tag.
2025-01-04 12:01:19 -05:00
// 11: Used by PCSX2 to indicate if the physical address is valid.
// 10 - 7: Unused.
2020-10-17 04:31:26 -05:00
// 6: Dirty flag.
// 5: Valid flag.
// 4: LRF flag - least recently filled flag.
// 3: Lock flag.
// 2-0: Unused.
enum Flags : decltype(rawValue)
{
DIRTY_FLAG = 0x40,
VALID_FLAG = 0x20,
LRF_FLAG = 0x10,
LOCK_FLAG = 0x8,
2025-01-04 12:01:19 -05:00
ALL_FLAGS = 0x7FF,
ALL_BITS = 0xFFF
2020-10-17 04:31:26 -05:00
};
int flags() const
{
return rawValue & ALL_FLAGS;
}
2024-06-25 11:36:12 -04:00
bool isValid() const { return rawValue & VALID_FLAG; }
bool isDirty() const { return rawValue & DIRTY_FLAG; }
bool lrf() const { return rawValue & LRF_FLAG; }
2020-10-17 04:31:26 -05:00
bool isLocked() const { return rawValue & LOCK_FLAG; }
bool isDirtyAndValid() const
{
return (rawValue & (DIRTY_FLAG | VALID_FLAG)) == (DIRTY_FLAG | VALID_FLAG);
}
2024-06-25 11:36:12 -04:00
void setValid() { rawValue |= VALID_FLAG; }
void setDirty() { rawValue |= DIRTY_FLAG; }
2020-10-17 04:31:26 -05:00
void setLocked() { rawValue |= LOCK_FLAG; }
2024-06-25 11:36:12 -04:00
void clearValid() { rawValue &= ~VALID_FLAG; }
void clearDirty() { rawValue &= ~DIRTY_FLAG; }
2020-10-17 04:31:26 -05:00
void clearLocked() { rawValue &= ~LOCK_FLAG; }
void toggleLRF() { rawValue ^= LRF_FLAG; }
2025-01-04 12:01:19 -05:00
uptr addr() const { return rawValue & ~ALL_BITS; }
2020-10-17 04:31:26 -05:00
void setAddr(uptr addr)
{
2025-01-04 12:01:19 -05:00
rawValue &= ALL_BITS;
rawValue |= (addr & ~ALL_BITS);
2020-10-17 04:31:26 -05:00
}
bool matches(uptr other) const
{
2025-01-04 12:01:19 -05:00
return isValid() && addr() == (other & ~ALL_BITS);
2020-10-17 04:31:26 -05:00
}
void clear()
{
rawValue &= LRF_FLAG;
}
2025-01-04 12:01:19 -05:00
constexpr bool isValidPFN() const
{
return rawValue & 0x800;
}
constexpr void setValidPFN(bool valid)
{
if (valid)
rawValue |= 0x800;
else
rawValue &= ~0x800;
}
2020-10-17 04:31:26 -05:00
};
struct CacheLine
{
CacheTag& tag;
CacheData& data;
int set;
uptr addr()
{
return tag.addr() | (set << 6);
}
void writeBackIfNeeded()
{
if (!tag.isDirtyAndValid())
return;
uptr target = addr();
CACHE_LOG("Write back at %zx", target);
2025-01-04 12:01:19 -05:00
if (tag.isValidPFN())
*reinterpret_cast<CacheData*>(target) = data;
2020-10-17 04:31:26 -05:00
tag.clearDirty();
}
void load(uptr ppf)
{
pxAssertMsg(!tag.isDirtyAndValid(), "Loaded a value into cache without writing back the old one!");
tag.setAddr(ppf);
2025-01-04 12:01:19 -05:00
if (!tag.isValidPFN())
{
// Reading from invalid physical addresses seems to return 0 on hardware
std::memset(&data, 0, sizeof(data));
}
else
{
std::memcpy(&data, reinterpret_cast<void*>(ppf & ~0x3FULL), sizeof(data));
}
2020-10-17 04:31:26 -05:00
tag.setValid();
tag.clearDirty();
}
void clear()
{
tag.clear();
2024-01-08 22:31:11 +10:00
std::memset(&data, 0, sizeof(data));
2020-10-17 04:31:26 -05:00
}
};
struct CacheSet
{
CacheTag tags[2];
CacheData data[2];
};
struct Cache
{
CacheSet sets[64];
int setIdxFor(u32 vaddr) const
{
return (vaddr >> 6) & 0x3F;
}
CacheLine lineAt(int idx, int way)
{
2024-06-25 11:36:12 -04:00
return {sets[idx].tags[way], sets[idx].data[way], idx};
2020-10-17 04:31:26 -05:00
}
};
2024-01-08 22:31:11 +10:00
static Cache cache = {};
2024-06-25 11:36:12 -04:00
} // namespace
2020-10-17 04:31:26 -05:00
void resetCache()
{
std::memset(&cache, 0, sizeof(cache));
2020-10-17 04:31:26 -05:00
}
void writebackCache()
{
for (int i = 0; i < 64; i++)
{
for (int j = 0; j < 2; j++)
{
cache.lineAt(i, j).writeBackIfNeeded();
}
}
}
2020-10-17 04:31:26 -05:00
static bool findInCache(const CacheSet& set, uptr ppf, int* way)
{
2024-06-25 11:36:12 -04:00
auto check = [&](int checkWay) -> bool {
2020-10-17 04:31:26 -05:00
if (!set.tags[checkWay].matches(ppf))
return false;
*way = checkWay;
return true;
};
return check(0) || check(1);
}
static int getFreeCache(u32 mem, int* way, bool validPFN)
2020-10-17 04:31:26 -05:00
{
const int setIdx = cache.setIdxFor(mem);
CacheSet& set = cache.sets[setIdx];
VTLBVirtual vmv = vtlbdata.vmap[mem >> VTLB_PAGE_BITS];
*way = set.tags[0].lrf() ^ set.tags[1].lrf();
if (validPFN)
pxAssertMsg(!vmv.isHandler(mem), "Cache currently only supports non-handler addresses!");
2020-08-19 03:37:23 -05:00
uptr ppf = vmv.assumePtr(mem);
[[unlikely]]
if ((cpuRegs.CP0.n.Config & 0x10000) == 0)
2020-10-17 04:31:26 -05:00
CACHE_LOG("Cache off!");
if (findInCache(set, ppf, way))
{
[[unlikely]]
2020-10-17 04:31:26 -05:00
if (set.tags[*way].isLocked())
{
// Check the other way
if (set.tags[*way ^ 1].isLocked())
{
Console.Error("CACHE: SECOND WAY IS LOCKED.", setIdx, *way);
}
else
{
// Force the unlocked way
*way ^= 1;
}
}
2009-07-30 23:50:39 +00:00
}
else
2020-10-17 04:31:26 -05:00
{
int newWay = set.tags[0].lrf() ^ set.tags[1].lrf();
[[unlikely]]
if (set.tags[newWay].isLocked())
{
// If the new way is locked, we force the unlocked way, ignoring the lrf bits.
newWay = newWay ^ 1;
[[unlikely]]
if (set.tags[newWay].isLocked())
{
Console.Warning("CACHE: SECOND WAY IS LOCKED.", setIdx, *way);
}
}
2020-10-17 04:31:26 -05:00
*way = newWay;
CacheLine line = cache.lineAt(setIdx, newWay);
2020-10-17 04:31:26 -05:00
line.writeBackIfNeeded();
2025-01-04 12:01:19 -05:00
line.tag.setValidPFN(validPFN);
2020-10-17 04:31:26 -05:00
line.load(ppf);
line.tag.toggleLRF();
}
return setIdx;
}
template <bool Write, int Bytes>
void* prepareCacheAccess(u32 mem, int* way, int* idx, bool validPFN = true)
{
*way = 0;
*idx = getFreeCache(mem, way, validPFN);
CacheLine line = cache.lineAt(*idx, *way);
if (Write)
line.tag.setDirty();
u32 aligned = mem & ~(Bytes - 1);
return &line.data.bytes[aligned & 0x3f];
}
2020-10-17 04:31:26 -05:00
template <typename Int>
void writeCache(u32 mem, Int value, bool validPFN)
2020-10-17 04:31:26 -05:00
{
int way, idx;
void* addr = prepareCacheAccess<true, sizeof(Int)>(mem, &way, &idx, validPFN);
2020-10-17 04:31:26 -05:00
CACHE_LOG("writeCache%d %8.8x adding to %d, way %d, value %llx", 8 * sizeof(value), mem, idx, way, value);
*reinterpret_cast<Int*>(addr) = value;
}
void writeCache8(u32 mem, u8 value, bool validPFN)
2019-07-13 17:49:51 -07:00
{
writeCache<u8>(mem, value, validPFN);
}
void writeCache16(u32 mem, u16 value, bool validPFN)
2019-07-13 17:49:51 -07:00
{
writeCache<u16>(mem, value, validPFN);
}
void writeCache32(u32 mem, u32 value, bool validPFN)
2019-07-13 17:49:51 -07:00
{
writeCache<u32>(mem, value, validPFN);
}
void writeCache64(u32 mem, const u64 value, bool validPFN)
2019-07-13 17:49:51 -07:00
{
writeCache<u64>(mem, value, validPFN);
}
void writeCache128(u32 mem, const mem128_t* value, bool validPFN)
2019-07-13 17:49:51 -07:00
{
int way, idx;
void* addr = prepareCacheAccess<true, sizeof(mem128_t)>(mem, &way, &idx, validPFN);
2019-07-13 17:49:51 -07:00
CACHE_LOG("writeCache128 %8.8x adding to %d, way %x, lo %llx, hi %llx", mem, idx, way, value->lo, value->hi);
*reinterpret_cast<mem128_t*>(addr) = *value;
}
2020-10-17 04:31:26 -05:00
template <typename Int>
Int readCache(u32 mem, bool validPFN)
2020-10-17 04:31:26 -05:00
{
int way, idx;
void* addr = prepareCacheAccess<false, sizeof(Int)>(mem, &way, &idx, validPFN);
2020-10-17 04:31:26 -05:00
Int value = *reinterpret_cast<Int*>(addr);
2020-10-17 04:31:26 -05:00
CACHE_LOG("readCache%d %8.8x from %d, way %d, value %llx", 8 * sizeof(value), mem, idx, way, value);
return value;
}
u8 readCache8(u32 mem, bool validPFN)
2019-07-13 17:49:51 -07:00
{
return readCache<u8>(mem, validPFN);
}
u16 readCache16(u32 mem, bool validPFN)
2019-07-13 17:49:51 -07:00
{
return readCache<u16>(mem, validPFN);
}
u32 readCache32(u32 mem, bool validPFN)
2019-07-13 17:49:51 -07:00
{
return readCache<u32>(mem, validPFN);
}
u64 readCache64(u32 mem, bool validPFN)
2019-07-13 17:49:51 -07:00
{
return readCache<u64>(mem, validPFN);
}
RETURNS_R128 readCache128(u32 mem, bool validPFN)
{
int way, idx;
void* addr = prepareCacheAccess<false, sizeof(mem128_t)>(mem, &way, &idx, validPFN);
r128 value = r128_load(addr);
u64* vptr = reinterpret_cast<u64*>(&value);
CACHE_LOG("readCache128 %8.8x from %d, way %d, lo %llx, hi %llx", mem, idx, way, vptr[0], vptr[1]);
return value;
2019-07-13 17:49:51 -07:00
}
2020-10-17 04:31:26 -05:00
template <typename Op>
void doCacheHitOp(u32 addr, const char* name, Op op)
2019-07-13 17:49:51 -07:00
{
2020-10-17 04:31:26 -05:00
const int index = cache.setIdxFor(addr);
CacheSet& set = cache.sets[index];
VTLBVirtual vmv = vtlbdata.vmap[addr >> VTLB_PAGE_BITS];
uptr ppf = vmv.assumePtr(addr);
int way;
2019-07-13 17:49:51 -07:00
2020-10-17 04:31:26 -05:00
if (!findInCache(set, ppf, &way))
{
CACHE_LOG("CACHE %s NO HIT addr %x, index %d, tag0 %zx tag1 %zx", name, addr, index, set.tags[0].rawValue, set.tags[1].rawValue);
return;
}
2019-07-13 17:49:51 -07:00
2020-10-17 04:31:26 -05:00
CACHE_LOG("CACHE %s addr %x, index %d, way %d, flags %x OP %x", name, addr, index, way, set.tags[way].flags(), cpuRegs.code);
2019-07-13 17:49:51 -07:00
2020-10-17 04:31:26 -05:00
op(cache.lineAt(index, way));
}
2024-06-25 11:36:12 -04:00
namespace R5900
{
2024-06-25 11:36:12 -04:00
namespace Interpreter
{
2024-06-25 11:36:12 -04:00
namespace OpcodeImpl
{
extern int Dcache;
void CACHE()
{
2024-06-25 11:36:12 -04:00
u32 addr = cpuRegs.GPR.r[_Rs_].UL[0] + _Imm_;
// CACHE_LOG("cpuRegs.GPR.r[_Rs_].UL[0] = %x, IMM = %x RT = %x", cpuRegs.GPR.r[_Rs_].UL[0], _Imm_, _Rt_);
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
switch (_Rt_)
{
case 0x1a: //DHIN (Data Cache Hit Invalidate)
doCacheHitOp(addr, "DHIN", [](CacheLine line) {
line.clear();
});
break;
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
case 0x18: //DHWBIN (Data Cache Hit WriteBack with Invalidate)
doCacheHitOp(addr, "DHWBIN", [](CacheLine line) {
line.writeBackIfNeeded();
line.clear();
});
break;
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
case 0x1c: //DHWOIN (Data Cache Hit WriteBack Without Invalidate)
doCacheHitOp(addr, "DHWOIN", [](CacheLine line) {
line.writeBackIfNeeded();
});
break;
2024-06-25 11:36:12 -04:00
case 0x16: //DXIN (Data Cache Index Invalidate)
{
const int index = cache.setIdxFor(addr);
const int way = addr & 0x1;
CacheLine line = cache.lineAt(index, way);
2024-06-25 11:36:12 -04:00
CACHE_LOG("CACHE DXIN addr %x, index %d, way %d, flag %x", addr, index, way, line.tag.flags());
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
line.clear();
break;
}
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
case 0x11: //DXLDT (Data Cache Load Data into TagLo)
{
const int index = cache.setIdxFor(addr);
const int way = addr & 0x1;
CacheLine line = cache.lineAt(index, way);
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
cpuRegs.CP0.n.TagLo = *reinterpret_cast<u32*>(&line.data.bytes[addr & 0x3C]);
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
CACHE_LOG("CACHE DXLDT addr %x, index %d, way %d, DATA %x OP %x", addr, index, way, cpuRegs.CP0.n.TagLo, cpuRegs.code);
break;
}
2016-06-04 12:57:14 +01:00
2024-06-25 11:36:12 -04:00
case 0x10: //DXLTG (Data Cache Load Tag into TagLo)
{
const int index = (addr >> 6) & 0x3F;
const int way = addr & 0x1;
CacheLine line = cache.lineAt(index, way);
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
// DXLTG demands that SYNC.L is called before this command, which forces the cache to write back, so presumably games are checking the cache has updated the memory
// For speed, we will do it here.
line.writeBackIfNeeded();
2016-06-04 12:57:14 +01:00
2024-06-25 11:36:12 -04:00
// Our tags don't contain PS2 paddrs (instead they contain x86 addrs)
cpuRegs.CP0.n.TagLo = line.tag.flags();
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
CACHE_LOG("CACHE DXLTG addr %x, index %d, way %d, DATA %x OP %x ", addr, index, way, cpuRegs.CP0.n.TagLo, cpuRegs.code);
CACHE_LOG("WARNING: DXLTG emulation supports flags only, things could break");
break;
}
2024-06-25 11:36:12 -04:00
case 0x13: //DXSDT (Data Cache Store 32bits from TagLo)
{
const int index = (addr >> 6) & 0x3F;
const int way = addr & 0x1;
CacheLine line = cache.lineAt(index, way);
2024-06-25 11:36:12 -04:00
*reinterpret_cast<u32*>(&line.data.bytes[addr & 0x3C]) = cpuRegs.CP0.n.TagLo;
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
CACHE_LOG("CACHE DXSDT addr %x, index %d, way %d, DATA %x OP %x", addr, index, way, cpuRegs.CP0.n.TagLo, cpuRegs.code);
break;
}
2024-06-25 11:36:12 -04:00
case 0x12: //DXSTG (Data Cache Store Tag from TagLo)
{
const int index = (addr >> 6) & 0x3F;
const int way = addr & 0x1;
CacheLine line = cache.lineAt(index, way);
2020-10-17 04:31:26 -05:00
// TagLo carries a guest physical page. Our tags do not: they hold the
// host pointer the fill translated to (CacheLine::load stores `ppf`),
// which is what writeBackIfNeeded dereferences, so copying the guest
// word in raw aimed a 64-byte store at an address the guest chose --
// setAddr zeroes the top 32 bits, so somewhere below 4 GiB: an
// emulator crash normally, or a write into whatever happened to be
// mapped there. Translate it the way a fill does instead, through the
// KSEG0 alias of the physical page (Memory.cpp maps 0x80000000 onto
// physical 0), so the write-back lands at the physical address the
// guest named, and take isValidPFN from the same translation so the
// two cannot disagree. A tag that does not resolve to plain memory --
// an MMIO handler page, or a physical address that does not exist --
// is marked unbacked; the line still caches and reports its flags,
// and loses its data on eviction (see the comment on CacheTag).
//
// The lookup goes through the physical map, not through the KSEG0
// alias of the page: KSEG0 is only 512 MB wide, so routing a
// physical page through it meant masking the tag to 29 bits, and
// every page at or above 0x20000000 then folded into the low half
// of the map and resolved to whatever lives there. A page past the
// end of the map folded onto ordinary RAM and the write-back went
// into it. vtlb_GetPhyPtr covers the whole 1 GB physical map and
// answers null both for a handler page and for an address off the
// end of it.
const u32 pageTag = cpuRegs.CP0.n.TagLo & ~static_cast<u32>(CacheTag::ALL_BITS);
void* const host = vtlb_GetPhyPtr(pageTag);
const bool backed = host != nullptr;
line.tag.setValidPFN(backed);
line.tag.setAddr(backed ? reinterpret_cast<uptr>(host) : static_cast<uptr>(pageTag));
2024-06-25 11:36:12 -04:00
line.tag.rawValue &= ~CacheTag::ALL_FLAGS;
line.tag.rawValue |= (cpuRegs.CP0.n.TagLo & CacheTag::ALL_FLAGS);
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
CACHE_LOG("CACHE DXSTG addr %x, index %d, way %d, DATA %x OP %x", addr, index, way, cpuRegs.CP0.n.TagLo, cpuRegs.code);
break;
}
2024-06-25 11:36:12 -04:00
case 0x14: //DXWBIN (Data Cache Index WriteBack Invalidate)
{
const int index = (addr >> 6) & 0x3F;
const int way = addr & 0x1;
CacheLine line = cache.lineAt(index, way);
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
CACHE_LOG("CACHE DXWBIN addr %x, index %d, way %d, flags %x paddr %zx", addr, index, way, line.tag.flags(), line.addr());
line.writeBackIfNeeded();
line.clear();
break;
}
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
case 0x7: //IXIN (Instruction Cache Index Invalidate)
{
//Not Implemented as we do not have instruction cache
break;
}
2019-07-13 17:49:51 -07:00
2024-06-25 11:36:12 -04:00
case 0xC: //BFH (BTAC Flush)
{
//Not Implemented as we do not cache Branch Target Addresses.
break;
}
2024-06-25 11:36:12 -04:00
default:
DevCon.Warning("Cache mode %x not implemented", _Rt_);
break;
}
}
} // end namespace OpcodeImpl
} // namespace Interpreter
} // namespace R5900