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

    Real-Time Executive
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    (c) 1990, 1991, 1992 Michael Podanoffsky.
	All Rights Reserved World Wide.

    Modified for WRI-Tech MasterLINK SCADA Centrol Controller
    2/15/2000

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

#include <dos.h>
#include <bios.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
#include <ctype.h>
#include <malloc.h>
#include <string.h>
#include "rtx.h"

extern void pascal sys_hault(void);

/* /////////////////////////////////////////////////////////////////////
   rtx internal data structures
///////////////////////////////////////////////////////////////////// */

typedef struct {
      int              p;
      int              task;
      } Priority;


typedef struct {
      int (pascal *fct) (event event_id, void far * arg );
      void far * argument;
      } eventFct;

/* /////////////////////////////////////////////////////////////////////
    Allocate Tasks and Counters

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

Q_Item queueItems[MAX_Qs] = { NULL };
Timer far Counters[ MAX_TIMERS ] = { NULL };
Task far Tasks[ MAX_TASKS ] = { NULL };

static   Priority priorities[ MAX_TASKS ] = { NULL };
event    SystemEvents[ MAX_EVENT_ALLOC ] = { NULL };     /* bits */
eventFct EventFunctions[ TOTAL_EVENTS ] = { NULL };      /* one per bit */

int far actual_tasks = 0;
int far actual_timers = 0;
Task far * far currentTask = NULL;
unsigned long far * far SystemClock = far_address(0x0040, 0x006C);

static int far ComPorts[] = {
      0x03f8, 0x02f8, 0x03e8, 0x02e8 };

static int far SystemBits [] = {
      0x8000, 0x4000, 0x2000, 0x1000,
      0x0800, 0x0400, 0x0200, 0x0100,
      0x0080, 0x0040, 0x0020, 0x0010,
      0x0008, 0x0004, 0x0002, 0x0001  };

/***
static int far SystemScannedBits [] = {
      0x8000, 0xC000, 0xE000, 0xF000,
      0xF800, 0xFC00, 0xFE00, 0xFF00,
      0xFF80, 0xFFC0, 0xFFE0, 0xFFF0,
      0xFFF8, 0xFFFC, 0xFFFE, 0xFFFF };
***/

#define NOT(a)                   ~(a)
#define PROTECTION_PATTERN       0xC0C4            /* stack protection signature */

/* /////////////////////////////////////////////////////////////////////
    Allocate Tasks and Counters
///////////////////////////////////////////////////////////////////// */

void far * pascal save_initregs
   ( void far * stack, void far * start_addr, void far * argument, int task_id );

void pascal init_IntTraps(void );
void pascal setClockInterrupt( unsigned long min_clock_value );
void pascal setClockIntValue(void );


/* /////////////////////
    Serial Port Initialization
///////////////////// */
int init_SerialPort( int Port,
		    char Baud,
		    char Parity,
		    char DataBits,
		    char StopBits )
{
    int control;  //al = parity/d-bits/s-bits; ah = baud rate index 0-8

    control = (Baud * 0x100) ^ Parity ^ DataBits ^ StopBits;
    return ( RS232_ini (Port, control) );
};


int enablePortIRQ( int Port )
{
    return ( port_IRQ(Port, ENABLED) );
};


int disablePortIRQ( int Port )
{
    return ( port_IRQ(Port, DISABLED) );
};



/* /////////////////////////////////////////////////////////////////////
    Queue Managers
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    You can queue any structure that contains a queuePtr to a task.
    The queued strcuture is called a queue event and acts like any other
    event.  The queued event that activates the task is moved to
    task->queue_event

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

/* /////////////////////////////////////////////////////////////////////
    queueMessage: manages a circular queue block repositior of size MAX_Qs.
    A queue item is generated and queued to a specified task.
///////////////////////////////////////////////////////////////////// */
int pascal queueMessage(Task far *task, char far *msg)
{
   static char q = 0;
   int return_q = NO_Qs;

   disableInts();

   if (queueItems[q].next == NULL)
      {
      queueItems[q].text = msg;
      queueEvent(task, &queueItems[q]);
      return_q = q;
      q = ++q % MAX_Qs;
      }

   enableInts();

   return return_q;
}


