		page	55,132
		title	COMM - Device drivers for COM1 and COM2.
		subttl	Parameter definitions:
;
; COMM.ASM - Installable device drivers for COM1 and COM2.
;
; All of this code originated at my dainty fingertips, and I won't be mad
; at anybody who wants to help himself to it. - Peter Pearson.
;
; Version of 20 June 1985.
;
; Revision history:
;	852005 - Send ^S when input buffer approaches fullness, ^Q when it
;		 is nearly empty again.
;
cseg		segment para public 'code'
		org	0
;
; Equates:
;
CTRLS		equ	19
CTRLQ		equ	17
 
primary 	equ	3f8h		;Base address for primary asynch.
secondary	equ	2f8h		;Base address for secondary asynch.
prim_int	equ	0ch		;Hardware interrupt vector number.
sec_int 	equ	0bh		;Hardware interrupt vector number.
;
;
;	Description of the Serial Port:
;
;	I/O	I/O
;	addr	addr
;	----	----
;	3F8	2F8	TX data, RX data, Div Latch LSB.
;	3F9	2F9	Interrupt Enable, Div Latch MSB.
;			    7 6 5 4 3 2 1 0
;			    | | | | | | |  \_ Data Available.
;			    | | | | | |  \___ Xmit Holding Reg Empty.
;			    | | | | |  \_____ Receiver Line Status.
;			    | | | |  \_______ Modem Status.
;			    | | |  \_________ = 0.
;			    | |  \___________ = 0.
;			    |  \_____________ = 0.
;			     \_______________ = 0.
;
;	3FA	2FA	Interrupt Identification.
;			    7 6 5 4 3 2 1 0
;			    | | | | |  \_\_\___________ 001 = no interrupt.
;			    | | | | |			110 = rcvr status.
;			    | | | | |			100 = rcvd data.
;			    | | | | |			010 = THR empty.
;			    | | | | |			000 = Modem status.
;			    | | | |  \_______ = 0.
;			    | | |  \_________ = 0.
;			    | |  \___________ = 0.
;			    |  \_____________ = 0.
;			     \_______________ = 0.
;
;	3FB	2FB	Line Control.
;			    7 6 5 4 3 2 1 0
;			    | | | | | | |  \_ Word Length Select Bit 0.
;			    | | | | | |  \___ Word Length Select Bit 1.
;			    | | | | |  \_____ Number Stop Bits.
;			    | | | |  \_______ Parity Enable.
;			    | | |  \_________ Even Parity Select.
;			    | |  \___________ Stick Parity.
;			    |  \_____________ Set Break.
;			     \_______________ Divisor Latch Access Bit.
;
;	3FC	2FC	Modem Control.
;			    7 6 5 4 3 2 1 0
;			    | | | | | | |  \_ Data Terminal Ready.
;			    | | | | | |  \___ Request to Send.
;			    | | | | |  \_____ Out 1.
;			    | | | |  \_______ Out 2. (= 1 to enable ints.)
;			    | | |  \_________ Loop.
;			    | |  \___________ = 0.
;			    |  \_____________ = 0.
;			     \_______________ = 0.
;
;	3FD	2FD	Line Status.
;			    7 6 5 4 3 2 1 0
;			    | | | | | | |  \_ Data Ready.
;			    | | | | | |  \___ Overrun Error.
;			    | | | | |  \_____ Parity Error.
;			    | | | |  \_______ Framing Error.
;			    | | |  \_________ Break interrupt.
;			    | |  \___________ Transmitter Holding Reg Empty.
;			    |  \_____________ Transmitter Shift Reg Empty.
;			     \_______________ = 0.
;
;	3FE	2FE	Modem Status.
;			    7 6 5 4 3 2 1 0
;			    | | | | | | |  \_ Delta Clear to Send.
;			    | | | | | |  \___ Delta Data Set Ready.
;			    | | | | |  \_____ Trailing Edge Ring Indicator.
;			    | | | |  \_______ Delta Rx Line Signal Detect.
;			    | | |  \_________ Clear to Send.
;			    | |  \___________ Data Set Ready.
;			    |  \_____________ Ring Indicator.
;			     \_______________ Receive Line Signal Detect.
;
;
R_tx		equ	0		;UART data-to-transmit register.
R_rx		equ	0		;UART received-data register.
R_ie		equ	1		;UART interrupt-enable register.
R_ii		equ	2		;UART interrupt-identification register.
R_lc		equ	3		;UART Line-Control register.
R_mc		equ	4		;UART Modem-Control register.
R_ls		equ	5		;UART line-status register.
LC_dlab 	equ	80h		;UART LC reg. DLAB bit.
MC_dtr		equ	1		;UART MC reg. DTR bit.
MC_rts		equ	2		;UART MC reg. RTS bit.
MC_out2 	equ	8		;UART MC reg. "OUT2" bit.
LS_dr		equ	1		;UART LS reg. "data ready" bit.
LS_or		equ	2		;UART LS reg. "overrun error" bit.
LS_pe		equ	4		;UART LS reg. "parity error" bit.
LS_fe		equ	8		;UART LS reg. "framing error" bit.
LS_bi		equ	10h		;UART LS reg. "Break Interrupt" bit.
LS_thre 	equ	20h		;UART LS reg. "Xmit Hold Reg empty" bit.
LS_tsre 	equ	40h		;UART LS reg. "Xmit Shift Reg Empty" bit.
IE_da		equ	1		;UART IE reg. "Data Available" bit.
IE_thre 	equ	2		;UART IE reg. "Xmit Hold Reg Empty" bit.
IE_rls		equ	4		;UART IE reg. "Receive Line Status" bit.
IE_ms		equ	8		;UART IE reg. "Modem Status" bit.
II_never	equ	0f8h		;UART II reg: these bits never on.
 
