version	equ	0

	include	defs.asm

;Ported from Phil Karn's ec.c, a C-language driver for the 3COM 3C501
;by Russell Nelson.  Any bugs are due to Russell Nelson.
;*  Updated to version 1.08 Feb. 17, 1989.

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

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

;The various IE command registers
EDLC_ADDR	equ	00h	;EDLC station address, 6 bytes
EDLC_RCV	equ	06h	;EDLC receive csr
EDLC_XMT	equ	07h	;EDLC transmit csr
IE_GP		equ	08h	;GP pointer
IE_RP		equ	0ah	;Receive buffer pointer
IE_SAPROM	equ	0ch	;window on station addr prom
IE_CSR		equ	0eh	;IE command/status
IE_BFR		equ	0fh	;window on packet buffer

;Bits in EDLC_RCV, interrupt enable on write, status when read
EDLC_NONE	equ	000h	;match mode in bits 5-6, write only
EDLC_ALL	equ	040h	;promiscuous receive, write only
EDLC_BROAD	equ	080h	;station address plus broadcast
EDLC_MULTI	equ	0c0h	;station address plus multicast

EDLC_STALE	equ	80h	;receive CSR status previously read
EDLC_GOOD	equ	20h	;well formed packets only
EDLC_ANY	equ	10h	;any packet, even those with errors
EDLC_SHORT	equ	08h	;short frame
EDLC_DRIBBLE	equ	04h	;dribble error
EDLC_FCS	equ	02h	;CRC error
EDLC_OVER	equ	01h	;data overflow

EDLC_RERROR	equ	EDLC_SHORT or EDLC_DRIBBLE or EDLC_FCS or EDLC_OVER
EDLC_RMASK	equ	EDLC_GOOD or EDLC_ANY or EDLC_RERROR

;bits in EDLC_XMT, interrupt enable on write, status when read
EDLC_IDLE	equ	08h	;transmit idle
EDLC_16		equ	04h	;packet experienced 16 collisions
EDLC_JAM	equ	02h	;packet experienced a collision
EDLC_UNDER	equ	01h	;data underflow

;bits in IE_CSR
IE_RESET	equ	80h	;reset the controller (wo)
IE_XMTBSY	equ	80h	;Transmitter busy (ro)
IE_RIDE		equ	40h	;request interrupt/DMA enable (rw)
IE_DMA		equ	20h	;DMA request (rw)
IE_EDMA		equ	10h	;DMA done (ro)

IE_BUFCTL	equ	0ch	;mask for buffer control field (rw)
IE_LOOP		equ	0ch	;2 bit field in bits 2,3, loopback
IE_RCVEDLC	equ	08h	;gives buffer to receiver
IE_XMTEDLC	equ	04h	;gives buffer to transmit
IE_SYSBFR	equ	00h	;gives buffer to processor

IE_CRC		equ	01h	;causes CRC error on transmit (wo)
IE_RCVBSY	equ	01h	;receive in progress (ro)

BFRSIZ		equ	2048	;number of bytes in a buffer

	public	int_no
int_no		db	3,0,0,0		; interrupt number.
io_addr		dw	0300h,0		; I/O address for card (jumpers)

	public	driver_class, driver_type, driver_name
driver_class	db	1		;from the packet spec
driver_type	db	1		;from the packet spec
driver_name	db	'3C501',0	;name of the driver.

	public	rcv_modes
rcv_modes	dw	4		;number of receive modes in our table.
		dw	0,0,0,rcv_mode_3

ipkt_size	dw	?
opkt_size	dw	?

is_186		db	0		;=0 if 808[68], =1 if 80[123]86.
our_type	dw	?

	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
	cmp	cx,RUNT		; minimum length for Ether
	jae	oklen
	mov	cx,RUNT		; make sure size at least RUNT
oklen:
	inc	cx			;round size up to next even number.
	and	cx,not 1

;Wait for transmitter ready, if necessary. IE_XMTBSY is valid
;only in the transmit mode, hence the initial check.

	loadport
	setport	IE_CSR
	in	al,dx
	and	al,IE_BUFCTL
	cmp	al,IE_XMTEDLC
	jne	send_pkt_2

	mov	bx,20000		;try this many times.
send_pkt_3:
	in	al,dx			;if not busy, exit.
	and	al,IE_XMTBSY
	je	send_pkt_2
	dec	bx
	jne	send_pkt_3
	mov	dh,CANT_SEND		;timed out, can't send.
	stc
	ret
