;************************************************************************
;
; __ExecIsr  Interrupt Service Routine Dispatcher
;
; This procedure dispatches a Turbo Pascal procedure to be  used  as  an
; interrupt  service  routine.	 It  is  NOT to be called from any Turbo
; Pascal program directly, and as such, it is not exported  through  the
; unit interface.
;
; The  procedure __InsIsr ("install an ISR") initializes the ISR control
; block and places the address of the control  block  in  the  specified
; interrupt  vector.   Because	the first few bytes of the control block
; implement a far call to the dispatcher, when an interrupt  occurs  the
; dispatcher  gains  control.  The return address pushed on the stack by
; the far call is the address of the next word of the control block.
;
; When __ExecIsr gains control, the stack, from  high  address	to  low,
; looks like:
;
;	    caller's flags
;	    return address (segment) of the caller
;	    return address (offset) of the caller
;	    segment of next entry (_IsrStackSeg) in the control block
;	    offset of the next entry in the control block
;
; The ISR control block is defined in the unit interface, and the fields
; are used as follows:
;
; Field Name	  Description and Use
; -------------   ----------------------------------------------------
; _IsrFarCode	  NOP instruction followed by far call to dispatcher
; _IsrDispPtr	  Address of the interrupt service routine dispatcher
; _IsrStackSeg	  Segment  of  the  space  allocated for the ISR stacks.
;		  This space is allocated by __InsIsr and is  guaranteed
;		  to be paragraph aligned.  The space allocated is large
;		  enough  to  accommodate  the ISR stack for a specified
;		  number of nested invocations (see _IsrLimit).
; _IsrStackSize   The size of the ISR stack  in  bytes.   When	the  ISR
;		  gains  control, the stack pointer is given this value,
;		  and represents the offset from the stack segment.   If
;		  the  stack  is  not large enough, and the ISR has been
;		  compiled with stack checking enabled ($S+),  a  normal
;		  stack  checking  error is reported by the Turbo Pascal
;		  runtime system.  The stack  size  is	always	an  even
;		  number  of  paragraphs; __InsIsr adjusts the requested
;		  value if required.
; _IsrStackPar	  The stack size in paragraphs.  This value is	used  to
;		  increment  the  stack  segment  if  a nested interrupt
;		  occurs to prevent the ISR stacks from overwriting each
;		  other.
; _IsrStackDepth  The current depth of the ISR	stack.	 This  value  is
;		  added  to  the  _IsrStackSeg	to determine the correct
;		  stack segment value.	The stack depth  is  incremented
;		  by  _IsrStackPar  at each invocation, and then reduced
;		  by the same amount upon exit.
; _IsrSS	  The current stack segment.  It is  calculated  as  the
;		  _IsrStackSeg plus the stack depth (_IsrStackDepth).
; _IsrSP	  The  current	stack  pointer;  it is always set to the
;		  stack size (_IsrStackSize).
; _IsrDS	  The value of the  data  segment  when  the  ISR  gains
;		  control.  The procedure _InsIsr sets this to DSeg, the
;		  default data segment.
; _IsrAddress	  The  address	of  the  Turbo	Pascal	procedure  to be
;		  installed as the ISR.  The dispatcher makes a far call
;		  to this routine, so it must be compiled using the  $F+
;		  compiler   directive.    Moreover,   the   two   "var"
;		  parameters, a register structure and the  ISR  control
;		  block, are passed to the procedure.  Therefore it must
;		  be declared as
;
;		  procedure FoobarIsr(	   var Reg : register;
;				      var IsrBlock : _IsrCtrl);
;
; _IsrPSP	  The  program	segment prefix of the program installing
;		  the  ISR.   The   dispatcher	 does	not   use   this
;		  information,	but  it  is  made  available  to the ISR
;		  through the ISR control block.
; _IsrPreVector   The original contents of the interrupt  vector.   This
;		  is  useful  for the ISR in case it needs to filter the
;		  interrupt.
; _IsrLevel	  The current level of nesting.  If  the  value  exceeds
;		  the	nesting   limit  (_IsrLimit),  the  ISR  is  not
;		  dispatcher (otherwise memory could be corrupted),  and
;		  the  dispatcher  just  exits.   This feature can cause
;		  system crashes if hardware interrupts  are  installed,
;		  and  over  nesting  causes  the  interrupt not to gain
;		  control.  If the interrupt is required to send an  EOI
;		  to  the  programmable interrupt controller (8259), but
;		  does not gain control, no interrupts of lower priority
;		  can be serviced.  For example, if over nesting  occurs
;		  on the timer interrupt, the system is dead.  It is the
;		  responsibility of the programmer to make sure that the
;		  nesting  level  is sufficient (using semaphores within
;		  the ISR can guarantee this).
; _IsrLimit	  The maximum level of nesting allowed.
; _IsrSignature   The signature string 'BCI' services  to  identify  the
;		  interrupt service routine control block.
; _IsrIdent	  This	string	is  used  to  identify	a particular ISR
;		  control block and the installation of an ISR.
;
; This routines performs  the  following  operations  to  actually  pass
; control to the specified Turbo Pascal procedure:
;
; 1.  All  registers  are  saved  (in  the order of the registers record
;     structure);
; 2.  The address of the ISR control block is located and saved;
; 3.  Determines if a new stack may be set up for this invocation of the
;     ISR (if not, then __ExecIsr just returns);
; 4.  Sets up the new stack;
; 5.  Initialize the new data segment;
; 6.  Push the parameters on the stack (the addresses  of  the	register
;     structure and the ISR control block);
; 7.  Make a far call to the Turbo Pascal procedure;
; 8.  Upon  return,  reestablish the stack and ISR control block working
;     values;
; 9.  Restore the registers and simulate an IRET to the caller.
;
;
;  Version 4.00  (C)Copyright Blaise Computing Inc.  1987
;________________________________________________________________________

