;  Copyright, 1988-1992, Russell Nelson, Crynwr Software

;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General MakePublic License as published by
;   the Free Software Foundation, version 1.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General MakePublic License for more details.
;
;   You should have received a copy of the GNU General MakePublic License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;
;
;
;
;   Copyright 1993, University of Minnesota
;
;   Changes at U of M:     (slip@booombox.micro.umn.edu)
;
;   Changed many things to make it possible to set serial parameters
;   on the fly.
;   Fixed many timing problems.
;   Added output handshaking.
;   Cleaned up loop timing code.
;   Made all opens delayed.
;   Configuration is done by another program (PHONE.EXE)
;
;   Still need to:
;   Compute correct timing delays.
;   Add input handshaking.
;   Timeout hung handshaking properly.
;   38,400 baud loses a few characters.
;   Doesnt detect missing com port.
;


majver		equ	1		;version number of the infrastructure.

MAX_ADDR_LEN	equ	16		;maximum number of bytes in our address.

MAX_HANDLE	equ	10		;maximum number of handles.

MAX_P_LEN	equ	8		;maximum type length

MAX_MULTICAST	equ	8		;maximum number of multicast addresses.


HT	equ	09h
CR	equ	0dh
LF	equ	0ah

;
;  Packet Driver Error numbers
NO_ERROR	equ	0		;no error at all.
BAD_HANDLE	equ	1		;invalid handle number
NO_CLASS	equ	2		;no interfaces of specified class found
NO_TYPE		equ	3		;no interfaces of specified type found
NO_NUMBER	equ	4		;no interfaces of specified number found
BAD_TYPE	equ	5		;bad packet type specified
NO_MULTICAST	equ	6		;this interface does not support
					;multicast
CANT_TERMINATE	equ	7		;this packet driver cannot terminate
BAD_MODE	equ	8		;an invalid receiver mode was specified
NO_SPACE	equ	9		;operation failed because of
					;insufficient space
TYPE_INUSE	equ	10		;the type had previously been accessed,
					;and not released.
BAD_COMMAND	equ	11		;the command was out of range, or not
					;implemented
CANT_SEND	equ	12		;the packet couldn't be sent (usually
					;hardware error)
CANT_SET	equ	13		;hardware address couldn't be changed
					;(more than 1 handle open)
BAD_ADDRESS	equ	14		;hardware address has bad length or
					;format
CANT_RESET	equ	15		;Couldn't reset interface (more than
					;1 handle open).
BAD_IOCB	equ	16		;an invalid iocb was specified

;a few useful Ethernet definitions.
RUNT		equ	60		;smallest legal size packet, no fcs
GIANT		equ	1514		;largest legal size packet, no fcs
EADDR_LEN	equ	6		;Ethernet address length.
ARCADDR_LEN	equ	1

BLUEBOOK	equ	1
IEEE8023	equ	11




MakePublic      macro   Sym
;;;             Public  &Sym            ; no publics now...
                endm

MakeExternal    macro   Sym
;;;             Extrn   &Sym            ; no externs now...
                endm








;The following two macros are used to manipulate port addresses.
;Use loadport to initialize dx.  Use setport to set a specific port on
;the board.  setport remembers what the current port number is, but beware!
;setport assumes that code is being executed in the same order as the
;code is presented in the source file.  Whenever this assumption is violated,
;you need to enter another loadport.  Some, but not all examples are:
;in a loop with multiple setports, or a backward jump over a setport, or
;a forward jump over a setport.  If you have any doubt, consult the
;individual driver sources for examples of usage.  If you suspect that
;you have too few loadports, define the symbol "no_confidence" to a
;one.  This will force a loadport before every setport.  If you wish to turn
;it off for some of your code, redefine it to a zero.

loadport	macro
	mov	dx,io_addr
port_no	=	0
	endm

;change the port number from the current value to the new value.
setport	macro	new_port_no
	ifdef	no_confidence		;define if you suspect that you don't
	  if	no_confidence
		loadport		;  have enough loadports, i.e. dx is
	  endif
	endif				;  set to the wrong port.
	if	new_port_no - port_no EQ 1
		inc	dx
	else
		if	new_port_no - port_no EQ -1
			dec	dx
		else
			if	new_port_no - port_no NE 0
				add	dx,new_port_no - port_no
			endif
		endif
	endif
port_no	=	new_port_no
	endm

Print	macro
	call	DosPrint
        endm

Delay           Macro
                call  DelaySub
                endm



SlowIn	macro
	Delay
        in	al,dx
        endm


SlowOut	macro
	Delay
        out	dx,al
        endm


segmoffs	struc			; defines offs as 0, segm as 2
offs		dw	?
segm		dw	?
segmoffs	ends

CY	equ	0001h
EI	equ	0200h

iocb		struc			; as_send_pkt structure
buffer		dd	?		; Pointer to the buffer
len		dw	?		; Its length
flags		db	?		; Some flags
ret_code	db	?		; Completion code
upcall		dd	?		; I/O completion upcall
next		dd	?		; Private next pointer (queue)
resv		db	4 dup (?)	; Unused private data
iocb		ends

DONE	equ	1		; I/O complete flag
CALLME	equ	2		; Please upcall me flag


send_queueempty	macro
; Check if send queue is empty.
; Enter with interrupts disabled.
; Exit with zr (zero) if empty, nz (not zero) if not.
; Destroys ax.
	mov ax,	word ptr send_head	; Queue empty?
	or ax,	word ptr send_head+2
	endm

send_peekqueue	macro
; Peek into the queue and get the next entry.
; Enter with interrupts disabled.
; Exit with es:di -> iocb.
	les di, send_head	; Get head segment:offset
	endm

; Bits in sys_features
MICROCHANNEL	equ	02		; a micro channel computer
TWO_8259	equ	40h		; 2nd 8259 exists

; Bits in flagbyte
CALLED_ETOPEN	equ	1		; have called etopen
D_OPTION	equ	2		; delayed initialization
N_OPTION	equ	4		; Novell protocol conversion
W_OPTION	equ	8		; Windows upcall checking.

;  Copyright, 1988-1992, Russell Nelson, Crynwr Software

;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General MakePublic License as published by
;   the Free Software Foundation, version 1.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General MakePublic License for more details.
;
;   You should have received a copy of the GNU General MakePublic License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.



code	segment word
	assume	cs:code, ds:code

	MakePublic	phd_environ
	org	2ch
phd_environ	dw	?

	MakePublic	phd_dioa
	org	80h
phd_dioa	label	byte

	org	100h
start:
	jmp	start_1
	MakeExternal	start_1: near
	even				;put the stack on a word boundary.

;we use our dioa for a stack space.  Very hard usage has shown that only
;  27 bytes were being used, so 128 should be sufficient.
our_stack	label	byte


	MakeExternal	int_no: byte
	MakePublic	packet_int_no, is_at, sys_features, flagbyte,quiet

packet_int_no	db	60h,0,0,0	; interrupt to communicate.
is_at		db	0		; =1 if we're on an AT.
sys_features	db	0		; 2h = MC   40h = 2nd 8259
flagbyte	db	0
quiet		db	0

	even

functions	label	word
	dw	f_not_implemented	;0
	dw	f_driver_info		;1
	dw	f_access_type		;2
	dw	f_release_type		;3
	dw	f_send_pkt		;4
	dw	f_terminate		;5
	dw	f_get_address		;6
	dw	f_reset_interface	;7
	dw	f_stop			;8
	dw	f_not_implemented	;9
	dw	f_get_parameters	;10
	dw	f_not_implemented	;11
	dw	f_as_send_pkt		;12
	dw	f_drop_pkt		;13
	dw	f_ser_func		;14
	dw	f_not_implemented	;15
	dw	f_not_implemented	;16
	dw	f_not_implemented	;17
	dw	f_not_implemented	;18
	dw	f_not_implemented	;19
	dw	f_set_rcv_mode		;20
	dw	f_get_rcv_mode		;21
	dw	f_set_multicast_list	;22
	dw	f_get_multicast_list	;23
	dw	f_get_statistics	;24
	dw	f_set_address		;25

	MakeExternal	driver_class: byte
	MakeExternal	driver_type: byte
	MakeExternal	driver_name: byte
	MakeExternal	driver_function: byte
	MakeExternal	parameter_list: byte

	MakeExternal	send_pkt: near
	MakeExternal	as_send_pkt: near
	MakeExternal	drop_pkt: near
	MakeExternal	get_address: near
	MakeExternal	set_address: near
	MakeExternal	terminate: near
	MakeExternal	reset_interface: near
	MakeExternal	xmit: near
	MakeExternal	recv: near
	MakeExternal	recv_exiting: near
	MakeExternal	etopen: near

	MakeExternal	rcv_modes: word		;count of modes followed by mode handles.

	MakeExternal	set_multicast_list: near

linc	macro	n			; inc a 32 bit integer
	local	a
	inc	n			;increment the low word
	jne	a			;go if not overflow
	inc	n+2			;increment the high word
a:
	endm

per_handle	struc
in_use		db	0		;non-zero if this handle is in use.
packet_type	db	MAX_P_LEN dup(0);associated packet type.
packet_type_len	dw	0		;associated packet type length.
receiver	dd	0		;receiver handler.
receiver_sig	db	8 dup(?)	;signature at the receiver handler.
class		db	?		;interface class
per_handle	ends

handles		per_handle MAX_HANDLE dup(<>)
end_handles	label	byte

	MakePublic	multicast_count, multicast_addrs, multicast_broad
multicast_count	dw	0		;count of stored multicast addresses.
multicast_broad	db	0ffh,0ffh,0ffh,0ffh,0ffh,0ffh	; entry for broadcast
multicast_addrs	db	MAX_MULTICAST*EADDR_LEN dup(?)

have_my_address	db	0		;nonzero if our address has been set.
my_address	db	MAX_ADDR_LEN dup(?)
my_address_len	dw	?

rcv_mode_num	dw	3

free_handle	dw	0		; temp, a handle not in use
found_handle	dw	0		; temp, handle for our packet
receive_ptr	dd	0		; the pkt receive service routine

	MakePublic	send_head, send_tail
send_head	dd	0		; head of transmit queue
send_tail	dd	0		; tail of transmit queue

statistics_list	label	dword
packets_in	dw	?,?
packets_out	dw	?,?
bytes_in	dw	?,?
bytes_out	dw	?,?
errors_in	dw	?,?
errors_out	dw	?,?
packets_dropped	dw	?,?		;dropped due to no type handler.

savess		dw	?		;saved during the stack swap.
savesp		dw	?

regs	struc				; stack offsets of incoming regs
_ES	dw	?
_DS	dw	?
_BP	dw	?
_DI	dw	?
_SI	dw	?
_DX	dw	?
_CX	dw	?
_BX	dw	?
_AX	dw	?
_IP	dw	?
_CS	dw	?
_F	dw	?			; flags, Carry flag is bit 0
regs	ends

CY	equ	0001h
EI	equ	0200h


bytes	struc				; stack offsets of incoming regs
	dw	?			; es, ds, bp, di, si are 16 bits
	dw	?
	dw	?
	dw	?
	dw	?
_DL	db	?
_DH	db	?
_CL	db	?
_CH	db	?
_BL	db	?
_BH	db	?
_AL	db	?
_AH	db	?
bytes	ends

	MakePublic	our_isr, their_isr

their_isr	dd	0		; original owner of pkt driver int
our_isr:
	jmp	our_isr_0		;the required signature.
	db	'PKT DRVR',0
