Metropoli BBS
VIEWER: 8390.asm MODE: TEXT (ASCII)
;History:640,1

dp8390_version	equ	1	;version number of the generic 8390 driver.

;  Copyright, 1988-1992, Russell Nelson, Crynwr Software

;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General 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.

; This driver is the work of several people: Bob Clements, Eric Henderson,
; Dave Horne, Glenn Talbott and Russell Nelson.


pause_	macro
;	jmp	$+2
;
; The reason for the pause_ macro is to establish a minimum time between
; accesses to the card hardware. The assumption is that the fetch and execution
; of the jmp $+2 instruction will provide this time. In a fast cache machine
; this may be a false assumption. In a fast cache machine, there may be 
; NO REAL TIME DIFFERENCE between the two I/O instruction streams below:
;
;	in	al,dx		in	al,dx
;	jmp	$+2
;	in	al,dx		in	al,dx
;
; To establish a minimum delay, an I/O instruction must be used. A good rule of
; thumb is that ISA I/O instructions take ~1.0 microseconds and MCA I/O
; instructions take ~0.5 microseconds. Reading the NMI Status Register (0x61)
; is a good way to pause on all machines.
;
; The National 8390 Chip (NIC) requires 4 bus clocks between successive
; chip selects (National DP8390 Data Sheet Addendum, June 1990 -- it took them
; long enough to figure this out and tell everyone) or the NIC behaves badly.
; Therefor one I/O instruction should be inserted between each successive
; NIC I/O instruction that could occur 'back - to - back' on a fast cache
; machine.
;   - gft - 910529
;
	push	ax
	in	ax, 61h
	pop	ax
;
endm

;
; The longpause macro was originally written as this:
;
;longpause macro
;	push	cx
;	mov	cx,0
;	loop	$
;	pop	cx
;endm
;
; It was only used to stall while hard resetting the card. On my
; 25Mhz 486 longpause was taking more than 18ms, and almost forever
; on slower machines, much longer than necessary and not predictable.
;
; To be able to utilize longpause elsewhere and make it machine independent and
; predictable, I have re-written it to be a fixed time of 1.6ms, which just
; happens to be the time National recommends waiting for the NIC chip to
; stop sending or receiving after being commanded to stop.
;
; Based on the assumption that ISA specs mandate a 1.0 uS minimum I/O cycle
; Microchannel a 0.5uS minimum I/O cycle, and the NMI Status register (location
; 61h) is readable via I/O cycle on all machines, the longpause macro is now
; defined below. - gft - 901604
; (I realize that on slow machines this may take much longer, but the point
; is that on FAST machines it should never be faster than 1.6ms)

longpause macro
	local lp_not_mc
	push cx
	push ax
	mov  cx,1600	; 1.6ms = 1600*1.0us
	test sys_features,MICROCHANNEL
	je   lp_not_mc
	shl  cx,1	; twice as many loops for Microchannel
lp_not_mc:
	in al,61h
	loop lp_not_mc
	pop  ax
	pop  cx
endm

	extrn	sys_features: byte

sm_rstop_ptr	db	SM_RSTOP_PG

rxcr_bits       db      ENRXCR_BCST     ; Default to ours plus multicast


	public	curr_hw_addr, mcast_list_bits, mcast_all_flag
curr_hw_addr	db	0,0,0,0,0,0	;Address set into the 8390
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.
mcast_sw_filter	db	0		; set if software filter is required.
mcast_sw_fin	dw	0
mcast_sw_fout	dw	0

	public	rcv_modes
rcv_modes	dw	7		;number of receive modes in our table.
		dw	0               ;There is no mode zero
		dw	rcv_mode_1
		dw	rcv_mode_2
		dw	rcv_mode_3
		dw	rcv_mode_4
		dw	rcv_mode_5
		dw	rcv_mode_6

	public	mcast_tab
