/*
    $Id: addtask.c 1.1 1995/12/17 21:37:10 digulla Exp digulla $
    $Log: addtask.c $
 * Revision 1.1  1995/12/17  21:37:10  digulla
 * Initial revision
 *
    Desc:
    Lang: english
*/
#include "exec_intern.h"
#include <aros/machine.h>

static void KillCurrentTask(void);
void TrapHandler(void);

/*****************************************************************************

    NAME */
	#include <clib/exec_protos.h>

	__AROS_LH3(APTR, AddTask,

/*  SYNOPSIS */
	__AROS_LA(struct Task *, task, A1),
	__AROS_LA(APTR         , initPC, A2),
	__AROS_LA(APTR         , finalPC, A3),

/*  LOCATION */
	struct ExecBase *, SysBase, 47, Exec)

/*  FUNCTION
	Add a new task to the system. If the new task has the highest
	priority of all and task switches are allowed it will be started
	immediately.
	Certain task fields should be intitialized and a stack must be
	allocated before calling this function. tc_SPReg will be used as the
	starting location for the stack pointer, i.e. a part of the stack can
	be reserved to pass the task some initial arguments.
	Memory can be added to the tc_MemEntry list and will be freed when the
	task dies. The new task's registers are set to 0.

    INPUTS
	task	  - Pointer to task structure.
	initPC	  - Entry point for the new task.
	finalPC   - Routine that is called if the initPC() function returns.
		    A NULL pointer installs the default finalizer.

    RESULT
	The address of the new task or NULL if the operation failed (can only
	happen with TF_ETASK set - currenty not implemented).

    NOTES

    EXAMPLE

    BUGS

    SEE ALSO
	RemTask()

    INTERNALS

    HISTORY
	29-10-95    digulla automatically created from
			    exec_lib.fd and clib/exec_protos.h
	17-12-95    digulla Incorporated code by Matthias Fleischner

*****************************************************************************/
{
    __AROS_FUNC_INIT
    APTR sp;

    /* Set node type to NT_TASK if not set to something else. */
    if(!task->tc_Node.ln_Type)
	task->tc_Node.ln_Type=NT_TASK;

    /* Sigh - you should provide a name for your task. */
    if(task->tc_Node.ln_Name==NULL)
	task->tc_Node.ln_Name="unknown task";

    /* Signals default to all system signals allocated. */
    if(task->tc_SigAlloc==0)
	task->tc_SigAlloc=0xffff;

    /* Currently only used for segmentation violation */
    if(task->tc_TrapCode==NULL)
	task->tc_TrapCode = (APTR)TrapHandler;

    /* Get new stackpointer. */
    sp=task->tc_SPReg;
    if(task->tc_SPReg==NULL)
#if STACK_GROWS_DOWNWARDS
	sp=(UBYTE *)task->tc_SPUpper-SP_OFFSET;
#else
	sp=(UBYTE *)task->tc_SPLower-SP_OFFSET;
#endif

    /* Default finalizer? */
    if(finalPC==NULL)
	finalPC = (APTR)KillCurrentTask;

    /* Init new context. */
    sp=PrepareContext(sp,initPC,finalPC);

    /* store sp */
    task->tc_SPReg=sp;

    /* Set the task flags for switch and launch. */
    if(task->tc_Switch)
	task->tc_Flags|=TF_SWITCH;

    if(task->tc_Launch)
	task->tc_Flags|=TF_LAUNCH;

    /* tc_MemEntry _must_ already be set. */

    /*
	Protect the task lists. This must be done with Disable() because
	of Signal() which is usable from interrupts and may change those
	lists.
     */
    Disable();

    /* Add the new task to the ready list. */
    task->tc_State=TS_READY;
    Enqueue(&SysBase->TaskReady,&task->tc_Node);

    /*
	Determine if a task switch is necessary. (If the new task has a
	higher priority than the current one and the current one
	is still active.) If the current task isn't of type TS_RUN it
	is already gone.
    */
    if(task->tc_Node.ln_Pri>SysBase->ThisTask->tc_Node.ln_Pri&&
       SysBase->ThisTask->tc_State==TS_RUN)
    {
	/* Are taskswitches allowed? (Don't count own Disable() here) */
	if(SysBase->TDNestCnt>=0||SysBase->IDNestCnt>0)
	    /* No. Store it for later. */
	    SysBase->AttnResched|=0x80;
	else
	{
	    /* Switches are allowed. Move the current task away. */
	    SysBase->ThisTask->tc_State=TS_READY;
	    Enqueue(&SysBase->TaskReady,&SysBase->ThisTask->tc_Node);

	    /* And force a rescedule. */
	    Switch();
	}
    }

    Enable();

    return task;
    __AROS_FUNC_EXIT
} /* AddTask */


/* Default finalizer. */
static void KillCurrentTask (void)
{
    /* I need the global SysBase variable here - there's no local way to get it. */
    extern struct ExecBase *SysBase;

    /* Get rid of current task. */
    RemTask(SysBase->ThisTask);
}
