;*****************************************************************************
;
;  Flop2PC - Transfer floppy image to PC over a serial port
;	     (For Poly-88 computer with a Micropolis FDC)
;
;	This program transmits an image of a Micropolis floppy to a PC 
;	through the Poly-88 serial port using the XMODEM protocol.
;	The disk is copied  in raw format (270 bytes per sector, 16 sectors
;	per track, 35 or 77 tracks).
;
;	This program runs standalone at 0x100 or under CP/M. The program
;	is exited with a CTRL-C and either warm boots CP/M or returns to
;	the POLEX monitor if running stand alone.
;
;	The disk image format matches the Vector Graphic Image (VGI) format
;	defined by Howard Harte for his VG work on the Simh emulator. Each
;	sector in the PC file is 275 bytes in length, organized as follows:
;
;	Length	Content
;	   1	Sync byte (always 0xff)
;	   1	Track number (0-76)
;	   1	Sector number (0-15)
;	 266	Sector data payload (CP/M uses last 256 bytes of this)
;	   1	Checksum
;	   4	ECC for HD-FD controller (not used by floppy, set to zero)
;	   1	ECC valid flag (not used by floppy, set to zero)
;
;	Written by Mike Douglas
;
;	Rev	 Date	    Desc
;	1.0	06/16/16    Original
;	1.1	06/21/16    Fix Z flag bug in conIn and serIn
;
;*****************************************************************************
; Disk information equates

NUMTRK	equ	77		;number of tracks
NUMSEC	equ	16		;number of sectors per track
SECLEN	equ	275		;sector length (as transmitted)
MINDRV	equ	0		;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
CPMBOOT	set	BPROM		;cold boot ROM entry point
RESVRAM	equ	0F000h		;reserved RAM starts here

; XMODEM equates

PKTLEN	equ	128		;128 byte xmodem packet length
SOH	equ	001h
EOT	equ	004h
ACK	equ	006h
NAK	equ	015h
EOF	equ	01Ah		;ctrl-z character
XMTRIES	equ	10		;number of xmodem retries

; CP/M Equates

WBOOT	equ	0		;warm boot jump location

; Misc equates

CR	equ	13		;ascii for carriage return
LF	equ	10		;ascii for line feed
CTRLC	equ	03		;ascii for control-c
DEL	equ	7Fh		;ascii DEL
BS	equ	08h		;ascii backspace
JMPINST	equ	0C3h		;jump instruction opcode

	org	0100h		;load address
;-----------------------------------------------------------------------------
;   Initialize for transfer
;-----------------------------------------------------------------------------
init	lxi	sp,ourStk	;initialize stack pointer
	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 get the drive number and port number
;    to use for the transfer from the operator.

	lxi	h,mWelcom	;display welcome message
	call	dispMsg

; getDrv - get drive number or letter from the user.

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

getType	lxi	h,mType		;display disk type menu
	call	dispMsg
	call	rcvCon		;get byte from the console
	ori	20h		;upper case letters to lower case
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	mvi	b,NUMTRK	;b=77 tracks 
	sui	'1'		;0 index options
	jz	saveAct		;77 tracks chosen, save actual tracks

	mvi	b,35		;b=35 tracks
	dcr	a		;option '2' decrements to zero
	jnz	getType		;neither 1 or 2 chosen, try again

saveAct	mov	a,b		;a=actual track ount
	sta	actTrks		;actTrks=actual track count

; 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

strtXfr	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	mvi	a,0ffh		;track position on drive not known
	sta	curTrk

	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	
	mov	b,a		;b=current track
	lda	actTrks		;a=actual number of tracks to process
	cmp	b		;done all tracks?
	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	sndTrks		;send the buffered tracks via xmodem
	lda	trkNum
	mov	b,a		;b=current track
	lda	actTrks		;a=number of tracks to process
	cmp	b		;all done? (had to preserve HL)
	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	getDrv		;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	lxi	h,trkNum	;get desired track from trkNum	
	mov	l,m		;and put into l
	call	dSeek		;seek to the track

	xra	a
	sta	trkErr		;no error on this track yet
	sta	rtStep		;current retry step 

	call	chkQuit		;check for ctrl-c
	
; secLoop - Read an entire track. Start at whatever sector comes up next

	mvi	d,NUMSEC	;d=sector counter

secLoop	call	dNxtSec		;wait for next (any) sector sync
	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;bc has offset of sector within track
	call	dRead		;read the sector
	jnz	secRtry		;error, go to retry logic

