;*****************************************************************************
;
;  COPY.ASM
;     Disk copy, verify, and format utility for CP/M on a Vector Graphic
;     8 inch floppy controller. This utility bypasses CP/M and the BIOS
;     and talks directly to the FDC. Supports IBM SSSD formats along
;     with Vector Graphic SSSD and DSDD formats.
;
;	Rev	  Date	    Desc
;	1.0	4/14/16   M. Douglas, Original
;	1.1 	5/17/16   Add support for Vector DSDD, SSSD, and IBM SSSD
;			  disk formats.
; 	1.2	9/30/18   Fix trail-out fill byte for DD format to be 4Eh
;			  instead of FFh
;
;*****************************************************************************

	maclib	z80

; Drive equates

NUMDRV	equ	4		;number of drives supported
NUMCYL	equ	77
NUMTRK	equ	154
NUMSEC	equ	26
TRKLEN	equ	(NUMSEC*256)	;length of track (double density)
SYSTRKS	equ	2		;number of system tracks
DATATRK	equ	2		;starting data track

IBMSSSD	equ	0		;disk type code saved in dskType
VGSSSD	equ	1
VGDSDD	equ	2

; Specify head settle delay (in addition to the 10ms step delay provided
;    by the 1793), and the amount of time to delay between reversing
;    track steps (e.g., step in/out or step out/in sequence). 
;
; Set ONEMS to give a 1ms delay for a 19 cycle loop cycle loop

HDSETL	equ	10		;10ms additional head settle after step
STPREV	equ	10		;10ms delay when reversing step direction
;ONEMS	equ	105		;105*19 cycles = 1ms at 2Mhz (8080)
ONEMS	equ	211		;211*19 cycles = 1ms at 4Mhz (Z80)

; 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

NMIVECT	equ	066h		;location of NMI interrupt vector
WRTRIES	equ	4		;write attempts
RDTRIES	equ	4		;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	h,0C9F1h	;NMI routine: POP PSW then RET
	shld	NMIVECT		;install NMI interrupt routine

	mvi	a,VGDSDD	;default disk type is DSDD
	sta	dskType
	mvi	a,NUMTRK	;default tracks on disk is 154
	sta	numTrks

	mvi	a,NUMDRV	;init current drive to invalid
	sta	curDrv

	lxi	h,0FFFFh	;init track table to invalid
	shld	trkTbl
	shld	trkTbl+2

	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	lxi	d,mMenu		;display menu
	call	dispMsg

	call	readChr		;read response
	ani	5fh		;clear bit 5 ('a' to 'A')

	cpi	'T'		;set disk type
	cz	cmdType

	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
	jz	WBOOT		;warm boot CP/M to exit		

	jmp	dspMenu

;-----------------------------------------------------------------------------
; cmdType - Set drive type in dskType and the number of tracks on
;    the selected disk type in numTrks. 
;-----------------------------------------------------------------------------
cmdType	lxi	d,mCmdTyp	;display disk type menu
	call	dispmsg

	call	readChr		;get byte from the console
	ori	20h		;upper to lower case, nums not affected
	cpi	'x'		;exit requested?
	rz			;yes, return to main menu (lower 'x' in A)

	sui	'1'		;'1' - '3' to binary 0-2
	cpi	3		;validate ascii 1-3
	jnc	cmdType		;invalid, prompt again

	sta	dskType		;save the disk type

	cpi	VGDSDD		;double sided?
	mvi	a,NUMTRK	;A=154 tracks for double sided
	jz	savTrks		;yes, save track count of 154

	rrc			;else, A=77 tracks for single sided	

savTrks	sta	numTrks
	ret			;'M' or 9Ah is in A

;-----------------------------------------------------------------------------
; cmdAll - Copy all tracks
;-----------------------------------------------------------------------------
cmdAll	lxi	d,mCmdAll	;display command name
	call	dispmsg

	lda	numTrks		;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	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
	call	dChkWp
	jnz	wpErr		;write protected

; 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	wrFail		;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

	jmp	cpyTrks		;do the copy (in cmdAll)

;-----------------------------------------------------------------------------
; cmdData - Copy data tracks only
;-----------------------------------------------------------------------------
cmdData	lxi	d,mCmdDat	;display command name
	call	dispmsg

	lda	numTrks		;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

	lda	numTrks		;format all tracks on the disk
	sta	trkCnt
	xra	a		;current track = track zero
	sta	dstTrk

	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	fmtFail		;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

	lda	numTrks		;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

; 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

	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	

	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->last byte of track+1
;    Clobbers all
;-----------------------------------------------------------------------------
readTrk	call	chkQuit		;allow operator abort

	lda	srcTrk		;seek to source track
	call	dSeek

	xra	a
	sta	trkErr		;no error on this track yet

	mvi	a,2		;init retry step
	sta	rtStep

