version	equ	2

	include	defs.asm	;SEE ENCLOSED COPYRIGHT MESSAGE

;Ported from Phil Karn's asy.c and slip.c, a C-language driver for the IBM-PC
;8250 by Russell Nelson.  Any bugs are due to Russell Nelson.

;/* PC/FTP Packet Driver source, conforming to version 1.05 of the spec
;*  Portions (C) Copyright 1988 Phil Karn
;*
;*  Permission is granted to any individual or institution to use, copy,
;*  modify, or redistribute this software and its documentation provided
;*  this notice and the copyright notices are retained.  This software may
;*  not be distributed for profit, either in original form or in derivative
;*  works.  Phil Karn makes no representations about the suitability
;*  of this software for any purpose.  PHIL KARN GIVES NO WARRANTY,
;*  EITHER EXPRESS OR IMPLIED, FOR THE PROGRAM AND/OR DOCUMENTATION
;*  PROVIDED, INCLUDING, WITHOUT LIMITATION, WARRANTY OF MERCHANTABILITY
;*  AND WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE.
;*/

code	segment	byte public
	assume	cs:code, ds:code

;8250 definitions
;Control/status register offsets from base address
THR	equ	0		;Transmitter holding register
RBR	equ	0		;Receiver buffer register
DLL	equ	0		;Divisor latch LSB
DLM	equ	1		;Divisor latch MSB
IER	equ	1		;Interrupt enable register
IIR	equ	2		;Interrupt ident register
FCR	equ	2		;16550 FIFO control register
LCR	equ	3		;Line control register
MCR	equ	4		;Modem control register
LSR	equ	5		;Line status register
MSR	equ	6		;Modem status register

;8250 Line Control Register
LCR_5BITS	equ	0	;5 bit words
LCR_6BITS	equ	1	;6 bit words
LCR_7BITS	equ	2	;7 bit words
LCR_8BITS	equ	3	;8 bit words
LCR_NSB		equ	4	;Number of stop bits
LCR_PEN		equ	8	;Parity enable
LCR_EPS		equ	10h	;Even parity select
LCR_SP		equ	20h	;Stick parity
LCR_SB		equ	40h	;Set break
LCR_DLAB	equ	80h	;Divisor Latch Access Bit

;16550 Line Control Register
FCR_RCVR	equ	002h	;Reset Receive FIFO
FCR_XMIT	equ	004h	;Reset Transmit FIFO
FCR_1		equ	001h	;One byte FIFO
FCR_4		equ	041h	;Four byte FIFO
FCR_8		equ	081h	;Eight byte FIFO
FCR_14		equ	0c1h	;Fourteen byte FIFO

;8250 Line Status Register
LSR_DR	equ	1	;Data ready
LSR_OE	equ	2	;Overrun error
LSR_PE	equ	4	;Parity error
LSR_FE	equ	8	;Framing error
LSR_BI	equ	10h	;Break interrupt
LSR_THRE equ	20h	;Transmitter line holding register empty
LSR_TSRE equ	40h	;Transmitter shift register empty

;8250 Interrupt Identification Register
IIR_IP		equ	1	;0 if interrupt pending
IIR_ID		equ	6	;Mask for interrupt ID
IIR_RLS		equ	6	;Receiver Line Status interrupt
IIR_RDA		equ	4	;Receiver data available interrupt
IIR_THRE	equ	2	;Transmitter holding register empty int
IIR_MSTAT	equ	0	;Modem status interrupt

;8250 interrupt enable register bits
IER_DAV	equ	1	;Data available interrupt
IER_TxE	equ	2	;Tx buffer empty interrupt
IER_RLS	equ	4	;Receive line status interrupt
IER_MS	equ	8	;Modem status interrupt

;8250 Modem control register
MCR_DTR	equ	1	;Data Terminal Ready
MCR_RTS	equ	2	;Request to Send
MCR_OUT1 equ	4	;Out 1 (not used)
MCR_OUT2 equ	8	;Master interrupt enable (actually OUT 2)
MCR_LOOP equ	10h	;Loopback test mode

;8250 Modem Status Register
MSR_DCTS equ	1	;Delta Clear-to-Send
MSR_DDSR equ	2	;Delta Data Set Ready
MSR_TERI equ	4	;Trailing edge ring indicator
MSR_DRLSD equ	8	;Delta Rx Line Signal Detect
MSR_CTS	equ	10h	;Clear to send
MSR_DSR equ	20h	;Data set ready
MSR_RI	equ	40h	;Ring indicator
MSR_RLSD equ	80h	;Received line signal detect

