	title	COM_PKG1
	page	60,132
;
; COM_PKG1 library of serial I/O routines
; For the IBM PC's first serial port
; Uses Microsoft Pascal calling conventions
;
; Adapted from code by John Romkey and Jerry Saltzer of MIT
; by Richard Gillmann (GILLMANN@ISIB), 1983
;
; package entry points (MS Pascal calling conventions) are:
;
; init_au(divisor:word)	initializes port and interrupt vector
; close_a		turns off interrupts from the aux port
; dtr_off		turns off dtr
; dtr_on		turns on dtr
; crcnt : word		returns number of characters in input buffer
; cread : byte		reads next character in input buffer
; cwcnt : word		returns number of free bytes in output buffer
; cwrit(ch:byte)	writes a character to the output buffer
; wlocal(ch:byte)	writes a character to the input buffer
; make_br		causes a break to be sent
;
	page
rsize	equ	2048		; size of receive buffer
tsize	equ	256		; size of transmit buffer
base	equ	3f0h		; base of address of aux. port registers
int	equ	0ch		; interrupt number for aux port
int_off	equ	int*4		; offset of interrupt vector
datreg	equ	base + 8h	; data register
dll	equ	base + 8h	; low divisor latch
dlh	equ	base + 9h	; high divisor latch
ier	equ	base + 9h	; interrupt enable register
iir	equ	base + 0ah	; interrupt identification register
lcr	equ	base + 0bh	; line control register
mcr	equ	base + 0ch	; modem control register
lsr	equ	base + 0dh	; line status register
msr	equ	base + 0eh	; modem status register
dla	equ	80h		; divisor latch access
mode	equ	03h		; 8-bits, no parity
dtr	equ	0bh		; bits to set dtr line
dtr_of	equ	00h		; turn off dtr, rts, and the interupt driver
thre	equ	20h		; mask to find status of xmit holding register
rxint	equ	01h		; enable data available interrupt
txint	equ	02h		; enable tx holding register empty interrupt
tcheck	equ	20h		; mask for checking tx reg status on interrupt
rcheck	equ	01h		; mask for checking rx reg status on interrupt
imr	equ	21h		; interuprt mask register
int_mask equ	0efh		; mask to clear bit 4
int_pend equ	01h		; there is an interrupt pending
mstat	equ	00h		; modem status interrupt
wr	equ	02h		; ready to xmit data
rd	equ	04h		; received data interrupt
lstat	equ	06h		; line status interrupt
ack	equ	244		; acknowledge symbol
parity	equ	7fh		; bits to mask off parity
ocw2	equ	20h		; operational control word on 8259
eoi	equ	64h		; specific end of interrupt 4
break	equ	40h		; bits to cause break
true	equ	1		; truth
false	equ	0		; falsehood
	page
data	segment public 'data'
int_offset	dw	0	; the original interrupt offset
int_segment	dw	0	; the original interrupt segment
start_tdata	dw	0	; index to first character in x-mit buffer
end_tdata	dw	0	; index to first free space in x-mit buffer
size_tdata	dw	0	; number of characters in x-mit buffer
start_rdata	dw	0	; index to first character in rec. buffer
end_rdata	dw	0	; index to first free space in rec. buffer
size_rdata	dw	0	; number of characters in rec. buffer
tdata		db	tsize dup(?)	; transmit buffer
rdata		db	rsize dup(?)	; receive buffr
data	ends
dgroup	group	data
	page
	assume	cs:auxhndlr,ds:dgroup,ss:dgroup
auxhndlr segment 'code'

	public	init_au		; initializes port and interrupt vector
	public	close_a		; turns off interrupts from the aux port
	public	dtr_off		; turns off dtr
	public	dtr_on		; turns on dtr
	public	crcnt		; returns number of characters in input buffer
	public	cread		; reads next character in input buffer
	public	cwcnt		; returns no. of free bytes in output buffer
	public	cwrit		; writes a character to output buffer
	public	wlocal		; writes a character to the input buffer
	public	make_br		; causes a break to be sent
	page
;
; int_hndlr - handles interrupts generated by the aux. port
;
dataseg	dw	0
int_hndlr proc	far
	push    bp
	push    ds
	push    di
	push    ax
	push    bx
	push    cx
	push    dx

; set up data segment
	mov	ax,cs:dataseg
	mov	ds,ax