; To make sure the head is settled following the track step, yet still
;    minimize latency, add 6 to the last sector number accessed (and
;    wrap as needed). When this sector number is used in the call to
;    dNxtSec, a total delay of about 38ms has transpired since the step
;    was issued.

	lda	lastSec		;last sector accessed
	adi	6		;bump by six sector times
	cpi	NUMSEC+1	;past 26?
	jc	noWrap

	sui	NUMSEC		;wrap to sector 1-n

noWrap	mov	e,a		;e=new "last" sector number

; Read an entire track. Start after the sector in e. 

	mvi	d,NUMSEC	;d=sector counter

secLoop	call	dNxtSec		;e=next sector, bc=offset within track

stpLoop	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;hl->sector buffe
	call	dRead		;read the sector
	jnz	secRtry		;error, run retry logic	

; Decrement sector count. If full track is done, increment trkPtr
;   to point to the next track buffer in trkBuf and exit.

nxtSec	dcr	d		;decrement sector count
	jnz	secLoop
	
	mvi	a,'.'		;display pacifier per track
updtTrk	call	conout
	
	lhld	trkPtr		;hl=current track pointer
	lxi	d,TRKLEN	;de=bytes in a DD track
	dad	d		;hl=start of next track in trkBuf
	shld	trkPtr

	xra	a		;return with good status
	ret

;--------------------------------------------------------------------------
; secRtry - Read error retry logic. Three retry steps are tried in
;   the order shown below:
;
;   Step    Action
;    2	Re-read RDTRIES times
;    1	Seek out one track and back, then re-read RDTRIES times
;    0	Seek in one track and back, then re-read RDTRIES times
;
;    Upon entry, if the error is "sector not found," then retries are
;    not performed since the controller has already tried to read the
;    address mark across three revolutions.
;--------------------------------------------------------------------------	
secRtry	ani	sSECERR		;sector not found error?
	jnz	nxtStep		;yes, skip retries, go to next step

	mvi	a,RDTRIES	;init retry counter
	sta	rdRetry

retryLp	call	dWtSec		;get bc=offset of sector e in track
	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;hl->sector buffer	
	call	dRead		;read the sector
	jz	rtDone		;success, retry is done

	lxi	h,rdRetry	;decrement retry counter
	dcr	m
	jnz	retryLp		;try again

; nxtStep - move to the next retry step

nxtStep	call	chkQuit		;allow operator to abort

	lxi	h,rtStep	;decrement to next retry step
	dcr	m
		
	jz	skPast		;step 0, step in one track and come back	
	jp	skBack		;step 1, step out one track and come back

; All retry steps failed. Display the error, then jump back into the main
;    read loop which will resume at the next sector.

	call	dspErr		;give up, display the error

; rtDone - retry logic is done. Reset the retry step counter and
;    jump to end of sector processing above

rtDone	mvi	a,2		;restart retry step counter
	sta	rtStep
	jmp	nxtSec

;-----------------------------------------------------------------------------
; skBack - step out then back in, retry read
; skPast - step in then back out, retry read
;-----------------------------------------------------------------------------
skBack	lda	srcTrk		;a=current track
	dcr	a		;move out one track
	jp	doBump		;not before track zero, do the step out

skPast	lda	srcTrk		;a=current track
	inr	a		;move in one track
	cpi	NUMTRK		;past end?
	jnc	skBack		;yes, do seek back instead
	
doBump	call	dSeek		;issue step out or in

	mvi	a,STPREV	;delay for step direction reversal
	call	delayMs

	lda	srcTrk		;seek back to the track
	call	dSeek

	mvi	a,HDSETL	;delay head settle time after step
	call	delayMs

	call	dWtSec		;get bc=offset of sector within track
	jmp	stpLoop		;start reads for new step

;-----------------------------------------------------------------------------
; 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
; Preserves D,E, all others clobbered
;-----------------------------------------------------------------------------
dspErr	push	d		;save D, E
	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,mErrTrk	;HL->where to put ascii decimal
	lda	srcTrk		;a=track with error on it
	call	bin2dec		;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	bin2dec

	lxi	d,mErrSec
	call	dispMsg		;display the error
	pop	d		;restore DE
	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

;-----------------------------------------------------------------------------
; wrtTrk - write a track with retries from (hl)
; On Entry:
;    dstTrk = track to write
;    trkPtr -> track buffer
; On Exit: 
;    trkPtr = track+1
;    Z set if OK, cleared for error
;    Clobbers all
;-----------------------------------------------------------------------------
wrtTrk	call	chkQuit		;allow operator to abort
	
	lda	dstTrk		;seek to the destination track
	call	dSeek

	mvi	a,WRTRIES	;init write retry counter
	sta	wrRetry

; To make sure the head is settled following the track step, yet still
;    minimize latency, add 6 to the last sector number accessed (and
;    wrap as needed). When this sector number is used in the call to
;    dNxtSec, a total delay of about 38ms has transpired since the step
;    was issued.

	lda	lastSec		;last sector accessed
	adi	6		;bump by six sector times
	cpi	NUMSEC+1	;past 26?
	jc	noWrap2

	sui	NUMSEC		;wrap to sector 1-n

