[comp.protocols.tcp-ip.ibmpc] NI5210 Packet Driver for 871225.30+

nelson@sun.soe.clarkson.edu (Russ Nelson) (07/28/88)

Here is a packet driver for the Micom NI5210 that works under KA9Q's NET
version 871225.30 and successors.  It is freely copyable but may not
be sold for profit.  Perhaps someone would like to port it to the
3c501 and/or wd8003?  I don't have access to a wd8003, and I refuse to
use a 3c501.
-russ

;/* PC/FTP Packet Driver source, conforming to version 1.05 of the spec
;*  Russell Nelson, Clarkson University.  July 20, 1988
;*  Portions (C) Copyright 1988 Russell Nelson
;*
;*  Permission is granted to any individual or institution to use, copy,
;*  modify, or redistribute this software and its documentation provided
;*  this notice and the copyright notices are retained.  This software may
;*  not be distributed for profit, either in original form or in derivative
;*  works.  Russell Nelson makes no representations about the suitability
;*  of this software for any purpose.  RUSSELL NELSON GIVES NO WARRANTY,
;*  EITHER EXPRESS OR IMPLIED, FOR THE PROGRAM AND/OR DOCUMENTATION
;*  PROVIDED, INCLUDING, WITHOUT LIMITATION, WARRANTY OF MERCHANTABILITY
;*  AND WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE.
;*/


;/*
;*  NCSA Telnet source code
;*  National Center for Supercomputing Applications
;*  November 1, 1987
;*  (C) Copyright 1987 The Board of Trustees of the University of Illinois
;*
;*  Permission is granted to any individual or institution to use, copy,
;*  modify, or redistribute this software and its documentation provided
;*  this notice and the copyright notices are retained.  This software
;*  may not be distributed for profit, either in original form or in
;*  derivative works.  The University of Illinois makes no representations
;*  about the suitability of this software for any purpose.  
;*  THE UNIVERSITY OF ILLINOIS GIVES NO WARRANTY,
;*  EITHER EXPRESS OR IMPLIED, FOR THE PROGRAM AND/OR DOCUMENTATION PROVIDED,
;*  INCLUDING, WITHOUT LIMITATION, WARRANTY OF MERCHANTABILITY AND WARRANTY
;*  OF FITNESS FOR A PARTICULAR PURPOSE.
;*/
	TITLE	NETSUPPORT -- LOW LEVEL DRIVERS FOR ETHERNET
;
;  Assembler support for interrupt-driven Ethernet I/O on the PC
;
;  Tim Krauskopf
;  National Center for Supercomputing Applications
;  9/1/87  Ungermann-Bass driver started, PC bus
;  9/14/87 MICOM NI5210 driver started, PC bus
;
;
	NAME	NET

code	segment
	assume	cs:code, ds:code

	org	2ch
phd_environ	dw	?

	org	80h
phd_dioa	label	byte

	org	100h
start:
	jmp	start_1

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


;
;  Equates for controlling the MICOM board
;
;  I/O addresses, writing anything in AL trips these gates
;
;  First six addresses are the EPROM board Ether address (read)
;
IORESET	EQU	0		; reset the board
IOCA	EQU	1		; execute command which is in SCB
IODIS	EQU	2		; disable network connect
IOENA	EQU	3		; enable network
IOINTON	EQU	4		; enable interrupts
IOINTOF	EQU	5		; disable interrupts, '586 thinks it still ints
;
;  Structure elements specific to the Intel 82586 chip
;
BDBASE	EQU	1874		; base address for 30 buffer descriptors
BUFBASE	EQU	2174		; base address for 30 200 byte buffers
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
;
SCB	EQU	10		; system control block base
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
;
FDBASE	EQU	1214		; base addr for 30 frame descriptors
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
;
SCPTR	EQU	01ff6h		; hardwired address for SCP
ISCPTR	EQU	01feeh		; my address for ISCP, points to SCB
CCBPTR	EQU	26		; offset of configure command block
TCBPTR	EQU	44		; xmit CB offset
TBDPTR	EQU	60		; xmit BD offset
TBUFPTR	EQU	68		; xmit buffer offset
;
;  Data segment
;

