version	equ	1

	include	defs.asm	;SEE ENCLOSED COPYRIGHT MESSAGE

; PARnet PC/FTP Packet Driver Source, conforming to version 1.08 of spec.
; Ported by S.A.Pechler (S.A.Pechler@bdk.tue.nl) from the Amiga's PARnet
; device. 
; Original Amiga code is Copyright (c) 1989 by Matthew Dillon.
; 
; Date: July 10, 1993

; Permission is granted to any individual or institution to use,copy,
; modify or redistribute this software provided this notice is retained.
; The authors make no guarantee to the suitability of the software for
; any purpose. Any damage caused by using this program is the responsibility
; of the user and not the authors. 

code	segment	word public
	assume	cs:code, ds:code

;	PARnet definitions

BUFSIZE		EQU 4096
PARPORT         EQU 4          ; This is 1024 in 2 byte network order (0400h)
TX_HDRSIZE      EQU 8
ARP_TYPE	EQU 0608h      ; ARP packet type identifier in host order

; Private Data
; ------------
; Driver definitions

ether_arp_frame	db 0,0,0,0,0
arp_srcaddr	db ?		; target ethernet address (myself)
		db 0,0,0,0,0,0	; source ethernet address (fake)
ether_arp_type	db 8,6		; type field (0806h)
		db 0,1		; hardware type (1 = ethernet)
		db 8,0		; protocol type ( 0800h = IP addresses)
		db 6		; hardware address length (6 for ethernet)
		db 4		; address length for high level (4 for IP addr)
		db 0,2		; operation: response
arp_sendhard	db 0,0,0,0,0,?	; sender hardware address
arp_sendIP	db 0,0,0,0	; sender IP address
arp_targhard	db 0,0,0,0,0,?	; target hardware address (myself)
arp_targIP	db 0,0,0,0	; target IP address (myself)

ether_arp_end label byte

ARP_LENGTH EQU ether_arp_end-ether_arp_frame

; Receiver buffer (contains PARnet packet header)
rx_buffer	db BUFSIZE dup(?)  ; receiver buffer (max 64 kb).
rx_header	EQU rx_buffer
rx_port		EQU rx_buffer	; ! port number is in network order.
rx_dlen		EQU rx_buffer+6 ; ! last 2 bytes of length (is in network
				; ! order!).
rx_data		EQU rx_buffer+8

; pointer to receiver buffer (given to upper level process).
;rx_offset       dw rx_data              ; buffer data offset

; transmitter packet header
tx_header       dw PARport      ; portnumber is fixed to 1024 (in network order)
		dw 0		; checksum
                dw 0            ; length of datafield; it should be a DD
tx_dlen         EQU this word
tx_dlenh        db 0            ; but on PC's a PARnet packet can't be
tx_dlenl        db 0            ; larger than 64 kb.

; pointer to transmitter buffer
tx_data		EQU this dword
tx_offset	dw	?	; offset of host transmit buffer 
tx_segment	dw	?	; segment of host transmit buffer 
tx_length	dw	?	; packet length

tx_dest         db      ?       ; physical destination address

output_active   db      0       ; flag, packet being written.

; Public Data
; -----------

public	int_no,io_addr,mem_base

int_no 	db	2,0,0,0			;must be four bytes long for get_number.
io_addr dw	0378h,0			;io addr for parallel card (default)
mem_base dw	0,0			;share memory addr (not used)

public	driver_class, driver_type, driver_name, driver_function, parameter_list