noWrap2	mov	e,a		;e=new "last" sector number

; rtryWrt - write a track and the retry entry point.

rtryWrt	mvi	d,NUMSEC	;d=count of sectors to write
	
; wrSecLp - write a full track starting after the sector in e.

wrSecLp	call	dNxtSec		;get next sector number and offset
	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;bc has offset of sector within track
	call	dWrite		;write the sector
	rnz			;error, exit

	dcr	d		;decrement sector count
	jnz	wrSecLp		;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	vfyRtry		;error, go to retry logic

vfyNext	dcr	d		;decrement sector count
	jnz	vfyLoop		;loop until all sectors verified

; verify successful, display "*" pacifier, update trkPtr and exit

	mvi	a,'*'		;display '*' pacifier
	jmp	updtTrk		;update trkPtr and exit (in readTrk)

; vfyRtry - verify failed, retry reads followed by a re-write
;   of the track in needed.

vfyRtry	mvi	a,RDTRIES	;init retry counter
	sta	rdRetry

wvRtry	lxi	h,secBuf	;hl->unused sector buffer
	call	dRead		;verify the sector by reading
	jz	vfyNext		;success, go verify next sector

; Re-verify failed. Decrement retry count and try again if not zero. Once
;   retry counter expires, do another write.

	lxi	h,rdRetry	;decrement the read retry counter
	dcr	m
	jnz	wvRtry		;try again

; read re-tries expired, decrement the write retry count and re-write
;    if not expired

	lxi	h,wrRetry	;decrement the write retry counter
	dcr	m
	jnz	rtryWrt		;retry starting with the write

	inr	m		;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->message to display
; 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
	rc			;below 'A', exit with non-zero

	cpi	NUMDRV		;valid drive number?
	jnc	getErr		;above 'D', exit

	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
	rc			;below 'A', exit with non-zero

	cpi	NUMDRV		;valid drive number?
	jnc	getErr		;above 'D', exit

	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	insType		;insert disk type into message
	call	dispMsg		;display passed prompt
	lxi	d,mCont		;continue? prompt
	call	dispMsg

	call	readChr		;read response
	ani	5fh		;clear bit 5 ('a' to 'A')
	cpi	'Y'		;confirmed?
	ret

;-----------------------------------------------------------------------------
; insType - insert disk type message (IBM SSSD, VG SSSD, VG DSDD) into
;    the message pointed to by DE. 
; On Entry:
;    de->message prompt to display
; On Exit: 
;    de->message with proper disk type inserted at front
;    Clobbers all
;-----------------------------------------------------------------------------
insType	lxi	h,pDskTyp	;HL->disk type pointer table
	lda	dskType		;form offset based on disk type
	rlc			;two bytes per table entry
	mov	c,a		;BC=offset for dskType
	mvi	b,0
	dad	b		;HL->disk type string pointer

	mov	c,m		;C=lsb of string address
	inx	h
	mov	b,m		;B=msb of string address

; move string at (BC) to (DE+2)

	push	d		;save DE
	inx	d		;start past CR/LF
	inx	d
	mvi	l,TYPELEN	;L=bytes to move

typeLp	ldax	b		;copy type string to (DE)
	stax	d
	inx	b
	inx	d
	dcr	l
	jnz	typeLp

	pop	d		;restore original address
	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

;-------------------------------------------------------------------
; wrFail, fmtFail - display fail message and return to main loop
;-------------------------------------------------------------------
wrFail	lxi	d,mWrtErr
	jmp	dispMsg

fmtFail	lxi	d,mFmtErr
	jmp	dispMsg

;--------------------------------------------------------------
; 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.
; 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
	ani	7fh
	cpi	CTRLC		;abort requested?
	jnz	chkExit		;no

; Ctrl-C typed while program is running. Return to drive prompt.

	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 number of MS specified in A. Clobbers B.
;    Be sure to set the ONEMS equate as required for the
;    target CPU.
;--------------------------------------------------------------------
delayMs	mvi	b,ONEMS		;loops for 1ms
	
dlyLoop	nop			;(4)
	dcr	b		;(5)
	jnz	dlyLoop		;(10)

	dcr	a		;decrement ms counter
	jnz	delayMs

	ret

;--------------------------------------------------------------
; selErr, seekErr, wpErr - Display corresponding error 
;    message, reset the stack pointer and re-enter the
;    command loop.
;--------------------------------------------------------------
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. Supports IBM SSSD, VG SSSD, and VG DSDD
;--------------------------------------------------------------------
format	lda	dstDrv		;select the disk
	call	dSelDrv
	call	dChkWp		;verify not write protected
	jnz	wpErr		;write protected

; track zero is single density for all disk types

	call	initSD		;initialize a SD track buffer

	xra	a		;seek to track zero
	call	dSeek
	rnz			;error

	lxi	h,trkBuf	;HL->track buffer with SD format
	call	dWrtTrk		;write the track
	rnz			;error

	mvi	a,'.'		;display '.' pacifier per track
	call	conOut