; find out where interrupt came from and jump to routine to handle it
	mov     dx,iir
	in	al,dx
	cmp     al,rd
	jz      rx_int		; if it's from the receiver
	cmp     al,wr
	jz      tx_int          ; if it's from the transmitter
	cmp     al,lstat
	jz      lstat_int       ; interrupt becuase of line status
	cmp     al,mstat
	jz      mstat_int       ; interrupt because of modem status
	jmp     far ptr int_end	; interrupt when no interrupt pending, go away

lstat_int:
	mov     dx,lsr		; clear interrupt
	in	al,dx
	jmp     repoll		; see if any more interrupts

mstat_int:
	mov     dx,msr		; clear interrupt
	in	al,dx
	jmp     repoll          ; see if any more interrupts

tx_int:
	mov     dx,lsr
	in	al,dx
	and     al,tcheck
	jnz     goodtx          ; good interrupt
	jmp     repoll          ; see if any more interrupts

goodtx: cmp     size_tdata,0	; see if any more data to send
	jne     have_data       ; if not equal then there is data to send

; if no data to send then reset tx interrupt and return
	mov     dx,ier
	mov     al,rxint
	out	dx,al
	jmp     repoll

have_data:
	mov     bx,start_tdata	; bx points to next char. to be sent
	mov     dx,datreg	; dx equals port to send data to
	mov     al,tdata[bx]    ; get data from buffer
	out     dx,al		; send data
	inc     bx              ; increment start_tdata
	cmp     bx,tsize	; see if gone past end
	jl      ntadj           ; if not then skip
	sub     bx,tsize	; reset to beginning
ntadj:  mov     start_tdata,bx  ; save start_tdata
	dec     size_tdata      ; one less character in x-mit buffer
	jmp     repoll

rx_int:
	mov     dx,lsr		; check and see if read is real
	in	al,dx
	and     al,rcheck	; look at receive data bit
	jnz     good_rx         ; real, go get byte
	jmp     repoll          ; go look for other interrupts

good_rx:
	mov     dx,datreg
	in      al,dx		; get data
	cmp     size_rdata,rsize	; see if any room
	jge	repoll          ; if no room then look for more interrupts
	mov     bx,end_rdata    ; bx points to free space
	mov     rdata[bx],al    ; send data to buffer
	inc     size_rdata      ; got one more character
	inc     bx              ; increment end_rdata pointer
	cmp     bx,rsize	; see if gone past end
	jl      nradj           ; if not then skip
	sub     bx,rsize	; else adjust to beginning
nradj:  mov     end_rdata,bx    ; save value

repoll:
	mov     dx,lsr		; we always expect receive data, so
	in      al,dx		; check status to see if any is ready.
	and     al,rcheck	; get received data bit
	jnz     good_rx         ; yes, go accept the byte

	mov     dx,ier		; look at transmit condition
	in      al,dx		; to see if we are enabled to send data
	and     al,txint
	jz      int_end		; not enabled, so go away
	mov     dx,lsr		; we are enabled, so look for tx condition
	in	al,dx
	and     al,tcheck
	jz	int_end
	jmp	goodtx          ; transmitter is finished, go get more data

int_end:
	mov     dx,ocw2		; tell the 8259 that I'm done
	mov     al,eoi
	out	dx,al

	pop     dx
	pop     cx
	pop     bx
	pop     ax
	pop     di
	pop     ds
	pop     bp
	iret
int_hndlr endp
	page
;
; init_aux(divisor:word)
; initialize the Intel 8250 and set up interrupt vector to int_hndlr
; divisor is the divisor for the baud rate generator
;
init_au	proc	far
	push    bp
	mov     bp,sp
	cli

	mov	ax,ds
	mov	cs:dataseg,ax

; reset the UART
	mov     al,0
	mov     dx,mcr
	out	dx,al

	mov     dx,lsr		; reset line status condition
	in	al,dx
	mov     dx,datreg	; reset recsive data condition
	in	al,dx
	mov     dx,msr		; reset modem deltas and conditions
	in	al,dx

; set baud rate with the passed argument
	mov     dx,lcr
	mov     al,dla+mode
	out	dx,al
	mov     dx,dll
	mov     al,6[bp]	; low byte of passed argument
	out	dx,al
	mov     dx,dlh
	mov     al,7[bp]	; high byte of passed argument
	out	dx,al

; set 8250 to 8 bits, no parity
	mov     dx,lcr
	mov     al,mode
	out	dx,al

; set interrupt vector
	push    ds
	mov     ax,0
	mov     ds,ax
	mov     bx,ds:int_off
	mov     cx,ds:int_off+2
	mov     word ptr ds:int_off,offset int_hndlr
	mov     ds:int_off+2,cs
	pop     ds
	mov     int_offset,bx
	mov     int_segment,cx

