Metropoli BBS
VIEWER: davidsys.asm MODE: TEXT (ASCII)
page 58, 132

;DBGINT3		equ	0
;DEBUG		equ	1		; FOR NOW
version		equ	5		; version of this file
s8005_version	equ	1		; SEEQ 8005 stuff (not separate (yet))

;version
; 1     = initially from Mark Dye
; 2     = Fix spin loop bug (cx=0 ->65536), broke on slower systems (6MHz)
; 3     = Fix '-n' bug, fix warnings
; 4     = Fix Compaq DOS bug (we must do STI after set_recv_isr)
;         Also, compile with DBGINT3 commmented out.
;         I thought that 0=false, 1=true, but they were IFDEFfed!
;         Also, Fix section to ignore broadcasts that we initiated
; 5     = Release version (nothing changed)

;s8005_version
;  0    = Initially from MD
;  1    = Fix  8-bit insb bug (broke on 8088 PCs)


; Copyright 1991  DAVID Systems, Inc (Sunnyvale, CA) and Marc S Dye
; (d.b.a. ayuda Company - Camarillo, CA).  Distribution of this code
; including any derivation thereof, shall be made so as to afford the
; recipient of such distribution a copy of this source code, including
; any alterations, at no charge (except for copying, media, and shipping
; costs).  Any distribution of this source or derivations must contain
; this copyright notice.  Documentation describing this work or its
; derivations must acknowledge the copyright holders herein.  The names
; of the copyright holders may not be used to advertise, signify, or
; otherwise characterize any derived work, except for the aforementioned
; copyright acknowledgement.  This work is provided AS IS, with NO
; WARRANTIES expressed or implied, NOR any representations made as to
; its suitability or fitness for any purpose whatsoever.  The recipient
; assumes full responsibility for use of this work.
; Some patches made by:
;   Jeff Douglass

ifdef	DEBUG
private		macro	SYM
		public	SYM
		endm
else
private		macro	SYM
		endm
endif

	include	defs.asm
; Ethernet-2 header length defined
;#
ENET_HDR	equ	2*EADDR_LEN + 4  ;was +2

; assemble a select few instructions (16-bit i/o) from the extended set
; otherwise, must do only 8086-compatible stuff!
		.286p


; Definitions of interface to the SEEQ 8005 EDLC.  See the SEEQ data book
; for complete details of this device.

; 8005 directly-addressable register offsets (from i/o base address)
COMSTAT_R	equ	0		; COMMAND / STATUS
CONFIG1_R	equ	2		; CONFIGURATION 1
CONFIG2_R	equ	4		; CONFIGURATION 2
REA_PTR_R	equ	6		; Receive End Area Pointer
BUFWIN_R	equ	8		; Buffer Window
RX_PTR_R	equ	10		; Receive Pointer
TX_PTR_R	equ	12		; Transmit Pointer
DMA_ADDR_R	equ	14		; DMA Address

; 8005 COMMAND register
DMA_INT_EN	equ	0001h
RX_INT_EN	equ	0002h
TX_INT_EN	equ	0004h
WINDOW_INT_EN	equ	0008h
DMA_INT_ACK	equ	0010h
RX_INT_ACK	equ	0020h
TX_INT_ACK	equ	0040h
WINDOW_INT_ACK	equ	0080h
SET_DMA_ON	equ	0100h
SET_RX_ON	equ	0200h
SET_TX_ON	equ	0400h
SET_DMA_OFF	equ	0800h
SET_RX_OFF	equ	1000h
SET_TX_OFF	equ	2000h
FIFO_READ	equ	4000h
FIFO_WRITE	equ	8000h

; 8005 STATUS register
DMA_INT		equ	0010h
RX_INT		equ	0020h
TX_INT		equ	0040h
WINDOW_INT	equ	0080h
DMA_ON		equ	0100h
RX_ON		equ	0200h
TX_ON		equ	0400h
FIFO_FULL	equ	2000h
FIFO_EMPTY	equ	4000h
FIFO_DIR	equ	8000h

; 8005 CONFIGURATION 1 register
MATCH_BITS_M	equ	0C000h
MATCH_ONLY	equ	0000h
MATCH_BROAD	equ	4000h
MATCH_MULTI	equ	8000h
MATCH_ALL	equ	0C000h
ALL_STATIONS	equ	3F00h
STATION_0_EN	equ	0100h
STATION_1_EN	equ	0200h
STATION_2_EN	equ	0400h
STATION_3_EN	equ	0800h
STATION_4_EN	equ	1000h
STATION_5_EN	equ	2000h
DMA_LENGTH_M	equ	00C0h
NBYTES_1	equ	0000h
NBYTES_2	equ	0040h
NBYTES_4	equ	0080h
NBYTES_8	equ	00C0h
DMA_INTERVAL_M	equ	0030h
CONTINUOUS	equ	0000h
DELAY_800	equ	0010h
DELAY_1600	equ	0020h
DELAY_3200	equ	0030h
BUFFER_CODE_M	equ	000Fh
STATION_0_SEL	equ	0000h
STATION_1_SEL	equ	0001h
STATION_2_SEL	equ	0002h
STATION_3_SEL	equ	0003h
STATION_4_SEL	equ	0004h
STATION_5_SEL	equ	0005h
PROM_SEL	equ	0006h
TEA_SEL		equ	0007h
BUFFER_MEM_SEL	equ	0008h
INT_VECTOR_SEL	equ	0009h
; Ether-T PC/AT device configuration register -- access is overloaded onto
; the set of registers accessible via the reserved range in CONFIGURATION 1
CARD_CONFIG_SEL	equ	000Fh
SET_16BITMODE_M	equ	0080h		; OR mask for 16 bit mode

; 8005 CONFIGURATION 2 register
BYTE_SWAP	equ	0001h
AUTO_UPDATE_REA	equ	0002h
CRC_ERR_EN	equ	0008h
DRIBBLE_EN	equ	0010h
SHORT_FRAME_EN	equ	0020h
SLOT_TIME_SEL	equ	0040h
XMIT_NO_PREAM	equ	0080h
ADDR_LENGTH	equ	0100h
RECEIVE_CRC	equ	0200h
XMIT_NO_CRC	equ	0400h
LOOPBACK_EN	equ	0800h
KILL_WATCHDOG	equ	1000h
RESET		equ	8000h

; 8005 TRANSMIT HEADER COMMAND BYTE  (byte 3 of transmit packet header)
BABBLE_INT_EN	equ	01h
COLL_INT_EN	equ	02h
COLL_16_INT_EN	equ	04h
XMIT_OK_INT_EN	equ	08h
DATA_FOLLOWS	equ	20h		; also in RECEIVE HEADER COMMAND BYTE
CHAIN_CONTINUE	equ	40h		; also in RECEIVE HEADER COMMAND BYTE
PACKET_PRESENT	equ	80h
TX_HEADER_Z	equ	4		; # of bytes in a TX header

; 8005 TRANSMIT HEADER STATUS BYTE  (byte 4 of transmit packet header)
BABBLE_ERR	equ	01h
COLL		equ	02h
COLL_16_ERR	equ	04h
HDR_DONE	equ	80h

; 8005 RECEIVE HEADER COMMAND BYTE (byte 3 of receive packet header)
;DATA_FOLLOWS		20h		from TRANSMIT HEADER COMMAND BYTE
;CHAIN_CONTINUE		40h		from TRANSMIT HEADER COMMAND BYTE

; 8005 RECEIVE HEADER STATUS BYTE (byte 4 of receive packet header)
OVERSIZE	equ	01h
CRC_ERR		equ	02h
DRIBBLE_ERR	equ	04h
SHORT_FRAME	equ	08h