; track one is double density for Vector Graphic SD and DD disks

	mvi	a,1		;assume loop will start on track 1
	sta	dstTrk
	call	dSeek
	rnz			;error

	lda	secSize		;dSeek sets sector size for current track
	ora	a
	jnz	sdParms		;single density (IBM SSSD disk)
	
	mvi	a,2		;start loop on track 2 instead of 1
	sta	dstTrk

	call	initDD		;initialize a DD track buffer
	lxi	h,trkBuf	;HL->track buffer with DD format
	call	dWrtTrk		;format track 1 as DD
	rnz			;error

	mvi	a,'.'		;display '.' pacifier per track
	call	conOut

; set parameters based on whether the rest of the disk is to be formatted
;     single or double density.

	lxi	h,DDTRKID	;HL->location of track ID in DD track
	lxi	d,DDSECL-1	;DE=length to add per sector for DD

	lda	dskType		;check disk type
	cpi	VGDSDD		;Vector Graphic double density?
	jz	savePrm		;yes, save the params we've loaded

	call	initSD		;otherwise, restore SD track format

sdParms	lxi	h,SDTRKID	;HL->location of track ID in SD track
	lxi	d,SDSECL-1	;DE=length to add per sector for SD

savePrm	shld	pTrkId		;save pointer to track ID
	xchg
	shld	fmtSecL		;save sector length 

; nextTrk - Format next track

nextTrk	lda	dstTrk		;seek to destination track
	call	dSeek		;also sets curCyl, curSide
	rnz			;error

	lda	curCyl		;A=current cylinder
	lxi	h,curSide	;B=current side (0 or 1)
	mov	b,m
	lhld	fmtSecl		;DE=sector length - 1
	xchg
	lhld	pTrkId		;HL->track ID in trkbuf
	mvi	c,NUMSEC	;C=sector counter

; Put the current cylinder number and side in the ID field of each sector
;    on the track

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 with proper SD or DD formt
	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	

	lda	trkCnt		;A=number of tracks to format
	cmp	m		;done?
	jnz	nextTrk		;no, do next track

	ret			;format done

;-------------------------------------------------------------------
;  initSD - initialize a full track of data for formatting track
;     zero (single density).
;-------------------------------------------------------------------
initSD	lxi	h,trkBuf	;HL->track of format data

	mvi	b,40		;gap 4 preindex 40 bytes of FF
	mvi	a,0FFh		;A=fill value
	call	fill

	mvi	b,6		;6 bytes of 00
	xra	a
	call	fill

	mvi	m,0FCh		;index mark of FC
	inx	h

	mvi	b,26		;26 bytes of FF
	mvi	a,0FFh
	call	fill

; sectSD - fill repeating sector portion of the track

	mvi	c,1		;c=sector number

sectSD	mvi	b,6		;6 bytes of 00
	xra	a
	call	fill

	mvi	m,0FEh		;ID address mark of FE
	inx	h

	mov	m,a		;track number zero
	inx	h
	mov	m,a		;00 after track
	inx	h

	mov	m,c		;sector number
	inx	h
	mov	m,a		;00 after sector
	inx	h

	mvi	m,0F7h		;force CRC code F7
	inx	h

	mvi	b,11		;11 pre-data bytes of FF
	mvi	a,0FFh
	call	fill

	mvi	b,6		;6 pre-data bytes of 00
	xra	a
	call	fill

	mvi	m,0FBh		;data address mark of FB
	inx	h

	mvi	b,128		;128 sector bytes of E5
	mvi	a,0E5h
	call	fill

	mvi	m,0F7h		;force CRC code F7
	inx	h

	mvi	b,27		;27 post data bytes of FF
	mvi	a,0FFh
	call	fill

	inr	c		;move to next sector number
	mov	a,c		;done all sectors?
	cpi	NUMSEC+1
	jc	sectSD		;no, do next sector

; sectors done, fill with 256 trailing FF for rest of track

	mvi	b,0		;256 bytes of FF
	mvi	a,0FFh
	jmp	fill		;call fill and return

;-------------------------------------------------------------------
;  initDD - initialize a full track of data for formatting tracks
;     1-153 (double density)
;-------------------------------------------------------------------
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
	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		;6 bytes of 00
	xra	a
	call	fill

	mvi	b,3		;3 bytes of F5
	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,1		;01 after sector
	inx	h

	mvi	m,0F7h		;force CRC code F7
	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		;256 sector bytes of E5
	mvi	a,0E5h
	call	fill

	mvi	m,0F7h		;force CRC code F7
	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


;****************************************************************************
;
; Vector Graphic 8" FDC disk I/O routines
;
;****************************************************************************

; Controller Equates

FDCBASE	equ	0E0H		;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/status port
FDCWAIT	equ	FDCBASE+5	;FDC stall/wait port

