version	equ	3

	include	defs.asm

;/* PC/FTP Packet Driver source, conforming to version 1.05 of the spec,
;*  for the 3-Com 3C503 interface card.
;*  Robert C Clements, K1BC, 14 February, 1989
;*  Portions (C) Copyright 1988, 1989 Robert C Clements
;*
;  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.

comment /
From: "James A. Harvey" <IJAH400@indyvax.iupui.edu>
Subject: Patches for 6.x packet drivers; lockup problem fixed!

Now for the best part, the lockup problem fix.  I think this may be one that
I keep hearing about that for most people the machine locks up for a minute
on startup, but then continues.  For me it was worse because it appears that
the "recovery" time is only short on heavily loaded networks.  The lockup is
caused by the "first page for RX" being set improperly in etopen; I finally
figured it out by looking at code from other drivers that used the DS8390
chip.  One must switch to the page 1 command registers first.
/


code	segment	word public
	assume	cs:code, ds:code

HT	equ	09h
CR	equ	0dh
LF	equ	0ah

;
;  Packet Driver Error numbers
BAD_HANDLE	equ	1		;invalid handle number
NO_CLASS	equ	2		;no interfaces of specified class found
NO_TYPE		equ	3		;no interfaces of specified type found
NO_NUMBER	equ	4		;no interfaces of specified number found
BAD_TYPE	equ	5		;bad packet type specified
NO_MULTICAST	equ	6		;this interface does not support
CANT_TERMINATE	equ	7		;this packet driver cannot terminate
BAD_MODE	equ	8		;an invalid receiver mode was specified
NO_SPACE	equ	9		;operation failed because of insufficient
TYPE_INUSE	equ	10		;the type had previously been accessed,
BAD_COMMAND	equ	11		;the command was out of range, or not
CANT_SEND	equ	12		;the packet couldn't be sent (usually

; Stuff specific to the 3-Com 3C503 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.

; Symbol prefix "EN" is for Ethernet, National chip
; Symbol prefix "E33" is for _E_thernet, _3_Com 50_3_
; Symbol prefix "E33G" is for registers in the Gate array ASIC.

; The E33 registers - For the ASIC on the 3C503 card:
; Offsets from the board's base address, which can be set by
; jumpers to be one of the following 8 values (hex):
;  350, 330, 310, 300, 2E0, 2A0, 280, 250
; Factory default address is 300H.
; The card occupies a block of 16 I/O addresses.
; It also occupies 16 addresses at base+400 through base+40F.
; These high-addressed registers are in the ASIC.
; Recall that the normal PC I/O decoding is only 10 bits. The 11'th
; bit (400H) can be used on the same card for additional registers.
; This offset requires word, not byte, arithmetic
; on the DX register for the setport macro. Current SETPORT is OK.

; The card can also be jumpered to have the shared memory disabled
; or enabled at one of four addresses: C8000, CC000, D8000 or DC000.
; This version of the driver REQUIRES the shared memory to be 
; enabled somewhere.
; The card can be operated using direct I/O instructions or by
; using the PC's DMA channels instead of the shared memory, but
; I haven't included the code for those other two methods. 
; They would be needed in a system where all four possible addresses
; for the shared memory are in use by other devices.  /Rcc

; Blocks of I/O addresses:

E33GA		equ	400h	; Registers in the gate array.
E33_SAPROM	equ	000h	; Window on station addr prom (if
				; E33G_CNTRL bits 3,2 = 0,1

; The EN registers - the DS8390 chip registers
; These appear at Base+0 through Base+0F when bits 3,2 of
; E33G_CNTRL are 0,0.
; 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)

; 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	048h	; Set burst mode, 8 deep FIFO

; 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

; Registers in the 3-Com custom Gate Array

E33G_STARTPG	equ E33GA+00h	; Start page, must match EN0_STARTPG
E33G_STOPPG	equ E33GA+01h	; Stop  page, must match EN0_STOPPG
E33G_NBURST	equ E33GA+02h	; Size of DMA burst before relinquishing bus
E33G_IOBASE	equ E33GA+03h	; Bit coded: where I/O regs are jumpered.
				; (Which you have to know already to read it)
E33G_ROMBASE	equ E33GA+04h	; Bit coded: Where/whether EEPROM&DPRAM exist
E33G_GACFR	equ E33GA+05h	; Config/setup bits for the ASIC GA
E33G_CNTRL	equ E33GA+06h	; Board's main control register
E33G_STATUS	equ E33GA+07h	; Status on completions.
E33G_IDCFR	equ E33GA+08h	; Interrupt/DMA config register
				; (Which IRQ to assert, DMA chan to use)
E33G_DMAAH	equ E33GA+09h	; High byte of DMA address reg
E33G_DMAAL	equ E33GA+0ah	; Low byte of DMA address reg
E33G_VP2	equ E33GA+0bh	; Vector pointer - for clearing RAM select
E33G_VP1	equ E33GA+0ch	;  on a system reset, to re-enable EPROM.
E33G_VP0	equ E33GA+0dh	;  3Com says set this to Ctrl-Alt-Del handler
E33G_FIFOH	equ E33GA+0eh	; FIFO for programmed I/O data moves ...
E33G_FIFOL	equ E33GA+0fh	; .. low byte of above.

; Bits in E33G_CNTRL register:

ECNTRL_RESET	equ	001h	; Software reset of the ASIC and 8390
ECNTRL_THIN	equ	002h	; Onboard thin-net xcvr enable
ECNTRL_SAPROM	equ	004h	; Map the station address prom
ECNTRL_DBLBFR	equ	020h	; FIFO configuration bit
ECNTRL_OUTPUT	equ	040h	; PC-to-3C501 direction if 1
ECNTRL_START	equ	080h	; Start the DMA logic

; Bits in E33G_STATUS register:

ESTAT_DPRDY	equ	080h	; Data port (of FIFO) ready
ESTAT_UFLW	equ	040h	; Tried to read FIFO when it was empty
ESTAT_OFLW	equ	020h	; Tried to write FIFO when it was full
ESTAT_DTC	equ	010h	; Terminal Count from PC bus DMA logic
ESTAT_DIP	equ	008h	; DMA In Progress

; Bits in E33G_GACFR register:

EGACFR_NORM	equ	049h	; Enable 8K shared mem, no DMA TC int
EGACFR_IRQOFF	equ	0c9h	; Above, and disable 8390 IRQ line

; Shared memory management parameters

XMIT_MTU	equ	600h	; Largest packet we have room for.
SM_TSTART_PG	equ	020h	; First page of TX buffer
SM_RSTART_PG	equ	026h	; Starting page of RX ring
SM_RSTOP_PG	equ	040h	; Last page +1 of RX ring

; Description of header of each packet in receive area of shared memory

EN_RBUF_STAT	equ	0	; Received frame status
EN_RBUF_NXT_PG	equ	1	; Page after this frame
EN_RBUF_SIZE_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 3C503 parameter definitions

; 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, thick_or_thin
int_no		db	2,0,0,0		; Interrupt level
io_addr		dw	0300h,0		; I/O address for card (jumpers)
thick_or_thin	dw	1,0		; Non-zero means thin net
	public	mem_base
mem_base	dw	00000h,0	; Shared memory addr (jumpers)
; (Not changeable by software in 3C503)	; (0 if disabled by jumpers)
thin_bit	db	ECNTRL_THIN	; Default to thin cable
rxcr_bits       db      ENRXCR_BCST     ; Default to ours plus multicast

	public	driver_class, driver_type, driver_name, driver_function, parameter_list
driver_class	db	1		;from the packet spec
driver_type	db	12		;from the packet spec
driver_name	db	'3C503',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,

	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.

	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

; send_pkt: - The Transmit Frame routine

	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
	loadport		; Point at chip command register
	setport EN_CCMD		; ..
	mov bx,	8000h		; Avoid infinite loop
tx_wait:
	in al,	dx		; Get chip command state
	test al,ENC_TRANS	; Is transmitter still running?
	jz	tx_idle		; Go if free
	dec	bx		; Count the timeout
	jnz	tx_wait		; Fall thru if TX is stuck
				; Should count these error timeouts
				; Maybe need to add recovery logic here
tx_idle:
	cmp	cx,XMIT_MTU	; Is this packet too large?
	ja	send_pkt_toobig

	cmp cx,	RUNT		; Is the frame long enough?
	jnb	tx_oklen	; Go if OK
	mov cx,	RUNT		; Stretch frame to minimum allowed
tx_oklen:
	push	cx		; Hold count for later
	loadport		; Set up for address of TX buffer in
	setport	E33G_GACFR	; Make sure gate array is set up and
	mov al,	EGACFR_NORM	;  the RAM is enabled (not EPROM)
	out dx,	al		; ..
	mov ax,	cs:mem_base	; Set up ES at the shared RAM
	mov es,	ax		; ..
	xor ax,	ax		; Set up DI at base of tx buffer
	mov ah,	SM_TSTART_PG	; Where to put tx frame
	mov di,	ax		; ..
	call	movemem
	pop	cx		; Get back count to give to board
	setport	EN0_TCNTLO	; Low byte of TX count
	mov al,	cl		; Get the count
	out dx,	al		; Tell card the count
	setport	EN0_TCNTHI	; High byte of TX count
	mov al,	ch		; Get the count
	out dx,	al		; Tell card the count
	setport	EN0_TPSR	; Transmit Page Start Register
	mov al,	SM_TSTART_PG
	out dx,	al		; Start the transmitter
	setport	EN_CCMD		; Chip command reg
	mov al,	ENC_TRANS+ENC_NODMA
	out dx,	al		; Start the transmitter
	clc			; Successfully started
	ret			; End of transmit-start routine
send_pkt_toobig:
	mov	dh,NO_SPACE
	stc
	ret


	include	movemem.asm


	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:
	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
	cli			; Protect from irq changing page bits
	mov al,	ENC_NODMA+ENC_PAGE1
	out dx,	al		; Switch to page one for writing eaddr
	setport	EN1_PHYS	; Where it goes in 8390
set_8390_1:
	lodsb
	out	dx,al
	inc	dx
	loop	set_8390_1
	loadport
	setport	EN_CCMD		; Chip command register
	mov al,	ENC_NODMA+ENC_PAGE0
	out dx,	al		; Restore to page zero
	sti			; OK for interrupts now
	ret

; Routines to set address filtering modes in the DS8390
rcv_mode_1:     ; Turn off receiver
	mov al,	ENRXCR_MON      ; Set to monitor for counts but accept none
	jmp short rcv_mode_set
rcv_mode_2:     ; Receive only packets to this interface
	mov al, 0               ; Set for only our packets
	jmp short rcv_mode_set
rcv_mode_3:     ; Mode 2 plus broadcast packets (This is the default)
	mov al,	ENRXCR_BCST     ; Set four ours plus broadcasts
	jmp short rcv_mode_set
rcv_mode_4:     ; Mode 3 plus selected multicast packets
	mov al,	ENRXCR_BCST+ENRXCR_MULTI ; Ours, bcst, and filtered multicasts
	mov     mcast_all_flag,0
	jmp short rcv_mode_set
rcv_mode_5:     ; Mode 3 plus ALL multicast packets
	mov al,	ENRXCR_BCST+ENRXCR_MULTI ; Ours, bcst, and filtered multicasts
	mov     mcast_all_flag,1
	jmp short rcv_mode_set
rcv_mode_6:     ; Receive all packets (Promiscuous physical plus all multi)
	mov al,	ENRXCR_BCST+ENRXCR_MULTI+ENRXCR_PROMP
	mov     mcast_all_flag,1
rcv_mode_set:
	push    ax              ; Hold mode until masks are right
	call    set_8390_multi  ; Set the multicast mask bits in chip
	pop     ax
	loadport
	setport	EN0_RXCR	; Set receiver to selected mode
	out dx,	al
	mov     rxcr_bits,al    ; Save a copy of what we set it to
	ret


	public	set_multicast_list
set_multicast_list:
;enter with ds:si ->list of multicast addresses, cx = number of addresses.
;return nc if we set all of them, or cy,dh=error if we didn't.
	mov	dh,NO_MULTICAST
	stc
	ret


; Set the multicast filter mask bits in case promiscuous rcv wanted
set_8390_multi:
	loadport
	setport	EN_CCMD		; Chip command register
	mov cx,	8		; Eight bytes of multicast filter
	mov si, offset mcast_list_bits  ; Where bits are, if not all ones
	push    cs
	pop     ds
	cli			; Protect from irq changing page bits
	mov al,	ENC_NODMA+ENC_PAGE1
	out dx,	al		; Switch to page one for writing eaddr
	setport	EN1_MULT	; Where it goes in 8390
	mov al, mcast_all_flag  ; Want all ones or just selected bits?
	or al,  al
	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
	mov al,	ENC_NODMA+ENC_PAGE0
	out dx,	al		; Restore to page zero
	sti			; OK for interrupts now
	ret

	public	terminate
terminate:
	ret

	public	reset_interface
reset_interface:
	assume ds:code
	loadport		; Base of I/O regs
	setport	EN_CCMD		; Chip command reg
	mov al,	ENC_STOP+ENC_NODMA
	out dx,	al		; Stop the DS8390
	setport	EN0_ISR		; Interrupt status reg
	mov al,	0ffh		; Clear all pending interrupts
	out dx,	al		; ..
	setport	EN0_IMR		; Interrupt mask reg
	xor al,	al		; Turn off all enables
	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.
;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	E33G_GACFR	; Make sure gate array is set up and
	mov al,	EGACFR_NORM	;  the RAM is enabled (not EPROM)
	out dx,	al		; ..
	setport	EN0_ISR		; Point at interrupt status register
	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
	mov al,	ENC_STOP+ENC_NODMA
	out dx,	al		; Write "stop" to command register

	mov al, ENC_NODMA+ENC_PAGE1	; Could be in previous out, but
	out dx,al		; was only tested this way
	setport EN1_CURPAG	; Get current page
	in al,dx
	mov bl,al		; save it
	setport	EN_CCMD		;
	mov al, ENC_NODMA+ENC_PAGE0
	out dx,al		; Back to page 0

; Remove one frame from the ring
	setport	EN0_BOUNDARY	; Find end of this frame
	in al,	dx		; Get memory page number
	inc	al		; Page plus 1
	cmp al,	SM_RSTOP_PG	; Wrapped around ring?
	jnz	rcv_ovr_nwrap	; Go if not
	mov al,	SM_RSTART_PG	; Yes, wrap the page pointer
rcv_ovr_nwrap:

	cmp	al,bl		; Check if buffer emptry
	je	rcv_ovr_empty	; Yes ? Don't receive anything

	xor ah,	ah		; Convert page to segment
	mov cl,	4
	mov bl,	al		; Page number as arg to rcv_frm
	shl ax,	cl		; ..
	add ax,	mem_base	; Page in this memory
	mov es,	ax		; Segment pointer to the frame header
	push	es		; Hold this frame pointer for later
	mov al,	es:[EN_RBUF_STAT]	; Get the buffer status byte
	test al,ENPS_RXOK	; Is this frame any good?
	jz	rcv_ovr_ng	; Skip if not
 	call	rcv_frm		; Yes, go accept it
rcv_ovr_ng:
	pop	es		; Back to start of this frame
	mov al,	es:[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	; ..
	out dx,	al		; Set the boundary
rcv_ovr_empty:
	setport	EN0_RCNTLO	; Point at byte count regs
	xor al,	al		; Clear them
	out dx,	al		; ..
	setport	EN0_RCNTHI
	out dx,	al
	setport	EN0_ISR		; Point at status reg
	mov cx,	8000h		; Timeout counter
rcv_ovr_rst_loop:
	in al,	dx		; Is it finished resetting?
	test al,ENISR_RESET	; ..
	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	; ..
	mov al,	ENTXCR_LOOP	; Put transmitter in loopback mode
	out dx,	al		; ..
	setport	EN_CCMD		; Point at Chip command reg
	mov al,	ENC_START+ENC_NODMA
	out dx,	al		; Start the chip running again
	setport	EN0_TXCR	; Back to TX control reg
	xor al,	al		; Clear the loopback bit
	out dx,	al		; ..
	setport	EN0_ISR		; Point at Interrupt status register
	mov al,	ENISR_OVER	; Clear the overrun interrupt bit
	out dx,	al		; ..
	call	count_in_err	; Count the anomaly
 	jmp	check_isr	; Done with the overrun case

recv_no_overrun:
; Handle receive flags, normal and with error (but not overrun).
	test al,ENISR_RX+ENISR_RX_ERR	; Frame received without overrun?
	jnz	recv_frame	; Go if so.
	jmp	recv_no_frame	; Go if not.
recv_frame:
	loadport		; Point at Chip's Command Reg
 	setport	EN_CCMD		; ..
	mov al,	ENC_NODMA+ENC_PAGE1
	out dx,	al		; Switch to page 1 registers
	setport	EN1_CURPAG	;Get current page of rcv ring
	in al,	dx		; ..
	mov ah,	al		; Hold current page in AH
 	setport	EN_CCMD		; Back to page zero registers
	mov al,	ENC_NODMA+ENC_PAGE0
	out dx,	al		; Switch back to page 0 registers
	setport	EN0_BOUNDARY	;Get boundary page
	in al,	dx		; ..
	inc	al		; Step boundary from last used page
	cmp al,	SM_RSTOP_PG	; Wrap if needed
	jne	rx_nwrap3	; Go if not
	mov al,	SM_RSTART_PG	; Wrap to first RX page
rx_nwrap3:
	cmp al,	ah		; Read all the frames?
	je	recv_frame_break	; Finished them all
	mov bl,	al		; Page number as arg to rcv_frm
	xor ah,	ah		; Make segment pointer to this frame
	mov cl,	4		; 16 * pages = paragraphs
	shl ax,	cl		; ..
	add ax,	mem_base	; That far into shared memory
	mov es,	ax		; Segment part of pointer
	push	es		; Hold on to this pointer for later
	mov al,	es:[EN_RBUF_STAT]	; Get the buffer status byte
	test al,ENPS_RXOK	; Good frame?
	jz	recv_no_rcv
	call	rcv_frm		; Yes, go accept it
recv_no_rcv:
	pop	es		; Back to base of frame
	mov al,	es:[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	; ..
	out dx,	al		; Set new boundary
	jmp	recv_frame	; See if any more frames

recv_frame_break:
	loadport		; Point at Interrupt Status Reg
 	setport	EN0_ISR		; ..
	mov al,	ENISR_RX+ENISR_RX_ERR+ENISR_OVER
	out dx,	al		; Clear those requests
	jmp	check_isr	; See if any other interrupts pending

recv_no_frame:				; Handle transmit flags.
	test al,ENISR_TX+ENISR_TX_ERR	; Frame transmitted?
	jnz	isr_tx		; Go if so.
	jmp	isr_no_tx	; Go if not.
isr_tx:
	mov ah,	al		; Hold interrupt status bits
	loadport		; Point at Transmit Status Reg
 	setport	EN0_TSR		; ..
	in al,	dx		; ..
	test ah,ENISR_TX	; Non-error TX?
	jz	isr_tx_err	; No, do TX error completion
	test al,ENTSR_COLL16	; Jammed for 16 transmit tries?
	jz	isr_tx_njam	; Go if not
	call	count_out_err	; Yes, count those
isr_tx_njam:
	setport	EN0_ISR		; Clear the TX complete flag
	mov al,	ENISR_TX	; ..
	out dx,	al		; ..	
	jmp	isr_tx_done
isr_tx_err:
	test al,ENTSR_FU	; FIFO Underrun?
	jz	isr_txerr_nfu
	call	count_out_err	; Yes, count those
isr_txerr_nfu:
	loadport		; Clear the TX error completion flag
	setport	EN0_ISR		; ..
	mov al,	ENISR_TX_ERR	; ..
	out dx,	al		; ..	
isr_tx_done:
; If TX queue and/or TX shared memory ring buffer were being
; used, logic to step through them would go here.  However,
; in this version, we just clear the flags for background to notice.

 	jmp	check_isr	; See if any other interrupts on

isr_no_tx:
; Now check to see if any counters are getting full
	test al,ENISR_COUNTERS	; Interrupt to handle counters?
	jnz	isr_stat	; 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	; ..
	in al,	dx		; Read the count, ignore it.
	setport	EN0_COUNTER1
	in al,	dx		; Read the count, ignore it.
	setport	EN0_COUNTER2
	in al,	dx		; Read the count, ignore it.
	setport	EN0_ISR		; Clear the statistics completion flag
	mov al,	ENISR_COUNTERS	; ..
	out dx,	al		; ..
isr_no_stat:
 	jmp	check_isr	; Anything else to do?

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/
; Also, es/ the paragraph number of that page.

rcv_frm:
; Old version checked size, memory space, queue length here. Now done
; in higher level code.
; Set cx to length of this frame.
	mov ch,	es:[EN_RBUF_SIZE_HI]	; Extract size of frame
	mov cl,	es:[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.  es is already at base of
; page where this frame starts.  Set di after the header and two addresses.
	mov di,	EN_RBUF_NHDR+EADDR_LEN+EADDR_LEN
	push	bx			; Save page number in bl
	push	cx			; Save frame size
	push	es
	mov ax,	cs			; Set ds = code
	mov ds,	ax
	assume	ds:code
	call	recv_find		; See if type and size are wanted
	pop	ds			; RX page pointer in ds now
	assume	ds:nothing
	pop	cx
	pop	bx
	cld			; Copies below are forward, please
	mov ax,	es		; Did recv_find give us a null pointer?
	or ax,	di		; ..
	je	rcv_no_copy	; If null, don't copy the data	

	push	cx		; We will want the count and pointer
	push	es		;  to hand to client after copying,
	push	di		;  so save them at this point

;; if ( (((size + 255 + EN_RBUF_NHDR) >> 8) + pg) > SM_RSTOP_PG){
	mov ax,	cx		; Length of frame
	add ax,	EN_RBUF_NHDR+255 ; Including the overhead bytes, rounded up
	add ah,	bl		; Compute page with last byte of data in ah
	cmp ah,	SM_RSTOP_PG	; Over the top of the ring?
	jg	rcopy_wrap	; Yes, move in two pieces
	mov si,	EN_RBUF_NHDR	; One piece, starts here in first page (in ds)
	jmp	rcopy_one_piece	; Go move it

rcopy_wrap:
;; Copy in two pieces due to buffer wraparound. */
;; n = ((SM_RSTOP_PG - pg) << 8) - EN_RBUF_NHDR;	/* To top of mem */
	mov ah,	SM_RSTOP_PG	; Compute length of first part
	sub ah,	bl		;  as all of the pages up to wrap point
	xor al,	al		; 16-bit count
	sub ax,	EN_RBUF_NHDR	; Less the four overhead bytes
	sub cx,	ax		; Move the rest in second part
	push	cx		; Save count of second part
	mov cx,	ax		; Count for first move
	mov si,	EN_RBUF_NHDR	; ds:si points at first byte to move
	shr cx,	1		; All above are even numbers, do words.
	rep	movsw		; Move first part of frame
	mov ax,	mem_base	; Paragraph of base of shared memory
	mov ds,	ax		; ..
	mov si,	SM_RSTART_PG*256  ; Offset to start of first receive page
	pop	cx		; Bytes left to move