driver_class    db      1,0,0,0         ;no class specified for PARnet,
                                        ;using class for serial line (the
					;trailing zero's are for the
					;print_number function
driver_type     db      0,0,0,0         ;no specific type.
driver_name	db	'PARnet',0	;name of the driver.  
driver_function	db	2		;basic and extended functions present.
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	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      7               ;number of receive modes in our table.
                dw      0,0,rcv_mode_2,0,0,0,rcv_mode_6

; Private routines
; ----------------

; See PARKERN.ASM

include parsubr.asm

; Public routines
; ---------------

	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

	;a PARnet network can't handle broadcasts, but ARP-requests will be
	;'answerred' by making an ARP-response with as hardware address the
	;LSB of the destination IP-address.

	mov	al,[si+EADDR_LEN-1]	; get LSB of destination hard.address
	cmp	al,-1			; is it 0xff?
	jne	send_pkt_nobrd		; no, just send the packet
	mov	ax,[si+EADDR_LEN+EADDR_LEN]	; get packet type
	cmp	ax,ARP_TYPE		; is it an ARP-packet?
	jne	send_pkt_err		; no, can't handle this packet
 
	; no need to save es:di here ?

	; swap sender & targer address fields:

	; 1. put sender addresses into target address fields
	add	si,22		; let SI point to sender's hardware address
	mov	cx,EADDR_LEN+4	; copy both the hardware & IP addresses
	mov	ax,cs
	mov	es,ax		; mov es,cs ; is this needed?
	mov	di,OFFSET arp_targhard
	rep	movsb

	; 2. put target IP address into sender IP address field
	add	si,EADDR_LEN	; skip target hardware address
	mov	cx,4		; IP address length
	mov	di,OFFSET arp_sendIP
	rep	movsb

	; now calculate the target hardware address from the last octet of
	; the IP address.
	mov	al,[si-1]
	mov	arp_sendhard+5,al

	; prepare arp response for the receiver

        mov     ax,cs                     ; ds=cs align
        mov     ds,ax
        assume  ds:code
        mov     es,ax                     ; for es:di pointer
        mov     di,offset ether_arp_type  ; pointer to type field
        mov     si,offset ether_arp_frame ; pointer to start of frame
        mov     cx,ARP_LENGTH             ; packet length
        jmp     recv_reply_arp     ; Pass the arp response to the receiver

send_pkt_nobrd:
	assume	ds:nothing

        mov     tx_segment,ds      ; tx->data

        mov     bx,cs              ; cs = ds align
        mov     ds,bx

        assume  ds:code

        mov     tx_dest,al         ; hardware destination address.
	mov	tx_offset,si	   ; tx_offset ->segment offset 
        mov     tx_length,cx       ; packet data length (host order)


        ;Create a PARnet packet header
	;(At the moment it's simple: just fill in the data-length, the rest
	; is default)
        mov     tx_dlenh,ch             ; put it in network order
        mov     tx_dlenl,cl

	;Send the packet
        mov   output_active,1           ; set flag, to undo recv-interrupt
        call  ParWrite                  ; see parsubr.asm
        mov   output_active,0           ; recv-intrps are now accepted again
        or    ax,ax                     ; check return value (# bytes written)
        jz    send_pkt_err              ; = 0, can't..
        js    send_pkt_err              ; < 0, error.
        cmp   tx_length,ax              ; check if all bytes were written
        jne   send_pkt_err              ; not, then error.
        clc
        jmp short send_pkt_end
send_pkt_err:
        mov dh,CANT_SEND                       ; errornum
	stc
send_pkt_end:
	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		; does caller want reasonable length?
	jb	get_addr_fail		; no , fails

	cld				; make sure of string operation
	mov	si,offset Par_EthAddr	; load source of address
	mov	cx,EADDR_LEN		; return length of address
	rep	movsb			; copy address
	mov	cx,EADDR_LEN		; return length of address
	clc
	ret
get_addr_fail:
	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		; check if address ok
	je	set_addr_1	
	mov	dh,BAD_ADDRESS		; don't like length
	stc
	ret
set_addr_1:
	; is ES always pointing to CS at this moment?
	mov	di,offset Par_EthAddr	;di->destination offset
	rep	movsb			;return address
	mov	cx,EADDR_LEN		;return their address length.
	clc
	ret

rcv_mode_2:
; Turn off receiver
        ret

rcv_mode_6:
; Receive all packets (Promiscuous physical plus all multi)
        ret


	public	set_multicast_list
set_multicast_list:
;enter with ds:si ->list of multicast addresses, cx = number of addresses.
;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:
	ret

	public	reset_interface
reset_interface:
;reset the interface.
	assume	ds:code

        call stable
	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

	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.
; all interrupts come here
;Upon exit, the interrupt will be acknowledged.
	assume	ds:code
	;sti
	cld


; an IRQ 7 occures only when an incoming packet is pending.
; so check with ParDataReady if the packet is addressed to me.
; otherwise quit.
; (is buffering needed?)

recv1:
        cmp  output_active,1
        je   Not_for_me         ; 1 = interrupt during write
	call pardataready
	or al,al
	jz Line_idle		; AL = 0, nothing on the line anymore
	js Not_for_me		; AL = -1, packet not addressed to me
	
	call parread
        or ax,ax
	jz Read_error		; AL = 0, no bytes read
	js Read_error		; AL = -1, read timeout
        cmp ax,BUFSIZE
	jg Read_overflow	; packet did not fit in buffer.

; push packet to upper layer.

	mov	ch,rx_dlen		; convert packet length
	mov	cl,rx_dlen+1		; to byte order.

        mov     ax,cs
        mov     es,ax                   ; for es:di pointer in recv_find call
        mov     di,offset rx_data       ; reset di to beginning
	mov	si,di			; si will be used later
        add     di,EADDR_LEN+EADDR_LEN  ; point to type field
                                        ; of buffer

recv_reply_arp:
        mov     dl, BLUEBOOK            ;packet class: assume bluebook Ethernet

        push    cx                      ; save packet_length
        push    si                      ; save packet start
	call	recv_find		; loop up our type (first call).
        pop     si
        pop     cx

	mov	ax,es			; ax->es
	or	ax,di			; is this pointer null? (es=di=0)
	je	rcv_no_copy

	push 	cx			; save packet length
        push    es                      ; remember client's buffer
	push	di

	rep	movsb			; copy from SI (packet start) to
					; DI (upper level buffer)
        pop     si                      ; make client's buffer a new source
        pop     ds                      ; for the recv_copy call.
	pop	cx

	call	recv_copy		; second call 
rcv_no_copy:
Line_Idle:
Not_for_me:
Read_error:
Read_overflow:
        mov   ax,cs               ; set DS back to CS, otherwise the
        mov   ds,ax               ; remaining ISR routine will crash.
        assume ds:code
        ret


	public	recv_exiting
recv_exiting:
;called from the recv isr after interrupts have been acknowledged.
;Only ds and ax have been saved.
	assume	ds:nothing
	ret

;any code after this will not be kept after initialization.
end_resident	label	byte

	public	usage_msg
usage_msg  db  "usage: PARNET [-n] [-d] [-w] <packet_int_no> <int_level> <io_addr> <netaddr>",CR,LF,'$'
	public	copyright_msg
copyright_msg   db    "Packet Driver for PARnet, version "
                db    '0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,CR,LF
                db    "Ported by S.A.Pechler@bdk.tue.nl. "
                db    "Portions Copyright 1989, Matthew Dillon.",CR,LF,'$'
int_no_name     db    "Interrupt number ",'$'
io_addr_name    db    "I/O port ",'$'
PARaddress_name db    "PARnet address ",'$'
bad_addr_msg    db    "Bad PARnet address given.",CR,LF,'$'

	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

	extrn	error: near

	public	parse_args
parse_args:
;exit with nc if all went well, cy otherwise.
	mov	di,offset int_no
	call 	get_number
	jc	param_error


	mov	di,offset io_addr
	call	get_number
	jc	param_error

	mov	di,offset parnetaddr
	call	get_number
	jc	param_error

;	call	print_parameters

        mov     al,parnetaddr
        or      al,al      ; address 0 is reserved
	je	parnet_err
        cmp     al,-1      ; address 255 is reserved
	je	parnet_err
	mov	arp_srcaddr,al	; initialize the arp response packet
	clc
	ret

param_error:
	mov dx,offset usage_msg
	jmp error

parnet_err:
	mov dx,offset bad_addr_msg
	jmp error	

	public	etopen
etopen:
	call	paraddress

	call	set_recv_isr		;install the interrupt handler
	mov	dx,offset end_resident
	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 parnetaddr
        mov     dx,offset PARaddress_name
	call	print_number
	ret

code	ends
	end
