/************************************************************************
thread.c        -       Source file for memory independant multi
			threading under DOS

Programmer      -       E.J.Pugh, FIAP

Copyright       -       (C) Edward James Pugh, June 1991
*************************************************************************/
#include <malloc.h>
#include <stdlib.h>
#include <dos.h>
#include "llist.h"
#include "screen.h"
#include "thread.h"

extern Window_t  *ACTWINDOW; /* in screen.c */

#define _AX     0
#define _BX     1
#define _CX     2
#define _DX     3
#define _ES     4
#define _SP     5
#define _BP     6
#define _SI     7
#define _DI     8
#define _IP     9
#if defined (__MEDIUM) /* medium model */
#define _PS     10
#define _DS     0
#elif defined (__COMPACT) /* Compact model */
#define _PS     0
#define _DS     10
#elif defined (__SMALL) /* Small Model */
#define _PS     0
#define _DS     0
#elif defined (__TINY)  /* Tiny model */
#define _PS     0
#define _DS     0
#else                   /* Large or Huge model */
#define _PS     10
#define _DS     11
#endif

typedef struct
	{
	Register_t Regs;
	char _near *Stack;
	}Process_t;

static llstack_t THREAD_STACK = { 0, NULL }; /* for storing Thread_t */

#define CUR_PROC ((Thread_t *)(THREAD_STACK.top->item))->CurProc
#define THREAD_TYPE ((Thread_t *)(THREAD_STACK.top->item))->Type
#define PROC_LIST ((Thread_t *)(THREAD_STACK.top->item))->PList
#define FIRST_PROC ((Thread_t *)(THREAD_STACK.top->item))->PList->first
#define LAST_PROC ((Thread_t *)(THREAD_STACK.top->item))->PList->last

#define NEWSTACK(s) (char _near *)_nmalloc(s)
#define FREESTACK(s) _nfree(s)

#pragma optimize("", off)

/* function prototypes */
static void _pascal EndProcess(void);
static llnode_t *InList(llnode_t *thread);
void _pascal Suspend(Register_t *registers); /* Register.asm */
void _pascal Resume(Register_t *target); /* Register.asm */

/*************************************************************************
NewThread       -       New Thread

Description     -       Creates a new Thread_t structure and assigns
			initial field values.

Parameters      -       NewThread(type)
			Thread_s type; NEXT, PREV or WINDOW

Returns         -       A pointer to the Thread_t structure created or
			NULL if insufficient memory
**************************************************************************/
Thread_t *NewThread(Thread_s type)
{
Thread_t *thread;

if(thread = (Thread_t *)malloc(sizeof(Thread_t)) )
	{
	if(thread->PList = NewList())
		{
		thread->CurProc = NULL;
		thread->Type = type;
		}
	else
		{
		free(thread);
		return(NULL);
		}
	}
return(thread);
}

/**************************************************************************
NewProcess      -       New Process

Description     -       Creates a new Process_t structure along with the
			requested stack space to be allocated.
			Assigns initial values to the ThreadReg structure
			by a call to Suspend() and then alters SS and SP
			elements to point to the stack created. Replaces
			PS and IP values with the starting location
			of the supplied process, places the address of
			EndProcess at the top of the new stack
			and adjusts SP field accordingly.

			Finally adds the NewProcess to the end of the
			given thread's process list

			If thread is NULL then the process is added
			to the thread at the top of the THREAD_STACK.

Parameters      -       NewProcess(thread, process, stacksize)
			Thread_t *thread; for adding to
			void (*process)(void);
			size_t stacksize;

Returns         -       A pointer to the node in the thread's PList if
			successful, else NULL.
**************************************************************************/
llnode_t *NewProcess(Thread_t *thread, void (*process)(void),
	size_t stacksize)
{
unsigned int bpReg;
llist_t *target;
Process_t *routine;
_asm
	{
	mov     ax, [bp]
	mov     bpReg, ax
	}
if(routine = (Process_t *)malloc(sizeof(Process_t)))
	{ // assign near stack
	if(routine->Stack = NEWSTACK(stacksize))
		{ // assign values
		#if _PS
		/* large code model */
		void _far *codeptr;
		#endif

		Suspend(&routine->Regs);

		routine->Regs[_SP] = (unsigned)routine->Stack + stacksize;
		routine->Regs[_SP] -= 2;
		routine->Regs[_BP] = bpReg;
		#if _PS
		/* far code model */
		/* save the required target process address */
		codeptr = (void _far *)process;
		routine->Regs[_PS] = FP_SEG(codeptr);
		routine->Regs[_IP] = FP_OFF(codeptr);
		/* now place the address of EndProcess on the private stack */
		codeptr = (void _far *)EndProcess;
		* (unsigned *)(routine->Stack + stacksize - 2) =
			FP_SEG(codeptr);
		routine->Regs[_SP] -= 2;
		* (unsigned *)(routine->Stack + stacksize - 4) =
			FP_OFF(codeptr);
		#else
		/* near code model */
		routine->Regs[_IP] = (unsigned) process;
		* (unsigned *)(routine->Stack + stacksize - 2) =
			(unsigned)EndProcess;
		#endif
		if(thread)
			{
			/* append to given target thread */
			if(target = AppendItem(
			thread->PList, (LLDATA *)routine) )
				return(target->last);
			}
		else if(THREAD_STACK.top) /* have a list to add to */
			{
			if(target = AppendItem(PROC_LIST, (LLDATA *)routine))
				return(target->last);
			}
		FREESTACK(routine->Stack);
		free(routine);
		return(NULL);
		} // assign values
	else
		free(routine);
	} // assign stack
return(NULL);
}