;		Definitions for the Interrupt Controller Chip:
 
ICC_msk 	equ	21h		;I/O address of ICC's "mask" register.
ICC_ctl 	equ	20h		;I/O address of ICC's "control" reg.
ICC_eoi 	equ	20h		;Non-specific end-of-interrupt.
MSK_d1		equ	10h		;Bit in MSK: disable IRQ4 (COM1).
MSK_d2		equ	08h		;Bit in MSK: disable IRQ3 (COM2).
 
		org	0	;Define layout of Request Header:
RH_rhlen	db	?		;Length of Request Header (bytes).
RH_unit 	db	?		;Unit code. (No meaning for char dev.)
RH_command	db	?		;Command code.
RH_status	dw	?		;Status.
RH_reserved	db	8 dup(?)	;Reserved for DOS.
RH_char 	db	?		;Character of data.
RH_end_addr	label	word		;Ending address for INIT call.
RH_buf_addr	dd	?		;Buffer address for INPUT or OUTPUT.
RH_count	dw	?		;Byte count for INPUT or OUTPUT.
		org	0
 
STAT_err	equ	8000h		;Req Hdr, status reg., "error" bit.
STAT_busy	equ	200h		;Req Hdr, status reg., "busy" bit.
STAT_done	equ	100h		;Req Hdr, status reg., "done" bit.
S_E_unkunit	equ	01h		;Req Hdr, status reg., "unknown unit" error code.
S_E_not_rdy	equ	02h		;Req Hdr, status reg., "not ready" error code.
S_E_unkcom	equ	03h		;Req Hdr, status reg., "unknown command" error code.
S_E_write	equ	0ah		;Req Hdr, status reg., "write fault" error code.
S_E_read	equ	0bh		;Req Hdr, status reg., "read fault" error code.
S_E_general	equ	0ch		;Req Hdr, status reg., "general failure" error code.
		page
;
; Equates relating to data structures:
;
;	Buffers:
;	--------
;
;	When we talk about a "buffer" in this program, we're talking not
;	just about the area of data storage, but about the pointers to that
;	data area as well. Four pointers are expected to precede the first
;	byte of data storage:
;		BEGINNING - Offset of the first byte of data storage.
;		END	- Offset of the first byte AFTER data storage.
;		IN	- Offset at which next byte is to be put into buffer.
;		OUT	- Offset from which next byte is to be taken out
;			  of buffer.
;	IN and OUT may range from BEGINNING to END-1. If IN = OUT, the
;	buffer is empty.
;	   These definitions must be coordinated with the storage-allocation
;	statements for the buffers near the end of this program (just before
;	the INIT handler.)
;
B_beg		equ	0
B_end		equ	2
B_in		equ	4
B_out		equ	6
B_data		equ	8
;
in_buf_len	equ	2000		;Length of input buffers.
out_buf_len	equ	100		;Length of output buffers.
near_full	equ	1200		;Input buffer is nearly full when it
					; has more than this number of data
					; bytes.
near_empty	equ	200		;Input buffer is nearly empty when it
					; has fewer than this number of data
					; bytes.
