version	equ	0
;History:916,1

; Copyright, 1990-1992, Russell Nelson, Crynwr Software

;   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.

	.286				;the tcenet requires a 286.

comment \

We have to lock all the data structures accessed by the board.  We
also have to keep track of which ones have been locked and which have not.

\

	include	defs.asm

pause_	macro
;	jmp	$+2
;
; The reason for the pause_ macro is to establish a minimum time between
; accesses to the card hardware. The assumption is that the fetch and execution
; of the jmp $+2 instruction will provide this time. In a fast cache machine
; this may be a false assumption. In a fast cache machine, there may be 
; NO REAL TIME DIFFERENCE between the two I/O instruction streams below:
;
;	in	al,dx		in	al,dx
;	jmp	$+2
;	in	al,dx		in	al,dx
;
; To establish a minimum delay, an I/O instruction must be used. A good rule of
; thumb is that ISA I/O instructions take ~1.0 microseconds and MCA I/O
; instructions take ~0.5 microseconds. Reading the NMI Status Register (0x61)
; is a good way to pause on all machines.
;
; The National 8390 Chip (NIC) requires 4 bus clocks between successive
; chip selects (National DP8390 Data Sheet Addendum, June 1990 -- it took them
; long enough to figure this out and tell everyone) or the NIC behaves badly.
; Therefor one I/O instruction should be inserted between each successive
; NIC I/O instruction that could occur 'back - to - back' on a fast cache
; machine.
;   - gft - 910529
;
	push	ax
	in	ax, 61h
	pop	ax
;
endm

DMA_8MASK_REG	equ	0Ah
DMA_8MODE_REG	equ	0Bh
DMA_16MASK_REG	equ	0D4h
DMA_16MODE_REG	equ	0D6h

CASCADE_MODE      equ	0C0h
SET_DMA_MASK      equ	4
DMA_CHANNEL_FIELD equ	3

;tcenet registers.
EBASE		equ	0
CONFIG		equ	0eh
PORTPAGE	equ	0fh
SONICREG	equ	10h

;we don't use setport to refer to Sonic registers, because of the crufty
;port paging.
sncport	macro	new_port_no
;	if ((new_port_no xor port_no) and (not 0fh)) or (port_no EQ 0)

	loadport
		setport	PORTPAGE
		push	ax
		mov	al, (new_port_no shr 1)
		out	dx,al
		in	al,61h		;see pause_ macro.
		pop	ax

;	endif

	setport	<(SONICREG+(new_port_no and 0fh))>
	endm


RDA_COUNT	equ	8

TRANSMIT_BUF_COUNT	equ	1
RECEIVE_BUF_COUNT	equ	2
TRANSMIT_BUF_SIZE	equ	1520
RECEIVE_BUF_SIZE	equ	1522*2

EOL		equ	1		;descriptor EOL flag

;-----------------------------------------------------------------------------
;
;	Sonic Register Definitions

;	Sonic command register bits

LCAM	equ	0200h			;Load CAM
RRRA	equ	0100h			;Read RRA
RST	equ	0080h			;Software Reset
ST	equ	0020h			;Start Timer
STP	equ	0010h			;Stop Timer
RXEN	equ	0008h			;Receiver Enable
RXDIS	equ	0004h			;Receiver Disable
TXP	equ	0002h			;Transmit packets
HTX	equ	0001h			;Halt transmission

;	Sonic Data Configuration Register Bits

EXBUS	equ	8000h			;Extended Bus mode
LBR	equ	2000h			;Latched Bus Retry
PO1	equ	1000h			;Programmable Outputs
PO0	equ	0800h			;. .
SBUS	equ	0400h			;Synchronous Bus Mode
USR1	equ	0200h			;User Definable Pins
USR0	equ	0100h			;. .
WC1	equ	0080h			;Wait State Control
WC0	equ	0040h			;. .
DW32	equ	0020h			;Data Width Select
BMS	equ	0010h			;Block Mode Select for DMA
RFT1	equ	0008h			;Receive FIFO Threshold
RFT0	equ	0004h			;. .
TFT1	equ	0002h			;Transmit FIFO Threshold
TFT0	equ	0001h			;. .

;	Sonic Receive Control Register bits

ERR	equ	8000h			;Accept Packet with Errors
RNT	equ	4000h			;Accept Runt Packets
BRD	equ	2000h			;Accept Broadcast Packets
PRO	equ	1000h			;Accept all Physical Packets (Promiscuous Mode)
AMC	equ	0800h			;Accept all Multicast Packets
LB1	equ	0400h			;Loopback Control 1
LB0	equ	0200h			;Loopback Control 0
MCRx	equ	0100h			;Multicast Packet Received
BCRx	equ	0080h			;Broadcast Packet Received
LPKT	equ	0040h			;Last Packet in RBA
CRS	equ	0020h			;Carrier Sense Activity
COL	equ	0010h			;Collision Activity
CRCR	equ	0008h			;CRC Error
FAER	equ	0004h			;Frame Alignment Error
LBK	equ	0002h			;LoopBack Packet Received
PRX	equ	0001h			;Packet Received OK

;	Transmit Control Register bits