our_isr_0:
	assume	ds:nothing
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
	push	es
	cld
	mov	bx,cs			;set up ds.
	mov	ds,bx
	assume	ds:code
	mov	bp,sp			;we use bp to access the original regs.
	and	_F[bp],not CY		;start by clearing the carry flag.

	mov	bl,ah			;jump to the correct function.
	mov	bh,0
	cmp	bx,25			;only twenty five functions right now.
	mov	dh,BAD_COMMAND		;in case we find a bad number.
	ja	our_isr_error

	add	bx,bx			;*2
	call	functions[bx]
	jnc	our_isr_return

our_isr_error:
	mov	_DH[bp],dh
	or	_F[bp],CY		;return their carry flag.

our_isr_return:
	pop	es
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	iret

	MakePublic	re_enable_interrupts
re_enable_interrupts:
; Possibly re-enable interrupts.  We put this here so that other routines
; don't need to know how we put things on the stack.
	test	_F[bp], EI		; Were interrupts enabled on pkt driver entry?
	je	re_enable_interrupts_1	; No.
	sti				; Yes, re-enable interrupts now.
re_enable_interrupts_1:
	ret


f_not_implemented:
	mov	dh,BAD_COMMAND
	stc
	ret


f_driver_info:
;	As of 1.08, the handle is optional, so we no longer verify it.
;	call	verify_handle
	cmp	_AL[bp],0ffh		; correct calling convention?
	jne	f_driver_info_1		; ne = incorrect, fail

					;For enhanced PD, if they call
	cmp	_BX[bp],offset handles	;with a handle, give them the
					;class they think it is
	jb	default_handle
	cmp	_BX[bp],offset end_handles ;otherwise default to first class
	jae	default_handle
	mov	bx, _BX[bp]
	cmp	[bx].in_use,0		;if it's not in use, it's bad.
	je	default_handle
	mov	al, [bx].class
	mov	_CH[bp], al
	jmp	short got_handle

default_handle:
	mov	al,driver_class
	mov	_CH[bp],al
got_handle:

	mov	_BX[bp],majver		;version
	mov	al,driver_type
	cbw
	mov	_DX[bp],ax
	mov	_CL[bp],0		;number zero.
	mov	_DS[bp],ds		; point to our name in their ds:si
	mov	_SI[bp],offset driver_name
	mov	al,driver_function
	mov	_AL[bp],al
	clc
	ret
f_driver_info_1:
	stc
	ret


f_set_rcv_mode:
	call	verify_handle

	cmp	cx,rcv_mode_num		;are we already using that mode?
	je	f_set_rcv_mode_4	;yes, no need to check anything.

	mov	dx,bx			;remember our handle.
	mov	bx,offset handles	; check that all handles are free
f_set_rcv_mode_2:
	cmp	bx,dx			; is this our handle?
	je	f_set_rcv_mode_3	; yes, of course it's not free.
	cmp	[bx].in_use,0		; is this handle free?
	jne	f_set_rcv_mode_1	; ne = no, can't change
f_set_rcv_mode_3:
	add	bx,(size per_handle)	; next handle
	cmp	bx,offset end_handles	; examined all handles?
	jb	f_set_rcv_mode_2	; b = no, continue examination

	mov	cx,_CX[bp]		;get the desired receive mode.
	cmp	cx,rcv_modes		;do they have this many modes?
	jae	f_set_rcv_mode_1	;no - must be a bad mode for us.
	mov	bx,cx
	add	bx,bx			;we're accessing words, not bytes.
	mov	ax,rcv_modes[bx]+2	;get the handler for this mode.
	or	ax,ax			;do they have one?
	je	f_set_rcv_mode_1	;no - must be a bad mode for us.
	mov	rcv_mode_num,cx		;yes - remember the number and
	call	ax			;  call it.
f_set_rcv_mode_4:
	clc
	ret
f_set_rcv_mode_1:
	mov	dh,BAD_MODE
	stc
	ret


f_get_rcv_mode:
	call	verify_handle
	mov	ax,rcv_mode_num		;return the current receive mode.
	mov	_AX[bp],ax
	clc
	ret


f_set_multicast_list:
	mov	cx,_CX[bp]		;Tell them how much room they have.

;verify that they supplied an even number of EADDR's.
	mov	ax,cx
	xor	dx,dx
	mov	bx,EADDR_LEN
	div	bx
	or	dx,dx			;zero remainder?
	jne	f_set_multicast_list_2	;no, we don't have an even number of
					;  addresses.

	cmp	ax,MAX_MULTICAST	;is this too many?
	ja	f_set_multicast_list_3	;yes - return NO_SPACE
f_set_multicast_list_1:
	mov	multicast_count,ax	;remember the number of addresses.
	push	cs
	pop	es
	mov	di,offset multicast_addrs
	push	ds
	mov	ds,_ES[bp]		; get ds:si -> new list.
	mov	si,_DI[bp]
	push	cx
	rep	movsb
	pop	cx
	pop	ds

	mov	si,offset multicast_addrs
	call	set_multicast_list
	ret
f_set_multicast_list_2:
	mov	dh,BAD_ADDRESS
	stc
	ret
f_set_multicast_list_3:
	mov	dh,NO_SPACE
	stc
	ret


f_get_multicast_list:
	mov	_ES[bp],ds		;return what we have remembered.
	mov	_DI[bp],offset multicast_addrs
	mov	ax,EADDR_LEN		;multiply the count by the length.
	mul	multicast_count
	mov	_CX[bp],ax		;because they want total bytes.
	clc
	ret


f_get_statistics:
	call	verify_handle		;just in case.
	mov	_DS[bp],ds
	mov	_SI[bp],offset statistics_list
	clc
	ret


access_type_class:
	mov	dh,NO_CLASS
	stc
	ret

access_type_type:
	mov	dh,NO_TYPE
	stc
	ret

access_type_number:
	mov	dh,NO_NUMBER
	stc
	ret

access_type_bad:
	mov	dh,BAD_TYPE
	stc
	ret

;register caller of pkt TYPE
f_access_type:
	mov	bx, offset driver_class
access_type_9:
	mov	al, [bx]		;get the next class.
	inc	bx
	or	al,al			;end of the list?
	je	access_type_class	;class failed (story of my life)
	cmp	_AL[bp],al		;our class?
	jne	access_type_9		;no, try again
access_type_1:
	cmp	_BX[bp],-1		;generic type?
	je	access_type_2		;yes.
	mov	al,driver_type
	cbw
	cmp	_BX[bp],ax		;our type?
	jne	access_type_type	;no.
access_type_2:
	cmp	_DL[bp],0		;generic number?
	je	access_type_3
	cmp	_DL[bp],1		;our number?
	jne	access_type_number
access_type_3:
	cmp	_CX[bp],MAX_P_LEN	;is the type length too long?
	ja	access_type_bad		;yes - can't be ours.

; now we do two things--look for an open handle, and check the existing
; handles to see if they're replicating a packet type.

	mov	free_handle,0		;remember no free handle yet.
	mov	bx,offset handles
access_type_4:
	cmp	[bx].in_use,0		;is this handle in use?
	je	access_type_5		;no - don't check the type.
	mov	al, _AL[bp]		;is this handle the same class as
	cmp	al, [bx].class		;  they're want?
	jne	short access_type_6
	mov	es,_DS[bp]		;get a pointer to their type
	mov	di,_SI[bp]		;  from their ds:si to our es:di
	mov	cx,_CX[bp]		;get the minimum of their length
					;  and our length.  As currently
					;  implemented, only one receiver
					;  gets the packets, so we have to
					;  ensure that the shortest prefix
					;  is unique.
	cmp	cx,[bx].packet_type_len	;Are we less specific than they are?
	jb	access_type_8		;no.
	mov	cx,[bx].packet_type_len	;yes - use their count.
access_type_8:
	lea	si,[bx].packet_type
	or	cx,cx			; pass-all TYPE? (zero TYPE length)
	jne	access_type_7		; ne = no
	mov	bx,offset handles+(MAX_HANDLE-1)*(size per_handle)
	jmp	short access_type_5	; put pass-all last
access_type_7:
	repe	cmpsb
	jne	short access_type_6	;go look at the next one.
access_type_inuse:
	mov	dh,TYPE_INUSE		;a handle has been assigned for TYPE
	stc				;and we can't assign another
	ret
access_type_5:				;handle is not in use
	cmp	free_handle,0		;found a free handle yet?
	jne	access_type_6		;yes.
	mov	free_handle,bx		;remember a free handle
access_type_6:
	add	bx,(size per_handle)	;go to the next handle.
	cmp	bx,offset end_handles	;examined all handles?
	jb	access_type_4		;no, continue.

	mov	bx,free_handle		;did we find a free handle?
	or	bx,bx
	je	access_type_space	;no - return error.

	mov	[bx].in_use,1		;remember that we're using it.

	mov	ax,_DI[bp]		;remember the receiver type.
	mov	[bx].receiver.offs,ax
	mov	ax,_ES[bp]
	mov	[bx].receiver.segm,ax

	push	ds
	mov	ax,ds
	mov	es,ax
	mov	ds,_DS[bp]		;remember their type.
	mov	si,_SI[bp]
	mov	cx,_CX[bp]
	mov	es:[bx].packet_type_len,cx	; remember the TYPE length
	lea	di,[bx].packet_type
	rep	movsb

	lds	si,es:[bx].receiver	;copy the first 8 bytes
	lea	di,[bx].receiver_sig	; to the receiver signature.
	mov	cx,8/2
	rep	movsw

	pop	ds

	mov	al, _AL[bp]
	mov	[bx].class, al

	mov	_AX[bp],bx		;return the handle to them.

	clc
	ret


access_type_space:
	mov	dh,NO_SPACE
	stc
	ret

f_release_type:
	call	verify_handle		;mark this handle as being unused.
	mov	[bx].in_use,0
	clc
	ret


f_send_pkt:
;ds:si -> buffer, cx = length
; XXX Should re-enable interrupts here, but some drivers are broken.
; Possibly re-enable interrupts.
;	test _F[bp], EI		; Were interrupts enabled on pkt driver entry?
;	je	f_send_pkt_1	; No.
;	sti			; Yes, re-enable interrupts now.
;f_send_pkt_1:

;following two instructions not needed because si and cx haven't been changed.
;	mov	si,_SI[bp]
;	mov	cx,_CX[bp]	; count of bytes in the packet.
	linc	packets_out
	add	bytes_out.offs,cx	;add up the received bytes.
	adc	bytes_out.segm,0

	push	ds		; set up proper ds for the buffer
	mov	ds,_DS[bp]	; address of buffer from caller's ds.
	assume	ds:nothing, es:nothing

; If -n option take Ethernet encapsulated Novell IPX packets (from BYU's 
; PDSHELL) and change them to be IEEE 802.3 encapsulated.
EPROT_OFF	equ	EADDR_LEN*2
	test	cs:flagbyte,N_OPTION
	jz	f_send_pkt_2
	cmp	ds:[si].EPROT_OFF,3781h ; if not Novell (prot 8137)
	jne	f_send_pkt_2		;  don't tread on it
	push	ax			; get scratch reg
	mov	ax,[si].EPROT_OFF+4	; get len
	xchg	ah,al
	inc	ax			; make even (rounding up)
	and	al,0feh
	xchg	ah,al
	mov	ds:[si].EPROT_OFF,ax	; save in prot field
	pop	ax			; restore old contents
f_send_pkt_2:
	call	send_pkt
	pop	ds
	assume	ds:code
	ret


f_as_send_pkt:
;es:di -> iocb.
	test	driver_function,4	; is this a high-performance driver?
	je	f_as_send_pkt_2		; no.
; Possibly re-enable interrupts.
	test _F[bp], EI			; Were interrupts enabled on pkt driver entry?
	je	f_as_send_pkt_1		; No.
	sti				; Yes, re-enable interrupts now.
