version	equ	3

	include	defs.asm

; PC/FTP Packet Driver source, conforming to version 1.09 of the spec
; Katie Stevens (dkstevens@ucdavis.edu)
; Computing Services, University of California, Davis
; Portions (C) Copyright 1988 Regents of the University of California

; 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.

; Change History
;
; 05-90		ks	Katie Stevens (dkstevens@ucdavis.edu):
;			- Created
; 07-19-91	jgn	Jim Noble (noble_jim@po.gis.prc.com):
;			- Added Change History and documented changes
;			- Fixed a bug that caused AppleTalk network numbers to
;			  be byte-swapped when printed
;			- Fixed a bug that caused the wrong buffer address to 
;			  be passed to the packet receiver upcall routine
;			- Made changes in the driver information structure to 
;			  match LocalTalk:  size of MAC-layer address, MTU,
;			  and multicast buffer size
;			- Changed the packet buffer sizes from 1024 to 586,
;			  the maximum amount of data in a DDP packet
;			- Commented-out the noop_upcall completion routine and
;			  changed the completion routine address passed to
;			  the AppleTalk driver to 0000:0000 for all driver
;			  calls
;			- Set the status field to -1 prior to the GetNetInfo
;			  AppleTalk driver call as per the recommendation of
;			  the driver interface spec.
;			- Added structure definitions for short and long 
;			  format DDP packets and used them when adding a
;			  check to ensure that received packets are IP packets
; 07-22-91	jgn	Jim Noble (noble_jim@po.gis.prc.com):
;			- Fixed a bug that would prevent the packet driver
;			  from initializing with a static address if there
;			  were no dynamic addresses available
; 07-23-91	jgn	Jim Noble (noble_jim@po.gis.prc.com):
;			- Added the optional IP address to the usage message
;			- Incremented version to 3
;

code	segment	word public
	assume	cs:code, ds:code

; Definitions specific to the ATALK.SYS driver for PC LocalTalk cards:
; these include Apple LocalTalk PC Card, Sun/TOPS FlashCard
; For a complete description of the LocalTalk commands, structures and
; methods used in this driver, please refer to Apple APDA document #M7055,
; LocalTalk PC Card and Driver Preliminary Notes.

driverstring	db	'AppleTalk', 0		; ATALK.SYS signature string
dot_char	db	'.', 0			; for IP address display

AT_INT		equ	5CH			; Software int# for ATALK.SYS

; General ATALK.SYS driver commands
AT_INIT			equ	01H		; Initialize driver software
AT_GETNETINFO		equ	03H		; Get driver info

; Datagram Delivery Protocol commands for ATALK.SYS driver
DDP_OPENSOCKET		equ	20H
DDP_CLOSESOCKET		equ	21H
DDP_WRITE		equ	22H
DDP_READ		equ	23H
DDP_CANCEL		equ	24H

; Name Binding Protocol commands for ATALK.SYS driver
NBP_REGISTER		equ	30H
NBP_REMOVE		equ	31H
NBP_LOOKUP		equ	32H
NBP_CONFIRM		equ	33H
NBP_CANCEL		equ	34H

; AppleTalk Transaction Protocol commands for ATALK.SYS driver
ATP_SEND_REQUEST	equ	42H

; ATALK.SYS command qualifiers
ASYNC_MASK	equ	8000H		; Start command, then return
INTR_MASK	equ	4000H		; Wait for intr service to complete

XO_BIT		equ	20H		; ATP - exactly once transaction

; Structure for short format DDP packet [jgn]
DDPshort struc
	lap_dst		db	?	; destination node
	lap_src		db	?	; source node
	lap_type	db	?	; 1 for short format DDP packet
	ddps_len	dw	?	; packet length
	ddps_dskt	db	?	; destination socket
	ddps_sskt	db	?	; source socket
	ddps_type	db	?	; 22 for IP packets
DDPshort ends

; Structure for long format DDP packet [jgn]
DDPlong struc
	lap_dst		db	?	; destination node
	lap_src		db	?	; source node
	lap_type	db	?	; 2 for long format DDP packets
	ddpl_len	dw	?	; packet length
	ddpl_chksum	dw	?	; checksum
	ddpl_dnet	dw	?	; destination network
	ddpl_snet	dw	?	; source network
	ddpl_dnode	db	?	; destination node
	ddpl_snode	db	?	; source node
	ddpl_dskt	db	?	; destination socket
	ddpl_sskt	db	?	; source socket
	ddpl_type	db	?	; 22 for IP packets
DDPlong ends

; Structure for AppleTalk node addressing
AddrBlk struc
	ablk_network	dw	0
	ablk_nodeid	db	0
	ablk_socket	db	0
AddrBlk ends

; Structure for general calls to AppleTalk driver (ATALK.SYS)
InfoParams struc
	atd_command	dw		AT_GETNETINFO
	atd_status	dw		0
	atd_compfun	segmoffs	<>
	inf_network	dw		0
	inf_nodeid	db		0
	inf_abridge	db		0
	inf_config	dw		0
	inf_buffptr	segmoffs	<>
	inf_buffsize	dw		0
