;*****************************************************************************
;
;  Flop2PC - Transfer floppy image to PC over a serial port
;		(Polymorphic Single Density Controller)
;
;	This program transmits an image of a Polymorphic single density 
;       floppy to a PC over the Poly-88 serial port using the XMODEM
;	protocol. The disk is copied in raw format (256 bytes per sector,
;	10 sectors per track, 35 tracks). The disk image is the linear
;	sequence of the 256 byte sectors from track 0, sector 0 through
;	track 34, sector 9. The leading zeros before the data payload,
;	track and sector metadata and the checksum are not transferred.
;
;	This program runs standalone at 0x100 or under CP/M. In either
;	case, the POLEX PROM is expected in CPU PROM socket 2. Any type
;	of disk image can be read including Polymorphic System-88
;	(Exec) disks. 
;
;	Written by Mike Douglas
;
;	Rev	 Date	    Desc
;	1.0	03/05/18    Original
;
;	1.1	11/16/19    Delay 5 seconds during startup to let the BIOS
;			    motor off delay expire.
;
;*****************************************************************************

; Disk information equates

NUMTRK	equ	35		;number of tracks
NUMSEC	equ	10		;number of sectors per track
SECLEN	equ	256		;sector length (as transmitted)
MINDRV	equ	1		;first drive number
MAXDRV	equ	3		;max drive number 

TRKLEN	equ	(NUMSEC*SECLEN)	;length of track in bytes
DKTRIES	equ	5		;number of disk read retries

; Monitor, CP/M boot entry points

MONITOR	set	PXWARM		;jump to Polex
WBOOT	equ	0		;warm boot jump location

; XMODEM equates

PKTLEN	equ	128		;128 byte xmodem packet length
SOH	equ	01h
EOT	equ	04h
ACK	equ	06h
NAK	equ	15h
EOF	equ	1Ah		;ctrl-z character
XMTRIES	equ	10		;number of xmodem retries

; Misc equates

MAXRAM	equ	0F800h		;max RAM address to check (e.g., video) 
CR	equ	13		;ascii for carriage return
LF	equ	10		;ascii for line feed
DEL	equ	7Fh		;ascii DEL
BS	equ	08h		;ascii backspace
CTRLC	equ	03		;ascii for control-c
JMPINST	equ	0C3h		;jump instruction opcode

	org	0100h		;load address
;-----------------------------------------------------------------------------
;   Initialize for transfer
;-----------------------------------------------------------------------------
init	lxi	sp,ourStk	;initialize stack pointer

	call	dInit		;init the floppy controller hardware
	call	chkCpm		;set flag for CP/M or not
	call	sizeRam		;determine amount of RAM available

	mvi	a,MINDRV	;a=default drive
	sta	drvNum		;need for pgmExit

;  Display welcome message, then wait 5 seconds to get past the motor off
;    delay of CP/M.

	lxi	h,mWelcom	;display welcome message
	call	dispMsg

	mvi	c,5		;delay 5 seconds
	lxi	d,0		;DE=16 bit counter

dlyLoop	dcx	d		;(5) decrement 16 bit counter
	mov	a,d		;(5) reach zero?
	ora	e		;(4)
	jnz	dlyLoop		;(10) loop until zero

	mvi	b,'.'		;progress pacifier
	call	conOut
	
	dcr	c
	jnz	dlyLoop

; restart - Restart the program but skip first entry initialization.

restart	call	dMotoff		;turn off drive motor

getDrv	lxi	h,mDrive	;display drive number prompt
	call	dispMsg
	call	rcvCon		;get byte from the console
	ori	20h		;upper case letters to lower case
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	lxi	h,baseDrv	;ascii 1st drive - baseDrv = 0
	sub	m
	cpi	MAXDRV-MINDRV+1	;validate <= max drive
	jnc	getDrv		;invalid, prompt again

	adi	MINDRV		;restore offset of 1st drive (if any)
	sta	drvNum		;save the drive number to use

; Prompt the user to start the XMODEM receive operation on the PC. 
;    Wait for a clear receive line for a least one second, then start
;    looking for NAK or 'C' (crc protocol) from the PC

	lxi	h,mStart	;display start file transfer prompt
	call	dispMsg

clrRcv1	call	rcvByte		;loop until input clear for 1 second
	jnz	clrRcv1

	xra	a		;set CRC flag to false (checksum mode)
	sta	crcFlag

waitNak	call	chkQuit		;give user chance to abort
	call	rcvByte		;wait for a character
	cpi	NAK
	jz	haveNak		;have a NAK, use checksum protocol

	cpi	'C'		;CRC protocol requested?
	jnz	waitNak		;no

	sta	crcFlag		;set crc flag non-zero = CRC mode

;  NAK or 'C' received meaning the XMODEM receive has started. Initialize
;     for the transfer.

haveNak	call	dRestor		;restore to track 0

	xra	a		;init track we want to zero
	sta	trkNum

	inr	a		;init xmodem block number to one
	sta	blkNum

	lxi	h,trkBuf	;trkBuf is initial starting point for
	shld	sndPtr		;   XMODEM send

;-----------------------------------------------------------------------------
;  mainLp - Loop through all tracks on the disk buffering as many tracks
;    as will fit in RAM before sending via xmodem, then repeat.
;-----------------------------------------------------------------------------
mainLp	lxi	h,trkBuf	;disk buffering always start at trkBuf
	shld	trkPtr

; Read and buffer bufTrks tracks unless all tracks on drive reached first

bufLoop	call	readTrk		;read into trkBuf

	lda	trkNum		;increment track number (preserve HL)
	inr	a
	sta	trkNum	
	cpi	NUMTRK		;done all tracks on the disk?
	jz	sendBuf		;yes, go send the buffered tracks

	lda	trkErr		;read failure on the track?
	ora	a		;if so, send buffered tracks now
	jnz	sendBuf		;so XMODEM won't time out

	xchg			;de=current track pointer
	lhld	bufEnd		;hl=end of buffering space
	call	cmpDeHl		;compare current-end
	jc	bufLoop		;still room, keep going	

; Track buffer is full or all tracks have been read. Send the buffered
;    tracks via xmodem.

sendBuf	call	dMotoff		;turn off drive motor
	call	sndTrks		;send the buffered tracks via xmodem

	lda	trkNum
	cpi	NUMTRK		;done all tracks?
	jnz	mainLp		;no, not done yet

; The entire disk has been read and (mostly) transmitted. See if there are
;    left over bytes to send. If so, send them.

	mov	a,l		;hl->next packet to send, a=lsb of ptr
	cpi	trkBuf AND 0FFh	;pointing to lsb of address of trkBuf?
	jz	noExtra		;same, no extra bytes to send
	call	sndPkt		;otherwise, send one last packet

; Send EOT and wait for response. Then display the success message and
;    start the program over.

noExtra	call	sndEot		;send and get response for EOT
	lxi	h,mDone		;print the all done message
	call	dispMsg
	jmp	restart		;start over asking for a drive
	