mcast_hcount	dw	0		; multicast header count
mcast_tab_b	db	0ffh,0ffh,0ffh,0ffh,0ffh,0ffh ; entry for broadcast
mcast_tab	db	(MAX_MULTICAST*EADDR_LEN) dup (0)
;
;	a temp buffer for the received header
;
RCV_HDR_SIZE	equ	26		; 2 ids @6 + protocol @2+8, + header @4
rcv_hdr		db	RCV_HDR_SIZE dup(0)

;
;	The board data
;
		public	board_data
BOARD_DATA_SIZE equ	32
board_data	db 	BOARD_DATA_SIZE dup(0)
soft_tx_errors		dw	0,0
soft_tx_err_bits	db	0
soft_rx_errors		dw	0,0
soft_rx_err_bits	db	0



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
	pause_
	in	al,0a0h		; get it
	mov log[bx].le_irr2, al
	mov	al,0bh		; read in-service register from
	out	0a0h,al		; secondary 8259
	pause_
	in	al,0a0h		; get it
	mov log[bx].le_isr2, al
	mov	al,0ah		; read request register from
	out	020h,al		; primary 8259
	pause_
	in	al,020h		; get it
	mov log[bx].le_irr1, al
	mov	al,0bh		; read in-service register from
	out	020h,al		; primary 8259
	pause_
	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	as_send_pkt
; The Asynchronous Transmit Packet routine.
; Enter with es:di -> i/o control block, ds:si -> packet, cx = packet length,
;   interrupts possibly enabled.
; Exit with nc if ok, or else cy if error, dh set to error number.
;   es:di and interrupt enable flag preserved on exit.
as_send_pkt:
	ret

	public	drop_pkt
; Drop a packet from the queue.
; Enter with es:di -> iocb.
drop_pkt:
	assume	ds:nothing
	ret

	public	xmit
; Process a transmit interrupt with the least possible latency to achieve
;   back-to-back packet transmissions.
; May only use ax and dx.
xmit:
	assume	ds:nothing
	ret


	public	send_pkt
send_pkt:
;enter with ds:si -> packet, cx = packet length.
;exit with nc if ok, or else cy if error, dh set to error number.
	assume	ds:nothing
	mkle LE_SP_E, cx, si, ds
;ne1000 checks the packet size at this point, which is probably more sensible.
	loadport		; Point at chip command register
	setport EN_CCMD		; ..
	pause_
;ne1000 fails to check to see if the transmitter is still busy.
	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
	call	count_out_err	; Should count these error timeouts
				; Maybe need to add recovery logic here
tx_idle:
	cmp	cx,GIANT	; 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
	loadport		; Set up for address
	setport EN0_ISR
	pause_
	mov	al,ENISR_RDC	; clear remote interrupt int.
	out	dx,al
	setport	EN0_TCNTLO	; Low byte of TX count
	pause_
	mov al,	cl		; Get the count
	out dx,	al		; Tell card the count
	setport	EN0_TCNTHI	; High byte of TX count
	pause_
	mov al,	ch		; Get the count
	out dx,	al		; Tell card the count
	xor ax,	ax		; Set up ax at base of tx buffer
	mov ah,	SM_TSTART_PG	; Where to put tx frame
	pop	cx		; Get back count to give to board
	call	block_output
	jc	tx_no_rdc
	loadport
	setport	EN0_TPSR	; Transmit Page Start Register
	pause_
	mov al,	SM_TSTART_PG
	out dx,	al		; Start the transmitter
	setport	EN_CCMD		; Chip command reg
	pause_
	mov al,	ENC_TRANS+ENC_NODMA+ENC_START
	out dx,	al		; Start the transmitter
	mkle LE_SP_X, cx, 1, 0
	clc			; Successfully started
	sti
	ret			; End of transmit-start routine
send_pkt_toobig:
	mov	dh,NO_SPACE
	stc
	sti
	ret
tx_no_rdc:
	mov	dh,CANT_SEND
	mkle LE_SP_X, cx, 0, 0
	stc
	sti
	ret

count_soft_err:
	add	word ptr soft_tx_errors,1
	adc	word ptr soft_tx_errors+2,0
	or	byte ptr soft_tx_err_bits,al
	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.
