{***********************************************************************}
{									}
{ __InsInt  Install an intervention procedure				}
{									}
{ procedure __InsInt(IntAddress : pointer;				}
{		      StackSize : word; 				}
{	     UsesDOS,UsesDOSKey : boolean;				}
{		    KeyTablePtr : pointer;				}
{		   KeyTableSize : byte; 				}
{		   TimeTablePtr : pointer;				}
{		  TimeTableSize : byte; 				}
{		       IntIdent : _IdentStr;				}
{		  var ErrorCode : word);				}
{									}
{	      IntAddress						}
{			Address of the Turbo Pascal procedure which is	}
{			to be installed as the intervention code.  The	}
{			procedure must be declared with a single var	}
{			parameter of type _Signal.  It must also be	}
{			compiled as a far procedure ($F+ directive).	}
{	      StackSize The size in bytes of the stack needed for the	}
{			intervention code.  If stack checking is en-	}
{			abled, the stack size should be at least 1024	}
{			bytes, but in any case must allow for 128 bytes }
{			of internal overhead.				}
{	      UsesDOS	This parameter must be set TRUE if the inter-	}
{			vention procedure needs DOS services (function	}
{			calls greater than 12). 			}
{	      UsesDOSKey						}
{			This parameter must be set TRUE if low level	}
{			DOS I/O services are required (function calls	}
{			1 through 12).	If no DOS services are required,}
{			both UsesDOS and UsesDOSKey should be set FALSE.}
{			If UsesDOSKey is set TRUE, then DOS services are}
{			required as well, and UsesDOS is forced TRUE.	}
{	      KeyTablePtr						}
{			An array of key sequences which is used by the	}
{			intervention task scheduler to determine if	}
{			control should be passed to the intervention	}
{			procedure.  It is a table of "hot keys" that    }
{			the scheduler checks.				}
{	      KeyTableSize						}
{			The number of entries in the KeyTable pointed	}
{			to by KeyTablePtr.  If no "hot key" signals     }
{			are needed, KeyTablePtr should be set to NIL	}
{			and KeyTableSize to 0.				}
{	      TimeTablePtr						}
{			Pointer to an array of times (or intervals)	}
{			used by the scheduler to determine if control	}
{			should be passed to the intervention procedure. }
{	      TimeTableSize						}
{			The number of entries in the TimeTable pointed	}
{			to by TimeTablePtr.  If no timer events are	}
{			scheduled, specify NIL for TimeTablePtr and	}
{			0 for TimeTableSize.				}
{	      IntIdent	Identification string for the installed inter-	}
{			vention code.					}
{	      ErrorCode Returned status code.  The possible values are: }
{			0 - Successful installation			}
{			1 - An intervention procedure is already in-	}
{			    stalled.  Only one per process may be used. }
{									}
{ Description __InsInt installs the specified intervention procedure	}
{	      by initializing the intervention code control block, and	}
{	      by installing the intervention filters.  The control block}
{	      is checked, and if the "already installed" flag is set,   }
{	      control is returned immediately with ErrorCode set to 1.	}
{									}
{	      Global variables required by the intervention task	}
{	      scheduler (it's source is included in this file as well)  }
{	      are also initialized.					}
{									}
{ Version     4.00 (C)Copyright Blaise Computing Inc.  1987		}
{_______________________________________________________________________}

{+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
{									}
{ Global variable declarations required by intervention code scheduler	}
{_______________________________________________________________________}

type
  G_TimeTable	  = array[1..1024] of _SignalTime;

var
  G_TimeTablePtr  : ^G_TimeTable;      { Pointer to table of timer sig- }
  G_TimeTableSize : byte;	       { nals and its size		}
  G_ClockTime	  : longint absolute $0040:$006C; { BIOS timer ticks	}
  G_IdleTicks	  : longint;	       { Ticks since code was serviced. }
  G_TimeIndex	  : integer;	       { Index of timer signal found	}
  G_TimeAction	  : _SignalTimeDef;    { Timer signal action type	}
  G_IntAddress	  : pointer;	       { Intervention code address	}
  G_SchedIsrBlock : _IsrCtrl;	       { ISR control block for scheduler}
  G_DTASeg	  : word;	       { DTA area location saved	}
  G_DTAOfs	  : word;

{$F+}
procedure TaskScheduler(    var IReg: registers;
			var TaskIsr : _IsrCtrl); forward;
{$F-}


procedure __InsInt(IntAddress : pointer;
		    StackSize : word;
	   UsesDOS,UsesDOSKey : boolean;
		  KeyTablePtr : pointer;
		 KeyTableSize : byte;
		 TimeTablePtr : pointer;
		TimeTableSize : byte;
		     IntIdent : _IdentStr;
		var ErrorCode : word);

  var
    StackSpacePtr : pointer;
    Reg 	  : registers;

  begin {__InsInt}

    with _IntCtrlAddr^ do
      if (_IntInstalled) then
	 begin
	   ErrorCode := 1;	       { Already installed, so exit	}
	   Exit 		       { immediately.			}
	 end;

    { First initialize the global variables.				}

    G_TimeTablePtr  := TimeTablePtr;
    G_TimeTableSize := TimeTableSize;
    G_IdleTicks     := 0;
    G_TimeIndex     := 0;
    G_TimeAction    := _NoTimeRequest;
    G_IntAddress    := IntAddress;

    { The task scheduler is invoked from the timer filter via an ISR	}
    { control block in order to change contexts to the Turbo Pascal	}
    { environment.  Therefore an ISR control block for the scheduler is }
    { constructed.  It is not, however, installed, but rather a far	}
    { call to its address is made.  The address of the task scheduler's }
    { ISR control block is stored in the intervention code control	}
    { block so it can be accessed by the timer filter.			}

    with G_SchedIsrBlock do
      begin
	_IsrFarCode := $9A90;			 { NOP followed by CALL }
	_IsrDispPtr := _ExecIsrAddr;		 { Dispatcher address	}

	{ Round the stack size up so that it is an even number of	}
	{ paragraphs (this is required by the dispatcher), and make	}
	{ sure that the nesting level is one.				}

	_IsrStackPar   := (StackSize + 15) shr 4;     { Size in paras	}
	_IsrStackSize  := _IsrStackPar shl 4;	      { Size in bytes	}
	_IsrLimit      := 1;

	{ Now allocate space for the ISR stack on the heap.  The size	}
	{ must be large enough to accommodate StackSize and must be	}
	{ paragraph aligned.						}

	GetMem(StackSpacePtr,_IsrStackSize + 15);
	_IsrStackSeg := _VectorAddr(StackSpacePtr)._Seg;
	if (_VectorAddr(StackSpacePtr)._Ofs <> 0) then
	   Inc(_IsrStackSeg);

	{ Initialize the working variables needed by the dispatcher.	}
	{ The _IsrStackDepth is the current offset (in paragraphs) of	}
	{ the ISR stack segment (SS) from the beginning of the space	}
	{ allocated for the ISR stack.					}

	_IsrStackDepth := 0;
	_IsrSS	       := _IsrStackSeg;
	_IsrSP	       := _IsrStackSize;
	_IsrDS	       := DSeg;
	_IsrAddress    := @TaskScheduler;
	_IsrPSP        := PrefixSeg;
	_IsrLevel      := 0;
	_IsrIdent      := 'TASKSCHED';
	_IsrSignature  := 'BCI';
	_IsrPreVector  := NIL	       { Not installed at a vector	}
      end;

    { Now intialize the intervention control block.			}

    with _IntCtrlAddr^ do
      begin
	_IntSignature  := 'BCI';
	_IntIdent      := IntIdent;
	_IntPSP        := PrefixSeg;
	_IntCurID      := PrefixSeg;
	with _IntVectors do
	  begin
	    GetIntVec($08,_TimerPtr);
	    GetIntVec($09,_KeyBdPtr);
	    GetIntVec($0B,_COM2Ptr);
	    GetIntVec($0C,_COM1Ptr);
	    GetIntVec($13,_DiskPtr);
	    GetIntVec($21,_DOSPtr);
	    GetIntVec($28,_IdlePtr);
	    _KeyIOPtr := NIL;
	    _BreakPtr := NIL
	  end;
	_IntInstalled := TRUE;
	_IntDOSNeed   := UsesDOS;
	_IntDKeyNeed  := UsesDOSKey;
	if (UsesDOSKey) then	       { If DOS key services are re-	}
	  _IntDosNeed := TRUE;	       { quested, then require DOS also.}
	_IntKeySize   := KeyTableSize;
	_IntKeyTable  := KeyTablePtr;
	with _IntKeyEvent do
	  begin
	    _HotKeySeq._Ch	 := #0;
	    _HotKeySeq._ScanCode := 0;
	    _Action		 := _NoKeyRequest
	  end;
	_IntWait := FALSE;

	{ If the enhanced (101/102 keys) keyboard is present, and the	}
	{ extended BIOS is present, the KeyBdFilter will use the	}
	{ extended BIOS calls to query the keyboard; otherwise the	}
	{ standard calls are used.					}

	if (_ExtendedKbdBIOS and _EnhancedKbd) then
	   begin
	     _IntReadyKey := $11;
	     _IntGetKey   := $10
	   end
	else
	   begin
	     _IntReadyKey := 1;
	     _IntGetKey   := 0
	   end;

	_IntRequest   := FALSE;
	_IntSleep     := FALSE;
	_IntTimerBusy := FALSE;
	_IntKeyBdBusy := FALSE;
	_IntCOM2Busy  := FALSE;
	_IntCOM1Busy  := FALSE;
	_IntDiskBusy  := FALSE;
	_IntKeyIOBusy := FALSE;
	_IntIdleBusy  := FALSE;
	_IntInIdle    := FALSE;
	_IntCtrlBreak := FALSE;
	_IntSchedPtr  := @G_SchedIsrBlock;

	{ Now initialize the InDOS flag pointer and the In Critical	}
	{ Error handler flag.  For DOS versions earlier than 3.00, the	}
	{ flag is one byte after the InDOS flag, and for versions later }
	{ than 3.00, it is one byte before.  For DOS version 3.00 the	}
	{ flag is at offset $167, but in the same segment.		}

	Reg.AH := $34;		       { Return the InDOS flag location }
	Intr(_DOS,Reg);
	_IntInDosPtr := Ptr(Reg.ES,Reg.BX);
	if (_DOSMajorVer < 3) then     { Set the in critical error flag }
	   _IntInCritPtr := Ptr(Reg.ES,Reg.BX + 1)
	else if ((_DOSMajorVer = 3) and (_DOSMinorVer = 0)) then
	   _IntInCritPtr := Ptr(Reg.ES,$167)
	else
	   _IntInCritPtr := Ptr(Reg.ES,Reg.BX - 1)

      end;

    { Finally install the intervention filters. 			}

    SetIntVec($08,@TimerFilter);
    SetIntVec($09,@KeybdFilter);
    SetIntVec($0B,@COM2Filter);
    SetIntVec($0C,@COM1Filter);
    SetIntVec($13,@DiskFilter);
    SetIntVec($21,@DOSFilter);
    SetIntVec($28,@IdleFilter);

    ErrorCode := 0

  end;	{__InsInt}


{***********************************************************************}
{									}
{ TaskScheduler  Intervention code task scheduler			}
{									}
{ procedure TaskScheduler(var Reg : registers; var TaskIsr : _IsrCtrl); }
{									}
{	      Reg	The register structure passed to the ISR	}
{	      TaskIsr	The ISR control block for the ISR		}
{									}
{ Descrption  This procedure is the intervention code task scheduler.	}
{	      It is invoked by the timer interrupt filter at every	}
{	      timer tick as an ISR so that a Turbo Pascal environment	}
{	      can be set.  The ISR control block is not installed in a	}
{	      vector, but the timer filter makes a far call directly to }
{	      the control block. The ISR control block is initialized	}
{	      by __InsInt ("install an intervention procedure").        }
{									}
{	      TaskScheduler determines a request for service has been	}
{	      made via the keyboard, or if a time has elapsed so that	}
{	      service is now due.  If the task requires DOS services	}
{	      or DOS functions 1 through 12 (DOS keyboard services),	}
{	      the scheduler ensures that DOS is in a stable state.  If	}
{	      service is requested and it can be granted, control is	}
{	      is passed to the intervention procedure (its address	}
{	      is stored in G_IntAddress). Upon return all necessary	}
{	      flags and internal timer values are reset.  If service	}
{	      cannot be granted (for example, DOS cannot be inter-	}
{	      rupted), the service request is left pending and control	}
{	      returns to the caller.  At the next timer interrupt,	}
{	      another attempt at furnishing service is made.		}
{									}
{	      TaskScheduler is only called by the timer filter, and	}
{	      because it is invoked as a POWER TOOLS interrupt service	}
{	      routine (and thus through the ISR dispatcher), it has	}
{	      full access to global variables and Turbo Pascal library	}
{	      functions.						}
{									}
{	      This procedure makes use of two helper routines:		}
{	      ReadyTask determines if a request for service can be	}
{	      honored; TimerRequest determines if a timer request has	}
{	      been made or is now pending.  Neither routine is nested	}
{	      in TaskScheduler (although that is logical) in order to	}
{	      to conserve intervention code stack space and reduce	}
{	      overhead. 						}
{									}
{	      Finally, recall that global variables in the data segment }
{	      are prefaced with "G_" and the intervention code control  }
{	      block is accessed through the pointer _IntCtrlAddr.	}
{_______________________________________________________________________}

{+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
{ ReadyTask - Return TRUE if the system is in a condition that a	}
{	      request can be serviced; otherwise FALSE. 		}
{_______________________________________________________________________}

function ReadyTask : boolean;

  begin {ReadyTask}

    with _IntCtrlAddr^ do	       { Access the intervention block	}

      { If _IntIdleBusy is TRUE, then the control was gained through	}
      { the DOS idle filter ($28).  As long as DOS function 1 - 12 are	}
      { not required and the filter is in a stable state (_IntInIdle is }
      { TRUE), the task can be scheduled.				}

      if (_IntIdleBusy) then
	 if ((not _IntDKeyNeed) and (_IntInIdle)) then
	    ReadyTask := TRUE
	 else
	    ReadyTask := FALSE

      { If control is not acquired through the DOS idle, and DOS	}
      { services are required by the intervention task, the DOS in use	}
      { flag, the DOS in critical error flag and the disk busy flag	}
      { must be clear.							}

      else if (_IntDOSNeed) then
	 if ((_IntInDOSPtr^ = 0) and (_IntInCritPtr^ = 0) and
						(not _IntDiskBusy)) then
	    ReadyTask := TRUE
	 else
	    ReadyTask := FALSE

      { Finally, if DOS services are not required, and control was not	}
      { obtained through the DOS Idle filter, the task can be serviced	}
      { immediately.							}

      else
	 ReadyTask := TRUE

  end;	{ReadyTask}

{+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++}
{ TimerRequest - Determine if a timer service request has been made	}
{_______________________________________________________________________}

procedure TimerRequest;

  var
    CurTime : longint;
    I	    : integer;

  begin {TimerRequest}

    { A timer request is made if the interval timer is greater than	}
    { the interval time in the time table, or if the clock has passed	}
    { an event time in the time table.	The global variable		}
    { G_TimeTablePtr points to the time table, and if a timer request	}
    { is made, the index in the time table and the action are recorded	}
    { in the global variables G_TimeIndex and G_TimeAction.		}

    CurTime := G_ClockTime;	       { Current time in timer ticks	}
    I	    := 0;
    while ((G_TimeAction = _NoTimeRequest) and (I < G_TimeTableSize)) do
      begin
	Inc(I);
	with G_TimeTablePtr^[I] do
	  if ( ((_Action = _Event) and (CurTime >= _Ticks)) or
	       ((_Action = _Interval) and (G_IdleTicks >= _Ticks)) ) then
	     begin
	       G_TimeAction := _Action;
	       G_TimeIndex  := I
	     end
      end

  end;	{TimerRequest}


{............  Start of the task scheduler code ........................}

procedure TaskScheduler;

  var
    Signal    : _Signal;
    Reg       : registers;

  begin {TaskScheduler}

    with _IntCtrlAddr^ do
      begin
	_IntWait := FALSE;	       { Release the wait semaphore	}

	{ If the task has been put to sleep, and there is not a request }
	{ to wait up, just return immediately.				}

	if ((_IntSleep) and (_IntKeyEvent._Action <> _Wake)) then
	   Exit;

	{ Determine a pending timer request if no request is out-	}
	{ standing and if the timer table size is greater than zero.	}

	if ((G_TimeAction = _NoTimeRequest) and
					     (G_TimeTableSize > 0)) then
	   TimerRequest;

	if ((_IntKeyEvent._Action <> _NoKeyRequest) or
				  (G_TimeAction <> _NoTimeRequest)) then

	   if (ReadyTask) then	       { Can pass control to the code	}
	      begin
		_IntRequest := FALSE;  { Request is no longer pending	}

		{ If there is a timer interval request, set the number	}
		{ ticks since the last service (G_IdleTicks) to zero;	}
		{ if the timer request is an event, reset the table	}
		{ value so that the event does not occur again.  The	}
		{ intervention procedure can alter the table value so	}
		{ that the event can occur later if desired.		}

		if (G_TimeAction = _Interval) then
		   G_IdleTicks := 0
		else if (G_TimeAction = _Event) then
		   G_TimeTablePtr^[G_TimeIndex]._Ticks := $7FFFFFFF;

		{ Now set up the Signal parameter to be passed to the	}
		{ intervention procedure.				}

		Signal._KeyAction   := _IntKeyEvent._Action;
		Signal._HotKey	    := _IntKeyEvent._HotKeySeq;
		Signal._TimeAction  := G_TimeAction;
		Signal._TimeIndex   := G_TimeIndex;
		Signal._ClockTime   := G_ClockTime;

		{ The current process id can only be safely recovered  }
		{ if DOS 3.00 or later is used.  Similarly, the ID can }
		{ only be altered if DOS 3.00 or later is installed.   }

		if (_DOSMajorVer >= 3) then
		   begin
		     Reg.AH := $51;    { Return the current process id }
		     Intr(_DOS,Reg);
		     _IntCurID := Reg.BX
		   end;

		{ If DOS services are required by the intervention pro- }
		{ cedure, some special precautions are required.  To	}
		{ insure that Ctrl/C and Ctrl/Break do no terminate the }
		{ the program, install the KeyBdIO filter and the	}
		{ BreakIsr interrupt service routine.  The CtrlBreak	}
		{ flag is also cleared. 				}

		if (_IntDOSNeed) then
		   begin

		     GetIntVec($16,_IntVectors._KeyIOPtr);
		     GetIntVec($1B,_IntVectors._BreakPtr);
		     SetIntVec($16,@KeyIOFilter);
		     SetIntVec($1B,@BreakIsr);
		     _IntCtrlBreak := FALSE;

		     { Set the process ID to the PSP of the inter-	}
		     { vention procedure.				}

		     if (_DOSMajorVer >= 3) then
			begin
			  Reg.AH := $50;
			  Reg.BX := _IntPSP;
			  Intr(_DOS,Reg)
			end;

		     { Save the DTA location and then restore it when	}
		     { returning.					}

		     with Reg do
		       begin
			 AH := $2F;
			 Intr(_DOS,Reg);
			 G_DTAOfs := BX;
			 G_DTASeg := ES
		       end
		   end;

		{ Now (at last!) call the intervention procedure as a	}
		{ far procedure.					}

		Inline(
			$8D/$B6/Signal		 { lea	si,[bp]Signal	}
		       /$16			 { push ss		}
		       /$56			 { push si		}
		       /$FF/$1E/G_IntAddress);	 { call (F)G_IntAddress }

		{ If DOS services were requested, put back the changes	}
		{ made before the intervention procedure had control.	}

		if (_IntDOSNeed) then
		   begin
		     SetIntVec($16,_IntVectors._KeyIOPtr);
		     SetIntVec($1B,_IntVectors._BreakPtr);

		     with Reg do
		       begin
			 AH := $1A;
			 DS := G_DTASeg;
			 DX := G_DTAOfs;
			 Intr(_DOS,Reg)
		       end
		   end;

		if (_DOSMajorVer >= 3) then
		   begin
		     Reg.AH := $50;	      { Reset current process ID}
		     Reg.BX := _IntCurID;
		     Intr(_DOS,Reg)
		   end;

		{ Reset the event request flags.			}

		if (_IntKeyEvent._Action = _Sleep) then
		   _IntSleep := TRUE
		else if (_IntKeyEvent._Action = _Wake) then
		   _IntSleep := FALSE;
		with _IntKeyEvent do
		  begin
		    _HotKeySeq._Ch	 := #0;
		    _HotKeySeq._ScanCode := 0;
		    _Action		 := _NoKeyRequest;
		  end;
		G_TimeAction := _NoTimeRequest;
		G_TimeIndex  := 0
	      end
	   else
	      _IntRequest := TRUE;     { Request is still pending	}

	if (G_TimeAction <> _Interval) then
	   Inc(G_IdleTicks)

      end

  end; {TaskScheduler}
