version	equ	6

	include	defs.asm

;   PC/FTP Packet Driver source, conforming to version 1.05 of the spec
;   Updated to version 1.08 Feb. 17, 1989 by Russell Nelson.
;   Robert C Clements, K1BC,  August 19, 1988
;   Portions (C) Copyright 1988 Robert C Clements

;   Version 3 updated by Jan Engvald LDC to handle WD8003ET/A (micro channel
;   card) and to utilize all 32 kbyte memory on the WD8003EBT card.

;   Version 10 updated by Drew D. Perkins at Carnegie Mellon University to
;   handle WD8013EBT 16-bit card, to implement a transmit ring buffer, and
;   to implement the High Performance Option.

;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General Public 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 Public License for more details.
;
;   You should have received a copy of the GNU General Public License
;   along with this program; if not, write to the Free Software
;   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

code	segment	word public
	assume	cs:code, ds:code

; Stuff specific to the Western Digital WD003E Ethernet controller board
; C version by Bob Clements, K1BC, May 1988 for the KA9Q TCP/IP package
; Symbol prefix "EN" is for Ethernet, Western-digital card

; The EN registers - First, the board registers

EN_CMD		equ	000h	; Board's command register
EN_REG1		equ	001h	; 8013 bus size register
EN_REG5		equ	005h	; New command register (REGISTER 5)
EN_SAPROM	equ	008h	; Window on station addr prom
EN_REGE		equ	00eh	; Board Id (code) byte

; The EN registers - Next, the DS8390 chip registers
; There are two (really 3) pages of registers in the chip. You select
; which page you want, then address them at offsets 10-1F from base.
; The chip command register (EN_CCMD) appears in both pages.

EN_CCMD		equ	010h	; Chip's command register

; Page 0

EN0_STARTPG	equ	011h	; Starting page of ring bfr
EN0_STOPPG	equ	012h	; Ending page +1 of ring bfr
EN0_BOUNDARY	equ	013h	; Boundary page of ring bfr
EN0_TSR		equ	014h	; Transmit status reg
EN0_TPSR	equ	014h	; Transmit starting page
EN0_TCNTLO	equ	015h	; Low  byte of tx byte count
EN0_TCNTHI	equ	016h	; High byte of tx byte count
EN0_ISR		equ	017h	; Interrupt status reg
EN0_RCNTLO	equ	01ah	; Remote byte count reg
EN0_RCNTHI	equ	01bh	; Remote byte count reg
EN0_RXCR	equ	01ch	; RX control reg
EN0_TXCR	equ	01dh	; TX control reg
EN0_COUNTER0	equ	01dh	; Rcv alignment error counter
EN0_DCFG	equ	01eh	; Data configuration reg
EN0_COUNTER1	equ	01eh	; Rcv CRC error counter
EN0_IMR		equ	01fh	; Interrupt mask reg
EN0_COUNTER2	equ	01fh	; Rcv missed frame error counter

; Page 1

EN1_PHYS	equ	011h	; This board's physical enet addr
EN1_CURPAG	equ	017h	; Current memory page
EN1_MULT	equ	018h	; Desired multicast addr


; Board commands in EN_CMD
EN_RESET	equ	080h	; Reset the board
EN_MEMEN	equ	040h	; Enable the shared memory
EN_MEM_MASK	equ	03fh	; B18-B13 of address of the shared memory

; Bits in REG1
ENR1_BUS16BIT	equ	001h	; Bus is 16 bits

; Commands for REG5 register
ENR5_MEM16EN	equ	080h	; Enable 16 bit memory access from bus (8013)
ENR5_LAN16EN	equ	040h	; Enable 16 bit memory access from chip (8013)
ENR5_MEM_MASK	equ	01fh	; B23-B19 of address of the memory (8013)
ENR5_LA19	equ	001h	; B19 of address of the memory (8013)
ENR5_EIL	equ	004h	; Enable 8390 interrupts to bus (microchannel)

; Bits in the REGE register
ENRE_MICROCHANEL equ	080h	; Microchannel bus (vs. PC/AT)
ENRE_LARGERAM	equ	040h	; Large RAM
ENRE_SOFTCONFIG	equ	020h	; Soft config
ENRE_REVMASK	equ	01eh	; Revision mask
ENRE_ETHERNET	equ	001h	; Ethernet (vs. Starlan)

; Chip commands in EN_CCMD
ENC_STOP	equ	001h	; Stop the chip
ENC_START	equ	002h	; Start the chip
ENC_TRANS	equ	004h	; Transmit a frame
ENC_NODMA	equ	020h	; No remote DMA used on this card
ENC_PAGE0	equ	000h	; Select page 0 of chip registers
ENC_PAGE1	equ	040h	; Select page 1 of chip registers

; Commands for RX control reg
ENRXCR_MON	equ	020h	; Monitor mode
ENRXCR_RUNT	equ	002h	; accept runt packets
ENRXCR_BCST	equ	004h	; Accept broadcasts
ENRXCR_MULTI	equ	008h	; Accept multicasts
ENRXCR_PROMP	equ	010h	; physical promiscuous mode

; Commands for TX control reg
ENTXCR_LOOP	equ	002h	; Set loopback mode

; Bits in EN0_DCFG - Data config register
ENDCFG_BM8	equ	048h	; Set burst mode, 8 deep FIFO
ENDCFG_WTS	equ	1	; Word Transfer Select

; Bits in EN0_ISR - Interrupt status register
ENISR_RX	equ	001h	; Receiver, no error
ENISR_TX	equ	002h	; Transmitter, no error
ENISR_RX_ERR	equ	004h	; Receiver, with error
ENISR_TX_ERR	equ	008h	; Transmitter, with error
ENISR_OVER	equ	010h	; Receiver overwrote the ring
ENISR_COUNTERS	equ	020h	; Counters need emptying
ENISR_RESET	equ	080h	; Reset completed
ENISR_ALL	equ	01fh	; Interrupts we will enable

; Bits in received packet status byte and EN0_RSR
ENPS_RXOK	equ	001h	; Received a good packet

; Bits in TX status reg

ENTSR_COLL	equ	004h	; Collided at least once
ENTSR_COLL16	equ	008h	; Collided 16 times and was dropped
ENTSR_FU	equ	020h	; TX FIFO Underrun

; Shared memory management parameters

; The transmit process now implements a transmit ring buffer.  The 8390
; chip divides transmit buffer memory into 256 byte pages.  On a card
; with only 8 Kbytes of total buffer memory, we allocate 6 pages for
; transmit--enough for one 1514 byte packet--leaving 6.5 Kbytes for
; packet reception.  On cards with more memory, we allocate 18 pages for
; transmit--enough for three 1514 byte packets--leaving 11.5 Kbytes (on
; a 16 Kbyte card) or 27.5 Kbytes (on a 32 Kbyte card) for packet
; reception.

; The ring buffer code views the allocated transmit buffer as a ring
; with one small kink thrown in.  Unfortunately, the 8390 doesn't
; actually implement a ring itself for transmit.  You can only tell it
; the starting page of a packet and the length of the packet.  The
; packet has to be entirely contiguous in the buffer.  Therefore, our
; ring has to prematurely wrap from the end back to the beginning
; whenever normal placement of a packet would cause it to straddle the
; transmit/receive buffer boundary.  When the ring wraps, the remaining
; space at the end of the buffer is wasted.

; The ring buffer code uses four variables: sm_tcur, sm_tboundary,
; sm_rstart_pg and sm_tlen. These first two variables parallel the
; curpage and boundary page registers that the chip uses for the receive
; buffer.  sm_tcur always indicates the next place that a packet will be
; added to the ring buffer (the tail of the queue).  sm_tboundary
; indicates the next place a packet will be removed from ring buffer
; (the head of the queue).  When the ring buffer is completely empty
; completely full, sm_tcur equals sm_tboundary.  sm_rstart_pg indicates
; the start page of the receive ring buffer, and hence the stop page of
; the transmit ring buffer.  The transmit ring buffer always starts at
; page zero.  sm_tlen is an array of packet lengths which shadows the
; actual ring buffer; there is one element in the array for each buffer
; page.  Each element of the array stores the length of a packet which
; starts on the corresponding page, or zero if there is no packet
; starting at that page.  A zero is also stored at the page indicated by
; sm_tcur except when the ring buffer is completely full, in which case
; sm_tcur equals sm_tboundary and sm_tlen[sm_tcur] is not zero.

; The transmit process also implements a very low latency transmit
; interrupt scheme which uses three other important variables: sm_tpsr,
; sm_tcnt, and sm_tnum.  These variables contain the pre-calculated
; values that the interrupt handler should hand the chip in order to
; initiate the next transmission as soon as possible.  sm_tpsr indicates
; the starting page of the next packet that the transmit interrupt code
; should initiate transmission of.  sm_tcnt indicates its length.
; sm_tnum always indicates the number of packets in the ring buffer
; which are completely ready to be transmitted, not how many packets are
; allocated in the ring buffer.  That is, sm_tnum is not incremented
; until the packet has been copied into the ring buffer, potentially a
; long time after space for the packet has been allocated.  sm_tnum is
; not decremented until the packet has actually been transmitted.
; Therefore, at interrupt time sm_tnum will have to be 2 or greater to
; indicate that there is actually another packet waiting to go.