;-----------------------------------------------------------------------------
; readTrk - read NUMSEC sectors from the current track into a trkBuf as
;   pointed to by trkPtr. After the track is read, trkPtr is updated
;   by the length of a track to point to the next track buffer. This
;   saved value is also returned in HL.
;-----------------------------------------------------------------------------
readTrk	call	dSeek		;seek to trkNum

	xra	a
	sta	trkErr		;no error on this track yet
	sta	rtStep		;current retry step 

	call	chkQuit		;check for ctrl-c
	call	dSkpSec		;make sure we're between two sector pulses
	mvi	d,NUMSEC	;d=sector counter

; secLoop - Read an entire track. Start at whatever sector comes up next

secLoop	call	dRead		;read the sector
	jnz	secErr		;error, go to retry logic

; Decrement sector count. If full track is done, increment trkPtr
;   to point to the next track buffer in trkBuf

nxtSec	dcr	d		;decrement sector count
	jnz	secLoop

	ei
	lhld	trkPtr		;hl=current track pointer
	lxi	d,TRKLEN	;de=bytes in a track
	dad	d		;hl=start of next track in trkBuf
	shld	trkPtr
	ret

;--------------------------------------------------------------------------
; secErr - read error retry logic. Five retry steps are tried in
;   the following order:
;
;   Step    Action
;    0	Re-read DKTRIES times
;    1	Seek in one track and back, then re-read DKTRIES times
;    2	Seek out to track 0 and back, then re-read DKTRIES times
;    3	Seek in one track and back, then re-read DKTRIES times
;    4	Seek out to track 0 and back, then re-read DKTRIES times
;--------------------------------------------------------------------------	
secErr	in	DSKSEC		;get the sector num that failed
	ani	SECMASK
	mov	e,a		;e=sector that needs to be retried

secRtry	mvi	a,DKTRIES	;init disk retry counter
	sta	dskRtry

retryLp	ei
	call	chkQuit		;check for ctrl-c
	call	dWtSec		;wait for sector specified in e
	call	dRead		;read the sector
	jz	rtDone		;success, retries done

; read failed. Decrement retry count and try again if not zero. Once
;   retry counter expires, increment the step counter to try the
;   next retry stage.

	lxi	h,dskRtry	;decrement retry counter
	dcr	m
	jnz	retryLp		;try again

; read re-tries expired, try next retry step

	ei
	lxi	h,rtStep	;move to next retry step
	inr	m

	mov	a,m		;a=new retry step
	dcr	a		;step 1, seek past track and come back
	jz	skPast

	dcr	a		;step 2 restore to track zero
	jz	restor0

	dcr	a		;step 3, seek past track and come back
	jz	skPast

	dcr	a		;step 4 restore to track zero
	jz	restor0

; retries failed. Display the error, wait for the failed sector number
;    to come under the head again, then jump back into the main
;    read loop which will resume at the next sector.

	call	dspErr		;give up, display the error
	call	dWtSec		;wait for the failed sector number-1
	call	dSkpSec		;skip to read next sector

; rtDone - retry logic is done. Reset the retry step counter and
;    jump to end of sector processing above

rtDone	xra	a		;restart retry step counter
	sta	rtStep
	jmp	nxtSec

;-----------------------------------------------------------------------------
; restor0 - retry step to restore to track zero, then seek back to
;    the current track.
;
; skPast - retry step to seek past the current track, then back to
;    the current track.
;-----------------------------------------------------------------------------
restor0	push	d		;save sector count and number
	call	dRestor		;restore to track zero

reseek	call	dSeek		;seek back to trkNum
	pop	d		;restore sector in e
	jmp	secRtry		;try again

skPast	lxi	h,trkNum	;HL->trkNum
	mov	a,m		;a=current track
	cpi	NUMTRK-1	;at max track?
	jz	restor0		;yes, just try restore again

	push	d		;save sector count and number
	inr	m		;step in one track
	call	dSeek

	dcr	m		;step back out to original track
	jmp	reseek

;-----------------------------------------------------------------------------
; sndTrks - send the tracks buffered in trkBuf via xmodem. trkPtr points
;    to the end+1 of the data to send
;-----------------------------------------------------------------------------
sndTrks	lhld	trkPtr		;hl=end of buffered data + 1
	xchg			;de=end of buffered data + 1
	lhld	sndPtr		;hl=start tranmission address

sndLoop	push	d		;save end pointer
	call	sndPkt		;send a packet
	pop	d		;de=end pointer

; At this point, hl->start of next packet and de->last byte read from disk+1.
;    If the next XMODEM packet will go past the end of the disk data,
;    we don't want to send it yet.

	lxi	b,PKTLEN	;bc=length of XMODEM packet
	dad	b		;hl=address at end of NEXT packet + 1
	call	cmpDeHl		;compare disk end - end of next packet
	jc	sndDone		;next packet will pass the end, stop

	lxi	b,-PKTLEN	;restore hl
	dad	b
	jmp	sndLoop

; sndDone - all the packets we can send have been sent. Move any bytes
;    left over to just before the start of trkBuf. The next group of
;    xmodem packets will be transmitted starting there.

sndDone	call	subDeHl		;hl=de-hl = left over byte count - PKTLEN
	mov	a,l		;a=left over count - PKTLEN
	adi	PKTLEN		;a=left over byte count
	mov	b,a		;b=count of bytes to move
	lxi	h,trkBuf	;hl->start of trkBuf
	jz	mvDone		;no bytes to move, we're done
	
; Copy the leftover data backwards to just before the track buffer. The
;   next XMODEM send sequence will start at the beginning of this copied
;   data instead of at the start of trkBuf.

moveLp	dcx	h		;hl->memory just before trkBuf
	dcx	d		;de->unsent bytes from end of trkBuf
	ldax	d		;move from end of trkBuf to before trkBuf
	mov	m,a	
	dcr	b
	jnz	moveLp

mvDone	shld	sndPtr		;save address from which to send next time
	ret

;-----------------------------------------------------------------------------
; sndPkt - send an xmodem format 128 byte packet. HL points to the 128 byte
;    buffer to send. On exit, HL points to the next 128 byte boundary.
;-----------------------------------------------------------------------------
sndPkt	mvi	a,XMTRIES	;init retry counter
	sta	xmRetry

; First, send header bytes

reSend	call	chkQuit		;check for ctrl-c
	push	h		;save hl for possible re-send

	mvi	a,SOH		;1st byte is SOH
	call	sndByte

	lda	blkNum		;2nd byte is the block number
	call	sndByte

	cma			;2nd complement of block number
	call	sndByte

; Init checksum and CRC and packet length for transmission of data portion

	xra	a		;init crc
	sta	crc16
	sta	crc16+1
	lxi	d,PKTLEN*256	;d=byte counter, e=0 (checksum)

; Loop sending the data bytes and updating checksum and CRC

pktLoop	mov	a,m
	call	sndByte		;send and update checksum in e
	call	calCrc		;update the CRC
	inx	h		;point to next byte
	dcr	d		;decrement bytes remaining
	jnz	pktLoop

