******************************************************************
*
* PCPUT
*    Allows transfer of files from a DOS-68 file system (Smoke
*    Signal Broadcasting) to a PC using the XMODEM protocol. An
*    MP-S board on port 0-4 is used for the transfer.
*
*    Ver    Date      Desc
*    1.0  04/09/23   Mike Douglas, Initial version
*
******************************************************************

* DOS-68 equates

SYSBAS	EQU	$C000		BASE ADDR FOR THIS SYSTEM

DOSFCB	EQU	SYSBAS+$0880	DOS FCB ADDRESS
TCA	EQU	SYSBAS+$0D80	ORG OF TRANSIENT CMD AREA
DOSJMP	EQU	SYSBAS+$1280	DOS JUMP TABLE
DFMORG	EQU	SYSBAS+$1780	BASE ADDR OF DFM

ZWARMS	EQU	DOSJMP+$03	MONITOR WARM START
ZGETCH	EQU	DOSJMP+$44	DIRECTED INPUT VECTOR
ZOUTST	EQU	DOSJMP+$26	OUTPUT STRING AT X (END ON $00)
ZFLSPC	EQU	DOSJMP+$11	GET A FILE SPECIFICATION
ZGNCHR	EQU	DOSJMP+$17	GET NEXT CHAR
ZTYPDE	EQU	DOSJMP+$29	TYPE THE DISC ERROR MESSAGE

* DFM LINKAGES

ODFM	EQU	DFMORG+0	OPEN DFM
CDFM	EQU	DFMORG+3	CLOSE DFM
DFM	EQU	DFMORG+6	I/O REQUEST ENTRY

* DFM FUNCTION CODES  (XFC)

QSO4R	EQU	4		OPEN FOR READ
QSREAD	EQU	5		READ
QSRC	EQU	6		READ CLOSE
QDEL	EQU	7		DELETE FILE

* DFM ERROR CODES  (XES)

EEOF	EQU	6		END-OF-FILE

* FILE CONTROL BLOCK (FCB) INDICES

XFC	EQU	0		DFM FUNCTION CODE
XES	EQU	1		ERROR STATUS
XFN	EQU	3		FILE NAME
XFE	EQU	9		FILE NAME EXTENSION
XFT	EQU	12		FILE TYPE

*  FILE TYPE CODES  (XFT)

FTCS	EQU	1		SEQUENTIAL COMPRESSED
FTSQ	EQU	2		BINARY SEQUENTIAL

* FLEX equates (this program created from the FLEX 1.0 version of PCGET)

FCB	equ	DOSFCB		;system FCB
FMS	equ	DFM		;entry point DFM calls
FMSCLS	equ	CDFM		;close all files
WARMS	equ	ZWARMS		;DOS68 re-entry address
GETCHR	equ	ZGETCH		;get character from console
PSTRNG	equ	ZOUTST		;display string on console
GETFIL	equ	ZFLSPC		;build FCB from command line parameter
NEXTCH	equ	ZGNCHR		;get next char from command line
RPTERR	equ	ZTYPDE		;report error from the FCB

* FMS equates

OPENRD	equ	QSO4R		;open for read
FMSEOF	equ	EEOF		;end of flag error code
SECLEN	equ	124		;length of user data in sector

* Offsets into the FMS file control block (FCB)

fcbCMD	equ	XFC    		;FMS command
fcbERR	equ	XES    		;error status
fcbFN	equ	XFN    		;file name
fcbEXT	equ	XFE		;file name extension
fcbBIN	equ	XFT		;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
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	TCA		;run in transient command area

pcPut	jsr	ODFM		;open file manager
	ldx	#FCB		;use the system FCB
	jsr	GETFIL		;get file name from command line
	bcs	dspHelp		;invalid, display help

	ldaa	FCB+fcbFN	;no filename at all?
	beq	dspHelp		;none

	ldaa	#OPENRD		;FMS open for read
	staa	fcbCMD,x
	jsr	FMS
	bne	notFnd		;file not found
	
	ldaa	#QSREAD		;init FCB with read command
	staa	fcbCMD,x	

	jsr	getType		;get file type
	jsr	getParm		;get command line parameters
	bne	dspHelp		;command line error

* Prompt the user to start the XMODEM receive operation on the PC. 
*    Wait for a clear receive line for a least one second, then start
*    looking for NAK from the PC.

	ldx	#mStart		;display start file transfer prompt
	jsr	PSTRNG
	ldaa	#1		;init xmodem block number to one
	staa	blkNum
	clr	prvByte		;set previous byte <> CR

clear1	jsr	rcvByte		;loop until input clear for 1 second
	bne	clear1
	
waitNak	jsr	rcvByte		;wait for a NAK = receiver ready
	cmpa	#NAK
	beq	rdFile		;have NAK

	cmpa	#CTRLC		;abort?
	bne	waitNak		;no
	bra	clsExit		;else exit

