;XMODEMA.68K	DEC-04-89	(ALSO SEE "INFOSTR")
;RS-232 handler using X-MODEM protocol
;by Loren Blaney
;
;REVISION HISTORY:
;MAR-15-86, Original
;SEP-14-86, Converted to ASM68K conventions, and modified.
;MAR-09-87, Modified for Amiga's RS-232 interface.
;JUN-14-88, Changed string termination conventions and fixed small error
; in baud rate.
;FEB-15-89, Handle EOTs so it can talk to the rest of the world.
;DEC-04-89, Modified to allow receiving binary files (EOTFLG), increased
; timeout to 10 seconds, and don't bomb if previous block is received twice.
;
;KNOWN BUGS:
; A garbled ACK is fatal. This is not as reliable as it should be.
; Should use serial I/O as a separate device (#4).
; Should this be a unit instead of a device?
;
;NOTES:
;The bytes in the X-modem format are:
;	SOH (CTRL-A)
;	block count
;	one's complement of the block count
;	128 bytes of data
;	checksum of data
;
;ACKs and NAKs are used as handshake signals. They are sent by the
; RECEIVING end, and are a request for a block of data to be sent from
; the SENDING end. Initially, the RECEIVING end sends NAKs, indicating
; that it is ready for a block of data. The SENDING end sends the data,
; and if it is correctly received, an ACK is sent back from the
; RECEIVING end. If it is incorrectly received, a NAK is sent back, and
; the SENDING end re-sends the same block of data. When the SENDING end
; is finished, it sends an EOT.

	NOLIST
	INCLUDE	SYSPAG		;Get the system page difinitions
	LIST

DEVNUM	EQU	5		;Install this handler as device # 5

;ASCII CHARACTERS:
SOH	EQU	$01
EOT	EQU	$04
ACK	EQU	$06
NAK	EQU	$15

	ORG	$7EAC0
START	EQU	@		;Address where this handler starts

;=======================================================================
;ENTRY POINTS:
;
XMODEM	DC.L	OPENI		;$00 = OPENI
	DC.L	OPENO		;$04 = OPENO
	DC.L	CHIN		;$08 = CHIN
	DC.L	CHOUT		;$0C = CHOUT
	DC.L	CLOSE		;$10 = CLOSE
	DC.L	GETINFO		;$14 = GETINFO
	DC.L	DUMMY		;$18 = SPARE
	DC.L	DUMMY		;$1C = SPARE

;VARIABLES:
INPTR	DC.L	0		;Input buffer byte pointer
OTPTR	DC.L	0		;Output buffer byte pointer
INBUFF	DS.B	128		;Input buffer
INBUFFE	EQU	@		;End of input buffer +1
OTBUFF	DS.B	128		;Output buffer
OTBUFFE	EQU	@		;End of output buffer +1
INCNT	DC.B	0		;Input block counter
OTCNT	DC.B	0		;Output block counter
ACKNAK	DC.B	0		;Contains an ACK or a NAK

;-----------------------------------------------------------------------
;Open (initialize) the RS-232 port and set the input buffer to empty
;
OPENI	MOVE.L	D0,-(SP)	;Save D0

	MOVE.B	#1,INCNT.L	;Init block counter
	MOVE.L	#INBUFFE,INPTR.L ;Set pointer to empty
	CLR.B	EOTFLG		;Init EOT flag
	BSR	OPEN		;Init RS-232 port for I/O, set baud rate

	MOVEQ	#ACK,D0		;Kludge to avoid CLOSEI case (Wayne
	BSR	BYTEOUT		; was write). Send ACK to tell send
				; CLOSE routine we're done
	BSR	FLUSH		;Flush out any bytes in the receive reg
	MOVE.B	#NAK,ACKNAK.L	;Initially send a NAK

	MOVE.L	(SP)+,D0	;Restore D0
DUMMY	RTS

