/* Copyright 1984 by the Massachusetts Institute of Technology */


/* MOS handler/tasking package. This package implements a simple
 * tasking mechanism with non-preemptable prioritized tasks. To
 * make priorities work in a non-preemptable environment, all
 * the subroutines should be kept fairly short. They must return
 * when complete; there is no provision for a handler or task
 * to suspend and retain stack.
 *
 * Handlers are high (all equal, at the moment) priority short
 * routines assocated with MOS signals; as long as any signals
 * remain on the process' queue they are handled. Tasks are
 * lower (settable, different) priority routines that run only
 * when no signals are waiting to be handled.
 * Handlers are activated by MOS signals coming in; a routine
 * associated with each signal is called with the data in the
 * signal as data. Tasks are added to the waiting queue by a
 * subroutine call; you specify up to one word of data and
 * a priority.
 *
 * You call init_tsk to set things up and then run_tsk to turn
 * tasking on. There are three entry points to deal with handlers,
 * based on the fact there are two different classes of handlers;
 * those that never change the routine associated with a signal and
 * those that change; multiple instances of the former are collapsed
 * onto the same signal. There is one entry point to deal with
 * tasks; it adds the task to the queue.
 *
 * The feature with the flagp argument in run_tsk is to allow another
 * process to be declared higher priority; i.e. if that process needs
 * to run while the current process has unrun tasks, the current process
 * will stop running tasks and call wait() to give up the processor (after
 * possibly queueing a message to ensure that it will be awoken when the
 * other process finishes. This occurs if the cell pointed to by flagp
 * is non-zero; the typical way to use this is to pass in the address of
 * the head of the other process' message queue in the PCT as flagp.
 *
 * Note that if the current process has the multiple-message capability set,
 * this strategy will not work; a possible fix would involve always queuing
 * a wake up signal and clearing the capability; the signal handler would
 * then reset the capability bit as soon as it ran.
 *
 *
 * WARNING: since this package needs some per process static to store
 * the database in, and even if the storage is alloc'd you still need
 * an environment pointer, calling init_tsk gives back the pointer to
 * the user to store and deal with. If you have some code running in more
 * than one process, you need a place to store that. You could use one of
 * user locations in the PCT, which is the only place where per process
 * storage (other than the registers and stack) exists.
 *
 * NOTE: before code that uses this package will compile correctly,
 * you have to include the aux/mosu.h header file to get the HEP
 * structure declaration.
 */
/* Modified 16 Jan 83 by dab to reflect change from MOS to just a tasking
 * operating system. Eventually I should get around to changing all the
 * comments and then this comment will go away.
 */

#include	<types.h>
#include	<sys.h>


/* Useful constants */
#define	NULL	0

/* Statistics */
int num_tsks;			/* Number of tasks on the task queue */
int max_tsks;			/* Maximum number of tasks ever queued */
int av1_tsks;			/* Average number of tasks queued */
int av5_tsks;			/* Average number of tasks queued */
int av15_tsks;			/* Average number of tasks queued */

/* Initialization of handler system; allocation of handler environment
 * and clearing and also storage of HE pointer.
 */

hndlre	*init_tsk(maxtsk)
unsb	maxtsk;

{	reg	hndlre	*hep;
	reg	unsb	i;
	reg	tskblk	*tbp;

	if ((hep = (hndlre *)mem_alloc(sizeof(hndlre))) == NULL)
		bughalt("Can't get mem for handler block");

	if ((tbp = (tskblk *)mem_alloc(sizeof(tskblk) * maxtsk)) == NULL)
	bughalt("Can't get mem for task blocks");
	hep->h_twait = NULL;
	hep->h_tfree = NULL;
	for (i = 0; i < maxtsk; i++) {
		tbp->tsk_link = hep->h_tfree;
		hep->h_tfree = tbp;
		tbp++;
	    }
	num_tsks = max_tsks = 0;
	av1_tsks = av5_tsks = av15_tsks = 0;

	return(hep);
}


/* Run the handlers and tasks; just sits in a loop waiting for signals;
 * if there are signals, it runs them; if there are no signals it runs
 * one task and looks again; if there are no tasks, it goes to sleep,
 * waiting for a signal.
 */

run_tsk(hep)
reg	hndlre	*hep;
{
	reg	tskblk	*tbp;
	xreg	unsw	imask;
	reg	void	(*tsk)();
	reg	unsw	data;

	for (;;) {
		if (hep->h_twait != NULL) {
			imask = disable();
			tbp = hep->h_twait;
			hep->h_twait = tbp->tsk_link;
			tsk = tbp->tsk_hndlr;
			data = tbp->tsk_data;
			tbp->tsk_link = hep->h_tfree;
			hep->h_tfree = tbp;
			enable(imask);
/*			printf("tsk: %x, data: %x\n", tsk, data); */
			(*tsk)(data);
			num_tsks--;
/*			printf("\ttsk: done\n"); */
		}
	}
}

/* Routine to add a task to the waiting queue */

addtsk(hep, pri, hand, data)
reg	hndlre	*hep;
unsb	pri;
void	(* hand)();
word	data;

{	reg	tskblk	*tbp, **ftbp;
	reg	int	imask;

	imask = disable();
	if ((tbp = hep->h_tfree) == NULL)
		bughalt("No free task blocks");
	hep->h_tfree = tbp->tsk_link;

	tbp->tsk_pri = pri;
	tbp->tsk_hndlr = hand;
	tbp->tsk_data = data;

	ftbp = &hep->h_twait;
	while ((*ftbp != NULL) && ((*ftbp)->tsk_pri <= tbp->tsk_pri))
		ftbp = &(*ftbp)->tsk_link;
	tbp->tsk_link = *ftbp;
	*ftbp = tbp;
	if (++num_tsks > max_tsks)
	  max_tsks = num_tsks;
	enable(imask);
}
