;**********************************************************************
;
;  PCPUT - This CP/M program sends a file from a CP/M system to a PC
;  via a serial port. The XMODEM protocol is used for file transfer.
;
;
;  Heath/Zenith H89 version. Supports the DCE and DTE serial ports at
;  320Q and 330Q respectively. The Baud rate is forced to 9600 baud.
;
;  Hacked together by Mike Douglas for the H89.

;  This program is gutted from the Ward Christenson Modem program.
;
;	Ver	Date	   Desc
;   	---    --------    ---------------------------
;	1.0    09/01/19	   Initial version
;
;***********************************************************************

;  Serial Port Equates

SIODCE	EQU	320Q		;DCE serial port at 320Q
SIOACR	EQU	SIODCE+5	;DCE status register
SIOADR	EQU	SIODCE+0	;DCE data register

SIODTE	EQU	330Q		;DTE serial port at 330Q
SIOBCR	EQU	SIODTE+5	;DTE status register
SIOBDR	EQU	SIODTE+0	;DTE data register

XMTMASK	EQU	20H		;MASK TO ISOLATE XMIT READY BIT
XMTRDY	EQU	20H		;VALUE WHEN READY
RCVMASK	EQU	01H		;MASK TO ISOLATE RECEIVE READY BIT
RCVRDY	EQU	01H		;VALUE WHEN READY

B9600   EQU     12		;8250 baud rate divisor for 9600 baud

; Transfer related equates

ERRLMT	EQU	5		;MAX ALLOWABLE ERRORS

SOH	EQU	1
EOT	EQU	4
ACK	EQU	6
NAK	EQU	15H
CTRLC	EQU	3
LF	EQU	10
CR	EQU	13

	org	100h

; Verify a file name was specified. Display help message if not.

	lda	PARAM1		;A=1st character of parameter 1
	cpi	' '		;make sure file name present
	jnz	haveFn		;yes, have a file name

	lxi	d,mHelp		;display usage message
	mvi	c,print
	call	BDOS
	ret			;return to CPM

; Switch to local stack and initialize for transfer

havefN	LXI	SP,STACK	;SP=MY STACK

	mvi	b,SIODCE	;assume DCE port used
	lxi	d,mRcvDce	;DE->DCE receive message
	lda	PARAM2		;A=1st character of parameter 2
	ani	5Fh		;force upper case
	cpi	'T'		;DTE port specified?
	jnz	doXfer		;no, go do the transfer

	mvi	b,SIODTE	;use DTE port
	lxi	d,mRcvDte	;DE->DTE send message

doXfer	xra	a		;init sector number to zero
	sta	SECTNO
	
	push	b		;save port address
	MVI	C,PRINT		;display the send file prompt
	CALL	BDOS
	pop	b

	call	initSio		;initialize the 8250 SIO
	CALL	OPEN$FILE	;OPEN THE FILE

;  GOBBLE UP GARBAGE CHARS FROM THE LINE

purge	MVI	B,1		;times out after 1 second if no data
	CALL	RECV
	jc	lineClr		;line is clear, go wait for initial NAK

	cpi	ctrlc		;exit if abort requested
	jz	abort
	jmp	purge

; WAIT FOR INITIAL NAK, THEN SEND THE FILE
	
lineClr	xra	a		;clear crc flag = checksum mode
	sta	crcFlag

WAITNAK	MVI	B,1		;TIMEOUT DELAY
	CALL	RECV
	JC	WAITNAK

	cpi	ctrlc		;abort requested?
	jz	abort

	CPI	NAK		;NAK RECEIVED?
	jz	SENDB		;yes, send file in checksum mode

	cpi	'C'		;'C' for CRC mode received?
	JNZ	WAITNAK		;no, keep waiting

	sta	crcFlag		;set CRC flag non-zero = true
				;fall through to start the send operation
;
;*****************SEND A FILE***************
;

;READ SECTOR, SEND IT

SENDB	CALL	READ$SECTOR
	LDA	SECTNO		;INCR SECT NO.
	INR	A
	STA	SECTNO

;SEND OR REPEAT SECTOR

REPTB	MVI	A,SOH
	CALL	SEND
	LDA	SECTNO
	CALL	SEND
	LDA	SECTNO
	CMA
	CALL	SEND
	lxi	h,0		;init crc to zero
	shld	crc16
	mov	c,h		;init checksum in c to zero
	LXI	H,80H

SENDC	MOV	A,M
	CALL	SEND
	call	calCrc		;update CRC
	INX	H
	MOV	A,H
	CPI	1		;DONE WITH SECTOR?
	JNZ	SENDC

