version	equ	0
;History:562,1

;  Copyright, 1988-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.

	include	defs.asm

code	segment	word public
	assume	cs:code, ds:code

comment \

The following says how to transfer a sequence of bytes.  The bytes
are structured as [ count-low, count-high, bytes, bytes, bytes, checksum ].

Send		Recv
8->
[ repeat the following
		<-1
10h+low_nib->
		<-0
high_nib->
until all bytes have been transferred ]
		<-0
\

DATA		equ	0
REQUEST_IRQ	equ	08h
STATUS		equ	1
CONTROL		equ	2

	public	int_no
int_no	db	7,0,0,0			;must be four bytes long for get_number.
io_addr	dw	0378h,0			; I/O address for card (jumpers)

	public	driver_class, driver_type, driver_name, driver_function, parameter_list
driver_class	db	1,0		;null terminated list of classes.
driver_type	db	90		;from the packet spec
driver_name	db	'parallel',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	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,

recv_buffer	dw	?		;-> buffer.
recv_count	dw	?

send_nib_count	db	?
recv_byte_count	db	?

	public	rcv_modes
rcv_modes	dw	7		;number of receive modes in our table.
		dw	0,0,0,rcv_mode_3
		dw	0,0,rcv_mode_6

	include	timeout.asm

hexout_ptr	dw	0
hexout_color	db	70h

hexout_more:
	ret
	push	di
	mov	di,cs:hexout_ptr
	call	hexout
	add	di,4
	cmp	di,25*80*2
	jb	hexout_more_1
	xor	di,di
hexout_more_1:
	mov	cs:hexout_ptr,di
	pop	di
	ret

hexout:
;enter with al = number, di->place on screen to store chars.
	push	ax
	push	es

	push	di
	mov	di,0b800h
	mov	es,di
	pop	di

	push	ax
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	call	nibble2hex
	mov	byte ptr es:[di+0],al
	mov	al,cs:hexout_color
	mov	byte ptr es:[di+1],al
	pop	ax
	call	nibble2hex
	mov	byte ptr es:[di+2],al
	mov	al,cs:hexout_color
	mov	byte ptr es:[di+3],al

	pop	es
	pop	ax
	ret


nibble2hex:
	and	al,0fh
	add	al,90h			;binary digit to ascii hex digit.
	daa
	adc	al,40h
	daa
	ret


	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.
;if we're a high-performance driver, es:di -> upcall.
;exit with nc if ok, or else cy if error, dh set to error number.
	assume	ds:nothing

	cmp	cx,GIANT		; Is this packet too large?
	ja	send_pkt_toobig

;cause an interrupt on the other end.
	loadport
	setport	DATA
	mov	al,REQUEST_IRQ
	out	dx,al

;wait for the other end to ack the interrupt.
	mov	ax,18
	call	set_timeout
	setport	STATUS
send_pkt_1:
	in	al,dx
	test	al,1 shl 3		;wait for them to output 1.
	jne	send_pkt_2
	call	do_timeout
	jne	send_pkt_1
	jmp	short send_pkt_4	;if it times out, they're not listening.
send_pkt_2:

	mov	send_nib_count,0
	setport	DATA
	mov	al,cl			;send the count.
	call	send_byte
	jc	send_pkt_4		;it timed out.
	mov	al,ch
	call	send_byte
	jc	send_pkt_4		;it timed out.
	xor	bl,bl
send_pkt_3:
	lodsb				;send the data bytes.
	add	bl,al
	call	send_byte
	jc	send_pkt_4		;it timed out.
	loop	send_pkt_3

	mov	al,bl			;send the checksum.
	mov	hexout_color,30h	;aqua
	call	hexout_more
	call	send_byte
	jc	send_pkt_4		;it timed out.

	mov	al,0			;go back to quiescent state.
	out	dx,al
	clc
	ret

send_pkt_toobig:
	mov	dh,NO_SPACE
	stc
	ret

send_pkt_4:
	loadport
	setport	DATA
	mov	al,send_nib_count
	mov	hexout_color,20h	;green
	call	hexout_more
	xor	al,al			;clear the data.
	out	dx,al
	mov	dh,CANT_SEND
	stc
	ret