rcopy_one_piece:
	call	movemem
	pop	si		; Recover pointer to destination
	pop	ds		; Tell client it's his source
	pop	cx		; And it's this long
	assume	ds:nothing
	call	recv_copy	; Give it to him
rcv_no_copy:
	push	cs		; Put ds back in code space
	pop	ds		; ..
	assume	ds:code
	ret			; That's it for rcv_frm


	public	recv_exiting
recv_exiting:
;called from the recv isr after interrupts have been acknowledged.
;Only ds and ax have been saved.
	assume	ds:nothing
	ret


;any code after this will not be kept after initialization.
end_resident	label	byte


	public	usage_msg
usage_msg	db	"usage: 3C503 [-n] [-d] [-w] <packet_int_no> <int_level(2-5)> <io_addr> <thin_net_flag>",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for 3-Com 3C503, version ",'0'+majver,".",'0'+version,CR,LF
		db	"Portions Copyright 1989, Robert C. Clements, K1BC",CR,LF,'$'

cfg_err_msg:
	db	"3C503 Configuration failed. Check parameters.",CR,LF,'$'
no_mem_msg:
	db	"3C503 memory jumper must be set to enable memory.",CR,LF
	db	"Driver cannot run with memory disabled.",'$'
int_no_name:
	db	"Interrupt number ",'$'
