mirror of
https://github.com/switchbrew/libnx.git
synced 2025-06-21 12:32:40 +02:00
445 lines
9.8 KiB
C
445 lines
9.8 KiB
C
#include <string.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <sys/iosupport.h>
|
|
#include <sys/time.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/reent.h>
|
|
#include "../internal.h"
|
|
#include "types.h"
|
|
#include "runtime/env.h"
|
|
#include "arm/counter.h"
|
|
#include "kernel/mutex.h"
|
|
#include "kernel/condvar.h"
|
|
#include "kernel/thread.h"
|
|
#include "kernel/svc.h"
|
|
#include "services/time.h"
|
|
#include "runtime/diag.h"
|
|
#include "result.h"
|
|
#include "alloc.h"
|
|
|
|
#define THRD_MAIN_HANDLE ((struct __pthread_t*)~(uintptr_t)0)
|
|
|
|
struct __pthread_t
|
|
{
|
|
Thread thr;
|
|
void *rc;
|
|
};
|
|
|
|
void __attribute__((weak)) NX_NORETURN __libnx_exit(int rc);
|
|
|
|
/// TimeType passed to timeGetCurrentTime() during time initialization. If that fails and __nx_time_type isn't TimeType_Default, timeGetCurrentTime() will be called again with TimeType_Default.
|
|
__attribute__((weak)) TimeType __nx_time_type = TimeType_Default;
|
|
|
|
static inline int errno_from_result(Result res)
|
|
{
|
|
switch (R_VALUE(res)) {
|
|
case 0:
|
|
return 0;
|
|
case KERNELRESULT(TimedOut):
|
|
return ETIMEDOUT;
|
|
default:
|
|
return EIO;
|
|
}
|
|
}
|
|
|
|
void __syscall_exit(int rc)
|
|
{
|
|
if (&__libnx_exit)
|
|
__libnx_exit(rc);
|
|
for (;;);
|
|
}
|
|
|
|
struct _reent* __syscall_getreent(void)
|
|
{
|
|
ThreadVars* tv = getThreadVars();
|
|
if (tv->magic != THREADVARS_MAGIC)
|
|
diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_BadReent));
|
|
return tv->reent;
|
|
}
|
|
|
|
|
|
void __syscall_lock_acquire(_LOCK_T *lock)
|
|
{
|
|
mutexLock(lock);
|
|
}
|
|
|
|
int __syscall_lock_try_acquire(_LOCK_T *lock)
|
|
{
|
|
return mutexTryLock(lock) ? 0 : 1;
|
|
}
|
|
|
|
void __syscall_lock_release(_LOCK_T *lock)
|
|
{
|
|
mutexUnlock(lock);
|
|
}
|
|
|
|
void __syscall_lock_acquire_recursive(_LOCK_RECURSIVE_T *lock)
|
|
{
|
|
rmutexLock(lock);
|
|
}
|
|
|
|
int __syscall_lock_try_acquire_recursive(_LOCK_RECURSIVE_T *lock)
|
|
{
|
|
return rmutexTryLock(lock) ? 0 : 1;
|
|
}
|
|
|
|
void __syscall_lock_release_recursive(_LOCK_RECURSIVE_T *lock)
|
|
{
|
|
rmutexUnlock(lock);
|
|
}
|
|
|
|
int __syscall_cond_signal(_COND_T *cond)
|
|
{
|
|
return errno_from_result(condvarWakeOne(cond));
|
|
}
|
|
|
|
int __syscall_cond_broadcast(_COND_T *cond)
|
|
{
|
|
return errno_from_result(condvarWakeAll(cond));
|
|
}
|
|
|
|
int __syscall_cond_wait(_COND_T *cond, _LOCK_T *lock, uint64_t timeout_ns)
|
|
{
|
|
return errno_from_result(condvarWaitTimeout(cond, lock, timeout_ns));
|
|
}
|
|
|
|
int __syscall_cond_wait_recursive(_COND_T *cond, _LOCK_RECURSIVE_T *lock, uint64_t timeout_ns)
|
|
{
|
|
uint32_t thread_tag_backup = 0;
|
|
if (lock->counter != 1)
|
|
return EBADF;
|
|
|
|
thread_tag_backup = lock->thread_tag;
|
|
lock->thread_tag = 0;
|
|
lock->counter = 0;
|
|
|
|
int errcode = errno_from_result(condvarWaitTimeout(cond, &lock->lock, timeout_ns));
|
|
|
|
lock->thread_tag = thread_tag_backup;
|
|
lock->counter = 1;
|
|
|
|
return errcode;
|
|
}
|
|
|
|
struct __pthread_t *__syscall_thread_self(void)
|
|
{
|
|
Thread* t = getThreadVars()->thread_ptr;
|
|
return t ? (struct __pthread_t *)t : THRD_MAIN_HANDLE;
|
|
}
|
|
|
|
void __syscall_thread_exit(void *value)
|
|
{
|
|
struct __pthread_t *thread = __syscall_thread_self();
|
|
if (thread == THRD_MAIN_HANDLE)
|
|
exit(EXIT_FAILURE);
|
|
|
|
thread->rc = value;
|
|
threadExit();
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
void* (*func)(void*);
|
|
void* arg;
|
|
|
|
bool thread_started;
|
|
Mutex mutex;
|
|
CondVar cond;
|
|
} __entry_args;
|
|
|
|
static void __thread_entry(void* __arg)
|
|
{
|
|
__entry_args* info = (__entry_args*)__arg;
|
|
void* (*func)(void*) = info->func;
|
|
void* arg = info->arg;
|
|
|
|
mutexLock(&info->mutex);
|
|
info->thread_started = true;
|
|
condvarWakeOne(&info->cond);
|
|
mutexUnlock(&info->mutex);
|
|
|
|
void* rc = func(arg);
|
|
__syscall_thread_exit(rc);
|
|
}
|
|
|
|
int __syscall_thread_create(struct __pthread_t **thread, void* (*func)(void*), void *arg, void *stack_addr, size_t stack_size)
|
|
{
|
|
if (((uintptr_t)stack_addr & 0xFFF) || (stack_size & 0xFFF))
|
|
return EINVAL;
|
|
if (!stack_size)
|
|
stack_size = 128*1024;
|
|
|
|
Result rc;
|
|
*thread = NULL;
|
|
|
|
u64 core_mask = 0;
|
|
rc = svcGetInfo(&core_mask, InfoType_CoreMask, CUR_PROCESS_HANDLE, 0);
|
|
if (R_FAILED(rc))
|
|
return EPERM;
|
|
|
|
struct __pthread_t* t = (struct __pthread_t*)__libnx_alloc(sizeof(struct __pthread_t));
|
|
if (!t)
|
|
return ENOMEM;
|
|
|
|
__entry_args info;
|
|
info.func = func;
|
|
info.arg = arg;
|
|
info.thread_started = false;
|
|
mutexInit(&info.mutex);
|
|
condvarInit(&info.cond);
|
|
|
|
rc = threadCreate(&t->thr, __thread_entry, &info, stack_addr, stack_size, 0x3B, -2);
|
|
if (R_FAILED(rc))
|
|
goto _error1;
|
|
|
|
rc = svcSetThreadCoreMask(t->thr.handle, -1, core_mask);
|
|
if (R_FAILED(rc))
|
|
goto _error2;
|
|
|
|
rc = threadStart(&t->thr);
|
|
if (R_FAILED(rc))
|
|
goto _error2;
|
|
|
|
mutexLock(&info.mutex);
|
|
while (!info.thread_started)
|
|
condvarWait(&info.cond, &info.mutex);
|
|
mutexUnlock(&info.mutex);
|
|
|
|
*thread = t;
|
|
return 0;
|
|
|
|
_error2:
|
|
threadClose(&t->thr);
|
|
_error1:
|
|
__libnx_free(t);
|
|
return ENOMEM;
|
|
}
|
|
|
|
void* __syscall_thread_join(struct __pthread_t *thread)
|
|
{
|
|
if (thread == THRD_MAIN_HANDLE)
|
|
return NULL;
|
|
|
|
Result rc = threadWaitForExit(&thread->thr);
|
|
if (R_FAILED(rc))
|
|
return NULL;
|
|
|
|
void* ret = thread->rc;
|
|
threadClose(&thread->thr);
|
|
__libnx_free(thread);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Unsupported
|
|
int __syscall_thread_detach(struct __pthread_t *thread)
|
|
{
|
|
}
|
|
*/
|
|
|
|
int __syscall_tls_create(uint32_t *key, void (*destructor)(void*))
|
|
{
|
|
s32 slot_id = threadTlsAlloc(destructor);
|
|
if (slot_id >= 0) {
|
|
*key = slot_id;
|
|
return 0;
|
|
}
|
|
|
|
return EAGAIN;
|
|
}
|
|
|
|
int __syscall_tls_set(uint32_t key, const void *value)
|
|
{
|
|
threadTlsSet(key, (void*)value);
|
|
return 0;
|
|
}
|
|
|
|
void* __syscall_tls_get(uint32_t key)
|
|
{
|
|
return threadTlsGet(key);
|
|
}
|
|
|
|
int __syscall_tls_delete(uint32_t key)
|
|
{
|
|
threadTlsFree(key);
|
|
return 0;
|
|
}
|
|
|
|
int sched_yield(void)
|
|
{
|
|
svcSleepThread(-1);
|
|
return 0;
|
|
}
|
|
|
|
int sched_getcpu(void)
|
|
{
|
|
return svcGetCurrentProcessorNumber();
|
|
}
|
|
|
|
static u64 __boottime;
|
|
static u64 __bootticks;
|
|
|
|
// setup boot time variables
|
|
void __libnx_init_time(void)
|
|
{
|
|
TimeCalendarAdditionalInfo info;
|
|
char envstr[64];
|
|
char *strptr;
|
|
bool is_west=0;
|
|
s32 tmp_offset, hour, minute, second;
|
|
|
|
Result rc = timeGetCurrentTime(__nx_time_type, &__boottime);
|
|
if (R_FAILED(rc) && __nx_time_type != TimeType_Default) rc = timeGetCurrentTime(TimeType_Default, &__boottime);
|
|
if (R_FAILED(rc)) {
|
|
__boottime = UINT64_MAX;
|
|
} else {
|
|
__bootticks = armGetSystemTick();
|
|
}
|
|
|
|
if (R_SUCCEEDED(rc)) rc = timeToCalendarTimeWithMyRule(__boottime, NULL, &info);
|
|
|
|
if (R_SUCCEEDED(rc)) {
|
|
info.timezoneName[7] = 0;
|
|
tmp_offset = info.offset;
|
|
if (tmp_offset < 0) {
|
|
is_west = 1;
|
|
tmp_offset = -tmp_offset;
|
|
}
|
|
|
|
second = tmp_offset % 60;
|
|
tmp_offset /= 60;
|
|
minute = tmp_offset % 60;
|
|
tmp_offset /= 60;
|
|
hour = tmp_offset % 24;
|
|
|
|
memset(envstr, 0, sizeof(envstr));
|
|
strptr = envstr;
|
|
// Some tznames have numeric characters and '-'/'+', so quote tzname with <>.
|
|
*strptr++ = '<';
|
|
//Avoid using *printf.
|
|
strncpy(strptr, info.timezoneName, sizeof(envstr)-1);
|
|
strptr = &envstr[strlen(envstr)];
|
|
*strptr++ = '>';
|
|
*strptr++ = is_west ? '+' : '-';
|
|
|
|
*strptr++ = '0' + (hour / 10);
|
|
*strptr++ = '0' + (hour % 10);
|
|
*strptr++ = ':';
|
|
|
|
*strptr++ = '0' + (minute / 10);
|
|
*strptr++ = '0' + (minute % 10);
|
|
*strptr++ = ':';
|
|
|
|
*strptr++ = '0' + (second / 10);
|
|
*strptr++ = '0' + (second % 10);
|
|
|
|
setenv("TZ", envstr, 0);
|
|
}
|
|
}
|
|
|
|
static const u64 nsec_clockres = 1000000000ULL / 19200000ULL;
|
|
|
|
int __syscall_clock_getres(clockid_t clock_id, struct timespec *tp)
|
|
{
|
|
if(clock_id != CLOCK_MONOTONIC && clock_id != CLOCK_REALTIME) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
if(tp) {
|
|
tp->tv_sec = 0;
|
|
tp->tv_nsec = nsec_clockres;
|
|
return 0;
|
|
} else {
|
|
errno = EFAULT;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int __syscall_clock_gettime(clockid_t clock_id, struct timespec *tp)
|
|
{
|
|
if(clock_id != CLOCK_MONOTONIC && clock_id != CLOCK_REALTIME) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
if(tp) {
|
|
|
|
if(__boottime == UINT64_MAX) {
|
|
errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
u64 now=armGetSystemTick() - __bootticks;
|
|
|
|
u64 __bootsecs = now / 19200000ULL;
|
|
|
|
tp->tv_sec = __bootsecs + __boottime;
|
|
u64 nsecs = (now - __bootsecs * 19200000ULL) * 10000ULL / 192ULL;
|
|
tp->tv_nsec = nsecs - nsecs % nsec_clockres;
|
|
return 0;
|
|
} else {
|
|
errno = EFAULT;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
int __syscall_gettod_r(struct _reent *ptr, struct timeval *tp, struct timezone *tz)
|
|
{
|
|
if (tp != NULL) {
|
|
|
|
if(__boottime == UINT64_MAX) {
|
|
ptr->_errno = EIO;
|
|
return -1;
|
|
}
|
|
|
|
u64 now=armGetSystemTick() - __bootticks;
|
|
|
|
u64 __bootsecs = now / 19200000ULL;
|
|
|
|
tp->tv_sec = __bootsecs + __boottime;
|
|
tp->tv_usec = (now - __bootsecs * 19200000ULL) * 10ULL / 192ULL;
|
|
}
|
|
|
|
if (tz != NULL) {
|
|
tz->tz_minuteswest = 0;
|
|
tz->tz_dsttime = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int __syscall_nanosleep(const struct timespec *req, struct timespec *rem)
|
|
{
|
|
if (!req) {
|
|
errno = EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
svcSleepThread(timespec2nsec(req));
|
|
if (rem) {
|
|
rem->tv_nsec = 0;
|
|
rem->tv_sec = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void newlibSetup(void)
|
|
{
|
|
// Initialize thread vars for the main thread
|
|
ThreadVars* tv = getThreadVars();
|
|
tv->magic = THREADVARS_MAGIC;
|
|
tv->thread_ptr = NULL;
|
|
tv->reent = _impure_ptr;
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Warray-bounds"
|
|
tv->tls_tp = __tls_start-getTlsStartOffset();
|
|
#pragma GCC diagnostic pop
|
|
tv->handle = envGetMainThreadHandle();
|
|
|
|
u32 tls_size = __tdata_lma_end - __tdata_lma;
|
|
if (tls_size)
|
|
memcpy(__tls_start, __tdata_lma, tls_size);
|
|
}
|