******************************************************************
*
* PCGET
*    Allows transfer of files from a PC to a FLEX 1.0 file 
*    system using the XMODEM protocol. An MP-S board on port
*    0-4 is used for the transfer.
*
*    Ver    Date      Desc
*    1.0  02/08/22   Mike Douglas, Initial version
*
******************************************************************

* FLEX 1.0 equates

FCB	equ	$7740		;FLEX system FCB
EOL	equ	$7082		;EOL delimiter from TTYSET
FMS	equ	$7806		;entry point for FMS calls
FMSCLS	equ	$7803		;close all files
WARMS	equ	$7103		;FLEX re-entry address
GETCHR	equ	$710F		;get character from console
PSTRNG	equ	$7118		;display string on console
GETFIL	equ	$7127		;build FCB from command line parameter
NEXTCH	equ	$7121		;get next char from command line
RPTERR	equ	$713C		;report error from the FCB

* FMS equates

OPENWRT	equ	$02		;open for write
DELFILE	equ	$0C		;delete file
EXISTS	equ	$03		;file exists error
SECLEN	equ	124		;length of user data in sector

* Offsets into the FMS file control block (FCB)

fcbCMD	equ	0		;FMS command
fcbERR	equ	1		;error status
fcbFN	equ	4		;file name
fcbEXT	equ	12		;file name extension
fcbBIN	equ	59		;binary/space compression flag

* XMODEM equates

PKTLEN	equ	128		;128 byte xmodem packet length
SOH	equ	$01
EOT	equ	$04
ACK	equ	$06
NAK	equ	$15
EOF	equ	$1A		;ctrl-z character
XMTRIES	equ	10		;number of xmodem retries

* MP-S serial board equates

SIORDRF	equ	$01		;receive data register full
SIOTDRE	equ	$02		;transmit data register empty

* Misc equates

SPACE	equ	$20		;ASCII space
TAB	equ	$09		;ASCII tab character
CR	equ	$0D		;ASCII for carriage return
LF	equ	$0A		;ASCII for line feed
CTRLC	equ	$03		;ASCII for ctrl-c

*-----------------------------------------------------------------------------
* Start of code
*-----------------------------------------------------------------------------
	org	$0100

pcGet	bra	start
	fcb	10		;version 1.0
	
* Validate command line file name and parameters

start	ldx	#FCB		;use the system FCB
	jsr	GETFIL		;get file name from command line
	bcs	dspHelp		;invalid, display help

	jsr	openWrt		;open file for write
	bne	dskErr 		;failed
	
	jsr	getType		;get file type
	jsr	getParm		;get command line parameters
	bne	dspHelp		;command line error

* Prompt the user to send the file, send NAK to tell the host we're 
*    ready for XMODEM reception.

	ldx	#mStart		;display start file transfer prompt
	jsr	PSTRNG
	
	ldx	#xfrBuf		;init pointers
	stx	insPtr		;receive insert pointer
	stx	remPtr		;remove pointer
	clr	mod124		;clear modulus 124 accumulator
	
	ldaa	#1		;init xmodem block number to one
	staa	blkNum
	ldaa	#NAK		;send starting NAK after purging input data
	jsr	purgSio

	jsr	rcvPkt		;queue and remain one packet ahead
	bne	pgmExit		;zero length file
	ldaa	#ACK		;else, ack the packet
	jsr	sndByte
 	
* File transfer loop
	
wrtFile	ldaa	mod124		;mod124 = total bytes received MOD 124
	adda	#PKTLEN		;add bytes received
	cmpa	#2*SECLEN	;>=2*124?
	bcs	not2x		;no
	suba	#SECLEN		;248 becomes 124
not2x	cmpa	#SECLEN		;>=124?
	bcs	saveMod		;no
	suba	#SECLEN		;perform stepwise MOD 124 as we go along
saveMod	staa	mod124		;save updated value
	
	bsr	rcvPkt		;get next XMODEM packet
	bne	rcvDone		;EOT received, all done
	bsr	wrtPkt		;else, write the packet to disk
	bra	wrtFile		;repeat

rcvDone	bsr	wrtLast		;write last packet (possibly partial)
	
pgmExit	ldaa	#ACK		;ack possible eot
	jsr	sndByte
	jsr	FMSCLS		;close open files
	jmp	WARMS		;exit to FLEX
	
* dskErr, dspHelp - Disk error or command line parameter error