io_addr_name:
	db	"I/O port ",'$'
mem_base_name:
	db	"Memory address ",'$'
thick_thin_msg:
	db	"Flag, non-zero if thin Ethernet: ",'$'
mem_busted_msg:
	db	"Shared RAM on 3C503 card is defective or there is an address conflict.",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		; May override interrupt channel
	call	get_number
	mov di,	offset io_addr		; May override I/O address
	call	get_number
	mov di,	offset thick_or_thin	; May override thick/thin cable flag
	call	get_number
	mov ax,	thick_or_thin		; Now make the right bit
	cmp ax,	0
	je	parse_thin1		; If zero, leave bit off
	mov al,	ECNTRL_THIN		; Else the bit for the card
parse_thin1:
	mov	thin_bit,al		; Save for setting up the card

;	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


mem_busted:
	mov dx,	offset mem_busted_msg
	jmp	short error
nomem_error:
	mov	dx,offset no_mem_msg
	jmp	short error
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 3C503 card

	public	etopen
etopen:				; Initialize interface
; First, initialize the Gate Array (ASIC) card logic.  Later do the 8390.
	loadport		; First, pulse the board reset
	setport	E33G_CNTRL
	mov al,	thin_bit	; Thick or thin cable bit
	or al,	ECNTRL_RESET
	out dx,	al		; Turn on board reset bit
	and al,	ECNTRL_THIN
	out dx,	al		; Turn off board reset bit
