;*****************************************************************************
;
;  COPY.ASM
;     Disk copy, verify, and format utility for CP/M on an MFA modular
;     computer. This utility bypasses CP/M and the BIOS and talks directly
;     to the FDC.
;
;	Rev	  Date	    Desc
;	---	--------   --------------------------------------------------
;	1.2	03/10/19   M. Douglas, Fix select and seek error logic in
;			   fillBuf, readTrk, and format. Previously fillbuf
;			   and readTrk ignored select and seek errors, and
;			   format didn't display specific "select error" and
;			   "seek error" messages.
;
;	1.1	02/16/19   M. Douglas, Update read error display (dspErr) to
;			   support three digit track numbers.
;
;	1.0	01/23/19   M. Douglas, Original
;
;*****************************************************************************

; Drive equates

NUMDRV	equ	2		;number of drives supported
NUMCYL	equ	80
NUMTRK	equ	160
NUMSEC	equ	9
TRKLEN	equ	(NUMSEC*512)	;length of track
SYSTRKS	equ	2		;number of system tracks
DATATRK	equ	2		;starting data track

; Specify head settle delay and the amount of time to delay between
;    reversing step direction 

MTRDLY	equ	8		;motor start delay in 0.1 sec intervals
HDSETL	equ	25		;25ms head settle delay after step
REVDIR	equ	25		;25ms delay when reversing step direction
HEADDLY	equ	50		;head load delay in ms

; CP/M equates

PRINT	equ	9		;BDOS write string to console
RDLINE	equ	10		;BDOS read a line from the console
BDOS	equ	5		;BDOS entry point
IBUFLEN	equ	80		;length of input buffer
WBOOT	equ	0		;warm boot vector

; Misc equates

WRTRIES	equ	3		;write attempts
RDTRIES	equ	5		;read attempts
CR	equ	13		;ascii carriage return
LF	equ	10		;ascii line feed
CTRLC	equ	3		;ascii control-c

;  BIOS Jump Table Offsets. This table is used to compute BIOS entry
;     points by adding the symbols below to the warm boot entry address
;     stored by CP/M at location 1 on page zero.
	
	org	0
bWboot	ds	3		;warm start
bConSt	ds	3		;console status
bConIn	ds	3		;console character in 
bConOut	ds	3		;console character out

	org	0100h		;CP/M load and entry address
;-----------------------------------------------------------------------------
;  Program entry and initialization
;-----------------------------------------------------------------------------
	lxi	h,0		;save stack pointer by HL=SP+0
	dad	sp		;HL=current stack pointer from CP/M
	shld	stack		;save for restore later
	lxi	sp,ourStk	;setup our own stack

	lxi	d,mBanner	;display startup message
	call	dispMsg

; determine the number of tracks we can buffer based without clobbering
;    the BDOS. Save in maxBuf.

	lhld	BDOS+1		;hl=address of BDOS entry
	mvi	l,0		;l=number of tracks that fit
	mvi	a,trkBuf/256	;a=msb of track buffer

sizeLp	inr	l		;count a track
	adi	TRKLEN/256	;msb of track length (ok because LSB is zero)
	cmp	h		;reached or passed BDOS yet?
	jc	sizeLp		;no, keep counting

	dcr	l		;back up one track
	mov	a,l		;maxBuf = maximum tracks in trkBuf	
	sta	maxBuf

;-----------------------------------------------------------------------------
; dspMenu - Main menu loop
;-----------------------------------------------------------------------------
dspMenu	xra	a
	sta	fStopE5		;clear the stop on E5's flag
	sta	fFormat		;not in a format operation

	sta	selByte		;deselect drives
	out	FDCSEL
	
	lxi	d,mMenu		;display menu
	call	dispMsg

	call	readChr		;read response
	ani	5Fh		;clear bit 5 ('a' to 'A')

	cpi	'A'		;copy all tracks
	cz	cmdAll

	cpi	'S'		;copy system tracks only
	cz	cmdSys

	cpi	'D'		;copy data tracks only
	cz	cmdData

	cpi	'F'		;format disk
	cz	cmdFmt

	cpi	'V'		;verify disk
	cz	cmdVfy	

	cpi	'X'		;exit
	jnz	dspMenu		;not exit, re-display menu


	lxi	d,mCpm		;display "insert cp/m disk"	
	call	dispMsg
	call	readChr		;wait for return

	jmp	WBOOT		;warm boot CP/M to exit

;-----------------------------------------------------------------------------
; cmdAll - Copy all tracks
;-----------------------------------------------------------------------------
cmdAll	lxi	d,mCmdAll	;display command name
	call	dispmsg
	
	mvi	a,NUMTRK	;copying all tracks on the disk
	sta	remTrks		;remaining tracks = all tracks
	xra	a		;current track = track zero
	sta	srcTrk
	sta	dstTrk

; cpyTrks - copy the number of tracks in remTrks, starting at src/dstTrk

cpyTrks	lxi	d,mCRLF		;assume no additional message
	lda	inBuf+1		;command length entered
	cpi	2		;see if more than 1 character typed
	jc	notExt		;no, so not an extended command
	
	lda	inbuf+3		;A=2nd character of command
	ani	5Fh		;clear bit 5 ('a' to 'A')
	sui	'Q'		;quick copy requested?
	jnz	notExt		;no

	cma			;set stop on E5h flag true
	sta	fStopE5

	lxi	d,mQuick	;add the quick copy message

notExt	call	dispMsg		;display additional message or just CR/LF
	
	call	getSrc		;get source and destination from operator
	rnz			;user aborted
	call	getDst
	rnz

; if not single drive copy, display prompt to insert disks

	lxi	h,dstDrv	;hl->destination drive
	lda	srcDrv		;source = destination?
	cmp	m
	jz	chkWp		;single drive copy, skip next message

	lxi	d,mRdyCpy	;display insert disks prompt
	call	getRdy		;get ready confirmation from operator
	rnz			;user aborted

chkWp	lda	dstDrv		;see if destination write protected
	call	dSelDrv
	jnz	selErr
	
	lda	fWrtPrt		;is it write protected?
	ora	a
	jnz	wpErr		;yes

; cpyLoop - filling trkBuf from source, then writing trkBuf to destination

cpyLoop	call	ldSrc		;prompt for source if single drive
	rnz			;user aborted

	call	fillBuf		;read as many tracks as possible

	call	ldDst		;prompt for destination if single drive
	rnz			;user aborted

	call	wrtBuf		;write the full track buffer
	jnz	wrtErr		;write error

	lda	remTrks		;any tracks left?
	ora	a
	jnz	cpyLoop