; 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	sndByte		;send it
	lda	crc16		;a=low byte of crc
	jmp	sndSkip		;skip next instruction	

sndCsum	mov	a,e		;send the checksum byte

sndSkip	call	sndByte

;  All bytes sent. Wait for the response.

	call	rcvByte		;get the response character
	jz	sndFail		;timeout on response

	cpi	ACK		;ack received?
	jnz	sndFail		;no, send failed

	lda	blkNum		;increment block number
	inr	a
	sta	blkNum

	pop	b		;remove pushed hl, but don't clobber hl
	ret

; sndFail - ACK not received, decrement retry and try again.

sndFail	lxi	h,xmRetry	;point to retry counter
	dcr	m
	jz	xmFail		;retries used up, failed xmodem transfer

; If we've had 3 NAKs on the 1st packet and CRC is selected, assume
;   we took so long to send the 1st packet due to disk retries that
;   the receiver has since timed out and switched to checksum

	lda	crcFlag		;are we in CRC mode
	jz	clrRcv2		;no, ignore the rest of this

	lda	blkNum		;on block 1?
	dcr	a
	jnz	clrRcv2		;no, go on

	lda	xmRetry		;failed three times in a row on block 1?
	sui	XMTRIES-3
	jnz	clrRcv2		;no

	sta	crcFlag		;clear crcFlag to force checksum

;  clrRcv2 - wait for one second of line clear time and send packet again.

clrRcv2	call	rcvByte		;wait for 1 second of clear line
	jnz	clrRcv2

	pop	h		;restore pointer to the packet
	jmp	reSend		;re-send the packet

;  xmFail - Display failure message then restart program

xmFail	lxi	h,mXmdm		;xmodem failure message

; errExit - Display error message at (HL), reset stack pointer, and
;	restart the program

errExit	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	restart		;start over asking for drive num

;-----------------------------------------------------------------------------
; 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,bc
;-----------------------------------------------------------------------------
calCrc	push	d
	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
	pop	d
	ret

;-----------------------------------------------------------------------------
;  sndEot - send EOT character and wait for ACK response
;-----------------------------------------------------------------------------
sndEot	mvi	a,XMTRIES	;init retry counter
	sta	xmRetry

reEot	mvi	a,EOT		;send the EOT
	call	sndByte
	call	rcvByte		;wait for response
	jz	eotFail		;timeout

	cpi	ACK		;ack received?
	jnz	eotFail		;no, eot send failed

	ret			;otherwise, we're done.

; timeout waiting for ACK to EOT. Decrement retry counter and try again

eotFail	lxi	h,xmRetry	;point to retry counter
	dcr	m
	rz			;just give up - xfer was probably good

clrRcv3	call	rcvByte		;wait for 1 second of clear line
	jnz	clrRcv3
	jmp	reEot		;try again

;-----------------------------------------------------------------------------
; dispMsg - display the null-terminated message passed in hl on the
;    console device. Clobbers b, hl
;-----------------------------------------------------------------------------
dispMsg	mov	a,m		;get the next message byte
	ora	a		;null terminates
	rz

	mov	b,a		;conOut wants character in b
	call	conOut
	inx	h		;move to next byte
	jmp	dispMsg

;-----------------------------------------------------------------------------
; rcvCon - Receive a character from the console device, echo it, then
;   wait for a CR. Exits program if Ctrl-c typed. Returns with invalid
;   character (null) if BS or DEL pressed after 1st character
;   Returns character in a, clobbers b.
;-----------------------------------------------------------------------------
rcvCon	call	conIn		;check for input
	jz	rcvCon		;nothing

	ani	7fh
	cpi	CTRLC		;abort requested?
	jz	pgmExit		;yes

	cpi	CR		;return pressed?
	rz			;yes, don't echo it

	mov	b,a		;conOut needs character in b
	call	conOut		;echo it

; Wait for CR, then return 1st character typed

rcvCr	call	conIn		;check for input
	jz	rcvCr		;nothing

	ani	7fh
	cpi	CTRLC		;abort requested?
	jz	pgmExit		;yes

	cpi	DEL		;delete
	rz			;yes, return DEL character

	cpi	BS		;backspace?
	rz			;yes, return BS character

	cpi	CR		;return pressed?
	jnz	rcvCr		;no, keep waiting

	mov	a,b		;return 1st character typed
	ret

;-----------------------------------------------------------------------------
; sndByte - send the byte in a through the specified transfer port. 
;     Adds the byte to register e for checksum. Clobbers b.
;     XMODEM send routine assumes serOut returns with a containing
;     the character sent.
;-----------------------------------------------------------------------------
sndByte	mov	b,a		;b=byte to transmit
	add	e		;update checksum
	mov	e,a		;e=updated checksum
	jmp	serOut		;send the character

;-----------------------------------------------------------------------------
; rcvByte - Receive a byte from specified transfer port with a one second
;     timeout. If a timeout occurs, zero is returned in a and the zero 
;     flag is true. Otherwise, the character is returned in a (could be
;     zero) and zero flag is false. ONESEC must be set based on processor
;     speed and the number of cycles in the serIn call + 46.
;     Clobbers a, b and c.
;-----------------------------------------------------------------------------
rcvByte	lxi	b,ONESEC	;bc=cycles through this loop for 1s

rcvWait	call	serIn		;(17+cycles in serIn)look for a byte
	rnz			;(5)byte received

	dcx	b		;(5)otherwise, decrement timer
	mov	a,b		;(5)one second expire?
	ora	c		;(4)
	jnz	rcvWait		;(10)no, keep waiting

	ret			;return with timeout (zero true and in a)

;-----------------------------------------------------------------------------
; dspErr - display the current track and sector number which just had
;     a read error. If this is the first error on a track, the track
;     number is displayed first.
;
;  on entry:
;     e = sector number
;-----------------------------------------------------------------------------
dspErr	lda	trkErr		;test track error flag
	ora	a
	jnz	dspSec		;track already displayed, go display sector

	inr	a		;set track flag non-zero
	sta	trkErr

; First error on this track. Display "Track xx errors: "

	lxi	h,errTrk	;hl->where to put ascii decimal
	lda	trkNum		;a=track with error on it
	call	bin2dec		;track to ascii
	lxi	h,mTrkErr	;display the track error message
	call	dispMsg

; dspSec - display the sector number with an error

dspSec	lxi	h,errSec	;hl->where to put ascii sector
	mov	a,e		;a=sector where error occured
	call	bin2dec
	call	dispMsg		;display the error
	ret

;--------------------------------------------------------------
; bin2dec - Binary byte in A to 2 ASCII digits at (HL)
;   HL is preserved
;--------------------------------------------------------------
bin2dec	mvi	m,' '		;assume zero supression
	sui	10		;value less than 10?
	jc	do1s		;yes, leading blank
	mvi	m,'1'		;have one ten already

loop10	sui	10		;count 10s
	jc	do1s		;done with 10s, do 1s
	inr	m
	jmp	loop10

