..

Detecting a PS2 Emulator: Remapping ScratchpadRam

This is the fourth entry in my series of detecting PlayStation 2 emulators.

  1. The VU0 Pipeline
  2. When 1 * X does not equal X
  3. GS Backpressure

Get ready, this one is exciting, and long. Sorry RSS readers, this also uses animations.

Translation Lookaside Buffer (TLB)

There are two types of addresses on the PlayStation 2 and modern computers. Virtual and Physical.
Virtual addresses are most important to developers because there is no way to directly access physical locations without first going through virtual addressing.
Physical addresses are only important if you want to change how virtual addresses are mapped.

Here is a totally accurate represenation of what it may look like inside of the EE while the EE is accessing memory.

There are multiple “entries” in the Translation Lookaside Buffer which can be modified. These entries map a Virtual Address “page” (illustrated as rectangles in the address spaces) to a Physical Address “page”.

These entries will be referred to as their proper name, “TLB Entries”, from now on.

TLB Entries are very complex when you include odd/even pages and caching options so thankfully Sony provides a default memory mapping for us. The great majority of games do not, and never need to adjust this mapping.

The default mapping can be found on ps2tek.

Scratchpad Ram (SPR)

I’ve used SPR in my post ‘PS2 DMAC Basics

Essentially, SPR is memory directly located on the EE. This has the benefit of not having to use the main bus to access it. Because you don’t have to use the main bus, it can be much faster to use this memory.

SPR can be used directory through its own virtual address via its TLB entry1, or through the DMAC.

Backtracking a little bit, part of configuring a TLB entry includes the ‘S’ bit. The ‘S’ bit is checked at the second last stage of the TLB mapping process and it simply determines if the virtual address is accessing SPR or not.

Interfacing With the TLB

The EE provides special coprocessor-0 (COP0) instructions and registers to manipulate the TLB.

Instructions

  • TLBR - TLB Read
  • TLBWI - TLB Write
  • TLBWR - TLB Write Random

Registers

  • PageMask
  • EntryHi
  • EntryLo0
  • EntryLo1
  • Index
  • Random
  • Wired
  • Context
  • BadVAddr

I wont bore you with the details of it all2, but firstly you set Index to the index of the TLB entry you want to read/write. Then you can use TLBR or TLBWI to read/write to that entry using the values in the registers listed above.

The most important registers for SPR mapping are EntryHi, which determines what virtual address the entry is for and EntryLo0 which contains the SPR flag.

I wrote some code that iterates through all of the TLB entries until it finds the entry for SPR. Here is the dirty work involved when reading the entries.

tlb_entry_t read_entry(int index)
{
	tlb_entry_t entry;
	// Set Index and Read the Entry
	asm volatile("mtc0 %0, $0\n"
				"sync.p"
				"tlbr\n"
				"sync.p\n"::"r"(index));

	asm volatile("mfc0 %0, $2\n"
				 "mfc0 %1, $3\n"
				 "mfc0 %2, $5\n"
				 "mfc0 %3, $10\n"
				 "sync.p\n":
				 "=r"(entry.EntryLo0),
				 "=r"(entry.EntryLo1),
				 "=r"(entry.PageMask),
				 "=r"(entry.EntryHi));
	entry.Index = index;

	return entry;
}

When used it resulted in this

Index: 0
EntryLo0: 0x80000006
EntryLo1: 0x00000006
PageMask: 0x00000000
EntryHi: 0x70000000

SPR (bit 31 of EntryLo0) is set!

Okay, so the first default entry is for SPR, easy enough. We can verify this is correct because SPR is known to be mapped at virtual address 0x70000000 and this entries EntryHi register value matches that.

Remapping an Entry

Part of the test is moving the virtual address of SPR to a different location, to remap the existing SPR TLB entry, we simply have to change EntryHi and do a tlbwi to commit this new value into the TLB.

	u32 newEntryHi = 0x80000000;
	asm volatile(
		"mtc0 %0, $10\n"
		"sync.p\n"
		"tlbwi\n"
		"sync.p\n"::"r"(newEntryHi));

Doing the Test

We know that SPR can be accessed from the DMAC. On real hardware, this completely avoids the TLB. We can consider what we read from the DMAC to be the truth of what is in SPR.

The idea is to

  • Remap SPR to 0x75000000 (from 0x70000000)
  • Write to 0x75000000
  • DMA transfer data back from SPR
  • See if what we wrote to 0x75000000 is also what the DMA transfer read

Here is a visualization:

And here is the code:

int test_spr_remap_works()
{
	// Clear SPR data
	u128* spr = (u128*)0x70000000;
	spr[0] = 0x0;
	spr[1] = 0x0;
	spr[2] = 0x0;

	remap_spr(0x75000000);

	// Write data to the newly mapped address
	qword_t* spr_address = (qword_t*)0x75000000;
	spr_address[0].dw[0] = 0x1111111111111111;
	spr_address[0].dw[1] = 0x1111111111111111;
	spr_address[1].dw[0] = 0x2222222222222222;
	spr_address[1].dw[1] = 0x2222222222222222;
	spr_address[2].dw[0] = 0xFFFFFFFFFFFFFFFF;
	spr_address[2].dw[1] = 0xFFFFFFFFFFFFFFFF;

	FlushCache(0);

	// Read data from the SPR using the DMAC
	qword_t spr_dma[3] __attribute__((aligned(16)));

	*R_EE_D8_MADR = (uiptr)spr_dma;
	*R_EE_D8_SADR = 0x0; // Relative to the start of SPR
	*R_EE_D8_QWC = sizeof(spr_dma) / sizeof(u128);
	*R_EE_D8_CHCR = 0x100;

	while(*R_EE_D8_CHCR & 0x100);

	FlushCache(0);
	
	int is_same = !memcmp(spr_dma, spr_address, sizeof(spr_dma));

	// Restore the original SPR address
	remap_spr(0x70000000);

	return is_same;
}

And that’s about it. On DobieStation and Play!, it does not work and hangs the ELF.

I am unable to test hps2x64.

As for PCSX2, originally it did not work. However, after some investigation, the systems were already in place to properly support remapping SPR. It was for some reason gated behind a hard-coded check for the original 0x70000000 address. Once my PR to PCSX2 is merged, this method will not work on PCSX2.

This led me down a rabbit hole and to probably one of my favourite discoveries I’ve made regarding weird PlayStation 2 optimizations. More on that in my next article :^).

I give this method a 5/5 in difficulty. It is quite complex in my opinion and if you’re not careful, you can easily deadlock the system.


  1. I didn’t actually know how I could include SPR in my animation above. SPR has no physical address and how exactly it was implemented on the EE is unknown to me. 

  2. If you’re interested, find a copy of the EE Core Users Manual and check out section 5.2 and 5.3