;   PC/FTP Packet Driver source, conforming to version 1.05 of the spec
;   Russell Nelson, Clarkson University.  July 20, 1988
;   Updated to version 1.08 Feb. 17, 1989.
;   Copyright 1988,1989 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.

	include	defs.asm

code	segment byte public
	assume	cs:code, ds:code

	extrn	phd_dioa: byte
	extrn	phd_environ: word

;usage_msg is of the form "usage: driver <packet_int_no> <args>"
	extrn	usage_msg: byte

;copyright_msg is of the form:
;"Packet driver for the foobar",CR,LF
;"Portions Copyright 19xx, J. Random Hacker".
	extrn	copyright_msg: byte

copyleft_msg	label	byte
 db "Packet driver skeleton copyright 1988-89, Russell Nelson.",CR,LF
 db "This program is free software; see the file COPYING for details.",CR,LF
 db "NO WARRANTY; see the file COPYING for details.",CR,LF
crlf_msg	db	CR,LF,'$'

;parse_args should parse the arguments.
;called with ds:si -> immediately after the packet_int_no.
	extrn	parse_args: near

	extrn	our_isr: near, their_isr: dword
	extrn	packet_int_no: byte
	extrn	is_at: byte
	extrn	int_no: byte
	extrn	driver_class: byte

packet_int_no_name	db	"Packet interrupt number ",'$'
eaddr_msg	db	"My Ethernet address is ",'$'

signature	db	'PKT DRVR',0
signature_len	equ	$-signature

already_msg	db	CR,LF,"There is already a packet driver at ",'$'
packet_int_msg	db	CR,LF
		db	"Error: <packet_int_no> should be in the range 0x60 to 0x80"
		db	'$'
int_msg		db	CR,LF
		db	"Error: <int_no> should be no larger than "
int_msg_num	label	word
		db	"xx"
		db	'$'

our_address	db	EADDR_LEN dup(?)

;etopen should initialize the device.  If it needs to give an error, it
;can issue the error message and quit to dos.
	extrn	etopen: near

;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.
	extrn	get_address: near

segmoffs	struc
offs		dw	?
segm		dw	?
segmoffs	ends

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

already_error:
	mov	dx,offset already_msg
	mov	di,offset packet_int_no
	call	print_number
	int	20h

usage_error:
	mov	dx,offset usage_msg
error:
	mov	ah,9
	int	21h
	int	20h

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

	mov	dx,offset copyleft_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	;parse the packet interrupt number
	mov	bx,offset packet_int_no_name
	call	get_number		;  for them.

	call	parse_args

	mov	dx,offset packet_int_msg;make sure that the packet interrupt
	cmp	packet_int_no,60h	;  number is in range.
	jb	error
	cmp	packet_int_no,80h
	ja	error

	mov	ah,35h			;get their packet interrupt.
	mov	al,packet_int_no
	int	21h

	lea	di,3[bx]		;see if there is already a signature
	mov	si,offset signature	;  there.
	mov	cx,signature_len
	repe	cmpsb
	je	already_error		;yes, so we can't go there.

	mov	ax,0f000h		;ROM segment
	mov	es,ax
	mov	al,7			;maximum interrupt on a PC
	mov	int_msg_num,'7'+' '*256
	cmp	word ptr es:[0fffeh],0fch	;is this an AT?
	jne	not_at			;no.
	mov	al,15			;maximum interrupt on an AT
	mov	int_msg_num,'1'+'5'*256

	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:

	mov	dx,offset int_msg	;make sure that the packet interrupt
	cmp	int_no,al		;  number is in range.
	jbe	int_ok
	jmp	error
int_ok:

	call	etopen			;init the driver.  If any errors,
					;this routine returns cy.
	jc	no_resident

	push	dx			;remember where they want to end.

	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

	cmp	driver_class,1		;Ethernet?
	jne	print_addr_2		;no, don't print what we don't have.

	push	ds
	pop	es
	mov	di,offset our_address
	mov	cx,EADDR_LEN
	call	get_address

	mov	dx,offset eaddr_msg
	mov	ah,9
	int	21h

	mov	cx,EADDR_LEN
	mov	si,offset our_address
print_addr:
	push	cx
	lodsb
	mov	cl,' '			;Don't eliminate leading zeroes.
	call	byteout
	pop	cx
	cmp	cx,1
	je	print_addr_1
	mov	al,':'
	call	chrout
print_addr_1:
	loop	print_addr

	mov	dx,offset crlf_msg	;can't depend on DOS to newline for us.
	mov	ah,9
	int	21h

print_addr_2:

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

	mov	bx,1			;get the stdout handle.
	mov	ah,3eh			;close it in case they redirected it.
	int	21h

	pop	dx			;get their ending address.
	add	dx,0fh			;round up to next highest paragraph.
	mov	cl,4
	shr	dx,cl
	mov	ah,31h			;terminate, stay resident.
	int	21h