InfoParams ends
; Parameter block for general calls to AppleTalk driver (ATALK.SYS)
MyInfo	InfoParams	<>

; Address block for our gateway
MyGateway	AddrBlk		<>

; Structure for calls to AppleTalk driver (ATALK.SYS) for Datagram
; Delivery Protocol (DDP) service
DDPParams struc
	ddp_command	dw		0
	ddp_status	dw		0
	ddp_compfun	segmoffs	<>
	ddp_addr	AddrBlk		<>
	ddp_socket	db		0
	ddp_type	db		0
	ddp_buffptr	segmoffs	<>
	ddp_buffsize	dw		0
	ddp_chksum	db		0
DDPParams ends
; Parameter blocks for AppleTalk DDP access
DDPio		DDPParams	<>		; Write on DDP socket
; 2 buffers for packet receive from ATALK.SYS
DDP1inuse	db	0			; Buffer occupied flag
DDP1buffsize	dw	0			; Buffer length during reads
DDP1buffer	db	586 dup (0)		; Buffer for DDP read [jgn]
DDP2inuse	db	0			; 2nd Buffer occupied flag
DDP2buffsize	dw	0			; 2nd Buffer length during reads
DDP2buffer	db	586 dup (0)		; 2nd Buffer for DDP read [jgn]

; Structure for calls to AppleTalk driver (ATALK.SYS) for Name
; Binding Protocol (NBP) service
NBPParams struc
	nbp_command	dw		0
	nbp_status	dw		0
	nbp_compfun	segmoffs	<>
	nbp_addr	AddrBlk		<>
	nbp_toget	dw		0
	nbp_buffptr	segmoffs	<>
	nbp_buffsize	dw		0
	nbp_interval	db		0
	nbp_retry	db		0
	nbp_entptr	segmoffs	<>
NBPParams ends
; Parameter block for AppleTalk NBP access
NBP		NBPParams	<>

; Structure for name-to-address bind entries
NBPTuple struc
	tup_address	AddrBlk		<>
	tup_enum	db		0
	tup_name	db		99 dup(0)
NBPTuple ends
; Name Binding Tuple for our IP gateway
NBPt		NBPTuple	<>

; Structure for name-to-address table
NBPEntry struc
	tab_next	segmoffs	<>
	tab_entry	NBPTuple	<>
NBPEntry ends
NBPtable	NBPEntry	<>

; Structure for calls to AppleTalk driver (ATALK.SYS) for AppleTalk
; Transaction Protocol (ATP) service
ATPParams struc
	atp_command	dw		0
	atp_status	dw		0
	atp_compfun	segmoffs	<>
	atp_addrblk	AddrBlk		<>
	atp_socket	db		0
	atp_fill	db		0
	atp_buffptr	segmoffs	<>
	atp_buffsize	dw		0
	atp_interval	db		0
	atp_retry	db		0
	atp_flags	db		0
	atp_seqbit	db		0
	atp_tranid	dw		0
	atp_userbytes	db		4 dup(0)
	atp_bdsbuffs	db		0
	atp_bdsresps	db		0
	atp_bdsptr	segmoffs	<>
ATPParams ends
; Parameter block for AppleTalk ATP access
ATP		ATPParams	<>

; Structure for BDS elements
BDSElement struc
	bds_buffptr	segmoffs	<>
	bds_buffsize	dw		0
	bds_datasize	dw		0
	bds_userbytes	db		4 dup(0)
BDSElement ends
; Parameter block for our BDS element
BDS		BDSElement	<>

; Struct for IP gateway information
IPGInfo struc
	ipg_opcode	db		0,0,0,1	; IPGP_ASSIGN
	ipg_ipaddress	dd		0	; our IP address
	ipg_ipname	dd		0	; nameserver IP address
	ipg_ipbroad	dd		0	; broadcast IP address
	ipg_ipfile	dd		0	; file server IP address
	ipg_ipother	dd		4 dup (0)
	ipg_string	db		128 dup (0), '$'
IPGInfo ends
; Parameter block for info about our IP gateway
IPG		IPGInfo		<>

IPG_ERROR	equ	-1

static_address	db	0, 0, 0, 0
use_static	db	0
test_address	db	0, 0, 0, 0
temp_4bytes	db	0, 0, 0, 0

; End of Appletalk parameter definitions

; The following values may be overridden from the command line.
; If they are omitted from the command line, these defaults are used.

	public	int_no
int_no	db	0,0,0,0			;must be four bytes long for get_number.

	public	driver_class, driver_type, driver_name, driver_function, parameter_list
driver_class	db	5,0		;from the packet spec
driver_type	db	1		;from the packet spec
driver_name	db	'LocalTalk',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	4	;length of MAC-layer address [jgn]
	dw	586	;MTU, including MAC headers [jgn]
	dw	0	;buffer size of multicast addrs [jgn]
	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

	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