* dspHelp, notFnd, dskErr - Disk error or command line parameter error

dspHelp	ldx	#mHelp

strExit	jsr	PSTRNG		;display the error message
	jsr	FMSCLS		;close any open files
	jmp	WARMS		;exit to DOS68

notFnd	ldx	#mNotFnd	;file not found message
	bra	strExit

dskErr	jsr	RPTERR		;let DOS68 report the error
	bra	clsExit		;close file and exit

* File transfer loop
	
rdFile	ldx	#xfrBuf		;init bufPtr->xfrBuf
	stx	bufPtr
	ldaa	#PKTLEN		;byteCnt=bytes in an XMODEM packet
	staa	byteCnt

rdLoop 	ldaa	prvByte		;was previous byte a CR?
	cmpa	#CR
	bne	rdNotCr		;no

	ldaa	fWindow		;Windows style EOL (CR/LF)?
	beq	rdNotCr		;no
	ldaa	#LF		;else, yes, inject an LF
	bra	rdStore

rdNotCr	ldx	#FCB		;issue read byte command to FMS
rdNext	jsr	FMS		;get next byte
	bne	readErr		;EOF most likely

	tsta			;null?
	bne	rdNot0		;no
	ldab	fcbBIN,x	;text mode?
	cmpb	#FTCS
	beq	rdNext		;yes, skip the null

rdNot0	cmpa	#CR		;carriage return?
	bne	rdStore		;no
	ldab	fLinux		;Linux style EOL (LF only)?
	beq	rdStore		;no
	ldaa	#LF		;else, substitute LF for CR

rdStore	ldx	bufPtr		;put byte in XMODEM buffer
 	staa	0,x
	staa	prvByte		;save as previous byte sent

 	inx			;increment pointer
 	stx	bufPtr
 	dec	byteCnt
 	bne	rdLoop		;repeat for 128 bytes (PKTLEN)
 	
 	bsr	sndPkt		;send the XMODEM packet
 	bra	rdFile		;repeat

* readErr - If EOF, fill the remainder of the XMODEM packet and
*    send it.

readErr	ldaa	fcbERR,x	;is the error code EOF?
	cmpa	#FMSEOF
	bne	dskErr		;no, something else is wrong
 
	ldab	byteCnt		;on packet boundary?
	cmpb	#PKTLEN
	beq	sndDone		;yes, nothing to fill, send complete
	
	ldaa	#EOF		;else, fill with CP/M EOF character
	ldx	bufPtr
	
fillLp	staa	0,x		;fill with EOF
	inx
	decb
	bne	fillLp
	
	bsr	sndPkt		;send final packet

sndDone	bsr	sndEot		;EOT to say we're done

clsExit	ldx	#FCB		;close the input file
	ldaa	#QSRC
	staa	fcbCMD,x
	jsr	FMS

	jsr	FMSCLS		;close the file manager
	jmp	WARMS		;exit to DOS68

*-----------------------------------------------------------------------------
* sndPkt - Send the 128 byte packet in xfrBuf using the XMODEM protocol.
*-----------------------------------------------------------------------------
sndPkt	ldaa	#XMTRIES	;init retry counter
	staa	xmRetry

* Send header bytes

reSend	ldaa	#SOH		;1st byte is SOH
	bsr	sndByte
	ldaa	blkNum		;2nd byte is the block number
	bsr	sndByte	
	coma			;send complement of block number
	bsr	sndByte

* Send the 128 byte data portion of the packet followed by checksum

	ldx	#xfrBuf		;X->packet data
	ldaa	#PKTLEN		;set transfer length
	staa	byteCnt
	clr	chkSum		;init checksum

pktLoop	ldaa	0,x		;get next byte to send
	bsr	sndByte		;send and update checksum
	inx			;point to next byte
	dec	byteCnt		;decrement bytes remaining
	bne	pktLoop

	ldaa	chkSum		send the checksum
	bsr	sndByte

* All bytes sent. Wait for the response.

	bsr	rcvByte		;get the response character
	beq	sndFail		;timeout on response
	
	cmpa	#ACK		;ack received?
	bne	sndFail		;no, send failed
	
	inc	blkNum		;increment block number
	rts

* sndFail - ACK not received, decrement retry count and try again.

sndFail	dec	xmRetry		;decrement retry counter
	beq	xmFail		;retries used up, failed xmodem transfer

clear2	bsr	rcvByte		;wait for 1 second of clear line
	bne	clear2

	bra	reSend		;re-send the packet

* xmFail - Display failure message then restart program

xmFail	ldx	#mXmdm		;xmodem failure message
	jsr	PSTRNG
	bra	clsExit

*-----------------------------------------------------------------------------
*  sndEot - Send EOT character and wait for ACK response
*-----------------------------------------------------------------------------
sndEot	ldaa	#XMTRIES	;init retry counter
	staa	xmRetry