firstfd		dw	FDBASE		; start of FD queue
lastfd		dw	0		; end of the FD chain
lastbd		dw	0		; end of the BD chain
io_addr		dw	0360h		; I/O address for card (jumpers)
base_addr	dw  	0d000h		; base segment for board (jumper set)
int_no		db	2,?		; interrupt number (jumper set)
packet_int_no	db	6ch,?		; interrupt to communicate.
is_at		dw	?		; =1 if we're on an AT.

;
;  data for configuring and setting up the MICOM 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	1		; 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

functions	label	word
	dw	driver_info
	dw	access_type
	dw	release_type
	dw	send_pkt
	dw	terminate
	dw	get_address
	dw	reset_interface

ni5210_name	db	'NI5210',0

MAX_P_LEN	equ	2		;IEEE 802.3 has max type length of 2.

per_handle	struc
in_use		db	?		;non-zero if this handle is in use.
packet_type	db	MAX_P_LEN dup(?);associated packet type.
packet_type_len	dw	?		;associated packet type length.
receiver	dd	?		;receiver handler.
per_handle	ends

max_handle	equ	2
handles		db	max_handle*(size per_handle) dup(0)
end_handles	label	byte

free_handle	dw	?		;->a handle not in use.
found_handle	dw	?		;->the handle for our packet.

regs	struc
_ES	dw	?
_DS	dw	?
_BP	dw	?
_DI	dw	?
_SI	dw	?
_DX	dw	?
_CX	dw	?
_BX	dw	?
_AX	dw	?
_IP	dw	?
_CS	dw	?
_F	dw	?		;flags, Carry flag is bit 0.
regs	ends


bytes	struc
	dw	?
	dw	?
	dw	?
	dw	?
	dw	?
_DL	db	?
_DH	db	?
_CL	db	?
_CH	db	?
_BL	db	?
_BH	db	?
_AL	db	?
_AH	db	?
bytes	ends

segmoffs	struc
offs		dw	?
segm		dw	?
segmoffs	ends


their_isr	dd	?

our_isr:
	jmp	our_isr_0		;the required signature.
	db	'PKT DRVR',0
our_isr_0:
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
	push	es
	mov	bx,cs			;set up ds.
	mov	ds,bx
	mov	bp,sp			;we use bp to access the original regs.
	and	_F[bp],not 1		;start by clearing the carry flag.
	mov	bl,ah			;jump to the correct function.
	mov	bh,0
	dec	bx			;first function is zero.
	cmp	bx,7			;highest function is 7 (now 6).
	jae	our_isr_2
	add	bx,bx
	jmp	functions[bx]
our_isr_2:
	mov	_DH[bp],BAD_COMMAND
our_isr_error:
	or	_F[bp],1		;return their carry flag.
our_isr_return:
	pop	es
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	iret


driver_info:
	call	verify_handle
	mov	_BX[bp],0		;version 0.
	mov	_CH[bp],1		;Ethernet
	mov	_DX[bp],11		;NI5210
	mov	_CL[bp],0		;number zero.
	mov	_DS[bp],ds
	mov	_SI[bp],offset ni5210_name
	mov	_AL[bp],1		;basic driver
	jmp	our_isr_return


access_type_class:
	mov	_DH[bp],NO_CLASS
	jmp	our_isr_error

access_type_type:
	mov	_DH[bp],NO_CLASS
	jmp	our_isr_error

access_type_number:
	mov	_DH[bp],NO_NUMBER
	jmp	our_isr_error

access_type:
	cmp	_AL[bp],1		;Ethernet?
	jne	access_type_class	;no.
	cmp	_BX[bp],-1		;generic type?
	je	access_type_2		;yes.
	cmp	_BX[bp],11		;NI5210 type?
	jne	access_type_type	;no.
access_type_2:
	cmp	_DL[bp],0		;generic number?
	je	access_type_3
	cmp	_DL[bp],1		;our number?
	jne	access_type_number
access_type_3:
	cmp	_CX[bp],0		;we'll let them specify zero.
	je	access_type_7
	cmp	_CX[bp],MAX_P_LEN	;IEEE 802.3 allows only MAX_P_LEN
	jne	access_type_inuse	;no - can't be ours.
access_type_7:

; now we do two things--look for an open handle, and check the existing
; handles to see if they're replicating a packet type.

	mov	free_handle,0		;remember no free handle yet.
	mov	bx,offset handles
