[comp.protocols.tcp-ip] Asynchronous Framing Technique Software: ASYNC.ASM

n2dsy@hou2d.UUCP (G.BEATTIE) (04/08/88)

page 58,132
;	file: ASYNC.ASM
TITLE	ASYNC DRIVER FOR MSDOS
SUBTTL	DESCRIPTION
;
;	Loadable asyncrounous device driver for msdos.
;	Written by: Mike Higgins
;	Copyright (c) April 1984 by The Computer Entomologist.
;
;	Permission is hearby granted to use or distribute this software
;without any restrictions.  You may make copies for yourself or your
;friends. You may include it in any hardware or software product that you
;sell for profit.
;
;	This software is distributed as is, and is not guaranteed to work
;on any particular hardware/software configuration.  Furthermore, no 
;liability is granted with this software: the user takes responcibility for
;any damage this software may do to his system.
;
;	Nasy notices aside, if you have any questions about this software, you
;can reach me at the address below.  If you impliment any new features or
;find (and fix!) any bugs, I would be happy to hear from you.
;
;	Mike Higgins
;	The Computer Entomologist
;	P.O. Box 197
;	Duncans Mills, CA 95430
;
;	-Impliments FULL RS232 support for IBM PC and compatable async cards.
;	-Includes 128-byte buffers on input and output.
;	-Supports Xon/Xoff or hardware handshake. Hardware handshake uses
;	 DSR or CTS (to throttle output data) All handshake modes are
;	 treated separately, an can be used in combinations.
;	-The 8th bit (parity) can optionally be stripped off on input
;	 and/or output.
;	-The IOCTRL read and write function is used to query or change
;	 baud rates, bits/byte, parity, as well as enabling/disabling all
;	 the optional features mentioned above.  This eliminates the
;	 necesity of a program that has special knowledge of the system.
;	 A program to change these features need not know the location of
;	 the driver or special words in low memory, or even the port
;	 address of the UART.  Instead, all that is needed is the name of
;	 the device, and the format of an IOCTL write to this driver.
;
;		ASSEMBLY INSTRUCTIONS:
;	MASM ASYNC,ASYNC,ASYNC,NUL
;	LINK ASYNC,ASYNC,ASYNC,NUL
;	EXE2BIN ASYNC
;	COPY ASYNC.BIN A:    (IF NOT THERE ALREADY)
;		ADD THE FOLLOWING LINE TO A:CONFIG.SYS:
;	DRIVER=ASYNC.BIN
;		RE-BOOT YOUR SYSTEM AND IT'S THERE! NOTE: THIS DRIVER
;	DOES NOT GET ALONG AT ALL WITH BASICA OR MODE, AND POSSIBLY 
;	MANY OTHER PCDOS PROGRAMS THAT DO NOT CONFORM TO THE MSDOS 
;	STANDARDS.  BASICA IN PARTICULAR MUCKS UP ALL THE ASYNC CARD
;	INTERNAL REGESTERS, REDIRECTS THE HARDWARE VECTOR, AND IT
;	NEVER PUTS THEM BACK THE WAY THEY WERE.  IF YOU TRY TO USE
;	THIS DRIVER AFTER BASICA HAS BEEN IN MEMORY, THE DRIVER WILL
;	PROBABLY HANG YOUR SYSTEM.
;
;		***BUGS***
;
;	If your RS232 device continually sends data to the driver, the driver
;	will hang when your system boots. (The EPSON RX80 serial card sends
;	^Q's constantly and causes this).  The current solution is to leave
;	your device turned off until the system boots completely.  Then turn
;	the RS232 device on after the driver is ready for it, and everything
;	works as expected.
;
SUBTTL	DEFINITIONS
PAGE
;
;		DEVICE TYPE CODES
DEVCHR	EQU	08000h	;THIS IS A CHARACTER DEVICE
DEVBLK	EQU	0H	;THIS IS A BLOCK (DISK) DEVICE
DEVIOC	EQU	04000H	;THIS DEVICE ACCEPTS IOCTRL REQUESTS
DEVNON	EQU	02000H	;NON IBM DISK DRIVER
DEVSPC	EQU	010H	;CONSOLE ACCEPTS SPECIAL INTERUPT 29
DEVCLK	EQU	08H	;THIS IS THE CLOCK DEVICE
DEVNUL	EQU	04H	;THIS IS THE NUL DEVICE
DEVSTO	EQU	02H	;THIS IS THE CURRENT STANDARD OUTPUT DEVICE
DEVSTI	EQU	01H	;THIS IS THE STANDARD INPUT DEVICE
;
;		ERROR STATUS BITS
STSERR	EQU	08000H	;GENERAL ERROR, SEE LOWER ORDER BITS FOR REASON
STSBSY	EQU	0200H	;DEVICE IS BUISY
STSDNE	EQU	0100H	;REQUEST IS COMPLETED
;		ERROR REASON VALUES FOR LOWER ORDER BITS.
ERRWP	EQU	0	;WRITE PROTECT ERROR
ERRUU	EQU	1	;UNKNOWN UNIT
ERRDNR	EQU	2	;DRIVE NOT READY
ERRUC	EQU	3	;UNKNOWN COMMAND
ERRCRC	EQU	4	;CYCLIC REDUNDANCY CHECK ERROR
ERRBSL	EQU	5	;BAD DRIVE REQUEST STRUCTURE LENGTH
ERRSL	EQU	6	;SEEK ERROR
ERRUM	EQU	7	;UNKNOWN MEDIA
ERRSNF	EQU	8	;SECTOR NOT FOUND
ERRPOP	EQU	9	;PRINTER OUT OF PAPER
ERRWF	EQU	10	;WRITE FAULT
ERRRF	EQU	11	;READ FAULT
ERRGF	EQU	12	;GENERAL FAILURE
;
;		DEFINE THE BIT MEANINGS OF THE OUTPUT STATUS BYTE
;
LINIDL	EQU	0FFH	;IF ALL BITS OFF, XMITTER IS IDLE.
LINXOF	EQU	1	;OUTPUT IS SUSPENDED BY XOFF
LINEXP	EQU	2	;XMITTER IS BUISY, INTERUPT EXPECTED.
LINDSR	EQU	10H	;OUTPUT IS SUSPENDED UNTIL DSR COMES ON AGAIN
LINCTS	EQU	20H	;OUTPUT IS SUSPENDED UNTIL CTS COMES ON AGAIN
;
;		BIT DEFINITIONS OF THE INPUT STATUS BYTE
;
MODIDL	EQU	0FFH	;MASK TO CHECK BLOCKING BITS
MODERR	EQU	1	;INPUT LINE ERRORS HAVE BEEN DETECTED.
MODOFF	EQU	2	;DEVICE IS OFFLINE NOW.
MODOVR	EQU	4	;RECEIVER BUFFER OVERFLOWED, DATA LOST.
MODXON	EQU	8	;RX IS BLOCKED SINCE WE SENT XOFF (^S).
MODDTR	EQU	10H	;RX IS BLOCKED SINCE DTR IS LOW.
MODRTS	EQU	20H	;RX IS BLOCKED SINCE RTS IS LOW.
;
;		DEFINE THE BIT MEANINGS IN THE SPECIAL CHARACTERISTICS WORDS
;
;	THE FIRST SPECIAL WORD CONTROLS HOW THE INPUT FROM THE UART IS TREATED
;
INDTR	EQU	2	;DTR IS DATA-THROTTLE SIGNAL.
INRTS	EQU	4	;RTS IS DATA-THROTTLE SIGNAL.
INXON	EQU	8	;XON/XOFF IS USED TO THROTTLE INPUT DATA.
INHDP	EQU	020H	;HALF DUPLEX: INPUT CHARS ARE ECHOED.
INEST	EQU	0400H	;ERRORS CAUSE STATUS RETURNS.
INEPC	EQU	0800H	;ERRORS TRANSLATE TO CODES WITH PARITY BIT ON.
INSTP	EQU	01000H	;STRIP PARITY BIT OFF ON INPUT
;
;	THE SECOND SPECIAL WORD CONTROLS HOW THE OUTPUT TO THE UART IS TREATED
;
OUTDSR	EQU	2	;DSR IS USED TO THROTTLE OUTPUT DATA.
OUTCTS	EQU	4	;CTS IS USED TO THROTTLE OUTPUT DATA.
OUTXON	EQU	8	;XON/XOFF IS USED TO THROTTLE OUTPUT DATA.
OUTCSF	EQU	010H	;CTS IS OFFLINE SIGNAL.
OUTCDF	EQU	020H	;CARRIER DETECT IS OFFLINE SIGNAL
OUTDRF	EQU	040H	;DSR IS OFFLINE SIGNAL.
OUTSTP	EQU	01000H	;STRIP PARITY OFF ON OUTPUT.
;
;		DEFINE THE PORT OFFSETS AND IMPORTANT ASYNC BOARD CONSTANTS
;		LINE CONTROL REG DEFINITIONS
;
BITS5	EQU	0
BITS6	EQU	1
BITS7	EQU	2
BITS8	EQU	3
ONESTOP	EQU	0	;DEFAULT
TWOSTOP	EQU	4
nopar	equ	0
PARON	EQU	8
ODDPAR	EQU	0+paron	;DEFAULT
EVENPAR	EQU	10H+PARON
STICKPAR	EQU	20H	;(THIS IS STRANGE)
BREAKON	EQU	40H
DLAB	EQU	080H	;DIVISOR LATCH ACCESS BIT
;
;		MODEM CONTROL REG DEFINITIONS
DTR		EQU	1
RTS		EQU	2
OUT1		EQU	4
OUT2		EQU	8
LOOPBACK	EQU	10H
;
ALLINT	EQU	01111B	;ENABLE ALL INTERUPTS IN INTEN REGESTER.
TXBUF	EQU	0	;OFFSET TO TRANSMITTER BUFFER REGESTER
RXBUF	EQU	0	;DITO FOR RECEIVER (DIRECTION DIFERENTIATES FUNCS)
BAUD0	EQU	0	;BAUD DIVISOR REG (DLAB IN LCTRL DIFFERENCIATES)
BAUD1	EQU	1	;BAUD DIVISOR HIGH BYTE
INTEN	EQU	1	;INTERUPT ENABLE REGESTER
INTID	EQU	2	;INTERUPT IDENTIFICATION REGESTER.
LCTRL	EQU	3	;LINE CONTROL REGESTER
MCTRL	EQU	4	;MODEM CONTROL REGESTER
LSTAT	EQU	5	;LINE STATUS REGESTER
MSTAT	EQU	6	;MODEM STATUS REGESTER

;	BAUD RATE CONVERSION TABLE
;
BD50		EQU	2304
BD75		EQU	1536
BD110	EQU	1047
BD134	EQU	857
BD150	EQU	786
BD300	EQU	384
BD600	EQU	192
BD1200	EQU	96
BD1800	EQU	64
BD2000	EQU	58
BD2400	EQU	48
BD3600	EQU	32
BD4800	EQU	24
BD7200	EQU	16
BD9600	EQU	12
;

SUBTTL	DRIVER LIST HEAD
PAGE
;*************************************************************************
;
;	BEGENING OF DRIVER CODE.
;
DRIVER	SEGMENT
	ASSUME	CS:DRIVER,DS:DRIVER,ES:DRIVER
;	ORG	0	;DRIVERS START AT 0
ASYNC2:
	DW	ASYNC1,-1	;POINTER TO NEXT DEVICE: DOS FILLS THIS IN.
	DW	DEVCHR OR DEVIOC	;CHARACTER DEVICE
	DW	STRATEGY		;OFFSET TO STRATEGY ROUTINE.
	DW	REQUEST2		;OFFSET TO "INTERUPT" ENTRYPOINT.
	DB	"ASYNC2  "		;DEVICE NAME.
ASYNC1:
	DW	-1,-1		;POINTER TO NEXT DEVICE: END OF LINKED LIST.
	DW	DEVCHR OR DEVIOC	;THIS DEVICE IS CHARACTER IOCTL
	DW	STRATEGY		;STRATEGY ROUTINE
	DW	REQUEST1		;I/O REQUEST ROUTINT
	DB	"ASYNC1  "
;
SUBTTL DRIVER INTERNAL DATA STRUCTURES
PAGE
;
ASY_UNITS	EQU	2	;NUMBER OF UNITS THIS DRIVER IS BUILT FOR

UNIT	STRUC	;EACH UNIT HAS A STRUCTURE DEFINING IT'S STATE:
PORT	DW	?	;I/O PORT ADDRESS
VECT	DW	?	;INTERUPT VECTOR OFFSET (NOT INTERUPT NUMBER!)
ISRADR	DW	?	;OFFSET TO INTERUPT SERVICE ROUTINE
LINE	DB	?	;DEFAULT LINE CONTROL BIT SETTINGS DURING INIT,
			;OUTPUT STATUS BITS AFTERWORDS.
MODEM	DB	?	;MODEM CONTROL BIT SETTINGS DURING INIT,
			;INPUT STATUS BITS AFTERWARDS.
INSPEC	DW	?	;SPECIAL CHAR INPUT TREATMENT, HANDSHAKING MODE.
OUTSPEC	DW	?	;SPECIAL MODE BITS FOR OUTPUT
BAUD	DW	?	;CURRENT BAUD RATE DIVISOR VALUE
IFIRST	DW	?	;OFFSET TO FIRST CHARACTER IN INPUT BUFFER.
IAVAIL	DW	?	;OFFSET TO NEXT AVAILABLE BYTE.
IBUF	DW	?	;POINTER TO 128 BYTE INPUT BUFFER.
OFIRST	DW	?	;OFFSET INTO FIRST CHARACTER IN OUTPUT BUFFER
OAVAIL	DW	?	;OFFSET INTO NEXT AVAIL BYTE IN OUTPUT BUFFER
OBUF	DW	?	;POINTER TO 128 BYTE OUTPUT BUFFER
UNIT	ENDS

	;TABLE OF STRUCTURES FOR EACH ASYNCROUNOUS UNIT
			;ASYNC1 DEFAULTS TO THE COM1 PORT AND VECTOR,
b81n		equ	bits8 or onestop or nopar
dtrrts	equ	dtr or rts or out2	;seems to need out2 all the time!!!!!!!!
ASY_TAB1:
	UNIT	<3F8H,30H,ASY1ISR,B81N,DTRRTS,INDTR+INEST,OUTCTS,BD1200,0,0,IN1BUF,0,0,OUT1BUF>

			;ASYNC2 DEFAULTS TO THE COM2 PORT AND VECTOR,
			;NO PARITY, 8 DATA BITS, 1 STOP BIT,
			;AND 9600 BAUD.
ASY_TAB2:
	UNIT	<2F8H,2CH,ASY2ISR,B81N,DTRRTS,INDTR+INEST,OUTCTS,BD1200,0,0,IN2BUF,0,0,OUT2BUF>

		;IF THE BUFFER SIZE IS A POWER OF TWO, THE PROCESS OF KEEPING
		;THE OFSETTS WITHIN THE BOUNDS OF THE BUFFER IS GREATLY
		;SIMPLIFIED.  IF YOU MODIFY THE BUFFER SIZE, KEEP IT A
		;POWER OF 2, AND MODIFY THE MASK ACCORDINGLY.
BUFSIZ	EQU	128		;INPUT BUFFER SIZE
BUFMSK	EQU	127		;MASK FOR CALCULATING OFFSETS MODULO BUFSIZ
ISTOP	EQU	120		;TRY TO STOP RX WHEN IBUF IS THIS FULL
ISTART	EQU	100		;LET THE GUY SEND AGAIN WHEN THIS FULL
IN1BUF	DB	BUFSIZ DUP (?)
IN2BUF	DB	BUFSIZ DUP (?)
OUT1BUF	DB	BUFSIZ DUP (?)
OUT2BUF	DB	BUFSIZ DUP (?)
;
ASY_BAUDT	DW	50,BD50	;FIRST VALUE IS DESIRED BAUD RATE,
		DW	75,BD75	;SECOND IS DIVISOR REGISTER VALUE.
		DW	110,BD110
		DW	134,BD134
		DW	150,BD150
		DW	300,BD300
		DW	600,BD600
		DW	1200,BD1200
		DW	1800,BD1800
		DW	2000,BD2000
		DW	2400,BD2400
		DW	3600,BD3600
		DW	4800,BD4800
		DW	7200,BD7200
		DW	9600,BD9600
;
;	STRUCTURE OF AN I/O REQUEST PACKET STATIC HEADER
;
PACK	STRUC
LEN	DB	?	;LENGTH OF RECORD
PRTNO	DB	?	;UNIT CODE
CODE	DB	?	;COMMAND CODE
STAT	DW	?	;RETURN STATUS
DOSQ	DD	?	;UNUSED DOS QUE LINK POINTER
DEVQ	DD	?	;UNUSED DRIVER QUE LINK POINTER
MEDIA	DB	?	;MEDIA CODE ON READ/WRITE
XFER	DW	?	;XFER ADDRESS OFFSET
XSEG	DW	?	;XFER ADDRESS SEGMENT
COUNT	DW	?	;TRANSFER BYTE COUNT.
PACK	ENDS
;
;	THE FOLLOWING TWO WORDS IS THE STORAGE AREA FOR THE REQUEST PACKET
;	ADDRESS, SENT TO ME BY A STRATEGY ROUTINE CALL.
;		AS REQUESTED BY THE MSDOS DRIVER MANUAL, I AM "THINKING
;	ABOUT" THE FUTURE, SO I`M DESIGNATING THIS POINTER AS THE QUEUE
;	LIST HEAD FOR REQUESTS TO THIS DRIVER.
;
PACKHEAD	DD	0
;
;	THE STRATEGY ROUTINE ITSELF.
STRATEGY	PROC	FAR
			;SQUIRREL AWAY THE POINTER FOR LATER.
	MOV	WORD PTR CS:PACKHEAD,BX		;STORE THE OFFSET,
	MOV	WORD PTR CS:PACKHEAD+2,ES	;AND THE SEGMENT.
	RET
STRATEGY	ENDP
SUBTTL	REQUEST ROUTINES
PAGE
;	PHYLOSOPHICAL RUMINATIONS:
;		Why does MicroSoft INSIST on choosing names for things that
;	already have firmly defined meanings for OTHER things?  Take for
;	example, the MASM definition of a SEGMENT: It bears little relation
;	to the deffinition of a segment in the intel 8088 processor handbook.
;	This leads to a GREAT DEAL of confusion.  Many other assemblers on
;	other systems have constructs that are equivalent to MASM's SEGMENT,
;	they are often called PSECTS for Program SECTionS.  Perhaps the
;	people at Microsoft wanted a word that made more sence in English,
;	but I wish they had chosen SECTION instead of SEGMENT.
;		The example that it bringing all this to mind now is the
;	MicroSoft device driver documentation, which insists on calling
;	the following routine an "interupt routine".  Go read the intel
;	manual, you will find that an interupt routine is defined THERE as
;	a bunch of code that is jumped to by a special kind of event in
;	the hardware.  That is NOT what the people at MicroSquishy mean
;	this time either.  Depending on weather you describe these routines
;	in terms of what they do now, or in the "future", the following
;	routine should be called the "I/O request routine" or the "I/O
;	completion routine".   But NO, they had to deside to call this
;	the "interupt routine", and create another layer of confusion for
;	those of us who already know the traditional deffinition of this
;	relatively well known phrase.
;
;		I am herby refering to the "interupt routine" as the
;	"request routine", and nameing all my labels accordingly.
;
;		I/O REQUEST ROUTINES
REQUEST1:		;ASYNC1 HAS BEEN REQUESTED
	PUSH	SI	;SAVE SI SO YOU CAN
	MOV	SI,OFFSET ASY_TAB1	;GET THE DEVICE UNIT TABLE ADDRESS.
	JMP	GEN_REQUEST	;THE GENERIC DRIVER DOES THE REST.
REQUEST2:		;ASYNC2 HAS BEEN REQUESTED TO DO SOMETHING
	PUSH	SI	;SAVE SI
	MOV	SI,OFFSET ASY_TAB2	;GET UNIT TABLE TWO`S ADDRESS

GEN_REQUEST:
	PUSHF		;I REQUIRE DIRECTION FLAG CLEARED, SO I SAVE
	CLD		;THE FLAGS AND CLEAR THEM HERE.
	PUSH	AX		;SAVE ALL THE REGESTERS, YOU MAY NOT
	PUSH	BX		;NEED THEM ALL, BUT YOU WILL REGRET IT
	PUSH	CX		;IF YOU FORGET TO SAVE JUST ONE OF THEM.
	PUSH	DX
	PUSH	DI
	PUSH	BP
	PUSH	DS
	PUSH	ES
	PUSH	CS		;COPY THE CS REGESTER
	POP	DS		;INTO THE DS TO ACCESS MY DATA
	LES	BX,PACKHEAD	;RECOVER THE POINTER TO THE PACKET.
	MOV	DI,OFFSET ASY_FUNCS	;GET THE POINTER TO THE DISPATCH TABLE
	MOV	AL,ES:CODE[BX]	;GET THE FUNCTION REQUEST CODE,
	MOV	AH,0		;MAKE IT INTO A WORD,
	SAL	AX,1		;CONVERT TO A WORD OFFSET,
	ADD	DI,AX		;AND ADD TO THE TABLE START ADDRESS
	JMP	[DI]		;JUMP TO THE APPROPRIATE ROUTINE
;
;		TABLE OF OFFSETS TO ALL THE DRIVER FUNCTIONS
ASY_FUNCS:
	DW	ASYNC_INIT	;INITIALIZE DRIVER
	DW	EXIT		;MEDIA CHECK (BLOCK DEVICES ONLY)
	DW	EXIT		;BUILD BPB (BLOCK DEVICES ONLY)
	DW	IOCTLIN		;IOCTL INPUT
	DW	READ		;READ
	DW	NDREAD		;NON-DESTRUCTIVE READ
	DW	RXSTAT		;INPUT STATUS
	DW	INFLUSH		;FLUSH INPUT BUFFER
	DW	WRITE		;WRITE
	DW	WRITE		;WRITE WITH VERIFY
	DW	TXSTAT		;OUTPUT STATUS
	DW	TXFLUSH		;FLUSH OUTPUT BUFFER
	DW	IOCTLOUT	;IOCTL OUTPUT
;
;	EXIT FROM DRIVER REQUEST
;		CALL WITH AX= RETURN STATUS VALUE
EXITP	PROC	FAR
EXIT:
	LES	BX,PACKHEAD	;RETREIVE POINTER TO PACKET
	OR	AX,STSDNE	;SET THE DONE BIT IN IT.
	MOV	ES:STAT[BX],AX	;STORE THE STATUS BACK IN THE PACKET.

	POP	ES		;RESTORE ALL THE REGESTERS
	POP	DS
	POP	BP
	POP	DI
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	POPF
	POP	SI
	RET
EXITP	ENDP
SUBTTL	READ DATA REQUEST ROUTINE
PAGE
;
;		ALL THE FOLLOWING ROUTINES ARE CALLED WITH THE SAME CONTEXT
;	FROM THE REQUEST ROUTINE:
;	- ES:BX POINTS TO THE I/O PACKET.
;	   ROUTINES CAN MUCK UP THESE TWO REGESTERS IF THEY WANT, AS EXIT
;	   WILL RESTORE THEM BEFORE IT TRIES TO SEND THE STATUS WORD BACK.
;	- CS: AND DS: POINT TO THE BEGENING OF THE DRIVER SEGMENT.
;	- DS:SI POINTS TO THE DEVICE UNIT TABLE DESCRIBING THE PARTICULAR
;	   PORT BEING ACCESSED.
;	- ALL OTHER REGESTERS ARE AVAILABLE FOR USE, THE EXIT ROUTINE
;	   RESTORES THEM ALL BEFORE RETURNING TO MSDOS.
;		ALL THE FOLLOWING ROUTINES SHOULD EXIT BY DOING A JMP
;	TO THE EXIT ROUTINE.  EXIT ASSUMES THAT AX
;	CONTAINS EITHER ZERO, OR THE ERROR BIT SET AND A VALID ERROR
;	RETURN VALUE IN THE LOW ORDER BITS.  EXIT SETS THE DONE BIT IN
;	THIS VALUE FOR YOU BEFORE IT RETURNS TO MSDOS.
;
SUBTTL	READ REQUEST ROUTINE
;		READ DATA FROM DEVICE
;
READ:
	MOV	CX,ES:COUNT[BX]		;GET THE REQUESTED NUMBER OF BYTES
	MOV	DI,ES:XFER[BX]		;DI IS OFFSET TO USER BUFFER
	MOV	DX,ES:XSEG[BX]		;SEGMENT IS LAST I NEED FROM PACKET,
	MOV	ES,DX			;NOW ES:DI POINTS TO USER BUFFER.
	CALL	START_IN	;TRY TO RESTART RX, ALL REGS PRESERVED
	TEST	MODEM[SI],MODERR OR MODOVR ;HAVE ANY LINE ERRORS OCCURED?
	JE	NO_LERR			;NOT LATELY.
	AND	MODEM[SI],NOT ( MODERR OR MODOVR ) ;YES, CLEAR THE BITS,
	MOV	AX,ERRRF		;AND RETURN ERROR INDICATION TO DOS
	JMP	EXIT
NO_LERR:

RLUP:
	CALL	GET_IN			;GET NEXT CHAR FROM INPUT BUFFER
	CMP	AH,0			;WAS THERE ONE?
	JE	TEST_READ		;YES, TEST FOR SPECIAL BITS
	JMP	RLUP			;NO, WAIT FOR A CHAR TO ARRIVE.
TEST_READ:
		;BEFORE RETURNING A CHARACTER TO MSDOS, I CHECK FOR ANY
		;SPECIAL PROCESSING I AM REQUIRED TO DO.  I DO AS MANY
		;OF THESE FUNCTIONS HERE AS POSSIBLE, TO SAVE THE
		;RECEIVER INTERUPT ROUTINE FROM HAVING TO DO THEM, AND
		;TO ALLOW INTELIGENT USE OF THE RING BUFFER.  FOR EXAMPLE,
		;CHARACTERS TO NOT ECHO-HALF-DUPLEX UNTIL THEY ARE READ
		;FROM THE BUFFER HERE, SO A PROGRAM THAT DISABLES ECHO
		;WHILE READING A PASSWORD WILL WORK EVEN IF THE USER TYPED
		;THE PASWORD IN BEFORE BEING PROMPTED FOR IT.
			;************************************
			;TEST FOR STRIPPING PARITY BIT
	TEST	INSPEC[SI],INSTP	;SHOULD I STRIP PARITY?
	JE	NO_STRIP		;NOPE.
	AND	AL,NOT 080H		;YES.
NO_STRIP:
			;**********************************8
			;TEST FOR HALF-DUPLEX MODE
	STOS	BYTE PTR [DI]		;BUT FIRST STORE CHAR IN USER BUFFER.
	TEST	INSPEC[SI],INHDP	;AM I IN HALF DUPLEX MODE?
	JE	NO_HALF			;NO.
HALF_WAIT:
	CALL	PUT_OUT			;YES, PUT THE CHARACTER IN OUTBUF.
	CMP	AH,0			;WAS THERE ROOM?
	JNE	HALF_WAIT		;NO, WAIT FOR IT.
	CALL	START_OUTPUT		;AND MAKE SURE THE XMITTER STARTS
NO_HALF:
		;ALTHOUGH MSDOS NEVER, TO MY KNOWLEDGE, ASKS FOR MORE THAN
		;ONE STUPID CHARACTER AT A TIME, I LOOP ON THE REQUEST SIZE
		;SO THAT THIS DRIVER WILL STILL WORK ON THAT GLORIOUS DAY
		;WHEN SOMEBODY ASKS FOR MORE THAN ONE.
	LOOP	RLUP			;KEEP GOING IF YOU WERE REQUESTED.
	MOV	AL,0			;RETURN NO ERRORS IN AX IF DONE.
	JMP	EXIT
SUBTTL	NON-DESTRUCTIVE READ REQUEST ROUTINE
PAGE
;		NON-DESTRUCTIVE READ FROM DEVICE
;
NDREAD:
	MOV	DI,IFIRST[SI]		;GET POINTER TO FIRST CHAR
	CMP	DI,IAVAIL[SI]		;IS THE BUFFER EMPTY?
	JNE	NDGET			;NO, GET ONE NON DESTRUCTIVELY.
	MOV	AX,STSBSY		;YES, RETURN DEVICE BUISY
	JMP	EXIT
NDGET:
	PUSH	BX			;SAVE AN EXTRA COPY OF BX.
	MOV	BX,IBUF[SI]		;GET BUFFER ADDRESS
	MOV	AL,[BX+DI]			;GET THE CHARACTER,
	POP	BX			;RECOVER BX AGAIN.
	MOV	ES:MEDIA[BX],AL		;RETURN IT IN THE REQUEST PACKET.
	MOV	AX,0			;RETURN NO ERRORS IN AX.
	JMP	EXIT
SUBTTL	INPUT STATUS REQUEST ROUTINE
PAGE
;		INPUT STATUS REQUEST
;
RXSTAT:
	MOV	DI,IFIRST[SI]		;GET POINTER TO FIRST CHAR
	CMP	DI,IAVAIL[SI]		;IS THE BUFFER EMPTY?
	JNE	RXFUL
	MOV	AX,STSBSY		;NO, RETURN STATUS BUISY.
	JMP	EXIT
RXFUL:
	MOV	AX,0			;YES, RETURN STATUS ZERO.
	JMP	EXIT
SUBTTL	INPUT FLUSH REQUEST ROUTINE
;		INPUT FLUSH REQUEST
;
INFLUSH:
	MOV	AX,IAVAIL[SI]		;GET THE POINTER TO THE NEXT EMPTY
	MOV	IFIRST[SI],AX		;CHAR AND POINT THE FIRST AT IT.
	MOV	AX,0			;AND RETURN DONE.
	JMP	EXIT
SUBTTL	WRITE REQUEST ROUTINE
PAGE
;		OUTPUT DATA TO DEVICE
;
WRITE:
	MOV	CX,ES:COUNT[BX]		;GET BYTE COUNT,
	MOV	DI,ES:XFER[BX]		;GET XFER ADDRESS OFFSET,
	MOV	AX,ES:XSEG[BX]		;GET XFER SEGMENT.
	MOV	ES,AX			;STORE IN ES NOW.
WLUP:
	MOV	AL,ES:[DI]		;GET THE NEXT CHAR,
	INC	DI			;AND INC DI PAST IT.
;
;		CHECK FOR STRIP PARITY ON OUTPUT.
;
	TEST	OUTSPEC[SI],OUTSTP	;IS THIS ENABLED?
	JE	NOOSTP			;NOPE
	AND	AL,NOT 080H		;YES, WACK OFF BIT SEVEN!
NOOSTP:
;
;		AFTER ALL THE SPECIAL OUTPUT PROCESSING, I DO A HARD WAIT
;		FOR A SLOT IN THE OUTPUT BUFFER.
WWAIT:
	CALL	PUT_OUT			;ATTEMPT TO PUT IN IN OUTPUT BUFFER
	CMP	AH,0			;DID IT WORK?
	JNE	WWAIT			;NO, KEEP TRYING.
	LOOP	WLUP			;YES, GO GET NEXT CHAR.
	CALL	START_OUTPUT		;START THE XMITTER IF NECC.
	MOV	AX,0			;RETURN SUCCESS
	JMP	EXIT
SUBTTL	OUTPUT STATUS REQUEST ROUTINE
;		OUTPUT STATUS REQUEST
;
TXSTAT:
	MOV	AX,OFIRST[SI]		;GET POINTER TO NEXT CHAR OUT
	DEC	AX			;SUBTRACT ONE FROM IT,
	AND	AX,BUFMSK		;MODULO 128.
	CMP	AX,OAVAIL[SI]		;IF THAT EQUALS THE INPUT PNTR,
	JNE	TXROOM
	MOV	AX,STSBSY		;THEN THE DEVICE IS BUISY.
	JMP	EXIT
TXROOM:
	MOV	AX,0			;OTHERWIZE THE DEVICE IS OK.
	JMP	EXIT
SUBTTL	I/O CONTROL READ REQUEST
PAGE
;
;		IOCONTROL READ REQUEST, RETURN LINE PARAMETERS
;
IOCTLIN:
	MOV	CX,ES:COUNT[BX]		;GET THE REQUESTED NUMBER OF BYTES
	MOV	DI,ES:XFER[BX]		;DI IS OFFSET TO USER BUFFER
	MOV	DX,ES:XSEG[BX]		;SEGMENT IS LAST I NEED FROM PACKET,
	MOV	ES,DX			;NOW ES:DI POINTS TO USER BUFFER.
	CMP	CX,14		;ONLY WORKS WHEN YOU GIVE ME AN
	JE	DOIOCIN		;14 BYTE BUFFER TO STOMP ON.
	MOV	AX,ERRBSL		;RETURN AN ERROR IF NOT 14 BYTES.
	JMP	EXIT
DOIOCIN:
	MOV	DX,PORT[SI]		;GET PORT NUMBER
	ADD	DX,LCTRL		;SLIDE UP TO LINE CONTROL
	MOV	CX,4			;SET UP FOR PORT LOOP.
GETPORT:
	IN	AL,DX			;GET NEXT BYTE FROM DEVICE
	STOS	BYTE PTR [DI]		;STORE THEM IN USER BUFFER
	INC	DX			;SKIP TO NEXT BYTE
	LOOP	GETPORT			;READ AND STORE 4 BYTES OF INFO

	MOV	AX,INSPEC[SI]		;GET THE SPECIAL INPUT BITS
	STOS	WORD PTR [DI]		;SEND BACK TO USER BUFFER
	MOV	AX,OUTSPEC[SI]		;GET THE SPECIAL OUTPUT BITS
	STOS	WORD PTR [DI]		;SEND BACK TO USER BUFFER
	MOV	AX,BAUD[SI]		;GET BAUD RATE DIVISOR
	MOV	BX,DI			;SAVE DI FOR A WHILE.
	MOV	DI,OFFSET ASY_BAUDT+2	;POINT AT BAUD RATE CONVERSION.
	MOV	CX,15			;JUST IN CASE, STOP AT 15 BAUDS
BAUDCIN:
	CMP	[DI],AX			;IS THIS THE BAUD I AM USING?
	JE	YESINB			;YES, RETURN THAT
	ADD	DI,4			;NO, SKIP TO NEXT ONE
	LOOP	BAUDCIN			;KEEP LOOKING.
YESINB:			;SEARCH SHOULD ALWAYS TERMINATE ON COMPARE
	MOV	AX,-2[DI]		;GET THE ASSOCIATED BAUD RATE
	MOV	DI,BX			;GET DI'S OLD VALUE BACK
	STOS	WORD PTR [DI]		;STORE THE BAUD RATE BACK.
	mov	ah,0
	mov	al,line[si]
	stos	byte ptr [di]
	mov	ah,0
	mov	al,modem[si]
	stos	byte ptr [di]
	MOV	AX,0			;RETURN NO ERRORS
	stos	word ptr [di]	;zero the extra word
	JMP	EXIT

;
;		FLUSH OUTPUT BUFFER REQUEST
;
TXFLUSH:
	MOV	AX,OAVAIL[SI]		;GET NEXT FREE BYTE OFFSET,
	MOV	OFIRST[SI],AX		;POINT THE FIRST BYTE OFFSET AT IT.
	MOV	AX,0
	JMP	EXIT
SUBTTL	I/O CONTROL WRITE REQUEST ROUTINE
PAGE
;		IOCONTROL REQUEST: CHANGE LINE PARAMETERS FOR THIS DRIVER
;
IOCTLOUT:
	MOV	CX,ES:COUNT[BX]		;GET THE REQUESTED NUMBER OF BYTES
	MOV	DI,ES:XFER[BX]		;DI IS OFFSET TO USER BUFFER
	MOV	DX,ES:XSEG[BX]		;SEGMENT IS LAST I NEED FROM PACKET,
	MOV	ES,DX			;NOW ES:DI POINTS TO USER BUFFER.
	CMP	CX,10			;ONLY WORKS WHEN YOU GIVE ME A
	JNL	DOIOCOUT		;atleast 10 BYTE BUFFER TO READ
	MOV	AX,ERRBSL		;RETURN AN ERROR IF NOT =>10 BYTES.
	JMP	EXIT
DOIOCOUT:
	MOV	DX,PORT[SI]		;GET PORT NUMBER
	ADD	DX,LCTRL		;SLIDE UP TO LINE CONTROL
	MOV	AL,ES:[DI]		;GET LINE CONTROL FROM USER BUF.
	INC	DI
	OR	AL,080H			;SET DLAB BIT FOR BAUD RATE
	OUT	DX,AL			;OUTPUT TO DEVICE
	INC	DX			;SKIP TO NEXT BYTE
	MOV	AL,ES:[DI]		;GET MODEM CONTROL FROM USER BUF.
	INC	DI
	OR	AL,08H			;MAKE SURE INTERUPTS ARE ENABLED.
	OUT	DX,AL			;SEND IT TO DEVICE.
	ADD	DI,2			;SKIP OVER THE STATUS BYTES
	MOV	AX,ES:[DI]		;GET THE SPECIAL INPUT BITS
	ADD	DI,2
	MOV	INSPEC[SI],AX		;STORE THE NEW BITS IN UNIT
	MOV	AX,ES:[DI]		;GET THE OUTPUT SPECIAL BITS
	ADD	DI,2
	MOV	OUTSPEC[SI],AX		;STORE THEM ALSO.
	MOV	AX,ES:[DI]		;GET THE REQUESTED BAUD RATE
	MOV	BX,DI			;SAVE DI FOR A WHILE.
	MOV	DI,OFFSET ASY_BAUDT	;POINT AT BAUD RATE CONVERSION
	MOV	CX,15			;JUST IN CASE, STOP AT 15 BAUDS
BAUDCOUT:
	CMP	[DI],AX			;IS THIS THE BAUD I AM USING?
	JE	YESOUTB			;YES, RETURN THAT
	ADD	DI,4			;NO, SKIP TO NEXT ONE
	LOOP	BAUDCOUT		;KEEP LOOKING.
	IN	AL,DX			;GET LINE CONTROL REGESTER AGAIN,
	AND	AL,NOT 080H		;CLEAR DLAB BIT.
	DEC	DX
	OUT	DX,AL			;AND WRITE IT BACK OUT.
	MOV	AX,ERRUM		;RETURN AN ERROR NUMBER IF
	JMP	EXIT			;BAUD RATE IS NOT IN TABLE.
YESOUTB:
	MOV	AX,2[DI]		;GET THE ASSOCIATED BAUD RATE
	MOV	BAUD[SI],AX		;STORE IT IN UNIT TABLE
	MOV	DX,PORT[SI]		;GET PORT ADDRESS AGAIN,
	OUT	DX,AL			;WRITE THE LOW BYTE,
	INC	DX			;SKIP TO NEXT ONE,
	MOV	AL,AH			;GET HIGH BYTE INTO AL
	OUT	DX,AL			;OUTPUT IT AS WELL.
	ADD	DX,LCTRL-BAUD1		;POINT AT THE LINE CONTROL REG.
	IN	AL,DX			;READ IT IN,
	AND	AL,NOT 080H		;CLEAR THE DLAB BIT.
	OUT	DX,AL			;OUTPUT IT BACK.
	MOV	AX,0			;RETURN NO ERROR
	JMP	EXIT
SUBTTL	RING BUFFER ROUTINES
PAGE
;		LOCAL ROUTINES FOR MANAGING THE RING BUFFERS ON INPUT
;	AND OUTPUT.  THE FOLLOWING FOUR ROUTINES ARE ALL CALLED WITH THE
;	SAME CONTEXT:
;
;	DS:SI	POINTS TO THE UNIT STRUCTURE FOR THIS UNIT
;	AL	IS THE CHARACTER TO BE PLACED IN OR REMOVED FROM A BUFFER
;	AH	IS THE RETURN STATUS FLAG: 0=SUCESS, -1=FAILURE
;
;	ALL OTHER REGESTERS ARE PRESERVED.
;
PUT_OUT	PROC	NEAR	;PUTS AL INTO THE OUTPUT RING BUFFER
	PUSHF
	CLI			;DISABLE INTERUPTS WHILE I HAVE OAVAIL
	PUSH	CX
	PUSH	DI
	MOV	CX,OAVAIL[SI]	;GET POINTER TO NEXT AVAILABLE BYTE IN
	MOV	DI,CX		;OUTPUT BUFFER.
	INC	CX		;INCRIMENT A COPY OF IT TO SEE IF THE
	AND	CX,BUFMSK	;BUFFER IS FULL.
	CMP	CX,OFIRST[SI]	;IS IT?
	JE	POERR		;YES, RETURN AN ERROR
	ADD	DI,OBUF[SI]	;NO, CALCULATE ACTUAL OFFSET OF CHAR
	MOV	[DI],AL		;AND STUFF THE CHARACTER INTO BUFFER
	MOV	OAVAIL[SI],CX	;UPDATE THE POINTER
	MOV	AH,0		;INDICATE SUCCESS
	JMP	PORET		;AND RETURN
POERR:
	MOV	AH,-1		;INDICATE FAILURE.
PORET:
	POP	DI
	POP	CX
	POPF		;RE-ENABLE INTERUPTS
	RET
PUT_OUT	ENDP

GET_OUT	PROC	NEAR	;GETS THE NEXT CHARACTER FROM OUTPUT RING BUFFER
	PUSHF			;JUST IN CASE, DISABLE INTERUPTS
	CLI			;WHILE IN THIS ROUTINE.
			;SURE YOU DISABLE INTERUPTS FIRST.
	PUSH	CX
	PUSH	DI
	MOV	DI,OFIRST[SI]	;GET POINTER TO FIRST CHARACTER TO OUTPUT
	CMP	DI,OAVAIL[SI]	;IS THE BUFFER EMPTY?
	JNE	NGOERR		;NO.
	MOV	AH,-1		;YES, INDICATE  FAILURE
	JMP	GORET		;AND RETURN
NGOERR:
	MOV	CX,DI		;SAVE A COPY OF THE POINTER
	ADD	DI,OBUF[SI]	;CALCULATE ACTUAL ADDRESS
	MOV	AL,[DI]		;GET THE CHAR INTO AL
	MOV	AH,0		;INDICATE SUCCESS.
	INC	CX		;INCRIMENT THE OFFSET
	AND	CX,BUFMSK	;MODULO 128
	MOV	OFIRST[SI],CX	;STORE BACK IN UNIT TABLE.
GORET:
	POP	DI
	POP	CX
	POPF
	RET
GET_OUT	ENDP

PUT_IN	PROC	NEAR	;PUT THE CHAR FROM AL INTO INPUT RING BUFFER
	PUSHF			;DISABLE INTS WHILE IN THIS ROUTINE
	CLI
	PUSH	CX
	PUSH	DI
	MOV	DI,IAVAIL[SI]	;GET POINTER TO NEXT AVAILABLE SLOT IN BUFFER
	MOV	CX,DI		;SAVE A COPY OF IT,
	INC	CX		;AND INCRIMENT THAT COPY (MODULO
	AND	CX,BUFMSK		;128) TO SEE IF THE BUFFER IS FULL.
	CMP	CX,IFIRST[SI]	;WELL, IS IT?
	JNE	NPIERR		;NO, THERE`S ROOM.
	MOV	AH,-1		;YES, INDICATE FAILURE
	JMP	PIRET		;AND RETURN
NPIERR:
	ADD	DI,IBUF[SI]	;CALCULATE ACTUAL ADDRES,
	MOV	[DI],AL		;STORE THE CHARACTER THERE
	MOV	IAVAIL[SI],CX	;UPDATE THE POINTER.
	MOV	AH,0		;AND INDICATE SUCCESS.
PIRET:
	POP	DI
	POP	CX
	POPF
	RET
PUT_IN	ENDP

GET_IN	PROC	NEAR	;GETS ONE CARACTER FROM INPUT RING BUFFER INTO AL
	PUSHF
	CLI		;DISABLE INTERUPTS WHILE I LOOK AT IFIRST.
	PUSH	CX
	PUSH	DI
	MOV	DI,IFIRST[SI]	;GET POINTER TO FIRST CHAR TO READ
	CMP	DI,IAVAIL[SI]	;IS THE BUFFER EMPTY?
	JE	GIERR		;THEN YOU CAN`T VERY WELL SQUEEZE WATER OUT OF IT
	MOV	CX,DI		;MAKE A COPY OF POINTER,
	ADD	DI,IBUF[SI]	;CALCULATE ACTUAL ADDRESS OF CHAR
	MOV	AL,[DI]		;GET THE CHAR INTO AL
	MOV	AH,0		;INDICATE SUCCESS
	INC	CX		;INCRIMENT THAT COPY OF YOUR POINTER,
	AND	CX,BUFMSK	;MODULO THE BUFFER SIZE,
	MOV	IFIRST[SI],CX	;SO YOU CAN UPDATE THE POINTER.
	JMP	GIRET
GIERR:
	MOV	AH,-1		;RETURN FAILURE INDICATOR
GIRET:
	POP	DI
	POP	CX
	POPF		;RE-ENABLE INTERUPTS BEFORE YOU RETURN
	RET
GET_IN	ENDP
SUBTTL	INPUT FLOW CONTROL CHECKING ROUTINES
STOP_IN	PROC	NEAR	;STOP INPUT IF BUFFER IS GETTING FULL
	PUSHF
	CLI
	PUSH	AX
	PUSH	DX
	MOV	AX,IAVAIL[SI]
	ADD	AX,BUFSIZ
	SUB	AX,IFIRST[SI]		;AX IS NOW THE # BYTES USED
	AND	AX,BUFMSK			;PUT IT IN THE RIGHT RANGE
	CMP	AX,ISTOP			;SHOULD WE STOP THE TURKEY?
	JL	S_IN_OK			;AX<STOP POINT IT'S OK
	TEST	INSPEC[SI],INXON	;IS XON/XOFF ENABLED?
	JE	S_IN_DTR			;NOPE, TRY DTR
	MOV	DX,PORT[SI]		;WAIT FOR THE TRANSMITTER
	ADD	DX,LSTAT			;HOLDING REG TO BE EMPTY
S_IN_LOP:
	IN	AL,DX			;GET THE REG VALUE
	TEST	AL,20H
	JE	S_IN_LOP			;LOOP UNTIL IT'S EMPTY
	MOV	DX,PORT[SI]		;NOW SENT THE DATA (^S)
	MOV	AL,'S' AND 1FH
	OUT	DX,AL
	OR	MODEM[SI],MODXON	;REMEMBER WE SENT THIS
	OR	LINE[SI],LINEXP	;WE ARE EXPECTING AN INTR
S_IN_DTR:
	MOV	DX,PORT[SI]		;POINT TO MODEM CONTROL REG
	ADD	DX,MCTRL
	TEST	INSPEC[SI],INDTR	;USING DTR FOR FLOW CONTROL?
	JE	S_IN_RTS			;NOPE, CHECK RTS
	IN	AL,DX
	AND	AL,NOT 1
	OUT	DX,AL
	OR	MODEM[SI],MODDTR
S_IN_RTS:
	TEST	INSPEC[SI],INRTS
	JE	S_IN_OK
	IN	AL,DX
	AND	AL,NOT 2
	OUT	DX,AL
	OR	MODEM[SI],MODRTS
S_IN_OK:
	POP	DX
	POP	AX
	POPF
	RET
STOP_IN	ENDP
;
START_IN	PROC	NEAR	;RESTART INPUT IF THERE IS SPACE
	PUSHF
	CLI
	PUSH	DX
	PUSH	AX
	TEST	MODEM[SI],MODIDL
	JE	Q_IN_OK
	MOV	AX,IAVAIL[SI]
	ADD	AX,BUFSIZ
	SUB	AX,IFIRST[SI]		;AX IS NOW THE # BYTES USED
	AND	AX,BUFMSK			;PUT IT IN THE RIGHT RANGE
	CMP	AX,ISTART			;SHOULD WE RESTART THE TURKEY?
	JG	Q_IN_OK			;AX>RESTART POINT DO IT LATER
	TEST	INSPEC[SI],INXON	;IS XON/XOFF ENABLED?
	JE	Q_IN_DTR			;NOPE, TRY DTR
	MOV	DX,PORT[SI]		;GET PORT ADDRESS
	ADD	DX,LSTAT			;POINT TO LSTAT
Q_IN_LOP:
	IN	AL,DX			;GET STATUS
	TEST	AL,20H			;HOLDING REG EMPTY?
	JE	Q_IN_LOP			;NOPE, WAIT FOR IT
	MOV	DX,PORT[SI]
	MOV	AL,'Q' AND 1FH
	OUT	DX,AL
	AND	MODEM[SI],NOT MODXON
	OR	LINE[SI],LINEXP
Q_IN_DTR:
	MOV	DX,PORT[SI]
	ADD	DX,MCTRL			;POINT TO MODEM CONTROL REG
	TEST	INSPEC[SI],INDTR	;DTR FLOW CONTROL ENABLED?
	JE	Q_IN_RTS			;NOPE, TRY RTS
	IN	AL,DX
	OR	AL,1				;TURN DTR ON
	OUT	DX,AL
Q_IN_RTS:
	TEST	INSPEC[SI],INRTS	;RTS FLOW CONTROL ENABLED?
	JE	Q_IN_OK			;NOPE, WE ARE DONE
	IN	AL,DX
	OR	AL,2				;TURN RTS ON
	OUT	DX,AL
Q_IN_OK:
	POP	AX
	POP	DX
	POPF
	RET
START_IN	ENDP
SUBTTL	INTERUPT SERVICE ROUTINES
PAGE
;		THE FOLLOWING ROUTINES ARE  WHAT I REALLY CALL AN INTERUPT
;	ROUTINE!  THESE ROUTINES ARE ONLY CALLED WHEN AN INTERUPT IS GENERATED
;	BY THE 8088, NOT BY A SOFWARE CALL THROUGH A LINKED LIST! ONE EASY
;	WAY TO TELL A REAL INTERUPT ROUTINE WHEN YOU SEE IT IS TO LOOK AT THE
;	LAST INSTRUCTION, WHICH IS AN "INTERUPT RETURN",  NOT A FAR RETURN
;	LIKE THE SO-CALLED MSDOS "INTERUPT ROUTINE" DOES.
;	THESE INTERUPT ROUTINES ARE ENVOKED WHENEVER A CHAR ARRIVES IN THE
;	UART, THE UART FINISHES SENDING A CHARACTER OUT, AN ERROR OCCURS
;	WHILE READING A CHARACTER INTO THE UART, OR THE MODEM STATUS LINES
;	CHANGE.

ASY1ISR:
	CLI
	PUSH	SI
	LEA	SI,ASY_TAB1	;POINT AT THE CORRECT TABLE FOR THIS UART,
	JMP	INT_SERVE	;JUMP INTO THE COMMON INTERUPT SERVER CODE.
ASY2ISR:
	CLI
	PUSH	SI
	LEA	SI,ASY_TAB2	;GET UNIT TABLE
;
;			IF YOU ADD MORE UNITS, YOU CAN ADD THEM HERE,
;			BUT DON'T FORGET TO ADD A JUMP INT_SERVE AFTER
;			ASY2ISR'S LEA INSTRUCTION!
;
;		THE FOLLOWING CODE IS THE COMMON SHARED CODE THAT ALL THE
;	ASYNC PORTS SHARE.  IT "KNOWS" WHICH ONE TO TALK TO BY REFERENCING
;	THE STRUCTURE POINTED TO BY CS:SI.

INT_SERVE:
	PUSH	AX	;PUSH ALL THE GP REGESTERS
	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	DI
	PUSH	DS	;SAVE THE DATA SEGMENT
	PUSH	CS	;SO YOU CAN LOAD CS
	POP	DS	;INTO DS AND FIND YOUR OWN STRUCTURES.
INT_EXIT:
	MOV	DX,PORT[SI]	;GET THE PORT ADDRESS OF THIS DEVICE
	ADD	DX,INTID	;SLIDE UP TO THE INTERUPT ID REGISTER
	IN	AL,DX		;GET THE INTERUPT REASON
	TEST	AL,1		;MAKE SURE IT IS VALID
	JNE	INT_DONE	;IT IS NOT.
	MOV	AH,0		;CONVERT IT TO A 16 BIT NUMBER
	ADD	AX,OFFSET INT_REASON	;ADD IT TO THE JUMP TABLE ADDRESS
	MOV	DI,AX		;PUT IT IN AN INDEX REGESTER,
	JMP	[DI]		;AND GO PROCESS THAT TYPE OF INTERUPT

INT_DONE:
	ADD	DX,LSTAT-INTID	;BECAUSE IT SEEMS TO BE NECESSARY,
	IN	Al,DX		;READ THE STATUS PORT BEFORE YOU EXIT.
	MOV	AL,020H		;OUTPUT A 20H TO THE UNDOCUMENTED INTERUPT
	OUT	020H,AL		;COMMAND PORT TO ENABLE FURTHER INTERUPTS?
	POP	DS	;RECOVER ALL THE REGESTERS
	POP	DI
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	POP	SI
	IRET

;
;		JUMP TABLE OF INTERUPT REASONS. INTERUPT ID REGISTER
;		IS USED AS INDEX TO THIS TABLE.
INT_REASON:
	DW	INT_MODEM	;INT WAS CAUSED BY A MODEM LINE TRANSITION
	DW	INT_TXMIT	;INT WAS CAUSED BY A TX REGISTER EMPTY
	DW	INT_RECEIVE	;NEW CHARACTER AVAILABLE IN UART
	DW	INT_RXSTAT	;CHANGE IN RECEIVER STATUS REGESTER
SUBTTL	RECEIVER INTERUPT SERVICE ROUTINE
PAGE
;
;		THE INTERUPT SERVICE ROUTINES BELOW ALL HAVE THE
;	FOLLOWING CONTEXT:
;	-CS AND DS POINT TO THE DRIVER SEGMENT.
;	-DS:[SI] POINTS TO THE UNIT STRUCTURE FOR THE ASYNC LINE THAT
;		FIRED THE INTERUPT.
;	-AX BX CX DX AND DI ARE AVAILABLE FOR SCRATCH.  ALL OTHERS
;		MUST BE LEFT ALONE OR SAVED AND RECOVERED.
;	TO EXIT FROM AN INTERUPT SERVICE ROUTINE, THESE SERVERS MUST
;	JUMP TO INT_EXIT.
;
SUBTTL	RECEIVER INTERUPT SERVICE ROUTINE
INT_RECEIVE:	;THE UART HAS RECEIVED A NEW CHARACTER, I MUST COPY IT
		;INTO THE INPUT TYPEAHEAD BUFFER.
	MOV	DX,PORT[SI]	;POINT DX BACK AT THE RXBUF REGESTER
	IN	AL,DX		;GET THE CHARACTER
;
;			BEFORE I STORE THE CHARACTER BACK IN THE RING
;			BUFFER, I CHECK TO SEE IF ANY OF THE SPECIAL
;			INPUT SEQUENCES ARE ENABLED, AND IF THEY
;			HAVE BEEN FOUND IN THE INPUT.
;
;
;		CHECK FOR XON/XOFF
;
	TEST	OUTSPEC[SI],OUTXON	;IS XON/XOFF ENABLED?
	JE	NOXON			;NO, SKIP THIS WHOLE SECTION
	CMP	AL,'S' AND 01FH		;IS THIS A CONTROL-S?
	JNE	ISQ			;NO, CHECK FOR CONTROL-Q
	OR	LINE[SI],LINXOF		;DISABLE OUTPUT.
	JMP	INT_EXIT		;DON'T STORE THIS CHAR.
ISQ:
	CMP	AL,'Q' AND 01FH		;IS THIS A CONTROL-Q?
	JNE	NOXON			;NO, SKIP TO THE NEXT TEST.
	TEST	LINE[SI],LINXOF		;AM I WAITING FOR THIS ^Q?
	JE	INT_EXIT		;NO, DON'T STIR UP DRIVER.
	AND	LINE[SI],NOT LINXOF	;CLEAR THE XOFF BIT,
	CALL	START_OUTPUT
	JMP	INT_EXIT		;DON'T BUFFER ^Q'S

NOXON:
STUFF_IN:
	CALL	PUT_IN		;PUT THE CHARACTER IN THE RING BUFFER
	CMP	AH,0		;WAS THERE ROOM?
	JE	INT_RX_CHK
	OR	MODEM[SI],MODOVR	;NO, SET THE OVERFLOW BIT
INT_RX_CHK:
	CALL	STOP_IN		;STOP RX IF GETTING FULL
	JMP	INT_EXIT
SUBTTL	RECEIVER LINE STATUS INTERUPT ROUTINE
PAGE
;		THE LSTAT REGISTER DETECTED A RECEIVER ERROR CONDITION
INT_RXSTAT:
	ADD	DX,LSTAT-INTID	;READ THE REGESTER AND FIND OUT WHY
	IN	AL,DX
	TEST	INSPEC[SI],INEPC	;DO I RETURN THEM AS CODES?
	JE	NOCODE			;NO, WHAT ELSE?
	AND	AL,01EH			;YES, MASK OFF ALL BUT ERROR BITS,
	OR	AL,080H			;SET THE PARITY BIT TO MAKE IT
					;AN ILLEGAL CHARACTER,
	JMP	STUFF_IN		;AND PUT IT IN THE INPUT BUFFER.
NOCODE:
	OR	MODEM[SI],MODERR	;SET A STATUS BIT THAT WILL
					;NOTIFY MSDOS ON THE NEXT REQUEST
	JMP	INT_EXIT
PAGE
SUBTTL	MODEM STATUS INTERUPT SERVICE ROUTINE
;		THE MODEM STATUS REGESTER DETECTED A CHANGE IN ONE OF THE
;	MODEM LINES.  I COULD CHECK THE "DELTA BITS" TO SEE EXACTLY WHICH
;	LINE TRIGGERED THIS INTERUPT, BUT I JUST CHECK ALL OF THEM WHEN
;	I GET ANY MODEM STATUS INTERUPT.
INT_MODEM:
	ADD	DX,MSTAT-INTID	;READ THE MODEM STATUS REGESTER
	IN	AL,DX
			;*********************************
			;CHECK THE CARIER-DETECT BIT (CD)
	TEST	AL,080H		;IS CARRIER DETECT OFF?
	JNE	MSDSR		;NO, CHECK DSR NEXT
	TEST	OUTSPEC[SI],OUTCDF	;IS CD THE OFF-LINE SIGNAL?
	JE	MSDSR		;NO, IGNORE CD THEN.
	OR	MODEM[SI],MODOFF	;YES,SET OFLINE FOR NEXT READ REQUEST
			;**************************************
			;CHECK THE DATA-SET-READY BIT (DSR)
MSDSR:
	TEST	AL,020H		;IS DSR OFF?
	JNE	DSRON		;NO, GO CHECK TO SEE IF I WAS WAITING ON IT.
	TEST	OUTSPEC[SI],OUTDSR	;IS DSR THE OUTPUT DATA THROTTLE FLG?
	JE	DSROFF			;NO, MABY IT'S OFFLINE SIGNAL
	OR	LINE[SI],LINDSR		;YES, SUSPEND OUTPUT WAITING ON DSR
DSROFF:
	TEST	OUTSPEC[SI],OUTDRF	;IS DSR THE OFFLINE SIGNAL?
	JE	MSCTS			;NOPE.
	OR	MODEM[SI],MODOFF	;YES, SET FLAG FOR NEXT READ.
	JMP	MSCTS
DSRON:
	TEST	LINE[SI],LINDSR		;WAS I WAITING FOR DSR TO COME ON?
	JE	MSCTS			;NO, IGNORE IT.
	AND	LINE[SI],NOT LINDSR	;YES, CLEAR THE BIT, AND
	CALL	START_OUTPUT		;START OUTPUT BACK UP AGAIN.
			;****************************************
			;CHECK THE CLEAR-TO-SEND BIT (CTS)
MSCTS:
	TEST	AL,010H			;IS CTS OFF?
	JNE	CTSON			;NO, GO CHECK TO SEE IF ITS EXPECTED
	TEST	OUTSPEC[SI],OUTCTS	;IS CSR THE OUTPUT DATA THROTTLER?
	JE	CTSOFF			;NO, MABY IT'S OFFLINE SIGNAL
	OR	LINE[SI],LINCTS		;YES, SUSPEND OUTPUT.
CTSOFF:
	TEST	OUTSPEC[SI],OUTCTS	;IS CTS THE OFF-LINE SIGNAL?
	JE	INT_EXIT4		;NOPE.
	OR	MODEM[SI],MODOFF	;YES, SET FLAG FOR NEXT READ.
	JMP	INT_EXIT
CTSON:
	TEST	LINE[SI],LINCTS		;WAS I WAITING FOR THIS CTS?
	JE	INT_EXIT4		;NO, THERE'S NOTHING LEFT TO CHECK.
	AND	LINE[SI],NOT LINCTS	;YES, CLEAR THE BIT, AND
	CALL	START_OUTPUT		;START OUTPUT UP AGAIN.
INT_EXIT4:
	JMP	INT_EXIT
SUBTTL	TRANSMITTER INTERUPT SERVICE ROUTINE
PAGE
;		THE TRANSMITTER HOLDING REGESTER IS EMPTY, LOOK TO SEE IF
INT_TXMIT:	;THERE ARE MORE CHARS TO PRINT NOW.
	AND	LINE[SI],NOT LINEXP	;CLEAR INTERUPT EXPECTED BIT.
	CALL	START_OUTPUT		;START THE NEXT CHARACTER
	JMP	INT_EXIT

;ROUTINE TO START THE NEXT CHARACTER PRINTING ON THE UART, IF OUTPUT
;IS NOT BEING SUSPENDED FOR ONE REASON OR ANOTHER.
;THIS ROUTINE MAY BE CALLED FROM REQUEST ROUTINES, OR FROM INTERUPT
;SEVICE ROUTINES.
;	THIS ROUTINE DESTROYS AX AND DX.
;	SX MUST POINT AT THE UNIT STRUCTURE.
START_OUTPUT	PROC	NEAR
	PUSHF				;SAVE THE FLAGS SO I CAN
	CLI				;DISABLE INTERUPTS
	TEST	LINE[SI],LINIDL		;AM I IN HOLD OUTPUT MODE?
	JNE	DONT_START		;YES, DON'T SEND ANY MORE CHARS.
;	MOV	DX,PORT[SI]	;CHECK HOLDING REG STATUS
;	ADD	DX,LSTAT
;	IN	AL,DX
;	TEST	AL,20H		;IS IT EMPTY?
;	JE	DONT_START	;NOPE, TRY NEXT TIME
	CALL	GET_OUT		;CHECK TO SEE IF THERE IS A CHAR IN THE BUF
	CMP	AH,0		;WELL, WAS THERE?
	JNE	DONT_START	;NO, BUFFER IS EMPTY
	MOV	DX,PORT[SI]	;YES, POINT DX AT THE TX OUT REGISTER
	OUT	DX,AL		;SEND HIM THE CHARACTER
	OR	LINE[SI],LINEXP		;WARN EVERYBODY THAT I'M BUSY.
DONT_START:
	POPF
	RET
START_OUTPUT	ENDP

;		THE FOLLOWING LABEL DEFINES THE END OF THE DRIVER, SO I
;		CAN TELL DOS HOW BIG I AM.
ASYNC_END:
SUBTTL	INITIALIZATION REQUEST ROUTINE
PAGE
;
;		THE INITIALIZE DRIVER ROUTINE IS STORED AFTER THE "END"
;	OF THE DRIVER HERE SO THAT THIS CODE CAN BE THROWN AWAY AFTER
;	THE DEVICE HAS BEEN INITIALIZED.  THIS CODE IS ONLY CALLED TWICE:
;	ONCE TO INITIALIZE EACH OF THE ASYNC UNITS THAT THIS DRIVER
;	CONTAINS.  (HOPEFULLY, MSDOS DOESN'T WRITE ANYTHING ON TOP OF
;	THIS CODE UNIT BOTH UNITS ARE INITIALIZED.
;		THE CONTEXT OF THE INITIALIZE CODE BELOW IS THE SAME AS
;	ALL THE OTHER REQUEST ROUTINES EARLIER IN THE DRIVER.
;
;		INITIALIZE THE DRIVER AND DEVICE
;
ASYNC_INIT:
	MOV	AX,OFFSET ASYNC_END	;GET THE SIZE OF THE DRIVER
	MOV	ES:XFER[BX],AX		;SEND THAT BACK IN PACKET
	MOV	ES:XSEG[BX],CS		;SEND THE CODE SEGMENT ALSO.
				;I HAVE SATISFIED ALL THE REQIREMENTS OF THE
				;INIT FUNCTION TO RETURN IN THE I/O PACKET, SO
				;I CAN DESTROY THE CONTENTS OF ES:BX AND USE
				;THEM FOR OTHER THINGS.
	MOV	AX,0			;POINT ES AT THE VECTOR SEGMENT
	MOV	ES,AX			;SO CAN INITIALIZE THE VECTORS
	MOV	AX,ISRADR[SI]		;GET ADRS OF INTERUPT SERVICE ROUTINE
				;THE FOLLOWING CODE IS SPECIFIC TO THE IBM:
				;BASIC USES LOCATIONS 400 AND 402 TO FIND
				;THE PORT ADDRESSES OF COM1 AND COM2. IF I
				;ZERO THESE, THEN BASIC CANNOT MUCK UP THE
				;REGESTERS ON ME ANY MORE!  (IT STILL
				;DISABLES INTERUPTS, THOUGH.  BUMMER!)
	MOV	DI,400			;POINT AT THE ASYNC ADDRESS LIST
	CLD
	STOS	WORD PTR [DI]		;CLOBBER THE FIRST ONE,
	STOS	WORD PTR [DI]		;AND THE SECOND ONE.
				;NOW WE'RE BACK ON THE GENERIC MSDOS TRACK.
	MOV	DI,VECT[SI]		;GET ADRS OF VECOTR,
	STOS	WORD PTR [DI]		;STORE THE OFFSET THERE, THEN
	MOV	ES:[DI],CS		;THE SEGMENT IN THE FOLLOWING WORD.
	MOV	CX,DI			;GET THE VECTOR NUMBER,
	SUB	CL,022H			;SUBTRACT BIAS TO HARDWARE INTS,
	SAR	CL,1			;DIVIDE BY 4 TO CONVERT TO
	SAR	CL,1			;HARDWARE INTERUPT NUMBER.
	MOV	AH,1			;SHIFT A MASK BY THAT MUCH TO
	SAL	AH,CL			;CREATE INTERUPT ENABLE MASK BIT,
	NOT	AH			;WHICH IS ACTIVE LOW...
	IN	AL,021H			;GET SYSTEM HARDWARE INTERUPT MASK
	AND	AL,AH			;AND MY BIT OUT OF IT,
	OUT	021H,AL			;WRITE IT BACK OUT AGAIN.
	MOV	DX,PORT[SI]		;GET THE PORT ADDRESS OF THIS LINE
	ADD	DX,INTID
INT_CAN:
	IN	AL,DX			;GET INTERUPT ID REGESTER
	TEST	AL,1			;IF HE HAS ANYTHING TO COMPLAINE
	JNZ	INT_NONE		;ABOUT, READ THEM ALL AND IGNORE.
	ADD	DX,LSTAT-INTID		;JUST TO MAKE UART HAPPY, READ THE
	IN	AL,DX			;LINE STATUS AND
	ADD	DX,MSTAT-LSTAT		;THE MODEM STATUS TO
	IN	AL,DX			;"RESET INTERUPT CONTROL"
	ADD	DX,RXBUF-MSTAT		;READING THE RECEIVER MIGHT
	IN	AL,DX			;HELP ALSO.
	ADD	DX,INTID-RXBUF		;POINT BACK AT INTERUPT ID,
	JMP	INT_CAN			;AND DO THIS AGAIN.
INT_NONE:
	ADD	DX,LSTAT-INTID		;CALC ADDRESS OF LINE STATUS,
	IN	AL,DX			;INPUT IT OR SOMETIMES IT DOESN'T WORK

	ADD	DX,LCTRL-LSTAT		;CALC ADDRESS OF LINE CONTROL REG
	MOV	AL,LINE[SI]		;GET THE DEFAULT LINE 
	OR	AL,DLAB			;SET THE DIVISOR LATCH BIT
	OUT	DX,AL			;SET UP DEFAULT LINE CHARS
	SUB	DX,LCTRL		;POINT BACK AT FIRST PORT
	MOV	AX,BAUD[SI]		;GET DIVISOR VALUE FOR BAUD RATE
	OUT	DX,AL			;SEND LOW BYTE
	INC	DX			;INC TO HIGH BYTE PORT
	MOV	AL,AH			;GET HIGH BYTE,
	OUT	DX,AL			;AND SEND TO BOARD.
	ADD	DX,LCTRL-1		;POINT AT LINE CONTROL AGAIN,
	MOV	AL,LINE[SI]		;GET DEFAULT LINE CONTROL BITS AGAIN
	OUT	DX,AL			;SET THEM WITHOUT DLAB ON.
	MOV	LINE[SI],0		;RE-USE LINE OFFSET AS STATUS
	SUB	DX,LCTRL-INTEN		;POINT DX AT INTERUPT ENABLE PORT
	MOV	AL,ALLINT		;SET UP TO GET ALL POSSIBLE INTS
	OUT	DX,AL			;IN THE INTEN REGESTER.
	ADD	DX,MCTRL-INTEN		;POINT AT MODEM STATUS REGESTER
	MOV	AL,MODEM[SI]		;GET THE DEFAULT MODEM STATUS BITS,
	OUT	DX,AL			;AND SET THEM IN MCTRL.
	MOV	MODEM[SI],0		;RE-USE THIS BYTE FOR INPUT STATUS.
	TEST	INSPEC[SI],INXON	;IS INPUT THROTTLED WITH XON?
	JE	DONE_INIT		;NO, LEAVE HIM BE.
	MOV	AL,'Q' AND 01FH		;YES, SEND A CONTROL-Q
	MOV	DX,PORT[SI]		;TO THE DEVICE AT INIT TIME,
	OUT	DX,AL			;TO MAKE SURE IT WAKES UP.
DONE_INIT:
	MOV	AX,0			;RETURN NO ERRORS.
	JMP	EXIT

DRIVER	ENDS
	END