/* /////////////////////////////////////////////////////////////////////
    link a queueItem to a task's queue.
///////////////////////////////////////////////////////////////////// */
int pascal queueEvent(Task far * task, void far *ptr )
{
   QueuePtr far *lastLink;
   QueuePtr far *nextLink = ptr;

   {
   disableInts();

   nextLink->next = NULL;

   lastLink = task->list.tail;
   if ( lastLink )
      lastLink->next = ptr;

   task->list.tail = ptr;

   if ( task->list.head == NULL )
      task->list.head = ptr;

   enableInts();
   }

   return TRUE;
}

/* /////////////////////////////////////////////////////////////////////
  returns the next queue event from a queue or returns NULL.
  also clear ->next (NULL) for reuse.
///////////////////////////////////////////////////////////////////// */
extern void far * pascal getnext_queueEvent(Task far * task )
{
  QueuePtr far *qEvent;

   {
   disableInts();

   qEvent = task->list.head;

   if ( qEvent->next == NULL )
      {
      task->list.head = NULL;
      task->list.tail = NULL;
      }
   else
      task->list.head = qEvent->next;
      qEvent->next = NULL;  //mark reusable

   enableInts();
   }

   return qEvent;
}

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

   looks ahead at a queue but does not remove the entry.

///////////////////////////////////////////////////////////////////// */
extern void far * pascal isnext_queueEvent(Task far * task )
{
   QueuePtr far *qEvent;

   {
   disableInts();

   qEvent = task->list.head;

   enableInts();
   }

   return qEvent;
}

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

   defines an event function.
   Us a null fct argument to clear an event function.

///////////////////////////////////////////////////////////////////// */
int pascal defineEventFct( event event_id,
		  int (pascal *fct) (event event_id, void far * arg ),
		  void far * argument )
{

   if ( event_id > TOTAL_EVENTS )
      return ERR_INVALID_EVENTID;

   EventFunctions[ event_id ].fct =  fct;
   EventFunctions[ event_id ].argument =  argument;
   return SUCCESS;
}

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

   Places the task pointed to by 'task' to wait for an event.

   Use NULL task pointer for current task.

///////////////////////////////////////////////////////////////////// */
int pascal waitEvent(Task far * task, unsigned event_id )
{
   register int offset, mask;

   if ( task == NULL )
      task = currentTask;

   if ( task == NULL || event_id > TOTAL_EVENTS )
	 return ERR_INVALID_EVENTID;

   offset = event_id / 16;                         /* which int */
   mask = SystemBits [ event_id % 16 ];            /* which bit */

   task->events[ offset ] |= mask;
   scheduler();
   return SUCCESS;
}

/* /////////////////////////////////////////////////////////////////////
   Returns the event id of the event that activated the task.
///////////////////////////////////////////////////////////////////// */
event pascal getActiveEvent(Task far * task )
{

   if ( task == NULL ) {
      if ( currentTask == NULL ) return -1;
      task = currentTask;
      }

   return task->startup_event;
}

/* /////////////////////////////////////////////////////////////////////
   waits for keyboard event
///////////////////////////////////////////////////////////////////// */
void pascal waitKbdEvent(void )
{            //v hooks the highest priority task ("name1" in demo)
	     //v which blocks that task waiting on a KBD event.
   waitEvent(CURRENT_TASK, KEYBOARD_WAIT );
}

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

   Sets keyboard event in SystemEvents

///////////////////////////////////////////////////////////////////// */
void pascal setKbdEvent(void )
{

   setSystemEvent( KEYBOARD_WAIT );
}

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

   Support routine: returns stack address of a suspended stack.

///////////////////////////////////////////////////////////////////// */
void far * pascal returntask_StackFrame(void )
{

   return ( currentTask ? currentTask->curr_stack_addr : NULL);
}

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

   Support routine: sets stack address of a suspended stack.

