;History:152,1
;Sun Jan 05 22:13:40 1992 increased RST_IVAL to 7.
;Tue Feb 27 10:56:15 1990 send_pkt wasn't timing out properly.
version	equ	0

	include	defs.asm

;Ported from Philip Prindeville's arcnet driver for PCIP
;by Russell Nelson.  Any bugs are due to Russell Nelson.

;  Copyright, 1988-1992, 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	word public
	assume	cs:code, ds:code


;Registers:

;the following I/O addresses are mapped to the COM 9026
IMASK		equ	0		; writeable
STATUS		equ	0		; readable
COMMAND		equ	1
;the following I/O addresses are mapped to the 8253 counter/timer
CNTR0		equ	4
CNTR1		equ	5
CNTR2		equ	6
MODE		equ	7
;reading the following I/O addresse performs a software reset.
SW_RST		equ	8

; time needed to do various things (in clock ticks)
RST_IVAL	equ	7		;reset
SEND_IVAL	equ	4		;send

; ARP type for ARCnet
ARP_ARC		equ	007h

; broadcast address is nid 0
ARC_BCAST	equ	0

; packet sizes
ARC_MTU		equ	253
ARC_MnTU	equ	257
ARC_XMTU	equ	508
;
;status/interrupt mask bit fields
;
ST_TA		equ	001h		; transmitter available
ST_TMA		equ	002h		; transmitted msg. ackd
ST_RECON	equ	004h		; system reconfigured
ST_TEST		equ	008h		; test flag
ST_POR		equ	010h		; power-on-reset
ST_ETS1		equ	020h		; unused
ST_ETS2		equ	040h		; unused
ST_RI		equ	080h		; receiver inhibited

;
;in the command register, the following bits have these meanings:
;		0-2	command
;		3-4	page number (enable rvc/xmt)
;		 7	rcv b'casts


DSBL_XMT	equ	001h		; disable transmitter
DSBL_RCV	equ	002h		; disable receiver
ENBL_XMT	equ	003h		; enable transmitter
ENBL_RCV	equ	004h		; enable receiver
DFN_CONF	equ	005h		; define configuration
CLR_FLGS	equ	006h		; clear flags
LD_TST_FLG	equ	007h		; load test flags

; flags for clear flags operation

FL_POR		equ	008h		; power-on-reset
FL_RECON	equ	010h		; system reconfigured

; flags for load test flags operation

FL_TST		equ	008h		; test flag (diagnostic)

; byte deposited into first address of buffers when POR
TSTWRD		equ	0321Q

; handy macros for enable receiver/transmitter

BCAST		equ	080h		; receiver only
;PAGE		equ(nn)	((nn)<<3)

; flags for define configuration

CONF_NORM	equ	000h		; 1-253 byte packets
CONF_XTND	equ	008h		; 256-508 byte packets

; macros to access buffers
;BUF	equ(page)	(((custom.c_basemem) + 512 * (page)))

; ARCnet pseudo header -- note that syscode must occupy last byte...

arc_hdr	struc
arc_sid		db	?		; source, valid on rcv
arc_did		db	?		; destination, 0 = b'cast
arc_cp		db	?		; continuation pointer. zero
					; for extended packets
arc_xcp		db	?		; extended cp, see above
arc_syscode	db	?		; system code/pkt type
arc_hdr	ends

; designations for receiver/transmitter buffers.  sorry, no cleverness here
RCVPAGE		equ	2
XMTPAGE		equ	3

	public	int_no
int_no		db	5,0,0,0		; interrupt number.
io_addr		dw	02e0h,0		; I/O address for card (jumpers)
mem_base	dw	0d800h,0

	public	driver_class, driver_type, driver_name, driver_function, parameter_list