do1s	adi	'0'+10		;form ASCII 1s digit
	inx	h		;move to 1s position
	mov	m,a
	dcx	h		;restore hl
	ret

;--------------------------------------------------------------
; chkQuit - check for the user to request abort (ctrl-c). If
;    a character is present on the console port, read it and
;    see if ctrl-c. Clobbers A
;--------------------------------------------------------------
chkQuit	call	conIn		;check for console input
	rz

	ani	7Fh
	cpi	CTRLC		;abort requested?
	rnz			;no

; Ctrl-C typed while program is running. Return to drive prompt.

	lxi	sp,ourStk	;initialize stack pointer
	jmp	restart		;start over asking for drive num

;--------------------------------------------------------------
; pgmExit - Exit to CP/M or to the monitor ROM based on the
;    CP/M flag
;--------------------------------------------------------------
pgmExit	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	cpmExit		;yes

; Exit to ROM monitor

	lxi	h,mExit		;display "exiting" message
	call	dispMsg
	jmp	MONITOR		;jump to ROM monitor entry point

; CP/M exit. If boot drive was used, prompt user to insert CP/M disk

cpmExit	lda	drvNum		;boot drive used?
	sui	MINDRV
	jnz	noDisk		;not 1, disk prompt not needed

	lxi	h,mCpm		;display "insert cp/m disk"	
	call	dispMsg
	call	rcvCon		;wait for a character

noDisk	lxi	h,mExit		;display "exiting" message
	call	dispMsg
	jmp	WBOOT		;warm reboot of CP?m

;--------------------------------------------------------------
; chkCpm - check if running under CP/M. CP/M flag is set true
;   (non-zero) if yes, cleared otherwise.
;--------------------------------------------------------------
; First, initialize entries for stand-alone

chkCpm	xra	a
	sta	cpmFlag		;clear CP/M flag

	mvi	a,MINDRV+'0'	;ascii for minimum drive number
	sta	mDrvMin		;store in the drive prompt message
	sta	baseDrv		;ascii 1st drive - baseDrv = 0

	mvi	a,MAXDRV+'0'	;ascii for max drive number
	sta	mDrvMax

; Determine if we're under CP/M or standalone. CP/M is assumed if
;   a jump instruction is present at the CP/M warm start location (0)
;   and five more jumps (e.g., a jump table) is present at the
;   jump-to destination.
		
	lda	WBOOT		;see if jump instruction present for CP/M
	cpi	JMPINST
	rnz			;no, not CP/M

; A jump instruction is present at the CP/M warm boot location (0),
;   now see if that jump points to five more jumps. If so, assume CP/M

	lxi	h,WBOOT+1	;point to lsb of jump address
	mov	e,m		;e=low byte of jump
	inx	h
	mov	d,m		;de=destination of jump
	mvi	b,5		;look for 5 more jumps (a jump table)

jmpTest	ldax	d		;a=opcode at jump destination
	sui	JMPINST		;another jump present?
	rnz			;no, not CP/M

	inx	d		;move to next jump
	inx	d
	inx	d
	dcr	b
	jnz	jmpTest

	dcr	a		;a=0ffh
	sta	cpmFlag		;CP/M flag to non-zero = true

; We're running under CP/M. Change drive prompt message to show drive
;    letters instead of drive numbers and change baseDrv to convert
;    an 'A' to the base drive number (MINDRV).

	mvi	a,'A'		;'A' in drive message instead of number
	sta	mDrvMin

	adi	MAXDRV-MINDRV	;max drive letter
	sta	mDrvMax

	mvi	a,'a'		;ascii 1st drive - baseDrv = 0
	sta	baseDrv

	ret

;------------------------------------------------------------------
; sizeRam - determine how much RAM we have for buffering tracks.
;   Sets the bufEnd variable which points to end address of 
;   the last possible track buffer + 1
;------------------------------------------------------------------

; If running under CP/M, use the BIOS pointer to determine the top
;    of available RAM. Otherwise, size RAM manually.

sizeRam	lhld	WBOOT+1		;get address of BIOS jump table+3
	dcx	h		
	dcx	h
	dcx	h		;hl->top of usable ram+1
	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	ramEnd		;yes, we have the end of RAM

;  Not under CP/M. Find the end of RAM manually.

	lxi	h,(trkBuf+0ffh) AND 0FF00h   ;next 256 byte boundary

ramLoop	mov	a,m		;a=current RAM content
	inr	m		;change RAM
	cmp	m		;did RAM change?
	mov	m,a		;restore RAM
	jz	ramEnd		;end of RAM found

	inr	h		;next page
	mov	a,h		;reached max RAM address?
	cpi	MAXRAM/256
	jnz	ramLoop

; ramEnd - end of RAM found. Now determine the end address + 1 of the
;   last track buffer that will fit in RAM. Store in bufEnd

ramEnd	xchg			;de=end of RAM + 1
	dcr	d		;subtract 256 bytes from end of RAM
	lxi	h,trkBuf	;hl=start of track buffer
	lxi	b,TRKLEN	;bc=length of track in bytes

; Loop increasing hl by TRKLEN until hl > end of RAM.

bfEndLp	dad	b		;hl=hl+track length
	call	cmpHlDe		;compare hl-de
	jc	bfEndLp		;still more room, keep going

; Subtract one track length from hl, this will be the end address + 1 of
;   the the last track buffer that will fit in RAM

	lxi	b,-TRKLEN	;subtract one track length
	dad	b		;hl = end address of last track + 1
	shld	bufEnd		;save as bufEnd
	ret

;--------------------------------------------------------------------
; cmHlDe - compare HL-DE. Carry set if HL<DE, carry clear if HL>=DE
;--------------------------------------------------------------------
cmpHlDe	mov	a,l		;compare HL-DE, do lsbs first
	sub	e
	mov	a,h		;do msbs
	sbb	d
	ret			;carry set HL<DE, clear HL>=DE

;--------------------------------------------------------------------
; cmDeHl - compare DE-HL. Carry set if DE<HL, carry clear if DE>=HL
;--------------------------------------------------------------------
cmpDeHl	mov	a,e		;compare DE-HL, do lsbs first
	sub	l
	mov	a,d		;do msbs
	sbb	h
	ret			;carry set DE<HL, clear DE>=HL

;--------------------------------------------------------------------
; subDeHl - HL=DE-HL
;--------------------------------------------------------------------
subDeHl	mov	a,e		;subtract DE-HL, do lsbs first
	sub	l
	mov	l,a		;lsb result in l
	mov	a,d		;do msbs
	sbb	h
	mov	h,a		;msb result in h	
	ret

;---------------------------------------------------------------------
; Message constants
;---------------------------------------------------------------------
mWelcom	db	cr,'===== Floppy to PC Image Transfer, Version 1.1 ====='
	db	cr,'    (Poly-88 with Polymorphic Single Density FDC)'
	db	cr,cr
	db	'This program transfers an image of a Polymorphic single'
	db	cr,'density disk to a PC through the serial port using the'
	db	cr,'XMODEM protocol (checksum or CRC).',0

