version	equ	5

	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.

;   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	byte 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_REG5		equ	005h	; New command register (REGISTER 5)
EN_SAPROM	equ	008h	; Window on station addr prom

; 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

; Commands for REG5 register
ENR5_EIL	equ	004h	; enable 8390 interrupts to bus

; 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

XMIT_MTU	equ	600h	; Largest packet we have room for.
SM_TSTART_PG	equ	0	; First page of TX buffer
SM_RSTART_PG	equ	6	; Starting page of ring
SM_BASE		equ	0C400h	; Default para where shared memory starts
				; Real value set at attach time.
sm_rstop_pg	db	32	; Last page +1 of ring


; 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	2
parameter_list	label	byte
	db	1	;major rev of packet driver
	db	9	;minor rev of packet driver
	db	14	;length of parameter list
	db	EADDR_LEN	;length of MAC-layer address
	dw	GIANT	;MTU, including MAC headers
	dw	MAX_MULTICAST * EADDR_LEN	;buffer size of multicast addrs
	dw	0	;(# of back-to-back MTU rcvs) - 1
	dw	0	;(# of successive xmits) - 1
	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
microchannel	equ	2		; flag in above byte
; send_pkt: - The Transmit Frame routine

	public	send_pkt
send_pkt:
;enter with es:di->upcall routine, (0:0) if no upcall is desired.
;  (only if the high-performance bit is set in driver_function)
;enter with ds:si -> packet, cx = packet length.
;exit with nc if ok, or else cy if error, dh set to error number.
	assume	ds:nothing
	loadport		; Point at chip command register
	setport EN_CCMD		; ..
	mov bx,	8000h		; Avoid infinite loop
tx_wait:
	in al,	dx		; Get chip command state
	test al,ENC_TRANS	; Is transmitter still running?
	jz	tx_idle		; Go if free
	dec	bx		; Count the timeout
	jnz	tx_wait		; Fall thru if TX is stuck
				; Should count these error timeouts
				; Maybe need to add recovery logic here
tx_idle:
	cmp	cx,XMIT_MTU	; Is this packet too large?
	ja	send_pkt_toobig

	cmp cx,	RUNT		; Is the frame long enough?
	jnb	tx_oklen	; Go if OK
	mov cx,	RUNT		; Stretch frame to minimum allowed
tx_oklen:
	push	cx		; Hold count for later
				; Now compute destination of move in es:di
	mov ax,	mem_base	; Compute base of transmit buffer
;	add ax,	SM_TSTART_PG*16	; The right page in mem (currently zero)
	mov es,	ax		; Paragraph of the TX buffer
	xor di,	di		; Fill starting at beginning of paragraph
; Can't use movemem which word aligns to the source, but needs to word
; align to the destination writing to WD8003ET/A. Fortunately works for
; all cards.
	inc	cx		; if odd bytes pad to word
	shr	cx,1		; convert bytes to words
	rep	movsw		; word access required by MC card
	pop	cx		; Get back count to give to board
	loadport		; Base of I/O regs
	setport	EN0_TCNTLO	; Low byte of TX count
	mov al,	cl		; Get the count
	out dx,	al		; Tell card the count
	setport	EN0_TCNTHI	; High byte of TX count
	mov al,	ch		; Get the count
	out dx,	al		; Tell card the count
	setport	EN0_TPSR	; Transmit Page Start Register
	mov al,	SM_TSTART_PG
	out dx,	al		; Start the transmitter
	setport	EN_CCMD		; Chip command reg
	mov al,	ENC_TRANS+ENC_NODMA
	out dx,	al		; Start the transmitter
	clc
	ret			; End of transmit-start routine

send_pkt_toobig:
	mov	dh,NO_SPACE
	stc
	ret


	public	get_address
get_address:
;get the address of the interface.
;enter with es:di -> place to get the address, cx = size of address buffer.
;exit with nc, cx = actual size of address, or cy if buffer not big enough.
	assume ds:code
	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
set_address:
	assume	ds:nothing
;enter with ds:si -> Ethernet address, CX = length of address.
;exit with nc if okay, or cy, dh=error if any errors.
;
	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
	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
	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
set_multicast_list:
;enter with ds:si ->list of multicast addresses, cx = number of addresses.
;return nc if we set all of them, or cy,dh=error if we didn't.
	mov	dh,NO_MULTICAST
	stc
	ret


; Set the multicast filter mask bits in case promiscuous rcv wanted
set_8390_multi:
	loadport
	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
	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
	setport	EN_CCMD		; Chip command register
	mov	al,ENC_NODMA+ENC_PAGE0
	out	dx,al		; Restore to page zero
	sti			; OK for interrupts now
	ret


	public	terminate
terminate:
	ret

	public	reset_interface
reset_interface:
	assume ds:code
	loadport		; Base of I/O regs
	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
	setport	EN_REG5
	mov	al,ENR5_EIL
	test	sys_features,microchannel
	jz	reset_no_mc
	out	dx,al			; enable 8390 interrupts to bus
reset_no_mc:
	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

	extrn	count_in_err: near
	extrn	count_out_err: near

	public	recv
recv:
;called from the recv isr.  All registers have been saved, and ds=cs.
;Actually, not just receive, but all interrupts come here.
;Upon exit, the interrupt will be acknowledged.

	assume	ds:code
check_isr:			; Was there an interrupt from this card?
	loadport		; Point at interrupt status register
	setport	EN0_ISR		; ..
	in al,	dx		; Get pending interrupts
	and al,	ENISR_ALL	; Any?
	jnz	isr_test_overrun
	ret			; Go if none

; First, 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:
	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		; Point at boundary reg
	setport	EN0_BOUNDARY	; ..
	out dx,	al		; Set the boundary

rcv_ovr_empty:

	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		; Point at Transmit control reg
 	setport	EN0_TXCR	; ..
	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	check_isr	; 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	recv_no_frame	; Go if not.
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	check_isr	; See if any other interrupts pending

recv_no_frame:				; Handle transmit flags.
	test al,ENISR_TX+ENISR_TX_ERR	; Frame transmitted?
	jnz	isr_tx		; Go if so.
	jmp	isr_no_tx	; Go if not.
isr_tx:
	mov ah,	al		; Hold interrupt status bits
	loadport		; Point at Transmit Status Reg
 	setport	EN0_TSR		; ..
	in al,	dx		; ..
	test ah,ENISR_TX	; Non-error TX?
	jz	isr_tx_err	; No, do TX error completion
	test al,ENTSR_COLL16	; Jammed for 16 transmit tries?
	jz	isr_tx_njam	; Go if not
	call	count_out_err	; Yes, count those
isr_tx_njam:
	setport	EN0_ISR		; Clear the TX complete flag
	mov al,	ENISR_TX	; ..
	out dx,	al		; ..	
	jmp	isr_tx_done
isr_tx_err:
	test al,ENTSR_FU	; FIFO Underrun?
	jz	isr_txerr_nfu
	call	count_out_err	; Yes, count those
isr_txerr_nfu:
	loadport		; Clear the TX error completion flag
	setport	EN0_ISR		; ..
	mov al,	ENISR_TX_ERR	; ..
	out dx,	al		; ..	
isr_tx_done:
; If TX queue and/or TX shared memory ring buffer were being
; used, logic to step through them would go here.  However,
; in this version, we just clear the flags for background to notice.

 	jmp	check_isr	; See if any other interrupts on

isr_no_tx:
; 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
isr_no_stat:
 	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.
        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)
	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 cx,	ax		; Count for first move
	mov si,	EN_RBUF_NHDR	; ds:si points at first byte to move
	shr cx,	1		; All above are even numbers, do words.
	rep	movsw		; Move first part of frame
	mov ax,	mem_base	; Paragraph of base of shared memory
	mov ds,	ax		; ..
	mov si,	SM_RSTART_PG*256  ; Offset to start of first receive page
	pop	cx		; Bytes left to move
rcopy_one_piece:
	shr	cx,1		; convert bytes to words
	rep	movsw		; word access desired for MC card
	jnc	rcv_even	; odd byte left over?
	lodsw			;   yes, word fetch
	stosb			;   and byte store
rcv_even:
	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
	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: WD8003E <packet_int_no> <int_level> <io_addr> <mem_base>",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for Western Digital WD8003 E EBT EB ET/A and E/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 already occupied",CR,LF,'$'
int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'
mem_base_name	db	"Memory address ",'$'

occupied_switch	db	0		;if zero, 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:
	mov	dx,offset occupied_msg
	mov	etopen_diagn,34
	jmp	short error_wrt
bad_cksum:
no_memory:
	mov	dx,offset no_board_msg
	mov	etopen_diagn,37
error_wrt:
	mov	ah,9
	int	21h
	stc
	ret


	public	etopen
etopen:				; Initialize interface
	loadport		; First, pulse the board reset
	setport	EN_CMD
	mov al,	EN_RESET
	out dx,	al		; Turn on board reset bit
	xor al,	al
	out dx,	al		; Turn off board reset bit
	setport	EN_REG5
	mov	al,ENR5_EIL
	test	sys_features,microchannel
	jz	etopen_no_mc
	out	dx,al		; enable 8390 interrupts to bus
etopen_no_mc:
	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?
	jnz	bad_cksum	; No, board is not happy
; 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	sys_features,microchannel
	jz	bytemove
	or	al,ENDCFG_WTS	; word access for MC card
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
	cmp	bh,0c0h			; low limit is C000
	jae	c0_up
	jmp	no_memory
c0_up:
	cmp	bh,0e0h			; upper limit is E000
	jb	e0_down
	jmp	no_memory
e0_down:
	test	bx,01ffh		; must be on a 8 k boundary
	jz	eightk
	jmp	no_memory
eightk:
	mov	di,8*1024/16		; 8 kbyte
	mov	sm_rstop_pg,32
	test	sys_features,microchannel
	jz	just_8k
	mov	cx,16*1024/16		; 16 kbytes
	mov	sm_rstop_pg,64
just_8k:
	cmp	occupied_switch,0	; did they insist?
	jne	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:
; If I knew how to recognize an WD8003 EBT or EB card, the following code
; could be skipped with just a slight change in the above code /JE.
	test	sys_features,microchannel
	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
	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.
	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+1
	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	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

	mov	dx,offset end_resident
	clc
	ret

	include	memtest.asm
	include	occupied.asm

code	ends

	end