;-----------------------------------------------------------------------
;Open (initialize) the RS-232 port and set the output buffer to empty
;
OPENO	MOVE.L	D0,-(SP)	;Save D0

	MOVE.B	#1,OTCNT.L	;Init block counter
	MOVE.L	#OTBUFF,OTPTR.L	;Set pointer to empty

	BSR	OPEN		;Init RS-232 port for I/O, set baud rate
	BSR	FLUSH		;Flush out any bytes in the receive reg

OPO10	BSR	BYTEIN		;Wait for a NAK
	BEQ.S	OPO10		;Wait forever (i.e. no timeout)
	CMPI.B	#NAK,D0		;This is required because SENDBUF must
	BNE.S	OPO10		; wait for an ACK or NAK at its end

	MOVE.L	(SP)+,D0	;Restore D0
	RTS

;-----------------------------------------------------------------------
;Get a character from the buffer and return it in D0.
; If the buffer is empty then receive another one.
;
CHIN	MOVE.L	A6,-(SP)	;Save A6

	MOVEA.L	INPTR.L,A6	;Get the buffer pointer
	CMPA.L	#INBUFFE,A6	;Is the buffer empty?
	BLO.S	CHI10		;Branch if not
	BSR	GETBUF		;Receive another buffer
	TST.L	ERRLOC		;Did we get an error?
	BNE.S	CHI90		;Branch if yes, exit
	MOVEA.L	#INBUFF,A6	;Reset byte pointer to start of buffer

CHI10	MOVEQ	#0,D0
	MOVE.B	(A6)+,D0	;Get the byte, and increment pointer
	MOVE.L	A6,INPTR.L	;Save the pointer

CHI90	MOVEA.L	(SP)+,A6	;Restore A6
	RTS

;-----------------------------------------------------------------------
;Output the byte in D0 to the buffered RS-232 port
;
CHOUT	MOVE.L	A6,-(SP)	;Save A6

	MOVEA.L	OTPTR.L,A6	;Is there room in the buffer for this
	CMPA.L	#OTBUFFE,A6	; byte?  i.e. is OTPTR < OTBUFFE?
	BLO.S	CHO50		;Branch if yes

	BSR	SENDBUF		;The buffer is full so send it
	TST.L	ERRLOC		;Did we get an error?
	BNE.S	CHO90		;Branch if yes, exit
	MOVEA.L	#OTBUFF,A6	;Reset byte pointer to start of output
				; buffer
CHO50	MOVE.B	D0,(A6)+	;Store the byte in the output buffer
	MOVE.L	A6,OTPTR.L	; bump pointer and save it

CHO90	MOVEA.L	(SP)+,A6	;Restore A6
	RTS

;-----------------------------------------------------------------------
;Close the output file
;
CLOSE	MOVE.L	D0,-(SP)	;Save D0

	MOVEQ	#EOF,D0		;Append an EOF to the end of file
	BSR	CHOUT

	MOVE.L	OTPTR.L,D0	;Is there anything in the output buffer?
	CMPI.L	#OTBUFF,D0
	BEQ.S	CL20		;Branch if not
	BSR	SENDBUF		;Send the output buffer
CL20
	MOVEQ	#EOT,D0		;Send an EOT
	BSR	BYTEOUT

CL90	MOVE.L	(SP)+,D0	;Restore D0
	RTS

;-----------------------------------------------------------------------
;Return the address of the information array in D0
;
GETINFO	MOVE.L	#INFO,D0
	RTS

INFO	DC.L	START		;Start and end addresses of this handler
	DC.L	END
	DC.L	INFOSTR		;Description
INFOSTR	ASCII	'XMODEMA   DEC-04-89  RS-232 handler, 19200 baud'
	DC.B	0

;=======================================================================
;SUBROUTINES
;
;Receive a buffer of data from the RS-232 port
;Register usage:
;	D0 - I/O
;	D1 - Scratch and loop counter
;	D2 - Checksum
;	A6 - Buffer pointer
;
GETBUF	MOVEM.L	D0-D2/A6,-(SP)	;Save registers

