/************************************************************************/
/* Copyright 1991 MBARI							*/
/************************************************************************/
/* $Header: /home/cvs/oasis3/src/test/RecipeCard/tick/task.c,v 1.1 2003/08/19 21:14:40 headley Exp $			    */
/* Summary  : Multitasking library for 80C196				*/
/* Filename : task.c							*/
/* Author   : Robert Herlien (rah)					*/
/* Project  : OASIS Mooring						*/
/* $Revision: 1.1 $							    */
/* Created  : 06/05/91							*/
/*									    */
/* MBARI provides this documentation and code "as is", with no warranty,    */
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium  */
/* Research Institute to assist in its use, correction, modification, or    */
/* enhancement. This information should not be published or distributed to  */
/* third parties without specific written permission from MBARI.            */
/*									    */
/************************************************************************/
/* Modification History:						*/
/* 05jun91 rah - created						*/
/* $Log: task.c,v $
/* Revision 1.1  2003/08/19 21:14:40  headley
/* no message
/*
 * Revision 4.4  2001/06/19  12:15:47  12:15:47  oasisa (Oasis users)
 * New Repository; 6/19/2001 (klh)
 * 
 * Revision 1.1  2001/06/19  11:45:01  11:45:01  oasisa (Oasis users)
 * Initial revision
 * 
 * Revision 4.1  98/05/12  09:35:07  09:35:07  bobh (Bob Herlien)
 * June '98 turnaround for EqPac
 * 
 * Revision 3.8  97/09/12  10:50:48  10:50:48  bobh (Bob Herlien)
 * Redeploy M1
 * 
 * Revision 3.4  96/06/18  15:24:12  15:24:12  bobh (Bob Herlien)
 * June '96 deployment of M1
 * 
 * Revision 3.1  95/03/09  19:30:52  19:30:52  hebo (Bob Herlien)
 * March '95 Deployment of M1A
 * 
 * Revision 3.0  95/02/21  18:42:38  18:42:38  hebo (Bob Herlien)
 * February '95 Deployment
 * 
 * Revision 2.4  93/10/29  11:12:42  11:12:42  hebo (Bob Herlien)
 * November 1993 Deployment
 * 
 * Revision 2.0  92/08/21  14:45:54  14:45:54  hebo (Bob Herlien)
 * August 1992 deployment
 * 
 * Revision 1.3  92/03/03  16:41:25  16:41:25  hebo (Bob Herlien 408-647-3748)
 * New defaults, restart check, perm power stuff, analog command
 * 
*/
/************************************************************************/
/* Note - This is a very simple, NON-PREEMPTIVE multitasking library for*/
/* the 80C196 family of microcontrollers.  Note in particular that it	*/
/* is non-preemptive.  When your task has the CPU, it keeps it until    */
/* you task_exit(), dispatch(), task_delay(), or sem_take().  The benefit*/
/* is a simpler programming model.  You don't have to worry about	*/
/* critical regions -- just don't call dispatch() until you're done with*/
/* a critical resource.  The down side is that latency and throughput   */
/* depend on the cooperation of all tasks.  The intent is that YOU, in  */
/* your application code, decide on a convention for how long it's      */
/* reasonable for a task to hold onto the CPU, and then you enforce 	*/
/* that policy in each task that you write.				*/
/*									*/
/* This was written for the OASIS mooring controller.  In OASIS, the	*/
/* convention is that each task will relinquish the CPU at least once	*/
/* per clock tick, and that the clock ticks at 100 Hz (10 ms).		*/
/*									*/
/* The Intel C96 compiler makes no distinction between registers used	*/
/* as globals and local (automatic) register variables.  Consequently,	*/
/* in order to prevent tasks from stepping on each others locals, we	*/
/* must (a) compile everyting with the REGCONSERVE keyword, and (b) not */
/* use register variables in functions.  (Actually, register variables  */
/* are usable if you're *sure* that neither the function it's in, nor	*/
/* any function it calls, will dispatch another task.  The conservative */
/* rule, though, is NO register variables except globals).		*/
/************************************************************************/

#include <types.h>			/* MBARI type definitions	    */
#include <const.h>			/* MBARI constants		    */
#include <oasis.h>			/* OASIS controller definitions	    */
#include <io.h>				/* Needed for default serial port   */
#include <task.h>			/* Task library definitions	    */
#include <80c196.h>			/* 80196 Register mapping           */
#include <string.h>			/* String library functions	    */


/********************************/
/*	External Functions	*/
/********************************/

Extern char	*tmpMalloc( Nat16 size );
Extern Void	tmpFree( char *ptr );
Extern Void	do_dispatch( TaskDesc *td );
Extern Void	bzero( void *s, int n );


/********************************/
/*	External Data		*/
/********************************/

Extern Reg FuncPtr	*SP;		/* Stack Pointer		    */
Extern FuncPtr		STACK;		/* Linker's STACK segment	    */
Extern Reg Nat16	PLMREG;		/* Stack Pointer		    */