f_as_send_pkt_1:
	push	ds			; set up proper ds for the buffer
	lds	si,es:[di].buffer	; ds:si -> buffer
	assume	ds:nothing
	mov	cx,es:[di].len		; cx = length
	linc	packets_out
	add	bytes_out.offs,cx	; add up the received bytes.
	adc	bytes_out.segm,0

;ds:si -> buffer, cx = length, es:di -> iocb.
	call	as_send_pkt
	pop	ds
	assume	ds:code
	ret
f_as_send_pkt_2:
	mov dh,	BAD_COMMAND		; return an error.
	stc
	ret


f_drop_pkt:
; es:di -> iocb.
	test	driver_function,4	; is this a high-performance driver?
	je	f_as_send_pkt_1		; no.
	push	ds			; Preserve ds
	mov	si,offset send_head	; Get head offset
dp_loop:
	mov	ax,ds:[si]		; Get offset
	mov	dx,ds:[si+2]		; Get segment
	mov	bx,ax
	or	bx,dx			; End of list?
	je	dp_endlist		; Yes
	cmp	ax,di			; Offsets equal?
	jne	dp_getnext		; No
	mov	bx,es
	cmp	dx,bx			; Segments equal?
	jne	dp_getnext		; No
	call	drop_pkt		; Pass to driver
	les	di,es:[di].next		; Get next segment:offset
	mov	ds:[si],di		; Set next offset
	mov	ds:[si+2],es		; Set next segment
	pop	ds			; Restore ds
	clc
	ret
dp_getnext:
	mov	ds,dx			; Get next segment
	mov	si,ax			; Get next iocb offset
	lea	si,ds:[si].next		; Get next iocb next ptr offset
	jmp	dp_loop			; Try again
dp_endlist:
	pop	ds			; Restore ds
	mov	dh,BAD_IOCB		; Return error
	stc				; Set carry
	ret

        MakeExternal	ser_funcs:near
f_ser_func:   				; direct serial functions
	call	ser_funcs
	clc
	ret

f_terminate:
	call	verify_handle		; must have a handle

f_terminate_1:
	mov	[bx].in_use,0		; mark handle as free
	mov	bx,offset handles	; check that all handles are free
f_terminate_2:
	cmp	[bx].in_use,0		; is this handle free?
	jne	f_terminate_4		; ne = no, so can't exit completely
	add	bx,(size per_handle)	; next handle
	cmp	bx,offset end_handles	; examined all handles?
	jb	f_terminate_2		; b = no, continue examination

;
; Now disable interrupts
;
	call	int_dis
	call	terminate		;terminate the hardware.

	mov	al,packet_int_no	;release our_isr.
	mov	ah,25h
	push	ds
	lds	dx,their_isr
	int	21h
	pop	ds

;
; Now free our memory
;
	push	cs
	pop	es
	mov	ah,49h
	int	21h
	clc
	ret
f_terminate_4:
	mov	dh, CANT_TERMINATE
	stc
	ret


	MakePublic	int_dis
int_dis:
	mov	al,int_no
	or	al,al			;are they using a hardware interrupt?
	je	f_term_no_irq		;no.

	call	maskint

;
; Now return the interrupt to their handler.
;
	mov	ah,25h			;get the old interrupt into es:bx
	mov	al,int_no
	add	al,8
	cmp	al,8+8			;is it a slave 8259 interrupt?
	jb	f_term_3		;no.
	add	al,70h - (8+8)		;map it to the real interrupt.
f_term_3:
	push	ds
	lds	dx,their_recv_isr
	int	21h
	pop	ds

f_term_no_irq:
	ret




f_get_address:
;	call	verify_handle
;	mov	es,_ES[bp]		; get new one
;	mov	di,_DI[bp]		; get pointer, es:di is ready
	mov	cx,_CX[bp]		;Tell them how much room they have.
	cmp	have_my_address,0	;has our address been set?
	jne	get_address_set		;yes - go report it.
	call	get_address		;no, can we get the address?
	jc	get_address_space	;no - we must not have enough space.
	mov	_CX[bp],cx		;Tell them how long our address is.
	clc
	ret
get_address_set:
	cmp	cx,my_address_len	;is there enough room?
	jb	get_address_space	;no.
	mov	cx,my_address_len	;yes - get our address length.
	mov	_CX[bp],cx		;Tell them how long our address is.
	mov	si,offset my_address	;copy it into their area.
	rep	movsb
	clc
	ret

get_address_space:
	mov	dh,NO_SPACE
	stc
	ret


f_set_address:
	mov	bx,offset handles
	mov	cl,0			;number of handles in use.
f_set_address_1:
	add	cl,[bx].in_use		;is this handle in use?
	add	bx,(size per_handle)	;go to the next handle.
	cmp	bx,offset end_handles
	jb	f_set_address_1

	cmp	cl,1			;more than one handle in use?
	ja	f_set_address_inuse	;yes - we can't set the address

	mov	ds,_ES[bp]		; set new one
	assume	ds:nothing
	mov	si,_DI[bp]		; set pointer, ds:si is ready
	mov	cx,_CX[bp]		;Tell them how much address is being set.
	call	set_address
;set_address restores ds.
	jc	f_set_address_exit	;Did it work?
	mov	_CX[bp],cx		;yes - return our address length.

	cmp	cx,MAX_ADDR_LEN		;is it too long for us to remember?
	ja	f_set_address_too_long	;yes, return a too-long error.

	mov	ds,_ES[bp]		; set new one
	mov	si,_DI[bp]		; set pointer, ds:si is ready
	mov	ax,cs
	mov	es,ax
	mov	my_address_len,cx	;remember how long our address is.
	mov	di,offset my_address
	rep	movsb
	mov	have_my_address,1
	mov	ds,ax			;restoer ds.
	assume	ds:code
	clc
	ret
f_set_address_inuse:
	mov	dh,CANT_SET
	stc
	ret
f_set_address_too_long:
	mov	dh,NO_SPACE
	stc
f_set_address_exit:
	ret


f_reset_interface:
	call	verify_handle
	call	reset_interface
	clc
	ret


; Stop the packet driver doing upcalls. Also a following terminate will
; always succed (no in use handles any longer).
f_stop:
	mov	bx,offset handles
f_stop_2:
	mov	[bx].in_use,0
	add	bx,(size per_handle)	; next handle
	cmp	bx,offset end_handles
	jb	f_stop_2
	clc
	ret


f_get_parameters:
;strictly speaking, this function only works for high-performance drivers.
	test	driver_function,4	;is this a high-performance driver?
	jne	f_get_parameters_1	;yes.
	mov	dh,BAD_COMMAND		;no - return an error.
	stc
	ret
f_get_parameters_1:
	mov	_ES[bp],cs
	mov	_DI[bp],offset parameter_list
	clc
	ret


verify_handle:
;Ensure that their handle is real.  If it isn't, we pop off our return
;address, and return to *their* return address with cy set.
	mov	bx,_BX[bp]		;get the handle they gave us
	cmp	bx,offset handles
	jb	verify_handle_bad	;no - must be bad.
	cmp	bx,offset end_handles
	jae	verify_handle_bad	;no - must be bad.
	cmp	[bx].in_use,0		;if it's not in use, it's bad.
	je	verify_handle_bad
	ret
verify_handle_bad:
	mov	dh,BAD_HANDLE
	add	sp,2			;pop off our return address.
	stc
	ret


	MakePublic	set_recv_isr
set_recv_isr:
	mov	ah,35h			;get the old interrupt into es:bx
	mov	al,int_no		;board's interrupt vector
	or	al,al
	je	set_isr_no_irq

	add	al,8
	cmp	al,8+8			;is it a slave 8259 interrupt?
	jb	set_recv_isr_1		;no.

	add	al,70h - 8 - 8		;map it to the real interrupt.

set_recv_isr_1:
	int	21h
	mov	their_recv_isr.offs,bx	;remember the old seg:off.
	mov	their_recv_isr.segm,es

	mov	ah,25h			;now set our recv interrupt.
	mov	dx,offset recv_isr
	int	21h

	mov	al,int_no		; Now enable interrupts
	call	unmaskint

set_isr_no_irq:
	ret



	MakePublic	count_in_err
count_in_err:
	assume	ds:nothing
	linc	errors_in
	ret

	MakePublic	count_out_err
count_out_err:
	assume	ds:nothing
	linc	errors_out
	ret

 	MakePublic	DelaySub

DelaySub:
         push   cx
         pop    cx
         ret

their_recv_isr	dd	0		; original owner of board int

;
; I have had a problem with some hardware which under extreme LAN loading
; conditions will re-enter the recv_isr. Since the 8259 interrupts for
; the card are masked off, and the card's interrupt mask register is 
; cleared (in 8390.asm at least) disabling the card from interrupting, this
; is clearly a hardware problem. Due to the low frequencey of occurance, and
; extreme conditions under which this happens, it is not lilely to be fixed
; in hardware any time soon, plus retrofitting of hardware in the field will
; not happen. To protect the driver from the adverse effects of this I am
; adding a simple re-entrancy trap here.  - gft - 910617
;

in_recv_isr	db 	0	; flag to trap re-entrancy

recv_isr:
	cmp	in_recv_isr, 0
	je	no_re_enter
	iret

no_re_enter:
	mov	in_recv_isr, 1

; I realize this re-entrancy trap is not perfect, you could be re-entered
; anytime before the above instruction. However since the stacks have not
; been swapped re-entrancy here will only appear to be a spurious interrupt.
; - gft - 910617

; In order to achieve back-to-back packet transmissions, we handle the
; latency-critical portion of transmit interrupts first.  The xmit
; interrupt routine should only start the next transmission, but do
; no other work.  It may only touch ax and dx (the only register necessary
; for doing "out" instructions) unless it first pushes any other registers
; itself.
	push	ax
	push	dx
	call	xmit

; Now switch stacks, push remaining registers, and do remaining interrupt work.
	push	ds
	mov	ax,cs			;ds = cs.
	mov	ds,ax
	assume	ds:code

	mov	savesp,sp
	mov	savess,ss

	mov	ss,ax
	mov	sp,offset our_stack
	cld

	push	bx
	push	cx
	push	si
	push	di
	push	bp
	push	es

; The following comment is wrong in that we now do a specific EOI command,
; and because we don't enable interrupts (even though we should).

; Chips & Technologies 8259 clone chip seems to be very broken.  If you
; send it a Non Specific EOI command, it clears all In Service Register
; bits instead of just the one with the highest priority (as the Intel
; chip does and clones should do).  This bug causes our interrupt
; routine to be reentered if: 1. we reenable processor interrupts;
; 2. we reenable device interrupts; 3. a timer or other higher priority
; device interrupt now comes in; 4. the new interrupting device uses
; a Non Specific EOI; 5. our device interrupts again.  Because of
; this bug, we now completely mask our interrupts around the call
; to "recv", the real device interrupt handler.  This allows us
; to send an EOI instruction to the 8259 early, before we actually
; reenable device interrupts.  Since the interrupt is masked, we
; are still guaranteed not to get another interrupt from our device
; until the interrupt handler returns.  This has another benefit:
; we now no longer prevent other devices from interrupting while our
; interrupt handler is running.  This is especially useful if we have
; other (multiple) packet drivers trying to do low-latency transmits.
	mov	al,int_no	; Disable further device interrupts
	call	maskint

; The following is from Bill Rust, <wjr@ftp.com>
; this code dismisses the interrupt at the 8259. if the interrupt number
;  is > 8 then it requires fondling two PICs instead of just one.
	mov	al, int_no	; get hardware int #
	cmp	al, 8		; see if its on secondary PIC
	jg	recv_isr_4
	add	al, 60h		; make specific EOI dismissal
	out	20h, al
	jmp	recv_isr_3	; all done

