; Packet driver for Novell's NE1000 
; Written by:
;	Eric Henderson
;	Brigham Young University
;	
; Based on the "generic" packet driver by Russell Nelson with help
; from the western digital pd by Russell Nelson.
; 80[123]86 processor support lifted from 3com driver by permission
;	from Russell Nelson
; Receive mode support lifted from Western Digital driver
;
;   Portions (C) Copyright 1989 BYU

version	equ	4

	include	defs.asm

code	segment	word public
	assume	cs:code, ds:code

;*****************************************************************************
;
;	NE1000 controller board offsets
;	IO port definition (BASE in io_addr)
;*****************************************************************************
ADDROM  EQU	10h			; LAN Address ROM
RACK	EQU	10h			; NE1000 Port Window (?)
NERESET EQU	1fh			; Issue a read for reset
; 8390 LAN Controller (page0) register offset for read and write 
CMDR	EQU	00h			; command register for read & write
CLDA0	EQU	01h			; current local dma addr 0 for read
PSTART	EQU	01h			; page start register for write
CLDA1	EQU	02h			; current local dma addr 1 for read
PSTOP	EQU	02h			; page stop register for write
BNRY	EQU	03h			; boundary reg for rd and wr
TSR	EQU	04h			; tx status reg for rd
TPSR	EQU	04h			; tx start page start reg for wr	
NCR	EQU	05h			; number of collision reg for rd
TBCR0	EQU	05h			; tx byte count 0 reg for wr
FIFO	EQU	06h			; FIFO for rd
TBCR1	EQU	06h			; tx byte count 1 reg for wr
ISR	EQU	07h			; interrupt status reg for rd and wr
CRDA0	EQU	08h			; current remote dma address 0 for rd
RSAR0	EQU	08h			; remote start address reg 0  for wr
CRDA1	EQU	09h			; current remote dma address 1 for rd
RSAR1	EQU	09h			; remote start address reg 1 for wr
RBCR0	EQU	0Ah			; remote byte count reg 0 for wr
RBCR1	EQU	0Bh			; remote byte count reg 1 for wr
RSR	EQU	0Ch			; rx status reg for rd
RCRWD	EQU	0Ch			; rx configuration reg for wr
CNTR0	EQU	0Dh			; tally cnt 0 for frm alg err for rd
TCR	EQU	0Dh			; tx configuration reg for wr
CNTR1	EQU	0Eh			; tally cnt 1 for crc err for rd
DCR	EQU	0Eh			; data configuration reg for wr
CNTR2	EQU	0Fh			; tally cnt 2 for missed pkt for rd
IMR	EQU	0Fh			; interrupt mask reg for wr
; 8390 LAN Controller (page1) register offset for read and write 
PAR0	EQU	01h 			; physical addr reg 0 for rd and wr
PAR1	EQU	02h 			; physical addr reg 1 for rd and wr
PAR2	EQU	03h 			; physical addr reg 2 for rd and wr
PAR3	EQU	04h 			; physical addr reg 3 for rd and wr
PAR4	EQU	05h 			; physical addr reg 4 for rd and wr
PAR5	EQU	06h 			; physical addr reg 5 for rd and wr
CURR	EQU	07h			; current page reg for rd and wr
MAR0	EQU	08h			; multicast addr reg 0 fro rd and WR
MAR1	EQU	09h			; multicast addr reg 1 fro rd and WR
MAR2	EQU	0Ah			; multicast addr reg 2 fro rd and WR
MAR3	EQU	0Bh			; multicast addr reg 3 fro rd and WR
MAR4	EQU	0Ch			; multicast addr reg 4 fro rd and WR
MAR5	EQU	0Dh			; multicast addr reg 5 fro rd and WR
MAR6	EQU	0Eh			; multicast addr reg 6 fro rd and WR
MAR7	EQU	0Fh			; multicast addr reg 7 fro rd and WR

;***********************************************************************
;
;	8003 control register operations
;***********************************************************************

