{
 ßßßßßßßß  ßßßßßß    ßß   ßß
   ßß     ßß   ßß   ßß  ßß
  ßß     ßß   ßßß  ßßßßß  The DoorKit!
 ßß     ßß   ßß   ßß  ßß
ßß     ßßßßßß    ßß    ßß
The BBS Door Development Kit By The People - For The People!


   Feel free to modify or optimize this code at will. All I ask is that if
   find a better way to do things (and you will), please send me a copy of
   your modifications. Thanks in advance!....Larry L. Athey....}

{*--------------------------------------------------------------------------*}
{*                                                                          *}
{*  Status byte definition (C_Status):                                      *}
{*                                                                          *}
{*  7   6   5   4   3   2   1   0                                           *}
{*  |   |   |   |   |   |   |   |____ Input buffer empty                    *}
{*  |   |   |   |   |   |   |________ Input buffer full                     *}
{*  |   |   |   |   |   |____________ Output buffer empty                   *}
{*  |   |   |   |   |________________ Output buffer full                    *}
{*  |   |   |   |____________________ Input buffer overflow                 *}
{*  |   |   |________________________ Output buffer overflow                *}
{*  |   |____________________________ Hard handshake active (xmit stopped)  *}
{*  |________________________________ Soft handshake active (xmit stopped)  *}
{*                                                                          *}
{*  Control byte definition (C_Ctrl):                                       *}
{*                                                                          *}
{*  7   6   5   4   3   2   1   0                                           *}
{*  |   |   |   |   |   |   |   |____ Enable RTS handshake                  *}
{*  |   |   |   |   |   |   |________ Enable CTS handshake                  *}
{*  |   |   |   |   |   |____________ Enable software handshake             *}
{*  |   |   |   |   |________________                                       *}
{*  |   |   |   |____________________                                       *}
{*  |   |   |________________________                                       *}
{*  |   |____________________________                                       *}
{*  |________________________________                                       *}
{*                                                                          *}
{****************************************************************************}

{$A+,B-,D+,E+,F+,G+,I-,L+,N-,O+,P-,Q-,R-,S-,T-,V+,X+}
UNIT ASYNC;

INTERFACE

{----------------------------------------------------------------------------}

CONST
  C_MinBaud  = 300;
  C_MaxBaud  = 115200;
  C_MaxPort  = 4;
  C_MaxCom   : BYTE = C_MaxPort;
  D_PortAddr : ARRAY[1..C_MaxPort] OF WORD = ($03F8,$02F8,$03E8,$02E8);
  D_PortInt  : ARRAY[1..C_MaxPort] OF BYTE = (4,3,4,3);

{----------------------------------------------------------------------------}

TYPE
  C_VectorArray  = ARRAY[0..15] OF POINTER;
  C_PointerArray = ARRAY[1..C_MaxPort]  OF POINTER;
  C_WordArray    = ARRAY[1..C_MaxPort] OF WORD;
  C_ByteArray    = ARRAY[1..C_MaxPort] OF BYTE;
  C_CharArray    = ARRAY[1..C_MaxPort] OF CHAR;
  C_BooleanArray = ARRAY[1..C_MaxPort] OF BOOLEAN;

{----------------------------------------------------------------------------}

VAR
  { Base port addresses & interrupt usage }
  C_PortAddr : ARRAY[1..C_MaxPort] OF WORD;
  C_PortInt  : ARRAY[1..C_MaxPort] OF BYTE;
  ComPort  : BYTE;
  C_InBufPtr,C_OutBufPtr : C_PointerArray;    { Input/output buffer pointers }
  C_InHead,C_OutHead     : C_WordArray;       { Input/output head pointers }
  C_InTail,C_OutTail     : C_WordArray;       { Input/output tail pointers }
  C_InSize,C_OutSize     : C_WordArray;       { Input/output buffer sizes }
  C_RTSOn,C_RTSOff       : C_WordArray;       { RTS assert/drop buffer points }
  C_StartChar,C_StopChar : C_CharArray;       { Soft hndshake start/stop char }
  C_Status,C_Ctrl        : C_ByteArray;       { STATUS and CONTROL registers }
  C_XL3Ptr               : C_ByteArray;
  C_PortOpen             : C_BooleanArray;    { Port open/close flags }
  C_Temp                 : WORD;              { Used for debugging }
  C_msrport              : WORD;
{ RTSOn,RTSOff           : Word;}             { RTS assert/drop buffer points }
  oldier,oldmcr          : BYTE;
  c_buffull              : c_wordarray;
  C_Cascade              : BYTE;              { Flag set 0 normally }
  C_CascadeOK            : BOOLEAN;           { Flag if IRQ > 7 }

{----------------------------------------------------------------------------}

FUNCTION  ComReadCh(ComPort : BYTE) : CHAR;
FUNCTION  ComReadChW(ComPort : BYTE) : CHAR;
{Procedure ComWriteCh(ComPort:Byte; Ch:Char); }
PROCEDURE ComWriteChW(ComPort : BYTE; Ch : CHAR);
PROCEDURE SetDTR(ComPort : BYTE; Assert : BOOLEAN);
PROCEDURE SetRTS(ComPort : BYTE; Assert : BOOLEAN);
{Procedure SetOUT1(ComPort:Byte; Assert:Boolean);
Procedure SetOUT2(ComPort:Byte; Assert:Boolean);}
FUNCTION  CTSStat(ComPort : BYTE) : BOOLEAN;
FUNCTION  RTSStat(ComPort : BYTE) : BOOLEAN;
FUNCTION  DSRStat(ComPort : BYTE) : BOOLEAN;
FUNCTION  RIStat(ComPort : BYTE) : BOOLEAN;
FUNCTION  DCDStat(ComPort : BYTE) : BOOLEAN;
PROCEDURE SetRTSMode(ComPort : BYTE; Mode : BOOLEAN; RTSOn,RTSOff : WORD);
PROCEDURE SetCTSMode(ComPort : BYTE; Mode : BOOLEAN);
PROCEDURE SoftHandshake(ComPort : BYTE; Mode : BOOLEAN; Start,Stop : CHAR);
PROCEDURE ClearCom(ComPort : BYTE; IO : CHAR);
FUNCTION  ComBufferLeft(ComPort : BYTE; IO : CHAR) : WORD;
PROCEDURE ComWaitForClear(ComPort : BYTE);
PROCEDURE ComWrite(ComPort : BYTE; St : STRING);
PROCEDURE ComWriteln(ComPort : BYTE; St : STRING);
PROCEDURE ComWriteWithDelay(ComPort : BYTE; St : STRING; Dly : WORD);
PROCEDURE ComReadln(ComPort : BYTE; VAR St : STRING; Size : BYTE; Echo : BOOLEAN);
FUNCTION  ComExist(ComPort : BYTE) : BOOLEAN;
FUNCTION  ComTrueBaud(Baud : LONGINT) : REAL;
PROCEDURE ComParams(ComPort : BYTE; Baud : LONGINT; WordSize : BYTE; Parity : CHAR; StopBits : BYTE);
FUNCTION  OpenCom(ComPort : BYTE; InBufferSize,OutBufferSize : WORD) : BOOLEAN;
PROCEDURE CloseCom(ComPort : BYTE);
PROCEDURE CloseAllComs;