; Give caller the one currently in the 8390, not necessarily the one in PROM.
	assume ds:code
	cmp cx,	EADDR_LEN	; Caller wants a reasonable length?
	jb	get_addr_x	; No, fail.
	mov cx,	EADDR_LEN	; Move one ethernet address from our copy
	mov si, offset curr_hw_addr     ; Copy from most recent setting
	rep     movsb
	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:
	push    cs              ; Copy from them to our RAM copy
	pop     es              ; Destination of move
	mov di, offset curr_hw_addr
	rep     movsb           ; Move their address
	call    set_8390_eaddr  ; Put that address in the chip
set_address_okay:
	mov	cx,EADDR_LEN		;return their address length.
	clc
set_address_done:
	push	cs
	pop	ds
	assume	ds:code
	ret

; Copy our Ethernet address from curr_hw_addr into the DS8390
set_8390_eaddr:
	cld
	push    cs              ; Get it from our local RAM copy
	pop     ds
	mov si, offset curr_hw_addr
	mov cx,	EADDR_LEN	; Move one ethernet address from our copy
	loadport
	setport	EN_CCMD		; Chip command register
	pause_
	cli			; Protect from irq changing page bits
	mov al,	ENC_NODMA+ENC_PAGE1	;+ENC_STOP
	out dx,	al		; Switch to page one for writing eaddr
	setport	EN1_PHYS	; Where it goes in 8390
set_8390_1:
	lodsb
	out	dx,al
	pause_
	inc	dx
	loop	set_8390_1
	loadport
	setport	EN_CCMD		; Chip command register
	pause_
	mov al,	ENC_NODMA+ENC_PAGE0	;+ENC_STOP
	out dx,	al		; Restore to page zero
	sti			; OK for interrupts now
	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	; need to do sw filter.
	mov	mcast_sw_filter,1	; because chip filter is not 100%
	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
	pause_
	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.
	assume ds:nothing
	push	cs
	pop	es		; set es to destination
	mov	di,offset mcast_tab
	mov	ax,cx		; save byte count
	repz	movsb
	mov	dx,0
	mov	cx,6
	div	cx
	mov	mcast_hcount,ax
;
	mov	word ptr mcast_list_bits,0
	mov	word ptr mcast_list_bits+2,0
	mov	word ptr mcast_list_bits+4,0
	mov	word ptr mcast_list_bits+6,0
;
	mov	cx,mcast_hcount
	inc	cx
	mov	di,offset mcast_tab_b
set_mcl_1:
	call	add_mc_bits
	add	di,6
	loop	set_mcl_1
	call    set_8390_multi  ; Set the multicast mask bits in chip
	clc
	mov	dh,0
	ret

;
;	multicast is at es:di
	assume	ds:nothing
add_mc_bits:
	push	cx
	push	di
	mov	cx,6
	mov	dx,0ffffh			; this is msw.
	mov	bx,0ffffh			; set 32 bit number
add_mcb_1:
	mov	al,es:[di]
	inc	di
	call	upd_crc			; update crc
	loop	add_mcb_1		; and loop.
	mov	ah,0
	mov	al,dh			; get ms 8 bits,
	rol	al,1
	rol	al,1
	rol	al,1			; put 3 bits at bottom
	and	al,7
	mov	dl,al			; save in dl
	mov	al,dh			; get ms 8 bits,
	ror	al,1
	ror	al,1			; but at bottom
	and	al,7
	mov	cl,al			; save in cl
	mov	al,1
	rol	al,cl			; set the correct bit,
	mov	di,offset mcast_list_bits
	mov	dh,0
	add	di,dx
	or	cs:[di],al
	pop	di
	pop	cx
	ret

;
;	dx is high,
;	bx is low.
;	al is data

upd_crc:
	push	cx
	mov	cx,8		; do 8 bits
	mov	ah,0
upd_crc1:
	shl	bx,1		; shift bx
	rcl	dx,1		; through dx
	rcl	ah,1		; carry is at bottom of ah
	xor	ah,al		; xor with lsb of data
	rcr	ah,1		; and put in carry bit
	jnc	upd_crc2
