Ryujinx-libsoundio/src/os.c
2016-02-17 18:05:08 -07:00

748 lines
19 KiB
C

/*
* Copyright (c) 2015 Andrew Kelley
*
* This file is part of libsoundio, which is MIT licensed.
* See http://opensource.org/licenses/MIT
*/
#if defined(__APPLE__)
#define _DARWIN_C_SOURCE
#undef _POSIX_C_SOURCE
#else
#define _GNU_SOURCE
#endif
#include "os.h"
#include "soundio_internal.h"
#include "util.h"
#include <stdlib.h>
#include <time.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#if defined(_WIN32)
#define SOUNDIO_OS_WINDOWS
#if !defined(NOMINMAX)
#define NOMINMAX
#endif
#if !defined(VC_EXTRALEAN)
#define VC_EXTRALEAN
#endif
#if !defined(WIN32_LEAN_AND_MEAN)
#define WIN32_LEAN_AND_MEAN
#endif
#if !defined(UNICODE)
#define UNICODE
#endif
// require Windows 7 or later
#if WINVER < 0x0601
#undef WINVER
#define WINVER 0x0601
#endif
#if _WIN32_WINNT < 0x0601
#undef _WIN32_WINNT
#define _WIN32_WINNT 0x0601
#endif
#include <windows.h>
#include <mmsystem.h>
#include <objbase.h>
#else
#include <pthread.h>
#include <unistd.h>
#include <sys/mman.h>
#if !defined(MAP_ANONYMOUS) && defined(MAP_ANON)
#define MAP_ANONYMOUS MAP_ANON
#endif
#endif
#if defined(__FreeBSD__) || defined(__MACH__)
#define SOUNDIO_OS_KQUEUE
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#endif
#if defined(__MACH__)
#include <mach/clock.h>
#include <mach/mach.h>
#endif
struct SoundIoOsThread {
#if defined(SOUNDIO_OS_WINDOWS)
HANDLE handle;
DWORD id;
#else
pthread_attr_t attr;
bool attr_init;
pthread_t id;
bool running;
#endif
void *arg;
void (*run)(void *arg);
};
struct SoundIoOsMutex {
#if defined(SOUNDIO_OS_WINDOWS)
CRITICAL_SECTION id;
#else
pthread_mutex_t id;
bool id_init;
#endif
};
#if defined(SOUNDIO_OS_KQUEUE)
static const uintptr_t notify_ident = 1;
struct SoundIoOsCond {
int kq_id;
};
#elif defined(SOUNDIO_OS_WINDOWS)
struct SoundIoOsCond {
CONDITION_VARIABLE id;
CRITICAL_SECTION default_cs_id;
};
#else
struct SoundIoOsCond {
pthread_cond_t id;
bool id_init;
pthread_condattr_t attr;
bool attr_init;
pthread_mutex_t default_mutex_id;
bool default_mutex_init;
};
#endif
#if defined(SOUNDIO_OS_WINDOWS)
static INIT_ONCE win32_init_once = INIT_ONCE_STATIC_INIT;
static double win32_time_resolution;
static SYSTEM_INFO win32_system_info;
#else
static bool initialized = false;
static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER;
#if defined(__MACH__)
static clock_serv_t cclock;
#endif
#endif
static int page_size;
double soundio_os_get_time(void) {
#if defined(SOUNDIO_OS_WINDOWS)
unsigned __int64 time;
QueryPerformanceCounter((LARGE_INTEGER*) &time);
return time * win32_time_resolution;
#elif defined(__MACH__)
mach_timespec_t mts;
kern_return_t err = clock_get_time(cclock, &mts);
assert(!err);
double seconds = (double)mts.tv_sec;
seconds += ((double)mts.tv_nsec) / 1000000000.0;
return seconds;
#else
struct timespec tms;
clock_gettime(CLOCK_MONOTONIC, &tms);
double seconds = (double)tms.tv_sec;
seconds += ((double)tms.tv_nsec) / 1000000000.0;
return seconds;
#endif
}
#if defined(SOUNDIO_OS_WINDOWS)
static DWORD WINAPI run_win32_thread(LPVOID userdata) {
struct SoundIoOsThread *thread = (struct SoundIoOsThread *)userdata;
HRESULT err = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
assert(err == S_OK);
thread->run(thread->arg);
CoUninitialize();
return 0;
}
#else
static void assert_no_err(int err) {
assert(!err);
}
static void *run_pthread(void *userdata) {
struct SoundIoOsThread *thread = (struct SoundIoOsThread *)userdata;
thread->run(thread->arg);
return NULL;
}
#endif
int soundio_os_thread_create(
void (*run)(void *arg), void *arg,
void (*emit_rtprio_warning)(void),
struct SoundIoOsThread ** out_thread)
{
*out_thread = NULL;
struct SoundIoOsThread *thread = ALLOCATE(struct SoundIoOsThread, 1);
if (!thread) {
soundio_os_thread_destroy(thread);
return SoundIoErrorNoMem;
}
thread->run = run;
thread->arg = arg;
#if defined(SOUNDIO_OS_WINDOWS)
thread->handle = CreateThread(NULL, 0, run_win32_thread, thread, 0, &thread->id);
if (!thread->handle) {
soundio_os_thread_destroy(thread);
return SoundIoErrorSystemResources;
}
if (emit_rtprio_warning) {
if (!SetThreadPriority(thread->handle, THREAD_PRIORITY_TIME_CRITICAL)) {
emit_rtprio_warning();
}
}
#else
int err;
if ((err = pthread_attr_init(&thread->attr))) {
soundio_os_thread_destroy(thread);
return SoundIoErrorNoMem;
}
thread->attr_init = true;
if (emit_rtprio_warning) {
int max_priority = sched_get_priority_max(SCHED_FIFO);
if (max_priority == -1) {
soundio_os_thread_destroy(thread);
return SoundIoErrorSystemResources;
}
if ((err = pthread_attr_setschedpolicy(&thread->attr, SCHED_FIFO))) {
soundio_os_thread_destroy(thread);
return SoundIoErrorSystemResources;
}
struct sched_param param;
param.sched_priority = max_priority;
if ((err = pthread_attr_setschedparam(&thread->attr, &param))) {
soundio_os_thread_destroy(thread);
return SoundIoErrorSystemResources;
}
}
if ((err = pthread_create(&thread->id, &thread->attr, run_pthread, thread))) {
if (err == EPERM && emit_rtprio_warning) {
emit_rtprio_warning();
err = pthread_create(&thread->id, NULL, run_pthread, thread);
}
if (err) {
soundio_os_thread_destroy(thread);
return SoundIoErrorNoMem;
}
}
thread->running = true;
#endif
*out_thread = thread;
return 0;
}
void soundio_os_thread_destroy(struct SoundIoOsThread *thread) {
if (!thread)
return;
#if defined(SOUNDIO_OS_WINDOWS)
if (thread->handle) {
DWORD err = WaitForSingleObject(thread->handle, INFINITE);
assert(err != WAIT_FAILED);
BOOL ok = CloseHandle(thread->handle);
assert(ok);
}
#else
if (thread->running) {
assert_no_err(pthread_join(thread->id, NULL));
}
if (thread->attr_init) {
assert_no_err(pthread_attr_destroy(&thread->attr));
}
#endif
free(thread);
}
struct SoundIoOsMutex *soundio_os_mutex_create(void) {
struct SoundIoOsMutex *mutex = ALLOCATE(struct SoundIoOsMutex, 1);
if (!mutex) {
soundio_os_mutex_destroy(mutex);
return NULL;
}
#if defined(SOUNDIO_OS_WINDOWS)
InitializeCriticalSection(&mutex->id);
#else
int err;
if ((err = pthread_mutex_init(&mutex->id, NULL))) {
soundio_os_mutex_destroy(mutex);
return NULL;
}
mutex->id_init = true;
#endif
return mutex;
}
void soundio_os_mutex_destroy(struct SoundIoOsMutex *mutex) {
if (!mutex)
return;
#if defined(SOUNDIO_OS_WINDOWS)
DeleteCriticalSection(&mutex->id);
#else
if (mutex->id_init) {
assert_no_err(pthread_mutex_destroy(&mutex->id));
}
#endif
free(mutex);
}
void soundio_os_mutex_lock(struct SoundIoOsMutex *mutex) {
#if defined(SOUNDIO_OS_WINDOWS)
EnterCriticalSection(&mutex->id);
#else
assert_no_err(pthread_mutex_lock(&mutex->id));
#endif
}
void soundio_os_mutex_unlock(struct SoundIoOsMutex *mutex) {
#if defined(SOUNDIO_OS_WINDOWS)
LeaveCriticalSection(&mutex->id);
#else
assert_no_err(pthread_mutex_unlock(&mutex->id));
#endif
}
struct SoundIoOsCond * soundio_os_cond_create(void) {
struct SoundIoOsCond *cond = ALLOCATE(struct SoundIoOsCond, 1);
if (!cond) {
soundio_os_cond_destroy(cond);
return NULL;
}
#if defined(SOUNDIO_OS_WINDOWS)
InitializeConditionVariable(&cond->id);
InitializeCriticalSection(&cond->default_cs_id);
#elif defined(SOUNDIO_OS_KQUEUE)
cond->kq_id = kqueue();
if (cond->kq_id == -1)
return NULL;
#else
if (pthread_condattr_init(&cond->attr)) {
soundio_os_cond_destroy(cond);
return NULL;
}
cond->attr_init = true;
if (pthread_condattr_setclock(&cond->attr, CLOCK_MONOTONIC)) {
soundio_os_cond_destroy(cond);
return NULL;
}
if (pthread_cond_init(&cond->id, &cond->attr)) {
soundio_os_cond_destroy(cond);
return NULL;
}
cond->id_init = true;
if ((pthread_mutex_init(&cond->default_mutex_id, NULL))) {
soundio_os_cond_destroy(cond);
return NULL;
}
cond->default_mutex_init = true;
#endif
return cond;
}
void soundio_os_cond_destroy(struct SoundIoOsCond *cond) {
if (!cond)
return;
#if defined(SOUNDIO_OS_WINDOWS)
DeleteCriticalSection(&cond->default_cs_id);
#elif defined(SOUNDIO_OS_KQUEUE)
close(cond->kq_id);
#else
if (cond->id_init) {
assert_no_err(pthread_cond_destroy(&cond->id));
}
if (cond->attr_init) {
assert_no_err(pthread_condattr_destroy(&cond->attr));
}
if (cond->default_mutex_init) {
assert_no_err(pthread_mutex_destroy(&cond->default_mutex_id));
}
#endif
free(cond);
}
void soundio_os_cond_signal(struct SoundIoOsCond *cond,
struct SoundIoOsMutex *locked_mutex)
{
#if defined(SOUNDIO_OS_WINDOWS)
if (locked_mutex) {
WakeConditionVariable(&cond->id);
} else {
EnterCriticalSection(&cond->default_cs_id);
WakeConditionVariable(&cond->id);
LeaveCriticalSection(&cond->default_cs_id);
}
#elif defined(SOUNDIO_OS_KQUEUE)
struct kevent kev;
struct timespec timeout = { 0, 0 };
memset(&kev, 0, sizeof(kev));
kev.ident = notify_ident;
kev.filter = EVFILT_USER;
kev.fflags = NOTE_TRIGGER;
if (kevent(cond->kq_id, &kev, 1, NULL, 0, &timeout) == -1) {
if (errno == EINTR)
return;
if (errno == ENOENT)
return;
assert(0); // kevent signal error
}
#else
if (locked_mutex) {
assert_no_err(pthread_cond_signal(&cond->id));
} else {
assert_no_err(pthread_mutex_lock(&cond->default_mutex_id));
assert_no_err(pthread_cond_signal(&cond->id));
assert_no_err(pthread_mutex_unlock(&cond->default_mutex_id));
}
#endif
}
void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond,
struct SoundIoOsMutex *locked_mutex, double seconds)
{
#if defined(SOUNDIO_OS_WINDOWS)
CRITICAL_SECTION *target_cs;
if (locked_mutex) {
target_cs = &locked_mutex->id;
} else {
target_cs = &cond->default_cs_id;
EnterCriticalSection(&cond->default_cs_id);
}
DWORD ms = seconds * 1000.0;
SleepConditionVariableCS(&cond->id, target_cs, ms);
if (!locked_mutex)
LeaveCriticalSection(&cond->default_cs_id);
#elif defined(SOUNDIO_OS_KQUEUE)
struct kevent kev;
struct kevent out_kev;
if (locked_mutex)
assert_no_err(pthread_mutex_unlock(&locked_mutex->id));
memset(&kev, 0, sizeof(kev));
kev.ident = notify_ident;
kev.filter = EVFILT_USER;
kev.flags = EV_ADD | EV_CLEAR;
// this time is relative
struct timespec timeout;
timeout.tv_nsec = (seconds * 1000000000L);
timeout.tv_sec = timeout.tv_nsec / 1000000000L;
timeout.tv_nsec = timeout.tv_nsec % 1000000000L;
if (kevent(cond->kq_id, &kev, 1, &out_kev, 1, &timeout) == -1) {
if (errno == EINTR)
return;
assert(0); // kevent wait error
}
if (locked_mutex)
assert_no_err(pthread_mutex_lock(&locked_mutex->id));
#else
pthread_mutex_t *target_mutex;
if (locked_mutex) {
target_mutex = &locked_mutex->id;
} else {
target_mutex = &cond->default_mutex_id;
assert_no_err(pthread_mutex_lock(target_mutex));
}
// this time is absolute
struct timespec tms;
clock_gettime(CLOCK_MONOTONIC, &tms);
tms.tv_nsec += (seconds * 1000000000L);
tms.tv_sec += tms.tv_nsec / 1000000000L;
tms.tv_nsec = tms.tv_nsec % 1000000000L;
int err;
if ((err = pthread_cond_timedwait(&cond->id, target_mutex, &tms))) {
assert(err != EPERM);
assert(err != EINVAL);
}
if (!locked_mutex)
assert_no_err(pthread_mutex_unlock(target_mutex));
#endif
}
void soundio_os_cond_wait(struct SoundIoOsCond *cond,
struct SoundIoOsMutex *locked_mutex)
{
#if defined(SOUNDIO_OS_WINDOWS)
CRITICAL_SECTION *target_cs;
if (locked_mutex) {
target_cs = &locked_mutex->id;
} else {
target_cs = &cond->default_cs_id;
EnterCriticalSection(&cond->default_cs_id);
}
SleepConditionVariableCS(&cond->id, target_cs, INFINITE);
if (!locked_mutex)
LeaveCriticalSection(&cond->default_cs_id);
#elif defined(SOUNDIO_OS_KQUEUE)
struct kevent kev;
struct kevent out_kev;
if (locked_mutex)
assert_no_err(pthread_mutex_unlock(&locked_mutex->id));
memset(&kev, 0, sizeof(kev));
kev.ident = notify_ident;
kev.filter = EVFILT_USER;
kev.flags = EV_ADD | EV_CLEAR;
if (kevent(cond->kq_id, &kev, 1, &out_kev, 1, NULL) == -1) {
if (errno == EINTR)
return;
assert(0); // kevent wait error
}
if (locked_mutex)
assert_no_err(pthread_mutex_lock(&locked_mutex->id));
#else
pthread_mutex_t *target_mutex;
if (locked_mutex) {
target_mutex = &locked_mutex->id;
} else {
target_mutex = &cond->default_mutex_id;
assert_no_err(pthread_mutex_lock(&cond->default_mutex_id));
}
int err;
if ((err = pthread_cond_wait(&cond->id, target_mutex))) {
assert(err != EPERM);
assert(err != EINVAL);
}
if (!locked_mutex)
assert_no_err(pthread_mutex_unlock(&cond->default_mutex_id));
#endif
}
static int internal_init(void) {
#if defined(SOUNDIO_OS_WINDOWS)
unsigned __int64 frequency;
if (QueryPerformanceFrequency((LARGE_INTEGER*) &frequency)) {
win32_time_resolution = 1.0 / (double) frequency;
} else {
return SoundIoErrorSystemResources;
}
GetSystemInfo(&win32_system_info);
page_size = win32_system_info.dwAllocationGranularity;
#else
page_size = sysconf(_SC_PAGESIZE);
#if defined(__MACH__)
host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
#endif
#endif
return 0;
}
int soundio_os_init(void) {
int err;
#if defined(SOUNDIO_OS_WINDOWS)
PVOID lpContext;
BOOL pending;
if (!InitOnceBeginInitialize(&win32_init_once, INIT_ONCE_ASYNC, &pending, &lpContext))
return SoundIoErrorSystemResources;
if (!pending)
return 0;
if ((err = internal_init()))
return err;
if (!InitOnceComplete(&win32_init_once, INIT_ONCE_ASYNC, NULL))
return SoundIoErrorSystemResources;
#else
assert_no_err(pthread_mutex_lock(&init_mutex));
if (initialized) {
assert_no_err(pthread_mutex_unlock(&init_mutex));
return 0;
}
initialized = true;
if ((err = internal_init()))
return err;
assert_no_err(pthread_mutex_unlock(&init_mutex));
#endif
return 0;
}
int soundio_os_page_size(void) {
return page_size;
}
static inline size_t ceil_dbl_to_size_t(double x) {
const double truncation = (size_t)x;
return truncation + (truncation < x);
}
int soundio_os_init_mirrored_memory(struct SoundIoOsMirroredMemory *mem, size_t requested_capacity) {
size_t actual_capacity = ceil_dbl_to_size_t(requested_capacity / (double)page_size) * page_size;
#if defined(SOUNDIO_OS_WINDOWS)
BOOL ok;
HANDLE hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, actual_capacity * 2, NULL);
if (!hMapFile)
return SoundIoErrorNoMem;
for (;;) {
// find a free address space with the correct size
char *address = (char*)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, actual_capacity * 2);
if (!address) {
ok = CloseHandle(hMapFile);
assert(ok);
return SoundIoErrorNoMem;
}
// found a big enough address space. hopefully it will remain free
// while we map to it. if not, we'll try again.
ok = UnmapViewOfFile(address);
assert(ok);
char *addr1 = (char*)MapViewOfFileEx(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, actual_capacity, address);
if (addr1 != address) {
DWORD err = GetLastError();
if (err == ERROR_INVALID_ADDRESS) {
continue;
} else {
ok = CloseHandle(hMapFile);
assert(ok);
return SoundIoErrorNoMem;
}
}
char *addr2 = (char*)MapViewOfFileEx(hMapFile, FILE_MAP_WRITE, 0, 0,
actual_capacity, address + actual_capacity);
if (addr2 != address + actual_capacity) {
ok = UnmapViewOfFile(addr1);
assert(ok);
DWORD err = GetLastError();
if (err == ERROR_INVALID_ADDRESS) {
continue;
} else {
ok = CloseHandle(hMapFile);
assert(ok);
return SoundIoErrorNoMem;
}
}
mem->priv = hMapFile;
mem->address = address;
break;
}
#else
char shm_path[] = "/dev/shm/soundio-XXXXXX";
char tmp_path[] = "/tmp/soundio-XXXXXX";
char *chosen_path;
int fd = mkstemp(shm_path);
if (fd < 0) {
fd = mkstemp(tmp_path);
if (fd < 0) {
return SoundIoErrorSystemResources;
} else {
chosen_path = tmp_path;
}
} else {
chosen_path = shm_path;
}
if (unlink(chosen_path)) {
close(fd);
return SoundIoErrorSystemResources;
}
if (ftruncate(fd, actual_capacity)) {
close(fd);
return SoundIoErrorSystemResources;
}
char *address = (char*)mmap(NULL, actual_capacity * 2, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (address == MAP_FAILED) {
close(fd);
return SoundIoErrorNoMem;
}
char *other_address = (char*)mmap(address, actual_capacity, PROT_READ|PROT_WRITE,
MAP_FIXED|MAP_SHARED, fd, 0);
if (other_address != address) {
munmap(address, 2 * actual_capacity);
close(fd);
return SoundIoErrorNoMem;
}
other_address = (char*)mmap(address + actual_capacity, actual_capacity,
PROT_READ|PROT_WRITE, MAP_FIXED|MAP_SHARED, fd, 0);
if (other_address != address + actual_capacity) {
munmap(address, 2 * actual_capacity);
close(fd);
return SoundIoErrorNoMem;
}
mem->address = address;
if (close(fd))
return SoundIoErrorSystemResources;
#endif
mem->capacity = actual_capacity;
return 0;
}
void soundio_os_deinit_mirrored_memory(struct SoundIoOsMirroredMemory *mem) {
if (!mem->address)
return;
#if defined(SOUNDIO_OS_WINDOWS)
BOOL ok;
ok = UnmapViewOfFile(mem->address);
assert(ok);
ok = UnmapViewOfFile(mem->address + mem->capacity);
assert(ok);
ok = CloseHandle((HANDLE)mem->priv);
assert(ok);
#else
int err = munmap(mem->address, 2 * mem->capacity);
assert(!err);
#endif
mem->address = NULL;
}