reEot	ldaa	#EOT		;send the EOT
	bsr	sndByte
	bsr	rcvByte		;wait for response
	beq	eotFail		;timeout

	cmpa	#ACK		;ACK received?
	bne	eotFail		;no, EOT send failed

eotExit	rts			;otherwise, we're done.

* Timeout waiting for ACK to EOT. Decrement retry counter and try again.

eotFail	dec	xmRetry		;decrement retry counter
	beq	eotExit		;just give up - xfer was probably good

clear3	bsr	rcvByte		;wait for 1 second of clear line
	bne	clear3

	jmp	reEot		;try again

*-----------------------------------------------------------------------------
* sndByte - Send the byte in A through the serial port. Adds the byte 
*     to chkSum. Clobbers B.
*-----------------------------------------------------------------------------
sndByte	tab			;B=character to send
	addb	chkSum		;update the checksum
	stab	chkSum

	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 one second
*     timeout. 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. 
*-----------------------------------------------------------------------------
rcvByte	ldx	#29412		;one second, 34 cycles per loop

rbLoop	stx	ioSaveX		;(6) preserve counter
	ldx	portAdr		;(5) X->serial port	
	ldaa	0,x		;(4) see if a new character is present
	anda	#SIORDRF	;(2)
	bne	rbRead		;(4) new character present

	ldx	ioSaveX		;(5) restore counter
	dex			;(4) decrement timeout
	bne	rbLoop		;(4)

	rts			;timeout, exit with Z true
	
rbRead	ldaa	1,x		;return character in A
	inx			;ensure Z is false
	rts

*-----------------------------------------------------------------------------
* getType - Get file type (binary or text). If binary, fcbBIN in the FCB
*   is set to FTSQ to tell the FMS to treat the file as binary.
*-----------------------------------------------------------------------------
getType	ldaa	#1		;default to Windows text file
	staa	fWindow
	clr	fLinux
	jsr	gpText		;init the FCB to text file
	ldx	#txtTbl		;X->table of text file types

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	FCB+fcbBIN	;set binary mode
	anda	#$F0		;affect only lower 4 bits
	oraa	#FTSQ		;set binary mode
	staa	FCB+fcbBIN
	clr	fWindow		;can't be Windows text mode
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 or L to force a text
*    mode transfer with CR/LF ("W"indows flag) or just CR ("L"inux flag).
*    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	
	beq	gpExit

	cmpa	#SPACE		;skip delimiters
	beq	gpLoop

	suba	#'0		;look for digit (port number)
	cmpa	#5		;accept 0-4
	bcc	gpCkTxt		;not a port, check for text flag

	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

* gpCkTxt - Check for forced text mode flag W or L

gpCkTxt	adda	#'0		;get back original character
	oraa	#$20		;upper case letters to lower case
	cmpa	#'w		;Windows text file option?
	beq	gpWin		;yes
	cmpa	#'l		;Linux text file option?
	bne	gpExit		;no, invalid command line

	inc	fLinux		;use Linux EOL (LF)
	clr	fWindow		;not Windows EOL
	bra	gpText

gpWin	inc	fWindow		;set Windows EOL mode

gpText	ldaa	FCB+fcbBIN	;force text file mode
	anda	#$F0		;affect only lower four bits
	oraa	#FTCS
	staa	FCB+fcbBIN
	clra			;exit with zero status
gpExit	rts

*-----------------------------------------------------------------------------
* Message constants 
*-----------------------------------------------------------------------------
mHelp	fcb	CR,LF
	fcc	"PCPUT v1.0 for DOS-68"
	fcb	CR,LF,LF
	fcc	"Usage:"
	fcb	CR,LF
	fcc	"  PCPUT 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	"  compatible text file. All others as a binary file."
	fcb	CR,LF,0

mStart	fcc	"Start XMODEM receive on the PC now..."
	fcb	0

mXmdm	fcb	CR,LF
	fcc	"XMODEM communication failure"
	fcb	CR,LF,0

mNotFnd	fcb	CR,LF
	fcc	"File not found"
	fcb	CR,LF,0

*-----------------------------------------------------------------------------
* Data area
*-----------------------------------------------------------------------------
bufPtr	rmb	2		;pointer into transfer buffer
byteCnt	rmb	1		;byte count remaining
prvByte	rmb	1		;previous byte read
fWindow	rmb	1		;non-zero=Windows text (CR/LF for EOL)
fLinux	rmb	1		;non-zero=Linux text (LF for EOL)

portAdr	rmb	2		;serial port address
xmRetry	rmb	1		;xmodem retry counter
blkNum	rmb	1		;current xmodem block number
chkSum	rmb	1		;packet checksum
ioSaveX	rmb	2		;save X for I/O subroutines

xfrBuf	rmb	PKTLEN		;XMODEM transfer buffer

	end	pcPut