/**************************************************************************
LaunchThread    -       Launch Thread

Description     -       Launches the given thread by pushing it onto the
			THREAD_STACK, suspending the Regs, plugging the
			current return address into the structure and
			resuming the first process required by the Type
			field.

Parameters      -       LaunchThread(thread)
			Thread_t *thread

Returns         -       The return value of the terminating process
			as an int in the AX register. -1 indicates
			an error and should therefore be reserved by
			user processes.
**************************************************************************/
void _pascal LaunchThread(Thread_t *thread)
{
unsigned int bpValue, bpReg, RetOff, axReg, bxReg, cxReg, dxReg, esReg,
siReg, diReg;
#if _PS
unsigned int RetSeg;
#endif
#if _DS
unsigned int dsReg;
#endif

/* grab register values before the compiler goes to work */
_asm
	{
	mov     axReg, ax
	mov     ax, [bp]
	mov     bpValue, bp
	mov     bpReg, ax
#if _PS
	mov     ax, [bp+4]
	mov     RetSeg, ax
#endif
	mov     ax, [bp+2]
	mov     RetOff, ax
#if _DS
	mov     ax, ds
	mov     dsReg, ax
#endif
	mov     bxReg, bx
	mov     cxReg, cx
	mov     dxReg, dx
	mov     siReg, si
	mov     diReg, di
	mov     ax, es
	mov     esReg, ax
	}

/* plug in the values */
thread->Regs[_IP] = RetOff;
thread->Regs[_BP] = bpReg;
thread->Regs[_AX] = axReg;
thread->Regs[_BX] = bxReg;
thread->Regs[_CX] = cxReg;
thread->Regs[_DX] = dxReg;
thread->Regs[_SI] = siReg;
thread->Regs[_DI] = diReg;
thread->Regs[_ES] = esReg;
thread->Regs[_SP] = bpValue;
/* adjust for compiler push of bp, si and di registers, and calling
routines push of argument and return address */
thread->Regs[_SP] += 6;
#if _DS
thread->Regs[_SP] += 2;
thread->Regs[_DS] = dsReg;
#endif
#if _PS
thread->Regs[_PS] = RetSeg;
thread->Regs[_SP] += 2;
#endif

/* and push it to the thread stack */
if( !PushStack(&THREAD_STACK, (LLDATA *)thread) )
	return;

/* now determine which process should be resumed */
switch( THREAD_TYPE )
	{
	case NEXT:
	case WINDOW:
	CUR_PROC = FIRST_PROC;
	break;
	case PREV:
	CUR_PROC = LAST_PROC;
	break;
	}
Resume(&((Process_t *)(CUR_PROC->item))->Regs);
}

