/*
 * This file is part of the UCB release of Plan 9. It is subject to the license
 * terms in the LICENSE file found in the top-level directory of this
 * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
 * part of the UCB release of Plan 9, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms contained
 * in the LICENSE file.
 */

/* 
 * Some notes on locking:
 *
 *	All the locking woes come from implementing
 *	threadinterrupt (and threadkill).
 *
 *	_threadgetproc()->thread is always a live pointer.
 *	p->threads, p->ready, and _threadrgrp also contain
 * 	live thread pointers.  These may only be consulted
 *	while holding p->lock or _threadrgrp.lock; in procs
 *	other than p, the pointers are only guaranteed to be live
 *	while the lock is still being held.
 *
 *	Thread structures can only be freed by the proc
 *	they belong to.  Threads marked with t->inrendez
 * 	need to be extracted from the _threadrgrp before
 *	being freed.
 *
 *	_threadrgrp.lock cannot be acquired while holding p->lock.
 */

typedef struct Pqueue	Pqueue;
typedef struct Rgrp		Rgrp;
typedef struct Tqueue	Tqueue;
typedef struct Thread	Thread;
typedef struct Execargs	Execargs;
typedef struct Proc		Proc;

/* must match list in sched.c */
typedef enum
{
	Dead,
	Running,
	Ready,
	Rendezvous,
} State;
	
typedef enum
{
	Channone,
	Chanalt,
	Chansend,
	Chanrecv,
} Chanstate;

enum
{
	RENDHASH = 13,
	Printsize = 2048,
	NPRIV = 8,
};

struct Rgrp
{
	Lock		lock;
	Thread	*hash[RENDHASH];
};

struct Tqueue		/* Thread queue */
{
	int		asleep;
	Thread	*head;
	Thread	**tail;
};

struct Thread
{
	Lock		lock;		/* protects thread data structure */
	jmp_buf		sched;		/* for context switches */
	int		id;		/* thread id */
	int 		grp;		/* thread group */
	int		moribund;	/* thread needs to die */
	State		state;		/* run state */
	State		nextstate;	/* next run state */
	unsigned char		*stk;		/* top of stack (lowest address of stack) */
	uint32_t		stksize;	/* stack size */
	Thread		*next;		/* next on ready queue */

	Proc		*proc;		/* proc of this thread */
	Thread		*nextt;		/* next on list of threads in this proc*/
	int		ret;		/* return value for Exec, Fork */

	char		*cmdname;	/* ptr to name of thread */

	int		inrendez;
	Thread		*rendhash;	/* Trgrp linked list */
	void*		rendtag;	/* rendezvous tag */
	void*		rendval;	/* rendezvous value */
	int		rendbreak;	/* rendezvous has been taken */

	Chanstate	chan;		/* which channel operation is current */
	Alt		*alt;		/* pointer to current alt structure (debugging) */

	void*	udata[NPRIV];	/* User per-thread data pointer */
};

struct Execargs
{
	char		*prog;
	char		**args;
	int		fd[2];
};

struct Proc
{
	Lock		lock;
	jmp_buf		sched;			/* for context switches */
	int		pid;			/* process id */
	int		splhi;			/* delay notes */
	Thread		*thread;		/* running thread */

	int		needexec;
	Execargs	exec;			/* exec argument */
	Proc		*newproc;		/* fork argument */
	char		exitstr[ERRMAX];	/* exit status */

	int		rforkflag;
	int		nthreads;
	Tqueue		threads;		/* All threads of this proc */
	Tqueue		ready;			/* Runnable threads */
	Lock		readylock;

	char		printbuf[Printsize];
	int		blocked;		/* In a rendezvous */
	int		pending;		/* delayed note pending */
	int		nonotes;		/*  delay notes */
	uint32_t		nextID;			/* ID of most recently created thread */
	Proc		*next;			/* linked list of Procs */

	void		*arg;			/* passed between shared and unshared stk */
	char		str[ERRMAX];		/* used by threadexits to avoid malloc */

	void*		wdata;			/* Lib(worker) per-proc data pointer */
	void*		udata;			/* User per-proc data pointer */
	char		threadint;		/* tag for threadexitsall() */
};

struct Pqueue {		/* Proc queue */
	Lock		lock;
	Proc		*head;
	Proc		**tail;
};

struct Ioproc
{
	int tid;
	Channel *c, *creply;
	int inuse;
	int32_t (*op)(va_list*);
	va_list arg;
	long ret;
	char err[ERRMAX];
	Ioproc *next;
};

void	_freeproc(Proc*);
void	_freethread(Thread*);
Proc*	_newproc(void(*)(void*), void*, uint32_t, char*, int, int);
int	_procsplhi(void);
void	_procsplx(int);
void	_sched(void);
int	_schedexec(Execargs*);
void	_schedexecwait(void);
void	_schedexit(Proc*);
int	_schedfork(Proc*);
void	_schedinit(void*);
void	_systhreadinit(void);
void	_threadassert(char*);
void	_threadbreakrendez(void);
void	_threaddebug(uint32_t, char*, ...);
void	_threadexitsall(char*);
void	_threadflagrendez(Thread*);
Proc*	_threadgetproc(void);
void	_threadsetproc(Proc*);
void	_threadinitstack(Thread*, void(*)(void*), void*);
void*	_threadmalloc(int32_t, int);
void	_threadnote(void*, char*);
void	_threadready(Thread*);
void*	_threadrendezvous(void*, void*);
void	_threadsignal(void);
void	_threadsysfatal(char*, ...);
void**	_workerdata(void);
void	_xinc(int32_t*);
long	_xdec(int32_t*);

extern int			_threaddebuglevel;
extern char*		_threadexitsallstatus;
extern Pqueue		_threadpq;
extern Channel*	_threadwaitchan;
extern Rgrp		_threadrgrp;

#define DBGAPPL	(1 << 0)
#define DBGSCHED	(1 << 16)
#define DBGCHAN	(1 << 17)
#define DBGREND	(1 << 18)
/* #define DBGKILL	(1 << 19) */
#define DBGNOTE	(1 << 20)
#define DBGEXEC	(1 << 21)

#define ioproc_arg(io, type)	(va_arg((io)->arg, type))
