program TERMINAL; { ************************************************************************** } { TERMINAL } { Turbo Pascal 7.0 Demo Programm } { Written 1995 by Stephan A. Maciej } { Internet: stephan@maciej.muc.de } { For any questions, please mail to support@maciej.muc.de ! } { WWW: http://www.muc.de/~stephanm } { ************************************************************************** } { This program is for demonstration purpose only. Any commercial use with- } { out the written permission of the author is illegal. Please report bugs, } { corrections or any other ideas to stephan@maciej.muc.de. You are allowed } { to distribute this program as often as you want as long as you do not } { change it or edit it anyway. The author is not responsible for any damage } { or destruction caused - directly or indirectly - by this program. } { ************************************************************************** } uses Crt; const TxDataReg = 0; { transmitter data register } RxDataReg = 0; { reciever data register } DivLow = 0; { divisor latch, low byte } DivHigh = 1; { divisor latch, high byte } IntrEnable = 1; { interrupt enable register } IntrId = 2; { interrupt identification register } FifoCtrl = 2; { first-in/first-out buffer controller } LineCtrl = 3; { line controll register } ModemCtrl = 4; { modem controll register } LineStatus = 5; { line status register } ModemStatus = 6; { modem status register } ScratchReg = 7; { scratch pad (free useable) } const QueueLen = 1024; { Length of a queue in bytes } type { The TQueue type } TQueue = record Content: array[0..QueueLen - 1] of byte; Start: word; Stop: word; end; type { The TQuadString type } TQuadString = string[4]; { For the Hex-function } var { The two queues, one for incoming characters, the other for } { outgoing characters. } InQueue: TQueue; { The two queues: one for input buffering, } OutQueue: TQueue; { the other for output buffering } const SpeedCount = 19; { Number of valid speeds stored below } const AllowedSpeeds: array[0..SpeedCount] of longint = ( 50, 75, 110, 150, 300, 600, 900, 1200, 1800, 2400, 3600, 4800, 7200, 9600, 14400, 19200, 28800, 38400, 57600, 115200); { Just add any other speeds supported by your UART/Modem - don't } { forget to increase the SpeedCount } var { Some global variables... } PortNr: byte; { Number of the used port (1, 2 etc.) } PortBase: word; { I/O base address of the used port } Speed: longint; { Speed in baud } UARTType: byte; { UART type (one of the UART_xxxx constants } UsedIRQ: byte; { The number of the used IRQ } procedure SendEOI; assembler; { Send a EOI to the Interrupt Controller } asm mov al, 20h out 20h, al end; function GetPortBase(N: byte): word; assembler; { Read the I/O base address of the desired port from the BIOS data segment } asm { Load the segment address of the BIOS data segment into ES } mov ax, 0040h mov es, ax { Calculate the offset of the I/O port base address } xor ax, ax mov al, N dec al shl ax, 1 mov si, ax { Read the desired value and return } mov ax, es:[si] end; procedure SetIntVec(N: byte; P: pointer); assembler; { Set an interrupt vector to the given address } asm push ds { Just use the MS-DOS function 25h to set the vector } mov ah, 25h mov al, N { ds:dx contains the vector to set } lds dx, P int 21h pop ds end; function GetIntVec(N: byte): pointer; assembler; { Get the interrupt vector } asm push es { Use the MS-DOS function 35h to read the vector } mov ah, 35h mov al, N int 21h { Move the vector from es:bx to dx:ax } mov ax, bx mov dx, es pop es end; procedure ResetQueue(var Q: TQueue); { Resets a Queue } begin Q.Start := 0; Q.Stop := 0; end; procedure PutQueueByte(var Q: TQueue; B: byte); { Put a byte into the Queue } begin { Put the byte into the Queue before incrementing the Queue end } Q.Content[Q.Stop] := B; { Now increment the Queue end position. When Q.Stop reaches QueueLen, } { be sure you don't increment Q.Stop but wrap it around to 0 again ! } inc(Q.Stop); if (Q.Stop = QueueLen) then Q.Stop := 0; end; function GetQueueByte(var Q: TQueue; var B: byte): boolean; { Get a byte out of the Queue } begin if (Q.Stop = Q.Start) then { If the Queue is empty, just return false. Don't set B anyway. } GetQueueByte := false else begin { Queue is not empty: return true. } GetQueueByte := true; { Get the first byte out of the Queue and return it in B. } B := Q.Content[Q.Start]; { Now increment the Queue position. Be sure to wrap it to zero } { if the Q.Start field reaches the QueueLen constant. } inc(Q.Start); if (Q.Start = QueueLen) then Q.Start := 0; end; end; procedure SerialInterrupt; interrupt; { This procedure handles any incoming events from the UART. } var Id: byte; Trash: byte; begin repeat { Now read the Interrupt Identification register } Id := Port[PortBase + IntrId]; { Check if there's any pending interrupt. } if ((Id and 1) = 0) then begin { Now select the event. } case ((Id and 6) shr 1) of $03: { The Line Status register changed. } begin { Just read the LSR to clear the event. } Trash := Port[PortBase + LineStatus]; end; $02: { Data arrived at the UART. } begin { Read out the data from the RxD register and store } { it in the incoming Queue. } Trash := Port[PortBase + RxDataReg]; PutQueueByte(InQueue, Trash); end; $01: { The TxD register is empty. } begin { If there's any byte in the outgoing Queue, send it to the } { UART, else disable this interrupt. } if (GetQueueByte(OutQueue, Trash)) then Port[PortBase + TxDataReg] := Trash else Port[PortBase + IntrEnable] := $0D; end; $00: { The Modem Status register changed. } begin { Just read the MSR to clear the event. } Trash := Port[PortBase + ModemStatus]; end; end; end; { Handle all interrupts ! Just check if there's } { one more interrupt pending. } until ((Id and 1) = 1); { Now tell the PIC our interrupt handler has finished it's work. } SendEOI; end; procedure SendChar(C: char); { Send a char to the modem } begin { Put the character into the outgoing queue. } PutQueueByte(OutQueue, byte(C)); { Enable the "Transmitter register empty" interrupt } Port[PortBase + IntrEnable] := $0F; end; const UART_Bad = 0; { Bad UART: not working or unidentifieable } UART_8250 = 1; { Standart 8250 UART } UART_16450 = 2; { 16450 UART (faster than 8250) } UART_16550 = 3; { 16550 UART (with buggy 16-byte FIFO) } UART_16550A = 4; { 16550A UART (with working FIFO) } function GetUARTType(Base: word): byte; assembler; { Check which UART type is assigned to the appropriate port } asm { First difference: The 16450 has a scratch register which is } { readable and writeable. Check if it's there. If not, we've } { got a 8250 UART. } mov dx, Base add dx, ScratchReg mov al, 0AAh out dx, al in al, dx cmp al, 0AAh je @@1 mov ax, UART_8250 jmp @@5 @@1: { Now check out if the UART has got a FIFO. If it has none, it's } { a 16450, if it has one but it's not working it's a 16550. } { The UART will be identified as a 16550A if the FIFO is working. } mov dx, Base add dx, FifoCtrl mov al, 01h out dx, al nop mov dx, Base add dx, IntrId in al, dx and al, 0C0h cmp al, 0C0h jne @@2 mov al, UART_16550A jmp @@5 @@2: cmp al, 80h jne @@3 mov al, UART_16550 jmp @@5 @@3: cmp al, 0 jne @@4 mov al, UART_16450 jmp @@5 @@4: mov al, UART_Bad @@5: nop end; procedure UpCaseStr(var S: string); assembler; { Convert all chars in a string to uppercase letters } asm les di, S xor cx, cx mov cl, es:[di] inc di @@1: mov al, es:[di] cmp al, 'a' jb @@2 cmp al, 'z' ja @@2 { Chars between 'a' and 'z' will be uppercased here. } sub al, 20h @@2: mov es:[di], al inc di loop @@1 end; function IsAllowedSpeed(Speed: longint): boolean; { Check if a speed is valid or not } var I: byte; begin { Return false by default. } IsAllowedSpeed := false; { Check if you can find the desired speed in the speeds table. } { If you found it, return "true". } for I := 0 to SpeedCount do if (AllowedSpeeds[I] = Speed) then begin IsAllowedSpeed := true; exit; end; { The default value ("false") will be returned if the speed wasn't } { found in the table. } end; procedure GetCommandLine(var PortId: byte; var Speed: longint); { Check the command line and extract all parameters } var S: string; I: byte; J: integer; begin { If less than 1 parameter is specified, print a little help and } { terminate the programm. } if (ParamCount <=0) THEN BEGIN writeln(' is one out of COM1, COM2, COM3 or COM4.'); writeln(' is the desired communication speed. The default value is 9600.'); writeln; { Halt the programm immediately. } halt; end; S := ''; { Just create one long string from all parameters } for I := 1 to ParamCount do S := S + ParamStr(I); { Convert all lower-cased characters in that string to uppercased-chars } UpCaseStr(S); { The first three bytes of the string must be 'COM'. If not, } { there's an error in the command line. } if (copy(S, 1, 3) <> 'COM') or (S[5] <> '/') then begin writeln('Error in command line. Call TERMINAL without options to see the help text.'); halt; end; { Extract the number of the COM port that shall be used. } I := byte(S[4]) - ord('0'); { Check for it's boundaries ! } if (i > 4) then begin writeln('Wrong COM port specified.'); halt; end else PortId := I; { Now extract the desired speed and check if it's a valid input. } val(copy(S, 6, length(S) - 5), Speed, J); if (J > 0) then begin writeln('Error in speed specifier.'); halt; end; { At last, check if the desired speed is supported. } if (not IsAllowedSpeed(Speed)) then begin writeln('The speed you selected is not supported by this TERMINAL.'); halt; end; end; procedure EnableIRQ(IRQ: byte); assembler; { Enable a given IRQ from 0 to 7 } asm mov cl, IRQ mov bl, 1 shl bl, cl mov ah, 255 sub ah, bl in al, 21h and al, ah out 21h, al end; procedure DisableIRQ(IRQ: byte); assembler; { Disable a given IRQ from 0 to 7 } asm mov cl, IRQ mov bl, 1 shl bl, cl in al, 21h or al, bl out 21h, al end; procedure PrintChar(Character: char); assembler; { Print a character on the screen - fast } asm mov ah, 0Eh mov al, Character xor bh, bh mov bl, 07h int 10h end; var OldInterruptVec: pointer; procedure SetupPort(PortBase: word; Speed: longint; IRQ: byte); { Setup the UART and prepare for communication. } var D: word; B: byte; begin { For startup, disable the IRQ for the UART. } DisableIRQ(IRQ); { Get the address of the old interrupt handler and set the vector } { to our won interrupt handling procedure ("SerialInterrupt") } OldInterruptVec := GetIntVec($08 + IRQ); SetIntVec($08 + IRQ, @SerialInterrupt); { Enable the "Recieved Data avaliable" interrupt so we can } { read all data out of the UART's RxD register. } Port[PortBase + IntrEnable] := $01; { Now clear all pending interrupts - if any } repeat { Read all important registers to clear any interrupt types, } { B is just used for temporary result storage } B := Port[PortBase + RxDataReg]; B := Port[PortBase + LineStatus]; B := Port[PortBase + ModemStatus]; { Just repeat until no more interrupts are pending. } until ((Port[PortBase + IntrId] and 1) = 1); { Enable the IRQ line for the UART after all pending interrupts } { have been cleared. } EnableIRQ(IRQ); { Calculate the divisor latch contents for the desired baud rate } D := (115200 div Speed); { Set the DLAB bit to 1, then write the divisor latch low and high bytes } Port[PortBase + LineCtrl] := $80; Port[PortBase + DivLow] := Lo(D); Port[PortBase + DivHigh] := Hi(D); { Now set the divisor latch bit to 0 and write all other values } Port[PortBase + LineCtrl] := $03; Port[PortBase + ModemCtrl] := $0B; Port[PortBase + IntrEnable] := $0F; { Check if a 16550A UART is present... } if (UARTType = UART_16550A) then begin { Clear the FIFO queues } Port[PortBase + FifoCtrl] := $07; { Enable the FIFO queues } Port[PortBase + FifoCtrl] := $C1; { Print a message so the user recognizes the FIFO queues are on } writeln('Enabling 16550A FIFO queues...'); end; end; procedure RunTerminal(PortBase: word); { Run the Terminal } var B: char; begin { Just print a free line } Writeln; repeat { Was a key pressed ? If yes, the character is written } { into the outgoing queue. } if (KeyPressed) then begin { Read the key. If it was , the terminal session will } { be aborted, else the character will be sent out. } B := ReadKey; if (B = #27) then SendChar(B); end { Is there any character in the incoming queue ? } else if (GetQueueByte(InQueue, byte(B))) then { If yes, just get the character and write it onto screen. } PrintChar(B); { Repeat this loop until the key was pressed. } until (B = #27); end; procedure ResetPort(PortBase: word; IRQ: byte); { Reset UART and reset interrupt vectors } begin { Reset the DLAB bit. Clear all other registers. } Port[PortBase + LineCtrl] := Port[PortBase + LineCtrl] and $7F; Port[PortBase + ModemCtrl] := 0; Port[PortBase + IntrEnable] := 0; { Disable the IRQ for the UART, then restore the old interrupt vector. } DisableIRQ(IRQ); SetIntVec($08 + IRQ, OldInterruptVec); { Inform the user that the terminal session was aborted. } Writeln; Writeln('Port closed.'); end; function Hex(W: word): TQuadString; { Convert a number into hexadecimal outfit } const HexChars: array[0..15] of char = '0123456789ABCDEF'; begin Hex := HexChars[W shr 12] + HexChars[(W shr 8) and 15] + HexChars[(W shr 4) and 15] + HexChars[W and 15]; end; procedure WritePortInfo; { Output some info about the selected port } begin write('Using COM', PortNr, ' (base address ', Hex(PortBase), ', IRQ ', UsedIRQ); write(', UART is a '); { Print the detected UART type } case UARTType of UART_8250: write('8250'); UART_16450: write('16450'); UART_16550: write('16550'); UART_16550A: write('16550A'); end; writeln(')...'); end; {--- Main Routine ---} begin { Just output some information. } writeln; writeln('TERMINAL Version 1.00 Written 1995 by Stephan A. Maciej'); writeln('Internet: stephan@maciej.muc.de http://www.muc.de/~stephanm'); writeln; { Check for some correct parameters on the command line. } GetCommandLine(PortNr, Speed); PortBase := GetPortBase(PortNr); if (PortBase = 0) then begin writeln('COM', PortNr, ': no such port !'); halt; end; UsedIRQ := 4 - ((PortNr - 1) and 1); UARTType := GetUARTType(PortBase); { Reset both the incoming as well as the outgoing Queue. } ResetQueue(InQueue); ResetQueue(OutQueue); { Write some information about the selected port. } WritePortInfo; { Setup the port, run the terminal and reset the port when ready. } SetupPort(PortBase, Speed, UsedIRQ); RunTerminal(PortBase); ResetPort(PortBase, UsedIRQ); end.