GameCube/Wii: build os/gekko on every devkitPPC

newlib.c serves all three generations of devkitPPC's newlib system
hooks, picked at compile time: r49's lock, condition and thread hooks;
newlib 4.3's split plain and recursive lock hooks over int-sized locks;
and the older single set that takes the lock kind at init, which also
leaves __getreent to the system.  On the int-sized locks a plain lock is
a gk_mutex_t word and a recursive one a small handle into a table of
gk_rmutex_t.

rthreads and rcheevos run on os/gekko's own threads and mutexes rather
than newlib's pthreads, which only exist from r49.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-10-04 09:50:15 +02:00
1 parent cfb1204314
commit 2c724ff91c
4 files changed
+302 -9

No files matched your search

+25
View File
@@ -249,6 +249,31 @@ void rc_mutex_unlock(rc_mutex_t* mutex)
sceKernelUnlockMutex(mutex->handle, 1);
}
#elif defined(GEKKO_NATIVE)
/* RetroArch-local: os/gekko's recursive mutex. */
void rc_mutex_init(rc_mutex_t* mutex)
{
mutex->m.word = 0;
mutex->depth = 0;
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
(void)mutex;
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
gk_rmutex_lock(mutex);
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
gk_rmutex_unlock(mutex);
}
#elif defined(WIIU)
/* RetroArch-local: OSMutex is recursive, matching the recursive pthread
+5
View File
@@ -102,6 +102,11 @@ RC_BEGIN_C_DECLS
typedef struct rc_mutex_t {
mutex_t handle;
} rc_mutex_t;
#elif defined(GEKKO_NATIVE)
/* RetroArch-local: os/gekko's recursive mutex, which every devkitPPC
* can build; newlib's pthreads only came with r49. */
#include <gekko/thread.h>
typedef gk_rmutex_t rc_mutex_t;
#elif defined(WIIU)
/* RetroArch-local: rthreads drives Cafe OS threads natively here, so
* rcheevos cannot use the pthread backend -- devkitPPC ships
+117 -2
View File
@@ -63,6 +63,113 @@
#include <ogc/mutex.h>
#include <ogc/cond.h>
#define STACKSIZE (8 * 1024)
#elif defined(GEKKO_NATIVE)
/* os/gekko's own threads under the pthread names used below, so this
* builds on every devkitPPC: newlib's pthreads only came with r49. */
#define USE_GEKKO_THREADS
#include <errno.h>
#include <gekko/gekko.h>
#include <gekko/thread.h>
#include <retro_inline.h>
typedef gk_thread_t *rthreads_gk_thread_t;
typedef struct
{
size_t stack_size;
} rthreads_gk_attr_t;
static INLINE int rthreads_gk_attr_init(rthreads_gk_attr_t *attr)
{
attr->stack_size = 0;
return 0;
}
static INLINE int rthreads_gk_attr_setstacksize(rthreads_gk_attr_t *attr,
size_t stack_size)
{
attr->stack_size = stack_size;
return 0;
}
static INLINE int rthreads_gk_create(gk_thread_t **t,
const rthreads_gk_attr_t *attr, void *(*fn)(void*), void *arg)
{
*t = gk_thread_create(fn, arg, NULL,
(attr && attr->stack_size) ? attr->stack_size : 64 * 1024,
GK_PRIO_DEFAULT);
return *t ? 0 : EAGAIN;
}
static INLINE int rthreads_gk_join(gk_thread_t *t)
{
gk_thread_join(t);
return 0;
}
static INLINE int rthreads_gk_detach(gk_thread_t *t)
{
gk_thread_detach(t);
return 0;
}
static INLINE int rthreads_gk_mutex_init(gk_mutex_t *m)
{
m->word = 0;
return 0;
}
static INLINE int rthreads_gk_mutex_lock(gk_mutex_t *m)
{
gk_mutex_lock(m);
return 0;
}
static INLINE int rthreads_gk_mutex_unlock(gk_mutex_t *m)
{
gk_mutex_unlock(m);
return 0;
}
static INLINE int rthreads_gk_cond_init(gk_cond_t *c)
{
c->seq = 0;
return 0;
}
static INLINE int rthreads_gk_cond_signal(gk_cond_t *c)
{
gk_cond_signal(c);
return 0;
}
static INLINE int rthreads_gk_cond_broadcast(gk_cond_t *c)
{
gk_cond_broadcast(c);
return 0;
}
#define pthread_t rthreads_gk_thread_t
#define pthread_attr_t rthreads_gk_attr_t
#define pthread_mutex_t gk_mutex_t
#define pthread_cond_t gk_cond_t
#define pthread_attr_init(a) rthreads_gk_attr_init(a)
#define pthread_attr_destroy(a) ((void)(a))
#define pthread_attr_setstacksize(a, n) rthreads_gk_attr_setstacksize(a, n)
#define pthread_create(t, a, fn, arg) rthreads_gk_create(t, a, fn, arg)
#define pthread_join(t, r) rthreads_gk_join(t)
#define pthread_detach(t) rthreads_gk_detach(t)
#define pthread_self() gk_thread_self()
#define pthread_equal(a, b) ((a) == (b))
#define pthread_mutex_init(m, a) rthreads_gk_mutex_init(m)
#define pthread_mutex_destroy(m) ((void)(m))
#define pthread_mutex_lock(m) rthreads_gk_mutex_lock(m)
#define pthread_mutex_trylock(m) gk_mutex_trylock(m)
#define pthread_mutex_unlock(m) rthreads_gk_mutex_unlock(m)
#define pthread_cond_init(c, a) rthreads_gk_cond_init(c)
#define pthread_cond_destroy(c) ((void)(c))
#define pthread_cond_wait(c, m) gk_cond_wait(c, m, GK_WAIT_FOREVER)
#define pthread_cond_signal(c) rthreads_gk_cond_signal(c)
#define pthread_cond_broadcast(c) rthreads_gk_cond_broadcast(c)
#elif defined(_3DS)
#define USE_CTR_THREADS
#include <3ds/thread.h>
@@ -1293,7 +1400,7 @@ static sthread_t *sthread_create_ex(void (*thread_func)(void*),
#ifdef HAVE_THREAD_ATTR
pthread_attr_init(&thread_attr);
/* devkitPPC's pthreads have no scheduling policy to set. */
/* os/gekko's threads take no scheduling policy. */
#ifndef GEKKO_NATIVE
if ((thread_priority >= 1) && (thread_priority <= 100))
{
@@ -2190,6 +2297,8 @@ void sthread_yield(void)
RotateThreadReadyQueue(0);
#elif defined(USE_PS3_THREADS)
rthreads_ps3_thread_yield();
#elif defined(USE_GEKKO_THREADS)
gk_thread_yield();
#else
sched_yield();
#endif
@@ -3572,6 +3681,11 @@ bool scond_wait_timeout(scond_t *cond, slock_t *lock, int64_t timeout_us)
}
slock_lock(lock);
return woken;
#elif defined(USE_GEKKO_THREADS)
if (timeout_us <= 0)
return false;
return gk_cond_wait(&cond->cond, &lock->lock,
GK_US_TO_TICKS(timeout_us)) != GK_ETIMEDOUT;
#elif defined(RTHREADS_FUTEX_SCOND)
/* FUTEX_WAIT_BITSET takes an absolute CLOCK_MONOTONIC deadline, so
* a spurious wake never shortens or stretches the wait. */
@@ -3751,7 +3865,8 @@ bool sthread_is_main_thread(void)
&& !defined(USE_CTR_THREADS) && !defined(USE_PSP_THREADS) \
&& !defined(USE_VITA_THREADS) && !defined(USE_WIIU_THREADS) \
&& !defined(USE_SWITCH_THREADS) && !defined(USE_PS2_THREADS) \
&& !defined(USE_PS3_THREADS) && !defined(__ANDROID__)
&& !defined(USE_PS3_THREADS) && !defined(USE_GEKKO_THREADS) \
&& !defined(__ANDROID__)
#define RTHREADS_HAVE_CANCEL 1
#endif
+155 -7
View File
@@ -1,11 +1,16 @@
/* The C library's system interface: heap, locks, threads, time.
*
* newlib's locks are the kernel's mutexes outright: _LOCK_T is one
* zero-initialised word and _LOCK_RECURSIVE_T a word and a depth,
* exactly gk_mutex_t and gk_rmutex_t. */
* devkitPPC's newlib has had three sets of system hooks, all served
* here. From r49 (sys/lock.h defines __COND_INITIALIZER) the locks are
* the kernel's mutexes outright: _LOCK_T is one zero-initialised word
* and _LOCK_RECURSIVE_T a word and a depth, exactly gk_mutex_t and
* gk_rmutex_t, and conditions and threads are hooks too. From newlib
* 4.3 there are separate hooks for plain and recursive locks, both one
* int; before it one set of hooks takes the kind at init. */
#include <errno.h>
#include <malloc.h>
#include <newlib.h>
#include <reent.h>
#include <stdlib.h>
#include <string.h>
@@ -18,14 +23,19 @@
#include "kernel.h"
/* newlib's lock, condition and thread hooks came with devkitPPC r49. */
#ifndef __COND_INITIALIZER
#error "os/gekko needs devkitPPC r49 or later"
#if defined(__COND_INITIALIZER)
#define GK_NEWLIB_HOOKS 3
#elif __NEWLIB__ > 4 || (__NEWLIB__ == 4 && __NEWLIB_MINOR__ >= 3)
#define GK_NEWLIB_HOOKS 2
#else
#define GK_NEWLIB_HOOKS 1
#endif
typedef char assert_lock_size[sizeof(_LOCK_T) == sizeof(gk_mutex_t) ? 1 : -1];
#if GK_NEWLIB_HOOKS >= 3
typedef char assert_rlock_size[
sizeof(_LOCK_RECURSIVE_T) == sizeof(gk_rmutex_t) ? 1 : -1];
#endif
typedef char assert_ctx_size[sizeof(struct gk_ctx) == CTX_SIZE ? 1 : -1];
typedef char assert_fp_off[
offsetof(struct gk_thread, fp) == THR_FP ? 1 : -1];
@@ -87,7 +97,7 @@ int posix_memalign(void **out, size_t align, size_t size)
/* ---- per-thread C library state ---- */
struct _reent *__syscall_getreent(void)
static struct _reent *cur_reent(void)
{
struct gk_thread *t = gk_cur;
if (!t || t == gk_sched_main())
@@ -95,8 +105,15 @@ struct _reent *__syscall_getreent(void)
return &t->reent;
}
#if GK_NEWLIB_HOOKS >= 2
struct _reent *__syscall_getreent(void) { return cur_reent(); }
#else
struct _reent *__getreent(void) { return cur_reent(); }
#endif
/* ---- locks ---- */
#if GK_NEWLIB_HOOKS >= 3
void __syscall_lock_init(_LOCK_T *lock) { *lock = 0; }
void __syscall_lock_acquire(_LOCK_T *lock)
{
@@ -132,6 +149,133 @@ void __syscall_lock_close_recursive(_LOCK_RECURSIVE_T *lock)
{
(void)lock;
}
#else
/* A plain int lock is a gk_mutex_t word. A recursive one holds 1 + its
* slot in rlocks, which no thread pointer is small enough to equal.
* newlib initialises a lock it finds zero just before taking it, so init
* never clears a word another thread may have taken in between. */
#define GK_RLOCKS 128
static gk_rmutex_t rlocks[GK_RLOCKS];
static int *rlock_owner[GK_RLOCKS];
static gk_rmutex_t *rlock_get(int *lock)
{
uint32_t i = (uint32_t)*lock - 1u;
return (i < GK_RLOCKS && rlock_owner[i] == lock) ? &rlocks[i] : NULL;
}
static gk_rmutex_t *rlock_init(int *lock)
{
gk_rmutex_t *m;
uint32_t i;
uint32_t level = gk_irq_disable();
if (!(m = rlock_get(lock)))
{
for (i = 0; i < GK_RLOCKS && rlock_owner[i]; i++);
if (i == GK_RLOCKS)
gk_panic("out of C library locks");
rlock_owner[i] = lock;
rlocks[i].m.word = 0;
rlocks[i].depth = 0;
*lock = (int)(i + 1u);
m = &rlocks[i];
}
gk_irq_restore(level);
return m;
}
static void rlock_close(int *lock)
{
uint32_t level = gk_irq_disable();
gk_rmutex_t *m = rlock_get(lock);
if (m)
{
rlock_owner[m - rlocks] = NULL;
*lock = 0;
}
gk_irq_restore(level);
}
static void rlock_release(int *lock)
{
gk_rmutex_t *m = rlock_get(lock);
if (m)
gk_rmutex_unlock(m);
}
#if GK_NEWLIB_HOOKS == 2
void __syscall_lock_init(_LOCK_T *lock) { (void)lock; }
void __syscall_lock_acquire(_LOCK_T *lock)
{
gk_mutex_lock((gk_mutex_t*)lock);
}
int __syscall_lock_try_acquire(_LOCK_T *lock)
{
return gk_mutex_trylock((gk_mutex_t*)lock);
}
void __syscall_lock_release(_LOCK_T *lock)
{
gk_mutex_unlock((gk_mutex_t*)lock);
}
void __syscall_lock_close(_LOCK_T *lock) { (void)lock; }
void __syscall_lock_init_recursive(_LOCK_RECURSIVE_T *lock)
{
rlock_init(lock);
}
void __syscall_lock_acquire_recursive(_LOCK_RECURSIVE_T *lock)
{
gk_rmutex_lock(rlock_init(lock));
}
int __syscall_lock_try_acquire_recursive(_LOCK_RECURSIVE_T *lock)
{
return gk_rmutex_trylock(rlock_init(lock));
}
void __syscall_lock_release_recursive(_LOCK_RECURSIVE_T *lock)
{
rlock_release(lock);
}
void __syscall_lock_close_recursive(_LOCK_RECURSIVE_T *lock)
{
rlock_close(lock);
}
#else
static int rlock_is_handle(const int *lock)
{
return (uint32_t)*lock - 1u < GK_RLOCKS;
}
int __syscall_lock_init(int *lock, int recursive)
{
if (recursive)
rlock_init(lock);
return 0;
}
int __syscall_lock_acquire(int *lock)
{
if (rlock_is_handle(lock))
gk_rmutex_lock(rlock_init(lock));
else
gk_mutex_lock((gk_mutex_t*)lock);
return 0;
}
int __syscall_lock_release(int *lock)
{
if (rlock_is_handle(lock))
rlock_release(lock);
else
gk_mutex_unlock((gk_mutex_t*)lock);
return 0;
}
int __syscall_lock_close(int *lock)
{
if (rlock_is_handle(lock))
rlock_close(lock);
return 0;
}
#endif
#endif
static uint64_t ns_to_ticks(uint64_t ns)
{
@@ -140,6 +284,7 @@ static uint64_t ns_to_ticks(uint64_t ns)
return ns / 1000u * gk_tb_hz / 1000000u;
}
#if GK_NEWLIB_HOOKS >= 3
int __syscall_cond_signal(_COND_T *cond)
{
gk_cond_signal((gk_cond_t*)cond);
@@ -221,6 +366,7 @@ int __syscall_tls_delete(uint32_t key)
gk_tls_delete(key);
return 0;
}
#endif
/* ---- thread-local slots ---- */
@@ -375,6 +521,7 @@ void __syscall_exit(int rc)
gk_exit_to_loader();
}
#if GK_NEWLIB_HOOKS >= 2
void __syscall_abort(void)
{
gk_panic("abort()");
@@ -386,6 +533,7 @@ void __syscall_assert_func(const char *file, int line, const char *func,
gk_panic("assertion \"%s\" failed: %s:%d (%s)", expr, file, line,
func ? func : "");
}
#endif
/* ---- stdout and stderr: lines to the debug channel ---- */