;
;
;	Unit Control Blocks:
;	--------------------
;
;	(I just made up that name. Any conflict with any other usage is
;	unfortunate.)
;	    A UCB is assigned to each asynchronous communications device
;	(COM) we handle. (E.g., one for COM1, another for COM2.) The
;	UCB contains
;		BASE	- The base I/O address for the hardware device.
;		HOLD	- XON/XOFF flag.
;			  HOLD & 1 nonzero means we have received a CTRLS.
;			  HOLD & 2 nonzero means we have sent a CTRLS.
;		INBUF	- An input buffer (as described above).
;		OUTBUF	- An output buffer (as described above).
;
;	    Take care to keep these definitions compatible with the actual
;	allocation of these data areas near the end of this program (just
;	before label "init:").
;
U_base		equ	0
U_stat		equ	2	;Status bits, defined below.
U_inbuf 	equ	4
U_outbuf	equ	U_inbuf+B_data+in_buf_len
U_totlen	equ	U_outbuf+B_data+out_buf_len
;
; Bits in U_stat:
;
US_outhold	equ	1	;Host has sent us a "hold it".
US_inhold	equ	2	;We have sent the host a "hold it".
US_saywait	equ	4	;We need to send a "hold it".
US_saygo	equ	8	;We need to send an "OK to send" (un-holdit).
		subttl	Device Headers:
		page
;
; Here are the Device Headers:
;
coms		proc	far
		assume	cs:cseg,ds:cseg
begin:
 
next_dev1	dw	DevHdr2 	;Pointer to next device.
		dw	-1
attribute1	dw	0c000h		;Attributes.
strategy1	dw	Com1Strat	;Pointer to "device strategy" entry.
interrupt1	dw	Com1Int 	;Pointer to software "interrupt" entry.
dev_name1	db	'COM1	 '
 
DevHdr2:
next_dev2	dw	-1		;Pointer to next device.
		dw	-1
attribute2	dw	0c000h		;Attributes.
strategy2	dw	Com2Strat	;Pointer to "device strategy" entry.
interrupt2	dw	Com2Int 	;Pointer to software "interrupt" entry.
dev_name2	db	'COM2	 '
		subttl	Dispatch Table:
		page
;
; Here is the dispatch table:
;
dispatch	label	byte
		dw	init		;Initialization.
		dw	MediaCheck	;Media check (block devices only).
		dw	BuildBPB	;Build BPB  (block devices only).
		dw	IoctlIn 	;IOCTL input.
		dw	input		;input (read).
		dw	NDInput 	;Non-destructive input, no wait.
		dw	InStat		;Status for input.
		dw	InFlush 	;Input flush.
		dw	output		;Output (write).
		dw	OutVerify	;Output with verify.
		dw	OutStat 	;Status for output.
		dw	OutFlush	;Output flush.
		dw	IoctlOut	;IOCTL output.
		subttl	Entry points:
		page
;
; Device "Strategy" entry points:
;
Com1Strat:
Com2Strat:
		mov	cs:ReqHdrSeg,es ;Segment of Request Header Pointer.
		mov	cs:ReqHdrOff,bx ;Offset of Request Header Pointer.
		ret
;
; Device "Interrupt" entry points:
;
Com1Int:	push	ax
		push	bx
		xor	al,al		;Unit # for COM1 is 0.
		mov	bx,offset UCB1
		jmp	JointInt
 
Com2Int:	push	ax
		push	bx
		mov	al,1		;Unit # for COM2 is 1.
		mov	bx,offset UCB2
 
JointInt:
		push	cx
		push	dx
		push	di
		push	si
		push	ds
		push	es
 
		push	cs
		pop	ds		;DS = CS.
 
		mov	unit,al 	;Save unit number.
		mov	dx,[bx] 	;DX = base address for appropriate card.
		les	bx,ReqHdr	;Load ES and BX pointers to Request Hdr.
;
;		Dispatch through the Dispatch Table:
;
		mov	al,es:[bx]+RH_command	;Get function byte.
		rol	al,1
		lea	di,dispatch
		xor	ah,ah
		add	di,ax		;Jump through dispatch table +
		jmp	word ptr[di]	; 2 * function code.
		subttl	Return paths:
		page
;
; Return "Unknown Unit" error code.
;
BadUnit:	mov	ax,STAT_err+STAT_done+S_E_unkunit
		jmp	SetStat
;
; Return "done" status.
;
NormalDone:	mov	ax,STAT_done
;
; Store AX in the STATUS word of the Request Header, then restore registers
; and return.
;
SetStat:	lds	bx,cs:ReqHdr
		mov	[bx]+RH_status,ax
;
; Restore the original registers and return.
;
RestRegsRet:
		pop	es
		pop	ds
		pop	si
		pop	di
		pop	dx
		pop	cx
		pop	bx
		pop	ax
		ret
		subttl	Command processors:
		page
;
; IOCTL input.
;	Do the requested function.
;	Set the actual number of bytes transferred.
;	Set the status word in the Request header.
;
IoctlIn:
		call	CheckUnit
		jc	BadUnit
		jmp	NormalDone
		page