; send packet to AppleTalk/DDP/IP gateway
	; load info about the packet we are sending
	mov	DDPio.ddp_buffptr.offs, si
	mov	DDPio.ddp_buffptr.segm, ds	; DDPio.buffptr -> IP packet
	mov	DDPio.ddp_buffsize, cx		; DDPio.buffsize = packet len

	; send all packets to the IP gateway
	mov	cx, (size AddrBlk)		; DDPio.ddp_addr = MyGateway
	mov	ax, cs
	mov	ds, ax
	mov	es, ax
	mov	si, offset MyGateway
	mov	di, offset DDPio.ddp_addr
	rep	movsb

	mov	bx, offset DDPio
	call	doATint				; Ask ATALK.SYS to send packet

	cmp	DDPio.ddp_status, 00H		; Packet sent okay?
	je	send_ret			; Yes, status is good
						; No, status gives error
send_err:
	call	count_out_err
	mov	dh, CANT_SEND			; set error flag
	stc
	ret

send_ret:
	clc					; packet sent successfully
	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, BAD_COMMAND
	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:
	push	ds
	push	cs
	pop	ds

terminate_write:
	; close the DDP socket
	mov	DDPio.ddp_command, DDP_CLOSESOCKET
	mov	bx, offset DDPio
	call	doATint

	mov	NBP.nbp_command, NBP_REMOVE
	mov	NBP.nbp_entptr.offs, offset NBPtable.tab_entry.tup_name
	mov	NBP.nbp_entptr.segm, ds
	mov	bx, offset NBP
	call	doATint

	pop	ds
	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

	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.
;NOTE: this packet driver merely makes calls to another hardware
;driver, ATALK.SYS. ATALK.SYS handles the hardware interrupt service;
;ATALK.SYS then calls this packet driver with FAR subroutine calls.
;the ATALK.SYS FAR subroutine is recv_at_upcall
	assume	ds:nothing
	ret

;*******************************************

; [jgn] Null completion routine is not needed.  Just specify 0000:0000 as the
; address of the completion routine for no call.
;
; NULL completion routine for ATALK.SYS drivers calls
;noop_upcall proc far
;	ret
;noop_upcall endp

;First half routine for DDP socket.
;ATALK.SYS calls this routine when a packet is received.
;ATALK.SYS assumes we are a far procedure.
;	AH = socket
;	CX = size of data packet
;	DS:BX = address of buffer
preview_upcall proc far
	assume	ds:nothing

;	maximum packet we can receive is 586 bytes [jgn]
	cmp	cx, 586				; Max size of DDP data field [jgn]
	ja	preview_drop

;	Is it an IP packet?  DDP type should be 22 [jgn]
	cmp	byte ptr ds:[bx].lap_type, 1	; Is it a short DDP packet?
	jne	ddp_long			; No, must be long DDP
	cmp	byte ptr ds:[bx].ddps_type, 22	; Is it an IP packet?
	jne	preview_drop			; No, refuse packet
	jmp	preview_buff1
ddp_long:
	cmp	byte ptr ds:[bx].ddpl_type, 22	; Is it an IP packet?
	jne	preview_drop			; No, refuse packet

preview_buff1:
	cmp	DDP1inuse, 00H
	jne	preview_buff2
	mov	DDP1inuse, 01H

;	repeat buffer size back to ATALK.SYS in CX
;	ask ATALK.SYS driver to pass us the buffer at DS:BX
;	tell ATALK.SYS address of 2nd half routine in ES:DX
	movseg	ds,cs
	mov	bx, offset DDP1buffer		; ds:bx->buffer
	movseg	es,cs
	mov	dx, offset recv_at_upcall	; es:dx->2nd half routine
	jmp	preview_ret

preview_buff2:
	cmp	DDP2inuse, 00H
	jne	preview_drop
	mov	DDP2inuse, 01H

;	repeat buffer size back to ATALK.SYS in CX
;	ask ATALK.SYS driver to pass us the buffer at DS:BX
;	tell ATALK.SYS address of 2nd half routine in ES:DX
	movseg	ds,cs
	mov	bx, offset DDP2buffer		; ds:bx->buffer
	movseg	es,cs
	mov	dx, offset recv_at_upcall	; es:dx->2nd half routine
	jmp	preview_ret


preview_drop:
;	ask ATALK.SYS to drop the packet
	call	count_in_err
	mov	cx, 00h

preview_ret:
	ret
preview_upcall endp

;Second half routine for DDP socket.
;ATALK.SYS calls this routine when the packet has been copied to our buffer.
;ATALK.SYS assumes we are a far procedure.
;	CX = size of data packet
;	DS:BX = address of buffer
recv_at_upcall proc far
	assume	ds:nothing

recv_buff1:
	cmp	bx, offset DDP1buffer
	jne	recv_buff2

;	check if we have a client waiting for packets
;	pass to recv_find    es:di->driver_type, cx=#bytes in packet
	mov	DDP1buffsize, cx
	mov	di, offset driver_type
	movseg	es,cs
	mov	dl,cs:driver_class
	call	recv_find

;	es:di->client buffer, or es:di=0 means drop the packet
	mov	ax, es
	or	ax, di
	je	recv_pass1

;	copy ds:si->es:di for cx bytes
	push	es				; save the client buffer
	push	di				; address (es:di) [jgn]
	movseg	ds,cs
	mov	si, offset DDP1buffer
	mov	cx, DDP1buffsize
	rep	movsb

