; TRANSMIT.ASM
;
; This program is the transmitter portion of the programs that transmit files
; across a Laplink compatible parallel cable.
;
; This program assumes that the user want to use LPT1: for transmission.
; Adjust the equates, or read the port from the command line if this
; is inappropriate.

		.286
		.xlist
		include 	stdlib.a
		includelib	stdlib.lib
		.list


dseg		segment	para public 'data'

TimeOutConst	equ	4000			;About 1 min on 66Mhz 486.
PrtrBase	equ	10			;Offset to LPT1: adrs.

MyPortAdrs	word	?			;Holds printer port address.
FileHandle	word	?			;Handle for output file.
FileBuffer	byte	512 dup (?)		;Buffer for incoming data.

FileSize	dword	?			;Size of incoming file.
FileNamePtr	dword	?			;Holds ptr to filename

dseg		ends

cseg		segment	para public 'code'
		assume	cs:cseg, ds:dseg


; TestAbort-	Check to see if the user has pressed ctrl-C and wants to
;		abort this program.  This routine calls BIOS to see if the
;		user has pressed a key.  If so, it calls DOS to read the
;		key (function AH=8, read a key w/o echo and with ctrl-C
;		checking).

TestAbort	proc	near
		push	ax
		push	cx
		push	dx
		mov	ah, 1
		int	16h			;See if keypress.
		je	NoKeyPress		;Return if no keypress.
		mov	ah, 8			;Read char, chk for ctrl-C.
		int	21h			;DOS aborts if ctrl-C.
NoKeyPress:	pop	dx
		pop	cx
		pop	ax
		ret
TestAbort	endp



; SendByte-	Transmit the byte in AL to the receiving site four bits
;		at a time.

SendByte	proc	near
		push	cx
		push	dx
		mov	ah, al		;Save byte to xmit.

		mov	dx, MyPortAdrs	;Base address of LPT1: port.

; First, just to be sure, write a zero to bit #4.  This reads as a one
; in the busy bit of the receiver.

		mov	al, 0
		out	dx, al		;Data not ready yet.

; Wait until the receiver is not busy.  The receiver will write a zero
; to bit #4 of its data register while it is busy. This comes out as a
; one in our busy bit (bit 7 of the status register).  This loop waits
; until the receiver tells us its ready to receive data by writing a
; one to bit #4 (which we read as a zero).  Note that we check for a
; ctrl-C every so often in the event the user wants to abort the
; transmission.

		inc	dx		;Point at status register.
W4NBLp:		mov	cx, 10000
Wait4NotBusy:	in	al, dx		;Read status register value.
		test	al, 80h		;Bit 7 = 1 if busy.
		loopne	Wait4NotBusy	;Repeat while busy, 10000 times.
		je	ItsNotbusy	;Leave loop if not busy.
		call	TestAbort	;Check for Ctrl-C.
		jmp	W4NBLp

; Okay, put the data on the data lines:

ItsNotBusy:	dec	dx		;Point at data register.
		mov	al, ah		;Get a copy of the data.
		and	al, 0Fh		;Strip out H.O. nibble
		out	dx, al		;"Prime" data lines, data not avail.
		jmp	$+2		;Short delay to allow the data time
		jmp	$+2		; to stablize on the data lines.
		or	al, 10h		;Turn data available on.
		out	dx, al		;Send data w/data available strobe.

; Wait for the acknowledge from the receiving site.  Every now and then
; check for a ctrl-C so the user can abort the transmission program from
; within this loop.

		inc	dx		;Point at status register.
W4ALp:		mov	cx, 10000	;Times to loop between ctrl-C checks.
Wait4Ack:	in	al, dx		;Read status port.
		test	al, 80h		;Ack = 1 when rcvr acknowledges.
		loope	Wait4Ack	;Repeat 10000 times or until ack.
		jne	GotAck		;Branch if we got an ack.
		call	TestAbort	;Every 10000 calls, check for a
		jmp	W4ALp		; ctrl-C from the user.

; Send the data not available signal to the receiver:

GotAck:		dec	dx		;Point at data register.
		mov	al, 0		;Write a zero to bit 4, this appears
		out	dx, al		; as a one in the rcvr's busy bit.


; Okay, on to the H.O. nibble:

		inc	dx		;Point at status register.
W4NB2:		mov	cx, 10000	;10000 calls between ctrl-C checks.
Wait4NotBusy2:	in	al, dx		;Read status register.
		test	al, 80h		;Bit 7 = 1 if busy.
		loopne	Wait4NotBusy2	;Loop 10000 times while busy.
		je	NotBusy2	;H.O. bit clear (not busy)?
		call	TestAbort	;Check for ctrl-C.
		jmp	W4NB2

; Okay, put the data on the data lines:

NotBusy2:	dec	dx		;Point at data register.
		mov	al, ah		;Retrieve data to get H.O. nibble.
		shr	al, 4		;Move H.O. nibble to L.O. nibble.
		out	dx, al		;"Prime" data lines.
		or	al, 10h		;Data + data available strobe.
		out	dx, al		;Send data w/data available strobe.

; Wait for the acknowledge from the receiving site:

		inc	dx		;Point at status register.
W4A2Lp:		mov	cx, 10000
Wait4Ack2:	in	al, dx		;Read status port.
		test	al, 80h		;Ack = 1
		loope	Wait4Ack2	;While while no acknowledge
		jne	GotAck2		;H.O. bit = 1 (ack)?
		call	TestAbort	;Check for ctrl-C
		jmp	W4A2Lp

; Send the data not available signal to the receiver:

GotAck2:	dec	dx		;Point at data register.
		mov	al, 0		;Output a zero to bit #4 (that
		out	dx, al		; becomes busy=1 at rcvr).

		mov	al, ah		;Restore original data in AL.
		pop	dx
		pop	cx
		ret
SendByte	endp



; Synchronization routines:
;
; Send0s-	Transmits a zero to the receiver site and then waits to
;		see if it gets a set of ones back.  Returns carry set if
;		this works, returns carry clear if we do not get a set of
;		ones back in a reasonable amount of time.

Send0s		proc	near
		push	cx
		push	dx

		mov	dx, MyPortAdrs

		mov	al, 0			;Write the initial zero
		out	dx, al			; value to our output port.

		xor	cx, cx			;Checks for ones 10000 times.
Wait41s:	inc	dx			;Point at status port.
		in	al, dx			;Read status port.
		dec	dx			;Point back at data port.
		and	al, 78h			;Mask input bits.
		cmp	al, 78h			;All ones yet?
		loopne	Wait41s
		je	Got1s			;Branch if success.
		clc				;Return failure.
		pop	dx
		pop	cx
		ret

Got1s:		stc				;Return success.
		pop	dx
		pop	cx
		ret
Send0s		endp


; Send1s-	Transmits all ones to the receiver site and then waits to
;		see if it gets a set of zeros back.  Returns carry set if
;		this works, returns carry clear if we do not get a set of
;		zeros back in a reasonable amount of time.

Send1s		proc	near
		push	cx
		push	dx

		mov	dx, MyPortAdrs		;LPT1: base address.

		mov	al, 0Fh			;Write the "all ones"
		out	dx, al			; value to our output port.

		mov	cx, 0
Wait40s:	inc	dx			;Point at input port.
		in	al, dx			;Read the status port.
		dec	dx			;Point back at data port.
		and	al, 78h			;Mask input bits.
		loopne	Wait40s			;Loop until we get zero back.
		je	Got0s			;All zeros?  If so, branch.
		clc				;Return failure.
		pop	dx
		pop	cx
		ret


Got0s:		stc				;Return success.
		pop	dx
		pop	cx
		ret
Send1s		endp




; Synchronize-	This procedure slowly writes all zeros and all ones to its
;		output port and checks the input status port to see if the
;		receiver site has synchronized.  When the receiver site
;		is synchronized, it will write the value 05h to its output
;		port.  So when this site sees the value 05h on its input
;		port, both sites are synchronized.  Returns with the
;		carry flag set if this operation is successful, clear if
;		unsuccessful.

Synchronize	proc	near
		print
		byte	"Synchronizing with receiver program"
		byte	cr,lf,0

		mov	dx, MyPortAdrs

		mov	cx, TimeOutConst	;Time out delay.
SyncLoop:	call	Send0s			;Send zero bits, wait for
		jc	Got1s			; ones (carry set=got ones).

; If we didn't get what we wanted, write some ones at this point and see
; if we're out of phase with the receiving site.

Retry0:		call	Send1s			;Send ones, wait for zeros.
		jc	SyncLoop		;Carry set = got zeros.

; Well, we didn't get any response yet, see if the user has pressed ctrl-C
; to abort this program.

DoRetry:	call	TestAbort

; Okay, the receiving site has yet to respond.  Go back and try this again.

		loop	SyncLoop

; If we've timed out, print an error message and return with the carry
; flag clear (to denote a timeout error).

		print
		byte	"Transmit: Timeout error waiting for receiver"
		byte	cr,lf,0
		clc
		ret

; Okay, we wrote some zeros and we got some ones.  Let's write some ones
; and see if we get some zeros.  If not, retry the loop.

Got1s:
		call	Send1s			;Send one bits, wait for
		jnc	DoRetry			; zeros (carry set=got zeros).

; Well, we seem to be synchronized.  Just to be sure, let's play this out
; one more time.

		call	Send0s			;Send zeros, wait for ones.
		jnc	Retry0
		call	Send1s			;Send ones, wait for zeros.
		jnc	DoRetry

