version	equ	2

	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.

code	segment	byte 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_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_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_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_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_class	db	1		;from the packet spec
driver_type	db	12		;from the packet spec
driver_name	db	'3C503',0	;name of the driver.

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


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


	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
	sti			; 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
	cli			; 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 es:di ->list of multicast addresses, cx = number of bytes.
;return nc if we set all of them, or cy,dh=error if we didn't.
	mov	dh,NO_MULTICAST
	stc
	ret


	public	get_multicast_list
get_multicast_list:
;return with nc, es:di ->list of multicast addresses, cx = number of bytes.
;return cy, NO_ERROR if we don't remember all of the addresses ourselves.
;return cy, NO_MULTICAST if we don't implement multicast.
	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
	sti			; 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
	cli			; OK for interrupts now
	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

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

	public	parse_args
parse_args:
	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 thick_or_thin	; May override thick/thin cable flag
	mov bx,	offset thick_thin_msg	; Message for it
	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.
	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

	extrn	memory_test: near
	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
	sti                     ; 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
	cli                     ; 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	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 dx,	offset end_resident	; Report our size
	clc				; Say no error
	ret				; Back to common code

code	ends

	end