MSK_RESET	EQU	80h	        ; reset LAN controller
MSK_ENASH	EQU	40h		; enable PC access to shared mem
MSK_DECOD	EQU	3Fh 		; ???? memory decode bits, corresponding
					; to SA 18-13. SA 19 assumed to be 1
;***********************************************************************
;
;	8390 CMDR MASK
;***********************************************************************

MSK_STP		EQU	01h		; software reset, take 8390 off line
MSK_STA		EQU	02h		; activate the 8390 NIC
MSK_TXP		EQU	26h		; initial txing of a frm  (With DMA)
MSK_RD2		EQU	20h		; abort remote DMA
MSK_PG0		EQU	00h		; select register page 0
MSK_PG1		EQU	40h		; select register page 1
MSK_PG2		EQU	80h		; select register page 2
MSK_DMA_RD	EQU	0ah		; start DMA read
MSK_DMA_WR	EQU	12h		; start DMA write

;***********************************************************************
;
;	8390 ISR & IMR MASK
;***********************************************************************

MSK_PRX  EQU	01h		; rx with no error
MSK_PTX  EQU	02h		; tx with no error
MSK_RXE  EQU	04h		; rx with error
MSK_TXE  EQU	08h		; tx with error
MSK_OVW  EQU	10h		; overwrite warning
MSK_CNT  EQU	20h		; MSB of one of the tally counters is set
MSK_RDC  EQU	40h		; remote dma completed
MSK_RST	 EQU	80h		; reset state indicator
UnmaskByte		equ	1fh
InterruptMask		equ	0fh

;***********************************************************************
;
;	8390 DCR MASK
;***********************************************************************

MSK_WTS EQU	01h		; word transfer mode selection
MSK_BOS	EQU	02h		; byte order selection
MSK_LAS	EQU	04h		; long addr selection
MSK_BMS	EQU	08h		; burst mode selection
MSK_ARM	EQU	10h		; atuoinitialize remote
MSK_FT00 EQU	00h		; burst lrngth selection
MSK_FT01 EQU	20h		; burst lrngth selection
MSK_FT10 EQU	40h		; burst lrngth selection
MSK_FT11 EQU	60h		; burst lrngth selection

;***********************************************************************
;
;	8390 RCR MASK
;***********************************************************************

MSK_SEP EQU	01h		; save error pkts
MSK_AR 	EQU	02h		; accept runt pkt
MSK_AB 	EQU	04h		; accept broadcast 
MSK_AM 	EQU	08h		; accept multicast 
MSK_PRO	EQU	10h		; promiscuous physical
				; accept all pkt with physical adr
MSK_MON EQU	20h		; monitor mode

;***********************************************************************
;
;	8390 TCR MASK
;***********************************************************************

MSK_CRC EQU	01h		; inhibit CRC, do not append crc
MSK_LB01 EQU	06h		; encoded loopback control
MSK_ATD	EQU	08h		; auto tx disable
MSK_OFST EQU	10h		; collision offset enable 

;***********************************************************************
;
;	8390 RSR MASK
;***********************************************************************

SMK_PRX  EQU	01h		; rx without error
SMK_CRC  EQU	02h		; CRC error
SMK_FAE  EQU	04h		; frame alignment error
SMK_FO   EQU	08h		; FIFO overrun
SMK_MPA  EQU	10h		; missed pkt
SMK_PHY  EQU	20h		; physical/multicase address
SMK_DIS  EQU	40h		; receiver disable. set in monitor mode
SMK_DEF	 EQU	80h		; deferring

;***********************************************************************
;
;	8390 TSR MASK
;***********************************************************************

SMK_PTX  EQU	01h		; tx without error
SMK_DFR  EQU	02h		; non deferred tx
SMK_COL  EQU	04h		; tx collided
SMK_ABT  EQU	08h		; tx aboort because of excessive collisions
SMK_CRS  EQU	10h		; carrier sense lost
SMK_FU   EQU	20h		; FIFO underrun
SMK_CDH  EQU	40h		; collision detect heartbeat
SMK_OWC	 EQU	80h		; out of window collision