; Now get the board's physical address from on-board PROM into card_hw_addr
	cli                     ; Protect the E33G_CNTRL contents
	setport E33G_CNTRL	; Switch control bits to enable SA PROM
	mov al,	thin_bit
	or al,	ECNTRL_SAPROM
	out dx,	al		; ..
	setport	E33_SAPROM	; Where the address prom is
	cld			; Make sure string mode is right
	push	cs		; Point es:di at local copy space
	pop	es
	mov di,	offset card_hw_addr
	mov cx,	EADDR_LEN	; Set count for loop
ini_addr_loop:
	in al,	dx		; Get a byte of address
	stosb			; Feed it to caller
	inc	dx		; Next byte at next I/O port
	loop	ini_addr_loop	; Loop over six bytes
	loadport		; Re-establish I/O base after dx mods
	setport E33G_CNTRL	; Switch control bits to turn off SA PROM
	mov al,	thin_bit
	out dx,	al		; Turn off SA PROM windowing
	sti                     ; Ok for E33G_CNTRL to change now
; Point the "Vector Pointer" registers off into the boonies so we
; don't get the shared RAM disabled on us while we're using it.
; Ideally a warm boot should reset this too, to get to ROM on this card,
; but I don't know a guaranteed way to determine that value.
	setport	E33G_VP2
	mov al,	0ffh		; Point this at the ROM restart location
	out dx,	al		;  of ffff0h.
	setport E33G_VP1
	out dx,	al
	xor al,	al
	setport E33G_VP0
	out dx,	al