;
; Input.
;	Do the requested function.
;	Set the actual number of bytes transferred.
;	Set the status word in the Request header.
;
input:
		mov	cx,es:[bx]+RH_count	;CX = # bytes to transfer.
		jcxz	NormalDone
		les	di,es:[bx]+RH_buf_addr	;ES:DI points to destination.
		call	GetBase
		jc	BadUnit
		call	EnabInt
		add	bx,U_inbuf	;BX -> appropriate input buffer.
		cld
input1: 	call	TakeByte		;Get byte in AL.
		jnc	input6			;Jump if no more chars avail.
		stosb				;Store in es:[di++].
		loop	input1			;Continue.
input6:
		mov	ax,[bx]+U_stat-U_inbuf
		test	ax,US_inhold
		jz	input8			;If the "I've sent CTRLS" flag
		  call	UsedBuff		; is set and there are fewer
		  cmp	ax,near_empty		; than near_empty data bytes
		  jns	input8			; in the buffer, make a note
						; to send a CTRLQ.
		    or	word ptr [bx]+U_stat-U_inbuf,US_saygo
input8:
		or	cx,cx			;If we didn't transfer the
		jz	NormalD2		; requested number of chars,
		les	bx,ReqHdr		; calculate the number of
		mov	ax,es:[bx]+RH_count	; characters moved and store
		sub	ax,cx			; it in the Request Header.
		mov	es:[bx]+RH_count,ax	;
		mov	ax,STAT_done+STAT_busy	;Return BUSY.
		jmp	SetStat
		page
;
; Non-Destructive Input, No Wait.
;	Return a byte from the device.
;	Set the status word in the Request Header.
;
NDInput:
		call	GetBase
		jc	BadUnit
		call	EnabInt
		add	bx,U_inbuf		;BX -> appropriate input buff.
		mov	ax,[bx]+B_in		;Examine buffer's pointers.
		cmp	ax,[bx]+B_out
		jz	ndin1			;Jump if buffer empty.
		  mov	si,ax
		  mov	al,[si] 		;Fetch a character.
		  mov	cx,STAT_done
		  jmp	ndin2
 
ndin1:		xor	al,al			;No character available.
		mov	cx,STAT_done+STAT_busy
 
ndin2:		les	bx,ReqHdr
		mov	es:[bx]+RH_char,al
		mov	ax,cx
		jmp	SetStat
		page
;
; Status for Input.
;	Perform the operation.
;	Set the status word in the Request Header.
;
InStat:
		call	GetBase
		jc	BadUnit2
		add	bx,U_inbuf		;BX -> appropriate input buff.
		mov	ax,STAT_done
		mov	cx,[bx]+B_in		;Examine buffer's pointers.
		cmp	cx,[bx]+B_out
		jnz	inst1			;Jump if buffer not empty.
		  or	ax,STAT_busy		;If empty, say "busy".
inst1:		jmp	SetStat
;
; Input Flush.
;	Perform the operation.
;	Set the status word in the Request Header.
;
InFlush:
		call	GetBase 	;BX -> appropriate input buff.
		jc	BadUnit2
		add	bx,U_inbuf
		mov	ax,[bx]+B_in	;Set the "out" pointer equal to
		mov	[bx]+B_out,ax	; the "in" pointer.
NormalD2:	jmp	NormalDone
		page
;
; Output (write) and Output with Verify.
;	Do the requested function.
;	Set the actual number of bytes transferred.
;	Set the status word in the Request header.
;
output:
OutVerify:
		mov	cx,es:[bx]+RH_count	;CX = # bytes to transfer.
		jcxz	NormalD2
		les	si,es:[bx]+RH_buf_addr	;ES:SI points to source.
		call	GetBase
		jc	BadUnit2
		add	bx,U_outbuf		;BX -> appropriate output buf.
		cld
output1:	lods	es:byte ptr [si]	;Get 1 char in AL.
		call	PutByte 		;Put character in output buff.
		jnc	output8 		;Jump if no room in output buffer.
		loop	output1
output8:	sub	bx,U_outbuf		;BX -> appropriate UCB.
		call	EnabInt 		;Enable interrupts, if approp.
		or	cx,cx			;If we didn't transfer the
		jz	NormalD2		; requested number of chars,
		les	bx,ReqHdr		; calculate the number of
		mov	ax,es:[bx]+RH_count	; characters moved and store
		sub	ax,cx			; it in the Request Header.
		mov	es:[bx]+RH_count,ax	;
		mov	ax,STAT_done+STAT_busy	;Return BUSY.
		jmp	SetStat
BadUnit2:	jmp	BadUnit
		page