PINTR	equ	8000h			;Programmable Interrupt
POWC	equ	4000h			;Program 'Out of Window Collision' Timer
CRCI	equ	2000h			;CRC Inhibit
EXDIS	equ	1000h			;Disable Excessive Deferral Timer
EXD	equ	0400h			;Excessive Deferral
DEF	equ	0200h			;Deferred Transmission
NCRS	equ	0100h			;No CRS
CRSL	equ	0080h			;CRS Lost
EXC	equ	0040h			;Excessive Collisions
OWC	equ	0020h			;Out of Window Collision
PMB	equ	0008h			;Packet Monitored Bad
FU	equ	0004h			;Tx FIFO UnderRun
BCM	equ	0002h			;Byte Count Mismatch
PTX	equ	0001h			;Packet Transmitted OK

TXDONE	equ	EXD or CRSL or EXC or PTX	;nonzero when transmit done.

;	Interrupt Mask Register bits

BREN	equ	4000h			;Bus Retry Occurred Enable
HBLEN	equ	2000h			;HeartBeat Lost Enable
LCDEN	equ	1000h			;Load CAM Done Interrupt Enable
PINTEN	equ	0800h			;Programmable Interrupt Enable
PRXEN	equ	0400h			;Packet Received Enable
PTXEN	equ	0200h			;Packet Transmitted OK Enable
TXEREN	equ	0100h			;Transmit Error Enable
TCEN	equ	0080h			;Timer Complete Enable
RDEEN	equ	0040h			;Receive Descriptors Exhausted Enable
RBEEN	equ	0020h			;Receive Buffers Exhausted Enable
RBAEEN	equ	0010h			;Receive Buffer Area Exceeded Enable
CRCEN	equ	0008h			;CRC Tally Counter Warning Enable
FAEEN	equ	0004h			;FAE Tally Counter Warning Enable
MPEN	equ	0002h			;MP Tally Counter Warning Enable
RFOEN	equ	0001h			;Receive FIFO Overrun Enable

; these are the interrupts we enable:
enabled_IMR	equ	(PTXEN or TXEREN or PRXEN or RBEEN or BREN)

;	Interrupt Status Register bits

BR	equ	4000h			;Bus Retry Occurred
HBL	equ	2000h			;HeartBeat Lost
LCD	equ	1000h			;Load CAM Done
PINT	equ	0800h			;Programmable Interrupt
PKTRX	equ	0400h			;Packet Received
TXDN	equ	0200h			;Transmission Done
TXER	equ	0100h			;Transmit Error
TC	equ	0080h			;Timer Complete
RDE	equ	0040h			;Receive Descriptors Exhausted
RBE	equ	0020h			;Receive Buffers Exhausted
RBAE	equ	0010h			;Receive Buffer Area Exceeded
CRC	equ	0008h			;CRC Tally Counter Rollover
FAE	equ	0004h			;FAE Tally Counter Rollover
MP	equ	0002h			;Missed Packet Tally Counter Rollover
RFO	equ	0001h			;Receive FIFO Overrun

;SONIC registers.  The registers are sixteen-bit registers that are also
;byte-addressable.  So, the word addresses must be multiplied by two.

;	Command and Status Register Offsets

SonicCR		equ	(0 * 2)
SonicDCR	equ	(1 * 2)
SonicRCR	equ	(2 * 2)
SonicTCR	equ	(3 * 2)
SonicIMR	equ	(4 * 2)
SonicISR	equ	(5 * 2)

;	Transmit Registers

SonicUTDA	equ	(6 * 2)
SonicCTDA	equ	(7 * 2)

;	Receive Registers

SonicURDA	equ	(0dh * 2)
SonicCRDA	equ	(0eh * 2)
SonicEOBC	equ	(13h * 2)
SonicURRA	equ	(14h * 2)
SonicRSA	equ	(15h * 2)
SonicREA	equ	(16h * 2)
SonicRRP	equ	(17h * 2)
SonicRWP	equ	(18h * 2)
SonicRSC	equ	(2bh * 2)

;	CAM Registers

SonicCEP	equ	(21h * 2)
SonicCAP2	equ	(22h * 2)
SonicCAP1	equ	(23h * 2)
SonicCAP0	equ	(24h * 2)
SonicCE		equ	(25h * 2)
SonicCDP	equ	(26h * 2)
SonicCDC	equ	(27h * 2)

;	Tally Counters

SonicCRCT	equ	(2ch * 2)
SonicFAET	equ	(2dh * 2)
SonicMPT	equ	(2eh * 2)

;	Watchdog Counters

SonicWT0	equ	(29h * 2)
SonicWT1	equ	(2ah * 2)

;	Silicon Revision

SonicSR		equ	(28h * 2)

;Receiver Resource Area

rra_struc	struc
rra_ptr		dd	?
rra_cnt		dd	?
rra_struc	ends

;Receiver Descriptor Area

rda_struc	struc
rda_status	dw	?
rda_byte_count	dw	?
rda_ptr		dd	?
rda_seq_number	dw	?
rda_link	dw	?
rda_in_use	dw	?
rda_backlink	dw	?		;for our own purposes, not SONIC's.
rda_forelink	dw	?		;for our own purposes, not SONIC's.
rda_struc	ends

;	Transmit Descriptor structure

tda_struc	struc
tda_status	dw	?
tda_config	dw	?
tda_size	dw	?
tda_frag_count	dw	?
tda_frag_ptr	dd	?
tda_frag_size	dw	?
tda_link	dw	?
tda_struc	ends

; Content-Addressible Memory descriptor.

cam_struc	struc
cam_entry_ptr	dw	0
cam_addr0	dw	0
cam_addr1	dw	0
cam_addr2	dw	0
cam_struc	ends