;
;	autodin is x^32+x^26+x^23x^22+x^16+
;	x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x^1+1

	xor	dx,0000010011000001b
	xor	bx,0001110110110111b
upd_crc2:
	shr	al,1		; shift the data
	loop	upd_crc1
	pop	cx
	ret

; Set the multicast filter mask bits in case promiscuous rcv wanted
set_8390_multi:
	push    cs
	pop     ds
	assume	ds:code
	loadport
	setport	EN_CCMD		; Chip command register
	pause_
	mov cx,	8		; Eight bytes of multicast filter
	mov si, offset mcast_list_bits  ; Where bits are, if not all ones
	cli			; Protect from irq changing page bits
	mov al,	ENC_NODMA+ENC_PAGE1+ENC_STOP
	out dx,	al		; Switch to page one for writing eaddr
	setport	EN1_MULT	; Where it goes in 8390
	pause_
	mov al, mcast_all_flag  ; Want all ones or just selected bits?
	or al,  al
	je      set_mcast_2     ; 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
	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
	loop	set_mcast_2 	; ..
set_mcast_x:
	loadport
	setport	EN_CCMD		; Chip command register
	pause_
	mov al,	ENC_NODMA+ENC_PAGE0+ENC_START
	out dx,	al		; Restore to page zero
	sti			; OK for interrupts now
	ret


	public	reset_board
reset_board:
	assume ds:nothing
	reset_8390
	setport	EN_CCMD		; Chip command reg
	pause_
	mov al,	ENC_STOP+ENC_NODMA
	out dx,	al		; Stop the DS8390
	setport EN0_ISR
	mov	ax,18			;half a second ought be to enuf.
	call	set_timeout
reset_board_loop:
	pause_
	in	al,dx		; get isr
	and	al,ENISR_RESET
	jnz	reset_board_done
	call	do_timeout
	jne	reset_board_loop
reset_board_done:
	ret

	public	terminate
terminate:
	terminate_board
	ret

	public	reset_interface
reset_interface:
	assume ds:code
	call	reset_board
	loadport		; Base of I/O regs
	setport	EN0_ISR		; Interrupt status reg
	pause_
	mov al,	0ffh		; Clear all pending interrupts
	out dx,	al		; ..
	setport	EN0_IMR		; Interrupt mask reg
	pause_
	xor al,	al		; Turn off all enables
	out dx,	al		; ..
	ret

; Linkages to non-device-specific routines
;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type, dl = packet class.
;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.
;ne1000 and ne2000 routines are identical to this point (except that ne2000
;  masks off interrupts).
	assume	ds:code
	mkle LE_RECV_E, 0, 0, 0

check_isr:			; Was there an interrupt from this card?
	loadport		; Point at card's I/O port base
	ram_enable
	setport EN0_IMR		; point at interrupt masks
	pause_			; switch off, this way we can
	mov	al,0		; leave the chip running.
	out	dx,al		; no interrupts please.
	setport	EN0_ISR		; Point at interrupt status register
	pause_
	in al,	dx		; Get pending interrupts
	and al,	ENISR_ALL	; Any?
	jnz	isr_test_overrun
	mkle LE_RECV_X, 0, 0, 0
	jmp	interrupt_done	; 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.
; This is handled differently in sample code from 3Com and from WD.
; This is close to the WD version.  May need tweaking if it doesn't
; work for the 3Com card.

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 chip
	pause_
	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
	pause_
	in al,	dx		; Get memory page number
	inc	al		; Page plus 1
	cmp al,	sm_rstop_ptr	; 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