mDrive	db	cr,cr,'Insert and specify source drive ('
mDrvMin	db	'x-'
mDrvMax	db	'x) or X to exit: ',0

mStart	db	cr,cr,'Start XMODEM receive operation on the PC now...',0
mDone	db	cr,cr,'Transfer complete!',cr,0
mExit	db	cr,cr,'Exiting...',cr,0
mCpm	db	cr,cr
	db	'Insert CP/M disk into drive A, then press Return...',0

mXmdm	db	cr,cr,'XMODEM communication failure',cr,0
mNoTrk0	db	cr,cr,'Seek to track 0 failed',cr,0
mNoDisk	db	cr,cr,'No disk present',cr,0

mTrkErr	db	cr,cr,'Track '
errTrk	db	'xx failed sectors: ',0
errSec	db	'xx ',0


;****************************************************************************
;
;  Hardware specific console and serial I/O routines. 
;     The following four routines must be written to provide a common
;     interface to the hardware on which this program is running. The
;     port number specified for serIn and serOut matches the port number
;     input from the operator via the port menu.
;
;****************************************************************************

; POLEX (Poly-88 Executive by M Eberhard) entry points

PXCSTA	EQU	0F403h		;console input status
PXCIN	EQU	0F406h		;console input
PXCOUT	EQU	0F409h		;console output
PXSSTA	EQU	0F40Ch		;serial port input status
PXSIN	EQU	0F40Fh		;serial port input
PXSOUT	EQU	0F412h		;serial port output
PXSOST	EQU	0F415h		;serial port output status
PXWARM	equ	0F421H		;Polex warm start entry
PTIMER  equ	0F7FDh		;LSB of 60hz real time counter

ONESEC	equ	15890		;rcvByte loop count for 1 second

;----------------------------------------------------------------------------
; conIn - input character from console
;    inputs:
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;----------------------------------------------------------------------------
conIn	call	PXCSTA		;see if new character present
	rz			;no

	call	PXCIN		;get the typed character
	ora	a		;attempt to force Z false		
	rnz			;return with Z false

	cpi	1		;A is zero so this compare fails
	ret

;----------------------------------------------------------------------------
; conOut - output character to console
;    inputs: b = character to send
;    clobbers a
;----------------------------------------------------------------------------
conOut	push	b		;save bc for compatibility
	mov	c,b		;PXCOUT wants character in C
	call	PXCOUT
	pop	b		;restore
	ret

;----------------------------------------------------------------------------
; Hardware specific I/O
; serIn - input character from serial port
;    inputs:
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;    70 cycles with no character 
;-----------------------------------------------------------------------------
serIn	call	PXSSTA		;(59) see if new character present
	rz			;(11) no

	call	PXSIN		;read and return typed character
	ora	a		;attempt to force Z false		
	rnz			;return with Z false

	cpi	1		;A is zero so this compare fails
	ret

;-----------------------------------------------------------------------------
; Hardware specific I/O
; serOut - output character to serial
;    inputs: b = character to send
;    output: a = character sent
;    clobbers none
;-----------------------------------------------------------------------------
serOut	push	b		;save bc for compatibility
	mov	c,b		;PXSOUT wants character in C
	call	PXSOUT

	pop	b		;restore bc
	mov	a,b		;exit with character in A
	ret

		
;****************************************************************************
;
; Polymorphic single density controller disk I/O routines
;
;****************************************************************************

;-----------------------------
; Polymorphic Sector Format
;
; 10 bytes 00h
;  1 byte  sector number with msb set
;  1 byte  track number
;  256 bytes data
;  2 bytes 16-bit checksum
;-----------------------------
MTOSTRT	equ	-60 AND 0FFh	;1 sec motor start count up @ 60hz
MTOSYNC	equ	-20 AND 0FFh	;1/3 sec index sync count up @ 60hz
SYNC	equ	0E6h	;disk sync character

DCBASE	equ	20h	;disk controller base I/O address

DSKSCTL	equ	DCBASE+00h	;6852 Status/Control Register 1
			;6852 status bits:
RDA	equ	01h	;RX data available
TDRA	equ	02h	;TX data register available
DCDN	equ	04h	;active-low DCD
CTSN	equ	08h	;active-low CTS
TUF	equ	10h	;transmit underflow
RXOVRN	equ	20h	;receiver overrun
PE	equ	40h	;parity error
IRQ	equ	80h	;interrupt request

			;Control Register 1 Commands
CR2SEL	equ	0Bh	;select Control Reg 2, reset things
CR3SEL	equ	40h	;select Control Reg 3
SYNSEL	equ	80h	;select sync-code register
IDLE68	equ	0C0h	;no command, select data register
RSET68	equ	0CBh	;reset things, select data register

			;Control Register 2 Commands
CR2RES	equ	18h	;8 bits, no parity (see data sheet)

			;Control Register 2 Commands
CR3RES	equ	0Ch	;clear status, internal sync,
			;..2-sync chr mode

DSKSDAT	equ	DCBASE+01h	;6852 Data/Control

DSKSEL	equ	DCBASE+08h	;8255 Port A (output), drive select
			;<1:0> = Unit number
			;0 means no unit selected

DSKSEC	equ	DCBASE+09h	;8255 Port B (input), sector register
SECMASK	equ	0Fh	;bits 3:0 are the current sector
TRACK0	equ	10h	;bit 4 is track 0 detect
WPROT	equ	20h	;bit 5 is write protect

DSKCTL	equ	DCBASE+0Ah	;8255 Port C write label
DSKSTAT	equ	DCBASE+0Ah	;8255 Port C read label
			;<0> = Sector pulse interrupt
			;      (Mode 1 interrupt output)
			;<1> = Sector pulse
			;      (Mode 1 input buffer full)
			;<2> = Sector int enable (internal write)
			;      (Mode 1 data strobe input)
			;<3> = Motor On
			;<4> = Enable Write
			;<5> = Enable Read
			;<6> = Direction Select
			;<7> = Step

SIMASK	equ	01h	;sector interrupt mask
SPMASK	equ	02h	;sector pulse mask
MOTMASK	equ	08h	;motor on/off mask

DSKCMD	equ	DCBASE+0Bh	;8255 Control 
			;Disk control commands:
SINTOFF	equ	04h	;Port C:2 (sector interrupt) off
SINTON	equ	05h	;Port C:2 (sector interrupt) on
MOTOFF	equ	06h	;Port C:3 (spindle motor) off
MOTON	equ	07h	;Port C:3 (spindle motor) on
WRDIS	equ	08h	;Port C:4 (enable write) off
WREN	equ	09h	;Port C:4 (enable write) on
RDDIS	equ	0Ah	;Port C:5 (enable read) off
RDEN	equ	0Bh	;Port C:5 (enable read) on
STPOUT	equ	0Ch	;Port C:6 low  (step out)
STPIN	equ	0Dh	;Port C:6 high (step in)
UNSTEP	equ	0Eh	;Port C:7 low (cancel step pulse)
STEP	equ	0Fh	;Port C:7 high (step)
INIT82	equ	86h	;initialize 8255:
			;  Group A: Mode 0, output
			;  Group B: Mode 1, input
			;  Port C set up for output on all bits
			;    but some bits forced to input for
			;    mode 1 handshake signals