;	tell receiver copy has been made; ds:si->the packet, cx=length
	pop	si				; restore the client buffer
	pop	ds				; address into ds:si [jgn]
	mov	cx, DDP1buffsize
	call	recv_copy

recv_pass1:
;	first buffer is free for use again
	mov	DDP1inuse, 00H
	jmp	recv_ret


recv_buff2:
	cmp	bx, offset DDP2buffer
	jne	recv_ret

;	check if we have a client waiting for packets
;	pass to recv_find    es:di->driver_type, cx=#bytes in packet
	mov	DDP2buffsize, cx
	mov	di, offset driver_type
	movseg	es,cs
	call	recv_find

;	es:di->client buffer, or es:di=0 means drop the packet
	mov	ax, es
	or	ax, di
	je	recv_pass2

;	copy ds:si->es:di for cx bytes
	push	es				; save the client buffer
	push	di				; address (es:di) [jgn]
	movseg	ds,cs
	mov	si, offset DDP2buffer
	mov	cx, DDP2buffsize
	rep	movsb

;	tell receiver copy has been made; ds:si->the packet, cx=length
;	push	es
	pop	si				; restore the client buffer
	pop	ds				; address into ds:si [jgn]
;	mov	si, offset DDP2buffer
	mov	cx, DDP2buffsize
	call	recv_copy
recv_pass2:
;	second buffer is now free for use
	mov	DDP2inuse, 00H

recv_ret:
	ret
recv_at_upcall endp

;*******************************************

; Call DOS software interrupt for AppleTalk
; caller must set ds:bx -> parameter block for ATALK.SYS
doATint:
	int	AT_INT				; Interrupt ATALK.SYS driver
	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
end_free_mem	label	byte
;****************************************************************************

	public	usage_msg
usage_msg	db	"usage: localtlk [options] <packet_int_no> [ <IP address> ]",CR,LF,'$' ; [jgn]

	public	copyright_msg
copyright_msg	db	"Packet driver for Apple LocalTalk PC Card, version ",'0'+(majver / 10),'0'+(majver mod 10),".",'0'+version,CR,LF
		db	"Portions Copyright 1990, Regents of the University of California",CR,LF,'$'

no_atalk_sys_msg:
	db	"Couldn't locate ATALK.SYS -- packet driver not installed",CR,LF,'$'
atalk_sys_found_msg:
	db	"ATALK.SYS driver located at software interrupt ",'$'
inf_nodeid_name:
	db	"Attaching to AppleTalk network as node ",'$'
inf_abridge_name:
	db	"AppleTalk network bridge is node ",'$'
ddp_failed_msg:
	db	"Datagram Delivery Protocol socket open failed; return status: ",'$'
ddp_wrong_socket_msg:
	db	"Datagram Delivery Protocol failed; unable to aquire requested socket",CR,LF,'$'
ddp_open_msg:
	db	"Datagram Delivery Protocol open on socket ",'$'
atalk_open_msg:
	db	"Attached to AppleTalk network as (net:node:sock): ",'$'
nbp_no_gateway_msg:
	db	"NBP: IPGATEWAY lookup failed; return status: ",'$'
nbp_ipg_addr_msg:
	db	"IPGATEWAY located on AppleTalk network as (net:node:sock): ",'$'
atp_no_gateway_msg:
	db	"ATP: IPGATEWAY transport setup failed; return status: ",'$'
ipg_gateway_err_msg:
	db	"IP Gateway error: ",'$'
myip_addr_msg:
	db	"My IP address: ",'$'
ns_ip_addr_msg:
	db	"Name Server IP address: ",'$'
bd_ip_addr_msg:
	db	"Broadcast IP address: ",'$'
fs_ip_addr_msg:
	db	"File Server IP address: ",'$'
opcode_msg:
	db	"IPG opcode: ",'$'
nbp_no_register_msg:
	db	"NBP: failed, couldn't register our name; return status: ",'$'
ddp_cant_recv:
	db	"DDP: couldn't initiate read on socket; return status: ",'$'
test_arg_msg:
	db	"Test IP arg parsing: ",'$'
null_msg	db	'$'

ipgateway_name:
	db	01H, '=', 09H, "IPGATEWAY", 01H, '*', '0'
myip_name:
	db	09H, "IPADDRESS", 01H, '*', '0'
myip_name_len	equ	12

; Temporary storage for calls to print_number
dtemp	dw	?,0

	extrn	set_recv_isr: near

;enter with si -> argument string, di -> wword to store.
;if there is no number, don't change the number.
	extrn	get_number: near

;enter with ds:dx -> argument string, ds:di -> dword to print.
	extrn	print_number: near

;enter with al = char to display
	extrn	chrout: near
;enter with ax,dx holding 32 bits to display in decimal (ax holds low word)
	extrn	decout: near
	extrn	byteout: near
	extrn	wordout: near

	extrn	skip_blanks: near

;enter with si -> argument string, di -> word to store.
;if there is no number, don't change the number.
	extrn	get_number: near

;called with ds:si -> immediately after the entry_point
	public	parse_args
