title Fido Drivers for IBM
;
;Fido standard Async and timer tick driver
;for IBM et al. Handles 8250/16450/16550 
;chips, and internal vs. FOSSIL interfaces.
;
;T. Jennings 4 Dec 91
;
;init_async(port);
;int port;
;Initialize the async system. If a FOSSIL driver
;is detected, it is used, otherwise the integral
;driver is installed.
;
;
;init_tick();	Install the time tick
;		functions. After this call
;		the counters should start 
;		incrementing.
;
;uninit_tick();	Remove the time circuit.
;
; h= set_tick(&timer);
; int h;
; long *timer;
;
;    Returns the handle of an allocated
;timer tick; the counter will be continuously
;incremented until the handle is released.
;The timer has a resolution of 1 mS, and
;unknown accuracy. It is generally incremented
;in increments of the system clock. -1 is
;returned if no handles are available.
;
; clr_tick(h);
; int h;
;
; Release a handle.
;
include mc.ash
.model small

;
;--------------------------------
;
.data

fossil db 0		;1 == FOSSIL installed

;
;Timer tick data.
;
MAXTICK	equ 16		;max. timer handles
ticks dw 0		;work counter
intno db (?)		;interrupt number
tickint dw (?)		;how often
pile dw MAXTICK dup (0)	;comma table of

;
;Async data.
;
ioport dw 0		;channel/device number
mdmbits db 0		;copy of DTR, RTS

IN_SIZE equ 400		;buffer size
in_beg	db IN_SIZE dup (?) ;input buffer
in_end	db 0
in_count dw 0		;chars in the buffer
in_head	dw 0
in_tail	dw 0

OUT_SIZE equ 300	;buffer size
out_beg	db OUT_SIZE dup (?)
out_end	db 0
out_count dw 0		;chars in the buffer
out_head dw 0
out_tail dw 0

;
;Baud rate divisor table. This contains
;baud rate, the divisor (for integral driver)
;and the AL value for the FOSSIL call.
;
baudtbl label word
	dw	300,384,01000011b
	dw	600,192,01100011b
	dw	1200,96,10000011b
	dw	2400,48,10100011b
	dw	4800,24,11000011b
	dw	9600,12,11100011b
	dw	19200,6,00000011b
	dw	38400,3,00100011b
	dw	0,0,0
;
; 8250 hardware configuration
;
base	dw	3f8h		; COM1 base port
intnum	db	12		; COM1 interrupt
intmsk	db	11101111b	; 

MDATA	equ	0		; modem data port 
MLINE	equ	3		; line control 
MLSTAT	equ	5		; line status 
MCNTRL	equ	4		; modem control 
MSTAT	equ	6		; modem status 
MBAUDL	equ	0		; baud rate low 
MBAUDH	equ	1		; baud rate high 
INTENB	equ	1		; interrupt enable register
INTID	equ	2		; interrupt ID
FIFO	equ	2		; 16550 FIFO control

DLAB	equ	80h		; baud reg. access bit 
RDA	equ	01h		; character avail 
TBE	equ	20h		; buffer empty 
MODE	equ	03h		; 8 bits, no parity 
MMODE	equ	00001000b	; enable interrupt
RTS	equ	2		; RTS bit
DTR	equ	1		; DTR bit,
CTS	equ	10h		; CTS bit,
B300	equ	0180h		; 300 baud 
B1200	equ	0060h		; 1200 baud 
B9600	equ	000ch		; 9600 baud 
;
;Interrupt enable bits.
;
RDAINT	equ	0001b		; Rx interrupt
TBEINT	equ	0010b		; Tx interrupt
RLSINT	equ	0100b		; line status
MDMINT	equ	1000b		; modem status
page

.code
;
;These are the timer tick parameters
;from the driver. They must be in CS.
;
dataseg dw (?)		;DS: value for ISRs
oldvec dd (?)		;saved tick vector,

int14vec dd 0		;INT 14h vector
;
;init_async(port);
;int port;
;
;If a FOSSIL driver is installed, use it, 
;otherwise install ours and init the hardware.
;
func _init_async
	mov	ax,arg0		;remember port #
	mov	ioport,ax
	call	fossil_setup	;detect FOSSIL
	test	fossil,1	;if there 
	jz	in0		;done!
	jmp	inz

