/* Copyright 2013 tueidj All Rights Reserved * This code may not be used in any project * without explicit permission from the author. */ #include #include #include #include #include #include #include "vm.h" #include typedef u8 vm_page[PAGE_SIZE]; static p_map phys_map[2048+(PTE_SIZE/PAGE_SIZE)]; static vm_map virt_map[65536]; static u16 pmap_max, pmap_head; static PTE* HTABORG; static vm_page* VM_Base; static vm_page* MEM_Base = NULL; static int pagefile_fd = -1; static mutex_t vm_mutex = LWP_MUTEX_NULL; static bool vm_initialized = 0; static __inline__ void tlbie(void* p) { asm volatile("tlbie %0" :: "r"(p)); } static u16 locate_oldest(void) { u16 head = pmap_head; for(;;++head) { PTE *p; if (head >= pmap_max) head = 0; if (!phys_map[head].valid || phys_map[head].locked) continue; p = HTABORG+phys_map[head].pte_index; tlbie(VM_Base+phys_map[head].page_index); if (p->C) { p->C = 0; phys_map[head].dirty = 1; continue; } if (p->R) { p->R = 0; continue; } p->data[0] = 0; pmap_head = head+1; return head; } } static PTE* StorePTE(PTEG pteg, u32 virtual, u32 physical, u8 WIMG, u8 PP, int secondary) { int i; PTE p = {{0}}; p.valid = 1; p.VSID = VM_VSID; p.hash = secondary ? 1:0; p.API = virtual >> 22; p.RPN = physical >> 12; p.WIMG = WIMG; p.PP = PP; for (i=0; i < 8; i++) { if (pteg[i].data[0] == p.data[0]) { // printf("Error: address %08x already had a PTE entry\r\n", virtual); // abort(); } else if (pteg[i].valid) continue; asm volatile("tlbie %0" : : "r"(virtual)); pteg[i].data[1] = p.data[1]; pteg[i].data[0] = p.data[0]; // if (i || secondary) // printf("PTE for address %08x/%08x in PTEG %p index %d (%s)\r\n", virtual, physical, pteg, i, secondary ? "secondary" : "primary"); return pteg+i; } return NULL; } static PTEG CalcPTEG(u32 virtual, int secondary) { uint32_t segment_index = (virtual >> 12) & 0xFFFF; u32 ptr = MEM_VIRTUAL_TO_PHYSICAL(HTABORG); u32 hash = segment_index ^ VM_VSID; if (secondary) hash = ~hash; hash &= (HTABMASK << 10) | 0x3FF; ptr |= hash << 6; return (PTEG)MEM_PHYSICAL_TO_K0(ptr); } static PTE* insert_pte(u16 index, u32 physical, u8 WIMG, u8 PP) { PTE *pte; int i; u32 virtual = (u32)(VM_Base+index); for (i=0; i < 2; i++) { PTEG pteg = CalcPTEG(virtual, i); pte = StorePTE(pteg, virtual, physical, WIMG, PP, i); if (pte) return pte; } // printf("Failed to insert PTE for %p\r\n", VM_Base+index); // abort(); return NULL; } static void tlbia(void) { int i; for (i=0; i < 64; i++) asm volatile("tlbie %0" :: "r" (i*PAGE_SIZE)); } /* This definition is wrong, pHndl does not take frame_context* as a parameter, * it has to adjust the stack pointer and finish filling frame_context itself */ void __exception_sethandler(u32 nExcept, void (*pHndl)(frame_context*)); extern void default_exceptionhandler(); // use our own exception stub because libogc stupidly requires it extern void dsi_handler(); void* VM_Init(u32 VMSize, u32 MEMSize) { u32 i; u16 index, v_index; if (vm_initialized) return VM_Base; // parameter checking if (VMSize>MAX_VM_SIZE || MEMSizeMAX_MEM_SIZE) { errno = EINVAL; return NULL; } VMSize = (VMSize+PAGE_SIZE-1)&PAGE_MASK; MEMSize = (MEMSize+PAGE_SIZE-1)&PAGE_MASK; //VM_Base = (vm_page*)(0x80000000 - VMSize); VM_Base = (vm_page*)(ARAM_VM_BASE); pmap_max = MEMSize / PAGE_SIZE + 16; // printf("VMSize %08x MEMSize %08x VM_Base %p pmap_max %u\r\n", VMSize, MEMSize, VM_Base, pmap_max); if (VMSize <= MEMSize) { errno = EINVAL; return NULL; } if (LWP_MutexInit(&vm_mutex, 0) != 0) { errno = ENOLCK; return NULL; } pagefile_fd = AR_Init(NULL, 0); // ISFS_Initialize(); // doesn't matter if this fails, will be caught when file is opened // ISFS_CreateFile(VM_FILENAME, 0, ISFS_OPEN_RW, ISFS_OPEN_RW, ISFS_OPEN_RW); // pagefile_fd = ISFS_Open(VM_FILENAME, ISFS_OPEN_RW); if (pagefile_fd < 0) { errno = ENOENT; return NULL; } MEMSize += PTE_SIZE; MEM_Base = (vm_page*)memalign(PAGE_SIZE, MEMSize); // printf("MEM_Base: %p\r\n", MEM_Base); if (MEM_Base==NULL) { AR_Reset(); // ISFS_Close(pagefile_fd); errno = ENOMEM; return NULL; } tlbia(); DCZeroRange(MEM_Base, MEMSize); HTABORG = (PTE*)(((u32)MEM_Base+0xFFFF)&~0xFFFF); // printf("HTABORG: %p\r\n", HTABORG); // attempt to make the pagefile the correct size /* ISFS_Seek(pagefile_fd, 0, SEEK_SET); for (i=0; i MEMSize) to_write = MEMSize; if (ISFS_Write(pagefile_fd, MEM_Base, to_write) != to_write) { free(MEM_Base); ISFS_Close(pagefile_fd); errno = ENOSPC; return NULL; } // printf("Wrote %u bytes to offset %u\r\n", to_write, page); i += to_write; }*/ // initial commit: map pmap_max pages to fill PTEs with valid RPNs for (index=0,v_index=0; index %u\r\n", index, index+(PTE_SIZE/PAGE_SIZE)); for (i=0; i<(PTE_SIZE/PAGE_SIZE); ++i,++index) phys_map[index].valid = 0; --index; --v_index; continue; } phys_map[index].valid = 1; phys_map[index].locked = 0; phys_map[index].dirty = 0; phys_map[index].page_index = v_index; phys_map[index].pte_index = insert_pte(v_index, MEM_VIRTUAL_TO_PHYSICAL(MEM_Base+index), 0, 0b10) - HTABORG; virt_map[v_index].committed = 0; virt_map[v_index].p_map_index = index; } // all indexes up to 65536 for (; v_index; ++v_index) { virt_map[v_index].committed = 0; virt_map[v_index].p_map_index = pmap_max; } pmap_head = 0; // set SDR1 mtspr(25, MEM_VIRTUAL_TO_PHYSICAL(HTABORG)|HTABMASK); //printf("SDR1: %08x\r\n", MEM_VIRTUAL_TO_PHYSICAL(HTABORG)); // enable SR asm volatile("mtsrin %0,%1" :: "r"(VM_VSID), "r"(VM_Base)); // hook DSI __exception_sethandler(EX_DSI, dsi_handler); atexit(VM_Deinit); vm_initialized = 1; return VM_Base; } void VM_Deinit(void) { if (!vm_initialized) return; // disable SR asm volatile("mtsrin %0,%1" :: "r"(0x80000000), "r"(VM_Base)); // restore default DSI handler __exception_sethandler(EX_DSI, default_exceptionhandler); free(MEM_Base); MEM_Base = NULL; if (vm_mutex != LWP_MUTEX_NULL) { LWP_MutexDestroy(vm_mutex); vm_mutex = LWP_MUTEX_NULL; } if (pagefile_fd) { AR_Reset(); // ISFS_Close(pagefile_fd); pagefile_fd = -1; // ISFS_Delete(VM_FILENAME); } vm_initialized = 0; } int vm_dsi_handler(frame_context* state, u32 DSISR) { u16 v_index; u16 p_index; if (state->DAR<(u32)VM_Base || state->DAR>=0x80000000) return 0; if ((DSISR&~0x02000000)!=0x40000000) return 0; if (!vm_initialized) return 0; LWP_MutexLock(vm_mutex); state->DAR &= ~0xFFF; v_index = (vm_page*)state->DAR - VM_Base; p_index = locate_oldest(); // purge p_index if it's dirty if (phys_map[p_index].dirty) { DCFlushRange(MEM_Base+p_index, PAGE_SIZE); AR_StartDMA(AR_MRAMTOARAM,(u32)(MEM_Base+p_index),phys_map[p_index].page_index*PAGE_SIZE,PAGE_SIZE); while (AR_GetDMAStatus()); virt_map[phys_map[p_index].page_index].committed = 1; virt_map[phys_map[p_index].page_index].p_map_index = pmap_max; phys_map[p_index].dirty = 0; // printf("VM page %d was purged\r\n", phys_map[p_index].page_index); } // fetch v_index if it has been previously committed if (virt_map[v_index].committed) { DCInvalidateRange(MEM_Base+p_index, PAGE_SIZE); AR_StartDMA(AR_ARAMTOMRAM,(u32)(MEM_Base+p_index),v_index*PAGE_SIZE,PAGE_SIZE); while (AR_GetDMAStatus()); // printf("VM page %d was fetched\r\n", v_index); } else DCZeroRange(MEM_Base+p_index, PAGE_SIZE); // printf("VM page %u (0x%08x) replaced page %u (%p)\r\n", v_index, state->DAR, phys_map[p_index].page_index, VM_Base+phys_map[p_index].page_index); virt_map[v_index].p_map_index = p_index; phys_map[p_index].page_index = v_index; phys_map[p_index].pte_index = insert_pte(v_index, MEM_VIRTUAL_TO_PHYSICAL(MEM_Base+p_index), 0, 0b10) - HTABORG; LWP_MutexUnlock(vm_mutex); return 1; }