#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: scheduler.c,v 1.55 2023/10/05 19:41:07 ad Exp $");
#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/cpu.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/namei.h>
#include <sys/queue.h>
#include <sys/select.h>
#include <sys/systm.h>
#include <rump-sys/kern.h>
#include <rump/rumpuser.h>
static struct rumpcpu {
struct cpu_info *rcpu_ci;
void *rcpu_prevlwp;
struct rumpuser_mtx *rcpu_mtx;
struct rumpuser_cv *rcpu_cv;
int rcpu_wanted;
unsigned int rcpu_fastpath;
unsigned int rcpu_slowpath;
unsigned int rcpu_migrated;
int rcpu_align[0] __aligned(CACHE_LINE_SIZE);
} rcpu_storage[MAXCPUS];
static inline struct rumpcpu *
cpuinfo_to_rumpcpu(struct cpu_info *ci)
{
return &rcpu_storage[cpu_index(ci)];
}
struct cpu_info rump_bootcpu;
#define RCPULWP_BUSY ((void *)-1)
#define RCPULWP_WANTED ((void *)-2)
static struct rumpuser_mtx *lwp0mtx;
static struct rumpuser_cv *lwp0cv;
static unsigned nextcpu;
kmutex_t unruntime_lock;
static bool lwp0isbusy = false;
#ifdef RUMPSCHED_STATS
#define SCHED_FASTPATH(rcpu) rcpu->rcpu_fastpath++;
#define SCHED_SLOWPATH(rcpu) rcpu->rcpu_slowpath++;
#define SCHED_MIGRATED(rcpu) rcpu->rcpu_migrated++;
#else
#define SCHED_FASTPATH(rcpu)
#define SCHED_SLOWPATH(rcpu)
#define SCHED_MIGRATED(rcpu)
#endif
struct cpu_info *
cpu_lookup(u_int index)
{
return rcpu_storage[index].rcpu_ci;
}
static inline struct rumpcpu *
getnextcpu(void)
{
unsigned newcpu;
newcpu = atomic_inc_uint_nv(&nextcpu);
if (__predict_false(ncpu > UINT_MAX/2))
atomic_and_uint(&nextcpu, 0);
newcpu = newcpu % ncpu;
return &rcpu_storage[newcpu];
}
void
rump_cpus_bootstrap(int *nump)
{
int num = *nump;
if (num > MAXCPUS) {
aprint_verbose("CPU limit: %d wanted, %d (MAXCPUS) "
"available (adjusted)\n", num, MAXCPUS);
num = MAXCPUS;
}
cpu_setmodel("rumpcore (virtual)");
mi_cpu_init();
rump_cpu_attach(&rump_bootcpu);
ncpu = 1;
*nump = num;
}
void
rump_scheduler_init(int numcpu)
{
struct rumpcpu *rcpu;
struct cpu_info *ci;
int i;
rumpuser_mutex_init(&lwp0mtx, RUMPUSER_MTX_SPIN);
rumpuser_cv_init(&lwp0cv);
for (i = 0; i < numcpu; i++) {
if (i == 0) {
ci = &rump_bootcpu;
} else {
ci = kmem_zalloc(sizeof(*ci), KM_SLEEP);
ci->ci_index = i;
}
rcpu = &rcpu_storage[i];
rcpu->rcpu_ci = ci;
rcpu->rcpu_wanted = 0;
rumpuser_cv_init(&rcpu->rcpu_cv);
rumpuser_mutex_init(&rcpu->rcpu_mtx, RUMPUSER_MTX_SPIN);
ci->ci_schedstate.spc_mutex =
mutex_obj_alloc(MUTEX_DEFAULT, IPL_SCHED);
ci->ci_schedstate.spc_flags = SPCF_RUNNING;
}
mutex_init(&unruntime_lock, MUTEX_DEFAULT, IPL_SCHED);
}
void
rump_schedlock_cv_signal(struct cpu_info *ci, struct rumpuser_cv *cv)
{
struct rumpcpu *rcpu = cpuinfo_to_rumpcpu(ci);
rumpuser_mutex_enter_nowrap(rcpu->rcpu_mtx);
rumpuser_cv_signal(cv);
rumpuser_mutex_exit(rcpu->rcpu_mtx);
}
void
rump_schedlock_cv_wait(struct rumpuser_cv *cv)
{
struct lwp *l = curlwp;
struct rumpcpu *rcpu = cpuinfo_to_rumpcpu(l->l_cpu);
rumpuser_cv_wait(cv, rcpu->rcpu_mtx);
}
int
rump_schedlock_cv_timedwait(struct rumpuser_cv *cv, const struct timespec *ts)
{
struct lwp *l = curlwp;
struct rumpcpu *rcpu = cpuinfo_to_rumpcpu(l->l_cpu);
return rumpuser_cv_timedwait(cv, rcpu->rcpu_mtx,
ts->tv_sec, ts->tv_nsec);
}
static void
lwp0busy(void)
{
KASSERT(curlwp == NULL || curlwp->l_stat != LSONPROC);
rumpuser_mutex_enter_nowrap(lwp0mtx);
while (lwp0isbusy)
rumpuser_cv_wait_nowrap(lwp0cv, lwp0mtx);
lwp0isbusy = true;
rumpuser_mutex_exit(lwp0mtx);
}
static void
lwp0rele(void)
{
rumpuser_mutex_enter_nowrap(lwp0mtx);
KASSERT(lwp0isbusy == true);
lwp0isbusy = false;
rumpuser_cv_signal(lwp0cv);
rumpuser_mutex_exit(lwp0mtx);
}
void
rump_schedule()
{
struct lwp *l;
if (__predict_true((l = curlwp) != NULL)) {
struct proc *p = l->l_proc;
rump_schedule_cpu(l);
if (l->l_cred != p->p_cred) {
kauth_cred_t oc = l->l_cred;
mutex_enter(p->p_lock);
l->l_cred = kauth_cred_hold(p->p_cred);
mutex_exit(p->p_lock);
kauth_cred_free(oc);
}
} else {
lwp0busy();
rump_schedule_cpu(&lwp0);
rump_lwproc_curlwp_set(&lwp0);
l = rump__lwproc_alloclwp(initproc);
rump_lwproc_switch(l);
lwp0rele();
rump_lwproc_releaselwp();
}
}
void
rump_schedule_cpu(struct lwp *l)
{
rump_schedule_cpu_interlock(l, NULL);
}
void
rump_schedule_cpu_interlock(struct lwp *l, void *interlock)
{
struct rumpcpu *rcpu;
struct cpu_info *ci;
void *old;
bool domigrate;
bool bound = l->l_pflag & LP_BOUND;
l->l_stat = LSRUN;
KASSERT(l->l_target_cpu != NULL);
rcpu = cpuinfo_to_rumpcpu(l->l_target_cpu);
if (atomic_cas_ptr(&rcpu->rcpu_prevlwp, l, RCPULWP_BUSY) == l) {
if (interlock == rcpu->rcpu_mtx)
rumpuser_mutex_exit(rcpu->rcpu_mtx);
SCHED_FASTPATH(rcpu);
goto fastlane;
}
if (ncpu == 1)
domigrate = false;
else
domigrate = true;
if (interlock != rcpu->rcpu_mtx)
rumpuser_mutex_enter_nowrap(rcpu->rcpu_mtx);
for (;;) {
SCHED_SLOWPATH(rcpu);
old = atomic_swap_ptr(&rcpu->rcpu_prevlwp, RCPULWP_WANTED);
if (old != RCPULWP_BUSY && old != RCPULWP_WANTED) {
if (atomic_cas_ptr(&rcpu->rcpu_prevlwp,
RCPULWP_WANTED, RCPULWP_BUSY) == RCPULWP_WANTED) {
break;
}
}
if (domigrate && !bound) {
domigrate = false;
SCHED_MIGRATED(rcpu);
rumpuser_mutex_exit(rcpu->rcpu_mtx);
rcpu = getnextcpu();
rumpuser_mutex_enter_nowrap(rcpu->rcpu_mtx);
continue;
}
rcpu->rcpu_wanted++;
rumpuser_cv_wait_nowrap(rcpu->rcpu_cv, rcpu->rcpu_mtx);
rcpu->rcpu_wanted--;
}
rumpuser_mutex_exit(rcpu->rcpu_mtx);
fastlane:
ci = rcpu->rcpu_ci;
l->l_cpu = l->l_target_cpu = ci;
l->l_mutex = rcpu->rcpu_ci->ci_schedstate.spc_mutex;
l->l_ru.ru_nvcsw++;
l->l_stat = LSONPROC;
ci->ci_curlwp = ci->ci_onproc = l;
}
void
rump_unschedule()
{
struct lwp *l = curlwp;
#ifdef DIAGNOSTIC
int nlock;
KERNEL_UNLOCK_ALL(l, &nlock);
KASSERT(nlock == 0);
#endif
KASSERT(l->l_mutex == l->l_cpu->ci_schedstate.spc_mutex);
rump_unschedule_cpu(l);
l->l_mutex = &unruntime_lock;
l->l_stat = LSSTOP;
if (__predict_false(l->l_flag & LW_WEXIT)) {
lwp0busy();
rump_schedule_cpu(l);
KASSERT(l->l_flag & LW_WEXIT);
rump_lwproc_switch(&lwp0);
rump_unschedule_cpu(&lwp0);
lwp0.l_mutex = &unruntime_lock;
lwp0.l_pflag &= ~LP_RUNNING;
lwp0rele();
rump_lwproc_curlwp_clear(&lwp0);
} else if (__predict_false(l->l_flag & LW_RUMP_CLEAR)) {
rump_lwproc_curlwp_clear(l);
l->l_flag &= ~LW_RUMP_CLEAR;
}
}
void
rump_unschedule_cpu(struct lwp *l)
{
rump_unschedule_cpu_interlock(l, NULL);
}
void
rump_unschedule_cpu_interlock(struct lwp *l, void *interlock)
{
if ((l->l_pflag & LP_INTR) == 0)
rump_softint_run(l->l_cpu);
rump_unschedule_cpu1(l, interlock);
}
void
rump_unschedule_cpu1(struct lwp *l, void *interlock)
{
struct rumpcpu *rcpu;
struct cpu_info *ci;
void *old;
ci = l->l_cpu;
ci->ci_curlwp = ci->ci_onproc = NULL;
rcpu = cpuinfo_to_rumpcpu(ci);
KASSERT(rcpu->rcpu_ci == ci);
if (interlock == rcpu->rcpu_mtx)
rumpuser_mutex_enter_nowrap(rcpu->rcpu_mtx);
else
membar_release();
old = atomic_swap_ptr(&rcpu->rcpu_prevlwp, l);
if (old == RCPULWP_BUSY) {
return;
}
KASSERT(old == RCPULWP_WANTED);
if (interlock != rcpu->rcpu_mtx)
rumpuser_mutex_enter_nowrap(rcpu->rcpu_mtx);
if (rcpu->rcpu_wanted)
rumpuser_cv_broadcast(rcpu->rcpu_cv);
if (interlock != rcpu->rcpu_mtx)
rumpuser_mutex_exit(rcpu->rcpu_mtx);
}
void
yield()
{
struct lwp *l = curlwp;
int nlocks;
KERNEL_UNLOCK_ALL(l, &nlocks);
rump_unschedule_cpu(l);
rump_schedule_cpu(l);
KERNEL_LOCK(nlocks, l);
}
void
preempt()
{
yield();
}
bool
kpreempt(uintptr_t where)
{
return false;
}
void
kpreempt_disable(void)
{
KPREEMPT_DISABLE(curlwp);
}
void
kpreempt_enable(void)
{
KPREEMPT_ENABLE(curlwp);
}
bool
kpreempt_disabled(void)
{
#if 0
const lwp_t *l = curlwp;
return l->l_nopreempt != 0 || l->l_stat == LSZOMB ||
(l->l_flag & LW_IDLE) != 0 || cpu_kpreempt_disabled();
#endif
return true;
}
void
suspendsched(void)
{
}
void
sched_nice(struct proc *p, int level)
{
}
void
setrunnable(struct lwp *l)
{
sched_enqueue(l);
}
void
sched_enqueue(struct lwp *l)
{
rump_thread_allow(l);
}
void
sched_resched_cpu(struct cpu_info *ci, pri_t pri, bool unlock)
{
}
void
sched_resched_lwp(struct lwp *l, bool unlock)
{
}
void
sched_dequeue(struct lwp *l)
{
panic("sched_dequeue not implemented");
}
void
preempt_point(void)
{
}
bool
preempt_needed(void)
{
return false;
}