root/sys/rump/librump/rumpkern/threads.c
/*      $NetBSD: threads.c,v 1.28 2023/10/04 21:56:15 ad Exp $  */

/*
 * Copyright (c) 2007-2009 Antti Kantee.  All Rights Reserved.
 *
 * Development of this software was supported by
 * The Finnish Cultural Foundation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: threads.c,v 1.28 2023/10/04 21:56:15 ad Exp $");

#include <sys/param.h>
#include <sys/atomic.h>
#include <sys/kmem.h>
#include <sys/kthread.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <sys/queue.h>

#include <rump-sys/kern.h>

#include <rump/rumpuser.h>

struct thrdesc {
        void (*f)(void *);
        void *arg;
        struct lwp *newlwp;
        int runnable;

        TAILQ_ENTRY(thrdesc) entries;
};

static bool threads_are_go;
static struct rumpuser_mtx *thrmtx;
static struct rumpuser_cv *thrcv;
static TAILQ_HEAD(, thrdesc) newthr;

static void *
threadbouncer(void *arg)
{
        struct thrdesc *td = arg;
        struct lwp *l = td->newlwp;
        void (*f)(void *);
        void *thrarg;

        f = td->f;
        thrarg = td->arg;

        /* don't allow threads to run before all CPUs have fully attached */
        if (!threads_are_go) {
                rumpuser_mutex_enter_nowrap(thrmtx);
                while (!threads_are_go) {
                        rumpuser_cv_wait_nowrap(thrcv, thrmtx);
                }
                rumpuser_mutex_exit(thrmtx);
        }

        /* schedule ourselves */
        rump_lwproc_curlwp_set(l);
        rump_schedule();

        /* free dance struct */
        kmem_intr_free(td, sizeof(*td));

        if ((curlwp->l_pflag & LP_MPSAFE) == 0)
                KERNEL_LOCK(1, NULL);

        f(thrarg);

        panic("unreachable, should kthread_exit()");
}

void
rump_thread_init(void)
{

        rumpuser_mutex_init(&thrmtx, RUMPUSER_MTX_SPIN);
        rumpuser_cv_init(&thrcv);
        TAILQ_INIT(&newthr);
}

void
rump_thread_allow(struct lwp *l)
{
        struct thrdesc *td;

        rumpuser_mutex_enter(thrmtx);
        if (l == NULL) {
                threads_are_go = true;
        } else {
                TAILQ_FOREACH(td, &newthr, entries) {
                        if (td->newlwp == l) {
                                td->runnable = 1;
                                break;
                        }
                }
        }
        rumpuser_cv_broadcast(thrcv);
        rumpuser_mutex_exit(thrmtx);
}

static struct {
        const char *t_name;
        bool t_ncmp;
} nothreads[] = {
        { "vrele", false },
        { "vdrain", false },
        { "cachegc", false },
        { "nfssilly", false },
        { "unpgc", false },
        { "pmf", true },
        { "xcall", true },
};

int
kthread_create(pri_t pri, int flags, struct cpu_info *ci,
        void (*func)(void *), void *arg, lwp_t **newlp, const char *fmt, ...)
{
        char thrstore[MAXCOMLEN];
        const char *thrname = NULL;
        va_list ap;
        struct thrdesc *td;
        struct lwp *l;
        int rv;

        thrstore[0] = '\0';
        if (fmt) {
                va_start(ap, fmt);
                vsnprintf(thrstore, sizeof(thrstore), fmt, ap);
                va_end(ap);
                thrname = thrstore;
        }

        /*
         * We don't want a module unload thread.
         * (XXX: yes, this is a kludge too, and the kernel should
         * have a more flexible method for configuring which threads
         * we want).
         */
        if (strcmp(thrstore, "modunload") == 0) {
                return 0;
        }

        if (!rump_threads) {
                bool matched;
                int i;

                /* do we want to fake it? */
                for (i = 0; i < __arraycount(nothreads); i++) {
                        if (nothreads[i].t_ncmp) {
                                matched = strncmp(thrstore, nothreads[i].t_name,
                                    strlen(nothreads[i].t_name)) == 0;
                        } else {
                                matched = strcmp(thrstore,
                                    nothreads[i].t_name) == 0;
                        }
                        if (matched) {
                                aprint_error("rump kernel threads not enabled, "
                                    "%s not functional\n", nothreads[i].t_name);
                                return 0;
                        }
                }
                panic("threads not available");
        }
        KASSERT(fmt != NULL);

        /*
         * Allocate with intr-safe allocator, give that we may be
         * creating interrupt threads.
         */
        td = kmem_intr_alloc(sizeof(*td), KM_SLEEP);
        td->f = func;
        td->arg = arg;
        td->newlwp = l = rump__lwproc_alloclwp(&proc0);
        l->l_flag |= LW_SYSTEM;
        if (flags & KTHREAD_MPSAFE)
                l->l_pflag |= LP_MPSAFE;
        if (flags & KTHREAD_INTR)
                l->l_pflag |= LP_INTR;
        if (ci) {
                l->l_pflag |= LP_BOUND;
                l->l_target_cpu = ci;
        }
        if (thrname) {
                l->l_name = kmem_alloc(MAXCOMLEN, KM_SLEEP);
                strlcpy(l->l_name, thrname, MAXCOMLEN);
        }
                
        rv = rumpuser_thread_create(threadbouncer, td, thrname,
            (flags & KTHREAD_MUSTJOIN) == KTHREAD_MUSTJOIN,
            pri, ci ? ci->ci_index : -1, &l->l_ctxlink);
        if (rv)
                return rv; /* XXX */

        if (newlp) {
                *newlp = l;
        } else {
                KASSERT((flags & KTHREAD_MUSTJOIN) == 0);
        }

        return 0;
}