///////////////////////////////////////////////////////////////////// */
void pascal saveCurrentTask_Stack(void far * stack)
{

   if ( currentTask && stack )
      currentTask->curr_stack_addr = stack;

}

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

   Sets any system event

///////////////////////////////////////////////////////////////////// */
int pascal setSystemEvent( unsigned event_id )
{
   register int offset, mask;
   int previous;

   if ( event_id > TOTAL_EVENTS )
      return ERR_INVALID_EVENTID;

   offset = event_id / 16;                         /* which int */
   mask = SystemBits [ event_id % 16 ];            /* which bit */

   {
   disableInts();
   previous = (SystemEvents[ offset ] & mask) ? 1 : 0;
   SystemEvents[ offset ] |= mask;
   enableInts();
   }

   return previous;
}

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

   Clear a system event

///////////////////////////////////////////////////////////////////// */
int pascal clearSystemEvent( unsigned event_id )
{
   register int offset, mask;
   int previous;

   if ( event_id > TOTAL_EVENTS )
      return ERR_INVALID_EVENTID;

   offset = event_id / 16;                         /* which int */
   mask = SystemBits [ event_id % 16 ];            /* which bit */

   {
   disableInts();
   previous = (SystemEvents[ offset ] & mask) ? 1 : 0;
   SystemEvents[ offset ] &= NOT(mask);
   enableInts();
   }

   return previous;
}

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

   Toggle a system event

///////////////////////////////////////////////////////////////////// */
int pascal toggleSystemEvent( unsigned event_id )
{
   register int offset, mask;
   int previous;

   if ( event_id > TOTAL_EVENTS )
      return ERR_INVALID_EVENTID;

   offset = event_id / 16;                         /* which int */
   mask = SystemBits [ event_id % 16 ];            /* which bit */

   {
   disableInts();
   previous = (SystemEvents[ offset ] & mask) ? 1 : 0;
   SystemEvents[ offset ] ^= mask;
   enableInts();
   }

   return previous;
}

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

   Internal Function: Matches waiting events with SystemEvents

///////////////////////////////////////////////////////////////////// */
static int pascal event_match( event far * events )
{

   int offset, event_id, temp_event;
   int all_events = 0;

   /* // examine event functions /////////////////////////////////////// */

   for ( event_id = 0; event_id < TOTAL_EVENTS; ++event_id ) {

      offset = event_id / 16;
      temp_event = events[ offset ];
      all_events |= temp_event;

      if ( temp_event == 0 ) event_id += 15;         /* skip entire word if zero */

      else if ( temp_event & SystemBits[ event_id & 15 ] ) {

	 //get event function pointer for execution
	 eventFct far * event_fct = &EventFunctions[ event_id ];
				  //v execute it here!!
	 if ((event_fct->fct && event_fct->fct(event_id, event_fct->argument ))
	    || (SystemEvents[ offset ] & SystemBits[ event_id & 15 ] ))
	    {
	    SystemEvents[ offset ] &= NOT(SystemBits[ event_id & 15 ]);
	    return event_id;
	    }
	 }
      }

   return (all_events == 0) ? 0 : -1;         /*  0, not waiting on any events */
					      /* -1, waiting on events/no match */
}


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

   Internal Function: Runs through all tasks looking for available task

