page 58,132
;	file: MIDI.ASM
TITLE	Z8470 MIDI driver for MSDOS
SUBTTL	DESCRIPTION
;THINGS TO ADD: (DELETE THESE LINES AS YOU IMPLIMENT THEM)
;data errors reported at the time you get to that byte in read buffer
;
;
;	Loadable midi device driver for msdos.
;	Written by: Mike Higgins
;	Copyright (c) April 1984 by The Computer Entomologist.
;
;************************************************************************
;	This file implements an interrupt driven character device driver
;for the midi board described in the article: "A MIDI Project", by Jay
;Kupicky, BYTE Magazine, June 1986, V.11 N.6.  The driver will work on any
;similar board that uses the Z8470 Z80 DART chip.  This code should work
;with several DART chips on one board, if you change the appropriate
;counters, add structures and buffers for each UART, and check the interrupt
;and initialization code.
;	Why write or use such a driver?  Because the code written by
;Jay Kubicky could only be compiled on one specific C compiler, and
;required non-standard communication channels between the main code and
;the assembly driver.
;	This driver implements the MIDI board as a standard MSDOS character
;device that can be called from any program written in any language.  Even
;interpretative BASIC can open this device and perform any function that
;the board is capable of.  All the communication with the driver is
;done with standard reads,writes, and IOCTL calls.  The character input/
;output is the MIDI data, and the IOCTL data allows a program to find the
;state of the MIDI UARTS, and to perform initialization functions. (Test
;for data available or error status, flush buffers, change echo/thru
;modes).   Additionally, there is a read-with-timeout feature that
;allows the driver to return to you if no data arrives after a specified
;number of milliseconds.
;	This driver implements a 128 byte buffer on input and output to
;make catching all the MIDI data as easy as possible for you programs.
;The output buffer allows your programs to send multiple byte commands
;without having to wait on each byte: your processing can continue while
;the driver sends the bytes out one at a time for you.
;	There is also a "MIDI out/through" option in this driver that
;will copy, if enabled, any incoming bytes immediately to the associated
;MIDI out port.  I call this a "poor man's MIDI merge" because it will
;scramble MIDI commands together if they arrive at the output buffer
;too close together.  But it does have some limited applications.  Note
;that a very intelligent MIDI merge program would be easy to write as an
;application on top of this board and driver.  (Requires both UARTs hooked
;up with MIDI drivers and receivers, like mine is).
;	While initializing the uarts, this driver also initializes the on
;board clock chip.  You can get direct access to this clock for timing midi
;events, and the driver uses this clock to count time for the read-with-
;timeout feature.  The delta times are in "digital" milliseconds, so the
;value changes 1024 times each second. The two byte clock value from timer2
;will allow you to time anything that is 64 seconds or less, which should be
;long enough for any typical MIDI use. (You will have to store delta times,
;not absolute time).  On my version of the board, timer2 overflows into
;timer3, so this can be considered a 16bit extension of the regular timer.
;This results in a four byte clock that only overflows after 136
;years.  If you can keep your PC running continuously that long (without
;re-booting) I'll be very impressed!
;
;************************************************************************
;
;	Permission is hereby 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 responsibility for
;any damage this software may do to his system.
;
;	Nasty notices aside, if you have any questions about this software,
;you can reach me at the address below.  If you implement 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
;
;
;		ASSEMBLY INSTRUCTIONS:
;	MASM MIDI,MIDI,MIDI,NUL
;	LINK MIDI,MIDI,MIDI,NUL
;	EXE2BIN MIDI
;	COPY MIDI.BIN A:    (IF NOT THERE ALREADY)
;		ADD THE FOLLOWING LINE TO A:CONFIG.SYS:
;	DRIVER=MIDI.BIN
;		RE-BOOT YOUR SYSTEM AND IT'S THERE!
;
;	NOTE: 
;
;	There are  hooks in the driver for features that do not work
;	yet.  So don't expect all of these things to work just
;	because there are comments about them or bits to set for them.
;	Write me if you do implement any of this stuff.
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 PORT OFFSETS AND IMPORTANT BOARD CONSTANTS
MIDDAT	EQU	0	;DATA REGESTER
MIDCON	EQU	2	;CONTROL REGESTER
MIDB	EQU	1	;BITS TO FORCE IO ADDRESS TO UART B WITH OR
MIDA	EQU	NOT MIDB	;MASK TO FORCE IO ADDR TO UART A WITH AND
;
;		BIT ASSIGNMENTS FOR THE STATUS WORD IN EACH UNIT STRUCTURE
OUTINT	EQU	1	;CHARACTER SENT OUT, INTERUPT SHOULD BE COMMING
OUTHRU	EQU	2	;INPUT IS ECHOED THRU OUTPUT (POOR MANS MIDI OUT/THRU
;
;		BIT ASSIGNMENTS FOR THE ERVAL WORD IN EACH UNIT STRUCTURE
ERITMO	EQU	1	;INPUT TIMEOUT ERROR
EROTMO	EQU	2	;OUTPUT TIMEOUT ERROR
ERIBFO	EQU	4	;INPUT BUFFER OVERFLOW
EROBFO	EQU	8	;OUPUT BUFFER OVERFLOW
ERPARE	EQU	10H	;PARITY ERROR (NOT ENABLED)
ERFRAM	EQU	20H	;FRAMING ERROR
ERRXOV	EQU	40H	;RECEVER OVERRUN (NOT LIKELY)
;
;BIT DEFINITIONS FOR ALL THE BITS/REGESTERS IN THE Z8470 ZILOG Z80 DART CHIP
;
	;WRITE REGESTER 0 BITS
ZRESET	EQU	010H	;RESET EXT/STATUS INTERUPTS
ZCHANR	EQU	018H	;CHANNEL RESET
ZRXINT	EQU	020H	;ENABLE INT ON NEXT Rx CHARACTER
ZTXINT	EQU	028H	;RESET Tx INT PENDING
ZERRES	EQU	030H	;ERROR RESET
ZREINT	EQU	038H	;RETURN FROM INT (CHANNEL A ONLY)
	;WRITE REGESTER 1 BITS
W1EXTI	EQU	001H	;EXT INT ENABLE
W1TXINT	EQU	002H	;Tx INT ENABLE
W1STAT	EQU	004H	;STATUS EFFECTS VECTOR (CHANNEL B ONLY)
W1RXDIS	EQU	000H	;Rx INT DISABLE
W1RX1ST	EQU	008H	;Rx INT ON FIRST CHARACTER
W1RXALP	EQU	010H	;Rx INT ON ALL CHARACTERS (PARITY AFFECTS VECTOR)
W1RXALL	EQU	018H	;Rx INT ON ALL CHARACTERS (PARITY DOESN'T AFF. VEC)
W1WAITR	EQU	020H	;WAIT/READY ON R/T
W1WAITF	EQU	040H	;WAIT/READY FUNCTION
W1WAITE	EQU	080H	;WAIT/READY ENABLE
	;WRITE REGESTER 3 BITS
W3RXEN	EQU	001H	;Rx ENABLE
W3AUTO	EQU	020H	;AUTO ENABLES
W3RX5	EQU	000H	;Rx 5 BITS/CHAR
W3RX7	EQU	040H	;Rx 7 BITS/CHAR
W3RX6	EQU	080H	;Rx 6 BITS/CHAR
W3RX8	EQU	0C0H	;Rx 8 BITS/CHAR
	;WRITE REGESTER 4 BITS
W4PARE	EQU	001H	;PARITY ENABLE
W4PEVN	EQU	002H	;PARITY IS EVEN
W4PODD	EQU	000H	;PARITY IS ODD
W4STOP1	EQU	004H	;ONE STOP BITS
W4STP15	EQU	008H	;1.5 STOP BITS
W4STOP2	EQU	00CH	;TWO STOP BITS
W4X1	EQU	000H	;X1 CLOCK MODE
W4X16	EQU	040H	;X16 CLOCK MODE
W4X32	EQU	080H	;X32 CLOCK MODE
W4X64	EQU	0C0H	;X64 CLOCK MODE
	;WRITE REGESTER 5 BITS
W5RTS	EQU	002H	;SET RTS BIT IN OUTPUT
W5TXEN	EQU	008H	;Tx ENABLE
W5BREAK	EQU	010H	;SEND BREAK
W5TX5	EQU	000H	;Tx 5 BITS/CHARACTER
W5TX7	EQU	020H	;Tx 7 BITS/CHAR
W5TX6	EQU	040H	;Tx 6 BITS/CHAR
W5TX8	EQU	060H	;Tx 8 BITS/CHAR
W5DTR	EQU	080H	;SET DTR BIT IN OUTPUT
	;READ REGESTER 0 BITS
RXCHAR	EQU	001H	;CHARACTER HAS BEEN RECEIVED
INTPEND	EQU	002H	;INTERUPT IS PENDING (CHANNEL A ONLY)
TXFREE	EQU	004H	;Tx BUFFER IS EMPTY
CD	EQU	008H	;CARRIER DETECT LINE IS ON
RI	EQU	010H	;RING INDICATOR LINE IS ON
CTS	EQU	020H	;CLEAR TO SEND LINE IS ON
BREAK	EQU	080H	;BREAK HAS BEEN DETECTED
	;READ REGESTER 1 BITS
R1ALL	EQU	001H	;ALL SENT (Tx BUFFER?)
R1PERR	EQU	010H	;PARRITY ERROR
R1RXOVR	EQU	020H	;Rx OFERRUN ERROR
R1FRAME	EQU	040H	;FRAMING ERROR
	;READ REGESTER 2 BITS
VECMASK	EQU	006H	;REASON-FOR-INTERUPT BITS
VECB	EQU	008H	;UART B FIRED INTERUPT
;
	;REGESTER OFFSETS AND BIT ASSIGNMENTS FOR THE CLOCK TIMER CHIP
TIMER1	EQU	-3	;OFFSET FROM CONTROL REGESTER TO TIMER DATA 1
TIMER2	EQU	-2	;TIMER 2 DATA REGESTER
TIMER3	EQU	-1	;TIMER 3
T1	EQU	0	;CONTROLLER TIMER 1 SELECT VALUE
T2	EQU	040H	;SELECT TIMER 2
T3	EQU	080H	;SELECT TIMER 3
LMSB	EQU	030H	;READ TIMER REGESTERS IN LITTLE ENDIAN MODE
SHOOT	EQU	002H	;ONE SHOT MODE
RATE	EQU	004H	;RATE GENERATOR MODE
SQUARE	EQU	006H	;SQUARE WAVE MODE
LATCH	EQU	0	;LATCH THE CURRENT VALUES FOR READING
;
SUBTTL	DRIVER LIST HEAD
PAGE
;*************************************************************************
;
;	BEGENING OF DRIVER CODE.
;
DRIVER	SEGMENT
	ASSUME	CS:DRIVER,DS:DRIVER,ES:DRIVER
;	ORG	0	;DRIVERS START AT 0
MIDIB:
	DW	MIDIA,-1	;POINTER TO NEXT DEVICE: DOS FILLS IN SEG.
	DW	DEVCHR OR DEVIOC	;CHARACTER DEVICE, IOCTL ALLOWED
	DW	STRATEGY		;OFFSET TO STRATEGY ROUTINE.
	DW	REQUESTB		;OFFSET TO "INTERUPT" ENTRYPOINT.
	DB	"MIDIB   "		;DEVICE NAME.
MIDIA:
	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	REQUESTA		;I/O REQUEST ROUTINT
	DB	"MIDIA   "

debug	dd	0b0000000h
notify	macro	char
;        local   store
;	push	es
;	push	di
;	push	ax
;	mov	al,'&char'
;	les	di,CS:debug
;	stos	byte ptr [di]
;	inc	di
;        cmp     di,80*25*2
;        jl      store
;        xor     di,di
;store:
;        mov	word ptr CS:debug,di
;        mov     al,'<'
;        stos    byte ptr es:[di]
;	pop	ax
;	pop	di
;	pop	es
endm

SUBTTL DRIVER INTERNAL DATA STRUCTURES
PAGE
;
MIDI_UNITS	EQU	2	;NUMBER OF UNITS THIS DRIVER IS BUILT FOR
MIDI_VEC	DW	028H	;INTERUPT VECTOR ADDRESS (NOT VECTOR NUMBER)
CLOCK_PORT	DW	0FFA7H	;PORT OF CLOCK CONTROL REGESTER
;
UNIT	STRUC	;EACH UNIT HAS A STRUCTURE DEFINING IT'S STATE:
PORT	DW	?	;I/O PORT ADDRESS
STATUS	DW	?	;STATUS BITS
TIMEOUT	DW	?	;TIMEOUT CONSTANT ON READ OR WRITE REQUEST
ERVAL	DW	?	;BIT ENCODED ERRORS SINCE LAST YOU LOOKED
EROFF	DW	?	;OFFSET TO FIRST CHARACTER AFTER ERROR
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 MIDI UNIT
;	THE STRUCTURES FOR ALL THE UARTS MUST BE ORGANIZED TOGETHER INTO
;	A CONTIGUOUS TABLE, BECAUSE THE INTERUPT SERVICE ROUTINE SCANS
;	THROUGH THEM WHILE DETERMINING WHICH DART FIRED THE INTERUPT.
;	SIMILARLY, THE STRUCTURES FOR THE TWO UARTS IN EACH DART MUST BI
;	IN A-B ORDER IN THIS TABLE.
;
MIDI_TABA:
	UNIT	<0FFA0H,0,-1,0,-1,0,0,INABUF,0,0,OUTABUF>
UNIT_SIZE	EQU	$-MIDI_TABA
MIDI_TABB:
	UNIT	<0FFA1H,0,-1,0,-1,0,0,INBBUF,0,0,OUTBBUF>

		;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
INABUF	DB	BUFSIZ DUP (?)
INBBUF	DB	BUFSIZ DUP (?)
OUTABUF	DB	BUFSIZ DUP (?)
OUTBBUF	DB	BUFSIZ DUP (?)
;
;	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.
	PUBLIC	STRATEGY
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

;		I/O REQUEST ROUTINES
	PUBLIC	REQUESTA
REQUESTA:		;MIDIA HAS BEEN REQUESTED
	PUSH	SI	;SAVE SI SO YOU CAN
	MOV	SI,OFFSET MIDI_TABA	;GET THE DEVICE UNIT TABLE ADDRESS.
	JMP	GEN_REQUEST	;THE GENERIC DRIVER DOES THE REST.
REQUESTB:		;MIDIB HAS BEEN REQUESTED TO DO SOMETHING
	PUSH	SI	;SAVE SI
	MOV	SI,OFFSET MIDI_TABB	;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	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,
	MOV	DI,AX		;AND ADD TO THE TABLE START ADDRESS
	MOV	AX,STSERR OR ERRUC	;SEND UNKNOWN COMMAND ERROR FOR EXIT
	JMP	MIDI_FUNCS[DI]		;JUMP TO THE APPROPRIATE ROUTINE
;
;		TABLE OF OFFSETS TO ALL THE DRIVER FUNCTIONS
;
MIDI_FUNCS DW	MIDI_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	EXIT		;NON-DESTRUCTIVE READ
	DW	EXIT		;INPUT STATUS
	DW	EXIT		;FLUSH INPUT BUFFER
	DW	WRITE		;WRITE
	DW	WRITE		;WRITE WITH VERIFY
	DW	EXIT		;OUTPUT STATUS
	DW	EXIT		;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:
	CALL	GET_CLOCK		;GET CLOCK CURRENT VALUE FOR READ-
	MOV	DX,AX			;WITH-TIMEOUT TEST, AND STORE IT IN DX
	MOV	CX,ES:COUNT[BX]		;GET THE REQUESTED NUMBER OF BYTES
	LES	DI,DWORD PTR ES:XFER[BX] ;DI IS OFFSET TO USER BUFFER
	MOV	BX,TIMEOUT[SI]		;GET THE TIMEOUT LIMIT.
RLUP:
	MOV	AX,EROFF[SI]		;CHECK TO SEE IF IT'S TIME
	CMP	AX,IFIRST[SI]		;TO REPORT AN ERROR
	JE	ERR_READ
	CALL	GET_IN			;GET NEXT CHAR FROM INPUT BUFFER
	CMP	AH,0			;WAS THERE ONE?
	JNE	READ_WAIT		;NO, TEST FOR TIMEOUT
	STOS	BYTE PTR[DI]		;YES,WRITE THIS BYTE OUT
		;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	AX,STSDNE		;RETURN NO ERRORS IN AX IF DONE.
	JMP	EXIT
ERR_READ:			;NOW IS THE TIME TO REPORT AN INPUT ERROR
	MOV	AX,-1			;DISABLE THE ERROR CHECKING BY
	MOV	EROFF[SI],AX		;SETTING OFFSET TO FFFF
	OR	ERVAL[SI],ERIBFO	;SET INPUT BUFFER OVERFLOW BIT
	JMP	ERR_RET
READ_WAIT:
	CMP	BX,0FFFFH		;IF THE TIMEOUT VALUE IS MAX INT,
	JE	RLUP			;LOOP BACK UP FOREVER
	CALL	GET_CLOCK
	SUB	AX,DX			;CALCULATE THE DELTA TIME
	NEG	AX
	CMP	AX,BX			;COMPARE AGAINST THE TIMOUT LIMIT
	JB	RLUP			;LOOP BACK IF TIME IS STILL OK
	OR	ERVAL[SI],ERITMO	;TELL THEM A TIMEOUT ERROR HAPPENED
ERR_RET:
	LES	BX,PACKHEAD		;GET IO PACKET BACK
	SUB	ES:COUNT[BX],CX		;TELL HIM HOW MANY BYTES MADE IT, AND
	MOV	AX,STSBSY		;RETURN A READ
	JMP	EXIT			;FAULT ERROR.
;
SUBTTL	WRITE REQUEST ROUTINE
PAGE
;		OUTPUT DATA TO DEVICE
;
WRITE:
	CALL	GET_CLOCK		;GET CLOCK VALUE FOR TIMEOUT TEST
	MOV	DX,AX			;DX WILL CONTAIN STARTING TIME
	MOV	CX,ES:COUNT[BX]		;GET BYTE COUNT,
	LES	DI,DWORD PTR ES:XFER[BX] ;GET XFER ADDRESS 
	MOV	BX,TIMEOUT[SI]		;GET TIMEOUT LIMIT
WLUP:
	MOV	AL,ES:[DI]	;GET THE NEXT CHAR DOS SENT YOU
	CALL	PUT_OUT		;ATTEMPT TO PUT IN IN OUTPUT BUFFER
	CMP	AH,0		;DID IT WORK?
	JNE	WWAIT		;NO, GO TEST FOR TIMEOUT
	INC	DI		;SKIP TO NEXT BYTE
	CALL	START_OUTPUT		;START THE XMITTER IF NECC.
	LOOP	WLUP			;YES, GO GET NEXT CHAR.
	MOV	AX,STSDNE		;RETURN SUCCESS
	JMP	EXIT
WWAIT:
	CALL	GET_CLOCK	;GET CURRENT CLOCK TIME
	SUB	AX,DX		;CALCULATE NUMBER OF MILISECONDS SINCE START
	NEG	AX
	CMP	AX,BX		;HAVE WE WAITED LONG ENUF?
	JB	WLUP		;NO, KEEP TRYING
	OR	ERVAL[SI],EROTMO	;TELL THEM OUTPUT TIMED OUT
	LES	BX,PACKHEAD		;GET IO PACKET BACK
	SUB	ES:COUNT[BX],CX		;TELL HIM HOW MANY BYTES MADE IT, AND
	MOV	AX,STSBSY		;YES, RETURN A WRITE FAULT ERROR
	JMP	EXIT
;
SUBTTL	I/O CONTROL READ REQUEST
PAGE
;
;		IOCONTROL READ REQUEST, RETURN INTERNAL STRUCTURE
;
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,UNIT_SIZE		;ONLY WORKS WHEN YOU GIVE ME A
	JE	DOIOCIN			;RIGHT SIZED BUFFER TO STOMP ON.
	MOV	AX,STSERR OR ERRBSL	;RETURN AN ERROR IF NOT RIGHT.
	JMP	EXIT
DOIOCIN:
	REP MOVSB		;JUST COPY THE STRUCTURE TO CALLER
	SUB	SI,UNIT_SIZE		;POINT BACK AT UNIT STRUCTURE
	MOV	AX,0			;SO YOU CAN ZERO THE
	MOV	ERVAL[SI],AX		;ERROR WORD EVERY TIME THEY READ IT
	MOV	AX,STSDNE		;RETURN NO ERRORS
	JMP	EXIT
;
SUBTTL	I/O CONTROL WRITE REQUEST ROUTINE
PAGE
;
;		I USE THIS COMMAND TO COMMUNICATE VARIOUS SPECIAL COMMANDS
;	TO THE DRIVER.  EACH BYTE SENT IS A SEPARATE COMMAND TO FLUSH A
;	BUFFER, CLEAR STATUS, SET SPECIAL FLAGS, ETC.
;
IOCTLOUT:
	MOV	CX,ES:COUNT[BX]		;GET THE BYTE COUNT
	LES	DI,DWORD PTR ES:XFER[BX] ;AND THE ADDRESS
IOWRITE:		;LOOP FOR ALL IO CONTROL WRITE BYTES
	MOV	BL,ES:[DI]		;GET NEXT BYTE
	INC	DI			;SKIP TO FOLLOWING ONE
	AND	BX,7			;CHOP OUT LOWER 3 BITS
	SHL	BX,1			;CONVERT TO WORD INDEX
	CMP	BL,IOWSIZE		;IS THIS A LEGAL REQUEST?
	JG	IOWERR			;NO RETURN A WRITE ERROR
	JMP	WORD PTR CS:IOTAB[BX]	;YES, EXECUTE APPROPRIATE CODE
IOTAB	DW	IOWFLI		;FLUSH INPUT BUFFER
	DW	IOWFLO		;FLUSH OUTPUT BUFFER
	DW	IOWTIM		;SET IO TIMEOUT LIMIT
	DW	IOWSEO		;SET BYTEWISE MIDI OUTPUT/THRU FLAG
	DW	IOWCLO		;CLEAR POOR MANS OUTPUT/THRU MERGE FLAG
IOWSIZE	EQU	$-IOTAB

IOWFLI:				;FLUSH INPUT BUFFER
        ;PUSHF
	CLI			;CLEAR INTERUPTS WHILE YOU
	MOV	AX,IFIRST[SI]	;MANIPULATE THE BUFFER POINTERS
	MOV	IAVAIL[SI],AX	;SET AVAIL EQUAL FIRST TO EMPTY
        STI
	;POPF			;ALL DONE AND CLEAR
	MOV	AX,0		;EVERY TIME YOU FLUSH INPUT BUFFER,
	MOV	ERVAL[SI],AX	;YOU CAN CLEAR THE ERROR BITS
	DEC	AX		;AND RESET THE
	MOV	EROFF[SI],AX	;OFFSET TO ERROR TO AN ILEGAL VALUE
	JMP	IOWNEXT
IOWFLO:				;FLUSH OUTPUT BUFFER
        ;PUSHF
	CLI			;CLEAR INTERUPTS WHILE YOU
	MOV	AX,OFIRST[SI]	;MANIPULATE THE BUFFER POINTERS
	MOV	OAVAIL[SI],AX	;SET AVAIL EQUAL FIRST TO EMPTY
        STI
       ;POPF			;ALL DONE AND CLEAR
	JMP	IOWNEXT
IOWTIM:				;LOAD TIMEOUT LIMIT
	MOV	AX,ES:[DI]	;GET NEXT WORD FROM INPUT
	ADD	DI,2		;SKIP WORD IN COMMAND STRING
	SUB	CX,2		;COUNT THOSE BYTES OUT OF LOOP
	JLE	IOWERR		;IF THERE WEREN'T ENUF, SCREAM
	MOV	TIMEOUT[SI],AX	;STORE THEM IN STRUCTURE
	JMP	IOWNEXT
IOWSEO:				;SET BYTEWISE MIDI OUTPUT/THRU FLAG
	OR	STATUS[SI],OUTHRU	;SET THE OUT/THRU BIT IN STATUS
	JMP	IOWNEXT
IOWCLO:				;CLEAR POOR MANS MIDI OPUTPUT/THRU MERGE
	AND	STATUS[SI],NOT OUTHRU	;CLEAR THE BIT
	JMP	IOWNEXT
IOWNEXT:
	LOOP	IOWRITE		;LOOP FOR ALL IO CONTROL WRITE BYTES
	MOV	AX,STSDNE	;RETURN 0 FOR SUCCESS
	JMP	EXIT
IOWERR:			;I/O CONTROL WRITE ERROR RETURN
	MOV	AX,STSERR OR ERRBSL	;RETURN WRITE FAULT FOR THESE
	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
	PUSH	CX
	PUSH	DI
	;PUSHF
	CLI			;DISABLE INTERUPTS WHILE I HAVE OAVAIL
	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:
        STI
	;POPF		;RE-ENABLE INTERUPTS
	POP	DI
	POP	CX
	RET
PUT_OUT	ENDP

GET_OUT	PROC	NEAR	;GETS THE NEXT CHARACTER FROM OUTPUT RING BUFFER
			;SURE YOU DISABLE INTERUPTS FIRST.
	PUSH	CX
	PUSH	DI
	;PUSHF			;JUST IN CASE, DISABLE INTERUPTS
	CLI			;WHILE IN THIS ROUTINE.
	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:
	;POPF
        STI
	POP	DI
	POP	CX
	RET
GET_OUT	ENDP

PUT_IN	PROC	NEAR	;PUT THE CHAR FROM AL INTO INPUT RING BUFFER
	PUSH	CX
	PUSH	DI
	;PUSHF			;DISABLE INTS WHILE IN THIS ROUTINE
	CLI
	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:
	;POPF
        STI
	POP	DI
	POP	CX
	RET
PUT_IN	ENDP

GET_IN	PROC	NEAR	;GETS ONE CARACTER FROM INPUT RING BUFFER INTO AL
	PUSH	CX
	PUSH	DI
	;PUSHF
	CLI		;DISABLE INTERUPTS WHILE I LOOK AT IFIRST.
	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:
        STI
	;POPF		;RE-ENABLE INTERUPTS BEFORE YOU RETURN
	POP	DI
	POP	CX
	RET
GET_IN	ENDP
SUBTTL	INTERUPT SERVICE ROUTINES
PAGE
;
;		 THESE ROUTINES ARE ONLY CALLED WHEN AN INTERUPT IS GENERATED
;	BY THE UART.
;	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.

MIDI_INT:
        PUSH    AX              ;SAVE AX FOR THE USER,
	MOV	AL,020H		;OUTPUT A 20H TO THE UNDOCUMENTED INTERUPT
	OUT	020H,AL		;CONTROL CHIP.
        STI                     ;AND LET THE PROCESSOR INTERUPT ME.
	PUSH	BX              ;SAVE THE REST OF THE REGESTERS.
	PUSH	CX
	PUSH	DX
	PUSH	SI
	PUSH	DI
	PUSH	DS	;SAVE THE DATA SEGMENT
	PUSH	CS	;SO YOU CAN LOAD CS
	POP	DS	;INTO DS AND FIND YOUR OWN STRUCTURES.
notify I
;
;	THE FOLLOWING CODE FIGURES OUT WHICH (IF SEVERAL) DAISY-CHAINED
;	DART CHIP FIRED THE INTERUPT, WHICH UART IN THE DART IT WAS,
;	AND THE REASON FOR THE INTERUPT (RECEIVE, XMIT, ERROR).
;
	MOV	CX,MIDI_UNITS	;GET THE TOTAL NUMBER OF UARTS
	MOV	SI,OFFSET MIDI_TABA	;ADDRESS OF FIRST UARTS UNIT TABLE
INT_LUP:			;HEAD OF LOOP TO CHECK ALL UARTS
	MOV	DX,PORT[SI]	;GET THE PORT ADDRESS
	ADD	DX,MIDCON	;SLIDE UP TO COMMAND/STATUS REGESTER
	MOV	AL,0		;MAKE SURE WE ARE TALKING TO REGESTER 0
	OUT	DX,AL
	JMP SHORT $+2
	IN	AL,DX		;READ REGESTER 0 STATUS
	TEST	AL,INTPEND	;IS THIS DART CHIP THE ONE?
	JNE	THIS_DART	;YES, GO ON TO FIND THE PORT
	ADD	SI,UNIT_SIZE*2	;NO, SKIP TWO UARTS TO NEXT WHOLE DART
	SUB	CX,2
	JG	INT_LUP		;IF THERE ARE ANY LEFT, KEEP TRYING
	JMP	INT_EXIT	;IF NOT, YOU'RE IN TROUBLE, BUT I IGNORE
THIS_DART:
	OR	DX,MIDB		;SLIDE UP TO UART B ON THIS DART
	MOV	AL,2		;SET UP TO READ REGISTER 2
	OUT	DX,AL
	JMP	SHORT $+2
	IN	AL,DX		;READ IN VECTOR VALUE
	TEST	AL,VECB		;WAS THIS INTERUPT CAUSED BY UART B?
	JNE	A_INT		;NO, IT MUST BE UART A
	DEC	CX		;YES, MAKE SURE THERE IS A UART B STRUCTURE
	JCXZ	INT_EXIT	;IGNORE INTERUPTS FROM UNUSED UARTS
	ADD	SI,UNIT_SIZE	;GET UART B'S UNIT STRUCTURE
	JMP	INT_REASON	;WE'RE READY TO GO FIND OUT WHY
A_INT:
	AND	DX,MIDA		;CONVERT PORT NUMBER BACK TO UART A
INT_REASON:
	AND	AX,VECMASK	;WHACK OUT ONLY THE BITS THAT MATTER
	MOV	BX,AX		;PUT IT INTO AN INDEX REGESTER
	JMP	INT_FTAB[BX]	;JUMP TO THE APPROPRIATE ROUTINE
;
INT_FTAB DW	INT_TXMIT	;TRANSMITTER HOLDING REGESTER EMPTY
	DW	INT_ERROR	;MODEM LINE INTERUPTS NOT IMPLIMENTED
	DW	INT_RECEIVE	;RECEIVER BUFFER HAS A CHARACTER
	DW	INT_ERROR	;RECEIVER FRAMING, OVER-RUN ERROR

INT_ERROR:			;ENTRYPOINT FOR SETTING BREAKPOINTS
notify E
	NOP			;FOR ILLEGAL VECTOR VALUES
INT_EXIT:
	MOV	DX,PORT[SI]	;GET THE PORT NUMBER AGAIN
	ADD	DX,MIDCON	;CHANGE TO CONTROL PORT
	AND	DX,MIDA		;MAKE SURE IT'S CHANNEL A OF DART
	MOV	AL,ZREINT	;SEND THE SIMULATED Z80 RETURN
	OUT	DX,AL		;FROM INTERUPT COMMAND TO DART
;INTERUPT STUF WAS HERE
        POP	DS	;RECOVER ALL THE REGESTERS
	POP	DI
	POP	SI
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	IRET
;
;		THE FOLLOWING INTERUPT SERVICE ROUTINES ALL HAVE THE
;	SAME CONTEXT:
;	-CS AND DS POINT TO THE DRIVER SEGMENT.
;	-DS:[SI] POINTS TO THE UNIT STRUCTURE FOR THE ASYNC LINE THAT
;		FIRED THE INTERUPT.
;	-DX POINTS TO THE CONTROL REGESTER OF THE DART
;	-AX BX CX 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
PAGE
INT_RECEIVE:	;THE PORT HAS RECEIVED A NEW CHARACTER, I MUST COPY IT
		;INTO THE INPUT TYPEAHEAD BUFFER.
notify R
        SUB	DX,MIDCON	;POINT AT THE DATA REGESTER
	IN	AL,DX		;GET THE CHARACTER
	CALL	PUT_IN		;PUT THE CHARACTER IN THE RING BUFFER
	CMP	AH,0		;WAS THERE ROOM?
	JE	REC_OK		;NO, SET FLAGS BEFORE RETURNING
notify O
	OR	ERVAL[SI],ERIBFO ;NO, SET THE OVERFLOW BIT
	CMP	EROFF[SI],-1	;IF YOU HAVN'T INDICATED ANY ERRORS YET
	JNE	REC_OK
	PUSH	AX	
	MOV	AX,IAVAIL[SI]	;THEN INDICATE THE POSITION THAT THE
	MOV	EROFF[SI],AX	;ERROR HAPPENED
	POP	AX
REC_OK:
	TEST	STATUS[SI],OUTHRU	;POOR MANS MIDI MERGE TURNED ON?
	JZ	NEXT_REC		;NOPE, DON'T ECHI THIS CHAR
	CALL	PUT_OUT			;YUP, SEND THE CHARACTER TO OUTPUT
	CALL	START_OUTPUT		;AND START THE TRANSMIT IF NECC.
NEXT_REC:			;CHECK TO SEE IF THERE ARE MORE CHARS READY
	ADD	DX,MIDCON	;POINT BACK AT THE COMMAND REGESTER
	IN	AL,DX		;READ REGESTER 0
	TEST	AL,RXCHAR	;IS THERE ANOTHER CHARACTER READY?
	JNZ	INT_RECEIVE	;YES, GO READ IT IN ALSO
	MOV	AL,ZRXINT	;NO, ENABLE INTERUPTS ON NEXT CHAR
	OUT	DX,AL		;THAT DOES COME IN.
	JMP	INT_EXIT
;
;
SUBTTL	SPECIAL RECEIVE CONDITION INTERUPT
PAGE
;
;		PARITY, FRAMING, OR OVERRUN ERROR INTERUPT
INT_RXSTAT:
notify S
	MOV	AL,1		;REQUEST READ REGESTER 1
	OUT	DX,AL
	JMP SHORT $+2
	IN	AL,DX		;READ IN THE VALUE
	AND	AX,070H		;CHOP OUT THE ERROR BITS
	OR	ERVAL[DI],AX	;ADD THOSE BITS TO THE ERROR WORD
	CMP	EROFF[SI],-1	;CHECK TO SEE IF YOU'VE ALREADY
	JNE	RXSTAT_DONE	;TRAPPED ONE UNREPORTED ERROR
	MOV	AX,IAVAIL[SI]	;IF NOT, THEN POINT THE
	MOV	EROFF[SI],AX	;ERROR OFFSET AT NEXT AVAIL BYTE
RXSTAT_DONE:
	MOV	AL,ZERRES	;RESET THE ERROR BITS IN THE
	OUT	DX,AL		;UART.
	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.
notify T
        AND	STATUS[SI],NOT OUTINT	;CLEAR INTERUPT EXPECTED BIT.
	CALL	START_OUTPUT		;START THE NEXT CHARACTER
	MOV	AL,ZTXINT		;RESET THE UART TRANSMITTER
	OUT	DX,AL			;PENDING BIT
	JMP	INT_EXIT

;ROUTINE TO START THE NEXT CHARACTER PRINTING ON THE PORT, 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.
;	SI MUST POINT AT THE UNIT STRUCTURE.
START_OUTPUT	PROC	NEAR
	;PUSHF				;SAVE THE FLAGS SO I CAN
	CLI				;DISABLE INTERUPTS
	TEST	STATUS[SI],OUTINT	;AM I IN HOLD OUTPUT MODE?
	JNE	DONT_START		;YES, DON'T SEND ANY MORE CHARS.
	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	STATUS[SI],OUTINT		;WARN EVERYBODY THAT I'M BUSY.
DONT_START:
	;POPF
        STI
	RET
START_OUTPUT	ENDP
;;
SUBTTL	READ CLOCK CURRENT VALUE
PAGE
;
;		THE GET_CLOCK ROUTINE RETURNS THE CURRENT CLOCK VALUE
;	IN AX.  REGESTER DX IS PRESERVED.
;	This routine returns the "unsullied" clock value, so beware! The
;	timer chip is a count DOWN timer, and you should calculate delta
;	values accordingly.  The C routines that read this clock invert
;	the current value to make it look like an increasing time count,
;	but down here in the driver I left the values raw.
;
GET_CLOCK	PROC	NEAR
	PUSH	DX
	MOV	DX,CLOCK_PORT	;GET PORT OF CLOCK CHIP
	MOV	AL,T2 OR LATCH	;TELL HIM YOU WANT TO LATCH TIMER 2
	OUT	DX,AL
	JMP SHORT $+2
	ADD	DX,TIMER2	;POINT DX AT TIMER2
	IN	AL,DX		;READ TIMER2 LSB
	JMP SHORT $+2
	MOV	AH,AL
	IN	AL,DX		;READ TIMER2 MSB
	XCHG	AH,AL		;SWAP THE BYTES TO NORMAL
	POP	DX
	RET
GET_CLOCK	ENDP
;
;
;		THE FOLLOWING LABEL DEFINES THE END OF THE DRIVER, SO I
;		CAN TELL DOS HOW BIG I AM.
MIDI_END:
SUBTTL	MIDI INITIALIZATION REQUEST ROUTINE
PAGE
;
;		THE INITIALIZE DRIVER ROUTINES ARE 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 MIDI UNITS THAT THIS DRIVER
;	CONTAINS.  (APPARENTLY, MSDOS DOESN'T WRITE ANYTHING ON TOP OF
;	THIS CODE UNTIL ALL UNITS ARE INITIALIZED.
;		THE CONTEXT OF THE INITIALIZE CODE BELOW IS THE SAME AS
;	ALL THE OTHER REQUEST ROUTINES EARLIER IN THE DRIVER.
;
INIT_TAB	DB	ZCHANR		;TABLE OF INITIALIZATION BYTES
		DB	1,W1TXINT OR W1RX1ST OR W1STAT	;ENABLEL XMITR INTS
		DB	3,W3RXEN OR W3RX8	;ENABLE RECEIVING, 8 BITS/CHARACTER
		DB	4,W4STOP1 OR W4X64	;1 STOP BIT, X64 CLOCK MODE
		DB	5,W5TXEN OR W5TX8	;8 BITS/CHARACTER ON INPUT
		DB	ZRXINT		;ALLOW NEXT CHAR INPUT TO INTERUPT
INIT_SIZE	EQU	$-INIT_TAB	;COUNT THE BYTES FOR ME
;
;	TABLE OF REGESTER OFFSETS AND VALUES FOR INITIALIZING THE CLOCK
CLK_TAB	DB	0,T1 OR LMSB OR RATE	;TIMER ONE IS A RATE GENERATOR
	DB	TIMER1,0,0,8		;THAT DIVIDES CLOCK BY 2048 (800H)
	DB	-TIMER1,T2 OR LMSB OR RATE ;TIMER TWO IS ALSO A RATE GENERATOR
	DB	TIMER2,0FFH,0,0FFH	;THAT DIVIDES BY THE LARGEST INT.
	DB	-TIMER2,T3 OR LMSB OR RATE	;TIMER 3 JUST COUNTS DOWN
	DB	TIMER3,0FFH,0,0FFH	;FROM LARGEST INTEGER (64K)
CLK_SIZE =	$-CLK_TAB		;SIZE OF THIS TABLE
;
;		INITIALIZE THE DRIVER AND DEVICE
;
MIDI_INIT:
	MOV	AX,OFFSET MIDI_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	DX,PORT[SI]		;GET THE PORT ADDRESS OF THIS LINE
	ADD	DX,MIDCON		;SLIDE UP TO THE COMMAND REGESTER
;			THE FOLLOWING CODE INITIALIZES THE Z8470 DART
;			FOR INTERUPT DRIVEN MIDI OPERATION
	MOV	BX,OFFSET INIT_TAB	;GET ADDRESS OF TABLE
	MOV	CX,INIT_SIZE		;SIZE OF TABLE
INIT_PORT:
	MOV	AL,[BX]			;GET NEXT PORT CONTROL BYTE
	OUT	DX,AL			;SEND IT TO CONTROL REGESTER
	JMP SHORT $+2
	INC	BX			;INCRIMENT TO NEXT BYTE
	LOOP	INIT_PORT		;LOOP UNTIL DONE

	TEST	DX,MIDB			;IF THIS IS UART B OF THE DART,
	JNE	DONE_INIT		;THEN YOU ARE DONE
			;ONLY ONCE PER DART IS IT NESESSARY TO INITIALIZE
			;THE VECTOR IN REGESTER 2
	OR	DX,MIDB			;SLIDE UP TO UART B
	MOV	AL,2			;AND THEN INITIALIZE THE
	OUT	DX,AL			;WRITE REGESTER 2 WITH
	JMP SHORT $+2
	MOV	AL,0			;A ZERO VECTOR NUMBER
	OUT	DX,AL
			;IT'S ONLY NECESSARY TO INITIALIZE THE INTERUPT
			;VECTOR AND MASK ONCE, BUT I ALLOW THE FOLLOWING
			;CODE TO EXECUTE ONCE FOR EVERY DART CHIP ON YOUR
			;BOARD.  IT CAN'T HURT.
	MOV	AX,0			;POINT ES AT THE VECTOR SEGMENT
	MOV	ES,AX			;SO CAN INITIALIZE THE VECTORS
	MOV	AX,OFFSET MIDI_INT	;GET ADRS OF INTERUPT SERVICE ROUTINE
	MOV	DI,MIDI_VEC		;GET ADRS OF VECTOR
	STOS	WORD PTR [DI]		;STORE THE OFFSET THERE, THEN
	MOV	ES:[DI],CS		;THE SEGMENT IN THE FOLLOWING WORD.
	MOV	CX,DI			;CALCULATE 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
	JMP SHORT $+2
	AND	AL,AH			;AND MY BIT OUT OF IT,
	OUT	021H,AL			;WRITE IT BACK OUT AGAIN.
;
;		THIS CODE INITIALIZES THE ON-BOARD CLOCK CHIP TO USEFUL
;	MODES AND RATES FOR MIDI USE.  TIMER1 IS USED TO DIVIDE THE CLOCK
;	PULSE DOWN BY 2048 TO A 1024 TICKS PER SECOND (ABOUT 1 MILISECOND)
;	RATE.  THAT RATE FEEDS TIMER2 WHICH IS THE FIRST CLOCK VALUE READ.
;	FOR LONG DELAY PERIODS, TIMER2 IS ASSUMED TO FEED TIMER3, SO IT
;	WILL CONTAIN THE OVERFLOW, ALLOWING COUNTS UP TO 137 YEARS!
;
	MOV	DX,CLOCK_PORT		;GET CLOCK CONTROL REGESTER PORT
	MOV	BX,OFFSET CLK_TAB	;GET ADDRS OF TABLE OF COMMANDS
	MOV	CX,CLK_SIZE/2		;SEND THIS MANY COMMANDS TO CLOCK
;	SAR	CX,1
CLK_LUP:
	MOV	AL,[BX]			;GET NEXT REGESTER OFFSET
	CBW
	ADD	DX,AX			;AND OFFSET FROM IT
	INC	BX			;SLIDE UP TO NEXT VALUE
	MOV	AL,[BX]			;GET IT, AND
	INC	BX			;DON'T FORGET TO SKIP PAST
	OUT	DX,AL			;SEND VALUE TO CLOCK REGESTER
	LOOP	CLK_LUP
;			THE FOLLOWING LOOP IS SUGESTED BY INTEL IN THE
;			CLOCK CHIP SPEC.'S TO MAKE SURE THE CLOCK HAS
;			SETTLED BEFORE YOU FIRST READ IT.  I ONLY WATCH
;			TIMER2 SETTLE, AND ASSUME THE REST ARE LONG GONE
	MOV	DX,CLOCK_PORT		;MAKE SURE YOU'RE LOOKING AT CONTROL
CLK_START:
	MOV	AL,T2 OR LATCH		;LATCH TIMER TWO
	OUT	DX,AL
	JMP SHORT $+2
	ADD	DX,TIMER2		;SLIDE UP TO IT'S DATA REGESTER
	IN	AL,DX			;READ AND
	JMP SHORT $+2
	MOV	AH,AL			;SAVE LEASTMOST BYTE
	IN	AL,DX			;READ MOST SIGNIFICANT BYTE
	SUB	DX,TIMER2		;SLIDE BACK TO CONTROL REGESTER
	XCHG	AL,AH			;MAKE INTO A BINARY NUMBER
	NOT	AX			;REVERSE INTO A UP-COUNTING NUMBER
	OR	AX,AX			;AND CHECK FOR ZERO
	JNZ	CLK_START		;WAIT FOR IT TO SETTLE DOWN
DONE_INIT:
	MOV	AX,STSDNE		;RETURN NO ERRORS.
	JMP	EXIT

DRIVER	ENDS
	END