FDMSIDE	equ	04h		;selects top side when set in FDCSEL
FDMDEN	equ	08h		;selects double density when set in FDCSEL

; Commands for 1793.

STPRATE	equ	0		;3ms step rate

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	088h		;read, head assumed settled
cWRITE	equ	0A8h		;write, head assumed settled
cRDADDR	equ	0C4h		;read address marker, wait head settle
cRESET	equ	0D0h		;reset 1793 to idle
cWRTRK	equ	0F4h		;write track command

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

smSEEK	equ	sSKERR+sCRCERR+sNOTRDY+sBUSY
smREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY+sBUSY
smWRITE	equ	smREAD+sWRPROT+sWRTFLT

;--------------------------------------------------------------
; dSelDrv - Select the drive passed in A and wait for the
;    drive to be ready. 
; On Entry:
;    A = drive to be selected
; On Exit:
;    Drive selected, track register in 1793 updated from trkTbl
;    curDrv = currently selected drive number
; Clobbers B,C,H,L
;--------------------------------------------------------------
dSelDrv	push	a		;preserve drive to select

; First, save the track the current drive is on in trkTbl

	lxi	h,curDrv	;put curDrv into C
	mov	c,m
	mvi	b,0		;BC=offset to curDrv
	lxi	h,trkTbl	;HL->trkTbl
	dad	b		;HL->curDrv in trkTbl

	in	FDCTRK		;save track curDrv is on
	mov	m,a

; Save new the drive in curDrv. Select and wait for the drive
;   to be ready.

	mvi	a,cRESET	;ensure 1793 is idle
	out	FDCCMD
	
	pop	a		;restore drive to select
	sta	curDrv		;save as the new current drive
	out	FDCSEL		;select the drive (SD, bottom)

dWtRdy	in	FDCSTAT
	rlc			;not ready bit in carry
	jc	dWtRdy		;wait for ready

; Load the 1793 track register from the track saved for curDrv
;   in trkTbl. If not intialized yet, do a restore to track zero.

	lxi	h,curDrv	;put curDrv into C
	mov	c,m
	lxi	h,trkTbl	;HL->trkTbl
	dad	b		;HL->new curDrv in trkTbl

	mov	a,m		;A=track from trkTbl
	cpi	0FFh		;track unknown?
	cz	dRestor		;yes, restore to track zero
	
	out	FDCTRK
	ret

;--------------------------------------------------------------
; dChkWP - check if disk is write protected. 
; On Entry:
;    disk already selected
; On Exit:
;    Z flag set = not write protect
;    Z flag clear = write protected
; Clobbers none
;--------------------------------------------------------------
dChkWp	mvi	a,cRESET	;ensure 1793 is idle
	out	FDCCMD
	xthl			;waste some time before checking
	xthl

	in	FDCSTAT		;read 1793 status
	ani	sWRPROT		;get write protect bit alone
	ret			;return write protect status

;--------------------------------------------------------------
; dSeek - Seek track specified in A. Selects drive and loads
;    head. If the head was not already loaded, then the E bit
;    (wait for head settle one-shot) is set in fWaitHd for
;    OR'ing into the subsequent read/write command.
; On Entry:
;    A=track to seek
;    curDrv = drive to use
; On Exit:
;    Z flag set = seek worked and verified
;    Z flag clear = error
;    curCyl = resulting cylinder of the track seek
;    curSide = resulting side (0 or 1) of the track seek
;    secSize = sector size (128 or 256) of current track
; Clobbers C,H,L
;--------------------------------------------------------------
dSeek	mov	l,a		;save requested track in L

; See if head is currently loaded. If not loaded, set the E
;    bit (wait for head-settle one-shot) in fWaitHd which is
;    OR'd into the subsequent read/write command.

	in	FDCSTAT		;see if head is currently loaded
	ani	sHDLOAD
	mvi	a,cWAITHD	;E flag set for R/W commands
	jnz	doSeek1 	;head already loaded

	sta	fWaitHd		;otherwise, set E flag

; Determine the track density, cylinder, and side based on the disk
;    type at the requested track number

doSeek1	mov	a,l		;A=requested track number
	lxi	h,dskType	;C=disk type
	mov	c,m

	mvi	h,0		;H=single density mask
	ora	a		;track zero?
	jz	doSeek2		;yes

	dcr	c		;IBM SSSD?
	jm	doSeek2		;yes, always single density

	mvi	h,FDMDEN	;H=double density, bottom side mask
	cpi	1		;track 1 always DD for VG disks
	jz	doSeek2

	mvi	h,0		;H=single density mask
	dcr	c		;VG SD disk?
	jm	doSeek2		;yes, single density if not track 1

	mvi	h,FDMDEN	;H=double density, bottom side mask
	cpi	NUMCYL		;top or bottom?
	jc	doSeek2		;bottom

	mvi	h,FDMSIDE+FDMDEN   ;H=double density, top side mask
	sui	NUMCYL		;A=proper cylinder

