;-------------------------------------------------------------------------
;
;  PCGET - This CP/M program receives a file from a PC via a serial port
;  and writes it to a file on the CP/M system. The file transfer uses the
;  XMODEM protocol. 
;
;  This program is gutted from the Ward Christenson Modem program.
;
;  Hacked together by Mike Douglas for the serial ports controlled by
;  the MEMON PROM monitor.
;
;	Ver	Date	Desc
;	1.0    3/04/22	Initial version
;
;-------------------------------------------------------------------------

;  MEMON equates (instead of a serial port)

MEBASE	equ	0F800h		;base address of MEMON

MECSTA	equ	MEBASE+06h	;console input status
MECIN	equ	MEBASE+09h 	;input from console
MECOUT	equ	MEBASE+0ch	;output to console
MECSTO	equ	MEBASE+0Fh	;console output status

METPSI	equ	MEBASE+18h	;transfer port input status
METPDI	equ	MEBASE+15h 	;input from transfer port
METPDO	equ	MEBASE+12h	;output to transfer port
METPSO	equ	MEBASE+1Bh	;transfer port output status

MECFG	equ	MEBASE+1Eh	;retrieve CPU speed in MHz*8

;  Transfer related equates

SOH	EQU	1
EOT	EQU	4
ACK	EQU	6
NAK	EQU	15H
CTRLC	EQU	3		;Control-C
LF	EQU	10
CR	EQU	13

	ORG	100H

; Verify a file name was specified

	lda	PARAM1		;A=1st character of parameter 1
	cpi	' '		;make sure something entered
	jnz	havep1
	lxi	d,mHelp		;display usage message
	mvi	c,print
	call	bdos
	ret			;return to CPM

havep1	call	MECFG		;get CPU speed*8 from MEMON
	sui	4*8		;A=0 for 4MHz, non zero otherwise
	sta	cpu2Mhz

; See if transfer port "T" specified (2nd parameter)

	mvi	b,0		;assume port b not used
	lxi	d,mSendA	;port a send message
	lda	PARAM2		;A=1st character of parameter 2
	ani	5Fh		;force upper case
	cpi	'T'		;transfer port specified?
	jnz	doXfer		;no, go do the transfer
	inr	b		;force flag to non-zero value
	lxi	d,mSendB	;port b send message
	
; doXfer - Switch to local stack and do the transfer

doXfer	mov	a,b		;store the port b flag
	sta	fPortB
	LXI	H,0		;HL=0
	DAD	SP		;HL=STACK FROM CP/M
	SHLD	STACK		;..SAVE IT
	LXI	SP,STACK	;SP=MY STACK
	xra	a
	sta	SECTNO		;init sector number to zero

	MVI	C,PRINT		;print the send message
	CALL	BDOS

; GOBBLE UP GARBAGE CHARS FROM THE LINE

purge	MVI	B,1		;times out after 1 second if no data
	CALL	RECV
	jc	RECEIVE$FILE	;line is clear, go receive the file
	cpi	ctrlc		;exit if abort requested
	jz	abort
	jmp	purge
;
;**************RECEIVE FILE****************
;
RECEIVE$FILE:
	CALL	ERASE$OLD$FILE
	CALL	MAKE$NEW$FILE
	MVI	A,NAK
	CALL	SEND		;SEND NAK

RECV$LOOP:
RECV$HDR:
	MVI	B,3		;3 SEC TIMEOUT
	CALL	RECV
	JNC	RHNTO		;NO TIMEOUT

RECV$HDR$TIMEOUT:
RECV$SECT$ERR:			;PURGE THE LINE OF INPUT CHARS
	MVI	B,1		;1 SEC W/NO CHARS
	CALL	RECV
	JNC	RECV$SECT$ERR 	;LOOP UNTIL SENDER DONE
	MVI	A,NAK
	CALL	SEND		;SEND NAK
	JMP	RECV$HDR

;GOT CHAR - MUST BE SOH OR CTRL-C TO ABORT

RHNTO:	CPI	SOH
	JZ	GOT$SOH
	cpi	ctrlc		;control-c to abort?
	jz	abort
	CPI	EOT
	JZ	GOT$EOT
	JMP	RECV$SECT$ERR

GOT$SOH:
	MVI	B,1
	CALL	RECV
	JC	RECV$HDR$TIMEOUT
	MOV	D,A		;D=BLK #
	MVI	B,1
	CALL	RECV		;GET CMA'D SECT #
	JC	RECV$HDR$TIMEOUT
	CMA
	CMP	D		;GOOD SECTOR #?
	JZ	RECV$SECTOR
	JMP	RECV$SECT$ERR