send_pkt_2:

;Get control of the board buffer and disable receiver
	mov	al,IE_RIDE or IE_SYSBFR
	setport	IE_CSR
	out	dx,al

;Point GP at beginning of packet
	mov	ax,BFRSIZ
	sub	ax,cx
	setport	IE_GP
	out	dx,ax

	setport	IE_BFR

	mov	opkt_size,cx	; opkt_size = cx;
	cmp	is_186,0	; Can we use rep outsb?
	je	out86		; no - have to do it slowly.
	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:

;Start transmitter
;Point GP at beginning of packet
	mov	ax,BFRSIZ
	sub	ax,opkt_size
	setport	IE_GP
	out	dx,ax

	mov	al,IE_RIDE or IE_XMTEDLC
	setport	IE_CSR
	out	dx,al

	clc
	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		;make sure that we have enough room.
	jb	get_address_2
	mov	cx,EADDR_LEN
	xor	bx,bx
get_address_1:
	mov	ax,bx
	loadport
	setport	IE_GP
	out	dx,ax

	setport	IE_SAPROM
	in	al,dx
	stosb
	inc	bx
	loop	get_address_1

	mov	cx,EADDR_LEN
	clc
	ret
get_address_2:
	stc
	ret


;Set Ethernet address on controller
	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:

	loadport
	setport	EDLC_ADDR
set_address_1:
	lodsb
	out	dx,al
	inc	dx
	loop	set_address_1
set_address_okay:
	mov	cx,EADDR_LEN		;return their address length.
	clc
set_address_done:
	push	cs
	pop	ds
	assume	ds:code
	ret


terminate:
;Pulse IE_RESET
	mov	al,IE_RESET
	loadport
	setport	IE_CSR
	out	dx,al
	mov	al,0
	out	dx,al

	ret


rcv_mode_3:
;receive mode 3 is the only one we support, so we don't have to do anything.
	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


	public	reset_interface
reset_interface:
;reset the interface.
;we don't do anything.
	ret


;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type.
	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.
;Upon exit, the interrupt will be acknowledged.
	assume	ds:code

;Check for transmit jam

	loadport
	setport	IE_CSR
	in	al,dx
	and	al,IE_XMTBSY
	jne	recv_isr_1

	setport	EDLC_XMT
	in	al,dx
	test	al,EDLC_16
	je	recv_isr_2
;ecp->estats.jam16++;
	call	rcv_fixup
	jmp	short 	recv_isr_3
recv_isr_2:
	test	al,EDLC_JAM
	je	recv_isr_3
;Crank counter back to beginning and restart transmit
;ecp->estats.jam++;
	mov	al,IE_RIDE or IE_SYSBFR
	setport	IE_CSR
	out	dx,al

	mov	ax,BFRSIZ
	sub	ax,opkt_size
	setport	IE_GP
	out	dx,ax

	mov	al,IE_RIDE or IE_XMTEDLC
	setport	IE_CSR
	out	dx,al

recv_isr_3:

recv_isr_1:
	loadport
	setport	EDLC_RCV
	in	al,dx
	test	al,EDLC_STALE
	jne	recv_isr_4_j_1
	test	al,EDLC_OVER
	je	recv_isr_5
;ecp->estats.over++;
	call	rcv_fixup
	jmp	recv_isr_1
recv_isr_5:
	test	al,EDLC_SHORT or EDLC_FCS or EDLC_DRIBBLE
	je	recv_isr_6
;ecp->estats.bad++;
	call	rcv_fixup
	jmp	recv_isr_1
recv_isr_4_j_1:
	jmp	recv_isr_4
recv_isr_6:
	test	al,EDLC_ANY
	je	recv_isr_4_j_1
;Get control of the buffer
	mov	al,IE_RIDE or IE_SYSBFR
	setport	IE_CSR
	out	dx,al

	setport	IE_RP
	in	ax,dx			;get the size.
	mov	ipkt_size,ax
	cmp	ax,RUNT			;less than RUNT?
	jb	recv_isr_7		;yes.
	cmp	ax,GIANT		;greater than GIANT?
	jbe	recv_isr_8		;no.
recv_isr_7:
	call	count_in_err
;ecp->estats.bad++;
	jmp	recv_isr_9