///////////////////////////////////////////////////////////////////// */
void far * pascal scheduleTask(void )
{
   int n;
   Task far * run_task = NULL;

   while ( run_task == NULL ) {

      /* -- any task available, in priority order ------------------- */
      for ( n = 0; n < actual_tasks; n++ ) {

	 Task far *task = &Tasks[ priorities[ n ].task ];
	 if ( task->sleep && task->sleep < *SystemClock )
	       task->sleep = 0L;

	 if ( !task->free && !task->suspended && !task->sleep )
	    {
	    if (task->startup_event == Q_EVENT)  //is it a Queue Event Task?
	       {
	       if ( task->list.head )   //is there a Queued Event?
		  {
		  task->queue_event = getnext_queueEvent(task );
		  task->startup_event = 0;
		  run_task = task;
		  break;
		  }
	       }
	    else
	       {
	       int startup_event = event_match( task->events );
	       if ( startup_event >= 0 )
		  {
		  task->startup_event = startup_event;
		  run_task = task;
		  break;
		  }
	       }
	    } /* end if task->free, ... */
	 } /* end for n ... */

      /* -- if none, then current task ------------------------------ */
      if ( run_task == NULL && currentTask
	 && !currentTask->sleep && event_match( currentTask->events ) >= 0)

	    run_task = currentTask;

      } /* end while ... */

   memset( run_task->events, '\0', sizeof(run_task->events));

   if ( run_task->time_slice_interval ) {
      run_task->time_slice = *SystemClock + run_task->time_slice_interval;
      setClockIntValue();
      }

   currentTask = run_task;
   return run_task->curr_stack_addr;

}

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

   Locate task by name.

///////////////////////////////////////////////////////////////////// */
Task far * pascal findTask(char far *name)
{
   int n;
   char search_name[ MAX_TASK_NAME ];

   if ( *name == NULL ) return NULL;

   strcpy(search_name, name);
   strlwr(search_name);

   for ( n = 0; n < actual_tasks; n++ ) {

      char far *name_ptr = Tasks[n].task_name;
      char far *search_ptr = search_name;

      if ( *name_ptr ) {

	 while (*name_ptr && *search_ptr) {

	    char ch = *name_ptr;

	    if (ch >= 'A' && ch <= 'Z') ch |= 0x20;
	    if ( ch != *search_ptr ) break;
	    ++name_ptr;
	    ++search_ptr;

	    } /* while ... */

	 if (*name_ptr == *search_ptr)

	    return &Tasks[n];

	 }  /* if name ... */

      } /* for ... */


   return NULL;

}

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

   Internal Fct: Set next clock interrupt value.

///////////////////////////////////////////////////////////////////// */
void pascal setClockIntValue(void )
{

   int n;
   unsigned long min_clock_value = 0xFFFFFFFF;


   for ( n = 0; n < MAX_TIMERS; ++n )

      if ( Counters[ n ].wait_value
	&& (Counters[ n ].wait_value <= min_clock_value) )

	    min_clock_value = Counters[ n ].wait_value;


   for ( n = 0; n < MAX_TASKS; ++n ) {

      if ( Tasks [ n ].sleep
	&& (Tasks [ n ].sleep <= min_clock_value) )

	    min_clock_value = Tasks [ n ].sleep;


      if ( Tasks [ n ].time_slice
	&& (Tasks [ n ].time_slice <= min_clock_value) )

	    min_clock_value = Tasks [ n ].time_slice;

      }

   setClockInterrupt( min_clock_value != 0xFFFFFFFF ? min_clock_value : 0L );

}

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

   Put task to sleep.

///////////////////////////////////////////////////////////////////// */
int pascal sleepTask(Task far * task, int ticks )
{

   /* -- is it a valid address --------------------------------------- */

   if ( task == NULL )
      task = currentTask;

   if ( task == NULL
     || task > &Tasks [ MAX_TASKS ] || task < &Tasks [ 0 ] )

      return ERR_INVALID_ADDRESS;


   /* -- compute when to sleep till ---------------------------------- */

   task->sleep = ( ticks == 0 ) ? 0L : (*SystemClock + ticks);
   setClockIntValue();

   if ( task == currentTask ) scheduler();
   return SUCCESS;
}

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

   Put task to sleep till.

///////////////////////////////////////////////////////////////////// */
int pascal sleepTill(Task far * task, long time_of_day )
{


   return sleepTask(task, ( time_of_day == 0 ) ? 0
			      : (int )(time_of_day - *SystemClock));
}

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

   Clear a timer.

