#include "addrspace.h" #include "hw/aica/aica_if.h" #include "hw/pvr/pvr_mem.h" #include "hw/pvr/elan.h" #include "hw/sh4/dyna/blockmanager.h" #include "hw/sh4/sh4_mem.h" #include "oslib/oslib.h" #include "oslib/virtmem.h" #include namespace addrspace { #define HANDLER_MAX 0x1F #define HANDLER_COUNT (HANDLER_MAX+1) //top registered handler static handler lastRegisteredHandler; //handler tables static ReadMem8FP* RF8[HANDLER_COUNT]; static WriteMem8FP* WF8[HANDLER_COUNT]; static ReadMem16FP* RF16[HANDLER_COUNT]; static WriteMem16FP* WF16[HANDLER_COUNT]; static ReadMem32FP* RF32[HANDLER_COUNT]; static WriteMem32FP* WF32[HANDLER_COUNT]; //upper 8b of the address static void* memInfo_ptr[0x100]; #define MAP_RAM_START_OFFSET 0 #define MAP_VRAM_START_OFFSET (MAP_RAM_START_OFFSET+RAM_SIZE) #define MAP_ARAM_START_OFFSET (MAP_VRAM_START_OFFSET+VRAM_SIZE) #define MAP_ERAM_START_OFFSET (MAP_ARAM_START_OFFSET+ARAM_SIZE) void *readConst(u32 addr, bool& ismem, u32 sz) { u32 page = addr >> 24; uintptr_t iirf = (uintptr_t)memInfo_ptr[page]; void *ptr = (void *)(iirf & ~HANDLER_MAX); if (ptr == nullptr) { ismem = false; const uintptr_t id = iirf; switch (sz) { case 1: return (void *)RF8[id]; case 2: return (void *)RF16[id]; case 4: return (void *)RF32[id]; default: die("Invalid size"); return nullptr; } } else { ismem = true; addr <<= iirf; addr >>= iirf; return &(((u8 *)ptr)[addr]); } } void *writeConst(u32 addr, bool& ismem, u32 sz) { u32 page = addr >> 24; uintptr_t iirf = (uintptr_t)memInfo_ptr[page]; void *ptr = (void *)(iirf & ~HANDLER_MAX); if (ptr == nullptr) { ismem = false; const uintptr_t id = iirf; switch (sz) { case 1: return (void *)WF8[id]; case 2: return (void *)WF16[id]; case 4: return (void *)WF32[id]; default: die("Invalid size"); return nullptr; } } else { ismem = true; addr <<= iirf; addr >>= iirf; return &(((u8 *)ptr)[addr]); } } template T DYNACALL readt(u32 addr) { constexpr u32 sz = sizeof(T); u32 page = addr >> 24; //1 op, shift/extract uintptr_t iirf = (uintptr_t)memInfo_ptr[page]; //2 ops, insert + read [vmem table will be on reg ] void *ptr = (void *)(iirf & ~HANDLER_MAX); //2 ops, and // 1 op insert if (likely(ptr != nullptr)) { addr <<= iirf; addr >>= iirf; return *(T *)&((u8 *)ptr)[addr]; } else { const u32 id = iirf; switch (sz) { case 1: return (T)RF8[id](addr); case 2: return (T)RF16[id](addr); case 4: return (T)RF32[id](addr); case 8: { T rv = RF32[id](addr); rv |= (T)((u64)RF32[id](addr + 4) << 32); return rv; } default: die("Invalid size"); return 0; } } } template u8 DYNACALL readt(u32 addr); template u16 DYNACALL readt(u32 addr); template u32 DYNACALL readt(u32 addr); template u64 DYNACALL readt(u32 addr); template void DYNACALL writet(u32 addr, T data) { constexpr u32 sz = sizeof(T); u32 page = addr>>24; uintptr_t iirf = (uintptr_t)memInfo_ptr[page]; void *ptr = (void *)(iirf & ~HANDLER_MAX); if (likely(ptr != nullptr)) { addr <<= iirf; addr >>= iirf; *(T *)&((u8 *)ptr)[addr] = data; } else { const u32 id = iirf; switch (sz) { case 1: WF8[id](addr,data); break; case 2: WF16[id](addr,data); break; case 4: WF32[id](addr,data); break; case 8: WF32[id](addr,(u32)data); WF32[id](addr+4,(u32)((u64)data>>32)); break; default: die("Invalid size"); break; } } } template void DYNACALL writet(u32 addr, u8 data); template void DYNACALL writet(u32 addr, u16 data); template void DYNACALL writet(u32 addr, u32 data); template void DYNACALL writet(u32 addr, u64 data); //ReadMem/WriteMem functions //ReadMem u8 DYNACALL read8(u32 Address) { return readt(Address); } u16 DYNACALL read16(u32 Address) { return readt(Address); } u32 DYNACALL read32(u32 Address) { return readt(Address); } u64 DYNACALL read64(u32 Address) { return readt(Address); } //WriteMem void DYNACALL write8(u32 Address,u8 data) { writet(Address,data); } void DYNACALL write16(u32 Address,u16 data) { writet(Address,data); } void DYNACALL write32(u32 Address,u32 data) { writet(Address,data); } void DYNACALL write64(u32 Address,u64 data) { writet(Address,data); } #define MEM_ERROR_RETURN_VALUE 0 //default read handler template static T DYNACALL readMemNotMapped(u32 addresss) { INFO_LOG(MEMORY, "[sh4]read%d from %08x, not mapped (default handler)", (int)sizeof(T), addresss); return (u8)MEM_ERROR_RETURN_VALUE; } //default write hander template static void DYNACALL writeMemNotMapped(u32 addresss, T data) { INFO_LOG(MEMORY, "[sh4]Write%d to %08x = %x, not mapped (default handler)", (int)sizeof(T), addresss, data); } //code to register handlers //0 is considered error handler registerHandler( ReadMem8FP *read8, ReadMem16FP *read16, ReadMem32FP *read32, WriteMem8FP *write8, WriteMem16FP *write16, WriteMem32FP *write32) { handler rv = lastRegisteredHandler++; assert(rv < HANDLER_COUNT); RF8[rv] = read8 == nullptr ? readMemNotMapped : read8; RF16[rv] = read16 == nullptr ? readMemNotMapped : read16; RF32[rv] = read32 == nullptr ? readMemNotMapped : read32; WF8[rv] = write8 == nullptr ? writeMemNotMapped : write8; WF16[rv] = write16 == nullptr? writeMemNotMapped : write16; WF32[rv] = write32 == nullptr? writeMemNotMapped : write32; return rv; } static u32 FindMask(u32 msk) { u32 s=-1; u32 rv=0; while(msk!=s>>rv) rv++; return rv; } //map a registered handler to a mem region void mapHandler(handler Handler, u32 start, u32 end) { assert(start < 0x100); assert(end < 0x100); assert(start <= end); for (u32 i = start; i <= end; i++) memInfo_ptr[i] = (u8 *)nullptr + Handler; } //map a memory block to a mem region void mapBlock(void *base, u32 start, u32 end, u32 mask) { assert(start < 0x100); assert(end < 0x100); assert(start <= end); assert((0xFF & (uintptr_t)base) == 0); assert(base != nullptr); u32 j = 0; for (u32 i = start; i <= end; i++) { memInfo_ptr[i] = &((u8 *)base)[j & mask] + FindMask(mask) - (j & mask); j += 0x1000000; } } void mirrorMapping(u32 new_region, u32 start, u32 size) { u32 end = start + size - 1; assert(start < 0x100); assert(end < 0x100); assert(start <= end); assert(!(start >= new_region && end <= new_region)); u32 j = new_region; for (u32 i = start; i <= end; i++) { memInfo_ptr[j & 0xFF] = memInfo_ptr[i & 0xFF]; j++; } } //init/reset/term void init() { //clear read tables memset(RF8, 0, sizeof(RF8)); memset(RF16, 0, sizeof(RF16)); memset(RF32, 0, sizeof(RF32)); //clear write tables memset(WF8, 0, sizeof(WF8)); memset(WF16, 0, sizeof(WF16)); memset(WF32, 0, sizeof(WF32)); //clear meminfo table memset(memInfo_ptr, 0, sizeof(memInfo_ptr)); //reset registration index lastRegisteredHandler = 0; //register default functions (0) for slot 0 handler defaultHandler = registerHandler(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); assert(defaultHandler == 0); (void)defaultHandler; } void term() { } u8* ram_base; static void *malloc_pages(size_t size) { return allocAligned(PAGE_SIZE, size); } static void free_pages(void *p) { freeAligned(p); } #if FEAT_SHREC != DYNAREC_NONE // Resets the FPCB table (by either clearing it to the default val // or by flushing it and making it fault on access again. void bm_reset() { // If we allocated it via vmem: if (ram_base) virtmem::reset_mem(p_sh4rcb->fpcb, sizeof(p_sh4rcb->fpcb)); else // We allocated it via a regular malloc/new/whatever on the heap bm_vmem_pagefill((void**)p_sh4rcb->fpcb, sizeof(p_sh4rcb->fpcb)); } // This gets called whenever there is a pagefault, it is possible that it lands // on the fpcb memory range, which is allocated on miss. Returning true tells the // fault handler this was us, and that the page is resolved and can continue the execution. bool bm_lockedWrite(u8* address) { if (!ram_base) return false; // No vmem, therefore not us who caused this. uintptr_t ptrint = (uintptr_t)address; uintptr_t start = (uintptr_t)p_sh4rcb->fpcb; uintptr_t end = start + sizeof(p_sh4rcb->fpcb); if (ptrint >= start && ptrint < end) { // Alloc the page then and initialize it to default values void *aligned_addr = (void*)(ptrint & (~PAGE_MASK)); virtmem::ondemand_page(aligned_addr, PAGE_SIZE); bm_vmem_pagefill((void**)aligned_addr, PAGE_SIZE); return true; } return false; } #endif bool reserve() { static_assert((sizeof(Sh4RCB) % PAGE_SIZE) == 0, "sizeof(Sh4RCB) not multiple of PAGE_SIZE"); if (ram_base != nullptr) return true; // Use vmem only if settings mandate so, and if we have proper exception handlers. #if !defined(TARGET_NO_EXCEPTIONS) if (!settings.dynarec.disable_nvmem) virtmem::init((void**)&ram_base, (void**)&p_sh4rcb, RAM_SIZE_MAX + VRAM_SIZE_MAX + ARAM_SIZE_MAX + elan::ERAM_SIZE_MAX); #endif return true; } static void termMappings() { if (ram_base == nullptr) { free_pages(p_sh4rcb); p_sh4rcb = nullptr; mem_b.free(); vram.free(); aica::aica_ram.free(); free_pages(elan::RAM); elan::RAM = nullptr; } } void initMappings() { termMappings(); // Fallback to statically allocated buffers, this results in slow-ops being generated. if (ram_base == nullptr) { WARN_LOG(VMEM, "Warning! nvmem is DISABLED (due to failure or not being built-in"); // Allocate it all and initialize it. p_sh4rcb = (Sh4RCB*)malloc_pages(sizeof(Sh4RCB)); #if FEAT_SHREC != DYNAREC_NONE bm_vmem_pagefill((void**)p_sh4rcb->fpcb, sizeof(p_sh4rcb->fpcb)); #endif memset(&p_sh4rcb->cntx, 0, sizeof(p_sh4rcb->cntx)); mem_b.alloc(RAM_SIZE); vram.alloc(VRAM_SIZE); aica::aica_ram.alloc(ARAM_SIZE); elan::RAM = (u8*)malloc_pages(elan::ERAM_SIZE); } else { NOTICE_LOG(VMEM, "Info: nvmem is enabled"); INFO_LOG(VMEM, "Info: p_sh4rcb: %p ram_base: %p", p_sh4rcb, ram_base); // Map the different parts of the memory file into the new memory range we got. const virtmem::Mapping mem_mappings[] = { {0x00000000, 0x00800000, 0, 0, false}, // Area 0 -> unused {0x00800000, 0x01000000, MAP_ARAM_START_OFFSET, ARAM_SIZE, false}, // Aica {0x01000000, 0x04000000, 0, 0, false}, // More unused {0x04000000, 0x05000000, MAP_VRAM_START_OFFSET, VRAM_SIZE, true}, // Area 1 (vram, 16MB, wrapped on DC as 2x8MB) {0x05000000, 0x06000000, 0, 0, false}, // 32 bit path (unused) {0x06000000, 0x07000000, MAP_VRAM_START_OFFSET, VRAM_SIZE, true}, // VRAM mirror {0x07000000, 0x08000000, 0, 0, false}, // 32 bit path (unused) mirror {0x08000000, 0x0A000000, 0, 0, false}, // Area 2 {0x0A000000, 0x0C000000, MAP_ERAM_START_OFFSET, elan::ERAM_SIZE, true}, // Area 2 (Elan RAM) {0x0C000000, 0x10000000, MAP_RAM_START_OFFSET, RAM_SIZE, true}, // Area 3 (main RAM + 3 mirrors) {0x10000000, 0x20000000, 0, 0, false}, // Area 4-7 (unused) // This is outside of the 512MB addr space. We map 8MB in all cases to help some games read past the end of aica ram {0x20000000, 0x20800000, MAP_ARAM_START_OFFSET, ARAM_SIZE, true}, // writable aica ram }; virtmem::create_mappings(&mem_mappings[0], ARRAY_SIZE(mem_mappings)); // Point buffers to actual data pointers aica::aica_ram.setRegion(&ram_base[0x20000000], ARAM_SIZE); // Points to the writable AICA addrspace vram.setRegion(&ram_base[0x04000000], VRAM_SIZE); // Points to first vram mirror (writable and lockable) mem_b.setRegion(&ram_base[0x0C000000], RAM_SIZE); // Main memory, first mirror elan::RAM = &ram_base[0x0A000000]; } // Clear out memory aica::aica_ram.zero(); vram.zero(); mem_b.zero(); NOTICE_LOG(VMEM, "BASE %p RAM(%d MB) %p VRAM64(%d MB) %p ARAM(%d MB) %p", ram_base, RAM_SIZE / 1024 / 1024, &mem_b[0], VRAM_SIZE / 1024 / 1024, &vram[0], ARAM_SIZE / 1024 / 1024, &aica::aica_ram[0]); } void release() { if (ram_base != nullptr) { virtmem::destroy(); ram_base = nullptr; } else { unprotectVram(0, VRAM_SIZE); termMappings(); } } void protectVram(u32 addr, u32 size) { addr &= VRAM_MASK; if (virtmemEnabled()) { virtmem::region_lock(ram_base + 0x04000000 + addr, size); // P0 //virtmem::region_lock(ram_base + 0x06000000 + addr, size); // P0 - mirror if (VRAM_SIZE == 0x800000) { // wraps when only 8MB VRAM virtmem::region_lock(ram_base + 0x04000000 + addr + VRAM_SIZE, size); // P0 wrap //virtmem::region_lock(ram_base + 0x06000000 + addr + VRAM_SIZE, size); // P0 mirror wrap } } else { virtmem::region_lock(&vram[addr], size); } } void unprotectVram(u32 addr, u32 size) { addr &= VRAM_MASK; if (virtmemEnabled()) { virtmem::region_unlock(ram_base + 0x04000000 + addr, size); // P0 //virtmem::region_unlock(ram_base + 0x06000000 + addr, size); // P0 - mirror if (VRAM_SIZE == 0x800000) { // wraps when only 8MB VRAM virtmem::region_unlock(ram_base + 0x04000000 + addr + VRAM_SIZE, size); // P0 wrap //virtmem::region_unlock(ram_base + 0x06000000 + addr + VRAM_SIZE, size); // P0 mirror wrap } } else { virtmem::region_unlock(&vram[addr], size); } } u32 getVramOffset(void *addr) { if (virtmemEnabled()) { ptrdiff_t offset = (u8*)addr - ram_base; if (offset < 0 || offset >= 0x20000000) return -1; if ((offset >> 24) != 4) return -1; return offset & VRAM_MASK; } else { ptrdiff_t offset = (u8*)addr - &vram[0]; if (offset < 0 || offset >= VRAM_SIZE) return -1; return (u32)offset; } } } // namespace addrspace