2013-04-17 21:09:55 -06:00
|
|
|
// Copyright 2013 Dolphin Emulator Project
|
2015-05-17 17:08:10 -06:00
|
|
|
// Licensed under GPLv2+
|
2013-04-17 21:09:55 -06:00
|
|
|
// Refer to the license.txt file included.
|
2008-12-07 22:30:24 -07:00
|
|
|
|
2014-02-19 20:11:52 -07:00
|
|
|
#include <cstddef>
|
|
|
|
#include <cstdlib>
|
2013-10-19 16:58:02 -06:00
|
|
|
#include <set>
|
2014-02-19 20:11:52 -07:00
|
|
|
#include <string>
|
2008-12-07 22:30:24 -07:00
|
|
|
|
2014-09-07 19:06:58 -06:00
|
|
|
#include "Common/CommonTypes.h"
|
2014-02-17 03:18:15 -07:00
|
|
|
#include "Common/MemArena.h"
|
|
|
|
#include "Common/StringUtil.h"
|
2008-12-07 22:30:24 -07:00
|
|
|
|
2009-01-15 19:58:34 -07:00
|
|
|
#ifdef _WIN32
|
|
|
|
#include <windows.h>
|
|
|
|
#else
|
2008-12-07 22:30:24 -07:00
|
|
|
#include <cerrno>
|
|
|
|
#include <cstring>
|
2014-02-19 20:11:52 -07:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <sys/mman.h>
|
2013-02-26 12:49:00 -07:00
|
|
|
#ifdef ANDROID
|
|
|
|
#include <sys/ioctl.h>
|
|
|
|
#include <linux/ashmem.h>
|
|
|
|
#endif
|
2008-12-07 22:30:24 -07:00
|
|
|
#endif
|
|
|
|
|
2013-02-26 12:49:00 -07:00
|
|
|
#ifdef ANDROID
|
|
|
|
#define ASHMEM_DEVICE "/dev/ashmem"
|
|
|
|
|
2014-09-22 15:45:42 -06:00
|
|
|
static int AshmemCreateFileMapping(const char *name, size_t size)
|
2013-02-26 12:49:00 -07:00
|
|
|
{
|
|
|
|
int fd, ret;
|
|
|
|
fd = open(ASHMEM_DEVICE, O_RDWR);
|
|
|
|
if (fd < 0)
|
|
|
|
return fd;
|
2013-03-19 19:51:12 -06:00
|
|
|
|
2013-10-28 23:09:01 -06:00
|
|
|
// We don't really care if we can't set the name, it is optional
|
2014-02-23 15:03:39 -07:00
|
|
|
ioctl(fd, ASHMEM_SET_NAME, name);
|
2013-03-19 19:51:12 -06:00
|
|
|
|
2013-02-26 12:49:00 -07: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-07 22:30:24 -07:00
|
|
|
|
2014-11-01 14:30:14 -06:00
|
|
|
void MemArena::GrabSHMSegment(size_t size)
|
2008-12-07 22:30:24 -07:00
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
2014-03-09 14:14:26 -06:00
|
|
|
hMemoryMapping = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, (DWORD)(size), nullptr);
|
2013-02-26 12:49:00 -07:00
|
|
|
#elif defined(ANDROID)
|
|
|
|
fd = AshmemCreateFileMapping("Dolphin-emu", size);
|
|
|
|
if (fd < 0)
|
|
|
|
{
|
|
|
|
NOTICE_LOG(MEMMAP, "Ashmem allocation failed");
|
|
|
|
return;
|
|
|
|
}
|
2008-12-07 22:30:24 -07: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-09 22:27:20 -07:00
|
|
|
for (int i = 0; i < 10000; i++)
|
|
|
|
{
|
2014-02-07 18:23:34 -07:00
|
|
|
std::string file_name = StringFromFormat("dolphinmem.%d", i);
|
|
|
|
fd = shm_open(file_name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600);
|
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-09 22:27:20 -07:00
|
|
|
if (fd != -1)
|
2014-02-07 18:23:34 -07:00
|
|
|
{
|
|
|
|
shm_unlink(file_name.c_str());
|
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-09 22:27:20 -07:00
|
|
|
break;
|
2014-02-07 18:23:34 -07:00
|
|
|
}
|
|
|
|
else if (errno != EEXIST)
|
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-09 22:27:20 -07:00
|
|
|
{
|
|
|
|
ERROR_LOG(MEMMAP, "shm_open failed: %s", strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2013-01-31 14:29:29 -07:00
|
|
|
if (ftruncate(fd, size) < 0)
|
|
|
|
ERROR_LOG(MEMMAP, "Failed to allocate low memory space");
|
2008-12-07 22:30:24 -07:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-11-01 14:30:14 -06:00
|
|
|
void MemArena::ReleaseSHMSegment()
|
2008-12-07 22:30:24 -07:00
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
CloseHandle(hMemoryMapping);
|
|
|
|
hMemoryMapping = 0;
|
|
|
|
#else
|
|
|
|
close(fd);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-07-11 21:54:50 -06:00
|
|
|
void *MemArena::CreateView(s64 offset, size_t size, void *base)
|
2008-12-07 22:30:24 -07:00
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
2009-01-19 14:42:24 -07:00
|
|
|
return MapViewOfFileEx(hMemoryMapping, FILE_MAP_ALL_ACCESS, 0, (DWORD)((u64)offset), size, base);
|
2008-12-07 22:30:24 -07:00
|
|
|
#else
|
2012-07-11 21:54:50 -06: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-09 22:27:20 -07:00
|
|
|
NOTICE_LOG(MEMMAP, "mmap failed");
|
2012-07-11 21:54:50 -06:00
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return retval;
|
|
|
|
}
|
2008-12-07 22:30:24 -07:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void MemArena::ReleaseView(void* view, size_t size)
|
|
|
|
{
|
|
|
|
#ifdef _WIN32
|
|
|
|
UnmapViewOfFile(view);
|
|
|
|
#else
|
|
|
|
munmap(view, size);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-01-17 14:17:36 -07:00
|
|
|
u8* MemArena::FindMemoryBase()
|
2008-12-07 22:30:24 -07:00
|
|
|
{
|
2014-03-02 04:21:50 -07:00
|
|
|
#if _ARCH_64
|
2008-12-07 22:30:24 -07:00
|
|
|
#ifdef _WIN32
|
|
|
|
// 64 bit
|
2015-01-17 14:17:36 -07:00
|
|
|
u8* base = (u8*)VirtualAlloc(0, 0x400000000, MEM_RESERVE, PAGE_READWRITE);
|
2008-12-07 22:30:24 -07:00
|
|
|
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
|
|
|
|
|
2014-08-10 03:35:14 -06:00
|
|
|
#else // 32 bit
|
2013-03-24 20:06:34 -06:00
|
|
|
#ifdef ANDROID
|
2013-09-02 03:10:21 -06: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-29 19:53:32 -06:00
|
|
|
const int flags = MAP_ANON | MAP_SHARED;
|
2013-03-24 20:06:34 -06:00
|
|
|
#else
|
2013-09-02 03:10:21 -06:00
|
|
|
const int flags = MAP_ANON | MAP_PRIVATE;
|
2013-03-24 20:06:34 -06:00
|
|
|
#endif
|
2013-09-02 03:10:21 -06:00
|
|
|
const u32 MemSize = 0x31000000;
|
|
|
|
void* base = mmap(0, MemSize, PROT_NONE, flags, -1, 0);
|
2014-08-30 14:14:56 -06:00
|
|
|
if (base == MAP_FAILED)
|
|
|
|
{
|
2008-12-07 22:30:24 -07:00
|
|
|
PanicAlert("Failed to map 1 GB of memory space: %s", strerror(errno));
|
|
|
|
return 0;
|
|
|
|
}
|
2013-08-16 03:55:33 -06:00
|
|
|
munmap(base, MemSize);
|
2008-12-07 22:30:24 -07:00
|
|
|
return static_cast<u8*>(base);
|
|
|
|
#endif
|
|
|
|
}
|
2009-11-07 11:53:10 -07: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; \
|
|
|
|
|
2014-10-28 05:02:12 -06:00
|
|
|
static bool Memory_TryBase(u8 *base, MemoryView *views, int num_views, u32 flags, MemArena *arena)
|
2014-08-30 14:14:56 -06:00
|
|
|
{
|
2009-11-07 11:53:10 -07:00
|
|
|
// OK, we know where to find free space. Now grab it!
|
|
|
|
// We just mimic the popular BAT setup.
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < num_views; i++)
|
|
|
|
{
|
2014-10-28 05:44:59 -06:00
|
|
|
MemoryView* view = &views[i];
|
|
|
|
void* view_base;
|
|
|
|
bool use_sw_mirror;
|
2014-10-28 05:02:12 -06:00
|
|
|
|
2014-10-28 05:44:59 -06:00
|
|
|
SKIP(flags, view->flags);
|
2014-10-28 05:02:12 -06:00
|
|
|
|
2014-03-02 04:21:50 -07:00
|
|
|
#if _ARCH_64
|
2014-10-28 05:44:59 -06:00
|
|
|
// On 64-bit, we map the same file position multiple times, so we
|
|
|
|
// don't need the software fallback for the mirrors.
|
|
|
|
view_base = base + view->virtual_address;
|
|
|
|
use_sw_mirror = false;
|
2009-11-07 11:53:10 -07:00
|
|
|
#else
|
2014-10-28 05:44:59 -06:00
|
|
|
// On 32-bit, we don't have the actual address space to store all
|
|
|
|
// the mirrors, so we just map the fallbacks somewhere in our address
|
|
|
|
// space and use the software fallbacks for mirroring.
|
|
|
|
view_base = base + (view->virtual_address & 0x3FFFFFFF);
|
|
|
|
use_sw_mirror = true;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (use_sw_mirror && (view->flags & MV_MIRROR_PREVIOUS))
|
2014-08-30 14:14:56 -06:00
|
|
|
{
|
2014-10-28 05:44:59 -06:00
|
|
|
view->view_ptr = views[i - 1].view_ptr;
|
2014-08-30 14:14:56 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-10-28 05:44:59 -06:00
|
|
|
view->mapped_ptr = arena->CreateView(view->shm_position, view->size, view_base);
|
|
|
|
view->view_ptr = view->mapped_ptr;
|
2009-11-07 11:53:10 -07:00
|
|
|
}
|
2014-10-28 04:45:01 -06:00
|
|
|
|
2014-10-28 05:44:59 -06:00
|
|
|
if (!view->view_ptr)
|
2014-11-03 20:45:54 -07:00
|
|
|
{
|
|
|
|
// Argh! ERROR! Free what we grabbed so far so we can try again.
|
|
|
|
MemoryMap_Shutdown(views, i+1, flags, arena);
|
|
|
|
return false;
|
|
|
|
}
|
2014-10-28 05:02:12 -06:00
|
|
|
|
2014-10-28 05:44:59 -06:00
|
|
|
if (view->out_ptr)
|
|
|
|
*(view->out_ptr) = (u8*) view->view_ptr;
|
2009-11-07 11:53:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-11-03 11:04:50 -07:00
|
|
|
static u32 MemoryMap_InitializeViews(MemoryView *views, int num_views, u32 flags)
|
2009-11-07 11:53:10 -07:00
|
|
|
{
|
2014-11-02 20:24:21 -07:00
|
|
|
u32 shm_position = 0;
|
2014-11-03 20:38:42 -07:00
|
|
|
u32 last_position = 0;
|
2010-01-08 15:56:27 -07:00
|
|
|
|
2009-11-07 11:53:10 -07:00
|
|
|
for (int i = 0; i < num_views; i++)
|
|
|
|
{
|
2014-11-02 20:24:21 -07:00
|
|
|
// Zero all the pointers to be sure.
|
|
|
|
views[i].mapped_ptr = nullptr;
|
|
|
|
|
2009-11-07 11:53:10 -07:00
|
|
|
SKIP(flags, views[i].flags);
|
2014-11-02 20:24:21 -07:00
|
|
|
|
2014-11-03 11:04:50 -07:00
|
|
|
if (views[i].flags & MV_MIRROR_PREVIOUS)
|
|
|
|
shm_position = last_position;
|
2014-11-02 20:24:21 -07:00
|
|
|
views[i].shm_position = shm_position;
|
2014-11-03 11:04:50 -07:00
|
|
|
last_position = shm_position;
|
|
|
|
shm_position += views[i].size;
|
2009-11-07 11:53:10 -07:00
|
|
|
}
|
2014-11-03 11:04:50 -07:00
|
|
|
|
|
|
|
return shm_position;
|
2014-11-02 20:24:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
u8 *MemoryMap_Setup(MemoryView *views, int num_views, u32 flags, MemArena *arena)
|
|
|
|
{
|
2014-11-03 11:04:50 -07:00
|
|
|
u32 total_mem = MemoryMap_InitializeViews(views, num_views, flags);
|
2014-11-01 14:30:14 -06:00
|
|
|
|
|
|
|
arena->GrabSHMSegment(total_mem);
|
2009-11-07 11:53:10 -07:00
|
|
|
|
|
|
|
// Now, create views in high memory where there's plenty of space.
|
2015-01-17 14:17:36 -07:00
|
|
|
u8 *base = MemArena::FindMemoryBase();
|
2009-11-07 11:53:10 -07:00
|
|
|
// 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);
|
2014-03-09 14:14:26 -06:00
|
|
|
return nullptr;
|
2009-11-07 11:53:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return base;
|
|
|
|
}
|
|
|
|
|
2014-10-28 05:02:12 -06:00
|
|
|
void MemoryMap_Shutdown(MemoryView *views, int num_views, u32 flags, MemArena *arena)
|
2009-11-07 11:53:10 -07:00
|
|
|
{
|
2013-08-13 08:27:47 -06:00
|
|
|
std::set<void*> freeset;
|
2009-11-07 11:53:10 -07:00
|
|
|
for (int i = 0; i < num_views; i++)
|
|
|
|
{
|
2014-10-28 05:02:12 -06:00
|
|
|
MemoryView* view = &views[i];
|
2014-11-02 18:32:22 -07:00
|
|
|
if (view->mapped_ptr && !freeset.count(view->mapped_ptr))
|
2013-08-13 08:27:47 -06:00
|
|
|
{
|
2014-10-28 05:02:12 -06:00
|
|
|
arena->ReleaseView(view->mapped_ptr, view->size);
|
|
|
|
freeset.insert(view->mapped_ptr);
|
|
|
|
view->mapped_ptr = nullptr;
|
2013-08-13 08:27:47 -06:00
|
|
|
}
|
2009-11-07 11:53:10 -07:00
|
|
|
}
|
2009-11-15 15:26:39 -07:00
|
|
|
}
|