#include	"u.h"
#include	"../port/lib.h"
#include	"mem.h"
#include	"dat.h"
#include	"fns.h"
#include	"../port/error.h"

static int semtrytimes = 100;


/*
 * Support for user-level optimistic semaphores.
 *
 * A semaphore is an integer n.
 * If n > 0, there are tickets
 * If n == 0, there are no tickets
 * If n < 0, there are |n| processes waiting for tickets
 * or preparing to wait for tickets.
 *
 * CAUTION: Do not use adec/ainc for semaphores, they would
 * trap if the integer is negative, but that's ok for semaphores.
 */


static void
semwakeup(Sem *s, int didwake, int dolock)
{
	Proc *p;

	DBG("semwakeup up %#p sem %#p\n", up, s->np);
	if(dolock)
		lock(s);
	/*
	 * If there are more processes sleeping than |*s->np| then
	 * there are ups not yet seen by sleepers, wake up those that
	 * have tickets.
	 */
	while(s->nq > 0 && s->nq > - *s->np){
		p = s->q[0];
		s->nq--;
		s->q[0] = s->q[s->nq];
		if(didwake){
			DBG("semwakeup up %#p waking up %#p\n", up, p);
			p->waitsem = s;
			/*
			 * p can be up if it's being killed, in which
			 * case it might be consuming a ticket being
			 * put while dying. In that case, another
			 * process might wait because we would get the
			 * ticket. Up must call semwakeup to be sure
			 * that other process no longer sleeps.
			 * But we can't be put in the scheduler queue.
			 */
			if(p != up)
				ready(p);
		}
	}
	if(dolock)
		unlock(s);
}

static void
semsleep(Sem *s, int dontblock)
{
	DBG("semsleep up %#p sem %#p\n", up, s->np);
	if(dontblock){
		/*
		 * User tried to down non-blocking, but someone else
		 * got the ticket between looking at n and adec(n).
		 * we have to safely undo our temporary down here.
		 * Adjust the value of the semaphore to reflect that we
		 * wanted a ticket for a while but no longer want one.
		 * Make sure that no other process is waiting because we
		 * made a temporary down.
		 */
		semainc(s->np);
		semwakeup(s, 1, 1);
		return;
	}
	lock(s);
	if(*s->np >= 0){
		/*
		 * A ticket came, either it came while calling the kernel,
		 * or it was a temporary sleep that didn't block.
		 * Either way, we are done.
		 */
		unlock(s);
		goto Done;
	}
	/*
	 * Commited to wait, we'll have to wait until
	 * some other process changes our state.
	 */
	s->q = realloc(s->q, (s->nq+1) * sizeof s->q[0]);
	if(s->q == nil)
		panic("semsleep: no memory");
	s->q[s->nq++] = up;
	up->waitsem = nil;
	up->state = Semdown;
	unlock(s);
	DBG("semsleep up %#p blocked\n", up);
	sched();
Done:
	DBG("semsleep up %#p awaken\n", up);
	if(up->waitsem == nil){
		/*
		 * nobody did awake us, we are probably being
		 * killed; we no longer want a ticket.
		 */
		lock(s);
		semainc(s->np);	/* we are no longer waiting; killed */
		semwakeup(s, 1, 0);
		unlock(s);
	}
}

void
syssemsleep(Ar0*, va_list list)
{
	int *np;
	int dontblock;
	Sem *s;
	Segment *sg;

	/*
	 * void semsleep(int*);
	 */
	np = va_arg(list, int*);
	np = validaddr(np, sizeof *np, 1);
	evenaddr(PTR2UINT(np));
	dontblock = va_arg(list, int);
	if((sg = seg(up, PTR2UINT(np), 0)) == nil)
		error(Ebadarg);
	s = segmksem(sg, np);
	semsleep(s, dontblock);
}

