#include "rumpuser_port.h"
#if !defined(lint)
__RCSID("$NetBSD: rumpfiber.c,v 1.13 2017/12/27 09:01:53 ozaki-r Exp $");
#endif
#include <sys/mman.h>
#include <sys/time.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <rump/rumpuser.h>
#include "rumpuser_int.h"
#include "rumpfiber.h"
static void init_sched(void);
static void join_thread(struct thread *);
static void switch_threads(struct thread *prev, struct thread *next);
static struct thread *get_current(void);
static int64_t now(void);
static void msleep(uint64_t millisecs);
static void abssleep(uint64_t millisecs);
TAILQ_HEAD(thread_list, thread);
static struct thread_list exited_threads = TAILQ_HEAD_INITIALIZER(exited_threads);
static struct thread_list thread_list = TAILQ_HEAD_INITIALIZER(thread_list);
static struct thread *current_thread = NULL;
static void (*scheduler_hook)(void *, void *);
static void printk(const char *s);
static void
printk(const char *msg)
{
int ret __attribute__((unused));
ret = write(2, msg, strlen(msg));
}
static struct thread *
get_current(void)
{
return current_thread;
}
static int64_t
now(void)
{
struct timespec ts;
int rv;
rv = clock_gettime(CLOCK_MONOTONIC, &ts);
assert(rv == 0);
return (ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL);
}
void
schedule(void)
{
struct thread *prev, *next, *thread, *tmp;
int64_t tm, wakeup;
struct timespec sl;
prev = get_current();
do {
tm = now();
wakeup = tm + 1000;
next = NULL;
TAILQ_FOREACH_SAFE(thread, &thread_list, thread_list, tmp) {
if (!is_runnable(thread) && thread->wakeup_time >= 0) {
if (thread->wakeup_time <= tm) {
thread->flags |= THREAD_TIMEDOUT;
wake(thread);
} else if (thread->wakeup_time < wakeup)
wakeup = thread->wakeup_time;
}
if (is_runnable(thread)) {
next = thread;
TAILQ_REMOVE(&thread_list, thread, thread_list);
TAILQ_INSERT_TAIL(&thread_list, thread, thread_list);
break;
}
}
if (next)
break;
sl.tv_sec = (wakeup - tm) / 1000;
sl.tv_nsec = ((wakeup - tm) - 1000 * sl.tv_sec) * 1000000;
#ifdef HAVE_CLOCK_NANOSLEEP
clock_nanosleep(CLOCK_MONOTONIC, 0, &sl, NULL);
#else
nanosleep(&sl, NULL);
#endif
} while (1);
if (prev != next)
switch_threads(prev, next);
TAILQ_FOREACH_SAFE(thread, &exited_threads, thread_list, tmp) {
if (thread != prev) {
TAILQ_REMOVE(&exited_threads, thread, thread_list);
if ((thread->flags & THREAD_EXTSTACK) == 0)
munmap(thread->ctx.uc_stack.ss_sp, STACKSIZE);
free(thread->name);
free(thread);
}
}
}
static void
create_ctx(ucontext_t *ctx, void *stack, size_t stack_size,
void (*f)(void *), void *data)
{
getcontext(ctx);
ctx->uc_stack.ss_sp = stack;
ctx->uc_stack.ss_size = stack_size;
ctx->uc_stack.ss_flags = 0;
ctx->uc_link = NULL;
makecontext(ctx, (void (*)(void))f, 1, data);
}
struct thread *
create_thread(const char *name, void *cookie, void (*f)(void *), void *data,
void *stack, size_t stack_size)
{
struct thread *thread = calloc(1, sizeof(struct thread));
if (!thread) {
return NULL;
}
if (!stack) {
assert(stack_size == 0);
stack = mmap(NULL, STACKSIZE, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANON, -1, 0);
if (stack == MAP_FAILED) {
free(thread);
return NULL;
}
stack_size = STACKSIZE;
} else {
thread->flags = THREAD_EXTSTACK;
}
create_ctx(&thread->ctx, stack, stack_size, f, data);
thread->name = strdup(name);
thread->cookie = cookie;
thread->wakeup_time = -1;
thread->lwp = NULL;
set_runnable(thread);
TAILQ_INSERT_TAIL(&thread_list, thread, thread_list);
return thread;
}
static void
switch_threads(struct thread *prev, struct thread *next)
{
int ret;
current_thread = next;
if (scheduler_hook)
scheduler_hook(prev->cookie, next->cookie);
ret = swapcontext(&prev->ctx, &next->ctx);
if (ret < 0) {
printk("swapcontext failed\n");
abort();
}
}
struct join_waiter {
struct thread *jw_thread;
struct thread *jw_wanted;
TAILQ_ENTRY(join_waiter) jw_entries;
};
static TAILQ_HEAD(, join_waiter) joinwq = TAILQ_HEAD_INITIALIZER(joinwq);
void
exit_thread(void)
{
struct thread *thread = get_current();
struct join_waiter *jw_iter;
while (thread->flags & THREAD_MUSTJOIN) {
thread->flags |= THREAD_JOINED;
TAILQ_FOREACH(jw_iter, &joinwq, jw_entries) {
if (jw_iter->jw_wanted == thread) {
wake(jw_iter->jw_thread);
break;
}
}
block(thread);
schedule();
}
TAILQ_REMOVE(&thread_list, thread, thread_list);
clear_runnable(thread);
TAILQ_INSERT_HEAD(&exited_threads, thread, thread_list);
while (1) {
schedule();
printk("schedule() returned! Trying again\n");
}
}
static void
join_thread(struct thread *joinable)
{
struct join_waiter jw;
struct thread *thread = get_current();
assert(joinable->flags & THREAD_MUSTJOIN);
while (! (joinable->flags & THREAD_JOINED)) {
jw.jw_thread = thread;
jw.jw_wanted = joinable;
TAILQ_INSERT_TAIL(&joinwq, &jw, jw_entries);
block(thread);
schedule();
TAILQ_REMOVE(&joinwq, &jw, jw_entries);
}
assert(joinable->flags & THREAD_JOINED);
joinable->flags &= ~THREAD_MUSTJOIN;
wake(joinable);
}
static void msleep(uint64_t millisecs)
{
struct thread *thread = get_current();
thread->wakeup_time = now() + millisecs;
clear_runnable(thread);
schedule();
}
static void abssleep(uint64_t millisecs)
{
struct thread *thread = get_current();
thread->wakeup_time = millisecs;
clear_runnable(thread);
schedule();
}
int abssleep_real(uint64_t millisecs)
{
struct thread *thread = get_current();
struct timespec ts;
uint64_t real_now;
int rv;
clock_gettime(CLOCK_REALTIME, &ts);
real_now = 1000*ts.tv_sec + ts.tv_nsec/(1000*1000);
thread->wakeup_time = now() + (millisecs - real_now);
clear_runnable(thread);
schedule();
rv = !!(thread->flags & THREAD_TIMEDOUT);
thread->flags &= ~THREAD_TIMEDOUT;
return rv;
}
void wake(struct thread *thread)
{
thread->wakeup_time = -1;
set_runnable(thread);
}
void block(struct thread *thread)
{
thread->wakeup_time = -1;
clear_runnable(thread);
}
int is_runnable(struct thread *thread)
{
return thread->flags & RUNNABLE_FLAG;
}
void set_runnable(struct thread *thread)
{
thread->flags |= RUNNABLE_FLAG;
}
void clear_runnable(struct thread *thread)
{
thread->flags &= ~RUNNABLE_FLAG;
}
static void
init_sched(void)
{
struct thread *thread = calloc(1, sizeof(struct thread));
if (!thread) {
abort();
}
thread->name = strdup("init");
thread->flags = 0;
thread->wakeup_time = -1;
thread->lwp = NULL;
set_runnable(thread);
TAILQ_INSERT_TAIL(&thread_list, thread, thread_list);
current_thread = thread;
}
void
set_sched_hook(void (*f)(void *, void *))
{
scheduler_hook = f;
}
struct thread *
init_mainthread(void *cookie)
{
current_thread->cookie = cookie;
return current_thread;
}
struct rumpuser_hyperup rumpuser__hyp;
int
rumpuser_init(int version, const struct rumpuser_hyperup *hyp)
{
int rv;
if (version != RUMPUSER_VERSION) {
printk("rumpuser version mismatch\n");
abort();
}
rv = rumpuser__random_init();
if (rv != 0) {
ET(rv);
}
rumpuser__hyp = *hyp;
init_sched();
return 0;
}
int
rumpuser_clock_gettime(int enum_rumpclock, int64_t *sec, long *nsec)
{
enum rumpclock rclk = enum_rumpclock;
struct timespec ts;
clockid_t clk;
int rv;
switch (rclk) {
case RUMPUSER_CLOCK_RELWALL:
clk = CLOCK_REALTIME;
break;
case RUMPUSER_CLOCK_ABSMONO:
clk = CLOCK_MONOTONIC;
break;
default:
abort();
}
if (clock_gettime(clk, &ts) == -1) {
rv = errno;
} else {
*sec = ts.tv_sec;
*nsec = ts.tv_nsec;
rv = 0;
}
ET(rv);
}
int
rumpuser_clock_sleep(int enum_rumpclock, int64_t sec, long nsec)
{
enum rumpclock rclk = enum_rumpclock;
uint64_t msec;
int nlocks;
rumpkern_unsched(&nlocks, NULL);
switch (rclk) {
case RUMPUSER_CLOCK_RELWALL:
msec = sec * 1000 + nsec / (1000*1000UL);
msleep(msec);
break;
case RUMPUSER_CLOCK_ABSMONO:
msec = sec * 1000 + nsec / (1000*1000UL);
abssleep(msec);
break;
}
rumpkern_sched(nlocks, NULL);
return 0;
}
int
rumpuser_getparam(const char *name, void *buf, size_t blen)
{
int rv;
const char *ncpu = "1";
if (strcmp(name, RUMPUSER_PARAM_NCPU) == 0) {
strncpy(buf, ncpu, blen);
rv = 0;
} else if (strcmp(name, RUMPUSER_PARAM_HOSTNAME) == 0) {
char tmp[MAXHOSTNAMELEN];
if (gethostname(tmp, sizeof(tmp)) == -1) {
snprintf(buf, blen, "rump-%05d", (int)getpid());
} else {
snprintf(buf, blen, "rump-%05d.%s",
(int)getpid(), tmp);
}
rv = 0;
} else if (*name == '_') {
rv = EINVAL;
} else {
if (getenv_r(name, buf, blen) == -1)
rv = errno;
else
rv = 0;
}
ET(rv);
}
void
rumpuser_putchar(int c)
{
putchar(c);
}
__dead void
rumpuser_exit(int rv)
{
if (rv == RUMPUSER_PANIC)
abort();
else
exit(rv);
}
void
rumpuser_seterrno(int error)
{
errno = error;
}
void
rumpuser_dprintf(const char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(stderr, format, ap);
va_end(ap);
}
int
rumpuser_kill(int64_t pid, int rumpsig)
{
int sig;
sig = rumpuser__sig_rump2host(rumpsig);
if (sig > 0)
raise(sig);
return 0;
}
TAILQ_HEAD(waithead, waiter);
struct waiter {
struct thread *who;
TAILQ_ENTRY(waiter) entries;
int onlist;
};
static int
wait(struct waithead *wh, uint64_t msec)
{
struct waiter w;
w.who = get_current();
TAILQ_INSERT_TAIL(wh, &w, entries);
w.onlist = 1;
block(w.who);
if (msec)
w.who->wakeup_time = now() + msec;
schedule();
if (w.onlist)
TAILQ_REMOVE(wh, &w, entries);
return w.onlist ? ETIMEDOUT : 0;
}
static void
wakeup_one(struct waithead *wh)
{
struct waiter *w;
if ((w = TAILQ_FIRST(wh)) != NULL) {
TAILQ_REMOVE(wh, w, entries);
w->onlist = 0;
wake(w->who);
}
}
static void
wakeup_all(struct waithead *wh)
{
struct waiter *w;
while ((w = TAILQ_FIRST(wh)) != NULL) {
TAILQ_REMOVE(wh, w, entries);
w->onlist = 0;
wake(w->who);
}
}
int
rumpuser_thread_create(void *(*f)(void *), void *arg, const char *thrname,
int joinable, int pri, int cpuidx, void **tptr)
{
struct thread *thr;
thr = create_thread(thrname, NULL, (void (*)(void *))f, arg, NULL, 0);
if (!thr)
return EINVAL;
if (thr && joinable)
thr->flags |= THREAD_MUSTJOIN;
*tptr = thr;
return 0;
}
void
rumpuser_thread_exit(void)
{
exit_thread();
}
int
rumpuser_thread_join(void *p)
{
join_thread(p);
return 0;
}
struct rumpuser_mtx {
struct waithead waiters;
int v;
int flags;
struct lwp *o;
};
void
rumpuser_mutex_init(struct rumpuser_mtx **mtxp, int flags)
{
struct rumpuser_mtx *mtx;
mtx = malloc(sizeof(*mtx));
memset(mtx, 0, sizeof(*mtx));
mtx->flags = flags;
TAILQ_INIT(&mtx->waiters);
*mtxp = mtx;
}
int
rumpuser_mutex_spin_p(struct rumpuser_mtx *mtx)
{
return (mtx->flags & RUMPUSER_MTX_SPIN) != 0;
}
void
rumpuser_mutex_enter(struct rumpuser_mtx *mtx)
{
int nlocks;
if (rumpuser_mutex_tryenter(mtx) != 0) {
rumpkern_unsched(&nlocks, NULL);
while (rumpuser_mutex_tryenter(mtx) != 0)
wait(&mtx->waiters, 0);
rumpkern_sched(nlocks, NULL);
}
}
void
rumpuser_mutex_enter_nowrap(struct rumpuser_mtx *mtx)
{
int rv;
rv = rumpuser_mutex_tryenter(mtx);
if (rv != 0) {
printk("no voi ei\n");
}
}
int
rumpuser_mutex_tryenter(struct rumpuser_mtx *mtx)
{
struct lwp *l = get_current()->lwp;
if (mtx->v && mtx->o != l)
return EBUSY;
mtx->v++;
mtx->o = l;
return 0;
}
void
rumpuser_mutex_exit(struct rumpuser_mtx *mtx)
{
assert(mtx->v > 0);
if (--mtx->v == 0) {
mtx->o = NULL;
wakeup_one(&mtx->waiters);
}
}
void
rumpuser_mutex_destroy(struct rumpuser_mtx *mtx)
{
assert(TAILQ_EMPTY(&mtx->waiters) && mtx->o == NULL);
free(mtx);
}
void
rumpuser_mutex_owner(struct rumpuser_mtx *mtx, struct lwp **lp)
{
*lp = mtx->o;
}
struct rumpuser_rw {
struct waithead rwait;
struct waithead wwait;
int v;
struct lwp *o;
};
void
rumpuser_rw_init(struct rumpuser_rw **rwp)
{
struct rumpuser_rw *rw;
rw = malloc(sizeof(*rw));
memset(rw, 0, sizeof(*rw));
TAILQ_INIT(&rw->rwait);
TAILQ_INIT(&rw->wwait);
*rwp = rw;
}
void
rumpuser_rw_enter(int enum_rumprwlock, struct rumpuser_rw *rw)
{
enum rumprwlock lk = enum_rumprwlock;
struct waithead *w = NULL;
int nlocks;
switch (lk) {
case RUMPUSER_RW_WRITER:
w = &rw->wwait;
break;
case RUMPUSER_RW_READER:
w = &rw->rwait;
break;
}
if (rumpuser_rw_tryenter(enum_rumprwlock, rw) != 0) {
rumpkern_unsched(&nlocks, NULL);
while (rumpuser_rw_tryenter(enum_rumprwlock, rw) != 0)
wait(w, 0);
rumpkern_sched(nlocks, NULL);
}
}
int
rumpuser_rw_tryenter(int enum_rumprwlock, struct rumpuser_rw *rw)
{
enum rumprwlock lk = enum_rumprwlock;
int rv;
switch (lk) {
case RUMPUSER_RW_WRITER:
if (rw->o == NULL) {
rw->o = rumpuser_curlwp();
rv = 0;
} else {
rv = EBUSY;
}
break;
case RUMPUSER_RW_READER:
if (rw->o == NULL && TAILQ_EMPTY(&rw->wwait)) {
rw->v++;
rv = 0;
} else {
rv = EBUSY;
}
break;
default:
rv = EINVAL;
}
return rv;
}
void
rumpuser_rw_exit(struct rumpuser_rw *rw)
{
if (rw->o) {
rw->o = NULL;
} else {
rw->v--;
}
if (!TAILQ_EMPTY(&rw->wwait)) {
if (rw->o == NULL)
wakeup_one(&rw->wwait);
} else if (!TAILQ_EMPTY(&rw->rwait) && rw->o == NULL) {
wakeup_all(&rw->rwait);
}
}
void
rumpuser_rw_destroy(struct rumpuser_rw *rw)
{
free(rw);
}
void
rumpuser_rw_held(int enum_rumprwlock, struct rumpuser_rw *rw, int *rvp)
{
enum rumprwlock lk = enum_rumprwlock;
switch (lk) {
case RUMPUSER_RW_WRITER:
*rvp = rw->o == rumpuser_curlwp();
break;
case RUMPUSER_RW_READER:
*rvp = rw->v > 0;
break;
}
}
void
rumpuser_rw_downgrade(struct rumpuser_rw *rw)
{
assert(rw->o == rumpuser_curlwp());
rw->v = -1;
}
int
rumpuser_rw_tryupgrade(struct rumpuser_rw *rw)
{
if (rw->v == -1) {
rw->v = 1;
rw->o = rumpuser_curlwp();
return 0;
}
return EBUSY;
}
struct rumpuser_cv {
struct waithead waiters;
int nwaiters;
};
void
rumpuser_cv_init(struct rumpuser_cv **cvp)
{
struct rumpuser_cv *cv;
cv = malloc(sizeof(*cv));
memset(cv, 0, sizeof(*cv));
TAILQ_INIT(&cv->waiters);
*cvp = cv;
}
void
rumpuser_cv_destroy(struct rumpuser_cv *cv)
{
assert(cv->nwaiters == 0);
free(cv);
}
static void
cv_unsched(struct rumpuser_mtx *mtx, int *nlocks)
{
rumpkern_unsched(nlocks, mtx);
rumpuser_mutex_exit(mtx);
}
static void
cv_resched(struct rumpuser_mtx *mtx, int nlocks)
{
if ((mtx->flags & (RUMPUSER_MTX_KMUTEX | RUMPUSER_MTX_SPIN)) ==
(RUMPUSER_MTX_KMUTEX | RUMPUSER_MTX_SPIN)) {
rumpkern_sched(nlocks, mtx);
rumpuser_mutex_enter_nowrap(mtx);
} else {
rumpuser_mutex_enter_nowrap(mtx);
rumpkern_sched(nlocks, mtx);
}
}
void
rumpuser_cv_wait(struct rumpuser_cv *cv, struct rumpuser_mtx *mtx)
{
int nlocks;
cv->nwaiters++;
cv_unsched(mtx, &nlocks);
wait(&cv->waiters, 0);
cv_resched(mtx, nlocks);
cv->nwaiters--;
}
void
rumpuser_cv_wait_nowrap(struct rumpuser_cv *cv, struct rumpuser_mtx *mtx)
{
cv->nwaiters++;
rumpuser_mutex_exit(mtx);
wait(&cv->waiters, 0);
rumpuser_mutex_enter_nowrap(mtx);
cv->nwaiters--;
}
int
rumpuser_cv_timedwait(struct rumpuser_cv *cv, struct rumpuser_mtx *mtx,
int64_t sec, int64_t nsec)
{
int nlocks;
int rv;
cv->nwaiters++;
cv_unsched(mtx, &nlocks);
rv = wait(&cv->waiters, sec * 1000 + nsec / (1000*1000));
cv_resched(mtx, nlocks);
cv->nwaiters--;
return rv;
}
void
rumpuser_cv_signal(struct rumpuser_cv *cv)
{
wakeup_one(&cv->waiters);
}
void
rumpuser_cv_broadcast(struct rumpuser_cv *cv)
{
wakeup_all(&cv->waiters);
}
void
rumpuser_cv_has_waiters(struct rumpuser_cv *cv, int *rvp)
{
*rvp = cv->nwaiters != 0;
}
void
rumpuser_curlwpop(int enum_rumplwpop, struct lwp *l)
{
struct thread *thread;
enum rumplwpop op = enum_rumplwpop;
switch (op) {
case RUMPUSER_LWP_CREATE:
case RUMPUSER_LWP_DESTROY:
break;
case RUMPUSER_LWP_SET:
thread = get_current();
thread->lwp = l;
break;
case RUMPUSER_LWP_CLEAR:
thread = get_current();
assert(thread->lwp == l);
thread->lwp = NULL;
break;
}
}
struct lwp *
rumpuser_curlwp(void)
{
return get_current()->lwp;
}