///////////////////////////////////////////////////////////////////// */
int pascal clearTimer(int timer_id )
{

   if ( timer_id >= MAX_TIMERS )
      return ERR_TIMERID_NOTFOUND;

   Counters[ timer_id ].orig_value =
      Counters[ timer_id ].wait_value = 0L;

   setClockIntValue();
   return SUCCESS;
}

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

   Clear specific timer id.

///////////////////////////////////////////////////////////////////// */
int pascal clearTimer_id(int timer_id )
{

   return clearTimer( timer_id );
}

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

   Set specific timer id.

///////////////////////////////////////////////////////////////////// */
int pascal setTimer_id( int timer_id,
		 unsigned flag,
		 unsigned long init_value,
		 int (pascal *action)(void far *targ),
		 void far *argument )
{

   if ( timer_id >= MAX_TIMERS )
      return ERR_TIMERID_NOTFOUND;

   if ( action == NULL )
      return ERR_INVALID_ADDRESS;

   {
   disableSched();

   if ( init_value )
      Counters[ timer_id ].wait_value = *SystemClock + init_value;

   else
      Counters[ timer_id ].wait_value = init_value;         /* zero */

   Counters[ timer_id ].orig_value = (flag == PERIODIC) ? init_value : 0L;
   Counters[ timer_id ].action = action;
   Counters[ timer_id ].argument = argument;

   setClockIntValue();
   enableSched();
   }

   return SUCCESS;
}

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

   Set first available timer.

///////////////////////////////////////////////////////////////////// */
int pascal setTimer( unsigned flag,
	      unsigned long init_value,
	      int (pascal *action)( void far *targ ), // function hook addr
	      void far *argument )
{

   int err_code;

   err_code = setTimer_id(actual_timers++, flag, init_value, action, argument );
   return (err_code < 0 ) ? err_code : actual_timers;
}

///////////////////////////////////////////////////////////////////////////
void pascal countDown_Timers(void )
{

   int n;

   for ( n = 0; n < MAX_TIMERS; ++n )

      if ( Counters[ n ].wait_value
	&& Counters[ n ].wait_value <= *SystemClock ) {

	    Counters[ n ].action( Counters[ n ].argument );

	    Counters[ n ].wait_value =
		  Counters[ n ].orig_value + *SystemClock;
	    }

   for ( n = 0; n < MAX_TASKS; ++n ) {

      if ( Tasks [ n ].sleep
	&& Tasks [ n ].sleep <= *SystemClock )  Tasks [ n ].sleep = 0L;

      if ( Tasks [ n ].time_slice
	&& Tasks [ n ].time_slice <= *SystemClock )  {

	 if ( Tasks [ n ].time_slice_interval )

	    Tasks [ n ].time_slice =
	       *SystemClock + Tasks [ n ].time_slice_interval;

	 else
	    Tasks [ n ].time_slice= 0L;
	 }
      }


   setClockIntValue();
}

///////////////////////////////////////////////////////////////////////////
int pascal getPriority(Task far * task )
{

   if ( task == NULL ) {
      if ( currentTask == NULL )  return 0;

      task = currentTask;
      }

   return task->priority;
}

///////////////////////////////////////////////////////////////////////////
int pascal setPriority(Task far * task, int prio )
{

   int t, j;
   int old_prio;

   int S;                              /* S is the current position in
					  priorities table */

   int D;                              /* D is the destination position
					  in priorities table */


   /* -- is it a valid address --------------------------------------- */

   if ( task == NULL )
      task = currentTask;

   if ( task == NULL
     || task > &Tasks [ actual_tasks ] || task < &Tasks [ 0 ] )

      return ERR_INVALID_ADDRESS;


   /* -- build priority ordered list --------------------------------- */

   disableSched();

   old_prio = task->priority;                 /* save old priority */
   task->priority = prio;                     /* update priority   */

   t = task - Tasks;                          /* which task is it ? */
   D = actual_tasks;

   if ( actual_tasks ) {

      /* -- find current priority entry ------------------------------ */

      for ( S = 0; S < actual_tasks && priorities[ S ].task != t; ++S ) ;


      /* -- find desired priority entry ------------------------------ */

      for ( D = 0; D < actual_tasks && prio <= priorities [ D ].p; ++D ) ;


      /* -- adjust priority table accordingly ------------------------ */

      if ( S < actual_tasks ) {

	 --D;                 /* insert before lowest */

	 if ( S < D )         /* if source before destination, pack upwards */

	    memcpy( &priorities[ S ], &priorities[ S+1 ],
			( D - S ) * sizeof( Priority ));


	 else if ( S > D ) {  /* if source after destination, pack downwards */

	    ++D;

	    for ( j = S; j > D; --j )
	       memcpy( &priorities[ j ], &priorities[ j-1 ],
			sizeof( Priority ));

	    }
	 }
      }


   priorities[ D ].p = prio;
   priorities[ D ].task = (task - Tasks);

   enableSched();

   return SUCCESS;

}