ISRCTRL    struc		       ; The ISR control block
  isr_farcode	 dw  ?		       ; NOP followed by far call
  isr_dispptr	 dd  ?		       ; Address of the dispatcher
  isr_stackseg	 dw  ?		       ; Allocated ISR stack space
  isr_stacksize  dw  ?		       ; Size of each stack instance
  isr_stackpar	 dw  ?		       ; Size of stack in paragraphs
  isr_stackdepth dw  ?		       ; Current paragraph usage
  isr_ss	 dw  ?		       ; Current stack segment, SS
  isr_sp	 dw  ?		       ; Current stack pointer, SP
  isr_ds	 dw  ?		       ; DS value required (DSeg)
  isr_address	 dd  ?		       ; Address of ISR itself
  isr_psp	 dw  ?		       ; PSP of the ISR
  isr_prevector  dd  ?		       ; Previous value of vector
  isr_level	 dw  ?		       ; Current level of nesting
  isr_limit	 dw  ?		       ; Maximum nesting level
  isr_signature  db  4	dup (?)        ; ISR Block identification
  isr_ident	 db  10 dup (?)        ; ISR identification
ISRCTRL ends

popff	   macro		      ;; Simulate POPF instruction
	   local   do_call, do_iret
	   jmp	   short do_call

do_iret:
	   iret 		      ;; Pop IP, CS, flags.

do_call:
	   push   cs		      ;; Push CS
	   call   do_iret	      ;; Push IP & jump.
	   endm


code	   segment
	   assume    cs:code
	   public    __ExecIsr
__ExecIsr  proc      far

	   pushf
	   cli			       ; Turn off interrupts until the
				       ; the new environment is set.
				       ; Save all registers in the
	   push      es 	       ; order of the REGISTERS type
	   push      ds 	       ; so the structure can be
	   push      di 	       ; accessed off the stack.
	   push      si
	   push      bp
	   push      dx
	   push      cx
	   push      bx
	   push      ax

	   push      bp 	       ; Now save the base pointer
	   mov	     bp,sp	       ; and use it to access the stack.

; Equate all the relevant information on the stack so we can
; access it using the mnemonics

call_flags equ	     [bp + 30]	       ; Caller's flags
caller_cs  equ	     [bp + 28]	       ; Caller's code segment
caller_ip  equ	     [bp + 26]	       ; Caller's instruction pointer
ret_seg    equ	     [bp + 24]	       ; Segment of ISR control block
ret_ofs    equ	     [bp + 22]	       ; Offset of isr_iretcode in ISR block
orig_flags equ	     [bp + 20]	       ; Flags upon entry
orig_es    equ	     [bp + 18]	       ; Registers upon entry to dispatcher
orig_ds    equ	     [bp + 16]
orig_di    equ	     [bp + 14]
orig_si    equ	     [bp + 12]
orig_bp    equ	     [bp + 10]
orig_dx    equ	     [bp + 08]
orig_cx    equ	     [bp + 06]
orig_bx    equ	     [bp + 04]
orig_ax    equ	     [bp + 02]

; Set ES:SI to point to the beginning of the ISR control block

	   les	     si,dword ptr ret_ofs
	   sub	     si,(isr_stackseg - isr_farcode)

; The flags pushed on the stack (which become part of the register
; structure passed to the ISR) are the flags after the interrupt and
; trap flags are cleared (by the INT instruction).  The actual flags
; desired are the caller's flags, call_flags; hence swap those with
; orig_flags.  Later, when calling the ISR, call_flags are restored.

	   mov	     ax,call_flags
	   mov	     bx,orig_flags
	   mov	     orig_flags,ax
	   mov	     call_flags,bx

; Replace the return address of the control block on the stack with
; the caller's return address.	This is done so the control block
; address can be removed from the stack and control returned to the
; caller.

	   mov	     cx,caller_ip
	   mov	     ret_ofs,cx
	   mov	     cx,caller_cs
	   mov	     ret_seg,cx

