334 lines
10 KiB
C++
334 lines
10 KiB
C++
#if defined(__SWITCH__)
|
|
#include "hw/sh4/sh4_if.h"
|
|
#include "hw/mem/addrspace.h"
|
|
#include "oslib/virtmem.h"
|
|
|
|
#include <switch.h>
|
|
#include <malloc.h>
|
|
#include <unordered_set>
|
|
|
|
namespace virtmem
|
|
{
|
|
|
|
#define siginfo_t switch_siginfo_t
|
|
|
|
// Allocated RAM
|
|
static void *ramBase;
|
|
static size_t ramSize;
|
|
|
|
// Reserved memory space
|
|
static void *reserved_base;
|
|
static size_t reserved_size;
|
|
static VirtmemReservation *virtmemReservation;
|
|
|
|
// Mapped regions
|
|
static Mapping *memMappings;
|
|
static u32 memMappingCount;
|
|
|
|
static void deleteMappings();
|
|
|
|
bool region_lock(void *start, size_t len)
|
|
{
|
|
const size_t inpage = (uintptr_t)start & PAGE_MASK;
|
|
len = (len + inpage + PAGE_SIZE - 1) & ~PAGE_MASK;
|
|
|
|
Result rc;
|
|
uintptr_t start_addr = (uintptr_t)start - inpage;
|
|
for (uintptr_t addr = start_addr; addr < (start_addr + len); addr += PAGE_SIZE)
|
|
{
|
|
rc = svcSetMemoryPermission((void *)addr, PAGE_SIZE, Perm_R);
|
|
if (R_FAILED(rc))
|
|
ERROR_LOG(VMEM, "Failed to SetPerm Perm_R on %p len 0x%x rc 0x%x", (void*)addr, PAGE_SIZE, rc);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool region_unlock(void *start, size_t len)
|
|
{
|
|
const size_t inpage = (uintptr_t)start & PAGE_MASK;
|
|
len = (len + inpage + PAGE_SIZE - 1) & ~PAGE_MASK;
|
|
|
|
Result rc;
|
|
uintptr_t start_addr = (uintptr_t)start - inpage;
|
|
for (uintptr_t addr = start_addr; addr < (start_addr + len); addr += PAGE_SIZE)
|
|
{
|
|
rc = svcSetMemoryPermission((void *)addr, PAGE_SIZE, Perm_Rw);
|
|
if (R_FAILED(rc))
|
|
ERROR_LOG(VMEM, "Failed to SetPerm Perm_Rw on %p len 0x%x rc 0x%x", (void*)addr, PAGE_SIZE, rc);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Implement vmem initialization for RAM, ARAM, VRAM and SH4 context, fpcb etc.
|
|
|
|
// vmem_base_addr points to an address space of 512MB that can be used for fast memory ops.
|
|
// In negative offsets of the pointer (up to FPCB size, usually 65/129MB) the context and jump table
|
|
// can be found. If the platform init returns error, the user is responsible for initializing the
|
|
// memory using a fallback (that is, regular mallocs and falling back to slow memory JIT).
|
|
bool init(void **vmem_base_addr, void **sh4rcb_addr, size_t ramSize)
|
|
{
|
|
// Allocate RAM
|
|
ramSize += sizeof(Sh4RCB);
|
|
virtmem::ramSize = ramSize;
|
|
ramBase = memalign(PAGE_SIZE, ramSize);
|
|
if (ramBase == nullptr) {
|
|
ERROR_LOG(VMEM, "memalign(%zx) failed", ramSize);
|
|
return false;
|
|
}
|
|
|
|
// Reserve address space
|
|
reserved_size = 512_MB + sizeof(Sh4RCB) + ARAM_SIZE_MAX;
|
|
virtmemLock();
|
|
// virtual mem
|
|
reserved_base = virtmemFindCodeMemory(reserved_size, PAGE_SIZE);
|
|
if (reserved_base != nullptr)
|
|
virtmemReservation = virtmemAddReservation(reserved_base, reserved_size);
|
|
virtmemUnlock();
|
|
if (reserved_base == nullptr || virtmemReservation == nullptr)
|
|
{
|
|
ERROR_LOG(VMEM, "virtmemReserve(%zx) failed: base %p res %p", reserved_size, reserved_base, virtmemReservation);
|
|
free(ramBase);
|
|
return false;
|
|
}
|
|
|
|
constexpr size_t fpcb_size = sizeof(Sh4RCB::fpcb);
|
|
void *sh4rcb_base_ptr = (u8 *)reserved_base + fpcb_size;
|
|
Handle process = envGetOwnProcessHandle();
|
|
|
|
// Now map the memory for the SH4 context, do not include FPCB on purpose (paged on demand).
|
|
size_t sz = sizeof(Sh4RCB) - fpcb_size;
|
|
DEBUG_LOG(VMEM, "mapping %lx to %p size %zx", (u64)ramBase + fpcb_size, sh4rcb_base_ptr, sz);
|
|
Result rc = svcMapProcessCodeMemory(process, (u64)sh4rcb_base_ptr, (u64)ramBase + fpcb_size, sz);
|
|
if (R_FAILED(rc))
|
|
{
|
|
ERROR_LOG(VMEM, "Failed to Map Sh4RCB (%p, %p, %zx) -> %x", sh4rcb_base_ptr, (u8 *)ramBase + fpcb_size, sz, rc);
|
|
destroy();
|
|
return false;
|
|
}
|
|
rc = svcSetProcessMemoryPermission(process, (u64)sh4rcb_base_ptr, sz, Perm_Rw);
|
|
if (R_FAILED(rc))
|
|
{
|
|
ERROR_LOG(VMEM, "Failed to set Sh4RCB perms (%p, %zx) -> %x", sh4rcb_base_ptr, sz, rc);
|
|
destroy();
|
|
return false;
|
|
}
|
|
*sh4rcb_addr = reserved_base;
|
|
*vmem_base_addr = (u8 *)reserved_base + sizeof(Sh4RCB);
|
|
NOTICE_LOG(VMEM, "virtmem::init successful");
|
|
|
|
return true;
|
|
}
|
|
|
|
void destroy()
|
|
{
|
|
deleteMappings();
|
|
constexpr size_t fpcb_size = sizeof(Sh4RCB::fpcb);
|
|
Result rc = svcUnmapProcessCodeMemory(envGetOwnProcessHandle(), (u64)reserved_base + fpcb_size,
|
|
(u64)ramBase + fpcb_size, sizeof(Sh4RCB) - fpcb_size);
|
|
if (R_FAILED(rc))
|
|
ERROR_LOG(VMEM, "Failed to Unmap Sh4RCB -> %x", rc);
|
|
if (virtmemReservation != nullptr)
|
|
{
|
|
virtmemLock();
|
|
virtmemRemoveReservation(virtmemReservation);
|
|
virtmemUnlock();
|
|
virtmemReservation = nullptr;
|
|
}
|
|
free(ramBase);
|
|
ramBase = nullptr;
|
|
NOTICE_LOG(VMEM, "virtmem::destroy done");
|
|
}
|
|
|
|
// Flush (unmap) the FPCB array
|
|
void reset_mem(void *ptr, unsigned size)
|
|
{
|
|
u64 offset = (u8 *)ptr - (u8 *)reserved_base;
|
|
DEBUG_LOG(VMEM, "Unmapping %lx from %p size %x", (u64)ramBase + offset, ptr, size);
|
|
Handle process = envGetOwnProcessHandle();
|
|
for (; offset < size; offset += PAGE_SIZE)
|
|
{
|
|
svcUnmapProcessCodeMemory(process, (u64)ptr + offset, (u64)ramBase + offset, PAGE_SIZE);
|
|
// fails if page is unmapped, which isn't an error
|
|
}
|
|
}
|
|
|
|
// Allocates a memory page for the FPCB array
|
|
void ondemand_page(void *address, unsigned size)
|
|
{
|
|
u64 offset = (u8 *)address - (u8 *)reserved_base;
|
|
//DEBUG_LOG(VMEM, "Mapping %lx to %p size %x", (u64)ramBase + offset, address, size);
|
|
Result rc = svcMapProcessCodeMemory(envGetOwnProcessHandle(), (u64)address, (u64)ramBase + offset, size);
|
|
if (R_FAILED(rc)) {
|
|
ERROR_LOG(VMEM, "svcMapProcessCodeMemory(%p, %lx, %x) failed: %x", address, (u64)ramBase + offset, size, rc);
|
|
return;
|
|
}
|
|
rc = svcSetProcessMemoryPermission(envGetOwnProcessHandle(), (u64)address, size, Perm_Rw);
|
|
if (R_FAILED(rc))
|
|
ERROR_LOG(VMEM, "svcSetProcessMemoryPermission(%p, %x) failed: %x", address, size, rc);
|
|
}
|
|
|
|
static void deleteMappings()
|
|
{
|
|
if (memMappings == nullptr)
|
|
return;
|
|
std::unordered_set<u64> backingAddrs;
|
|
Handle process = envGetOwnProcessHandle();
|
|
for (u32 i = 0; i < memMappingCount; i++)
|
|
{
|
|
// Ignore unmapped stuff
|
|
if (memMappings[i].memsize == 0)
|
|
continue;
|
|
if (!memMappings[i].allow_writes) // don't (un)map ARAM read-only
|
|
continue;
|
|
|
|
u64 ramOffset = (u64)ramBase + sizeof(Sh4RCB) + memMappings[i].memoffset;
|
|
if (backingAddrs.count(ramOffset) != 0)
|
|
continue;
|
|
backingAddrs.insert(ramOffset);
|
|
u64 offset = memMappings[i].start_address + sizeof(Sh4RCB);
|
|
Result rc = svcUnmapProcessCodeMemory(process, (u64)reserved_base + offset,
|
|
ramOffset, memMappings[i].memsize);
|
|
if (R_FAILED(rc))
|
|
ERROR_LOG(VMEM, "deleteMappings: error unmapping offset %lx from %lx", memMappings[i].memoffset, offset);
|
|
}
|
|
delete [] memMappings;
|
|
memMappings = nullptr;
|
|
memMappingCount = 0;
|
|
}
|
|
|
|
// Creates mappings to the underlying ram (not including mirroring sections)
|
|
void create_mappings(const Mapping *vmem_maps, unsigned nummaps)
|
|
{
|
|
deleteMappings();
|
|
memMappingCount = nummaps;
|
|
memMappings = new Mapping[nummaps];
|
|
memcpy(memMappings, vmem_maps, nummaps * sizeof(Mapping));
|
|
|
|
std::unordered_set<u64> backingAddrs;
|
|
Handle process = envGetOwnProcessHandle();
|
|
for (u32 i = 0; i < nummaps; i++)
|
|
{
|
|
// Ignore unmapped stuff, it is already reserved as PROT_NONE
|
|
if (vmem_maps[i].memsize == 0)
|
|
continue;
|
|
if (!vmem_maps[i].allow_writes) // don't map ARAM read-only
|
|
continue;
|
|
|
|
u64 ramOffset = (u64)ramBase + sizeof(Sh4RCB) + vmem_maps[i].memoffset;
|
|
if (backingAddrs.count(ramOffset) != 0)
|
|
// already mapped once so ignore other mirrors
|
|
continue;
|
|
backingAddrs.insert(ramOffset);
|
|
u64 offset = vmem_maps[i].start_address + sizeof(Sh4RCB);
|
|
Result rc = svcMapProcessCodeMemory(process, (u64)reserved_base + offset, ramOffset, vmem_maps[i].memsize);
|
|
if (R_FAILED(rc)) {
|
|
ERROR_LOG(VMEM, "create_mappings: error mapping offset %lx to %lx", vmem_maps[i].memoffset, offset);
|
|
}
|
|
else
|
|
{
|
|
rc = svcSetProcessMemoryPermission(process, (u64)reserved_base + offset, vmem_maps[i].memsize, Perm_Rw);
|
|
if (R_FAILED(rc))
|
|
ERROR_LOG(VMEM, "svcSetProcessMemoryPermission() failed: %x", rc);
|
|
else
|
|
DEBUG_LOG(VMEM, "create_mappings: mapped offset %lx to %lx size %lx", vmem_maps[i].memoffset, offset, vmem_maps[i].memsize);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Prepares the code region for JIT operations, thus marking it as RWX
|
|
bool prepare_jit_block(void *code_area, size_t size, void **code_area_rwx)
|
|
{
|
|
die("Not supported in libnx");
|
|
|
|
return false;
|
|
}
|
|
|
|
void release_jit_block(void *code_area, size_t size)
|
|
{
|
|
die("Not supported in libnx");
|
|
}
|
|
|
|
// Use two addr spaces: need to remap something twice, therefore use allocate_shared_filemem()
|
|
bool prepare_jit_block(void *code_area, size_t size, void **code_area_rw, ptrdiff_t *rx_offset)
|
|
{
|
|
const size_t size_aligned = ((size + PAGE_SIZE) & (~(PAGE_SIZE-1)));
|
|
|
|
virtmemLock();
|
|
void* ptr_rw = virtmemFindAslr(size_aligned, 0);
|
|
bool failure = ptr_rw == nullptr
|
|
|| R_FAILED(svcMapProcessMemory(ptr_rw, envGetOwnProcessHandle(), (u64)code_area, size_aligned));
|
|
virtmemUnlock();
|
|
if (failure)
|
|
{
|
|
ERROR_LOG(DYNAREC, "Failed to map jit rw block...");
|
|
return false;
|
|
}
|
|
|
|
*code_area_rw = ptr_rw;
|
|
*rx_offset = (char*)code_area - (char*)ptr_rw;
|
|
INFO_LOG(DYNAREC, "Info: Using NO_RWX mode, rx ptr: %p, rw ptr: %p, offset: %ld\n", code_area, ptr_rw, (long)*rx_offset);
|
|
|
|
return true;
|
|
}
|
|
|
|
void release_jit_block(void *code_area1, void *code_area2, size_t size)
|
|
{
|
|
const size_t size_aligned = ((size + PAGE_SIZE) & (~(PAGE_SIZE-1)));
|
|
virtmemLock();
|
|
svcUnmapProcessMemory(code_area2, envGetOwnProcessHandle(), (u64)code_area1, size_aligned);
|
|
virtmemUnlock();
|
|
}
|
|
|
|
} // namespace virtmem
|
|
|
|
#include <ucontext.h>
|
|
void fault_handler(int sn, siginfo_t * si, void *segfault_ctx);
|
|
|
|
extern "C"
|
|
{
|
|
alignas(16) u8 __nx_exception_stack[0x1000];
|
|
u64 __nx_exception_stack_size = sizeof(__nx_exception_stack);
|
|
|
|
void context_switch_aarch64(void* context);
|
|
|
|
void __libnx_exception_handler(ThreadExceptionDump *ctx)
|
|
{
|
|
alignas(16) static ucontext_t u_ctx;
|
|
u_ctx.uc_mcontext.pc = ctx->pc.x;
|
|
for (int i = 0; i < 29; i++)
|
|
u_ctx.uc_mcontext.regs[i] = ctx->cpu_gprs[i].x;
|
|
|
|
siginfo_t sig_info;
|
|
sig_info.si_addr = (void*)ctx->far.x;
|
|
|
|
fault_handler(0, &sig_info, (void *)&u_ctx);
|
|
|
|
alignas(16) static uint64_t savedRegisters[66];
|
|
uint64_t *ptr = savedRegisters;
|
|
// fpu registers (64 bits)
|
|
for (int i = 0; i < 32; i++)
|
|
ptr[i] = *(u64 *)&ctx->fpu_gprs[i].d;
|
|
// cpu gp registers
|
|
for (int i = 0; i < 29; i++)
|
|
ptr[i + 32] = u_ctx.uc_mcontext.regs[i];
|
|
// Special regs
|
|
ptr[29 + 32] = ctx->fp.x; // frame pointer
|
|
ptr[30 + 32] = ctx->lr.x; // link register
|
|
ptr[31 + 32] = ctx->pstate; // sprs
|
|
ptr[32 + 32] = ctx->sp.x; // stack pointer
|
|
ptr[33 + 32] = u_ctx.uc_mcontext.pc; // PC
|
|
|
|
context_switch_aarch64(ptr);
|
|
}
|
|
}
|
|
|
|
#ifndef LIBRETRO
|
|
[[noreturn]] void os_DebugBreak()
|
|
{
|
|
diagAbortWithResult(MAKERESULT(350, 1));
|
|
}
|
|
#endif
|
|
#endif // __SWITCH__
|