;Slip Definitions
FR_END		equ	0c0h		;Frame End
FR_ESC		equ	0dbh		;Frame Escape
T_FR_END	equ	0dch		;Transposed frame end
T_FR_ESC	equ	0ddh		;Transposed frame escape

	public	int_no
int_no		db	3,?		; interrupt number.
io_addr		dw	03f8h		; I/O address for COM1.
baud_rate	dw	?,?		; We support baud rates higher than 65535.
baudclk		label	word
		dd	115200		;1.8432 Mhz / 16

	public	driver_class, driver_type, driver_name
driver_class	db	6		;from the packet spec
driver_type	db	0		;from the packet spec
driver_name	db	'SLIP8250',0	;name of the driver.


recv_buf_size	dw	3000		;receive buffer size
recv_buf	dw	?		;->receive buffer
recv_buf_end	dw	?		;->after end of buffer
recv_buf_head	dw	?		;->next character to get
recv_buf_tail	dw	?		;->next character to store
recv_cnt	dw	?		;number of characters in the queue.

send_buf_size	dw	3000		;send buffer size
send_buf	dw	?		;->send buffer
send_buf_end	dw	?		;->after end of buffer
send_buf_head	dw	?		;->next character to get
send_buf_tail	dw	?		;->next character to store
send_cnt	dw	?		;number of characters in the queue.


	public	send_pkt
send_pkt:
;enter with ds:si -> packet, cx = packet length.
;exit with nc if ok, or else cy if error, dh set to error number.
	assume	ds:nothing

	push	cs
	pop	es
	mov	di,send_buf_tail
	mov	bx,send_cnt

	mov	al,FR_END		;Flush out any line garbage
	call	send_char

;Copy input to output, escaping special characters
send_pkt_1:
	lodsb
	cmp	al,FR_ESC		;escape FR_ESC with FR_ESC and T_FR_ESC
	jne	send_pkt_2
	mov	al,FR_ESC
	call	send_char
	mov	al,T_FR_ESC
	jmp	short send_pkt_3
send_pkt_2:
	cmp	al,FR_END		;escape FR_END with FR_ESC and T_FR_END
	jne	send_pkt_3
	mov	al,FR_ESC
	call	send_char
	mov	al,T_FR_END
send_pkt_3:
	call	send_char
	loop	send_pkt_1
	mov	al,FR_END		;terminate it with a FR_END
	call	send_char
	mov	send_buf_tail,di
	mov	send_cnt,bx

;Enable transmitter buffer empty interrupt.

	mov	ah,IER_TxE
	loadport
	setport	IER
	call	setbit
	ret


send_char:
;stuff the character in al into the transmit buffer, but only if there
;is enough room, otherwise ignore the char.
	assume	ds:nothing
	mov	dx,send_cnt		;too many chars?
	cmp	dx,send_buf_size
	je	send_char_1		;yes.
	inc	bx			;count up a char.
	stosb				;store the char.
	cmp	di,send_buf_end		;do we need to wrap around?
	jne	send_char_1		;no.
	mov	di,send_buf		;yes - reload with beginning.
send_char_1:
	ret


	public	get_address
get_address:
;get the address of the interface.
;enter with es:di -> place to get the address, cx = size of address buffer.
;exit with nc, cx = actual size of address, or cy if buffer not big enough.
	assume	ds:code
	clc
	ret


	public	reset_interface
reset_interface:
;reset the interface.
	assume	ds:code
	ret


;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type.
	extrn	recv_find: near

;called after we have copied the packet into the buffer.
;enter with ds:si ->the packet, cx = length of the packet.
	extrn	recv_copy: near

	extrn	count_in_err: near
	extrn	count_out_err: near

	public	recv
recv:
;called from the recv isr.  All registers have been saved, and ds=cs.
;Upon exit, the interrupt will be acknowledged.
	assume	ds:code
recv_2:
	loadport
	setport	IIR
	in	al,dx			;any interrupts at all?
	test	al,IIR_IP
	jne	recv_1			;no.
	and	al,IIR_ID
	cmp	al,IIR_RDA		;Receiver interrupt
	jne	recv_3
	call	asyrxint
	jmp	recv_2
recv_3:
	cmp	al,IIR_THRE		;Transmit interrupt
	jne	recv_4
	call	asytxint
	jmp	recv_2
recv_4:
;process IIR_RLS here.
recv_1:
	ret


;Process 8250 receiver interrupts
asyrxint:

asyrxint_2:
	loadport
	setport	LSR
	in	al,dx
	test	al,LSR_DR
	je	asyrxint_1

	setport	RBR
	in	al,dx