;***********************************************************************
;
;	on board memory constant definition
;***********************************************************************
; for rcv buff ring of onboard mem
START_PG EQU	26h	      		; start at page 26
STOP_PG  EQU	40h			; end at page 40 
; for tx buff of shr mem
TB_SIZE EQU	1			; number of tb buff in shr mem
TB_PGNO EQU	6			; number of pages in one tb buff


	public	int_no, io_addr
int_no		db	3,0,0,0		;must be four bytes long for get_number.
io_addr		dw	0300h,0		; I/O address for card (jumpers)
my_eaddr  	db	EADDR_LEN dup(?) ; 6 byte LAN address
m_channel 	dw 	0		; micro channel flag	
is_186		db	0		;=0 if 808[68], =1 if 80[123]86.

rd_NE	MACRO	port
	mov	DX, CS:io_addr
	add	DX, port		; DX contains address of port
	in	AL, DX			; AL contains data read from port
	ENDM

wr_NE	MACRO	port
	mov	DX, CS:io_addr
	add	DX, port		; DX contains address of port
	out	DX, AL			; AL contains data to be written to port
	ENDM

ReceiveHeaderStructure	struc
   RReceiveStatus	db	?
   RNextBuffer		db	?
   RByteCount		dw	?

   RDestinationAddress	db	EADDR_LEN dup(?)
   RSourceAddress	db	EADDR_LEN dup(?)
   RPacketLength	dw	?
   RChecksum		dw	?
   RRPacketLength	dw	?
   RTranControl		db	?
   RHPacketType		db	?
   RDestinationNet	db	4 dup(?)
   RDestinationNode	db	EADDR_LEN dup(?)
   RDestinationSocket	dw	?
ReceiveHeaderStructure	ends

ReceiveHeader	ReceiveHeaderStructure	<>

EN_RBUF_NHDR	equ	4	; Length of controller header bytes

BLUEBOOK	equ	1
IEEE8023	equ	11h
	public	driver_class, driver_type, driver_name, driver_function, parameter_list
driver_class	db	BLUEBOOK, 0	;from the packet spec
driver_type	dw	53		;Wild card matches any type
driver_name	db	'NE1000',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
int_num	dw	0	;Interrupt # to hook for post-EOI
			;processing, 0 == none,

mcast_list_bits db      0,0,0,0,0,0,0,0 ;Bit mask from last set_multicast_list
mcast_all_flag  db      0               ;Non-zero if hware should have all
					; ones in mask rather than this list.

	public	rcv_modes
rcv_modes	dw	7		;number of receive modes in our table.
		dw	0		;there is no mode 1.
		dw	rcv_mode_1
		dw	rcv_mode_2
		dw	rcv_mode_3
		dw	rcv_mode_4
		dw	rcv_mode_5
		dw	rcv_mode_6
rxcr_bits       db      MSK_AB		; Default to ours plus multicast

	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 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
; get txblk length						  
	inc	cx
	and	cl, 0feh
	cmp	CX, RUNT
	jnb	length_ok
	mov	cx, RUNT
length_ok:
	cmp	cx, GIANT
	jbe	length1_ok
	mov	dh, NO_SPACE
	stc	
	jmp	count_out_err
length1_ok:
;new stuff
	mov	AX, CX
	wr_NE	TBCR0			; Transmit byte count
	mov	al, ah
	wr_NE	TBCR1
	mov	al, 0
	wr_NE	RSAR0
	mov	al, 20h
	wr_NE	RSAR1
	mov	ax, cx
	wr_NE	RBCR0			; Remote byte count
	mov	al, ah
	wr_NE	RBCR1


; Clear out DMA complete interrupt
	mov	al, MSK_PG0		
	wr_NE	CMDR
	mov	al, 40h
	wr_NE	ISR

	mov	al, MSK_DMA_WR
	wr_NE	CMDR

	mov	DX, CS:io_addr
	add	DX, RACK		; DX has address NE1000 Port window (?)

	cmp	is_186,0	; Can we use rep outsb?
	je	out86		; no - have to do it slowly.
	db	0f3h, 06eh	;masm 4.0 doesn't grok "rep outsb"
	jmp	short ocnteven