;  Receive Sector

RECV$SECTOR:
	MOV	A,D		;GET SECTOR #
	STA	RSECTNO
	MVI	C,0		;INIT CKSUM
	LXI	H,80H		;POINT TO BUFFER
RECV$CHAR:
	MVI	B,1		;1 SEC TIMEOUT
	CALL	RECV		;GET CHAR
	JC	RECV$HDR$TIMEOUT
	MOV	M,A		;STORE CHAR
	INR	L		;DONE?
	JNZ	RECV$CHAR

;VERIFY CHECKSUM

	MOV	D,C		;SAVE CHECKSUM
	MVI	B,1		;TIMEOUT
	CALL	RECV		;GET CHECKSUM
	JC	RECV$HDR$TIMEOUT
	CMP	D		;CHECK
	JNZ	RECV$SECT$ERR
;
;GOT A SECTOR, WRITE IF = 1+PREV SECTOR
;
	LDA	RSECTNO
	MOV	B,A		;SAVE IT
	LDA	SECTNO		;GET PREV
	INR	A		;CALC NEXT SECTOR #
	CMP	B		;MATCH?
	JNZ	DO$ACK

;GOT NEW SECTOR - WRITE IT

	LXI	D,FCB
	MVI	C,WRITE
	CALL	BDOS
	ORA	A
	JNZ	WRITE$ERROR
	LDA	RSECTNO
	STA	SECTNO		;UPDATE SECTOR #
DO$ACK	MVI	A,ACK
	CALL	SEND
	JMP	RECV$LOOP

WRITE$ERROR:
	CALL	ERXIT
	DB	13,10,10,'Error Writing File',13,10,'$'

GOT$EOT:
	MVI	A,ACK		;ACK THE EOT
	CALL	SEND
	LXI	D,FCB
	MVI	C,CLOSE
	CALL	BDOS
	INR	A
	JNZ	XFER$CPLT
	CALL	ERXIT
	DB	13,10,10,'Error Closing File',13,10,'$'
;
ERASE$OLD$FILE:
	LXI	D,FCB
	MVI	C,SRCHF		;SEE IF IT EXISTS
	CALL	BDOS
	INR	A		;FOUND?
	RZ			;NO, RETURN
ERAY:	LXI	D,FCB
	MVI	C,ERASE
	CALL	BDOS
	RET
;
MAKE$NEW$FILE:
	LXI	D,FCB
	MVI	C,MAKE
	CALL	BDOS
	INR	A		;FF=BAD
	RNZ			;OPEN OK

;DIRECTORY FULL - CAN'T MAKE FILE
	CALL	ERXIT
	DB	13,10,10,'Error - Can''t Make File',13,10
	DB	'(directory must be full)',13,10,'$'
;
; S U B R O U T I N E S
;
; - - - - - - - - - - - - - - -

;EXIT PRINTING MESSAGE FOLLOWING 'CALL ERXIT'

ERXIT	POP	D		;GET MESSAGE
	MVI	C,PRINT
	CALL	BDOS		;PRINT MESSAGE
EXIT	LHLD	STACK		;GET ORIGINAL STACK
	SPHL			;RESTORE IT
	RET			;--EXIT-- TO CP/M

; - - - - - - - - - - - - - - -
;MODEM RECV
;-------------------------------------
RECV	PUSH	D		;SAVE
MSEC	lxi	d,23611		;1 sec @ 2MHz
	lda	cpu2Mhz		;2MHz or 4MHz?
	ora	a
	jnz	chkPort		;2MHz
	lxi	d,2*23611	;1 sec @ 4MHz
	
chkPort	lda	fPortB		;using port B (transfer port)
	ora	a
	jnz	MWTIB		;yes, use port B

; Use console port

MWTI	call	MECSTA		;(55) character present?
	jnz	MCHAR		;(10) yes
	
	DCR	E		;(5) ELSE, COUNT DOWN
	JNZ	MWTI		;(10) FOR TIMEOUT
	DCR	D
	JNZ	MWTI
	DCR	B		;DCR # OF SECONDS
	JNZ	MSEC

;MODEM TIMED OUT RECEIVING

	POP	D		;RESTORE D,E
	STC			;CARRY SHOWS TIMEOUT
	RET

;GOT MODEM CHAR