access_type_4:
	cmp	[bx].in_use,0		;is this handle in use?
	je	access_type_5		;no - don't check the type.
	mov	es,_DS[bp]		;get a pointer to their type.
	mov	di,_SI[bp]
	mov	cx,_CX[bp]
	cmp	cx,[bx].packet_type_len	;lengths equal?
	jne	access_type_6		;no - keep looking.
	lea	si,[bx].packet_type
	or	cx,cx			;in case cx is zero.
	repe	cmpsb
	jne	short access_type_6	;go look at the next one.
access_type_inuse:
	mov	_DH[bp],TYPE_INUSE
	jmp	our_isr_error
access_type_5:
	cmp	free_handle,0		;found a free handle yet?
	jne	access_type_6		;yes.
	mov	free_handle,bx		;remember a free handle
access_type_6:
	add	bx,(size per_handle)	;go to the next handle.
	cmp	bx,offset end_handles
	jb	access_type_4

	mov	bx,free_handle		;did we find a free handle?
	or	bx,bx
	je	access_type_handle	;no - return error.

	mov	[bx].in_use,1		;remember that we're using it.

	mov	ax,_DI[bp]		;remember the receiver type.
	mov	[bx].receiver.offs,ax
	mov	ax,_ES[bp]
	mov	[bx].receiver.segm,ax

	push	ds
	mov	ax,ds
	mov	es,ax
	mov	ds,_DS[bp]		;remember their type.
	mov	si,_SI[bp]
	mov	cx,_CX[bp]
	mov	es:[bx].packet_type_len,cx	;remember the length.
	lea	di,[bx].packet_type
	rep	movsb
	pop	ds

	mov	_AX[bp],bx		;return the handle to them.

	jmp	our_isr_return


access_type_handle:
	mov	_DH[bp],BAD_HANDLE
	jmp	our_isr_error


release_type:
	call	verify_handle		;mark this handle as being unused.
	mov	[bx].in_use,0
	jmp	our_isr_return


send_pkt:
	mov	ax,base_addr
	mov	es,ax		; base for board

	push	ds		; set up proper ds for the buffer
	mov	ds,_DS[bp]	; address for buffer
	mov	si,_SI[bp]

	mov	cx,_CX[bp]	; count of bytes
	mov	dx,cx		; save a copy, might be less than 60, ok

	cmp	dx,60		; minimum length for Ether
	jnb	oklen
	mov	dx,60		; make sure size at least 60
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
	rep	movsb				; copy into buffer
;
;  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

	pop	ds			; get back my ds
	mov	dx,io_addr
	inc	dx
	out	dx,al		; issue CA to get it going

	jmp	our_isr_return


terminate:
	call	verify_handle
;don't really know what else to do here.
	mov	_DH[bp],CANT_TERMINATE
	jmp	our_isr_error


get_address:
	call	verify_handle
	mov	dx,io_addr		; Get our IO base address.
	mov	es,_ES[bp]		; get new one
	mov	di,_DI[bp]		; get pointer, es:di is ready

	mov	cx,6
	cmp	cx,_CX[bp]		;is there enough room?
	ja	get_address_space	;no.
	mov	_CX[bp],cx		;Tell them how long our address is.

	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

	jmp	our_isr_return

get_address_space:
	mov	_DH[bp],NO_SPACE
	jmp	our_isr_error


reset_interface:
	call	verify_handle
;do nothing--return with no error.
	jmp	our_isr_return


verify_handle:
;Ensure that their handle is real.  Jump to our_isr_error if it
;isn't real.
	mov	bx,_BX[bp]		;is this handle in range?
	cmp	bx,offset handles
	jb	verify_handle_bad	;no - must be bad.
	cmp	bx,offset end_handles
	jae	verify_handle_bad	;no - must be bad.
	cmp	[bx].in_use,0		;if it's not in use, it's bad.
	je	verify_handle_bad
	ret
verify_handle_bad:
	mov	_DH[bp],BAD_HANDLE
	add	sp,2			;pop off our return address.
	jmp	our_isr_error

their_recv_isr	dd	?

recv_isr:
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
	push	es
	mov	bx,cs			;ds = cs.
	mov	ds,bx
	mov	es,base_addr		;es = base_addr (of board).

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

	and	ax,0f000h		;isolate the ACK bits.
	mov	es:[SCB+SCOM],ax	;make a command to
					;acknowledge the interrupt.
	mov	dx,io_addr		;acknowledge the interrupts.
	inc	dx			; issue CA
	out	dx,al

	test	ah,40h			;did we receive a frame?
	je	recv_isr_2
	call	recv			;yes - receive any frames...