dskErr	jsr	RPTERR		;let FLEX report the error
	bra	pgmExit

dspHelp	ldx	#mHelp
	jsr	PSTRNG
	jmp	WARMS

*-----------------------------------------------------------------------------
* wrtPkt - Write PKTLEN bytes to disk from the XMODEM buffer.
* wrtLast - Same but for the final write. For binary files, this is most
*    likely less than a full packet based on mod124.
*-----------------------------------------------------------------------------
wrtLast	ldaa	fText		;text file?
	bne	wrtPkt		;yes, process all bytes
	
	ldaa	#PKTLEN		;compute number of bytes left to write
	suba	mod124		;write count = 128 minus the excess bytes		
	bra	wpSave

* wrtPkt entry

wrtPkt	ldaa	#PKTLEN		;write 128 bytes
wpSave	staa	byteCnt		;byteCnt is num bytes to write
	
wdLoop	ldx	remPtr		;get next byte from xfrBuf
	ldaa	0,x
	inx			;increment pointer
 	stx	remPtr
 	
	ldab	fText		;text file?
	beq	wdBin		;no

	cmpa	#CR		;ASCII CR?
	beq	wdSkip		;yes, skip it
	cmpa	#EOF		;ASCII (CP/M) EOF?
	beq	wdSkip		;yes, skip it

	cmpa	#LF		;ASCII LF?
	bne	wdTab		;no, check for tab
	ldaa	#CR		;substitute CR
	bra	wdBin

wdTab	cmpa	#TAB		;is byte a tab?
	bne	wdBin		;no
	ldaa	#SPACE		;else, substitute space for tab
		
wdBin	ldx	#FCB		;X->FCB for disk I/O	
	jsr	FMS		;write the byte
	bne	dskErr

wdSkip	dec	byteCnt
 	bne	wdLoop		;repeat for 128 bytes (typically)

	ldx	remPtr		;increment and wrap the remove pointer
	cpx	#BUFEND
	bne	wdAck
	ldx	#xfrBuf
	stx	remPtr

wdAck	ldaa	#ACK		;ack the packet
	jsr	sndByte
 	rts

*-----------------------------------------------------------------------------
* rcvPkt - Receive a 128 byte xmodem packet into xfrBuf as pointed to
*   by insPtr. Updates insPtr on exit.
*-----------------------------------------------------------------------------
nakPkt	ldaa	#NAK		;NAK the packet
	jsr	purgSio		;purge receive for 1s then xmit NAK

* Wait for SOH, EOT or CTRL-C

rcvPkt	ldaa	#3		;3 second timeout for SOH
	bsr	rcvByte		
	beq	nakPkt		;timeout

	cmpa	#EOT		;EOT to say we're done?
	bne	notEot		;no
	tsta			;return zero false
	rts

notEot	cmpa	#CTRLC		;ctrl-c to abort?
	bne	notCtlc		;no
	tsta			;return zero false
	rts
	
notCtlc	cmpa	#SOH		;SOH received?
	bne	rcvPkt		;no, keep looking

	bsr	rcv1Sec		;get the block number (1s timeout)
	beq	nakPkt		;timeout, NAK and start over
	staa	rcvBlk		;save the received block number
	
	bsr	rcv1Sec		;get not block number
	beq	nakPkt		;timeout, NAK and start over
	staa	rcvNBlk		;save not block number

* Receive the 128 byte data portion of the packet

	ldx	insPtr		;init buffer pointer
	ldaa	#PKTLEN		;set transfer length
	staa	byteCnt
	clr	chkSum		;init checksum

pktLoop	bsr	rcv1Sec		;get data byte
	beq	nakPkt		;timeout

	staa	0,x		;save the byte
	adda	chkSum		;update checksum
	staa	chkSum

	inx			;increment buffer pointer
	dec	byteCnt		;decrement byte counter
	bne	pktLoop		;and repeat

* Verify the checksum

	bsr	rcv1Sec		;get data byte
	beq	nakPkt		;timeout

	cmpa	chkSum		;checksum match
	bne	nakPkt		;no, packet error

* Checksum good, verify block numbers

	ldaa	rcvNBlk		;verify rcvBlk = not(rcvNBlk)
	coma
	cmpa	rcvBlk		;match?
	bne	nakPkt		;no, packet error

	cmpa	blkNum		;rcvBlk = expected bklNum?
	bne	nakPkt		;no, packet error
	
	inca			;increment expected block number
	staa	blkNum
	
	cpx	#BUFEND		;at end of xfrBuf?
	bne	savePtr		;no
	ldx	#xfrBuf		;reset to start
