;----------------------------------------------------------------------------
;  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 Poly-8813 computer
;
;	Ver	Date	Desc
;	1.0    11/9/19	Initial version 
;----------------------------------------------------------------------------

; ASCII equates

SOH	EQU	01h
EOT	EQU	04h
ACK	EQU	06h
NAK	EQU	15h
CTRLC	EQU	03h
LF	EQU	0Ah
CR	EQU	0Dh

; BIOS Jump Table Offsets. This table is used to compute BIOS entry
;     points by adding the symbols below to the warm boot entry address
;     stored by CP/M at location 1 on page zero.

CONST	equ	06h		;console status
CONIN	equ	09h		;console input
CONOUT	equ	0Ch		;console output
LIST	equ	0Fh		;list output
PUNCH	equ	12h		;punch output
READER	equ	15h		;reader input
LISTST	equ	2Dh		;list status

;  Serial port I/O is performed using calls directly to the BIOS and 
;     manipulating the IOBYTE to force access to the serial port. The
;     IOBYTE below sets CON to the BAT device. This, in turn, maps console
;     input to the RDR which is mapped to the serial port. Calling conSt
;     performs an input status check on the serial port. Similarly, LST
;     is mapped to the serial port, so calling listSt performs an output
;     status check on the serial port.

SERIOB	equ	96h		;access serial port via CON, RDR, and LST
KBDIOB	equ	95h		;access keyboard via CONST, CONIN
IOBYTE	equ	03h		;location of IOBYTE on page zero
WBOOT	equ	0		;warm boot jump location

;----------------------------------------------------------------------------
; Start of code
;----------------------------------------------------------------------------
	ORG	100H

;  Verify a file name was specified

	lda	PARAM1		;A=1st character of parameter 1
	cpi	' '		;make sure something entered
	jnz	doXfer
	lxi	d,mHelp		;display usage message
	mvi	c,print
	call	bdos
	ret			;return to CPM
	
; Switch to local stack and do the transfer

doXfer	LXI	H,0		;HL=0
	DAD	SP		;HL=STACK FROM CP/M
	SHLD	STACK		;..SAVE IT
	LXI	SP,STACK	;SP=MY STACK
	
	lda	IOBYTE		;save incoming IOBYTE
	sta	iobSave

	xra	a
	sta	SECTNO		;init sector number to zero

	lxi	d,mSend		;DE->send file prompt
	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	CR,LF,CR,'Error Writing File',CR,LF,'$'

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	CR,LF,CR,'Error Closing File',CR,LF,'$'
;
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	CR,'Error - Can''t Make File',CR
	DB	'(directory must be full)',CR,LF,'$'
;
; S U B R O U T I N E S
;
; - - - - - - - - - - - - - - -

;EXIT PRINTING MESSAGE FOLLOWING 'CALL ERXIT'

ERXIT	lda	iobSave		;restore original IOBYTE
	sta	IOBYTE
	
	POP	D		;GET MESSAGE
	MVI	C,PRINT
	CALL	BDOS		;PRINT MESSAGE
	
	LHLD	STACK		;GET ORIGINAL STACK
	SPHL			;RESTORE IT
	RET			;--EXIT-- TO CP/M

; - - - - - - - - - - - - - - -
;MODEM RECV
;-------------------------------------
RECV	PUSH	D		;SAVE DE
MSEC	lxi	d,(7 shl 8)	;1024 cycle loop, 143ms/wrap * 7 = 1 second

MWTI	mvi	a,SERIOB	;set IOBYTE for serial I/O
	sta	IOBYTE
	
	mvi	a,CONST		;console status BIOS call (serial status)
	call	goBios		;character received?
	jnz	MCHAR		;yes

	mvi	a,KBDIOB	;set IOBYTE for keyboard input
	sta	IOBYTE
	
	mvi	a,CONST		;console status BIOS call (console status)
	call	goBios		;operator type something?	
	jz	noChar		;no

	mvi	a,CONIN		;console input BIOS call (console input)
	call	goBios		;get the character
	cpi	CTRLC		;abort requested?
	jnz	noChar		;no

	pop	d		;restore d,e
	ret			;carry is clear (no timeout), exit

noChar	DCR	E		;COUNT DOWN
	JNZ	MWTI		;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	mvi	a,SERIOB	;set IOBYTE for serial I/O	
	sta	IOBYTE

	mvi	a,READER	;reader input BIOS call (serial input)
	call	goBios		;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
	ADD	C		;CALC CKSUM
	MOV	C,A

	pop	psw		;restore char to send
	push 	b		;save checksum
	mov	c,a		;C=char to send
	
	mvi	a,SERIOB	;set IOBYTE for serial I/O	
	sta	IOBYTE

	mvi	a,LIST		;list output BIOS call (serial output)
	call	goBios		;send the character in C

	pop	b		;restore checksum
	ret
	
;-------------------------------------------------------------------
; goBios - Call the BIOS entry vector specified in A. All registers
;    preserved except A.
;-------------------------------------------------------------------
goBios	push	b		;preserve registers
	push	d
	push	h
	call	doBios
	pop	h
	pop	d
	pop	b
	ret
	
doBios	lhld	WBOOT+1		;HL->warm start entry in BIOS
	mov	l,a		;HL->desired BIOS entry
	pchl			;jump to console status function

;DONE - CLOSE UP SHOP

XFER$CPLT:
	CALL	ERXIT
	DB	CR,LF,CR,'Transfer Complete',CR,LF,'$'

abort:	call	erxit
	db	CR,LF,CR,'Transfer Aborted',CR,LF,'$'

;-------------------------------------------------------------------
;  Data area
;-------------------------------------------------------------------
mSend	db	'Send the file now using XMODEM...$'

mHelp	db	CR,'PCGET Ver 1.0 for Poly-8813',CR,LF
	db	'  Receives a file from a PC through the serial',CR,LF
	db	'  port using the XMODEM protocol.',CR,LF,CR
	db	'Usage: PCGET file.ext',CR,LF,'$'
	
	DS	40		;STACK AREA
STACK	DS	2		;STACK POINTER
RSECTNO	DS	1		;RECEIVED SECTOR NUMBER
SECTNO	DS	1		;CURRENT SECTOR NUMBER 
iobSave	ds	1		;save of incoming IOBYTE


; BDOS EQUATES (VERSION 2)

RDCON	EQU	1
WRCON	EQU	2
PRINT	EQU	9
BCONST	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