recv_isr_2:

;
; The following code is ruthlessly stolen from Phil Karn's NET package.
;
	cmp	is_at,1
	jnz	recv_isr_3	; Only one 8259, so skip this stuff
	mov	al,0bh		; read in-service register from
	out	0a0h,al		; secondary 8259
	nop			; settling delay
	nop
	nop
	in	al,0a0h		; get it
	or	al,al		; Any bits set?
	jz	recv_isr_3	; nope, not a secondary interrupt
	mov	al,20h		; Get EOI instruction
	out	0a0h,al		; Secondary 8259 (PC/AT only)
recv_isr_3:
	mov	al,20h			;acknowledge the interrupt.
	out	20h,al

	pop	es
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	iret


maskint:
	mov	dx,21h			;assume the master 8259.
	mov	cl,int_no
	cmp	cl,8			;using the slave 8259 on an AT?
	jb	mask_not_irq2
	mov	dx,0a1h			;go enable it on slave 8259
	sub	cl,8
mask_not_irq2:

	in	al,dx			;enable interrupts on the master 8259.
	mov	ah,1			;clear the bit.
	shl	ah,cl
	or	al,ah
	out	dx,al

	ret


unmaskint:
	mov	dx,21h			;assume the master 8259.
	mov	cl,int_no
	cmp	cl,8			;using the slave 8259 on an AT?
	jb	unmask_not_irq2
	mov	dx,0a1h			;go enable it on slave 8259
	sub	cl,8
unmask_not_irq2:

	in	al,dx			;enable interrupts on the master 8259.
	mov	ah,1			;clear the bit.
	shl	ah,cl
	not	ah
	and	al,ah
	out	dx,al

	ret


recv:
;  Get whatever packets are on the board
;
	mov	bx,firstfd	; get addr of first FD in list
	mov	ds,base_addr	; base for board
	assume	ds:nothing
;
;
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:
	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.

	mov	ax,0			;we have the size needed.
	push	bx
	push	cx

	push	cx			;save the packet length.

	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,6+6			;skip the ethernet addreses and
					;  point to the packet type.

	mov	bx,offset handles
find_type_1:
	cmp	[bx].in_use,0		;is this handle in use?
	je	find_type_2		;no - don't check the type.
	mov	ax,[bx].receiver.offs	;do they have a receiver?
	or	ax,[bx].receiver.segm
	je	find_type_2		;no - they're not serious about it.
	mov	cx,[bx].packet_type_len	;compare the packets.
	lea	si,[bx].packet_type
	or	cx,cx			;in case cx is zero.
	push	di
	repe	cmpsb
	pop	di
	je	find_type_3		;we've got it!
find_type_2:
	add	bx,(size per_handle)	;go to the next handle.
	cmp	bx,offset end_handles
	jb	find_type_1

	pop	cx			;we didn't find it -- discard it.
	xor	di,di			;"return" a null pointer.
	mov	es,di
	jmp	short find_type_4
find_type_3:
	pop	cx			; the packet_length

	mov	found_handle,bx		;remember what our handle was.
	mov	ax,0			;allocate request.
	call	[bx].receiver		;ask the client for a buffer.
find_type_4:
	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
	rep	movsb			;copy the bytes from this packet segment
	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
	assume	ds:nothing
	pop	cx

	push	bx
	mov	bx,found_handle
	mov	ax,1			;store request.
	call	code:[bx].receiver
	pop	bx

	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
;
;  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
	mov	dx,io_addr
	inc	dx			; issue CA
	out	dx,al
hasres:
	ret


; end of resident code.

HT	equ	09h
CR	equ	0dh
LF	equ	0ah

start_1:
	mov	dx,offset copyright_msg
	mov	ah,9
	int	21h

	mov	si,offset phd_dioa+1
	cmp	byte ptr [si],CR	;end of line?
	je	usage_error

	mov	di,offset packet_int_no
	call	get_number
	mov	di,offset int_no
	call	get_number
	mov	di,offset io_addr
	call	get_number
	mov	di,offset base_addr
	call	get_number

	mov	ax,0f000h		;ROM segment
	mov	es,ax
	cmp	word ptr es:[0fffeh],0fch	;is this an AT?
	jne	not_at			;no.

	inc	is_at			;yes - remember that we've got an AT.

	cmp	int_no,2		;map IRQ 2 to IRQ 9.
	jne	not_at
	mov	int_no,9