;Make sure shared memory is jumpered on. Find its address.
	setport E33G_ROMBASE	; Point at rom/ram cfg reg
	in al,	dx		; Read it
	test al,0f0h		; Any bits on?
	jne	memcfg_1	; Yes
	jmp	nomem_error	; No, can't run without it
memcfg_1:
	mov bx,	0c600h		; Build mem segment here
	test al,0c0h		; DC00 or D800?
	je	memcfg_2	; No
	add bx,	01000h		; Yes, make Dx00
memcfg_2:
	test al,0a0h		; DC00 or CC00?
	je	memcfg_3
	add bx,	00400h		; Yes, make xC00
memcfg_3:
	mov mem_base,bx		; Remember segment addr of memory
; Set up Gate Array's Config Reg to enable and size the RAM.
	setport	E33G_GACFR	; Make sure gate array is set up and
	mov al,	EGACFR_IRQOFF	;  the RAM is enabled (not EPROM)
	out dx,	al		; ..
; Check the card's memory
	mov ax,	mem_base	; Set segment of the shared memory
	add ax,	16*SM_TSTART_PG	;  which starts 2000h up from "base"
	mov cx,	2000h		; Length of RAM to test
	call	memory_test	; Check it out
	jz	mem_works	; Go if it's OK
	jmp	mem_busted	; Go report failure if it's bad
