{
     Device driver for IBM 5150 PC's serial port (AUX).
     By J. Eric Roskos
     Public Domain, may not be sold for profit.

     This driver is initialized by calling AuxInit, and MUST be
     deactivated by calling AuxOff before the program exits (or a sub-
     sequent serial interrupt will cause the system to hang).
     It uses serial interrupts, and thus works considerably better than
     the standard AUX device driver supplied with the IBM PC.

     Once AuxInit is called, you can read and write to the predefined
     Turbo Pascal file "Aux" to access the serial port.  The procedure
     AuxSt returns True if a character is presently available at the
     serial port, equivalent to the KeyPressed procedure for the console.
     You don't have to (and shouldn't) call any of the other procedures
     in this file other than AuxInit, AuxOff, and AuxSt.

     Presently, only input is done via interrupts; output is via
     conventional polling, and a write to the serial port will cause
     a busy-wait until any previous character is completely transmitted.
     The input buffer's size is defined by the constant BUFSIZ below,
     which must be a power of 2 (or the wrap-around algorithm won't work).
     The serial port is presently initialized to 1200 baud; if you want
     to set it via the MODE command of DOS instead, you can comment out
     the statements indicated below.

     If the input buffer becomes full, a ^S is sent to the remote machine;
     a ^Q is then sent when it empties sufficiently to resume.  This
     is indeed used at 1200 baud if a number of escape sequences are
     sent in succession.

     Only the top 24 lines of the screen are used, in order to comply with
     most standard terminals.  However, for this to work you have to
     compile the program with Turbo Pascal version 2.0 or later, and use
     an IBM PC version of the compiler (not the generic MS-DOS version).
}

(*

unit IO5150;
interface
     procedure AuxInit;     { Initialize the unit; must be called at start }
     procedure AuxOff;      { Terminates the unit; must be called at exit  }
     procedure AuxSt;       { Returns True if char immed. avail from Aux   }

implementation

*)

{$R-}
{$C-}

const
     BUFSIZ = 256;    { Buffer size -- must be a power of 2 }

     { Port addresses for COM1: you must change these to use COM2 }

     RX     = 1016;   { Receiver Buffer Register            }
     TX     = 1016;   { Transmitter Buffer Register         }
     IE     = 1017;   { Interrupt Enable Register           }
     II     = 1018;   { Interrupt Identification Register   }
     LC     = 1019;   { Line Control Register               }
     MC     = 1020;   { Modem Control Register              }
     LS     = 1021;   { Line Status Register                }
     MS     = 1022;   { Modem Status Register               }
     DLL    = 1016;   { Divisor Latch, Low Order Byte       }
     DLH    = 1017;   { Divisor Latch, High Order Byte      }


