$R-,S-,I-,D+,T+,F-,V+,B-,N-,L+ 
UNIT ASYNC;

INTERFACE

Uses Delays;

(**************************** ASYNC.PAS *********************************)
(*                                                                      *)
(*     Modul for bruk av 1,2,3 el. 4 COM-porter samtidig, med interrupt *)
(*     bde ved sending og mottak og uavhengige ring-buffere opptil     *)
(*     64k for hver retning og port.                                    *)
(*                                                                      *)
(*     Oslo, November 1987 Terje Mathisen, Norsk Hydro                  *)
(*                                                                      *)
(**************************** ASYNC.PRO *********************************)

CONST RX_int     = 1;
      TX_int     = 2;
      RLS_int    = 4;
      MODEM_int  = 8;
      SumOf_int  =15;
TYPE
  ComPortType = 1..4;
  ParityType = (No_Parity, Even_Parity, Odd_Parity, Zero_Parity, One_Parity);

  RS_IntSet = 0..SumOf_int;
  RS_BufPtrType  = ^RS_BufferType;
  RS_BufferType = RECORD
              ICadr, IntNr     : WORD;

              oldModemContrReg : BYTE;
              oldLevel  : BYTE;
              oldVector : Pointer;

              xin : Pointer;
              xout, SizeX, LimitX    : WORD;

              Tin : WORD;
              Tout : Pointer;
              SizeT, SendFirst : WORD;

              ShowXoffPtr : Pointer;
              Toggle_Xoff, RLS_user, MODEM_user : Pointer;

              Ctrl_P    : BYTE;  {0 - > default, 1..4 -> NOTIS}

              UseTint, HostXoff : BOOLEAN;
              Bufferfilled : BYTE;

              AutoXoff, AltXoff : BOOLEAN;
              Xoff1C, Xoff2C, Xon1C, Xon2C     : CHAR;

              Line_Status, MODEM_status : BYTE;
              WaitTX : BOOLEAN;
              Int_Mask : BYTE;
              oldIntEnableReg : BYTE;
            END;

VAR
  RS_BufPtr : ARRAY [ComPortType] OF RS_BufPtrType;
  RS_TimeOut : WORD;

  RS_Buffer : ARRAY [ComPortType] OF RS_BufferType; { Must be in data-seg! }

  PROCEDURE RS_MakeBuffer(Rsize,Tsize,IOaddr,SWint:WORD; com : WORD);

  PROCEDURE RS_Init (baudRate : LongInt;
                  NbrOfBits,           { 5|6|7|8 }
                  StopBits: WORD;      { 1|2 }
                  Parity: ParityType;
            { (No_Parity, Even_Parity, Odd_Parity, Zero_Parity, One_Parity) }
                  VAR result: BOOLEAN;
                  com: ComPortType); { 1..4 }

  PROCEDURE RS_Stop(com: ComPortType);

  PROCEDURE RS_Start(rs_int: RS_IntSet; com: ComPortType);

  PROCEDURE RS_BusyRead(VAR ch:CHAR;VAR done : BOOLEAN; com : WORD);

  PROCEDURE RS_ReadBlock(VAR buf;max:WORD;VAR bytes:WORD;com : WORD);

  PROCEDURE RS_Write(ch: CHAR;VAR done : BOOLEAN; com: WORD );

  PROCEDURE RS_WriteBlock(VAR buf;len: WORD;VAR bytes:WORD; com: WORD);

  FUNCTION RS_GetChar(VAR ch : CHAR; com : WORD): BOOLEAN;

  FUNCTION RS_Avail(com : WORD): WORD;

  FUNCTION RS_Room(com : WORD): WORD;

  PROCEDURE RS_Enable(com : WORD);

  PROCEDURE RS_WriteFirst(ch:CHAR;com:WORD);

  PROCEDURE RS_ClrBuffer(com: WORD);

  PROCEDURE RS_Set_TX_Int(rs_int : RS_IntSet; com : WORD);

  FUNCTION RS_Empty(com : WORD) : BOOLEAN;

  PROCEDURE RS_Break(ms : WORD;com : WORD);

  PROCEDURE RS_StopLink(com : WORD);

  PROCEDURE RS_StartLink(com : WORD);

  PROCEDURE RS_StopAll;

IMPLEMENTATION

