/*
 *  FILE: subtask.c
 *  Support routines for spawning subtasks (not processes).
 *  This means the subtask shares the same memory space as its parent task.
 *
 *  Note: this "library" is re-entrant, so any task can spawn and keep track
 *  of its own subtasks independantly of other users of this "library".
 *
 *  Public Domain, but keep my name in it as the original author.
 *  31-Aug-88	Jan Sven Trabandt   first release version
 *  31-Oct-88	Jan Sven Trabandt   (de)initialize routines moved to subtinit.c
 *				      so that minimal routines need to be
 *				      linked in when using stdstuff.c
 *				    renamed getRidOfSubTask to killSubTask
 */


#define I_AM_SUBTASK
#include "gimmelib/gimmefuncs.h"


#define MIN_STACK   100L

#define MIN_COUNT   2
#define MAX_EXTRA   2


typedef struct {
    struct Message msg;
    ULONG flags;
    APTR  ptr;
} DONEMSG;

#define DONEFLAG    ((1L << 31) + 1857329L)


/* internal use only!!
 *
 * NAME:	a4get, a4put
 *
 * SYNOPSIS:	a4value = a4get();
 *		a4put( a4value );
 *		LONG a4value;
 *
 * DESCRIPTION: Get or put a value into register A4.	MANX specific!!!!
 *		These two functions are support routines for task creation.
 *		BECAUSE A4 GETS CLEARED IN THE SUBTASK
 *		and Manx needs it as a base pointer in small-code model.
 *		Manx's geta4() won't work if its code is too far away,
 *		so this ensures a4 is set up.
 *		I'm not sure if this is only in small code and/or small data.
 *

static LONG a4get();
static VOID a4put();

#asm
	cseg
_a4get:
	move.l	a4,d0
	rts

_a4put:
	move.l	4(a7),a4
	rts
#endasm


struct Task *gimmeSubTask( countptr, portptr, stack_size, data_size,
			    myportptr )
    SHORT	    *countptr;
    struct MsgPort  **portptr;
    LONG	    stack_size, data_size;
    struct MsgPort  **myportptr;
{
    struct Task     *task;
    DONEMSG	    *msg = NULL;
    struct MemList  *mymemlist;
    struct myneeds {
	struct MemList mn_head; 	    /* 1 MemEntry in the head */
	struct MemEntry mn_body[MIN_COUNT+MAX_EXTRA-1];
    } myneeds;
    UWORD	    count = 0;

    myneeds.mn_head.ml_Node.ln_Type = NT_MEMORY;
    myneeds.mn_head.ml_Node.ln_Pri  = 0;
    myneeds.mn_head.ml_Node.ln_Name = NULL;

    if( data_size > 0L ) {
	myneeds.mn_head.ml_me[count].me_Reqs = MEMF_PUBLIC | MEMF_CLEAR;
	myneeds.mn_head.ml_me[count].me_Length = data_size;
	++count;
    }
    if( myportptr && portptr ) {
	myneeds.mn_head.ml_me[count].me_Reqs = MEMF_PUBLIC | MEMF_CLEAR;
	myneeds.mn_head.ml_me[count].me_Length = (LONG) sizeof(DONEMSG);
	++count;
    }

    /* leave stack and struct Task as last two memory blocks */

    if( stack_size < MIN_STACK ) {
	stack_size = MIN_STACK;
    }
    myneeds.mn_head.ml_me[count].me_Reqs = MEMF_PUBLIC;
    myneeds.mn_head.ml_me[count].me_Length = stack_size;
    ++count;
    myneeds.mn_head.ml_me[count].me_Reqs = MEMF_PUBLIC | MEMF_CLEAR;
    myneeds.mn_head.ml_me[count].me_Length = (LONG) sizeof(struct Task);
    ++count;

    myneeds.mn_head.ml_NumEntries = count;
    mymemlist = (struct MemList *) AllocEntry( &myneeds );
    if( (ULONG)(mymemlist) & (1L<<31) ) {
	return( NULL );
    }

    --count;
    task = (struct Task *) mymemlist->ml_me[count].me_Addr;
    NewList( &task->tc_MemEntry );
    AddTail( &task->tc_MemEntry, mymemlist );

    --count;
    task->tc_SPLower = mymemlist->ml_me[count].me_Addr;     /* stack */
    task->tc_SPUpper = (APTR) (stack_size + (ULONG) task->tc_SPLower);
    task->tc_SPReg = task->tc_SPUpper;
    task->tc_Node.ln_Type = NT_TASK;

    if( myportptr && portptr ) {
	--count;
	msg = (DONEMSG *) mymemlist->ml_me[count].me_Addr;
	msg->msg.mn_Length = mymemlist->ml_me[count].me_Length;
	msg->msg.mn_Node.ln_Type = NT_MESSAGE;
	msg->ptr = (APTR) *portptr;     /* save subtask monitoring port */
	msg->flags = DONEFLAG;
    } /* endif */
    pushTaskStack( task, msg );

    if( data_size > 0L ) {
	--count;
	task->tc_UserData = mymemlist->ml_me[count].me_Addr;
    }
    pushTaskStack( task, task->tc_UserData );
    pushTaskStack( task, myportptr );
    pushTaskStack( task, countptr );

    return( task );
} /* gimmeSubTask */


