version	equ	3
	page	,132
	include	defs.asm

;/* PC/FTP Packet Driver source, conforming to version 1.05 of the spec,
;*  for the NE2000 interface card.
;*  Robert C Clements, K1BC, 14 February, 1989
;*  Portions (C) Copyright 1988, 1989 Robert C Clements
;*  Modified from 3com503 driver by D.J.Horne
;*
;  Copyright, 1988, 1989, Russell Nelson

;   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.

;* Change history:
;*  Updated to driver spec version 1.08 Feb. 17, 1989 by Russell Nelson.
;*  Changes 27 Jul 89 by Bob Clements (/Rcc)
;*	Added Thick versus Thin Ethernet switch  27 Jul 89 by Bob Clements (/Rcc)
;*	Added call to memory_test.
;*	Added rcv_mode logic.  Started, but didn't finish, multicast logic. 
;*      Fixed get_address to return current, not PROM, address.
;*      Minor races fixed.
;*  Changes 19 Oct 89, Dave Horne
;*	Modified for NE2000, use i/o instead of shared memory,
;*	remove thick/thin logic, remove gate array logic

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

; Stuff specific to the NE2000 Ethernet controller board
; WD version in C by Bob Clements, K1BC, May 1988 for the KA9Q TCP/IP package
; 3Com version based on WD8003E version in .ASM, also by Bob Clements, dated
;  19 August 1988.  The WD and 3Com cards both use the National DS8390.
; NE2000 based on 3COM503 version.

; Symbol prefix "EN" is for Ethernet, National chip
; Symbol prefix "NE" is for NE2000 register(s)

; The EN registers - the DS8390 chip registers
; These appear at Base+0 through Base+0F
; There are two (really 3) pages of registers in the chip. You select
; which page you want, then address them at offsets 00-0F from base.
; The chip command register (EN_CCMD) appears in both pages.

EN_CCMD		equ	000h	; Chip's command register

; Page 0

EN0_STARTPG	equ	001h	; Starting page of ring bfr
EN0_STOPPG	equ	002h	; Ending page +1 of ring bfr
EN0_BOUNDARY	equ	003h	; Boundary page of ring bfr
EN0_TSR		equ	004h	; Transmit status reg
EN0_TPSR	equ	004h	; Transmit starting page
EN0_TCNTLO	equ	005h	; Low  byte of tx byte count
EN0_TCNTHI	equ	006h	; High byte of tx byte count
EN0_ISR		equ	007h	; Interrupt status reg
EN0_RSARLO	equ	008h	; Remote start address reg 0
EN0_RSARHI	equ	009h	; Remote start address reg 1
EN0_RCNTLO	equ	00ah	; Remote byte count reg
EN0_RCNTHI	equ	00bh	; Remote byte count reg
EN0_RXCR	equ	00ch	; RX control reg
EN0_TXCR	equ	00dh	; TX control reg
EN0_COUNTER0	equ	00dh	; Rcv alignment error counter
EN0_DCFG	equ	00eh	; Data configuration reg
EN0_COUNTER1	equ	00eh	; Rcv CRC error counter
EN0_IMR		equ	00fh	; Interrupt mask reg
EN0_COUNTER2	equ	00fh	; Rcv missed frame error counter

; Page 1

EN1_PHYS	equ	001h	; This board's physical enet addr
EN1_CURPAG	equ	007h	; Current memory page
EN1_MULT	equ	008h	; Multicast filter mask array (8 bytes)

; Board regs

NE_DATAPORT	equ	10h
NE_OTHERPORT	equ	1fh

; 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_RREAD	equ	008h	; remote read
ENC_RWRITE	equ	010h	; remote write
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 (no packets rcvd)
ENRXCR_PROMP	equ	010h	; Promiscuous physical addresses 
ENRXCR_MULTI	equ	008h	; Multicast (if pass filter)
ENRXCR_BCST	equ	004h	; Accept broadcasts
ENRXCR_BAD	equ	003h	; Accept runts and bad CRC frames

; Commands for TX control reg
ENTXCR_LOOP	equ	002h	; Set loopback mode

; Bits in EN0_DCFG - Data config register
ENDCFG_BM8	equ	049h	; Set burst mode, 8 deep FIFO, words

; 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_RDC	equ	040h	; remote dma complete
ENISR_RESET	equ	080h	; Reset completed
ENISR_ALL	equ	03fh	; 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_PTX	equ	001h	; Packet transmitted without error
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	040h	; First page of TX buffer
SM_RSTART_PG	equ	046h	; Starting page of RX ring
SM_RSTOP_PG	equ	080h	; Last page +1 of RX ring

; Description of header of each packet in receive area of memory

EN_RBUF_STAT	equ	0	; Received frame status
EN_RBUF_NXT_PG	equ	1	; Page after this frame
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 NE2000 parameter definitions

pause_	macro
	jmp	$+2
endm

longpause macro
	push	cx
	mov	cx,0
	loop	$
	pop	cx
endm

; The following two values may be overridden from the command line.
; If they are omitted from the command line, these defaults are used.
; The shared memory base is set by a jumper.  We read it from the
; card and set up accordingly.

	public	int_no, io_addr
int_no		db	2,0,0,0		; Interrupt level
io_addr		dw	0300h,0		; I/O address for card (jumpers)

	public	driver_class, driver_type, driver_name, driver_function, parameter_list
driver_class	db	1		;from the packet spec
driver_type	db	54		;from the packet spec
driver_name	db	'NE2000',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,

rxcr_bits       db      ENRXCR_BCST     ; Default to ours plus multicast


	public	card_hw_addr, curr_hw_addr, mcast_list_bits, mcast_all_flag
card_hw_addr	db	0,0,0,0,0,0	;Physical ethernet address
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.
is_186		db	0
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	18		; 2 ids @6 + protocol, + 4byte header
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



; send_pkt: - The Transmit Frame routine

	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
	loadport		; Point at chip command register
	setport EN_CCMD		; ..
	pause_
	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,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
	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
	loadport
	mov	cx,0
	setport	EN0_ISR
	in	al,dx
tx_check_rdc:
	test	al,ENISR_RDC	; dma done ???
	jnz	tx_start
	loop	tx_check_rdc
	jmp	tx_no_rdc
tx_start:
	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
	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
	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

movemem:
;does the same thing as "rep movsb", only 50% faster.
;moves words instead of bytes, and handles the case of both addresses odd
;efficiently.  There is no way to handle one address odd efficiently.
;This routine always aligns the source address in the hopes that the
;destination address will also get aligned.  This is from Phil Karn's
;code from ec.c, a part of his NET package.  I bummed a few instructions
;out.
	jcxz	movemem_cnte		; If zero, we're done already.
	test	si,1			; Does source start on odd byte?
	jz	movemem_adre		; Go if not
	movsb				; Yes, move the first byte
	dec	cx			; Count that byte
movemem_adre:
	shr	cx,1			; convert to word count
	rep	movsw			; Move the bulk as words
	jnc	movemem_cnte		; Go if the count was even
	movsb				; Move leftover last byte
movemem_cnte:
	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

;
;get the board data. This is (16) bytes starting at remote
;dma address 0. Put it in a buffer called board_data.

get_board_data:
	mov	cx,10h		; get 16 bytes,
	push	ds
	pop	es		; set es to ds
	mov	di,offset board_data
	mov	ax,0		; from address 0
	call	sp_block_input
	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:
	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
	pause_
set_8390_1:
	lodsb
	out	dx,al
	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_chip

reset_chip:
	assume ds:nothing
	loadport		; Base of I/O regs
	setport	NE_OTHERPORT
	in	al,dx
	longpause
	out	dx,al		; should set command 21, 80
	longpause
	setport	EN_CCMD		; Chip command reg
	pause_
	mov al,	ENC_STOP+ENC_NODMA
	out dx,	al		; Stop the DS8390
	setport EN0_ISR
	pause_
	mov	cx,0
reset_chip_loop:
	in	al,dx		; get isr
	and	al,ENISR_RESET
	jnz	reset_chip_done
	jmp	reset_chip_loop
reset_chip_done:
	ret

	public	terminate
terminate:
	ret

	public	reset_interface
reset_interface:
	assume ds:code
	call	reset_chip
	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

;
;	Special case Block input routine. Used on extra memory
;	space for board ID etc. DMA count is set X2,
;	CX = byte count, es:si = buffer location, ax = buffer address

sp_block_input:
	push	ax		; save buffer address
	loadport
	setport EN_CCMD
	pause_
	mov	al,ENC_NODMA+ENC_STOP
	out	dx,al		; stop & clear the chip
	setport	EN0_RCNTLO	; remote byte count 0
	pause_
	mov	ax,cx
	add	ax,ax	
	out	dx,al
	setport	EN0_RCNTHI
	pause_
	mov	al,ah
	out	dx,al
	pop	ax		; get our page back
	setport	EN0_RSARLO
	pause_
	out	dx,al		; set as hi address
	setport	EN0_RSARHI
	pause_
	mov	al,ah
	out	dx,al
	setport EN_CCMD
	pause_
	mov	al,ENC_RREAD+ENC_START	; read and start
	out	dx,al
	setport	NE_DATAPORT
	pause_
	jmp	read_loop
;
;	Block input routine
;	CX = byte count, es:si = buffer location, ax = buffer address

	public	block_input
block_input:
	push	ax		; save buffer address
	loadport
	setport EN_CCMD
	pause_
	mov	al,ENC_NODMA+ENC_PAGE0+ENC_START
	out	dx,al
	setport	EN0_RCNTLO	; remote byte count 0
	pause_
	mov	al,cl	
	out	dx,al
	setport	EN0_RCNTHI
	pause_
	mov	al,ch
	out	dx,al
	pop	ax		; get our page back
	setport	EN0_RSARLO
	pause_
	out	dx,al		; set as hi address
	setport	EN0_RSARHI
	pause_
	mov	al,ah
	out	dx,al
	setport EN_CCMD
	pause_
	mov	al,ENC_RREAD+ENC_START	; read and start
	out	dx,al
	setport	NE_DATAPORT
	pause_
	cmp	byte ptr is_186,0
	jnz	read_186
read_loop:
	in	al,dx		; get a byte
	stosb			; save it
	loop	read_loop
	ret
read_186:
	inc	cx		; make even
	shr	cx,1		; word count
	db	0f3h, 06dh	;masm 4.0 doesn't grok "rep insw"
	ret
;
;	Block output routine
;	CX = byte count, ds:si = buffer location, ax = buffer address

block_output:
	assume	ds:nothing
	push	ax		; save buffer address
	inc	cx		; make even
	and	cx,0fffeh
	loadport
	setport EN_CCMD
	pause_
	mov	al,ENC_NODMA+ENC_START
	out	dx,al		; stop & clear the chip
	setport	EN0_RCNTLO	; remote byte count 0
	pause_
	mov	al,cl	
	out	dx,al
	setport	EN0_RCNTHI
	pause_
	mov	al,ch
	out	dx,al
	pop	ax		; get our page back
	setport	EN0_RSARLO
	pause_
	out	dx,al		; set as lo address
	setport	EN0_RSARHI
	pause_
	mov	al,ah
	out	dx,al
	setport EN_CCMD
	pause_
	mov	al,ENC_RWRITE+ENC_START	; write and start
	out	dx,al
	setport	NE_DATAPORT
	pause_
	cmp	byte ptr is_186,0
	jnz	write_186
write_loop:
	lodsb			; get a byte
	out	dx,al		; save it
	loop	write_loop
	ret
write_186:
	shr	cx,1		; word count
	db	0f3h, 06fh	;masm 4.0 doesn't grok "rep outsw"
	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.
;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 card's I/O port base
	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
	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_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

	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,ENPS_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?
	jge	rcv_ovr_nwr2
	mov al,	SM_RSTOP_PG-1	; Yes, back to end of ring
rcv_ovr_nwr2:
	loadport		; Point at boundary reg
	setport	EN0_BOUNDARY	; ..
	pause_
	out dx,	al		; Set the boundary
rcv_ovr_empty:
	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	; ..
	jnz	rcv_ovr_rst	; Go if so
	dec	cx		; Loop til reset, or til timeout
	jnz	rcv_ovr_rst_loop
rcv_ovr_rst:
	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	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_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	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,ENPS_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_PG-1	; Yes
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 Interrupt Status Reg
 	setport	EN0_ISR		; ..
	pause_
	mov al,	ENISR_RX+ENISR_RX_ERR+ENISR_OVER
	out dx,	al		; Clear those requests
	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:
; 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
	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
	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


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


	public	usage_msg
usage_msg	db	"usage: NE2000 <packet_int_no> <int_level> <io_addr>",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for Novell NE2000, version ",'0'+majver,".",'0'+version,CR,LF
		db	"Portions Copyright 1989, Robert C. Clements, K1BC",CR,LF,'$'

cfg_err_msg:
	db	"NE2000 Configuration failed. Check parameters.",CR,LF,'$'
int_no_name:
	db	"Interrupt number ",'$'
io_addr_name:
	db	"I/O port ",'$'
using_186_msg	db	"Using 80[123]86 I/O instructions.",CR,LF,'$'

	extrn	set_recv_isr: near

;enter with si -> argument string, di -> word to store.
;if there is no number, don't change the number.
	extrn	get_number: near

	public	parse_args
parse_args:
;exit with nc if all went well, cy otherwise.
	mov di,	offset int_no		; May override interrupt channel
	mov bx,	offset int_no_name	; Message for it
	call	get_number
	mov di,	offset io_addr		; May override I/O address
	mov bx,	offset io_addr_name	; Message for it
	call	get_number
;	mov di,	offset mem_base		; Not movable in 3C503
;	mov bx,	offset mem_base_name	; Message for it
;	call	get_number		; Must get from jumpers.
	clc
	ret


cfg_error:
	mov	dx,offset cfg_err_msg
error:
	mov	ah,9		; Type the msg
	int	21h
	stc			; Indicate error
	ret			; Return to common code

; Called once to initialize the NE2000 card

	extrn	memory_test: near
	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:

; Now, initialize the DS8390 Ethernet Controller chip
ini_8390:
	call	reset_chip
	loadport
	setport	EN0_DCFG	; Configure the fifo organization
	pause_
	mov al,	ENDCFG_BM8	; Fifo threshold = 8 bytes
	out dx,	al
	setport	EN_CCMD		; DS8390 chip's command register
	pause_
	mov al,	ENC_NODMA+ENC_PAGE0+ENC_STOP
	out dx,	al		; Switch to page zero

	setport	EN0_TXCR	; Set transmitter mode to normal
	pause_
	xor al,	al
	out dx,	al
	setport	EN0_RXCR	; Set receiver to monitor mode
	pause_
	mov al,	ENRXCR_MON
	out dx,	al


; Set up control of shared memory, buffer ring, etc.

	setport	EN0_STARTPG	; Set receiver's first buffer page
	pause_
	mov al,	SM_RSTART_PG
	out dx,	al

	setport	EN0_STOPPG	;  and receiver's last buffer page + 1
	pause_
	mov al,	SM_RSTOP_PG
	out dx,	al

	setport	EN0_BOUNDARY	; Set initial "last page we have emptied"
	pause_
	mov al,	SM_RSTOP_PG	; (WD doc says set to RSTART_PG)
	dec	al		; (3Com doc says set to RSTOP_PG-1 ?)
;				; (and 3Com handling of BOUNDARY is
;				;  different throughout.)
	out dx,	al		; (Check out why WD and 3Com disagree)
;

	setport	EN0_IMR		; Clear all interrupt enable flags
	pause_
	xor al,	al
	out dx,	al

	setport	EN0_ISR		; Clear all interrupt assertion flags
	pause_
	mov al,	0ffh
	out dx,	al

	setport	EN_CCMD
	pause_
	mov al,	ENC_NODMA+ENC_PAGE1+ENC_STOP
	out	dx,al
	
	setport	EN1_CURPAG
	pause_
	mov al,	SM_RSTART_PG
	out dx,	al
	
	setport	EN_CCMD
	pause_
	mov al,	ENC_NODMA+ENC_PAGE0+ENC_START
	out	dx,al
	
	call	get_board_data	; read board data

	push    ds              ; Copy from card's address to current address
	pop     es

	mov si, offset board_data	; address is at start
	mov di, offset curr_hw_addr
	mov cx, EADDR_LEN       ; Copy one address length
	rep     movsb           ; ..
	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
	loadport		; Base of I/O regs
	setport	EN_CCMD		; Chip command register
	pause_
	mov al,	ENC_NODMA+ENC_PAGE0+ENC_STOP
	out dx,	al		; Back to page zero
	setport	EN0_RCNTLO	; Clear the byte count registers
	pause_
	xor al,	al		; ..
	out dx,	al
	setport	EN0_RCNTHI
	pause_
	out dx,	al		; Clear high byte, too
	setport	EN0_IMR		; Clear all interrupt enable flags
	pause_
	xor al,	al
	out dx,	al
	setport	EN0_ISR		; Clear all interrupt assertion flags
	pause_
	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
	pause_
	mov al,	ENC_START+ENC_NODMA
	out dx,	al		; interrupt be enabled
	setport	EN0_RXCR	; Tell it what frames to accept
	pause_
	mov al,	rxcr_bits       ; As most recently set by set_mode
	out dx,	al
	setport	EN0_IMR		; Tell card it can cause these interrupts
	pause_
	mov al,	ENISR_ALL
	out dx,	al
done:	mov dx,	offset end_resident	; Report our size
	clc				; Say no error
	ret				; Back to common code




code	ends

	end