;---------------------------------------------------------------------------
; dInit - Initialize the parallel ports and synchronous serial adapter
;    on the Polymorphic single density controller
;---------------------------------------------------------------------------
dInit	mvi	a,INIT82	;Group A: Mode 0, output
				;Group B: Mode 1, input
				;Port C set up for output
	out	DSKCMD		;init the 8255

	xra	a		;clear all outputs
	out	DSKSEL		;8255 Port A (drive select register)
	out	DSKCTL		;8255 Port C (drive control/command)	

; Initialize the 6852 serial adapter and 8852 on the controller

	mvi	a,CR2SEL	;Clear sync, Tx, Rx, select Control Reg 2
	out	DSKSCTL		;6852 Control Reg 1

	mvi	a,CR2RES	;8 bits, no parity
	out	DSKSDAT		;To 68B52 Control register 2

	mvi	a,CR3SEL	;Select Control Register 3
	out	DSKSCTL		;6852 Control Reg 1

	mvi	a,CR3RES	;Clear status, internal sync,
				;..2-sync chr mode
	out	DSKSDAT		;To 6852 Control Register 3

	mvi	a,SYNSEL	;Select sync-code register
	out	DSKSCTL		;6852 Control Reg 1

	mvi	a,SYNC		;Sync code
	out	DSKSDAT		;To 6852 sync code

	mvi	a,IDLE68	;Select Transmit Data Fifo
	out	DSKSCTL		;6852 Control Reg 1

	ret		

;---------------------------------------------------------------------------
; dSelDrv - Select the drive specified in drvNum and start the motor if
;    needed.
;
; On Entry
;    drvNum = drive to be selected
;
; On Exit
;    Drive selected and motor up to speed
;    Write protect flag (wrtProt) updated
;    Returns for success, else displays error message and restarts the
;	program.
;    Clobbers A,B
;---------------------------------------------------------------------------
dSelDrv	lda	drvNum		;A=drive to select
	out	DSKSEL		;select the new drive on the controller

	in	DSKSTAT		;see if motor on or not
	ani	MOTMASK		;zero true if motor is off
	
	mvi	a,MOTON		;turn (or ensure) motor on
	out	DSKCMD
	
	rnz			;exit if motor already on
	
	mvi	a,MTOSTRT	;A=motor startup delay
	sta	PTIMER		;starting count-up value in 60hz RTC
	mvi	b,0		;B=sector pulse counter
	
dsLoop	in	DSKSEC		;clear sector interrupt

dsSecWt	lda	PTIMER		;timeout expire?
	ora	a		
	jz	dsDone		;yes, timeout done
	
	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dsSecWt		;no, keep waiting

	inr	b		;count the sector pulse
	jmp	dsLoop
	
dsDone	in	DSKSEC		;update write protect status
	ani	WPROT
	sta	wrtProt

 	mvi	a,8		;more than 8 sectors seen?
	cmp	b		;compare 8 - sector count
	rc			;yes, drive selected and ready
	
	lxi	h,mNoDisk	;HL->"no disk message"
	jmp	errExit		;display the error and start over

;---------------------------------------------------------------------------
; dRestor - Restore to track 0
;
; On Entry
;    drvNum = drive to be selected
;
; On Exit
;    If restore successful
;    	Drive selected, up to speed, and on track zero
;       curTrk = 0
;    else
;       error message displayed and program restarted.
;    Clobbers A,B,C,D
;---------------------------------------------------------------------------
dRestor	call	dSelDrv		;make sure drive is selected

	mvi	c,3		;C=step in 3 tracks
	mvi	b,STPIN		;B=step in command
	call	dStep		;do 3 steps in

	mvi	c,NUMTRK+10	;C=maximum number of step outs to try
	mvi	b,STPOUT	;B=step out command
	call	dStep		;step until track 0 detected
	rz			;exit if restore worked
	
	lxi	h,mNoTrk0	;HL->"No track zero" message
	jmp	errExit		;display it and start over

;---------------------------------------------------------------------------
; dSeek - Seek to track trkNum
;
; On Entry
;    drvNum = drive to select
;    trkNum = desired track
;    curTrk = current track (assumed to always be valid)
;
; On Exit
;    If successful
;    	Drive selected, up to speed, and on track trkNum
;       curTrk = trkNum
;    else
;       error message displayed and program restarted.
;    HL->trkNum
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dSeek	call	dSelDrv		;make sure drive is selected

	lda	curTrk		;A=current track drive is on
	lxi	h,trkNum	;HL->requested track numbe
	sub	m		;A=current-requested
	rz			;return if already on correct track

	mvi	b,STPOUT	;B=step out (assume current>requested)
	jnc	dDoSeek		;it is
	
	inr	b		;B=step in command (current<requested)
	cma			;compute A=-A
	inr	a

dDoSeek	mov	c,a		;C=number of steps
	mov	a,m		;save new track in curTrk
	sta	curTrk
				;fall into dStep to do the seek and return

;---------------------------------------------------------------------------
; dStep - Step number of steps in C, direction in B. Since more than
;   one sector time (20ms) will pass after this seek and before
;   reading or writing data, the 10ms head settle wait after a seek
;   is not needed here.
;
;   If track zero is detected during a step-out operation, curTrk
;   is forced to zero and zero is returned in A.
;
; On Entry
;    C = number of steps > 0
;    B = step direction (STPOUT or STPIN)
;
; On Exit
;    If stepping out and track zero hit
;	curTrk = 0
;	A = 0
;	Zero true
;    else
;	Zero false
;    Clobbers A,C,D
;---------------------------------------------------------------------------
dStep	mov	a,b		;set step direction
	out	DSKCMD

dStep1	mvi	a,STEP		;set step bit
	out	DSKCMD
	dcr	a		;clear step bit
	out	DSKCMD

	mvi	a,40		;delay step time of 40ms 

delayMs	mvi	d,(1843/15)+1	;15 cycles in the loop below

delayLp	dcr	d		;decrement inner loop counter
	jnz	delayLp

	dcr	a		;decrement ms counter
	jnz	delayMs

; See if track zero hit while stepping out

	in	DSKSEC
	ani	TRACK0		;track 0 detected?
	jz	dStep2		;no, keep stepping
	
	mov	a,b		;stepping out?
	sui	STPOUT
	jz	dStep0		;yes, we've hit track 0
	
dStep2	dcr	c		;decrement step counter
	jnz	dStep1		;loop until count reaches zero
	
	inr	c		;force non-zero status for exit
	ret

; Track zero while stepping out. Set curTrk to zero and exit with
;    zero status true.

dStep0	sta	curTrk		;curTrk=0
	ret			;return with zero true and A=0

 if 0