doSeek2	out	FDCDATA		;give 1793 the desired cylinder number
	sta	curCyl		;save the cylinder we're on

	mov	a,h		;save the side selected...
	ani	FDMSIDE		;bit 2 (4's) is side flag 
	rrc	
	rrc
	sta	curSide		;curside = 0 (bot) or 1 (top)	
		
	mov	a,h		;set 128 or 256 bytes (SD or DD)
	ani	FDMDEN		;A=density bit alone
	xri	FDMDEN		;A=0 if DDEN
	jz	savSlen		;save the sector length (0=256)

	mvi	a,128		;otherwise, length is 128

savSlen	sta	secSize
		
	lda	curDrv		;set density and side on FDC board
	ora	h		;include density and side bits
	out	FDCSEL		;this also selects drive

	mvi	a,cSEEK		;issue seek command
	out	FDCCMD
	call	dWtComp		;wait for seek to complete

	in	FDCSTAT		;test status of the seek
	ani	smSEEK		;any of these bits is an error
	ret

;--------------------------------------------------------------
; dRestor - Restore the currently selected drive to track 0.
; On Entry:
;    Drive to restore already selected
; On Exit:
;    Z flag set = restore operation worked
;    Z flag clear = restore failed
;    A=0
; Clobbers none
;--------------------------------------------------------------
dRestor	mvi	a,cRESTOR	;issue restore to track zero
	out	FDCCMD
	call	dWtComp		;wait for restore to complete

	in	FDCSTAT		;verify restore worked
	ani	smSEEK		;isolate seek bits
	mvi	a,0		;A=0, preserve zero bit
	ret			;return status

;--------------------------------------------------------------
; dWtComp - Wait here for NMI to return to caller. The NMI
;    routine aborts the loop and does the return.
; On Entry:
;    A command has been issued to the 1793
; On Exit:
;    1793 has completed the command
; Clobbers none
;--------------------------------------------------------------
dWtComp	in	FDCSEL		;allows NMI to occur
	nop
	jmp	dWtComp		

;--------------------------------------------------------------
; dRead - Read sector on current track
; 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	di			
	mov	a,e		;give sector number to FDC
	out	FDCSEC

	lda	fWaitHd		;load head settle (E) flag
	ori	cREAD		;combine with read command

	push	b		;preserve BC, DE
	push	d
	call	dRdSec		;do the data transfer
	pop	d		;restore
	pop	b

	xra	a		;no head settle wait after 1st read
	sta	fWaitHd

	in	FDCSTAT		;get status of the read
	ani	smREAD		;any of these bits is an error
	ei			;re-enable interrupts
	ret		

;--------------------------------------------------------------
; dReadAm - Read address mark from disk. Returns track
;    in A 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
; Clobbers H,L
;--------------------------------------------------------------
dReadAm	di			;disable interrupts
	lxi	h,amBuf		;HL->address mark buffer
	mvi	a,cRDADDR	;A=read address mark command

	push	b		;save BC
	push	d		;save DE
	call	dRdSec		;do the data transfer
	pop	d		;restore
	pop	b

	in	FDCSTAT		;get status of the read
	ani	smREAD		;any of these bits is an error
	lda	amTrk		;A=track from address mark
	ei

	ret			;exit with data and status

;--------------------------------------------------------------
; dRdSec - Do the physical transfer of data from the disk
;    into memory. When the disk controller completes the
;    command, it generates an NMI interrupt that returns
;    control to the caller of this subroutine.
; 
;    The FDCWAIT register can't be used for data sync until
;    after the 1793 presents the 1st byte. Therefore, a poll
;    for DRQ must be used for the 1st byte. In order to meet
;    1793 timing requirements for DD data (13.5us worst case)
;    the poll loop uses a computed jump based on reading
;    the board status (select) register. When DRQ is asserted,
;    0FFh is returned, otherwise 0FCh is returned. This loop
;    must be located at xxFCh within a page.
;
;    To handle a race condition that can occur with the Rev 0
;    controller, B' is loaded with FFh (the LSB of the jump
;    address when DRQ is asserted) because the race condition
;    can cause the DRQ poll loop to jump into the middle of
;    the INP L instruction. The 2nd byte evaluates to a MOV L,B
;    instruction, and therefore, moves the LSB of the proper
;    jump address into L for subsequent PCHL jump.
;
; On Entry:
;    Read sector or read address command issued to controller
;    HL->sector (or address mark) buffer
; On Exit:
; Clobbers All (may exit while in alternate registers)
;
; One cycle has been added to all instruction times to account
; for the wait state added by the VG ZCB during M1.
;--------------------------------------------------------------

RDSIZE	set	drWt1st-dRdSec

;	org	(($+RDSIZE+100h-0FCh) AND 0FF00h) + (0FCh - RDSIZE)
	org	(($+0Ch+100h-0FCh) AND 0FF00h) + (0FCh - 12h)