CONST
  LineContrReg    = 3; { to specify format of transmitted data }
  LowBaudRateDiv  = 0; { lower byte of divisor to select baud rate }
  HighBaudRateDiv = 1; { higher byte of divisor }
  LineStatusReg   = 5; { holds status info on the data transfer }
  ReceiverReg     = 0; { received CHAR is in this register }
  TransmitReg     = 0; { CHAR to send is put in this reg }
  IntEnableReg    = 1; { to enable the selected interrupt }
  IntIdentReg     = 2;
  ModemContrReg   = 4; { controls the interface to a modem }

    PROCEDURE GetAlignMem(VAR p : Pointer; size : WORD);
    VAR temp : ^BYTE;
    BEGIN
      REPEAT
        GetMem(p,size);
        IF Ofs(p^) = 0 THEN Exit;
        FreeMem(p,size);
        New(temp);
      UNTIL FALSE;
    END;

  PROCEDURE RS_MakeBuffer(Rsize, Tsize, IOaddr, SWint, com: WORD);
  CONST PortTab : ARRAY [ComPortType] OF WORD = ($3F8,$2F8,$3E8,$2E8);
        IntTab  : ARRAY [ComPortType] OF BYTE = (12,11,12,11);
  VAR c, c0, c1 : WORD;
  BEGIN
    IF Rsize + Tsize > MemAvail - $100 THEN BEGIN
      Halt(1);
    END;
    IF com = 0 THEN BEGIN
      c0 := 1; c1 := 4;
    END
    ELSE BEGIN
      IF com > 4 THEN Halt(1);
      c0 := com; c1 := com;
    END;
    FOR c := c0 TO c1 DO WITH RS_Buffer[c] DO BEGIN
      IF (com = 0) AND (c > 1) THEN
        RS_Buffer[c] := RS_Buffer[1]
      ELSE BEGIN
        IF Rsize > 0 THEN BEGIN
          GetAlignMem(xin,Rsize);
          SizeX := Rsize;
          LimitX := Rsize DIV 8;
        END;

        IF Tsize > 0 THEN BEGIN
          GetAlignMem(Tout,Tsize);
          SizeT := Tsize;
        END;
      END;

      IF IOaddr = 0 THEN
        ICadr := PortTab[c]
      ELSE
        ICadr := IOaddr;

      IF SWint = 0 THEN
        IntNr := IntTab[c]
      ELSE
        IntNr := SWint;

{                       Disse variablene er nullstilt allerede!
      xin := 0;
      xout := 0;
      SendFirst := 0;
      tin := 0;
      tout := 0;
      Ctrl_P := 0;
      UseTint := FALSE;
      Sending := FALSE;
      Receiving := FALSE;
      HostXoff := FALSE;
      BufferFilled := 0;
      AltXoff  := FALSE;
      ShowXoffPtr := NIL;
      Toggle_Xoff := 0;
      RLS_user := 0;
      MODEM_user := 0;
}
                                       {Default to use XON/XOFF!}
      AutoXoff := TRUE;
      Xoff1C   := ^S;
      Xon1C    := ^Q;
    END;
  END;

  PROCEDURE RS_Init (baudRate  : LongInt;
                  NbrOfBits,           { 5|6|7|8 }
                  StopBits: WORD;      { 1|2 }
                  Parity: ParityType;
            { (No_Parity, Even_Parity, Odd_Parity, Zero_Parity, One_Parity) }
                  VAR result: BOOLEAN;
                  com: ComPortType); { 1..4 }
  CONST ParityTab : ARRAY [ParityType] OF BYTE = (0,$18,$08,$38,$28);
    VAR divisor : WORD;
        parameters: BYTE;

  BEGIN (* Init *)
    result := FALSE;

    WITH RS_Buffer[com] DO BEGIN
      IF Xin = NIL THEN BEGIN          {No buffer allocated!}
        Halt(1);
      END;

    (* load the divisor of the baud rate generator: *)
      IF baudrate < 1 THEN Exit;
      divisor := (115200 + (baudrate DIV 2)) DIV baudrate;
      Port[ICadr+LineContrReg] := $80;
      Port[ICadr+HighBaudRateDiv] := Hi(divisor);
      Port[ICadr+LowBaudRateDiv]  := Lo(divisor);

        (* prepare the parameters: *)
      parameters := ParityTab[Parity];

      IF stopBits = 2 THEN
        parameters := parameters + 4
      ELSE IF stopBits <> 1 THEN Exit;

      IF (nbrOfBits < 5) OR (nbrOfBits > 8) THEN Exit;
      Port[ICadr+LineContrReg] := parameters + (nbrOfBits - 5);

        (* Disable Interrupts: *)
      Port[ICadr+IntEnableReg] := 0;
      result := TRUE;
    END;
  END                                  { Init };

    CONST
      I8259ContrWord1 = $21;  (* Interrupt controller,
                                 Operation Control Word 1 *)