recv_isr_4:
	add	al,60h - 8	; make specific EOI (# between 9 & 15).
	out	0a0h,al		; Secondary 8259 (PC/AT only)
	mov	al,62h		; Acknowledge on primary 8259.
	out	20h,al
recv_isr_3:

;	sti				; Interrupts are now completely safe
	call	recv

	cli				;interrupts *must* be off between
					;here and the stack restore, because
					;if we have one of our interrupts
					;pending, we would trash our stack.
;
; According to Novell IMSP Technical Memo #2, Lost Interrupts and NetWare,
; some LAN controllers can glitch the interrupt line when the interrupt
; mask register on the LAN controller is enabled to allow the LAN controller
; to interrupt. This can cause spurious/lost interrupt problems, especially
; on some 486 processors which latch the int line to the processor. To prevent
; glitches from being propogated throug, move the call to recv_exiting (re-
; enables interrupts on the LAN card) to before when the 8259 interrupts are
; unmasked. - gft - 910617

	call	recv_exiting

	mov	al,int_no	; Now reenable device interrupts
	call	unmaskint

	pop	es
	pop	bp
	pop	di
	pop	si
	pop	cx
	pop	bx

	mov	ss,savess
	mov	sp,savesp

; move the next call up to above the call unmaskint - gft - 910617
;	call	recv_exiting		;this routine can enable interrupts.
; DDP - This is a BIG mistake.  This routine SHOULD NOT enable interrupts.
;	doing so can cause interrupt recursion and blow your stack.
;	Processor interrupts SHOULD NOT be enabled after enabling device
;	interrupts until after the "iret".  You will lose atleast 12 bytes
;	on the stack for each recursion.

	pop	ds
	assume	ds:nothing
	pop	dx
	pop	ax
	mov	in_recv_isr, 0	; clear the re-entrancy flag - gft - 901617
	iret


	MakePublic	maskint
maskint:
	or	al,al			;are they using a hardware interrupt?
	je	maskint_1		;no, don't mask off the timer!

	assume	ds:code
	mov	dx,21h			;assume the master 8259.
	cmp	al,8			;using the slave 8259 on an AT?
	jb	mask_not_irq2
	mov	dx,0a1h			;go disable it on slave 8259
	sub	al,8
mask_not_irq2:
	mov	cl,al

	SlowIn				;disable them on the correct 8259.
	mov	ah,1			;set the bit.
	shl	ah,cl
	or	al,ah
;
; 500ns Stall required here, per INTEL documentation for eisa machines
; - gft - 910617
;
	Delay
	SlowOut

maskint_1:
	ret


	MakePublic	unmaskint
unmaskint:
	assume	ds:code
	mov	dx,21h			;assume the master 8259.
	mov	cl,al
	cmp	cl,8			;using the slave 8259 on an AT?
	jb	unmask_not_irq2		;no

	SlowIn				;get master mask
	and	al,not (1 shl 2)	; and clear slave cascade bit in mask
	SlowOut				;set new master mask (enable slave int)
;
; 500ns Stall required here, per INTEL documentation for eisa machines
; - gft - 910617
;
	mov	dx,0a1h			;go enable int on slave 8259
	sub	cl,8

unmask_not_irq2:

	SlowIn				;enable interrupts on the correct 8259.
	mov	ah,1			;clear the bit.
	shl	ah,cl
	not	ah
	and	al,ah
;
; 500ns Stall required here, per INTEL documentation for eisa machines
; - gft - 910617
;
	SlowOut
	ret


	MakePublic	recv_find
recv_find:
;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type, dl = packet class.
;exit with es:di = 0 if the packet is not desired, or es:di -> packet buffer
;  to be filled by the driver.
	assume	ds:code, es:nothing
	push	cx

; If -n option take IEEE 802.3 encapsulated packets that could be Novell IPX 
; and make them Ethernet encapsulated Novell IPX packets (for PDSHELL).
	test	flagbyte,N_OPTION
	jz	not_n_op

; Make IEEE 802.3-like packets that could be Novell IPX into BlueBook class
; Novell type 8137 packets.
	cmp	dl,IEEE8023		;Is this an IEEE 802.3 packet?
	jne	recv_not_802_3		;no
	cmp	word ptr es:[di],0ffffh	;if this word not ffff
	jne	recv_not_8137		;  then not Novell
	sub	di,2			; back it up to the 8137 word.
	mov	es:[di],3781h		; fake as Novell protocol (8137)
	mov	dl,BLUEBOOK
	jmp	short recv_not_8137
recv_not_802_3:
; Convert incoming Ethernet type 8137 IPX packets to type 8138, as with -n in 
; effect we can't send type 8137, and it will only confuse Netware.
	cmp	dl,BLUEBOOK		;Is this a BLUEBOOK packet?
	jne	recv_not_8137		;no, don't change it.
	cmp	word ptr es:[di],3781h	;Is it an 8137 packet?
	jne	recv_not_8137		;no, don't change it.
	mov	es:[di],word ptr 3881h	;yes, mung it slightly.
recv_not_8137:
not_n_op:

	mov	bx,offset handles
recv_find_1:
	cmp	[bx].in_use,0		;is this handle in use?
	je	recv_find_2		;no - don't check the type.

	mov	ax,[bx].receiver.offs	;do they have a receiver?
	or	ax,[bx].receiver.segm
	je	recv_find_2		;no - they're not serious about it.

	mov	cx,[bx].packet_type_len	;compare the packets.
	lea	si,[bx].packet_type
	jcxz	recv_find_3		;if cx is zero, they want them all.

	cmp	[bx].class, dl		;is this the right class?
	jne	recv_find_2		;no- don't bother

	push	di
	repe	cmpsb
	pop	di
	je	recv_find_3		;we've got it!
recv_find_2:
	add	bx,(size per_handle)	;go to the next handle.
	cmp	bx,offset end_handles
	jb	recv_find_1

	linc	packets_dropped

	pop	cx			;we didn't find it -- discard it.
recv_find_5:
	xor	di,di			;"return" a null pointer.
	mov	es,di
	ret
recv_find_3:
	pop	cx			; the packet_length

	linc	packets_in
	add	bytes_in.offs,cx	;add up the received bytes.
	adc	bytes_in.segm,0

	les	di,[bx].receiver	;remember the receiver upcall.
	mov	receive_ptr.offs,di
	mov	receive_ptr.segm,es

	test	flagbyte,W_OPTION	;did they select the Windows option?
	je	recv_find_6		;no, don't check for the upcall.

; does the receiver signature match whats currently in memory?  if not,
; jump to fake return
	push	si
	push	cx
	lea	si,[bx].receiver_sig
	mov	cx,8/2
	repe	cmpsw
	pop	cx
	pop	si
	jne	recv_find_5
recv_find_6:

	mov	found_handle,bx		;remember what our handle was.
	mov	ax,0			;allocate request.
	stc				;with stc, flags must be an odd number
	push	ax			; save a number that cant be flags
	pushf				;save flags in case iret used.
	call	receive_ptr		;ask the client for a buffer.
	; on return, flags should be at top of stack. if an IRET has been used,
	; then 0 will be at the top of the stack
	pop	bx
	cmp	bx,0
	je	recv_find_4		;0 is at top of stack
	add	sp,2
recv_find_4:
	ret


	MakePublic	recv_copy
recv_copy:
;called after we have copied the packet into the buffer.
;enter with ds:si ->the packet, cx = length of the packet.
;preserve bx.
	assume	ds:nothing, es:nothing

	push	bx
	mov	bx,found_handle
	mov	ax,1			;store request.
	clc				;with clc, flags must be an even number
	push	ax			; save a number that can't be flags
	pushf				;save flags incase iret used.
	call	receive_ptr		;ask the client for a buffer.
	pop	bx
	cmp	bx,1			;if this is a 1, IRET was used.
	je	recv_copy_1
	pop	bx
recv_copy_1:
	pop	bx
	ret

	MakePublic	send_queue
send_queue:
; Queue an iocb.
; Enter with es:di -> iocb, interrupts disabled.
; Destroys ds:si.
	assume	ds:nothing, es:nothing
	mov	es:[di].next.offs,0	; Zero next offset
	mov	es:[di].next.segm,0	; Zero next segment
	mov	si,send_head.offs	; Queue empty?
	or	si,send_head.segm
	jnz	sq_notempty		; No
	mov	send_head.offs,di	; Set head offset
	mov	send_head.segm,es	; Set head segment
	jmp	sq_settail
sq_notempty:				; Queue is not empty
	lds	si,send_tail		; Get tail segment:offset
	mov	ds:[si].next.offs,di	; Set next offset
	mov	ds:[si].next.segm,es	; Set next segment
sq_settail:
	mov	send_tail.offs,di	; Set tail offset
	mov	send_tail.segm,es	; Set tail segment
	ret


	MakePublic	send_dequeue
send_dequeue:
; Dequeue an iocb and possibly call its upcall.
; Enter with device or processor interrupts disabled, ah = return code.
; Exits with es:di -> iocb; destroys ds:si, ax, bx, cx, dx, bp.
	assume	ds:nothing, es:nothing
	les	di,send_head		; Get head segment:offset
	lds	si,es:[di].next		; Get next segment:offset
	mov	send_head.offs, si	; Set head offset
	mov	send_head.segm, ds	; Set head segment
	or	es:flags[di], DONE	; Mark done
	mov	es:ret_code[di], ah	; Set retcode
	test	es:[di].flags,CALLME	; Does he want an upcall?
	je	send_dequeue_1		; No.
	push	es			; Push iocb segment
	push	di			;  and offset
	clc				; Clear carry.
	mov	ax,1			; Push a number that cant be flags.
	push	ax
	pushf				; Save flags in case iret used.
	call	es:[di].upcall		; Call the client.
	pop	ax			; Pop first word.
	cmp	ax,1			; If this is a 1, IRET was used.
	je	send_dequeue_2		; Far return used.
	add	sp,2			; Pop flags.
send_dequeue_2:
	pop	di			; Pop iocb segment
	pop	es			;  and offset
send_dequeue_1:
	ret


code	ends

;;;grg	equ	99

base		=	50

Alloc   macro   var,len
&var    equ     base*2
base    =     base+1+0&len
        endm

                Alloc   col_chout
                Alloc   col_chin
                Alloc   col_ovrr,2
                Alloc   col_frame,2
                Alloc   col_serfull,2
                Alloc   col_TCPFull,2
                Alloc   col_InIn
                Alloc   col_InOut
                Alloc   col_inmove
                Alloc   col_paks

Show	macro	Col,Ch
  ifdef  grg
  	push	es
	push	ax
        mov	ax,0B800h
        mov	es,ax
        mov	byte ptr es:[&col],&Ch		; twiddle screen if buf full
	pop	ax
	pop	es
  endif
  	endm


ShowInc	macro	Col,Ch
  ifdef  grg
  	push	es
	push	ax
        mov	ax,0B800h
        mov	es,ax
        mov	byte ptr es:[&col],&Ch
        inc	byte ptr es:[&col+2]
	pop	ax
	pop	es
  endif
  	endm


TheVersion	equ	1
majver		equ	1		;version number of the infrastructure.

MAX_ADDR_LEN	equ	16		;maximum number of bytes in our address.

MAX_HANDLE	equ	10		;maximum number of handles.

MAX_P_LEN	equ	8		;maximum type length

MAX_MULTICAST	equ	8		;maximum number of multicast addresses.

;  Copyright, 1988-1992, Russell Nelson, Crynwr Software

;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General MakePublic License as published by
;   the Free Software Foundation, version 1.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General MakePublic License for more details.
;
;   You should have received a copy of the GNU General MakePublic License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

HT	equ	09h
CR	equ	0dh
LF	equ	0ah

;
;  Packet Driver Error numbers
NO_ERROR	equ	0		;no error at all.
BAD_HANDLE	equ	1		;invalid handle number
NO_CLASS	equ	2		;no interfaces of specified class found
NO_TYPE		equ	3		;no interfaces of specified type found
NO_NUMBER	equ	4		;no interfaces of specified number found
BAD_TYPE	equ	5		;bad packet type specified
NO_MULTICAST	equ	6		;this interface does not support
					;multicast
CANT_TERMINATE	equ	7		;this packet driver cannot terminate
BAD_MODE	equ	8		;an invalid receiver mode was specified
NO_SPACE	equ	9		;operation failed because of
					;insufficient space
TYPE_INUSE	equ	10		;the type had previously been accessed,
					;and not released.
BAD_COMMAND	equ	11		;the command was out of range, or not
					;implemented
CANT_SEND	equ	12		;the packet couldn't be sent (usually
					;hardware error)