parse_args:
	call	skip_blanks
	lodsb
	cmp	al, CR
	je	no_more_args
	cmp	al, '['				; check for square brackets
	je	past_brackets
	dec	si				; not a bracket, back up
past_brackets:
	mov	di, offset temp_4bytes		; get first IP address byte
	call	get_number
	mov	byte ptr test_address, cl
	lodsb
	cmp	al, '.'
	jne	no_more_args

	mov	di, offset temp_4bytes		; get second IP address byte
	call	get_number
	mov	byte ptr test_address+1, cl
	lodsb
	cmp	al, '.'
	jne	no_more_args

	mov	di, offset temp_4bytes		; get third IP address byte
	call	get_number
	mov	byte ptr test_address+2, cl
	lodsb
	cmp	al, '.'
	jne	no_more_args

	mov	di, offset temp_4bytes		; get first IP address byte
	call	get_number
	mov	byte ptr test_address+3, cl

;	mov	dx, offset test_arg_msg
;	mov	di, offset test_address
;push	si
;	call	print_ip_addr
;pop	si

	mov	ax, word ptr test_address
	mov	word ptr static_address, ax
	mov	ax, word ptr test_address+2
	mov	word ptr static_address+2, ax
	mov	use_static, 01H

	lodsb
	cmp	al, ']'
	je	arg_return

;exit with nc if all went well, cy otherwise.
no_more_args:
	dec	si
arg_return:
	clc
	ret


; Initialize our interface to the ATALK.SYS driver.
; NOTE: this initialization code is modeled after the PC/IP LocalTalk
; driver written by Dan Lanciani (ddl@harvard.harvard.edu); the PCIP
; software package can found at husc6.harvard.edu:pub/pcip/pcip.tar.Z
	public	etopen
etopen:
	assume	ds:code

; ATALK.SYS driver may be loaded at a software interrupt somewhere
; between 5CH and 70H. Locate ATALK.SYS driver by scanning for signature.
isATLoaded:					; Look for ATALK.SYS driver
	cld
	call	ATGetInt			; Load start of intr range
	mov	dx, ax				; Save start value in DX
chkloop:
	cmp	dx, 70H				; Scanned all possible vectors?
	jne	checkstring			; No, check this vector
	xor	ax, ax				; Yes, driver not found
	jmp	chksplit			; Skip ahead to return
checkstring:
	mov	bx, dx				; Load intr# for scan
	shl	bx, 1				; Multiply by 2 (for seg bytes)
	shl	bx, 1				; Multiply by 2 (for off bytes)
	xor	ax, ax
	mov	es, ax				; Lowest page of memory
	lds	si, es:[bx]			; Load vector for scan intr#
	mov	ax, ds				; Load segment this scan intr#
	or	ax, si				; OR with off this scan intr#
	jz	keepchecking			; Keep checking if no bits
	sub	si, 16				; Signature is just before code
	mov	di, offset driverstring		; Load compare string
	mov	cx, 9				; Load length of compare string
	movseg	es,cs
	repe	cmpsb				; Compare ds:si to es:di
	jne	keepchecking			; Keep checking if not matched
	call	ATGetInt			; Matched, get INT# again
	cmp	ax, dx				; INT# already set properly?
	jz	chksplit			; Yes, use this INT#
						; No, we found INT# by scanning
	call	ATPatch				; Modify code to match scan
	call	ATGetInt			; Retrieve final INT#
	jmp	chksplit			; Skip ahead to return

keepchecking:					; Havent found ATALK.SYS driver
	inc	dx				; Check next possible INT#
	jmp	chkloop				; Loop back to check next INT#

chksplit:					; Done with scan for ATALK.SYS
	cmp	ax, 00H				; ATALK.SYS driver found?
	jne	atalk_sys_found			; Yes, skip ahead to continue

	mov	dx, offset no_atalk_sys_msg	; No, ATALK.SYS not loaded
	jmp	error_wrt			; Skip ahead to report error

atalk_sys_found:				; ATALK.SYS driver found
	movseg	ds,cs
	mov	dtemp, dx			; Report intr# of ATALK.SYS
	mov	di, offset dtemp
	mov	dx, offset atalk_sys_found_msg
	call	print_number

; We need to establish our Appletalk node
get_our_info:					; Get info params from ATALK
	mov	MyInfo.atd_command, AT_GETNETINFO
	mov	MyInfo.atd_status, -1		; As per PC LocalTalk spec. [jgn]
	mov	MyInfo.atd_compfun.offs, 0	; No completion routine [jgn]
	mov	MyInfo.atd_compfun.segm, 0
	mov	bx, offset MyInfo
	call	doATint

	cmp	MyInfo.atd_status, 00H		; Already initialized?
	je	get_ddp_socket			; Yes, skip ahead

	mov	MyInfo.atd_command, AT_INIT	; No, initialize our node
	mov	MyInfo.atd_compfun.offs, 0	; No completion routine [jgn]
	mov	MyInfo.atd_compfun.segm, 0
	mov	bx, offset MyInfo
	call	doATint

