                     Turbo interrupt handler code

I've had several requests for the Turbo interrupt handler installation
code I mentioned a few weeks back, and I've been having increasing difficulty
constructing return addresses. So, at the suggestion of several
correspondents, I'm sending the code to you.

It provides two procedures which allow the installation and removal of
ordinary Turbo procedures as interrupt handlers. Optionally the
interrupt handler may use its own internal stack and chain the old
interrupt vector on exit.

Procedures dealing with external interrupts will need to handle the 8259
interrupt controller themselves. Since writing the code below I have
sorted out more or less how to do this, and should there be any interest
I'll put together a summary of what to do.

Jim Hague				jmh@ukc.uucp
--------------------------------------------------------------------
{ A Turbo package to ease the use of interrupt routines written in
  Turbo Pascal by allowing you to install ordinary Turbo procedures
  as interrupt handlers without the need for gobs of inline statements
  within each procedure.

  As far as the outside world is concerned, it provides

  type  interrupt_workspace ;

  procedure InstallInterruptHandler
     ( routine_offset, intrrupt_no : integer ;
       chain_old_vector, enable_ints_during, new_stack : boolean ;
       var workspace : interrupt_workspace )

     This installs a Turbo procedure whose offset is routine_ptr
     (ie ofs(RoutineName) ) to be activated by interrupt no intr_no.
     Note that if the interrupt is an external one the 8259 is not
     touched - you must do this yourself. If chain_old_vector is true
     then the previous interrupt vector is chained to at the end of
     the interrupt procedure. If enable_ints_during is true then
     interrupts are enabled during the course of the interrupt
     procedure. If new_stack is true then then the stack is switched
     to an internal one within 'workspace' for the duration of the
     interrupt (you won't need this only for interrupts called with
     INT instructions when you know their stack has room enough).
     Interrupts arriving at unpredictable times may arrive during
     a DOS call and overflow the DOS stack, hence the need to switch
     stacks.
     Each interrupt must have a variable of type
     interrupt_workspace accompanying it - it MUST appear in a
     var declaration, not 'new'ed off the stack.

  procedure RemoveInterruptHandler
     ( interrupt_no : integer ; var workspace : interrupt_workspace ) ;

     Removes the interrupt handler at interrupt no intr_no
     that was installed by InstallInterruptHandler and replaces
     it with the vector that was there previously, as held in
     workspace. FEED THIS THE CORRECT WORKSPACE !

  Also provided are constants CLI and STI. Inline(CLI) will disable
  interrupts until the next Inline(STI).

  *** NOTE ***
     Interrupt routines installed with new_stack true (eg for where interrupts
     may arrive during DOS) and all routines they call which in turn call
     other routines MUST be compiled with the K- switch to disable stack
     checking - since a new stack is in use it confuses Turbo, so off with
     the checking. The stack depth with 'new_stack' is limited to
     'intr_stack_size' bytes (see below), so be careful with over-generous
     use of local variables or recursion.
     Procedures responding to external interrupts will have to inform
     the 8259 of the end of the interrupt themselves - issueing a generic
     EOI via a Port[$20] := $20 should usually be sufficient.

  A summary of how it works.
  ==========================

     Code is placed into link_code in workspace for each routine that
     switches the stack if necessary, saves DS and SI on the stack,
     places the offset of the routine being called into SI and the
     current segment (the Turbo data segment) into DS, and then does
     a far call to code in the typed constant (and thus in the code
     segment) at goto_si. This saves the rest of the registers and does
     a near call to the handling routine, which means the code generated
     by the compiler to enter and exit the handling routine will work ok.
     On exit it back to goto_si there is a far ret back to the code
     in workspace which then restores DS and SI, restores the stack if
     necessary and does either an IRET or a jump to the old vector
     depending on the setup instructions.

}



const                                       { Actual constants }
   intr_stack_size       = $40 ;            { Size of 'new_stack's }
   max_link_code_size    = 60  ;            { Max size of link code space }

   STI                   = $fb ;            { Interrupt flag instructions }
   CLI                   = $fa ;            { op codes }


type
   routine_ptr           = ^byte ;          { Any pointer really }
   interrupt_workspace   = record
                              link_code   : array [0 .. max_link_code_size]
                                                  of byte ;
                              old_routine : routine_ptr ;
                              sp, ss      : integer ;
                              int_stack   : array [0 .. intr_stack_size]
                                                  of byte
                           end ;