; 8005 Receive Area Base Pointer
RCV_PTR		equ	((4 + GIANT + 4 + 255) AND NOT 0FFh)
; 8005 Transmit End Area Pointer (upper 8-bits)
TEA_INIT	equ	(((RCV_PTR - 1) / 256) AND 0FFh)

; macro to provide a delay after dinking w/ 8005 registers
io_delay	macro	CNTVAR
		local	io_delay_loop, skip_delay
		push	cx
		mov	cx, CNTVAR
                jcxz    skip_delay
io_delay_loop:	nop
		loop	io_delay_loop
skip_delay:	pop	cx
		endm

; macros to get and set the various device registers
; these assume that a proper 'loadport' context exists, that input/output
; register values are in AX (for word i/o) or AL (for byte i/o)
get_r		macro	R
		setport	R
		call	inb_8005
		endm

getw_r		macro	R
		setport	R
		call	[inw_fn]
		endm

set_r		macro	R
		setport	R
		call	outb_8005
		endm

setw_r		macro	R
		setport	R
		call	[outw_fn]
		endm

; wait for the 8005 FIFO to be free
fifo_wait	macro
		local	fifo_wait_loop
		setport	COMSTAT_R	; fix access within the loop
fifo_wait_loop:	getw_r	COMSTAT_R	; wait for the FIFO to be free to use
		and	ax, FIFO_EMPTY+FIFO_DIR
		cmp	ax, FIFO_EMPTY
		jne	fifo_wait_loop	; loop
		endm


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

	public	INT_NO, IO_ADDR
INT_NO		db	3, 0, 0, 0	; interrupt number
IO_ADDR		dw	300h, 0		; (factory) i/o port address

	public	DRIVER_CLASS, DRIVER_TYPE, DRIVER_NAME, DRIVER_FUNCTION
	public	PARAMETER_LIST, INT_NUM
DRIVER_CLASS	db	BLUEBOOK, 0, 0
DRIVER_TYPE	db	70		; Ether-T PC/AT, specifically
DRIVER_NAME	db	"Ether-T PC/AT", 0  ; name of the driver
DRIVER_FUNCTION	db	2		; standard plus extensions (except
					;  specific multicast)