;ne1000 and ne2000 routines are identical to this point (except that ne2000
;  masks off interrupts).
	mov	ah,al		; make a byte address. e.g. page
	mov	bl,ah		; and save in bl
	mov	al,0		; 46h becomes 4600h into buffer
	mov	cx,RCV_HDR_SIZE	; size of rcv_hdr
	mov	di,offset rcv_hdr ;point to header
	push	ds
	pop	es		; set es to right place
	call	block_input
	mov al,	rcv_hdr+EN_RBUF_STAT	; Get the buffer status byte
	test al,ENRSR_RXOK	; Is this frame any good?
	jz	rcv_ovr_ng	; Skip if not
 	call	rcv_frm		; Yes, go accept it
rcv_ovr_ng:
	mov	al,rcv_hdr+EN_RBUF_NXT_PG ; Get pointer to next frame
	dec	al		; Back up one page
	cmp	al,SM_RSTART_PG	; Did it wrap?
	jae	rcv_ovr_nwr2
	mov	al,sm_rstop_ptr	; Yes, back to end of ring
	dec	al
rcv_ovr_nwr2:
	loadport		; Point at boundary reg
	setport	EN0_BOUNDARY	; ..
	pause_
	out dx,	al		; Set the boundary
rcv_ovr_empty:
	loadport		; Point at boundary reg
	setport	EN0_RCNTLO	; Point at byte count regs
	pause_
	xor al,	al		; Clear them
	out dx,	al		; ..
	setport	EN0_RCNTHI
	pause_
	out dx,	al
	setport	EN0_ISR		; Point at status reg
	pause_
	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	; ..
	pause_
	mov al,	ENTXCR_LOOP	; Put transmitter in loopback mode
	out dx,	al		; ..
	setport	EN_CCMD		; Point at Chip command reg
	pause_
	mov al,	ENC_START+ENC_NODMA
	out dx,	al		; Start the chip running again
	setport	EN0_TXCR	; Back to TX control reg
	pause_
	xor al,	al		; Clear the loopback bit
	out dx,	al		; ..
	setport	EN0_ISR		; Point at Interrupt status register
	pause_
	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	EN0_ISR		; Point at Interrupt status register
	pause_
	mov al,	ENISR_RX+ENISR_RX_ERR
	out dx,	al		; Clear those requests
 	setport	EN_CCMD		; ..
	pause_
	mov al,	ENC_NODMA+ENC_PAGE1+ENC_START
	out dx,	al		; Switch to page 1 registers
	setport	EN1_CURPAG	;Get current page of rcv ring
	pause_
	in al,	dx		; ..
	mov ah,	al		; Hold current page in AH
 	setport	EN_CCMD		; Back to page zero registers
	pause_
	mov al,	ENC_NODMA+ENC_PAGE0+ENC_START
	out dx,	al		; Switch back to page 0 registers
	setport	EN0_BOUNDARY	;Get boundary page
	pause_
	in al,	dx		; ..
	inc	al		; Step boundary from last used page
	cmp al,	sm_rstop_ptr	; 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	ah,al		; make a byte address. E.G. page
	mov	al,0		; 46h becomes 4600h into buffer
	mov	bl,ah
	mov	cx,RCV_HDR_SIZE
	mov	di,offset rcv_hdr
	push	ds
	pop	es		; set es to right place
	call	block_input
	mov al,	rcv_hdr+EN_RBUF_STAT	; Get the buffer status byte
	test al,ENRSR_RXOK	; Good frame?
	jz	recv_err_no_rcv
	call	rcv_frm		; Yes, go accept it
	jmp	recv_no_rcv
recv_err_no_rcv:
	or	byte ptr soft_rx_err_bits,al
	add	word ptr soft_rx_errors,1
	adc	word ptr soft_rx_errors+2,0
recv_no_rcv:
	mov al,	rcv_hdr+EN_RBUF_NXT_PG	; Start of next frame
	dec	al		; Make previous page for new boundary
	cmp al,	SM_RSTART_PG	; Wrap around the bottom?
	jge	rcv_nwrap4
	mov al,	sm_rstop_ptr	; Yes
	dec al
rcv_nwrap4:
	loadport		; Point at the Boundary Reg again
 	setport	EN0_BOUNDARY	; ..
	pause_
	out dx,	al		; Set new boundary
	jmp	recv_frame	; See if any more frames