savePtr	stx	insPtr
	clra			;return zero status
	rts

*-----------------------------------------------------------------------------
* purgSio - Wait for 1 second of idle time on receive line, then transmit the
*	character passed in A (usually ACK or NAK). Clobbers B.
*-----------------------------------------------------------------------------
purgSio	psha			;save character to send

purge	bsr	rcv1Sec		;wait for 1 second without activity
	bne	purge

	pula			;get back the character to send
*				;fall through to sndByte
				
*-----------------------------------------------------------------------------
* sndByte - Send the byte in A through the serial port. Clobbers B.
*-----------------------------------------------------------------------------
sndByte	stx	ioSaveX		;preserve X
	ldx	portAdr		;X->serial port
		
sbLoop	ldab	0,x		;wait for OK to transmit
	andb	#SIOTDRE
	beq	sbLoop	

	staa	1,x		;send the character
	ldx	ioSaveX		;restore X
	rts

*-----------------------------------------------------------------------------
* rcvByte - Receive a byte from the serial port with a timeout specified
*     in seconds in A. If a timeout occurs, zero is returned in A and the
*     zero flag is true. Otherwise, the character is returned in A and zero 
*     flag is false. Clobbers A, B
*-----------------------------------------------------------------------------
rcv1Sec	ldaa	#1		;one second timeout

rcvByte	stx	saveX		;preserve X
rb1Sec	ldx	#29412		;one second, 34 cycles per loop

rbLoop	stx	ioSaveX		;(6) preserve counter
	ldx	portAdr		;(5) X->serial port
	ldab	0,x		;(4) see if a new character is present
	andb	#SIORDRF	;(2)
	bne	rbRead		;(4) new character present

	ldx	ioSaveX		;(5) restore counter
	dex			;(4) decrement timeout
	bne	rbLoop		;(4)

	deca			;decrement seconds count
	bne	rb1Sec
	
	ldx	saveX		;restore X
	clra			;exit with zero status (timeout)
	rts
	
rbRead	ldaa	1,x		;A=byte received
	ldx	saveX		;restore X (non-zero)
	rts

*-----------------------------------------------------------------------------
* openWrt - Open file for write. If the file already exists, prompt
*    the user to confirm replacement. Exits with zero true if the
*    open is successful.
*-----------------------------------------------------------------------------
openWrt	ldx	#FCB		;X->FCB to use
	ldaa	#OPENWRT	;FMS open for write
	staa	fcbCMD,x
	jsr	FMS
	beq	owExit 		;new file opened

	ldaa	fcbERR,x	;status code from open
	cmpa	#EXISTS		;file exists error?
	bne	owExit		;no, some other error
	
	ldx	#mExists	;"file exists, delete?"
	jsr	PSTRNG
	jsr	GETCHR		;get byte from the console
	oraa	#$20		;upper case letters to lower case
	cmpa	#'y		;confirmed?
	beq	owDelet		;yes, delete
	jmp	WARMS		;else exit to FLEX
	
owDelet	ldx	#mWork		"Working..."
	jsr	PSTRNG

	ldx	#FCB		;delete the file
	ldaa	#DELFILE
	staa	fcbCMD,x
	ldaa	fcbFN,x		;save 1st byte of file name
	staa	saveX
	jsr	FMS
	bne	owExit		;delete failed
	
	ldaa	saveX		;restore first char of file name
	staa	fcbFN,x
	ldaa	#OPENWRT	;FMS open for write
	staa	fcbCMD,x
	jsr	FMS
	
owExit	rts

*-----------------------------------------------------------------------------
* getType - Get file type (binary or text). If binary, fcbBIN in the FCB
*   is set to $FF to tell the FMS to treat the file as binary.
*-----------------------------------------------------------------------------
getType	ldx	#txtTbl		;X->table of text file types
	ldaa	#1		;default to text file
	staa	fText

gtLoop	ldaa	0,x		;test for end of table
	beq	gtBin		;match not found, treat as binary
	
	cmpa	FCB+fcbEXT	;compare 1st byte of extension
	bne	gtNext		;doesn't match

	ldaa	1,x		;compare 2nd byte
	cmpa	FCB+fcbEXT+1
	bne	gtNext		;doesn't match

	ldaa	2,x		;compare 3rd byte
	cmpa	FCB+fcbEXT+2
	beq	gtExit		;found match, leave as text file