PARAMETER_LIST	label	byte
		db	1		; major rev of packet driver spec
		db	9		; minor rev of packet driver spec
		db	14		; length of parameter list
		db	EADDR_LEN	; length of MAC-layer address
		dw	GIANT		; MTU, including headers
		dw	MAX_MULTICAST * EADDR_LEN
					; buffer size of multicast addresses
		dw	0		; (# of back-to-back MTU recvs) - 1
		dw	0		; (# of successive xmits) - 1
INT_NUM		dw	0		; interrupt # to hook for post-EOI
					;  processing, 0 == none

	public	RCV_MODES
; Receive mode set-up dispatch table.  Refers to actual code frags below that 
; do the job.
RCV_MODES	dw	7		; number of receive modes in our table
		dw	0		; none exists
		dw	rcv_mode_1	; receiver off
		dw	rcv_mode_2	; only packets for this station
		dw	rcv_mode_3	; mode 2 plus broadcast
		dw	0		; mode 3 plus limited multicast
		dw	rcv_mode_5	; mode 3 plus all multicast
		dw	rcv_mode_6	; all packets

; per-device instance structures -- keep state of the device -- ones marked
; as (copy) are soft copies of things actually on the device
	private	command_r
command_r	dw	0		; COMMAND (copy, sort of)
	private	config1_r_copy
config1_r_copy	dw	0		; CONFIGURATION 1 (copy)
	private	config2_r_copy
config2_r_copy	dw	0		; CONFIGURATION 2 (copy)
	private	cardconfig_r
cardconfig_r	db	0		; CARD CONFIGURATION (copy)
	private	rcv_errors
rcv_errors	db	0		; want to receive even flawed packets
	private	rcv_ptr_copy
rcv_ptr_copy		dw	0		; current (next) received packet ptr
	private	transmit
transmit	db	0		; currently transmitting
		db	?
station_0       db      6 dup(0)        ;current address (for disallowing
                                        ; broadcast packets)(JLD)(9-25-91)

; i/o instruction processing variables
	private	delay_mult
delay_mult	dw	10, 0		; delay multiplier / divisor - is
					; a tens fraction: delay becomes
					; (calibrated_delay * delay_mult) / 10
	private	io_16
io_16		db	0		; do 16-bit i/o?
		db	?
	private	io_delay_cnt
io_delay_cnt	dw	1, 0		; # of NOP spins to do to effect the
					; required delay (2000 ns) for the
					; worst-case i/o access
	private	iowm_delay_cnt
iowm_delay_cnt	dw	1, 0		; # of NOP spins to effect the required
					; delay (800 ns) for a write to the
					; buffer memory in the window register
	private	inw_fn
inw_fn		dw	OFFSET inwb_8005  ; function to input a word (w/ delay)
	private	outw_fn
outw_fn		dw	OFFSET outwb_8005 ; function to output a word (w/ delay)

; interrupt processing variables
	private	int_test
int_test	db	0		; in the initial interrupt testing?
	private	int_tested
int_tested	db	0		; was initial interrupt test successful?

ifdef	DEBUG
	public	ghost_int_c, send_wait_c
ghost_int_c	dw	0		; ghost (empty) interrupt counter
rx_restart	dw	0		; stalled receiver restart counter
send_wait_c	dw	0		; previous send completion wait counter
endif

	extrn	THEIR_ISR : dword

	extrn	COUNT_IN_ERR : near
	extrn	COUNT_OUT_ERR : near
	extrn	GET_NUMBER : near
	extrn	MASKINT : near
	extrn	PRINT_NUMBER : near
	extrn	RECV_COPY : near
	extrn	RECV_FIND : near
	extrn	SET_RECV_ISR : near
	extrn	UNMASKINT : near


	public	AS_SEND_PKT
; Asynchronous packet transmit routine (high-performance drivers only).
; Entry with:
;	ES:DI	control block pointer
;	DS:SI	packet to send
;	CX	packet length
; Interrupts *may* be enabled.  Returns:
;	ES:DI	preserved from call (control block pointer)
;	   and
;	NC	if OK
;	   or
;	CY	if transmission error
;	DH	error code
; Interrupt disablement on entry is preserved on exit.
AS_SEND_PKT:
		assume	ds:nothing
		ret


	public	DROP_PKT
; Drop a packet from the queue (high-performance drivers only.)
DROP_PKT:
		assume	ds:nothing
		ret


	public	GET_ADDRESS
; Get the MAC-layer address of the device.  Entry with:
;	ES:DI	spot to plop the gotten address
;	CX	size of the address buffer at ES:DI
; Returns:
;	NC	if OK
;	CX	actual size of address gotten
;	   or
;	CY	if buffer not big enough
GET_ADDRESS:
		assume	ds:CODE
		cmp	cx, EADDR_LEN	; make sure there's enough room
		jb	get_address_err
		mov	cx, EADDR_LEN	; prepare to copy 6 bytes

		; mask receiver interrupts (note the receiver is still
		;   running however) -- this also arbitrates use of the FIFO
		push	cx		; push stuff so MASKINT won't bite us
		mov	al, INT_NO
		call	MASKINT
		pop	cx		; revive stuff pushed for MASKINT

		; get the station address
		loadport
		mov	ax, config1_r_copy	; select STATION address reg 0
		or	ax, STATION_0_SEL
		setw_r	CONFIG1_R
		xor	ax, ax
		setw_r	DMA_ADDR_R
		setport	BUFWIN_R	; fix access within the loop
GET_ADDRESS_1:	get_r	BUFWIN_R	; input bytes of the address
		mov	es:[di], al
		inc	di
		loop	GET_ADDRESS_1

		; unmask receiver interrupts, releasing FIFO
		mov	al, INT_NO
		call	UNMASKINT

		mov	cx, EADDR_LEN	; return the length copied
		clc			; OK
		ret

get_address_err:
		stc			; error
		ret


	private	inb_8005
; input a byte from an arbitrary 8005 port
inb_8005:
		assume	ds:nothing
		in	al, dx
		io_delay  io_delay_cnt	; generic delay
		ret


	private	inwb_8005
; input a word from an arbitrary 8005 port -- 8 bit version
inwb_8005:
		assume	ds:nothing
		in	al, dx
		io_delay  io_delay_cnt	; generic delay
		inc	dx
		xchg	al, ah
		in	al, dx
		xchg	al, ah
		io_delay  io_delay_cnt	; generic delay
		dec	dx		; to keep 'setport' in synch
		ret


	private	inw_8005
; input a word from an arbitrary 8005 port -- 16 bit version
inw_8005:
		assume	ds:nothing
		in	ax, dx
		io_delay  io_delay_cnt	; generic delay
		ret


	private	outb_8005
; output a byte to an arbitrary 8005 port
outb_8005:
		assume	ds:nothing
		out	dx, al
		io_delay  io_delay_cnt	; generic delay
		ret


	private	outwb_8005
; output a word to an arbitrary 8005 port -- 8 bit version
outwb_8005:
		assume	ds:nothing
		out	dx, al
		io_delay  io_delay_cnt	; generic delay
		inc	dx
		xchg	al, ah
		out	dx, al
		xchg	al, ah
		io_delay  io_delay_cnt	; generic delay
		dec	dx		; to keep 'setport' in synch
		ret


	private	outw_8005
; output a word to an arbitrary 8005 port -- 16 bit version
outw_8005:
		assume	ds:nothing
		out	dx, ax
		io_delay  io_delay_cnt	; generic delay
		ret


	private	rcv_mode_1
	private	rcv_mode_2
	private	rcv_mode_3
	private	rcv_mode_5
	private	rcv_mode_6
; Receive mode set-up routines.  Dispatched to out of the RCV_MODES
; switch (above) or called directly by ETOPEN (once).
		assume	ds:CODE
rcv_mode_1:	; turn off receiver
		call	turn_rx_off
		ret
rcv_mode_2:	; only packets to this station address
		mov	ax, MATCH_ONLY	; config 1
		mov	dx, 0		; config 2
		jmp	SHORT rcv_mode_common
rcv_mode_3:	; mode 2 plus broadcast packets
		mov	ax, MATCH_BROAD	; config 1
		mov	dx, CRC_ERR_EN+DRIBBLE_EN+SHORT_FRAME_EN ; config 2
		jmp	SHORT rcv_mode_common
rcv_mode_5:	; mode 3 plus all multicast packets
		mov	ax, MATCH_MULTI	; config 1
		mov	dx, CRC_ERR_EN+DRIBBLE_EN+SHORT_FRAME_EN ; config 2
		jmp	SHORT rcv_mode_common
rcv_mode_6:	; all packets (promiscuous, including errors)
		mov	ax, MATCH_ALL	; config 1
		mov	dx, CRC_ERR_EN+DRIBBLE_EN+SHORT_FRAME_EN ; config 2
rcv_mode_common:
		push	dx		; save the changes while we
		push	ax		;   wang on the receiver
		call	turn_rx_off		; shut down whilst we play
		loadport
		pop	bx		; alter the CONFIGURATION 1 register
		mov	ax, config1_r_copy
		and	ax, NOT MATCH_BITS_M
		or	ax, bx
		mov	rcv_errors, 0
		cmp	bx, MATCH_ALL	; figure if we want to see errors
		jne	rcv_mode_common_1
		mov	rcv_errors, 1
rcv_mode_common_1:
		mov	config1_r_copy, ax
		setw_r	CONFIG1_R
		pop	bx		; alter the CONFIGURATION 2 register
		mov	ax, config2_r_copy
		and	ax, NOT (CRC_ERR_EN+DRIBBLE_EN+SHORT_FRAME_EN)
		or	ax, bx
		mov	config2_r_copy, ax
		setw_r	CONFIG2_R
		call	turn_rx_on		; drop trou now, brown cow
		ret


	private	turn_rcv_on
; turn the receiver back on, as per the COMMAND register atop the stack
; removes the COMMAND register upon return
rcv_on:
		assume	ds:CODE
		mov	bx, sp		; get former COMMAND register
		mov	ax, ss:[bx+2]
		test	ax, SET_RX_OFF	; see if it's to go back on
		jnz	rcv_on_1
		mov	command_r, ax	; restore it
		call	turn_rx_on
rcv_on_1:	ret	2		; clear stack of COMMAND register too


	private	rcvxmt_off
; forcibly turn off the receiver and/or transmitter -- if the transmitter
; is running, this will abort the transmission
; leaves the prior COMMAND register on top of the stack (for use by a
; matching 'rcv_on' call)
rcvxmt_off:
		assume	ds:CODE
		pop	bx		; return address
		push	command_r	; left for 'rcv_on'
		or	command_r, SET_TX_OFF
		mov	transmit, 0	; not doing it now
		call	turn_rx_off
		jmp	bx		; return


	public	RECV
; Part one of the ISR receive packet processing.  Called before interrupt
; acknowledgement.  All registers have been saved, DS == CS, and we're
; running on a private stack with interrupts disabled.
RECV:
		assume	ds:CODE
ifdef	DBGINT3
		int	03h		; Danger, Will Robinson!
endif
		loadport
		; check for and acknowledge the 8005 receive Interrupt
ifdef	DEBUG
		getw_r	COMSTAT_R
		test	ax, RX_INT
		jnz	recv_1
		inc	ghost_int_c	; ghost interrupt counter
recv_1:
endif
		mov	ax, command_r	; acknowledge the Rx Interrupt
		or	ax, RX_INT_ACK
		setw_r	COMSTAT_R
		cmp	int_test, 0	; is this interrupt a test one?
		je	recv_setup
		inc	int_tested	; indicated tested and be done
		ret

recv_setup:	; set-up DMA access of the buffer read memory
		mov	ax, config1_r_copy	; select BUFFER MEMORY in BUFWIN
		or	ax, BUFFER_MEM_SEL
		setw_r	CONFIG1_R

		setport	DMA_ADDR_R	; code block port state synch
; COME HERE w/ port @ DMA_ADDR_R
recv_rcvptr:	mov	ax, rcv_ptr_copy	; address the next RECEIVE HEADER
		setw_r	DMA_ADDR_R
		mov	ax, command_r	; set the FIFO to READ now
		or	ax, FIFO_READ
		setw_r	COMSTAT_R

		setport	BUFWIN_R	; code block port state synch
; COME HERE w/ port @ BUFWIN_R
recv_loop:	; check for an end-of-list header (0 next pointer)
		getw_r	BUFWIN_R	; get the HEADER pointer
		cmp	al, 0		; check for end of receive list
		je	recv_loop_1	;  looking only at first byte
		xchg	al, ah		; swap to proper host order
		mov	cx, ax		; save pointer away
		getw_r	BUFWIN_R	; get the HEADER & PACKET STATI
		test	al, CHAIN_CONTINUE  ; test for completion
		xchg	al, ah		; AL now holds PACKET STATUS byte
		jnz	recv_continue
recv_loop_1:	jmp	recv_done

recv_continue:	; process a new frame - compute length and check it
		push	cx		; squirrel away HEADER POINTER
		sub	cx, rcv_ptr_copy	; compute length, checking for wrappage
		jae	recv_continue_1
		sub	cx, RCV_PTR	; adjust for wrap, sans xmit area
recv_continue_1:
		sub	cx, 4		; less size of 8005 garf
		cmp	cx, ENET_HDR	; < a header is tallied and pitched
		jb	recv_continue_2
		cmp	cx, GIANT	; > GIANT likewise
		ja	recv_continue_2
		test	al, SHORT_FRAME+DRIBBLE_ERR+CRC_ERR
		jz	recv_alloc	; no error is a-OK
		cmp	rcv_errors, 0	; see if we have to allow bad ones
		jne	recv_alloc	; yes, keep this dreck too
recv_continue_2:
		call	COUNT_IN_ERR	; tally this error
		jmp	recv_sail_it

recv_alloc:	; this one we want - allocate enough space on the stack
		;  to hold the Ethernet-2 packet header (so we can upcall
		;  pointing at the type field)
		mov	bx, cx		; save the real length
		mov	cx, ENET_HDR
		sub	sp, cx		; allocate a header on the stack
		mov	di, sp		;   and point at it w/ DI
		mov	ax, ss		; set-up ES too
		mov	es, ax
		cmp	io_16, 0	; do we do it fast or slow?
		je	recv_8bit_hdr
		sar	cx, 1		; turn into words
;	rep	insw
recv_16bit_hdr:
		insw			; 16-bit input	
		io_delay iowm_delay_cnt	; reduced delay w/i loop
		loop	recv_16bit_hdr
		jmp	SHORT recv_upcall1
recv_8bit_hdr:				; 8-bit input
		in	al, dx
		mov	es:[di], al
		inc	di
		loop	recv_8bit_hdr

recv_upcall1:
;#              ; check to make sure this isn't our own broadcast(JLD)(9-25-91)
                push    di
	    	mov	di, sp		; point at the destination
                add     di, 2           ; which is 2 bytes away(remember push di?)
                mov     ax, 0ffffh
                mov     cx, 6/2
            repe scasw
                jne     NotBroadcast
            
                push    si
                mov     si, offset station_0
                mov     cx, 6/2
            repe cmps   word ptr cs:[si], word ptr es:[di]
                pop     si
            
                jne     NotBroadcast

                pop     di
		add	sp, ENET_HDR	; pop the Ethernet-2 header from stack
                                        ; I forgot this until it bit me (11-6)
                jmp     recv_sail_it    ; if it's us, bail out!
NotBroadcast:
                pop     di


                ; prepare an upcall with CX == length, ES:DI pointing at type,
		;  DL == packet class number (V8)
		mov	di, sp		; point at the type field
		add	di, EADDR_LEN*2
		mov	cx, bx		; get the real length for RECV_FIND
		push	bx		; save the real length
		push	dx		;  and save our precious port setup too
;#
	mov	dl, BLUEBOOK		;assume bluebook Ethernet.
	mov	ax, es:[di]
	xchg	ah, al
	cmp 	ax, 1500
	ja	BlueBookPacket
	inc	di			;set di to 802.2 header
	inc	di
	mov	dl, IEEE8023
BlueBookPacket:
;;;;		mov	dl, BLUEBOOK	; Ethernet-2 'Bluebook' (V8)
		call	RECV_FIND

		pop	dx		; get back port setup
		pop	bx		;  and true packet length
		mov	ax, es		; see if the client passed on this one
		or	ax, di
		jz	recv_sail_pop

		mov	si, sp		; prepare to MOVS the Ethernet-2 header
		mov	ax, ss
		mov	ds, ax
		assume	ds:nothing
		mov	cx, ENET_HDR
		push	di		; save client packet base pointer
	rep	movsb
		pop	si		; restore client packet base pointer
		add	sp, ENET_HDR	; pop the Ethernet-2 header from stack

		mov	cx, bx		; compute residual length to bring in
		sub	cx, ENET_HDR
		cmp	io_16, 0	; do we do it fast or slow?
		je	recv_8bit_data

		sar	cx, 1		; /2: compute # of words to transfer
;	rep	insw			; loosely termed "full-bandwidth i/o"
recv_16bit_data:
		insw
		io_delay iowm_delay_cnt	; reduced delay w/i loop
		loop	recv_16bit_data

		mov	cx, bx		; get length back one more time
		test	bl, 1		; see if there's a dangling byte
		jz	recv_upcall2
		mov	cx, 1		; set-up to fall through to 8-bit

recv_8bit_data:	in	al, dx
		mov	es:[di], al
		inc	di
		loop	recv_8bit_data
		mov	cx, bx		; get length back one more time
		xor	bl, bl		; for sail testing after upcall2

recv_upcall2:	; prepare an upcall with CX == length, DS:SI pointing at
		;  client-allocated and now filled packet
		mov	ax, es
		mov	ds, ax
		push	dx		; save our precious port setup

		call	RECV_COPY

		pop	dx		; get back port setup
		mov	ax, cs		;  and set DS back to CODE
		mov	ds, ax
		assume	ds:CODE

		; have to check if 16-bit i/o processed an odd-length packet
		;  - if so, we MAY have blown the harmony of the 8005 DMA
		;  (may now be off by one) so just sail it
		test	bl, 1
		jnz	recv_sail_it

		pop	cx		; pop squirreled next HEADER POINTER
		mov	rcv_ptr_copy, cx	;  and make that the new pointer
		mov	al, ch		; update the REA PTR register, making
		set_r	REA_PTR_R	;  room for more inbound packets
		setport	BUFWIN_R
		jmp	recv_loop

recv_sail_pop:	add	sp, ENET_HDR	; pop the Ethernet-2 header from stack

recv_sail_it:	; rid ourselves of this frame the hard way: by resetting the
		;   DMA, in addition to our own bookkeeping stuff
		loadport
		mov	ax, command_r	; release the DMA FIFO
		or	ax, FIFO_WRITE+DMA_INT_ACK
		setw_r	COMSTAT_R
		pop	cx		; pop squirreled next HEADER POINTER
		mov	rcv_ptr_copy, cx	;  and make that the new pointer
		mov	al, ch		; update the REA PTR register, making
		set_r	REA_PTR_R	;  room for more inbound packets
		setport	DMA_ADDR_R
		jmp	recv_rcvptr

recv_done:	; all inbound frames processed
		loadport
		mov	ax, command_r	; release the DMA FIFO
		or	ax, FIFO_WRITE+DMA_INT_ACK
		setw_r	COMSTAT_R
		; restart a stalled receiver
		getw_r	COMSTAT_R	; grab the STATUS register
		test	ax, RX_ON	; receiver still on?
		jnz	recv_done_1
ifdef	DEBUG
		inc	rx_restart
endif
		call	turn_rx_on
recv_done_1:
		ret


	public	RECV_EXITING
; Part two of the ISR receive packet processing.  Called after interrupts
; have been acknowledged.  Only DS and AX have been saved, DS == CS and
; we're running on the original stack (i.e. not the private one).  On
; entry, interrupts are still disabled but it is possible to intelligently
; turn them back on.
RECV_EXITING:
		assume	ds:CODE
		ret


	public	RESET_INTERFACE
; Reset the interface.
RESET_INTERFACE:
		assume	ds:CODE
		loadport
		mov	ax, RESET
		setw_r	CONFIG2_R
		; wait a good, long while
		mov	ax, 1
		call	set_timeout
RESET_INTERFACE_1:
		call	do_timeout
		jnz	RESET_INTERFACE_1
;;;;		; delay another 4 microseconds
;;;;		io_delay  io_delay_cnt	; generic delay
;;;;		io_delay  io_delay_cnt	; generic delay
		ret


	private	turn_rx_off
; Turn off the receiver portion of the device.  Undoes 'turn_rx_on'.
turn_rx_off:
		assume	ds:CODE
		loadport
		mov	ax, command_r
		and	ax, NOT RX_INT_EN
		or	ax, SET_RX_OFF
		mov	command_r, ax
		setw_r	COMSTAT_R
		ret


	private	turn_rx_on
; Turn on the receiver portion of the device.  Assumes the station address
; has already been programmed, receive ISR registered, etc.  Just brings it
; all to life on the wire.  Undone by 'turn_rx_off'.
turn_rx_on:
		assume	ds:CODE
		loadport
		; load up Receive End Area Pointer
		mov	al, TEA_INIT + 1
		set_r	REA_PTR_R
		; load up the Receive Pointer Register
		mov	ax, RCV_PTR
		setw_r	RX_PTR_R
		mov	rcv_ptr_copy, ax
		; now turn it on
		mov	ax, command_r
		and	ax, NOT SET_RX_OFF
		or	ax, RX_INT_EN
		mov	command_r, ax
		or	ax, SET_RX_ON
		setw_r	COMSTAT_R
		ret


	public	SEND_PKT
; Send one packet.  Entry with:
;	DS:SI	packet buffer to xmit
;	CX	packet length
; 	ES:DI	upcall routine (0:0 if no upcall is desired) -- only supported
;		if the high-performance bit is set in 'driver_function' byte
; Returns:
;	NC	if OK
;	   or
;	CY	if transmission error
;	DH	error code
SEND_PKT:
		assume	ds:nothing
ifdef	DBGINT3
		int	03h		; Danger, Will Robinson!
endif
		mov	ax, ds		; restore DS usage right here, saving
		mov	es, ax		;   it in ES (as we don't do upcalls)
		mov	ax, cs
		mov	ds, ax
		assume 	ds:CODE
		cmp	cx, ENET_HDR	; firewall against trivia
		jae	SEND_PKT_1
		jmp	send_pkt_ret
SEND_PKT_1:
		cmp	cx, GIANT	; see if too big
		jbe	SEND_PKT_2
		jmp	send_pkt_NOSPACE
SEND_PKT_2:
		cmp	cx, RUNT	; see if too small
		jae	SEND_PKT_3	; ... sounds like the Three Bears
		mov	cx, RUNT	; transmit at least this much
SEND_PKT_3:
		; mask receiver interrupts (note the receiver is still
		;   running however) -- this also arbitrates use of the FIFO
		push	cx		; push stuff so MASKINT won't bite us
		mov	al, INT_NO
		call	MASKINT

		loadport
		mov	ax, config1_r_copy	; select BUFFER MEMORY in BUFWIN
		or	ax, BUFFER_MEM_SEL
		setw_r	CONFIG1_R

		; check status of the previous transmission
		cmp	transmit, 0	; see if a transmit is incomplete
		je	send_pkt_load
send_pkt_wait:
		loadport		; synch for the loop
		mov	ax, 3		; address the TRANSMIT STATUS byte
		setw_r	DMA_ADDR_R
		mov	ax, command_r	; set the FIFO to READ now, also
		or	ax, FIFO_READ+TX_INT_ACK  ; ACKs any TX int bit
		setw_r	COMSTAT_R
		get_r	BUFWIN_R	; get the STATUS byte
		mov	bl, al		; save the gotten STATUS byte
		mov	ax, command_r	; set the FIFO back to WRITE (aborts,
		or	ax, FIFO_WRITE+DMA_INT_ACK  ; drains, ACKs FIFO int bit)
		setw_r	COMSTAT_R
		test	bl, HDR_DONE	; check for transmit done
ifdef	DEBUG
		jnz	send_pkt_wait_1
		inc	send_wait_c	; count send completion waits
		jmp	send_pkt_wait	; loop
send_pkt_wait_1:
else
		jz	send_pkt_wait	; loop
endif
		; done -- check for transmit error & count if so
		test	bl, BABBLE_ERR+COLL_16_ERR
		jz	send_pkt_load
		call	COUNT_OUT_ERR	; count the error

send_pkt_load:	; load the new packet into the transmit area
		pop	cx		; revive stuff pushed for MASKINT
		; load up the transmit buffer -- allows only a single
		;   transmit (doesn't chain 'em); however, this transmission
		;   is overlapped with the preparation of the next
		loadport		; synch the state
		xor	ax, ax		; TRANSMIT NEXT HEADER word
		setw_r	DMA_ADDR_R
		mov	ax, cx		; calculate xmit request length
		add	ax, 4		;   including 8005 garf
		xchg	ah, al		; SWAP
		setw_r	BUFWIN_R
		mov	al, PACKET_PRESENT+DATA_FOLLOWS ; TRANSMIT COMMAND byte
		xor	ah, ah		; TRANSMIT STATUS byte
		setw_r	BUFWIN_R	; both in one SWAPPED swoop

		; semi-quick programmed output here -- direct use of macros
		mov	ax, es		; go back to DS-relative packet data
		mov	ds, ax
		assume	ds:nothing
		cmp	io_16, 0	; can do 16-bit i/o?
		je	send_pkt_8bit
		test	cx, 1		; odd length?
		jz	send_pkt_load_1
		inc	cx		; make even for 16-bit mode output
send_pkt_load_1:
		sar	cx, 1		; /2: compute # of words to transfer
		cmp	iowm_delay_cnt, 0  ; check for full-tilt boogie
		jne	send_pkt_load_2	; nope, need delays
		rep	outsw		; blow-out clearance i/o
		jmp	SHORT send_pkt_xmt
send_pkt_load_2:
		mov	ax, [si]	; Note: DS-relative packet here
		out	dx, ax
		io_delay iowm_delay_cnt	; reduced delay w/i loop
		inc	si
		inc	si
		loop	send_pkt_load_2
		jmp	SHORT send_pkt_xmt	; done w/ buffer load

send_pkt_8bit:	; 8 bit packet load
		cmp	iowm_delay_cnt, 0  ; check for full-tilt boogie
		jne	UpHere_1	; nope, need delays

;the following replaces rep outsb, which doesn't work too well on 8088-based PCs
;;	rep	outsb			; blow-out clearance i/o

UpHere:		lodsb			; Note: DS-relative packet here
		out	dx, al
                loop    UpHere

		jmp	SHORT send_pkt_xmt
UpHere_1:
		mov	al, [si]	; Note: DS-relative packet here
		out	dx, al
		io_delay iowm_delay_cnt	; reduced delay w/i loop
		inc	si
		loop	UpHere_1

send_pkt_xmt:	; packet now loaded, finish up and transmit it
		mov	ax, cs		; restore our DS
		mov	ds, ax
		assume	ds:CODE
		; set TRANSMIT POINTER (overlap as FIFO drains)
		xor	ax, ax		; start chain at address 0
		setw_r	TX_PTR_R
		; wait for DMA FIFO to drain
		fifo_wait
		; now transmit it
		mov	ax, command_r	; remove default transmitter off cmd
		and	ax, NOT SET_TX_OFF
		mov	command_r, ax
		or	ax, SET_TX_ON
		setw_r	COMSTAT_R	; Send!
		mov	transmit, 1	; note to check completion next time
		; unmask receiver interrupts, releasing FIFO
		mov	al, INT_NO
		call	UNMASKINT
send_pkt_ret:
		clc			; OK
		ret

send_pkt_NOSPACE:
		mov	dh, NO_SPACE
		stc			; error
		ret


	public	SET_ADDRESS
; Set the MAC station receiver address.  Entry with:
;	DS:SI	the new MAC address to adopt
;	CX	length of address at DS:SI
; Returns:
;	DS	restored to point at CODE segment
;	   plus
;	NC	if OK
;	   or
;	CY	if error
;	DH	error code
SET_ADDRESS:
		assume	ds:nothing
		mov	ax, ds		; undo the twisted DS usage
		mov	es, ax		;   back to addressing our memory
		mov	ax, cs
		mov	ds, ax
		 assume	ds:CODE
		cmp	cx, EADDR_LEN	; check for proper address length
		jne	set_addr_BADADDRESS
		test	BYTE PTR es:[si], 1  ; deny broadcast or multicast
		jnz	set_addr_BADADDRESS

;#		; set the new station address (JLD)(9-25-91)
                mov     ax, ds:[si][0]
                mov     word ptr ds:station_0[0], ax

                mov     ax, ds:[si][2]
                mov     word ptr ds:station_0[2], ax

                mov     ax, ds:[si][4]
                mov     word ptr ds:station_0[4], ax

		; disable receiver and/or transmitter
		call	rcvxmt_off	; leaves COMMAND register on stack

		; set the new station address
		loadport
		mov	ax, config1_r_copy	; select STATION address reg 0
		or	ax, STATION_0_SEL
		setw_r	CONFIG1_R
		xor	ax, ax
		setw_r	DMA_ADDR_R
		setport	BUFWIN_R	; fix access within the loop
SET_ADDRESS_1:
		mov	al, es:[si]	; output bytes of the new address
		inc	si
		set_r	BUFWIN_R
		loop	SET_ADDRESS_1

		; reenable the receiver if it was on before
		call	rcv_on		; removes COMMAND register from stack

		clc			; OK
		ret

set_addr_BADADDRESS:
		mov	dh, BAD_ADDRESS	; improper address
		stc			; error
		ret


	public	SET_MULTICAST_LIST
; Set the multicast list.  Entry with:
;	DS:SI	the list of multicast MAC addresses
;	CX	number of addresses at DS:SI
; Returns:
;	NC	if OK
;	   or
;	CY	if error
;	DH	error code
SET_MULTICAST_LIST:
		assume	ds:CODE
		jmp	SHORT set_multicast_err	; FOR NOW: no multicast support
		clc			; OK
		ret
set_multicast_err:
		mov	dh, NO_MULTICAST
		stc			; error
		ret


	public	TERMINATE
; Terminate the packet driver.  Nothing special to do.
TERMINATE:
		assume	ds:CODE
		ret

	public	XMIT
; Routine to process a queued transmission with the least possible latency.
; Called immediately within the receive ISR.  The attempt here is to effect
; back-to-back transmissions.  This routine may only use AX and DX freely
; and is running whatever stack exists at interrupt time (not the private
; one).  This routine isn't necessary on the 8005, because it can chain
; transmissions (if anyone really cared).
XMIT:
		assume	ds:nothing
		ret


	include timeout.asm


; Everything above this line is resident upon successful load.

	private	END_RESIDENT
END_RESIDENT	label	byte

; Everything below this line is discarded upon installation.

	public	USAGE_MSG
USAGE_MSG	db	"usage: davidsys [-n] [-d] [-w] <packet_int_no> <int_no> <io_addr> <delay_mult>",CR,LF,'$'

	public	COPYRIGHT_MSG
COPYRIGHT_MSG	db	"Packet driver for the Ether-T PC/AT ",'0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,".",'0'+s8005_version,CR,LF
		db	"Portions Copyright 1991 DAVID Systems, Inc and Marc S Dye",CR,LF,'$'

badint_msg	db	"Specified device interrupt not supported for"
		db	" your installation",CR,LF,'$'
int8_msg	db	" Try: 2, 3, 4, 5, 6, or 7",CR,LF,'$'
int16_msg	db	" Try: 3, 4, 5, 6, 7, 9, 10, 11, 12, 14, or 15"
		db	  CR,LF,'$'
inttimedout_msg	db	"Timed out awaiting device test interrupt",CR,LF,'$'
nodev_msg	db	"No device found at specified i/o address",CR,LF,'$'
setaddr_msg	db	"Unable to set initial station address",CR,LF,'$'

delay_mult_name	db	"Delay multipler (x10) ",'$'
int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'

ifdef	DEBUG
count_del_name	db	"Incremental i/o delay spin (nsec x10) ",'$'
delay_del_name	db	"Basic i/o delay (nsec x100) ",'$'
io_delay_name	db	"I/O delay (spins) ",'$'
iowm_delay_name	db	"I/O write memory delay (spins) ",'$'
null_bias_name	db	"Null (overhead) cost (nsec x100) ",'$'
null_spin_name	db	"Null spin count ",'$'
ref_spin_name	db	"Reference spin count ",'$'
unit_spin_name	db	"Single spin count ",'$'
endif

	private	int_map
; Interrupt number to Ether-T PC/AT configuration register code mapping table
		;	 0    1    2    3    4    5    6    7
int_map		db	00h, 00h, 02h, 01h, 03h, 04h, 05h, 06h
		;	 8    9   10   11   12   13   14   15
		db	00h, 02h, 07h, 08h, 09h, 00h, 0Ah, 0Bh

REF_DELAY	equ	64

	private	count_delay
count_delay	dw	?, 0		; # of 10 nsecs in each io_delay spin
	private	delay_delay
delay_delay	dw	?, 0		; # of 100 nsecs in the basic io_delay
	private	null_bias
null_bias	dw	?, 0		; # of 100 nsecs in a null spin
	private	null_spins
null_spins	dw	?, 0		; # of spins for without io_delay macro
	private	ref_spins
ref_spins	dw	?, 0		; # of spins for io_delay of 1+REF_DELAY
	private	test_delay
test_delay	dw	?		; holding tank for current sample count
	private	unit_spins
unit_spins	dw	?, 0		; # of spins for io_delay of 1


; macro to sample timer zero's counter register
timer0_sample	macro
		xor	al, al		; latch timer zero counter 
		out	43h, al
		in	al, 40h		; sample timer zero counter
		mov	ah, al
		in	al, 40h
		xchg	ah, al
		endm


	private	calibrate_one
calibrate_one:
		assume	ds:CODE
		mov	test_delay, ax
		; find the timer near zero
		mov	cx, 100h
calibrate_one_1:
		timer0_sample
		cmp	ax, cx
		ja	calibrate_one_1
		; now find the timer above that value (after wrapping to max)
calibrate_one_2:
		timer0_sample
		cmp	ax, cx
		jbe	calibrate_one_2
		; wait till it gets to the one quarter point
		mov	cx, 0C000h
calibrate_one_3:
		timer0_sample
		cmp	ax, cx
		ja	calibrate_one_3
		; calibration time - disable interrupts, watch for half a
		;  clock period to elapse
		pushf
		cli
		xor	bx, bx
		mov	cx, 4000h
		cmp	test_delay, 0		; see if measuring overhead?
		jne	calibrate_io
		; calibrating the overhead of this timing stuff only
calibrate_one_4:
		inc	bx
		jc	calibrate_exit		; overflow - exit w/ 0
		timer0_sample
		cmp	ax, cx
		ja	calibrate_one_4
		jmp	SHORT calibrate_exit
calibrate_io:	io_delay  test_delay
		inc	bx
		jc	calibrate_exit		; overflow - exit w/ 0
		timer0_sample
		cmp	ax, cx
		ja	calibrate_io
calibrate_exit:	popf
		mov	ax, bx
		ret


	private	calibrate_delay
calibrate_delay:
		assume	ds:CODE
		; if user wanted no delays, skip all this stuff
		cmp	delay_mult, 0
		jne	calibrate_delay_1
		xor	ax, ax			; normalize to zero
		jmp	calibrate_store
calibrate_delay_1:
		; calibrate the overhead of the time sampling loop
		xor	ax, ax
		call	calibrate_one
		or	ax, ax
		jz	calibrate_dj		; can't time -- default it all
		mov	null_spins, ax
ifdef	DEBUG
		mov	di, OFFSET null_spins
		mov	dx, OFFSET null_spin_name
		call	PRINT_NUMBER
endif
		; calibrate a unit (single spin) loop
		mov	ax, 1
		call	calibrate_one
		or	ax, ax
		jz	calibrate_dj		; can't time -- default it
		mov	unit_spins, ax
ifdef	DEBUG
		mov	di, OFFSET unit_spins
		mov	dx, OFFSET unit_spin_name
		call	PRINT_NUMBER
		mov	ax, unit_spins	; reload AX
endif
		cmp	null_spins, ax		; ensure null >= unit
		jb	calibrate_dj		; bogonic -- default it
		; now one with some teeth in it
		mov	ax, 1+REF_DELAY
		call	calibrate_one
		or	ax, ax
		jz	calibrate_dj		; can't time -- default it all
		mov	ref_spins, ax
ifdef	DEBUG
		mov	di, OFFSET ref_spins
		mov	dx, OFFSET ref_spin_name
		call	PRINT_NUMBER
		mov	ax, ref_spins		; reload AX
endif
		cmp	unit_spins, ax		; ensure unit > ref
		ja	calibrate_dj_1
calibrate_dj:	jmp	SHORT calibrate_def	; bogonic -- default it
calibrate_dj_1:

		; now do the math - compute the overhead of the timing code
		;  (in units of 100 nsec)
		mov	cx, null_spins
		call	compute_100nsecs
		jc	calibrate_def
		mov	null_bias, ax
ifdef	DEBUG
		mov	di, OFFSET null_bias
		mov	dx, OFFSET null_bias_name
		call	PRINT_NUMBER
endif
		; compute cost of the basic i/o delay macro (in units of
		;  100 nsec)
		mov	cx, unit_spins
		call	compute_100nsecs
		jc	calibrate_def
		sub	ax, null_bias
		mov	delay_delay, ax
ifdef	DEBUG
		mov	di, OFFSET delay_delay
		mov	dx, OFFSET delay_del_name
		call	PRINT_NUMBER
endif
		; compute cost of the reference spin in the i/o delay
		;  macro (this time, in units of 10 nsec)
		mov	cx, ref_spins
		call	compute_100nsecs
		jc	calibrate_def
		sub	ax, null_bias
		sub	ax, delay_delay		; now delay for REF_COUNT spins
		mov	cx, 10
		mul	cx
		jnc	calibrate_dj_2
		; this machine is *sooo* slow, a minimum delay is fine
		mov	ax, 1
		jmp	SHORT calibrate_store
calibrate_dj_2:
		mov	cx, REF_DELAY
		div	cx
		mov	count_delay, ax
ifdef	DEBUG
		mov	di, OFFSET count_delay
		mov	dx, OFFSET count_del_name
		call	PRINT_NUMBER
endif

		; now, see if basic i/o delay (w/o extra spins) is enough
		mov	bx, 1			; basic macro includes one spin
		mov	ax, delay_delay
		sub	ax, 22			; >= 2.2 usec ?
		jae	calibrate_dj_3
		neg	ax			; correct sign of residual
		mov	cx, 10			; scale to 10 nsec units
		mul	cx
		jc	calibrate_def
		div	count_delay
		inc	ax
		add	bx, ax			; back into spin count result
calibrate_dj_3:
		mov	ax, bx			; the calibrated basic result
		jmp	SHORT calibrate_store

calibrate_def:	; this processor is *sooo* fast that we can't calibrate it!?
		; just use a conservative default (should be good to about a
		;  400MHz 80486)
		mov	ax, 120

		; scale the delay by the user-request multiplier (if any)
calibrate_store:
		mul	delay_mult		; a tens multiple
		mov	cx, 10			; so reduce / 10
		div	cx			; allowed to be zero
		mov	io_delay_cnt, ax
		; compute the .9 usec 'iowm_delay_cnt'
		mov	cx, 9
		mul	cx
		mov	cx, 20
		div	cx
		mov	iowm_delay_cnt, ax
		ret


	private	compute_100nsecs
; Given a value in CX (representing spin counter during a 1/36.04 timing
;  run), compute the number of hundreds of nanoseconds per unit spin. Return
;  result in AX, or set CY and return if algorithm won't scale.
compute_100nsecs:
		assume	ds:CODE
		mov	ax, 9			; compute spins * 9 (36.04/4)
		mul	cx
		jc	compute_100n_err	; won't fit -- return error
		mov	cx, ax			; prepare for division
		mov	dx, 26h			; put 2,500,000 into DX,AX
		mov	ax, 25a0h
		div	cx			; compute 10M / (spins*36.04)
		clc
		ret
compute_100n_err:
		ret


	public	ETOPEN
; Perform initial open of the device.  Called only once: immediately prior to
; hooking the packet driver service interrupt and TSRing.  Device interrupts
; are to be hooked (if this routine deems reasonable to do so).  No registers
; are given on entry.  On return:
;	NC	if OK
;	DX	offset of the end of the resident portion of the driver
;	   or
;	CY	if error
ETOPEN:
		assume	ds:CODE
ifdef	DBGINT3
		int	03h		; Danger, Will Robinson!
endif
		; calibrate the i/o delays needed
		call	calibrate_delay
ifdef	DEBUG
		mov	di, OFFSET io_delay_cnt
		mov	dx, OFFSET io_delay_name
		call	PRINT_NUMBER
		mov	di, OFFSET iowm_delay_cnt
		mov	dx, OFFSET iowm_delay_name
		call	PRINT_NUMBER
endif

		; test for access to the device; check for 16-bit i/o
		call	try_io
		jnc	ETOPEN_1
		jmp	etopen_err
ETOPEN_1:
		; try out the interrupt channel & leave hooked if successful
		call	try_interrupt
		jnc	ETOPEN_2
		jmp	etopen_err
ETOPEN_2:
		; get the PROM Ethernet address
		call	RESET_INTERFACE	; reset the device (again!)
		loadport
		mov	ax, command_r	; everything still off
		or	ax, FIFO_WRITE
		setw_r	COMSTAT_R
		xor	ax, ax		; reset the DMA address to 0
		setw_r	DMA_ADDR_R
		mov	ax, config1_r_copy	; select the PROM
		or	ax, PROM_SEL
		setw_r	CONFIG1_R
		mov	ax, config2_r_copy	; blast the watchdog
		or	ax, KILL_WATCHDOG
		setw_r	CONFIG2_R
		mov	cx, EADDR_LEN	; allocate some space for the address
		sub	sp, cx
		mov	bp, sp
		setport	BUFWIN_R	; fix access within the loop
ETOPEN_3:
		get_r	BUFWIN_R	; next byte of PROM Ethernet address
		mov	[bp], al
		inc	bp
		loop	ETOPEN_3

		; set the PROM address as the default STATION 0 address
		call	RESET_INTERFACE	; reset the device (yet again!)

		mov	cx, EADDR_LEN	; size of Ethernet address
		mov	si, sp		; PROM Ethernet address atop stack
		call	SET_ADDRESS
		add	sp, EADDR_LEN	; pop Ethernet address from the stack

		; load up Transmit End Area Pointer
		loadport
		mov	ax, config1_r_copy
		or	ax, TEA_SEL
		setw_r	CONFIG1_R
		mov	al, TEA_INIT
		set_r	BUFWIN_R

		; set up a few other default goodies once only here:
		;  config 1 :	STATION 0 address (Matchmode by 'rcv_mode_3')
		;  config 2 :	defaults are OK
		or	config1_r_copy, STATION_0_EN
		mov	ax, config1_r_copy
		setw_r	CONFIG1_R

		; we're go now -- wake up and smell the roses!
		and	command_r, NOT SET_RX_OFF
		; reload the CARD CONFIGURATION register
		mov	ax, config1_r_copy	; select CARD CONFIGURATION register
		or	ax, CARD_CONFIG_SEL
		setw_r	CONFIG1_R
		mov	al, cardconfig_r
		set_r	BUFWIN_R	; writes CARD CONFIGURATION register
		; turn on the receiver now, in (default) mode 3
		call	rcv_mode_3

		; tell the caller where resident memory ends
		mov	dx, OFFSET END_RESIDENT
		clc			; OK
		ret

etopen_err:
		stc			; error
		ret


	public	PARSE_ARGS
; Parse the device-specific portion of the invokation command line (after
; the packet driver vector number).  Entry with:
;	DS:SI	residual command line
; Can emit device-specific bitches as necessary (using DOS i/o) and exit
; without returning.  If it returns:
;	NC	if OK
;	   or
;	CY	if usage error
; Returning w/ carry set will cause a usage error, then exit without further
; processing.
PARSE_ARGS:
		assume	ds:CODE
		mov	di, OFFSET INT_NO
		call	GET_NUMBER
		mov	di, OFFSET IO_ADDR
		call	GET_NUMBER
		mov	di, OFFSET delay_mult
		call	GET_NUMBER
		clc			; OK
		ret


	public	PRINT_PARAMETERS
; Spew out all of the configuration parameters to the console device.
; Called within the main program, just prior to deciding to TSR.  Should
; dump out each device-specific parameter, parsed by PARSE_ARGS (just to
; make the user warm-and-fuzzy I suppose...)  Entry with:
;	no special register contents
; Returns:
;	nothing
PRINT_PARAMETERS:
		assume	ds:CODE
		mov	di, OFFSET INT_NO
		mov	dx, OFFSET int_no_name
		call	PRINT_NUMBER
		mov	di, OFFSET IO_ADDR
		mov	dx, OFFSET io_addr_name
		call	PRINT_NUMBER
		mov	di, OFFSET delay_mult
		mov	dx, OFFSET delay_mult_name
		call	PRINT_NUMBER
		ret


	private	try_interrupt
; try out the receiver interrupt (once at the outset); in the process,
; sets up the CARD CONFIGURATION register
try_interrupt:
		assume ds:CODE
		call	RESET_INTERFACE
		loadport
		mov	ax, config1_r_copy	; program CARD CONFIGURATION register
		or	ax, CARD_CONFIG_SEL
		setw_r	CONFIG1_R
		; map interrupt number to card config enumeration value
		mov	bx, WORD PTR INT_NO
		mov	al, [bx + OFFSET int_map]
		or	al, al
		jz	try_int_badint_err
		; check for 16-bit i/o usage; include in card config value
		cmp	io_16, 0
		je	try_interrupt_1
		or	al, SET_16BITMODE_M
try_interrupt_1:
		set_r	BUFWIN_R	; writes CARD CONFIGURATION register
		mov	cardconfig_r, al  ; save soft-copy of this
		; install the 'recv_isr' handler
		mov	int_test, 1	; flag that we're testing only
		call	SET_RECV_ISR
		; cause a receiver interrupt
		loadport
		mov	ax, command_r	; SET_RX_OFF in here now
		or	ax, RX_INT_EN+SET_RX_ON
		setw_r	COMSTAT_R	; Interrupt!
;# Compaq DOS leaves CPU ints disabled!
                sti
		mov	ax, 1		; wait one tick
		call	set_timeout
try_interrupt_2:
		call	do_timeout
		jz	try_int_timedout
		cmp	int_tested, 0	; did it happen?
		je	try_interrupt_2
		; the interrupt worked -- leave w/ the receiver off, the
		;   ISR hooked but masked
		mov	ax, command_r	; SET_RX_OFF in here now
		setw_r	COMSTAT_R	; shut up!
		mov	al, INT_NO
		call	MASKINT
		mov	int_test, 0	; from now on, it's the real thing
		clc			; OK
		ret

try_int_timedout:
		; timed-out (bummer dude!) -- turn receiver back off
		mov	ax, command_r	; SET_RX_OFF in here now
		or	ax, RX_INT_ACK 	; just in case ...
		setw_r	COMSTAT_R	; shut up!
		; deregister handler and boogie
		;   This should be a standard call -- I've asked Russ ...
		mov	al, INT_NO
		add	al, 8
		cmp	al, 8+8		; is it a slave 8259 interrupt?
		jb	try_int_timedout_1; no.
		add	al, 70h - (8+8)	; map it to the real interrupt
try_int_timedout_1:
		push	ds
		assume	ds:nothing
		lds	dx, THEIR_ISR
		mov	ah, 25h
		int	21h
		pop	ds
		assume	ds:CODE
		mov	dx, OFFSET inttimedout_msg	
		jmp	SHORT try_int_err
try_int_badint_err:
		mov	dx, OFFSET badint_msg
		mov	ah, 9		; blab to the user
		int	21h
		cmp	io_16, 0	; tell him/her/it what's legit
		jne	try_int_badint_err_1
		mov	dx, OFFSET int8_msg
		jmp	SHORT try_int_err
try_int_badint_err_1:
		mov	dx, OFFSET int16_msg
try_int_err:
		mov	ah, 9		; blab to the user
		int	21h
		stc			; error
		ret


	private	try_io
; try out i/o to the device; in the process, determines if 16-bit i/o is OK
; NOTE: This code was transliterated from the C source for DAVID's NDIS driver.
;   In the absence of any written documentation on the CARD CONFIGURATION
;   register, the many magic numbers herein remain a mystery.
try_io:
		assume ds:CODE
		call	RESET_INTERFACE
		mov	command_r, SET_TX_OFF+SET_RX_OFF+SET_DMA_OFF
		loadport
		mov	ax, config1_r_copy	; select CARD CONFIGURATION register
		or	ax, CARD_CONFIG_SEL
		set_r	CONFIG1_R	; BYTE-size only here!
		; first taunting of the device ...
		mov	al, 05h		; C sez: "4 bit is LSBS value"
		set_r	BUFWIN_R	;        " ... and ... "
		get_r	BUFWIN_R	; C sez: "comes back in the 4 MSBs"
		and	al, 0F0h
		cmp	al, 50h		; this has to compare
		jne	try_io_err
		; second taunting of the device ...
		mov	al, 0Ah		; "Your mother was a hamster ..."
		set_r	BUFWIN_R
		get_r	BUFWIN_R	; "... and your father smelt of"
		and	al, 0F0h	;     "ELDERBERRIES!"
		cmp	al, 0A0h
		jne	try_io_err
		; now for something completely different -- ask the CARD
		;   CONFIGURATION register if it's plugged into an 8- or
		;   16-bit slot; mark it well
		get_r	BUFWIN_R
		and	al, 1		; bit on means 8-bit slot
		xor	al, 1		; complement sense for 'io_16'
		mov	io_16, al
		jz	try_io_1
		; set-up 16-bit versions of word i/o functions
		; N.B. From this point forward, we MUST to 16 bit i/o
		;   on 16 bit objects if the card told us to!
		mov	inw_fn, OFFSET inw_8005   ; input function
		mov	outw_fn, OFFSET outw_8005 ; output function
try_io_1:
		clc			; OK
		ret

try_io_err:
		mov	dx, OFFSET nodev_msg
		mov	ah, 9		; blab to the user
		int	21h
		stc			; error
		ret	; "Now go away or I shall taunt you a second time-uh!"


CODE		ends

		end


; eof
[ RETURN TO DIRECTORY ]