; ** Micropolis Custom Code: Store 5 zeros in the unused ECC bytes
;    at the end of the sector

	lhld	hdrPtr		;hl->track byte in sector
	lxi	b,SECLEN-6	;move to end where unused ECC bytes are
	dad	b		;hl->ECC bytes in sector
	xra	a		;write 5 zeros to the ECC bytes
	mov	m,a
	inx	h
	mov	m,a
	inx	h
	mov	m,a
	inx	h
	mov	m,a
	inx	h
	mov	m,a

; 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

	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

;--------------------------------------------------------------------------
; secRtry - read error retry logic. Six retry steps are tried in
;   the order shown below:
;
;   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
;--------------------------------------------------------------------------	
secRtry	mvi	a,DKTRIES	;init disk retry counter
	sta	dskRtry

retryLp	call	chkQuit		;check for ctrl-c
	call	dWtSec		;wait for sector specified in e
	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;bc has offset of sector within track
	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

	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

; 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	lxi	h,trkNum	;get desired track from trkNum
	mov	l,m		;and put into l
	call	dSeek		;seek back to the track
	pop	d		;restore sector in e
	jmp	secRtry		;try again

skPast	lda	trkNum		;a=current track
	cpi	NUMTRK-1	;at max track?
	jz	restor0		;yes, just try restore again

	push	d		;save sector count and number
	inr	a		;otherwise step in a track
	mov	l,a
	call	dSeek
	jmp	reseek		;then back out

;-----------------------------------------------------------------------------
; 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
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive

;-----------------------------------------------------------------------------
; 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	getDrv		;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	CPMBOOT		;reboot 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
;------------------------------------------------------------------
sizeRam	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 reserved RAM area?
	cpi	(RESVRAM SHR 8) AND 0FFh
	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
	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,lf
	db	'===== Floppy to PC Image Transfer, Version 1.1 ====='
	db	cr,lf
	db	'      (Poly-88 Computer with Micropolis FDC)'
	db	cr,lf,cr,lf
	db	'This program transfers an image of a Micropolis floppy'
	db	cr,lf
	db	'to a PC through the serial port using XMODEM.',0

mDrive	db	cr,lf,cr,lf,'Insert and specify source drive ('
mDrvMin	db	'x-'
mDrvMax	db	'x) or X to exit: ',0

mType	db	cr,lf,cr,lf
	db	'Transfer a 77 track or 35 track disk?',cr,lf
	db	'  1) 77 track (Mod-II drives)',cr,lf
	db	'  2) 35 track (Mod-I drives)',cr,lf
	db	'Choose disk type or X to exit: ',0

mStart	db	cr,lf,cr,lf,'Start XMODEM receive operation on the PC now...',0

mDone	db	cr,lf,cr,lf,'Transfer complete!',cr,lf,0

mXmdm	db	cr,lf,cr,lf,'XMODEM communication failure',cr,lf,0

mNoTrk0	db	cr,lf,cr,lf,'Seek to track 0 failed',cr,lf,0

mStkTk0	db	cr,lf,cr,lf,'Drive stuck on track 0',cr,lf,0

mNotRdy	db	cr,lf,cr,lf,'Drive not ready',cr,lf,0

mTrkErr	db	cr,lf,'Track '
errTrk	db	'xx failed sectors: ',0
errSec	db	'xx ',0

mExit	db	cr,lf,cr,lf,'Exiting...',cr,lf,0

mCpm	db	cr,lf,cr,lf
	db	'Insert CP/M disk into drive A, then press Return...',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

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


;****************************************************************************
;
;  DISK DRIVER FOR MICROPOLIS FLEXIBLE DISK SUBSYSTEM
;
;  Created for PC2Flop and Flop2PC by Mike Douglas 11/2014 from
;  the Micropolis driver code (COPYRIGHT MICROPOLIS CORPORATION 8 JUNE 1977)
;
;  Original code is in all caps. New code is in lower case.
;
;****************************************************************************


;***************************************
;	REGISTER DEFINITIONS AND
;	FLAG EQUATES FOR MICROPOLIS
;	FLEXIBLE DISK CONTROLLER B
;***************************************

BPROM 	EQU 	0FC00H
FDCBASE	EQU 	BPROM+0200H
SCLEN 	EQU 	270		;sector length including sync and cksum

; FDC STATUS REGISTERS