; copy complete, display message and exit

	lxi	d,mCpyDon	;copy done message
	call	dispMsg
	xra	a		;clear command character
	ret

;-----------------------------------------------------------------------------
; cmdSys - Copy system tracks only
;-----------------------------------------------------------------------------
cmdSys	lxi	d,mCmdSys	;display command name
	call	dispmsg

	mvi	a,SYSTRKS	;copying just system tracks
	sta	remTrks		;remaining tracks = just system tracks
	xra	a		;current track = track zero
	sta	srcTrk
	sta	dstTrk

	sta	inBuf+1		;disable quick copy option
	jmp	cpyTrks		;do the copy (in cmdAll)

;-----------------------------------------------------------------------------
; cmdData - Copy data tracks only
;-----------------------------------------------------------------------------
cmdData	lxi	d,mCmdDat	;display command name
	call	dispmsg

	mvi	a,NUMTRK	;copying just data tracks
	sui	SYSTRKS
	sta	remTrks		;remaining tracks = just data tracks
	mvi	a,DATATRK	;current track = first data track
	sta	srcTrk
	sta	dstTrk

	jmp	cpyTrks		;do the copy (in cmdAll)

;-----------------------------------------------------------------------------
; cmdFmt - Format the disk
;-----------------------------------------------------------------------------
cmdFmt	lxi	d,mCmdFmt	;display command name
	call	dispmsg

	call	getDst		;get destination disk from operator
	rnz			;user aborted

	lxi	d,mRdyFmt	;display insert disk prompt
	call	getRdy		;get ready confirmation from operator
	rnz			;user aborted
	
	call	format		;format the disk
	jnz	fmtErr		;error, format failed

; format complete, display message and exit

	lxi	d,mFmtDon	;formatting done message
	call	dispMsg
	xra	a		;clear command character
	ret

;-----------------------------------------------------------------------------
; cmdVfy - Verify all tracks
;-----------------------------------------------------------------------------
cmdVfy	lxi	d,mCmdVfy	;display command name
	call	dispmsg

	mvi	a,NUMTRK	;verifying all tracks on the disk
	sta	trkCnt
	xra	a		;current track = track zero
	sta	srcTrk

	call	getSrc		;get source disk from operator
	rnz			;user aborted

	lxi	d,mRdyVfy	;display insert disk prompt
	call	getRdy		;get ready confirmation from operator
	rnz			;user aborted

	lda	srcDrv		;set drive to use
	call	dSelDrv
	jnz	selErr

; cVfyLp - read all tracks which verifies checksum

cVfyLp	lxi	h,trkBuf	;restart at beginning of trkBuf each time
	shld	trkPtr

	call	readTrk		;read a track

	lxi	h,srcTrk	;increment current track
	inr	m
	lxi	h,trkCnt	;decrement track counter
	dcr	m
	jnz	cVfyLp

; verify complete, display message and exit

	lxi	d,mVfyDon	;de->verify done message
	call	dispMsg
	xra	a		;clear command character
	ret

;-----------------------------------------------------------------------------
; fillBuf - fill trkBuf with as many tracks that will fit, or the
;    remaining number of tracks to process, whichever is fewer.
; On Entry:
;    srcDrv = drive to read
;    srcTrk = track to start from
;    remTrks = number of tracks left to read
; On Exit:
;    srcTrk, remTrks updated (remTrks may go negative)
;    fillCnt = number of tracks read
;    Clobbers all
;-----------------------------------------------------------------------------
; compute fillCnt, the number of tracks to read into trkBuf

fillBuf	lda	srcDrv		;select drive to read
	call	dSelDrv
	jnz	selErr		;no disk

	lxi	h,maxBuf	;set b=max tracks that fit in trkBuf
	mov	b,m

	lxi	h,remTrks	;set a&c=tracks remaining to process
	mov	a,m
	mov	c,a

	sub	b		;a=remaining-max in buffer
	mov	m,a		;save remaining tracks 
	mov	a,b		;a=max tracks in buffer	
	jnc	doMax		;remaining >= max in buffer, do max in buffer

	mvi	m,0		;zero tracks remaining
	mov	a,c		;otherwise, fill count=remaining tracks

doMax	sta	fillCnt		;fillCnt=number of tracks to read (saved)
	sta	trkCnt		;trkCnt=counter of tracks to read (decrements)

; fillLp - read fillCnt tracks.
	
	lxi	h,trkBuf	;hl->beginning of track buffer
	shld	trkPtr

fillLp	call	readTrk		;read a track into (trkPtr)
	
	lxi	h,srcTrk	;increment current track
	inr	m
	lxi	h,trkCnt	;decrement track counter
	dcr	m
	jnz	fillLp

	ret			;fill done, zero status

;-----------------------------------------------------------------------------
; wrtBuf - write and verify fillCnt tracks from trkbuf
; On Entry:
;    dstDrv = drive to read
;    dstTrk = track to start from
;    fillCnt = number of tracks to write
; On Exit:
;    dstTrk updated 
;    Z set if OK, cleared for error
;    Clobbers all
;-----------------------------------------------------------------------------
wrtBuf	lda	dstDrv		;select drive
	call	dSelDrv	
	rnz			;error

	lda	fillCnt		
	sta	trkCnt		;trkCnt=counter of tracks to write

	lxi	h,trkBuf	;hl->beginning of track buffer
	shld	trkPtr

; wrtLoop - write loop. Write a track and verify. Repeat trkCnt times.

wrtLoop	call	wrtTrk		;write a track from (trkPtr)
	rnz			;exit if error

	lxi	h,dstTrk	;increment current track
	inr	m
	lxi	h,trkCnt	;decrement track counter
	dcr	m
	jnz	wrtLoop

	ret

;-----------------------------------------------------------------------------
; readTrk - read a track into memory at (trkPtr). Sector errors are displayed
;    and reading continues with the next sector.
; On Entry:
;    srcTrk = track to read
;    trkPtr->location within track buffer for this track
; On Exit: 
;    trkPtr->start of next track buffer
;    Clobbers all
;-----------------------------------------------------------------------------
readTrk	call	chkQuit		;allow operator abort
	mvi	c,1		;C=restore-reseek counter
	
rtRseek	lda	srcTrk		;seek to source track number
	call	dSeek		;returns E=sector under head
	jnz	seekErr		;seek error

	mvi	a,0FFh		;init track error flag to no errors
	sta	trkErr
	