/**************************************************************************
Yield           -       Yield

Description     -       Suspends the current process and passes control
			to the given thread or (if parameter is NULL) to
			the next process in the present thread's
			PList for given thread type.

Parameters      -       Yield(target)
			llnode_t *target;

Returns         -       NOTHING
**************************************************************************/
void _pascal Yield(llnode_t *target)
{
unsigned int bpValue, bpReg, RetOff, axReg, bxReg, cxReg, dxReg, esReg,
siReg, diReg;
#if _PS
unsigned int RetSeg;
#endif
#if _DS
unsigned int dsReg;
#endif

/* grab register values before the compiler goes to work */
_asm
	{
	mov     axReg, ax
	mov     ax, [bp]
	mov     bpValue, bp
	mov     bpReg, ax
#if _PS
	mov     ax, [bp+4]
	mov     RetSeg, ax
#endif
	mov     ax, [bp+2]
	mov     RetOff, ax
#if _DS
	mov     ax, ds
	mov     dsReg, ax
#endif
	mov     bxReg, bx
	mov     cxReg, cx
	mov     dxReg, dx
	mov     siReg, si
	mov     diReg, di
	mov     ax, es
	mov     esReg, ax
	}

if(THREAD_STACK.top == NULL)
	return; /* no thread so nothing to do */
if( PROC_LIST->length == 1 )
	return; /* no other process to choose from */
if(target)
	/* check to see if it is valid before we go to the trouble of
	suspending the current process */
	if(!InList(target))
		return;

/* ok, so now plug in the values */
((Process_t *)(CUR_PROC->item))->Regs[_IP] = RetOff;
((Process_t *)(CUR_PROC->item))->Regs[_BP] = bpReg;
((Process_t *)(CUR_PROC->item))->Regs[_AX] = axReg;
((Process_t *)(CUR_PROC->item))->Regs[_BX] = bxReg;
((Process_t *)(CUR_PROC->item))->Regs[_CX] = cxReg;
((Process_t *)(CUR_PROC->item))->Regs[_DX] = dxReg;
((Process_t *)(CUR_PROC->item))->Regs[_SI] = siReg;
((Process_t *)(CUR_PROC->item))->Regs[_DI] = diReg;
((Process_t *)(CUR_PROC->item))->Regs[_ES] = esReg;
((Process_t *)(CUR_PROC->item))->Regs[_SP] = bpValue;

/* adjust for compiler push of bp, si and di registers, and calling
routine's push of argument and return address */
((Process_t *)(CUR_PROC->item))->Regs[_SP] += 6;
#if _DS
((Process_t *)(CUR_PROC->item))->Regs[_SP] += 2;
((Process_t *)(CUR_PROC->item))->Regs[_DS] = dsReg;
#endif
#if _PS
((Process_t *)(CUR_PROC->item))->Regs[_PS] = RetSeg;
((Process_t *)(CUR_PROC->item))->Regs[_SP] += 2;
#endif
/* now determine which process should be resumed */
if(target)
	CUR_PROC = target;
else
	{
	/* choose a process given the thread's type */
	switch( THREAD_TYPE )
		{
		case NEXT:
		CUR_PROC = CUR_PROC->next ? CUR_PROC->next : FIRST_PROC;
		break;
		case WINDOW:
			{
			Window_t *window;

			if(window = BottomWindow())
				CUR_PROC = window->Process;
			else
				CUR_PROC = NULL;
			}
		break;
		case PREV:
		CUR_PROC = CUR_PROC->prev ? CUR_PROC->prev : LAST_PROC;
		break;
		default: return; /* unknown type */
		break;
		}
	}
Resume(&((Process_t *)(CUR_PROC->item))->Regs);
}

/***************************************************************************
EndProcess      -       End Process

Description     -       Any executing thread process returns to the start
			of this routine when they terminate.

			EndProcess removes the current process from the
			current thread's process list and determines the
			next process to be resumed - unless there is none, in
			which case the thread on the top of the stack is
			popped and then resumed.

Parameters      -       EndProcess(void)

Returns         -       NOTHING.
***************************************************************************/
static void _pascal EndProcess(void)
{
llnode_t *next = NULL;
unsigned int axReg;

_asm
	{
	mov     axReg, ax
	}

/* if there is more than one process available we must determine the
next one to be resumed */
if( PROC_LIST->length > 1)
	{
	/* choose a process given the thread's type */
	switch( THREAD_TYPE )
		{
		case NEXT:
		next = CUR_PROC->next ? CUR_PROC->next : FIRST_PROC;
		break;
		case PREV:
		next = CUR_PROC->prev ? CUR_PROC->prev : LAST_PROC;
		break;
		case WINDOW:
		next = ACTWINDOW ? ACTWINDOW->Process : NULL;
		break;
		}
	}
/* now we can destroy the current process */
FREESTACK( ((Process_t *)(CUR_PROC->item))->Stack);
free(CUR_PROC->item);
DeleteNode( PROC_LIST, CUR_PROC, 1);
CUR_PROC = next;
if(CUR_PROC == NULL)
	{
	/* no processes left so resume the main thread */
	Thread_t *thread;

	thread = (Thread_t *)PopStack(&THREAD_STACK);
	thread->CurProc = NULL;
	thread->Regs[_AX] = axReg; /* terminating process return value */
	Resume(&thread->Regs);
	}
else
	Resume( &((Process_t *)(CUR_PROC->item))->Regs );
}

/**************************************************************************
InList          -       In List

Description     -       Locates the supplied thread in the THREAD_STACK

Parameters      -       InList(thread)
			Process_t *thread

Returns         -       A llnode_t pointer to the thread if successful,
			else NULL.

			The routine is only visible in this file.
**************************************************************************/
static llnode_t *InList(llnode_t *thread)
{
llnode_t *next;

next = PROC_LIST->first;

while( next && ( next != thread) )
	next = next->next;
return(next);
}

#pragma optimize("", on)