;---------------------------------------------------------------------------
; dChkWp - Check write protect status
;
; On Entry
;    wrtProt flag valid only if drive has been selected and come up
;    to speed at some point since diskette inserted.
;
; On Exit
;    Returns if not write protected, else displays error message
;	and restarts the program.
;    Clobbers A
;---------------------------------------------------------------------------
dChkWp	lda	wrtProt		;test write protect flag and return
	ora	a
	rz			;not protected, continue

	lxi	h,mWrtPrt	;otherwise, display error message
	jmp	errExit		;display error and abort
 endif

;----------------------------------------------------------------------------
; rst6852 - Reset the 6852 serial adapter on the floppy controller
;
; On Entry
;
; On Exit
;    Clobbers A
;----------------------------------------------------------------------------
rst6852	mvi	a,RSET68	;reset sync, Tx, Rx
	out	DSKSCTL		;6852 Control Register 1
	mvi	a,IDLE68	;..and release them
	out	DSKSCTL		;6852 Control Register 1 
	
	ret

;---------------------------------------------------------------------------
; dRead - Read a sector, verify checksum, sector number, track number
;
; On Entry
;    Drive is selected and ready, known to be in between sector pulses
;    HL->start of sector in trkBuf
;
; On Exit
;    Sector read to (HL)
;    HL->next sector in track buffer
;    Zero true if no error, false otherwise
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dRead	call	rst6852		;reset the 6852
	mvi	a,RDEN		;enable read on the controller
	out	DSKCMD

	push	d		;preserve DE
	xchg			;DE->sector buffer
	lxi	h,0		;HL=initial checksum
	
; Wait for next sector pulse
	
	in	DSKSEC		;clear sector interrupt

drSecWt	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	drSecWt		;no, keep waiting

; Wait up to 1.75ms for start of data

	mvi	c,83		;C=timeout counter (83*39 cycles)

drWait1	dcr	c		;(5) timeout?
	jz	drError		;(10) yes, preamble error abort

	in	DSKSCTL		;(10) A=6852 status
	rrc			;(4) data byte present?
	jnc	drWait1		;(10) Wait for data

; Read and verify sector and track from disk

	in	DSKSDAT		;read disk data 1st byte (sector)
	mov	b,a		;B=sector
	in	DSKSDAT		;read disk data 2nd byte (track)
	mov	c,a		;C=track

	in	DSKSEC		;A=sector from controller
	ani	SECMASK		;get the sector bits alone
	ori	80h		;set the flag bit
	cmp	b		;sector match?
	jnz	drError		;no, exit
	
	lda	curTrk		;A=expected track
	cmp	c		;match the track we found?
	jnz	drError		;no, exit
	
	mvi	a,128		;128 pairs of bytes/sector

; drLoop - Read sector data into memory at (DE)

drLoop	push	psw		;save loop counter on stack

drWait2	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	drWait2		;no, keep waiting
	
	in	DSKSDAT		;read disk data even byte
	mov	c,a		;C=even byte
	stax	d		;write to buffer
	inx	d
	
	in	DSKSDAT		;read disk data odd byte
	mov	b,a		;B=odd byte
	stax	d		;write to buffer
	inx	d

	dad	b		;update 16-bit checksum

	pop	psw		;A=byte pair count
	dcr	a		;decrement loop count
	jnz	drLoop		;loop through sector data

; Read and verify 1's complement 16-bit checksum from disk

drWait3	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	drWait3		;no, keep waiting

	in	DSKSDAT		;read low byte of checksum
	mov	c,a		;C=checksum low byte
	in	DSKSDAT		;read high byte of checksum
	mov	b,a		;BC=1's complement checksum from disk
	
	dad	b		;add to positive computed checksum
	inx	h		;plus 1 should give zero
	mov	a,h		;start test for HL=0
 
; drExit - OR register L into A then fall into dNext to exit

drExit	ora	l		;test A and L for zero
	
; dNext - Disable read and write output, put address of next sector
;    in HL, pop PSW and DE from stack and exit.

dNext	push	psw		;save the status on the stack

	mvi	a,RDDIS		;disable read and write
	out	DSKCMD
	mvi	a,WRDIS
	out	DSKCMD

	call	dNxtSec		;HL->next sector in trkBuf
	
	pop	psw		;restore the result status
	pop	d		;restore DE
	ret			;and exit

; drError - Force non-zero status and exit through drExit

drError	mvi	a,1		;force non-zero status
	jmp	drExit		;exit read

 if 0				;verify and write not used in FLOP2PC
;---------------------------------------------------------------------------
; dVerify - Verify a sector: header, data, and checksum
;
; On Entry
;    Drive is selected and ready, known to be in between sector pulses.
;    HL->sector buffer
;
; On Exit
;    HL->next sector in track buffer
;    Zero true if no error, false otherwise
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dVerify	call	rst6852
	mvi	a,RDEN		;enable read on the controller
	out	DSKCMD

	push	d		;preserve DE
	lxi	d,0		;DE=initial checksum
	
; Wait for next sector pulse
	
	in	DSKSEC		;clear sector interrupt

dvSecWt	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dvSecWt		;no, keep waiting

; Wait up to 1.75ms for start of data

	mvi	c,83		;C=timeout counter (83*39 cycles)

dvWait1	dcr	c		;(5) timeout?
	jz	drError		;(10) yes, preamble error abort

	in	DSKSCTL		;(10) A=6852 status
	rrc			;(4) data byte present?
	jnc	dvWait1		;(10) Wait for data

; Read and verify sector and track from disk

	in	DSKSDAT		;read disk data 1st byte (sector)
	mov	b,a		;B=sector
	in	DSKSDAT		;read disk data 2nd byte (track)
	mov	c,a		;C=track

	in	DSKSEC		;A=sector from controller
	ani	SECMASK		;get the sector bits alone
	ori	80h		;set the flag bit
	cmp	b		;sector match?
	jnz	drError		;no, exit

	lda	curTrk		;A=expected track
	cmp	c		;match the track we found?
	jnz	drError		;no, exit

	mvi	a,128		;128 pairs of bytes/sector

; dvLoop - Verify sector data against memory at (HL)

dvLoop	push	psw		;save loop counter on stack

dvWait2	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	dvWait2		;no, keep waiting
	
	in	DSKSDAT		;read disk data even byte
	cmp	m		;match byte in RAM buffer?
	jnz	dvCmpFl		;compare failure
	
	mov	c,a		;C=even byte	
	inx	h		;bump buffer pointer
	
	in	DSKSDAT		;read disk data odd byte
	cmp	m		;match byte in RAM buffer?
	jnz	dvCmpFl		;compare failure
	
	mov	b,a		;B=odd byte
	inx	h		;bump buffer pointer

	xchg			;put checksum in HL
	dad	b		;update 16-bit checksum
	xchg			;put buffer pointer back in HL

	pop	psw		;A=byte pair count
	dcr	a		;decrement loop count
	jnz	dvLoop		;loop through sector data

; Read and verify 1's complement 16-bit checksum from disk