recv_frame_break:
	loadport		; Point at Command register
 	setport	EN_CCMD		; ..
	pause_
	mov al,	ENC_NODMA+ENC_PAGE0+ENC_START
	out	dx,al
	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		; ..
	pause_
	in al,	dx		; ..
	test ah,ENISR_TX	; Non-error TX?
	jz	isr_tx_err	; No, do TX error completion
	call	count_soft_err	; soft error ??
	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
	pause_
	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		; ..
	pause_
	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	; Go if so.
	jmp	isr_no_stat	; Go if not.
isr_stat:
; We have to read the counters to clear them and to clear the interrupt.
; Version 1 of the PC/FTP driver spec doesn't give us
; anything useful to do with the data, though.
; Fix this up for V2 one of these days.
	loadport		; Point at first counter
 	setport	EN0_COUNTER0	; ..
	pause_
	in al,	dx		; Read the count, ignore it.
	setport	EN0_COUNTER1
	pause_
	in al,	dx		; Read the count, ignore it.
	setport	EN0_COUNTER2
	pause_
	in al,	dx		; Read the count, ignore it.
	setport	EN0_ISR		; Clear the statistics completion flag
	pause_
	mov al,	ENISR_COUNTERS	; ..
	out dx,	al		; ..
isr_no_stat:
 	jmp	check_isr	; Anything else to do?

interrupt_done:
	ret

; Do the work of copying out a receive frame.
; Called with bl/ the page number of the frame header in shared memory

	public	rcv_frm
rcv_frm:
	mkle LE_RCVFRM_E, 0, 0, 0

; first do a software multicast filter.
	push	bx			; save page.
	cmp	mcast_sw_filter,1	; do software check of mcast ?
	jnz	rcv_frm_ok		; no, accept.
	mov	ax,word ptr rcv_hdr+EN_RBUF_NHDR ; get first word of address
	test al,1			; odd first byte
	jz	rcv_frm_ok		; must be our address if even
	inc	word ptr mcast_sw_fin

	mov	bx,word ptr rcv_hdr+EN_RBUF_NHDR+2 ; get second word of address
	mov	dx,word ptr rcv_hdr+EN_RBUF_NHDR+4 ; get third word of address

	mov	di,offset mcast_tab_b	; point to table and broadcast
	mov	cx,mcast_hcount		; get number in table
	inc	cx			; plus the broadcast
rcv_loop:
	mov	si,di			; save this table entry
	cmp	ax,[di]
	jnz	rcv_trynext
	inc	di
	inc	di
	cmp	bx,[di]
	jnz	rcv_trynext
	inc	di
	inc	di
	cmp	dx,[di]
	jz	rcv_mc_ok		; got it.
rcv_trynext:
	mov	di,si			; get table back,
	add	di,6
	loop	rcv_loop
	pop	bx			; restore bx
	jmp	rcv_no_copy

rcv_mc_ok:
	inc	word ptr mcast_sw_fout
rcv_frm_ok:
; Set cx to length of this frame.
	mov ch,	rcv_hdr+EN_RBUF_SIZE_HI	; Extract size of frame
	mov cl,	rcv_hdr+EN_RBUF_SIZE_LO	; Extract size of frame
	sub cx,	EN_RBUF_NHDR		; Less the header stuff
; Set es:di to point to Ethernet type field.
	mov di,	offset rcv_hdr+EN_RBUF_NHDR+EADDR_LEN+EADDR_LEN
	push	cx			; Save frame size
	push	es
	mov ax,	cs			; Set ds = code
	mov ds,	ax
	mov es,ax
	assume	ds:code

	mov	dl, BLUEBOOK		;assume bluebook Ethernet.
	mov	ax, es:[di]
	xchg	ah, al
	cmp 	ax, 1500
	ja	BlueBookPacket
	inc	di			;set di to 802.2 header
	inc	di
	mov	dl, IEEE8023