(************************* ASSEMBLER ROUTINES FOR MAX SPEED ****************)

    PROCEDURE RS_Com1Int;                            EXTERNAL;

    PROCEDURE RS_Com2Int;                            EXTERNAL;

    PROCEDURE RS_Com3Int;                            EXTERNAL;

    PROCEDURE RS_Com4Int;                            EXTERNAL;

    PROCEDURE RS_BusyRead(VAR ch:CHAR;VAR done : BOOLEAN;
              com : WORD);                    EXTERNAL;

    PROCEDURE RS_ReadBlock(VAR buf;max:WORD;
              VAR bytes : WORD;com : WORD);EXTERNAL;

    PROCEDURE RS_Write(ch: CHAR;VAR done : BOOLEAN;
              com: WORD );                    EXTERNAL;

    PROCEDURE RS_WriteBlock(VAR buf;len: WORD;
              VAR bytes : WORD; com: WORD);EXTERNAL;

    FUNCTION RS_GetChar(VAR ch : CHAR;
             com : WORD): BOOLEAN;            EXTERNAL;

    FUNCTION RS_Avail(com : WORD): WORD;   EXTERNAL;

    FUNCTION RS_Room(com : WORD): WORD;    EXTERNAL;

    PROCEDURE RS_Enable(com : WORD);          EXTERNAL;

    PROCEDURE RS_WriteFirst(ch:CHAR;com:WORD);EXTERNAL;

  {$L ASYNC.OBJ}

(***************************************************************************)

VAR vect_tab : ARRAY [0..255] OF Pointer ABSOLUTE 0:0;

PROCEDURE Disable; Inline($FA);

PROCEDURE Enable; Inline($FB);

PROCEDURE GetVector(vnr : WORD; VAR vector : Pointer);
BEGIN
  vector := vect_tab[vnr];
END;                                   {GetVector}

PROCEDURE SetVector(vnr : WORD; vector : Pointer);
BEGIN
  Disable;
    vect_tab[vnr] := vector;
  Enable;
END;                                   {PutVector}

PROCEDURE RS_Start(rs_int : RS_IntSet; com: ComPortType);
VAR
  adr : Pointer;
  mask, tempSet : BYTE;
  dummy : WORD;
  ch : CHAR;
  ok : BOOLEAN;