;
; Status for Output.
;	Perform the operation.
;	Set the status word in the Request Header.
;
OutStat:
		call	GetBase
		jc	BadUnit2
		add	bx,U_outbuf		;BX -> appropriate output buf.
		call	IsRoom			;Is there room in the buffer?
		mov	ax,STAT_done
		jc	outst1			;Jump if buffer not full.
		  or	ax,STAT_busy		;If full, say "busy".
outst1: 	jmp	SetStat
;
; Output Flush.
;	Perform the operation.
;	Set the status word in the Request Header.
;
OutFlush:
		call	CheckUnit
		jc	BadUnit2
		jmp	NormalDone
;
; IOCTL output.
;	Do the requested function.
;	Set the actual number of bytes transferred.
;	Set the status word in the Request header.
;
IoctlOut:
		call	CheckUnit
		jc	BadUnit2
		jmp	NormalDone
		page
;
; Entry points for functions not implemented:
;
NotImplemented:
MediaCheck:
BuildBPB:
		mov	ax,STAT_err+STAT_done+S_E_unkcom
		jmp	SetStat
		subttl	Interrupt handlers:
		page
;
; Hardware interrupt handlers.
;	We run with two interrupts enabled:
;		Data Available
;		Transmit Holding Register Empty.
;
;	If Data Available is set
;		If there's room in the IN buffer, put the byte there.
;	If there are data in the OUT buffer,
;		If the Transmit Holding Register is empty, output a byte.
;
Hdw1Int:
		push	ax
		mov	ax,offset UCB1
		jmp	HdwInt
 
Hdw2Int:
		push	ax
		mov	ax,offset UCB2
 
HdwInt:
		push	bx
		push	ds
		push	cx
		push	dx
		push	di
		push	si
		push	es
 
		push	cs
		pop	ds
 
		mov	bx,ax
		mov	di,[bx] 	;DI = base I/O address.
		lea	dx,R_lc[di]
		in	al,dx		; the Line Control Register.
		and	al,255-LC_dlab
		out	dx,al
		lea	dx,R_ls[di]
		in	al,dx		;Read the Line Status Reg.
		test	al,LS_dr	;"Data Ready" set?
		jz	HdwI2
		  push	ax
		  call	ReadByte	;Process the incoming byte.
		  pop	ax
HdwI2:		test	al,LS_thre	;"Xmit Holding Reg Empty" set?
		jz	HdwI3
					;If we want to send a CTRLS or
					; CTRLQ to the host, this is our
					; chance to do it.
					;Note that, if both are set, the
					; CTRLS is not sent.
					;
		  test	word ptr [bx]+U_stat,US_saywait+US_saygo
		  jz	HdwI5
		    test   word ptr [bx]+U_stat,US_saygo
		    jz	   HdwI6
		      mov  al,CTRLQ
		      and  word ptr [bx]+U_stat,0ffffh-(US_saywait+US_saygo+US_inhold)
		      jmp  HdwI7
HdwI6:		    ;else
		      mov  al,CTRLS
		      and  word ptr [bx]+U_stat,0ffffh-(US_saywait+US_saygo)
		      or   word ptr [bx]+U_stat,US_inhold
HdwI7:		    mov    dx,di
		    out    dx,al
 
		    jmp    HdwI3
					; If we get here, we didn't want to
					; send a CTRLS or CTLRQ.
HdwI5:
		  test	word ptr [bx]+U_stat,US_outhold
		  jnz	HdwI3		;Jump if output held by CTRL-S.
		    add 	bx,U_outbuf
		    call	TakeByte
		    jnc 	HdwI4		;Jump if nothing to output.
		      mov	dx,di
		      out	dx,al		;Output another character.
HdwI4:		    sub bx,U_outbuf
HdwI3:		  ;;;
		pop	es
		pop	si
		pop	di
		pop	dx
		pop	cx
		cli
		mov	al,ICC_eoi	;Issue "end-of-interrupt" command
		out	ICC_ctl,al	; to the interrupt-controller chip.
		call	EnabInt 	;Re-enable interrupts at the UART.
		pop	ds
		pop	bx
		pop	ax
		iret
		page
;
; Process an incoming character.
;	Pick up the character in the device.
;	If it's a CTRLS or CTRLQ,
;		set or clear the "he said hold" flag for this device;
;	otherwise
;		put it into the buffer.
;
; Enter with:
;	BX	pointing to the UCB.
;	DI	containing the base address for the device.
;
; Clobbers:	AX, DX.
;
; Side effects:
;		May set or clear the low-order bit of the "hold" word
;		for this device.
;
ReadByte	proc	near
		lea	dx,R_rx[di]
		in	al,dx		;Read the data byte.
		cmp	al,CTRLS	;Watch for XOFF and XON (CTRLS and
		jnz	ReadB1		; CTRLQ).
		  mov	ax,US_outhold
		  or	word ptr [bx]+U_stat,ax
		  ret
 