/********************************/
/*	Global Data		*/
/********************************/

Global Reg Int16	task_tick;	/* Ticks since last dispatch()	    */
Global Reg Nat16	tasks;		/* Number of tasks that are active  */


/********************************/
/*	Module Local Data	*/
/********************************/

MLocal Reg LstHead	ready_list;	/* Ready list			    */
MLocal Reg LstHead	delay_list;	/* Delay list			    */


/************************************************************************/
/* Function    : ready							*/
/* Purpose     : Put task on ready list					*/
/* Inputs      : TaskDesc of task to make ready				*/
/* Outputs     : None							*/
/************************************************************************/
	Void
ready( Reg TaskDesc *td )
{
    td->td_state = READY;		 /* Make task ready		*/
    list_add( &ready_list, (Node *)td ); /* Put on ready list		*/

} /* ready() */


/************************************************************************/
/* Function    : task_get						*/
/* Purpose     : Return Tid of running task				*/
/* Inputs      : None							*/
/* Outputs     : Task Descriptor					*/
/************************************************************************/
	TaskDesc *
task_get( Void )
{
    return( (TaskDesc *)(ready_list.lst_head) );

} /* task_get() */


/************************************************************************/
/* Function    : unready						*/
/* Purpose     : Take running task off ready list, return its Tid	*/
/* Inputs      : None							*/
/* Outputs     : Task ID						*/
/************************************************************************/
	TaskDesc *
unready( Void )
{
    return( (TaskDesc *)list_head(&ready_list) );

} /* unready() */


/************************************************************************/
/* Function    : ready_delayed_tasks					*/
/* Purpose     : Decrement delay count of tasks on delay list, put	*/
/*		   ready ones on ready_list				*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/* Comments    : Instead of decrementing delay counts during the clock  */
/*		 tick interrupt, which would require mutual exclusion	*/
/*		 on both the delay_list and ready_list, we do the	*/
/*		 following.  The tick interrupt simply increments global*/
/*		 variable task_tick.  This function, which actually	*/
/*		 manages the delay list, is called at dispatch time from*/
/*		 function do_dispatch().  It uses task_tick to determine*/
/*		 how many ticks expired since the last time it ran, and	*/
/*		 decrements all delay counts by that amount.  It then	*/
/*		 zeroes task_tick.  This, and the fact that dispatching */
/*		 is non-preemptive, ensure mutual exclusion on the kernel*/
/*		 lists without explicit semaphores or interrupt-off regions*/
/************************************************************************/
	Void
ready_delayed_tasks( Void )
{
    Reg Node		*np, *nnp;	/* Current, next task		    */
    Reg Int16		count;		/* Ticks since dispatch()	    */

    count = task_tick;				/* Get ticks since last time*/
    task_tick -= count;				/* Zero tick counter	    */
    if ( count == 0 )				/* If no ticks, return	    */
	return;

    for( np = delay_list.lst_head; np != NULLNODE; )
    {						/* For each task on dlay lst*/
	nnp = np->lst_next;
	if ( (((TaskDesc *)np)->td_delay -= count) <= 0 )
	{					/* if delay done,	    */
	    list_get( &delay_list, np );	/* take task off delay list */
	    ready( (TaskDesc *)np );		/* and put on ready list    */
	}
	np = nnp;
    }

} /* ready_delayed_tasks() */


/************************************************************************/
/* Function    : dispatch						*/
/* Purpose     : Application-level function to dispatch a task		*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/* Comments    : Puts current task at end of ready list, dispatches	*/
/*		 task at head of ready list				*/
/************************************************************************/
	Void
dispatch( Void )
{
    Reg TaskDesc	*td;		/* TD of running task		  */
    
    ready_delayed_tasks();		/* Take ready tasks off delay list*/
    td = unready();			/* Get our Task Descriptor	*/
    ready( td );			/* Put task at end of ready list  */
    do_dispatch( td );			/* Call ass'y language dispatcher */

} /* dispatch() */


/************************************************************************/
/* Function    : task_delay						*/
/* Purpose     : Delay a number of ticks				*/
/* Inputs      : Number of ticks to delay				*/
/* Outputs     : None							*/
/* Comments    : Because delay-list management can result in decrementing*/
/*		 delays by multiple counts (see comments in function	*/
/*		 ready_delayed_tasks(), above), the delay count must be */
/*		 a signed integer.  So count is limited to a positive	*/
/*		 integer, or 32767.					*/
/************************************************************************/
	Void
task_delay( Int16 count )
{
    Reg TaskDesc	*td;
    					/* No disable(), non-preemptive	*/
    td = unready();			/* Get our TaskDesc		*/
    td->td_delay = count + task_tick;	/* Add ticks already past to dly cnt*/
    td->td_state = DELAY;		/* Set delay state		*/
    list_add( &delay_list, (Node *)td ); /* Put task on delay_list	*/
    do_dispatch( td );			/* Dispatch new task		*/

} /* task_delay() */