GET00	MOVE.B	ACKNAK.L,D0	;Send handshake character (ACK or NAK)
	BSR	BYTEOUT		; to indicate we're ready to receive
	MOVE.B	#NAK,ACKNAK.L	;Assume the transmission will fail

GET10	BSR	BYTEIN		;Read the first byte of the block
	BEQ.S	GET00		;Send a NAK if we time out
	CMPI.B	#EOT,D0		;Is it an EOT?
	BNE.S	GET15		;Branch if not
	ST	EOTFLG		;Set EOT flag
	BRA	GET90		; and exit
GET15
	CMPI.B	#SOH,D0		;Loop until we get the start-of-heading
	BNE.S	GET10

	BSR	BYTEIN		;Get the block number
	BEQ.S	GET00		;Send a NAK if we time out
	MOVE.B	D0,D1
	BSR	BYTEIN		;Get the complement of the block number
	BEQ.S	GET00		;Send a NAK if we time out
	NOT.B	D0
	CMP.B	D1,D0
	BNE.S	GET00		;NAK if they don't match

	CMP.B	INCNT.L,D0	;Is this the block we expect?
	BEQ.S	GET20		;Branch if yes

	ADDQ.B	#1,D0		;Is this the previous block?
	CMP.B	INCNT.L,D0
	BEQ.S	GET17		;Branch if yes

	JSR	VERROR		;Else fatal error
	ASCII	'402 - BAD XMODEM BLOCK NO.'
	DC.B	0
	BRA.S	GET90		;Exit
GET17
	MOVE.W	#127+1,D1	;Init counter for 128 bytes + checksum
GET18	BSR	BYTEIN		;Get the byte
	BEQ.S	GET00		;Send a NAK if we time out, & start over
	DBF	D1,GET18	;Loop for 128 bytes
	MOVE.B	#ACK,ACKNAK.L	;Indicate no errors
	BRA	GET00		;Get next block
GET20
	MOVE.W	#127,D1		;Init loop counter to receive 128 bytes
	MOVEA.L	#INBUFF,A6	;Point to start of output buffer
	CLR.B	D2		;Init checksum
GET50	BSR	BYTEIN		;Get the byte
	BEQ	GET00		;Send a NAK if we time out, & start over
	ADD.B	D0,D2		;Update checksum
	MOVE.B	D0,(A6)+	;Store byte into input buffer
	DBF	D1,GET50	;Loop for 128 bytes

	BSR	BYTEIN		;Read in the checksum
	BEQ	GET00		;Send a NAK if we time out, & start over
	CMP.B	D2,D0		;Compare it to what we computed
	BNE	GET00		;Branch if we failed
	MOVE.B	#ACK,ACKNAK.L	;Otherwise, indicate no errors and exit
	ADDQ.B	#1,INCNT.L	;Count this block

GET90	MOVEM.L	(SP)+,D0-D2/A6	;Restore registers
	RTS

;-----------------------------------------------------------------------
;Send the output buffer out the RS-232 port
;
SENDBUF	MOVEM.L	D0-D2/A6,-(SP)	;Save registers

SEND00	MOVEQ	#SOH,D0		;Send the start-of-heading byte
	BSR	BYTEOUT

	BSR	FLUSH		;Flush out any bytes in the receive reg

	MOVE.B	OTCNT.L,D0	;Send the block count
	BSR	BYTEOUT
	NOT.B	D0		;Send its complement
	BSR	BYTEOUT

	MOVE.W	#127,D1		;Init loop counter to send 128 bytes
	MOVEA.L	#OTBUFF,A6	;Point to start of output buffer
	CLR.B	D2		;Init checksum
SEND10	MOVE.B	(A6)+,D0	;Get byte from output buffer
	ADD.B	D0,D2		;Update checksum
	BSR	BYTEOUT		;Send the byte
	DBF	D1,SEND10	;Loop for 128 bytes

	MOVE.B	D2,D0		;Send the checksum
	BSR	BYTEOUT