rtTrkLp	mvi	d,NUMSEC	;D=physical sector counter

rtSecLp	mvi	b,RDTRIES	;B=sector retry count
	call	dNxtSec		;E=sector, HL->sector in track buffer

rtRtry	call	dRead		;read the sector
	jnz	rtError		;read error

	dcr	d		;decrement sector count
	jnz	rtSecLp		;read another sector

	call	trackE5		;check for full track of E5h (empty)
			
	mvi	a,'.'		;display pacifier per track
updtTrk	call	conout
	
	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

	xra	a		;return with good status
	ret

; rtError - try to read the failed sector again up to RDTRIES times.
;   Then try a restore to track zero and a re-read of the track
;   one time. Restore isn't tried again for subsequent sector errors.

rtError	call	dSecAdr		;HL->sector buffer of failed read
	dcr	b		;decrement sector retry counter
	jnz	rtRtry		;try again
	
	dcr	c		;decrement restore-reseek counter
	jm	rtSecBd		;sector is bad

	call	dRestor		;else try a restore to track zero
	jmp	rtRseek		;reseek and try to read track again

rtSecBd	call	dspErr		;display the sector error
	jmp	rtSecLp		;continue with next sector

;-----------------------------------------------------------------------------
; 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
; Clobbers H,L
;-----------------------------------------------------------------------------
dspErr	push	b		;save BC
	push	d		;save DE
	lxi	h,trkErr	;HL->track error count
	inr	m		;increment track error count 
	jnz	dspSec		;track already displayed, go display sector

; First error on this track. Display "Track xx errors: "

	lxi	h,mErrTrk	;HL->where to put ascii decimal
	lda	srcTrk		;a=track with error on it
	call	b2dec3		;track to ascii
	lxi	d,mTrkErr	;display the track error message
	call	dispMsg
	pop	d		;restore sector number into E
	push	d

; dspSec - display the sector number with an error

dspSec	lxi	h,mErrSec	;HL->where to put ascii sector
	mov	a,e		;A=sector number with error
	call	b2dec2

	lxi	d,mErrSec
	call	dispMsg		;display the error
	pop	d		;restore DE
	pop	b		;restore BC
	ret

;--------------------------------------------------------------
; b2dec3 - Binary byte in A to three ASCII digits at (HL)
; b2dec2 - Binary byte in A to two ASCII digits at (HL)
;--------------------------------------------------------------
b2dec3	mvi	m,'0'		;starting ASCII value

loop100	sui	100		;subtract another 100
	jc	do10s		;no more 100's, go do 10's
	inr	m		;increment 100's result
	jmp	loop100

do10s	adi	100		;account for extra 100 taken out
	inx	h		;HL->10's digit

b2dec2	mvi	m,'0'		;starting ASCII value

loop10	sui	10		;count 10s
	jc	do1s		;done with 10s, do 1s
	inr	m		;increment 10's result
	jmp	loop10

do1s	adi	'0'+10		;form ASCII 1s digit
	inx	h		;move to 1s position
	mov	m,a
	ret

;-----------------------------------------------------------------------------
; trackE5 - check if the track just read is all E5h (empty). If so and
;    the fStopE5 flag is true, then set the remain track count to zero
;    so the copy process will stop.
; On Entry:
;    trkPtr->track buffer to check
;    fStopE5 = stop on track of E5 flag
; On Exit: 
;    remTrks set to zero if fStopE5 true and full track of E5h
;    Clobbers B,D,E,H,L
;-----------------------------------------------------------------------------
trackE5	lda	fStopE5		;stopping on track of all E5?
	ora	a
	rz			;no
	
	lhld	trkPtr		;HL=current track pointer
	lxi	d,TRKLEN	;DE=bytes in a track
	mvi	b,0E5h		;compare for E5h
	
teLoop	mov	a,m		;next byte=E5?
	cmp	b
	rnz			;no, exit
	
	inx	h		;move to next byte in track
	dcx	d		;decrement byte counter
	mov	a,d		;reach zero?
	ora	e
	jnz	teLoop		;try next byte

	sta	remTrks		;set remaining tracks to 0
	ret
	
;-----------------------------------------------------------------------------
; wrtTrk - write a track with retries from (trkPtr)
; On Entry:
;    dstTrk = track to write
;    trkPtr->track buffer
; On Exit: 
;    trkPtr->start of next track buffer 
;    Z set if OK, cleared for error
;    Clobbers all
;-----------------------------------------------------------------------------
wrtTrk	call	chkQuit		;allow operator to abort
	
	lda	dstTrk		;seek to the destination track number
	call	dSeek
	rnz			;seek failed

	mvi	b,WRTRIES	;B=write retry counter

wtTrkLp	mvi	d,NUMSEC	;D=count of sectors to write

wtSecLp	call	dNxtSec		;E=sector, HL->sector in track buffer
	call	dWrite		;write the sector
	rnz			;error, exit

	dcr	d		;decrement sector count
	jnz	wtSecLp		;loop until all sectors written

; Verify the track just written by reading to verify CRC

	mvi	d,NUMSEC	;D=count of sectors to verify

vfyLoop	call	dNxtSec		;get next sector number and offset
	lxi	h,secBuf	;HL->unused sector buffer
	call	dRead		;verify the sector by reading
	jnz	vfyErr		;error

vfyNext	dcr	d		;decrement sector count
	jnz	vfyLoop		;loop until all sectors verified
	
	mvi	a,'*'		;display '*' pacifier
	jmp	updtTrk		;update trkPtr and exit (in readTrk)

; vfyErr - verify failed, retry reads followed by a re-write
;   of the track in needed

vfyErr	mvi	c,RDTRIES	;C=read retry counter

vfyRtry	lxi	h,secBuf	;HL->unused sector buffer
	call	dRead		;verify the sector by reading
	jz	vfyNext		;success, go verify next sector

	dcr	c		;decrement retry counter
	jnz	vfyRtry		;try again
	
	dcr	b		;decrement the write retry counter
	jnz	wtTrkLp		;retry starting with the write

	dcr	b		;force non-zero status
	ret			;exit with error

;----------------------------------------------------------------------
; dispMsg - use the BDOS print message command to display the string
;    pointed to by DE.
; On Entry:
;    de->message to display
; On Exit
;    clobbers all
;----------------------------------------------------------------------
dispMsg	mvi	c,PRINT		;BDOS command to display a string
	jmp	BDOS		;call BDOS