CANT_SET	equ	13		;hardware address couldn't be changed
					;(more than 1 handle open)
BAD_ADDRESS	equ	14		;hardware address has bad length or
					;format
CANT_RESET	equ	15		;Couldn't reset interface (more than
					;1 handle open).
BAD_IOCB	equ	16		;an invalid iocb was specified

;a few useful Ethernet definitions.
RUNT		equ	60		;smallest legal size packet, no fcs
GIANT		equ	1514		;largest legal size packet, no fcs
EADDR_LEN	equ	6		;Ethernet address length.
ARCADDR_LEN	equ	1

BLUEBOOK	equ	1
IEEE8023	equ	11

;The following two macros are used to manipulate port addresses.
;Use loadport to initialize dx.  Use setport to set a specific port on
;the board.  setport remembers what the current port number is, but beware!
;setport assumes that code is being executed in the same order as the
;code is presented in the source file.  Whenever this assumption is violated,
;you need to enter another loadport.  Some, but not all examples are:
;in a loop with multiple setports, or a backward jump over a setport, or
;a forward jump over a setport.  If you have any doubt, consult the
;individual driver sources for examples of usage.  If you suspect that
;you have too few loadports, define the symbol "no_confidence" to a
;one.  This will force a loadport before every setport.  If you wish to turn
;it off for some of your code, redefine it to a zero.

loadport	macro
	mov	dx,io_addr
port_no	=	0
	endm

;change the port number from the current value to the new value.
setport	macro	new_port_no
	ifdef	no_confidence		;define if you suspect that you don't
	  if	no_confidence
		loadport		;  have enough loadports, i.e. dx is
	  endif
	endif				;  set to the wrong port.
	if	new_port_no - port_no EQ 1
		inc	dx
	else
		if	new_port_no - port_no EQ -1
			dec	dx
		else
			if	new_port_no - port_no NE 0
				add	dx,new_port_no - port_no
			endif
		endif
	endif
port_no	=	new_port_no
	endm

Print	macro
	call	DosPrint
        endm

Delay           Macro
                call  DelaySub
                endm



SlowIn	macro
	Delay
        in	al,dx
        endm


SlowOut	macro
	Delay
        out	dx,al
        endm


segmoffs	struc			; defines offs as 0, segm as 2
offs		dw	?
segm		dw	?
segmoffs	ends

CY	equ	0001h
EI	equ	0200h

iocb		struc			; as_send_pkt structure
buffer		dd	?		; Pointer to the buffer
len		dw	?		; Its length
flags		db	?		; Some flags
ret_code	db	?		; Completion code
upcall		dd	?		; I/O completion upcall
next		dd	?		; Private next pointer (queue)
resv		db	4 dup (?)	; Unused private data
iocb		ends

DONE	equ	1		; I/O complete flag
CALLME	equ	2		; Please upcall me flag


send_queueempty	macro
; Check if send queue is empty.
; Enter with interrupts disabled.
; Exit with zr (zero) if empty, nz (not zero) if not.
; Destroys ax.
	mov ax,	word ptr send_head	; Queue empty?
	or ax,	word ptr send_head+2
	endm

send_peekqueue	macro
; Peek into the queue and get the next entry.
; Enter with interrupts disabled.
; Exit with es:di -> iocb.
	les di, send_head	; Get head segment:offset
	endm

; Bits in sys_features
MICROCHANNEL	equ	02		; a micro channel computer
TWO_8259	equ	40h		; 2nd 8259 exists

; Bits in flagbyte
CALLED_ETOPEN	equ	1		; have called etopen
D_OPTION	equ	2		; delayed initialization
N_OPTION	equ	4		; Novell protocol conversion
W_OPTION	equ	8		; Windows upcall checking.

;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.
;16550 support ruthlessly stolen from Phil Karn's 8250.c. 
;  Bugs by Denis DeLaRoca
; Stopped failures from lost transmit interrupts (by eliminating the ints
; altogether). Remove unneeded transmitter buffer.
; Version 6 by Joe Doupnik, jrd@cc.usu.edu, Utah State University, Dec 1991.

;  Copyright, 1988, 1991, Russell Nelson

;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General MakePublic License as published by
;   the Free Software Foundation, version 1.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General MakePublic License for more details.
;
;   You should have received a copy of the GNU General MakePublic License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

code	segment	word
	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 FIFO control register values
FIFO_ENABLE     equ     001h    ;Enable TX and RX fifo
FIFO_CLR_RX     equ     002h    ;Clear RX fifo
FIFO_CLR_TX     equ     004h    ;Clear TX fifo
FIFO_START_DMA  equ     008h    ;Enable TXRDY/RXRDY pin DMA handshake
FIFO_SIZE_1     equ     000h    ;RX fifo trigger levels
FIFO_SIZE_4     equ     040h
FIFO_SIZE_8     equ     080h
FIFO_SIZE_14    equ     0c0h
FIFO_SIZE_MASK  equ     0c0h

FIFO_TRIGGER_LEVEL equ FIFO_SIZE_4
FIFO_SETUP         equ FIFO_ENABLE+FIFO_CLR_RX+FIFO_CLR_TX+FIFO_TRIGGER_LEVEL
OUTPUT_FIFO_SIZE   equ 16

;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
IIR_FIFO_TIMEOUT  equ   008h    ;FIFO timeout interrupt pending - 16550 only
IIR_FIFO_ENABLED  equ   080h    ;FIFO enabled (FCR0 = 1) - 16550 only

;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

pr_ch_al	macro		alvalue
ifdef TRACEON
	mov	al,alvalue
	call	pr_ch
endif
	endm

;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

ser_buf_size	equ	3000		; (grg)

BRC		equ	115200

	MakePublic	int_no
TheInfo:
int_no		db	4,0,0,0		; interrupt number.
io_addr		dw	03f8h,0		; I/O address for COM1
baud_rate	dw	1234,0		; support baud higher than 65535
ser_misc        db      LCR_8BITS,0,0,0	; misc control bits
hardware_switch	db	0,0,0,0		; if zero, don't use hw handshaking
xmit_time	dw	0,0		; loop timer for ari

baudclk		label	word
		dd	BRC		; 1.8432 Mhz / 16

is_16550        db      0               ; 0=no, 1=yes (try using fifo)

	MakePublic	driver_class, driver_type, driver_name
	MakePublic	driver_function, parameter_list
