1639 lines
41 KiB
C
1639 lines
41 KiB
C
/* $NetBSD: kern_sa.c,v 1.46 2003/11/28 08:18:03 scw Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2001 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Nathan J. Williams.
|
|
*
|
|
* 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.
|
|
* 3. All advertising materials mentioning features or use of this software
|
|
* must display the following acknowledgement:
|
|
* This product includes software developed by the NetBSD
|
|
* Foundation, Inc. and its contributors.
|
|
* 4. Neither the name of The NetBSD Foundation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``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 FOUNDATION 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: kern_sa.c,v 1.46 2003/11/28 08:18:03 scw Exp $");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/pool.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/types.h>
|
|
#include <sys/ucontext.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/sa.h>
|
|
#include <sys/savar.h>
|
|
#include <sys/syscallargs.h>
|
|
|
|
#include <uvm/uvm_extern.h>
|
|
|
|
static __inline int sast_compare(struct sastack *, struct sastack *);
|
|
static void sa_setwoken(struct lwp *);
|
|
static int sa_newcachelwp(struct lwp *);
|
|
static struct lwp *sa_vp_repossess(struct lwp *l);
|
|
|
|
static __inline int sa_pagefault(struct lwp *, ucontext_t *);
|
|
|
|
void sa_upcall_getstate(union sau_state *, struct lwp *);
|
|
|
|
MALLOC_DEFINE(M_SA, "sa", "Scheduler activations");
|
|
|
|
#define SA_DEBUG
|
|
|
|
#ifdef SA_DEBUG
|
|
#define DPRINTF(x) do { if (sadebug) printf x; } while (0)
|
|
#define DPRINTFN(n,x) do { if (sadebug & (1<<(n-1))) printf x; } while (0)
|
|
int sadebug = 0;
|
|
#else
|
|
#define DPRINTF(x)
|
|
#define DPRINTFN(n,x)
|
|
#endif
|
|
|
|
|
|
#define SA_LWP_STATE_LOCK(l, f) do { \
|
|
(f) = (l)->l_flag; \
|
|
(l)->l_flag &= ~L_SA; \
|
|
} while (/*CONSTCOND*/ 0)
|
|
|
|
#define SA_LWP_STATE_UNLOCK(l, f) do { \
|
|
(l)->l_flag |= (f) & L_SA; \
|
|
} while (/*CONSTCOND*/ 0)
|
|
|
|
SPLAY_PROTOTYPE(sasttree, sastack, sast_node, sast_compare);
|
|
SPLAY_GENERATE(sasttree, sastack, sast_node, sast_compare);
|
|
|
|
|
|
/*
|
|
* sadata_upcall_alloc:
|
|
*
|
|
* Allocate an sadata_upcall structure.
|
|
*/
|
|
struct sadata_upcall *
|
|
sadata_upcall_alloc(int waitok)
|
|
{
|
|
|
|
/* XXX zero the memory? */
|
|
return (pool_get(&saupcall_pool, waitok ? PR_WAITOK : PR_NOWAIT));
|
|
}
|
|
|
|
/*
|
|
* sadata_upcall_free:
|
|
*
|
|
* Free an sadata_upcall structure, and any associated
|
|
* argument data.
|
|
*/
|
|
void
|
|
sadata_upcall_free(struct sadata_upcall *sau)
|
|
{
|
|
extern struct pool siginfo_pool; /* XXX Ew. */
|
|
|
|
/*
|
|
* XXX We have to know what the origin of sau_arg is
|
|
* XXX in order to do the right thing, here. Sucks
|
|
* XXX to be a non-garbage-collecting kernel.
|
|
*/
|
|
if (sau->sau_arg) {
|
|
switch (sau->sau_type) {
|
|
case SA_UPCALL_SIGNAL:
|
|
case SA_UPCALL_SIGEV:
|
|
pool_put(&siginfo_pool, sau->sau_arg);
|
|
break;
|
|
default:
|
|
panic("sadata_free: unknown type of sau_arg: %d",
|
|
sau->sau_type);
|
|
}
|
|
}
|
|
|
|
pool_put(&saupcall_pool, sau);
|
|
}
|
|
|
|
int
|
|
sys_sa_register(struct lwp *l, void *v, register_t *retval)
|
|
{
|
|
struct sys_sa_register_args /* {
|
|
syscallarg(sa_upcall_t) new;
|
|
syscallarg(sa_upcall_t *) old;
|
|
syscallarg(int) flags;
|
|
} */ *uap = v;
|
|
struct proc *p = l->l_proc;
|
|
struct sadata *sa;
|
|
sa_upcall_t prev;
|
|
int error;
|
|
|
|
if (p->p_sa == NULL) {
|
|
/* Allocate scheduler activations data structure */
|
|
sa = pool_get(&sadata_pool, PR_WAITOK);
|
|
/* Initialize. */
|
|
memset(sa, 0, sizeof(*sa));
|
|
simple_lock_init(&sa->sa_lock);
|
|
sa->sa_flag = SCARG(uap, flags) & SA_FLAG_ALL;
|
|
sa->sa_vp = NULL;
|
|
sa->sa_wokenq_head = NULL;
|
|
sa->sa_concurrency = 1;
|
|
SPLAY_INIT(&sa->sa_stackstree);
|
|
SLIST_INIT(&sa->sa_stackslist);
|
|
sa->sa_nstacks = 0;
|
|
sa->sa_vp_faultaddr = 0;
|
|
sa->sa_vp_ofaultaddr = 0;
|
|
LIST_INIT(&sa->sa_lwpcache);
|
|
SIMPLEQ_INIT(&sa->sa_upcalls);
|
|
p->p_sa = sa;
|
|
sa_newcachelwp(l);
|
|
}
|
|
|
|
prev = p->p_sa->sa_upcall;
|
|
p->p_sa->sa_upcall = SCARG(uap, new);
|
|
if (SCARG(uap, old)) {
|
|
error = copyout(&prev, SCARG(uap, old),
|
|
sizeof(prev));
|
|
if (error)
|
|
return (error);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
sa_release(struct proc *p)
|
|
{
|
|
struct sadata *sa;
|
|
struct sastack *sast, *next;
|
|
|
|
sa = p->p_sa;
|
|
KDASSERT(sa != NULL);
|
|
|
|
for (sast = SPLAY_MIN(sasttree, &sa->sa_stackstree); sast != NULL;
|
|
sast = next) {
|
|
next = SPLAY_NEXT(sasttree, &sa->sa_stackstree, sast);
|
|
SPLAY_REMOVE(sasttree, &sa->sa_stackstree, sast);
|
|
pool_put(&sastack_pool, sast);
|
|
}
|
|
|
|
p->p_flag &= ~P_SA;
|
|
pool_put(&sadata_pool, sa);
|
|
p->p_sa = NULL;
|
|
}
|
|
|
|
static __inline int
|
|
sast_compare(struct sastack *a, struct sastack *b)
|
|
{
|
|
if ((vaddr_t)a->sast_stack.ss_sp + a->sast_stack.ss_size <=
|
|
(vaddr_t)b->sast_stack.ss_sp)
|
|
return (-1);
|
|
if ((vaddr_t)a->sast_stack.ss_sp >=
|
|
(vaddr_t)b->sast_stack.ss_sp + b->sast_stack.ss_size)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
sys_sa_stacks(struct lwp *l, void *v, register_t *retval)
|
|
{
|
|
struct sys_sa_stacks_args /* {
|
|
syscallarg(int) num;
|
|
syscallarg(stack_t *) stacks;
|
|
} */ *uap = v;
|
|
struct sadata *sa = l->l_proc->p_sa;
|
|
struct lwp *l2;
|
|
struct sastack *sast, newsast;
|
|
int count, error, f, i;
|
|
|
|
/* We have to be using scheduler activations */
|
|
if (sa == NULL)
|
|
return (EINVAL);
|
|
|
|
count = SCARG(uap, num);
|
|
if (count < 0)
|
|
return (EINVAL);
|
|
|
|
SA_LWP_STATE_LOCK(l, f);
|
|
|
|
error = 0;
|
|
|
|
for (i = 0; i < count; i++) {
|
|
error = copyin(SCARG(uap, stacks) + i, &newsast.sast_stack,
|
|
sizeof(stack_t));
|
|
if (error) {
|
|
count = i;
|
|
break;
|
|
}
|
|
if ((sast = SPLAY_FIND(sasttree, &sa->sa_stackstree, &newsast))) {
|
|
DPRINTFN(9, ("sa_stacks(%d.%d) returning stack %p\n",
|
|
l->l_proc->p_pid, l->l_lid,
|
|
newsast.sast_stack.ss_sp));
|
|
if ((l2 = sast->sast_blocker)) {
|
|
l2->l_upcallstack = NULL;
|
|
sast->sast_blocker = NULL;
|
|
}
|
|
if (SLIST_NEXT(sast, sast_list) != (void *)-1) {
|
|
count = i;
|
|
error = EEXIST;
|
|
break;
|
|
}
|
|
} else if (sa->sa_nstacks >= SA_MAXNUMSTACKS * sa->sa_concurrency) {
|
|
DPRINTFN(9, ("sa_stacks(%d.%d) already using %d stacks\n",
|
|
l->l_proc->p_pid, l->l_lid,
|
|
SA_MAXNUMSTACKS * sa->sa_concurrency));
|
|
count = i;
|
|
error = ENOMEM;
|
|
break;
|
|
} else {
|
|
DPRINTFN(9, ("sa_stacks(%d.%d) adding stack %p\n",
|
|
l->l_proc->p_pid, l->l_lid,
|
|
newsast.sast_stack.ss_sp));
|
|
sast = pool_get(&sastack_pool, PR_WAITOK);
|
|
sast->sast_stack = newsast.sast_stack;
|
|
sast->sast_blocker = NULL;
|
|
SPLAY_INSERT(sasttree, &sa->sa_stackstree, sast);
|
|
sa->sa_nstacks++;
|
|
}
|
|
SLIST_INSERT_HEAD(&sa->sa_stackslist, sast, sast_list);
|
|
}
|
|
|
|
/*
|
|
* Check if there are any pending upcalls we didn't make
|
|
* because there were not enough stacks.
|
|
*/
|
|
if (sa->sa_wokenq_head != NULL)
|
|
l->l_flag |= L_SA_UPCALL;
|
|
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
|
|
*retval = count;
|
|
return (error);
|
|
}
|
|
|
|
|
|
int
|
|
sys_sa_enable(struct lwp *l, void *v, register_t *retval)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
struct sadata *sa = p->p_sa;
|
|
int error;
|
|
|
|
DPRINTF(("sys_sa_enable(%d.%d)\n", l->l_proc->p_pid,
|
|
l->l_lid));
|
|
|
|
/* We have to be using scheduler activations */
|
|
if (sa == NULL)
|
|
return (EINVAL);
|
|
|
|
if (p->p_flag & P_SA) /* Already running! */
|
|
return (EBUSY);
|
|
|
|
error = sa_upcall(l, SA_UPCALL_NEWPROC, l, NULL, 0, NULL);
|
|
if (error)
|
|
return (error);
|
|
|
|
/* Assign this LWP to the virtual processor */
|
|
sa->sa_vp = l;
|
|
|
|
p->p_flag |= P_SA;
|
|
l->l_flag |= L_SA; /* We are now an activation LWP */
|
|
|
|
/* This will not return to the place in user space it came from. */
|
|
return (0);
|
|
}
|
|
|
|
|
|
int
|
|
sys_sa_setconcurrency(struct lwp *l, void *v, register_t *retval)
|
|
{
|
|
struct sys_sa_setconcurrency_args /* {
|
|
syscallarg(int) concurrency;
|
|
} */ *uap = v;
|
|
struct sadata *sa = l->l_proc->p_sa;
|
|
|
|
DPRINTF(("sys_sa_concurrency(%d.%d)\n", l->l_proc->p_pid,
|
|
l->l_lid));
|
|
|
|
/* We have to be using scheduler activations */
|
|
if (sa == NULL)
|
|
return (EINVAL);
|
|
|
|
if (SCARG(uap, concurrency) < 1)
|
|
return (EINVAL);
|
|
|
|
*retval = sa->sa_concurrency;
|
|
/*
|
|
* Concurrency greater than the number of physical CPUs does
|
|
* not make sense.
|
|
* XXX Should we ever support hot-plug CPUs, this will need
|
|
* adjustment.
|
|
*/
|
|
sa->sa_concurrency = min(SCARG(uap, concurrency), 1 /* XXX ncpus */);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
sys_sa_yield(struct lwp *l, void *v, register_t *retval)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
|
|
if (p->p_sa == NULL || !(p->p_flag & P_SA)) {
|
|
DPRINTFN(1,("sys_sa_yield(%d.%d) proc %p not SA (p_sa %p, flag %s)\n",
|
|
p->p_pid, l->l_lid, p, p->p_sa, p->p_flag & P_SA ? "T" : "F"));
|
|
return (EINVAL);
|
|
}
|
|
|
|
sa_yield(l);
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
sa_yield(struct lwp *l)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
#ifdef DEBUG
|
|
struct sadata *sa = p->p_sa;
|
|
#endif
|
|
int ret, s;
|
|
|
|
#if defined(MULTIPROCESSOR)
|
|
KDASSERT(l->l_flag & L_BIGLOCK);
|
|
#endif
|
|
/*
|
|
* If we're the last running LWP, stick around to recieve
|
|
* signals.
|
|
*/
|
|
KDASSERT((l->l_flag & L_SA_YIELD) == 0);
|
|
DPRINTFN(1,("sa_yield(%d.%d) going dormant\n",
|
|
p->p_pid, l->l_lid));
|
|
/*
|
|
* A signal will probably wake us up. Worst case, the upcall
|
|
* happens and just causes the process to yield again.
|
|
*/
|
|
s = splsched(); /* Protect from timer expirations */
|
|
KDASSERT(sa->sa_vp == l);
|
|
/*
|
|
* If we were told to make an upcall or exit before
|
|
* the splsched(), make sure we process it instead of
|
|
* going to sleep. It might make more sense for this to
|
|
* be handled inside of tsleep....
|
|
*/
|
|
ret = 0;
|
|
while (ret == 0 && p->p_userret == NULL &&
|
|
(l->l_flag & L_SA_UPCALL) == 0) {
|
|
l->l_flag |= L_SA_YIELD;
|
|
ret = tsleep((caddr_t) l, PUSER | PCATCH, "sawait", 0);
|
|
l->l_flag &= ~L_SA_YIELD;
|
|
if (p->p_flag & P_WEXIT)
|
|
lwp_exit(l);
|
|
KDASSERT(sa->sa_vp == l);
|
|
}
|
|
splx(s);
|
|
DPRINTFN(1,("sa_yield(%d.%d) returned\n",
|
|
p->p_pid, l->l_lid));
|
|
}
|
|
|
|
|
|
int
|
|
sys_sa_preempt(struct lwp *l, void *v, register_t *retval)
|
|
{
|
|
|
|
/* XXX Implement me. */
|
|
return (ENOSYS);
|
|
}
|
|
|
|
|
|
/* XXX Hm, naming collision. */
|
|
void
|
|
sa_preempt(struct lwp *l)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
struct sadata *sa = p->p_sa;
|
|
|
|
/*
|
|
* Defer saving the lwp's state because on some ports
|
|
* preemption can occur between generating an unblocked upcall
|
|
* and processing the upcall queue.
|
|
*/
|
|
if (sa->sa_flag & SA_FLAG_PREEMPT)
|
|
sa_upcall(l, SA_UPCALL_PREEMPTED | SA_UPCALL_DEFER_EVENT,
|
|
l, NULL, 0, NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* Help userspace library resolve locks and critical sections:
|
|
* - return if the unblocked upcall has already been delivered.
|
|
* This case is usually already detected in userspace.
|
|
* - recycles the calling LWP and its stack if it was not preempted
|
|
* and the unblocked upcall was not yet delivered. Put the sa_id
|
|
* LWP on the VP and wait until it unblocks or switch to it if it's
|
|
* ready. There will be no unblocked upcall.
|
|
* - recycles the blocked LWP if up_preempted == NULL. This is used
|
|
* if the blocked LWP is an idle thread and we don't care for the
|
|
* unblocked upcall.
|
|
* - otherwise, wait for the blocked LWP to get ready. The unblocked
|
|
* upcall is delivered when we return.
|
|
* This is used if a thread blocks (mostly because of a pagefault) and
|
|
* is in a critical section in the userspace library and the critical
|
|
* section resolving code cannot continue until the blocked thread is
|
|
* unblocked.
|
|
*/
|
|
int
|
|
sys_sa_unblockyield(struct lwp *l, void *v, register_t *retval)
|
|
{
|
|
struct sys_sa_unblockyield_args /* {
|
|
syscallarg(int) sa_id;
|
|
syscallarg(void *) up_preempted;
|
|
syscallarg(stack_t *) up_stack;
|
|
} */ *uap = v;
|
|
struct sadata *sa = l->l_proc->p_sa;
|
|
struct proc *p = l->l_proc;
|
|
struct lwp *l2, **hp;
|
|
struct sastack sast;
|
|
int error, f, s;
|
|
void *preempted;
|
|
|
|
if (sa == NULL)
|
|
return (EINVAL);
|
|
|
|
SA_LWP_STATE_LOCK(l, f);
|
|
error = copyin(SCARG(uap, up_stack), &sast.sast_stack,
|
|
sizeof(stack_t));
|
|
if (error) {
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
return (error);
|
|
}
|
|
|
|
if (SCARG(uap, up_preempted) != NULL) {
|
|
error = copyin(SCARG(uap, up_preempted), &preempted,
|
|
sizeof(void *));
|
|
if (error) {
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
return (error);
|
|
}
|
|
} else
|
|
preempted = (void *)-1;
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
|
|
SCHED_LOCK(s);
|
|
LIST_FOREACH(l2, &p->p_lwps, l_sibling) {
|
|
if (l2->l_lid == SCARG(uap, sa_id)) {
|
|
break;
|
|
}
|
|
}
|
|
if (l2 && (l2->l_upcallstack == NULL ||
|
|
(l2->l_upcallstack->sast_blocker != l2 &&
|
|
l2->l_upcallstack->sast_blocker != NULL)))
|
|
l2 = NULL;
|
|
|
|
if (l2 == NULL) {
|
|
/* just return, prevented in userland most of the time */
|
|
DPRINTFN(11,("sys_sa_unblockyield(%d.%d) unblocked upcall done\n",
|
|
p->p_pid, l->l_lid));
|
|
KDASSERT(preempted != NULL);
|
|
} else if (preempted == (void *)-1) {
|
|
/* recycle blocked LWP */
|
|
DPRINTFN(11,("sys_sa_unblockyield(%d.%d) recycle %d "
|
|
"(was %sready) upcall stack %p\n",
|
|
p->p_pid, l->l_lid, l2->l_lid,
|
|
(l2->l_upcallstack->sast_blocker == NULL) ? "" :
|
|
"not ", l2->l_upcallstack->sast_stack.ss_sp));
|
|
|
|
if (l2->l_upcallstack->sast_blocker == NULL) {
|
|
/*
|
|
* l2 is on the wokenq, remove it and put l2
|
|
* in the cache
|
|
*/
|
|
hp = &sa->sa_wokenq_head;
|
|
while (*hp != l2)
|
|
hp = &(*hp)->l_forw;
|
|
*hp = l2->l_forw;
|
|
if (sa->sa_wokenq_tailp == &l2->l_forw)
|
|
sa->sa_wokenq_tailp = hp;
|
|
l2->l_flag &= ~L_SA_BLOCKING;
|
|
l2->l_upcallstack = NULL;
|
|
sa_putcachelwp(p, l2); /* PHOLD from sa_setwoken */
|
|
} else
|
|
/* let sa_setwoken put it in the cache */
|
|
l2->l_upcallstack->sast_blocker = NULL;
|
|
} else if (preempted != NULL) {
|
|
/* wait for the blocked LWP to get ready, then return */
|
|
DPRINTFN(11,("sys_sa_unblockyield(%d.%d) waiting for %d "
|
|
"(was %sready) upcall stack %p\n",
|
|
p->p_pid, l->l_lid, l2->l_lid,
|
|
(l2->l_upcallstack->sast_blocker == NULL) ? "" :
|
|
"not ", l2->l_upcallstack->sast_stack.ss_sp));
|
|
|
|
if (l2->l_upcallstack->sast_blocker != NULL) {
|
|
l2->l_upcallstack->sast_blocker = l;
|
|
SCHED_UNLOCK(s); /* XXXcl we're still holding
|
|
* the kernel lock, is that
|
|
* good enough? */
|
|
SA_LWP_STATE_LOCK(l, f);
|
|
tsleep((caddr_t) &l2->l_upcallstack, PWAIT,
|
|
"saunblock", 0);
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
if (p->p_flag & P_WEXIT)
|
|
lwp_exit(l);
|
|
return(0);
|
|
}
|
|
} else {
|
|
/* recycle calling LWP and resume blocked LWP */
|
|
DPRINTFN(11,("sys_sa_unblockyield(%d.%d) resuming %d "
|
|
"(is %sready) upcall stack %p\n",
|
|
p->p_pid, l->l_lid, l2->l_lid,
|
|
(l2->l_upcallstack->sast_blocker == NULL) ? "" :
|
|
"not ", l2->l_upcallstack->sast_stack.ss_sp));
|
|
|
|
sa->sa_vp = l2;
|
|
l2->l_flag &= ~L_SA_BLOCKING;
|
|
SLIST_INSERT_HEAD(&sa->sa_stackslist, l2->l_upcallstack,
|
|
sast_list);
|
|
|
|
if (l2->l_upcallstack->sast_blocker == NULL) {
|
|
/*
|
|
* l2 is on the wokenq, remove it and
|
|
* continue l2
|
|
*/
|
|
hp = &sa->sa_wokenq_head;
|
|
while (*hp != l2)
|
|
hp = &(*hp)->l_forw;
|
|
*hp = l2->l_forw;
|
|
if (sa->sa_wokenq_tailp == &l2->l_forw)
|
|
sa->sa_wokenq_tailp = hp;
|
|
l2->l_upcallstack = NULL;
|
|
setrunnable(l2);
|
|
PRELE(l2); /* PHOLD from sa_setwoken */
|
|
} else {
|
|
/* cleanup l_upcallstack */
|
|
l2->l_upcallstack->sast_blocker = NULL;
|
|
l2->l_upcallstack = NULL;
|
|
l2 = NULL; /* don't continue l2 yet */
|
|
}
|
|
|
|
p->p_nrlwps--;
|
|
PHOLD(l);
|
|
sa_putcachelwp(p, l);
|
|
mi_switch(l, l2);
|
|
/* mostly NOTREACHED */
|
|
SCHED_ASSERT_UNLOCKED();
|
|
splx(s);
|
|
KDASSERT(p->p_flag & P_WEXIT);
|
|
lwp_exit(l);
|
|
}
|
|
|
|
SCHED_UNLOCK(s);
|
|
return (0);
|
|
}
|
|
|
|
|
|
/*
|
|
* Set up the user-level stack and trapframe to do an upcall.
|
|
*
|
|
* NOTE: This routine WILL FREE "arg" in the case of failure! Callers
|
|
* should not touch the "arg" pointer once calling sa_upcall().
|
|
*/
|
|
int
|
|
sa_upcall(struct lwp *l, int type, struct lwp *event, struct lwp *interrupted,
|
|
size_t argsize, void *arg)
|
|
{
|
|
struct sadata_upcall *sau;
|
|
struct sadata *sa = l->l_proc->p_sa;
|
|
struct sastack *sast;
|
|
int error, f;
|
|
|
|
/* XXX prevent recursive upcalls if we sleep formemory */
|
|
SA_LWP_STATE_LOCK(l, f);
|
|
sau = sadata_upcall_alloc(1);
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
|
|
sast = SLIST_FIRST(&sa->sa_stackslist);
|
|
if (sast == NULL) {
|
|
/* assign to assure that it gets freed */
|
|
sau->sau_type = type & SA_UPCALL_TYPE_MASK;
|
|
sau->sau_arg = arg;
|
|
sadata_upcall_free(sau);
|
|
return (ENOMEM);
|
|
}
|
|
SLIST_REMOVE_HEAD(&sa->sa_stackslist, sast_list);
|
|
SLIST_NEXT(sast, sast_list) = (void *)-1;
|
|
DPRINTFN(9,("sa_upcall(%d.%d) using stack %p\n",
|
|
l->l_proc->p_pid, l->l_lid, sast->sast_stack.ss_sp));
|
|
|
|
error = sa_upcall0(l, type, event, interrupted, argsize, arg, sau,
|
|
&sast->sast_stack);
|
|
if (error) {
|
|
SLIST_INSERT_HEAD(&sa->sa_stackslist, sast, sast_list);
|
|
sadata_upcall_free(sau);
|
|
return (error);
|
|
}
|
|
|
|
SIMPLEQ_INSERT_TAIL(&sa->sa_upcalls, sau, sau_next);
|
|
l->l_flag |= L_SA_UPCALL;
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
sa_upcall0(struct lwp *l, int type, struct lwp *event, struct lwp *interrupted,
|
|
size_t argsize, void *arg, struct sadata_upcall *sau, stack_t *st)
|
|
{
|
|
|
|
KDASSERT((event == NULL) || (event != interrupted));
|
|
|
|
sau->sau_flags = 0;
|
|
sau->sau_type = type & SA_UPCALL_TYPE_MASK;
|
|
sau->sau_argsize = argsize;
|
|
sau->sau_arg = arg;
|
|
sau->sau_stack = *st;
|
|
|
|
if (type & SA_UPCALL_DEFER_EVENT) {
|
|
sau->sau_event.ss_deferred.ss_lwp = event;
|
|
sau->sau_flags |= SAU_FLAG_DEFERRED_EVENT;
|
|
} else
|
|
sa_upcall_getstate(&sau->sau_event, event);
|
|
if (type & SA_UPCALL_DEFER_INTERRUPTED) {
|
|
sau->sau_interrupted.ss_deferred.ss_lwp = interrupted;
|
|
sau->sau_flags |= SAU_FLAG_DEFERRED_INTERRUPTED;
|
|
} else
|
|
sa_upcall_getstate(&sau->sau_interrupted, interrupted);
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
void
|
|
sa_upcall_getstate(union sau_state *ss, struct lwp *l)
|
|
{
|
|
|
|
if (l) {
|
|
getucontext(l, &ss->ss_captured.ss_ctx);
|
|
ss->ss_captured.ss_sa.sa_context = (ucontext_t *)
|
|
(intptr_t)((_UC_MACHINE_SP(&ss->ss_captured.ss_ctx) -
|
|
sizeof(ucontext_t))
|
|
#ifdef _UC_UCONTEXT_ALIGN
|
|
& _UC_UCONTEXT_ALIGN
|
|
#endif
|
|
);
|
|
ss->ss_captured.ss_sa.sa_id = l->l_lid;
|
|
ss->ss_captured.ss_sa.sa_cpu = 0; /* XXX extract from l_cpu */
|
|
} else
|
|
ss->ss_captured.ss_sa.sa_context = NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Detect double pagefaults and pagefaults on upcalls.
|
|
* - double pagefaults are detected by comparing the previous faultaddr
|
|
* against the current faultaddr
|
|
* - pagefaults on upcalls are detected by checking if the userspace
|
|
* thread is running on an upcall stack
|
|
*/
|
|
static __inline int
|
|
sa_pagefault(struct lwp *l, ucontext_t *l_ctx)
|
|
{
|
|
struct proc *p;
|
|
struct sadata *sa;
|
|
struct sastack sast;
|
|
|
|
p = l->l_proc;
|
|
sa = p->p_sa;
|
|
|
|
KDASSERT(sa->sa_vp == l);
|
|
|
|
if (sa->sa_vp_faultaddr == sa->sa_vp_ofaultaddr) {
|
|
DPRINTFN(10,("sa_pagefault(%d.%d) double page fault\n",
|
|
p->p_pid, l->l_lid));
|
|
return 1;
|
|
}
|
|
|
|
sast.sast_stack.ss_sp = (void *)(intptr_t)_UC_MACHINE_SP(l_ctx);
|
|
sast.sast_stack.ss_size = 1;
|
|
|
|
if (SPLAY_FIND(sasttree, &sa->sa_stackstree, &sast)) {
|
|
DPRINTFN(10,("sa_pagefault(%d.%d) upcall page fault\n",
|
|
p->p_pid, l->l_lid));
|
|
return 1;
|
|
}
|
|
|
|
sa->sa_vp_ofaultaddr = sa->sa_vp_faultaddr;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Called by tsleep(). Block current LWP and switch to another.
|
|
*
|
|
* WE ARE NOT ALLOWED TO SLEEP HERE! WE ARE CALLED FROM WITHIN
|
|
* TSLEEP() ITSELF! We are called with sched_lock held, and must
|
|
* hold it right through the mi_switch() call.
|
|
*/
|
|
void
|
|
sa_switch(struct lwp *l, int type)
|
|
{
|
|
struct proc *p = l->l_proc;
|
|
struct sadata *sa = p->p_sa;
|
|
struct sadata_upcall *sau;
|
|
struct lwp *l2;
|
|
struct sastack *sast;
|
|
int error, s;
|
|
|
|
DPRINTFN(4,("sa_switch(%d.%d type %d VP %d)\n", p->p_pid, l->l_lid,
|
|
type, sa->sa_vp ? sa->sa_vp->l_lid : 0));
|
|
SCHED_ASSERT_LOCKED();
|
|
|
|
if (p->p_flag & P_WEXIT) {
|
|
mi_switch(l, NULL);
|
|
return;
|
|
}
|
|
|
|
if (l->l_flag & L_SA_YIELD) {
|
|
/*
|
|
* Case 0: we're blocking in sa_yield
|
|
*/
|
|
if (sa->sa_wokenq_head == NULL) {
|
|
l->l_flag |= L_SA_IDLE;
|
|
mi_switch(l, NULL);
|
|
} else {
|
|
/* make us running again. */
|
|
unsleep(l);
|
|
l->l_stat = LSONPROC;
|
|
l->l_proc->p_nrlwps++;
|
|
s = splsched();
|
|
SCHED_UNLOCK(s);
|
|
}
|
|
return;
|
|
} else if (sa->sa_vp == l) {
|
|
/*
|
|
* Case 1: we're blocking for the first time; generate
|
|
* a SA_BLOCKED upcall and allocate resources for the
|
|
* UNBLOCKED upcall.
|
|
*/
|
|
/*
|
|
* The process of allocating a new LWP could cause
|
|
* sleeps. We're called from inside sleep, so that
|
|
* would be Bad. Therefore, we must use a cached new
|
|
* LWP. The first thing that this new LWP must do is
|
|
* allocate another LWP for the cache. */
|
|
l2 = sa_getcachelwp(p);
|
|
if (l2 == NULL) {
|
|
/* XXXSMP */
|
|
/* No upcall for you! */
|
|
/* XXX The consequences of this are more subtle and
|
|
* XXX the recovery from this situation deserves
|
|
* XXX more thought.
|
|
*/
|
|
|
|
/* XXXUPSXXX Should only happen with concurrency > 1 */
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_switch(%d.%d): no cached LWP for upcall.\n",
|
|
p->p_pid, l->l_lid);
|
|
#endif
|
|
mi_switch(l, NULL);
|
|
return;
|
|
}
|
|
|
|
sast = SLIST_FIRST(&sa->sa_stackslist);
|
|
if (sast == NULL) {
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_switch(%d.%d flag %x): Not enough stacks.\n",
|
|
p->p_pid, l->l_lid, l->l_flag);
|
|
#endif
|
|
sa_putcachelwp(p, l2); /* PHOLD from sa_getcachelwp */
|
|
mi_switch(l, NULL);
|
|
return;
|
|
}
|
|
SLIST_REMOVE_HEAD(&sa->sa_stackslist, sast_list);
|
|
SLIST_NEXT(sast, sast_list) = (void *)-1;
|
|
DPRINTFN(9,("sa_switch(%d.%d) using stack %p\n",
|
|
l->l_proc->p_pid, l->l_lid, sast->sast_stack.ss_sp));
|
|
|
|
/*
|
|
* XXX We need to allocate the sadata_upcall structure here,
|
|
* XXX since we can't sleep while waiting for memory inside
|
|
* XXX sa_upcall(). It would be nice if we could safely
|
|
* XXX allocate the sadata_upcall structure on the stack, here.
|
|
*/
|
|
sau = sadata_upcall_alloc(0);
|
|
if (sau == NULL) {
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_switch(%d.%d): couldn't allocate upcall data.\n",
|
|
p->p_pid, l->l_lid);
|
|
#endif
|
|
SLIST_INSERT_HEAD(&sa->sa_stackslist, sast, sast_list);
|
|
sa_putcachelwp(p, l2); /* PHOLD from sa_getcachelwp */
|
|
mi_switch(l, NULL);
|
|
return;
|
|
}
|
|
|
|
cpu_setfunc(l2, sa_switchcall, l2);
|
|
error = sa_upcall0(l2, SA_UPCALL_BLOCKED, l, NULL, 0, NULL,
|
|
sau, &sast->sast_stack);
|
|
if (error) {
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_switch(%d.%d): Error %d from sa_upcall()\n",
|
|
p->p_pid, l->l_lid, error);
|
|
#endif
|
|
SLIST_INSERT_HEAD(&sa->sa_stackslist, sast, sast_list);
|
|
sa_putcachelwp(p, l2); /* PHOLD from sa_getcachelwp */
|
|
mi_switch(l, NULL);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Perform the double/upcall pagefault check.
|
|
* We do this only here since we need l's ucontext to
|
|
* get l's userspace stack. sa_upcall0 above has saved
|
|
* it for us.
|
|
* The L_SA_PAGEFAULT flag is set in the MD
|
|
* pagefault code to indicate a pagefault. The MD
|
|
* pagefault code also saves the faultaddr for us.
|
|
*/
|
|
if ((l->l_flag & L_SA_PAGEFAULT) && sa_pagefault(l,
|
|
&sau->sau_event.ss_captured.ss_ctx) != 0) {
|
|
sadata_upcall_free(sau);
|
|
SLIST_INSERT_HEAD(&sa->sa_stackslist, sast, sast_list);
|
|
sa_putcachelwp(p, l2); /* PHOLD from sa_getcachelwp */
|
|
mi_switch(l, NULL);
|
|
DPRINTFN(10,("sa_switch(%d.%d) page fault resolved\n",
|
|
p->p_pid, l->l_lid));
|
|
return;
|
|
}
|
|
|
|
DPRINTFN(8,("sa_switch(%d.%d) blocked upcall %d, stack %p\n",
|
|
p->p_pid, l->l_lid, l2->l_lid, sast->sast_stack.ss_sp));
|
|
SIMPLEQ_INSERT_TAIL(&sa->sa_upcalls, sau, sau_next);
|
|
l2->l_flag |= L_SA_UPCALL;
|
|
|
|
l->l_flag |= L_SA_BLOCKING;
|
|
sast->sast_blocker = l;
|
|
l->l_upcallstack = sast;
|
|
l2->l_priority = l2->l_usrpri;
|
|
sa->sa_vp = l2;
|
|
setrunnable(l2);
|
|
PRELE(l2); /* Remove the artificial hold-count */
|
|
|
|
KDASSERT(l2 != l);
|
|
} else if (sa->sa_vp != NULL) {
|
|
/*
|
|
* Case 2: We've been woken up while another LWP was
|
|
* on the VP, but we're going back to sleep without
|
|
* having returned to userland and delivering the
|
|
* SA_UNBLOCKED upcall (select and poll cause this
|
|
* kind of behavior a lot). We just switch back to the
|
|
* LWP that had been running and let it have another
|
|
* go. If the LWP on the VP was idling, don't make it
|
|
* run again, though.
|
|
*/
|
|
if (sa->sa_vp->l_flag & L_SA_YIELD)
|
|
l2 = NULL;
|
|
else {
|
|
l2 = sa->sa_vp; /* XXXUPSXXX Unfair advantage for l2 ? */
|
|
if((l2->l_stat != LSRUN) || ((l2->l_flag & L_INMEM) == 0))
|
|
l2 = NULL;
|
|
}
|
|
} else {
|
|
/* NOTREACHED */
|
|
panic("sa_vp empty");
|
|
}
|
|
|
|
DPRINTFN(4,("sa_switch(%d.%d) switching to LWP %d.\n",
|
|
p->p_pid, l->l_lid, l2 ? l2->l_lid : 0));
|
|
mi_switch(l, l2);
|
|
|
|
DPRINTFN(4,("sa_switch(%d.%d flag %x) returned.\n",
|
|
p->p_pid, l->l_lid, l->l_flag));
|
|
KDASSERT(l->l_wchan == 0);
|
|
|
|
SCHED_ASSERT_UNLOCKED();
|
|
}
|
|
|
|
void
|
|
sa_switchcall(void *arg)
|
|
{
|
|
struct lwp *l;
|
|
struct proc *p;
|
|
struct sadata *sa;
|
|
int f;
|
|
|
|
l = arg;
|
|
p = l->l_proc;
|
|
sa = p->p_sa;
|
|
sa->sa_vp = l;
|
|
|
|
DPRINTFN(6,("sa_switchcall(%d.%d)\n", p->p_pid, l->l_lid));
|
|
|
|
if (LIST_EMPTY(&sa->sa_lwpcache)) {
|
|
/* Allocate the next cache LWP */
|
|
DPRINTFN(6,("sa_switchcall(%d.%d) allocating LWP\n",
|
|
p->p_pid, l->l_lid));
|
|
SA_LWP_STATE_LOCK(l, f);
|
|
sa_newcachelwp(l);
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
}
|
|
upcallret(l);
|
|
}
|
|
|
|
static int
|
|
sa_newcachelwp(struct lwp *l)
|
|
{
|
|
struct proc *p;
|
|
struct lwp *l2;
|
|
vaddr_t uaddr;
|
|
boolean_t inmem;
|
|
int s;
|
|
|
|
p = l->l_proc;
|
|
if (p->p_flag & P_WEXIT)
|
|
return (0);
|
|
|
|
inmem = uvm_uarea_alloc(&uaddr);
|
|
if (__predict_false(uaddr == 0)) {
|
|
return (ENOMEM);
|
|
} else {
|
|
newlwp(l, p, uaddr, inmem, 0, NULL, 0, child_return, 0, &l2);
|
|
/* We don't want this LWP on the process's main LWP list, but
|
|
* newlwp helpfully puts it there. Unclear if newlwp should
|
|
* be tweaked.
|
|
*/
|
|
PHOLD(l2);
|
|
SCHED_LOCK(s);
|
|
sa_putcachelwp(p, l2);
|
|
SCHED_UNLOCK(s);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* Take a normal process LWP and place it in the SA cache.
|
|
* LWP must not be running!
|
|
*/
|
|
void
|
|
sa_putcachelwp(struct proc *p, struct lwp *l)
|
|
{
|
|
struct sadata *sa;
|
|
|
|
SCHED_ASSERT_LOCKED();
|
|
|
|
sa = p->p_sa;
|
|
|
|
LIST_REMOVE(l, l_sibling);
|
|
p->p_nlwps--;
|
|
l->l_stat = LSSUSPENDED;
|
|
l->l_flag |= (L_DETACHED | L_SA);
|
|
/* XXX lock sadata */
|
|
DPRINTFN(5,("sa_putcachelwp(%d.%d) Adding LWP %d to cache\n",
|
|
p->p_pid, curlwp->l_lid, l->l_lid));
|
|
LIST_INSERT_HEAD(&sa->sa_lwpcache, l, l_sibling);
|
|
sa->sa_ncached++;
|
|
/* XXX unlock */
|
|
}
|
|
|
|
/*
|
|
* Fetch a LWP from the cache.
|
|
*/
|
|
struct lwp *
|
|
sa_getcachelwp(struct proc *p)
|
|
{
|
|
struct sadata *sa;
|
|
struct lwp *l;
|
|
|
|
SCHED_ASSERT_LOCKED();
|
|
|
|
l = NULL;
|
|
sa = p->p_sa;
|
|
/* XXX lock sadata */
|
|
if (sa->sa_ncached > 0) {
|
|
sa->sa_ncached--;
|
|
l = LIST_FIRST(&sa->sa_lwpcache);
|
|
LIST_REMOVE(l, l_sibling);
|
|
LIST_INSERT_HEAD(&p->p_lwps, l, l_sibling);
|
|
p->p_nlwps++;
|
|
DPRINTFN(5,("sa_getcachelwp(%d.%d) Got LWP %d from cache.\n",
|
|
p->p_pid, curlwp->l_lid, l->l_lid));
|
|
}
|
|
/* XXX unlock */
|
|
return l;
|
|
}
|
|
|
|
|
|
void
|
|
sa_unblock_userret(struct lwp *l)
|
|
{
|
|
struct proc *p;
|
|
struct lwp *l2;
|
|
struct sadata *sa;
|
|
struct sadata_upcall *sau;
|
|
struct sastack *sast;
|
|
int f, s;
|
|
|
|
p = l->l_proc;
|
|
sa = p->p_sa;
|
|
|
|
if (p->p_flag & P_WEXIT)
|
|
return;
|
|
|
|
SCHED_ASSERT_UNLOCKED();
|
|
|
|
KERNEL_PROC_LOCK(l);
|
|
SA_LWP_STATE_LOCK(l, f);
|
|
|
|
DPRINTFN(7,("sa_unblock_userret(%d.%d %x) \n", p->p_pid, l->l_lid,
|
|
l->l_flag));
|
|
|
|
sa_setwoken(l);
|
|
/* maybe NOTREACHED */
|
|
|
|
SCHED_LOCK(s);
|
|
if (l != sa->sa_vp) {
|
|
/* Invoke an "unblocked" upcall */
|
|
DPRINTFN(8,("sa_unblock_userret(%d.%d) unblocking\n",
|
|
p->p_pid, l->l_lid));
|
|
|
|
l2 = sa_vp_repossess(l);
|
|
|
|
SCHED_UNLOCK(s);
|
|
|
|
if (l2 == NULL)
|
|
lwp_exit(l);
|
|
|
|
PHOLD(l2);
|
|
|
|
sau = sadata_upcall_alloc(1);
|
|
sau->sau_arg = NULL;
|
|
|
|
if (p->p_flag & P_WEXIT) {
|
|
sadata_upcall_free(sau);
|
|
lwp_exit(l);
|
|
}
|
|
|
|
sast = SLIST_FIRST(&sa->sa_stackslist);
|
|
KDASSERT(sast != NULL);
|
|
SLIST_REMOVE_HEAD(&sa->sa_stackslist, sast_list);
|
|
SLIST_NEXT(sast, sast_list) = (void *)-1;
|
|
DPRINTFN(9,("sa_unblock_userret(%d.%d) using stack %p\n",
|
|
l->l_proc->p_pid, l->l_lid, sast->sast_stack.ss_sp));
|
|
|
|
/*
|
|
* Defer saving the event lwp's state because a
|
|
* PREEMPT upcall could be on the queue already.
|
|
*/
|
|
if (sa_upcall0(l, SA_UPCALL_UNBLOCKED | SA_UPCALL_DEFER_EVENT,
|
|
l, l2, 0, NULL, sau, &sast->sast_stack) != 0) {
|
|
/*
|
|
* We were supposed to deliver an UNBLOCKED
|
|
* upcall, but don't have resources to do so.
|
|
*/
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_unblock_userret: out of upcall resources"
|
|
" for %d.%d\n", p->p_pid, l->l_lid);
|
|
#endif
|
|
sigexit(l, SIGABRT);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
SCHED_LOCK(s);
|
|
SIMPLEQ_INSERT_TAIL(&sa->sa_upcalls, sau, sau_next);
|
|
l->l_flag |= L_SA_UPCALL;
|
|
l->l_flag &= ~L_SA_BLOCKING;
|
|
l->l_upcallstack = NULL;
|
|
sa_putcachelwp(p, l2);
|
|
}
|
|
SCHED_UNLOCK(s);
|
|
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
KERNEL_PROC_UNLOCK(l);
|
|
}
|
|
|
|
void
|
|
sa_upcall_userret(struct lwp *l)
|
|
{
|
|
struct lwp *l2, *eventq;
|
|
struct proc *p;
|
|
struct sadata *sa;
|
|
struct sa_t **sapp, *sap;
|
|
struct sadata_upcall *sau;
|
|
struct sa_t self_sa;
|
|
struct sa_t *sas[3], *sasp;
|
|
union sau_state e_ss;
|
|
struct sastack *sast;
|
|
void *stack, *ap;
|
|
ucontext_t u, *up;
|
|
int f, i, nint, nevents, s, type;
|
|
|
|
p = l->l_proc;
|
|
sa = p->p_sa;
|
|
|
|
SCHED_ASSERT_UNLOCKED();
|
|
|
|
KERNEL_PROC_LOCK(l);
|
|
SA_LWP_STATE_LOCK(l, f);
|
|
|
|
DPRINTFN(7,("sa_upcall_userret(%d.%d %x) \n", p->p_pid, l->l_lid,
|
|
l->l_flag));
|
|
|
|
KDASSERT(l->l_upcallstack == NULL);
|
|
|
|
SCHED_LOCK(s);
|
|
if (SIMPLEQ_EMPTY(&sa->sa_upcalls) && sa->sa_wokenq_head != NULL &&
|
|
!SLIST_EMPTY(&sa->sa_stackslist)) {
|
|
/* Invoke an "unblocked" upcall */
|
|
l2 = sa->sa_wokenq_head;
|
|
sa->sa_wokenq_head = l2->l_forw;
|
|
|
|
sast = SLIST_FIRST(&sa->sa_stackslist);
|
|
KDASSERT(sast != NULL);
|
|
SLIST_REMOVE_HEAD(&sa->sa_stackslist, sast_list);
|
|
SLIST_NEXT(sast, sast_list) = (void *)-1;
|
|
DPRINTFN(9,("sa_upcall_userret(%d.%d) using stack %p\n",
|
|
l->l_proc->p_pid, l->l_lid, sast->sast_stack.ss_sp));
|
|
|
|
SCHED_UNLOCK(s);
|
|
|
|
if (p->p_flag & P_WEXIT)
|
|
lwp_exit(l);
|
|
|
|
DPRINTFN(8,("sa_upcall_userret(%d.%d) unblocking %d\n",
|
|
p->p_pid, l->l_lid, l2->l_lid));
|
|
|
|
sau = sadata_upcall_alloc(1);
|
|
sau->sau_arg = NULL;
|
|
if (p->p_flag & P_WEXIT) {
|
|
sadata_upcall_free(sau);
|
|
lwp_exit(l);
|
|
}
|
|
|
|
if (sa_upcall0(l, SA_UPCALL_UNBLOCKED, l2, l, 0, NULL, sau,
|
|
&sast->sast_stack) != 0) {
|
|
/*
|
|
* We were supposed to deliver an UNBLOCKED
|
|
* upcall, but don't have resources to do so.
|
|
*/
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_upcall_userret: out of upcall resources"
|
|
" for %d.%d\n", p->p_pid, l->l_lid);
|
|
#endif
|
|
sigexit(l, SIGABRT);
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
SIMPLEQ_INSERT_TAIL(&sa->sa_upcalls, sau, sau_next);
|
|
|
|
l2->l_flag &= ~L_SA_BLOCKING;
|
|
l2->l_upcallstack = NULL;
|
|
SCHED_LOCK(s);
|
|
sa_putcachelwp(p, l2); /* PHOLD from sa_setwoken */
|
|
}
|
|
SCHED_UNLOCK(s);
|
|
|
|
KDASSERT(sa->sa_vp == l);
|
|
|
|
if (SIMPLEQ_EMPTY(&sa->sa_upcalls)) {
|
|
l->l_flag &= ~L_SA_UPCALL;
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
KERNEL_PROC_UNLOCK(l);
|
|
return;
|
|
}
|
|
|
|
sau = SIMPLEQ_FIRST(&sa->sa_upcalls);
|
|
SIMPLEQ_REMOVE_HEAD(&sa->sa_upcalls, sau_next);
|
|
|
|
if (sau->sau_flags & SAU_FLAG_DEFERRED_EVENT)
|
|
sa_upcall_getstate(&sau->sau_event,
|
|
sau->sau_event.ss_deferred.ss_lwp);
|
|
if (sau->sau_flags & SAU_FLAG_DEFERRED_INTERRUPTED)
|
|
sa_upcall_getstate(&sau->sau_interrupted,
|
|
sau->sau_interrupted.ss_deferred.ss_lwp);
|
|
|
|
stack = (void *)
|
|
(((uintptr_t)sau->sau_stack.ss_sp + sau->sau_stack.ss_size)
|
|
& ~ALIGNBYTES);
|
|
|
|
self_sa.sa_id = l->l_lid;
|
|
self_sa.sa_cpu = 0; /* XXX l->l_cpu; */
|
|
sas[0] = &self_sa;
|
|
nevents = 0;
|
|
nint = 0;
|
|
if (sau->sau_event.ss_captured.ss_sa.sa_context != NULL) {
|
|
if (copyout(&sau->sau_event.ss_captured.ss_ctx,
|
|
sau->sau_event.ss_captured.ss_sa.sa_context,
|
|
sizeof(ucontext_t)) != 0) {
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_upcall_userret(%d.%d): couldn't copyout"
|
|
" context of event LWP %d\n",
|
|
p->p_pid, l->l_lid, sau->sau_event.ss_captured.ss_sa.sa_id);
|
|
#endif
|
|
sigexit(l, SIGILL);
|
|
/* NOTREACHED */
|
|
}
|
|
sas[1] = &sau->sau_event.ss_captured.ss_sa;
|
|
nevents = 1;
|
|
}
|
|
if (sau->sau_interrupted.ss_captured.ss_sa.sa_context != NULL) {
|
|
KDASSERT(sau->sau_interrupted.ss_captured.ss_sa.sa_context !=
|
|
sau->sau_event.ss_captured.ss_sa.sa_context);
|
|
if (copyout(&sau->sau_interrupted.ss_captured.ss_ctx,
|
|
sau->sau_interrupted.ss_captured.ss_sa.sa_context,
|
|
sizeof(ucontext_t)) != 0) {
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_upcall_userret(%d.%d): couldn't copyout"
|
|
" context of interrupted LWP %d\n",
|
|
p->p_pid, l->l_lid, sau->sau_interrupted.ss_captured.ss_sa.sa_id);
|
|
#endif
|
|
sigexit(l, SIGILL);
|
|
/* NOTREACHED */
|
|
}
|
|
sas[2] = &sau->sau_interrupted.ss_captured.ss_sa;
|
|
nint = 1;
|
|
}
|
|
eventq = NULL;
|
|
if (sau->sau_type == SA_UPCALL_UNBLOCKED) {
|
|
SCHED_LOCK(s);
|
|
eventq = sa->sa_wokenq_head;
|
|
sa->sa_wokenq_head = NULL;
|
|
SCHED_UNLOCK(s);
|
|
l2 = eventq;
|
|
while (l2 != NULL) {
|
|
nevents++;
|
|
l2 = l2->l_forw;
|
|
}
|
|
}
|
|
|
|
/* Copy out the activation's ucontext */
|
|
u.uc_stack = sau->sau_stack;
|
|
u.uc_flags = _UC_STACK;
|
|
up = stack;
|
|
up--;
|
|
if (copyout(&u, up, sizeof(ucontext_t)) != 0) {
|
|
sadata_upcall_free(sau);
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_upcall_userret: couldn't copyout activation"
|
|
" ucontext for %d.%d\n", l->l_proc->p_pid, l->l_lid);
|
|
#endif
|
|
sigexit(l, SIGILL);
|
|
/* NOTREACHED */
|
|
}
|
|
sas[0]->sa_context = up;
|
|
|
|
/* Next, copy out the sa_t's and pointers to them. */
|
|
sap = (struct sa_t *) up;
|
|
sapp = (struct sa_t **) (sap - (1 + nevents + nint));
|
|
KDASSERT(nint <= 1);
|
|
for (i = nevents + nint; i >= 0; i--) {
|
|
sap--;
|
|
sapp--;
|
|
if (i == 1 + nevents) /* interrupted sa */
|
|
sasp = sas[2];
|
|
else if (i <= 1) /* self_sa and event sa */
|
|
sasp = sas[i];
|
|
else { /* extra sas */
|
|
KDASSERT(sau->sau_type == SA_UPCALL_UNBLOCKED);
|
|
KDASSERT(eventq != NULL);
|
|
l2 = eventq;
|
|
KDASSERT(l2 != NULL);
|
|
eventq = l2->l_forw;
|
|
DPRINTFN(8,("sa_upcall_userret(%d.%d) unblocking extra %d\n",
|
|
p->p_pid, l->l_lid, l2->l_lid));
|
|
sa_upcall_getstate(&e_ss, l2);
|
|
SCHED_LOCK(s);
|
|
l2->l_flag &= ~L_SA_BLOCKING;
|
|
l2->l_upcallstack = NULL;
|
|
sa_putcachelwp(p, l2); /* PHOLD from sa_setwoken */
|
|
SCHED_UNLOCK(s);
|
|
if (copyout(&e_ss.ss_captured.ss_ctx,
|
|
e_ss.ss_captured.ss_sa.sa_context,
|
|
sizeof(ucontext_t)) != 0) {
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_upcall_userret(%d.%d): couldn't copyout"
|
|
" context of event LWP %d\n",
|
|
p->p_pid, l->l_lid, e_ss.ss_captured.ss_sa.sa_id);
|
|
#endif
|
|
sigexit(l, SIGILL);
|
|
/* NOTREACHED */
|
|
}
|
|
sasp = &e_ss.ss_captured.ss_sa;
|
|
}
|
|
if ((copyout(sasp, sap, sizeof(struct sa_t)) != 0) ||
|
|
(copyout(&sap, sapp, sizeof(struct sa_t *)) != 0)) {
|
|
/* Copying onto the stack didn't work. Die. */
|
|
sadata_upcall_free(sau);
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_upcall_userret: couldn't copyout sa_t "
|
|
"%d for %d.%d\n", i, p->p_pid, l->l_lid);
|
|
#endif
|
|
sigexit(l, SIGILL);
|
|
/* NOTREACHED */
|
|
}
|
|
}
|
|
KDASSERT(eventq == NULL);
|
|
|
|
/* Copy out the arg, if any */
|
|
/* xxx assume alignment works out; everything so far has been
|
|
* a structure, so...
|
|
*/
|
|
if (sau->sau_arg) {
|
|
ap = (char *)sapp - sau->sau_argsize;
|
|
stack = ap;
|
|
if (copyout(sau->sau_arg, ap, sau->sau_argsize) != 0) {
|
|
/* Copying onto the stack didn't work. Die. */
|
|
sadata_upcall_free(sau);
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_upcall_userret(%d.%d): couldn't copyout"
|
|
" sadata_upcall arg %p size %ld to %p \n",
|
|
p->p_pid, l->l_lid,
|
|
sau->sau_arg, (long) sau->sau_argsize, ap);
|
|
#endif
|
|
sigexit(l, SIGILL);
|
|
/* NOTREACHED */
|
|
}
|
|
} else {
|
|
ap = 0;
|
|
stack = sapp;
|
|
}
|
|
|
|
type = sau->sau_type;
|
|
|
|
sadata_upcall_free(sau);
|
|
|
|
DPRINTFN(7,("sa_upcall_userret(%d.%d): type %d\n", p->p_pid,
|
|
l->l_lid, type));
|
|
|
|
cpu_upcall(l, type, nevents, nint, sapp, ap, stack, sa->sa_upcall);
|
|
|
|
if (sa->sa_wokenq_head != NULL)
|
|
l->l_flag |= L_SA_UPCALL;
|
|
else if (SIMPLEQ_EMPTY(&sa->sa_upcalls))
|
|
l->l_flag &= ~L_SA_UPCALL;
|
|
|
|
SA_LWP_STATE_UNLOCK(l, f);
|
|
KERNEL_PROC_UNLOCK(l);
|
|
}
|
|
|
|
static void
|
|
sa_setwoken(struct lwp *l)
|
|
{
|
|
struct lwp *l2, *vp_lwp;
|
|
struct proc *p;
|
|
struct sadata *sa;
|
|
int s;
|
|
|
|
SCHED_LOCK(s);
|
|
|
|
if ((l->l_flag & L_SA_BLOCKING) == 0) {
|
|
SCHED_UNLOCK(s);
|
|
return;
|
|
}
|
|
|
|
p = l->l_proc;
|
|
sa = p->p_sa;
|
|
vp_lwp = sa->sa_vp;
|
|
l2 = NULL;
|
|
|
|
KDASSERT(vp_lwp != NULL);
|
|
DPRINTFN(3,("sa_setwoken(%d.%d) woken, flags %x, vp %d\n",
|
|
l->l_proc->p_pid, l->l_lid, l->l_flag,
|
|
vp_lwp->l_lid));
|
|
|
|
if (l->l_upcallstack && l->l_upcallstack->sast_blocker == NULL) {
|
|
DPRINTFN(11,("sa_setwoken(%d.%d) recycle\n",
|
|
l->l_proc->p_pid, l->l_lid));
|
|
l->l_flag &= ~(L_SA_UPCALL|L_SA_BLOCKING);
|
|
l->l_flag |= L_SA;
|
|
l->l_upcallstack = NULL;
|
|
p->p_nrlwps--;
|
|
PHOLD(l);
|
|
sa_putcachelwp(p, l);
|
|
mi_switch(l, NULL);
|
|
/* mostly NOTREACHED */
|
|
SCHED_ASSERT_UNLOCKED();
|
|
splx(s);
|
|
KDASSERT(p->p_flag & P_WEXIT);
|
|
lwp_exit(l);
|
|
}
|
|
|
|
#if notyet
|
|
if (vp_lwp->l_flag & L_SA_IDLE) {
|
|
KDASSERT((vp_lwp->l_flag & L_SA_UPCALL) == 0);
|
|
KDASSERT(sa->sa_wokenq_head == NULL);
|
|
DPRINTFN(3,("sa_setwoken(%d.%d) repossess: idle vp_lwp %d state %d\n",
|
|
l->l_proc->p_pid, l->l_lid,
|
|
vp_lwp->l_lid, vp_lwp->l_stat));
|
|
vp_lwp->l_flag &= ~L_SA_IDLE;
|
|
SCHED_UNLOCK(s);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
DPRINTFN(3,("sa_setwoken(%d.%d) put on wokenq: vp_lwp %d state %d\n",
|
|
l->l_proc->p_pid, l->l_lid, vp_lwp->l_lid,
|
|
vp_lwp->l_stat));
|
|
|
|
PHOLD(l);
|
|
if (sa->sa_wokenq_head == NULL)
|
|
sa->sa_wokenq_head = l;
|
|
else
|
|
*sa->sa_wokenq_tailp = l;
|
|
*(sa->sa_wokenq_tailp = &l->l_forw) = NULL;
|
|
|
|
switch (vp_lwp->l_stat) {
|
|
case LSONPROC:
|
|
if (vp_lwp->l_flag & L_SA_UPCALL)
|
|
break;
|
|
vp_lwp->l_flag |= L_SA_UPCALL;
|
|
if (vp_lwp->l_flag & L_SA_YIELD)
|
|
break;
|
|
/* XXX IPI vp_lwp->l_cpu */
|
|
break;
|
|
case LSSLEEP:
|
|
if (vp_lwp->l_flag & L_SA_IDLE) {
|
|
vp_lwp->l_flag &= ~L_SA_IDLE;
|
|
vp_lwp->l_flag |= L_SA_UPCALL;
|
|
setrunnable(vp_lwp);
|
|
break;
|
|
}
|
|
vp_lwp->l_flag |= L_SA_UPCALL;
|
|
break;
|
|
case LSSUSPENDED:
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_setwoken(%d.%d) vp lwp %d LSSUSPENDED\n",
|
|
l->l_proc->p_pid, l->l_lid, vp_lwp->l_lid);
|
|
#endif
|
|
break;
|
|
case LSSTOP:
|
|
vp_lwp->l_flag |= L_SA_UPCALL;
|
|
break;
|
|
case LSRUN:
|
|
if (vp_lwp->l_flag & L_SA_UPCALL)
|
|
break;
|
|
vp_lwp->l_flag |= L_SA_UPCALL;
|
|
if (vp_lwp->l_flag & L_SA_YIELD)
|
|
break;
|
|
if (vp_lwp->l_slptime > 1) {
|
|
void updatepri(struct lwp *);
|
|
updatepri(vp_lwp);
|
|
}
|
|
vp_lwp->l_slptime = 0;
|
|
if (vp_lwp->l_flag & L_INMEM) {
|
|
if (vp_lwp->l_cpu == curcpu())
|
|
l2 = vp_lwp;
|
|
else
|
|
need_resched(vp_lwp->l_cpu);
|
|
} else
|
|
sched_wakeup(&proc0);
|
|
break;
|
|
default:
|
|
panic("sa_vp LWP not sleeping/onproc/runnable");
|
|
}
|
|
|
|
if (l->l_upcallstack) {
|
|
if (l->l_upcallstack->sast_blocker != l)
|
|
sched_wakeup(&l->l_upcallstack);
|
|
l->l_upcallstack->sast_blocker = NULL;
|
|
}
|
|
|
|
l->l_stat = LSSUSPENDED;
|
|
p->p_nrlwps--;
|
|
mi_switch(l, l2);
|
|
/* maybe NOTREACHED */
|
|
SCHED_ASSERT_UNLOCKED();
|
|
splx(s);
|
|
if (p->p_flag & P_WEXIT)
|
|
lwp_exit(l);
|
|
}
|
|
|
|
static struct lwp *
|
|
sa_vp_repossess(struct lwp *l)
|
|
{
|
|
struct lwp *l2;
|
|
struct proc *p = l->l_proc;
|
|
struct sadata *sa = p->p_sa;
|
|
|
|
SCHED_ASSERT_LOCKED();
|
|
|
|
/*
|
|
* Put ourselves on the virtual processor and note that the
|
|
* previous occupant of that position was interrupted.
|
|
*/
|
|
l2 = sa->sa_vp;
|
|
sa->sa_vp = l;
|
|
if (l2->l_flag & L_SA_YIELD)
|
|
l2->l_flag &= ~(L_SA_YIELD|L_SA_IDLE);
|
|
|
|
DPRINTFN(1,("sa_vp_repossess(%d.%d) vp lwp %d state %d\n",
|
|
p->p_pid, l->l_lid, l2->l_lid, l2->l_stat));
|
|
|
|
KDASSERT(l2 != l);
|
|
if (l2) {
|
|
switch (l2->l_stat) {
|
|
case LSRUN:
|
|
remrunqueue(l2);
|
|
p->p_nrlwps--;
|
|
break;
|
|
case LSSLEEP:
|
|
unsleep(l2);
|
|
l2->l_flag &= ~L_SINTR;
|
|
break;
|
|
case LSSUSPENDED:
|
|
#ifdef DIAGNOSTIC
|
|
printf("sa_vp_repossess(%d.%d) vp lwp %d LSSUSPENDED\n",
|
|
l->l_proc->p_pid, l->l_lid, l2->l_lid);
|
|
#endif
|
|
break;
|
|
#ifdef DIAGNOSTIC
|
|
default:
|
|
panic("SA VP %d.%d is in state %d, not running"
|
|
" or sleeping\n", p->p_pid, l2->l_lid,
|
|
l2->l_stat);
|
|
#endif
|
|
}
|
|
l2->l_stat = LSSUSPENDED;
|
|
}
|
|
return l2;
|
|
}
|
|
|
|
|
|
|
|
#ifdef DEBUG
|
|
int debug_print_sa(struct proc *);
|
|
int debug_print_lwp(struct lwp *);
|
|
int debug_print_proc(int);
|
|
|
|
int
|
|
debug_print_proc(int pid)
|
|
{
|
|
struct proc *p;
|
|
|
|
p = pfind(pid);
|
|
if (p == NULL)
|
|
printf("No process %d\n", pid);
|
|
else
|
|
debug_print_sa(p);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
debug_print_sa(struct proc *p)
|
|
{
|
|
struct lwp *l;
|
|
struct sadata *sa;
|
|
|
|
printf("Process %d (%s), state %d, address %p, flags %x\n",
|
|
p->p_pid, p->p_comm, p->p_stat, p, p->p_flag);
|
|
printf("LWPs: %d (%d running, %d zombies)\n",
|
|
p->p_nlwps, p->p_nrlwps, p->p_nzlwps);
|
|
LIST_FOREACH(l, &p->p_lwps, l_sibling)
|
|
debug_print_lwp(l);
|
|
sa = p->p_sa;
|
|
if (sa) {
|
|
if (sa->sa_vp)
|
|
printf("SA VP: %d %s\n", sa->sa_vp->l_lid,
|
|
sa->sa_vp->l_flag & L_SA_YIELD ?
|
|
(sa->sa_vp->l_flag & L_SA_IDLE ?
|
|
"idle" : "yielding") : "");
|
|
printf("SAs: %d cached LWPs\n", sa->sa_ncached);
|
|
LIST_FOREACH(l, &sa->sa_lwpcache, l_sibling)
|
|
debug_print_lwp(l);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
debug_print_lwp(struct lwp *l)
|
|
{
|
|
|
|
printf("LWP %d address %p ", l->l_lid, l);
|
|
printf("state %d flags %x ", l->l_stat, l->l_flag);
|
|
if (l->l_wchan)
|
|
printf("wait %p %s", l->l_wchan, l->l_wmesg);
|
|
printf("\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|