;----------------------------------------------------------------------
; readChr - read and return a single character but use the BDOS input
;   line command for "normal" look and feel. Return zero if no input
;   entered (e.g., just return pressed).
; On Entry:
;    DE->input buffer
; On Exit
;    a = first character typed, zero if nothing typed
;    clobbers all
;----------------------------------------------------------------------
readChr	lxi	d,inBuf		;DE=pointer to input buffer
	mvi	a,IBUFLEN	;store max length to read
	stax	d
	mvi	c,RDLINE	;BDOS command, read line
	call	BDOS

	lxi	d,mCRLF
	call	dispMsg

	lda	inBuf+1		;get length read
	ora	a		;see if nothing entered
	rz			;if so, return zero

	lda	inBuf+2		;otherwise, return first character
	ret

;-----------------------------------------------------------------------------
; getSrc - get source drive from user, return in srcDrv, update drive
;    letter in message prompts.
; On Entry:
; On Exit: 
;    srcDrv = 0-3 if specified
;    Z set if valid drive entered, cleared otherwise
;    Clobbers all
;-----------------------------------------------------------------------------
getSrc	lxi	d,mSrc		;enter source drive prompt 
	call	dispMsg

	call	readChr		;read drive response
	ani	5Fh		;clear bit 5 ('a' to 'A')
	sta	mSrcDrv		;update drive in messages
	sta	mVfyDrv
	sta	mSglSrc

	sui	'A'		;subtract ascii offset
	cpi	NUMDRV		;valid drive number?
	jnc	getErr		;no, exit with non-zero status

	sta	srcDrv		;save it

	xra	a		;return zero status
	ret

;-----------------------------------------------------------------------------
; getDst - get destination drive from user, return in dstDrv, update
;    drive letter in message prompts.
; On Entry:
; On Exit: 
;    dstDrv = 0-3 if specified
;    Z set if valid drive entered, cleared otherwise
;    Clobbers all
;-----------------------------------------------------------------------------
getDst	lxi	d,mDst		;enter source drive prompt 
	call	dispMsg

	call	readChr		;read drive response
	ani	5fh		;clear bit 5 ('a' to 'A')
	sta	mDstDrv		;update drive in messages
	sta	mFmtDrv
	sta	mSglDst

	sui	'A'		;subtract ascii offset
	cpi	NUMDRV		;valid drive number?
	jnc	getErr		;no, exit with non-zero status

	sta	dstDrv		;save it

	xra	a		;return zero status
	ret

; getErr - invalid response, return non-zero status

getErr	ora	a		;force non-zero staus
	ret

;-----------------------------------------------------------------------------
; getRdy - prompt user for confirmation of ready. 
; On Entry:
;    DE->message prompt to display
; On Exit: 
;    Z set if user confirmed, cleared if user aborted.
;    Clobbers all
;-----------------------------------------------------------------------------
getRdy	call	dispMsg		;display passed prompt

grLoop	lxi	d,mCont		;continue? prompt
	call	dispMsg

	call	readChr		;read response
	ani	5Fh		;clear bit 5 ('a' to 'A')
	cpi	'Y'		;confirmed?
	rz

	sui	'N'		;No entered?
	jnz	grLoop		;neither Y or N, do it again

	dcr	a		;return non-zero status for N
	ret

;-----------------------------------------------------------------------------
; ldSrc, ldDst - prompt user to load the source or destination disk if
;    this is a single drive copy.
; On Entry:
;    srcDrv = source drive
;    dstDrv = destination drive
; On Exit: 
;    Z set if ok to continue, cleared if user aborted.
;    Clobbers all
;-----------------------------------------------------------------------------
ldSrc	lxi	d,mInsSrc	;de->prompt user to insert source disk
	jmp	chkSngl		;go see if single drive copy

ldDst	lxi	d,mInsDst	;de->prompt user to insert destination disk

chkSngl	lxi	h,dstDrv	;hl->destination drive
	lda	srcDrv		;source = destination?
	cmp	m
	jz	getRdy		;single drive copy, get ready from user

	xra	a		;not single drive, return zero status
	ret

;--------------------------------------------------------------
; chkQuit - If anything is typed on the console port, abort
;    current process.
; Preserves B,C,D,E, assume all others clobbered
;--------------------------------------------------------------
chkQuit	push	b		;save registers
	push	d
	call	conSt		;check for console input
	ora	a		;set z flag
	jz	chkExit		;nothing present

	call	conIn		;read the character
	lxi	sp,ourStk	;restore stack pointer
	jmp	dspMenu		;start over

; chkExit - ctrl-c not typed

chkExit	pop	d		;restore registers and exit
	pop	b
	ret

;------------------------------------------------------------------------------
; delayMs - delay ms specified in A
; On Entry: A = delay in ms
; On Exit:
;    Z flag set
; Clobbers none
;------------------------------------------------------------------------------
delayMs	push	b		;preserve B,C

dms1ms	mvi	b,2000/15	;B=1ms through following loop

dmsLoop	dcr	b		;(5) decrement B
	jnz	dmsLoop		;(10)

	dcr	a		;decrement ms left
	jnz	dms1ms		;do another ms

	pop	b		;restore B,C
	ret
	
;--------------------------------------------------------------
; wrtErr, fmtErr, selErr, seekErr, wpErr - Display corresponding error 
;    message, reset the stack pointer and re-enter the
;    command loop.
;--------------------------------------------------------------
wrtErr	lxi	d,mWrtErr
	jmp	errExit

fmtErr	lxi	d,mFmtErr
	jmp	errExit

selErr	lxi	d,mSelErr	;drive select error
	jmp	errExit

seekErr	lxi	d,mSkErr	;seek error message
	jmp	errExit

wpErr	lxi	d,mWrtPrt	;write protect message

errExit	call	dispMsg

	lxi	sp,ourStk	;reset stack pointer
	jmp	dspMenu		;start over

;-------------------------------------------------------------------
;  BIOS function calls. Single byte parameters all come in A and
;     are moved to C as required. Address passed in HL for setdma
;     moved to B, C as required.
;-------------------------------------------------------------------
conSt	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bConSt	;DE=offset of console status function
	dad	d
	pchl			;jump to console status function

conIn	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bConIn	;DE=offset of console input function
	dad	d
	pchl			;jump to console input function

conOut	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bConOut	;DE=offset of console input function
	dad	d
	mov	c,a		;character to send in c
	pchl			;jump to console outpput function