SEND20	BSR	BYTEIN		;Wait for and get response
	BEQ.S	SEND20		;Wait forever -- no timeout
	CMPI.B	#ACK,D0		;If it is not an ACK then re-send
	BNE.S	SEND00
	ADDQ.B	#1,OTCNT.L	;Count this block

	MOVEM.L	(SP)+,D0-D2/A6	;Restore registers
	RTS

;======================================================================
;LOWEST LEVEL I/O ROUTINES:
;
SERDATR	EQU	$DFF018		;Serial port data and status register
SERDAT	EQU	$DFF030		;Serial port data register (write)
SERPER	EQU	$DFF032		;Serial port period and control register
INTREQ	EQU	$DFF09C		;Interrupt request (status) bits

;----------------------------------------------------------------------
;Routine to initialize RS-232 I/O.
;
OPEN	MOVE.W	#185,SERPER.L	;Set 8 bits, 19200 baud
	MOVE.W	#$0800,INTREQ.L	;Clear "receive buffer full" status bit
	RTS

;-----------------------------------------------------------------------
;Flush out any characters in the receive register.
;
FLUSH	MOVE.L	D0,-(SP)	;Save D0

FL10	MOVE.W	SERDATR.L,D0	;Read status and data
	BTST	#14,D0		;Receive buffer full
	BEQ.S	FL90		;Branch if not -- exit

	MOVE.W	#$0800,INTREQ.L	;Clear "receive buffer full" status bit
	BRA.S	FL10		;Loop until all bytes flushed

FL90	MOVE.L	(SP)+,D0	;Restore D0
	RTS

;-----------------------------------------------------------------------
;Get a byte from the RS-232 port and return it in D0.
; "EQ" status is returned if a timeout occurs.
;
BYTEIN	MOVEM.L	D1,-(SP)	;Save D1

	MOVEQ	#20,D1		;Init timeout counter for about 10 seconds
	SWAP	D1		;(Trick to save one whole word!)

BYIN10	MOVE.W	SERDATR.L,D0	;Read status and data
	BTST	#14,D0		;Receive buffer full
	BNE.S	BYIN30		;Branch if yes -- go read byte

	SUBQ.L	#1,D1		;Else tick off the timeout counter
	BNE.S	BYIN10		;Loop back if there is still time left

	MOVEM.L	(SP)+,D1	;Restore D1 without changing status
	RTS			;Timed out -- return with "EQ" status

BYIN30	MOVE.W	#$0800,INTREQ.L	;Clear "receive buffer full" status bit
	ANDI.L	#$000000FF,D0	;Return byte in D0
	ANDI.B	#$FB,CCR	;Make sure Z flag is clear (NE)

	MOVEM.L	(SP)+,D1	;Restore D1 without changing status
	RTS

;----------------------------------------------------------------------
;Routine to output the byte in D0 to an RS-232 port.
;
BYTEOUT	MOVEM.W	D0-D1,-(SP)	;Save low word of registers

BYO10	MOVE.W	SERDATR.L,D1	;Get serial port status
	BTST	#13,D1		;Test transmitter buffer empty
	BEQ.S	BYO10		;Branch if it is not empty
	ANDI.W	#$00FF,D0	;Add start and stop bits to the
	ORI.W	#$0100,D0	; data byte
	MOVE.W	D0,SERDAT.L	;Output data

	MOVEM.W	(SP)+,D0-D1	;Restore registers
	RTS


END	EQU	@-1		;Address where this handler ends
	IF	END > $7EF00
;	IF	END > START +$400
	ERROR -- FILE IS TOO LONG
	ENDIF

;-----------------------------------------------------------------------
;Hook this handler into the device handler table
;
	ORG	4 *DEVNUM +DEVTBL
	DC.L	XMODEM

	END
--------------------------
;Hook this handler into the device handler table
;
	ORG	4 *DEVNUM +DEVTBL
	DC