home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Beijing Paradise BBS Backup
/
PARADISE.ISO
/
software
/
BBSDOORW
/
UUPC11XS.ZIP
/
UUCICO
/
COMMFIFO.ASM
< prev
next >
Wrap
Assembly Source File
|
1992-12-18
|
50KB
|
1,617 lines
TITLE COMM
PAGE 83,132
; $Id: COMMFIFO.ASM 1.2 1992/12/18 12:08:25 ahd Exp $
;
; $Log: COMMFIFO.ASM $
;; Revision 1.2 1992/12/18 12:08:25 ahd
;; Add Plummer's fix for bad TASM assemble of com_errors
;;
;
; 2-Dec-92 plummer Fix com_errors() again. Change got lost.
; Fix com_errors() to avoid problems with tasm. Plummer, 11/16/92
; 8259 EOI issued after interrupts serviced. Plummer, 3/25/92
; Fix botch in Set_Baud. Plummer, 3/20/92
; Put in Gordon Lee's cure from dropped interrupts. Plummer, 3/19/92
; TEMPORARY ioctl_com(). Plummer, 3/9/92.
; Clear OUT2 bit in UART. Some machines use it so enable IRQ. Plummer, 3/9/92
; Release send buffer if we can't assign a recv buffer. Plummer, 3/9/92
; Move EQU's outside of SP_TAB struc definition. ahd, 3/8/92.
; ahd changes: short jmp's out of range in INST, OPEN ??? (ahd, 3/?/92)
; open_com() leaves DTR unchanged so Drew's autobaud works. Plummer, 3/2/92
; Missing DX load in close_com() -- FIFO mode not cleared. Plummer, 3/2/92
; C calling convention does not require saving AX, BX, CX, DX. Plummer 2/23/92
; Flush consideration of the PC Jr. Wm. W. Plummer, 2/15/92
; Cleanup PUSHF/POPF and CLI/STI useage. Wm. W. Plummer, 2/15/92
; Make SENDII have Giles Todd's change. Wm. W. Plummer, 2/15/92
; Changes to Giles Todd's code to support dynamic buffers. Plummer, 2/3/92
; 26 Jan 92 Giles Todd Prime THR for UARTs which do not give a Tx empty
; interrupt when Tx interrupts are enabled.
; S_SIZE & R_SIZE may be set with -D to MASM. Wm. W. Plummer, 1/19/92
; Assign buffers dynamically. Wm. W. Plummer, 1/19/92
; Unfix byte length -- I screwed up. Wm. W. Plummer, 1/15/92
; Fix byte length with specific PARITY select. Wm. W. Plummer, 1/13/92
; Buffers up to 4096 per AHD. Wm. W. Plummer, 1/1/92
; Always use FIFO length of 16 on send side. Wm. W. Plummer, 12/30/91.
; Init DSR and CTS previous state from current status. Wm. Plummer, 12/30/91.
; UUPC conditional to disable v.24. Wm. W. Plummer, 12/30/91.
; Buffer sizes up to 2048 per ahd. Wm. W. Plummer, 12/15/91.
; dtr_on() switches to D connection if CTS&DSR don't come up. WWP, 12/15/91.
; New dtr_on() logic. Wm. W. Plummer, 12/11/91
; Fix bad reg. per report from user. Wm. W. Plummer, 12/11/91
; Semicolon before control-L's for MASM 5.00 per ahd. Wm. W. Plummer, 12/8/91
; Use AHD's handling of COM ports. Wm. W. Plummer, 11/29/91
; Buffer sizes reduced and required to be 2**N. Wm. W. Plummer, 11/11/91
; Accomodate V.24 requirements on DTR flaps. Wm. W. Plummer 10/15/91
; Revised DTR_ON_COM to solve user problem. Wm. W. Plummer, 10/3/91
; Make time delays independent of CPU speed. Wm. W. Plummer, 9/16/91
; Use interrupts to trace CD, DSR, Wm. W. Plummer, 9/16/91
; Remove modem control from TXI. Wm. W. Plummer, 9/13/91
; Completely redo the XOFF/XON logic. Too many races before. Wm. W. Plummer
; Revise interrupt dispatch for speed & function. William W. Plummer, 9/12/91
; Merge in ahd's changes to flush control Q,S when received as flow control
; SEND buffer allows one byte for a SENDII call. Avoids flow control
; lockups. - William W. Plummer, 8/30/91
; Support for NS16550A chip with SILO - William W. Plummer, 8/30/91
; Add modem_status() routine - William W. Plummer, 7/2/91
; Put wrong code under AHD conditional - William W. Plummer, 7/2/91
; Change TITLE, repair bad instr after INST3 - William W. Plummer, 7/1/91
; Modified to use COM1 thru COM4 - William W. Plummer, 2/21/91
; Eliminate (incomplete) support for DOS1 - William W. Plummer, 11/13/90
; Changes may be copied and modified with no notice. Copyrights and copylefts
; are consider silly and do not apply. -- William W. Plummer
; modified to use MSC calling sequence. jrr 3/86
;****************************************************************************
; Communications Package for the IBM PC, XT, AT and strict compatibles.
; May be copied and used freely -- This is a public domain program
; Developed by Richard Gillmann, John Romkey, Jerry Saltzer,
; Craig Milo Rogers, Dave Mitton and Larry Afrin.
;
; We'd sure like to see any improvements you might make.
; Please send all comments and queries about this package
; to GILLMANN@USC-ISIB.ARPA
;
; o Supports both serial ports simultaneously
; o All speeds to 19200 baud
; o Compatible with PC, XT, AT
; o Built in XON/XOFF flow control option
; o C language calling conventions
; o Logs all comm errors
; o Direct connect or modem protocol
PAGE;
;
; Buffer sizes -- *** MUST be powers of 2 ****
IFDEF UUPC
R_SIZE EQU 4096
S_SIZE EQU 4096
ENDIF
; If not set above, maybe on assembler command line. But if not, ...
IFNDEF R_SIZE
R_SIZE EQU 512 ; Recv buffer size
ENDIF
IFNDEF S_SIZE
S_SIZE EQU 512 ; Send buffer size
ENDIF
; INTERRUPT NUMBERS
INT_COM1 EQU 0CH ; COM1: FROM 8259
INT_COM2 EQU 0BH ; COM2: FROM 8259
INT_COM3 EQU 0CH ; COM3: FROM 8259
INT_COM4 EQU 0BH ; COM4: FROM 8259
; 8259 PORTS
INTA00 EQU 20H ; 8259A PORT, A0 = 0
INTA01 EQU 21H ; 8259A PORT, A0 = 1
; COM1: & COM3: LEVEL 4
IRQ4 EQU 2*2*2*2 ; 8259A OCW1 MASK, M4=1, A0=0
NIRQ4 EQU NOT IRQ4 AND 0FFH ; COMPLEMENT OF ABOVE
EOI4 EQU 4 OR 01100000B ; 8259A OCW2 SPECIFIC IRQ4 EOI, A0=0
; COM2: & COM4: LEVEL 3
IRQ3 EQU 2*2*2 ; 8259A OCW1 MASK, M3=1, A0=0
NIRQ3 EQU NOT IRQ3 AND 0FFH ; COMPLEMENT OF ABOVE
EOI3 EQU 3 OR 01100000B ; 8259A OCW2 SPECIFIC IRQ3 EOI, A0=0
; FLOW CONTROL CHARACTERS
CONTROL_Q EQU 11H ; XON
CONTROL_S EQU 13H ; XOFF
; MISC.
DOS EQU 21H ; DOS FUNCTION CALLS
;
; ROM BIOS Data Area
;
RBDA SEGMENT AT 40H
RS232_BASE DW 4 DUP(?) ; ADDRESSES OF RS232 ADAPTERS
RBDA ENDS
PAGE;
;
; TABLE FOR EACH SERIAL PORT
;
SP_TAB STRUC
PORT DB ? ; 1 OR 2 OR 3 OR 4
; PARAMETERS FOR THIS INTERRUPT LEVEL
INT_COM DB ? ; INTERRUPT NUMBER
IRQ DB ? ; 8259A OCW1 MASK
NIRQ DB ? ; COMPLEMENT OF ABOVE
EOI DB ? ; 8259A OCW2 SPECIFIC END OF INTERRUPT
; INTERRUPT HANDLERS FOR THIS LEVEL
INT_HNDLR DW ? ; OFFSET TO INTERRUPT HANDLER
OLD_COM_OFF DW ? ; OLD HANDLER'S OFFSET
OLD_COM_SEG DW ? ; OLD HANDLER'S SEGMENT
; ATTRIBUTES
INSTALLED DB ? ; IS PORT INSTALLED ON THIS PC? (1=YES,0=NO)
BAUD_RATE DW ? ; 19200 MAX
CONNECTION DB ? ; M(ODEM), D(IRECT)
PARITY DB ? ; N(ONE), O(DD), E(VEN), S(PACE), M(ARK)
STOP_BITS DB ? ; 1, 2
XON_XOFF DB ? ; E(NABLED), D(ISABLED)
; FLOW CONTROL STATE
HOST_OFF DB ? ; HOST XOFF'ED (1=YES,0=NO)
PC_OFF DB ? ; PC XOFF'ED (1=YES,0=NO)
URGENT_SEND DB ? ; We MUST send one byte (XON/XOFF)
SEND_OK DB ? ; DSR and CTS are ON
; ERROR COUNTS
ERROR_BLOCK DW 8 DUP(?); EIGHT ERROR COUNTERS
; UART PORTS - DATREG thru MSR must be in order shown.
DATREG DW ? ; DATA REGISTER
IER DW ? ; INTERRUPT ENABLE REGISTER
IIR DW ? ; INTERRUPT IDENTIFICATION REGISTER (RO)
LCR DW ? ; LINE CONTROL REGISTER
MCR DW ? ; MODEM CONTROL REGISTER
LSR DW ? ; LINE STATUS REGISTER
MSR DW ? ; MODEM STATUS REGISTER
UART_SILO_LEN DB ? ; Size of a silo chunk (1 for 8250)
;
; BUFFER POINTERS
START_TDATA DW ? ; INDEX TO FIRST CHARACTER IN X-MIT BUFFER
END_TDATA DW ? ; INDEX TO FIRST FREE SPACE IN X-MIT BUFFER
START_RDATA DW ? ; INDEX TO FIRST CHARACTER IN REC. BUFFER
END_RDATA DW ? ; INDEX TO FIRST FREE SPACE IN REC. BUFFER
; BUFFER COUNTS
SIZE_TDATA DW ? ; NUMBER OF CHARACTERS IN X-MIT BUFFER
SIZE_RDATA DW ? ; NUMBER OF CHARACTERS IN REC. BUFFER
; BUFFERS
TBuff DD ? ; Pointer to transmit buffer
RBuff DD ? ; Pointer to receive buffer
SP_TAB ENDS
; SP_TAB EQUATES
; WE HAVE TO USE THESE BECAUSE OF PROBLEMS WITH STRUC
EOVFLOW EQU ERROR_BLOCK ; BUFFER OVERFLOWS
EOVRUN EQU ERROR_BLOCK+2 ; RECEIVE OVERRUNS
EBREAK EQU ERROR_BLOCK+4 ; BREAK CHARS
EFRAME EQU ERROR_BLOCK+6 ; FRAMING ERRORS
EPARITY EQU ERROR_BLOCK+8 ; PARITY ERRORS
EXMIT EQU ERROR_BLOCK+10 ; TRANSMISSION ERRORS
EDSR EQU ERROR_BLOCK+12 ; DATA SET READY ERRORS
ECTS EQU ERROR_BLOCK+14 ; CLEAR TO SEND ERRORS
DLL EQU DATREG ; LOW DIVISOR LATCH
DLH EQU IER ; HIGH DIVISOR LATCH
;
; Equates having to do with the FIFO
;
FCR EQU IIR ; FIFO Control Register (WO)
; Bits in FCR for NS16550A UART. Note that writes to FCR are ignored
; by other chips.
FIFO_ENABLE EQU 001H ; Enable FIFO mode
FIFO_CLR_RCV EQU 002H ; Clear receive FIFO
FIFO_CLR_XMT EQU 004H ; Clear transmit FIFO
FIFO_STR_DMA EQU 008H ; Start DMA Mode
; 10H and 20H bits are register bank select on some UARTs (not handled)
FIFO_SZ_4 EQU 040H ; Warning level is 4 before end
FIFO_SZ_8 EQU 080H ; Warning level is 8 before end
FIFO_SZ_14 EQU 0C0H ; Warning level is 14 before end
;
; Commands used in code to operate FIFO. Made up as combinations of above
;
FIFO_CLEAR EQU 0 ; Turn off FIFO
FIFO_SETUP EQU FIFO_SZ_14 OR FIFO_ENABLE
FIFO_INIT EQU FIFO_SETUP OR FIFO_CLR_RCV OR FIFO_CLR_XMT
;
; Miscellaneous FIFO-related stuff
;
FIFO_ENABLED EQU 0C0H ; 16550 makes these equal FIFO_ENABLE
FIFO_LEN EQU 16 ; Length of the FIFOs in a 16550A
PAGE;
; put the data in the DGROUP segment
; far calls enter with DS pointing to DGROUP
;
DGROUP GROUP _DATA
_DATA SEGMENT PUBLIC 'DATA'
;
DIV50 DW 2304 ; ACTUAL DIVISOR FOR 50 BAUD IN USE
CURRENT_AREA DW AREA1 ; CURRENTLY SELECTED AREA
; DATA AREAS FOR EACH PORT
AREA1 SP_TAB <1,INT_COM1,IRQ4,NIRQ4,EOI4> ; COM1 DATA AREA
AREA2 SP_TAB <2,INT_COM2,IRQ3,NIRQ3,EOI3> ; COM2 DATA AREA
AREA3 SP_TAB <3,INT_COM3,IRQ4,NIRQ4,EOI4> ; COM3 DATA AREA
AREA4 SP_TAB <4,INT_COM4,IRQ3,NIRQ3,EOI3> ; COM4 DATA AREA
_DATA ENDS
COM_TEXT SEGMENT PARA PUBLIC 'CODE'
ASSUME CS:COM_TEXT,DS:DGROUP,ES:NOTHING
PUBLIC AREA1, AREA2, AREA3, AREA4
PUBLIC _select_port
PUBLIC _save_com
PUBLIC _install_com
PUBLIC _restore_com
PUBLIC _open_com
PUBLIC _ioctl_com
PUBLIC _close_com
PUBLIC _dtr_on
PUBLIC _dtr_off
PUBLIC _r_count
PUBLIC _s_count
PUBLIC _receive_com
PUBLIC _send_com
PUBLIC _sendi_com
IFNDEF UUPC
PUBLIC _send_local
ENDIF
PUBLIC _break_com
PUBLIC _com_errors
PUBLIC _modem_status
IFDEF DEBUG
PUBLIC INST2, INST4
PUBLIC OPEN1, OPEN2, OPENX
PUBLIC DTRON1, DTRON6, DTRONF, DTRONS, DTRONX
PUBLIC RECV1, RECV3, RECV4, RECVX
PUBLIC SEND1, SENDX
PUBLIC WaitN, WaitN1, WaitN2
PUBLIC SENDII, SENDII2, SENDII4, SENDIIX
PUBLIC CHROUT, CHROUX
PUBLIC BREAKX
PUBLIC INT_HNDLR1, INT_HNDLR2, INT_HNDLR3, INT_HNDLR4
PUBLIC INT_COMMON, REPOLL, INT_END
PUBLIC LSI
PUBLIC MSI
PUBLIC TXI, TXI1, TXI2, TXI3, TXI9
PUBLIC TX_CHR
PUBLIC RXI, RXI0, RXI1, RXI2, RXI6, RXIX
ENDIF
PAGE;
; Notes, thoughts and explainations by Bill Plummer. These are intended to
; help those of you who would like to make modifications.
; Here's the order of calls in UUPC. The routines in COMM.ASM are called
; from ulib.c.
; First (when a line in system has been read?), ulib&openline calls
; select_port() ; Sets up CURRENT_AREA
; then, save_com() ; Save INT vector
; then, install_com() ; Init area, hook INT
; then, open_com(&cmd, 'D', 'N', STOP*T, 'D') ; Init UART, clr bufs
; then, dtr_on().
; At that point the line is up and running. UUPC calls ulib&sread()
; which calls, receive_com();
; And UUPC calls ulib&swrite()
; which calls, send_com();
; To cause an error that the receiver will see, UUPC calls ulib&ssendbrk();
; which calls, break_com();
; When all done with the line, UUPC calls ulib&closeline()
; which calls, dtr_off();
; then, close_com();
; then, restore_com(); ; Unhook INT
; and, stat_errors();
; Note: On the PC COM1 and COM3 share IRQ4, while COM2 and COM4 share IRQ3.
; BUT, only one device on a given IRQ line can be active at a time. So it is
; sufficient for UUPC to hook whatever IRQ INT its modem is on as long as it
; unhooks it when it is done with that COM port. COMM cannot be an installed
; device driver since it must go away when UUPC is done so that other devices
; on the same INTs will come back to life. Also, it is OK to have a static
; CURRENT_AREA containing the current area that UUPC is using.
; Note about using the NS16550A UART chip's FIFOs. These are operated as
; silos. In other words when an interrupt happens because the receive(send)
; FIFO is nearly full(empty), as many bytes as possible are transferred and
; the interrupt dismissed. Thus, the interrupt load is lowered.
; Concerning the way the comm line is brought up.
; There are two basic cases, the Direct ('D') connection and the Modem ('M')
; connection. For either UUPC calls dtr_on_com() to bring up the line. This
; causes Data Terminal Ready (DTR) and Request To Send (RTS) to be set. Note
; this is OK for a simple 3-wire link but may be REQUIRED for a COM port
; connected to an external modem.
; The difference between a D connection and an M connection is
; whether or not the PC can expect any signals back from the modem. If
; there is a simple 3-wire link, Data Set Ready will be floating.
; (Actually, some wise people jumper Data Terminal Read back to Data
; Set Ready so the PC sees its own DTR appear as DSR.) UUPC should be
; able to handle the simplest cable as a design feature. So both D and
; M connections send out DTR and RTS, but only the M connection expects
; a modem to respond.
; Then, if it is full modem connection (M), we wait for a few
; seconds hoping that both Data Set Ready (DSR) and Clear To Send (CTS)
; will come up. If they don't, the associated counters are incremented
; for subsequent printing in the error log. Note that no error is
; reported from COMM to UUPC at this point, although this would be a
; good idea. COMMFIFO.ASM forces the connection to be a D type and lets
; UUPC storm ahead with its output trying to
; establish a link, but the output is never sent due to one of the
; control signals being false. UUPC could check the modem status using
; a call which has been installed just for this purpose.
; Note, if you are going to connect your PC running UUPC to,
; say, a mainframe and you need hardware flow control (i.e., RTS-CTS
; handshaking), use a Modem connection. Using a simple 3-wire cable
; forbids hardware flow control and UUPC must be instructed to use a
; Direct connection. Refer to comments in the SYSTEMS file on how to
; make this selection.
; References used in designing the revisions to COMM.ASM:
; 1. The UNIX fas.c Driver code.
; 2. SLIP8250.ASM from the Clarkson driver set.
; 3. NS16550A data sheet and AN-491 from National Semiconductor.
; 4. Bell System Data Communications, Technical Reference for
; Data Set 103A, Interface Specification, February, 1967
; 5. Network mail regarding V.24
; 6. Joe Doupnik
PAGE;
;
; void far select_port(int)
; Arg is 1..4 and specifies which COM port is referenced by
; all other calls in this package.
;
_select_port PROC FAR
push bp
mov bp,sp
mov AX,[bp+6] ; get aguement
CMP AL,1 ; Port 1?
JE SP1 ; Yes
CMP AL,2 ; Port 2?
JE SP2 ; Yes
CMP AL,3 ; Port 3?
JE SP3 ; Yes
CMP AL,4 ; Port 4?
JE SP4 ; Yes
INT 20H ; N.O.T.A. ????? Halt for debugging!
; Assume port 1 if continued
SP1: MOV AX,OFFSET DGROUP:AREA1 ; SELECT COM1 DATA AREA
JMP SHORT SPX ; CONTINUE
SP2: MOV AX,OFFSET DGROUP:AREA2 ; SELECT COM2 DATA AREA
JMP SHORT SPX ; CONTINUE
SP3: MOV AX,OFFSET DGROUP:AREA3 ; SELECT COM3 DATA AREA
JMP SHORT SPX ; CONTINUE
SP4: MOV AX,OFFSET DGROUP:AREA4 ; SELECT COM4 DATA AREA
;Fall into SPX
SPX: MOV CURRENT_AREA,AX ; SET SELECTION IN MEMORY
mov sp,bp
pop bp
RET
_select_port ENDP
PAGE;
;
; void far save_com(void)
; Save the interrupt vector of the selected COM port.
; N.B. save_com() and restore_com() call MUST be properly nested
;
_save_com PROC FAR
push bp
mov bp,sp
PUSH SI
PUSH ES ; SAVE EXTRA SEGMENT
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
MOV AREA1.INT_HNDLR,OFFSET INT_HNDLR1
MOV AREA2.INT_HNDLR,OFFSET INT_HNDLR2
MOV AREA3.INT_HNDLR,OFFSET INT_HNDLR3
MOV AREA4.INT_HNDLR,OFFSET INT_HNDLR4
; Save old interrupt vector
MOV AH,35H ; FETCH INTERRUPT VECTOR CONTENTS
MOV AL,INT_COM[SI] ; INTERRUPT NUMBER
INT DOS ; DOS 2 FUNCTION
MOV OLD_COM_OFF[SI],BX ; SAVE
MOV BX,ES ; ES:BX
MOV OLD_COM_SEG[SI],BX ; FOR LATER RESTORATION
POP ES ; RESTORE ES
POP SI
mov sp,bp
pop bp
RET ; DONE
_save_com ENDP
PAGE;
;
; int far install_com(void)
;
; Install the selected COM port.
; Returns: 0: Failure
; 1: Success
;
; SET UART PORTS FROM RS-232 BASE IN ROM BIOS DATA AREA
; INITIALIZE PORT CONSTANTS AND ERROR COUNTS
;
; Assign blocks of memory for transmit and receive buffers
;
_install_com PROC FAR
push bp
mov bp,sp
PUSHF ; Save caller's interrupt state
PUSH SI
PUSH DI
PUSH ES
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
CMP INSTALLED[SI],1 ; Is port installed on this machine?
JNE INST1 ; NO, CONTINUE
JMP INST9 ; ELSE JUMP IF ALREADY INSTALLED
; Assign memory for transmit and receive buffers
INST1: MOV BX,S_SIZE ; Send buffer size
ADD BX,0FH ; Round up
SHR BX,1 ; Must run on an XT
SHR BX,1
SHR BX,1
SHR BX,1 ; Now have number of paragraphs
MOV AX,4800H ; Allocate memory
INT DOS
JC INSTFAIL ; Give fail return
MOV WORD PTR TBuff[SI],0 ; Save in private block for this port
MOV WORD PTR TBuff[SI+2],AX
MOV BX,R_SIZE ; Receive buffer size
ADD BX,0FH ; Round up
SHR BX,1 ; Must run on an XT
SHR BX,1
SHR BX,1
SHR BX,1 ; Now have number of paragraphs
MOV AX,4800H ; Allocate memory
INT DOS
JNC INSTSUCC ; Success --> Continue
; Unhand the send buffer assigned above
MOV AX,WORD PTR TBuff+2[SI] ; Transmit buffer paragraph
MOV ES,AX ; Honest. That's where the arg goes.
MOV AX,4900H ; Release memory
INT DOS
; Ignore error
; Fall into INSTFAIL
INSTFAIL:
JMP INST666 ; Failure --> Give failed response
INSTSUCC:
MOV WORD PTR RBuff[SI],0 ; Save in private block for this port
MOV WORD PTR RBuff[SI+2],AX
IFDEF DEBUG
PUSH DI
CLD ; Go up in memory
XOR AX,AX ; A zero to store
LES DI,TBuff[SI] ; Transmit buffer location
MOV CX,S_SIZE ; Size of buffer
REP STOSB ; Clear entire buffer
LES DI,RBuff[SI] ; Receive buffer location
MOV CX,R_SIZE ; Size of buffer
REP STOSB ; Clear entire buffer
POP DI
ENDIF
PAGE;
; CLEAR ERROR COUNTS
CLI ; Stray interrupts cause havoc
MOV WORD PTR EOVFLOW[SI],0 ; BUFFER OVERFLOWS
MOV WORD PTR EOVRUN[SI],0 ; RECEIVE OVERRUNS
MOV WORD PTR EBREAK[SI],0 ; BREAK CHARS
MOV WORD PTR EFRAME[SI],0 ; FRAMING ERRORS
MOV WORD PTR EPARITY[SI],0 ; PARITY ERRORS
MOV WORD PTR EXMIT[SI],0 ; TRANSMISSION ERRORS
MOV WORD PTR EDSR[SI],0 ; DATA SET READY ERRORS
MOV WORD PTR ECTS[SI],0 ; CLEAR TO SEND ERRORS
MOV BX,RBDA ; ROM BIOS DATA AREA
MOV ES,BX ; TO ES
ASSUME ES:RBDA
; Map port number (COMx) into IO Address using the RS232_Base[x] table in
; the BIOS data area. If any of the ports is missing there should be a
; zero in the table for this COM port. BIOS startup routines pack the table
; so that if you have a COM4 but no COM3, 2E8 will be found in 40:4 and 0
; will be in 40:6.
; N.B. The exact IO address in 40:x is irrelevant and may well be something
; other than the "standard" values if specially designed hardware is used.
; To minimize flack, we will use the standard value if the slot in the table
; is 0. The bad side effect of this is that (in the standard losing case of
; a COM4 but no COM3) both COM3 and COM4 will reference the hardware at 2E8.
CMP PORT[SI],1 ; PORT 1?
JE INST3F8 ; Yes
CMP PORT[SI],2 ; PORT 2?
JE INST2F8 ; Yes
CMP PORT[SI],3 ; PORT 3?
JE INST3E8 ; Yes
CMP PORT[SI],4 ; PORT 4?
JE INST2E8 ; Yes
INT 20H ; NOTA. (Caller is screwed up badly)
INST3F8:MOV AX,3F8H ; Standard COM1 location
CMP RS232_BASE+0,0000H ; We have information?
JE INST2 ; No --> Use default
MOV AX,RS232_BASE+0 ; Yes --> Use provided info
JMP SHORT INST2 ; CONTINUE
INST2F8:MOV AX,2F8H ; Standard COM2 location
CMP RS232_BASE+2,0000H ; We have information?
JE INST2 ; No --> Use default
MOV AX,RS232_BASE+2 ; Yes --> Use provided info
JMP SHORT INST2 ; CONTINUE
INST3E8:MOV AX,3E8H ; Standard COM3 location
CMP RS232_BASE+4,0000H ; We have information?
JE INST2 ; No --> Use default
MOV AX,RS232_BASE+4 ; Yes --> Use provided info
JMP SHORT INST2 ; CONTINUE
INST2E8:MOV AX,2E8H ; Standard COM4 location
CMP RS232_BASE+6,0000H ; We have information?
JE INST2 ; No --> Use default
MOV AX,RS232_BASE+6 ; Yes --> Use provided info
; Fall into INST2
; Now we have an IO address for the COMx that we want to use. If it is
; anywhere in RS232_Base, we know that it has been check and is OK to use.
; So, even if my 2E8 (COM4) appears in 40:6 (normally for COM3), I can use
; it.
INST2: CMP AX,RS232_BASE ; INSTALLED?
JE INST2A ; JUMP IF SO
CMP AX,RS232_BASE+2 ; INSTALLED?
JE INST2A ; JUMP IF SO
CMP AX,RS232_BASE+4 ; INSTALLED?
JE INST2A ; JUMP IF SO
CMP AX,RS232_BASE+6 ; INSTALLED?
JNE INST666 ; JUMP IF NOT
; Fall into INST2A
INST2A: MOV BX,DATREG ; OFFSET OF TABLE OF PORTS
MOV CX,7 ; LOOP SIX TIMES
INST3: MOV WORD PTR [SI][BX],AX ; SET PORT ADDRESS
INC AX ; NEXT PORT
ADD BX,2 ; NEXT WORD ADDRESS
LOOP INST3 ; RS232 BASE LOOP
MOV DX,FCR[SI] ; Get FIFO Control Register
MOV AL,FIFO_INIT
OUT DX,AL ; Try to initialize the FIFO
MOV DX,IIR[SI] ; Get interrupt ID register
IN AL,DX ; See how the UART responded
AND AL,FIFO_ENABLED ; Keep only these bits
MOV CX,1 ; Assume chunk size of 1 for 8250 case
CMP AL,FIFO_ENABLED ; See if 16550A
JNE INST4 ; Jump if not
MOV CX,FIFO_LEN
INST4: MOV UART_SILO_LEN[SI],CL ; Save chunk size for XMIT side only
MOV AL,FIFO_CLEAR
OUT DX,AL
MOV AH,25H ; SET INTERRUPT VECTOR CONTENTS
MOV AL,INT_COM[SI] ; INTERRUPT NUMBER
MOV DX,INT_HNDLR[SI] ; OUR INTERRUPT HANDLER [WWP]
PUSH DS ; SAVE DATA SEGMENT
PUSH CS ; COPY CS
POP DS ; TO DS
INT DOS ; DOS FUNCTION
POP DS ; RECOVER DATA SEGMENT
; PORT INSTALLED
INST9: MOV AX,1
JMP SHORT INSTX
; PORT NOT INSTALLED
INST666:MOV AX,0
;Fall into INSTX
; Common exit
INSTX: MOV INSTALLED[SI],AL ; Indicate whether installed or not
POP ES
POP DI
POP SI
POPF ; Restore caller's interrupt state
mov sp,bp
pop bp
RET
_install_com ENDP
PAGE;
;
; void far restore_com(void)
; Restore original interrupt vector and release storage
;
_restore_com PROC FAR
push bp
mov bp,sp
PUSHF
PUSH SI
PUSH ES
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
CLI
MOV INSTALLED[SI],0 ; PORT IS NO LONGER INSTALLED
MOV AH,25H ; SET INTERRUPT VECTOR FUNCTION
MOV AL,INT_COM[SI] ; INTERRUPT NUMBER
MOV DX,OLD_COM_OFF[SI] ; OLD OFFSET TO DX
MOV BX,OLD_COM_SEG[SI] ; OLD SEG
PUSH DS ; SAVE DS
MOV DS,BX ; TO DS
INT DOS ; DOS FUNCTION
POP DS ; Recover our data segment
MOV AX,WORD PTR TBuff+2[SI] ; Transmit buffer paragraph
MOV ES,AX ; Honest. That's where the arg goes.
MOV AX,4900H ; Release memory
INT DOS
; Ignore error
MOV AX,WORD PTR RBuff+2[SI] ; Receive buffer paragraph
MOV ES,AX
MOV AX,4900H
INT DOS
; Ignore error
POP ES
POP SI
POPF
mov sp,bp
pop bp
RET
_restore_com ENDP
PAGE;
;
; void far open_com(int Baud, char Conn, char Parity, char Stops, char Flow);
;
; CLEAR BUFFERS
; RE-INITIALIZE THE UART
; ENABLE INTERRUPTS ON THE 8259 INTERRUPT CONTROL CHIP
;
; [bp+6] = BAUD RATE
; [bp+8] = CONNECTION: M(ODEM), D(IRECT)
; [bp+10] = PARITY: N(ONE), O(DD), E(VEN), S(PACE), M(ARK)
; [bp+12] = STOP BITS: 1, 2
; [bp+14] = XON/XOFF: E(NABLED), D(ISABLED)
;
_open_com PROC FAR
push bp
mov bp,sp
PUSHF
PUSH SI
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
CLI ; INTERRUPTS OFF
TEST INSTALLED[SI],1 ; Port installed?
JNZ OPEN1 ; Yes --> Proceed
JMP OPENX ; No --> Get out
OPEN1: mov ax,[bp+6]
MOV BAUD_RATE[SI],AX ; SET
mov bh,[bp+8]
MOV CONNECTION[SI],BH ; ARGS
mov bl,[bp+10]
MOV PARITY[SI],BL ; IN
mov ch,[bp+12]
MOV STOP_BITS[SI],CH ; MEMORY
mov cl,[bp+14]
MOV XON_XOFF[SI],CL
; RESET FLOW CONTROL
MOV HOST_OFF[SI],0 ; HOST FLOWING
MOV PC_OFF[SI],0 ; PC FLOWING
MOV URGENT_SEND[SI],0 ; No (high priority) flow ctl
MOV SEND_OK[SI],0 ; DTR&CTS are not on yet
; RESET BUFFER COUNTS AND POINTERS
MOV START_TDATA[SI],0
MOV END_TDATA[SI],0
MOV START_RDATA[SI],0
MOV END_RDATA[SI],0
MOV SIZE_TDATA[SI],0
MOV SIZE_RDATA[SI],0
;
; RESET THE UART
MOV DX,MCR[SI] ; Modem Control Register
IN AL,DX ; Get current settings
AND AL,0FEH ; Clr RTS, OUT1, OUT2 & LOOPBACK, but
OUT DX,AL ; Not DTR (No hangup during autobaud)
MOV DX,MSR[SI] ; Modem Status Register
IN AL,DX ; Get current DSR and CTS states.
AND AL,30H ; Init PREVIOUS STATE FLOPS to current
OUT DX,AL ; state and clear Loopback, etc.
IN AL,DX ; Re-read to get delta bits & clr int
AND AL,30H ; Leave the two critical bits
CMP AL,30H ; Both on?
JNE OPEN2 ; No. Leave SEND_OK zero.
MOV SEND_OK[SI],1 ; Allow TXI to send out data
OPEN2: MOV DX,FCR[SI] ; I/O Address of FIFO control register
MOV AL,FIFO_CLEAR ; Disable FIFOs
OUT DX,AL ; Non-16550A chips will ignore this
MOV DX,LSR[SI] ; RESET LINE STATUS CONDITION
IN AL,DX
MOV DX,DATREG[SI] ; RESET RECEIVE DATA CONDITION
IN AL,DX
MOV DX,MSR[SI] ; RESET MODEM DELTAS AND CONDITIONS
IN AL,DX
CALL Set_Baud ; Set the baud rate from arg
PAGE;
; SET PARITY AND NUMBER OF STOP BITS
MOV AL,03H ; Default: NO PARITY + 8 bits data
CMP PARITY[SI],'O' ; ODD PARITY REQUESTED?
JNE P1 ; JUMP IF NOT
MOV AL,0AH ; SELECT ODD PARITY + 7 bits data
JMP SHORT P4 ; CONTINUE
;
P1: CMP PARITY[SI],'E' ; EVEN PARITY REQUESTED?
JNE P2 ; JUMP IF NOT
MOV AL,1AH ; SELECT EVEN PARITY + 7 bits data
JMP SHORT P4 ; CONTINUE
;
P2: CMP PARITY[SI],'M' ; MARK PARITY REQUESTED?
JNE P3 ; JUMP IF NOT
MOV AL,2AH ; SELECT MARK PARITY + 7 bits data
JMP SHORT P4
P3: CMP PARITY[SI],'S' ; SPACE parity requested?
JNE P4 ; No. Must be 'N' (NONE)
MOV AL,3AH ; Select SPACE PARITY + 7 bits data
P4: TEST STOP_BITS[SI],2 ; 2 STOP BITS REQUESTED?
JZ STOP1 ; NO
OR AL,4 ; YES
STOP1: MOV DX,LCR[SI] ; LINE CONTROL REGISTER
OUT DX,AL ; SET UART PARITY MODE AND DLAB=0
; Initialize the FIFOs
MOV DX,FCR[SI] ; I/O Address of FIFO control register
MOV AL,FIFO_INIT ; Clear FIFOs, set size, enable FIFOs
OUT DX,AL ; Non-16550A chips will ignore this
; ENABLE INTERRUPTS ON 8259 AND UART
IN AL,INTA01 ; SET ENABLE BIT ON 8259
AND AL,NIRQ[SI]
OUT INTA01,AL
MOV DX,IER[SI] ; Interrupt enable register
MOV AL,0DH ; Line & Modem status, recv [GT]
OUT DX,AL ; Enable those interrupts
OPENX: POP SI
POPF ; Restore interrupt state
mov sp,bp
pop bp
RET ; DONE
_open_com ENDP
PAGE;
;
; void far ioctl_com(int Flags, int Arg1, ...)
; Flags have bits saying what to do or change (IGNORED TODAY)
; Arg1, ... are the new values
;
_ioctl_com PROC FAR
PUSH BP
MOV BP,SP
PUSHF ; Save interrupt context
PUSH SI
MOV SI,CURRENT_AREA ; Pointer to COMi private area
CLI ; Prevent surprises
TEST INSTALLED[SI],1
JE IOCTLX ; No good. Just return.
MOV AX,[BP+6] ; Flags
; Check bits here...
MOV AX,[BP+8] ; Line speed
MOV BAUD_RATE[SI],AX ; Save in parameter block
CALL Set_Baud ; Set the baud rate in UART
IOCTLX: POP SI
POPF ; Restore interrupt state
MOV SP,BP
POP BP
RET
_ioctl_com ENDP
PAGE;
; ioctl-called routines (internal) ...
; SI: COMi private block
; CALL Set_Baud
; Returns: (nothing)
; Clobber: AX, BX, DX
Set_Baud PROC NEAR
MOV AX,50
MUL DIV50 ; Could be different on a PCJr!
DIV BAUD_RATE[SI] ; Get right number for the UART
MOV BX,AX ; Save it
MOV DX,LCR[SI] ; Line Control Register
IN AL,DX ; Get current size, stops, parity,...
PUSH AX
OR AL,80H ; DLAB bit
OUT DX,AL ; Talk to the baud rate regs now
MOV DX,WORD PTR DLL[SI] ; Least significant byte
MOV AL,BL ; New value
OUT DX,AL ; To UART
MOV DX,WORD PTR DLH[SI] ; Most signifiant byte
MOV AL,BH ; New value
OUT DX,AL
MOV DX,LCR[SI] ; Line Control Register
POP AX
OUT DX,AL ; Turn off DLAB, keep saved settings
RET
Set_Baud ENDP
PAGE;
;
; void far close_com(void)
; Turn off interrupts from the COM port
;
_close_com PROC FAR
push bp
mov bp,sp
PUSHF
PUSH SI
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ CCX ; ABORT IF NOT
; TURN OFF UART and clear FIFOs in NS16550A
CLI
MOV DX,IER[SI]
MOV AL,0
OUT DX,AL ; No interrupts right now, please
MOV DX,FCR[SI] ; FIFO Control Register
MOV AL,FIFO_CLEAR ; Disable FIFOs
OUT DX,AL
MOV DX,MCR[SI] ; Modem control register
XOR AL,AL ; OUT2 is IRQ enable on some machines,
OUT DX,AL ; So, clear RTS, OUT1, OUT2, LOOPBACK
; TURN OFF 8259
MOV DX,INTA01
IN AL,DX
OR AL,IRQ[SI]
JMP $+2 ; DELAY FOR AT
JMP $+2 ; DELAY FOR AT
JMP $+2 ; DELAY FOR AT
OUT DX,AL
CCX: POP SI
POPF ; Restore interrupt state
mov sp,bp
pop bp
RET
_close_com ENDP
PAGE;
;
; void far dtr_off(void)
; Tells modem we are done. Remote end should hang up also.
;
_dtr_off PROC FAR
push bp
mov bp,sp
PUSH SI
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ DFX ; ABORT IF NOT
MOV DX,MCR[SI] ; Modem Control Register
IN AL,DX ; Get current state
PUSH AX ; Save MCR
MOV AL,08H ; DTR off, RTS off, OUT2 on
OUT DX,AL
POP AX ; Get previous state
AND AL,1 ; Just look at the DTR bit
JE DFX ; Not on. Don't clr. Don't wait.
MOV AX,50 ; 50/100 of second
IFNDEF UUPC
CALL WaitN ; V.24 says it must be low >1/2 sec
ENDIF
DFX: POP SI
mov sp,bp
pop bp
RET
_dtr_off ENDP
PAGE;
;
; void far dtr_on(void) Tell modem we can take traffic
;
_dtr_on PROC FAR
push bp
mov bp,sp
PUSH SI
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ DTRONF ; Suppress output if not
; Tell modem we are ready and want to send with line idle
MOV DX,MCR[SI] ; Modem Control Register
MOV AL,00001011B ; OUT 2, RTS, DTR
OUT DX,AL
CMP CONNECTION[SI],'D' ; Direct connection (no DSR,CTS)?
JNE DTRON0 ; Go wait for DSR, CTS
MOV SEND_OK[SI],1 ; Set output enable flag
JMP SHORT DTRONS ; Give success return
; Wait for awhile to give the modem time to respond
DTRON0: MOV AH,2CH ; Get time (H:M:S:H to CH:CL:DH:DL)
INT 21H
MOV BX,DX ; Save seconds&hundreths
ADD BH,06 ; Allow a few seconds
CMP BH,60 ; Wrap around check
JL DTRON1 ; No wrap
SUB BH,60
DTRON1: CMP SEND_OK[SI],1 ; Did the modem come up?
JE DTRONS ; Yes. Both DSR and CTS are true.
INT 21H ; Get the time again
CMP DX,BX ; Current time is passed the deadline?
JB DTRON1 ; No, keep checking 'til time runs out
; Modem failed to come up. Bump counts that tell why.
MOV DX,MSR[SI] ; MODEM STATUS REGISTER
IN AL,DX ; GET MODEM STATUS
TEST AL,20H ; DATA SET READY?
JNZ DTRON6 ; Yup.
INC WORD PTR EDSR[SI] ; BUMP ERROR COUNT
DTRON6: TEST AL,10H ; Clear To Send?
JNZ DTRONF ; That's OK.
INC WORD PTR ECTS[SI] ; BUMP ERROR COUNT - WE TIMED OUT
; Fall into DTRONF
PAGE;
; Failure return
DTRONF: MOV SEND_OK[SI],1 ; Make believe DSR & CTS are up!!!
MOV CONNECTION[SI],'D' ; Switch to DIR connection (MSTATINT)
JMP SHORT DTRONX
; Successful return
DTRONS: ; SEND_OK is on. Setting it again could confuse interrupt level
; Fall into DTRONX
DTRONX: MOV AX,200H ; 2 Seconds
IFNDEF UUPC
CALL WaitN ; V.24 says 2 sec hi before data
ENDIF
POP SI
mov sp,bp
pop bp
RET
_dtr_on ENDP
PAGE;
;
; Wait for specified time using the 18.2 ticks/second clock
;
; Call: AX has seconds,hundreths
; CALL WaitN
; Return: At least the requested time has passed
;
WaitN PROC NEAR
PUSH BP
MOV BP,SP
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH AX ; Save a copy of the arg
MOV AH,2CH ; Get time (H:M:S:H to CH:CL:DH:DL)
INT DOS
POP BX ; Recover S:H arg
ADD BX,DX ; Determine deadline
CMP BL,100 ; Wrap around?
JL WaitN1 ; No
SUB BL,100 ; Yes. Subtract 100 hundreths
INC BH ; And add a second
WaitN1: CMP BH,60 ; Wrap around check
JL WaitN2 ; No wrap
SUB BH,60 ; Forget about Days and Hours
WaitN2: INT DOS ; Get the time again
CMP DX,BX ; Is current time after the deadline?
JB WaitN2 ; No, keep checking 'til time runs out
POP DX
POP CX
POP BX
POP CX
MOV SP,BP
POP BP
RET
WaitN ENDP
PAGE;
;
; unsigned long r_count(void)
; Value is really two uints: Buffer size in high half, count in low.
; Count returned is <= number of chars waiting to be read.
; (More may come in after you asked.)
;
_r_count PROC FAR
push bp
mov bp,sp
PUSH SI
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
XOR AX,AX ; Say nothing available if not inst'd
MOV DX,R_SIZE ; Size of entire receive buffer
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ RCX ; ABORT IF NOT
MOV AX,SIZE_RDATA[SI] ; GET NUMBER OF BYTES USED
RCX: POP SI
mov sp,bp
pop bp
RET
_r_count ENDP
PAGE;
;
; char far receive_com(void)
; Returns AX: -1 if port not installed or no characters available
; or AX: the next character with parity stipped if not in P mode
;
_receive_com PROC FAR
push bp
mov bp,sp
PUSHF ; Save interrupt state
PUSH SI
PUSH ES
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
mov ax,-1 ; -1 if bad call
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ RECVX ; ABORT IF NOT
CLI
CMP SIZE_RDATA[SI],0 ; ANY CHARACTERS?
JE RECVX ; Return -1 in AX
mov ah,0 ; good call
LES BX,RBuff[SI] ; Location of receive buffer
ADD BX,START_RDATA[SI] ; GET POINTER TO OLDEST CHAR
MOV AL,ES:[BX] ; Get character from buffer
CMP PARITY[SI],'N' ; ARE WE RUNNING WITH NO PARITY? LBA
JE RECV1 ; IF SO, DON'T STRIP HIGH BIT LBA
AND AL,7FH ; STRIP PARITY BIT
RECV1: MOV BX,START_RDATA[SI] ; Get the start index again
INC BX ; BUMP START_RDATA
AND BX,R_SIZE-1 ; Ring the pointer
MOV START_RDATA[SI],BX ; SAVE THE NEW START_RDATA VALUE
DEC SIZE_RDATA[SI] ; ONE LESS CHARACTER
CMP XON_XOFF[SI],'E' ; FLOW CONTROL ENABLED?
JNE RECVX ; DO NOTHING IF DISABLED
CMP HOST_OFF[SI],1 ; HOST TURNED OFF?
JNE RECVX ; JUMP IF NOT
CMP SIZE_RDATA[SI],R_SIZE/16; RECEIVE BUFFER NEARLY EMPTY?
JGE RECVX ; DONE IF NOT
MOV HOST_OFF[SI],0 ; TURN ON HOST IF SO
PUSH AX ; SAVE RECEIVED CHAR
MOV AL,CONTROL_Q ; TELL HIM TO TALK
RECV3: CLI ; TURN OFF INTERRUPTS
CMP URGENT_SEND[SI],1 ; Previous send still in progress?
JNE RECV4 ; No. There is space now.
STI ; Yes. Wait for interrupt to take it.
JMP SHORT RECV3 ; Loop 'til it's gone
RECV4: CALL SENDII ; SEND IMMEDIATELY INTERNAL
POP AX ; RESTORE RECEIVED CHAR
RECVX: POP ES
POP SI
POPF ; Restore interrupt state
mov sp,bp
pop bp
RET
_receive_com ENDP
PAGE;
;
; unsigned long s_count(void)
; Value is really two uints: Buffer size in high half (returned in DX).
; Free space count in low (returned in AX).
; Count returned is <= number of chars which can be sent without blocking.
; (More may become available after you asked.)
;
; N.B. The free space might be negative (-1) if the buffer was full and then
; the program called SENDI or RXI required sending a control-S to squelch
; the remote sender. Return 0 in this case.
;
_s_count PROC FAR
push bp
mov bp,sp
PUSH SI
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
MOV AX,0 ; NO SPACE LEFT IF NOT INSTALLED
mov dx,S_SIZE-1 ; Leave 1 byte for a SENDII call
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ SCX ; ABORT IF NOT
MOV AX,S_SIZE-1 ; Size, keeping one aside for SENDII
SUB AX,SIZE_TDATA[SI] ; Minus number in use right now
JGE SCX ; Avoid returning negative number
XOR AX,AX ; Return 0
SCX: POP SI
mov sp,bp
pop bp
RET
_s_count ENDP
PAGE;
;
; void far send_com(char)
; Send a character to the selected port
;
_send_com PROC FAR
push bp
mov bp,sp
PUSHF ; Save interrupt state
PUSH SI
PUSH ES
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ SENDX ; ABORT IF NOT
SEND1: CMP SIZE_TDATA[SI],S_SIZE-1 ; BUFFER FULL? (Leave room for SENDII)
JGE SEND1 ; Wait for interrupts to empty buffer
CLI
LES BX,TBUFF[SI] ; Pointer to buffer
ADD BX,END_TDATA[SI] ; ES:BX points to free space
MOV AL,[BP+6] ; Character to send
MOV ES:[BX],AL ; Move character to buffer
MOV BX,END_TDATA[SI] ; Get index of end
INC BX ; INCREMENT END_TDATA
AND BX,S_SIZE-1 ; Ring the pointer
MOV END_TDATA[SI],BX ; SAVE NEW END_TDATA
INC SIZE_TDATA[SI] ; ONE MORE CHARACTER IN X-MIT BUFFER
TEST PC_OFF[SI],1 ; Were we stopped by a ^S from host?
JNZ SENDX ; Yes. Don't enable interrupts yet.
CALL CHROUT ; Put a character out to the UART
SENDX: POP ES
POP SI
POPF ; Restore interrupt state
mov sp,bp
pop bp
RET
_send_com ENDP
PAGE;
;
; void far sendi_com(char)
; Send a character immediately by placing it at the head of the queue
;
_sendi_com PROC FAR
push bp
mov bp,sp
PUSHF ; Save interrupt state
PUSH SI
mov al,[bp+6]
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ SENDIX ; Return if not
SENDI3: CLI ; TURN OFF INTERRUPTS
CMP URGENT_SEND[SI],1 ; Previous send still in progress?
JNE SENDI4 ; No. There is space now.
STI ; Yes. Wait for interrupt to take it.
JMP SHORT SENDI3 ; Loop 'til it's gone
SENDI4: CALL SENDII ; CALL INTERNAL SEND IMMEDIATE
SENDIX: POP SI
POPF ; Restore interrupt state
mov sp,bp
pop bp
RET
_sendi_com ENDP
PAGE;
; SENDII(AL, SI) [internal routine]
; Put char at head of output queue so it will go out next
; Called from process level and (receive) interrupt level
; DEPENDS ON CALLER TO KEEP INTERRUPTS CLEARED AND SET SI
;
SENDII PROC NEAR
PUSH BX
PUSH DX
PUSH ES
LES BX,TBuff[SI] ; Location of transmit buffer
CMP SIZE_TDATA[SI],S_SIZE ; BUFFER FULL?
JB SENDII2 ; JUMP IF NOT
INC WORD PTR EOVFLOW[SI] ; BUMP ERROR COUNT (Can this happen?)
ADD BX,START_TDATA[SI] ; ES:BX point to 1st chr in buffer
MOV ES:[BX],AL ; Overwrite 1st character
JMP SHORT SENDII4
SENDII2:MOV DX,START_TDATA[SI] ; DX is index of 1st char
DEC DX ; Back it up
AND DX,S_SIZE-1 ; Ring it
MOV START_TDATA[SI],DX ; Save new value
ADD BX,DX ; Address within buffer
MOV ES:[BX],AL ; Move character to buffer
INC SIZE_TDATA[SI] ; ONE MORE CHARACTER IN X-MIT BUFFER
MOV URGENT_SEND[SI],1 ; Flag high priority message
; No check for PC_OFF here. Flow control ALWAYS gets sent!
SENDII4:CALL CHROUT ; Output a chr if possible
SENDIIX:POP ES
POP DX
POP BX
RET
SENDII ENDP
; CHROUT() Process level routine to remove a chr from the buffer,
; give it to the UART and adjust the pointer and count.
; If interrupts are disabled at entry, nothing is done.
; If a character is successfully output, Tx ints are enabled.
; Requires: SI pointing at the appropriate data area
; Clobbers: AX, BX, DX, ES
; Must preserve: CX in case there is a count there
CHROUT PROC NEAR
MOV DX,IER[SI] ; Interrupt Enable Register
IN AL,DX
TEST AL,2 ; Tx interrupts enabled?
JNZ CHROUX ; Jump if not
CMP SEND_OK[SI],1 ; See if Data Set Ready & CTS are on
JNE CHROUX ; No. Still can't enable TX ints
CALL TX_CHR ; Actually transmit the chr
MOV DX,IER[SI] ; Interrupt Enable Register
MOV AL,0FH ; Rx, Tx, Line & Modem enable bits
OUT DX,AL ; Enable those interrupts
CHROUX: RET
CHROUT ENDP
PAGE;
IFNDEF UUPC
;
; void far send_local(char);
; Simulate a loopback by placing characters sent in recv buffer
;
_send_local PROC FAR
push bp
mov bp,sp
PUSHF
PUSH SI
PUSH ES
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ SLX ; ABORT IF NOT
CLI ; INTERRUPTS OFF
CMP SIZE_RDATA[SI],R_SIZE ; SEE IF ANY ROOM
JB SL3 ; SKIP IF ROOM
INC WORD PTR EOVFLOW[SI] ; BUMP OVERFLOW COUNT
JMP SHORT SLX ; PUNT
SL3: LES BX,RBuff[SI] ; Receive buffer location
ADD BX,END_RDATA[SI] ; ES:BX POINTS TO FREE SPACE
MOV AL,[BP+6] ; Get the byte to send
MOV ES:[BX],AL ; Put into buffer
MOV BX,END_RDATA[SI] ; Get the end pointer
INC BX ; INCREMENT END_RDATA POINTER
AND BX,R_SIZE-1 ; Ring the pointer
MOV END_RDATA[SI],BX ; SAVE VALUE
INC SIZE_RDATA[SI] ; GOT ONE MORE CHARACTER
SLX: POP ES
POP SI
POPF ; Restore interrupt state
mov sp,bp
pop bp
RET ; DONE
_send_local ENDP
ENDIF
PAGE;
;
; void far break_com(void) Send a BREAK out to alert the remote end
;
_break_com PROC FAR
push bp
mov bp,sp
PUSH SI
MOV SI,CURRENT_AREA ; SI POINTS TO DATA AREA
TEST INSTALLED[SI],1 ; PORT INSTALLED?
JZ BREAKX ; ABORT IF NOT
MOV DX,LCR[SI] ; LINE CONTROL REGISTER
IN AL,DX ; GET CURRENT SETTING
OR AL,40H ; TURN ON BREAK BIT
OUT DX,AL ; SET IT ON THE UART
MOV AX,25 ; 25/100 of a second
CALL WaitN
MOV DX,LCR[SI] ; LINE CONTROL REGISTER
IN AL,DX ; GET CURRENT SETTING
AND AL,0BFH ; TURN OFF BREAK BIT
OUT DX,AL ; RESTORE LINE CONTROL REGISTER
BREAKX: POP SI
mov sp,bp
pop bp
RET
_break_com ENDP
PAGE;
;
; ERROR_STRUCT far *com_errors(void)
; Returns a pointer to the table of error counters
;
_com_errors PROC FAR
push bp
mov bp,sp
PUSH SI
MOV SI,CURRENT_AREA ; Point to block for selected port
LEA AX,ERROR_BLOCK[SI] ; Offset to error counters
MOV DX,DS ; Value is in DX:AX
POP SI
mov sp,bp
pop bp
RET
_com_errors ENDP
;
; char far modem_status(void)
; Returns the modem status register in AL
;
; Bits are: 0x80: Carrier Detect
; 0x40: Ring Indicator
; 0x20: Data Set Ready
; 0x10: Clear To Send
; 0x08: Delta Carrier Detect (CD changed)
; 0x04: Trailing edge of RI (RI went OFF)
; 0x02: Delta DSR (DSR changed)
; 0x01: Delta CTS (CTS changed)
_modem_status PROC FAR
push bp
mov bp,sp
PUSH SI
MOV SI,CURRENT_AREA ; Point to block for selected port
MOV DX,MSR[SI] ; IO Addr of Modem Status Register
IN AL,DX ; Get the live value
XOR AH,AH ; Flush unwanted bits
POP SI
mov sp,bp
pop bp
RET
_modem_status ENDP
PAGE;
;
; INT_HNDLR1 - HANDLES INTERRUPTS GENERATED BY COM1:
;
INT_HNDLR1 PROC FAR
PUSH SI
MOV SI,OFFSET DGROUP:AREA1 ; DATA AREA FOR COM1:
JMP SHORT INT_COMMON ; CONTINUE
;
; INT_HNDLR2 - HANDLES INTERRUPTS GENERATED BY COM2:
;
INT_HNDLR2 PROC FAR
PUSH SI
MOV SI,OFFSET DGROUP:AREA2 ; DATA AREA FOR COM2:
JMP SHORT INT_COMMON ; CONTINUE
;
; INT_HNDLR3 - HANDLES INTERRUPTS GENERATED BY COM3:
;
INT_HNDLR3 PROC FAR
PUSH SI
MOV SI,OFFSET DGROUP:AREA3 ; DATA AREA FOR COM3:
JMP SHORT INT_COMMON ; CONTINUE
;
; INT_HNDLR4 - HANDLES INTERRUPTS GENERATED BY COM4:
;
INT_HNDLR4 PROC FAR
PUSH SI
MOV SI,OFFSET DGROUP:AREA4 ; DATA AREA FOR COM4:
; Fall into INT_COMMON
PAGE;
;
; BODY OF INTERRUPT HANDLER
;
INT_COMMON: ; SI has been pushed and loaded
PUSH AX
PUSH BX
PUSH CX
PUSH DX
PUSH DS
PUSH ES
MOV AX,DGROUP ; Offsets are relative to DGROUP [WWP]
MOV DS,AX
; FIND OUT WHERE INTERRUPT CAME FROM AND JUMP TO ROUTINE TO HANDLE IT
REPOLL: MOV DX,IIR[SI] ; Interrupt Identification Register
IN AL,DX
TEST AL,1 ; Check the "no interrupt present" bit
JNZ INT_END ; ON means we are done
MOV BL,AL ; Put where we can index by it
AND BX,000EH ; Ignore FIFO_ENABLED bits, etc.
JMP WORD PTR CS:INT_DISPATCH[BX]; Go to appropriate routine
INT_DISPATCH:
DW MSI ; 0: Modem status interrupt
DW TXI ; 2: Transmitter interrupt
DW RXI ; 4: Receiver interrupt
DW LSI ; 6: Line status interrupt
DW REPOLL ; 8: (Future use by UART makers)
DW REPOLL ; A: (Future use by UART makers)
DW RXI ; C: FIFO Timeout
DW REPOLL ; E: (Future use by UART makers)
INT_END: ; Now tell 8259 we handled that IRQ
MOV DX,IER[SI] ; Gordon Lee's cure for dropped ints
IN AL,DX ; Get enabled interrupts
MOV AH,AL
XOR AL,AL
OUT DX,AL ; Disable UART interrupts
MOV AL,EOI[SI] ; End of Interrupt. With input gone,
OUT INTA00,AL ; Give EOI to 8259 Interrupt ctlr
MOV AL,AH ; Get save interrupt enable bits
OUT DX,AL ; Restore them, maybe causing a
; transition into the 8259!!!
POP ES
POP DS
POP DX
POP CX
POP BX
POP AX
POP SI
IRET
PAGE;
;
; Line status interrupt
;
LSI: MOV DX,LSR[SI] ; Line status register
IN AL,DX ; Read line status & bump error counts
TEST AL,2 ; OVERRUN ERROR?
JZ LSI1 ; JUMP IF NOT
INC WORD PTR EOVRUN[SI] ; ELSE BUMP ERROR COUNT
LSI1: TEST AL,4 ; PARITY ERROR?
JZ LSI2 ; JUMP IF NOT
INC WORD PTR EPARITY[SI] ; ELSE BUMP ERROR COUNT
LSI2: TEST AL,8 ; FRAMING ERROR?
JZ LSI3 ; JUMP IF NOT
INC WORD PTR EFRAME[SI] ; ELSE BUMP ERROR COUNT
LSI3: TEST AL,16 ; BREAK RECEIVED?
JZ LSI4 ; JUMP IF NOT
INC WORD PTR EBREAK[SI] ; ELSE BUMP ERROR COUNT
LSI4: JMP REPOLL ; SEE IF ANY MORE INTERRUPTS
;
; Modem status interrupt
;
MSI: MOV DX,MSR[SI] ; Modem Status Register
IN AL,DX ; Read status & clear interrupt
CMP CONNECTION[SI],'D' ; Direct connection - ignore int
JE MSI0 ; Just noise on DSR,CTS pins
AND AL,30H ; Expose CTS and Data Set Ready
CMP AL,30H ; Both on?
JE MSI0 ; Yes. Enable output at TXI
XOR AL,AL
JMP SHORT MSI1
MSI0: MOV AL,1
MSI1: MOV SEND_OK[SI],AL ; Put where TXI and send_com can see
MOV DX,IER[SI] ; Let a TX int happen for thoro chks
MOV AL,0FH ; Line & modem sts, recv, send.
OUT DX,AL
JMP REPOLL ; Check for other interrupts
PAGE;
;
; Tranmit interrupt
;
TXI: CMP SEND_OK[SI],1 ; Harware (CTS & DSR on) OK?
JNE TXI9 ; No. Must wait 'til cable right!
MOV CX,1 ; Transfer count for flow ctl
CMP URGENT_SEND[SI],1 ; Flow control character to send?
JE TXI3 ; Yes. Always send flow control.
CMP PC_OFF[SI],1 ; Flow control (XON/XOFF) OK?
JE TXI9 ; Stifled & not urgent. Forget it.
TXI1: MOV CL,UART_SILO_LEN[SI] ; MAX size chunk (1 for simple 8250)
; Too bad there is no "Tranmitter FIFO Full" indication!
CMP SIZE_TDATA[SI],CX ; SEE IF ANY MORE DATA TO SEND
JG TXI2 ; UART is the limit
MOV CX,SIZE_TDATA[SI] ; Buffer space limited. Use that.
TXI2: JCXZ TXI9 ; No data, disable TX ints
TXI3: CALL TX_CHR ; Transmit a character
LOOP TXI3 ; Keep going 'til silo is full
MOV URGENT_SEND[SI],0 ; Tell process level we sent flow ctl
JMP REPOLL
; IF NO DATA TO SEND, or can't send, RESET TX INTERRUPT AND RETURN
TXI9: MOV DX,IER[SI]
MOV AL,0DH ; Line & modem sts, recv, no send.
OUT DX,AL
JMP REPOLL
; TX_CHR Internal routine used to actually move a character from
; the transmit buffer to the UART and adjust pointers.
; Called from process and interrupt levels with interrutps
; enabled or disabled.
; Requires: SI pointing at data area for this UART
; Clobbers: AX, BX, DX, ES
; Must preserve: CX (Caller has count here).
TX_CHR PROC NEAR
LES BX,TBUFF[SI] ; Pointer to buffer
ADD BX,START_TDATA[SI] ; ES:BX points to next char to send
MOV AL,ES:[BX] ; Get character from buffer
MOV DX,DATREG[SI] ; I/O address of data register
OUT DX,AL ; Output the character
INC BX ; Bump the head pointer
AND BX,S_SIZE-1 ; Ring it
MOV START_TDATA[SI],BX ; Store back in private block
DEC SIZE_TDATA[SI] ; One fewer in buffer now
RET
TX_CHR ENDP
PAGE;
;
; Receive interrupt
;
RXI:
RXI0: MOV DX,LSR[SI] ; Line Status Register
IN AL,DX ; Read it
TEST AL,1 ; Check the RECV DATA READY bit
JE RXIX ; No more data available
MOV DX,DATREG[SI] ; UART DATA REGISTER
IN AL,DX ; Get data, clear status
CMP XON_XOFF[SI],'E' ; FLOW CONTROL ENABLED?
JNE RXI2 ; No. Don't check for XON/XOFF
; Check each character for possible flow control (XON/XOFF)
AND AL,7FH ; STRIP PARITY
CMP AL,CONTROL_S ; STOP COMMAND RECEIVED?
JNE RXI1 ; Jump if not. Might be ^Q though.
MOV PC_OFF[SI],1 ; Stop output
JMP SHORT RXI0 ; Don't store character
RXI1: CMP AL,CONTROL_Q ; GO COMMAND RECEIVED?
JNE RXI2 ; No. Not a flow control character
MOV PC_OFF[SI],0 ; Enable output
JMP SHORT RXI0 ; Don't store character
; Have a real data byte. Store if possible.
RXI2: CMP SIZE_RDATA[SI],R_SIZE ; SEE IF ANY ROOM
JL RXI6 ; CONTINUE IF SO
INC WORD PTR EOVFLOW[SI] ; BUMP OVERFLOW ERROR COUNT
JMP SHORT RXIX
RXI6: LES BX,RBuff[SI] ; Receive buffer location
ADD BX,END_RDATA[SI] ; ES:BX points to free space
MOV ES:[BX],AL ; Put character in buffer
INC SIZE_RDATA[SI] ; GOT ONE MORE CHARACTER
MOV BX,END_RDATA[SI] ; Get the end index
INC BX ; Bump it passed location just used
AND BX,R_SIZE-1 ; Ring the pointer
MOV END_RDATA[SI],BX ; SAVE VALUE
; See if we must tell remote host to stop outputting.
CMP XON_XOFF[SI],'E' ; FLOW CONTROL ENABLED?
JNE RXI0 ; No
CMP HOST_OFF[SI],1 ; Already told remote to shut up?
JE RXI0 ; Yes. Don't flood him with ^Ss
CMP SIZE_RDATA[SI],R_SIZE/2 ; RECEIVE BUFFER NEARLY FULL?
JLE RXIX ; No. No need to stifle remote end
; Would like to wait here for URGENT_SEND to go off if it is on.
; But we need to take a TX interrupt for that to happen.
MOV AL,CONTROL_S ; TURN OFF HOST IF SO
CALL SENDII ; SEND IMMEDIATELY INTERNAL
MOV HOST_OFF[SI],1 ; HOST IS NOW OFF
RXIX: JMP REPOLL
INT_HNDLR4 ENDP
INT_HNDLR3 ENDP
INT_HNDLR2 ENDP
INT_HNDLR1 ENDP
COM_TEXT ENDS
END