void
syssemwakeup(Ar0*, va_list list)
{
	int *np;
	Sem *s;
	Segment *sg;

	/*
	 * void semwakeup(int*);
	 */
	np = va_arg(list, int*);
	np = validaddr(np, sizeof *np, 1);
	evenaddr(PTR2UINT(np));
	if((sg = seg(up, PTR2UINT(np), 0)) == nil)
		error(Ebadarg);
	s = segmksem(sg, np);
	semwakeup(s, 1, 1);
}

static void
semdequeue(Sem *s)
{
	int i;

	assert(s != nil);
	lock(s);
	for(i = 0; i < s->nq; i++)
		if(s->q[i] == up)
			break;

	if(i == s->nq){
		/*
		 * We didn't perform a down on s, yet we are no longer queued
		 * on it; it must be because someone gave us its
		 * ticket in the mean while. We must put it back.
		 */
		semainc(s->np);
		semwakeup(s, 0, 0);
	}else{
		s->nq--;
		s->q[i] = s->q[s->nq];
	}
	unlock(s);
}

/*
 * Alternative down of a Sem in ss[].
 * The logic is similar to multiple downs, see comments in semsleep().
 */
static int
semalt(Sem *ss[], int n)
{
	int i, j, r;
	Sem *s;

	DBG("semalt up %#p ss[0] %#p\n", up, ss[0]->np);
	r = -1;
	for(i = 0; i < n; i++){
		s = ss[i];
		n = semadec(s->np);
		if(n >= 0){
			r = i;
			goto Done;
		}
		lock(s);
		s->q = realloc(s->q, (s->nq+1) * sizeof s->q[0]);
		if(s->q == nil)
			panic("semalt: not enough memory");
		s->q[s->nq++] = up;
		unlock(s);
	}

	DBG("semalt up %#p blocked\n", up);
	up->state = Semdown;
	sched();

Done:
	DBG("semalt up %#p awaken\n", up);
	for(j = 0; j < i; j++){
		assert(ss[j] != nil);
		if(ss[j] != up->waitsem)
			semdequeue(ss[j]);
		else
			r = j;
	}
	if(r < 0)
		panic("semalt");
	return r;
}

void
syssemalt(Ar0 *ar0, va_list list)
{
	int **sl;
	int i, *np, ns;
	Segment *sg;
	Sem *ksl[16];

	/*
	 * void semalt(int*[], int);
	 */
	ar0->i = -1;
	sl = va_arg(list, int**);
	ns = va_arg(list, int);
	sl = validaddr(sl, ns * sizeof(int*), 1);
	if(ns > nelem(ksl))
		panic("syssemalt: bug: too many semaphores in alt");
	for(i = 0; i < ns; i++){
		np = sl[i];
		np = validaddr(np, sizeof(int), 1);
		evenaddr(PTR2UINT(np));
		if((sg = seg(up, PTR2UINT(np), 0)) == nil)
			error(Ebadarg);
		ksl[i] = segmksem(sg, np);
	}	
	ar0->i = semalt(ksl, ns);
}

/*
 * These are the entry points from the C library, adapted
 * for use within the kernel, so that kprocs may share sems
 * with users.
 * They must be run in a process context.
 * Within the kernel, semaphores are used through their
 * kernel Sem structures, and not directly by their int* value.
 * Otherwise, we would have to look up each time they are used.
 */

void
upsem(Sem *s)
{
	int n;

	n = semainc(s->np);
	if(n <= 0)
		semwakeup(s, 1, 1);
}

int
downsem(Sem *s, int dontblock)
{
	int n, i;

	for(i = 0; *s->np <= 0 && i < semtrytimes; i++)
		yield();
	if(*s->np <= 0 && dontblock)
		return -1;
	n = semadec(s->np);
	if(n < 0)
		semsleep(s, dontblock);
	return 0;
}

int
altsems(Sem *ss[], int n)
{
	int i, w;

	/* busy wait */
	for(w = 0; w < semtrytimes; w++){
		for(i = 0; i < n; i++)
			if(*ss[i]->np > 0)
				break;
		if(i < n)
			break;
	}
	for(i = 0; i < n; i++)
		if(downsem(ss[i], 1) != -1)
			return i;
	return semalt(ss, n);
}