BEGIN
  WITH RS_Buffer[com] DO
    IF OldVector = NIL THEN BEGIN

      (* enable interrupts in the interrupt controller (8259): *)
      tempSet := Port[I8259ContrWord1];
      (* set the interrupt vector *)

      GetVector(IntNr,OldVector);
      CASE com OF
        1 : adr := @RS_Com1int;
        2 : adr := @RS_Com2int;
        3 : adr := @RS_Com3int;
        4 : adr := @RS_Com4int;
      END;
      SetVector(IntNr,adr);

      mask := 1 Shl (IntNr - 8);
      oldLevel := tempSet AND mask;

      DISABLE;
      Port[I8259ContrWord1] := tempSet AND NOT mask;

      dummy  :=  Port[ICadr+IntIdentReg] +
                 Port[ICadr+LineStatusReg] +
                 Port[ICadr+ModemContrReg] +
                 Port[ICadr+ReceiverReg];    (* clear the controller *)

      WORD(xin)    := 0;
      xout         := 0;

      SendFirst    := 0;
      tin          := 0;
      WORD(tout)   := 0;

      HostXoff     := FALSE;
      WaitTX       := FALSE;
      AutoXoff     := TRUE;      
      BufferFilled := 0;
      Line_Status  := 0;
      MODEM_Status := 0;

      tempSet := Port[ICadr+ModemContrReg];
      oldModemContrReg := tempSet AND 11;    { DTR and RTS }

      Port[ICadr+ModemContrReg] := tempSet OR 11;

      Int_Mask := rs_int;
      oldIntEnableReg := Port[ICadr+IntEnableReg];
      Port[ICadr+IntEnableReg] := rs_int;
      UseTint := (TX_int AND rs_int) <> 0;

      ENABLE;

    END;
    dummy := 50;
    REPEAT
      RS_BusyRead(ch,ok,com);               { Remove pending int's }
      Dec(dummy);
    UNTIL NOT ok OR (dummy = 0);
END                                {RS_Start};

PROCEDURE RS_Stop(com: ComPortType);
BEGIN
  WITH RS_Buffer[com] DO
    IF OldVector <> NIL THEN BEGIN
      DISABLE;

    (* restore old mask in 8259: *)
      Port[I8259ContrWord1] := Port[I8259ContrWord1] OR oldLevel;

    (* disable interrupts in 8250: *)
      Port[ICadr+IntEnableReg] := oldIntEnableReg;
    (* restore modem control register in 8250: *)
      Port[ICadr+ModemContrReg] :=
         (Port[ICadr+ModemContrReg] AND 244) OR oldModemContrReg;
      ENABLE;

    (* restore the old interrupt vector *)
      SetVector(IntNr,OldVector);
      OldVector := NIL;
    END;
END                                {RS_Stop};
(*
PROCEDURE RS_Read(VAR ch: CHAR;com: WORD );
VAR done : BOOLEAN;
BEGIN
  REPEAT
    RS_BusyRead (ch, done, com);
  UNTIL done;
END                                {RS_Read};
*)
PROCEDURE RS_ClrBuffer(com: WORD);
BEGIN
  WITH RS_Buffer[com] DO BEGIN
    Disable;
    WORD(xin) := 0;
    xout := 0;
    tin := 0;
    WORD(tout) := 0;
    SendFirst := 0;
    Enable;
  END;
END;                               {ClrBuffer}

PROCEDURE RS_Set_TX_Int(rs_int : RS_IntSet; com : WORD);
BEGIN
  WITH RS_Buffer[com] DO BEGIN
    Disable;
    tin := 0;
    WORD(tout) := 0;
    SendFirst := 0;
    Int_Mask := rs_int;
    Port[ICadr+IntEnableReg] := rs_int;
    UseTint := (TX_int AND rs_int) <> 0;
    Enable;
  END;
END;                               {RS_Set_TX_Int}

FUNCTION RS_Empty(com : WORD) : BOOLEAN;
VAR ch : CHAR;
    ok : BOOLEAN;
BEGIN
  WITH RS_Buffer[com] DO
    RS_Empty := WORD(xin) = xout;
END;                                 {EmptyBuffer}

PROCEDURE RS_Break(ms : WORD;com : WORD);
VAR oldreg : BYTE;
BEGIN
  WITH RS_Buffer[com] DO BEGIN
    WaitTX := TRUE;
    WHILE Port[ICadr+LineStatusReg] AND 32 = 0 DO ; { wait for no traffic }
    oldreg := Port[ICadr+LineContrReg];
    Port[ICadr+LineContrReg]:= oldreg OR 64;
    Delay(ms);
    Port[ICadr+LineContrReg] := OldReg;
    Delay(250);
    WaitTX := FALSE;
    IF NOT HostXoff THEN RS_Enable(com);
  END;
END;                                 {RS_Break}

PROCEDURE RS_StopLink(com : WORD);
VAR bf : BYTE;
BEGIN
  WITH RS_Buffer[com] DO
    IF AutoXoff THEN BEGIN
      Disable;
      bf := BufferFilled;
      BufferFilled := BufferFilled OR 2;
      Enable;
      IF bf = 0 THEN BEGIN
        RS_WriteFirst(Xoff1C,com);
        Delay(10);
      END;
    END;
END;

PROCEDURE RS_StartLink(com : WORD);
VAR bf : BYTE;
BEGIN
  WITH RS_Buffer[com] DO
    IF AutoXoff THEN BEGIN
      Disable;
      BufferFilled := BufferFilled AND 253;
      bf := BufferFilled;
      Enable;
      IF bf = 0 THEN BEGIN
        RS_WriteFirst(Xon1C,com);
      END;
    END;
END;

VAR SaveExit : Pointer;

PROCEDURE RS_StopAll;
BEGIN
  RS_Stop(1);
  RS_Stop(2);
  RS_Stop(3);
  RS_Stop(4);
  ExitProc := SaveExit;
END;

BEGIN
  FillChar(RS_Buffer,SizeOf(RS_Buffer),#0);
  RS_BufPtr[1] := Addr(RS_Buffer[1]);
  RS_BufPtr[2] := Addr(RS_Buffer[2]);
  RS_BufPtr[3] := Addr(RS_Buffer[3]);
  RS_BufPtr[4] := Addr(RS_Buffer[4]);
  RS_TimeOut := 0;
  SaveExit := ExitProc;
  ExitProc := @RS_StopAll;
END.
