flycast/core/hw/mem/vmem32.cpp

392 lines
10 KiB
C++

/*
Created on: Apr 11, 2019
Copyright 2019 flyinghead
This file is part of reicast.
reicast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
reicast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with reicast. If not, see <https://www.gnu.org/licenses/>.
*/
#include "vmem32.h"
#include "_vmem.h"
#include "hw/sh4/dyna/ngen.h"
#include "hw/sh4/modules/mmu.h"
#include <unordered_set>
#include <mutex>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#ifdef __ANDROID__
#include <linux/ashmem.h>
#endif
#endif
#ifndef MAP_NOSYNC
#define MAP_NOSYNC 0
#endif
extern bool VramLockedWriteOffset(size_t offset);
extern std::mutex vramlist_lock;
#ifdef _WIN32
extern HANDLE mem_handle;
#else
extern int vmem_fd;
#endif
#define VMEM32_ERROR_NOT_MAPPED 0x100
static const u64 VMEM32_SIZE = 0x100000000L;
static const u64 USER_SPACE = 0x80000000L;
static const u64 AREA7_ADDRESS = 0x7C000000L;
#define VRAM_PROT_SEGMENT (1024 * 1024) // vram protection regions are grouped by 1MB segment
static std::unordered_set<u32> vram_mapped_pages;
struct vram_lock {
u32 start;
u32 end;
};
static std::vector<vram_lock> vram_blocks[VRAM_SIZE_MAX / VRAM_PROT_SEGMENT];
static u8 sram_mapped_pages[USER_SPACE / PAGE_SIZE / 8]; // bit set to 1 if page is mapped
bool vmem32_inited;
// stats
//u64 vmem32_page_faults;
//u64 vmem32_flush;
static void* vmem32_map_buffer(u32 dst, u32 addrsz, u32 offset, u32 size, bool write)
{
void* ptr;
void* rv;
//printf("MAP32 %08X w/ %d\n",dst,offset);
u32 map_times = addrsz / size;
#ifdef _WIN32
rv = MapViewOfFileEx(mem_handle, FILE_MAP_READ | (write ? FILE_MAP_WRITE : 0), 0, offset, size, &virt_ram_base[dst]);
if (rv == NULL)
return NULL;
for (u32 i = 1; i < map_times; i++)
{
dst += size;
ptr = MapViewOfFileEx(mem_handle, FILE_MAP_READ | (write ? FILE_MAP_WRITE : 0), 0, offset, size, &virt_ram_base[dst]);
if (ptr == NULL)
return NULL;
}
#else
u32 prot = PROT_READ | (write ? PROT_WRITE : 0);
rv = mmap(&virt_ram_base[dst], size, prot, MAP_SHARED | MAP_NOSYNC | MAP_FIXED, vmem_fd, offset);
if (MAP_FAILED == rv)
{
ERROR_LOG(VMEM, "MAP1 failed %d", errno);
return NULL;
}
for (u32 i = 1; i < map_times; i++)
{
dst += size;
ptr = mmap(&virt_ram_base[dst], size, prot , MAP_SHARED | MAP_NOSYNC | MAP_FIXED, vmem_fd, offset);
if (MAP_FAILED == ptr)
{
ERROR_LOG(VMEM, "MAP2 failed %d", errno);
return NULL;
}
}
#endif
return rv;
}
static void vmem32_unmap_buffer(u32 start, u64 end)
{
#ifdef _WIN32
UnmapViewOfFile(&virt_ram_base[start]);
#else
mmap(&virt_ram_base[start], end - start, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
#endif
}
static void vmem32_protect_buffer(u32 start, u32 size)
{
verify((start & PAGE_MASK) == 0);
#ifdef _WIN32
DWORD old;
VirtualProtect(virt_ram_base + start, size, PAGE_READONLY, &old);
#else
mprotect(&virt_ram_base[start], size, PROT_READ);
#endif
}
static void vmem32_unprotect_buffer(u32 start, u32 size)
{
verify((start & PAGE_MASK) == 0);
#ifdef _WIN32
DWORD old;
VirtualProtect(virt_ram_base + start, size, PAGE_READWRITE, &old);
#else
mprotect(&virt_ram_base[start], size, PROT_READ | PROT_WRITE);
#endif
}
void vmem32_protect_vram(u32 addr, u32 size)
{
if (!vmem32_inited)
return;
for (int page = (addr & VRAM_MASK) / VRAM_PROT_SEGMENT; page <= ((addr & VRAM_MASK) + size - 1) / VRAM_PROT_SEGMENT; page++)
{
vram_blocks[page].push_back({ addr, addr + size - 1 });
}
}
void vmem32_unprotect_vram(u32 addr, u32 size)
{
if (!vmem32_inited)
return;
for (int page = (addr & VRAM_MASK) / VRAM_PROT_SEGMENT; page <= ((addr & VRAM_MASK) + size - 1) / VRAM_PROT_SEGMENT; page++)
{
std::vector<vram_lock>& block_list = vram_blocks[page];
for (auto it = block_list.begin(); it != block_list.end(); )
{
if (it->start >= addr && it->end < addr + size)
it = block_list.erase(it);
else
it++;
}
}
}
static const u32 page_sizes[] = { 1024, 4 * 1024, 64 * 1024, 1024 * 1024 };
static u32 vmem32_paddr_to_offset(u32 address)
{
u32 low_addr = address & 0x1FFFFFFF;
switch ((address >> 26) & 7)
{
case 0: // area 0
// Aica ram
if (low_addr >= 0x00800000 && low_addr < 0x00800000 + 0x00800000)
{
return ((low_addr - 0x00800000) & (ARAM_SIZE - 1)) + MAP_ARAM_START_OFFSET;
}
else if (low_addr >= 0x02800000 && low_addr < 0x02800000 + 0x00800000)
{
return low_addr - 0x02800000 + MAP_ARAM_START_OFFSET;
}
break;
case 1: // area 1
// Vram
if (low_addr >= 0x04000000 && low_addr < 0x04000000 + 0x01000000)
{
return ((low_addr - 0x04000000) & (VRAM_SIZE - 1)) + MAP_VRAM_START_OFFSET;
}
else if (low_addr >= 0x06000000 && low_addr < 0x06000000 + 0x01000000)
{
return ((low_addr - 0x06000000) & (VRAM_SIZE - 1)) + MAP_VRAM_START_OFFSET;
}
break;
case 3: // area 3
// System ram
if (low_addr >= 0x0C000000 && low_addr < 0x0C000000 + 0x04000000)
{
return ((low_addr - 0x0C000000) & (RAM_SIZE - 1)) + MAP_RAM_START_OFFSET;
}
break;
//case 4:
// TODO vram?
//break;
default:
break;
}
// Unmapped address
return -1;
}
static u32 vmem32_map_mmu(u32 address, bool write)
{
#ifndef NO_MMU
u32 pa;
const TLB_Entry *entry;
u32 rc = mmu_full_lookup<false>(address, &entry, pa);
if (rc == MMU_ERROR_NONE)
{
//0X & User mode-> protection violation
//if ((entry->Data.PR >> 1) == 0 && p_sh4rcb->cntx.sr.MD == 0)
// return MMU_ERROR_PROTECTED;
//if (write)
//{
// if ((entry->Data.PR & 1) == 0)
// return MMU_ERROR_PROTECTED;
// if (entry->Data.D == 0)
// return MMU_ERROR_FIRSTWRITE;
//}
u32 page_size = page_sizes[entry->Data.SZ1 * 2 + entry->Data.SZ0];
if (page_size == 1024)
return VMEM32_ERROR_NOT_MAPPED;
u32 vpn = (entry->Address.VPN << 10) & ~(page_size - 1);
u32 ppn = (entry->Data.PPN << 10) & ~(page_size - 1);
u32 offset = vmem32_paddr_to_offset(ppn);
if (offset == -1)
return VMEM32_ERROR_NOT_MAPPED;
bool allow_write = (entry->Data.PR & 1) != 0;
if (offset >= MAP_VRAM_START_OFFSET && offset < MAP_VRAM_START_OFFSET + VRAM_SIZE)
{
// Check vram protected regions
u32 start = offset - MAP_VRAM_START_OFFSET;
if (!vram_mapped_pages.insert(vpn).second)
{
// page has been mapped already: vram locked write
vmem32_unprotect_buffer(address & ~PAGE_MASK, PAGE_SIZE);
u32 addr_offset = start + (address & (page_size - 1));
VramLockedWriteOffset(addr_offset);
return MMU_ERROR_NONE;
}
verify(vmem32_map_buffer(vpn, page_size, offset, page_size, allow_write) != NULL);
u32 end = start + page_size;
const std::vector<vram_lock>& blocks = vram_blocks[start / VRAM_PROT_SEGMENT];
{
std::lock_guard<std::mutex> lock(vramlist_lock);
for (int i = blocks.size() - 1; i >= 0; i--)
{
if (blocks[i].start < end && blocks[i].end >= start)
{
u32 prot_start = std::max(start, blocks[i].start);
u32 prot_size = std::min(end, blocks[i].end + 1) - prot_start;
prot_size += prot_start % PAGE_SIZE;
prot_start &= ~PAGE_MASK;
vmem32_protect_buffer(vpn + (prot_start & (page_size - 1)), prot_size);
}
}
}
}
else if (offset >= MAP_RAM_START_OFFSET && offset < MAP_RAM_START_OFFSET + RAM_SIZE)
{
// Check system RAM protected pages
u32 start = offset - MAP_RAM_START_OFFSET;
if (bm_IsRamPageProtected(start) && allow_write)
{
if (sram_mapped_pages[start >> 15] & (1 << ((start >> 12) & 7)))
{
// Already mapped => write access
vmem32_unprotect_buffer(address & ~PAGE_MASK, PAGE_SIZE);
bm_RamWriteAccess(ppn);
}
else
{
sram_mapped_pages[start >> 15] |= (1 << ((start >> 12) & 7));
verify(vmem32_map_buffer(vpn, page_size, offset, page_size, false) != NULL);
}
}
else
verify(vmem32_map_buffer(vpn, page_size, offset, page_size, allow_write) != NULL);
}
else
// Not vram or system ram
verify(vmem32_map_buffer(vpn, page_size, offset, page_size, allow_write) != NULL);
return MMU_ERROR_NONE;
}
#else
u32 rc = MMU_ERROR_PROTECTED;
#endif
return rc;
}
static u32 vmem32_map_address(u32 address, bool write)
{
u32 area = address >> 29;
switch (area)
{
case 3: // P0/U0
if (address >= AREA7_ADDRESS)
// area 7: unmapped
return VMEM32_ERROR_NOT_MAPPED;
/* no break */
case 0:
case 1:
case 2:
case 6: // P3
return vmem32_map_mmu(address, write);
default:
break;
}
return VMEM32_ERROR_NOT_MAPPED;
}
#if !defined(NO_MMU) && defined(HOST_64BIT_CPU)
bool vmem32_handle_signal(void *fault_addr, bool write, u32 exception_pc)
{
if (!vmem32_inited || (u8*)fault_addr < virt_ram_base || (u8*)fault_addr >= virt_ram_base + VMEM32_SIZE)
return false;
//vmem32_page_faults++;
u32 guest_addr = (u8*)fault_addr - virt_ram_base;
u32 rv = vmem32_map_address(guest_addr, write);
DEBUG_LOG(VMEM, "vmem32_handle_signal handled signal %s @ %p -> %08x rv=%d", write ? "W" : "R", fault_addr, guest_addr, rv);
if (rv == MMU_ERROR_NONE)
return true;
if (rv == VMEM32_ERROR_NOT_MAPPED)
return false;
#if HOST_CPU == CPU_ARM64
p_sh4rcb->cntx.pc = exception_pc;
#else
p_sh4rcb->cntx.pc = p_sh4rcb->cntx.exception_pc;
#endif
DoMMUException(guest_addr, rv, write ? MMU_TT_DWRITE : MMU_TT_DREAD);
ngen_HandleException();
// not reached
return true;
}
#endif
void vmem32_flush_mmu()
{
//vmem32_flush++;
vram_mapped_pages.clear();
memset(sram_mapped_pages, 0, sizeof(sram_mapped_pages));
vmem32_unmap_buffer(0, USER_SPACE);
// TODO flush P3?
}
bool vmem32_init()
{
#ifdef _WIN32
return false;
#else
if (settings.dynarec.disable_vmem32 || !_nvmem_4gb_space())
return false;
vmem32_inited = true;
vmem32_flush_mmu();
return true;
#endif
}
void vmem32_term()
{
if (vmem32_inited)
{
vmem32_inited = false;
vmem32_flush_mmu();
}
}