const                                       { Type const in code seg }
   goto_si : array [1 .. 15] of byte =
             ( $50,          { push ax }
               $53,          { push bx }
               $51,          { push cx }
               $52,          { push dx }
               $57,          { push di }
               $06,          { push es }
               $ff,$d6,      { call si }
               $07,          { pop  es }
               $5f,          { pop  di }
               $5a,          { pop  dx }
               $59,          { pop  cx }
               $5b,          { pop  bx }
               $58,          { pop  ax }
               $cb ) ;       { ret far }



procedure InstallInterruptHandler
   ( routine_offset, interrupt_no : integer ;
     chain_old_vector, enable_ints_during, new_stack : boolean ;
     var workspace : interrupt_workspace ) ;

var
   i : integer ;
   interrupt_vector : array [0 .. 255] of routine_ptr absolute 0:0 ;

   { Add a byte of code to the link, giving error if overflow }
   procedure l ( b : byte ) ;
   begin
      if i > max_link_code_size then
         begin
            writeln ('Link code overflow') ;
            halt (1) ;                      { Give error and stop }
         end
      else
         begin
            workspace.link_code[i] := b ;
            i := i + 1
         end
   end ;

   { Add a word of code to the link (lo:hi), giving error if overflow }
   procedure w ( w : integer ) ;
   begin
      l (lo(w)) ;
      l (hi(w))
   end ;

begin
   i := 0 ;                                 { Reset link code buffer counter }
   with workspace do
      begin
         { Code to swap stacks, if necessary }
         if new_stack then
            begin
               l($2e) ; l($8c) ;       { mov  cs:[ss],ss        save SS }
               l($16) ; w(ofs(ss)) ;
               l($2e) ; l($a3) ;       { mov  cs:[sp],ax        save AX }
               w(ofs(sp)) ;
               l($8c) ; l($c8) ;       { mov  ax,cs             set SS to CS }
               l($8e) ; l($d0) ;       { mov  ss,ax }
               l($89) ; l($e0) ;       { mov  ax,sp             save SP and }
               l($2e) ; l($87) ;       { xchg ax,cs:[sp]        recover AX }
               l($06) ; w(ofs(sp)) ;
               l($bc) ;                { mov  sp,<top of new stack> }
               w(ofs(int_stack) + intr_stack_size)
            end ;

         { Now save DS and SI and set them to CS and the routine offset }
         l($56) ;                      { push si }
         l($1e) ;                      { push ds }
         l($0e) ;                      { push cs }
         l($1f) ;                      { pop  ds }
         l($be) ; w(routine_offset) ;  { mov  si,routine_offset }

         { Re-enable interrupts if desired }
         if enable_ints_during then
            l(STI) ;                   { sti }

         { Far call to the code at goto_si }
         l($9a) ; w(ofs(goto_si)) ;    { call far goto_si }
         w(Cseg) ;

         { Recover DS and SI }
         l($1f) ;                      { pop  ds }
         l($5e) ;                      { pop  si }

         { Reset stack to original if necessary }
         if new_stack then
            begin
               l($2e) ; l($8e) ;       { mov  ss,cs:[ss]        recover SS }
               l($16) ; w(ofs(ss)) ;
               l($2e) ; l($8b) ;       { mov  sp,cs:[sp]        and SP }
               l($26) ; w(ofs(sp))
            end ;

         { Bung in either an IRET or a jump to next link }
         if chain_old_vector then
            begin
               l($2e) ; l($ff) ;       { jmp far cs:[old_routine] }
               l($2e) ; w(ofs(old_routine))
            end
         else
            l($cf) ;                   { iret }

         { Now we can install the vector to link_code, saving the
           old vector in old_routine. Interrupts are switched off
           for this bit, just in case. }
         inline (CLI) ;
         old_routine := interrupt_vector[interrupt_no] ;
         interrupt_vector[interrupt_no] := addr (link_code) ;
         inline (STI)                  { Interrupts back on }
      end
end ;



procedure RemoveInterruptHandler
   ( interrupt_no : integer ; var workspace : interrupt_workspace ) ;
var
   interrupt_vector : array [0 .. 255] of routine_ptr absolute 0:0 ;
begin
   inline (CLI) ;                      { Interrupts off }
   interrupt_vector[interrupt_no] :=
      workspace.old_routine ;
   inline (STI)                        { and on again }
end ;