in0:	mov	intnum,12	;defaults for
	mov	intmsk,11101111b;COM1
	mov	base,3f8h
	cmp word ptr arg0,0	;port #
	je	in1
	mov	intnum,11	;COM2
	mov	intmsk,11110111b
	mov	base,2f8h
in1:	mov	al,intnum
	mov	dx,offset i8250
	mov	cs:dataseg,ds	;store DS
	push	ds
	mov	cx,cs		;set DS to CS,
	mov	ds,cx		;set Rx int vec
	call	setvec
	pop	ds
;
;Special code for the buggy 8250 A version.
;
	mov	bx,1		;set fast baud
	call	setbaud
	mov	dx,base
	add	dx,MDATA	;then send a char
	mov	al,0
	call	outp
	mov	cx,-1		;delay a bit
in2:	jmp	$ + 2		;yucky poo
	loop	in2
;
;Enable the 16550 FIFO and clear them.
;
	mov	dx,base
	add	dx,FIFO
	mov	al,00000111b	;on 8250,
	call	outp		;writes IIR

	pushf
	cli
	mov	mdmbits,MMODE + RTS
	call	__mflush	;buffers empty

	mov	dx,base
	add	dx,MLINE	;reset the DLAB
	mov 	al,MODE		;bit, set 8 data
	call	outp		;bits, no parity,

	mov	dx,base
	add	dx,INTENB	;enable interrupt
	mov	al,RDAINT + MDMINT;receive only!
	call	outp
	call	_raise_dtr	;raise DTR, 

	mov	dx,base
	add	dx,MDATA	;clear chars
	call	inp		;in the UART,
	call	inp
	call	inp
	call	inp

	mov	dx,base
	add	dx,MLSTAT	;clear errors, etc
	call	inp

	mov	dx,base
	add	dx,MSTAT	;just for the hell of it,
	call	inp		;clear delta CTS, etc

	mov	dx,21h
	call	inp
	and	al,intmsk	;clear com interrupt bit
	call	outp
	popf
inz:
endf _init_async

;
;Local routine: If a FOSSIL driver is installed,
;initialize it, flag it, and yank the vector
;so we can do a long call instead of a miserable
;Intel INT.
;
fossil_setup:
	test	fossil,1	;if already checked
	jnz	fsz		;don't repeat

	mov	dx,ioport	;DX= port #
	mov	bx,0		;BX= (not 4f50)
	mov	ah,4		;AH= init
	int	14h		;do it
	cmp	ax,1954h	;present?
	jne	fsz

	push	es		;yup, 
	mov	al,14h		;get INT 14h
	call	getvec		;vector
	mov	word ptr cs:int14vec,bx ;save it,
	mov	word ptr cs:int14vec + 2,es
	pop	es
	mov	fossil,1	;yup, flag it
fsz:	ret

;
;This disgusting bit of code calls the saved 
;INT 14h vector instead of using the sluggish 
;INT xx isntruction.
;
int14:	mov	dx,ioport	;all calls
	pushf			;setup for IRET
	call dword ptr cs:[int14vec]
	ret
;
;uninit_async();
;
;If a FOSSIL driver was used, de-init it.
;
func _uninit_async
	test	fossil,1
	jz	ui1		;if FOSSIL used
	mov	ah,5		;AH= Deinit
	call	int14		;call FOSSIL
	jmp short uiz

ui1:	pushf
	cli
;
;Turn off RDA & TBE interrupt sources in
;the 8250.
;
	mov	dx,base
	add	dx,INTENB 	;reset RDA/TBE
	mov 	al,0		;in 8250,
	call	outp
;
;Leave DTR and RTS high and IRQx as-is.
;
	mov	dx,base
	add	dx,MCNTRL
	mov	al,DTR + RTS
	and	al,mdmbits
	call	outp
;
;Disable the 8250 interrupt at the 8259A
;
	mov	dx,21h
	call	inp		;reset 8259 msk
	mov	ah,intmsk
	xor	ah,11111111b
	or	al,ah
	call	outp
;
;Disable the FIFO.
;
	mov	dx,base
	add	dx,FIFO
	mov	al,0		;on 8250,
	call	outp		;writes IIR

	popf