driver_class	db	6,0,0,0		;from the packet spec
driver_type	db	0,0,0,0		;from the packet spec
driver_name	db	'UMSLIP  ',0	;name of the driver.
driver_function	db	2
parameter_list	label	byte
	db	1	;major rev of packet driver
	db	9	;minor rev of packet driver
	db	14	;length of parameter list
	db	EADDR_LEN	;length of MAC-layer address
	dw	GIANT	;MTU, including MAC headers
	dw	MAX_MULTICAST * EADDR_LEN    ;buffer size of multicast addrs
	dw	0	;(# of back-to-back MTU rcvs) - 1
	dw	0	;(# of successive xmits) - 1
int_num	dw	0	;Interrupt # to hook for post-EOI
			;processing, 0 == none,

	MakePublic recv_buf_size, recv_buf,	recv_buf_end, recv_buf_head
	MakePublic recv_buf_tail,end_resident

recv_buf_size	dw	ser_buf_size,0		;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
dummy		dw	1234h

IP_TYPE	DW	0800H

  ifdef debug
	MakePublic Pak_Count, xmit_time
  endif
Pak_Count	dw	0		; semaphore for	packets received
asyrxint_cnt	dw	0		; loop counter in ari

	MakePublic	rcv_modes
rcv_modes	dw	4		;number	of receive modes in our table
		dw	0,0,0,rcv_mode_3

	MakePublic	as_send_pkt
; The Asynchronous Transmit Packet routine.
; Enter with es:di -> i/o control block, ds:si -> packet, cx = packet length,
;   interrupts possibly enabled.
; Exit with nc if ok, or else cy if error, dh set to error number.
;   es:di and interrupt enable flag preserved on exit.
as_send_pkt:
	ret

	MakeExternal	delaysub:near


	MakePublic	drop_pkt
; Drop a packet from the queue.
; Enter with es:di -> iocb.
drop_pkt:
	assume	ds:nothing
	ret

	MakePublic	xmit
; Process a transmit interrupt with the least possible latency to achieve
;   back-to-back packet transmissions.
; May only use ax and dx.
xmit:
	assume	ds:nothing
	ret


	MakePublic	send_pkt
;
; mod 7/25/89 John Grover
; - operates with interrupts on. Xmits one byte per interrupt
; - only turns transmitter buffer empty interrupt off when
; - all bytes of all packets are transmitted.

send_pkt:
;enter with es:di->upcall routine, (0:0) if no upcall is desired.
;  (only if the high-performance bit is set in driver_function)

;enter with ds:si -> packet, cx = packet length.
;exit with nc if ok, or else cy if error, dh set to error number.
;called from telnet layer via software interrupt
; We just send each byte in turn. No UART interrupts are needed nor wanted.
; In fact the overdone receiver material omits to note that xmtr interrupts
; can be lost while processing rcvr ones. Small benefits are no stalled
; programs, no transmitter buffer, no problems at 19200 b/s. Joe Doupnik

	assume	ds:nothing, es:nothing

        Show	col_InOut,'O'
	sti				; enable interrupts
	cld
	mov	al,FR_END		; Flush out any line garbage
	call	send_char
	jc	send_pkt_end		; c = failure to send

;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
	jc	send_pkt_end

	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
	jc	send_pkt_end

	mov	al,T_FR_END

send_pkt_3:
	call	send_char
	jc	send_pkt_end

	loop	send_pkt_1		; do cx user characters

	mov	al,FR_END		; terminate it with a FR_END
	call	send_char
	jc	send_pkt_end

	clc

send_pkt_end:
        Show	col_InOut,' '
	ret



;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------

;
;      our serial driver
;

	MakePublic	ser_funcs


ser_DTR:
	mov	al,MCR_RTS or MCR_OUT2
	or	cx,cx
        jz	no_DTR

	or	al,MCR_DTR

no_DTR:	loadport
	setport	MCR
	SlowOut
	jmp	ser_ret


	MakeExternal	int_dis:near,DOSPrint:near

ser_setall:
	push	ds
	push	es
	push	si
	call	int_dis			; return old interrupt
	pop	si
        pop	ds                      ; source segment
        push	cs
        pop 	es                      ; dest segment
        mov	di,offset TheInfo       ; dest offset
        mov	cx,10			; data length in words
        cld
        rep movsw
        pop	ds
        call	set_recv_isr
        call	etopen			; reinitialize this new chip
	jmp	ser_ret


ser_info:
	cmp	cx,24
        jl	nov

	cld
        mov     di,si
        mov     ax,majver
        stosw
	mov     ax,TheVersion
        stosw
        mov     si,offset TheInfo
        mov     cx,10                   ; number of words
        rep movsw

nov:	jmp	ser_ret

ser_lines:
	loadport
        SetPort	MSR
        SlowIn
        shr	ax,4
        and	ax,000Fh
        mov	word ptr es:[si],ax
        jmp	ser_ret

ser_funcs:
        cmp	al,1
        je	ser_avail

        cmp	al,2
        je	send_raw

	cmp	al,3
	je	get_raw

        cmp	al,4
        je	ser_DTR

        cmp	al,5
        je	ser_setall

        cmp	al,6
        je	ser_info

        cmp	al,7
        je	ser_lines


	stc		; unknown function code			
        jmp	ser_x

ser_ret:
	clc		; ok return code

ser_x:	ret



ser_avail:
	call	ser_av
	jmp	ser_ret	

ser_av:
	call	ser_av_reg
	mov	es:[si],cx
	ret

ser_av_reg:
	mov	cx,recv_buf_tail
        sub	cx,recv_buf_head
        jge	ser_av_1		; positive length

        add	cx,recv_buf_size	; negative, add buf size
ser_av_1:
	ret


	assume	ds:code


get_raw:         			; read cx chars to es:di
        push	cs
        pop	ds			; ds now points to code seg
	mov	dx,cx
        call	ser_av_reg		; find amount available
        cmp	dx,cx
        jle	get_lok

        mov	dx,cx			; trim request to avail

get_lok:
	mov	cx,dx			; requested length
	mov	ax,cx
	cld
	stosw 				; return cx to caller
	or	cx,cx
        jle	get_ex			; requested <= 0 !!

	mov	si,recv_buf_head

get_loop:
	lodsb				; get byte from buffer
        stosb 				; store in user's buffer
        cmp	si,recv_buf_end
        jb	get_bot

        mov	si,recv_buf		; back to top of buffer

get_bot:
        loop	get_loop

        mov	recv_buf_head,si	; update head ptr

get_ex:
	mov	cx,dx			; return length read
	jmp	ser_ret

	assume	ds:nothing, es:nothing

;
;  send_raw: send a stream of characters (no interpretation)
;
;

send_raw:  				; send cx chars from ds:si
	sti				; enable interrupts
	cld
        push	es
        pop	ds
        or	cx,cx
        jle	send_raw_end


send_raw_1:
	lodsb
	call	send_char
	jc	send_raw_end

	loop	send_raw_1		; do cx user characters

	clc

send_raw_end:
	jmp	ser_ret			; done ok



;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------

; mod 7/25/89 John Grover
; redone by Joe Doupnik, Dec 1991
;
;
	assume	ds:nothing, es:nothing
send_char:				; send the character in al
  ifdef grg
  	push	es
	push	ax
        mov	ax,0B800h
        mov	es,ax
	inc	byte ptr es:[col_chout]
	pop	ax
	pop	es
  endif
  	push	dx
	push	cx
	xchg	ah,al			; put data char into ah
	xor	cx,cx			; 64K retry counter
        mov     bl,hardware_switch      ; Get hardware check (CTS) switch

outc1:	loadport
	SetPort	LSR
	SlowIn
	test	al,LSR_THRE		; Transmitter (THRE) ready?
	jnz	outc2			; nz = yes

	loop	outc1

	stc				; carry set for failure
	jmp	short outc3		; timeout

outc2:	xor	cx,cx			; reset retry counter
	or	bl,bl                   ; Yes -- check CTS ready if needed.
        jz      outc25			; No check -- send the character.

        SetPort	MSR
	SlowIn
        test    al,MSR_CTS              ; Is CTS ready?
        jz	outc1			; no, go check again


outc25:	xchg	al,ah			; now send it
	mov	dx,io_addr
	Delay
	SlowOut				; send the byte
	clc				; status of success

outc3:	pop	cx
	pop	dx
	ret



	MakePublic	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
	xor	cx,cx
	clc
	ret


	MakePublic	set_address
set_address:
;set 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:nothing
	clc
	ret


rcv_mode_3:
;receive mode 3 is the only one we support, so we don't have to do anything.
	ret


	MakePublic	set_multicast_list
set_multicast_list:
;enter with ds:si ->list of multicast addresses, cx = number of addresses.
;return nc if we set all of them, or cy,dh=error if we didn't.
	mov	dh,NO_MULTICAST
	stc
	ret


	MakePublic	get_multicast_list
get_multicast_list:
;return with nc, es:di ->list of multicast addresses, cx = number of bytes.
;return	cy, NO_ERROR if we don't remember all of the addresses ourselves.
;return cy, NO_MULTICAST if we don't implement multicast.
	mov	dh,NO_MULTICAST
	stc
	ret


	MakePublic	terminate
terminate:
	ret

	MakePublic	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, dl = packet class.
	MakeExternal	recv_find: near

;called after we have copied the packet into the buffer.
;enter with ds:si ->the packet, cx = length of the packet.
	MakeExternal	recv_copy: near

	MakeExternal	count_in_err: near
	MakeExternal	count_out_err: near

	MakePublic	recv

;
; mod 7/25/89 John Grover
;
;called from the recv isr.  All registers have been saved, and ds=cs.
;Upon exit, the interrupt will be acknowledged.

	assume	ds:code

recv:

rci_mo:	loadport
	setport	IIR
	SlowIn   			; any interrupts at all?
	test	al,IIR_IP
	jne	rci_x			; no.

	and	al,IIR_ID
	cmp	al,IIR_RDA		; Receiver interrupt
	je	ari

rci_ni:	cmp	al,IIR_MSTAT
	jne	rci_x

	setport	MSR			; make sure of CTS status
	SlowIn
;;;;	test	al, MSR_CTS		; test removed?????
	jmp	rci_mo


rci_x:	ret


;-------------------------------------------------------------------------
;Process 8250 receiver interrupts
;
; mod 7/25/89 John Grover
; - this branches off when bps < 9600. See ari_a.
; - Above 9600 bps we go into a loop to process a packet at
; - a time. If not data ready for a certain amount of time,
; - the process exits and waits for the next byte. This certain
; - amount of time to wait depends on the bps and CPU processor speed
; - and is determined in the initialization of the driver.
; - Upon receiving the FR_END character for the first frame in the
; - buffer a semaphore is set which tells rf to run.

ari:	mov	bx,ds			; get set up for the routine
	mov	es,bx
	mov	bx,xmit_time            ; is zero if no polling desired

ari_a:	mov	di,recv_buf_tail
	xor	bp,bp			; set flag to indicate 1st char processed
	mov	si,Pak_Count		; optimization
	mov	ah,LSR_DR

ari_again:
	xor	cx,cx			; initialize counter
	loadport
	setport	LSR

ari_in:	SlowIn
	test	al,LSR_OE
        jz	noovr

	ShowInc    col_ovrr,'O'

noovr:	test	al,LSR_FE
	jz	nofr

	ShowInc    col_frame,'F'

nofr:	test	al,ah
	jnz	ari_gd			; yes - break out of loop

   	inc	cx			; no - increase loop counter
	cmp	cx,bx			; timeout?
	jae	ari_ex			; yes - leave

	jmp	ari_in			; no - keep looping

;------  got data ready, read the char

ari_gd:	setport	RBR
	SlowIn

; Process incoming data;
; If buffer is full, we have no choice but
; to drop the character

	cmp	di,recv_buf_head
	jne	ari_ok			; none - continue

        ShowInc col_serfull,'S'		; twiddle screen if buf full

	or	si,si                   ; maybe - if there are packets
	jnz	ari_ex			; yes exit

ari_ok:	stosb
	cmp	di,recv_buf_end		; did we hit the end of the buffer?
	jne	ari_3			; no.

	mov	di,recv_buf		; yes - wrap around.

ari_3:	cmp	al,FR_END		; might	this be	the end of a frame?
	jne	ari_reset		; no - reset flag and loop

	inc	si                      ; yes - indicate packet ready
	or	bp,bp                   ; was this the first char?
	jne	ari_ex              	; no - exit handler

ari_reset:
   	inc	bp			; set 1st character flag
	jmp	ari_again		; get another character

ari_ex:	mov	recv_buf_tail,di
	mov	Pak_Count, si

        mov     bx,bp
        add     bl,'0'
        Show    col_serfull,bl		; show char gotten count
        jmp     rci_x


; --------------------------------------------------------------
;
;  recv_exiting
;
	MakePublic	recv_exiting
recv_exiting:
	cmp	Pak_Count,0		; is a packet ready?
	je	recv_isr_exit           ; no - skip to end


	push	ax
	push	bx
	push	cx
	push	dx
	push	ds
	push	es
	push	bp
	push	di
	push	si
	push	cs			; point ds properly
	pop	ds
	sti				; enable interrupts

	call	rf

	cli
	pop	si
	pop	di
	pop	bp
	pop	es
	pop	ds
	pop	dx
	pop	cx
	pop	bx
	pop	ax

recv_isr_exit:
	ret


; --------------------------------------------------------------
;
;  rf
;
; mod 7/25/89 John Grover
;
; - rf now operates with interrupts on. It is triggered
; - by the Pak_Count flag and continues until all bytes
; - in all packets in the buffer have been transmitted to the upper
; - layer.
;
  ifdef debug
	MakePublic rf
  endif

rf:	cmp	Pak_Count, 0		; should we do this?
	jle	rf_end			; no - exit

rf_0:	mov	si,recv_buf_head	;process characters.
	xor	cx,cx			;count up the size here.
	cld				; quite important

rf_1:	call	recv_char		;get a char.
	je	rf_2			;go if no more chars.

	cmp	al,FR_ESC		;an escape?
	je	rf_1			;yes - don't count this char.

	inc	cx			;no - count this one.
	jmp	rf_1

rf_2:	jcxz	rf_3z			;count zero? yes - 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,CS
	MOV	ES,DI
	mov	di,0
	mov	dl,cs:driver_class
	call	recv_find		;look up our type.
	pop	bx
	pop	si

	mov	ax,es			;is this pointer null?
	or	ax,di
	je	rf_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.

rf_4:	call	recv_char
	je	rf_6			;yes - we're all done.

	cmp	al,FR_ESC		;an escape?
	jne	rf_5			;no - just store it.

	call	recv_char		;get the next character.
	je	rf_6

	cmp	al,T_FR_ESC
	mov	al,FR_ESC		;assume T_FR_ESC
	je	rf_5			;yup, that's it	- store FR_ESC

	mov	al,FR_END		;nope, store FR_END

rf_5:	stosb				;store the byte.
	jmp	rf_4

rf_6:	mov	recv_buf_head,si	;we're skipped to the end.
	pop	si			;now give the frame to the client.
	pop	ds
	pop	cx
	assume	ds:nothing
	call	recv_copy		; do the upcall to the tcp driver
	push	cs
	pop	ds
	assume	ds:code
	jmp	rf_end

rf_3:
	ShowInc col_TCPFull,'T'

rf_3z:	mov	recv_buf_head,si	;remember the new starting point.

rf_end:	dec	Pak_Count

	cmp	Pak_Count,0		; are there more packets ready?
	jbe	rf_9

	jmp	rf_0              	; yes - execute again

rf_9:	ret


; --------------------------------------------------------------
;
;  recv_char
;
; mod 7/25/89 John Grover
; - Now	uses buffer pointers to determine if there are
; - characters left.
;

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:
	mov	bx, recv_buf_tail
	cmp	si, bx
	je	recv_char_2

	cmp	al,FR_END

recv_char_2:
	ret



set_baud:
;compute the divisor given the baud rate.

	mov	dx,baudclk+2
	mov	ax,baudclk
	mov	bx,0

sb_1:	inc	bx
	sub	ax,baud_rate
	sbb	dx,baud_rate+2
	jnc	sb_1

	dec	bx
	add	ax,baud_rate
	adc	dx,baud_rate+2

sb_2:	call	flush_chars
	loadport			;Purge the receive data buffer
	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
	SlowOut
	mov	al,bh
	setport	DLM
	SlowOut

	mov	ah,LCR_DLAB		;Turn off divisor latch access bit
	setport	LCR
	call	clrbit
	ret



        

flush_chars:
	loadport			;Purge the receive data buffer
	mov	cx,1000			;flush up to 1K chars

flush_next:
	SlowIn
	test	al,LSR_DR
	jz	flush_done		; yes - break out of loop

	setport	RBR
	SlowIn 				; read and ignore the char
	loop	flush_next

flush_done:
	ret



; --------------------------------------------------------------
;
;  etopen
;
; mod 7/25/89 John Grover
; - Contains a loop to determine a pseudo timeout for ari.
; - The value is determined by transmitting characters in a
; - loop whose clock cycles are nearly the same as the "sister"
; - loop in ari. The per character, maximum time used
; - basis which is then multiplied by a factor to achieve a timeout
; - value for the particular bps and CPU speed of the host.

	MakePublic	etopen
etopen:
	pushf
	cli
;
; mod  3/16/90  Denis DeLaRoca
; - determine if 16550 uart is present
; - if so initialize fifo buffering
;
	loadport
	setport	FCR
	mov	al,FIFO_ENABLE
	SlowOut				;outportb(base+FCR,(char) FIFO_ENABLE)
	setport	IIR
	SlowIn				;inportb(base+IIR)
	and	al,IIR_FIFO_ENABLED     ;     & IIR_FIFO_ENABLED
	cmp	al,IIR_FIFO_ENABLED	;both bits must be on   NEW, 11/20/90
	jnz	not_16550               ;nope, we don't have 16550 chip

	mov	is_16550,1              ;yes, note fact
	mov	al,FIFO_SETUP           ;and setup FIFO
	setport	FCR
	SlowOut				;outportb(base+FCR,(char) FIFO_SETUP)

not_16550:
	call	flush_chars

;Set line control register: 8 bits, no parity
	loadport
	mov	al,ser_misc

	setport	LCR
	SlowOut

;Turn on receive interrupt enable in 8250, leave transmit
; and modem status interrupts turned off for now
	mov	al,IER_DAV
	setport	IER
	SlowOut

; 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
	SlowOut

	call	set_baud
	call	set_recv_isr		;Set interrupt vector to SIO handler

;set up the various pointers.
	mov	dx,offset end_resident

	mov	recv_buf,dx
	mov	recv_buf_head,dx
	mov	recv_buf_tail,dx
	add	dx,recv_buf_size
	mov	recv_buf_end,dx

        xor	si,si
	cmp	baud_rate,9600		; below 19200 we're strictly
	jbe	t_a              	; interrupt driven


	; the following code attempts to determine a pseudo timeout
	; value	to use in the loop that waits for an incoming character
	; in ari. The value returned in xmit_time is the number of
	; loops processed between characters - therefore the loop used below
	; is and should	remain similar to the loop used in ari.

	mov	ah,LSR_THRE
	mov	cx,16			; take the highest of 16 runs
	xor	si,si			; will hold highest value

xmit_time_start:
	xor	di,di			; initialize counter
	loadport
	setport	THR			; xmit a character
        mov	al,' '			; send a space to minimize damage
	SlowOut
	setport	LSR		       ; set up	to check for an empty buffer

; next is the loop actually being timed

	mov	bx,1			; wait up to 65K times

xmit_time_top:
	SlowIn
	test	al,ah
	jnz	xmit_time_done

	inc	di
	cmp	cx,cx			; these next few instructions do nothing
	jmp	xmit_time_1		; except maintain similarity with the
					; "sister" loop in ari
xmit_time_1:
	inc	bx
	jmp	xmit_time_top

xmit_time_done:				; end of timed loop

	cmp	si,di			; compare highest value with new value
	ja	xmit_time_end		; no bigger - just loop
	mov	si,di			; bigger - save it

xmit_time_end:
	loop	xmit_time_start		; bottom of outer loop

	call	flush_chars		; throw away any echoed zeroes

	shl	si,3			; we'll wait 8 characters worth

t_a:	mov	xmit_time,si		; retain largest value
	mov	al, int_no		; Get board's interrupt vector
	add	al, 8
	cmp	al, 8+8			; Is it a slave 8259 interrupt?
	jb	set_int_num		; No.

	add	al, 70h - 8 - 8		; Map it to the real interrupt.

set_int_num:
	xor	ah, ah			; Clear high byte
	mov	int_num, ax		; Set parameter_list int num.

	popf
	clc				;indicate no errors.
	ret


;Set bit(s) in I/O port
setbit:
;enter with dx = port, ah = bit to set.
	SlowIn
	or	al,ah
	SlowOut
	ret


;Clear bit(s) in I/O port
clrbit:
;enter with dx = port, ah = bit to set.
	SlowIn
	not	al			;perform an and-not using DeMorgan's.
	or	al,ah
	not	al
	SlowOut
	ret

is_nochip_msg:	db	'Serial Port is not there??',CR,LF,'$'


;**************************************************************************
;**************************************************************************
;**************************************************************************
;any code after this will not be kept after initialization.

end_resident	label	byte

;**************************************************************************
;**************************************************************************
;**************************************************************************

	MakePublic	usage_msg
usage_msg	db	"usage: UMSLIP [?] [-n] [-w] [-s] [packet_int_no]"
		db	" [recv_buf_size]",CR,LF,'$'

	MakePublic	copyright_msg
copyright_msg	db	"Packet driver for SLIP, version ",'0'+majver mod 10
		db	".",'0'+TheVersion mod 10,CR,LF
		db	"Portions Copyright 1988 Phil Karn,"
		db	" 1991 Joe Doupnik,",CR,LF
		db	"Portions Copyright 1993 Univ. of Minnesota",cr,lf,'$'


	MakeExternal	set_recv_isr: near

;enter with si -> argument string, di -> word to store.
;if there is no number, don't change the number.
	MakeExternal	get_number: near

;enter with dx -> name of word, di -> dword to print.
	MakeExternal	print_number: near
	MakeExternal	print_number_dec: near
	MakeExternal	print_number_hex: near
	MakeExternal	print_crlf: near

;enter with si -> argument string.
;skip spaces and tabs.  Exit with si -> first non-blank char.
	MakeExternal	skip_blanks: near


	MakePublic	parse_args

parse_args:
	mov	di,offset recv_buf_size
	call	get_number
	clc
	ret


code	ends

;   PC/FTP Packet Driver source, conforming to version 1.05 of the spec
;   Updated to version 1.08 Feb. 17, 1989.
;   Copyright 1988-1992 Russell Nelson

;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General MakePublic License as published by
;   the Free Software Foundation, version 1.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General MakePublic License for more details.
;
;   You should have received a copy of the GNU General MakePublic License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

code	segment word
	assume	cs:code, ds:code

	MakeExternal	phd_dioa: byte
	MakeExternal	phd_environ: word
	MakeExternal	flagbyte: byte,quiet:byte,end_resident:near,recv_buf_size:word

;put into the MakePublic domain by Russell Nelson, nelson@crynwr.com

	MakePublic	print_number_hex
print_number_hex:
	Print
	mov	al,'0'
	call	chrout
	mov	al,'x'
	call	chrout
	mov	ax,[di]			;print the number in hex.
	mov	dx,[di+2]
	call	dwordout
	ret



	MakePublic	print_number_dec

print_number_dec:
	or	dx,dx
        je	NoTxt

	Print

NoTxt:	mov	ax,[di]			;print the number in decimal.
	mov	dx,[di+2]
	call	decout
	ret

	MakePublic	print_crlf
print_crlf:
	mov	al,CR
	call	chrout
	mov	al,LF
	call	chrout
        ret


	MakePublic	print_number
print_number:
;enter with dx -> dollar terminated name of number, di ->dword.
;exit with the number printed and the cursor advanced to the next line.

	call	print_number_hex
	mov	al,' '
	call	chrout
	mov	al,'('
	call	chrout
        mov	dx,0
	call	print_number_dec
	mov	al,')'
	call	chrout
	call	print_crlf
	ret


;put into the MakePublic domain by Russell Nelson, nelson@crynwr.com

	MakePublic	decout
decout:
	mov	si,ax			;get the number where we want it.
	mov	di,dx
	or	ax,dx			;is the number zero?
	jne	decout_nonzero
	mov	al,'0'			;yes - easier to just print it, than
	jmp	chrout			;  to eliminate all but the last zero.
decout_nonzero:

	xor	ax,ax			;start with all zeroes in al,bx,bp
	mov	bx,ax
	mov	bp,ax

	mov	cx,32			;32 bits in two 16 bit registers.
decout_1:
	shl	si,1
	rcl	di,1
	xchg	bp,ax
	call	addbit
	xchg	bp,ax
	xchg	bx,ax
	call	addbit
	xchg	bx,ax
	adc	al,al
	daa
	loop	decout_1

	mov	cl,'0'			;prepare to eliminate leading zeroes.
	call	byteout			;output the first two.
	mov	ax,bx			;output the next four
	call	wordout			;output the next four
	mov	ax,bp
	jmp	wordout

addbit:	adc	al,al
	daa
	xchg	al,ah
	adc	al,al
	daa
	xchg	al,ah
	ret

;put into the MakePublic domain by Russell Nelson, nelson@crynwr.com

	MakePublic	dwordout, wordout, byteout, digout
dwordout:
	mov	cl,'0'			;prepare to eliminate leading zeroes.
	xchg	ax,dx			;just output 32 bits in hex.
	call	wordout			;output dx.
	xchg	ax,dx
wordout:
	push	ax
	mov	al,ah
	call	byteout
	pop	ax
byteout:
	mov	ah,al
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	call	digout
	mov	al,ah

digout:
	and	al,0fh
	add	al,90h	;binary digit to ascii hex digit.
	daa
	adc	al,40h
	daa
	cmp	al,cl			;leading zero?
	je	digout_1
	mov	cl,-1			;no more leading zeros.
	jmp	chrout

digout_1:
	ret
;put into the MakePublic domain by Russell Nelson, nelson@crynwr.com

	MakePublic	chrout
chrout:
	push	ax			;print the char in al.
	push	dx
	mov	dl,al
	test	quiet,1
	jnz	nochout

	mov	ah,2
	int	21h

nochout:
	pop	dx
	pop	ax
	ret


;*************************************************************************

;usage_msg is of the form "usage: driver [-d -n] <packet_int_no> <args>"
	MakeExternal	usage_msg: byte

;copyright_msg is of the form:
;"Packet driver for the foobar",CR,LF
;"Portions Copyright 19xx, J. Random Hacker".
	MakeExternal	copyright_msg: byte

copyleft_msg	label	byte
 db "Packet driver skeleton copyright 1988-92, Crynwr Software.",CR,LF
 db "This program is free software; see the file COPYING for details.",CR,LF
 db "NO WARRANTY; see the file COPYING for details."
crlf_msg	db	CR,LF,'$'

no_resident_msg	label	byte
 db CR,LF,"*** Packet driver failed to initialize the board ***",CR,LF,'$'

;parse_args should parse the arguments.
;called with ds:si -> immediately after the packet_int_no.
	MakeExternal	parse_args: near

;print_parameters should print the arguments.
	MakeExternal	print_parameters: near

	MakeExternal	our_isr: near, their_isr: dword
	MakeExternal	ser_their_isr: dword
	MakeExternal	packet_int_no: byte
	MakeExternal	is_at: byte, sys_features: byte
	MakeExternal	int_no: byte
	MakeExternal	driver_class: byte


packet_int_no_name	db	"Packet interrupt number ",'$'
eaddr_msg	db	"My Ethernet address is ",'$'
aaddr_msg	db	"My ARCnet address is ",'$'

already_msg	db	CR,LF,"There is already a packet driver at ",'$'
int_msg		db	CR,LF
		db	"Error: <int_no> should be between 0 and "
int_msg_num	label	word
		db	"15 inclusive", '$'

our_address	db	EADDR_LEN dup(?)
	MakePublic	etopen_diagn
etopen_diagn	db	0		; errorlevel from etopen if set

;etopen should initialize the device.  If it needs to give an error, it
;can issue the error message and quit to dos.
	MakeExternal	etopen: near

;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.
	MakeExternal	get_address: near

already_error:
	mov	quiet,0
	mov	dx,offset already_msg
	mov	di,offset packet_int_no
	call	print_number
	mov	ax,4c05h		; give errorlevel 5
	int	21h

usage_error:
	mov	dx,offset usage_msg

	MakePublic	error
error:
	mov	quiet,0
	Print
	mov	ax,4c0ah		; give errorlevel 10
	int	21h

	MakePublic	start_1
start_1:
	cld

;
; Get the feature byte (if reliable) so we can know if it is a microchannel
; computer and how many interrupts there are.
;
	mov	ah,0c0h
	int	15h			; es:bx <- sys features block
	jc	look_in_ROM		; error, must use rom.
	or	ah,ah
	jnz	look_in_ROM
	mov	dx,es:[bx]		; # of feature bytes
	cmp	dx,4			; do we have the feature byte we want?
	jae	got_features		;yes.
look_in_ROM:
	mov	dx,0f000h		;ROM segment
	mov	es,dx
	cmp	byte ptr es:[0fffeh],0fch;is this an AT?
	jne	identified		;no.
	or	sys_features,TWO_8259	; ATs have 2nd 8259
	jmp	identified		; assume no microchannel
got_features:
	mov	ah,es:[bx+2]		; model byte
	cmp	ah,0fch
	je	at_ps2
	ja	identified		; FD, FE and FF are not ATs
	cmp	ah,0f8h
	je	at_ps2
	ja	identified		; F9, FA and FB are not ATs
	cmp	ah,09ah
	jbe	identified		; old non-AT Compacs go here

at_ps2:					; 9B - F8 and FC are assumed to
	mov	ah,es:[bx+5]		;   have reliable feature byte
	mov	sys_features,ah

identified:
	mov	si,offset phd_dioa+1
	call	skip_blanks		;end of line?
	cmp	al,CR
	je	nopknum			; yes, assume 0x60

chk_options:
	call	skip_blanks
	cmp	al,'-'			; any options?
	jne	no_more_opt

	inc	si			; skip past option char
	lodsb				; read next char
	or	al,20h			; convert to lower case
	cmp	al,'n'
	jne	not_n_opt
	or	flagbyte,N_OPTION
	jmp	chk_options

not_n_opt:
	cmp	al,'w'
	jne	not_w_opt

	or	flagbyte,W_OPTION
	jmp	chk_options

not_w_opt:
	cmp	al,'s'
        jne	usage_error_j_1

	mov	quiet,1
        jmp	chk_options


usage_error_j_1:
	jmp	usage_error


no_more_opt:
	cmp	al,'?'
        je	usage_error_j_1

	mov	di,offset packet_int_no	;parse the packet interrupt number
	mov	bx,offset packet_int_no_name
	call	get_number		;  for them.

	call	parse_args
	jc	usage_error_j_1

	call	skip_blanks		;end of line?
	cmp	al,CR
	jne	usage_error_j_1

nopknum:
	call	verify_packet_int
	jnc	packet_int_ok
	jmp	error

packet_int_ok:
	jne	packet_int_unused
	jmp	already_error		;give an error if there's one there.
packet_int_unused:

;
; Verify that the interrupt number they gave is valid.
;
	cmp	int_no,15		;can't possibly be > 15.
	ja	int_bad
	test	sys_features,TWO_8259	; 2nd 8259 ?
	jnz	int_ok			;yes, no need to check for <= 7.
	mov	int_msg_num,'7'+' '*256	;correct the error message, just in case.
	cmp	int_no,7		;make sure that the packet interrupt
	jbe	int_ok			;  number is in range.
int_bad:
	mov	dx,offset int_msg
	jmp	error
int_ok:

;
; Map IRQ 2 to IRQ 9 if needed.
;
	test	sys_features,TWO_8259	; 2nd 8259 ?
	je	no_mapping_needed	;no, no mapping needed
	cmp	int_no,2		;map IRQ 2 to IRQ 9.
	jne	no_mapping_needed
	mov	int_no,9

no_mapping_needed:

	mov	dx,offset copyright_msg
	Print
	mov	dx,offset copyleft_msg
	Print

	call	take_packet_int

	mov	ah,49h			;free our environment, because
	mov	es,phd_environ		;  we won't need it.
	int	21h

	mov	bx,1			;get the stdout handle.
	mov	ah,3eh			;close it in case they redirected it.
	int	21h

	mov	dx,offset end_resident
	add	dx,recv_buf_size
	add	dx,0fh			;round up to next highest paragraph.
	mov	cl,4
	shr	dx,cl
	mov	ah,31h			;terminate, stay resident.
	mov	al,etopen_diagn		; errorlevel (0 - 9, just diagnostics)
	int	21h




no_resident:
	mov	dx,offset no_resident_msg
	Print

	mov	ax,4c00h + 32		; give errorlevel 32
	cmp	al,etopen_diagn
	ja	no_et_diagn		; etopen gave specific reason?
	mov	al,etopen_diagn		; yes, use that for error level
no_et_diagn:
	int	21h

; 			Suggested errorlevels:
;
; _____________________  0 = normal
; 			 1 = unsuitable memory address given; corrected
; In most cases every-	 2 = unsuitable IRQ level given; corrected
; thing should work as	 3 = unsuitable DMA channel given; corrected
; expected for lev 1-5	 4 = unsuitable IO addr given; corrected (only 1 card)
; _____________________	 5 = packet driver for this int # already loaded
; External errors, when	20 = general cable failure (but pkt driver is loaded)
; corrected normal	21 = network cable is open             -"-
; operation starts	22 = network cable is shorted          -"-
; _____________________ 23 = 
; Packet driver not	30 = usage message
; loaded. A new load	31 = arguments out of range
; attempt must be done	32 = unspecified device initialization error
;			33 = 
;			34 = suggested memory already occupied
;			35 = suggested IRQ already occupied
;			36 = suggested DMA channel already occupied
;			37 = could not find the network card at this IO address


take_packet_int:
	mov	ah,35h			;remember their packet interrupt.
	mov	al,packet_int_no
	int	21h
	mov	their_isr.offs,bx
	mov	their_isr.segm,es

	mov	ah,25h			;install our packet interrupt
	mov	dx,offset our_isr
	int	21h

        ret


	MakePublic	DOSPrint

DOSPrint:
	test	quiet,1
        jnz	NoPrint

        mov	ah,9
        int	21h

NoPrint:
	ret


signature	db	'PKT DRVR',0
signature_len	equ	$-signature

packet_int_msg	db	CR,LF
		db	"Error: <packet_int_no> should be in the range 0x60 to 0x80"
		db	'$'

verify_packet_int:
;enter with no special registers.
;exit with cy,dx-> error message if the packet int was bad,
;  or nc,zr,es:bx -> current interrupt if there is a packet driver there.
;  or nc,nz,es:bx -> current interrupt if there is no packet driver there.
	cmp	packet_int_no,60h	;make sure that the packet interrupt
	jb	verify_packet_int_bad	;  number is in range.
	cmp	packet_int_no,80h
	jbe	verify_packet_int_ok
verify_packet_int_bad:
	mov	dx,offset packet_int_msg
	stc
	ret
verify_packet_int_ok:

	mov	ah,35h			;get their packet interrupt.
	mov	al,packet_int_no
	int	21h

	lea	di,3[bx]		;see if there is already a signature
	mov	si,offset signature	;  there.
	mov	cx,signature_len
	repe	cmpsb
	clc
	ret
;put into the MakePublic domain by Russell Nelson, nelson@crynwr.com

	MakePublic	get_number
get_number:
	mov	bp,10			;we default to 10.
	jmp	short get_number_0

	MakePublic	get_hex
get_hex:
	mov	bp,16
;get a hex number, skipping leading blanks.
;enter with si->string of digits,
;	di -> dword to store the number in.  [di] is not modified if no
;		digits are given, so it acts as the default.
;return cy if there are no digits at all.
;return nc, bx:cx = number, and store bx:cx at [di].
get_number_0:
	call	skip_blanks
	call	get_digit		;is there really a number here?
	jc	get_number_3

	xor	ah,ah
	cmp	ax,bp			;larger than our base?
	jae	get_number_3		;yes.

	or	al,al			;Does the number begin with zero?
	jne	get_number_4		;no.
	mov	bp,8			;yes - they want octal.
get_number_4:

	xor	cx,cx			;get a hex number.
	xor	bx,bx
get_number_1:
	lodsb
	cmp	al,'x'			;did they really want hex?
	je	get_number_5		;yes.
	cmp	al,'X'			;did they really want hex?
	je	get_number_5		;yes.
	call	get_digit		;convert a character into an int.
	jc	get_number_2		;not a digit (neither hex nor dec).
	xor	ah,ah
	cmp	ax,bp			;larger than our base?
	jae	get_number_2		;yes.

	push	ax			;save the new digit.

	mov	ax,bp			;multiply the low word by ten.
	mul	cx
	mov	cx,ax			;keep the low word.
	push	dx			;save the high word for later.
	mov	ax,bp
	mul	bx
	mov	bx,ax			;we keep only the low word (which is our high word)
	pop	dx
	add	bx,dx			;add the high result from earlier.

	pop	ax			;get the new digit back.
	add	cx,ax			;add the new digit in.
	adc	bx,0
	jmp	get_number_1

get_number_5:
	mov	bp,16			;change the base to hex.
	jmp	get_number_1

get_number_2:
	dec	si
	mov	[di],cx			;store the parsed number.
	mov	[di+2],bx
	clc
	jmp	short get_number_6

get_number_3:
	cmp	al,'?'			;did they ask for the default?
	stc
	jne	get_number_6		;no, return cy.

	add	si,1			;skip past the question mark.
	clc

get_number_6:
	ret


;put into the MakePublic domain by Russell Nelson, nelson@crynwr.com

	MakePublic	get_digit
get_digit:
;enter with al = character
;return nc, al=digit, or cy if not a digit.
	cmp	al,'0'			;decimal digit?
	jb	get_digit_1		;no.
	cmp	al,'9'			;. .?
	ja	get_digit_2		;no.
	sub	al,'0'
	clc
	ret
get_digit_2:
	cmp	al,'a'			;hex digit?
	jb	get_digit_3
	cmp	al,'f'			;hex digit?
	ja	get_digit_3
	sub	al,'a'-10
	clc
	ret
get_digit_3:
	cmp	al,'A'			;hex digit?
	jb	get_digit_1
	cmp	al,'F'			;hex digit?
	ja	get_digit_1
	sub	al,'A'-10
	clc
	ret
get_digit_1:
	stc
	ret


;put into the MakePublic domain by Russell Nelson, nelson@crynwr.com

	MakePublic	skip_blanks
skip_blanks:
	lodsb				;skip blanks.
	cmp	al,' '
	je	skip_blanks
	cmp	al,HT
	je	skip_blanks
	dec	si
	ret



	MakePublic	print_ether_addr


print_ether_addr:
	mov	cx,EADDR_LEN

print_ether_addr_0:
	push	cx
	lodsb
	mov	cl,' '			;Don't eliminate leading zeroes.
	call	byteout
	pop	cx
	cmp	cx,1
	je	print_ether_addr_1

	mov	al,':'
	call	chrout

print_ether_addr_1:
	loop	print_ether_addr_0
	ret

code	ends

	end     start