; Since sm_tpsr at interrupt time always indicates the next page to be
; transmitted, it also contains the value that sm_tboundary should be updated
; to in order to free the associated buffer space.  This fact is used to avoid
; recomputation of sm_tboundary from the normal ring buffer data structure
; (specifically sm_tboundary and sm_tlen).  Unfortunately, using this scheme
; also added alot of hair (as you will see) to the code to make sure that
; sm_tpsr and sm_tboundary always tracked each other appropriately in all
; boundary conditions.  If I was going to do this again, I would probably
; reevaluate this design decision and do things a bit differently (probably
; I would use another array to indicate the next sm_tboundary value).

	public	sm_tcur, sm_tboundary, sm_tlen, sm_tnum, sm_tcnt, sm_tpsr
SM_TSTART_PG	equ	0	; First page of TX buffer
SM_TMAX_PG	equ	18	; 3 MTU-sized packets
sm_tcur		dw	0	; Offset of current page of TX buffer
sm_tboundary	dw	0	; Offset of boundary page of TX buffer
sm_tlen		dw	SM_TMAX_PG dup (0) ; Length of packets in TX buffer
sm_tnum		dw	0	; Number of packets in TX buffer
sm_tcnt		dw	0	; Length of next packet in TX buffer
sm_tpsr		db	0	; Starting page of next packet in TX buffer
sm_rstart_pg	db	6	; Starting page of ring
sm_rstop_pg	db	32	; Last page +1 of ring

xmit_isr	db	0	; ISR shared between xmit and recv
xmit_tsr	db	0	; TSR shared between xmit and recv

board_features	db	0	; Board features
BF_EIL		equ	1
BF_WTS		equ	2	; 16-bit board, enable Word Transfer
BF_MEM16EN	equ	4	; 16-bit board, enable 16-bit memory
BF_16K		equ	8	; Board has 16 KB or shared memory

; Description of header of each packet in receive area of shared memory

EN_RBUF_STAT	equ	0	; Received frame status
EN_RBUF_NXT_PG	equ	1	; Page after this frame
EN_RBUF_SIZE	equ	2	; Length of this frame (word access)
EN_RBUF_SIZE_LO	equ	2	; Length of this frame
EN_RBUF_SIZE_HI	equ	3	; Length of this frame
EN_RBUF_NHDR	equ	4	; Length of above header area

; End of WD8003E parameter definitions

; The following three values may be overridden from the command line.
; If they are omitted from the command line, these defaults are used.

	public	int_no, io_addr, mem_base
int_no		db	3,0,0,0		; Interrupt level
io_addr		dw	0280h,0		; I/O address for card (jumpers)
mem_base	dw	0d000h,0	; Shared memory addr (software)

	public	driver_class, driver_type, driver_name, driver_function, parameter_list
driver_class	db	1		;from the packet spec
driver_type	db	14		;from the packet spec
driver_name	db	'WD8003E',0	;name of the driver.
driver_function	db	6		;basic, high-performance, extended
parameter_list	label	byte
		db	1	;major rev of packet driver
		db	10	;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 mcast addrs