/* internal use only!!
 *
 * pushTaskStack : push a long value onto the subtask's stack
 * task struct and stack must already be initialized,
 * task must not have been started yet.
static pushTaskStack( task, val )
    struct Task *task;
    LONG	val;
{
    --((LONG *)task->tc_SPReg);
    *((LONG *)task->tc_SPReg) = val;
} /* pushTaskStack */


/* internal use only!!
 *
 * popTaskStack : pop a long value off the subtask's stack
 * task struct and stack must already be initialized,
 * task must not have been started yet.
static LONG popTaskStack( task )
    struct Task *task;
{
    return( *((LONG *)task->tc_SPReg)++ );
} /* popTaskStack */


VOID undoGimmeSubTask( task )
    struct Task *task;
{
    if( task ) {
	FreeEntry( task->tc_MemEntry.lh_Head );
    }
} /* undoGimmeSubTask */


VOID killSubTask( countptr, portptr, task )
    SHORT	    *countptr;
    struct MsgPort  **portptr;
    struct Task     *task;
{
    struct MsgPort  *myport;

    if( portptr && (myport = *portptr) ) {
	*portptr = NULL;
	DeletePort( myport );
    }
    /*	It is permissible to do a Forbid() and RemTask(NULL) [kill myself]
     *	even though the following Permit() statement would not get executed
     *	since the OS will take care of things just fine.
    */
    Forbid();
    if( countptr ) {
	--(*countptr);
    }
    RemTask( task );
    Permit();
} /* killSubTask */


/*  internal use only!!
 *
 *  getRidOfMyself
 *  what's actually on the stack:
 *	func	a4	countptr   myport   data    msg  -> see bridgeSubTask
 *  what getRidOfMyself thinks is there:
 *	ret-addr dum	[countptr] [myport] [data]  [msg]
 *  note it doesn't actually know about data, msg and port.
 *  thus countptr is 1 long past dum, etc.
static VOID getRidOfMyself( dum )
    LONG    dum;
{
    LONG	    *parm;
    DONEMSG	    *msg;
    struct MsgPort  *mp, *myport;

    parm = &dum;
    msg = (DONEMSG *) *(parm + 4);
    if( msg && (myport = (struct MsgPort *) *(parm + 2)) ) {
	mp = (struct MsgPort *) msg->ptr;
	msg->ptr = (APTR) FindTask( NULL );
	msg->msg.mn_ReplyPort = myport;
	PutMsg( mp, msg );
	for( ;; ) {
	    WaitPort( myport );
	    while ( msg = (DONEMSG *) GetMsg(myport) ) {
		if( msg->msg.mn_Node.ln_Type != NT_REPLYMSG ) {
		    ReplyMsg( msg );
		} else if( msg->flags == DONEFLAG ) {
		    goto gromyself_done;
		}
	    } /* while */
	} /* for */
    }
gromyself_done:
    killSubTask( *(parm + 1), parm + 2, NULL );
} /* getRidOfMyself */


/*  internal use only!!
 *
 *  bridgeSubTask
 *  Used as the actual initialPC for the subtask, it gets a message port
 *  if necessary and calls the real subtask routine.
 *  Also increments the subtask counter!!!!
 *  Also changes portptr on the stack to port (ie a pointer to a port, not
 *  a pointer to a pointer) so things are kosher for getRidOfMyself.
 *  startSubTask does the set-up so that the new subtask starts executing
 *  with this routine.
static VOID bridgeSubTask( func, a4, countptr, myportptr, data, msg )
    long	    (*func)();
    LONG	    a4;
    SHORT	    *countptr;
    struct MsgPort  **myportptr;
    APTR	    data;
    DONEMSG	    *msg;
{
    a4put( a4 );
    if( countptr ) {
	Forbid();
	++(*countptr);
	Permit();
    }
    if( myportptr ) {
	*myportptr = CreatePort( NULL, 0L );
	if( !*myportptr ) {
	    return;
	}
	*((struct MsgPort **)&myportptr) = *myportptr;
    }
    func( data );
} /* bridgeSubTask */


short startSubTask( task, name, pri, initialpc, finalpc )
    struct Task *task;
    char	*name;
    BYTE	pri;
    void	(*initialpc)(), (*finalpc)();
{
    if( !task || !initialpc ) {
	return( -1 );
    }
    task->tc_Node.ln_Name = name;
    task->tc_Node.ln_Pri  = pri;
    pushTaskStack( task, a4get() );
    pushTaskStack( task, initialpc );
    if( !finalpc ) {
	(APTR) finalpc = (APTR) getRidOfMyself;
    }
    AddTask( task, bridgeSubTask, finalpc );
    return( 0 );
} /* startSubTask */