dRdSec	out	FDCCMD		;issue command in A

	lda	secSize		;put sector size-1 in B
	dcr	a
	mov	b,a

	mvi	c,FDCDATA	;C=port to read for data byte

	push	h		;putting HL into DE'
	inx	h		;HL->2nd byte location
	exx
	pop	d		;DE'=original HL

	mvi	h,(drWt1st SHR 8) AND 0FFh   ;H'=msb of jump address
	lxi	b,((dRd1st AND 0FFh) SHL 8) + FDCSEL

drWt1st	inp	l		;(13) L=FCh (no DRQ) or FFh (DRQ)
	pchl			;(5) jump to drWt1st or dRd1st

dRd1st	in	FDCDATA		;(12) read 1st byte
	stax	d		;(8) store in buffer
	exx			;(5) back to regular registers

dRdLoop	in	FDCWAIT		;(12) wait for next byte
	ini			;(17) retrieve and store byte
	jnz	dRdLoop		;(11)

	jmp	dWtComp		;all bytes read, go wait for 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	di
	mov	a,e		;give sector number to FDC
	out	FDCSEC

	lda	fWaitHd		;load head settle (E) flag
	ori	cWrite		;combine with write command

	push	b		;save BC
	push	d		;save DE
	call	dWrSec		;do the data transfer
	pop	d		;restore
	pop	b

	xra	a		;no head settle wait after 1st write
	sta	fWaitHd

	in	FDCSTAT		;get status of the write
	ani	smWRITE		;any of these bits is an error
	ei			;re-enable interrupts
	ret

;--------------------------------------------------------------
; dWrtTrk - Write a track for formatting
; On Entry:
;    Drive is selected, on proper track
;    HL->track buffer
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dWrtTrk	di

	lda	fWaitHd		;load head settle (E) flag
	ori	cWRTRK		;combine with write track command

	push	b		;save BC
	push	d		;save DE
	call	dWrSec		;do the data transfer
	pop	d		;restore
	pop	b

	xra	a		;no head settle wait after 1st write
	sta	fWaitHd

	in	FDCSTAT		;get status of the write
	ani	smWRITE		;any of these bits is an error
	ei			;re-enable interrupts
	ret

;--------------------------------------------------------------
; dWrSec - Do the physical transfer of data from memory to
;    the disk. When the disk controller completes the
;    command, it generates an NMI interrupt that returns
;    control to the caller of this subroutine.
;
;    The FDCWAIT register can't be used for data sync until
;    after the 1793 requests the 2nd byte. Therefore, a poll
;    for DRQ must be used for the 1st and 2nd byte. The 1st
;    byte is not timing critical. The 2nd byte is. In order to
;    meet 1793 timing requirements for DD data (11.5us worst
;    case) the poll loop uses a computed jump based on reading
;    the board status (select) register. When DRQ is asserted,
;    0FFh is returned, otherwise 0FCh is returned. This loop
;    must be located at xxFCh within a page.
;
;    To handle a race condition that can occur with the Rev 0
;    controller, B' is loaded with FFh (the LSB of the jump
;    address when DRQ is asserted) because the race condition
;    can cause the DRQ poll loop to jump into the middle of
;    the INP L instruction. The 2nd byte evaluates to a MOV L,B
;    instruction, and therefore, moves the LSB of the proper
;    jump address into L for subsequent PCHL jump.
;
; On Entry:
;    Write sector command issued to controller
;    HL->sector (or address mark) buffer
; On Exit:
; Clobbers All (may exit while in alternate registers)
;--------------------------------------------------------------

WRSIZE	set	dwWt2nd-dWrSec

;	org	(($+WRSIZE+100h-0FCh) AND 0FF00h) + (0FCh - RDSIZE)
	org	(($+15h+100h-0FCh) AND 0FF00h) + (0FCh - 16h)

dWrSec	out	FDCCMD		;issue passed write command
	
	mvi	c,FDCDATA	;C=port to write with data byte

dwWt1st	in	FDCSEL		;wait for 1st DRQ
	rrc
	jnc	dwWt1st

	outi			;give 1793 1st byte from HL

	mov	a,m		;A=2nd byte from buffer
	inx	h		;HL->3rd byte location
	push	psw		;push to move to A'
	exx
	pop	psw		;A'=2nd byte to send

	mvi	h,(dwWt2nd SHR 8) AND 0FFh   ;H'=msb of jump address
	lxi	b,((dWr2nd AND 0FFh) SHL 8) + FDCSEL

dwWt2nd	inp	l		;(13) L=FCh (no DRQ) or FFh (DRQ)
	pchl			;(5) jump to dwWt1st or dRd1st

dWr2nd	out	FDCDATA		;(12) give 2nd byte to 1793
	exx			;(5) back to regular registers

dWrLoop	in	FDCWAIT		;(12) wait for next byte
	outi			;(17) give next byte to 1793
	jmp	dWrLoop		;(11)