; Make sure nesting depth is not beyond the limit

	   mov	     dx,es:[si].isr_level
	   cmp	     dx,es:[si].isr_limit
	   jb	     level_ok
	   jmp	     exit	       ; Too deep - so exit

level_ok:

; Now set up the ISR stack.  It is assummed that the values in
; the ISR control block provide the (paragraph aligned) values for
; the stack segment and pointer.  The stack segment is set to the
; beginning of the allocated ISR stack space (isr_stackseg) plus
; the current stack depth (isr_stackdepth).  The stack pointer is
; always set to the stack size (isr_stacksize), but the stack depth
; is incremented by the stack size in paragraphs (isr_stackpar) at
; each innvocation.

	   inc	     dx 	       ; Increment and the nesting level
	   mov	     es:[si].isr_level,dx

	   mov	     ax,ss	       ; Save the stack.
	   mov	     bx,sp
	   mov	     cx,es:[si].isr_stackseg	 ; Initialize SS
	   add	     cx,es:[si].isr_stackdepth
	   mov	     es:[si].isr_ss,cx
	   mov	     ss,cx
	   mov	     cx,es:[si].isr_stackdepth	 ; Increment stack depth
	   add	     cx,es:[si].isr_stackpar
	   mov	     es:[si].isr_stackdepth,cx
	   mov	     cx,es:[si].isr_stacksize	 ; Initialize SP
	   mov	     es:[si].isr_sp,cx
	   mov	     sp,cx

	   push      ax 	       ; Save the stack by pushing SS
	   push      bx 	       ; and then SP.
	   push      dx 	       ; Save copy of nesting level
	   push      es 	       ; Need ES:SI later. They must be
	   push      si 	       ; saved in case the ISR alters them

; Set the data segment, and restore the original flags.  This may
; reenable interrupts, and insures the ISR has the intended flags.

	   mov	     ds,es:[si].isr_ds ; DS register set..

	   push      es 	       ; Need to address call_flags
	   push      ax 	       ; relative to old SS (now in AX)
	   pop	     es
	   push      es:call_flags     ; Restore flags that existed
	   popff		       ; upon entry.
	   pop	     es

; Push the parameters on the stack.  First the registers
; structure, and then the address of the ISR constrol block.
; Then invoke the Turbo Pascal procedure.  It will remove the
; parameters from the stack.  The registers structure is pointed
; to by SS:BP + 2 (the previous stack segment), and the ISR
; control block by ES:SI.  Recall that AX contains the original SS.

	   push      ax 	       ; Put the original SS on the stack
	   mov	     ax,bp	       ;  and the offset of the registers.
	   add	     ax,2
	   push      ax
	   push      es 	       ; Now the address of the ISR
	   push      si 	       ; control block.

; Make a far call to the ISR procedure (it must be compiled with $F+)

	   call      dword ptr es:[si].isr_address

	   cli			       ; Turn off interrupts while
				       ; returning to previous state
	   pop	     si 	       ; Now can address ES:SI, and the
	   pop	     es 	       ; ISR control block.

; Discard the ISR stack created, that is, reset isr_level, isr_ss and
; isr_sp.  If isr_level exceeds the local (placed on the ISR stack)
; copy of level, then this ISR invocation returned BEFORE a more
; deeply nested invocation returned.  In this case, assume that deeper
; invocations will never return, so we adjust isr_level and isr_ss to
; discard deeper stacks.  Note that interrupts are disabled during
; this operation.

	   mov	     cx,es:[si].isr_level
	   pop	     dx 	       ; The local level
	   dec	     dx 	       ;  is reduced by one.
	   mov	     es:[si].isr_level,dx
	   sub	     cx,dx	       ; CX = number of stacks to discard,
				       ;  and is normally equal to 1.

	   jbe	     stacks_discarded
	   mov	     ax,es:[si].isr_stackpar
discard_stack:
	   sub	     es:[si].isr_stackdepth,ax
	   loop      discard_stack
stacks_discarded:

	   pop	     bx 	       ; Stack pointer
	   pop	     ax 	       ; Stack segment
	   mov	     ss,ax	       ; Recover the stack
	   mov	     sp,bx

exit:
	   sti			       ; Interrupts are now allowed
	   pop	     bp 	       ; Restore the state of the machine
	   pop	     ax 	       ; Notice that the registers have
	   pop	     bx 	       ; been altered by the ISR.
	   pop	     cx
	   pop	     dx
	   pop	     bp
	   pop	     si
	   pop	     di
	   pop	     ds
	   pop	     es
	   popff		       ; Restore flags, simulating IRET

	   ret	     6		       ; Because the return address was
				       ; copied, we can return and
				       ; remove the extra six bytes
				       ; (flags and return address).
__ExecIsr  endp
code	   ends
	   end