not_at:

	call	etopen			;init the ni5210.

	mov	ah,35h			;remember their packet interrupt.
	mov	al,packet_int_no
	int	21h
	mov	their_isr.offs,bx
	mov	their_isr.segm,es

	mov	ah,25h			;install our packet interrupt
	mov	dx,offset our_isr
	int	21h

	mov	ah,49h			;free our environment, because
	mov	es,phd_environ		;  we won't need it after this.
	int	21h

	mov	ah,31h			;terminate, stay resident.
	mov	dx,offset start_1+0fh
	mov	cl,4
	shr	dx,cl
	int	21h


timeout_error:
	mov	dx,offset timeout_msg
	jmp	short error
no_5210_error:
	mov	dx,offset no_5210_msg
	jmp	short error
usage_error:
	mov	dx,offset usage_msg
error:
	mov	ah,9
	int	21h
	int	20h

copyright_msg	db	"NI5210 Packet Driver.  Copyright 1988, Russell Nelson.",CR,LF
		db	"Derived from NCSA's NI5210 driver.  Don't pretend that you",CR,LF
		db	"wrote it, and don't try to make money off of it.",CR,LF,'$'
usage_msg	db	"usage: ni5210 <packet_int_no> <int_no> <io_addr> <base_addr>",CR,LF,'$'
no_5210_msg	db	"No 5210 found at that address.",CR,LF,'$'
timeout_msg	db	"Timed out while initializing 5210.",CR,LF,'$'

;******************************************************************
;  ETOPEN
;     Initialize the Ethernet board, set receive type.
;
;  usage:  etopen(s,irq,address,io_addr)
;           char s[6];       ethernet address
;           int irq;         (unused, we don't use no interrupts)
;           int address	     base mem address
;           int io_addr       io base address
;
etopen:
;
;  check for correct EPROM location
;
	mov	dx,io_addr	; i/o address
	add	dx,6
	in	al,dx
	mov	bl,al		; assemble pattern to check
	inc	dx
	in	al,dx
	mov	bh,al
	cmp	bx,05500h		; pattern known to be there in ROM
	jz	have_5210
	jmp	no_5210_error		;not there -- no 5210.
have_5210:
;
;  Initialize MICOM 5210
;
;  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
;
;  Install 16K SCP, just in case they have that much.
;
	mov	si,offset SCP	; get pre-set values
	mov	di,SCPTR+2000h	; offset for 16K board
	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
;
;  Turn off interrupts, I don't want them
;
	mov	dx,io_addr	; base for IO control
	add	dx,IOINTOF	; turn ints off
;	out	dx,al		; any value in ax
	sub	dx,IOINTOF	; return to base address = reset
	out	dx,al		; reset the chip
;
;  Issue a CA to initialize the chip after reset
;
	inc	dx
	out	dx,al		; CA
;
;  Disconnect from network
	inc	dx		; one greater than CA
	out	dx,al
;
;  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
	mov	dx,io_addr
	add	dx,IOCA			; CA
	out	dx,al			; send it
	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:
;
;  Next step, load our address into the board
;     reuses the space that the configure command used, with different command
;
	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)

;  move my addr onto board location inside IA command
;
	mov	dx,io_addr		; Get our IO base address
	mov	cx,6
store_address_1:
	in	al,dx			; get a byte of the eprom address
	stosb				; put it away
	inc	dx			; next register
	loop	store_address_1		; go back for rest

;
;  start the IA setup command
;
	mov	word ptr es:[SCB+SCOM],0100h	; do-command command
	mov	dx,io_addr
	add	dx,IOCA			; CA
	out	dx,al			; send it
	xor	cx,cx			; timeout
waitia:
	mov	ax,word ptr es:[CCBPTR]	; get status word
	test	ax,08000h		; is command complete?
	loopz	waitia
	jnz	iaok
	jmp	timeout_error
iaok:
;
;  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,30			; # 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,30
	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
;
;  configure to connect to network
;
	mov	dx,io_addr
	add	dx,IOENA		; enable network
	out	dx,al			; any al value