rcv_bufs	dw	4	;(# of back-to-back MTU rcvs) - 1
xmt_bufs	dw	0	;(# of successive xmits) - 1
int_num		dw	0	;Interrupt # to hook for post-EOI
			;processing, 0 == none,

mcast_list_bits db      0,0,0,0,0,0,0,0 ;Bit mask from last set_multicast_list
mcast_all_flag  db      0               ;Non-zero if hware should have all
					; ones in mask rather than this list.

	public	rcv_modes
rcv_modes	dw	7		;number of receive modes in our table.
		dw	0		;there is no mode 1.
		dw	rcv_mode_1
		dw	rcv_mode_2
		dw	rcv_mode_3
		dw	rcv_mode_4
		dw	rcv_mode_5
		dw	rcv_mode_6
rxcr_bits       db      ENRXCR_BCST     ; Default to ours plus multicast

	extrn	sys_features: byte

	extrn	send_head: dword	; head of transmit queue
	extrn	send_tail: dword	; tail of transmit queue

	extrn	send_queue: near
; Called when we want to queue an iocb.
; Enter with es:di -> iocb.

	extrn	send_dequeue: near
; Called to dequeue an iocb and possibly call its upcall.
; Enter with interrupts disabled.
; Destroys ds:si.


ifdef	debug			; Include a very useful logging mechanism.  

; The log entry structure.  Log entries include useful data such as
; a type (each place a log entry is made uses a different type), various
; chip status, ring buffer status, log entry dependent data, and optionally
; 8259 interrupt controller status.
logentry	struc
le_type		db	0	; Log entry type
le_ccmd		db	?	; Value of CCMD register
le_isr		db	?	; Value of ISR register
le_tsr		db	?	; Value of TSR register
le_tcur		dw	?	; Value of sm_tcur
le_tboundary	dw	?	; Value of sm_tboundary
le_tnum		dw	?	; Value of sm_tnum
le_dw		dw	?	; Log type specific dw data
ifndef	mkle8259		; Log 8259 status?
le_dd		dd	?	; Log type specific dd data
else
le_irr1		db	?	; Value of 8259-1 IRR register
le_isr1		db	?	; Value of 8259-1 ISR register
le_irr2		db	?	; Value of 8259-2 IRR register
le_isr2		db	?	; Value of 8259-2 ISR register
endif
logentry	ends

; The types of log entries.
LE_SP_E		equ	0	; send_pkt entry
LE_SP_X		equ	1	; send_pkt exit
LE_ASP_E	equ	2	; as_send_pkt entry
LE_ASP_X	equ	3	; as_send_pkt exit
LE_RBALLOC_E	equ	4	; tx_rballoc entry
LE_RBALLOC_X	equ	5	; tx_rballoc exit
LE_COPY_E	equ	6	; sm_copy entry
LE_COPY_X	equ	7	; sm_copy exit
LE_START_E	equ	8	; tx_start entry
LE_START_X	equ	9	; tx_start exit
LE_XMIT_E	equ	0ah	; xmit entry
LE_XMIT_X	equ	0bh	; xmit exit
LE_TXISR_E	equ	0ch	; txisr entry
LE_TXISR_X	equ	0dh	; txisr exit
LE_RECV_E	equ	0eh	; recv entry
LE_RECV_X	equ	0fh	; recv exit
LE_RCVFRM_E	equ	10h	; rcv_frm entry
LE_RCVFRM_X	equ	11h	; rcv_frm exit
LE_COPY_L	equ	12h	; sm_copy loop
LE_TIMER_E	equ	13h	; timer entry
LE_TIMER_X	equ	14h	; timer exit

	public	log, log_index
log		logentry 256 dup (<>) ; The log itself
log_index	db	0	; Index to current log entry

; The macro used to create log entries.
mkle	macro	letype, ledw, ledd, ledd2 ; Make an entry in the log
	pushf			; Save interrupt enable state
	cli			; Disable interrupts
	push	dx		; Save registers
	push	bx
	push	ax
	mov bl,	log_index	; Get current log_index
	xor bh,	bh		; Clear high byte
	shl bx,	1		; Multiply by sixteen
	shl bx,	1
	shl bx,	1
	shl bx,	1
	mov log[bx].le_type, letype ; Store log entry type
	loadport		; Base of device
	setport EN_CCMD	; Point at chip command register
	in al,	dx		; Get chip command state
	mov log[bx].le_ccmd, al	; Store CCMD value
	setport EN0_ISR		; Point at chip command register
	in al,	dx		; Get chip command state
	mov log[bx].le_isr, al	; Store ISR value
	setport EN0_TSR		; Point at chip command register
	in al,	dx		; Get chip command state
	mov log[bx].le_tsr, al	; Store TSR value
	mov ax,	sm_tcur		; Get current sm_tcur
	mov log[bx].le_tcur, ax	; Store sm_tcur value
	mov ax,	sm_tboundary	; Get current sm_tboundary
	mov log[bx].le_tboundary, ax ; Store sm_tboundary value
	mov ax,	sm_tnum		; Get current sm_tnum
	mov log[bx].le_tnum, ax	; Store sm_tnum value
	mov log[bx].le_dw, ledw	; Store log entry dw
ifndef	mkle8259		; Include extra per-type data
	mov word ptr log[bx].le_dd, ledd ; Store low word of log entry dd
	mov word ptr log[bx].le_dd+2, ledd2 ; Store high word of log entry dd
else				; Include 8259 status
	mov	al,0ah		; read request register from
	out	0a0h,al		; secondary 8259
	nop			; settling delay
	nop
	nop
	in	al,0a0h		; get it
	mov log[bx].le_irr2, al
	mov	al,0bh		; read in-service register from
	out	0a0h,al		; secondary 8259
	nop			; settling delay
	nop
	nop
	in	al,0a0h		; get it
	mov log[bx].le_isr2, al
	mov	al,0ah		; read request register from
	out	020h,al		; primary 8259
	nop			; settling delay
	nop
	nop
	in	al,020h		; get it
	mov log[bx].le_irr1, al
	mov	al,0bh		; read in-service register from
	out	020h,al		; primary 8259
	nop			; settling delay
	nop
	nop
	in	al,020h		; get it
	mov log[bx].le_isr1, al
endif
ifdef	screenlog		; Log the entry type to the screen too
	push	es
	mov ax,	0b800h		; Color screen only...
	mov es,	ax
	mov bl,	log_index	; Get current log_index
	xor bh,	bh		; Clear high byte
	shl bx,	1		; Multiply by sixteen
	add bx,	3360
	mov byte ptr es:[bx-1], 07h
	mov byte ptr es:[bx], letype+30h
	mov byte ptr es:[bx+1], 70h
	pop	es
endif
	inc	log_index	;
	pop	ax		; Restore registers
	pop	bx
	pop	dx
	popf			; Restore interrupt enable state
	endm
else
mkle	macro	letype, ledw, ledd, ledd2 ; Define an empty macro
	endm
endif

	public	send_pkt
; The Transmit Packet routine.
; Enter with ds:si -> packet, cx = packet length,
; Exit with nc if ok, or else cy if error, dh set to error number.
send_pkt:
	assume	ds:nothing
	mkle LE_SP_E, cx, si, ds

	call	re_enable_interrupts

; Convert synchronous call to asynchronous call.
	add sp,	-(size iocb)	; Allocate iocb on stack
	mov di,	sp		; Get iob offset
	mov ax,	ss		; Get iocb segment
	mov es,	ax
	mov word ptr es:[di].buffer, si		; Set buffer offset
	mov word ptr es:[di].buffer+2, ds	; Set buffer segment
	mov es:[di].len, cx	; Set buffer length
	mov es:[di].flags, 0	; Clear all flags
	call	as_send_pkt	; Call asynchronous send (es:di preserved)
ifndef	debug
	jc	sp_error	; Error, return immediately
else
	jnc	sp_loop
	jmp	sp_error
endif

; Check if asynchronous send has completed.
sp_loop:
	mov cx,	8000h		; Avoid infinite loop
sp_chkdone:
	test es:[di].flags, DONE ; Transmission complete?
ifndef	debug
	jz	sp_notdone	; No
else
	jnz	sp_done
	jmp	sp_notdone
sp_done:
endif
	mov dh,	es:[di].ret_code	; Get return code
	cmp dh,	0		; Error?
ifndef	debug
	jne	sp_error	; Yes
else
	je	sp_success
	jmp	sp_error
sp_success:
endif

; Send has completed successfully.
	add sp,	size iocb	; Free iocb on stack
	mkle LE_SP_X, cx, 1, 0
	clc			; No error, clear carry
	ret

; Send has completed unsuccessfully.
sp_error:
	add sp,	size iocb	; Free iocb on stack
	mkle LE_SP_X, cx, 0, 0
	stc			; popf may have cleared carry
	ret

sp_notdone:
	pushf			; Make interrupt enable bit accessible
	pop	bx		;  in bx
	test bx, EI		; Were interrupts enabled on pkt driver entry?
	jnz	sp_deccnt	; Yes

; Interrupts are disabled, poll chip to determine when the transmit completes.
	loadport		; Base of device
	setport	EN0_ISR		; Point at interrupt status register
	in al,	dx		; Get pending interrupts
	test al,ENISR_ALL	; Any interrupts?
	jz	sp_deccnt	; No
	pushf			; Push flags
	call	xmit		; Fake first half of transmit interrupt
	jmp	sp_fakeint	; Fake second half of transmit interrupt

; Decrement the loop counter
sp_deccnt:
	dec	cx		; Reached zero?
ifndef	debug
	jnz	sp_chkdone	; No, loop again
else
	jz	sp_txstuck
	jmp	sp_chkdone
sp_txstuck:
endif

; TX is stuck.  Fall thru into sp_fakeint code after setting xmit variables.
	pushf			; Make sure interrupts are disabled
	cli
	mov xmit_isr, ENISR_TX_ERR ; Frame transmitted error
	mov xmit_tsr, 0		; Unknown error
	call	count_out_err	; Count the anomaly
	call	xmit_fake	; Fake first half of transmit interrupt
sp_fakeint:
	push	es		; Push iocb segment
	push	di		; Push iocb offset
	mov dx,	cs		; Set up ds=cs
	mov ds,	dx
	call	recv		; Let recv do the hard work
	pop	di		; Pop iocb offset
	pop	es		; Pop iocb segment
	popf			; Restore interrupt enable bit
	jmp	sp_loop		; Restart loop (check the DONE bit again)


	public	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:
	assume	ds:nothing
	mkle LE_ASP_E, cx, di, es

; Check if this packet is too large.
	cmp cx,	GIANT		; Bigger than this?
	jbe	tx_oklen1	; No
	mov dh,	CANT_SEND	; Yes, return error
	mov es:[di].ret_code, dh
	or es:[di].flags, DONE
	stc
	ret

; Frame is small enough, check if it's large enough.
tx_oklen1:
	cmp cx,	RUNT		; Smaller than this?
	jnb	tx_oklen2	; No
	mov cx,	RUNT		; Yes, stretch frame to minimum allowed
	mov es:[di].len, cx	; Update iocb too

; Frame is an acceptable size.
tx_oklen2:
	push	es		; Push iocb segment
	push	di		;  and offset

; If queue is empty and have ring buffer space, allocate it.  Else, queue iocb.
	pushf			; Push original interrupt enable flag
	cli			; Disable interrupts
	send_queueempty		; Queue empty?
ifndef	debug
	jnz	tx_queue	; No, queue this one too
else
	jz	tx_alloc
	jmp	tx_queue
tx_alloc:
endif
	mov ax,	cx		; Move length to ax
	call	tx_rballoc	; Allocate space in the ring buffer
ifndef	debug
	jc	tx_nospace	; No space right now, queue the packet
else
	jnc	tx_space
	jmp	tx_nospace
tx_space:
endif
	popf			; Pop original interrupt enable flag

; Do the actual copy of the packet into the ring buffer.
	call	sm_copy

; Start the packet transmission.
	pushf			; Push original interrupt enable flag
	cli			; Disable interrupts
	call	tx_start	; Set up hints and start transmission
	popf			; Pop original interrupt enable flag

	pop	di		; Pop iocb segment
	pop	es		;  and offset
	or es:flags[di], DONE	; Mark done
	mov es:ret_code[di], 0	; Success
	mkle LE_ASP_X, 0, 0, 0
	clc
	ret			; End of send_pkt routine

; No space in ring buffer, get the iocb from the stack and queue it.
tx_nospace:
	mov bx,	sp		; Need es:di -> iocb, but can't pop flags.
	mov di,	ss:[bx+2]	; Get iocb offset
	mov es,	ss:[bx+4]	; Get iocb segment

; Queue the iocb and return.
tx_queue:
	call	send_queue
	popf			; Pop original interrupt enable flag
	pop	di		; Pop iocb segment
	pop	es		;  and offset
	mkle LE_ASP_X, 1, 0, 0
	clc
	ret


	public	tx_rballoc
; Allocate some space in the transmit ring buffer.
; Enter with ds:si -> buffer source, ax = length, interrupts disabled.
; If not enough space, exit with: cy, ds:si -> source, ax = length.
; Else, nc, ds:si -> buffer source, es:di -> buffer destination, ax = length,
;	bl = transmit starting page.
tx_rballoc:
	assume	ds:nothing
	mkle LE_RBALLOC_E, ax, 0, 0

; First, compute likely destination, index, and new sm_tcur (may have to wrap)
	mov es,	mem_base	; Paragraph of the TX buffer
	mov di,	sm_tcur		; Offset of current TX buffer page
	mov bx,	ax		; Length of packet
	lea dx, [bx+255][di]	; Compute packet end + 255
	xor dl,	dl		; Only want the page number

; Second, make sure ring buffer isn't completely full (sm_tlen[sm_tcur] != 0)
	mov bx,	di		; Compute index into length table
	mov cl, 7		; Index is offset shifted right seven bits
	shr bx,	cl
tx_chkfull:
	cmp sm_tlen[bx], 0	; Current sm_tlen must equal zero
ifndef	debug
	jne	tx_noroom	; TX buffer full
else
	je	tx_room
	jmp	tx_noroom
tx_room:
endif

; Third, check if packet will fit in ring buffer at this position
	cmp di,	sm_tboundary	; Has sm_tcur wrapped?
ifndef	debug
	jae	tx_notwrapped	; No
else
	jb	tx_wrapped
	jmp	tx_notwrapped
tx_wrapped:
endif
	cmp dx,	sm_tboundary 	; Will packet fit between sm_tcur/sm_tboundary?
ifndef	debug
	ja	tx_noroom	; No
else
	jbe	tx_willfit
	jmp	tx_noroom
endif
tx_willfit:			; Yes, packet will fit
	mov sm_tlen[bx], ax	; Copy length to sm_tlen
	mov sm_tcur, dx		; Update current TX buffer offset
	shr bx,	1		; Convert length table index to tpsr
	mkle LE_RBALLOC_X, bx, di, es
	clc			; Clear carry
	ret			; Return success

tx_notwrapped:			; sm_tcur >= sm_tboundary
	cmp dh,	sm_rstart_pg	; Will packet fit between sm_tcur/sm_rstart_pg?
ifndef	debug
	jb	tx_willfit	; Yes
else
	jae	tx_mightfit
	jmp	tx_willfit
tx_mightfit:
endif
	jne	tx_wrapcur	; No
	xor dx,	dx		; Packet fit exactly, but wrap sm_tcur now
	jmp	tx_willfit

; Packet won't fit in remaining TX ring buffer, wrap sm_tcur.
; If ring buffer is completely empty, wrap sm_tboundary as well.
; If ring buffer is not empty, sm_tpsr will need updated if sm_tnum is 1.
tx_wrapcur:
	sub dx,	di		; Compute next sm_tcur (offset zero + length)
	mov di,	sm_tboundary	; Get sm_tboundary (don't need di for a bit)
	mov cl,	7		; Convert it to an index into length table
	shr di,	cl
	xor cx,	cx		; Put a zero value in a register
	cmp sm_tlen[di], cx	; Is the buffer completely empty?
	mov di,	cx		; Wrap sm_tcur to zero
	jne	tx_notempty	; No
	mov sm_tboundary, cx	; Wrap sm_tboundary
	mov bx,	cx		; Wrap index
	jmp	tx_notwrapped	; Check new position

tx_notempty:
	mov sm_tlen[bx], cx	; Insert 0 length pkt to skip remaining buffer
	mov sm_tcur, cx		; Interrupt handler may need this to be updated
	mov bx,	cx		; Wrap index
	cmp sm_tnum, 1		; Will sm_tpsr be corrupted?
	jne	tx_tpsrok	; No, its ok
	mov sm_tpsr, cl		; Wrap sm_tpsr as well
tx_tpsrok:
	jmp	tx_chkfull	; Check packet fit again

; Packet won't fit now, return failure.
tx_noroom:
	mkle LE_RBALLOC_X, 0, 0, 0
	stc			; Set carry
	ret			; Return failure


	public	sm_copy
; Copy a packet from the receive ring buffer.
; Enter with ds:si -> source, es:di -> destination, ax = length.
; Exits with ds:si updated, es:di updated, ax and bx preserved,
;   cx, and dx destroyed.
sm_copy:
	assume	ds:nothing
	mkle LE_COPY_E, ax, di, es

	push	ax		; Preserve ax
	push	bx		; Preserve bx
	mov cx,	ax		; Copy length to cx
	shr cx,	1		; Convert bytes to words
	pushf			; Save carry and interrupt enable bits
	test board_features, BF_MEM16EN	; Is this a WD8013?
ifndef	debug
	jne	sm_wd8013	; yes
else
	je	sm_wd8003
	jmp	sm_wd8013
sm_wd8003:
endif
	rep	movsw		; Copy a word at a time
sm_copied:
	mkle LE_COPY_L, 0, 0, 0
	popf			; Restore carry bit
	jnc	sm_even		; odd byte left over?
	lodsw			;   yes, word fetch
	stosb			;   and byte store
sm_even:
	pop	bx		; Get back bx
	pop	ax		; Get back ax
	mkle LE_COPY_X, 0, 0, 0
	ret			; Return success

sm_wd8013:			; Board is a WD8013
	cli			; Disable interrupts
	loadport		; Base of device
	setport	EN_REG5		; Enable 16-bit access
	mov al,	ENR5_MEM16EN+ENR5_LAN16EN+ENR5_LA19
	out dx,	al
	mov bx,	sp
	test	ss:[bx], EI	; Were interrupts enabled on entry?
	jne	sm_piecemeal	; Yes, copy piecemeal
sm_allatonce:			; No, copy all at once
	rep	movsw		; Copy packet
sm_disable:
	mov al,	ENR5_LAN16EN+ENR5_LA19 ; Disable 16-bit access to WD8013
	out dx,	al
	jmp	sm_copied

; Do copy a piece at a time, enabling and disabling interrupts as we go,
;  in order to guarantee reasonable interrupt latency
sm_piecemeal:
	mov ax,	cx		; Copy length to ax
	mov bx,	cx		;  and bx
	add bx,	63
	mov cx,	6		; Divide length by 64 to get number of loops
	shr bx,	cl
	mov cx,	ax		; Copy length back to cx
	and cx,	63		; Do up to 63 copies first time
	jne	sm_copypiece	; cx was not zero, do cx copies
sm_loop:
	mov cx,	64		; Do 64 copies
sm_copypiece:
	rep	movsw		; Copy a word at a time
	dec	bx		; Decrement loop counter
	je	sm_disable	; All done
	mov al,	ENR5_LAN16EN+ENR5_LA19 ; Disable 16-bit access to WD8013
	out dx,	al
	mkle LE_COPY_L, bx, di, es
	sti			; Enable interrupts for a moment
	cli			; Disable interrupts again
	mov al,	ENR5_MEM16EN+ENR5_LAN16EN+ENR5_LA19
	out dx,	al
	jmp	sm_loop		; Do more copies


	public	tx_start
; Start the transmitter and manage the low-latency transmitter hints.
; Enters with ax = length, bl = transmit starting page.
tx_start:
	assume	ds:nothing
	mkle LE_START_E, ax, bx, 0

; First, increment the count of packets in the ring buffer and figure out what
;   needs to be done.
	inc	sm_tnum		; Increment count of packets (pkt now queued)
	cmp sm_tnum, 3		; Already have a second packet set up?
	jae	tx_running	; Yes.
	cmp sm_tnum, 2		; Transmitter already running?
	jae	tx_setnext	; Yes

; Start the transmitter running.
	loadport		; Base of device
	setport	EN0_TCNTLO	; Low byte of TX count
	out dx,	al		; Tell card the low byte
	setport	EN0_TCNTHI	; High byte of TX length
	mov al,	ah		; Get the high byte into al
	out dx,	al		; Tell card the high byte
	setport	EN0_TPSR	; Transmit Page Start Register
	mov al,	bl		; Get the starting page
	out dx,	al		; Tell card the starting page
	setport	EN_CCMD		; Chip command reg
	mov al,	ENC_TRANS+ENC_NODMA
	out dx,	al		; Start the transmitter

; Set new sm_tboundary.  If we wrapped sm_tcur in sm_rballoc AND the last
; interrupt came in during the sm_copy then sm_tboundary will not have
; wrapped and will now be corrupt.
	mov byte ptr sm_tboundary+1, bl
	mov bl,	byte ptr sm_tcur+1 ; Get sm_tcur page number (to set sm_tpsr)

; Set the low-latency transmitter hints.
tx_setnext:
	mov sm_tcnt, ax		; Set up next transmit count
	mov sm_tpsr, bl		; Set up next transmit starting page

tx_running:
	mkle LE_START_X, ax, bx, 0
	ret


	public	drop_pkt
; Drop a packet from the queue.
; Enter with es:di -> iocb.
drop_pkt:
	assume	ds:nothing

	ret


	public	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.
get_address:
	assume ds:code

	cmp cx,	EADDR_LEN	; Caller wants a reasonable length?
	jb	get_addr_x	; No, fail.
	mov cx,	EADDR_LEN	; Yes. Set count for loop
	loadport		; Base of device
	setport	EN_SAPROM	; Where the address prom is
	cld			; Make sure string mode is right
get_addr_loop:
	in al,	dx		; Get a byte of address
	stosb			; Feed it to caller
	inc	dx		; Next byte at next I/O port
	loop	get_addr_loop	; Loop over six bytes
	mov cx,	EADDR_LEN	; Tell caller how many bytes we fed him
	clc			; Carry off says success
	ret
get_addr_x:
	stc			; Tell caller our addr is too big for him
	ret


	public	set_address
; Enter with ds:si -> Ethernet address, CX = length of address.
; Exit with nc if okay, or cy, dh=error if any errors.
set_address:
	assume	ds:nothing

	cmp	cx,EADDR_LEN		;ensure that their address is okay.
	je	set_address_4
	mov	dh,BAD_ADDRESS
	stc
	jmp	short set_address_done
set_address_4:

	loadport		; Base of device
	setport	EN1_PHYS
set_address_1:
	lodsb
	out	dx,al
	inc	dx
	loop	set_address_1

set_address_okay:
	mov	cx,EADDR_LEN		;return their address length.
	clc
set_address_done:
	push	cs
	pop	ds
	assume	ds:code
	ret


; Routines to set address filtering modes in the DS8390
rcv_mode_1:     ; Turn off receiver
	mov al,	ENRXCR_MON      ; Set to monitor for counts but accept none
	jmp short rcv_mode_set
rcv_mode_2:     ; Receive only packets to this interface
	mov al, 0               ; Set for only our packets
	jmp short rcv_mode_set
rcv_mode_3:     ; Mode 2 plus broadcast packets (This is the default)
	mov al,	ENRXCR_BCST     ; Set four ours plus broadcasts
	jmp short rcv_mode_set
rcv_mode_4:     ; Mode 3 plus selected multicast packets
	mov al,	ENRXCR_BCST+ENRXCR_MULTI ; Ours, bcst, and filtered multicasts
	mov     mcast_all_flag,0
	jmp short rcv_mode_set
rcv_mode_5:     ; Mode 3 plus ALL multicast packets
	mov al,	ENRXCR_BCST+ENRXCR_MULTI ; Ours, bcst, and filtered multicasts
	mov     mcast_all_flag,1
	jmp short rcv_mode_set
rcv_mode_6:     ; Receive all packets (Promiscuous physical plus all multi)
	mov al,	ENRXCR_BCST+ENRXCR_MULTI+ENRXCR_PROMP
	mov     mcast_all_flag,1
rcv_mode_set:
	push    ax              ; Hold mode until masks are right
	call    set_8390_multi  ; Set the multicast mask bits in chip
	pop     ax
	loadport		; Base of device
	setport	EN0_RXCR	; Set receiver to selected mode
	out dx,	al
	mov     rxcr_bits,al    ; Save a copy of what we set it to
	ret


	public	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.
set_multicast_list:
	mov	dh,NO_MULTICAST
	stc
	ret


; Set the multicast filter mask bits in case promiscuous rcv wanted
set_8390_multi:
	loadport		; Base of device
	setport	EN_CCMD		; Chip command register
	mov	cx,8		; Eight bytes of multicast filter
	mov	si,offset mcast_list_bits  ; Where bits are, if not all ones
	push    cs
	pop     ds
	pushf
	cli			; Protect from irq changing page bits
	mov	al,ENC_NODMA+ENC_PAGE1
	out	dx,al		; Switch to page one for writing eaddr
	setport	EN1_MULT	; Where it goes in 8390
	mov	al,mcast_all_flag  ; Want all ones or just selected bits?
	or	al,al
	jz	set_mcast_2     ; z = just selected ones
	mov	al,0ffh		; Ones for filter
set_mcast_all:
	out	dx,al		; Write a mask byte
	inc	dl		; Step to next one
	jmp	$+2		; limit chip access rate
	loop	set_mcast_all
	jmp short set_mcast_x

set_mcast_2:
	lodsb                   ; Get a byte of mask bits
	out	dx,al		; Write a mask byte
	inc	dl		; Step to next I/O register
	jmp	$+2		; limit chip access rate
	loop	set_mcast_2
set_mcast_x:
	loadport		; Base of device
	setport	EN_CCMD		; Chip command register
	mov	al,ENC_NODMA+ENC_PAGE0
	out	dx,al		; Restore to page zero
	popf			; OK for interrupts now
	ret


	public	terminate
terminate:
	loadport		; First, pulse the board reset
	setport	EN_CMD
	mov	al,EN_RESET
	out	dx,al		; Turn on board reset bit
	jmp	$+2
	xor	al,al
	jmp	$+2
	out	dx,al		; Turn off board reset bit
	ret

	public	reset_interface
reset_interface:
	assume ds:code
	loadport		; Base of device
	setport	EN_CCMD		; Chip command reg
	mov al,	ENC_STOP+ENC_NODMA
	out dx,	al		; Stop the DS8390
	setport	EN0_ISR		; Interrupt status reg
	mov al,	0ffh		; Clear all pending interrupts
	out dx,	al		; ..
	setport	EN0_IMR		; Interrupt mask reg
	xor al,	al		; Turn off all enables
	out dx,	al		; ..
	setport	EN_REG5
	mov	al,ENR5_EIL
	test	board_features,BF_EIL
	jz	reset_no_eil
	out	dx,al			; enable 8390 interrupts to bus
reset_no_eil:
	ret


	public	xmit
; Process a transmit interrupt with the least possible latency to achieve
;   back-to-back packet transmissions.
; Second entry point, xmit_fake, used when chip is hung.
; May only use ax and dx.
xmit:
	assume	ds:nothing
	mkle LE_XMIT_E, ax, di, es

; Was there a transmit interrupt from this card?
	loadport		; Base of device
	setport	EN0_ISR		; Point at interrupt status register
	in al,	dx		; Get pending interrupts
	mov xmit_isr, al	; Store ISR for later reference
	test al,ENISR_TX+ENISR_TX_ERR	; Frame transmitted?
	jz	xmit_done	; No

; Get the transmit status.
 	setport	EN0_TSR
	in al,	dx
	mov xmit_tsr, al	; Store TSR for later reference

; Clear the interrupt.
xmit_fake:			; Entry point used when chip is hung.
	setport	EN0_ISR		; Clear the TX and TX error complete flags
	mov al,	ENISR_TX+ENISR_TX_ERR
	out dx,	al
	cmp sm_tnum, 1		; Any packets left to transmit?
	jbe	xmit_done	; No

; Quickly start the next transmission going.
	setport	EN0_TCNTLO	; Low byte of TX count
	mov ax,	sm_tcnt		; Get length of next packet
	out dx,	al		; Tell card the low byte
	setport	EN0_TCNTHI	; High byte of TX length
	mov al,	ah		; Get the high byte into al
	out dx,	al		; Tell card the high byte
	setport	EN0_TPSR	; Transmit Page Start Register
	mov al,	sm_tpsr		; Get the starting page into al
	out dx,	al		; Tell card the starting page
	setport	EN_CCMD		; Chip command reg
	mov al,	ENC_TRANS+ENC_NODMA
	out dx,	al		; Start the transmitter

xmit_done:
	mkle LE_XMIT_X, ax, di, es
	ret


	public	tx_isr
; Process a transmit interrupt with the least possible latency.
; Enter with al = contents of interrupt status register (EN0_ISR)
tx_isr:
	assume	ds:code
	mkle LE_TXISR_E, 0, 0, 0

; Examine the status bits in and count errors.
	test xmit_isr, ENISR_TX	; Non-error TX?
	jz	tx_err		; No, do TX error completion.
	test xmit_tsr, ENTSR_COLL16 ; Jammed for 16 transmit tries?
	jz	tx_rbfree	; No.
	call	count_out_err	; Yes, count those.
	jmp	tx_rbfree
tx_err:
	test xmit_tsr, ENTSR_FU	; FIFO Underrun?
	jz	tx_rbfree	; No.
	call	count_out_err	; Yes, count those.

; Now, drain the last packet from the TX shared memory ring buffer.
tx_rbfree:
	mov bx,	sm_tboundary	; Get sm_tboundary
	mov cl, 7		; Compute index into length table
	shr bx,	cl
	mov sm_tlen[bx], 0	; Zero length
	mov bh,	sm_tpsr		; Get the starting page into bh
	xor bl,	bl		; Clear bl
	mov sm_tboundary, bx	; Set new sm_tboundary
	dec	sm_tnum		; Decrement number of packets
	jz	tx_dequeue	; No interrupts following this one

; Now, set up sm_tpsr and sm_tcnt for the next transmit interrupt.
	mov di,	sm_tcnt		; Get length of current packet
	lea dx, [bx+255][di]	; Compute new sm_tpsr
	xor dl,	dl
	cmp dh,	sm_rstart_pg	; See if sm_tpsr has wrapped
	jb	tx_nowrap	; It has not yet wrapped
tx_wrap:
	xor dx,	dx		; Wrap to top
tx_nowrap:
	cmp	dx, sm_tcur	; Any more packets following this one?
	jbe	tx_settpsr	; No
	mov bx,	dx		; Compute index of length of next packet
	mov cl, 7
	shr bx,	cl
	mov ax,	sm_tlen[bx]	; Get length of next packet
	or ax,	ax		; Should we punt rest of TX buffer?
	je	tx_wrap		; Yes, wrap to top and try again
tx_settpsr:
	mov sm_tpsr, dh		; Store back new sm_tpsr
	mov sm_tcnt, ax		;  and new sm_tcnt

; Finally, process any packets in the transmit queue.
tx_dequeue:
	send_queueempty		; Queue empty?
	jz	tx_done		; Yes.

	send_peekqueue		; Peek at the first element in the queue
	lds si,	es:[di].buffer	; Get buffer segment:offset
	assume	ds:nothing
	mov ax,	es:[di].len	; Get buffer length
	call	tx_rballoc	; Allocate space in the ring buffer
	jc	tx_done		; Still no space for this packet.

; Do the actual copy of the packet into the ring buffer.
	call	sm_copy

; Start the packet transmission.
	call	tx_start	; Set up hints and start transmission

	mov	ax,0
	call	send_dequeue	; Dequeue this element

tx_done:
	mov dx,	cs		; Restore ds=cs
	mov ds,	dx
	assume	ds:code
	mkle LE_TXISR_X, 0, 0, 0
	ret


;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type.
;It returns with es:di = 0 if don't want this type or if no buffer available.
	extrn	recv_find: near

;called after we have copied the packet into the buffer.
;enter with ds:si ->the packet, cx = length of the packet.
	extrn	recv_copy: near

;called to re-enable interrupts
	extrn	re_enable_interrupts: near

	extrn	count_in_err: near
	extrn	count_out_err: near

	public	recv
; Called from the recv isr.  All registers have been saved, and ds=cs.
; Actually, not just receive, but all interrupts come here.
; Upon exit, the interrupt will be acknowledged.
recv:
	assume	ds:code
	mkle LE_RECV_E, 0, 0, 0

; First cleanup after xmit routine if transmit interrupt occurred.
check_isr:
	mov al,	xmit_isr	; Get xmit ISR
	test al,ENISR_TX+ENISR_TX_ERR	; Frame transmitted?
	jz	tx_no_frame	; No
	call	tx_isr		; Yes, cleanup after transmit interrupt
	mov al,	xmit_isr	; Get xmit ISR

tx_no_frame:
	and al,	ENISR_ALL	; Any interrupts at all?
ifndef	debug
	jnz	isr_test_overrun
else
	jz	isr_no_isr
	jmp	isr_test_overrun
isr_no_isr:
endif
	mkle LE_RECV_X, 0, 0, 0
	ret			; Go if none

; Now, a messy procedure for handling the case where the rcvr
; over-runs its ring buffer.  This is spec'ed by National for the chip.
isr_test_overrun: 
	test al,ENISR_OVER	; Was there an overrun?
	jnz	recv_overrun	; Go if so.
	jmp	recv_no_overrun	; Go if not.
recv_overrun:
	loadport		; Base of device
	setport	EN_CCMD		; Stop the card
	mov al,	ENC_STOP+ENC_NODMA
	out dx,	al		; Write "stop" to command register

	mov al, ENC_NODMA+ENC_PAGE1	; Could be in previous out, but
	out dx,al		; was only tested this way
	setport EN1_CURPAG	; Get current page
	in al,dx
	mov bl,al		; save it
	setport	EN_CCMD		;
	mov al, ENC_NODMA+ENC_PAGE0
	out dx,al		; Back to page 0

; Remove one frame from the ring
	setport	EN0_BOUNDARY	; Find end of this frame
	in al,	dx		; Get memory page number
	inc	al		; Page plus 1
	cmp al,	sm_rstop_pg	; Wrapped around ring?
	jnz	rcv_ovr_nwrap	; Go if not
	mov al,	sm_rstart_pg	; Yes, wrap the page pointer
rcv_ovr_nwrap:

	cmp	al,bl		; Check if buffer emptry
	je	rcv_ovr_empty	; Yes ? Don't receive anything

	xor ah,	ah		; Convert page to segment
	mov cl,	4
	mov bl,	al		; Page number as arg to rcv_frm
	shl ax,	cl		; ..
	add ax,	mem_base	; Page in this memory
	mov es,	ax		; Segment pointer to the frame header
	push	es		; Hold this frame pointer for later
	mov ax,	es:[EN_RBUF_STAT]	; Get the buffer status byte
	test al,ENPS_RXOK	; Is this frame any good?
	jz	rcv_ovr_ng	; Skip if not
 	call	rcv_frm		; Yes, go accept it
rcv_ovr_ng:
	pop	es		; Back to start of this frame
	mov ax,	es:[EN_RBUF_NXT_PG and 0fffeh] ; Get pointer to next frame
	mov	al,ah
	dec	al		; Back up one page
	cmp al,	sm_rstart_pg	; Did it wrap?
	jae	rcv_ovr_nwr2
	mov al,	sm_rstop_pg	; Yes, back to end of ring
	dec	al
rcv_ovr_nwr2:
	loadport		; Base of device
	setport	EN0_BOUNDARY	; Point at boundary reg
	out dx,	al		; Set the boundary

rcv_ovr_empty:
	loadport		; Base of device
	setport	EN0_RCNTLO	; Point at byte count regs
	xor al,	al		; Clear them
	out dx,	al		; ..
	setport	EN0_RCNTHI
	out dx,	al
	setport	EN0_ISR		; Point at status reg
	mov cx,	8000h		; Timeout counter
rcv_ovr_rst_loop:
	in al,	dx		; Is it finished resetting?
	test al,ENISR_RESET	; ..
	jmp	$+2		; limit chip access rate
	loopnz	rcv_ovr_rst_loop; Loop til reset, or til timeout
	loadport		; Base of device
 	setport	EN0_TXCR	; Point at Transmit control reg
	mov al,	ENTXCR_LOOP	; Put transmitter in loopback mode
	out dx,	al		; ..
	setport	EN_CCMD		; Point at Chip command reg
	mov al,	ENC_START+ENC_NODMA
	out dx,	al		; Start the chip running again
	setport	EN0_TXCR	; Back to TX control reg
	xor al,	al		; Clear the loopback bit
	out dx,	al		; ..
	setport	EN0_ISR		; Point at Interrupt status register
	mov al,	ENISR_OVER	; Clear the overrun interrupt bit
	out dx,	al		; ..
	call	count_in_err	; Count the anomaly
 	jmp	isr_no_stat	; Done with the overrun case

recv_no_overrun:
; Handle receive flags, normal and with error (but not overrun).
	test al,ENISR_RX+ENISR_RX_ERR	; Frame received without overrun?
	jnz	recv_frame	; Go if so.
 	jmp	isr_no_rx
recv_frame:
	loadport		; Point at Chip's Command Reg
 	setport	EN_CCMD		; ..
	mov al,	ENC_NODMA+ENC_PAGE1
	out dx,	al		; Switch to page 1 registers
	setport	EN1_CURPAG	;Get current page of rcv ring
	in al,	dx		; ..
	mov ah,	al		; Hold current page in AH
 	setport	EN_CCMD		; Back to page zero registers
	mov al,	ENC_NODMA+ENC_PAGE0
	out dx,	al		; Switch back to page 0 registers
	setport	EN0_BOUNDARY	;Get boundary page
	in al,	dx		; ..
	inc	al		; Step boundary from last used page
	cmp al,	sm_rstop_pg	; Wrap if needed
	jne	rx_nwrap3	; Go if not
	mov al,	sm_rstart_pg	; Wrap to first RX page
rx_nwrap3:
	cmp al,	ah		; Read all the frames?
	je	recv_frame_break	; Finished them all
	mov bl,	al		; Page number as arg to rcv_frm
	xor ah,	ah		; Make segment pointer to this frame
	mov cl,	4		; 16 * pages = paragraphs
	shl ax,	cl		; ..
	add ax,	mem_base	; That far into shared memory
	mov es,	ax		; Segment part of pointer
	push	es		; Hold on to this pointer for later
	mov ax,	es:[EN_RBUF_STAT]	; Get the buffer status byte
	test al,ENPS_RXOK	; Good frame?
	jz	recv_no_rcv
	call	rcv_frm		; Yes, go accept it
recv_no_rcv:
	pop	es		; Back to base of frame
	mov ax,	es:[EN_RBUF_NXT_PG and 0fffeh] ; Start of next frame
	mov	al,ah
	dec	al		; Make previous page for new boundary
	cmp al,	sm_rstart_pg	; Wrap around the bottom?
	jae	rcv_nwrap4
	mov al,	sm_rstop_pg	; Yes
	dec	al
rcv_nwrap4:
	loadport		; Point at the Boundary Reg again
 	setport	EN0_BOUNDARY	; ..
	out dx,	al		; Set new boundary
	jmp	recv_frame	; See if any more frames

recv_frame_break:
	loadport		; Point at Interrupt Status Reg
 	setport	EN0_ISR		; ..
	mov al,	ENISR_RX+ENISR_RX_ERR+ENISR_OVER
	out dx,	al		; Clear those requests
	jmp	isr_no_stat	; See if any other interrupts pending

isr_no_rx:
if	0	; The following code never executed
; Now check to see if any counters are getting full
	test al,ENISR_COUNTERS	; Interrupt to handle counters?
	jnz	isr_stat	; nz = yes
	jmp	isr_no_stat
isr_stat:
; We have to read the counters to clear them and to clear the interrupt.
; The structure of the PC/FTP driver system doesn't give us
; anything useful to do with the data, though.
	loadport		; Point at first counter
 	setport	EN0_COUNTER0
	in	al,dx		; Read the count, ignore it
	setport	EN0_COUNTER1
	in	al,dx		; Read the count, ignore it
	setport	EN0_COUNTER2
	in	al,dx		; Read the count, ignore it
	setport	EN0_ISR		; Clear the statistics completion flag
	mov	al,ENISR_COUNTERS
	out	dx,al
endif		; never executed code

isr_no_stat:
	call	xmit		; Let xmit read ISR
 	jmp	check_isr	; Anything else to do?


; Do the work of copying out a receive frame.
; Called with bl/ the page number of the frame header in shared memory/
; Also, es/ the paragraph number of that page.

rcv_frm:
; Old version checked size, memory space, queue length here. Now done
; in higher level code.
; Set cx to length of this frame.
	mkle LE_RCVFRM_E, 0, 0, 0

        mov cx, es:[EN_RBUF_SIZE]       ; Extract size of frame
        sub cx, EN_RBUF_NHDR            ; Less the header stuff
        cmp cx, 1514
        jbe rcv_size_ok                 ; is the size sane? 
        cmp ch, cl                      ; is it starlan bug (dup of low byte)
        jz  rcv_starlan_bug
        mov cx, 1514                    ; cap the length
        jmp rcv_size_ok
rcv_starlan_bug:                        ; fix the starlan bug
        mov ch, es:[EN_RBUF_NXT_PG]     ; Page after this frame
        cmp ch, bl
        ja  rcv_frm_no_wrap
        add ch, sm_rstop_pg             ; Wrap if needed
        dec ch
rcv_frm_no_wrap:
        sub ch, bl
        dec ch
rcv_size_ok:
; Set es:di to point to Ethernet type field.  es is already at base of
; page where this frame starts.  Set di after the header and two addresses.
	mov di,	EN_RBUF_NHDR+EADDR_LEN+EADDR_LEN
	push	bx			; Save page number in bl
	push	cx			; Save frame size
	push	es
	mov ax,	cs			; Set ds = code
	mov ds,	ax
	assume	ds:code
	call	recv_find		; See if type and size are wanted
	pop	ds			; RX page pointer in ds now
	assume	ds:nothing
	pop	cx
	pop	bx
	cld			; Copies below are forward, please
	mov ax,	es		; Did recv_find give us a null pointer?
	or ax,	di		; ..
	je	rcv_no_copy	; If null, don't copy the data	

	push	cx		; We will want the count and pointer
	push	es		;  to hand to client after copying,
	push	di		;  so save them at this point

;; if ( (((size + 255 + EN_RBUF_NHDR) >> 8) + pg) > sm_rstop_pg){
	mov ax,	cx		; Length of frame
	add ax,	EN_RBUF_NHDR+255 ; Including the overhead bytes, rounded up
	add ah,	bl		; Compute page with last byte of data in ah
	cmp ah,	sm_rstop_pg	; Over the top of the ring?
	ja	rcopy_wrap	; Yes, move in two pieces
	mov si,	EN_RBUF_NHDR	; One piece, starts here in first page (in ds)
	mov ax,	cx		; Move length to ax
	jmp	rcopy_one_piece	; Go move it

rcopy_wrap:
;; Copy in two pieces due to buffer wraparound.
;; n = ((sm_rstop_pg - pg) << 8) - EN_RBUF_NHDR;	/* To top of mem */
	mov ah,	sm_rstop_pg	; Compute length of first part
	sub ah,	bl		;  as all of the pages up to wrap point
	xor al,	al		; 16-bit count
	sub ax,	EN_RBUF_NHDR	; Less the four overhead bytes
	sub cx,	ax		; Move the rest in second part
	push	cx		; Save count of second part
	mov si,	EN_RBUF_NHDR	; ds:si points at first byte to move
	call	sm_copy		; Copy first piece
	mov ax,	mem_base	; Paragraph of base of shared memory
	mov ds,	ax		; ..
	mov ah,	sm_rstart_pg	; Offset to start of first receive page
	xor al,	al		; 16-bit offset
	mov si, ax
	pop	ax		; Bytes left to move
rcopy_one_piece:
	call	sm_copy		; Copy piece
	pop	si		; Recover pointer to destination
	pop	ds		; Tell client it's his source
	pop	cx		; And it's this long
	assume	ds:nothing
	call	recv_copy	; Give it to him
rcv_no_copy:
	push	cs		; Put ds back in code space
	pop	ds		; ..
	assume	ds:code
	mkle LE_RCVFRM_X, 0, 0, 0
	ret			; That's it for rcv_frm


	public	recv_exiting
recv_exiting:
;called from the recv isr after interrupts have been acknowledged.
;Only ds and ax have been saved.
	assume	ds:nothing
	ret


;any code after this will not be kept after initialization.
end_resident	label	byte


	public	usage_msg
usage_msg	db	"usage:", CR, LF
		db	"  WD8003E [-n] [-d] [-w] <packet_int_no> <int_level> <io_addr> <mem_segm_addr>",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for Western Digital WD80x3 E S W(T) EB(T) E(T)/A ..., version "
		db	'0'+majver,".",'0'+version,CR,LF
		db	"Portions Copyright 1988, Robert C. Clements, K1BC",CR,LF,'$'

no_board_msg:
	db	"WD8003E apparently not present at this IO address.",CR,LF,'$'
occupied_msg:
	db	"Suggested WD8003E memory address bad or already occupied",CR,LF,'$'
int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'
mem_base_name	db	"Memory address ",'$'
mem_size_name	db	"Memory size ",'$'

occupied_switch	db	0		;if nonzero, don't use occupied test.

	extrn	set_recv_isr: near
	extrn	skip_blanks: near

;enter with si -> argument string, di -> word to store.
;if there is no number, don't change the number.
	extrn	get_number: near

;enter with dx -> name of word, di -> dword to print.
	extrn	print_number: near

	public	parse_args
parse_args:
	call	skip_blanks
	cmp	al,'-'			;did they specify a switch?
	jne	not_switch
	cmp	byte ptr [si+1],'o'	;did they specify '-o'?
	je	got_occupied_switch
	stc				;no, must be an error.
	ret
got_occupied_switch:
	mov	occupied_switch,1
	add	si,2			;skip past the switch's characters.
	jmp	parse_args		;go parse more arguments.
not_switch:
	mov	di,offset int_no
	call	get_number
	mov	di,offset io_addr
	call	get_number
	mov	di,offset mem_base
	call	get_number
	ret

	extrn	etopen_diagn: byte
addr_not_avail:
no_memory:
	mov	dx,offset occupied_msg
	mov	etopen_diagn,34
	jmp	short error_wrt
bad_cksum:
	mov	dx,offset no_board_msg
	mov	etopen_diagn,37
error_wrt:
	mov	ah,9
	int	21h
	stc
	ret


	public	etopen
etopen:				; Initialize interface
	call	terminate	; First, pulse the board reset
	test	sys_features,MICROCHANNEL
	jz	etopen_no_mc
	or	board_features,BF_EIL+BF_WTS+BF_16K
	loadport
	setport	EN_REG5
	mov	al,ENR5_EIL
	out	dx,al		; enable 8390 interrupts to bus
	jmp	etopen_have_id
etopen_no_mc:			; Check for WD8013EBT
	loadport		; WD8013EBT doesn't have register alaasing
	setport	EN_CMD		; Register 0 may be aliased to Register 8
	mov bx,	dx
	setport	EN_SAPROM
	mov cx,	EN_SAPROM-EN_CMD ; Check 8 bytes
alias_loop:
	in al,	dx		; Get one register
	mov ah,	al
	xchg bx, dx		; Switch to other register
	in al,	dx		; Get other register
	cmp al,	ah		; Are they the same?
	jne	not_aliased	; Nope, not aliased
	inc	bx		; Increment register pair
	inc	dx
	dec	cx		; Decrement loop counter
	jne	alias_loop	; Finished?
	jmp	etopen_have_id	; Aliased; not WD8013EBT
not_aliased:			; Not aliased; Check for 16-bit board
	loadport
	setport	EN_REG1		; Bit 0 must be unmodifiable
	in al,	dx		; Get register 1
	mov bl,	al		; Store original value
	xor al,	ENR1_BUS16BIT	; Flip bit 0
	out dx,	al		; Write it back
	and al,	ENR1_BUS16BIT	; Throw other bits away
	mov ah,	al		; Store bit value
	in al,	dx		; Read register again
	and al,	ENR1_BUS16BIT	; Throw other bits away
	cmp al,	ah		; Was it modified?
	jne	board16bit	; No; board is a WD8013EBT !
	mov al,	bl		; Get original value
	out dx,	al		; Write it back
	jmp	etopen_have_id
board16bit:			; But is it plugged into a 16-bit slot?
	and al,	ENR1_BUS16BIT	; Throw other bits away
	je	etopen_have_id	; Nope; silly board installer!
	or	board_features,BF_WTS+BF_MEM16EN+BF_16K
	setport	EN_REG5
	mov	al,ENR5_LAN16EN+ENR5_LA19 ; Write LA19 now, but not MEM16EN
	out	dx,al		; enable 8390 interrupts to bus
	
etopen_have_id:
	loadport
	setport	EN_CCMD		; DS8390 chip's command register
	mov al,	ENC_NODMA+ENC_PAGE0	
	out dx,	al		; Switch to page zero
	setport	EN0_ISR		; Clear all interrupt flags
	mov al,	0ffh		; ..
	out dx,	al		; ..
; Copy our Ethernet address from PROM into the DS8390
; (No provision in driver spec for setting a false address.)
	setport	EN_CCMD		; Chip command register
	mov al,	ENC_NODMA+ENC_PAGE1
	out dx,	al		; Switch to page one for writing eaddr
	mov cl,	EADDR_LEN	; Loop for six bytes
	xor ch,	ch		; Clear the index of bytes
	xor bx,	bx		; Clear the addr ROM checksum
cpy_adr_loop:
	loadport		; Base of registers
	setport	EN_SAPROM	; Prom address
	add dl,	ch		; Plus which byte this is
	in al,	dx		; Get a byte of address
	add	bl,al		; Compute the checksum
	add dl,	EN1_PHYS-EN_SAPROM ; Point at reg in chip
	out dx,	al		; Copy that byte
	inc	ch		; Step the index
	dec	cl		; Count bytes
	jnz	cpy_adr_loop	; Loop for six
	loadport		; Get last two bytes into cksum
	setport	EN_SAPROM+EADDR_LEN
	in al,	dx		; Get seventh byte
	add bl,	al		; Add it in
	inc	dx		; Step to eighth byte
	in al,	dx		; Get last byte
	add bl,	al		; Final checksum
	cmp bl, 0ffh		; Correct?
	je	good_cksum	; Damn 128 byte jump limit...
	jmp	bad_cksum	; No, board is not happy
good_cksum:
; Clear the multicast filter enables, we don't want any of them.
	mov cl,	8		; Eight bytes of multicast filter
	xor al,	al		; Zeros for filter
	loadport		; Base of multicast filter locations
	setport	EN1_MULT	; ..
clr_mcast_l:
	out dx,	al		; Clear a byte
	inc	dl		; Step to next one
	dec	cl		; Count 8 filter locs
	jnz	clr_mcast_l	; ..	
	loadport		; Base of I/O regs
	setport	EN_CCMD		; Chip command register
	mov al,	ENC_NODMA+ENC_PAGE0
	out dx,	al		; Back to page zero
	setport	EN0_DCFG	; Configure the fifo organization
	mov al,	ENDCFG_BM8	; Fifo threshold = 8 bytes
	test	board_features,BF_WTS
	jz	bytemove
	or	al,ENDCFG_WTS	; word access for 16-bit cards
bytemove:
	out dx,	al
	setport	EN0_RCNTLO	; Clear the byte count registers
	xor al,	al		; ..
	out dx,	al
	setport	EN0_RCNTHI
	out dx,	al		; Clear high byte, too
	setport	EN0_RXCR	; Set receiver to monitor mode
	mov al,	ENRXCR_MON
	out dx,	al
	setport	EN0_TXCR	; Set transmitter mode to normal
	xor al,	al
	out dx,	al

; Check if the shared memory address range is availabe to us
	mov	bx,mem_base
	test	occupied_switch,1
	jnz	no_lim_chk
	cmp	bh,80h			; low limit is 8000
	jae	fr_8000
	jmp	no_memory
fr_8000:
	cmp	bh,0f0h			; upper limit is F000
	jb	to_F000
	jmp	no_memory
to_F000:
	test	bx,01ffh		; must be on a 8 k boundary
	jz	eightk
	jmp	no_memory
eightk:
no_lim_chk:
	mov	di,8*1024/16		; 8 kbyte
	mov	sm_rstop_pg,32
	test	board_features,BF_16K
	jz	just_8k
	mov	di,16*1024/16		; 16 kbytes
	mov	sm_rstop_pg,64
just_8k:
	test	occupied_switch,1	; did they insist?
	jnz	is_avail		; yes, don't check.
	call	occupied_chk		; check if address range is available
	jnc	is_avail
	jmp	addr_not_avail		; we HAVE to have at least 8/16 kbyte
is_avail:
	test	board_features,BF_16K
	jnz	not_32k
	mov	di,32*1024/16		; may be there is space for 32 kbyte
	call	occupied_chk
	jc	not_32k			; no, then don't try it later either
	and	bh,7
	jnz	not_32k			; must be on a 32k boundary
	mov	sm_rstop_pg,128		; yes, there is space for a WD8003EBT
not_32k:

; Turn on the shared memory block
	setport	EN_CMD		; Point at board command register
	mov ax,	mem_base	; Find where shared memory will be mapped
	mov al,	ah		; Shift to right location
	sar al,	1		;  in the map control word
	and al,	EN_MEM_MASK	; Just these bits
	or al,	EN_MEMEN	; Command to turn on map
	test	sys_features,MICROCHANNEL
	jz	AT_card
	mov	al,EN_MEMEN	; membase handled different for MC card
AT_card:
	out dx,	al		; Create that memory

; Find how much memory this card has (without destroying other memory)
	mov	si,ax			; save bord command value
	mov	es,mem_base
	mov	bl,0FFh			; first try 32 kbyte (WD8003EBT)
	mov	bh,sm_rstop_pg		;   or what is available
	dec	bh
memloop:
	dec	bx			; use even address
	cli				; disable interrupts
	mov	cx,es:[bx]		; save old memory contents
	mov	word ptr es:[bx],05A5Ah	; put testpattern
	loadport
	setport	EN_CCMD			; drain the board bus for any
	in	al,dx			;   capacitive memory
	cmp	word ptr es:[bx],05A5Ah	; any real memory there?
	jne	not_our_mem		;   no
	setport	EN_CMD			;   yes
	mov	ax,si
	and	al,0FFh xor EN_MEMEN
	out	dx,al			; turn off our memory
	jmp	short $+2
	or	al,EN_MEMEN
	cmp	word ptr es:[bx],05A5Ah	; was it OUR memory?
	out	dx,al
	jmp	short $+2
	mov	es:[bx],cx
	sti
	jne	our_mem			;   yes, it wasn't there any more
not_our_mem:				;   no, it was still there
	shr	bx,1			; test if half as much memory
	cmp	bx,1FFFh		; down to 8 kbyte
	jae	memloop
	jmp	no_memory		; no memory at address mem_base
our_mem:				; it IS our memory!
	inc	bh
	mov	sm_rstop_pg,bh		; # of 256 byte ring bufs + 1
	cmp	bh,32			; Eight k card?
	jbe	rstart_set		; Yes
	mov	sm_rstart_pg,SM_TMAX_PG	; Allocate more transmit buffers
	mov	xmt_bufs,SM_TMAX_PG/6	; Set xmt_bufs
	mov	al,bh			; Calculate rcv_bufs as
	sub	al,SM_TMAX_PG		;  bufs - xmt_bufs
	xor	ah,ah
	mov	cl,6			;  divide by 6 (pages per MTU pkt)
	div	cl
	xor	ah,ah
	mov	rcv_bufs,ax
rstart_set:
	mov	ch,bh
	xor	cl,cl
	mov	ax,mem_base
	call	memory_test		; check all of that memory
	je	mem_ok
	jmp	no_memory
mem_ok:

; Set up control of shared memory, buffer ring, etc.
	loadport
	setport	EN0_STARTPG	; Set receiver's first buffer page
	mov al,	sm_rstart_pg
	out dx,	al
	setport	EN0_STOPPG	;  and receiver's last buffer page + 1
	mov al,	sm_rstop_pg
	out dx,	al
	setport	EN0_BOUNDARY	; Set initial "last page we have emptied"
	mov al,	sm_rstart_pg
	out dx,	al
	setport	EN_CCMD		; Switch to page one registers
	mov al,	ENC_NODMA+ENC_PAGE1
	out dx,	al
	setport	EN1_CURPAG	; Set current shared page for RX to work on
	mov al,	sm_rstart_pg
	inc al
	out dx,	al
	setport	EN_CCMD		; Switch back to page zero registers
	mov al,	ENC_NODMA+ENC_PAGE0
	out dx,	al
	setport	EN0_IMR		; Clear all interrupt enable flags
	xor al,	al
	out dx,	al
	setport	EN0_ISR		; Clear all interrupt assertion flags
	mov al,	0ffh		; again for safety before making the
	out dx,	al		; interrupt be enabled
	call	set_recv_isr	; Put ourselves in interrupt chain
	loadport
	setport	EN_CCMD		; Now start the DS8390
	mov al,	ENC_START+ENC_NODMA
	out dx,	al		; interrupt be enabled
	setport	EN0_RXCR	; Tell it to accept broadcasts
	mov al,	ENRXCR_BCST
	out dx,	al
	setport	EN0_IMR		; Tell card it can cause these interrupts
	mov al,	ENISR_ALL
	out dx,	al

	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.

	mov	dx,offset end_resident
	clc
	ret

	public	print_parameters
print_parameters:
;echo our command-line parameters
	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 mem_base
	mov	dx,offset mem_base_name
	call	print_number
	xor	ax,ax
	push	ax
	mov	ah,sm_rstop_pg
	push	ax
	mov	di,sp
	mov	dx,offset mem_size_name
	call	print_number
	add	sp,4
	ret

	include	memtest.asm
	include	occupied.asm

code	ends
	end