; Sector register is at FDCBASE
;     0-3 SECTOR COUNT
;	4 SPARE
;	5 SPARE
;	6 SCTR INTERRUPT FLAG
;	7 SECTOR FLAG
;
;	FLAG BITS

SIFLG	EQU 	040H
SFLG	EQU 	080H
DTMR	EQU 	020H

; Status register is at FDCBASE+1
;     0-1 UNIT ADDRESS
;	2 UNIT SELECTED (LOW TRUE)
;	3 TRACK 0
;	4 WRITE PROTECT
;	5 DISK READY
;	6 PINTE
;	7 TRANSFER FLAG
;
;	FLAG BITS

TFLG	EQU 	80H
INTE	EQU 	40H
RDY	EQU 	20H
WPT	EQU 	10H
TK0	EQU 	08H
USLT	EQU 	04H

; Command register at FDCBASE (and again at FDCBASE+1)
;	0-1 COMMAND MODIFIER
;	5-7 COMMAND
;
;	COMMANDS

SLUN	EQU 	020H		;SELECT UNIT
;  MODIFIER CONTAINS UNIT ADDRESS

SINT	EQU 	040H		;SET INTERRUPT
;  MODIFIER = l ENABLE INTERRUPT
;  	      0 DISABLE INTERRUPT

STEP	EQU 	060H		;STEP CARRIAGE
;  MODIFIER = 00 STEP OUT
;  	      01 STEP IN

WTCMD	EQU 	080H		;ENABLE WRITE
;  NO MODIFIER USED

RESET	EQU 	0A0H		;RESET CONTROLLER
;  NO MODIFIER USED

	if	0
;-------------------------------------------------------------
; dWrite - Write a sector on current track
; On Entry:
;    Drive is selected, motor is running and sector
;        sync was just previously detected
;    hl->buffer address
; On Exit:
;   clobbers b,c,h,l
;-------------------------------------------------------------
dWrite	push	d		;save de
	xchg			;de->buffer
	lxi	h,FDCBASE+1	;hl->FDC status register

; ENABLE WRITE AND WAIT FOR TRANSFER FLAG

	MVI 	M,WTCMD		;command register at offset of 0 and 1

dWrWait	ORA 	M
	JP 	dWrWait

; Write 270 bytes from memory including the sync byte and checksum.
;    If wrong in the memory buffer, they'll be wrong on disk.

	INX 	H		;hl->FDC data register
	XCHG			;de->FDC data register, hl->buffer
	mvi	c,SCLEN/2	;transferring 270 bytes

; Write loop

dWrLoop	MOV 	A,M		;GET BYTE FROM MEM
	STAX 	D		;WRITE TO DISK
	INX 	H		;NEXT BYTE

	MOV 	A,M		;-ETC-
	STAX 	D
	INX 	H

	DCR 	C
	JNZ 	dWrLoop

	pop	d		;restore de
	RET

;-------------------------------------------------------------
; dChkWP - check if disk is write protected. Selects disk
;    if needed.
; On Entry:
;    drvNum contains to disk to check
; On Exit:
;    returns non-zero if disk write protected
; clobbers h,l
;-------------------------------------------------------------
dChkWP	call 	SLCT		;ensure drive is selected
	lxi	h,FDCBASE+1	;hl->FDC status register
	mov	a,m		;check for write protect
	ani	WPT
	rz			;return if not protected

	lxi	h,mWrtPrt	;otherwise, display error message
	call	dispMsg		;display the error
	inr	a		;dispMsg returns with a=0
	ret			;return non-zero status
	
	endif

;-------------------------------------------------------------
; dRead - Read sector on current track. Copies sync byte,
;    266 byte data section, and checksum byte.
; On Entry:
;    Drive is selected, motor is running and sector
;        sync was just previously detected
;    hl->sector buffer
;    e = sector number (used for compare)
; On Exit:
;    Success = zero true (valid checksum, track, sector)
;    clobbers b,c,h,l
;-------------------------------------------------------------
; Branch if using MZOS checksum

dRead	push	d		;save de
	xchg			;de->buffer
	lxi	h,FDCBASE+1	;hl->FDC status register

; wait for transfer ready flag (sector data available)

	xra	a		;a=0
dRdWait	ora	m		;wait for transfer ready flag in msb
	jp	dRdWait