; We need to establish our AppleTalk/DDP socket
get_ddp_socket:					; Open a DDP socket
	mov	DDPio.ddp_command, DDP_OPENSOCKET
	mov	DDPio.ddp_compfun.offs, 0	; No completion routine [jgn]
	mov	DDPio.ddp_compfun.segm, 0
	mov	DDPio.ddp_buffptr.offs, offset preview_upcall
	mov	DDPio.ddp_buffptr.segm, cs
	mov	DDPio.ddp_socket, 72		; ask for experimental sock#
	mov	DDPio.ddp_type, 22		; ask for IP socket type
	mov	bx, offset DDPio			; ds:bx-> DDP param block
	call	doATint				; ask ATALK.SYS for a socket

	cmp	DDPio.ddp_status, 00H		; error return from ATALK.SYS?
	je	chk_ddp_socket			; no, skip ahead to continue
						; yes, no socket for us
	mov	ax, DDPio.ddp_status
	mov	dtemp, ax
	mov	di, offset dtemp
	mov	dx, offset ddp_failed_msg
	call	print_number			; report error and stat return
	jmp	error_ret

;**** do we really require socket 72?
chk_ddp_socket:					; check the socket we opened
	cmp	DDPio.ddp_socket, 72		; did we get the one requested?
	je	ddp_ready			; yes, socket is as expected
						; no, but must have socket 72
	mov	DDPio.ddp_command, DDP_CLOSESOCKET
	mov	bx, offset DDPio
	call	doATint				; close the assigned socket

	mov	dx, offset ddp_wrong_socket_msg	; load error msg
	jmp	error_wrt			; skip ahead to display and ret

ddp_ready:					; DDP socket 72 is ready
	mov	DDPio.ddp_command, DDP_WRITE	; Use param block for WRITE now

; AppleTalk node and DDP socket have been established
	mov	ax, MyInfo.inf_network
	mov	word ptr dtemp, ax
	mov	al, MyInfo.inf_nodeid
	mov	ah, DDPio.ddp_socket
	mov	byte ptr dtemp+2, al
	mov	byte ptr dtemp+3, ah
	mov	di, offset dtemp
	mov	dx, offset atalk_open_msg
	call	print_at_addr			; display AppleTalk node info
	mov	ax, 00H
	mov	dtemp+2, ax

; We need an IP gateway node
nbp_ipgateway:					; Locate our IP gateway node
	movseg	ds,cs
	mov	NBP.nbp_command, NBP_LOOKUP
	mov	NBP.nbp_compfun.offs, 0		; No completion routine [jgn]
	mov	NBP.nbp_compfun.segm, 0
	mov	NBP.nbp_toget, 01H
	mov	NBP.nbp_buffptr.offs, offset NBPt
	mov	NBP.nbp_buffptr.segm, ds
	mov	NBP.nbp_buffsize, (size NBPTuple)
	mov	NBP.nbp_interval, 5
	mov	NBP.nbp_retry, 12
	mov	NBP.nbp_entptr.offs, offset ipgateway_name
	mov	NBP.nbp_entptr.segm, ds
	mov	bx, offset NBP
	call	doATint				; do name-bind lookup

	cmp	NBP.nbp_status, 00H		; status return=error?
	jne	nbp_no_gateway			; yes, report error and exit

	cmp	NBP.nbp_toget, 01H
	je	atp_setup

nbp_no_gateway:					; NBP lookup failed
	mov	ax, NBP.nbp_status
	mov	dtemp, ax
	mov	di, offset dtemp
	mov	dx, offset nbp_no_gateway_msg	; display error msg
	call	print_number

	mov	DDPio.ddp_command, DDP_CLOSESOCKET
	mov	bx, offset DDPio
	call	doATint				; close the assigned socket

	jmp	error_ret			; skip ahead to return

; We need a transport layer to the IP gateway
atp_setup:
	mov	cx, (size AddrBlk)		; MyGateway = NBPt.tup_addr
	movseg	es,cs
	mov	si, offset NBPt.tup_address
	mov	di, offset MyGateway
	rep	movsb

	mov	di, offset NBPt.tup_address	; Display our gateway node
	mov	dx, offset nbp_ipg_addr_msg
	call	print_at_addr

	cmp	use_static, 00H			; Do we have a static address? [jgn]
	je	bds_setup			; No, don't change anything
	mov	IPG.ipg_opcode+3, 3		; Yes, change to IPG_SERVER

bds_setup:
	mov	BDS.bds_buffptr.offs, offset IPG
	mov	BDS.bds_buffptr.segm, ds
	mov	BDS.bds_buffsize, (size IPGInfo)

	mov	ATP.atp_command, ATP_SEND_REQUEST
	mov	ATP.atp_compfun.offs, 0		; No completion routine [jgn]
	mov	ATP.atp_compfun.segm, 0
	mov	cx, (size AddrBlk)		; ATP.atp_addr = NBPt.tup_addr
	movseg	es,cs
	mov	si, offset NBPt.tup_address
	mov	di, offset ATP.atp_addrblk
	rep	movsb
	mov	ATP.atp_buffptr.offs, offset IPG
	mov	ATP.atp_buffptr.segm, ds
	mov	ATP.atp_buffsize, (size IPGInfo)
	mov	ATP.atp_interval, 05H
	mov	ATP.atp_retry, 05H
	mov	ATP.atp_flags, XO_BIT
	mov	ATP.atp_bdsbuffs, 01H
	mov	ATP.atp_bdsptr.offs, offset BDS
	mov	ATP.atp_bdsptr.segm, ds
	mov	bx, offset ATP
	call	doATint

	cmp	ATP.atp_status, 00H		; status return=error?
	jne	atp_no_gateway			; yes, report error and exit

	cmp	ATP.atp_bdsbuffs, 01H
	je	chk_ip_opcode

