; Stripped XMODEM receiver for CDOS
; By M.Eberhard

WRCONF	EQU	2
PRINTF	EQU	9
CLOSEF	EQU	16
SRCHF	EQU	17
WRITEF	EQU	21
CREATF	EQU	22
STDMAF	EQU	26

CDOS	EQU	5
FCB	EQU	5CH
FCBEXT	EQU	FCB+12
FCBRCNT	EQU	FCB+15
FCBSEC	EQU	FCB+32
BLKBUF	EQU	80H

SOH	EQU	1
EOT	EQU	4
ACK	EQU	6
NAK	EQU	15H
LF	EQU	10
CR	EQU	13

MSTAT	EQU	20H
MRBF	EQU	  40H
MTBE	EQU	  80H
MDATA	EQU	MSTAT+1
MBAUD	EQU	0C0H		;9600 baud

;-------------------
	ORG	100H

;Save CDOS SP, create local stack
	LD	HL,0
	ADD	HL,SP
	LD	(STACK),HL
	LD	SP,STACK

;Init TUART & FCB vars
	LD	A,MBAUD
	OUT	MSTAT

	XOR	A
	LD	(FCBSEC),A
	LD	(FCBEXT),A

;Eat UART garbage
	IN	MDATA
	IN	MDATA

;abort if file exists
	LD	DE,FCB
	LD	C,SRCHF
	CALL	CDOS
	INC	A		;-1 means not there
	JR	Z,FILEX

;Create file
	LD	C,CREATF
	CALL	CDOS
	INC	A		;-1 means error
	JR	Z,MFERR

;Tell user we are ready
	CALL	ILPRT

	DB	RTRLEN
RTRMSG:	DB	CR,LF,'Receiving'
RTRLEN	EQU	$-RTRMSG

	LD	A,NAK		;initial NAK
	CALL	TXBYTE

RXLP:	CALL	GETBLK
	JR	C,RXEOT		;C set: EOT received

	CALL	WBLOCK		;Write block to SECBUF

	LD	HL,CURBLK
	INC	(HL)

	LD	A,'.'		;pacifier
	CALL	CTYPE

	CALL	TXACK
	JR	RXLP		;LOOP until EOF

FILEX:	CALL	MSGXIT
	DB	CR,LF,'++FILE EXISTS++$'

MFERR:	CALL	MSGXIT
	DB	CR,LF,'++CREATE ERROR++$

;IF EOT from Modem, flush SECBUF & end
RXEOT:	CALL	WFLUSH
	CALL	TXACK
	CALL	FCLOSE

	CALL	MSGXIT
	DB	CR,LF,'Done$'

;***Subroutine to get block

;Wait for SOH
GETBLK:	LD	HL,ERRCNT
	CLR	(HL)

RXRPT:	LD	B,20		;Timeout for SOH
	CALL	RXBYTE
	JR	C,RXSERR

	CP	SOH
	JR	Z,RXSOH		;SOH: get block

	OR	A		;ignore nulls
	JR	Z,RXRPT

	CP	EOT		;C set, done if EOT
	SCF
	RET	Z

;Eat chrs until timeout, then send NAK
RXSERR:	CALL	RXBYT1
	JR	NC,RXSERR

	LD	A,NAK
	CALL	TXBYTE

	INC	(HL)		;bump error count

	LD	A,(HL)		;Too many errors?
	CP	10
	JR	C,RXRPT		;No: try again

	CALL	FCLOSE

	CALL	MSGXIT
	DB	CR,LF,'++BLOCK ERROR++$'

;Get header
RXSOH:	CALL	RXBYT1		;block #
	JR	C,RXSERR

	LD	D,A		;D= block #

	CALL	RXBYT1		;Get -block #
	JR	C,RXSERR

	CPL
	CMP	D
	JR	NZ,RXSERR	;No match: error

	CPL			;fix block #
	STA	RXBLK		;Save block #

;Receive & store 128 bytes
	LD	BC,8000H	;B=128 bytes, Clr Cksum in C
	LD	HL,BLKBUF

RXCHR:	CALL	RXBYT1
	JR	C,RXSERR
	LD	(HL),A
	INC	HL
	DJNZ	RXCHR

;Verify cksum
	LD	D,C		;D = computed cksum
	CALL	RXBYT1		;Get received cksum
	JR	C,RXSERR

	CMP	D		;cksum match?
	JR	NZ,RXSERR	;No: error

;Good block. See if duplicate
	LD	A,(RXBLK)	;B=Rx block number
	LD	B,A

	LD	A,(CURBLK)
	CMP	B		;Repeated previous block?
	JR	Z,RXACK		;Yes: send ACK to catch up

	INC	A		;next block?
	CMP	B

	RET	Z		;with Carry clear

;Blocks out of order
	CALL	MSGXIT
	DB	CR,LF,'++SYNC ERROR++$'

;Send new ACK to catch up, try again.
RXACK:	CALL	TXACK
	JP	GETBLK