;
;Clear the 'fossil' flag, to allow init again.
;
uiz:	mov	fossil,0
endf _uninit_async
page
;
;Set the baud rate as specified. Return the
;baud rate, or zero if an illegal one was
;specified.
;
func _baud
	mov	si,offset baudtbl
b1:	mov	ax,[si]		;AX= baud
	mov	bx,[si + 2]	;BX= divisor
	mov	cx,[si + 4]	;CL= FOSSIL val
	add	si,6
	or	ax,ax		;if zero, quit
	je	bz
	cmp	ax,arg0		;correct one?
	jne	b1

	call	setbaud		;set it,
	mov	ax,arg0		;return baud
bz:
endf _baud
;
;Set the baud rate, the divisor in BX and the
;FOSSIL call value in CL.
;
setbaud:
	test	fossil,1	;check for FOSSIL
	jz	sb1
	mov	ah,0		;AH= Set Baud
	mov	al,cl		;AL= bits
	call	int14
	ret

sb1:	pushf
	cli
	mov	dx,base
	add	dx,MLINE	;select baud rate port
	mov	al,DLAB+MODE	;set DLAB, 8 bits,
	call	outp
	mov	dx,base
	add	dx,MBAUDL	;low byte of baud
	mov	al,bl
	call	outp
	mov	dx,base
	add	dx,MBAUDH	;now high byte
	mov	al,bh
	call	outp
	mov	dx,base
	add	dx,MLINE	;reset DLAB,
	mov	al,MODE
	call	outp		;select data ports
	popf
	ret
page
;
;Start or end a line break. Assumes that no 
;characters will be sent after break is 
;started and that it lasts at least two char 
;times.
;
func __mbreak
	mov	ax,arg0		;AL == flag
	test	fossil,1
	jz	mk1
	mov	ah,1ah		;AH= Break
	call	int14
	jmp short mkz

mk1:	cmp	ax,0
	je	mk3
	mov	al,0		;set break
	call	outa		;send a null
mk2:	cmp	out_count,0	;wait til sent
	jnz	mk2

	mov	dx,base
	add	dx,MLINE	;cause a break
	mov	al,MODE + 40h
	call	outp
	jmp	mkz

mk3:	mov	dx,base		;reset break
	add	dx,MLINE
	mov	al,MODE		;set normal mode
	call	outp
mkz:
endf __mbreak
;
;Lower DTR. RTS and IRQx are in the port, so
;we have to preserve them.
;
func _lower_dtr
	test	fossil,1
	jz	ld1
	mov	ah,6		;AH= DTR
	mov	al,0		;AL= lower DTR
	call	int14
	jmp short ldz

ld1:	mov	al,DTR		;clear the DTR
	not	al		;bit in memory
	and	al,mdmbits
	mov	mdmbits,al
	mov	dx,base
	add	dx,MCNTRL
	call	outp		;and the 8250
ldz:
endf _lower_dtr
;
;Raise DTR. 
;
func _raise_dtr
	test	fossil,1
	jz	rd1
	mov	ah,6		;AH= DTR
	mov	al,1		;AL= raise DTR
	call	int14
	jmp short rdz

rd1:	or	mdmbits,DTR	;set DTR,
	mov	al,mdmbits	;set the 8250
	mov	dx,base
	add	dx,MCNTRL
	call	outp		;and the 8250
rdz:
endf _raise_dtr
;
;Flush both buffers.
;
func __mflush
	test	fossil,1
	jz	mf1
	mov	ah,9		;AH= purge out
	call	int14
	mov	ah,0ah		;AH= purge in
	call	int14
	jmp short mfz

mf1:	pushf
	cli
	mov	out_head,offset out_beg + 1
	mov	out_tail,offset out_beg
	mov	in_head,offset in_beg + 1
	mov	in_tail,offset in_beg
	mov	in_count,0
	mov	out_count,0
	popf
mfz:
endf __mflush
;
;Return true if the output buffer is not empty.
;
func __mbusy
	mov	ax,out_count	;AX= chars left
	test	fossil,1
	jz	mbz
	mov	ah,3		;AH= status
	call	int14
	xor	ax,4000h	;flip TSRE
	and	ax,4000h	;mask TSRE