;--------------------------------------------------------------------
; format - format the disk
;--------------------------------------------------------------------
format	mvi	a,0FFh		;set formatting flag true
	sta	fFormat

	lda	dstDrv		;select the disk
	call	dSelDrv
	jnz	selErr
	
	lda	fWrtPrt		;write protected?
	ora	a
	jnz	wpErr		;yes
	
	call	initDD		;initialize a DD track buffer
	xra	a		;start on track zero
	sta	dstTrk
		
nextTrk	call	dSeek		;seek to track in A
	jnz	seekErr		;error

	mvi	b,0		;assume side 0
	lda	selByte		;get side bit from selByte
	ani	pSIDE		;isolate side bit
	jz	ftSide0		;it's side 0
	inr	b		;else, side 1

ftSide0	lxi	h,DDTRKID	;HL->location of track ID in track
	lxi	d,DDSECL-1	;DE=length to add per sector 
	mvi	c,NUMSEC	;C=sector counter
	lda	cylNum		;A=current cylinder

idLoop	mov	m,a		;store cylinder# in sector track ID
	inx	h		;move to side byte
	mov	m,b		;store side value

	dad	d		;compute address of next sector
	dcr	c		;decrement sector counter
	jnz	idLoop

; Issue the write track command

	lxi	h,trkBuf	;HL->track buffer
	call	dWrtTrk		;write the track
	rnz			;error, exit
	
; display '.' pacifier, check for operator abort, loop to next cylinder
		
	mvi	a,'.'		;display '.' pacifier per track
	call	conOut		;display pacifier
	call	chkQuit		;see if abort requested

	lxi	h,dstTrk	;increment track number
	inr	m
	mov	a,m		;A=next track number
	cpi	NUMTRK		;done?
	jnz	nextTrk		;no, do next track

	ret			;format done

;-------------------------------------------------------------------
;  initDD - initialize a full track of data for formatting tracks
;-------------------------------------------------------------------
initDD	lxi	h,trkBuf	;HL->track of format data

	mvi	b,80		;80 bytes of 4E
	mvi	a,04Eh		;A=fill value
	call	fill

	mvi	b,12		;12 bytes of 00
	xra	a
	call	fill

	mvi	b,3		;3 bytes of F6(C2)
	mvi	a,0F6h
	call	fill

	mvi	m,0FCh		;index mark of FC
	inx	h

	mvi	b,50		;50 bytes of 4E
	mvi	a,04Eh
	call	fill

; sectDD - fill repeating sector portion of the track

	mvi	c,1		;c=sector number

sectDD	mvi	b,12		;12 bytes of 00
	xra	a
	call	fill

	mvi	b,3		;3 bytes of F5(A1)
	mvi	a,0F5h
	call	fill

	mvi	m,0FEh		;ID address mark of FE
	inx	h

	mvi	m,1		;track number 1
	inx	h
	mvi	m,0		;side 0
	inx	h

	mov	m,c		;sector number
	inx	h	
	mvi	m,2		;sector length 512
	inx	h
	
	mvi	m,0F7h		;F7 generates CRC
	inx	h

	mvi	b,22		;22 pre-data bytes of 4E
	mvi	a,04Eh
	call	fill

	mvi	b,12		;12 pre-data bytes of 00
	xra	a
	call	fill

	mvi	b,3		;3 bytes of F5
	mvi	a,0F5h
	call	fill

	mvi	m,0FBh		;data address mark of FB
	inx	h

	mvi	b,0		;512 sector bytes of E5
	mvi	a,0E5h
	call	fill		;1st 256 bytes
	call	fill		;2nd 256 bytes

	mvi	m,0F7h		;F7 generates CRC
	inx	h

	mvi	b,54		;54 post data bytes of 4E
	mvi	a,04Eh
	call	fill

	inr	c		;move to next sector number
	mov	a,c		;done all sectors?
	cpi	NUMSEC+1
	jc	sectDD		;no, do next sector

; sectors done, fill with 768 trailing 4E for rest of track

	mvi	b,0		;256 bytes of FF
	mvi	a,04Eh
	call	fill
	call	fill		;another 256
	jmp	fill		;another 256 and return

; fill - store A (HL++) B times

fill	mov	m,a
	inx	h
	dcr	b
	jnz	fill

	ret

;****************************************************************************
;
; MFA floppy controller disk I/O routines
;
;****************************************************************************

; Disk controller equates (WD1793)

FDCBASE	equ	0C0H		;controller base address
FDCCMD	equ	FDCBASE		;1793 command register
FDCSTAT	equ	FDCBASE		;1793 status register
FDCTRK	equ	FDCBASE+1	;1793 track register
FDCSEC	equ	FDCBASE+2	;1793 sector register
FDCDATA	equ	FDCBASE+3	;1793 data in/out register
FDCSEL	equ	FDCBASE+4	;FDC drive select port
FDCWAIT	equ	FDCBASE+8	;FDC stall/wait port

; Commands for 1793

STPRATE	equ	0		;6ms step rate (1mhz clock)

cRESTOR	equ	008h+STPRATE	;restore, head load, no verify
cSEEK	equ	018h+STPRATE	;seek, head load, no verify
cSTPIN	equ	058h+STPRATE	;step in, update track reg, head loaded
cSTPOUT	equ	078h+STPRATE	;step out, update track reg, head loaded
cREAD	equ	080h		;read, head assumed settled
cRDADDR	equ	0C0h		;read address marker, head assumed settled
cWRITE	equ	0A0h		;write, head assumed settled
cWRTTRK	equ	0F0h		;write track command, head settled
cRESET	equ	0D0h		;reset 1793 to idle
cIDXINT	equ	0D4h		;force interrupt on index pulse

cSTPTGL	equ	020h		;xor toggles between step in and out
cWAITHD	equ	004h		;bit for read/write to wait for head settle

; Status bits from 1793

sNOTRDY	equ	80h		;1=not ready
sWRPROT	equ	40h		;1=write protected
sHDLOAD	equ	20h		;1=head is loaded
sSKERR	equ	10h		;1=seek error
sCRCERR	equ	08h		;1=CRC error
sTRACK0	equ	04h		;1=on track zero
sINDEX	equ	02h		;1=index detected
sBUSY	equ	01h		;1=1793 busy

sSECERR	equ	10h		;1=sector not found
sWRTFLT	equ	20h		;1=write fault
sDLOST	equ	04h		;1=lost data 

; Combined status flag masks for status of a seek, read, and write

smTRK0	equ	sSKERR+sNOTRDY+sBUSY+sTRACK0
smSEEK	equ	sSKERR+sNOTRDY+sBUSY
smREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY+sBUSY
smWRITE	equ	smREAD+sWRPROT+sWRTFLT