out86:
	test	si,1		; (buf & 1) ?
	jz	obufeven	; no
	lodsb			; al = *si++;
	out	dx,al		; out(dx,al);
	dec	cx		; cx--;
obufeven:
	mov	di,cx		; save for later test
	shr	cx,1		; cx = cnt >> 1; (convert to word count)
; Do the bulk of the buffer, a word at a time
	jcxz	onobuf		; if(cx != 0){
xb:	lodsw			; do { ax = *si++; (si is word pointer)
	out	dx,al		; out(dx,lowbyte(ax));
	mov	al,ah
	out	dx,al		; out(dx,hibyte(ax));
	loop	xb		; } while(--cx != 0); }
; now check for odd trailing byte
onobuf:	shr	di,1		; if (di & 1)
	jnc	ocnteven
	lodsb			;   out(dx,*si++);
	out	dx,al
ocnteven:
	mov	cx, 1000h		; Prevent infinite loop
WaitForDMAComplete:
	rd_NE	ISR
	test	al, 40h
	jnz	DMAComplete
	loop	WaitForDMAComplete

DMAComplete:
	mov	al, MSK_TXP
	wr_NE	CMDR
	clc
exit_now:
	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
	mov	si, offset cs:my_eaddr
	cld			; Make sure string mode is right
	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:
;enter with ds:si -> Ethernet address, CX = length of address.
;exit with nc if okay, or cy, dh=error if any errors.
;This proc will set the first CX bytes of the ethernet address, leaving
; the rest unchanged.
	assume	ds:nothing

	cmp	cx, EADDR_LEN
	jbe	set1
	mov	dh, BAD_ADDRESS
	stc
	ret
set1:
	push	es
	push	di
	mov	di, offset my_eaddr
	mov	ax, cs
	mov	es, ax

	mov	al, 61h
	wr_NE	CMDR
	mov	DX, CS:io_addr
	add	DX, PAR0		; DX has address 8390 Phys Address Reg
AddressToChip1:
	lodsb
	out	dx, al
	mov	es:[di], al
	inc	di
	inc	dx
	nop
	nop
	nop	
	nop
	loop	AddressToChip1
	pop	di
	pop	es

	mov 	al, 21h
	wr_NE 	CMDR
	clc
	ret


; Routines to set address filtering modes in the DS8390
;	This was lifted from R. Clements' WD PD
rcv_mode_1:     ; Turn off receiver
	mov al,	MSK_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,	MSK_AB     ; Set four ours plus broadcasts
	jmp short rcv_mode_set
rcv_mode_4:     ; Mode 3 plus selected multicast packets
	mov al,	MSK_AB+MSK_AM ; 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,	MSK_AB+MSK_AM; 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,	MSK_AB+MSK_AM+MSK_PRO
	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
	WR_NE	RCRWD
	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
;	This was lifted from R. Clements' WD PD
set_8390_multi:
	
	mov	al,MSK_RD2+MSK_PG1
	WR_NE	CMDR 
	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	DX, CS:io_addr
	add	DX, MAR0
	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
	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:
	mov	al, MSK_RD2+MSK_PG0
	WR_NE	CMDR
	sti			; OK for interrupts now
	ret

	public	terminate
terminate:
	ret

	public	reset_interface
reset_interface:
;reset the interface.
	assume	ds:code
	mov	al, MSK_STP + MSK_RD2	
	wr_NE	CMDR
	mov al,	0ffh		; Clear all pending interrupts
	wr_NE	ISR
	xor al,	al		; Turn off all enables
	wr_NE	IMR
	rd_NE   NERESET		; Hard reset NE1000
	wr_NE   NERESET
	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

; read irq status register

rd_isr:
	rd_NE	ISR			; read isr into AL
	and	AL, 3Fh			; check bit0-bit5, if all 1's no irq
	cmp	AL, 0			; if any irq
	jne	tst_ovw			; some irq
	ret				; no more irq, exit