mbz:
endf __mbusy
;
;Return true if CD or DSR is true. 
;
func __cd
	test	fossil,1
	jz	cd1
	mov	ah,3		;AH= status
	call	int14
	mov	ah,0		;(zap UART stat)
	jmp short cd2

cd1:	mov	dx,base
	add	dx,MSTAT
	call	inp		;flush deltas
	call	inp
cd2:	and	ax,arg0		;mask it off
endf __cd

;
;Return true if there is a receive character
;ready.
;
func __mconstat
	test	fossil,1
	jz	mis1
	mov	ah,3		;AH= status
	call	int14
	and	ax,0100h	;mask RDA
	jmp short mis2
mis1:	call	inas
mis2:	mov	ax,0
	jz	misz
	mov	ax,1
misz:
endf __mconstat

;
;Output a character to the modem.
;
func __mconout
	mov	al,arg0		;AL= char
	test	fossil,1
	jz	mo1
	mov	ah,1		;AH= char out
	call	int14
	jmp short moz
mo1:	call	outa
moz:
endf __mconout
;
;Return true if modem output ready.
;
func __mconostat
	test	fossil,1
	jz	mos1
	mov	ah,3		;AH= status
	call	int14
	and	ax,2000h	;mask THRE
	jmp short mos2
mos1:	call	outas
mos2:	mov	ax,0
	jz	mosz
	mov	ax,1
mosz:
endf __mconostat
;
;Get a character from the modem
;
func __mconin
	test	fossil,1
	jz	mi1
	mov	ah,2		;AH= read char
	call	int14
	jmp short mi2
mi1:	call	ina
mi2:	mov	ah,0
miz:
endf __mconin

;
;Local routine: Return a character from the 
;input buffer, wait if necessary.
;
ina:	call	inas		;check for char
	jz	ina		;wait if none
	mov	in_tail,bx	;else return it
	dec	in_count	;and count it
	ret
;
;Return Z clear, a character in AL, and BX 
;pointing to the next location if there is a
;character ready, else Z set.
;
inas:	call	rtsctl		;RTS control

	mov	bx,in_tail	;char in stat,
	inc	bx
	cmp	bx,offset in_end
	jb	ias1
	mov	bx,offset in_beg
ias1:	mov	al,[bx]
	cmp	bx,in_head
	ret
;
;Put the character in AL into the output
;buffer, wait for room if necessary.
;
outa:	push	ax
oa0:	call	outas		;wait for room
	jz	oa0

	mov	bx,out_head
	pop	ax
	mov	[bx],al		;stash it,
	inc	bx
	cmp	bx,offset out_end
	jb	oa1
	mov	bx,offset out_beg
oa1:	mov	out_head,bx
	inc	out_count
	call	outas		;send it
	ret
;
;Return Z set if the buffer is full.
;
outas:
	call	mlint		;check CTS
;;	mov	dx,base
;;	add	dx,INTENB
;;	mov	al,RDAINT + TBEINT + MDMINT
;;	call	outp

	mov	ax,out_head
	sub	ax,out_tail
	ret	
page
;
;Interrupt vector table, from the least three
;bits in the IIR. Note that in FIFO mode 
;(16550) 1100b means FIFO Rx timeout; since
;bit 3 is masked off it is treated like an Rx
;interrupt.
;
vectbl label word	; B  W  offset
	dw	mlint	;000 00 modem status
	dw	txint	;010 01 Tx interrupt
	dw	rxint	;100 10 Rx interrupt
	dw	lsint	;110 11 line status
;
;Interrupt service for the 8250. Check for
;either interrupt, handle both in series here.
;NOTE: BP is the 8250 base address for RxINT
;and TxINT.
;
i8250:
	push	ax
	push	bx
	push	dx
	push	bp
	push	ds
	mov	ds,cs:dataseg
	mov	bp,base		;pre-set BP,
;
;Loop until there are no more interrupts
;pending.
;
i0:	mov	dx,bp
	add	dx,INTID	;find source
	call	inp		;of interrupt
	mov	bx,ax
	and	bx,111b		;only bits 0-2
	test	bl,1		;check EOI
	jnz	i4
	mov	ax,offset i0
	push	ax		;return address
	jmp	cs:vectbl[bx]	;call it