; Drive select register parameters

pDRIVE0	equ	001h		;1=drive 0 select
pDRIVE1	equ	002h		;1=drive 1 select
pDENS	equ	004h		;1=single density, 0=double density
pSIDE	equ	008h		;1=side, 0=side 0

; Misc Equates

FDCINTV	equ	02Ch		;RST5.5 interrupt vector for FDC
SIM	equ	030h		;8085 SIM opcode

;--------------------------------------------------------------
; dSelDrv - Select drive, start and wait on motor startup.
;    Updates the track register in the 1793 with the track
;    the newly selected drive is on. Updates the write protect
;    flag (fWrtPrt) for the selected drive.
; On Entry:
;    A = drive to be selected
; On Exit:
;    fWrtPrt = non-zero if drive write protected		
;    Z flag set = drive selected and ready
;    Z flag clear = error
; Clobbers B,C,H,L
;--------------------------------------------------------------
dSelDrv	ani	01h		;limit to physical drives 0 and 1
	inr	a		;drv 0 sets 1's bit, drv 1 sets 2's bit
	mov	b,a		;B=drive select bits
	
	lxi	h,selByte	;HL->current select value
	mov	c,m		;C=current select value
	mov	m,b		;store new select value in selByte
	
	in	FDCSTAT		;see if drive ready (motor running)
	ani	sNOTRDY		;Z true if drive is ready

	mov	a,b		;select drive, start motor
	out	FDCSEL

	jz	dMtrRdy		;motor is already up to speed

	call	dWtMtr		;wait for motor to get up to speed
	jz	dSkipHd		;drive, head, and disk ready

	ret			;else return with error (no disk)

dMtrRdy	sub	c		;compare new select byte to previous
	ani	3		;only look at the drive selects
	jz	dSkipHd		;same drive so head already loaded

	call	dWtIdx		;wait for an index pulse on new drive
	rnz			;no disk

	mov	a,h		;if > 28ms waiting for index, then
	ani	0FCh		;   skip the head load delay
	jnz	dSkipHd		;   (test for HL >= 1024, 27us lsbit)

	mvi	a,HEADDLY	;A=head load delay 
	ora	a		;test for zero
	cnz	delayMs		;delay headDly if not zero

dSkipHd	mvi	a,cRESET	;ensure 1793 is idle
	out	FDCCMD		;follow with min 28us delay

	mvi	a,JMP		;(7) rewrite FDC interrupt vector to be safe
	sta	FDCINTV		;(13)
	lxi	h,fdcIsr	;(10) ISR address
	shld	FDCINTV+1	;(16)
	shld	FDCINTV+1	;(16) just waste time

	in	FDCSTAT		;read 1793 status (after delay)
	ani	sWRPROT		;get write protect bit alone
	sta	fWrtPrt		;save write protect status flag

	lda	fFormat		;are we formatting?
	ora	a
	jnz	dRestor		;yes, don't read an address mark, force track 0

	call	dReadAm		;returns A=track from address mark
	out	FDCTRK		;update track register in 1793
	rz			;exit if address mark worked
	
	jmp	dRestor		;else, restore to track 0 and exit

;--------------------------------------------------------------
; dWtMtr - Wait the motor startup time specified in the mtrDly
;    parameter. Fall into dWtIdx to wait for an index pulse
;    to ensure drive ready is asserted.
; On Entry:
;    mtrDly = motor delay in 0.1s increments
; On Exit:
;    Z flag set if disk present
;    Z flag clear if no disk present
; Clobbers H
;--------------------------------------------------------------
dWtMtr	mvi	a,MTRDLY	;motor delay in 0.1s increments
	ani	0Fh		;limit to 0-15 (0-1.5s)
	mov	h,a		;H=number of 100ms delays

dw100	mvi	a,100		;delay 100ms
	call	delayMs
	dcr	h		;decrement number of 100ms's to wait
	jnz	dw100
				;fall into dWtIdx to verify index pulse

;--------------------------------------------------------------
; dWtIdx - Wait for an index pulse to come by. This ensures
;    the READY line is asserted because of the currently 
;    selected drive and not the previously selected drive.
; On Entry:
; On Exit:
;    HL = time spent looking for index pulse (27us lsbit)
;    Z flag set if index detected
;    Z flag false if no index (disk not present)
; Clobbers H,L
;--------------------------------------------------------------
dWtIdx	mvi	a,cRESET	;force type 1 status response
	out	FDCCMD		;follow with min 28us delay

	xthl			;(18)
	xthl

	lxi	h,0		;timeout counter, 54 cycle loop

dwIdx1	inx	h		;(5) increment timeout
	mov	a,h		;(5) compare MSB value
	sui	0E0h		;(7) reach E000h (1.55s)?
	jz	dNoDisk		;(10) index not changing, no disk

	in	FDCSTAT		;(10) wait for NOT index pulse
	ani	sINDEX		;(7)
	jnz	dwIdx1		;(10) loop while index asserted

dWtIdx2	inx	h		;(5) increment timeout
	mov	a,h		;(5) compare MSB value
	sui	0E0h		;(7) reach E000h (1.55s)?
	jz	dNoDisk		;index not detected, no disk

	in	FDCSTAT		;(10) wait for index pulse
	ani	sINDEX		;(7)
	jz	dWtIdx2		;(10) loop while index not asserted

	xra	a		;have index, return zero
	ret

dNoDisk dcr	a		;no index, return non-zero
	ret

;--------------------------------------------------------------
; dSeek - Seek to the track specified in A on the currently
;    selected drive. After the seek completes, an address
;    mark is read to verify the seek and to determine what
;    sector is under the head.
; On Entry:
;    A = desired track number
; On Exit:
;    E = sector number from the address mark
;    Z flag set = seek worked and verified
;    Z flag clear = error
; Clobbers D,E,H,L
;--------------------------------------------------------------

; Convert the logical track in A to the physical track (cylNum).
;    Even tracks are on side 0, odd tracks are on side 1. Update
;    the drive select byte to include the side bit based on the
;    specified track number.

dSeek	ora	a		;make sure carry is clear
	rar			;convert track number to cylinder
	sta	cylNum		;save cylinder to seek to

	mvi	a,pSIDE		;assume side 1
	jc	dSide1		;side 1 is correct (odd tracks)
	xra	a		;else, force side 0
dSide1	mov	d,a		;D=side bit in proper position

	lxi	h,selByte	;HL->drive select byte
	mov	a,m		;A=current select byte
	ani	3		;drive bits alone
	ora	d		;include the side bit
	mov	m,a		;save updated selByte
	out	FDCSEL		;and output to the controller