ReadB1: 	cmp	al,CTRLQ
		jnz	ReadB3
		  and	word ptr [bx]+U_stat,0ffffh-US_outhold
		  ret
 
ReadB3: 	add	bx,U_inbuf
		call	PutByte 	;Put it into the buffer.
		call	UsedBuff
		cmp	ax,near_full	;If the buffer is getting full,
		js	ReadB4		; and we haven't already asked the
					; host to wait, do that.
		  test	word ptr [bx]+U_stat-U_inbuf,US_inhold
		  jnz	ReadB4
		    or	word ptr [bx]+U_stat-U_inbuf,US_saywait
ReadB4:
		sub	bx,U_inbuf
		ret
ReadByte	endp
		subttl	General-Purpose Subroutines:
		page
;
; General-purpose subroutines.
;
; ----------------------------------------------------------------------
;
; Return a pointer to the "base" of the data block for this unit.
;
; Enter with: unit number (0 or 1) in the variable "unit".
;	DS = CS.
;
; Returns: BX containing the offset of either UCB1 or UCB2.
;
GetBase 	proc	near
		call	CheckUnit
		jnc	gb0
		ret
gb0:		test	al,0ffh
		jz	gb1
		mov	bx,offset UCB2
		ret
gb1:		mov	bx,offset UCB1
		ret
GetBase 	endp
;
; ----------------------------------------------------------------------
;
; Check the unit number to make sure it's valid.
 
; Enter with:
;	the variable "unit" set to the unit number to be checked;
;	the variable "maxunit" set to the maximum unit number allowed.
;
; Returns with:
;	"carry" condition set if "unit" < 0 or "unit" > "maxunit".
;	AL = contents of the variable "unit".
;
CheckUnit	proc	near
		mov	al,unit
		or	al,al
		js	RetCarry
		test	maxunit,080h
		jnz	RetCarry
		cmp	al,maxunit
		jg	RetCarry
		clc
		ret
RetCarry:	stc
		ret
CheckUnit	endp
		page
;
; ----------------------------------------------------------------------
;
; Take a byte out of a buffer and return it in AL.
;
; Enter with:
;	BX pointing to the buffer from which a byte is to be taken.
;
; Returns:
;	Carry condition and a byte in AL if the buffer was not empty,
;	NoCarry condition if the buffer was empty.
;
; Side effect: The "output" pointer for the buffer is advanced to reflect
;	the used-upness of the character taken.
;
; Note:
;	Since this subroutine may be interrupted, it is important that
;	the character be taken out of the buffer before the OUT pointer
;	is advanced to reflect its absence.
;
TakeByte	proc	near
		mov	ax,[bx]+B_out		;Output pointer.
		cmp	ax,[bx]+B_in		; = input pointer?
		jz	tb9			;If equal, buffer is empty.
		  push	si
		  push	cx
		  mov	si,ax
		  mov	cl,[si] 		;Fetch the character.
		  call	advance 		;Advance the "out" pointer.
		  mov	[bx]+B_out,ax		;Store updated ptr.
		  mov	al,cl
		  pop	cx
		  pop	si
		  stc				;Set carry flag.
tb9:		ret
TakeByte	endp
		page