type
     { The input buffer structure }

     buffer=record
          buf: packed array[0..BUFSIZ] of char; { The character buffer       }
          ip,op,cnt: integer; { Input pointer, output pointer, char count    }
     end;

     { Turbo Pascal's DOS/BIOS call parameter block }

     regpack=record
          ax,bx,cx,dx,bp,si,di,ds,es,flags: integer;
     end;


var
     Buf:    buffer;                       { The input buffer                }
     AuxOset, AuxSeg: integer;             { Saved orig. serial int vector   }
     SvOset: integer absolute $0000:$0030; { The serial interrupt vector     }
     SvSeg:  integer absolute $0000:$0032; {  "    "        "       "        }
     SavDs:  integer absolute $0000:$002E; { Program's DS addr is saved here }
     Run:    boolean;                      { True while emulator is to run   }
     XedOff: boolean;                      { True if IntSer Xed off remote   }
     c:      char;                         { Current input character         }


{
     Interrupt service routine.

     This routine saves all registers, and then sets up its Data Segment
     from the value saved in 0:$2E by AuxInit (since, contrary to what the
     Turbo manual tells you, the DS may be changed by the ROM BIOS and thus
     may be incorrect on entry to the interrupt routine).  It then gets the
     input character (if the interrupt was due to a received character) and
     stores it in the buffer, handling XOFFs if necessary.
}

procedure IntSer;
var
     i: integer;

begin
     { Save all registers and set up our Data Segment }

     inline($50/$53/$51/$52/$57/$56/$06/$1e);
     inline($06/$50/$B8/$00/$00/$8e/$C0/$26/$8E/$1E/$2E/$00/$58/$07);

     { Turn interrupts back on }

     inline($FB);
     Port[$20] := $20;

     { Process the interrupt }

     case Port[II] of
     0:   i := Port[MS];                           { Modem Status Intr }
     1:   ;                                        { No Interrupt      }
     2:   writeln('Error: THRE interrupt');        { Transmit Intr     }
     4:                                            { Receive Intr      }
          begin
               Buf.buf[Buf.ip] := chr(Port[RX]);
               Buf.ip := (Buf.ip + 1) and (BUFSIZ-1);
               inline($FA); { CLI }
               Buf.cnt := Buf.cnt + 1;
               inline($FB); { STI }
               if (Buf.cnt >= BUFSIZ-25) and not Xedoff then
               begin
                    Xedoff := true;
                    Port[TX] := ord(^S);
               end;
          end;
     6:   i := Port[LS];                           { Line Status Intr  }
     end {case};

     { Restore saved registers and do IRET }

     inline($1F/$07/$5E/$5F/$5A/$59/$5B/$58/$8B/$E5/$5D/$CF);
end;


{
     AUX port status.  Returns True if there is a character available
     for reading, same as KeyPressed for console.
}

function AuxSt: boolean;
begin
     AuxSt := (Buf.cnt > 0);
end;


{
     AUX input routine.  Not called by the user: called by the Turbo
     runtime system when you do a read from the Aux file.
}

function AuxIn: char;
begin
     while Buf.cnt = 0 do ;

     AuxIn := Buf.buf[Buf.op];
     Buf.op := (Buf.op + 1) and (BUFSIZ-1);

     inline($fa); { CLI }
     Buf.cnt := Buf.cnt - 1;
     inline($fb); { STI }

     if (Buf.cnt < 25) and Xedoff then
     begin
          Xedoff := false;
          Port[TX] := ord(^Q);
     end;
end;


{
     AUX port output routine.  Not called by the user: called by the
     Turbo runtime system when you do a write to the Aux file.
}

procedure AuxOut(c:char);
begin
     while (Port[LS] and $20) = 0 do ; { Busy Wait }
     Port[TX] := ord(c);
end;


{
     AUX device driver initialization.  You must call this before accessing
     the Aux file if you want to use these device drivers.
}

procedure AuxInit;
begin
     inline($fa); { CLI }

     { Initialize interrupt vector and 8259A }

     SavDs     := Dseg;
     AuxOset   := SvOset;
     AuxSeg    := SvSeg;
     SvOset    := ofs(IntSer);
     SvSeg     := Cseg;
     Port[$21] := $ac;   { enable ints 8, 9, C, E }

     { Initialize 8250 UART }
     { Comment the starred lines out if you want to use MODE to init AUX: }

     Port[MC]  := $00;  { *** } { Negate DTR and RTS             }
     Port[LC]  := $80;  { *** } { Set baud rate to 1200 baud     }
     Port[DLL] := $60;  { *** } { Use $80 here for 300 baud      }
     Port[DLH] := $00;  { *** } { Use $01 here for 300 baud      }
     Port[LC]  := $2a;  { *** } { Set stick parity 7 data 1 stop }
     Port[IE]  := $05;          { Turn on interrupts             }
     Port[MC]  := $0b;          { Turn on int gate, DTR, and RTS }

     { Initialize buffer }

     Buf.ip  := 0;
     Buf.op  := 0;
     Buf.cnt := 0;
     Xedoff  := false;

     { Initialize I/O system }

     AuxInPtr  := ofs(AuxIn);
     AuxOutPtr := ofs(AuxOut);

     inline($fb); { STI }
end;


{
     Reset the AUX port to non-interrupt mode.  You MUST call this routine
     before you exit.
}

procedure AuxOff;
begin
     inline($fa); { CLI }

     SvOset := AuxOset;{ Restore serial int vector to its original value  }
     SvSeg  := AuxSeg;

     Port[IE]  := $00; { Turn off UART's interrupt enables                }
     Port[MC]  := $00; { Negate RTS and CTS, and turn off interrupt gate  }
     Port[$21] := $BC; { Set 8259 PIC back to disabling serial interrupts }
     Port[$20] := $20; { Send EOI to PIC                                  }

     inline($fb); { STI }
end;