void
kthread_exit(int ecode)
{

        if ((curlwp->l_pflag & LP_MPSAFE) == 0)
                KERNEL_UNLOCK_LAST(NULL);
        rump_lwproc_releaselwp();
        /* unschedule includes membar */
        rump_unschedule();
        rumpuser_thread_exit();
}

int
kthread_join(struct lwp *l)
{
        int rv;

        KASSERT(l->l_ctxlink != NULL);
        rv = rumpuser_thread_join(l->l_ctxlink);
        membar_consumer();

        return rv;
}

int
kthread_fpu_enter(void)
{
        struct lwp *l = curlwp;
        int s;

        KASSERTMSG(l->l_flag & LW_SYSTEM,
            "%s is allowed only in kthreads", __func__);
        s = l->l_flag & LW_SYSTEM_FPU;
        l->l_flag |= LW_SYSTEM_FPU;

        return s;
}

void
kthread_fpu_exit(int s)
{
        struct lwp *l = curlwp;

        KASSERT(s == (s & LW_SYSTEM_FPU));
        KASSERTMSG(l->l_flag & LW_SYSTEM,
            "%s is allowed only in kthreads", __func__);
        KASSERT(l->l_flag & LW_SYSTEM_FPU);
        l->l_flag ^= s ^ LW_SYSTEM_FPU;
}

/*
 * Create a non-kernel thread that is scheduled by a rump kernel hypercall.
 *
 * Sounds strange and out-of-place?  yup yup yup.  the original motivation
 * for this was aio.  This is a very infrequent code path in rump kernels.
 * XXX: threads created with lwp_create() are eternal for local clients.
 * however, they are correctly reaped for remote clients with process exit.
 */
static void *
lwpbouncer(void *arg)
{
        struct thrdesc *td = arg;
        struct lwp *l = td->newlwp;
        void (*f)(void *);
        void *thrarg;
        int run;

        f = td->f;
        thrarg = td->arg;

        /* do not run until we've been enqueued */
        rumpuser_mutex_enter_nowrap(thrmtx);
        while ((run = td->runnable) == 0) {
                rumpuser_cv_wait_nowrap(thrcv, thrmtx);
        }
        rumpuser_mutex_exit(thrmtx);

        /* schedule ourselves */
        rump_lwproc_curlwp_set(l);
        rump_schedule();
        kmem_free(td, sizeof(*td));

        /* should we just die instead? */
        if (run == -1) {
                rump_lwproc_releaselwp();
                lwp_userret(l);
                panic("lwpbouncer reached unreachable");
        }

        /* run, and don't come back! */
        f(thrarg);
        panic("lwp return from worker not supported");
}

int
lwp_create(struct lwp *l1, struct proc *p2, vaddr_t uaddr, int flags,
    void *stack, size_t stacksize, void (*func)(void *), void *arg,
    struct lwp **newlwpp, int sclass, const sigset_t *sigmask,
    const stack_t *sigstk)
{
        struct thrdesc *td;
        struct lwp *l;
        int rv;

        if (flags)
                panic("lwp_create: flags not supported by this implementation");
        td = kmem_alloc(sizeof(*td), KM_SLEEP);
        td->f = func;
        td->arg = arg;
        td->runnable = 0;
        td->newlwp = l = rump__lwproc_alloclwp(p2);

        rumpuser_mutex_enter_nowrap(thrmtx);
        TAILQ_INSERT_TAIL(&newthr, td, entries);
        rumpuser_mutex_exit(thrmtx);

        rv = rumpuser_thread_create(lwpbouncer, td, p2->p_comm, 0,
            PRI_USER, -1, NULL);
        if (rv)
                panic("rumpuser_thread_create failed"); /* XXX */

        *newlwpp = l;
        return 0;
}

void
lwp_exit(struct lwp *l)
{
        struct thrdesc *td;

        rumpuser_mutex_enter_nowrap(thrmtx);
        TAILQ_FOREACH(td, &newthr, entries) {
                if (td->newlwp == l) {
                        td->runnable = -1;
                        break;
                }
        }
        rumpuser_mutex_exit(thrmtx);

        if (td == NULL)
                panic("lwp_exit: could not find %p\n", l);
}

void
lwp_userret(struct lwp *l)
{

        if ((l->l_flag & LW_RUMP_QEXIT) == 0)
                return;

        /* ok, so we should die */
        rump_unschedule();
        rumpuser_thread_exit();
}

void
lwp_need_userret(struct lwp *l)
{

        /* do what? */
}