BlueBookPacket:

	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
	mov	ah,bl		; set ax to page to start from
	mov	al,EN_RBUF_NHDR	; skip the header stuff
	call	block_input
	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
	push	dx
	loadport
	setport	EN0_IMR		; Tell card it can cause these interrupts
	pause_
	mov al,	ENISR_ALL
	out dx,	al
	pop	dx
	ret

	include	timeout.asm

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

using_186_msg	db	"Using 80[123]86 I/O instructions.",CR,LF,'$'

;standard EN0_DCFG contents:
endcfg	db	048h			; Set burst mode, 8 deep FIFO

; Called once to initialize the card

	public	etopen
etopen:				; Initialize interface

;Determine the processor type.  The 8088 and 8086 will actually shift ax
;over by 33 bits, while the 80[123]86 use a shift count mod 32.
;This bit lifted from NI5010 driver.

	mov	cl,33
	mov	ax,0ffffh
	shl	ax,cl
	jz	not_186
	mov	is_186,1
	mov	dx,offset using_186_msg
	mov	ah,9
	int	21h
not_186:

;Step 1. Reset and stop the 8390.

	call	reset_board

;Step 2. Init the Data Config Reg.

	loadport
	mov	al,endcfg
	setport	EN0_DCFG
	pause_
	out	dx,al

;Step 3. Clear Remote Byte Count Regs.

	mov	al, 0
	setport	EN0_RCNTLO
	pause_
	out	dx,al
	setport	EN0_RCNTHI
	pause_
	out	dx,al

;Step 4. Set receiver to monitor mode

	mov	al, ENRXCR_MON
	setport	EN0_RXCR
	pause_
	out	dx,al

;Step 5. Place NIC into Loopback Mode 1.

	mov	al,ENTXCR_LOOP
	setport	EN0_TXCR
	pause_
	out	dx,al

;Step 6. Do anything special that the card needs.

	call	init_card

;Step 7. Re-init endcfg in case they put it into word mode.

	loadport
	mov	al,endcfg
	setport	EN0_DCFG
	pause_
	out	dx,al

;Step 8. Init EN0_STARTPG to same value as EN0_BOUNDARY

	loadport
	mov	al,SM_RSTART_PG
	setport	EN0_STARTPG
	pause_
	out	dx,al
	mov	al,SM_RSTART_PG
	setport	EN0_BOUNDARY
	pause_
	out	dx,al
	mov	al,sm_rstop_ptr
	setport	EN0_STOPPG
	pause_
	out	dx,al

;Step 9. Write 1's to all bits of EN0_ISR to clear pending interrupts.

	mov	al, 0ffh
	setport	EN0_ISR
	pause_
	out	dx,al

;Step 10. Init EN0_IMR as desired.

	mov	al, ENISR_ALL
	setport	EN0_IMR
	pause_
	out	dx,al

;Step 11. Init the Ethernet address and multicast filters.

	call	set_8390_eaddr  ; Now set the address in the 8390 chip
	call	set_8390_multi  ; Put the right stuff into 8390's multicast masks

;Step 12. Program EN_CCMD for page 1.

	loadport
	mov	al, ENC_PAGE1 + ENC_NODMA + ENC_STOP
	setport	EN_CCMD
	pause_
	out	dx,al

;Step 13. Program the Current Page Register to same value as Boundary Pointer.

	mov	al,SM_RSTART_PG
	setport	EN1_CURPAG
	pause_
	out	dx,al

;Step 14. Program EN_CCMD back to page 0, and start it.

	mov	al, ENC_NODMA + ENC_START
	setport	EN_CCMD
	pause_
	out	dx,al

	mov	al, 0			;set transmitter mode to normal.
	setport	EN0_TXCR
	pause_
	out	dx,al

	call	set_recv_isr	; Put ourselves in interrupt chain

	loadport
	setport	EN0_RXCR	; Tell it what frames to accept
	pause_
	mov al,	rxcr_bits       ; As most recently set by set_mode
	out dx,	al

	ram_enable

	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	; Report our size
	clc				; Say no error
	ret				; Back to common code

[ RETURN TO DIRECTORY ]