mem_works:
; Set up control of shared memory, buffer ring, etc.
	loadport
	setport	E33G_STARTPG	; Set ASIC copy of rx's first buffer page
	mov al,	SM_RSTART_PG
	out dx,	al
	setport	E33G_STOPPG	;  and ASIC copy of rx's last buffer page + 1
	mov al,	SM_RSTOP_PG
	out dx,	al
; Set up interrupt/DMA control register in ASIC.
; For now, we won't use the DMA, so B0-B3 are zero.
	xor ah,	ah		; Get the interrupt level from arg line
	mov al,	int_no		; ..
	cmp al,	9		; If converted to 9, make back into 2
	jne	get_irq1	; Not 9
	mov al,	2		; Card thinks it's IRQ2
get_irq1:			; Now should have level in range 2-5
	sub ax,	2		; Make 0-3 for tables
	cmp ax,	5-2		; In range?
	jna	get_irq2
	jmp	cfg_error	; If not, can't configure.
get_irq2:
	xor cx,	cx		; Make the bit for the ASIC
	mov cl,	al		; Shift count
	mov al,	10h		; Bit for irq2
	jcxz	get_irq3	; Go if it's 2
	shl al,	cl		; Shift over for 3-5
get_irq3:
	setport	E33G_IDCFR	; Point at ASIC reg for IRQ level
	out dx,	al		; Set the bit
	setport	E33G_NBURST	; Set burst size to 8
	mov al,	8
	out dx,	al		; ..
	setport	E33G_DMAAH	; Set up transmit bfr in DMA addr
	mov al,	SM_TSTART_PG
	out dx,	al
	xor ax,	ax
	setport E33G_DMAAL
	out dx,	al