;
; ----------------------------------------------------------------------
;
; Put a character into a buffer.
;
; Enter with:
;	BX pointing to the beginning of a buffer,
;	AL holding the character to be put into the buffer.
;
; Returns:
;	NoCarry condition if the buffer is full (character couldn't be put).
;	Carry condition if the character is successfully put into the buffer.
;
; Note:
;	Since this subroutine may be interrupted, it is important that
;	the character be put into the buffer before the IN pointer is
;	adjusted to reflect its availablility.
;
PutByte 	proc	near
		push	cx
		push	di
		mov	cl,al		;CL = the character.
		mov	ax,[bx]+B_in
		mov	di,ax		;DI = original IN pointer.
		call	advance 	;AX = updated IN pointer.
		cmp	ax,[bx]+B_out
		jz	pb9		;Jump if buffer is full.
		  mov	[di],cl 	;Store the character.
		  mov	[bx]+B_in,ax	;Update the pointer.
		  stc
pb9:		pop	di
		pop	cx
		ret
PutByte 	endp
		page
;
; ----------------------------------------------------------------------
;
; Is there room in this buffer?
;
; Enter with BX pointing to the beginning of a buffer.
;
; Returns:
;	NoCarry condition if the buffer is full,
;	Carry condition if there is room in the buffer.
;
IsRoom		proc	near
		mov	ax,[bx]+B_in
		call	advance
		cmp	ax,[bx]+B_out
		jz	ir9
		stc
ir9:		ret
IsRoom		endp
;
; ----------------------------------------------------------------------
;
; How many data bytes are in this buffer?
;
; Enter with BX pointing to the beginning of a buffer.
;
; Returns:
;		AX containing the number of bytes used in the buffer.
;		Z flag set if AX contains 0.
;
UsedBuff	proc	near
		mov	ax,[bx]+B_in
		sub	ax,[bx]+B_out
		jns	ub9
		add	ax,[bx]+B_end
		sub	ax,[bx]+B_beg
ub9:		ret
UsedBuff	endp
		page
;
; ----------------------------------------------------------------------
;
; Advance a buffer pointer.
;
; Enter with
;	BX pointing to the beginning of the buffer.
;	AX containing a pointer into the buffer.
;
; Returns with
;	AX incremented by one, wrapped to the beginning of the buffer
;		if necessary.
;
advance 	proc	near
		inc	ax
		cmp	ax,[bx]+B_end
		jnz	adv9
		mov	ax,[bx]
adv9:		ret
advance 	endp
;
; ----------------------------------------------------------------------
;
; Reset the input-buffer pointers for one unit.
;
; Enter with BX pointing to the Unit Control Block for the unit whose input-
;	buffer pointers are to be cleared.
;
ResInbuf	proc	near
		mov	ax,[bx]+U_inbuf+B_beg
		mov	[bx]+U_inbuf+B_in,ax
		mov	[bx]+U_inbuf+B_out,ax
		ret
ResInbuf	endp
;
; ----------------------------------------------------------------------
;
; Reset the output-buffer pointers for one unit.
;
; Enter with BX pointing to the Unit Control Block for the unit whose output-
;	buffer pointers are to be cleared.
;
ResOutbuf	proc	near
		mov	ax,[bx]+U_outbuf+B_beg
		mov	[bx]+U_outbuf+B_in,ax
		mov	[bx]+U_outbuf+B_out,ax
		ret
ResOutbuf	endp
		page
;
; ----------------------------------------------------------------------
;
; Enable appropriate interrupts.
;
; Enter with:
;	BX pointing to the Unit Control Block controlling the device
;	whose interrupts are to be enabled.
;
EnabInt 	proc	near
		push	cx
		push	dx
		push	di
		xor	cx,cx
		add	bx,U_inbuf
		call	IsRoom
		jnc	ei1		;If there's room in the input buffer,
		  or	cl,IE_da	; enable "data available" interrupt.
ei1:		sub	bx,U_inbuf
 
					;Enable the "transmit holding register
					; empty" interrupt if:
					; - there's something in the output
					;   buffer, and output is not being
					;   "held"; or
					; - we want to send a CTRLS or CTRLQ.
 
		mov	ax,[bx]+U_outbuf+B_in
		cmp	ax,[bx]+U_outbuf+B_out
		mov	ax,[bx]+U_stat
		jz	ei2
		  test	ax,US_outhold
		  jz	ei4
ei2:		;;;
		test	ax,US_saygo+US_saywait
		jz	ei3
ei4:		or	cl,IE_thre
ei3:
		mov	di,[bx]
		lea	dx,R_lc[di]
		in	al,dx		;Turn off DLAB (just in case).
		and	al,255-LC_dlab
		out	dx,al
		lea	dx,R_ie[di]
		mov	al,0
		out	dx,al		;Interrupt Enable = 0 (to ensure a
		mov	al,cl		; transition).
		out	dx,al		;Set final Interrupt Enable.
 
		lea	dx,R_mc[di]		;Turn the enabling "out2" bit
		mov	al,MC_out2+MC_rts+MC_dtr ; on.
		out	dx,al
 
		pop	di
		pop	dx
		pop	cx
		ret
EnabInt 	endp
		subttl	Storage:
		page
;
; Storage for both COM1 and COM2.
;
ReqHdr		dd	?	;The Request Header:
ReqHdrOff	equ	word ptr ReqHdr
ReqHdrSeg	equ	ReqHdrOff+2
;
;
unit		db	?		;0 for COM1, 1 for COM2.
					;(Set according to which "interrupt"
					; entry point is used.)
maxunit 	db	-1		;Maximum unit number allowed.
					;(Set during INIT.)
end0:				;Ending address if no asynch cards found.
;
; Unit Control Block for COM1:
; Keep this space allocation consistent with the definitions of U_inbuf,
; B_end, etc.
;
UCB1:
base_1: 	dw	?		;Base address for COM1.
					;(Set during INIT.)
hold_1: 	dw	?
inb1_beg:	dw	offset inb1
inb1_end:	dw	offset inb1+in_buf_len
inb1_in:	dw	?
inb1_out:	dw	?
inb1:		db	in_buf_len dup(?)
 
outb1_beg:	dw	offset outb1
outb1_end	dw	offset outb1+out_buf_len
outb1_in:	dw	?
outb1_out:	dw	?
outb1:		db	out_buf_len dup(?)
end1:				;Ending address if only 1 asynch card found.
;
; Unit Control Block for COM2.
; Keep this space allocation consistent with the definitions of U_inbuf,
; B_end, etc.
;
UCB2:
base_2: 	dw	?		;Base address for COM2.
					;(Set during INIT.)
hold_2: 	dw	?
inb2_beg:	dw	offset inb2
inb2_end:	dw	offset inb2+in_buf_len
inb2_in:	dw	?
inb2_out:	dw	?
inb2:		db	in_buf_len dup(?)
 
outb2_beg:	dw	offset outb2
outb2_end	dw	offset outb2+out_buf_len
outb2_in:	dw	?
outb2_out:	dw	?
outb2:		db	out_buf_len dup(?)
;
end2:				;Ending address if 2 asynch cards found.
		subttl	Initialization:
		page
;
; Initialization:
;	Perform any initialization code.
;		If COM2, skip this part.
;		Fill base_1 and base_2, as appropriate.
;		Initialize pointers to input & output buffers.
;		Install our hardware interrupt vectors.
;	Set up the ending address for resident code.
;	Set the status word in the Request Header.
;
init:
		mov	ax,endaddr	;Have we been called before?
		or	ax,ax
		jnz	initend 	;Skip this part if so.
		  mov	dx,primary
		  mov	bx,offset base_1
		  call	InitUCB
		  call	HaveUnit		;Is "primary" card there?
		  jnz	init2
		    push dx			;Assign our interrupt handler
		    mov  dx,NextHandler 	; to its interrupt.
		    mov  al,prim_int
		    mov  ah,25h
		    int  21h
		    mov  ax,offset Hdw2Int	;Use other interrupt handler
		    mov  NextHandler,ax 	; next time.
		    pop  dx
		    mov [bx],dx 		;Use the first UCB
		    call EnabInt		; for it.
		    mov bx,offset base_2
		    call InitUCB
		    inc maxunit
init2:		  mov	dx,secondary
		  call	HaveUnit		;Is "secondary" card there?
		  jnz	init3
		    push dx			;Assign our interrupt handler
		    mov  dx,NextHandler 	; to its interrupt.
		    mov  al,sec_int
		    mov  ah,25h
		    int  21h
		    pop  dx
		    mov [bx],dx 		;Use for it whichever
		    call EnabInt		; UCB is indicated by
		    inc maxunit 		; BX.
init3:		  mov	bl,maxunit
		  inc	bl
		  xor	bh,bh
		  rol	bl,1
		  mov	ax,[bx]+endtable
		  mov	endaddr,ax
initend:	mov	ax,endaddr	;Store the ending address.
		lds	bx,cs:ReqHdr
		mov	[bx]+RH_end_addr,ax
		mov	[bx]+RH_end_addr+2,cs
 
		in	al,ICC_msk	;Tell the interrupt controller chip
		and	al,255-(MSK_d1+MSK_d2)	; to allow these interrupts.
		out	ICC_msk,al
 
		jmp	NormalDone
;
; Is there an asynch card answering at this address?
;
; Enter with:
;	DX holding the base I/O address to be interrogated.
;
; Returns:
;	Z condition if there is a card there,
;	NZ condition if there is NO card there.
;
HaveUnit	proc	near
		add	dx,R_ii
		in	al,dx		;Read the Interrupt Identification
		sub	dx,R_ii 	; register.
		and	al,II_never	;If there's a device there, these bits
		ret			; will be zero.
HaveUnit	endp
;
; Initialize a Unit Control Block (UCB).
;
; Enter with
;	BX pointing to the data block for the channel whose input
;		and output buffer pointers are to be reset.
;
InitUCB 	proc	near
		mov	word ptr [bx]+U_stat,0
		call	ResInbuf
		call	ResOutbuf
		ret
InitUCB 	endp
;
endaddr:	dw	0		;END address returned on INIT call.
endtable:	dw	offset end0	;END address if 0 asynch cards found.
		dw	offset end1	;END address if 1 asynch cards found.
		dw	offset end2	;END address if 2 asynch cards found.
NextHandler:	dw	offset Hdw1Int	;Successive interrupt-handler addresses.
coms		endp
cseg		ends
		end	begin