; process OVW	(OVW = 1)       
;    may report error here
tst_ovw:
	test	AL, MSK_OVW		; if OVW irq
	jnz	prcs_ov			; OVW (OVW = 1) 
	jmp	test_rx			; no OVW (OVW = 0) 

; **************************************************************
; follow the DP8390 datasheet addendum to handle the buff ring overflow
; 5/1/90 Use Carl Beame's fix
prcs_ov:
; 1. issue a STOP mode command
	mov	AL, MSK_STP + MSK_RD2
	wr_NE	CMDR

	mov	al, MSK_RD2 + MSK_PG1	;Carl Beame's fix
	wr_NE	CMDR
	rd_NE	CURR
	mov	bl, al
	mov	al, MSK_RD2 + MSK_PG0

; 6. remove one packet from the ring
	rd_NE	BNRY			; BNRY in AL
	add	AL, 1			; start page of frm in AL
	cmp	AL, STOP_PG		; check boundary
        jne	get1
	mov	AL, START_PG		
; ring not empty
get1:
	cmp	al, bl			;Check if buffer empty (Beame's fix)
	je	recv_ovr_empty		;Yes? Don't receive anything

	mov	BH, AL			; BX has the rx_frm pointer

	mov	al, SIZE ReceiveHeader
	wr_NE	RBCR0
	xor	al, al
	jmp	$+2
	wr_NE	RBCR1
	jmp	$+2
	wr_NE	RSAR0
	mov	al, bh
	wr_NE	RSAR1
	mov	al, MSK_DMA_RD
	wr_NE	CMDR

	mov	DX, CS:io_addr
	add	DX, RACK		; DX has address NE1000 Port window (?)
	mov	di, OFFSET ReceiveHeader
	mov	ax, cs
	mov	es, ax
	mov	cx, SIZE ReceiveHeader
	
Receive1:
	in	al, dx
	stosb
	loop	Receive1

SkipReceive1:
	mov	bx, offset ReceiveHeader
	mov	AL, CS:[BX]		; AL has the status byte
	test	AL, SMK_PRX		; if rx good
	jz	fd_bnr			; rx error, drop frm by forward bnry

; good frm, call _rcv_frm
	call 	_rcv_frm   		

fd_bnr:					;drop frm by forward BNRY
	mov	al, cs:ReceiveHeader.RNextBuffer	; al = next pointer 
	sub	AL, 1			; new BNRY in AL
	cmp	AL, START_PG		; check boundary
	jae	wrbnr			; unsigned arithmetic
	mov	AL, STOP_PG		;
	dec	AL			;
wrbnr:
	wr_NE	BNRY
	jmp	$+2			;
; 2. clear the remote byte count registers (RBCR0,RBCR1)
recv_ovr_empty:
	xor	AL, AL
	wr_NE	RBCR0
	jmp	$+2			;
	wr_NE	RBCR1
	jmp	$+2			;
; 3. poll the ISR for the RST bit
plisr:
	rd_NE	ISR
	jmp	$+2			;
	test	AL, MSK_RST
	jz	plisr		; keep polling until the RST bit set
; 4. place the NIC in loopback mode (mode 1 or 2) by writing 02 or 04 to TCR
	mov	AL, 02h		; put it in mode 2 (internal loopback)
	wr_NE	TCR
	jmp	$+2			;
; 5. issue start mode command
	mov	AL, MSK_STA + MSK_RD2
	wr_NE	CMDR
	jmp	$+2			;
; 7. out from loopback mode by writing 00 to TCR
	xor	AL, AL
	wr_NE	TCR		; normal operation configuration
	jmp	$+2			;
; clear OVW in ISR		      
	mov	AL, MSK_OVW 
	wr_NE	ISR			; clear OVW
	call	count_in_err		; increment overflow counter
	jmp	rd_isr			; back to the top


; end of the modification 
; *****************************************************
;	
;process PRX and RXE
;
test_rx:	    
	test  	AL, MSK_PRX	
	jnz	prcs_rx	     		; PRX = 1
	test 	AL, MSK_RXE		
	jnz	prcs_rxe		; RXE = 1
	jmp	test_tx

prcs_rxe:
	call	count_in_err
prcs_rx:
	mov	AL, MSK_PG1 + MSK_RD2	; read CURR reg
	wr_NE	CMDR
	jmp	$+2
	rd_NE	CURR
	jmp	$+2
	mov	BL, AL			; CURR in BL 
	mov	AL, MSK_PG0 + MSK_RD2	; read BNRY reg
	wr_NE  CMDR
	jmp	$+2
	rd_NE	BNRY			; BNRY in AL
	add	AL, 1			; start page of frm in AL
	cmp	AL, STOP_PG	 	; check boundary
	jne	go_cmp
	mov	AL, START_PG 		; 		
go_cmp:
	cmp	AL, BL			
	je	end_rx			; buff ring empty

; Ring Not Empty
	wr_NE	RSAR1
	jmp	$+2
	xor	al, al
	jmp	$+2
	wr_NE	RBCR1
	jmp	$+2
	wr_NE	RSAR0
	mov	al, SIZE ReceiveHeader
	wr_NE	RBCR0
	mov	al, MSK_DMA_RD
	wr_NE	CMDR

	mov	DX, CS:io_addr
	add	DX, RACK		; DX has address NE1000 Port window (?)
	mov	di, OFFSET ReceiveHeader
	mov	ax, cs
	mov	es, ax
	mov	cx, SIZE ReceiveHeader

Receive2:
	in	al, dx
	stosb
	loop	Receive2

SkipReceive2:
	mov	bx, offset ReceiveHeader
	mov	AL, CS:[BX]		; AL has the status byte
	test	AL, SMK_PRX		; if rx good
	jz	fd_bnry			; rx error, drop frm by forward bnry

; good frm, call _rcv_frm
   	call 	_rcv_frm   		

fd_bnry:				; drop frm by forward BNRY
	mov	al, CS:ReceiveHeader.RNextBuffer	; al = next pointer 
	sub	AL, 1			; new BNRY in AL
	cmp	AL, START_PG		; check boundary
	jae	wrbnry			; unsigned arithmetic
	mov	AL, STOP_PG		;
	dec	AL			;
wrbnry:
	wr_NE	BNRY
	jmp	prcs_rx	 
       
; clear PRX, RXE, OVW in ISR		      
end_rx:
	mov	AL, MSK_OVW + MSK_RXE + MSK_PRX
	wr_NE	ISR			; clear OVW, RXE, PRX
	jmp	rd_isr			; back to the top

;process PTX and TXE
test_tx:
	test  	AL, MSK_PTX	
	jnz	prc_ptx	     		; PTX = 1
	test 	AL, MSK_TXE		
	jnz	prc_txe			; TXE = 1
	jmp	test_cnt
      

; process tx good, update txok, lostcrs, collsn
prc_ptx:				; tx good 
	rd_NE	TSR
	test	AL, SMK_CRS		; is crs set in TSR
	jz	nocrs			; no		       
nocrs:	
	rd_NE	NCR			; read number of collision in AL
	mov	AL, MSK_PTX
	wr_NE	ISR			; clear PTX
	jmp	rd_isr

; process tx error, update .txbad .underrun
prc_txe:				; tx bad
	call	count_out_err	
	rd_NE	TSR
	test	AL, SMK_FU		; it fu set in TSR
	jz	nofu			; no		       
nofu:								  
    	mov	AL, MSK_TXE
	wr_NE	ISR			; clear PTX
	jmp	rd_isr

; process counter overflow, update .algerr .crcerr .???(missed pkt)
test_cnt:
	test  	AL, MSK_CNT	
	jnz	prc_cnt			; yes, process cnt
	jmp	rd_isr	     		; no CNT irq, back to the top
; process CNT			
prc_cnt:
	mov	AL, MSK_CNT
	wr_NE	ISR			; clear CNT
	jmp	rd_isr			; back to the top

; End of RECV

_rcv_frm:
	
; read byte count 
	mov 	cx, cs:ReceiveHeader.RByteCount	; Extract size of frame
	sub	CX, EN_RBUF_NHDR    		; 4 control bytes
	cmp	CX, GIANT
	jbe	rxlen_ok
	mov	CX, GIANT
rxlen_ok:
	mov	AX, cs
	mov	ES, AX
	mov 	di, offset cs:ReceiveHeader.RPacketLength

	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
					; 	CX = packet length
					;    ES:DI = packet type

	pop	ds			; RX page pointer in ds now
	assume	ds:nothing
	pop	cx

	cld				; Copies below are forward
	mov ax,	es			; Did recv_find give us a null pointer?
	or  ax,	di			; ..
	je	no_buff			; If null, don't copy the data	
has_buf:  

;Tell DMA to copy the whole packet for us
	mov	ax, EN_RBUF_NHDR	
	wr_NE	RSAR0		; Don't copy  4 8390 control bytes
	mov	ax, cx		; CX has byte count
	wr_NE	RBCR0		; LSB first
	mov	al, ah
	wr_NE	RBCR1		; Now MSB

	mov	al, MSK_DMA_RD  ; Issue DMA read command
	wr_NE	CMDR

; copy from NE1000 on board memory using the window RACK
; use IN and stosb to do the copy, IN -> AX -> ES:DI (CX has byte count)	       			 

copynow:
	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	DX, CS:io_addr
	add	DX, RACK		; DX has address NE1000 Port window (?)

	cmp	is_186,0	; Can we use rep insb?
	je	in86		; no - have to do it slowly.
	db	0f3h, 06ch	;masm 4.0 doesn't grok "rep insb"
	jmp	short icnteven
in86:
; If buffer doesn't begin on a word boundary, get the first byte
	test	di,1	; if(buf & 1){
	jz	ibufeven ;
	in	al,dx	; al = in(dx);
	stosb		; *di++ = al
	dec	cx	; cx--;
ibufeven:
	mov	si,cx	; size = cx;
	shr	cx,1	; cx = cnt >> 1; (convert to word count)
; Do the bulk of the buffer, a word at a time
	jcxz	inobuf	; if(cx != 0){
rb:	in	al,dx	; do { al = in(dx);
	mov	ah,al
	in	al,dx	; ah = in(dx);
	xchg	al,ah
	stosw		; *si++ = ax; (di is word pointer)
	loop	rb	; } while(--cx != 0);
; now check for odd trailing byte
inobuf:	shr	si,1
	jnc	icnteven
	in	al,dx
	stosb		; *di++ = al
icnteven:

call_rc:
	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
	jmp	short rcv_ret

; no system buff availble to hold rx frm
no_buff:
rcv_ret:
	push	cs		; Put ds back in code space
	pop	ds		; ..
	assume	ds:code
	ret



	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: NE1000 [-n] [-d] [-w] <packet_int_no> <int_no> <io_addr>",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for NE1000, version "
		db	'0'+majver,".",'0'+version,CR,LF
		db	"Portions Copyright 1989, Eric Henderson, BYU",CR,LF,'$'
int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'
no_board_msg:
	db	"NE1000 apparently not present at this IO address.",CR,LF,'$'
HardwareFailure:
	db	"The NE1000 is not responding.",CR,LF,'$'
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

;enter with dx -> name of word, di -> dword to print.
	extrn	print_number: near

	public	parse_args
parse_args:
;exit with nc if all went well, cy otherwise.
	mov	di,offset int_no
	call	get_number
	mov	di,offset io_addr
	call	get_number
	clc
	ret

	extrn	etopen_diagn: byte

BoardNotResponding:
	mov	dx, offset HardwareFailure
	mov	etopen_diagn, 35
	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:
;if all is okay,

	; reset NE1000 board 
	rd_NE	NERESET
	wr_NE	NERESET
	mov	al, 21h
	wr_NE	CMDR

	; Test to see if we're OK
	rd_NE	CMDR
	cmp	al, 21h
	je	WeBeOK
	jmp	BoardNotResponding

WeBeOK:
	mov	al, 0
	wr_NE	DCR		
	mov	al, 60h
	wr_NE	TPSR		
	mov	al, 0
	wr_NE	TCR		
	mov	al, 20h
	wr_NE	RCRWD		
	mov	al, 4
	wr_NE	PSTART		
	mov	al, 4
	wr_NE	BNRY		
	mov	al, 0ffh
	wr_NE	PSTOP		
	mov	al, 3ch
	wr_NE	TBCR0		
	mov	al, 0
	wr_NE	TBCR1		
	mov	al, 0ffh
	wr_NE	ISR		
	mov	al, 61h
	wr_NE	CMDR		
	mov	al, 4
	wr_NE	CURR		
	mov	al, 22h
	wr_NE	CMDR		

	; Test to see if we're still OK
	rd_NE	CMDR
	cmp	al, 22h
	je	WeBeStillOK
	jmp	BoardNotResponding

WeBeStillOK:

	; set up my_eaddr from addr ROM 
	mov	al, 21h
	wr_NE	CMDR
	mov	al, EADDR_LEN
	wr_NE	RBCR0
	mov	al, 0
	wr_NE	RBCR1
	wr_NE	RSAR0
	wr_NE	RSAR1
	mov	al, MSK_DMA_RD
	wr_NE	CMDR

	mov	DX, CS:io_addr
	add	DX, RACK		; DX has address NE1000 Port window (?)
	mov	di, OFFSET my_eaddr
	mov	ax, cs
	mov	es, ax
	mov	cx, EADDR_LEN
GetEnetAddress:
	in	al, dx
	stosb
	loop	GetEnetAddress

	mov	ax, cs
	mov	ds, ax
	mov	si, OFFSET my_eaddr
	mov	cx, EADDR_LEN
	call	set_address

	mov 	al, 21h
	wr_NE 	CMDR

	mov	al, START_PG
	jmp	$+2
	wr_NE	PSTART
	jmp	$+2
	wr_NE	BNRY
	mov	al, STOP_PG
	jmp	$+2
	wr_NE	PSTOP

	mov	al, MSK_PG1+MSK_RD2+MSK_STP
	jmp	$+2
	wr_NE	CMDR
	mov	al, START_PG + 1
	jmp	$+2
	wr_NE	CURR

; 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
	mov dx, cs:io_addr	; Base of multicast filter locations
	add dx, MAR0		; ..
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	; ..	

	mov	al, 21h
	jmp	$+2
	wr_NE	CMDR
	mov 	al, 48h
	jmp	$+2
	wr_NE	DCR
	mov	al, 0
	jmp	$+2
	wr_NE	TCR
	mov	al, MSK_RXE
	jmp	$+2
	wr_NE	RCRWD
	mov	al, 0ffh
	jmp	$+2
	wr_NE	ISR
	mov	al, UnmaskByte
	jmp	$+2
	wr_NE	InterruptMask

;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.
	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:

	call	set_recv_isr	; Put ourselves in interrupt chain
	
	mov	al, 22h
	jmp	$+2
	wr_NE	CMDR	

	mov	al, int_no		; Get board's interrupt vector
	add	al, 8
	cmp	al, 8+8			; Is it a slave 8259 interrupt?
	jb	set_int_num		; No.
	add	al, 70h - 8 - 8		; Map it to the real interrupt.
set_int_num:
	xor	ah, ah			; Clear high byte
	mov	int_num, ax		; Set parameter_list int num.

	mov	dx,offset end_resident
	clc
	ret

	public	print_parameters
print_parameters:
;echo our command-line parameters
	mov	di,offset int_no
	mov	dx,offset int_no_name
	call	print_number
	mov	di,offset io_addr
	mov	dx,offset io_addr_name
	call	print_number
	ret

code	ends

	end
