Asynchronous Framing Technique Software: ASYNC.ASM


G.BEATTIE (mtunx!whuts!homxb!hou2d!n2dsy@rutgers.edu)
7 Apr 88 19:42:12 GMT


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



This archive was generated by hypermail 2.0b3 on Thu Mar 09 2000 - 14:41:55 GMT