; Issue the seek command and verify the proper cylinder is under the
;    head by reading an address mark. 

	mvi	d,3		;D=retry counter

dsRetry	call	dSeekC		;issue seek command
	rnz			;hard error

	lda	fFormat		;are we formatting?
	ora	a
	jz	dsVfyTk		;no, go verify track

	xra	a		;return zero status
	rz

dsVfyTk	call	dReadAm		;returns A=track from address mark
	jnz	dsFail		;couldn't read address mark

	lxi	h,cylNum	;HL->desired cylinder number
	cmp	m		;does actual match requested?
	rz			;yes, we're done

dsFail	dcr	d		;decrement retry counter
	rm			;went below zero, exit with <> 0

	call	dRestor		;restore to track zero
	jz	dsRetry		;try again
	
	ret			;return with error
	
;--------------------------------------------------------------
; dSeekC - Issue seek command. Delay head settle time after
;    performing the seek.
; On Entry:
;    cylNum = cylinder to seek to
; On Exit:
;    A = 1793 status register
; Clobbers none
;--------------------------------------------------------------
dSeekC	lda	cylNum		;give track to seek to 1793
	out	FDCDATA

	mvi	a,cSEEK		;issue seek command
	call	dDoCmd
	ani	smSEEK		;any of these bits is an error
	rnz			;hard seek failure

	mvi	a,HDSETL	;wait for head settle time
	jmp	delayMs		;return with zero through delayMs

;--------------------------------------------------------------
; dRestor - Restore the currently selected drive to track 0.
;    Waits for the direction reversal delay (revDir) before
;    exiting in case a subsequent seek is immediately done.
; On Entry:
;    
; On Exit:
;    Z flag set = restore operation worked
;    Z flag clear = restore failed
; Clobbers none
;--------------------------------------------------------------
dRestor	mvi	a,cRESTOR	;issue restore to track zero command
	call	dDoCmd
	ani	smSEEK		;any of these bits is an error
	rnz			;hard restore failure
	
	mvi	a,REVDIR	;wait direction reversal delay
	jmp	delayMs		;return with zero through delayMs

;--------------------------------------------------------------
; dDoCmd - Issue command passed in A to the 1793. Control
;    is returned to caller upon command completion by the
;    interrupt service routine.
; On Entry:
;    A = command to write to 1793
; On Exit:
;    A = 1793 status register
; Clobbers none
;--------------------------------------------------------------
dDoCmd	push	psw		;save passed command
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	pop	psw		;A = command for 1793
	out	FDCCMD
	
dsWait	jmp	dsWait		;wait for completion interrupt

;--------------------------------------------------------------
; dRead - Read sector on current cylinder and side.
; On Entry:
;    Drive is selected, on proper track, head loaded
;    HL->sector buffer
;    E = sector number to read
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dRead	mov	a,e		;give sector number to FDC
	out	FDCSEC
	
	mvi	a,cREAD		;output read command
	call	dReadC		;do the read
	ani	smREAD		;any of these bits is an error
	ret

;--------------------------------------------------------------
; dReadAm - Read address mark from disk. Returns track number
;    in A and sector in E from the first address mark found.
; On Entry:
;    Drive is selected
; On Exit:
;    Z flag set = success
;    Z flag clear = error
;    A = track number from disk
;    E = sector number from disk
; Clobbers H,L
;--------------------------------------------------------------
dReadAm	lxi	h,amBuf		;HL->address mark buffer
	mvi	a,cRDADDR	;read address mark command
	call	dReadC		;read with command in A
	ani	smREAD		;any of these status bits is an error
	rnz			;exit with error

	lxi	h,amSec		;HL->sector from address mark
	mov	e,m		;E=sector from address mark

	lda	amTrk		;A=track from address mark
	ret			;exit with data and status
	
;------------------------------------------------------------------
; dReadC - Issue read command from A and then go into a loop
;    receiving the bytes into the buffer pointed to by HL.
;    The ISR returns control directly to the caller of this
;    subroutine when the 1793 signals completion of the read.
; On Entry:
;    A = read sector or read address mark
;    HL->sector buffer
; On Exit:
;    A = completion status from 1793.
; Clobbers H,L
;------------------------------------------------------------------
dReadC	push	psw		;save command from A
	lda	selByte		;keep motor running
	out	FDCSEL
	
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	pop	psw		;A=read or read address mark
	out	FDCCMD		;issue read or read address command

drLoop	out	FDCWAIT		;wait for data byte
	in	FDCDATA		;get byte
	nop			;make sure interrupt occurs
	mov	m,a		;save in memory
	inx	h
	jmp	drLoop		;loop until completion interrupt
	
;--------------------------------------------------------------
; dWrite - Write a sector on current track.
; On Entry:
;    Drive is selected, on proper track, head loaded
;    HL->buffer address
;    E = sector number to write
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dWrite	mov	a,e		;give sector number to FDC
	out	FDCSEC
	
	mvi	a,cWRITE	;issue write command
	call	dWriteC
	ani	smWRITE		;any of these bits is an error
	ret

;--------------------------------------------------------------
; dWrtTrk - Write a complete track for formatting
; On Entry:
;    Drive is selected, on proper track, head loaded
;    HL->track buffer address
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dWrtTrk	mvi	a,cWRTTRK	;issue write track command
	call	dWriteC
	ani	smWRITE		;any of these bits is an error
	ret
	
;------------------------------------------------------------------
; dWriteC - Issue write command and then go into a loop
;    sending the bytes from the buffer pointed to by HL.
;    The ISR returns control directly to the caller of this
;    subroutine when the 1793 signals completion of the write.
; On Entry:
;    A = Write command for the 1793
;    HL->sector buffer
; On Exit:
;    A = completion status from 1793.
; Clobbers HL
;------------------------------------------------------------------
dWriteC	push	psw		;save command
	lda	selByte		;keep motor running
	out	FDCSEL
	
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	pop	psw		;A=write command to use
	out	FDCCMD

dwLoop	out	FDCWAIT		;wait for controller	
	mov	a,m		;A=next byte to write
	out	FDCDATA		;give byte to 1793
	inx	h
	jmp	dwLoop		;interrupt will exit this loop