{----------------------------------------------------------------------------}

IMPLEMENTATION

Uses DOS,CRT;

{$L ASYNC.OBJ}

CONST
  C_IER = 1;                           { 8250 register offsets }
  C_IIR = 2;
  C_LCR = 3;
  C_MCR = 4;
  C_LSR = 5;
  C_MSR = 6;
  C_SCR = 7;

VAR
  C_OldINTVec : C_VectorArray;        { Storage for old hardware INT vectors }
  X : BYTE;                            { Used by initialization code }

{****************************************************************************}
{*                                                                          *}
{*  Procedure INT_Handler; External;                                        *}
{*                                                                          *}
{*  Hardware interrupts 0-15 (vectors $08 - $0F,$70 - $77) are pointed to   *}
{*  this routine.  It is for internal use only and should NOT be called     *}
{*  directly.  Written in assembly language (see SLASYNC.ASM).              *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE INT_Handler; EXTERNAL;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComReadCh(ComPort:Byte) : Char; External;                     *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*                                                                          *}
{*  Returns character from input buffer of specified port.  If the buffer   *}
{*  is empty, the port # invalid or not opened, a Chr(0) is returned.       *}
{*  Written in assembly language for best possible speed (see ASYNC11.ASM)  *}
{*                                                                          *}
{****************************************************************************}

FUNCTION ComReadCh(ComPort : BYTE) : CHAR; EXTERNAL;

{****************************************************************************}
{*                                                                          *}
{*  Function ComReadChW(ComPort:Byte) : Char; External;                     *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*                                                                          *}
{*  Works like ComReadCh, but will wait until at least 1 character is       *}
{*  present in the specified input buffer before exiting.  Thus, ComReadChW *}
{*  works much like the ReadKey predefined function.  Written in assembly   *}
{*  language to maximize performance (see ASYNC11.ASM)                      *}
{*                                                                          *}
{****************************************************************************}

FUNCTION ComReadChW(ComPort : BYTE) : CHAR; EXTERNAL;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComWriteCh(ComPort:Byte; Ch:Char); External                   *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*  Ch:Char       ->  Character to send                                     *}
{*                                                                          *}
{*  Places the character [Ch] in the transmit buffer of the specified port. *}
{*  If the port specified is not open or nonexistent, or if the buffer is   *}
{*  filled, the character is discarded.  Written in assembly language to    *}
{*  maximize performance (see ASYNC11.ASM)                                  *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ComWriteCh(ComPort : BYTE; Ch : CHAR); EXTERNAL;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComWriteChW(ComPort:Byte; Ch:Char); External;                 *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*  Ch:Char       ->  Character to send                                     *}
{*                                                                          *}
{*  Works as ComWriteCh, but will wait until at least 1 free position is    *}
{*  available in the output buffer before attempting to place the character *}
{*  [Ch] in it.  Allows the programmer to send characters without regard to *}
{*  available buffer space.  Written in assembly language to maximize       *}
{*  performance (see ASYNC11.ASM)                                           *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ComWriteChW(ComPort : BYTE; Ch : CHAR); EXTERNAL;

{****************************************************************************}
{*                                                                          *}
{*  Procedure SetDTR(ComPort:Byte; Assert:Boolean);                         *}
{*                                                                          *}
{*  ComPort:Byte    ->  Port # to use (1 - C_MaxCom)                        *}
{*                      Call ignored if out-of-range                        *}
{*  Assert:Boolean  ->  DTR assertion flag (TRUE to assert DTR)             *}
{*                                                                          *}
{*  Provides a means to control the port's DTR (Data Terminal Ready) signal *}
{*  line.  When [Assert] is TRUE, the DTR line is placed in the "active"    *}
{*  state, signalling to a remote system that the host is "on-line"         *}
{*  (although not nessesarily ready to receive data - see SetRTS).          *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE SetDTR(ComPort : BYTE; Assert : BOOLEAN);

VAR
  P,X : INTEGER;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) THEN EXIT;
  P := C_PortAddr[ComPort];

  X := Port[P + C_MCR];
  IF Assert THEN
    X := X OR $01
  ELSE
    X := X AND $FE;
  Port[P + C_MCR] := X;
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure SetRTS(ComPort:Byte; Assert:Boolean)                          *}
{*                                                                          *}
{*  ComPort:Byte    ->  Port # to use (1 - C_MaxCom)                        *}
{*                      Call ignored if out-of-range                        *}
{*  Assert:Boolean  ->  RTS assertion flag (Set TRUE to assert RTS)         *}
{*                                                                          *}
{*  SetRTS allows a program to manually control the Request-To-Send (RTS)   *}
{*  signal line.  If RTS handshaking is disabled (see C_Ctrl definition     *}
{*  and the the SetRTSMode procedure), this procedure may be used.  SetRTS  *}
{*  should NOT be used if RTS handshaking is enabled.                       *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE SetRTS(ComPort : BYTE; Assert : BOOLEAN);

VAR
  P,X : INTEGER;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) THEN EXIT;
  P := C_PortAddr[ComPort];

  X := Port[P + C_MCR];
  IF Assert THEN
    X := X OR $02
  ELSE
    X := X AND $FD;
  Port[P + C_MCR] := X;
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure SetOUT1(ComPort:Byte; Assert:Boolean)                         *}
{*                                                                          *}
{*  ComPort:Byte    ->  Port # to use (1 - C_MaxCom)                        *}
{*                      Call ignored if out-of-range                        *}
{*  Assert:Boolean  ->  OUT1 assertion flag (set TRUE to assert OUT1 line)  *}
{*                                                                          *}
{*  SetOUT1 is provided for reasons of completeness only, since the         *}
{*  standard PC/XT/AT configurations do not utilize this control signal.    *}
{*  If [Assert] is TRUE, the OUT1 signal line on the 8250 will be set to a  *}
{*  LOW logic level (inverted logic).  The OUT1 signal is present on pin 34 *}
{*  of the 8250 (but not on the port itself).                               *}
{*                                                                          *}
{****************************************************************************}
{
Procedure SetOUT1(ComPort:Byte; Assert:Boolean);

Var
  P,X : Integer;

Begin
  If (ComPort<1) Or (ComPort>C_MaxCom) Then Exit;
  P := C_PortAddr[ComPort];

  X := Port[P+C_MCR];
  If Assert Then
    X := X Or $04
  Else
    X := X And $FB;
  Port[P+C_MCR] := X;
End;
 }
{****************************************************************************}
{*                                                                          *}
{*  Procedure SetOUT2(ComPort:Byte; Assert:Boolean)                         *}
{*                                                                          *}
{*  ComPort:Byte    ->  Port # to use (1 - C_MaxCom)                        *}
{*                      Call ignored if out-of-range                        *}
{*  Assert:Boolean  ->  OUT2 assertion flag (set TRUE to assert OUT2 line)  *}
{*                                                                          *}
{*  The OUT2 signal line, although not available on the port itself, is     *}
{*  used to gate the 8250 <INTRPT> (interrupt) line and thus acts as a      *}
{*  redundant means of controlling 8250 interrupts.  When [Assert] is TRUE, *}
{*  the /OUT2 line on the 8250 is lowered, which allows the passage of the  *}
{*  <INTRPT> signal through a gating arrangement, allowing the 8250 to      *}
{*  generate interrupts.  Int's can be disabled bu unASSERTing this line.   *}
{*                                                                          *}
{****************************************************************************}
{
Procedure SetOUT2(ComPort:Byte; Assert:Boolean);

Var
  P,X : Integer;

Begin
  If (ComPort<1) Or (ComPort>C_MaxCom) Then Exit;
  P := C_PortAddr[ComPort];

  X := Port[P+C_MCR];
  If Assert Then
    X := X Or $08
  Else
    X := X And $F7;
  Port[P+C_MCR] := X;
End;
 }
{****************************************************************************}
{*                                                                          *}
{*  Function CTSStat(ComPort:Byte) : Boolean                                *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*                    Call ignored if out-of-range                          *}
{*  Returns status of Clear-To-Send line (TRUE if CTS asserted)             *}
{*                                                                          *}
{*  CTSStat provides a means to interrogate the Clear-To-Send hardware      *}
{*  handshaking line.  In a typical arrangement, when CTS is asserted, this *}
{*  signals the host (this computer) that the receiver is ready to accept   *}
{*  data (in contrast to the DSR line, which signals the receiver as        *}
{*  on-line but not nessesarily ready to accept data).  An automated mech-  *}
{*  ansim (see CTSMode) is provided to do this, but in cases where this is  *}
{*  undesirable or inappropriate, the CTSStat function can be used to int-  *}
{*  terrogate this line manually.                                           *}
{*                                                                          *}
{****************************************************************************}

FUNCTION CTSStat(ComPort : BYTE) : BOOLEAN;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) THEN
    CTSStat := FALSE
  ELSE
    CTSStat := (Port[C_PortAddr[ComPort] + C_MSR] AND $10 <> $10);
END;

{****************************************************************************}
{*                                                                          *}
{*  Function RTSStat(ComPort:Byte) : Boolean                                *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*                    Call ignored if out-of-range                          *}
{*  Returns status of Ready-To-Send line (TRUE if RTS asserted)             *}
{*                                                                          *}
{****************************************************************************}


FUNCTION RTSStat(ComPort : BYTE) : BOOLEAN;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) THEN
    RTSStat := FALSE
  ELSE
    RTSStat := (Port[C_PortAddr[ComPort] + C_LSR] AND $20 <> $20);
END;

{****************************************************************************}
{*                                                                          *}
{*  Function DSRStat(ComPort:Byte) : Boolean                                *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*                    Call ignored if out-of-range                          *}
{*  Returns status of Data Set Ready (DSR) signal line.                     *}
{*                                                                          *}
{*  The Data Set Ready (DSR) line is typically used by a remote station     *}
{*  to signal the host system that it is on-line (although not nessesarily  *}
{*  ready to receive data yet - see CTSStat).  A remote station has the DSR *}
{*  line asserted if DSRStat returns TRUE.                                  *}
{*                                                                          *}
{****************************************************************************}

FUNCTION DSRStat(ComPort : BYTE) : BOOLEAN;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) THEN
    DSRStat := FALSE
  ELSE
    DSRStat := (Port[C_PortAddr[ComPort] + C_MSR] AND $20) > 0;
END;

{****************************************************************************}
{*                                                                          *}
{*  Function RIStat(ComPort:Byte) : Boolean                                 *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*                    Call ignored if out-of-range                          *}
{*                                                                          *}
{*  Returns the status of the Ring Indicator (RI) line.  This line is       *}
{*  typically used only by modems, and indicates that the modem has detect- *}
{*  ed an incoming call if RIStat returns TRUE.                             *}
{*                                                                          *}
{****************************************************************************}

FUNCTION RIStat(ComPort : BYTE) : BOOLEAN;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) THEN
    RIStat := FALSE
  ELSE
    RIStat := (Port[C_PortAddr[ComPort] + C_MSR] AND $40) > 0;
END;

{****************************************************************************}
{*                                                                          *}
{*  Function DCDStat(ComPort:Byte) : Boolean                                *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*                    Call ignored if out-of-range                          *}
{*                                                                          *}
{*  Returns the status of the Data Carrier Detect (DCD) line from the rem-  *}
{*  ote device, typically a modem.  When asserted (DCDStat returns TRUE),   *}
{*  the modem indicates that it has successfuly linked with another modem   *}
{*  device at another site.                                                 *}
{*                                                                          *}
{****************************************************************************}

FUNCTION DCDStat(ComPort : BYTE) : BOOLEAN;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) THEN
    DCDStat := FALSE
  ELSE
    DCDStat := (Port[C_PortAddr[ComPort] + C_MSR] AND $80) > 0;
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure SetRTSMode(ComPort:Byte; Mode:Boolean; RTSOn,RTSOff:Word)     *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Request ignored if out of range or unopened.          *}
{*  Mode:Boolean  ->  TRUE to enable automatic RTS handshake                *}
{*  RTSOn:Word    ->  Buffer-usage point at which the RTS line is asserted  *}
{*  RTSOff:Word   ->  Buffer-usage point at which the RTS line is dropped   *}
{*                                                                          *}
{*  SetRTSMode enables or disables automated RTS handshaking.  If [MODE] is *}
{*  TRUE, automated RTS handshaking is enabled.  If enabled, the RTS line   *}
{*  will be DROPPED when the # of buffer bytes used reaches or exceeds that *}
{*  of [RTSOff].  The RTS line will then be re-asserted when the buffer is  *}
{*  emptied down to the [RTSOn] usage point.  If either [RTSOn] or [RTSOff] *}
{*  exceeds the input buffer size, they will be forced to (buffersize-1).   *}
{*  If [RTSOn] > [RTSOff] then [RTSOn] will be the same as [RTSOff].        *}
{*  The actual handshaking control is located in the interrupt driver for   *}
{*  the port (see ASYNC11.ASM).                                             *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE SetRTSMode(ComPort : BYTE; Mode : BOOLEAN; RTSOn,RTSOff : WORD);

VAR
  X : BYTE;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxPort) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  X := C_Ctrl[ComPort];
  IF Mode THEN X := X OR $01 ELSE X := X AND $FE;
  C_Ctrl[ComPort] := X;

  IF Mode THEN
    BEGIN
      IF (RTSOff >= C_InSize[ComPort]) THEN RTSOff := C_InSize[ComPort] - 1;
      IF (RTSOn > RTSOff) THEN RTSOff := RTSOn;
      C_RTSOn[ComPort] := RTSOn;
      C_RTSOff[ComPort] := RTSOff;
    END;
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure SetCTSMode(ComPort:Byte; Mode:Boolean)                        *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Request ignored if out of range or unopened.          *}
{*  Mode:Boolean  ->  Set to TRUE to enable automatic CTS handshake.        *}
{*                                                                          *}
{*  SetCTSMode allows the enabling or disabling of automated CTS handshak-  *}
{*  ing.  If [Mode] is TRUE, CTS handshaking is enabled, which means that   *}
{*  if the remote drops the CTS line, the transmitter will be disabled      *}
{*  until the CTS line is asserted again.  Automatic handshake is disabled  *}
{*  if [Mode] is FALSE.  CTS handshaking and "software" handshaking (pro-   *}
{*  vided by the SoftHandshake procedure) ARE compatable and may be used    *}
{*  in any combination.  The actual logic for CTS handshaking is located    *}
{*  in the communications interrupt driver (see ASYNC11.ASM).               *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE SetCTSMode(ComPort : BYTE; Mode : BOOLEAN);

VAR
  X : BYTE;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxPort) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  X := C_Ctrl[ComPort];
  IF Mode THEN X := X OR $02 ELSE X := X AND $FD;
  C_Ctrl[ComPort] := X;
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure SoftHandshake(ComPort:Byte; Mode:Boolean; Start,Stop:Char)    *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Request ignored if out of range or unopened.          *}
{*  Mode:Boolean  ->  Set to TRUE to enable transmit software handshake     *}
{*  Start:Char    ->  START control character (usually ^Q)                  *}
{*                    Defaults to ^Q if character passed is >= <Space>      *}
{*  Stop:Char     ->  STOP control character (usually ^S)                   *}
{*                    Defaults to ^S if character passed is >= <Space>      *}
{*                                                                          *}
{*  SoftHandshake controls the usage of "Software" (control-character)      *}
{*  handshaking on transmission.  If "software handshake" is enabled        *}
{*  ([Mode] is TRUE), transmission will be halted if the character in       *}
{*  [Stop] is received.  Transmission is re-enabled if the [Start] char-    *}
{*  acter is received.  Both the [Start] and [Stop] characters MUST be      *}
{*  CONTROL characters (i.e. Ord(Start) and Ord(Stop) must both be < 32).   *}
{*  Also, <Start> and <Stop> CANNOT be the same character.  If either one   *}
{*  of these restrictions are violated, the defaults (^Q for <Start> and ^S *}
{*  for <Stop>) will be used.  Software handshaking control is implimented  *}
{*  within the communications interrupt driver (see ASYNC11.ASM).           *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE SoftHandshake(ComPort : BYTE; Mode : BOOLEAN; Start,Stop : CHAR);

VAR
  X : BYTE;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxPort) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  X := C_Ctrl[ComPort];
  IF Mode THEN
    BEGIN
      X := X OR $04;
      IF Start = Stop THEN BEGIN Start := ^Q; Stop := ^S; END;
      IF Start > #32 THEN Start := ^Q;
      IF Stop > #32 THEN Stop := ^S;
      C_StartChar[ComPort] := Start;
      C_StopChar[ComPort] := Stop;
    END
  ELSE
    X := X AND $FB;
  C_Ctrl[ComPort] := X;
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ClearCom(ComPort:Byte); IO:Char)                              *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Request ignored if out of range or unopened.          *}
{*  IO:Char       ->  Action code; I=Input, O=Output, B=Both                *}
{*                    No action taken if action code unrecognized.          *}
{*                                                                          *}
{*  ClearCom allows the user to completely clear the contents of either     *}
{*  the input (receive) and/or output (transmit) buffers.  The "action      *}
{*  code" passed in <IO> determines if the input (I) or output (O) buffer   *}
{*  is cleared.  Action code (B) will clear both buffers.  This is useful   *}
{*  if you wish to cancel a transmitted message or ignore part of a         *}
{*  received message.                                                       *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ClearCom(ComPort : BYTE; IO : CHAR);

VAR
  P,X : WORD;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  IO := UPCASE(IO);
  P := C_PortAddr[ComPort];

  INLINE($FA);
  IF (IO = 'I') OR (IO = 'B') THEN
    BEGIN
      C_InHead[ComPort] := 0;
      C_InTail[ComPort] := 0;
      C_Status[ComPort] := (C_Status[ComPort] AND $EC) OR $01;
      X := Port[P] + Port[P + C_LSR] + Port[P + C_MSR] + Port[P + C_IIR];
    END;
  IF (IO = 'O') OR (IO = 'B') THEN
    BEGIN
      C_OutHead[ComPort] := 0;
      C_OutTail[ComPort] := 0;
      C_Status[ComPort] := (C_Status[ComPort] AND $D3) OR $04;
      X := Port[P + C_LSR] + Port[P + C_MSR] + Port[P + C_IIR];
    END;
  INLINE($FB);
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComBufferLeft(ComPort:Byte; IO:Char) : Word                   *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Returns 0 if Port # invalid or unopened.              *}
{*  IO:Char       ->  Action code; I=Input, O=Output                        *}
{*                    Returns 0 if action code unrecognized.                *}
{*                                                                          *}
{*  ComBufferLeft will return a number (bytes) indicating how much space    *}
{*  remains in the selected buffer.  The INPUT buffer is checked if <IO> is *}
{*  (I), and the output buffer is interrogated when <IO> is (O).  Any other *}
{*  "action code" will return a result of 0.  Use this function when it is  *}
{*  important to avoid program delays due to calls to output procedures or  *}
{*  to prioritize the reception of data (to prevent overflows).             *}
{*                                                                          *}
{****************************************************************************}

FUNCTION ComBufferLeft(ComPort : BYTE; IO : CHAR) : WORD;

BEGIN
  ComBufferLeft := 0;
  IF (ComPort < 1) OR (ComPort > C_MaxCom) OR (NOT C_PortOpen[ComPort]) THEN EXIT;
  IO := UPCASE(IO);

  IF IO = 'I' THEN
    IF C_InHead[ComPort] >= C_InTail[ComPort] THEN
      ComBufferLeft := C_InSize[ComPort] - (C_InHead[ComPort] - C_InTail[ComPort])
    ELSE
      ComBufferLeft := C_InTail[ComPort] - C_InHead[ComPort];

  IF IO = 'O' THEN
    IF C_OutHead[ComPort] >= C_OutTail[ComPort] THEN
      ComBufferLeft := C_OutHead[ComPort] - C_OutTail[ComPort]
    ELSE
      ComBufferLeft := C_OutSize[ComPort] - (C_OutTail[ComPort] - C_OutHead[ComPort]);
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComWaitForClear(ComPort:Byte)                                 *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Exits immediately if out of range or port unopened.   *}
{*                                                                          *}
{*  A call to ComWaitForClear will stop processing until the selected out-  *}
{*  put buffer is completely emptied.  Typically used just before a call    *}
{*  to the CloseCom procedure to prevent premature cut-off of messages in   *}
{*  transit.                                                                *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ComWaitForClear(ComPort : BYTE);

VAR
  Empty : BOOLEAN;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) OR (NOT C_PortOpen[ComPort]) THEN EXIT;
  REPEAT
    Empty := (C_Status[ComPort] AND $04) = $04;
    Empty := Empty AND ((Port[C_PortAddr[ComPort] + C_IER] AND $02) = $00);
  UNTIL Empty;
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComWrite(ComPort:Byte; St:String)                             *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Exits immediately if out of range or port unopened.   *}
{*  St:String     ->  String to send                                        *}
{*                                                                          *}
{*  Sends string <St> out communications port <ComPort>.                    *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ComWrite(ComPort : BYTE; St : STRING);

VAR
  X : BYTE;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  FOR X := 1 TO LENGTH(St) DO
    ComWriteChW(ComPort,St[X]);
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComWriteln(ComPort:Byte; St:String);                          *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Exits immediately if out of range or port unopened.   *}
{*  St:String     ->  String to send                                        *}
{*                                                                          *}
{*  Sends string <St> with a CR and LF appended.                            *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ComWriteln(ComPort : BYTE; St : STRING);

VAR
  X : BYTE;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  FOR X := 1 TO LENGTH(St) DO
    ComWriteChW(ComPort,St[X]);
  ComWriteChW(ComPort,#13);
  ComWriteChW(ComPort,#10);
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComWriteWithDelay(ComPort:Byte; St:String; Dly:Word);         *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Exits immediately if out of range or port unopened.   *}
{*  St:String     ->  String to send                                        *}
{*  Dly:Word      ->  Time, in milliseconds, to delay between each char.    *}
{*                                                                          *}
{*  ComWriteWithDelay will send string <St> to port <ComPort>, delaying     *}
{*  for <Dly> milliseconds between each character.  Useful for systems that *}
{*  cannot keep up with transmissions sent at full speed.                   *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ComWriteWithDelay(ComPort : BYTE; St : STRING; Dly : WORD);

VAR
  X : BYTE;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxCom) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  ComWaitForClear(ComPort);
  FOR X := 1 TO LENGTH(St) DO
    BEGIN
      ComWriteChW(ComPort,St[X]);
      ComWaitForClear(ComPort);
      DELAY(Dly);
    END;
END;

{****************************************************************************}
{*                                                                          *}
{* Procedure ComReadln(ComPort:Byte; Var St:String; Size:Byte; Echo:Boolean)*}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom).                         *}
{*                    Exits immediately if out of range or port unopened.   *}
{*  St:String     <-  Edited string from remote                             *}
{*  Size:Byte;    ->  Maximum allowable length of input                     *}
{*  Echo:Boolean; ->  Set TRUE to echo received characters                  *}
{*                                                                          *}
{*  ComReadln is the remote equivalent of the standard Pascal READLN pro-   *}
{*  cedure with some enhancements.  ComReadln will accept an entry of up to *}
{*  40 printable ASCII characters, supporting ^H and ^X editing commands.   *}
{*  Echo-back of the entry (for full-duplex operation) is optional.  All    *}
{*  control characters, as well as non-ASCII (8th bit set) characters are   *}
{*  stripped.  If <Echo> is enabled, ASCII BEL (^G) characters are sent     *}
{*  when erroneous characters are intercepted.  Upon receipt of a ^M (CR),  *}
{*  the procedure is terminated and the final string result returned.       *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ComReadln(ComPort : BYTE; VAR St : STRING; Size : BYTE; Echo : BOOLEAN);

VAR
  Len,X : BYTE;
  Ch : CHAR;
  Done : BOOLEAN;

BEGIN
  St := '';
  IF (ComPort < 1) OR (ComPort > C_MaxCom) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  Done := FALSE;
  REPEAT
    Len := LENGTH(St);
    Ch := CHR(ORD(ComReadChW(ComPort)) AND $7F);

    CASE Ch OF
      ^H : IF Len > 0 THEN
             BEGIN
               DEC(Len);
               St[0] := CHR(Len);
               IF Echo THEN ComWrite(ComPort,#8#32#8);
             END
           ELSE
             ComWriteChW(ComPort,^G);
      ^M : BEGIN
             Done := TRUE;
             IF Echo THEN ComWrite(ComPort,#13#10);
           END;
      ^X : BEGIN
             St := '';
             IF Len = 0 THEN ComWriteCh(ComPort,^G);
             IF Echo THEN
               FOR X := 1 TO Len DO
                 ComWrite(ComPort,#8#32#8);
           END;
      #32..#127 : IF Len < Size THEN
                    BEGIN
                      INC(Len);
                      St[Len] := Ch;
                      St[0] := CHR(Len);
                      IF Echo THEN ComWriteChW(ComPort,Ch);
                    END
                  ELSE
                    IF Echo THEN ComWriteChW(ComPort,^G);
    ELSE
      IF Echo THEN ComWriteChW(ComPort,^G)
    END;

  UNTIL Done;
END;

{****************************************************************************}
{*                                                                          *}
{*  Function ComExist(ComPort:Byte) : Boolean                               *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to use (1 - C_MaxCom)                          *}
{*                    Returns FALSE if out of range                         *}
{*  Returns TRUE if hardware for selected port is detected & tests OK       *}
{*                                                                          *}
{*  Function ComExist performs a high-speed short loopback test on the      *}
{*  selected port to determine if it indeed exists.  Use this function      *}
{*  before attempts to OPEN a port for I/O (although this function is       *}
{*  called by OpenCom to prevent such an occurance).                        *}
{*  NOTE!  Although pains are taken to preserve the 8250 state before the   *}
{*  port test takes place, it is nonetheless recommended that this function *}
{*  NOT be called while a port is actually OPEN.  Doing so may cause the    *}
{*  port to behave erratically.                                             *}
{*                                                                          *}
{****************************************************************************}

FUNCTION ComExist(ComPort : BYTE) : BOOLEAN;

CONST
  TestByte1 : BYTE = $0F;
  TestByte2 : BYTE = $F1;

VAR
  P : WORD;
  M,L,B1,B2 : BYTE;

BEGIN
  ComExist := FALSE;
  IF (ComPort < 1) OR (ComPort > C_MaxPort) THEN EXIT;

  P := C_PortAddr[ComPort];
  M := Port[P + C_MCR];                            { Save MCR }
  L := Port[P + C_LCR];                            { Save LCR }
  Port[P + C_MCR] := $10;                          { Enable loopback mode }
  Port[P + C_LCR] := $80;                          { Enable divisor latch mode }
  B1 := Port[P];                                 { Save current baud rate }
  B2 := Port[P + 1];
  Port[P] := 4;                                  { Set baud rate to 28000 }
  Port[P + 1] := 0;
  Port[P + C_LCR] := $03;                          { Transmit mode 28000:8N1 }

  Port[P] := TestByte1;                          { Test byte #1 }
  DELAY(20);                                     { Wait a bit for loopback }
  IF Port[P] <> TestByte1 THEN EXIT;             { Exit w/error if not echoed }
  Port[P] := TestByte2;                          { Test byte #2 }
  DELAY(20);                                     { Wait a bit for loopback }
  IF Port[P] <> TestByte2 THEN EXIT;             { Exit w/error if not echoed }

  ComExist := TRUE;                              { Test passed: Port exists }
  Port[P + C_LCR] := $80;                          { Restore baud rate }
  Port[P] := B1;
  Port[P + 1] := B2;
  Port[P + C_LCR] := L;                            { Restore parameters }
  Port[P + C_MCR] := M;                            { Restore control lines }
END;

{****************************************************************************}
{*                                                                          *}
{*  Function ComTrueBaud(Baud:Longint) : Real                               *}
{*                                                                          *}
{*  Baud:Longint  ->  User baud rate to test.                               *}
{*                    Should be between C_MinBaud and C_MaxBaud.            *}
{*  Returns the actual baud rate based on the accuracy of the 8250 divider. *}
{*                                                                          *}
{*  The ASYNC11 communications package allows the programmer to select ANY  *}
{*  baud rate, not just those that are predefined by the BIOS or other      *}
{*  agency.  Since the 8250 uses a divider/counter chain to generate it's   *}
{*  baud clock, many non-standard baud rates can be generated.  However,    *}
{*  the binary counter/divider is not always capable of generating the      *}
{*  EXACT baud rate desired by a user.  This function, when passed a valid  *}
{*  baud rate, will return the ACTUAL baud rate that will be generated.     *}
{*  The baud rate is based on a 8250 input clock rate of 1.73728 MHz.       *}
{*                                                                          *}
{****************************************************************************}

FUNCTION ComTrueBaud(Baud : LONGINT) : REAL;

VAR
  X : REAL;
  Y : WORD;

BEGIN
  X := Baud;
  IF X < C_MinBaud THEN X := C_MinBaud;
  IF X > C_MaxBaud THEN X := C_MaxBaud;
  ComTrueBaud := 115200 / ROUND($900 / (X / 50));
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure ComParams(ComPort:Byte; Baud:Longint;                         *}
{*                      WordSize:Byte; Parity:Char; StopBits:Byte);         *}
{*                                                                          *}
{*  ComPort:Byte   ->  Port # to initialize.  Must be (1 - C_MaxCom)        *}
{*                     Procedure aborted if port # invalid or unopened.     *}
{*  Baud:Longint   ->  Desired baud rate.  Should be (C_MinBaud - C_MaxBaud)*}
{*                     C_MinBaud or C_MaxBaud used if out of range.         *}
{*  WordSize:Byte  ->  Word size, in bits.  Must be 5 - 8 bits.             *}
{*                     8-bit word used if out of range.                     *}
{*  Parity:Char    ->  Parity classification.                               *}
{*                     May be N)one, E)ven, O)dd, M)ark or S)pace.          *}
{*                     N)one selected if classification unknown.            *}
{*  StopBits:Byte  ->  # of stop bits to pad character with.  Range (1-2)   *}
{*                     1 stop bit used if out of range.                     *}
{*                                                                          *}
{*  ComParams is used to configure an OPEN'ed port for the desired comm-    *}
{*  unications parameters, namely baud rate, word size, parity form and     *}
{*  # of stop bits.  A call to this procedure will set up the port approp-  *}
{*  riately, as well as assert the DTR, RTS and OUT2 control lines and      *}
{*  clear all buffers.                                                      *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE ComParams(ComPort : BYTE; Baud : LONGINT; WordSize : BYTE; Parity : CHAR; StopBits : BYTE);

CONST
  C_Stopbit1    = $00;                 { Bit masks for parity, stopbits }
  C_Stopbit2    = $04;
  C_NoParity    = $00;
  C_OddParity   = $08;
  C_EvenParity  = $18;
  C_MarkParity  = $28;
  C_SpaceParity = $38;

VAR
  X : REAL;
  Y,P : WORD;
  DivMSB,DivLSB,BaudB : BYTE;
  WS,SB,PTY : BYTE;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxPort) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  INLINE($FA);
  P := C_PortAddr[ComPort];

  { Calculate baud rate divisors }

  X := Baud;
  IF X < C_MinBaud THEN X := C_MinBaud;
  IF X > C_MaxBaud THEN X := C_MaxBaud;

  Y := ROUND($900 / (X / 50));
  DivMSB := HI(Y);
  DivLSB := LO(Y);

  { Determine parity mask }
  { Default if unknown: No parity }

  CASE UPCASE(Parity) OF
    'N' : PTY := C_NoParity;
    'E' : PTY := C_EvenParity;
    'O' : PTY := C_OddParity;
    'M' : PTY := C_MarkParity;
    'S' : PTY := C_SpaceParity;
  ELSE
    PTY := C_NoParity;
  END;

  { Determine stop-bit mask }
  { Default if out of range: 1 Stop bit }

  CASE StopBits OF
    1 : SB := C_StopBit1;
    2 : SB := C_StopBit2;
  ELSE
    SB := C_StopBit1;
  END;

  { Determine word-size mask }
  { Default if out of range: 8 bit word size }

  IF (WordSize >= 5) AND (WordSize <= 8) THEN
    WS := WordSize - 5
  ELSE
    WS := 3;

  { Initialize line-control register }

  Y := Port[P] + Port[P + C_LSR];

  Port[P + C_LCR] := WS + SB + PTY;

  { Initialize baud rate divisor latches }

  Port[P + C_LCR] := Port[P + C_LCR] OR $80;
  Port[P] := DivLSB;
  Port[P + 1] := DivMSB;
  Port[P + C_LCR] := Port[P + C_LCR] AND $7F;
  X := Port[P] + Port[P + C_LSR] + Port[P + C_MSR] + Port[P + C_IIR];

  { Assert RS323 control lines (DTR,RTS,OUT2) & exit }

  Port[P + C_MCR] := $0B;
  ClearCom(ComPort,'B');

  {begin new stuff srl*}
  Port[$20] := $20;
  IF C_CascadeOK THEN
    Port[$A0] := $20;
  {end new stuff srl*}

  INLINE($FB);

END;

{****************************************************************************}
{*                                                                          *}
{*  Function OpenCom(ComPort:Byte; InBufferSize,OutBufferSize:Word):Boolean *}
{*                                                                          *}
{*  ComPort:Byte        ->  Port # to OPEN (1 - C_MaxCom)                   *}
{*                          Request will fail if out of range or port OPEN  *}
{*  InBufferSize:Word   ->  Requested size of input (receive) buffer        *}
{*  OutBufferSize:Word  ->  Requested size of output (transmit) buffer      *}
{*  Returns success/fail status of OPEN request (TRUE if OPEN successful)   *}
{*                                                                          *}
{*  OpenCom must be called before any activity (other than existence check, *}
{*  see the ComExist function) takes place.  OpenCom initializes the        *}
{*  interrupt drivers and serial communications hardware for the selected   *}
{*  port, preparing it for I/O.  Memory for buffers is allocated on the     *}
{*  Pascal "heap", thus freeing data-segment memory for larger more data-   *}
{*  intensive programs.  Once a port has been OPENed, a call to ComParams   *}
{*  should be made to set up communications parameters (baud rate, parity   *}
{*  and the like).  Once this is done, I/O can take place on the port.      *}
{*  OpenCom will return a TRUE value if the opening procedure was success-  *}
{*  ful, or FALSE if it is not.                                             *}
{*                                                                          *}
{****************************************************************************}

FUNCTION OpenCom(ComPort : BYTE; InBufferSize,OutBufferSize : WORD) : BOOLEAN;

VAR
  TempVec : POINTER;
  P : WORD;
  IntLn,Cas_IntLn,X : BYTE;

BEGIN
  { Ensure that port was not previously open }

  OpenCom := FALSE;
  C_CascadeOK := FALSE;
  C_cascade := 0;
  IF (ComPort < 1) OR (ComPort > C_MaxPort) OR C_PortOpen[ComPort] THEN EXIT;
  C_msrport := c_portaddr[comport] + c_msr;

  { Clear any pending activity from 8250 interrupt queue }

  INLINE($FA);

  { Set up interrupt vectors & 8259 PIC }
  P := C_PortAddr[ComPort];
  oldier := port[P + c_ier];
  oldmcr := port[P + c_mcr];
  Port[P + C_IER] := $0D;
  X := Port[P] + Port[P + C_LSR] + Port[P + C_MSR] + Port[P + C_IIR];

  IntLn := C_PortInt[ComPort];
  IF IntLn > 7 THEN
     C_CascadeOK := TRUE;

  IF C_CascadeOK THEN
    BEGIN
      Cas_IntLn := IntLn - 8;
      GETINTVEC($70 + Cas_IntLn,TempVec);
      IF C_OldINTVec[IntLn] <> TempVec THEN
        BEGIN
          C_Cascade := 1;
          C_OldINTVec[IntLn] := TempVec;
          SETINTVEC($70 + Cas_IntLn,@Int_Handler);
          Port[$21] := Port[$21] AND (($01 SHL $02) XOR $FF);
          X := Port[$21];
          Port[$A1] := Port[$A1] AND (($01 SHL Cas_IntLn) XOR $FF);
          X := Port[$A1];
        END;
    END
  ELSE
    BEGIN
      GETINTVEC(8 + IntLn,TempVec);
      IF C_OldINTVec[IntLn] <> TempVec THEN
        BEGIN
          C_OldINTVec[IntLn] := TempVec;
          SETINTVEC(8 + IntLn,@Int_Handler);
          Port[$21] := Port[$21] AND (($01 SHL IntLn) XOR $FF);
          X := Port[$21];
        END;
    END;

  { new stuff srl*}
  Port[P + C_MCR] := $0B;

  { Allocate memory for I/O buffers }

  C_InSize[ComPort] := InBufferSize;
  C_OutSize[ComPort] := OutBufferSize;
  GETMEM(C_InBufPtr[ComPort],InBufferSize);
  GETMEM(C_OutBufPtr[ComPort],OutBufferSize);

  { Set up default parameters for port }

  C_RTSOn[ComPort] := InBufferSize - 2;
  C_RTSOff[ComPort] := InBufferSize - 1;
  C_StartChar[ComPort] := ^Q;
  C_StopChar[ComPort] := ^S;
  C_PortOpen[ComPort] := TRUE;
  OpenCom := TRUE;

  INLINE($FB);
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure CloseCom(ComPort:Byte)                                        *}
{*                                                                          *}
{*  ComPort:Byte  ->  Port # to close                                       *}
{*                    Request ignored if port closed or out of range.       *}
{*                                                                          *}
{*  CloseCom will un-link the interrupt drivers for a port, deallocate it's *}
{*  buffers and drop the DTR and RTS signal lines for a port opened with    *}
{*  the OpenCom function.  It should be called before exiting your program  *}
{*  to ensure that the port is properly shut down.                          *}
{*  NOTE:  CloseCom shuts down a communications channel IMMEDIATELY,        *}
{*         even if there is data present in the input or output buffers.    *}
{*         Therefore, you may wish to call the ComWaitForClear procedure    *}
{*         before closing the ports.                                        *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE CloseCom(ComPort : BYTE);

VAR
  ClosePort : BOOLEAN;
  P,IntLn,Cas_IntLn,X : WORD;

BEGIN
  IF (ComPort < 1) OR (ComPort > C_MaxPort) OR (NOT C_PortOpen[ComPort]) THEN EXIT;

  { Drop RS232 control lines (DTR,RTS,OUT2) and reset 8250 interrupt mode }

  INLINE($FA);
  P := C_PortAddr[ComPort];
  Port[P + C_IER] := oldier;
  C_PortOpen[ComPort] := FALSE;

  { Reset INT vectors & 8259 PIC if all COMs on selected INT are closed }

  IntLn := C_PortInt[ComPort];
  ClosePort := TRUE;
  FOR X := 1 TO C_MaxCom DO
    IF C_PortOpen[X] AND (C_PortInt[X] = IntLn) THEN
      ClosePort := FALSE;

  IF ClosePort THEN
    IF C_CascadeOk THEN
      BEGIN
        Cas_IntLn := IntLn - 8;
        Port[$21] := Port[$21] OR ($01 SHR $02);
        X := Port[$21];
        Port[$A1] := Port[$A1] OR ($01 SHR Cas_IntLn);
        X := Port[$A1];
        SETINTVEC($70 + Cas_IntLn,C_OldINTVec[IntLn]);
      END
    ELSE
      BEGIN
        Port[$21] := Port[$21] OR ($01 SHR IntLn);
        X := Port[$21];
        SETINTVEC(8 + IntLn,C_OldINTVec[IntLn]);
      END;

  X := Port[P] + Port[P + C_LSR] + Port[P + C_MSR] + Port[P + C_IIR];

  { Deallocate buffers }

  FREEMEM(C_InBufPtr[ComPort],C_InSize[ComPort]);
  FREEMEM(C_OutBufPtr[ComPort],C_OutSize[ComPort]);
  INLINE($FB);
END;

{****************************************************************************}
{*                                                                          *}
{*  Procedure CloseAllComs                                                  *}
{*                                                                          *}
{*  CloseAllComs will CLOSE all currently OPENed ports.  See the CloseCom   *}
{*  procedure description for more details.                                 *}
{*                                                                          *}
{****************************************************************************}

PROCEDURE CloseAllComs;

VAR
  X : BYTE;

BEGIN
  FOR X := 1 TO C_MaxCom DO
    IF C_PortOpen[X] THEN
      CloseCom(X);
END;

{****************************************************************************}
{*                                                                          *}
{*                        UNIT Initialization Code                          *}
{*                                                                          *}
{****************************************************************************}

BEGIN
  FOR x := 1 TO C_MaxPort DO
    BEGIN
      C_PortOpen[x] := FALSE;
      C_InBufPtr[x] := NIL;
      C_OutBufPtr[x] := NIL;
      C_OldIntVec[x] := NIL;
      C_InHead[x] := 0;
      C_OutHead[x] := 0;
      C_InTail[x] := 0;
      C_OutTail[x] := 0;
      C_InSize[x] := 0;
      C_OutSize[x] := 0;
      C_RTSOn[x] := $FFFF;
      C_RTSOff[x] := $FFFF;
      C_StartChar[x] := ^Q;
      C_StopChar[x] := ^S;
      C_Status[x] := $05;
      C_Ctrl[x] := 0;
      C_XL3Ptr[x] := 0;
      C_buffull[x] := 0;
      C_cascade := 0;
    END;
END.