; Send checksum or CRC based on crcFlag

	lda	crcFlag		;crc or checksum?
	ora	a
	jz	sndCsum		;flag clear = checksum

	lda	crc16+1		;a=high byte of CRC
	call	SEND		;send it
	lda	crc16		;a=low byte of crc
	jmp	sndSkip		;skip next instruction	

sndCsum	mov	a,c		;send the checksum byte

sndSkip	call	SEND

;GET ACK ON SECTOR

	MVI	B,4		;WAIT 4 SECONDS MAX
	CALL	RECV
	JC	REPTB		;TIMEOUT, SEND AGAIN

;NO TIMEOUT SENDING SECTOR

	CPI	ACK		;ACK RECIEVED?
	JZ	SENDB		;..YES, SEND NEXT SECT

	cpi	ctrlc		;control-c to abort?
	jz	abort

	JMP	REPTB		;PROBABLY NAK - TRY AGAIN
;
;
; S U B R O U T I N E S
;
;OPEN FILE
OPEN$FILE LXI	D,FCB
	MVI	C,OPEN
	CALL	BDOS
	INR	A		;OPEN OK?
	RNZ			;GOOD OPEN

	CALL	ERXIT
	DB	CR,LF,'Can''t Open File',CR,LF,'$'

; - - - - - - - - - - - - - - -
;EXIT PRINTING MESSAGE FOLLOWING 'CALL ERXIT'
;-------------------------------------
ERXIT	POP	D		;GET MESSAGE
	MVI	C,PRINT
	CALL	BDOS		;PRINT MESSAGE
	JMP	WBOOT		;DO WARM BOOT TO EXIT

; - - - - - - - - - - - - - - -
;MODEM RECV
;-------------------------------------
RECV	PUSH	D		;save DE

MSEC	push	b		;save BC and HL for BDOS call to check
	push	h		;  for operator CTRL-C
	mvi	c,DIRCTIO	;see if abort requested at console
	mvi	e,0FFh		;E=FF means input operation
	call	BDOS
	pop	h		;restore saved registers
	pop	b
	
	cpi	CTRLC		;abort requested?
	jnz	noAbort		;no

	pop	d		;restore DE
	ret			;exit with CTRL-C as the character
	
noAbort	lxi	d,(160 shl 8)	;50 cycles, 6.25ms/wrap*160=1s

rcvSCR	equ	$+1		;address of I/O port for the following IN
MWTI	IN	SIOACR
	ANI	RCVMASK
	CPI	RCVRDY
	JZ	MCHAR		;GOT CHAR

	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

rcvSDR	equ	$+1		;address of I/O port for the following IN
MCHAR	IN	SIOADR
	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		;CHECK IF MONITORING OUTPUT
	ADD	C		;CALC CKSUM
	MOV	C,A

sndSCR	equ	$+1		;address of I/O port for the following IN
SENDW	IN	SIOACR
	ANI	XMTMASK
	CPI	XMTRDY
	JNZ	SENDW
	POP	PSW		;GET CHAR

sndSDR	equ	$+1		;address of I/O port for the following IN
	OUT	SIOADR
	RET

;--------------------------------------------------------------------
; initSio - Initialize status and character IN/OUT instructions based
;   on the selected I/O port passed in B. Force baud rate to 9600. 
;--------------------------------------------------------------------
initSio	mov	a,b		;A=serial port base address
	adi	SIOADR-SIODCE	;A=data register address
	sta	rcvSDR		;modify IN instruction for data register
	sta	sndSDR		;modify OUT instruction for data register

	mov	a,b		;A=serial port base address
	adi	SIOACR-SIODCE	;A=status register address
	sta	rcvSCR		;modify IN for control register in RECV
	sta	sndSCR		;modify IN for control register in SEND

	lxi	d,B9600		;DE=divisor for 9600 baud
        lxi     h,outh84+1      ;point to port in/out instruction
        mvi     a,3             ;baud rate access bit on base+3 port
        add     b               ;A=base+3
        mov     c,a             ;save in c for later
        mov     m,a             ;and modify output instruction
        mvi     a,83H           ;set divisor latch access bit to 1
        call    outh84

        inr     m               ;point to modem control register
        mvi     a,03H           ;  and set DSR & CTS high
        call    outh84

        mov     m,b             ;baud rate divisor LSB
        mov     a,e
        call    outh84

        inr     m               ;baud rate divisor MSB
        mov     a,d
        call    outh84

        mov     m,c             ;reset divisor latch access and
        mvi     a,3             ;  set 8N1
	call    outh84

        dcr     m               ;access interrupt control port
        dcr     m
        xra     a               ;disable device interrupts
        call    outh84