///////////////////////////////////////////////////////////////////////////
int pascal suspendTask(Task far * task, int cond )
{

   if ( task == NULL ) {
      if ( currentTask == NULL )
	 return FALSE;

      task = currentTask;
      }

   task->suspended = cond;

   if ( task == currentTask )  scheduler();

   return TRUE;
}


///////////////////////////////////////////////////////////////////////////
void pascal initTaskSystem(void )
{

   disableSched();
   memset(Counters, 0, sizeof(Counters));
   memset(Tasks, 0, sizeof(Tasks));
   memset(priorities, 0, sizeof(priorities));
   memset(SystemEvents, 0, sizeof(SystemEvents));
   memset(EventFunctions, 0, sizeof(EventFunctions));

   actual_tasks = 0;
   actual_timers = 0;
   currentTask = NULL;

   enableSched();
   init_IntTraps();

}

///////////////////////////////////////////////////////////////////////////
Task far * pascal defineTask(int (pascal *start_addr)(void far * Targ),
			     void far * argument,
			     unsigned stack_size,
			     int priority,
			     char far * task_name )

{
   int task_id = actual_tasks;
   void far * stack_addr;
   int far * stack_bottom;
   int far * stack_temp;
   Task far * task;
					  /* -- possible errors ---------- */
   if ( actual_tasks >= MAX_TASKS         /* if too many tasks already,    */
    ||  start_addr == NULL                /* or if no start_address given, */
    ||  stack_size == 0                   /* stack size not given,         */
    ||  stack_size < MIN_STACK_SIZE )     /* or too small.                 */

	 return NULL;


   disableSched();
   task = &Tasks[ actual_tasks ];
   memset(task, NULL, sizeof(Task));

   if ( (stack_addr = farmalloc( stack_size )) == NULL )
      return NULL;                                /* if malloc failed */

   stack_bottom = (int far *)stack_addr;
   stack_bottom[ 0 ] = PROTECTION_PATTERN;

   stack_addr = (void far * )((char far *)stack_addr + stack_size - sizeof(int));
   //stack_addr[ 0 ] = PROTECTION_PATTERN;

   task->stack_size = stack_size;
   task->init_stack_addr = stack_addr;
   task->curr_stack_addr = save_initregs
	    ( stack_addr, start_addr, argument, task_id );

   task->time_slice_interval = TIME_SLICE;
   strncpy(task->task_name, task_name, MAX_TASK_NAME );

   if ( priority == 0 ) priority = 1;

   task->priority = priority;
   setPriority(task, priority );

   ++actual_tasks;
   enableSched();
   return task;
}

///////////////////////////////////////////////////////////////////////////
int pascal terminateTask(Task far * task )
{
   void far * stack_addr = task->init_stack_addr;

   if ( task == currentTask )
      currentTask = NULL;

   setPriority(task, 0 );
   task->free = TRUE;
   farfree( task->init_stack_addr );

}


///////////////////////////////////////////////////////////////////////////
int pascal terminateTask_id(int task_id ) {
   
   return terminateTask( &Tasks[ task_id ]);
}