/************************************************************************/
/* Function    : delay_secs						*/
/* Purpose     : Delay a number of secondss				*/
/* Inputs      : Number of seconds to delay				*/
/* Outputs     : None							*/
/************************************************************************/
	Void
delay_secs( Nat16 secs )
{
    Nat16	i;

    for( i = secs; i; i-- )
	task_delay( TICKS_PER_SECOND );

} /* delay_secs() */


/************************************************************************/
/* Function    : task_exit						*/
/* Purpose     : Kill a task						*/
/* Inputs      : None							*/
/* Outputs     : None							*/
/************************************************************************/
	Void
task_exit( Void )
{
    Reg TaskDesc	*td;		/* Our own task descriptor	*/
    Reg Driver		*dp;

    td = unready();			/* Take task off ready list	*/
    dp = td->td_drvr;			/* Get driver ptr		*/
    dp->drv_td = NULLTID;		/* Show driver not running	*/
    if ( (dp->drv_flags & FLAG_PERM) == 0 )
	dp->drv_flags = 0;
    tmpFree( (char *)td );		/* Free task descriptor & stack	*/
    if ( --tasks == 0 )			/* Decrement task count		*/
	asm rst;			/* Should never go to zero	*/

    do_dispatch( NULLTASK );		/* Dispatch next task		*/

} /* task_exit() */


/************************************************************************/
/* Function    : task_alloc						*/
/* Purpose     : Allocate TaskDesc and stack for a new task		*/
/* Inputs      : Stack size						*/
/* Outputs     : Task Descriptor					*/
/************************************************************************/
	TaskDesc *
task_alloc( Nat16 stksize )
{
    Reg TaskDesc	*td;
    Reg Nat16		size;

    size = sizeof(TaskDesc) + stksize;
    if ( (td = (TaskDesc *)tmpMalloc(size)) == NULLTASK )
	return( NULLTASK );		/* Alloc TaskDesc, fail if no space*/

    bzero((void *)td, sizeof(TaskDesc)); /* Clear task descriptor	*/
    if ( stksize > 0 )			/* Fill stack with RST instrucs */
	memset( (void *)(td + 1), 0xff, stksize );
    td->td_sem = NULLSEM;		/* Init sem ptr			*/
    td->td_state = READY;		/* Init state			*/
    td->td_stack = (FuncPtr *)((Byte *)td + size);
    return( td );
    
} /* task_alloc() */


/************************************************************************/
/* Function    : task_create						*/
/* Purpose     : Create a task						*/
/* Inputs      : Driver pointer						*/
/* Outputs     : None							*/
/************************************************************************/
	TaskDesc *
task_create( Reg Driver *dp )
{
    Reg TaskDesc	*td;
    Reg FuncPtr		*taskSP;

    if ( (td = task_alloc(STKSIZE)) == NULLTASK )
	return( NULLTASK );		/* Alloc TaskDesc, fail if no space*/

    td->td_name = dp->drv_name;		/* Insert name pointer		*/
					/* Set up serial parameters	*/
    td->td_drvr = dp;			/* Save driver ptr		*/

					/* Now run the task		*/
    taskSP = td->td_stack;		/* Initialize task stack pointer*/
    *--taskSP = (FuncPtr)dp;		/* Push driver ptr as calling arg*/
    *--taskSP = task_exit;		/* Push a return to exit func	*/
    *--taskSP = dp->drv_task;		/* Push program entry ptr	*/
    *--taskSP = (FuncPtr)dp->drv_parms[SER_PORT];
    *--taskSP = (FuncPtr)dp->drv_parms[SER_SETUP];
    *--taskSP = (FuncPtr)SP;		/* Dummy push SP replaces ?FRAME01*/
    td->td_sp = taskSP;			/* Store task stack pointer	*/
    ready( td );			/* Put task on ready list	*/
    tasks++;				/* Increment task count		*/
    return( td );			/* Return td			*/

} /* task_create() */


/************************************************************************/
/* Function    : task_init						*/
/* Purpose     : Initialize Multitasking library			*/
/* Inputs      : Pointer to valid TaskDesc for calling task		*/
/* Outputs     : None							*/
/* Comments    : MUST be first call to multitasking library		*/
/*		 Makes the calling routine into the initial task, using	*/
/*		 the stack allocated by linker as calling task's stack	*/
/************************************************************************/
	TaskDesc *
task_init( Void )
{
    Reg TaskDesc	*td;

    tasks = 1;
    task_tick = 0;
    list_init( &ready_list );
    list_init( &delay_list );
    if ( (td = task_alloc(0)) != NULLTASK )
    {
	td->td_name = "Init";
	td->td_stack = &STACK;
	ready( td );
    }
    return( td );

} /* task_init() */