;
;  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
	mov	dx,io_addr
	inc	dx			; issue CA
	out	dx,al

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

	out	dx,al			;issue CA (dx still set from above)

;
; Now hook in our interrupt
;
	mov	ah,35h			;get the old interrupt into es:bx
	mov	al,int_no
	add	al,8
	cmp	al,8+8			;is it a slave 8259 interrupt?
	jb	set_recv_isr		;no.
	add	al,70h - 8 - 8		;map it to the real interrupt.
set_recv_isr:
	int	21h
	mov	their_recv_isr.offs,bx
	mov	their_recv_isr.segm,es

	mov	ah,25h			;now set our recv interrupt.
	mov	dx,offset recv_isr
	int	21h
;
; Now enable interrupts
;
	call	unmaskint

	ret


get_number:
;get a hex number from [si], skipping leading blanks.
get_number_0:
	lodsb				;skip blanks.
	cmp	al,' '
	je	get_number_0
	cmp	al,HT
	je	get_number_0
	dec	si
	cmp	al,CR			;is there really a number here?
	je	get_number_3

	xor	ah,ah			;get a hex number.
	xor	cx,cx
get_number_1:
	lodsb
	call	get_digit
	jc	get_number_2
	shl	cx,1
	shl	cx,1
	shl	cx,1
	shl	cx,1
	add	cx,ax
	jmp	get_number_1
get_number_2:
	dec	si
	mov	[di],cx			;store the parsed number.
	clc
	ret
get_number_3:
	stc
	ret


get_digit:
;enter with al = character
;return nc, al=digit, or cy if not a digit.
	cmp	al,'0'			;decimal digit?
	jb	get_digit_1		;no.
	cmp	al,'9'			;. .?
	ja	get_digit_2		;no.
	sub	al,'0'
	clc
	ret
get_digit_2:
	or	al,20h
	cmp	al,'a'			;hex digit?
	jb	get_digit_1
	cmp	al,'f'			;hex digit?
	ja	get_digit_1
	sub	al,'a'-10
	clc
	ret
get_digit_1:
	stc
	ret

comment \


/* Install hardware interrupt handler.
 * Takes IRQ numbers from 0-7 (0-15 on AT) and maps to actual 8086/286 vectors
 * Note that bus line IRQ2 maps to IRQ9 on the AT
 */
setirq(irq,handler)
unsigned irq;
void (*handler)();
{
	/* Set interrupt vector */
	if(irq < 8){
		setvect(8+irq,handler);
	} else if(irq < 16){
		isat = 1;
		setvect(0x70 + irq - 8,handler);
	} else {
		return -1;
	}
	return 0;
}
/* Return pointer to hardware interrupt handler.
 * Takes IRQ numbers from 0-7 (0-15 on AT) and maps to actual 8086/286 vectors
 */
void
(*getirq(irq))()
unsigned int irq;
{
	void (*getvect())();

	/* Set interrupt vector */
	if(irq < 8){
		return getvect(8+irq);
	} else if(irq < 16){
		return getvect(0x70 + irq - 8);
	} else {
		return NULLVFP;
	}
}
/* Disable hardware interrupt */
maskoff(irq)
unsigned irq;
{
	if(irq < 8){
		setbit(0x21,(char)(1<<irq));
	} else if(irq < 16){
		irq -= 8;
		setbit(0xa1,(char)(1<<irq));
	} else {
		return -1;
	}
	return 0;
}
/* Enable hardware interrupt */
maskon(irq)
unsigned irq;
{
	if(irq < 8){
		clrbit(0x21,(char)(1<<irq));
	} else if(irq < 16){
		irq -= 8;
		clrbit(0xa1,(char)(1<<irq));
	} else {
		return -1;
	}
	return 0;
}
/* Return 1 if specified interrupt is enabled, 0 if not, -1 if invalid */
getmask(irq)
unsigned irq;
{
	if(irq < 8)
		return (inportb(0x21) & (1 << irq)) ? 0 : 1;
	else if(irq < 16){
		irq -= 8;
		return (inportb(0xa1) & (1 << irq)) ? 0 : 1;
	} else
		return -1;
}

\

code	ends

	end	start
-- 
nelson@clutx.bitnet, nelson@clutx.clarkson.edu, uunet!clutx.clarkson.edu!nelson