atp_no_gateway:					; ATP setup failed
	mov	ax, ATP.atp_status
	mov	dtemp, ax
	mov	di, offset dtemp
	mov	dx, offset atp_no_gateway_msg	; display error msg
	call	print_number

	mov	DDPio.ddp_command, DDP_CLOSESOCKET
	mov	bx, offset DDPio
	call	doATint				; close the assigned socket

	jmp	error_ret			; skip ahead to return

chk_ip_opcode:
	cmp	IPG.ipg_opcode.offs, IPG_ERROR	; opcode is 32 bit
	jne	save_ipaddr			; check one word at a time
	cmp	IPG.ipg_opcode.segm, IPG_ERROR	; error from IP gateway?
	jne	save_ipaddr			; no, transport established
						; yes, ATP setup failed
	mov	dx, offset ipg_gateway_err_msg	; display IPG error msg
	mov	ah, 9
	int	21H
	mov	dx, offset IPG.ipg_string
	mov	ah, 9
	int	21H
	mov	al, 13				; display CR-LF
	call	chrout
	mov	al, 10
	call	chrout

	mov	DDPio.ddp_command, DDP_CLOSESOCKET
	mov	bx, offset DDPio
	call	doATint				; close the assigned socket

	jmp	error_ret

; AppleTalk/IP transport layer established
save_ipaddr:
	mov	dx, offset myip_addr_msg
	cmp	use_static, 00H
	jne	show_static
show_dynamic:
	mov	di, offset IPG.ipg_ipaddress
	jmp	show_ipaddr
show_static:
	mov	di, offset static_address
show_ipaddr:
	call	print_ip_addr

	mov	dx, offset ns_ip_addr_msg
	mov	di, offset IPG.ipg_ipname
	call	print_ip_addr

	mov	dx, offset bd_ip_addr_msg
	mov	di, offset IPG.ipg_ipbroad
	call	print_ip_addr

	mov	dx, offset fs_ip_addr_msg
	mov	di, offset IPG.ipg_ipfile
	call	print_ip_addr

; We need to register ourself with the AppleTalk Name Binding Agent
nbp_register_ourself:
	mov	al, MyInfo.inf_nodeid
	mov	NBPtable.tab_entry.tup_address.ablk_nodeid, al
	mov	al, DDPio.ddp_socket
	mov	NBPtable.tab_entry.tup_address.ablk_socket, al

	; print our IP address in our NBP table entry
	mov	bx, offset NBPtable.tab_entry.tup_name
	inc	bx
	xor	dx, dx
	cmp	use_static, 00H
	jne	reg_static1
reg_dynamic1:
	mov	dl, byte ptr IPG.ipg_ipaddress
	jmp	reg_format1
reg_static1:
	mov	dl, byte ptr static_address
reg_format1:
	call	decstr
	mov	al, dot_char
	mov	byte ptr ds:[bx], al
	inc	bx
	cmp	use_static, 00H
	jne	reg_static2
reg_dynamic2:
	mov	dl, byte ptr IPG.ipg_ipaddress+1
	jmp	reg_format2
reg_static2:
	mov	dl, byte ptr static_address+1
reg_format2:
	call	decstr
	mov	al, dot_char
	mov	ds:[bx], al
	inc	bx
	cmp	use_static, 00H
	jne	reg_static3
reg_dynamic3:
	mov	dl, byte ptr IPG.ipg_ipaddress+2
	jmp	reg_format3
reg_static3:
	mov	dl, byte ptr static_address+2
reg_format3:
	call	decstr
	mov	al, dot_char
	mov	ds:[bx], al
	inc	bx
	cmp	use_static, 00H
	jne	reg_static4
reg_dynamic4:
	mov	dl, byte ptr IPG.ipg_ipaddress+3
	jmp	reg_format4
reg_static4:
	mov	dl, byte ptr static_address+3
reg_format4:
	call	decstr

	mov	ax, bx
	sub	ax, offset NBPtable.tab_entry.tup_name
	sub	ax, 1
	mov	NBPtable.tab_entry.tup_name, al

	mov	cx, myip_name_len	; append IPADDR command to our IP
	movseg	es,cs
	mov	si, offset myip_name	; ds:si -> source
	mov	di, bx			; es:di -> dest
	rep	movsb

	; Register our name with NBP agent
	mov	NBP.nbp_command, NBP_REGISTER
	mov	NBP.nbp_compfun.offs, 0		; No completion routine [jgn]
	mov	NBP.nbp_compfun.segm, 0
	mov	NBP.nbp_buffptr.offs, offset NBPtable
	mov	NBP.nbp_buffptr.segm, ds
	mov	NBP.nbp_interval, 01H
	mov	NBP.nbp_retry, 03H
	mov	bx, offset NBP
	call	doATint

	cmp	NBP.nbp_status, 00H
	je	atinit_done