gtNext	inx			;move to next entry
	inx
	inx
	bra	gtLoop

gtBin	ldaa	#$FF		;set binary mode for FMS
	staa	FCB+fcbBIN
	clr	fText		;not a text file
gtExit	rts

* txtTbl- Table of "strong" text file types

txtTbl	fcc	"TXT"
	fcc	"ASM"
	fcc	"BAS"
	fcb	0		;terminate the table

*-----------------------------------------------------------------------------
* getParm - Get command line parameters. Look for optional digit specifying
*    a transfer port number and text file flags (W, L). Returns zero false
*    for invalid parameters.
*-----------------------------------------------------------------------------
getParm	ldx	#$8004		;default to console port
	stx	portAdr
	
gpLoop	jsr	NEXTCH		;get next command line character
	cmpa	#CR		;terminate with CR or EOL character	
	beq	gpExit
	cmpa	EOL
	beq	gpExit

	cmpa	#SPACE		;skip delimiters
	beq	gpLoop
	cmpa	#',
	beq	gpLoop

	suba	#'0		;look for digit (port number)
	cmpa	#5		;accept 0-4
	bcc	gpFlag		;not a port, look for other flags

	asla			;convert to port address
	asla
	staa	portAdr+1	;LSB of port address
	ldab	#$80		;MSB of port address
	stab	portAdr

	ldx	portAdr		;initialize transfer port
	cmpa	#$04		;using console?
	beq	gpNoRes		;yes, skip port reset
	ldaa	#3		;else, reset serial port
	staa	0,x

gpNoRes	ldaa	#$15		;use 8N1 instead of 8N2
	staa	0,x
	bra	gpLoop		;look for more params

* gpFlag - Check for forced text mode flags

gpFlag	adda	#'0		;get back original character
	oraa	#$20		;upper case letters to lower case
	
	cmpa	#'w		;Windows text file option?
	beq	gpText		;yes
	cmpa	#'l		;Linux text file option?
	bne	gpExit		;no, have bad command line
	
gpText	staa	fText		;set text flag true
	clr	FCB+fcbBIN	;force text file mode
gpExit	rts

*-----------------------------------------------------------------------------
* Message constants 
*-----------------------------------------------------------------------------
mHelp	fcb	CR,LF
	fcc	"PCGET v1.0 for FLEX 1.0"
	fcb	CR,LF,LF
	fcc	"Usage:"
	fcb	CR,LF
	fcc	"  PCGET FILE.EXT [PORT] [W|L]"
	fcb	CR,LF,LF
	fcc	"Where:"
	fcb	CR,LF
	fcc	"  PORT = MP-S port number for transfer (0-4)"
	fcb	CR,LF
	fcc	"  W|L = Force transfer as 'W'indows or 'L'inux text file"
	fcb	CR,LF,LF
	fcc	"Defaults:"
	fcb	CR,LF
	fcc	"  PORT = 1 (console port)"
	fcb	CR,LF
	fcc	"  Extensions TXT, ASM, and BAS transfer as a Windows"
	fcb	CR,LF
	fcc	"  or Linux text file. All others as a binary file."
	fcb	CR,LF,EOT

mStart	fcc	"Start XMODEM send on the PC now..."
	fcb	EOT

mExists	fcc	"File exists, erase? "
	fcb	EOT

mWork	fcc	"Working..."
	fcb	EOT

*-----------------------------------------------------------------------------
* Data area
*-----------------------------------------------------------------------------
remPtr	rmb	2		;xfrBuf remove pointer
byteCnt	rmb	1		;byte count remaining
mod124	rmb	1		;byte count for last write
saveX	rmb	2		;generic save location for X
fText	rmb	1		;non-zero if text file

portAdr	rmb	2		;serial port address
insPtr	rmb	2		;xfrBuf insert pointer
xmRetry	rmb	1		;xmodem retry counter
blkNum	rmb	1		;current xmodem block number
chkSum	rmb	1		;packet checksum
rcvBlk	rmb	1		;block number received
rcvNBlk	rmb	1		;NOT block number received
ioSaveX	rmb	2		;save X for I/O subroutines

xfrBuf	rmb	2*PKTLEN	;XMODEM transfer buffer
BUFEND	equ	*		;end of buffer address

	end	pcGet