;--------------------------------------------------------------
; dNxtSec - wait for next (any) sector. For the 1793, this
;    routine simply increments and wraps the sector number
;    passed in E, then falls into dWtSec to return BC with
;    the offset of the sector within the track buffer.
; On Entry:
;    Drive is selected, motor is running
;    E = most recent sector number
; On Exit:
;    E = next sector sector number
;    BC = byte offset of this sector in a track
;--------------------------------------------------------------
dNxtSec	inr	e		;move to next sector
	jz	dSec1		;in case uninitialized rolled to 0

	mov	a,e		;see if past end
	cpi	NUMSEC+1	;verify between 1 and 26
	jc	dWtSec		;good sector, compute bc

dSec1	mvi	e,1		;restart at sector 1
				;fall into dWtSec

;--------------------------------------------------------------
; dWtSec - wait for sector specified in e. For the 1793
;    controller, this routine simply returns the offset
;    of the sector within this track in bc.
; On Entry:
;    e = desired sector number (must be 1-26 for 1793)
;    secSize = sector length on current track
; On Exit:
;    E = sector number
;    BC = byte offset of this sector in a track
;--------------------------------------------------------------
dWtSec	mov	a,e		;save the computed sector
	sta	lastSec

; compute bc=256*(sector-1)

	dcr	a		;zero index sector number
	mov	b,a		;B=MSB of offset
	mvi	c,0		;BC=256*(E-1)

	ret


;-------------------------------------------------------------------
;  String Constants
;-------------------------------------------------------------------
mBanner
 db CR,LF,'COPY Version 1.2 by M. Douglas',CR,LF
 db 'Disk utility for Vector Graphic 8 inch FDC',CR,LF,LF
 db 'Current disk type: Vector DSDD$'

mMenu
 db CR,LF,LF,'======  O P T I O N   T A B L E  ======',CR,LF,LF
 db '  T = Set disk type',CR,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 'Enter Selection: $'

mCmdTyp
 db CR,LF,'Specify the disk type',CR,LF
 db '  1) SSSD (IBM Format)',CR,LF
 db '  2) SSSD (VG Format, track 1 is DD)',CR,LF
 db '  3) DSDD (VG Format)',CR,LF
 db 'Enter disk type or X to exit: $'

mCmdAll
 db CR,LF,'  Copy All Tracks',CR,LF,'$'

mCmdSys
 db CR,LF,'  Copy Only System Tracks',CR,LF,'$'

mCmdDat
 db CR,LF,'  Copy Only Data Tracks',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-D or X to abort): $' 

mDst
 db CR,LF,'Enter destination drive (A-D or X to abort): $' 

mRdyCpy	db	CR,LF,'IBM SSSD source on drive '
mSrcDrv	db	'A, destination on drive '
mDstDrv	db	'B$'

mRdyFmt	db	CR,LF,'IBM SSSD disk to format in drive '
mFmtDrv	db	'A$'

mRdyVfy	db	CR,LF,'IBM SSSD 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)? $'

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',CR,LF,'$'

mFmtErr
 db CR,LF,LF,'** Formatting failed **'

mCRLF
 db CR,LF,'$'

mTrkErr
 db CR,LF,'Track '
mErrTrk
 db 'xx failed sectors: $'
mErrSec
 db 'xx $'

; disk type table and strings

pDskTyp	dw	mISSSD
	dw	mVSSSD
	dw	mVDSDD

mISSSD	db 	'IBM SSSD'
mVSSSD	db 	' VG SSSD'
mVDSDD	db 	' VG DSDD'

TYPELEN	equ	(mVSSSD-mISSSD) and 0FFh



;-------------------------------------------------------------------
;  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)

trkTbl	ds	NUMDRV+1	;track number each drive is on
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

lastSec	ds	1		;last sector accessed on track
fWaitHd	ds	1		;E bit (wait head settle) flag
curCyl	ds	1		;current cylinder
curSide	ds	1		;current side (0=bot, 1=top)
secSize	ds	1		;sector size (128 or 256)
numTrks	ds	1		;number of tracks to process

secBuf	ds	256		;sector read buffer when verifying writes

; retry logic variables

wrRetry	ds	1		;disk write retry counter
rdRetry	ds	1		;disk read retry counter
rtStep	ds	1		;current retry step
trkErr	ds	1		;non zero if error occured on track

; Formatting paramters

pTrkId	ds	2		;pointer to track ID in trkbuf
fmtSecL	ds	2		;length of sector when formatting

; Address mark buffer

amBuf	equ	$
amTrk	ds	1		;track
	ds	1
amSec	ds	1		;sector
amSecL	ds	1		;sector length code
amCrc	ds	2		;CRC 
	ds	4		;safety space

	ds	64		;our stack
ourStk	equ	$
trkBuf	equ	$

; Equates for formatting

SDTRKID	equ	trkBuf+80	;offset of 1st track ID (single density)
SDSECL	equ	128+58		;sector length when formatting (SD)

DDTRKID	equ	trkBuf+162	;offset of 1st track ID (double density)
DDSIDE	equ	trkBuf+163
DDSECL	equ	256+114		;sector length when formatting (DD)

	end
