version	equ	0

	include	defs.asm

;Ported from Tim Krauskopf's micnet.asm, an assembly language
;driver for the MICOM-Interlan NI5210 by Russell Nelson.  Any bugs
;are due to Russell Nelson.
;3c523 version Dan Lanciani ddl@harvard.* (received 5-18-89)
;Added Brad Clements' 1500 byte MTU, Russell Nelson.

;  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

;
;  Equates for controlling the 3c523 board
;
IOC	EQU	6

;
;  Structure elements specific to the Intel 82586 chip
;
BUF_COUNT EQU	28			; number of buffers
XMIT_MTU EQU	1500+14			; size of xmit buffer (1500 MTU +
					;   14 bytes for Ethernet headers

B	EQU	0c000h			; base offset for the board.
SCB	EQU	B + 10			; system control block base
CCBPTR	EQU	B + 26			; offset of configure command block
TCBPTR	EQU	B + 44			; xmit CB offset
TBDPTR	EQU	B + 60			; xmit BD offset
TBUFPTR	EQU	B + 68			; xmit buffer offset
FDBASE	EQU	TBUFPTR+XMIT_MTU	; base addr for 30 frame descriptors
FDSIZE  EQU     BUF_COUNT * 22
BDBASE	EQU	FDBASE+FDSIZE		; base address for 30 buffer descriptors
BDASIZE	EQU     BUF_COUNT * 10
BUFBASE	EQU	BDBASE+BDASIZE		; base address for 30 200 byte buffers
BUFFTOP EQU     BUFBASE+BUF_COUNT*200
ISCPTR	EQU	B + 03feeh		; my address for ISCP, points to SCB
SCPTR	EQU	B + 03ff6h		; hardwired address for SCP

if2
        %out Buffer top is BUFFTOP
if BUFFTOP > ISCPTR
        %out Warning, buffers extend beyond ISCPTR, reduce BUF_COUNT.
endif
endif
BDSTAT	EQU	0			; status word in BD
BDLINK	EQU	2			; 16pointer to next BD
BDPTR	EQU	4			; 24pointer to actual buffer
BDSIZE	EQU	8			; size of the buffer
;
SSTAT	EQU	0			; status word for SCB
SCOM	EQU	2			; command word in SCB
SCBL	EQU	4			; 16pointer to command block list
SRFA	EQU	6			; 16pointer to receive frame list
SERRS	EQU	8			; 4 words of error counts
;
FDSTAT	EQU	0			; status word for frame
FDEOL	EQU	2			; end of FD list flag
FDLINK	EQU	4			; 16pointer to next FD
FDPTR	EQU	6			; 16pointer to list of BD's
;
TSTAT	EQU	0			; status word for xmit
TCOM	EQU	2			; command to transmit
TLINK	EQU	4			; 16pointer to next command (always ffff)
TPTR	EQU	6			; 16pointer to xmit TBD
TTRIES	EQU	8			; number of transmit retries
;
;  Data segment
;

	public	int_no
int_no		db	3,0,0,0		; interrupt number
io_addr		dw	300h,0		; I/O address for card
base_addr	dw  	0c000h,0	; base segment for board

	public	driver_class, driver_type, driver_name
driver_class	db	1		;from the packet spec
driver_type	db	13		;from the packet spec
driver_name	db	"3C523",0	;name of the driver.

firstfd		dw	FDBASE		; start of FD queue
lastfd		dw	0		; end of the FD chain
lastbd		dw	0		; end of the BD chain


	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
	mov	ax,base_addr
	mov	es,ax			; base for board

	mov	word ptr es:[SCB+SCBL],TCBPTR; say where xmit command is

	cmp	cx,XMIT_MTU		; Is this packet too large?
	ja	send_pkt_toobig

	mov	dx,cx			; save a copy, might be less than 60, ok

	cmp	dx,RUNT			; minimum length for Ether
	jnb	oklen
	mov	dx,RUNT			; make sure size at least RUNT
oklen:
	mov	di,TBUFPTR		; start of xmit buffer

;
;  check for previous xmit
;
xwait:
	mov	bx,word ptr es:[SCB+SCOM]	; is command zeroed yet?
	or	bx,bx
	jnz	xwait				; not there yet, wait for it
;
;  move the data using word moves.
;
	call	movemem
;
;  put the correct size into the TDB
;
	or	dx,08000h			; end of frame bit flag
	mov	word ptr es:[TBDPTR],dx		; store it
	mov	word ptr es:[TCBPTR],0		; zero status wd
	mov	word ptr es:[TCBPTR+TCOM],08004h; xmit command in TCB
	mov	word ptr es:[SCB+SCOM],0100h	; execute command

	call	doca

	clc
	ret
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.
	assume	ds:code
	cmp	cx,EADDR_LEN		;make sure that we have enough room.
	jb	get_address_2
	mov	cx,EADDR_LEN
	mov	dx,io_addr		; Get our IO base address.
	cld
get_address_1:
	in	al,dx			; get a byte of the eprom address
	stosb				; put it away
	inc	dx			; next register
	loop	get_address_1		; go back for rest
	mov	cx,EADDR_LEN
	clc
	ret
get_address_2:
	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.
	mov	ds,base_addr	; base for board
	assume	ds:nothing

	mov	ax,ds:[SCB+SSTAT]	;get the status.
recv_isr_1:
	cmp	word ptr ds:[SCB+SCOM],0 ;are we done yet?
	jne	recv_isr_1		;no -- keep waiting.

	and	ax,0f000h		;isolate the ACK bits.
	mov	ds:[SCB+SCOM],ax	;make a command to
					;acknowledge the interrupt.
	call	doca
recv_isr_2:
	cmp	word ptr ds:[SCB+SCOM],0 ;are we done yet?
	jne	recv_isr_2		;no -- keep waiting.

;  Get whatever packets are on the board
;
	mov	bx,firstfd	; get addr of first FD in list
;
;
ckframe:
	mov	ax,[bx+FDSTAT]	; status word of frame
	test	ax,08000h	; frame written?
	jnz	okframe

	jmp	ru_start	; no, restore receiver if necessary
ptrupdate_j_1:
	jmp	ptrupdate
frame_bad_j_1:
	call	count_in_err
	jmp	frame_bad

;  we have a frame, read it in
;
okframe:

	test	ax,02000h		;check frame OK bit
	jz	frame_bad_j_1		;bad, fix it.
	mov	si,[bx+FDPTR]		;get pointer to buffer descriptor
	xor	cx,cx			;start with zero bytes.
countbuf:				;es:di is already set to receive packet
	mov	dx,si			;save a copy of current BD ptr
	mov	ax,[si+BDSTAT]		;get status and count word for BD
	test	ax,04000h		;is count field there?
	jz	ptrupdate_j_1		;no - we give up here.
	add	cl,al			;add the count into cx.
	adc	ch,0
	mov	si,[si+BDLINK]		;go to next BD in list
	test	ax,8000h		;is this the last frame?
	je	countbuf		;no - keep counting.

	push	bx
	push	cx

	mov	ax,cs			;we need ds = code.
	mov	ds,ax
	assume	ds:code

	mov	es,base_addr		;get a pointer to their type.
	mov	di,es:[bx+FDPTR]	;get pointer to buffer descriptor
	mov	di,es:[di+BDPTR]	;get offset of data
	add	di,EADDR_LEN+EADDR_LEN	;skip the ethernet addreses and
					;  point to the packet type.

	call	recv_find		;look up our type.

	pop	cx
	pop	bx
	mov	ds,base_addr		;restore ds to the board.
	assume	ds:nothing

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

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

	mov	si,[bx+FDPTR]		;get pointer to buffer descriptor
copybuf:
	mov	dx,si			;save a copy of current BD ptr
	xor	ch,ch			;200 bytes is largest this can be
	mov	cl,[si+BDSTAT]		;get count word for BD
	mov	si,[si+BDPTR]		;get offset of data
	call	movemem
	mov	si,dx			;get back current BD ptr
	test	[si+BDSTAT],8000h	;check EOF bit
	mov	si,[si+BDLINK]		;go to next BD in list
	jz	copybuf			;not done, keep copying it.

	pop	si			;now give the frame to the client.
	pop	ds
	pop	cx
	assume	ds:nothing

	call	recv_copy

	jmp	short ptrupdate

frame_bad:
;
;  we are done with the frame, do the list management
;
ptrupdate:
	push	cs
	pop	ds
	assume	ds:code
	mov	es,base_addr		; reload board segment

	mov	si,es:[bx+FDPTR]	; first BD in frame list
nextbd:
	mov	cx,es:[si+BDSTAT]	; count word for BD, EOF bit
	test	cx,08000h		; EOF bit, if set, save si in lastbd
	jnz	dolastbd
	mov	word ptr es:[si+BDSTAT],0 ; clear status word, EOF bit
	cmp	si,lastbd		; see if we are wrapping
	jz	dolastbd		; yes, just undo it
	mov	si,es:[si+BDLINK]	; follow link
	jmp	nextbd
dolastbd:
	mov	di,lastbd		; where end of BD list is now
	mov	lastbd,si		; store last known BD
	mov	word ptr es:[si+BDSIZE],08000h+200	; end of list here
	mov	word ptr es:[si+BDSTAT],0 ; clear status word, EOF bit
; size field for not end of list
	mov	word ptr es:[di+BDSIZE],200	; remove old end-of-list

;
;  update the FD list flags, new end-of-list
;
	mov	word ptr es:[bx+FDEOL],08000h	; store new EOL
	mov	word ptr es:[bx+FDSTAT],0	; clear status word for frame
	mov	di,lastfd		; get old end-of-list
	mov	word ptr es:[di+FDEOL],0 ; zero old one
	mov	lastfd,bx		; update stored pointer
	mov	si,es:[bx+FDLINK]	; where next fd is
	mov	firstfd,si		; store that info for next time

ru_start:
; re-start receive unit
;
;  check to see if the receiver went off because of no resources
;  and restart receiver if necessary
;
	push	cs
	pop	ds
	mov	es,base_addr
	mov	ax,es:[SCB+SSTAT]	; status word for SCB
	and	ax,070h		; receiver status
	cmp	al,020h		; receiver has no resources
	jnz	hasres

	call	count_out_err
;
;  setup lists for starting the RU on the chip
;  we know that there isn't anything in the buffer that we want
;

	mov	bx,firstfd	; get first FD on free list (assume free)
	mov	word ptr es:[SCB+SRFA],bx	; put into SCB
	mov	si,lastbd	; pointer to a BD, end of chain
	mov	ax,word ptr es:[si+BDLINK]	; pointer to next BD
	mov	word ptr es:[bx+FDPTR],ax	; set to start of BDs
;
;
;  Start the RU, doesn't need CB, only SCB parms.
;   command, to start receiving again
;
	mov	word ptr es:[SCB+SSTAT],0	; clear status word
	mov	word ptr es:[SCB+SCOM],010h	; start RU
	call	doca
hasres:
recv_isr_end:
	cmp	word ptr es:[SCB+SCOM],0 ;are we done yet?
	jne	recv_isr_end		;no -- keep waiting.
	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:

;  Next step, load our address into the board
;     reuses the space that the configure command used, with different command
;
	mov	es,base_addr		; set to base address
	mov	word ptr es:[SCB+SCBL],CCBPTR	; say where conf command is

	mov	di,CCBPTR		; start of config command block
	xor	ax,ax
	stosw				; zero status word for commmand
	mov	ax,8001h		; IA setup command + EL
	stosw
	xor	ax,ax
	dec	ax
	stosw				; set link value to -1 (unused)

	rep	movsb			; move their ethernet address in.
;
;  start the IA setup command
;
	mov	word ptr es:[SCB+SCOM],0100h	; do-command command
	call	doca
	xor	cx,cx			; timeout
set_address_1:
	mov	ax,word ptr es:[CCBPTR]	; get status word
	test	ax,08000h		; is command complete?
	loopz	set_address_1
	jnz	set_address_okay
	stc
	mov	dh,-1			; no error in the list applies.
	jmp	short set_address_done
set_address_okay:
	clc
set_address_done:
	push	cs
	pop	ds
	assume	ds:code
	ret


doca:
;we may be called from places in which ds is unknown.
	assume	ds:nothing
	loadport
	setport IOC
	mov	al, 0c7h
	pushf
	cli
	out	dx, al
	jmp	$+2
	jmp	$+2
	jmp	$+2
	mov	al, 087h
	out	dx, al
	popf
	ret
	assume	ds:code
;yet, we really should assume ds==code for the rest of this stuff.


end_resident	label	byte

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

	public	copyright_msg
copyright_msg	db	"Packet driver for the 3C523, version ",'0'+majver,".",'0'+version,CR,LF
		db	"Portions Copyright 1988 The Board of Trustees of the University of Illinois",CR,LF,'$'

timeout_msg	db	"Timed out while initializing 3C523.",CR,LF,'$'
int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'
base_addr_name	db	"Memory address ",'$'
our_address	db	6 dup(?)	;temporarily hold our address

	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:ax = number to print.
	extrn	hexout: near, decout: 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
	mov	di,offset base_addr
	mov	bx,offset base_addr_name
	call	get_number
	sub	base_addr, 0c00h
	ret


timeout_error:
	mov	dx,offset timeout_msg
error:
	mov	ah,9
	int	21h
	stc
	ret

;
;  data for configuring and setting up the board
;
;  chip always looks at SCP for config info which points to ISCP for the
;  pointer to the CONTROL BLOCK which handles everything from there.
;  Kind of indirect, but it works.
;
SCP	DB	0		; bus use flag
	DB	5 DUP(0)	; unused
	DW	ISCPTR		; 24pointer to ISCP offset
	DW	0		; high part
;
; Intermediate SCP
;
ISCP	DW	1		; busy flag
	DW	SCB		; 16pointer to SCB
	DW	0,0		; base for all 16 pointers, lo, hi
				; board is hardwired to 0 for these values
;
; Configuration block for 82586, this comprises one config command
;  Parameters taken from MICOM driver
;
CBCONF	DW	0		; status word
	DW	8002H		; end of command list + configure command
	DW	0ffffh		; link to next command (not used)
	DW	080CH		; fifo=8, byte count=C
	DW	2E00H		; important! Addr (AL) not inserted on the fly!
	DW	6000H		; IFS = 60h
	DW	0F200H		; retry=F, slot time=200h
	DW	0		; flags, set to 1 for promiscuous
	DW	40H		; min frame length=40h
;
; CB for xmit, followed by BD for xmit, copied together
;
TCB	DW	0		; status word
	DW	08004H		; command word for xmit + EL
	DW	0ffffh		; no command link
	DW	TBDPTR		; 16pointer to xmit BD
	DW	0,0,0,0		; no addressing used here
;
; BD template for xmit
TBD	DW	0
	DW	0		; next BD pointer, unused
	DW	TBUFPTR		; 24pointer to xmit buffer
	DW	0		; high part of pointer

	public	etopen
etopen:
;  Initialize the Ethernet board, set receive type.
	loadport
	setport	IOC
	mov	al,23h
	out	dx,al		; reset the chip
	jmp	$+2
	jmp	$+2
	jmp	$+2
	mov	al,63h
	out	dx,al		; reset the chip
	jmp	$+2
	jmp	$+2
	jmp	$+2
	mov	al,23h
	out	dx,al		; reset the chip
	jmp	$+2
	jmp	$+2
	jmp	$+2
;
;  Install 8K SCP, we only use 8K bytes no matter what
;
	mov	es,base_addr	; set to base address
	mov	di,SCPTR
	mov	si,offset SCP	; get pre-set values
	mov	cx,5		; 5 words
	rep	movsw		; install SCP
;
;  Intermediate SCP
;
	mov	si,offset ISCP	; addr of pre-set values
	mov	di,ISCPTR
	mov	cx,4		; 4 words
	rep	movsw		; install ISCP
;
;  Issue a CA to initialize the chip after reset
;
	call	lbca
;
;  configure 82586
;
	mov	si,offset CBCONF	; configure command
	mov	di,CCBPTR		; where command will reside
	mov	cx,9
	rep	movsw			; copy to board
;
;  issue the configure command
;
	mov	word ptr es:[SCB+SCOM],0100h	; do-command command
	mov	word ptr es:[SCB+SCBL],CCBPTR	; where conf command is
	mov	word ptr es:[SCB+SERRS],0	; zero errs field
	mov	word ptr es:[SCB+SERRS+2],0	; zero errs field
	mov	word ptr es:[SCB+SERRS+4],0	; zero errs field
	mov	word ptr es:[SCB+SERRS+6],0	; zero errs field
	call	lbca
	xor	cx,cx			; timeout
waitconf:
	mov	ax,word ptr es:[CCBPTR]	; get status word
	test	ax,08000h		; is command complete?
	loopz	waitconf
	jnz	confok
	jmp	timeout_error
confok:
;
;  Ask the board for the Ethernet address, and then use set_address to set it.
;
	mov	dx,io_addr		; Get our IO base address
	mov	si,offset our_address
	mov	cx,EADDR_LEN
store_address_1:
	in	al,dx			; get a byte of the eprom address
	mov	[si],al			; put it away
	inc	si
	inc	dx			; next register
	loop	store_address_1		; go back for rest

	mov	si,offset our_address
	mov	cx,EADDR_LEN
	call	set_address
	jnc	store_address_2
	jmp	timeout_error
store_address_2:
;
;  IA sent, setup all of the other data structures on the board
;  start with xmit command descriptors
;
	mov	si,offset TCB		; template for xmit
	mov	di,TCBPTR		; where it goes on board
	mov	cx,12			; copies CB and BD for xmit
	rep	movsw
;
;  Set up frame and buffer descriptors, 30 each
;
	mov	cx,BUF_COUNT		; # of FDs
	mov	di,FDBASE		; base addr for FDs
fdloop:
	xor	ax,ax
	mov	bx,di			; save pointer
	stosw				; clear status wd
	stosw				; clear EL field
	add	bx,22			; points to next one
	mov	es:[di],bx		; put in link ptr
	inc	di
	inc	di
	dec	ax
	stosw				; clear BD ptr to -1
	add	di,14
	loop	fdloop

	sub	di,20			; point back to last EL field
	mov	ax,08000h		; end of list
	stosw				; put into last FD
	sub	di,4			; back to beginning of last FD
	mov	lastfd,di		; save the pointer
	mov	word ptr es:[di+FDLINK],FDBASE	; make list circular,
						; from last to first

	mov	ax,BDBASE		; first BD
	mov	word ptr es:[FDBASE+FDPTR],ax	; put it in the first FD frame
;
;  now BDs
	mov	cx,BUF_COUNT
	mov	di,BDBASE		; start of BD area
	mov	dx,BUFBASE		; start of buffer area
bdloop:
	xor	ax,ax
	mov	bx,di			; save pointer
	stosw				; zero status field
	add	bx,10			; point to next record
	mov	es:[di],bx		; put in link ptr
	inc	di
	inc	di
	mov	es:[di],dx		; address of buffer, lo part
	inc	di
	inc	di
	stosw				; zero out high part
	mov	ax,200
	stosw				; store length field
	add	dx,ax			; add in length of buffer, updates ptr
	loop	bdloop

	sub	di,2			; back to last BD size field
	mov	ax,08000h+200		; end of list + 200
	stosw				; mark end of list
	sub	di,8			; back to last BDLINK field
	mov	ax,BDBASE
	stosw				; put link to beginning of list here
	sub	di,4			; back to beginning of last BD
	mov	lastbd,di		; save pointer to end of list
;
;  minor detail, but important
;  Change SCB command block pointer to setup for xmit commands
;      = only commands needed when operational
;
	mov	word ptr es:[SCB+SCBL],TCBPTR	; where xmit command is
;
;  Start the RU, doesn't need CB, only SCB parms.
;   command, to start receiving
;
	mov	word ptr es:[SCB],0		; clear status word
	mov	word ptr es:[SCB+SRFA],FDBASE	; set to frame descriptors
	mov	word ptr es:[SCB+SCOM],010h	; start RU
	call	lbca
;
; Now reset CX, FR, CNA, and RNR so that we don't get a spurious interrupt.
;
store_ack_1:
	cmp	word ptr es:[SCB+SCOM],0 ;are we done yet?
	jne	store_ack_1		;no -- keep waiting.

	mov	ax,es:[SCB+SSTAT]	;get the status.
	and	ax,0f000h		;isolate the ACK bits.
	mov	es:[SCB+SCOM],ax	;make a command to
					;acknowledge the interrupt.
	call	doca
;
; Now hook in our interrupt
;
	call	set_recv_isr

	mov	dx,offset end_resident
	clc
	ret

lbca:	loadport
	setport IOC
	mov	al, 0e3h
	out	dx, al
	jmp	$+2
	jmp	$+2
	jmp	$+2
	mov	al, 0a3h
	out	dx, al
	ret


code	ends

	end