; We're syncronized.  Let's send out the 05h value to the receiving
; site to let it know everything is cool:

		mov	al, 05h			;Send signal to receiver to
		out	dx, al			; tell it we're sync'd.

		xor	cx, cx			;Long delay to give the rcvr
FinalDelay:	loop	FinalDelay		; time to prepare.

		print
		byte	"Synchronized with receiving site"
		byte	cr,lf,0
		stc
		ret
Synchronize	endp



; File I/O routines:
;
; GetFileInfo-	Opens the user specified file and passes along the file
;		name and file size to the receiving site.  Returns the
;		carry flag set if this operation is successful, clear if
;		unsuccessful.

GetFileInfo	proc	near

; Get the filename from the DOS command line:

		mov	ax, 1
		argv
		mov	word ptr FileNamePtr, di
		mov	word ptr FileNamePtr+2, es

		printf
		byte	"Opening %^s\n",0
		dword	FileNamePtr

; Open the file:

		push	ds
		mov	ax, 3D00h		;Open for reading.
		lds	dx, FileNamePtr
		int	21h
		pop	ds
		jc	BadFile
		mov	FileHandle, ax

; Compute the size of the file (do this by seeking to the last position
; in the file and using the return position as the file length):

		mov	bx, ax			;Need handle in BX.
		mov	ax, 4202h		;Seek to end of file.
		xor	cx, cx			;Seek to position zero
		xor	dx, dx			; from the end of file.
		int	21h
		jc	BadFile

		mov	word ptr FileSize, ax	;Save file length
		mov	word ptr FileSize+2, dx

; Need to rewind file back to the beginning (seek to position zero):

		mov	bx, FileHandle		;Need handle in BX.
		mov	ax, 4200h		;Seek to beginning of file.
		xor	cx, cx			;Seek to position zero
		xor	dx, dx
		int	21h
		jc	BadFile


; Okay, transmit the good stuff over to the receiving site:

		mov	al, byte ptr FileSize		;Send the file
		call	SendByte			; size over.
		mov	al, byte ptr FileSize+1
		call	SendByte
		mov	al, byte ptr FileSize+2
		call	SendByte
		mov	al, byte ptr FileSize+3
		call	SendByte

		les	bx, FileNamePtr			;Send the characters
SendName:	mov	al, es:[bx]			; in the filename to
		call	SendByte			; the receiver until
		inc	bx				; we hit a zero byte.
		cmp	al, 0
		jne	SendName
		stc					;Return success.
		ret

BadFile:	print
		byte	"Error transmitting file information:",0
		puti
		putcr
		clc
		ret
GetFileInfo	endp


; GetFileData-	This procedure reads the data from the file and transmits
;		it to the receiver a byte at a time.

GetFileData	proc	near
		mov	ah, 3Fh			;DOS read opcode.
		mov	cx, 512			;Read 512 bytes at a time.
		mov	bx, FileHandle		;File to read from.
		lea	dx, FileBuffer		;Buffer to hold data.
		int	21h			;Read the data
		jc	GFDError		;Quit if error reading data.

		mov	cx, ax			;Save # of bytes actually read.
		jcxz	GFDDone			; quit if at EOF.
		lea	bx, FileBuffer		;Send the bytes in the file
XmitLoop:	mov	al, [bx]		; buffer over to the rcvr
		call	SendByte		; one at a time.
		inc	bx
		loop	XmitLoop
		jmp	GetFileData		;Read rest of file.

GFDError:	print
		byte	"DOS error #",0
		puti
		print
		byte	" while reading file",cr,lf,0
GFDDone:	ret
GetFileData	endp



; Okay, here's the main program that controls everything.

Main		proc
		mov	ax, dseg
		mov	ds, ax
		meminit


; First, get the address of LPT1: from the BIOS variables area.

		mov	ax, 40h
		mov	es, ax
		mov	ax, es:[PrtrBase]
		mov	MyPortAdrs, ax

; See if we have a filename parameter:

		argc
		cmp	cx, 1
		je	GotName
		print
		byte	"Usage: transmit <filename>",cr,lf,0
		jmp	Quit



GotName:	call	Synchronize	;Wait for the transmitter program.
		jnc	Quit

		call	GetFileInfo	;Get file name and size.
		jnc	Quit

		call	GetFileData	;Get the file's data.

Quit:		ExitPgm			;DOS macro to quit program.
Main		endp

cseg		ends

sseg		segment	para stack 'stack'
stk		byte	1024 dup ("stack   ")
sseg		ends

zzzzzzseg	segment	para public 'zzzzzz'
LastBytes	byte	16 dup (?)
zzzzzzseg	ends
		end	Main
