perfect_dark/src/lib/vminit.c

346 lines
11 KiB
C

#include <ultra64.h>
#include "lib/tlb.h"
#include "constants.h"
#include "bss.h"
#include "lib/boot.h"
#include "lib/crash.h"
#include "lib/rzip.h"
#include "lib/dma.h"
#include "lib/lib_48150.h"
#include "lib/vm.h"
#include "data.h"
#include "types.h"
/**
* vm - virtual memory
*
* To get around memory limitations, Perfect Dark implements a memory paging
* system similar to ones found in modern operating systems. Memory is divided
* into pages and can be accessed by virtual addresses. If the page does not
* exist in physical memory then the operating system can replace a page in
* physical memory with the required one and try again. This allows the system
* to address more memory than is actually loaded and have a smaller memory
* footprint.
*
* All code in the game is divided into two segments: Unpagable and pagable.
*
* Unpagable:
* - Is what decomp calls lib.
* - Is virtual at 0x70000000 and statically mapped to 0x80000000.
* - Contains frequently used code which would be a bad idea to swap in and out,
* as well as all of libultra.
*
* Pagable:
* - Is what decomp calls game.
* - Is virtual at 0x7f000000.
* - Contains the majority of the game code.
*
* Paging cannot occur for any sections which are writeable in memory because
* the N64 ROM is read only. Sections of this type are .data and .bss.
* These sections reside in physical memory at 0x80000000 and are not pagable.
*
* Paging is implemented for the .text (ie. code) and .rodata segments. When
* building the ROM image, these are grouped into one binary then sliced into
* chunks of size 4KB (the page size). Each chunk is zipped and then each zip is
* placed on the ROM along with a zip offset table.
*
* The N64 contains a translation lookaside buffer (TLB), a feature of the CPU
* which can map virtual memory to physical memory. The TLB contains a table of
* 32 entries. Each entry can map two pages, and PD's page size is 4KB. The
* unpagable segment described above is mapped using one TLB entry, leaving 31
* entries for dynamic paging.
*
* A page an be in one of three states:
* - Mapped in the TLB and loaded in physical memory.
* - Not mapped in the TLB but still loaded in physical memory.
* - Not mapped in the TLB and not in physical memory
* (must be unzipped from the ROM to access).
*
* A TLB miss occurs when a request is made for a page that isn't mapped in the
* TLB. When this happens, the system's exception handler checks if the page
* exists in physical memory or not. If it does, a TLB entry is created for it.
*
* A page miss occurs when the page didn't exist in physical memory. In this
* case the exception handler must load it from the ROM, unzip the page and
* replace an existing one.
*
* Paging is only implemented for 4MB systems (ie. when the expansion pak is not
* being used). When using the expansion pak all game code is loaded and the
* mappings in the TLB are static.
*/
u8 g_Is4Mb;
u32 g_VmNumTlbMisses;
u32 g_VmNumPageMisses;
u32 g_VmNumPageReplaces;
u8 *g_VmMarker;
u32 g_VmRamEnd;
u32 g_VmVirtualToPhysicalTableEnd;
#ifdef DEBUG
u8 g_VmShowStats = false;
#endif
#if VERSION == VERSION_NTSC_BETA
u32 fillnb[2] = {0};
#endif
#if VERSION == VERSION_PAL_BETA
s32 g_VmNumPages = 0;
u32 var8005cf80 = 0;
#else
u32 var8005cf80 = 0;
s32 g_VmNumPages = 0;
#endif
u32 var8005cf88 = 0;
extern u8 _gameSegmentStart;
extern u8 _gameSegmentEnd;
extern u8 _gamezipSegmentRomStart;
extern u32 g_VmPhysicalSlots;
extern u32 *g_VmVirtualToPhysicalTable;
extern u8 g_VmInitialised;
extern u32 g_VmZipBuffer;
extern u32 *g_VmZipTable;
/**
* Initialise the virtual memory.
*
* The logic here is different depending on whether the system has the 4MB
* of onboard memory or is using the expansion pak for a total of 8MB.
*
* -- For 4MB systems --
*
* vm_init allocates space in memory for the TLB to be able to load zips in its
* exception handler. It initialises the zip table then leaves it to the TLB to
* load the game zips as needed.
*
* The memory is laid out like this:
*
* (zip buffer) (zip table) (state table) (stack) (end of onboard memory)
* Addresses: 0x??? 0x??? 0x803f50b8 0x80400000
* Lengths: 0x6ec 0xdd8 0xaf48 0
*
* zip buffer - is sized according to the biggest single game zip, and is
* reserved space where the TLB's exception handler can DMA the zip to RAM
* before unzipping it.
* zip table - is the ROM offset table where each zip can be found, which is
* used by the TLB's exception handler.
* state table - is cleared by vm_init then left to the TLB's exception handler
* for it to populate as zips are loaded and paged out.
* stack - is reserved stack space for different threads, which vm_init must not
* write into.
*
* -- For 8MB systems --
*
* vm_init loads all game zips into memory and sets TLB entries to map it to
* virtual address space. The page swapping feature is not used as the TLB
* never encounters a page miss.
*
* The memory is laid out like this:
*
* (zip buffer) (zip table) (game seg) (stack) (end of onboard memory)
* Addresses: 0x??? 0x80220000 0x803f50b8 0x80400000
* Lengths: 0x6ec 0x1b99e0 0xaf48 0
*
* zip buffer: is sized as VM_PAGE_SIZE * 2, which guarantees it's big enough to
* hold any zip.
* zip table: is the ROM offset table where each zip can be found.
* game seg: is where the entire game segment is unzipped to.
* stack: is reserved stack space for different threads, which vm_init must not
* write into.
*
* -- Both systems --
*
* Regardless of the amount of memory being used, it is critical that vm_init
* sets the g_VmMarker global variable correctly. This marks the point in memory
* where memory must be preserved. The main thread uses this variable as the end
* address of memp's heap.
*
* In 4MB, g_VmMarker is set to the start of the zip buffer because the zip
* buffer is used by the exception handler.
*
* In 8MB, the zip buffer and zip table are no longer needed, so g_VmMarker is
* set to the start of the unzipped game segment.
*/
void vm_init(void)
{
s32 s1;
u32 *romaddrs;
u32 numpages;
u8 *s2;
u8 *s1p;
u8 *chunkbuffer;
u8 *s7; // nb: 154c
u8 *gameseg;
u8 *zip; // 48
s32 pagenum;
s32 statetablelen;
#if VERSION < VERSION_NTSC_1_0
u32 stack1;
#endif
u32 size;
u32 numentries2; // N/A, 1474
u32 numentries;
u32 *ptr;
#if VERSION >= VERSION_NTSC_1_0
s32 i;
u8 sp68[1024 * 5]; // 128, 68
#else
u8 sp68[1024 * 5]; // 128, 68
char message[128]; // nb: a8
s32 len;
s32 stack2;
#endif
g_VmInitialised = true;
rzip_init();
if (osGetMemSize() <= 0x400000) {
g_Is4Mb = true;
g_VmNumPages = (s32)((&_gameSegmentEnd - &_gameSegmentStart) + (VM_PAGE_SIZE - 1)) / VM_PAGE_SIZE;
g_VmRamEnd = 0x7f000000 + VM_PAGE_SIZE * g_VmNumPages;
g_VmVirtualToPhysicalTableEnd = STACK_START;
gameseg = (u8 *) (STACK_START - g_VmNumPages * 8);
g_VmVirtualToPhysicalTable = (u32 *) gameseg;
numpages = (u32) (((uintptr_t) &_gameSegmentEnd - (uintptr_t) &_gameSegmentStart) + (VM_PAGE_SIZE - 1)) / VM_PAGE_SIZE;
numentries = numpages + 1;
g_VmZipTable = (u32 *) ((uintptr_t) ((u32 *) gameseg - (numentries + 4)) & ~0xf);
// Load gamezips pointer list
dma_exec(g_VmZipTable, (romptr_t) &_gamezipSegmentRomStart, ALIGN16((numentries + 1) << 2));
// Make pointers absolute instead of relative to their segment
for (pagenum = 0; pagenum < numentries; pagenum++) {
g_VmZipTable[pagenum] += (romptr_t) &_gamezipSegmentRomStart;
}
// Find the size of the biggest compressed zip
s1 = 0;
for (pagenum = 0; pagenum < numentries - 1; pagenum++) {
size = g_VmZipTable[pagenum + 1] - g_VmZipTable[pagenum];
if (size > s1) {
s1 = size;
}
}
s1 += 0x40;
s1 &= (u32) ~0xf;
g_VmZipBuffer = (uintptr_t) g_VmZipTable - s1;
g_VmZipBuffer &= ~0xf;
gameseg = (u8 *) (g_VmZipBuffer - VM_NUM_SLOTS * VM_PAGE_SIZE);
gameseg -= (uintptr_t) gameseg & 0x1fff;
g_VmPhysicalSlots = (uintptr_t) gameseg;
g_VmMarker = gameseg;
vm_init_vars();
// Clear the state table
ptr = g_VmVirtualToPhysicalTable;
statetablelen = (g_VmNumPages * 8) >> 2;
for (s1 = 0; s1 < statetablelen; s1++) {
ptr[s1] = 0;
}
vm_init_vacant();
} else {
// Expansion pak is being used
g_Is4Mb = numentries * false;
numpages = (u32)((&_gameSegmentEnd - &_gameSegmentStart) + (VM_PAGE_SIZE - 1)) / VM_PAGE_SIZE;
s7 = (u8 *) STACK_START;
#if VERSION >= VERSION_NTSC_1_0
numentries = numpages + 1;
gameseg = (u8 *) ((uintptr_t) (s7 - (u8 *) ALIGN64((uintptr_t) &_gameSegmentEnd - (uintptr_t) &_gameSegmentStart)) & 0xfffe0000);
#else
gameseg = (u8 *) ((uintptr_t) (s7 - (u8 *) ALIGN64((uintptr_t) &_gameSegmentEnd - (uintptr_t) &_gameSegmentStart)) & 0xfffe0000);
numentries = numpages + 1;
#endif
romaddrs = (u32 *) (((uintptr_t) gameseg - ((numentries + 4) << 2)) & ~0xf);
g_VmMarker = gameseg;
numentries2 = numentries;
// Load gamezips pointer list
dma_exec(romaddrs, (romptr_t) &_gamezipSegmentRomStart, ALIGN16((numentries2 + 1) << 2));
if (pagenum);
#if VERSION >= VERSION_NTSC_1_0
#define ITER i
#else
#define ITER s1
#endif
// Make pointers absolute instead of relative to their segment
for (ITER = 0; ITER < numentries2; ITER++) { \
romaddrs[ITER] += (romptr_t) &_gamezipSegmentRomStart;
}
// Load each zip from the ROM and inflate them to the game segment
s2 = gameseg;
chunkbuffer = (u8 *) ((uintptr_t) romaddrs - VM_PAGE_SIZE * 2);
zip = chunkbuffer + 2;
for (ITER = 0; ITER < numentries2 - 1;) {
dma_exec(chunkbuffer, romaddrs[ITER], ALIGN16(romaddrs[ITER + 1] - romaddrs[ITER]));
#if VERSION >= VERSION_NTSC_1_0
s2 += rzip_inflate(zip, s2, sp68);
#else
len = rzip_inflate(zip, s2, sp68);
if (len == 0) {
sprintf(message, "DMA-Crash %s %d Ram: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
"vm_m.c", 298,
chunkbuffer[0], chunkbuffer[1], chunkbuffer[2], chunkbuffer[3],
chunkbuffer[4], chunkbuffer[5], chunkbuffer[6], chunkbuffer[7],
chunkbuffer[8], chunkbuffer[9], chunkbuffer[10], chunkbuffer[11],
chunkbuffer[12], chunkbuffer[13], chunkbuffer[14], chunkbuffer[15]);
crash_set_message(message);
CRASH();
}
s2 += len;
#endif
ITER++;
}
// This loop sets the following TLB entries:
// entry 2: 0x7f000000 to 0x7f010000 and 0x7f010000 to 0x7f020000
// entry 3: 0x7f020000 to 0x7f030000 and 0x7f030000 to 0x7f040000
// ...
// entry 14: 0x7f1a0000 to 0x7f1b0000 and 0x7f1b0000 to 0x7f1c0000
s1p = (u8 *) 0x7f000000;
pagenum = 2; // reusing variable
while (gameseg <= s7) {
osMapTLB(pagenum, OS_PM_64K, s1p,
osVirtualToPhysical((void *) gameseg),
osVirtualToPhysical((void *) (gameseg + 0x10000)), -1);
gameseg += 0x20000;
s1p += 0x20000;
pagenum++;
}
}
g_VmNumTlbMisses = 0;
g_VmNumPageMisses = 0;
g_VmNumPageReplaces = 0;
osInvalICache(0, ICACHE_SIZE);
}