; delay for approximately two character times to let baud rate settle

        xchg                    ;put baud rate divisor in hl
        dad     h               ;multiply by 16 to get delay
        dad     h
        dad     h
        dad     h

baudDly dcx     h
        mov     a,l
        ora     h
        jnz     baudDly
        ret

; self modifying out instruction used by initSio

outh84	out     0               ;port is modified
        ret

;
;FILE READ ROUTINE
;
READ$SECTOR:
	LXI	D,FCB
	MVI	C,READ
	CALL	BDOS
	ORA	A
	RZ
	DCR	A		;EOF?
	JNZ	RDERR

;EOF

	XRA	A
	STA	ERRCT
SEOT	MVI	A,EOT
	CALL	SEND
	MVI	B,3		;WAIT 3 SEC FOR TIMEOUT
	CALL	RECV
	JC	EOTTOT		;EOT TIMEOUT
	CPI	ACK
	JZ	XFER$CPLT

;ACK NOT RECIEVED

EOTERR	LDA	ERRCT
	INR	A
	STA	ERRCT
	CPI	ERRLMT
	JC	SEOT

	CALL	ERXIT
	db	CR,LF,LF
	db	'No ACK received on EOT, but transfer is complete.',CR,LF,'$'

;
;TIMEOUT ON EOT
;
EOTTOT	JMP	EOTERR
;
;READ ERROR
;
RDERR	CALL	ERXIT
	DB	CR,LF,'File Read Error',CR,LF,'$'

;DONE - CLOSE UP SHOP

XFER$CPLT:
	CALL	ERXIT
	DB	CR,LF,LF,'Transfer Complete',CR,LF,'$'

abort	call	erxit
	DB	CR,LF,LF,'Transfer Aborted',CR,LF,'$'

;-----------------------------------------------------------------------------
; calCrc - update the 16-bit CRC with one more byte. 
;    (Copied from M. Eberhard)
; On Entry:
;   a has the new byte
;   crc16 is current except this byte
; On Exit:
;   crc16 has been updated
;   Trashes a,de
;-----------------------------------------------------------------------------
calCrc	push	b		;save bc, hl
	push	h
	lhld	crc16		;get CRC so far
	xra	h		;XOR into CRC top byte
	mov	h,a
	lxi	b,1021h		;bc=CRC16 polynomial
	mvi	d,8		;prepare to rotate 8 bits

; do 8 bit shift/divide by CRC polynomial

cRotLp	dad	h		;16-bit shift
	jnc	cClr		;skip if bit 15 was 0
	mov	a,h		;CRC=CRC xor 1021H
	xra	b
	mov	h,a
	mov	a,l
	xra	c
	mov	l,a
cClr	dcr	d
	jnz	cRotLp		;rotate 8 times

; save the updated CRC and exit

	shld	crc16		;save updated CRC
	pop	h		;restore hl, bc
	pop	b
	ret

;-----------------------------------------
;  messages
;-----------------------------------------

mRcvDce	db	'Start XMODEM receive from DCE port (320Q) now...$'
mRcvDte	db	'Start XMODEM receive from DTE port (330Q) now...$'
mHelp	db	CR,LF,'PCPUT Ver 1.0 for Heath H89',CR,LF,LF
	db	'Transmits a file to a PC through the DCE or DTE',CR,LF
	db	'serial port using the XMODEM protocol.',CR,LF,LF
	db	'Usage: PCPUT file.ext [T]',CR,LF
	db	'   Uses the DCE serial port (320Q) by default.',CR,LF
	db	'   Specify the ''T'' option to use the DTE port (330Q).'
	db	CR,LF,'$'

; Data Area

	DS	40		;STACK AREA
STACK	EQU	$
SECTNO	DS	1		;CURRENT SECTOR NUMBER 
ERRCT	DS	1		;ERROR COUNT
crcFlag	ds	1		;non-zero if using CRC
crc16	ds	2		;computed crc

;
; BDOS EQUATES (VERSION 2)
;
WBOOT	EQU	0		;WARM BOOT JUMP ADDRESS
RDCON	EQU	1
WRCON	EQU	2
DIRCTIO	EQU	6		;CONSOLE DIRECT I/O
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		;SYSTEM FCB
PARAM1	EQU	FCB+1		;COMMAND LINE PARAMETER 1 IN FCB
PARAM2	EQU	PARAM1+16	;COMMAND LINE PARAMETER 2

	END
