2013-04-18 03:09:55 +00:00
|
|
|
// Copyright 2013 Dolphin Emulator Project
|
|
|
|
// Licensed under GPLv2
|
|
|
|
// Refer to the license.txt file included.
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2013-10-19 22:58:02 +00:00
|
|
|
#include <set>
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2010-07-18 05:17:09 +00:00
|
|
|
#include "MemoryUtil.h"
|
2008-12-08 05:30:24 +00:00
|
|
|
#include "MemArena.h"
|
|
|
|
|
2009-01-16 02:58:34 +00:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include <windows.h>
|
|
|
|
#else
|
2010-07-29 17:52:43 +00:00
|
|
|
#include <sys/stat.h>
|
2008-12-08 05:30:24 +00:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <cerrno>
|
|
|
|
#include <cstring>
|
2013-02-26 19:49:00 +00:00
|
|
|
#ifdef ANDROID
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <linux/ashmem.h>
|
|
|
|
#endif
|
2008-12-08 05:30:24 +00:00
|
|
|
#endif
|
|
|
|
|
2013-02-26 19:49:00 +00:00
|
|
|
#ifdef ANDROID
|
|
|
|
#define ASHMEM_DEVICE "/dev/ashmem"
|
|
|
|
|
|
|
|
int AshmemCreateFileMapping(const char *name, size_t size)
|
|
|
|
{
|
|
|
|
int fd, ret;
|
|
|
|
fd = open(ASHMEM_DEVICE, O_RDWR);
|
|
|
|
if (fd < 0)
|
|
|
|
return fd;
|
2013-03-20 01:51:12 +00:00
|
|
|
|
2013-10-29 05:09:01 +00:00
|
|
|
// We don't really care if we can't set the name, it is optional
|
2013-02-26 19:49:00 +00:00
|
|
|
ret = ioctl(fd, ASHMEM_SET_NAME, name);
|
2013-03-20 01:51:12 +00:00
|
|
|
|
2013-02-26 19:49:00 +00:00
|
|
|
ret = ioctl(fd, ASHMEM_SET_SIZE, size);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
close(fd);
|
|
|
|
NOTICE_LOG(MEMMAP, "Ashmem returned error: 0x%08x", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
return fd;
|
|
|
|
}
|
|
|
|
#endif
|
2008-12-08 05:30:24 +00:00
|
|
|
|
|
|
|
void MemArena::GrabLowMemSpace(size_t size)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
2010-03-27 06:37:37 +00:00
|
|
|
hMemoryMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)(size), NULL);
|
2013-02-26 19:49:00 +00:00
|
|
|
#elif defined(ANDROID)
|
|
|
|
fd = AshmemCreateFileMapping("Dolphin-emu", size);
|
|
|
|
if (fd < 0)
|
|
|
|
{
|
|
|
|
NOTICE_LOG(MEMMAP, "Ashmem allocation failed");
|
|
|
|
return;
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
#else
|
Fix an idiotic race condition when starting games in multiple Dolphin instances at the same time on Unix.
MemArena mmaps the emulated memory from a file in order to get the same
mapping at multiple addresses. A file which, formerly, was located at a
static filename: it was unlinked after creation, but the open did not
use O_EXCL, so if two instances started up on the same system at just
the right time, they would get the same memory. Naturally, this caused
extremely mysterious crashes, but only in Netplay, where the game is
automatically started when the client receives a broadcast from the
server, so races are actually quite likely.
And switch to shm_open, because it fits the bill better and avoids any
issues with using /tmp.
2013-12-10 05:27:20 +00:00
|
|
|
char fn[64];
|
|
|
|
for (int i = 0; i < 10000; i++)
|
|
|
|
{
|
|
|
|
sprintf(fn, "dolphinmem.%d", i);
|
|
|
|
fd = shm_open(fn, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
|
|
if (fd != -1)
|
|
|
|
break;
|
|
|
|
if (errno != EEXIST)
|
|
|
|
{
|
|
|
|
ERROR_LOG(MEMMAP, "shm_open failed: %s", strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
shm_unlink(fn);
|
2013-01-31 21:29:29 +00:00
|
|
|
if (ftruncate(fd, size) < 0)
|
|
|
|
ERROR_LOG(MEMMAP, "Failed to allocate low memory space");
|
2008-12-08 05:30:24 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MemArena::ReleaseSpace()
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
CloseHandle(hMemoryMapping);
|
|
|
|
hMemoryMapping = 0;
|
|
|
|
#else
|
|
|
|
close(fd);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-12 03:54:50 +00:00
|
|
|
void *MemArena::CreateView(s64 offset, size_t size, void *base)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
2009-01-19 21:42:24 +00:00
|
|
|
return MapViewOfFileEx(hMemoryMapping, FILE_MAP_ALL_ACCESS, 0, (DWORD)((u64)offset), size, base);
|
2008-12-08 05:30:24 +00:00
|
|
|
#else
|
2012-07-12 03:54:50 +00:00
|
|
|
void *retval = mmap(
|
|
|
|
base, size,
|
|
|
|
PROT_READ | PROT_WRITE,
|
|
|
|
MAP_SHARED | ((base == nullptr) ? 0 : MAP_FIXED),
|
|
|
|
fd, offset);
|
|
|
|
|
|
|
|
if (retval == MAP_FAILED)
|
|
|
|
{
|
Fix an idiotic race condition when starting games in multiple Dolphin instances at the same time on Unix.
MemArena mmaps the emulated memory from a file in order to get the same
mapping at multiple addresses. A file which, formerly, was located at a
static filename: it was unlinked after creation, but the open did not
use O_EXCL, so if two instances started up on the same system at just
the right time, they would get the same memory. Naturally, this caused
extremely mysterious crashes, but only in Netplay, where the game is
automatically started when the client receives a broadcast from the
server, so races are actually quite likely.
And switch to shm_open, because it fits the bill better and avoids any
issues with using /tmp.
2013-12-10 05:27:20 +00:00
|
|
|
NOTICE_LOG(MEMMAP, "mmap failed");
|
2012-07-12 03:54:50 +00:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return retval;
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MemArena::ReleaseView(void* view, size_t size)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
UnmapViewOfFile(view);
|
|
|
|
#else
|
|
|
|
munmap(view, size);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
u8* MemArena::Find4GBBase()
|
|
|
|
{
|
|
|
|
#ifdef _M_X64
|
|
|
|
#ifdef _WIN32
|
|
|
|
// 64 bit
|
|
|
|
u8* base = (u8*)VirtualAlloc(0, 0xE1000000, MEM_RESERVE, PAGE_READWRITE);
|
|
|
|
VirtualFree(base, 0, MEM_RELEASE);
|
|
|
|
return base;
|
|
|
|
#else
|
|
|
|
// Very precarious - mmap cannot return an error when trying to map already used pages.
|
|
|
|
// This makes the Windows approach above unusable on Linux, so we will simply pray...
|
|
|
|
return reinterpret_cast<u8*>(0x2300000000ULL);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#else
|
|
|
|
// 32 bit
|
|
|
|
#ifdef _WIN32
|
|
|
|
// The highest thing in any 1GB section of memory space is the locked cache. We only need to fit it.
|
|
|
|
u8* base = (u8*)VirtualAlloc(0, 0x31000000, MEM_RESERVE, PAGE_READWRITE);
|
|
|
|
if (base) {
|
|
|
|
VirtualFree(base, 0, MEM_RELEASE);
|
|
|
|
}
|
|
|
|
return base;
|
|
|
|
#else
|
2013-03-25 02:06:34 +00:00
|
|
|
#ifdef ANDROID
|
2013-09-02 09:10:21 +00:00
|
|
|
// Android 4.3 changed how mmap works.
|
|
|
|
// if we map it private and then munmap it, we can't use the base returned.
|
|
|
|
// This may be due to changes in them support a full SELinux implementation.
|
2013-09-30 01:53:32 +00:00
|
|
|
const int flags = MAP_ANON | MAP_SHARED;
|
2013-03-25 02:06:34 +00:00
|
|
|
#else
|
2013-09-02 09:10:21 +00:00
|
|
|
const int flags = MAP_ANON | MAP_PRIVATE;
|
2013-03-25 02:06:34 +00:00
|
|
|
#endif
|
2013-09-02 09:10:21 +00:00
|
|
|
const u32 MemSize = 0x31000000;
|
|
|
|
void* base = mmap(0, MemSize, PROT_NONE, flags, -1, 0);
|
2008-12-08 05:30:24 +00:00
|
|
|
if (base == MAP_FAILED) {
|
|
|
|
PanicAlert("Failed to map 1 GB of memory space: %s", strerror(errno));
|
|
|
|
return 0;
|
|
|
|
}
|
2013-08-16 09:55:33 +00:00
|
|
|
munmap(base, MemSize);
|
2008-12-08 05:30:24 +00:00
|
|
|
return static_cast<u8*>(base);
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
}
|
2009-11-07 18:53:10 +00:00
|
|
|
|
|
|
|
|
|
|
|
// yeah, this could also be done in like two bitwise ops...
|
|
|
|
#define SKIP(a_flags, b_flags) \
|
|
|
|
if (!(a_flags & MV_WII_ONLY) && (b_flags & MV_WII_ONLY)) \
|
|
|
|
continue; \
|
|
|
|
if (!(a_flags & MV_FAKE_VMEM) && (b_flags & MV_FAKE_VMEM)) \
|
|
|
|
continue; \
|
|
|
|
|
|
|
|
|
|
|
|
static bool Memory_TryBase(u8 *base, const MemoryView *views, int num_views, u32 flags, MemArena *arena) {
|
|
|
|
// OK, we know where to find free space. Now grab it!
|
|
|
|
// We just mimic the popular BAT setup.
|
|
|
|
u32 position = 0;
|
|
|
|
u32 last_position = 0;
|
|
|
|
|
|
|
|
// Zero all the pointers to be sure.
|
|
|
|
for (int i = 0; i < num_views; i++)
|
|
|
|
{
|
|
|
|
if (views[i].out_ptr_low)
|
|
|
|
*views[i].out_ptr_low = 0;
|
|
|
|
if (views[i].out_ptr)
|
|
|
|
*views[i].out_ptr = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < num_views; i++)
|
|
|
|
{
|
|
|
|
SKIP(flags, views[i].flags);
|
|
|
|
if (views[i].flags & MV_MIRROR_PREVIOUS) {
|
|
|
|
position = last_position;
|
|
|
|
} else {
|
|
|
|
*(views[i].out_ptr_low) = (u8*)arena->CreateView(position, views[i].size);
|
|
|
|
if (!*views[i].out_ptr_low)
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
#ifdef _M_X64
|
2012-07-12 03:54:50 +00:00
|
|
|
*views[i].out_ptr = (u8*)arena->CreateView(
|
2009-11-07 18:53:10 +00:00
|
|
|
position, views[i].size, base + views[i].virtual_address);
|
|
|
|
#else
|
|
|
|
if (views[i].flags & MV_MIRROR_PREVIOUS) {
|
|
|
|
// No need to create multiple identical views.
|
|
|
|
*views[i].out_ptr = *views[i - 1].out_ptr;
|
|
|
|
} else {
|
2012-07-12 03:54:50 +00:00
|
|
|
*views[i].out_ptr = (u8*)arena->CreateView(
|
2009-11-07 18:53:10 +00:00
|
|
|
position, views[i].size, base + (views[i].virtual_address & 0x3FFFFFFF));
|
|
|
|
if (!*views[i].out_ptr)
|
|
|
|
goto bail;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
last_position = position;
|
|
|
|
position += views[i].size;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
bail:
|
|
|
|
// Argh! ERROR! Free what we grabbed so far so we can try again.
|
2013-08-13 14:27:47 +00:00
|
|
|
MemoryMap_Shutdown(views, i+1, flags, arena);
|
2009-11-07 18:53:10 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena *arena)
|
|
|
|
{
|
|
|
|
u32 total_mem = 0;
|
2010-01-08 22:56:27 +00:00
|
|
|
int base_attempts = 0;
|
|
|
|
|
2009-11-07 18:53:10 +00:00
|
|
|
for (int i = 0; i < num_views; i++)
|
|
|
|
{
|
|
|
|
SKIP(flags, views[i].flags);
|
|
|
|
if ((views[i].flags & MV_MIRROR_PREVIOUS) == 0)
|
|
|
|
total_mem += views[i].size;
|
|
|
|
}
|
|
|
|
// Grab some pagefile backed memory out of the void ...
|
|
|
|
arena->GrabLowMemSpace(total_mem);
|
|
|
|
|
|
|
|
// Now, create views in high memory where there's plenty of space.
|
|
|
|
#ifdef _M_X64
|
|
|
|
u8 *base = MemArena::Find4GBBase();
|
|
|
|
// This really shouldn't fail - in 64-bit, there will always be enough
|
|
|
|
// address space.
|
|
|
|
if (!Memory_TryBase(base, views, num_views, flags, arena))
|
|
|
|
{
|
|
|
|
PanicAlert("MemoryMap_Setup: Failed finding a memory base.");
|
|
|
|
exit(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#ifdef _WIN32
|
|
|
|
// Try a whole range of possible bases. Return once we got a valid one.
|
|
|
|
u32 max_base_addr = 0x7FFF0000 - 0x31000000;
|
|
|
|
u8 *base = NULL;
|
2010-01-08 22:56:27 +00:00
|
|
|
|
2010-01-08 21:57:31 +00:00
|
|
|
for (u32 base_addr = 0x40000; base_addr < max_base_addr; base_addr += 0x40000)
|
2009-11-07 18:53:10 +00:00
|
|
|
{
|
2010-01-08 22:56:27 +00:00
|
|
|
base_attempts++;
|
2009-11-07 18:53:10 +00:00
|
|
|
base = (u8 *)base_addr;
|
2013-10-29 05:23:17 +00:00
|
|
|
if (Memory_TryBase(base, views, num_views, flags, arena))
|
2009-11-07 18:53:10 +00:00
|
|
|
{
|
|
|
|
INFO_LOG(MEMMAP, "Found valid memory base at %p after %i tries.", base, base_attempts);
|
2010-01-08 21:57:31 +00:00
|
|
|
base_attempts = 0;
|
2009-11-07 18:53:10 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
// Linux32 is fine with the x64 method, although limited to 32-bit with no automirrors.
|
|
|
|
u8 *base = MemArena::Find4GBBase();
|
|
|
|
if (!Memory_TryBase(base, views, num_views, flags, arena))
|
|
|
|
{
|
|
|
|
PanicAlert("MemoryMap_Setup: Failed finding a memory base.");
|
|
|
|
exit(0);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif
|
2010-01-08 21:57:31 +00:00
|
|
|
if (base_attempts)
|
2009-11-07 18:53:10 +00:00
|
|
|
PanicAlert("No possible memory base pointer found!");
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MemoryMap_Shutdown(const MemoryView *views, int num_views, u32 flags, MemArena *arena)
|
|
|
|
{
|
2013-08-13 14:27:47 +00:00
|
|
|
std::set<void*> freeset;
|
2009-11-07 18:53:10 +00:00
|
|
|
for (int i = 0; i < num_views; i++)
|
|
|
|
{
|
2013-08-13 14:27:47 +00:00
|
|
|
const MemoryView* view = &views[i];
|
|
|
|
u8** outptrs[2] = {view->out_ptr_low, view->out_ptr};
|
2013-10-29 05:09:01 +00:00
|
|
|
for (auto outptr : outptrs)
|
2013-08-13 14:27:47 +00:00
|
|
|
{
|
|
|
|
if (outptr && *outptr && !freeset.count(*outptr))
|
|
|
|
{
|
|
|
|
arena->ReleaseView(*outptr, view->size);
|
|
|
|
freeset.insert(*outptr);
|
|
|
|
*outptr = NULL;
|
|
|
|
}
|
|
|
|
}
|
2009-11-07 18:53:10 +00:00
|
|
|
}
|
2009-11-15 22:26:39 +00:00
|
|
|
}
|