reino@euraiv1.UUCP (Reino de Boer) (11/15/88)
The following unit is used at our site as the low level interface in a program called uPnew, which interfaces to a network of Multilink Nodes. To use it leave out the MULTILINK and UPNEW defines. The other units needed are simple enough, although Mark Rochkind deserves a mention for the ideas used in UNIT Console. {$define DEBUG} {$define MULTILINK} {$define UPNEW} UNIT Mserial; {$ifdef MULTILINK} { Special serial handler for Multilink stand-alone nodes } { Version 3 firmware } { Only intelligent mode supported } { Copyright (c) 1988 by Reino de Boer/Evas/EUR } {$endif} INTERFACE USES Dos, Timer, Memory, Console, Debug; CONST { First 2 bits for LCR } DataBits5 = 0; DataBits6 = 1; DataBits7 = 2; DataBits8 = 3; { Third bit for LCR } StopBits1 = 0; StopBits2 = 4; { 1.5 stop bits when 5 data bits } { Bits 3, 4, and 5 of LCR } NoParity = 0; OddParity = 8; EvenParity = 24; Parity1 = 40; Parity0 = 56; { Bits for LSR } RDR = 1; { Received Data Ready } OverrunErr = 2; { New character in RBR before previous read } ParityErr = 4; { Inappropriate parity in character received } FramingErr = 8; { Invalid stop bit in character received } BreakInt = 16; { BREAK received } THRempty = 32; { THR empty } TSRempty = 64; { Transmitter Shift Register empty } ErrorMask = 30; { 2 OR 4 OR 8 OR 16 } { Other possible error values } NoError = 0; OutOverflow = 128; { Output buffer overflow } InOverflow = 256; { Input buffer overflow } TimeOut = 512; { Time out for whatever is timed } UserInt = 1024; { User interrupted by pressing Control-C } { Indices for varying data } COM1 = 1; COM2 = 2; TYPE { Possible "Baud rates" ( actually bits per second ) } BpsName = ( Bps45_5, Bps50, Bps75, Bps110, Bps134_5, Bps150, Bps300, Bps600, Bps1200, Bps1800, Bps2000, Bps2400, Bps4800, Bps9600, Bps19200, Bps38400 ); CONST { Buffer definitions } BufferSize = 4096; { Power of 2 ( for IndexMask ) } IndexMask = 4095; { BufferSize - 1 } TYPE Buffer = ARRAY [0..IndexMask] OF byte; TYPE { UART status for each serial port } PortData = RECORD { Identification } BaseAddress : word; IRQno : byte; { Interrupt configuration } EnableBits : byte; { Port configuration } DataBits, StopBits, Parity, LineControl : byte; Bps : BpsName; BaudDiv : word; { Error status } Error : integer; { Port status } THRfree : boolean; { RS232 status } CTSvalue, DSRvalue, RIvalue, DCDvalue : byte; { UART status } Lstat, Mstat : byte; { Buffering } { Input buffer } InWaiting, InFront, InRear : integer; InData : Buffer; { Output buffer } OutWaiting, OutFront, OutRear : integer; OutData : Buffer END; PortPtr = ^PortData; PortNo = COM1..COM2; VAR ComPort : ARRAY [PortNo] OF PortPtr; Duration, ByteCount : longint; {$ifdef MULTILINK} { Multilink definitions } CONST { Highest channel number in version 3 firmware } MaxChannel = 63; { Ring status value } Mended = $80; { COM port used by multilink } OurCom = COM1; VAR { Status per channel } Busy, Connected : ARRAY [0..MaxChannel] OF boolean; { Status per node } RingStatus : byte; NodeName : string; NoKeys : boolean; {$endif} PROCEDURE StartTiming; PROCEDURE StopTiming; PROCEDURE SelectBitRate( COM : PortNo; Br : BpsName ); PROCEDURE SelectWordLength( COM : PortNo; Len : byte ); PROCEDURE SelectFraming( COM : PortNo; Stop : byte ); PROCEDURE SelectParity( COM : PortNo; Par : byte ); FUNCTION AllSent( COM : PortNo ) : boolean; PROCEDURE Poll( COM : PortNo; Sending : boolean ); PROCEDURE Send( COM : PortNo; B : byte ); FUNCTION Receive( COM : PortNo; VAR B : byte; Sending : boolean ) : boolean; PROCEDURE Break( COM : PortNo ); PROCEDURE Install( COM : PortNo ); PROCEDURE TurnOnDTR( COM : PortNo ); PROCEDURE TurnOffDTR( COM : PortNo ); IMPLEMENTATION { Returns true if user pressed Control-C } FUNCTION Interrupted : boolean; VAR Ch : char; Spc : boolean; BEGIN {$ifndef UPNEW} IF( NoKeys ) THEN BEGIN Interrupted := false; exit END; {$endif} Interrupted := false; IF( keypressed ) THEN BEGIN ReadKbd( Ch, Spc ); IF( NOT Spc ) THEN Interrupted := ( Ch = ^C ) END END; { Interrupted } CONST NotUsed = $00; { *** 8259A Progammable Interrupt Controller ( PIC ) ************************ } CONST { PIC registers } IRR = NotUsed; { Interrupt Request Register } ISR = $20; { In Service Register } IMR = $21; { Interrupt Mask Register ( R/W ) } { Disable all maskable interrupts } PROCEDURE CLI; INLINE( $FA ); { Enable all maskable interrupts } PROCEDURE STI; INLINE( $FB ); { Disable Interrupt Request Line IRQ } PROCEDURE Disable( IRQ : byte ); BEGIN port[IMR] := port[IMR] OR ( 1 SHL IRQ ) END; { Disable } { Enable Interrupt Request Line IRQ } PROCEDURE Enable( IRQ : byte ); BEGIN port[IMR] := port[IMR] AND NOT ( 1 SHL IRQ ) END; { Enable } { Send IRQ specific End Of Interrupt. INTERRUPTS OFF } PROCEDURE SendEOI( IRQ : byte ); BEGIN port[ISR] := $60 OR IRQ END; { SendEOI } { Report Interrupt level } { Only to be called at the start of a handler for a Maskable Interrupt } FUNCTION RIL : byte; BEGIN { Report Interrupt Level Request } port[ISR] := $0B; { Return Interrupt level } RIL := port[ISR] END; { RIL } { *** 8250 Universal Asynchronous Receiver Transmitter ( UART ) ************* } CONST { UART registers. General offsets } RBR = $00; { Receive Buffer Register when read from } THR = $00; { Transmitter Holding Register when written to } IER = $01; { Interrupt Enable Register } IIR = $02; { Interrupt Identification Register } LCR = $03; { Line Control Register } MCR = $04; { Modem Control Register } LSR = $05; { Line Status Register } MSR = $06; { Modem Status Register } { Bits for IER; see bits for IIR } RBRenable = 1; { enable RBRfilled } THRenable = 2; { enable THRemptied } LSRenable = 4; { enable LSRerror } MSRenable = 8; { enable MSRchange } { Bits for IIR } MSRchange = 0; InActive = 1; THRemptied = 2; RBRfilled = 4; LSRerror = 6; { Bit 6 of LCR } SetBreak = 64; { Bit on : send break signal } { Last bit of LCR } { see INTERFACE section for other bits } { If set, RBR and IER are used to communicate with the } { Programmable Baud Rate Generator ( PBRG ) } DLAB = 128; { Divisor Latch Access Bit } { Bits for MCR } DTR = 1; { Data Terminal Ready } RTS = 2; { Request To Send } OUT1 = 4; { Used by Hayes international modems to } { cause a power-on reset of their circuits } OUT2 = 8; { Controls 8250 interrupt signals } { Unless OUT2 is set to 1, interrupt signals } { from the UART cannot reach the rest of the PC } SelfTest = 16; { Turns on UART self-test configuration } { Bits for MSR } CTSchanged = 1; { Clear To Send } DSRchanged = 2; { Data Set Ready } RIchanged = 4; { Ring Indicator } DCDchanged = 8; { Data Carrier Detect } CTSlevel = 16; DSRlevel = 32; RIlevel = 64; DCDlevel = 128; { Base Addresses } Base : ARRAY [PortNo] OF integer = ( $3F8, $2F8 ); { IRQ lines } IRQline : ARRAY [PortNo] OF byte = ( 4, 3 ); { Interrupt Numbers } IntrNo : ARRAY [PortNo] OF integer = ( $0C, $0B ); CONST { Bit rate divisor table } Divisor : ARRAY [BpsName] OF word = ( 2532, 2304, 1536, 1047, 857, 768, 384, 192, 96, 64, 58, 48, 24, 12, 6, 3 ); { Minimum length of a BREAK signal in milliseconds } BreakLen = 350; { Empirical } { Delay after using PBRG } PBRGlen = 250; { Empirical } { Settle time for change in DTR or RTS } DTRSettle = 2; { Empirical } RTSsettle = 2; { Empirical } { Delay after sending, to prevent timing problems } BugDelay = 1; { Empirical } VAR DoTiming : boolean; StartTime : longint; { Exception handling } ExitSave : pointer; OldVector : ARRAY [PortNo] OF pointer; Installed, VectorSet : ARRAY [PortNo] OF boolean; {$ifdef MULTILINK} { *** Multilink definitions ************************************************* } CONST { Some ascii control codes with high bit on } DLEh = $90; { Prefix to command } EMh = $99; { Ring status change indicator } SOh = $8E; { Busy signal } SIh = $8F; { Free again signal } EOTh = $84; { Disconnect signal } ACKh = $86; { Acknowledge } HighBit = $80; { Other ascii control codes } ACK = $06; { Acknowledge } VAR NodeChannel : integer; { Current "receive" channel } EMreceived, DLEreceived : boolean; { Exception handling } NodeInit : boolean; {$endif} { *** Buffer primitives ***************************************************** } PROCEDURE PutOut( COM : PortNo; B : byte ); BEGIN WITH ComPort[COM]^ DO IF( OutWaiting >= BufferSize ) THEN Error := Error OR OutOverflow ELSE BEGIN OutData[OutRear] := B; OutRear := succ( OutRear ) AND IndexMask; inc( OutWaiting ) END END; { PutOut } FUNCTION GetOut( COM : PortNo; VAR B : byte ) : boolean; BEGIN WITH ComPort[COM]^ DO IF( OutWaiting <= 0 ) THEN GetOut := false ELSE BEGIN B := OutData[OutFront]; OutFront := succ( OutFront ) AND IndexMask; dec( OutWaiting ); GetOut := true END END; { GetOut } PROCEDURE PutIn( COM : PortNo; B : byte ); BEGIN WITH ComPort[COM]^ DO BEGIN IF( InWaiting >= BufferSize ) THEN Error := Error OR InOverflow ELSE BEGIN {$ifdef MULTILINK} { Multilink Handling } IF( DLEreceived ) THEN BEGIN DLEreceived := false; IF( EMreceived ) THEN BEGIN RingStatus := B; EMreceived := false END ELSE CASE B OF SOh : BEGIN Busy[NodeChannel] := true; mem[$B800:0158] := ord( '*' ) END; SIh : BEGIN Busy[NodeChannel] := false; mem[$B800:0158] := ord( ' ' ) END; EMh : BEGIN EMreceived := true; DLEreceived := true END; EOTh : Connected[NodeChannel] := false; ELSE IF( B <= MaxChannel ) THEN NodeChannel := B END END ELSE IF( B = DLEh ) THEN DLEreceived := true; {$endif} { Normal buffering } InData[InRear] := B; InRear := succ( InRear ) AND IndexMask; inc( InWaiting ) END END END; { PutIn } FUNCTION GetIn( COM : PortNo; VAR B : byte ) : boolean; BEGIN WITH ComPort[COM]^ DO BEGIN IF( InWaiting <= 0 ) THEN GetIn := false ELSE BEGIN { Disable interrupts that share this memory } CLI; B := InData[InFront]; InFront := succ( InFront ) AND IndexMask; dec( InWaiting ); { Free to be interrupted again } STI; GetIn := true END END END; { GetIn } { Send RTS signal } PROCEDURE SendRTS( COM : PortNo ); BEGIN WITH ComPort[COM]^ DO IF( ( port[BaseAddress + MCR] AND RTS ) = 0 ) THEN BEGIN port[BaseAddress + MCR] := port[BaseAddress + MCR] OR RTS; delay( RTSsettle ) END END; { SendRTS } { Return true if DTR is off } FUNCTION DTRoff( COM : PortNo ) : boolean; BEGIN WITH ComPort[COM]^ DO DTRoff := ( ( port[BaseAddress + MCR] AND DTR ) = 0 ) END; { DTRoff } { Tell DCE that DTE is ready } PROCEDURE TurnOnDTR( COM : PortNo ); BEGIN WITH ComPort[COM]^ DO port[BaseAddress + MCR] := port[BaseAddress + MCR] OR DTR; delay( DTRsettle ) END; { TurnOnDTR } { Tell DCE that DTE is not ready } PROCEDURE TurnOffDTR( COM : PortNo ); BEGIN WITH ComPort[COM]^ DO port[BaseAddress + MCR] := port[BaseAddress + MCR] AND NOT DTR; delay( DTRsettle ) END; { TurnOffDTR } { Establish equipment readiness status } { TurnOnDTR, SendRTS, and keep OUT2 on } PROCEDURE Establish( COM : PortNo ); BEGIN WITH ComPort[COM]^ DO port[BaseAddress + MCR] := DTR OR RTS OR OUT2 END; { Establish } { Set default values } PROCEDURE InitComPortData( COM : PortNo ); BEGIN ComPort[COM] := Malloc( sizeof( PortData ) ); IF( ComPort[COM] = NIL ) THEN Fatal( 'Out of memory' ); WITH ComPort[COM]^ DO BEGIN BaseAddress := Base[COM]; IRQno := IRQline[COM]; { Enable all interrupt types } EnableBits := RBRenable OR THRenable OR LSRenable OR MSRenable; DataBits := DataBits8; StopBits := StopBits1; Parity := NoParity; LineControl := DataBits OR StopBits OR Parity; Bps := Bps9600; BaudDiv := Divisor[Bps]; Error := NoError; { The following should be re-initialized because they are volatile } Lstat := 0; Mstat := 0; THRfree := false; CTSvalue := 0; DSRvalue := 0; RIvalue := 0; DCDvalue := 0; { Queue initialization } InWaiting := 0; InFront := 0; InRear := 0; OutWaiting := 0; OutFront := 0; OutRear := 0 END END; { InitComPortData } { Select bit rate by programming the PBRG } PROCEDURE SelectBitRate( COM : PortNo; Br : BpsName ); BEGIN WITH ComPort[COM]^ DO BEGIN { Update port data } Bps := Br; BaudDiv := Divisor[Br]; { Set Divisor Latch Access Bit } port[BaseAddress + LCR] := LineControl OR DLAB; { Bit rate divisor to PBRG } portw[BaseAddress + RBR] := BaudDiv; { Give port some time to settle } delay( PBRGlen ); { Reset function of RBR } port[BaseAddress + LCR] := LineControl END END; { SelectBitRate } { Set word length in Line Control Register } PROCEDURE SelectWordLength( COM : PortNo; Len : byte ); BEGIN WITH ComPort[COM]^ DO BEGIN { Update port data } DataBits := Len; LineControl := ( LineControl AND ( NOT DataBits8 ) ) OR DataBits; { Set relevant bits } port[BaseAddress + LCR] := LineControl END END; { SelectWordLength } { Set stopbits in Line Control Register } PROCEDURE SelectFraming( COM : PortNo; Stop : byte ); BEGIN WITH ComPort[COM]^ DO BEGIN { Update port data } StopBits := Stop; LineControl := ( LineControl AND ( NOT StopBits2 ) ) OR StopBits; { Set relevant bits } port[BaseAddress + LCR] := LineControl END END; { SelectFraming } { Set parity in Line Control Register } PROCEDURE SelectParity( COM : PortNo; Par : byte ); BEGIN WITH ComPort[COM]^ DO BEGIN { Update port data } Parity := Par; LineControl := ( LineControl AND ( NOT Parity0 ) ) OR Parity; { Set relevant bits } port[BaseAddress + LCR] := LineControl END END; { SelectParity } { Asynchronous interrupt handler } PROCEDURE ComHandler; INTERRUPT; VAR COM : PortNo; Id, DataByte : byte; BEGIN { Find out which port interrupted } IF( ( RIL AND ( 1 SHL IRQline[COM1] ) ) <> 0 ) THEN COM := COM1 ELSE COM := COM2; WITH ComPort[COM]^ DO BEGIN { Find out type of interrupt } Id := port[BaseAddress + IIR]; { Enable all other interrupts } STI; CASE Id OF MSRchange : BEGIN Mstat := port[BaseAddress + MSR]; IF( ( Mstat AND CTSchanged ) <> 0 ) THEN IF( ( Mstat AND CTSlevel ) <> 0 ) THEN CTSvalue := 1 ELSE CTSvalue := 0; IF( ( Mstat AND DSRchanged ) <> 0 ) THEN IF( ( Mstat AND DSRlevel ) <> 0 ) THEN DSRvalue := 1 ELSE DSRvalue := 0; {$ifndef UPNEW} IF( ( Mstat AND RIchanged ) <> 0 ) THEN IF( ( Mstat AND RIlevel ) <> 0 ) THEN RIvalue := 1 ELSE RIvalue := 0; IF( ( Mstat AND DCDchanged ) <> 0 ) THEN IF( ( Mstat AND DCDlevel ) <> 0 ) THEN DCDvalue := 1 ELSE DCDvalue := 0; {$endif} { Take the opportunity to maybe update THRfree } Lstat := port[BaseAddress + LSR]; IF( ( Lstat AND THRempty ) <> 0 ) THEN THRfree := true END; THRemptied : BEGIN THRfree := true; Lstat := port[BaseAddress + LSR] END; RBRfilled : BEGIN DataByte := port[BaseAddress + RBR]; Lstat := port[BaseAddress + LSR]; PutIn( COM, DataByte ) END; LSRerror : BEGIN Lstat := port[BaseAddress + LSR]; Error := Error OR ( Lstat AND ErrorMask ); { Take the opportunity to maybe update THRfree } IF( ( Lstat AND THRempty ) <> 0 ) THEN THRfree := true END ELSE BEGIN { Inactive, but take the opportunity } Mstat := port[BaseAddress + MSR]; IF( ( Mstat AND CTSchanged ) <> 0 ) THEN IF( ( Mstat AND CTSlevel ) <> 0 ) THEN CTSvalue := 1 ELSE CTSvalue := 0; IF( ( Mstat AND DSRchanged ) <> 0 ) THEN IF( ( Mstat AND DSRlevel ) <> 0 ) THEN DSRvalue := 1 ELSE DSRvalue := 0; {$ifndef UPNEW} IF( ( Mstat AND RIchanged ) <> 0 ) THEN IF( ( Mstat AND RIlevel ) <> 0 ) THEN RIvalue := 1 ELSE RIvalue := 0; IF( ( Mstat AND DCDchanged ) <> 0 ) THEN IF( ( Mstat AND DCDlevel ) <> 0 ) THEN DCDvalue := 1 ELSE DCDvalue := 0; {$endif} { Take the opportunity to maybe update THRfree } Lstat := port[BaseAddress + LSR]; IF( ( Lstat AND THRempty ) <> 0 ) THEN THRfree := true END END; { Disable all maskable interrupts } CLI; { Send specific End Of Interrupt } SendEOI( IRQno ) END END; { ComHandler } { Try to send a byte to COM } { RS-232-C protocol for transmitting data : Turn on DTR | Static condition Modem signals DSR | Static condition Send RTS signal to ask for permission Modem signals CTS when clear to send Send character when THR is empty } FUNCTION OutByte( COM : PortNo; B : byte ) : boolean; CONST Period = 1; { maybe one second ? } VAR Start : longint; S : string[3]; BEGIN {$ifdef MULTILINK} { Some multilink handling here } IF( RingStatus <> Mended ) THEN BEGIN str( RingStatus, S ); Message( 'Ring Break At ' + S ); REPEAT IF( Interrupted ) THEN Fatal( 'User interrupt' ) UNTIL( RingStatus = Mended ); ClearMessage END; { End of special multilink handling } {$endif} WITH ComPort[COM]^ DO BEGIN Establish( COM ); REPEAT IF( DSRvalue = 0 ) THEN BEGIN { Wait for DSR to come up } Start := ClockTicks; REPEAT UNTIL( DSRvalue = 1 ) OR ( TimedOut( Start, Period ) ); IF( DSRvalue = 0 ) THEN BEGIN OutByte := false; Error := Error OR TimeOut; exit END END; IF( CTSvalue = 0 ) THEN BEGIN { Wait for CTS to come up } Start := ClockTicks; REPEAT UNTIL( CTSvalue = 1 ) OR ( TimedOut( Start, Period ) ); IF( CTSvalue = 0 ) THEN BEGIN OutByte := false; Error := Error OR TimeOut; exit END END; IF( NOT THRfree ) THEN BEGIN { Wait for THR to become empty } Start := ClockTicks; REPEAT UNTIL( THRfree ) OR ( TimedOut( Start, Period ) ); IF( NOT THRfree ) THEN BEGIN OutByte := false; Error := Error OR TimeOut; exit END END UNTIL( DSRvalue = 1 ) AND ( CTSvalue = 1 ) AND ( THRfree ); port[BaseAddress + THR] := B; { Update THRfree, which is shared by the interrupt handler } THRfree := false; {$ifdef UPNEW} delay( BugDelay ); {$endif} {$ifdef DEBUG} IF( Debugging ) THEN DebugOut( B ); {$endif} OutByte := true END END; { OutByte } { Try to send as many bytes as possible } PROCEDURE OutBytes( COM : PortNo ); VAR B : byte; Junk : boolean; BEGIN WITH ComPort[COM]^ DO WHILE( OutWaiting > 0 ) DO BEGIN IF( Interrupted ) THEN BEGIN Error := Error OR UserInt; exit END; B := OutData[OutFront]; { Peek ahead } IF( OutByte( COM, B ) ) THEN { No data lost: update buffer } Junk := GetOut( COM, B ) ELSE exit END END; { OutBytes } { Report serial interface errors only } PROCEDURE ReportErrors( COM : PortNo ); BEGIN WITH ComPort[COM]^ DO IF( ( Error AND pred( TimeOut ) ) = 0 ) THEN { Do not report here } exit ELSE BEGIN { Fatal error } Vselect( output ); IF( ( Error AND OverrunErr ) <> 0 ) THEN writeln( 'Overrun error' ); IF( ( Error AND ParityErr ) <> 0 ) THEN writeln( 'Parity error' ); IF( ( Error AND FramingErr ) <> 0 ) THEN writeln( 'Framing error' ); IF( ( Error AND BreakInt ) <> 0 ) THEN writeln( 'BREAK received' ); IF( ( Error AND OutOverflow ) <> 0 ) THEN writeln( 'Output buffer overflow' ); IF( ( Error AND InOverflow ) <> 0 ) THEN writeln( 'Input buffer overflow' ); { Call exception handler } halt END END; { ReportErrors } { Return true if no more output waiting } FUNCTION AllSent( COM : PortNo ) : boolean; BEGIN WITH ComPort[COM]^ DO AllSent := ( OutWaiting <= 0 ) END; { AllSent } { Poll port: error handling, output and preparing input } PROCEDURE Poll( COM : PortNo; Sending : boolean ); BEGIN { ** Error handling ** } ReportErrors( COM ); { ** Output ** } IF( Sending ) THEN OutBytes( COM ); { ** Input ** } { Do the handshaking necessary to allow reception of characters } IF( Interrupted ) THEN Fatal( 'User interrupt' ); TurnOnDTR( COM ) END; { Poll } { Send byte B to port COM } PROCEDURE Send( COM : PortNo; B : byte ); BEGIN IF( DoTiming ) THEN inc( ByteCount ); { Buffer first, to relieve higher levels from checking burden } PutOut( COM, B ); { Now try to send it } Poll( COM, true ) END; { Send } { Try to receive a byte from the input buffer } FUNCTION Receive( COM : PortNo; VAR B : byte; Sending : boolean ) : boolean; CONST Period = 3; { Maybe should be OneSecond ? } VAR Start : longint; BEGIN Poll( COM, Sending ); Start := ClockTicks; WITH ComPort[COM]^ DO BEGIN REPEAT IF( GetIn( COM, B ) ) THEN BEGIN { Success } Receive := true; {$ifdef DEBUG} IF( Debugging ) THEN DebugIn( B ); {$endif} exit END UNTIL( TimedOut( Start, Period ) ); { Failed } Error := Error OR TimeOut; Poll( COM, Sending ); Receive := false END END; { Receive } { Collect and dispose of garbage after BREAK } PROCEDURE DisposeGarbage( COM : PortNo ); VAR Junk : byte; BEGIN REPEAT UNTIL( NOT Receive( COM, Junk, true ) ); { Reset time out error set by Receive } WITH ComPort[COM]^ DO Error := Error AND ( NOT TimeOut ) END; { DisposeGarbage } { Send break signal to modem } PROCEDURE Break( COM : PortNo ); BEGIN WITH ComPort[COM]^ DO BEGIN { Set break bit } port[BaseAddress + LCR] := LineControl OR SetBreak; { Keep break bit high } delay( BreakLen ); { reset break bit } port[BaseAddress + LCR] := LineControl; { Waste some cycles } delay( 100 ); DisposeGarbage( COM ) END END; { Break } { Reset UART } PROCEDURE ResetChip( COM : PortNo ); VAR Junk : byte; BEGIN WITH ComPort[COM]^ DO BEGIN { Dump all waiting input } WHILE( ( port[BaseAddress + LSR] AND RDR ) <> 0 ) DO Junk := port[BaseAddress + RBR]; CLI; { Allow none of the interrupt types } port[BaseAddress + IER] := 0; { Tell modem we're not ready } port[BaseAddress + MCR] := port[BaseAddress + MCR] AND NOT ( OUT2 OR DTR OR RTS ); { Disable all interrupts for this port } Disable( IRQno ); STI END END; { ResetChip } { Deinstall a previously installed port } PROCEDURE DeInstall( COM : PortNo ); BEGIN Installed[COM] := false; ResetChip( COM ) END; { DeInstall } { Install interrupt handling for port COM } PROCEDURE Install( COM : PortNo ); CONST Period = OneSecond; VAR Start : longint; BEGIN WITH ComPort[COM]^ DO BEGIN { Initialize volatile data per existing signals } Mstat := port[BaseAddress + MSR]; IF( ( Mstat AND CTSlevel ) <> 0 ) THEN CTSvalue := 1 ELSE CTSvalue := 0; IF( ( Mstat AND DSRlevel ) <> 0 ) THEN DSRvalue := 1 ELSE DSRvalue := 0; IF( ( Mstat AND RIlevel ) <> 0 ) THEN RIvalue := 1 ELSE RIvalue := 0; IF( ( Mstat AND DCDlevel ) <> 0 ) THEN DCDvalue := 1 ELSE DCDvalue := 0; Lstat := port[BaseAddress + LSR]; THRfree := ( ( Lstat AND THRempty ) <> 0 ); { Get old vector handler } IF( NOT VectorSet[COM] ) THEN BEGIN getintvec( IntrNo[COM], OldVector[COM] ); VectorSet[COM] := true END; { Set our interrupt handler } setintvec( IntrNo[COM], @ComHandler ); ResetChip( COM ); CLI; Establish( COM ); { Enable interrupts for this port } Enable( IRQno ); { Enable all wanted interrupt types } port[BaseAddress + IER] := EnableBits; { Just to be sure } SendEOI( IRQno ); STI; { Set Bit rate and other characteristics } port[BaseAddress + LCR] := DLAB; portw[BaseAddress + RBR] := BaudDiv; port[BaseAddress + LCR] := LineControl; { Waste some cycles } delay( PBRGlen ); Installed[COM] := true; { Wait for port to react } Start := ClockTicks; WHILE( CTSvalue = 0 ) DO BEGIN IF( TimedOut( Start, Period ) ) THEN Fatal( 'No modem connected..( Time out CTS )' ); END END END; { Install } PROCEDURE StartTiming; BEGIN StartTime := ClockTicks; ByteCount := 0; DoTiming := true; END; { StartTiming } PROCEDURE StopTiming; BEGIN DoTiming := false; Duration := ClockTicks - StartTime END; { StopTiming } {$ifdef MULTILINK} { *** Multilink handling **************************************************** } PROCEDURE DoString( S : string ); VAR I : integer; BEGIN FOR I := 1 TO length( S ) DO BEGIN Send( OurCom, ord( S[I] ) ); IF( ComPort[OurCom]^.Error <> NoError ) THEN exit END END; { DoString } { Initialize Multilink Node to intelligent mode } PROCEDURE InitNode; CONST MaxTries = 10; VAR Tries : integer; B : byte; Options : string; BEGIN Tries := 0; REPEAT { Reset errors from last try } WITH ComPort[OurCom]^ DO Error := Error AND NOT ( TimeOut OR UserInt ); inc( Tries ); { Send BREAK <space> INIT to node } Break( OurCom ); DoString( ' INIT'^M ); DisposeGarbage( OurCom ); { Waste some cycles } delay( 200 ); { Select channel 0 by sending a BREAK } Break( OurCom ); { Set Intelligent mode, no Xon/Xoff } DoString( 'SINX'^M ); { Select channel 0 } Break( OurCom ); { Ask for options } DoString( 'O'^M ); Options := ''; REPEAT IF( Receive( OurCom, B, true ) ) THEN Options := Options + chr( B ) ELSE IF( ( ComPort[OurCom]^.Error AND UserInt ) <> 0 ) THEN Fatal( 'User interrupt' ) UNTIL( B = ACK ) OR ( ComPort[OurCom]^.Error <> NoError ) { If we have an error here, it is a Timeout error } UNTIL( ( ComPort[OurCom]^.Error = NoError ) AND ( pos( ' INV', Options ) > 0 ) ) { Ok, I know, but it works } OR ( Tries > MaxTries ); IF( Tries > MaxTries ) THEN Fatal( 'Node initialization failed' ); NodeInit := true END; { InitNode } { Read real 2-byte nodename from RAM in node } PROCEDURE ReadNodeName; CONST Digits : string[16] = '0123456789ABCDEF'; VAR B : byte; I : integer; Temp : ARRAY [1..50] OF byte; { 50 is large enough } PROCEDURE CheckError; BEGIN WITH ComPort[OurCom]^ DO IF( Error <> NoError ) THEN IF( ( Error AND UserInt ) <> 0 ) THEN Fatal( 'User interrupt' ) ELSE Fatal( 'Failed to read node name' ) END; { CheckError } { Decode returned data } FUNCTION Character( I, J : integer ) : char; BEGIN Character := chr( 16 * pred( pos( chr( Temp[I] ), Digits ) ) + pred( pos( chr( Temp[J] ), Digits ) ) ) END; { Character } BEGIN { Select channel 0 } Send( OurCom, DLEh ); Send( OurCom, 0 ); { Request RAM reading } DoString( 'R 0050'^M ); CheckError; { Make sure we sent all } REPEAT Poll( OurCom, true ) UNTIL( AllSent( OurCom ) ); I := 0; REPEAT IF( Receive( OurCom, B, true ) ) THEN BEGIN inc( I ); Temp[I] := B END UNTIL( ComPort[OurCom]^.Error <> NoError ) OR ( ( B OR HighBit ) = ACKh ); CheckError; NodeName := Character( 4, 5 ) + Character( 7, 8 ) END; { ReadNodeName } { General multlink initialization } PROCEDURE InitMultilink; VAR Chan : integer; BEGIN { Init status variables } FOR Chan := 0 TO MaxChannel DO BEGIN Busy[Chan] := false; Connected[Chan] := false END; NodeChannel := 0; EMreceived := false; DLEreceived := false; RingStatus := Mended; { Install handler for COM } Install( OurCom ); { Initialize node to a predetermined state } InitNode; { Get real node name } ReadNodeName; { Re-initialize node } InitNode END; { InitMultilink } {$endif} {$F+} { Exception handler } PROCEDURE ExitUnit; VAR COM : PortNo; BEGIN {$ifdef MULTILINK} IF( NodeInit ) THEN BEGIN { Multilink cleaning up } NodeInit := false; { Select channel 0 } Send( OurCom, DLEh ); Send( OurCom, 0 ); { Request RAM reading } DoString( 'n' + NodeName + ^M ); REPEAT Poll( OurCom, true ) UNTIL( AllSent( OurCom ) ); Break( OurCom ); DoString( ' INIT'^M ); Break( OurCom ) END; {$endif} FOR COM := COM1 TO COM2 DO BEGIN IF( Installed[COM] ) THEN DeInstall( COM ); IF( VectorSet[COM] ) THEN BEGIN VectorSet[COM] := false; setintvec( IntrNo[COM], OldVector[COM] ) END; Free( ComPort[COM] ) END; exitproc := ExitSave END; { ExitUnit } {$F-} PROCEDURE InitExceptionHandling; VAR COM : PortNo; BEGIN FOR COM := COM1 TO COM2 DO BEGIN VectorSet[COM] := false; Installed[COM] := false END; {$ifdef MULTILINK} NodeInit := false; {$endif} ExitSave := exitproc; exitproc := @ExitUnit END; { InitExceptionHandling } { Overall initialization } PROCEDURE InitUnit; VAR COM : PortNo; BEGIN NoKeys := false; DoTiming := false; InitExceptionHandling; FOR COM := COM1 TO COM2 DO InitComPortData( COM ); {$ifdef MULTILINK} InitMultilink {$endif} END; { InitUnit } BEGIN InitUnit END. -- Reino de Boer Erasmus Universiteit Rotterdam The Netherlands. Vakgroep AIV reino@euraiv1.uucp [All hail Discordia]