;--------------------------------------------------------------
; dNxtSec - Increment and wrap the sector number passed in E,
;    then fall into dSecAdr to return HL with a pointer to
;    the sector address within the track buffer.
; On Entry:
;    E = most recent sector number
; On Exit:
;    E = next sector number
;    HL->sector within trkBuf
; Clobbers none
;--------------------------------------------------------------
dNxtSec	inr	e		;bump to next sector
	jz	dSec1		;in case uninitialized rolled to 0

	mvi	a,NUMSEC	;A=number of sectors
	cmp	e		;compare NUMSEC - next sector
	jnc	dSecAdr		;next is good (1-9)

dSec1	mvi	e,1		;restart at sector 1
				;fall into dSecAdr

;--------------------------------------------------------------
; dSecAdr - Return HL with a pointer to the sector buffer in
;    the track buffer pointed to by trkPtr.
; On Entry:
;    E = sector number
;    trkPtr->start of track buffer to use
; On Exit:
;    E = sector number (not changed)
;    HL->sector within trkBuf
; Clobbers none
;--------------------------------------------------------------
dSecAdr	mov	a,e		;A=physical sector number
	dcr	a		;zero index the sector
	add	a		;A=MSB of sector*512
	
	lhld	trkPtr		;HL->current track buffer
	add	h		;compute MSB of sector address
	mov	h,a		;HL->sector in track buffer
	ret

;------------------------------------------------------------------
; fdcIsr - Interrupt service routine when the 1793 signals command
;    completion. The interrupted address is removed from the stack
;    and control returns to the caller of the interrupted routine.
;    The status from the 1793 is returned in A.
;------------------------------------------------------------------
fdcIsr	pop	psw		;remove ISR return address

wtBusy	in	FDCSTAT		;get 1793 status
	rrc			;make sure it's not busy
	jc	wtBusy		;shouldn't happen, but be safe

	rlc			;store status in A
	ret			;return to caller of command

;-------------------------------------------------------------------
;  String Constants
;-------------------------------------------------------------------
mBanner
 db CR,LF,'COPY Version 1.2 by M. Douglas',CR,LF
 db 'Disk utility for MFA Computer with 720K disks',CR,LF,'$'

mMenu
 db CR,LF,LF,'======  O P T I O N   T A B L E  ======',CR,LF,LF
 db '  A = Copy all tracks',CR,LF
 db '  S = Copy only system tracks (0-1)',CR,LF
 db '  D = Copy only data tracks (2-n)',CR,LF
 db '  F = Format disk',CR,LF
 db '  V = Verify disk',CR,LF
 db '  X = Exit and return to CP/M',CR,LF,LF
 db '  Follow A or D with Q for quick copy (stops at end of data)',CR,LF,LF
 db 'Enter Selection: $'

mCmdAll
 db CR,LF,'  Copy All Tracks$'

mCmdSys
 db CR,LF,'  Copy Only System Tracks$'

mCmdDat
 db CR,LF,'  Copy Only Data Tracks$'

mQuick
 db ' (until track of all E5h detected)',CR,LF,'$'

mCmdFmt
 db CR,LF,'  Format Disk',CR,LF,'$'

mCmdVfy
 db CR,LF,'  Verify Disk',CR,LF,'$'

mSrc
 db CR,LF,'Enter source drive (A,B or X to abort): $' 

mDst
 db CR,LF,'Enter destination drive (A,B or X to abort): $' 

mRdyCpy	db	CR,LF,'Source in drive '
mSrcDrv	db	'A, destination in drive '
mDstDrv	db	'B$'

mRdyFmt	db	CR,LF,'Disk to format in drive '
mFmtDrv	db	'A$'

mRdyVfy	db	CR,LF,'Disk to verify in drive '
mVfyDrv	db	'A$'

mInsSrc	db	CR,LF,'Insert source disk in drive '
mSglSrc	db	'A$'

mInsDst	db	CR,LF,'Insert destination disk in drive '
mSglDst	db	'A$'

mCont	db	CR,LF,'  Continue(Y/N)? $'

mCpm	
 db CR,LF,'Insert CP/M disk into drive A, then press Return...$'

mSelErr	
 db CR,LF,'*** Drive select failure ***',CR,LF,'$'

mSkErr	
 db CR,LF,'*** Restore/seek failure ***',CR,LF,'$'

mWrtErr	
 db CR,LF,LF,'** Unrecoverable write error **',CR,LF,'$'

mCpyDon
 db CR,LF,LF,'Copy complete. Note any displayed errors.',CR,LF,'$'

mVfyDon
 db CR,LF,LF,'Verify complete. Note any displayed errors.',CR,LF,'$'

mWrtPrt	
 db CR,LF,'** Disk is write protected **',CR,LF,'$'

mFmtDon
 db CR,LF,LF,'Formatting complete'
 db CR,LF,'Use (V)erify command to check the disk if desired.',CR,LF,'$'

mFmtErr
 db CR,LF,LF,'** Formatting failed **'

mCRLF
 db CR,LF,'$'

mTrkErr
 db CR,LF,'Track '
mErrTrk
 db 'xxx failed sectors: $'
mErrSec
 db 'xx $'


;-------------------------------------------------------------------
;  Data Area
;-------------------------------------------------------------------	
stack	ds	2		;save area for CP/M stack pointer
inBuf	ds	IBUFLEN		;storage for command string

srcDrv	ds	1		;source drive for copy
dstDrv	ds	1		;destination drive for copy
curDrv	ds	1		;currently selected drive number
dskType	ds	1		;disk type (IBM, VG, and density)

srcTrk	ds	1		;track source disk is on
dstTrk	ds	1		;track destination disk is on

maxBuf	ds	1		;number of tracks that can be buffered
fillCnt	ds	1		;number of tracks to buffer this time
trkCnt	ds	1		;counter of tracks while processing
remTrks	ds	1		;number of tracks remaining to process
trkPtr	ds	2		;pointer into trkBuf

selByte	ds	1		;select byte written to the controller
cylNum	ds	1		;current cylinder number
trkErr	ds	1		;non zero if error occured on track
fWrtPrt	ds	1		;write protect status of selected drive
fStopE5	ds	1		;flag to stop if full track of E5's detected
fFormat	ds	1		;non-zero if in a format operation

secBuf	ds	512		;sector read buffer when verifying 

; Address mark buffer

amBuf	equ	$
amTrk	ds	1		;track
amSide	ds	1		;side
amSec	ds	1		;sector
amSecL	ds	1		;sector length code
amCrc	ds	2		;CRC

	ds	64		;our stack
ourStk	equ	$
trkBuf	equ	$

; Equates for formatting

DDTRKID	equ	trkBuf+162	;offset of 1st track ID
DDSECL	equ	512+114		;sector length when formatting

	end