;Process incoming data;
; If buffer is full, we have no choice but
; to drop the character

	mov	cx,recv_cnt
	cmp	cx,recv_buf_size
	je	asyrxint_1

	push	ds
	pop	es
	mov	di,recv_buf_tail
	stosb

	cmp	di,recv_buf_end		;did we hit the end of the buffer?
	jne	asyrxint_3		;no.
	mov	di,recv_buf		;yes - wrap around.
asyrxint_3:
	mov	recv_buf_tail,di
	inc	recv_cnt

	cmp	al,FR_END		;might this be the end of a frame?
	jne	asyrxint_4		;no.
	call	recv_frame
asyrxint_4:

	jmp	asyrxint_2
asyrxint_1:
	ret


recv_frame:
	mov	si,recv_buf_head	;process characters.
	mov	bx,recv_cnt
	mov	cx,0			;count up the size here.
recv_frame_1:
	call	recv_char		;get a char.
	je	recv_frame_2		;go if no more chars.
	cmp	al,FR_ESC		;an escape?
	je	recv_frame_1		;yes - don't count this char.
	inc	cx			;no - count this one.
	jmp	recv_frame_1
recv_frame_2:

	jcxz	recv_frame_3		;count zero? yes - just free the frame.
;we don't need to set the type because none are defined for SLIP.
	push	si			;save si in case we reject it.
	push	bx
	mov	di,0			;but we avoid any segment end bullshit.
	call	recv_find		;look up our type.
	pop	bx
	pop	si

	mov	ax,es			;is this pointer null?
	or	ax,di
	je	recv_frame_3		;yes - just free the frame.

	push	cx
	push	es			;remember where the buffer pointer is.
	push	di

	mov	si,recv_buf_head	;process characters.
	mov	bx,recv_cnt
recv_frame_4:
	call	recv_char
	je	recv_frame_6		;yes - we're all done.
	cmp	al,FR_ESC		;an escape?
	jne	recv_frame_5		;no - just store it.

	call	recv_char		;get the next character.
	je	recv_frame_6
	cmp	al,T_FR_ESC
	mov	al,FR_ESC		;assume T_FR_ESC
	je	recv_frame_5		;yup, that's it - store FR_ESC
	mov	al,FR_END		;nope, store FR_END
recv_frame_5:
	stosb				;store the byte.
	jmp	recv_frame_4
recv_frame_6:
	mov	recv_buf_head,si	;we're skipped to the end.
	mov	recv_cnt,bx

	pop	si			;now give the frame to the client.
	pop	ds
	pop	cx
	assume	ds:nothing

	call	recv_copy
	assume	ds:code
	ret

recv_frame_3:
	mov	recv_buf_head,si	;remember the new starting point.
	mov	recv_cnt,bx
	ret


recv_char:
;enter with si -> receive buffer, bx = receive count.  Wrap around if needed.
;return with nz, al = next char.  Return zr if there are no more chars in
;  this frame.
	lodsb
	cmp	si,recv_buf_end
	jb	recv_char_1
	mov	si,recv_buf
recv_char_1:
	dec	bx
	je	recv_char_2
	cmp	al,FR_END
recv_char_2:
	ret


;Handle 8250 transmitter interrupts
asytxint:

	mov	si,send_buf_head
asytxint_2:
	loadport			;can we load another character?
	setport	LSR
	in	al,dx
	test	al,LSR_THRE
	je	asytxint_1		;no.

	lodsb				;fill up the transmit buffer.
	cmp	si,send_buf_end		;have we hit the end yet?
	jb	asytxint_3		;no.
	mov	si,send_buf
asytxint_3:
	setport	THR
	out	dx,al

	dec	send_cnt		;keep sending until we run out.
	jne	asytxint_2

;No more characters to transmit -- disable transmit interrupts.

	setport	IER			;Disable transmit interrupts
	mov	ah,IER_TxE
	call	clrbit
;Call completion interrupt here
asytxint_1:
	mov	send_buf_head,si
	ret


;Set bit(s) in I/O port
setbit:
;enter with dx = port, ah = bit to set.
	in	al,dx
	or	al,ah
	out	dx,al
	ret


;Clear bit(s) in I/O port
clrbit:
;enter with dx = port, ah = bit to set.
	in	al,dx
	not	al			;perform an and-not using DeMorgan's.
	or	al,ah
	not	al
	out	dx,al
	ret


;any code after this will not be kept after initialization.
end_resident	label	byte

	public	usage_msg