no_resident:
	int	20h


	public	get_number
get_number:
	mov	bp,10			;we default to 10.
	jmp	short get_number_0

	public	get_hex
get_hex:
	mov	bp,16
;get a hex number, skipping leading blanks.
;enter with si->string of digits,
;	bx -> dollar terminated name of number,
;	di -> dword to store the number in.  [di] is not modified if no
;		digits are given, so it acts as the default.
;return cy if there are no digits at all.
;return nc, bx:cx = number, and store bx:cx at [di].
get_number_0:
	push	bx			;remember the name of this number.
	call	skip_blanks
	call	get_digit		;is there really a number here?
	jc	get_number_3
	or	al,al			;Does the number begin with zero?
	jne	get_number_4		;no.
	mov	bp,8			;yes - they want octal.
get_number_4:

	xor	cx,cx			;get a hex number.
	xor	bx,bx
get_number_1:
	lodsb
	cmp	al,'x'			;did they really want hex?
	je	get_number_5		;yes.
	cmp	al,'X'			;did they really want hex?
	je	get_number_5		;yes.
	call	get_digit		;convert a character into an int.
	jc	get_number_2		;not a digit (neither hex nor dec).
	xor	ah,ah
	cmp	ax,bp			;larger than our base?
	jae	get_number_2		;yes.

	push	ax			;save the new digit.

	mov	ax,bp			;multiply the low word by ten.
	mul	cx
	mov	cx,ax			;keep the low word.
	push	dx			;save the high word for later.
	mov	ax,bp
	mul	bx
	mov	bx,ax			;we keep only the low word (which is our high word)
	pop	dx
	add	bx,ax			;add the high result from earlier.

	pop	ax			;get the new digit back.
	add	cx,ax			;add the new digit in.
	adc	bx,0
	jmp	get_number_1
get_number_5:
	mov	bp,16			;change the base to hex.
	jmp	get_number_1
get_number_2:
	dec	si
	mov	[di],cx			;store the parsed number.
	mov	[di+2],bx
	clc
	jmp	short get_number_6
get_number_3:
	stc
get_number_6:
	pop	dx			;get the name of the number back.

	pushf				;save some stuff.
	push	bx
	push	cx
	push	si
	push	di
	call	print_number
	pop	di
	pop	si
	pop	cx
	pop	bx
	popf
	ret


print_number:
;enter with dx -> dollar terminated name of number, di ->dword.
;exit with the number printed and the cursor advanced to the next line.
	mov	ah,9			;print the name of the number.
	int	21h
	mov	al,'0'
	call	chrout
	mov	al,'x'
	call	chrout
	mov	ax,[di]			;print the number in hex.
	mov	dx,[di+2]
	call	hexout
	mov	al,' '
	call	chrout
	mov	al,'('
	call	chrout
	mov	ax,[di]			;print the number in decimal.
	mov	dx,[di+2]
	call	decout
	mov	al,')'
	call	chrout
	mov	al,CR
	call	chrout
	mov	al,LF
	call	chrout
	ret


	public	skip_blanks
skip_blanks:
	lodsb				;skip blanks.
	cmp	al,' '
	je	skip_blanks
	cmp	al,HT
	je	skip_blanks
	dec	si
	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


	public	hexout
hexout:
	mov	cl,'0'			;prepare to eliminate leading zeroes.
	xchg	ax,dx			;just output 32 bits in hex.
	call	wordout			;output dx.
	xchg	ax,dx
	jmp	wordout			;output ax.

	public	decout
decout:
	mov	si,ax			;get the number where we want it.
	mov	di,dx

	xor	ax,ax			;start with all zeroes in al,bx,bp
	mov	bx,ax
	mov	bp,ax

	mov	cx,32			;32 bits in two 16 bit registers.
decout_1:
	shl	si,1
	rcl	di,1
	xchg	bp,ax
	call	addbit
	xchg	bp,ax
	xchg	bx,ax
	call	addbit
	xchg	bx,ax
	adc	al,al
	daa
	loop	decout_1

	mov	cl,'0'			;prepare to eliminate leading zeroes.
	call	byteout			;output the first two.
	mov	ax,bx			;output the next four
	call	wordout			;output the next four
	mov	ax,bp
wordout:
	push	ax
	mov	al,ah
	call	byteout
	pop	ax
byteout:
	mov	ah,al
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	call	digout
	mov	al,ah
digout:
	and	al,0fh
	add	al,90h	;binary digit to ascii hex digit.
	daa
	adc	al,40h
	daa
	cmp	al,cl			;leading zero?
	je	return
	mov	cl,-1			;no more leading zeros.
chrout:
	push	ax			;print the char in al.
	xchg	al,dl
	mov	ah,2
	int	21h
	xchg	al,dl
	pop	ax
return:
	ret


addbit:	adc	al,al
	daa
	xchg	al,ah
	adc	al,al
	daa
	xchg	al,ah
	ret


code	ends

	end