driver_class	db	8,0		;ARCnet (from the packet spec)
driver_type	db	1		;Datapoint RIM (from the packet spec)
driver_name	db	'ARCnet',0	;name of the driver.
driver_function	db	2
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	1	;length of MAC-layer address
	dw	507	;MTU, including MAC headers
	dw	MAX_MULTICAST * EADDR_LEN	;buffer size of multicast addrs
	dw	0	;(# of back-to-back MTU rcvs) - 1
	dw	0	;(# of successive xmits) - 1
int_num	dw	0	;Interrupt # to hook for post-EOI
			;processing, 0 == none,

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

	include	popf.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 es:di->upcall routine, (0:0) if no upcall is desired.
;  (only if the high-performance bit is set in driver_function)
;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

;Wait for transmitter ready.
	loadport
	setport	STATUS

	mov	ax,SEND_IVAL		;only wait this long for it.
	call	set_timeout		;  otherwise we can't send.
send_pkt_3:
	in	al,dx			;if not busy, exit.
	and	al,ST_TA
	jne	send_pkt_2
	call	do_timeout		;did we time out yet?
	jne	send_pkt_3		;no, not yet.

	setport	COMMAND			;stop the transmit.
	mov	al,DSBL_XMT
	out	dx,al
	mov	dh,CANT_SEND		;timed out, can't send.
	stc
	ret

send_pkt_toobig:
	mov	dh,NO_SPACE
	stc
	ret

send_pkt_2:
;store the packet on the board.
	mov	es,mem_base
	mov	di,XMTPAGE * 512
	movsw				;move the SID and DID to the board.
	sub	cx,2			;leave them out of the count.

	cmp	cx,ARC_XMTU		;is this one too big?
	ja	send_pkt_toobig		;yes, can't store it.
	cmp	cx,ARC_MTU		;is this one small enough
	jbe	send_pkt_1		;yes, just move it in.
	cmp	cx,ARC_MnTU		;is it *too* large AND *too* small?
	jae	send_pkt_5		;no.
	mov	cx,ARC_MnTU		;yes - use the larger size.
send_pkt_5:
	xor	al,al			;use a zero cp to indicate xcp.
	mov	ah,cl			;store the length in xcp.
	neg	ah
	stosw
	jmp	short send_pkt_4
send_pkt_1:
	mov	al,cl			;store the length in cp.
	neg	al
	stosb
send_pkt_4:
	mov	ax,di			;continue the put.
	mov	al,cl			;  advance the cp to its proper place.
	neg	al
	mov	di,ax
	call	movemem

;start the transmit.
	mov	al,ENBL_XMT or (XMTPAGE shl 3)
	loadport
	setport	COMMAND
	out	dx,al

	clc
	ret

;Set address on controller
	public	set_address
set_address:
	assume	ds:nothing
;enter with ds:si -> address, CX = length of address.
;exit with nc if okay, or cy, dh=error if any errors.
	mov	dh,CANT_SET
	stc
	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 ds:si ->list of multicast addresses, ax = number of 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	terminate
terminate:
	assume	ds:code
	loadport
	setport	IMASK
	mov	al,0
	out	dx,al

	setport COMMAND
	mov	al,DSBL_RCV
	out	dx,al
	mov	al,DSBL_XMT
	out	dx,al

	setport	STATUS			;do we need to do this [rnn]?
	in	al,dx

	ret


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


	include	timeout.asm

;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

	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

recv_1:
	loadport			;get the status to see if we got
	setport	STATUS			;  a false alarm.
	in	al,dx
	test	al,ST_RI
	je	recv_2			;yup, exit now.

	mov	es,mem_base
	mov	bx,RCVPAGE * 512

;decode data size.

	mov	cx,256			;compute the actual length.
	mov	al,es:[bx].arc_cp
	or	al,al			;is this a normal or continuation pkt?
	jne	recv_3			;go if normal.
	mov	cx,512			;extended packets have 512 max.
	mov	al,es:[bx].arc_xcp
recv_3:
	xor	ah,ah
	sub	cx,ax
	add	cx,2			;add in SID and DID.
	mov	bl,al			;use al as the low byte of the address.
	mov	di,bx
	mov	dl,driver_class
	call	recv_find		;look up our type.

	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

	mov	ds,mem_base		;copy the packet into their buffer.
	assume	ds:nothing
	mov	si,RCVPAGE * 512	;  (don't worry about ds.
	movsw				;move SID and DID.
	mov	ax,si
	lodsb				;get arc_cp.
	or	al,al			;extended?
	jne	recv_5			;no.
	lodsb				;yes - get arc_xcp.
recv_5:
	mov	si,ax			;set the new pointer.
	push	cx			;move the data part of the packet.
	sub	cx,2			;don't move the two we've already
	call	movemem			;  moved.
	pop	cx

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

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

recv_isr_9:

	loadport			;enable reception again.
	setport	COMMAND
	mov	al,ENBL_RCV or (RCVPAGE shl 3) or BCAST
	out	dx,al

	jmp	recv_1
recv_2:
	ret


	public	timer_isr
timer_isr:
;if the first instruction is an iret, then the timer is not hooked
	iret

;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
end_resident	label	byte
end_free_mem	label	byte

	public	usage_msg
usage_msg	db	"usage: arcnet [options] <packet_int_no> <hardware_irq> <io_addr> <mem_base>",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for the DataPoint RIM (ARCnet), version ",'0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,CR,LF
		db	"Portions Copyright 1988 Philip Prindeville",CR,LF,'$'

no_arcnet_msg	db	"No ARCnet found at that address.",CR,LF,'$'
failed_test_msg	db	"Failed self test.",CR,LF,'$'

int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'
mem_base_name	db	"Memory 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 -> name of word, di -> dword to print.
	extrn	print_number: near

;=length of our address.
	extrn	address_len: word

;-> the assigned address of the card.
	extrn	rom_address: byte

	public	parse_args
parse_args:
;exit with nc if all went well, cy otherwise.
	mov	di,offset int_no
	call	get_number
	mov	di,offset io_addr
	call	get_number
	mov	di,offset mem_base
	call	get_number
	clc
	ret


no_arcnet_error:
	mov	dx,offset no_arcnet_msg
	stc
	ret
failed_test_error:
	mov	dx,offset failed_test_msg
	stc
	ret


	public	etopen
etopen:
;reset the board via the I/O reset port, then wait for it to become sane again.

	mov	ax,mem_base		;test the memory first.
	mov	cx,2048
	call	memory_test
	jne	no_arcnet_error

	mov	es,mem_base

	loadport
	setport SW_RST
	in	al,dx

	mov	ax,RST_IVAL
	call	set_timeout
etopen_1:
	call	do_timeout
	jne	etopen_1

	setport	STATUS
	in	al,dx

;since we've just reset:
;	reset the POR flag,
;	check the diagnostic byte in the buffer,
;	grab the node ID, and assign it to the host number.

	test	al,ST_POR
	je	etopen_2

	setport	COMMAND
	mov	al,CLR_FLGS or FL_POR or FL_RECON
	out	dx,al

	mov	al,es:[0]
	cmp	byte ptr es:[0],TSTWRD
	je	etopen_3
	jmp	failed_test_error	;failed power on self-test.
etopen_3:
	mov	al,es:[1]
	mov	rom_address,al
	mov	address_len,ARCADDR_LEN
etopen_2:

;another simple diagnostic:
;	force test flag on in RIM,
;	check to see that it is set,
;	reset it.

	loadport
	setport	COMMAND
	mov	al,LD_TST_FLG or FL_TST
	out	dx,al

	setport STATUS
	in	al,dx

	test	al,FL_TST
	jne	etopen_4
	jmp	failed_test_error	;failed forced self-test.
etopen_4:
	setport	COMMAND
	mov	al,LD_TST_FLG
	out	dx,al
	setport STATUS
	in	al,dx

	pushf
	cli

	call	set_recv_isr

;now we enable the board to interrupt
;us on packet received.  Not transmiter available
;(i.e. transmission complete).  We don't have
;any control over POR, since it is NMI...
;RECON seems useless.

	loadport
	setport	IMASK
	mov	al,ST_RI
	out	dx,al

	; we should allow extended packets
	setport	COMMAND
	mov	al,DFN_CONF or CONF_XTND
	out	dx,al

	mov	al,ENBL_RCV or (RCVPAGE shl 3) or BCAST;
	out	dx,al

	popf

	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.

	clc
	ret

	public	print_parameters
print_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
	mov	di,offset mem_base
	mov	dx,offset mem_base_name
	call	print_number
	ret

	include	memtest.asm

code	ends

	end