; enable interrupts on 8259 and 8250
	in      al,imr		; set enable bit on 8259
	and     al,int_mask
	out     imr,al
	mov     dx,ier		; enable interrupts on 8250
	mov     al,rxint
	out	dx,al
	mov     dx,mcr		; set dtr and enable int driver
	mov     al,dtr
	out	dx,al

	sti
	pop     bp
	ret	2
init_au	endp
	page
;
; close_a - turns off interrupts from the auxiliary port
;
close_a	proc	far
; turn off 8250
	mov     dx,ier
	mov     al,0
	out	dx,al

; turn off 8259
	mov     dx,imr
	in	al,dx
	or      al,not int_mask
	out	dx,al

; reset interrupt vector
	cli
	mov     bx,int_offset
	mov     cx,int_segment
	push    ds
	mov     ax,0
	mov     ds,ax
	mov     ds:int_off,bx
	mov     ds:int_off+2,cx
	pop     ds
	sti
	ret
close_a	endp
	page
;
; dtr_off - turns off dtr to tell modems that the terminal has gone away
;           and to hang up the phone
;
dtr_off	proc	far
	mov     dx,mcr
	mov     al,dtr_of
	out	dx,al
	ret
dtr_off	endp
;
; dtr_on - turns dtr on
;
dtr_on	proc	far
	mov     dx,mcr
	mov     al,dtr
	out	dx,al
	ret
dtr_on	endp
	page
;
; crcnt - returns number of bytes in the receive buffer
;
crcnt	proc	far
	mov     ax,size_rdata	; get number of bytes used
	ret
crcnt	endp
;
; cread - returns the next character from the receive buffer and
;         removes it from the buffer
;
cread	proc	far
	mov     bx,start_rdata
	mov     al,rdata[bx]
	mov     ah,0
	inc     bx              ; bump start_rdata so it points at next char
	cmp     bx,rsize	; see if past end
	jl      L12             ; if not then skip
	sub     bx,rsize	; adjust to beginning
L12:    mov     start_rdata,bx  ; save the new start_rdata value
	dec     size_rdata      ; one less character
	ret
cread	endp
	page
;
; cwcnt - returns the amount of free space remaining in the transmit buffer
;
cwcnt	proc	far
	mov     ax,tsize	; get the size of the x-mit buffer
	sub     ax,size_tdata   ; subtract the number of bytes used
	ret
cwcnt	endp
;
; cwrit(ch:byte) - the passed character is put in the transmit buffer
;
cwrit	proc	far
	push    bp
	mov     bp,sp
	mov     bx,end_tdata    ; bx points to free space
	mov     al,6[bp]	; move data from stack to x-mit buffer
	mov     tdata[bx],al
	inc     bx              ; increment end_tdata to point to free space
	cmp     bx,tsize	; see if past end
	jl      L4              ; if not then skip
	sub     bx,tsize	; adjust to beginning
L4:     mov     end_tdata,bx    ; save new end_tdata
	inc     size_tdata      ; one more character in x-mit buffer
	mov     dx,ier		; see if tx interrupts are enabled
	in	al,dx
	and     al,txint
	or      al,al
	jnz     L44
	mov     al,rxint+txint	; if not then set them
	out	dx,al
L44:    pop     bp
	ret	2
cwrit	endp
;
; wlocal(ch:byte) - writes a character to the input buffer
;
wlocal	proc	far
	push    bp
	mov     bp,sp
	cli

	cmp     size_rdata,rsize	; see if any room
	jge	L14		; if no room then quit
	mov     bx,end_rdata    ; bx points to free space
	mov     al,6[bp]	; get data
	mov     rdata[bx],al    ; send data to buffer
	inc     size_rdata      ; got one more character
	inc     bx              ; increment end_rdata pointer
	cmp     bx,rsize	; see if gone past end
	jl      L13             ; if not then skip
	sub     bx,rsize	; else adjust to beginning
L13:    mov     end_rdata,bx    ; save value

L14:	sti
	pop     bp
	ret	2
wlocal	endp
	page
;
; make_break - causes a break to be sent out on the line
;
make_br	proc	far
	mov     dx,lcr		; save the line control register
	in	al,dx
	mov     bl,al
	        
	mov     al,break	; set break condition
	out	dx,al

	mov     cx,0		; wait a while
wait:   loop    wait

	mov     al,bl           ; restore the line control register
	out	dx,al
	ret
make_br	endp
auxhndlr ends
	end