;
; It's important to ensure that the most recent setport is a setport DATA.
;
send_byte:
;enter with al = byte to send.
;exit with cy if it timed out.
	push	ax
	or	al,10h			;set the clock bit.
	call	send_nibble
	pop	ax
	jc	send_nibble_2
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1			;clock bit is cleared by shr.
send_nibble:
;enter with setport DATA, al[3-0] = nibble to output.
;exit with dx set to DATA.
	out	dx,al
	and	al,10h			;get the bit we're waiting for to come back.
	shl	al,1			;put it in the right position.
	shl	al,1
	shl	al,1
	mov	ah,al
	mov	hexout_color,60h	;orange
	call	hexout_more

	setport	STATUS
	push	cx
	xor	cx,cx
send_nibble_1:
	in	al,dx			;keep getting the status until
	xor	al,87h
	and	al,80h
	cmp	al,ah			;  we get the status we're looking for.
	loopne	send_nibble_1
	pop	cx
	jne	send_nibble_2

	mov	hexout_color,50h	;purple
	call	hexout_more

	inc	send_nib_count
	setport	DATA			;leave with setport DATA.
	clc
	ret
send_nibble_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
	mov	dh,CANT_SET
	stc
	ret


rcv_mode_3:
rcv_mode_6:
;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:
	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

recv_char	db	'0'

	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			;see if we've gotten a real interrupt.
	setport	STATUS
	in	al,dx
	cmp	al,0c7h			;it must be 0c7h, otherwise spurious.
	je	recv_real
	jmp	recv_err
recv_real:

	mov	al,recv_char
	inc	recv_char
	and	recv_char,'7'
	to_scrn	24,79,al

	movseg	es,ds
	mov	di,recv_buffer

	loadport
	setport	DATA
	mov	al,1			;say that we're ready.
	out	dx,al

	mov	recv_byte_count,0

	setport	STATUS
	call	recv_byte		;get the count.
	jc	recv_err			;it timed out.
	mov	cl,al
	call	recv_byte
	jc	recv_err			;it timed out.
	mov	ch,al
	xor	bl,bl
	mov	recv_count,cx
recv_1:
	call	recv_byte		;get a data byte.
	jc	recv_err			;it timed out.
	add	bl,al
	stosb
	loop	recv_1

	call	recv_byte		;get the checksum.
	jc	recv_err			;it timed out.
	cmp	al,bl			;checksum okay?
	jne	recv_err		;no.

	movseg	es,cs
	mov	di,recv_buffer
	push	es
	push	di

	mov	cx,recv_count
	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
	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
	rep	movsb
	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

;wait for the other end to reset to zero.
	mov	ax,10			;1/9th of a second.
	call	set_timeout
	loadport
	setport	STATUS
recv_pkt_1:
	in	al,dx
	cmp	al,87h			;wait for them to output 0.
	je	recv_4

	mov	hexout_color,20h	;green
	call	hexout_more

	call	do_timeout
	jne	recv_pkt_1

	mov	al,recv_byte_count
	mov	hexout_color,20h	;green
	call	hexout_more
recv_4:
	loadport
	setport	DATA
	xor	al,al
	out	dx,al
	ret

	setport	STATUS			;this code doesn't get executed,
					;but it sets up for call to recv_byte.

recv_byte:
;called with setport STATUS.
;exit with nc, al = byte, or cy if it timed out.

	push	cx
	mov	cx,65535
recv_low_nibble:
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h

	in	al,dx			;get the next data value.
	test	al,80h			;wait for handshake low (transmitted hi).
	loopne	recv_low_nibble
	pop	cx
	jne	recv_byte_1

	shr	al,1			;put our bits into position.
	shr	al,1
	shr	al,1
	mov	ah,al
	and	ah,0fh

	mov	al,10h			;send our handshake back.
	setport	DATA
	out	dx,al
	setport	STATUS

	push	cx
	mov	cx,65535