; sector data ready - pull off sync byte
		
	xchg			;hl->buffer, de->FDC status register
	inx	d		;de->FDC data register
	ldax	d		;read and save sync byte
	mov	m,a
	inx	h
	shld	hdrPtr		;pointer to sector header (track #, sec #)

; dRdLoop - Read loop. Read 268 bytes of sector content two bytes
;    at a time computing checksum in B. Carry started clear with
;    ora instruction at dRdWait.

	lxi	b,(SCLEN-2)/2	;b=0 checksum, c=268 bytes/2

dRdLoop	LDAX 	D		;READ FROM DISK
	MOV 	M,A		;MOVE TO BUFFER
	INX 	H		;NEXT LOC
	ADC 	B		;ADD TO CHECKSUM
	MOV 	B,A		;AND SAVE

	LDAX 	D		;NEXT READ
	MOV 	M,A		;-ETC-
	INX 	H
	ADC 	B
	MOV 	B,A

	DCR 	C		;END OF DATA?
	JNZ 	dRdLoop		;NO-LOOP

; read checksum and compare

	LDAX 	D		;load checksum from disk
	mov	m,a		;store in read buffer
	inx	h
	CMP 	B		;COMPARE WITH computed
	POP	D		;RESTORE DE	
	RNZ			;RETURN IF ERROR

; CHECKSUM OK - verify track and sector

	lhld	hdrPtr		;hl->track number read (sector header)
	lda	trkNum		;a=expected track number
	cmp	m		;right track?
	rnz			;no, exit with error

	inx	h		;hl->sector number read
	mov	a,m		;a=sector number read
	cmp	e		;right sector?
	ret			;return with status

;-------------------------------------------------------------
; dVerify - Verify sector on current track. Compares all 266
;    bytes of the sector plus sync and checksum.
; On Entry:
;    Drive is selected, motor is running and sector
;        sync was just previously detected
;    hl->sector buffer to compare against
; On Exit:
;    Success = zero true (valid compare)
;    clobbers b,c,h,l
;-------------------------------------------------------------
; First, wait for transfer ready flag (sector data available)

dVerify	push	d		;save de
	xchg			;de->buffer
	lxi	h,FDCBASE+1	;hl->FDC status register
	xra	a		;a=0

dVfWait	ora	m		;wait for transfer ready flag in msb
	jp	dVfWait

; Sector data ready, prepare to compare bytes
		
	xchg			;hl->buffer, de->FDC status register
	inx	d		;de->FDC data register
	mvi	c,SCLEN/2	;c=270 bytes (270/2)

; dVfLoop - Verify loop. Read sector content two bytes at a time
;    comparing to buffer.

dVfLoop	ldax	d		;a=byte from disk
	cmp	m		;match buffer?
	jnz	dVfErr		;no, have an error
	inx	h		;move to next byte

	ldax	d		;a=byte from disk
	cmp	m		;match buffer?
	jnz	dVfErr		;no, have an error
	inx	h		;move to next byte

	dcr	c		;all bytes done?
	jnz	dVfLoop		;no, keep going

; All bytes verified including sync and checksum bytes

dVfErr	pop	d		;restore de
	ret			;return with status

;-------------------------------------------------------------
; dWtSec - Wait for specified sector.
; On Entry:
;    Drive is selected, motor is running
;    e = desired sector number
; On Exit:
;    drive is at proper sector
;    e = sector number
;    bc = byte offset of this sector in a track
;    clobbers hl
;-------------------------------------------------------------
dWtSec 	di
	lxi	h,FDCBASE	;hl->sector register

dWtSec1	mov	a,m		;a=sector register
	ora	a		;test for msb to be set
	jp 	dWtSec1

; have sector pulse. See if this is the right sector. If so,
;   return the byte offset of the sector within a track in bc

	ani	0fh		;get sector number alone
	cmp	e		;right sector?
	jnz	dWtSec1		;no

	mvi	h,(mult275 SHR 8)  ;h=msb of "multiply by 275" table
	rlc			;sector*2 (2 bytes per table entry)
	mov	l,a		;hl->sec*275 (table on 256 byte boundary)
	mov	c,m		;get bc=sec*275
	inx	h
	mov	b,m		;bc=byte offset of sector within a track
	ret

;-------------------------------------------------------------
; dNxtSec - Wait for next (any) sector.
; On Entry:
;    Drive is selected, motor is running
; On Exit:
;    drive is at proper sector
;    e = sector number
;    bc = byte offset of this sector in a track
;    clobbers hl
;-------------------------------------------------------------
dNxtSec	di
	lxi	h,FDCBASE	;hl->sector register

dWtSec2	mov	a,m		;a=sector register
	ora	a		;test for msb to be set
	jp 	dWtSec2

; have sector pulse, return the sector number in e and the
;   byte offset of the sector within a track in bc

	ani	0fh		;get sector number alone
	mov	e,a		;return sector number in e
	mvi	h,(mult275 SHR 8)  ;h=msb of "multiply by 275" table
	rlc			;sector*2 (2 bytes per table entry)
	mov	l,a		;hl->sec*275 (table on 256 byte boundary)
	mov	c,m		;form bc=sec*275
	inx	h
	mov	b,m		;bc=byte offset of sector within a track
	ret

;------------------------------------------------------------------
; mult275 - table to multiply byte 0-15 by 275. This table is
;    on a 256 byte boundary to speed up its use.
;------------------------------------------------------------------
	org	($+255) and 0ff00h
mult275	dw	00*275,01*275,02*275,03*275,04*275,05*275,06*275,07*275
	dw	08*275,09*275,10*275,11*275,12*275,13*275,14*275,15*275	
	

;-------------------------------------------------------------
; dSeek - Seek Track. The motor timeout is restarted 
;    as a result of this call.
; On Entry:
;    curTrk = current track, FFh means we don't know
;    l = desired track
; On Exit:
;    curTrk = current track
; trashes all registers
;-------------------------------------------------------------
dSeek	CALL 	SLCT		;ENSURE DRIVE SELECTED
	mov	c,l		;leave track in c from here on
	lxi	h,curTrk	;hl->current track
	mvi	a,0ffh		;has head position been initialized?
	cmp	m
	jnz	SEEK1		;yes	
	CALL 	dRestor		;no, restore to track 0
SEEK1	mov	a,c		;a=desired track
	SUB 	M		;ALREADY AT TRACK?
	rz 			;yes, exit

; NOT AT TRACK — ISSUE THE
;    APPROPRIATE NUMBER OF STEPS TO
;    MOVE TO THE DESIRED TRACK

	mov	m,c		;update curTrk with new position
	JM 	SEKOUT
SEKIN	CALL 	STEPIN
	DCR 	A
	JNZ 	SEKIN
	JMP 	SEEKR1

SEKOUT	CALL 	STPOUT
	INR 	A
	JNZ 	SEKOUT

SEEKR1	CALL 	SETTLE 		;WAIT HEAD SETTLE
	RET

; STEP POSITIONER IN 1 TRACK

STEPIN	PUSH 	PSW
	PUSH	H
	lxi	h,FDCBASE	;STEP IN ONE TRK
	MVI 	M,STEP+1
	LXI 	D,30		;WAIT STEP TIME
	CALL	TIMER
	POP	H
	POP 	PSW
	RET

; STEP POSITIONER OUT 1 TRACK

STPOUT	PUSH 	PSW
	PUSH	H
	lxi	h,FDCBASE	;STEP IN ONE TRK
	MVI 	M,STEP 		;STEP OUT ONE TRK
	LXI 	D,30		;WAIT STEP TIME
	CALL	TIMER
	POP	H
	POP 	PSW
	RET

; WAIT HEAD SETTLE TIME

SETTLE	LXI 	D,10 		;10 MILLISECONDS
	CALL 	TIMER
	RET

;-------------------------------------------------------------
; dRestor - Restore to track 0. Selects the drive
;    and starts the motor if needed.
; On Exit:
;   Z set and curTrk=0 if track 0 found
; trashes psw,d,e
;-------------------------------------------------------------
dRestor	PUSH	H		;save hl and bc		
	PUSH 	B
	lxi	h,curTrk	;init current track to "bad"
	MVI 	M,0FFH	 	;PRESET TO BAD TRK
	CALL 	RESTR1 		;RESTORE TO TK 0
	MVI 	M,0		;SET TRACK=0
	POP	B
	POP 	H
	RET

; RESTR1 - RESTORE TO TK 0

RESTR1 	PUSH 	H
	CALL 	SLCT		;ENSURE UNIT selected and read
	lxi	h,FDCBASE+1	;hl->FDC status register
	MOV 	A,M		;ALREADY AT track zero?
	ANI 	TK0
	JZ 	REST3		;NO - PRESS ON

; ALREADY AT TRACK 0 - STEP
;    IN 8 TIMES THEN RESTORE
;    TO ENSURE GOOD POSITION

	MVI 	A,8
REST2	CALL 	STEPIN 		;STEP IN 8
	DCR 	A		;TRACKS
	JNZ 	REST2
	CALL 	SETTLE 		;WAIT SETTLE TIME

; STEP OUT UNTIL TRACK 0 SWITCH
;    IS ACTUATED OR UNTIL 85 STEPS
;    HAVE BEEN ISSUED SO THAT WE
;    DONT BANG AGAINST THE STOP
;    FOREVER IF TK0 SWITCH IS
;    BROKEN

REST3	MVI	C,85		;LOAD MAX STEPCNT
REST3A	MOV	A,M		;TRACK 0?
	ANI	TK0
	JNZ	REST4		;YES- PRESS ON
	CALL	STPOUT		;STEP OUT ONE TK
	DCR	C		;MAX STEPS ?
	JNZ 	REST3A 		;NO - TRY AGAIN

; MAXIMUM NUMBER OF STEPS HAVE
;    BEEN ISSUED - ERROR ABORT

	lxi	h,mNoTrk0
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

; FOUND TRACK 0 - WAIT
;    SETTLE TIME THEN EXIT

REST4	CALL 	SETTLE		;WAIT HEAD SETTLE
	POP 	H
	RET

;-------------------------------------------------------------
; SELECT DRIVE specified in drvNum
;-------------------------------------------------------------
SLCT 	PUSH 	D
	PUSH 	B
	PUSH 	H
	lxi	h,FDCBASE+1	;hl->FDC status register
	lda	drvNum		;a=disk number
	MOV 	B,A		;AND SAVE
	MOV 	A,M		;read selected unit from controller
	MOV 	C,A		;SAVE in c
	ANI 	7		;MASK selected device number
	XRA 	B		;DESIRED UNIT PREV

; NOTE-THIS TEST WILL FAIL IF CONTROLLER IS NOT PLUGGED IN

	MOV 	A,C		;SELECTED?
	JZ 	SL010		;YES-CHECK RDY
	MOV 	A,B		;GET UNIT ADDRESS
	ORI 	SLUN		;BUILD COMMAND
	MOV 	M,A		;OUTPUT COMMAND
				;WAIT 750 MSEC (up from 250ms) FOR
	LXI 	D,750		;SECTOR CNTR TO
	CALL 	TIMER		;GET IN SYNC
	MOV 	A,M		;GET STATUS
	ANI 	7		;SELECTED NOW?
	XRA 	B
	MOV 	A,M		;GET STATUS AGAIN
	JNZ 	SL020		;ERROR IF NOT SLTD
SL010 	ANI 	RDY		;ENSURE UNIT IS
	XRI 	RDY		;READY
SL020 	POP 	H
	POP 	B
	POP 	D
	RZ			;RETURN IF OK

; DRIVE NOT UP ERROR

	lxi	h,mNotRdy	;drive not ready message
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;-------------------------------------------------------------
; TIMER - 1 MILLISECOND TIMER
;    DE=(DELAY) TIME IN MSEC
;
;    A IS DESTROYED
;-------------------------------------------------------------
TIMER 	PUSH	B
	PUSH 	H
	lxi	h,FDCBASE	;hl->FDC sector register
	MOV 	A,M		;2 or 4 Mhz?
	ANI	DTMR
	MVI	A,96		;1MS @2MHZ
	JNZ	TI005		;IF NOT ZERO, 2MHZ
	RLC			;DOUBLE COUNTER FOR 4MHZ
TI005	MOV	B,A		;SAVE COUNTER IN B
TI010 	MOV 	A,B		;COUNT
	SUI 	1		;DELAY LOOP-1.008
	ORA 	A		;MSEC @500 NSEC
	JNZ 	TI010+1

; 1MSEC EXPIRED - DECREMENT DELAY
;    MULTIPLIER & CHECK FOR DONE

	DCX 	D
	MOV 	A,E
	ORA 	D
	JNZ 	TI010
	POP 	H
	POP 	B
	RET

;-------------------------------------------------------------
; RAM STORAGE REQUIRED FOR DRIVER
;-------------------------------------------------------------
hdrPtr	ds	2		;pointer to sector header (track #, sec #)
	

;**************************************************************************
; 
;  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
trkPtr	ds	2		;pointer into trkBuf at track boundaries
bufEnd	ds	2		;end address + 1 of last track spot in RAM
actTrks	ds	1		;actual number of tracks on disk

; 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