usage_msg	db	"usage: SLIP8250 packet_int_no [driver_class] [int_no] [io_addr] [baud_rate]",CR,LF,"   [send_buf_size] [recv_buf_size]",CR,LF
		db	'   The driver_class should be SLIP, KISS, AX.25, or a number.",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for SLIP8250, version ",'0'+version,CR,LF
		db	"Portions Copyright 1988 Phil Karn",CR,LF,'$'

approximate_msg	db	"Warning: This baud rate can only be approximated using the 8250",CR,LF
		db	"because it is not an even divisor of 115200",CR,LF,'$'

	extrn	set_recv_isr: near

;enter with si -> argument string, di -> word to store.
;if there is no number, don't change the number.
	extrn	get_number: near

;enter with si -> argument string.
;skip spaces and tabs.  Exit with si -> first non-blank char.
	extrn	skip_blanks: near


	public	parse_args
parse_args:
	call	skip_blanks
	mov	al,[si]			;now classify the argument.
	or	al,20h			;convert to lower case (assuming letter).
	cmp	al,'k'
	jne	parse_args_2
	mov	driver_class,10		;KISS, from packet spec.
	jmp	short parse_args_5
parse_args_2:
	cmp	al,'s'
	jne	parse_args_3
	mov	driver_class,6		;SLIP, from packet spec.
	jmp	short parse_args_5
parse_args_3:
	cmp	al,'a'
	jne	parse_args_4
	mov	driver_class,9		;AX.25, from packet spec.
	jmp	short parse_args_5
parse_args_4:
	mov	di,offset driver_class
	call	get_number
	jmp	short parse_args_6
parse_args_5:
	mov	al,[si]			;skip to the next blank or CR.
	cmp	al,' '
	je	parse_args_6
	cmp	al,CR
	je	parse_args_6
	inc	si			;skip the character.
	jmp	parse_args_5
parse_args_6:
	mov	di,offset int_no
	call	get_number
	mov	di,offset io_addr
	call	get_number
	mov	di,offset baud_rate
	call	get_number
	jc	parse_args_1		;did we really get one?
	mov	[di+2],bx		;yes - store the high word.
parse_args_1:
	mov	di,offset send_buf_size
	call	get_number
	mov	di,offset recv_buf_size
	call	get_number
	ret


	public	etopen
etopen:
	pushf
	cli

	loadport			;Purge the receive data buffer
	setport	RBR
	in	al,dx

	;Enable FIFO, set FIFO trigger level to 8
	mov	al,FCR_8
	setport	FCR
	out	dx,al

	;Set line control register: 8 bits, no parity
	mov	al,LCR_8BITS
	setport	LCR
	out	dx,al

	;Turn on receive interrupt enable in 8250, leave transmit
	; and modem status interrupts turned off for now
	mov	al,IER_DAV
	setport	IER
	out	dx,al

	;Set modem control register: assert DTR, RTS, turn on 8250
	; master interrupt enable (connected to OUT2)

	mov	al,MCR_DTR or MCR_RTS or MCR_OUT2
	setport	MCR
	out	dx,al

;compute the divisor given the baud rate.
	mov	dx,baudclk+2
	mov	ax,baudclk
	mov	bx,0
asy_speed_1:
	inc	bx
	sub	ax,baud_rate
	sbb	dx,baud_rate+2
	jnc	asy_speed_1
	dec	bx
	add	ax,baud_rate
	adc	dx,baud_rate+2
	or	ax,dx
	je	asy_speed_2

	mov	dx,offset approximate_msg
	mov	ah,9
	int	21h

asy_speed_2:

	loadport			;Purge the receive data buffer
	setport	RBR
	in	al,dx

	mov	ah,LCR_DLAB		;Turn on divisor latch access bit
	setport	LCR
	call	setbit

	mov	al,bl			;Load the two bytes of the divisor.
	setport	DLL
	out	dx,al
	mov	al,bh
	setport	DLM
	out	dx,al

	mov	ah,LCR_DLAB		;Turn off divisor latch access bit
	setport	LCR
	call	clrbit

	call	set_recv_isr		;Set interrupt vector to SIO handler

;set up the various pointers.
	mov	dx,offset end_resident
	mov	send_buf,dx
	mov	send_buf_head,dx
	mov	send_buf_tail,dx
	add	dx,send_buf_size
	mov	send_buf_end,dx

	mov	recv_buf,dx
	mov	recv_buf_head,dx
	mov	recv_buf_tail,dx
	add	dx,recv_buf_size
	mov	recv_buf_end,dx

	popf
	clc				;indicate no errors.
	ret

code	ends

	end