recv_high_nibble:
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h
	in	al,61h

	in	al,dx			;get the next data value.
	test	al,80h			;check for handshake high (transmitted low).
	loope	recv_high_nibble
	pop	cx
	je	recv_byte_1

	shl	al,1			;put our bits into position.
	and	al,0f0h
	or	ah,al

	mov	al,0			;send our handshake back.
	setport	DATA
	out	dx,al
	setport	STATUS

	inc	recv_byte_count

	mov	al,ah
	mov	hexout_color,40h	;red
	call	hexout_more
	clc
	ret
recv_byte_1:
	stc
	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 after initialization. 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
	db	GIANT dup(?)
end_free_mem	label	byte


	public	usage_msg
usage_msg	db	"usage: parallel [options] <packet_int_no> [-t] <hardware_irq> <io_addr> [<Ethernet_address>]",CR,LF,'$'

	public	copyright_msg
copyright_msg	db	"Packet driver for parallel port interfaces, version ",'0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,CR,LF
		db	"Portions Copyright 1992, Russell Nelson",CR,LF,'$'
no_memory_msg	db	"Unable to allocate enough memory, look at end_resident in PARALLEL.ASM",CR,LF,'$'

int_no_name	db	"Interrupt number ",'$'
io_addr_name	db	"I/O port ",'$'

test_switch	db	0
test_bit	db	1

	extrn	set_recv_isr: near

	extrn	chrout: near
	extrn	skip_blanks: 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 -> argument string, di -> dword to print.
	extrn	print_number: near

;-> the assigned Ethernet address of the card.
	extrn	rom_address: byte

	public	parse_args
parse_args:
;exit with nc if all went well, cy otherwise.
	call	skip_blanks
	cmp	al,'-'			;did they specify a switch?
	jne	not_switch
	cmp	byte ptr [si+1],'t'	;did they specify '-t'?
	je	got_test_switch
	stc				;no, must be an error.
	ret
got_test_switch:
	mov	test_switch,1
	add	si,2			;skip past the switch's characters.
	jmp	parse_args		;go parse more arguments.
not_switch:

	mov	di,offset int_no
	call	get_number
	mov	di,offset io_addr
	call	get_number

	mov	rom_address[0],2	;locally administered.

	mov	ax,40h
	mov	es,ax
	mov	ax,es:[6ch]
	mov	word ptr rom_address[2],ax
	mov	ax,es:[6eh]
	mov	word ptr rom_address[4],ax

	movseg	es,ds
	mov	di,offset rom_address
	call	get_eaddr
	clc
	ret


	public	etopen
etopen:
	mov	dx,37ah			;enable interrupts on the board.
	mov	al,10h
	out	dx,al

	cmp	test_switch,0
	je	etopen_1

	mov	ax,5
	call	set_timeout
etopen_2:
;print the status of the incoming lines.
	loadport
	setport	STATUS
	in	al,dx
	xor	al,87h
	mov	ah,al
	mov	cx,5			;print five bits.
etopen_3:
	push	ax			;time to toggle the outgoing bits?
	call	do_timeout
	jne	etopen_4		;no.
	setport	DATA			;yes, shuffle the bit around.
	mov	al,test_bit
	shr	al,1
	jnc	etopen_5
	mov	al,10h
etopen_5:
	mov	test_bit,al
	out	dx,al
	setport	STATUS
	mov	ax,5
	call	set_timeout
etopen_4:
	pop	ax
	shl	ah,1
	mov	al,'0'
	adc	al,0
	call	chrout
	loop	etopen_3
	mov	al,CR
	call	chrout

	mov	ah,1			;did they hit a key?
	int	16h
	jz	etopen_2		;no.

	mov	ah,0			;yes, suck the key out.
	int	16h

	setport	DATA			;reset the data port.
	xor	al,al
	out	dx,al

etopen_1:

	mov	dx,GIANT		;allocate memory for the receive buffer.
	call	malloc
	jc	no_memory
	mov	recv_buffer,dx

	call	set_recv_isr

	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
no_memory:
	mov	dx,offset no_memory_msg
	stc
	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

	extrn	get_hex: near
	include	getea.asm

code	ends

	end