MCHAR	call	MECIN		;get byte received
	POP	D		;RESTORE DE
	PUSH	PSW		;CALC CHECKSUM
	ADD	C
	MOV	C,A
	POP	PSW
	ORA	A		;TURN OFF CARRY TO SHOW NO TIMEOUT
	RET

; Port B input (transfer port). Look for Ctrl-C on console to abort.

MWTIB	call	METPSI		;(65) character present?
	jnz	MCHARB		;(10) yes
	
	DCR	E		;(5) ELSE, COUNT DOWN
	JNZ	MWTIB		;(10) FOR TIMEOUT
	
	call	MECSTA		;see if anything typed
	jz	noCtrlC		;no
	call	MECIN		;get the character
	cpi	ctrlc		;is it a Ctrl-C?
	jnz	noCtrlC		;no
	pop	d		;restore d,e
	ret			;carry is clear (no timeout), exit

noCtrlC	DCR	D
	JNZ	MWTIB
	DCR	B		;DCR # OF SECONDS
	JNZ	MSEC

;MODEM TIMED OUT RECEIVING

	POP	D		;RESTORE D,E
	STC			;CARRY SHOWS TIMEOUT
	RET

;GOT MODEM CHAR

MCHARB	call	METPDI		;get the character
	POP	D		;RESTORE DE
	PUSH	PSW		;CALC CHECKSUM
	ADD	C
	MOV	C,A
	POP	PSW
	ORA	A		;TURN OFF CARRY TO SHOW NO TIMEOUT
	RET

; - - - - - - - - - - - - - - -
;MODEM SEND CHAR ROUTINE
;----------------------------------
;
SEND	PUSH	PSW		;SAVE BYTE TO SEND
	ADD	C		;CALC CKSUM
	MOV	C,A
	
	lda	fPortB		;using port B (transfer port))?
	ora	a
	jnz	SENDWB		;yes, use port b

; Use console port

SENDW	pop	psw		;get back byte to send
	push	b		;save checksum (it's in C)
	mov	c,a		;MECOUT wants byte in C
	call	MECOUT
	pop	b		;restore checksum
	RET

; Use port B (transfer port)

SENDWB	pop	psw		;get back byte to send
	push	b		;save checksum (it's in C)
	mov	c,a		;METPDO wants byte in C
	call	METPDO
	pop	b		;restore checksum
	RET

; Messages

mSendA	db	'Send file using XMODEM...$'
mSendB	db	'Send file through the transfer port using XMODEM...$'
mHelp	db	CR,LF,'PCGET Ver 1.0 for MEMON',CR,LF,LF
	db	'Receives a file from a PC through a MEMON',CR,LF
	db	'serial port using the XMODEM protocol',CR,LF,LF
	db	'Usage: PCGET file.ext [T]',CR,LF
	db	'   MEMON console port used by default',CR,LF
	db	'   Specify T to use the MEMON "Transfer Port"',CR,LF,'$'

;DONE - CLOSE UP SHOP

XFER$CPLT:
	CALL	ERXIT
	DB	13,10,10,'Transfer Complete',13,10,'$'

abort:	call	erxit
	db	13,10,10,'Transfer Aborted',13,10,'$'

	DS	40	;STACK AREA
STACK	DS	2	;STACK POINTER
RSECTNO	DS	1	;RECEIVED SECTOR NUMBER
SECTNO	DS	1	;CURRENT SECTOR NUMBER 
fPortB	ds	1	;flag to use port B instead of A
cpu2Mhz	ds	1	;non-zero if 2MHz CPU

;
; BDOS EQUATES (VERSION 2)
;
RDCON	EQU	1
WRCON	EQU	2
PRINT	EQU	9
CONST	EQU	11	;CONSOLE STAT
OPEN	EQU	15	;0FFH=NOT FOUND
CLOSE	EQU	16	;   "	"
SRCHF	EQU	17	;   "	"
SRCHN	EQU	18	;   "	"
ERASE	EQU	19	;NO RET CODE
READ	EQU	20	;0=OK, 1=EOF
WRITE	EQU	21	;0=OK, 1=ERR, 2=?, 0FFH=NO DIR SPC
MAKE	EQU	22	;0FFH=BAD
REN	EQU	23	;0FFH=BAD
STDMA	EQU	26
BDOS	EQU	5
REIPL	EQU	0
FCB	EQU	5CH	;DEFAULT FCB
PARAM1	EQU	FCB+1	;COMMAND LINE PARAMETER 1 IN FCB
PARAM2	EQU	PARAM1+16	;COMMAND LINE PARAMETER 2
	END