;
;Line Status Interrupt: Overrun, Parity, etc. 
;Ignore these interrupts. Clear 'em and go.
;
lsint:	mov	dx,bp
	add	dx,MLSTAT	;clear errors, etc
	call	inp
badint:	ret	
;
;Modem status change. We just watch CTS here and
;possibly reenable Tx interrupts if it is off and
;goes true.
;
;Also called by local routines to re-enable Tx
;interrupts if they were off. (Doesn't use BP.)
;
mlint:	mov	dx,base
	add	dx,MSTAT	;check modem
	call	inp		;status,
;;	test	al,1		;ignore all but
;;	jz	ml2		;CTS changes
	and	al,CTS		;test for CTS
	mov	al,RDAINT + MDMINT
	jz	ml1		;if CTS true,
	or	al,TBEINT	;enable Tx ints
ml1:	mov	dx,base		;else 
	add	dx,INTENB	;disable Tx
	call	outp
ml2:	ret
;
;End of interrupt: clear it and go.
;
i4:	mov	dx,20h
	mov 	al,20h		;non-specific EOI,
	call	outp		;to the 8259A.

	pop	ds
	pop	bp
	pop	dx
	pop	bx
	pop	ax
	iret
page
;
;TBE interrupt service. If no characters
;to send, mask off the transmitter interrupt.
;Output as many characters as possible, until
;we lose TBE, CTS or no more characters.
;
;BP = 8250 base address
;
txint:	mov	dx,bp
	add	dx,MLSTAT	;UART status,
	call	inp		;quit if
	and	al,TBE		;no TBE.
	jz	p0w4
;
;If CTS is false just exit. 
;
	mov	dx,bp
	add	dx,MSTAT	;check modem
	call	inp		;status,
	and	al,CTS		;quit if
	jz	p0w3		;no CTS.
;
;See if any characters are waiting for output;
;if not, turn off Tx interrupts. The next output
;call will queue some up and call TxISR.
;
;NOTE: This counts by predecrement; it will go
;negative if the count was already zero.
;
	sub	out_count,1	;if it was <= 0
	jl	p0w2		;go disable

	mov	bx,out_tail
	inc	bx
	cmp	bx,offset out_end
	jb	p0w1
	mov	bx,offset out_beg
p0w1:	cmp	bx,out_head	;If empty??!
	je	p0w2		;then disable.
	mov	al,[bx]		;get a char,
	mov	out_tail,bx	;save ptr,
	mov	dx,bp
	add	dx,MDATA	;send it
	call	outp		;to the modem
	jmp	txint		;repeat
;
;No more characters to output; turn off Tx
;interrupts.
;
p0w2:	mov	out_count,0	;(if < 0)
p0w3:	mov	dx,bp
	add	dx,INTENB	;disable Tx
	mov	al,RDAINT + MDMINT;interrupts
	call	outp
p0w4:	ret

;
;Character input (RDA) interrupt. Put the
;character into the receive buffer, or throw
;it away if full.
;
;BP = 8250 base address.
;
rxint:	call	rtsctl		;RTS control

	mov	dx,bp
	add	dx,MLSTAT	;check RDA
	call	inp
	and	al,RDA		;exit if none
	jz	p0rz

	mov	dx,bp
	add	dx,MDATA	;get the char
	call	inp
	mov	bx,in_head
	cmp	in_tail,bx	;check full,
	je	rxint		;toss it if so,

	mov	[bx],al		;stash char,
	inc	bx
	cmp	bx,offset in_end ;wrap ptr,
	jb	p0r1
	mov	bx,offset in_beg
p0r1:	mov	in_head,bx
	inc	in_count	;count it,
	jmp	rxint		;get next
p0rz:	ret
;
;Rx interrupt buffer RTS control. Turn RTS on 
;or off depending on the condition of the 
;buffer; off if the buffer becomes full,
;on if becomes half empty.
;
rtsctl:
	mov	ax,IN_SIZE	;find room
	sub	ax,in_count	;remaining,
;
;If 1/2 empty, raise RTS if it is not already.
;
	cmp	ax,(IN_SIZE / 2)
	jb	rs1		;if 1/2 or less
	test	mdmbits,RTS	;full raise RTS
	jnz	rsz		;if its low.
	or	mdmbits,RTS	;set the bit,
	jmp short rs2		;go set it.
;
;If almost full, lower RTS if it is raised.
;
rs1:	cmp	ax,4		;if nearly full
	ja	rsz		;lower RTS
	mov	al,RTS
	test	mdmbits,al	;if high now
	jz	rsz
	not	al		;clear RTS bit
	and	mdmbits,al
rs2:	mov	al,mdmbits	;set control
	mov	dx,base		;register bits
	add	dx,MCNTRL
	call	outp
rsz:	ret

;
;Low level in and out routines. These slow 
;things down to accomodate the x86.
;
outp:
	out	dx,al
	ret

inp:	in	al,dx
	ret

; --------------------------------
;
;Set clock: Insert our routine in series
;with the existing clock.
;
func _init_tick
	call	fossil_setup
	mov	dx,55		;tick in Ms
	mov	al,8		;int #8
	test	fossil,1	;use FOSSIL
	jz	sc1		;if present
	mov	ah,7		;AH= get tick
	call	int14

sc1:	mov	tickint,dx	;set INTERVAL
	mov	intno,al	;INT NUMBER
	mov	cs:dataseg,ds	;set for ISR
	mov	al,intno	;vector number,
	call	getvec		;get vector
	mov	word ptr cs:oldvec,bx ;save it,
	mov	word ptr cs:oldvec + 2,es

	mov	al,intno
	mov	dx,cs		;set for ticker
	mov	ds,dx
	mov	dx,offset ticker
	call	setvec
endf _init_tick

;
;Remove our timer from the circuit.
;
func _uninit_tick
	mov	al,intno
	mov	dx,word ptr cs:oldvec
	mov	ds,word ptr cs:oldvec + 2
	call	setvec
endf _uninit_tick

;
; h= set_tick(&timer);
;
func _set_tick
	mov	cx,MAXTICK
	mov	ax,0	;AX == handle
st1:	mov	bx,ax
	shl	bx,1	;word ptr
	cmp	word ptr pile[bx],0
	jne	st2	;find a zero,
	mov	dx,arg0
	mov	pile[bx],dx ;store pointer
	jmp	st3	;return handle
st2:	inc	ax	;next handle ...
	loop	st1
	mov	ax,-1	;no free handles
st3:
endf _set_tick
;
; clr_tick(h);
;
func _clr_tick
	mov	ax,-1
	mov	bx,arg0
	cmp	bx,MAXTICK
	jae	ct1
	shl	bx,1
	mov	word ptr pile[bx],0
	mov	ax,0
ct1:
endf _clr_tick

page
;
;Interrupt service routine for
;the timer ticks. AX contains the number
;of milliseconds since the last tick. This
;must preserve all registers.
;
ticker:	sti			;interruptable
	push	ds
	push	si
	push	cx
	push	bx
	push	ax

	mov	ds,cs:dataseg	;set DS
	mov	cx,tickint	;interval
	xor	bx,bx		;index & count
	xor	ax,ax		;zero
;
;For each non-zero handle, add the interval in 
;Ms to each 32-bit timer.
;
tf1:	mov	si,pile[bx]
	or	si,si
	jz	tf2
	add	word ptr [si],cx ;add Ms since
	adc	word ptr [si + 2],ax;last tick
tf2:	add	bx,2
	cmp	bx,(MAXTICK * 2)
	jb	tf1

	pop	ax
	pop	bx
	pop	cx
	pop	si
	pop	ds
	jmp	dword ptr cs:[oldvec] ;chain.

;
;Set interrupt vector AL to DS:DX.
;
setvec:	push	es
	mov	bx,0
	mov	es,bx
	mov	bl,al
	mov	bh,0
	shl	bx,1
	shl	bx,1
	pushf
	cli
	mov	es:[bx],dx
	mov	es:[bx + 2],ds
	popf
	pop	es
	ret
;
;Get interupt vector AL, return in ES:BX.
;
getvec:	push	ds
	mov	di,0		;set DS to 0
	mov	ds,di		;(low mem)
	mov	ah,0
	mov	di,ax		;DI= int #
	shl	di,1
	shl	di,1		;DI= DWORD ptr
	pushf
	cli
	mov	bx,ds:[di]
	mov	es,ds:[di + 2]
	popf
	pop	ds
	ret

	end