dds_struc	struc
dds_size	dd	0		;region size.
dds_offset	dd	0		;offset (using 32 bits)
dds_seg		dw	0		;segment or selector
dds_buffer_id	dw	0
dds_physical	dd	0		;physical address
dds_struc	ends

;MAX8023LENGTH		equ	1514
MAX8023LENGTH		equ	1520

code	segment	para public
	assume	cs:code, ds:code

	public	int_no
int_no		db	3,0,0,0		;must be four bytes long for get_number.
io_addr		dw	-1,-1
port_addr	dw	?
dma_no		db	?

	public	driver_class, driver_type, driver_name, driver_function, parameter_list
driver_class	db	BLUEBOOK, IEEE8023, 0		;from the packet spec
driver_type	db	89		;from the packet spec
driver_name	db	'TCENET',0	;name of the driver.
driver_function	db	2		;basic, extended
parameter_list	label	byte
	db	1	;major rev of packet driver
	db	9	;minor rev of packet driver
	db	14	;length of parameter list
	db	EADDR_LEN	;length of MAC-layer address
	dw	GIANT	;MTU, including MAC headers
	dw	MAX_MULTICAST * EADDR_LEN	;buffer size of multicast addrs
	dw	RECEIVE_BUF_COUNT-1	;(# of back-to-back MTU rcvs) - 1
	dw	TRANSMIT_BUF_COUNT-1	;(# of successive xmits) - 1
int_num	dw	0	;Interrupt # to hook for post-EOI
			;processing, 0 == none,

		align	2
	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	;only ours.
		dw	rcv_mode_3	;ours plus broadcast
		dw	0		;some multicasts
		dw	rcv_mode_5	;all multicasts
		dw	rcv_mode_6	;all packets

my_dds		dds_struc<>
vds_active	db	?		;<>0 if memory mapping is on.

tdaptr		dw	tda1		;-> the TDA's we're using.
rraptr		dw	rra1
camptr		dw	cam1		;-> the CAM we're using.
camcount	dw	1		;always has our address.
rdaptr		dw	rda1
next_rda	dw	?

	include	timeout.asm
	include	movemem.asm

	public bad_command_intercept
bad_command_intercept:
;called with ah=command, unknown to the skeleton.
;exit with nc if okay, cy, dh=error if not.
	mov	dh,BAD_COMMAND
	stc
	ret

	public	as_send_pkt
; The Asynchronous Transmit Packet routine.
; Enter with es:di -> i/o control block, ds:si -> packet, cx = packet length,
;   interrupts possibly enabled.
; Exit with nc if ok, or else cy if error, dh set to error number.
;   es:di and interrupt enable flag preserved on exit.
as_send_pkt:
	ret

	public	drop_pkt
; Drop a packet from the queue.
; Enter with es:di -> iocb.
drop_pkt:
	assume	ds:nothing
	ret

	public	xmit
; Process a transmit interrupt with the least possible latency to achieve
;   back-to-back packet transmissions.
; May only use ax and dx.
xmit:
	assume	ds:nothing
	ret


	public	send_pkt
send_pkt:
;enter with 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	bx,tdaptr

	mov	ax,18
	call	set_timeout
	loadport
	sncport	SonicCR
send_pkt_1:
	in	ax,dx
	test	ax,TXP
	je	send_pkt_2		;zero means yes, done transmitting.
;	test	code:[bx].tda_status,TXDONE	;Is the SONIC done transmitting it?
;	jne	send_pkt_2		;any non-zero means yes.
	call	do_timeout
	jne	send_pkt_1
	mov	dh,CANT_SEND
	stc
	ret
send_pkt_2:

	mov	ax,cx			;store the count.
	cmp	ax,RUNT			; minimum length for Ether
	ja	oklen
	mov	ax,RUNT			; make sure size at least RUNT
oklen:
	mov	code:[bx].tda_frag_size,cx
	mov	code:[bx].tda_size,cx

	mov	code:[bx].tda_config,0

	mov	ax,code:[bx].tda_frag_ptr.offs	;store the packet.
	mov	dx,code:[bx].tda_frag_ptr.segm
	call	phys_to_segmoffs
	call	movemem

	lea	di,code:[bx].tda_status	;get the address of the TDA.
	movseg	es,cs
	call	segmoffs_to_phys
	loadport			;point the CTDA to the TDA.
	sncport	SonicCTDA
	out	dx,ax
	sncport	SonicCR			;command SONIC to transmit.
	mov	ax,TXP
	out	dx,ax
	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
	loadport			; Get our Ethernet address base.
	setport	EBASE
	cld
get_address_1:
	insb				; get a byte of the eprom address
	inc	dx			; next register
	loop	get_address_1		; go back for rest
	mov	cx,EADDR_LEN
	clc
	ret
get_address_2:
	stc
	ret


	public	set_address
set_address:
;enter with ds:si -> Ethernet address, CX = length of address.
;exit with nc if okay, or cy, dh=error if any errors.
	assume	ds:nothing
	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:

	movseg	es,cs
	mov	di,camptr		;point di to our Ethernet adddr.
	add	di,2
	rep	movsb

	movseg	ds,cs
	assume	ds:code

	call	load_cam		;initialize with our new address.
	mov	dh,CANT_SET		;just in case.
	jc	set_address_done
set_address_okay:
	mov	cx,EADDR_LEN		;return their address length.
	clc
set_address_done:
	ret

load_cam:
	loadport
	sncport	SonicCR
load_cam_1:
	in	ax,dx			;wait for transmit done.
	test	ax,TXP
	jne	load_cam_1		;nonzero means still transmitting.

	movseg	es,ds
	mov	di,camptr		;point the Descriptor Pointer Register
	call	segmoffs_to_phys
	sncport	SonicCDP		;  to our CAM area.
	out	dx,ax
	pause_

	sncport	SonicCDC		;load the CAM descriptor table length.
	mov	ax,camcount
	out	dx,ax
	pause_

	sncport	SonicCR
	mov	ax,LCAM			;load the CAM.
	out	dx,ax

	ret


rcv_mode_2:
	mov	ax,0			;accept only mine
	jmp	short rcv_mode_set
rcv_mode_3:
	mov	ax,BRD			;accept mine + broadcast
	jmp	short rcv_mode_set
rcv_mode_5:
	mov	ax,AMC			;accept any multicast frames.
	jmp	short rcv_mode_set
rcv_mode_6:
	mov	ax,RNT or PRO or BRD or AMC	;promiscuous mode (runts also)
rcv_mode_set:
	loadport
	sncport	SonicRCR
	out	dx,ax
	pause_
	mov	ax,RXEN			;enable reception.
	jmp	short rcv_mode_set_1
rcv_mode_1:
	mov	ax,RXDIS		;disable the receiver.
rcv_mode_set_1:
	loadport			;enable or disable reception.
	sncport	SonicCR
	out	dx,ax
	pause_
	ret


	public	set_multicast_list
set_multicast_list:
;enter with ds:si ->list of multicast addresses, ax = number of addresses,
;  cx = address byte count.
;return nc if we set all of them, or cy,dh=error if we didn't.

	mov	cx,ax
	inc	ax			;one more for our Ethernet address.
	mov	camcount,ax
	mov	ax,1
	mov	di,camptr
	add	di,8			;skip first entry (our address).
set_multicast_list_1:
	stosw				;store the CAM entry number.
	movsw				;move the address over.
	movsw
	movsw
	loop	set_multicast_list_1

	mov	cx,camcount		;compute the enable mask.
	mov	ax,-1			;shift zeroes in, then turn them
	shl	ax,cl
	not	ax			;  into ones.
	stosw				;store the enable mask.

	call	load_cam		;now stick it to the hardware.

	mov	dh,NO_MULTICAST		;for some reason we can't do multi's.
	stc
	ret


	public	terminate
terminate:
	loadport
	sncport SonicCR
	mov	ax,RST or STP or RXDIS	;reset the sonic, stop timer, stop rcv.
	out	dx,ax

;This routine will remove the (host) DMA controller from
;cascade mode of operation.
	mov	al,dma_no
	or	al,SET_DMA_MASK
	cmp	dma_no,4		;If channel 5 or 6,
	ja	terminate_16		;  use sixteen bit dma.
terminate_8:
	out	DMA_8MASK_REG,al
	jmp	short terminate_done
terminate_16:
	out	DMA_16MASK_REG,al
terminate_done:

; unlock the dma block.
	mov	di,offset my_dds
	mov	ax,cs
	mov	es,ax
	mov	dx,0
	mov	ax,8104h
	int	4bh

	ret

	public	reset_interface
reset_interface:
;reset the interface.
	assume	ds:code
	ret


;called when we want to determine what to do with a received packet.
;enter with cx = packet length, es:di -> packet type, dl = packet class.
	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

;call this routine to schedule a subroutine that gets run after the
;recv_isr.  This is done by stuffing routine's address in place
;of the recv_isr iret's address.  This routine should push the flags when it
;is entered, and should jump to recv_exiting_exit to leave.
;enter with ax = address of routine to run.
	extrn	schedule_exiting: near

;recv_exiting jumps here to exit, after pushing the flags.
	extrn	recv_exiting_exit: near

;enter with dx = amount of memory desired.
;exit with nc, dx -> that memory, or cy if there isn't enough memory.
	extrn	malloc: 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

	loadport
	sncport	SonicIMR
	in	ax,dx
	pause_
	mov	cx,ax			;remember the interrupt mask.
	xor	ax,ax			;clear interrupt requests.
	out	dx,ax
	pause_
	sncport	SonicISR
	in	ax,dx			;now match the IMR against the ISR.
	and	cx,ax
	jnz	recv_1			;if they overlap, then we have a real interrupt.
	ret				;spurious interrupt.

recv_1:
;check out and get rid of interrupt causes.

	test	ax,PKTRX		;packet received?
	jne	recv_2			;yes.
	jmp	recv_rbe		;no, go see if we ran out of buffers.
recv_2:
	and	ax,PKTRX or RBAE	;clear the PKTRX and RBAE flags.
	out	dx,ax

; handle received packet

recv_do_next:
	mov	bx,next_rda		;point to the rda that the sonic filled.
	cmp	[bx].rda_in_use,0	;did they really fill it?
	je	recv_do_next_1		;yes.
	jmp	recv_rbe
recv_do_next_1:
  if 0
	cmp	TossRxFlag,0		;should we toss this one?
	je	recv_do_next_2		;no.
	mov	TossRxFlag,0		;yes, just toss it.
	jmp	recv_err
recv_do_next_2:
  endif
	test	ax,RBAE			;did we exceed the size of the buffer?
	je	recv_do_next_3		;no.
;;;	inc	ReceiveBufferExceeded	;yes.
	jmp	recv_err
recv_do_next_3:
	mov	ax,[bx].rda_status
	test	ax,CRCR			;checksum error?
	je	recv_do_next_4		;no.
;;;	inc	CRCErrors
	jmp	recv_err
recv_do_next_4:
	test	ax,FAER			;alignment error?
	je	recv_do_next_5		;no.
;;;	inc	FrameAlignmentErrors
	jmp	recv_err

recv_do_next_5:
	test	ax,PRX			;otherwise okay?
	jne	recv_do_next_6		;yes.
	jmp	recv_err

recv_do_next_6:

; get logical address of the buffer.

	mov	cx,[bx].rda_byte_count		; bp := # of bytes received
	sub	cx,4			;leave the FCS behind.
	mov	ax,[bx].rda_ptr.offs
	mov	dx,[bx].rda_ptr.segm
	call	phys_to_segmoffs	;make es:di -> packet.

	push	bx
	push	es
	push	di

	add	di,EADDR_LEN+EADDR_LEN	;skip the ethernet addreses and
					;  point to the packet type.
	mov	dl, BLUEBOOK		;assume bluebook Ethernet.
	mov	ax, es:[di]
	xchg	ah, al
	cmp 	ax, 1500
	ja	BlueBookPacket
	inc	di			;set di to 802.2 header
	inc	di
	mov	dl, IEEE8023
BlueBookPacket:
	push	cx
	call	recv_find
	pop	cx

	pop	si
	pop	ds
	pop	bx
	assume	ds:nothing

	mov	ax,es			;is this pointer null?
	or	ax,di
	je	recv_free		;yes - just free the frame.

	push	es
	push	di
	push	cx
	call	movemem
	pop	cx
	pop	si
	pop	ds
	assume	ds:nothing

	call	recv_copy

	jmp	short recv_free

recv_err:
	call	count_in_err
recv_free:
	movseg	ds,cs
	assume	ds:code

	test	[bx].rda_status,LPKT	;is it the last packet in the RBA?
	je	recv_not_last		;no, go deal with it.

	push	bx			;we filled up all the RDAs, have
	call	update_rwp		;  to clean up.
	pop	bx

recv_not_last:
	or	[bx].rda_link,EOL	; this RDA is now new EOL.
	mov	[bx].rda_in_use,0ffffh	; sonic now has this one.

	mov	si,[bx].rda_backlink
	and	[si].rda_link,not EOL	;clear EOL in previous RDA.

	sncport	SonicCR
	mov	ax,RXEN
	out	dx,ax			; re-enable Receive

	mov	bx,[bx].rda_forelink	; now move to next rda.
	mov	next_rda,bx

	sncport	SonicISR		; setup for next check
	in	ax,dx
	jmp	recv_do_next

recv_rbe:
;we get here with dx = SonicISR
	in	ax,dx			; get interrupt status
	test	ax,RBE			; is it a Receive Buf Exhaust?
	jz	recv_txdn

; SONIC thinks its out of receive bufs.  ReRead the RDA

;	inc	RxBufferExhaustedCount
	call	update_rwp

	loadport
	sncport	SonicISR
	mov	ax,RBE
	out	dx,ax				; clr int & reread the RDA
	pause_

recv_txdn:
;we get here with dx = SonicISR
	in	ax,dx
	and	ax, (TXDN or TXER)	;done sending pkts?
	jz	recv_txdn_1		;go if not.
	pause_
	out	dx,ax			;clear this cause.
recv_txdn_1:
;we get here with dx = SonicISR
;	cli
	add	dx,-2			;point dx to SonicIMR
	mov	ax,enabled_IMR		;re-enable our ints.
	out	dx,ax

	ret


update_rwp:
	loadport
	sncport	SonicRSA
	in	ax,dx
	pause_
	mov	si,ax			;get the RSA into si.

	sncport	SonicREA
	in	ax, dx
	pause_
	mov	di,ax			;get the REA into di.

	sncport	SonicRWP
	in	ax,dx			; get RWP

	add	si,(RECEIVE_BUF_COUNT * (size rra_struc))/2
					; assume we'll set @ 1/2
	cmp	ax,di			; if at end set to 1/2
	je	update_rwp_1
	mov	si,di			; else its at 1/2 - set to end
update_rwp_1:
	mov	ax,si
	out	dx,ax			; update RWP
	pause_

	ret


phys_to_segmoffs:
;enter with dx:ax as the physical address of the buffer,
;exit with es:di -> buffer.
	cmp	vds_active,0
	jne	phys_to_segmoffs_1
	shl	dx,16-4			;move the upper four bits into position.
	mov	di,ax			;now get the low 12 bits of the segment.
	shr	di,4
	or	dx,di			;combine them.
	mov	es,dx
	mov	di,ax
	and	di,0fh			;now compute the offset.
	ret
phys_to_segmoffs_1:
	sub	ax,my_dds.dds_physical.offs	;make into physical addresses.
	add	ax,offset begin_dma
	mov	di,ax			;make dx:ax be offset into dma region.
	movseg	es,cs
	ret


segmoffs_to_phys:
;enter with es:di -> buffer.
;exit with dx:ax as the physical address of the buffer,
	cmp	vds_active,0
	jne	segmoffs_to_phys_1
	mov	dx,es			;get the high 4 bits of the segment,
	shr	dx,16-4
	mov	ax,es			;and the low 12 bits of the segment.
	shl	ax,4
	add	ax,di			;add in the offset.
	adc	dx,0
	ret
segmoffs_to_phys_1:
	xor	dx,dx			;make dx:ax be offset into dma region.
	mov	ax,di
	sub	ax,offset begin_dma
	add	ax,my_dds.dds_physical.offs	;make into physical addresses.
	adc	dx,my_dds.dds_physical.segm
	ret

	public	timer_isr
timer_isr:
;if the first instruction is an iret, then the timer is not hooked
	iret

;beginning of the area of memory that the SONIC knows about (and that we will
;  have to lock down for DMA).
begin_dma	label	byte

;we have two tda's because one of them might cross a physical 64K boundary.
;If that happens, then we use the other.
	align	2
tda1	db	(TRANSMIT_BUF_COUNT * size tda_struc) dup(?)
	align	2
tda2	db	(TRANSMIT_BUF_COUNT * size tda_struc) dup(?)

;the rra and cam share a common upper 16 bits of address, therefore they
;must be on the same 64K physical segment.  There's also alignment
;considerations.
	align	2
rra1	dw	RECEIVE_BUF_COUNT * 4 dup(?)
cam1	dw	0			;our address is always zero.
	db	EADDR_LEN dup(?)	;leave room for our address.
	dw	1			;for the CAM Enable register.
	db	(EADDR_LEN+2)*15 dup(?)		;leave room for up to 15 more.
RRA1SIZE	equ	$ - rra1

	align	2
rra2	dw	RECEIVE_BUF_COUNT * 4 dup(?)
cam2	dw	0			;our address is always zero.
	db	EADDR_LEN dup(?)	;leave room for our address.
	dw	1			;for the CAM Enable register.
	db	(EADDR_LEN+2)*15 dup(?)		;leave room for up to 15 more.

;we have two rda's because one of them might cross a physical 64K boundary.
;If that happens, then we use the other.
	align	2
rda1	db	(RDA_COUNT * size rda_struc) dup(?)
	align	2
rda2	db	(RDA_COUNT * size rda_struc) dup(?)

;any code after this will not be kept.  Buffers used by the program, if any,
;are allocated from the memory between end_resident and end_free_mem.
	public end_resident,end_free_mem
	align	4			;just for efficiency's sake.
end_resident	label	byte
	db	(RECEIVE_BUF_COUNT*RECEIVE_BUF_SIZE) + (TRANSMIT_BUF_COUNT*TRANSMIT_BUF_SIZE) dup(?)
end_free_mem	label	byte
;end of the area of memory that the SONIC knows about (and that we will
;  have to lock down for DMA).


	public	usage_msg
usage_msg	db	"usage: tcenet [options] <packet_int_no> <int_no> <io_addr>",CR,LF,'$'
no_board_msg	db	"No tcenet detected.",CR,LF,'$'
io_addr_funny_msg	label	byte
		db	"No tcenet detected, continuing anyway.",CR,LF,'$'
bad_reset_msg	db	"Unable to reset the tcenet.",CR,LF,'$'
bad_init_msg	db	"Unable to initialize the tcenet.",CR,LF,'$'
vds_ver_msg	db	"Using VDS version ",'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for a Thomas-Conrad tcenet, version ",'0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,CR,LF
		db	'$'

io_addrs	dw	100h,120h,140h,160h,300h,320h,340h,0
io_addrs_end	label	word
int_nos		db	2,3,5,7,10,11,12,15	;interrupt numbers.
dma_nos		db	1,5,6,7		;dma channel numbers

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
	extrn	maskint: near

;enter with si -> argument string, di -> dword to store.
;if there is no number, don't change the number.
	extrn	get_number: near

;enter with dx -> name of word, di -> dword to print.
	extrn	print_number: near

	extrn	decout: near
	extrn	chrout: near

;print a crlf
	extrn	crlf: near

	public	parse_args
parse_args:
;exit with nc if all went well, cy otherwise.
	assume	ds:code
	mov	di,offset int_no
	call	get_number
	mov	di,offset io_addr
	call	get_number
	clc
	ret


	public	etopen
etopen:
	assume	ds:code

	cmp	io_addr,-1		;Did they ask for auto-detect?
	je	find_board

	call	detect_board		;no, just verify its existance.
	je	find_board_found

	mov	dx,offset io_addr_funny_msg
	mov	ah,9
	int	21h

	jmp	find_board_found

find_board:
	mov	bx,offset io_addrs	;Search for the Ethernet address.
	mov	io_addr+2,0
find_board_0:
	mov	ax,[bx]
	mov	io_addr,ax
	call	detect_board
	je	find_board_found
find_board_again:
	add	bx,2			;not at this port, try another.
	cmp	bx,offset io_addrs_end	;last address?
	jb	find_board_0

	mov	dx,offset no_board_msg	;Tell them that we can't find it.
	mov	ah,9
	int	21h

	stc
	ret
find_board_found:

	loadport
	sncport SonicCR
	mov	ax,RST or STP or RXDIS	;reset the sonic, stop timer, stop rcv.
	out	dx,ax
	pause_

	setport	CONFIG			;get the configuration register and
	in	ax,dx			;  determine the interrupt number.
	and	ax,7
	mov	bx,ax
	mov	al,int_nos[bx]
	mov	int_no,al

;This routine will put the (host) DMA controller into
;cascade mode of operation.

	in	ax,dx			;get the dma channel field.
	shr	ax,3
	and	ax,3
	mov	bx,ax
	mov	al,dma_nos[bx]
	mov	dma_no,al
	mov	ah,al			;save a copy.
	and	al,DMA_CHANNEL_FIELD
	or	al,SET_DMA_MASK
	cmp	ah,4			;If channel 5 or 6,
	ja	dma_16			;  use sixteen bit dma.
dma_8:
	out	DMA_8MASK_REG,al	;mask the channel off.
	mov	al,ah
	or	al,CASCADE_MODE		;put it into cascade mode.
	out	DMA_8MODE_REG,al
	mov	al,ah			;turn the channel on.
	out	DMA_8MASK_REG,al
	jmp	short dma_done
dma_16:
	and	ah,DMA_CHANNEL_FIELD	;turn off the extra bits.
	out	DMA_16MASK_REG,al	;mask the channel off.
	mov	al,ah
	or	al,CASCADE_MODE		;put it into cascade mode.
	out	DMA_16MODE_REG,al
	mov	al,ah			;turn the channel on.
	out	DMA_16MASK_REG,al
dma_done:

	mov	al, int_no		; Get board's interrupt vector
	add	al, 8
	cmp	al, 8+8			; Is it a slave 8259 interrupt?
	jb	set_int_num		; No.
	add	al, 70h - 8 - 8		; Map it to the real interrupt.
set_int_num:
	xor	ah, ah			; Clear high byte
	mov	int_num, ax		; Set parameter_list int num.

	mov	al,int_no
	call	maskint			;disable these interrupts.

	loadport
	sncport SonicCR
	xor	ax,ax			;take sonic out of reset.
	out	dx,ax
	pause_

	sncport	SonicDCR
	mov	ax,PO1 or USR1 or WC1 or WC0 or BMS or RFT1 or TFT0
	out	dx,ax
	pause_

	sncport	SonicRCR
	mov	ax,BRD			; accept broadcast
	out	dx,ax
	pause_

	sncport	SonicTCR
;;;voodoo programming -- these bits are read-only.
;;;	mov	ax,04eh 			; national's value
	xor	ax,ax			;normal Ethernet.
	out	dx,ax
	pause_

	sncport	SonicIMR
	xor	ax,ax			; disable all, for now.
	out	dx,ax
	pause_

	sncport	SonicISR
	mov	ax,-1			; clear all
	out	dx,ax
	pause_

;
; now we lock down the memory that we'll be using for DMA.  That is,
; assuming we're running under some sort of memory mapper.
;
	mov	ax,40h			;is some program providing VDS?
	mov	es,ax
	test	byte ptr es:[7bh],20h
	jz	memory_locked		;no.

	mov	ax,08102h		;get version information.
	mov	dx,1010b		;no auto-remap, copy data into buffer.
	int	4bh
	cmp	ax,8102h		;did it change into version number?
	je	memory_locked		;no, must not be there...

	push	ax
	mov	dx,offset vds_ver_msg
	mov	ah,9
	int	21h
	pop	ax

	push	ax
	mov	al,ah			;print major version number.
	xor	ah,ah
	xor	dx,dx
	call	decout
	mov	al,'.'
	call	chrout
	pop	ax			;print minor version number.
	xor	ah,ah
	xor	dx,dx
	call	decout
	call	crlf

	mov	vds_active,1

	movseg	es,cs
	mov	di,offset my_dds
	mov	dx,0
	call	malloc
	sub	dx,offset begin_dma
	mov	[di].dds_size.offs,dx
	mov	[di].dds_size.segm,0
	mov	[di].dds_offset.offs,offset begin_dma
	mov	[di].dds_offset.segm,0
	mov	[di].dds_seg,cs
	mov	[di].dds_buffer_id,0
	mov	ax,08103h		;request that the memory be locked.
	mov	dx,1010b		;no auto-remap, copy data into buffer.
	int	4bh

memory_locked:


;
; We need to keep the all the TDAs in the same physical 64K segment.  So, if the
; first crosses a segment, the second cannot, so we'll use it.
;
	mov	di,offset tda1
	movseg	es,cs
	call	segmoffs_to_phys
	add	ax,TRANSMIT_BUF_COUNT * (size tda_struc)
	jnc	no_tda_overflow
	mov	di,offset tda2
no_tda_overflow:
	mov	tdaptr,di		;remember where the tda's are.

	mov	cx,TRANSMIT_BUF_COUNT
	mov	bx,di
init_tdas:
	mov	dx,TRANSMIT_BUF_SIZE
	call	malloc
	mov	di,dx
	mov	[bx].tda_size,0
	mov	[bx].tda_status,0
	mov	[bx].tda_frag_count,1	;packet driver spec only allows one.
	mov	[bx].tda_link,EOL

	call	segmoffs_to_phys	;convert es:di into physical address.
	mov	[bx].tda_frag_ptr.offs,ax	;store the packet's physical address.
	mov	[bx].tda_frag_ptr.segm,dx
	add	bx,(size tda_struc)
	loop	init_tdas

; init transmit regs UTDA

	mov	di,tdaptr
	call	segmoffs_to_phys	; get physical
	push	dx			;save the high word.
	loadport
	sncport	SonicUTDA
	pop	ax			; hi word phys
	out	dx,ax

;
; We need to keep the all the RDAs in the same physical 64K segment.  So, if the
; first crosses a segment, the second cannot, so we'll use it.
;
	mov	si,offset rda1
	mov	ax,si
	call	segmoffs_to_phys
	add	ax,RDA_COUNT * (size rda_struc)
	jnc	no_rda_overflow
	mov	si,offset rda2
no_rda_overflow:
	mov	rdaptr,si		;remember which one we're using
	mov	next_rda,si

	mov	di,rdaptr
	mov	cx,RDA_COUNT
	xor	si,si
init_rda_1:
	push	di
	add	di,(size rda_struc)
	call	segmoffs_to_phys	;get physical offset of next link.
	pop	di

	mov	[di].rda_link,ax	;make a pointer to the next.
	mov	[di].rda_backlink,si	;make a pointer to the previous.
	mov	[di].rda_in_use,0ffffh	;set ownership to SONIC.
	lea	ax,[di]+(size rda_struc)
	mov	[di].rda_forelink,ax	;make a pointer to the next.
	mov	si,di			;remember where we were.
	add	di,(size rda_struc)	;point di to next.
	loop	init_rda_1

	mov	di,rdaptr
	mov	[si].rda_forelink,di	;succ(end) = begin.
	mov	[di].rda_backlink,si	;prev(begin) = end.
	call	segmoffs_to_phys	;get physical offset of first link.

	mov	[si].rda_link,ax
	or	[si].rda_link,EOL	;mark this one as the last.

;set SONIC receive descriptor area pointers.

	push	dx			;save upper word.
	loadport
	sncport	SonicCRDA		;set current
	out	dx,ax
	pause_
	sncport	SonicURDA		;set upper.
	pop	ax			;restore upper word (pushed as dx).
	out	dx,ax

; Init the RRA.  The RRA and CAM share the same upper address bits, so they,
; like all the TDAs and RDAs, must be in the same physical segment.

	mov	si,offset rra1
	mov	ax,si
	call	segmoffs_to_phys	;get the physical address
	add	ax,RRA1SIZE		;fits in one segment?
	jnc	no_rra_overflow		;yes.
	mov	si, offset rra2		;no, the second *must*.
no_rra_overflow:
	mov	rraptr,si		;save the pointer to the right rra.
	add	si,cam1-rra1		;compute the pointer to the CAM.
	mov	camptr,si

	mov	bx,rraptr		;point di to our rra.
	mov	cx,RECEIVE_BUF_COUNT
InitRRALoop:
	mov	dx,RECEIVE_BUF_SIZE
	call	malloc
	mov	di,dx
	call	segmoffs_to_phys	; get physical

	mov	[bx].rra_ptr.offs,ax	;low word address
	mov	[bx].rra_ptr.segm,dx	;high word address
	mov	[bx].rra_cnt.offs,MAX8023LENGTH	;count low word
	mov	[bx].rra_cnt.segm,0	;count high word.
	add	bx,(size rra_struc)
	loop	InitRRALoop

	loadport
	sncport	SonicEOBC
	mov	ax, (MAX8023LENGTH)/2	;count of words.
	out	dx,ax
	pause_

	mov	di,rraptr		;init the pointers.
	call	segmoffs_to_phys

	push	ax			;preserve the low word.
	mov	ax,dx
	loadport
	sncport	SonicURRA
	out	dx,ax			;high word.
	pause_
	pop	ax

	sncport	SonicRSA		;output the low word.
	out	dx,ax

	sncport	SonicRRP
	out	dx,ax

	add	ax,cam1-rra1		;point to the end of the RRA.
	sncport	SonicREA
	out	dx,ax			;set the end pointer.

	setport	SonicRWP
	out	dx,ax			; write reg gets it

; Init SONIC internal counters

	mov	ax,-1			;clear registers (inverted on write)
	sncport	SonicCRCT
	out	dx,ax			;CRC Tally
	sncport	SonicFAET
	out	dx,ax			;FAE Tally
	sncport	SonicMPT
	out	dx,ax			;Missed Packet Tally
;take SONIC out of reset.
	sncport	SonicCR
	xor	ax,ax
	out	dx,ax
	pause_

	mov	ax, RRRA		;Start the RRA.
	out	dx,ax

; get our address out of ROM, and stuff it into the CAM.
	movseg	es,ds
	mov	di,offset our_address
	mov	cx,EADDR_LEN
	call	get_address
	mov	si,offset our_address
	mov	cx,EADDR_LEN
	call	set_address

	loadport
	sncport	SonicCR
	mov	ax,RXEN			;Enable receive.
	out	dx,ax

	sncport	SonicIMR		;Enable interrupts.
	mov	ax,enabled_IMR
	out	dx,ax

	call	set_recv_isr

	clc
	ret

	public	print_parameters
print_parameters:
;echo our command-line parameters
	mov	di,offset int_no
	mov	dx,offset int_no_name
	call	print_number
	mov	di,offset io_addr
	mov	dx,offset io_addr_name
	call	print_number
	ret

;TCC's manufacturer prefix is 13:80:00.  They've also used the token
;ring version of the prefix (bit-reversed) , which is 00:01:8c.  Oops.

detect_board:
;test to see if a board is located at io_addr.
;return nz if not.
	loadport
	setport	EBASE
	in	ax,dx
	setport	EBASE+2
	cmp	ax,00h + 01h*256
	jne	detect_board_1
	in	al,dx
	cmp	al,0c8h
	ret
detect_board_1:
	cmp	ax,13h + 80h*256
	jne	detect_board_exit
	in	al,dx
	cmp	al,0
detect_board_exit:
	ret

code	ends

	end