nbp_no_register:
	mov	ax, NBP.nbp_status
	mov	dtemp, ax
	mov	di, offset dtemp
	mov	dx, offset nbp_no_register_msg	; display error msg
	call	print_number

	mov	DDPio.ddp_command, DDP_CLOSESOCKET
	mov	bx, offset DDPio
	call	doATint				; close the assigned socket

	jmp	error_ret			; skip ahead to return

;**** LocalTalk PC Card initialized, ready to TSR
atinit_done:
	movseg	ds,cs
	clc
	ret

;**** Got an error while initializing LocalTalk PC Card
error_wrt:					; Display an error message
	movseg	ds,cs
	stc
	ret
error_ret:				; Board not initialized
	mov	dx,offset null_msg	;error message already printed.
	stc
	ret

	public	print_parameters
print_parameters:
;echo our command-line parameters
	ret

;*******************************************

; Modify ATALK.SYS interrupt number in doATint code (self-modifying code!)
ATPatch:
	mov	al, dl				; Load new interrupt number
	movseg	es,cs
	lea	bx, doATint			; es:bx=offset of doATint code
	inc	bx				; skip to operator for INT
	mov	es:[bx], al			; modify the code
	ret

; Get ATALK.SYS interrupt number
ATGetInt:
	movseg	es,cs
	lea	bx, doATint			; es:bx=offset of doATint code
	inc	bx				; skip to operator for INT
	mov	al, es:[bx]			; load operator for INT
	xor	ah, ah				; zero high byte
	ret					; return INT# to caller

;*******************************************

;*******************************************

; caller must set ds:si -> dest for string, dx 16-bit value to sprint
decstr:
	mov	di,dx
	cmp	dx, 0
	jne	decstr_nonzero
	mov	al,'0'			;yes - easier to just print it, than
	jmp	chrstr			;  to eliminate all but the last zero.
decstr_nonzero:

	xor	ax,ax			;start with all zeroes in al,bx,bp
	mov	bp,ax

	mov	cx,16			;16 bits in one 16 bit registers.
decstr_1:
	rcl	di,1
	xchg	bp,ax
	call	addbit
	xchg	bp,ax
	adc	al,al
	daa
	loop	decstr_1

	mov	cl,'0'			;prepare to eliminate leading zeroes.
	call	bytestr			;output the first two.
	mov	ax,bp
	jmp	wordstr			;output the next four.

addbit:	adc	al,al
	daa
	xchg	al,ah
	adc	al,al
	daa
	xchg	al,ah
	ret

;print the char in al at ds:bx
chrstr:
	mov	byte ptr [bx], al
	inc	bx
	ret

wordstr:
	push	ax
	mov	al,ah
	call	bytestr
	pop	ax
bytestr:
	mov	ah,al
	shr	al,1
	shr	al,1
	shr	al,1
	shr	al,1
	call	digstr
	mov	al,ah
digstr:
	and	al,0fh
	add	al,90h	;binary digit to ascii hex digit.
	daa
	adc	al,40h
	daa
	cmp	al,cl			;leading zero?
	je	digstr_1
	mov	cl,-1			;no more leading zeros.
	jmp	chrstr
digstr_1:
	ret

; caller must set ds:dx -> argument string, ds:di -> AddrBlk struct
print_at_addr:
;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	ax, [di].ablk_network	;print the network number
	xchg	ah, al			; byte-swap network number [jgn]
	mov	dx, 00H
	push	di
	call	decout
	pop	di
	mov	al, ':'
	call	chrout
	xor	ax, ax
	mov	al, [di].ablk_nodeid	; print the nodeid number
	push	di
	call	decout
	pop	di
	mov	al, ':'
	call	chrout
	xor	ax, ax
	mov	al, [di].ablk_socket	; print the socket number
	call	decout
	mov	al,CR
	call	chrout
	mov	al,LF
	call	chrout
	ret

; caller must set ds:dx -> argument string, ds:di -> 32 bit ip address
print_ip_addr:
;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, '['
	call	chrout
	xor	ax, ax
	mov	al, [di]		;print first byte in decimal.
	mov	dx, 00H
	push	di
	call	decout
	pop	di
	mov	al, '.'
	call	chrout
	xor	ax, ax
	mov	al, [di+1]		; print second byte in decimal
	push	di
	call	decout
	pop	di
	mov	al, '.'
	call	chrout
	xor	ax, ax
	mov	al, [di+2]		; print third byte in decimal
	push	di
	call	decout
	pop	di
	mov	al, '.'
	call	chrout
	xor	ax, ax
	mov	al, [di+3]		; print fourth byte in decimal
	call	decout
	mov	al, ']'
	call	chrout
	mov	al,CR
	call	chrout
	mov	al,LF
	call	chrout
	ret

code	ends

	end