recv_isr_8:
;Put it on the receive queue

	mov	ax,EADDR_LEN+EADDR_LEN	;seek to the type word.
	setport	IE_GP
	out	dx,ax

	setport	IE_BFR
	in	al,dx			;read the type word out of the board.
	mov	ah,al
	in	al,dx
	xchg	al,ah			;should be in network byte order.
	mov	our_type,ax

	mov	ax,ds			;look up our type.
	mov	es,ax
	mov	di,offset our_type
	mov	cx,ipkt_size
	call	recv_find

	mov	ax,es			;is this pointer null?
	or	ax,di
	je	recv_isr_9		;yes - just free the frame.

	push	es			;remember where the buffer pointer is.
	push	di

	xor	ax,ax			;seek to the beginning again.
	loadport
	setport	IE_GP
	out	dx,ax

	mov	cx,ipkt_size
	setport	IE_BFR

	cmp	is_186,0	; Can we use rep insb?
	je	in86		; no - have to do it slowly.
	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:

	pop	si
	pop	ds
	assume	ds:nothing
	mov	cx,ipkt_size
	call	recv_copy		;tell them that we copied it.

	mov	ax,cs			;restore our ds.
	mov	ds,ax
	assume	ds:code

recv_isr_9:
	mov	al,IE_RIDE or IE_RCVEDLC
	loadport
	setport	IE_CSR
	out	dx,al
	xor	al,al
	setport	IE_RP
	out	dx,al
recv_isr_4:
;Clear any spurious interrupts
	loadport
	setport	EDLC_RCV
	in	al,dx

	setport	EDLC_XMT
	in	al,dx

	ret


rcv_fixup:
	call	count_out_err

	mov	al,IE_RIDE or IE_SYSBFR
	loadport
	setport	IE_CSR
	out	dx,al

	mov	al,IE_RIDE or IE_RCVEDLC
	setport	IE_CSR
	out	dx,al

	mov	al,0
	setport	IE_RP
	out	dx,al

	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


end_resident	label	byte

	public	usage_msg
usage_msg	db	"usage: 3C501 <packet_int_no> <int_no> <io_addr>",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for the 3COM 3C501, version ",'0'+majver,".",'0'+version,CR,LF
		db	"Portions Copyright 1988 Phil Karn",CR,LF,'$'

using_186_msg	db	"Using 80[123]86 I/O instructions.",CR,LF,'$'
no_3c501_msg	db	"No 3C501 found at that address.",CR,LF,'$'
ether_bdcst	db	6 dup(-1)	;ethernet broadcast address.
our_address	db	6 dup(?)	;temporarily hold our address

int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'

	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
	mov	bx,offset int_no_name
	call	get_number
	mov	di,offset io_addr
	mov	bx,offset io_addr_name
	call	get_number
	ret


no_3c501_error:
	mov	dx,offset no_3c501_msg
	mov	ah,9
	int	21h
	stc
	ret


	public	etopen
etopen:
;  Initialize the Ethernet board, set receive type.
;
;  check for correct EPROM location
;
;Pulse IE_RESET
	mov	al,IE_RESET
	loadport
	setport	IE_CSR
	out	dx,al

;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

	push	ds
	pop	es
	mov	di,offset our_address
	mov	cx,EADDR_LEN
	call	get_address
	mov	si,offset our_address
	mov	cx,EADDR_LEN
	call	set_address

;See if there really is a 3c501 there.
	mov	cx,EADDR_LEN
	mov	si,offset our_address
	mov	di,offset ether_bdcst
	repe	cmpsb
	jne	have_3c501		;not broadcast address -- must be real.
	jmp	no_3c501_error		;not there -- no 3c501.
have_3c501:

;Enable DMA/interrupt request, gain control of buffer
	mov	al,IE_RIDE or IE_SYSBFR
	loadport
	setport	IE_CSR
	out	dx,al

;Enable transmit interrupts
	mov	al,EDLC_16 or EDLC_JAM
	setport	EDLC_XMT
	out	dx,al

;Set up the receiver interrupts and flush status
	mov	al,EDLC_MULTI or EDLC_GOOD or EDLC_ANY or EDLC_SHORT or EDLC_DRIBBLE or EDLC_FCS or EDLC_OVER
	setport	EDLC_RCV
	out	dx,al
	in	al,dx			;flush status.

;Start receiver
	mov	ax,0			;Reset read pointer
	setport	IE_RP
	out	dx,ax
	mov	al,IE_RIDE or IE_RCVEDLC
	setport	IE_CSR
	out	dx,al

	mov	dx,offset end_resident
	clc
	ret


code	ends

	end