dvWait3	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	dvWait3		;no, keep waiting

	in	DSKSDAT		;read low byte of checksum
	mov	c,a		;C=checksum low byte
	in	DSKSDAT		;read high byte of checksum
	mov	b,a		;BC=1's complement checksum from disk
	
	xchg			;HL=computed checksum
	dad	b		;add to positive computed checksum
	inx	h		;plus 1 should give zero
	
	mov	a,h		;start test of HL for zero
	jmp	drExit		;jump to drExit to finish and exit

dvCmpFl	pop	psw		;remove transfer counter from stack
	jmp	drExit		;A<>0, exit through drExit

;---------------------------------------------------------------------------
; dWrite - Write a sector
;
; On Entry
;    Drive is selected and ready, known to be in between sector pulses
;    HL->flag byte in sector buffer
;
; On Exit
;   Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dWrite	mvi	a,WREN		;enable write on controller
	out	DSKCMD		;8255 Port C BSR mode control	

	push	d		;preserve DE
	xchg			;DE->sector in track buffer
	
; Wait for next sector pulse
	
	in	DSKSEC		;clear sector interrupt

dwSecWt	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dwSecWt		;no, keep waiting

;Write sector preamble to disk:
;  10 zeros
;  2 sync characters
;  1 byte sector number (msb set)
;  1 byte track number

	mvi	l,5		;write 10 0's

dwZeros	call	dwWtTx		;wait for transmitter to be available
	xra	a		;transmit a couple 0's
	out	DSKSDAT
	out	DSKSDAT
	dcr	l		;more 0's to write?
	jnz	dwZeros		;yes

	mov	h,l		;HL=initial checksum = 0
	call	dwWtTx		;wait for tranmsitter
	mvi	a,SYNC		;write 2 sync characters
	out	DSKSDAT
	out	DSKSDAT

	call	dwWtTx		;wait for transmitter to be available
	in	DSKSEC		;current sector from hardware
	ani	SECMASK		;just the sector bits
	ori	80h		;set msb to indicate sector number
	out	DSKSDAT		;write sector number

	lda	curTrk		;A=current track
	out	DSKSDAT		;write to disk

; Write sector data, computing 16-bit checksum as we go

	mvi	a,128		;128 byte-pairs per sector

dwLoop	push	psw		;save byte-pair count on stack

	call	dwWtTx		;wait for transmitter
	ldax	d		;get data
	inx	d		;bump memory pointer
	out	DSKSDAT		;write data to disk
	mov	c,a		;save for checksum
	
	ldax	d		;get data
	inx	d		;bump pointer
	out	DSKSDAT		;write data to disk
	mov	b,a		;save for checksum
	dad	b		;update 16-bit checksum
	
	pop	psw		;A=byte-pair count
	dcr	a		;decrement byte-pair count
	jnz	dwLoop		;loop until done

	call	dwWtTx		;Wait for transmitter
	mov	a,l		;low byte of checksum
	cma			;inverted
	out	DSKSDAT		;Write to disk
	
	mov	a,h		;high byte of checksum
	cma			;inverted
	out	DSKSDAT		;Write to disk

	jmp	dNext		;compute next sector address and exit

;---------------------------------------------------------------------------
; dwWtTx - Wait for serial transmit ready in the 6852
;---------------------------------------------------------------------------
dwWtTx:	in	DSKSCTL		;6852 status
	ani	TDRA		;transmit ready?
	jz	dwWtTx		;no, keep waiting

	ret
 endif
 
;---------------------------------------------------------------------------
; dSkpSec - Wait for a sector pulse, then compute and return the buffer
;    address of the NEXT sector to come. Since this function must be 
;    called before any sector I/O, this is used as a common point to
;    disable interrupts.
;
; dNxtSec - Compute and return in HL the buffer address of the NEXT
;    sector to come.
;
; On Entry
;    Drive is selected and ready
;
; On Exit
;    Drive is known to be in between sector pulses
;    HL->sector buffer of next sector to come
;    Interrupts disabled
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dSkpSec	in	DSKSEC		;clear sector pulse flag

dsWait	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dsWait		;no, keep waiting
	
dNxtSec	in	DSKSEC		;A=sector we're on
	inr	a		;compute next sector number
	daa			;wrap 10 back to zero
	ani	SECMASK		;A=next sector
	
dSecAdr	lhld	trkPtr		;HL->track buffer
	mov	b,a		;form BC=sector*256
	mvi	c,0
	dad	b		;HL->next sector in trkBuf

	di			;disable interrupts for upcoming I/O
	ret

;---------------------------------------------------------------------------
; dWtSec - Wait for the sector PRIOR to the one specified in E and then
;    compute and return in HL the buffer address of sector E.
;
; On Entry
;    E=sector number
;    Drive is selected and ready
;
; On Exit
;    Sector prior to E just detected
;    HL->sector buffer for sector E
;    Interrupts disabled
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dWtSec	mvi	a,9		;compute B=sector prior to desired sector
	add	e		;subtract 1 using 9's complement
	daa
	ani	0Fh		;A = (E-1) MOD 10
	mov	b,a		;B=sector prior to desired sector

	in	DSKSEC		;clear sector pulse flag

dwWait	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dwWait		;no, keep waiting
	
	in	DSKSEC		;A=sector register
	ani	SECMASK		;A=sector number alone
	cmp	b		;at sector prior to requested?
	jnz	dwWait		;no, keep waiting

	mov	a,e		;A=desired sector (next to come)
	jmp	dSecAdr		;compute sector address and exit

;---------------------------------------------------------------------------
; dMotOff - Deselect the current drive and turn off the motor
;---------------------------------------------------------------------------
dMotOff	xra	a		;deselect drive
	out	DSKSEL

	mvi	a,MOTOFF	;turn off motor
	out	DSKCMD
	ret			;exit
	

;**************************************************************************
; 
;  Data area
;
;**************************************************************************
; disk variables

drvNum	ds	1		;drive number to use
curTrk	ds	1		;current track number drive is on
trkNum	ds	1		;track number to read
tmpTrk	ds	1		;MUST follow trkNum
trkPtr	ds	2		;pointer into trkBuf at track boundaries
bufEnd	ds	2		;end address + 1 of last track spot in RAM
wrtProt	ds	1		;non-zero if disk write protected

; retry logic variables

dskRtry	ds	1		;disk read retry counter
rtStep	ds	1		;current retry step
trkErr	ds	1		;non zero if error occured on track

; xmodem variables

xmRetry	ds	1		;xmodem retry counter
blkNum	ds	1		;current xmodem block number
crcFlag	ds	1		;non zero if using CRC instead of checksum
crc16	ds	2		;crc-16 result
sndPtr	ds	2		;pointer for start of XMODEM send

; misc variables

cpmFlag	ds	1		;non-zero if running under CP/M
baseDrv	ds	1		;ascii 1st drive - baseDrv = 0
	ds	64		;stack space
ourStk	equ	$

; track buffer runs from here to the end of memory

	ds	PKTLEN		;space for pre-pended unsent data
trkBuf	equ	$

	end