; Now, initialize the DS8390 Ethernet Controller chip
ini_8390:
	setport	EN_CCMD		; DS8390 chip's command register
	mov al,	ENC_NODMA+ENC_PAGE0+ENC_STOP
	out dx,	al		; Switch to page zero
	setport	EN0_ISR		; Clear all interrupt flags
	mov al,	0ffh		; ..
	out dx,	al		; ..
	setport	EN0_DCFG	; Configure the fifo organization
	mov al,	ENDCFG_BM8	; Fifo threshold = 8 bytes
	out dx,	al
	setport	EN0_TXCR	; Set transmitter mode to normal
	xor al,	al
	out dx,	al
	setport	EN0_RXCR	; Set receiver to monitor mode
	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
	mov al,	SM_RSTART_PG
	out dx,	al
	setport	EN0_STOPPG	;  and receiver's last buffer page + 1
	mov al,	SM_RSTOP_PG
	out dx,	al
	setport	EN0_BOUNDARY	; Set initial "last page we have emptied"
	mov al,	SM_RSTART_PG	; (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)
	push    ds              ; Copy from card's address to current address
	pop     es
	mov si, offset card_hw_addr
	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		;[jah] Switch to page 1 registers
	mov al,ENC_NODMA+ENC_PAGE1	;[jah]
	out dx, al		;[jah]
	setport	EN1_CURPAG	; Set current shared page for RX to work on
	mov al,	SM_RSTART_PG+1
	out dx,	al
	setport	EN_CCMD		; Chip command register
	mov al,	ENC_NODMA+ENC_PAGE0
	out dx,	al		; Back to page zero
	setport	EN0_RCNTLO	; Clear the byte count registers
	xor al,	al		; ..
	out dx,	al
	setport	EN0_RCNTHI
	out dx,	al		; Clear high byte, too
	setport	EN0_IMR		; Clear all interrupt enable flags
	xor al,	al
	out dx,	al
	setport	EN0_ISR		; Clear all interrupt assertion flags
	mov al,	0ffh		; again for safety before making the
	out dx,	al		; interrupt be enabled
	call	set_recv_isr	; Put ourselves in interrupt chain
	loadport
	setport	EN_CCMD		; Now start the DS8390
	mov al,	ENC_START+ENC_NODMA
	out dx,	al		; interrupt be enabled
	setport	EN0_RXCR	; Tell it what frames to accept
	mov al,	rxcr_bits       ; As most recently set by set_mode
	out dx,	al
	setport	E33G_GACFR	; Now let it interrupt us
	mov al,	EGACFR_NORM	;  and leave RAM enabled
	out dx,	al
	setport	EN0_IMR		; Tell card it can cause these interrupts
	mov al,	ENISR_ALL
	out dx,	al

	mov	al, int_no		; Get board's interrupt vector
	add	al, 8
	cmp	al, 8+8			; Is it a slave 8259 interrupt?
	jb	set_int_num		; No.
	add	al, 70h - 8 - 8		; Map it to the real interrupt.
set_int_num:
	xor	ah, ah			; Clear high byte
	mov	int_num, ax		; Set parameter_list int num.

	mov dx,	offset end_resident	; Report our size
	clc				; Say no error
	ret				; Back to common code

	public	print_parameters
print_parameters:
	mov di,	offset int_no		; May override interrupt channel
	mov dx,	offset int_no_name	; Message for it
	call	print_number
	mov di,	offset io_addr		; May override I/O address
	mov dx,	offset io_addr_name	; Message for it
	call	print_number
	mov di,	offset thick_or_thin	; May override thick/thin cable flag
	mov dx,	offset thick_thin_msg	; Message for it
	call	print_number
	ret

	include memtest.asm

code	ends

	end