;***Subroutine to close file
FCLOSE:	LD	DE,FCB
	LD	C,CLOSEF
	CALL	CDOS
	INC	A		;-1 means error
	RET	NZ

	CALL	MSGXIT
	DB	CR,LF,'++CLOSE ERROR++$'

;***Subroutine to write block from BLKBUF to SECBUF.
; If SECBUF is full, flush it to disk.
WBLOCK:	LD	HL,(SECPTR)
	EX	DE,HL		;(DE)=dest
	LD	HL,BLKBUF	;(HL)=source
	CALL	MVBLOC

	EX	DE,HL
	LD	(SECPTR),HL

	LD	HL,SECCNT
	INC	(HL)

	CP	16		;Is SECBUF full?
	RET	NZ		;No: return for more

;	fall into WFLUSH

;***Subroutine to write all the data in SECBUF to disk
WFLUSH:	LD	A,(SECCNT)	;# of sectors in SECBUF
	OR	A		;End of file (nothing to send)?
	RET	Z		;Return with Z set if so

	LD	B,A		;Sector count in B
	LD	DE,SECBUF	;beginning of sector data

DKWRLP:	LD	C,STDMAF
	CALL	CDOS

	EX	DE,HL		;move pointer to HL, free up DE

	LD	DE,FCB		;Write to disk
	LD	C,WRITEF
	CALL	CDOS

	OR	A		;return 0 if OK
	JR	NZ,WRERR

				;HL = address in SECBUF
	LD	DE,80H		;DE=Sector length = block size
	ADD	HL,DE		;(HL)=next sector data
	EX	DE,HL		;move address to DE for STDMAF

	DJNZ	DKWRLP

;Reset pointers for empty SECBUF
	XOR	A
	STA	SECCNT		;SECCNT = 0
	LD	HL,SECBUF	;reset SECPTR
	LD	(SECPTR),HL

	RET

WRERR:	CALL	MSGXIT
	DB	CR,LF,'++WRITE ERROR++$'

;***Subroutine to Receive a byte, with 1-sec timeout
RXBYT1:	LD	B,2

;	fall into RXBYTE

;***Subroutine to receive a byte from the modem port
RXBYTE:	PUSH	D

MSEC:	LD	DE,40000	;1/2 sec count down

MWTI:	IN	MSTAT		;Read the receiver status
	AND	A,MRBF
	JR	NZ,MBYTE

	DEC	DE		;Count down timeout timer
	LD	A,D		;Test for 0
	OR	E
	JR	NZ,MWTI	

	DEC	B
	JR	NZ,MSEC

;Modem timeout
	POP	D
	SCF			;Carry means timeout
	RET

;Got byte from modem. Add to checksum & return
MBYTE:	IN	MDATA

	LD	D,A		;Temp save received byte
	ADD	C		;Add to checksum
	LD	C,A
	LD	A,D		;Restore received byte

	POP	D
	OR	A		;Clear Carry: no error
	RET

;***Subroutine to send ACK
TXACK:	LD	A,ACK
;	fall into TXBYTE

;***Subroutine to send byte to modem
TXBYTE:	PUSH	AF		;Save the byte
	ADD	C		;Add byte to checksum in C
	LD	C,A

TXWAIT:	IN	MSTAT
	AND	A,MTBE
	JR	Z,TXWAIT

	POP	AF		;Recover the byte
	OUT	MDATA
	RET

;***Subroutine to move 128 bytes
MVBLOC:	LD	B,128
	LD	A,(HL)
	LD	(DE),A
	INC	HL
	INC	DE
	DJNZ	MOVE
	RET

;***Subroutine to print A on console
CTYPE:	PUSH	D
	LD	E,A
	LD	C,WRCONF
	CALL	CDOS
	POP	D
	RET

;***Subroutine to print In-line Message
ILPRT:	EX	(SP),HL		;Save HL, get message address
	LD	B,(HL)		;Byte count

ILPLP:	INC	HL
	CALL	CTYPE
	DJNZ	ILPLP

	EX	(SP),HL		;Restore HL, find return address
	RET

;***Message Exit: Print string following CALL, return to CDOS
MSGXIT:	POP	D		;Get message address
	LD	C,PRINTF
	CALL	CDOS

	LD	D,DSKBUF	;Reset CDOS DMA Address
	LD	C,STDMAF
	CALL	CDOS

;Restore CDOS's stack
	LD	HL,(STACK)
	LD	SP,HL
	RET			;EXIT TO CDOS

;------------------
RXBLK:	DB	0		;Received block #
CURBLK:	DB	0		;Current block #
ERRCNT:	DB	0		;Errors

SECPTR:	DW	SECBUF		;next sector in SECBUF
SECCNT:	DB	0		;Count of sectors in SECBUF

	DS	30
STACK:	DS	2		;top of stack

SECBUF:	EQU	$		;Sector buffer

	END

