*****************************************************************************
*
*  FASTBACK - Fast disk to disk copy for FLEX 2.0 and newer
*
*	This program is for a SWTPC 6800 with a DC-x floppy controller
*	running FLEX 2.0+. It performs a very fast disk to disk copy
*	and also offers the option to format the target disk. FLEX 2.0+
*	uses 10 sectors per track, 256 bytes pers sector with 35, 40,
*	or 40 double sided tracks per disk.
*
*	Written by Mike Douglas
*
*	Rev	 Date	    Desc
*	1.0	01/27/22    Original
*
*	1.1	02/17/22    Get disk geometry from the SIR of the source
*			    disk. Respond to Y/N confirmation with "m" to
*			    force manaul selection of geometry. Handle
*			    case of Read Address on track zero returning
*			    sector 0.
*
*	1.2	09/11/22    Look at controller ready line to determine if
*			    a 1 sec motor start delay is required. 
*
*****************************************************************************

* FLEX Equates

WARMS	equ	$AD03		;FLEX warm start entry
GETCHR	equ	$AD15
PUTCHR	equ	$AD18
PSTRNG	equ	$AD1E
STACK	equ	$A07F		;stack in SWTBUG RAM
FCB	equ	$A840		;FLEX system FCB

* Disk equates

NUMSEC	equ	10		;sectors per side
SECLEN	equ	256		;sector length
MINDRV	equ	0		;first drive number
MAXDRV	equ	3		;max drive number 
TRKLEN	equ	NUMSEC*SECLEN	;length of track in bytes

WRTRIES	equ	4
RDTRIES	equ	4		;read retries

* FMS equates

FMS	equ	$B406		;entry point for FMS calls
RDSSEC	equ	9		;read single sector

fcbCMD	equ	0		;FMS command
fcbDRV	equ	3		;drive number
fcbTRK	equ	30		;track to read
fcbSEC	equ	31		;sector to read
fcbBUF	equ	64		;start of sector buffer

sirMTRK	equ	fcbBUF+38	;max track number (from SIR)
sirMSEC	equ	fcbBUF+39	;max sector number (from SIR)

* Misc equates

CR	equ	$0D		;ASCII for carriage return
LF	equ	$0A		;ASCII for line feed
EOT	equ	$04		;end of string for FLEX

* Layout of raw sector in format buffer.

	org	0
	rmb	6		;gap bytes
idMark	rmb	1		;address mark
idTrk	rmb	1		;LSB of track number
	rmb	1
idSec	rmb	1		;sector number
idSize	rmb	1		;sector length code
idCrc	rmb	1		;CRC mark
	rmb	17		;gap bytes
dataMrk	rmb	1		;data mark
	rmb	256		;sector data
dataCrc	rmb	1		;CRC on sector data
	rmb	13		;gap bytes

RSECLEN	equ	*		;raw sector length
FMTLEN	equ	RSECLEN*NUMSEC+$100	;track space for format

	org	$0100		;load address
*-----------------------------------------------------------------------------
* Program entry
*-----------------------------------------------------------------------------
entry	bra	start
	fcb	12		;current version number

start	lds	#STACK		;initialize stack pointer

	ldx	#mBanner	;display startup and confirmation
	jsr	PSTRNG
	jsr	GETCHR
	oraa	#$20		;upper case letters to lower case

	cmpa	#'m'		;force manual selection of disk geometry?
	beq	getTrks		;yes

	cmpa	#'y'		;continue?
	beq	getGeo		;yes

	jmp	WARMS		;else, exit

* Read system information record (SIR) of source disk and try to figure
*   out disk geometry. If not availabke, ask operator.

getGeo	jsr	readSir		;get disk geometry from the SIR
	beq	chkFmt		;success		

getTrks	ldx	#mTrks		;get disk layout from the user
	jsr	PSTRNG
	jsr	GETCHR		;get byte from the console
	oraa	#$20		;upper case letters to lower case
	cmpa	#'x'		;exit requested?
	bne	chkTrks		;no, check the tracks number entered
	jmp	WARMS		;else, exit as requested

chkTrks	suba	#'1'		;subtract ACII offset
	cmpa	#3		;must be 0-2
	bcc	getTrks		;invalid response, re-enter

	ldx	#TRKLEN		;assume single sided track length
	clr	fDouble		;assume single sided flag
	ldab	#35		;and 35 tracks
	tsta			;if 0, then 35 tracks
	beq	savTrks

	ldab	#40		;testing for 40 tracks
	deca			;if 1, then 40 tracks
	beq	savTrks

	inc	fDouble		;else, double-sided (must be 1)
	ldx	#2*TRKLEN	;double length of the track

savTrks	stab	numTrk		;save 35 or 40 tracks
	stx	trkLen		;save track length

* chkFmt - See if the user wants to format the target disk

chkFmt	ldx	#mFormat	;see if user wants to format target
	jsr	PSTRNG
	jsr	GETCHR
	oraa	#$20		;upper case letters to lower case
	cmpa	#'y'		;format?
	bne	init		;no
	jsr	format		;else, format disk
	
init	jsr	sizeRam		;determine amount of RAM available
	clr	srcTrk		;start both drives in track 0
	clr	destTrk

	ldx	#mCopy		;display "Copying..."
	jsr	PSTRNG
	
*-----------------------------------------------------------------------------
*  main - Loop through all tracks on the disk reading as many tracks
*    as will fit in RAM before writing to target disk, then repeat.
*-----------------------------------------------------------------------------
main	ldx	#trkBuf		;initialize track pointer
	stx	trkPtr

	clr	drvNum		;A=source drive number
	ldab	srcTrk		;set B to track drive is on
	jsr	dSelect		;select the drive
	
* Read and buffer tracks until RAM is full or all tracks have been read.

fillBuf	bsr	readTrk		;read a track into trkBuf
	ldaa	#'.'		;display pacifier
	jsr	PUTCHR

	inc	srcTrk		;increment track number
	ldaa	srcTrk		;done all tracks on the disk?
	cmpa	numTrk		
	beq	wrtDisk		;yes, go write the buffered tracks

	ldx	trkPtr		;RAM buffer full?
	cpx	bufEnd
	bne	fillBuf		;no, keep reading

* Write buffered tracks

wrtDisk	ldaa	TRKREG		;save the track source disk is on
	staa	srcTrk
	
	ldx	#trkBuf		;initialize track pointer
	stx	trkPtr

	inc	drvNum		;A=destination drive number
	ldab	destTrk		;set B to track drive is on
	jsr	dSelect
		
wrtBuf	jsr	wrtTrk		;write a track 
	ldaa	#'*'		;display pacifier
	jsr	PUTCHR

	inc	destTrk		;increment track number
	ldaa	destTrk
	cmpa	numTrk		;done all tracks on the disk?
	beq	allDone		;yes, we're all done

	ldx	trkPtr		;end of bufferd tracks reached?
	cpx	bufEnd
	bne	wrtBuf		;no, keep writing

	ldaa	TRKREG		;save the track dest disk is on
	staa	destTrk
	bra	main		;go back and read source

allDone	ldx	#mDone		;"Done" message
	
pgmExit	jsr	PSTRNG

	ldaa	#1		;restore both drives to track 0
	staa	drvNum
	jsr	dSelect
	jsr	dRestor

	clr	drvNum		;restore both drives to track 0
	jsr	dSelect
	jsr	dRestor

	jmp	WARMS		;exit to FLEX
	
*-----------------------------------------------------------------------------
* readTrk - read NUMSEC sectors from the current track into a trkBuf as
*   pointed to by trkPtr. After the track is read, trkPtr is updated
*   by the length of a track to point to the next track buffer.
*-----------------------------------------------------------------------------
readTrk	clr	side		;start on side 0 (bottom)
	ldaa	srcTrk		;seek to srcTrk
	jsr	dSeek
	bne	seekErr
	
	jsr	dReadAm		;get sector head is over
	bne	secErr		;fail
	jsr	getIdx		;get index of sector under head

rtSide	ldaa	#2		;init retry step
	staa	rtStep
	ldaa	#NUMSEC		;set number of sectors to read
	staa	secCnt

rtLoop	jsr	incSec		;move to next sector index
	jsr	dSecAdr		;get sector number and buffer address

rtRead	jsr	dRead		;read the sector
	bne	secRtry		;read error, run retry logic
	
rtNext	dec	secCnt		;decrement sector count
	bne	rtLoop		;loop until all sectors read

	ldaa	side		;done with required number of sides?
	cmpa	fDouble
	beq	rtDone		;yes, track has been read

	inc	side		;else, now on side 1
	jsr	dSide1		;select top side
	bra	rtSide		;repeat track read for top side

rtDone	ldaa	trkPtr+1	;increment tkrPtr by the track length
	adda	trkLen+1
	staa	trkPtr+1
	
	ldaa	trkPtr		;MSB
	adca	trkLen
	staa	trkPtr

	rts

*-----------------------------------------------------------------------------
* Seek error. This is either an actual seek error or the disk is
*    write protected. 
*-----------------------------------------------------------------------------
seekErr	ldx	#mWrtPrt	;write protect error message
	bita	#sWRPROT	;write protect?
	bne	pgmExit		;yes
	
	ldx	#mSkErr		;else, seek error message
	bra	pgmExit

readErr	ldx	#mRdErr
	bra	pgmExit
	
secErr	ldx	#mNoSec
	jmp	pgmExit

*--------------------------------------------------------------------------
* 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	bita	#sSECERR	;sector not found error?
	bne	srNext		;yes, skip retries, go to next step

	ldaa	#RDTRIES	;init retry counter
	staa	rdRetry

srLoop	jsr	dRead		;try the read again
	beq	srDone		;success, retry is done

	dec	rdRetry		;decrement retry counter
	bne	srLoop		;try again

* srNext - move to next retry step

srNext	dec	rtStep		;decrement to next retry step
	beq	stpPast		;step 0, step in one track and come back
	bpl	stpBack		;step 1, step out one track and come back
	bra	readErr		;else, give up

srDone	ldaa	#2		;restart the retry step counter
	staa	rtStep
	bra	rtNext		;re-enter the sector loop

* stpBack - step out then back in, retry read

stpBack	ldaa	srcTrk		;A=current track
	beq	stpPast		;already on track zero, step in instead
	deca			;else, step out one track
	bra	doBump		;go bump the head

* stpPast - step in then back out, retry read

stpPast	ldaa	srcTrk		;A=current rack
	inca			;step in one track
	cmpa	numTrk		;already at max track?	
	bcc	stpBack		;yes, seek back instead

doBump	jsr	dSeek		;see to new track
	ldaa	srcTrk		;restore track number
	jsr	dSeek		;back to original track
	jmp	rtRead		;try reads again

*-----------------------------------------------------------------------------
* wrtTrk - Write NUMSEC sectors to the current track from a trkBuf as
*   pointed to by trkPtr. After the track is written, trkPtr is updated
*   by the length of a track to point to the next track buffer. 
*-----------------------------------------------------------------------------
wrtTrk	clr	side		;start on side 0 (bottom)
	ldaa	destTrk		;seek to destination track
	jsr	dSeek
	bne	seekErr		;seek error or write protected

	jsr	dReadAm		;get sector head is over
	bne	secErr
	jsr	getIdx		;get index of sector under head
	
wtSide	ldaa	#WRTRIES	;init write retry counter
	staa	wrRetry
	
wtRetry	ldaa	#NUMSEC		;set number of sectors to write
	staa	secCnt
	
wtLoop	jsr	incSec		;move to next sector index
	jsr	dSecAdr		;get sector and buffer address for write
	jsr	dWrite		;write the sector
	bne	wrtErr		;write failed
	dec	secCnt		;decrement sector count
	bne	wtLoop		;loop until all sectors written

* Verify the track just written

	ldaa	secNum		;A=sector just written
	jsr	gtIdxA		;get index of sector just written
	bsr	incSec		;skip 1 sector for tunnel erase

	ldaa	#NUMSEC		;B=number of sectors to verify
	staa	secCnt

vfyLoop	jsr	incSec		;move to next sector index
	jsr	dSecAdr		;get sector number and buffer address
	jsr	dVerify
	bne	vfyErr		;error, goto retry logic

vfyNext	dec	secCnt		;decrement sector count
	bne	vfyLoop		;loop until all sectors verified

	ldaa	side		;done with required number of sides?
	cmpa	fDouble
	beq	wtDone		;yes, write is done

	inc	side		;now on side 1
	jsr	dSide1		;select top side
	bra	wtSide		;repeat track write for top side
	
wtDone	ldaa	trkPtr+1	;increment tkrPtr by the track length
	adda	trkLen+1
	staa	trkPtr+1
	
	ldaa	trkPtr		;MSB
	adca	trkLen
	staa	trkPtr

	rts

* vfyErr - verify failed, retry reads followed by a re-write
*   of the track.

vfyErr	ldaa	#RDTRIES	;init read retry counter
	staa	rdRetry

vfyRtry	jsr	dVerify		;attempt to verify again
	beq	vfyNext		;worked - move on to next sector

	dec	rdRetry		;decrement the read retry counter
	bne	vfyRtry

* Read retries expired, decrement the write retry count and re-write
*   if the count is not expired

	dec	wrRetry		;decrement the write retry counter
	bne	wtRetry

wrtErr	ldx	#mWrtErr
	jmp	pgmExit	

*-----------------------------------------------------------------------------
* incSec - Increment and wrap the sector index secIdx
*-----------------------------------------------------------------------------
incSec	ldaa	secIdx
	inca
	cmpa	#NUMSEC
	bne	isExit
	clra
isExit	staa	secIdx
	rts

*-----------------------------------------------------------------------------
* readSir - Read the System Information Record and use the data to set
*    the number of tracks and single/double sided.
*----------------------------------------------------------------------------
readSir	ldx	#FCB		;read the SIR (track 0, sec 3)
	clr	fcbDRV,x	;drive 0
	clr	fcbTRK,x	;track 0
	ldaa	#3		;sector 3
	staa	fcbSEC,x

	ldaa	#RDSSEC		;read single sector command
	staa	fcbCMD,x
	jsr	FMS
	bne	rsExit		;read error

	ldaa	sirMTRK,x	;A=max track number on the disk
	inca			;A=number of tracks
	staa	numTrk

	cmpa	#35		;validate number of tracks
	beq	rsSSSD		;valid for SSSD
	cmpa	#40
	bne	rsExit		;invalid track count, exit with error

	ldaa	sirMSEC,x	;A=max sector number on a track
	cmpa	#NUMSEC		;single or double sided count?
	beq	rsSSSD		;single

	suba	#NUMSEC*2	;verify double sided count
	bne	rsExit		;invalid
	
	inca			;set double sided flag (must be 1)
	staa	fDouble		;set flag for double sided true
	ldx	#TRKLEN*2	;set double sided track length
	bra	rsSet

rsSSSD	clr	fDouble		;single sided
	ldx	#TRKLEN		;single sided track length

rsSet	stx	trkLen		;save track length
	clra			;exit with zero status

rsExit	rts
	
*-----------------------------------------------------------------------------
* getIdx - Search for the sector in amSec in the skew table. Return
*    the index of the match in secIdx
*-----------------------------------------------------------------------------
getIdx	ldaa	amSec		;A=sector number under head
	
gtIdxA	tsta			;sector zero?
	bne	giNot0		;no
	inca			;force 0 to be 1

giNot0	clrb			;B=index into skew table
	ldx	#skewTbl	;assume bottom skew table
	tst	side		;top or bottom?
	beq	giLoop		;bottom
	ldx	#skewTop	;use top skew table

giLoop	cmpa	0,x		;sector found?
	beq	giMatch		;yes
	inx			;move to next entry
	incb
	cmpb	#NUMSEC		;past end of table?
	bne	giLoop		;no, keep looking

	jmp	secErr		;couldn't find match
	
giMatch	stab	secIdx	
	rts
		
*------------------------------------------------------------------
* sizeRam - determine how much RAM we have for buffering tracks.
*   Sets the bufEnd variable which points to end address of 
*   the last possible track buffer + 1
*------------------------------------------------------------------
sizeRam	ldx	#trkBuf+$100	;look for end of RAM starting
	stx	saveX		;    at trkBuf+$100

ramLoop	ldaa	0,x		;A=current RAM content
	inc	0,x		;change value in RAM
	ldab	0,x		;B=new value from RAM
	staa	0,x		;restore original value
	
	cba			;did RAM change?
	beq	ramEnd		;no, end of RAM found
	
	inc	saveX		;increment to next page
	ldx	saveX		;X->next page in RAM
	bpl	ramLoop		;loop until $80xx reached
	
* ramEnd - end of RAM found. Now determine the end address + 1 of the
*   last track buffer that will fit in RAM. Store in bufEnd

ramEnd	ldab	saveX		;B=1st page not RAM
	ldx	#trkBuf		;start saveX at trkBuf
	stx	saveX

bfEndLp	ldaa	saveX+1		;increment saveX by TRKLEN
	staa	bufEnd+1	;bufEnd has previous value of saveX
	adda	trkLen+1
	staa	saveX+1

	ldaa	saveX		;MSB
	staa	bufEnd		;bufEnd has previous value of saveX
	adca	trkLen
	staa	saveX

	cba			;saveX MSB < RAM end MSB?
	bcs	bfEndLp		;yes, still more room,

	rts			;exit with bufEnd properly set

*---------------------------------------------------------------------
* format - Format disk
*---------------------------------------------------------------------
format	ldx	#mFmting	;display "formatting..."
	jsr	PSTRNG

	ldaa	#1		;format drive 1
	staa	drvNum
	jsr	dSelect
	jsr	dRestor		;restore to track 0
	beq	fmtInit		;no error, go init format buffer
	jmp	seekErr		;else, probably write protect error

fmtInit	ldx	#trkBuf		;x->buffer for track format data

clrLoop	clr	0,x		;init entire buffer to zero
	inx	
	cpx	#trkBuf+FMTLEN
	bne	clrLoop
	
* fmtLoop - Loop through each track writing the track buffer to
*    disk after updating ID fields in each sector for each track.
		
	clr	destTrk		;start on track 0

fmtLoop	clr	side		;side 0
	clr	secIdx		;very 1st sector
	ldaa	#NUMSEC		;max sector index on side 0
	staa	maxSec

fmtTrk	ldx	#trkBuf+2	;+2 gives 8 byte gap 1 like NEWDISK

secLoop	bsr	secData		;insert sector data
	inc	secIdx		;repeat for all 10 sectors on a side
	ldaa	secIdx
	cmpa	maxSec
	bne	secLoop

	jsr	dWrtTrk		;write the formatted track
	tst	fDouble		;double sided?
	beq	fmtNext		;no, move to next track

	tst	side		;on side 0 or 1?
	bne	fmtNext		;just finished side 1, track done

	inc	side		;indicate we're on side 1 
	jsr	dSide1		;switch drive to side 1
	asl	maxSec		;max sector index on side 1
	bra	fmtTrk		;repeat for the top side

fmtNext	ldaa	#'*'		;display pacifier
	jsr	PUTCHR

	inc	destTrk		;increment track number
	ldaa	destTrk
	cmpa	numTrk		;last track done?
	beq	fmtDone		;yes, exit

	jsr	dSeek		;else, seek to next track
	bra	fmtLoop		;repeat

fmtDone	rts	

* secData - Update the ID record for each sector in the track

secData	ldaa	#$FE		;$FE = ID address mark
	staa	idMark,x

	ldaa	#1		;sector size flag (256 bytes)
	staa	idSize,x

	ldaa	destTrk		;set track in ID record
	staa	idTrk,x

	stx	saveX		;convert secIdx into sector number
	ldab	secIdx
	ldx	#skewTbl	;x->sector mapping table
	stab	*+4		;modify offset in the following read
	ldab	0,x		;offset set to secIdx
	ldx	saveX		;restore x
	stab	idSec,x		;set sector in ID record

	tst	destTrk		;on track zero?
	bne	notTrk0		;no
	cmpb	#1		;sector 1 
	bhi	notTrk0		;no
	dec	idSec,x		;change sector 1 to 0

notTrk0	ldaa	#$FB		;place the data mark
	staa	dataMrk,x

	ldaa	#$F7		;generate CRCs
	staa	idCrc,x		;ID record CRC

	ldab	#128		;increment X by 128
	jsr	addB2X
	staa	dataCrc-128,x	;data CRC
	
	ldab	#RSECLEN-128	;b=length of raw sector left to add

* addB2X - Add B to X

addB2X	stx	saveX		;increment x by raw sector length
	addb	saveX+1		;compute LSB
	stab	saveX+1
	bcc	addExit		;done if no carry

	inc	saveX		;else increment MSB

addExit	ldx	saveX		;return result in X
	rts

*---------------------------------------------------------------------
* Disk I/O subroutines
*---------------------------------------------------------------------
DRVREG	equ	$8014 		;drive select register
CMDREG	equ	$8018 		;1771 command register
TRKREG	equ	$8019 		;1771 track register
SECREG	equ	$801A 		;1771 sector register
DATAREG	equ	$801B 		;1771 data register

* Select register equates

rMOTOFF	equ	$80		;1=motor off
rSIDE1	equ	$40		;1=side 1 (top)

* 1771 Commands

cREAD	equ	$88		;read, head assumed settled
cWRITE	equ	$A8		;write, head assumed settled
cRDADDR	equ	$C4		;readd address mark
cWAITHD	equ	$04		;bit to wait for head settle

cRESTOR	equ	$0B		;restore to track 0, load head
cSEEK	equ	$1B		;seek to track, load head
cWRTRK	equ	$F4		;write track (for formatting disk)
cRESET	equ	$D0		;reset 1771 to idle

* 1771 Status Masks

sNOTRDY	equ	$80		;1=drive not ready
sWRPROT	equ	$40		;1=write protected
sHEADLD	equ	$20		;1=head is loaded
sSKERR	equ	$10		;1=seek error
sCRCERR	equ	$08		;1=CRC error
sTRACK0	equ	$04		;1=on track zero
sINDEX	equ	$02		;1=index detected
sBUSY	equ	$01		;1=1771 busy

sSECERR	equ	$10		;1=sector not found
sWRTFLT	equ	$20		;1=write fault
sDLOST	equ	$04		;1=lost data
sDRQ	equ	$02		;1=data request

* Error masks for status of a seek, read, write

eSEEK	equ	sSKERR+sNOTRDY
eREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY
eVERIFY	equ	sSECERR+sCRCERR+sNOTRDY
eWRITE	equ	eREAD+sWRPROT+sWRTFLT

*--------------------------------------------------------------
* dRead
*    Read sector specified by secNum into buffer at secPtr
*--------------------------------------------------------------
dRead	ldx	secPtr		;X->sector buffer
	ldaa	secNum		;give sector number to the 1771
	staa	SECREG

	ldaa	#cREAD		;1771 read command
	sei			;disable interrupts
	staa	CMDREG		;issue command to 1771
	jsr	dCmdDly		;ensure command processing time

drLoop	ldaa	CMDREG		;A=1771 status
	bita	#sBUSY		;read still in progress>?
	beq	drDone		;no, read has finished
	bita	#sDRQ		;data ready flag set?
	beq	drLoop		;no, keep waiting
	
	ldaa	DATAREG		;get the next byte
	staa	0,x		;save in buffer
	inx
	bra	drLoop

drDone	cli			;enable interrupts
	anda	#eREAD		;test and return read error bits
	rts

*--------------------------------------------------------------
* dReadAm
*    Read next address mark into amBuf
*--------------------------------------------------------------
dReadAm	ldaa	#RDTRIES	;set up retry counter
	staa	rdRetry

daRetry	ldx	#amBuf		;read into the address mark buffer
	ldaa	#cRDADDR	;1771 read address command
	sei
	staa	CMDREG		;issue command to 1771
	jsr	dCmdDly		;ensure command processing time

daLoop	ldaa	CMDREG		;A=1771 status
	bita	#sBUSY		;read still in progress>?
	beq	daDone		;no, read has finished
	bita	#sDRQ		;data ready flag set?
	beq	daLoop		;no, keep waiting
	
	ldaa	DATAREG		;get the next byte
	staa	0,x		;save in buffer
	inx
	bra	daLoop

daDone	cli			;enable interrupts
	anda	#eREAD		;any errors?
	bne	daFail		;yes

	ldaa	amSec		;see if reasonable data present
	cmpa	#NUMSEC*2
	bhi	daFail		;invalid sector

	clra			;exit with zero status		
daExit	rts

daFail	dec	rdRetry		;any more retries?
	bpl	daRetry		;yes, try again
	rts			;exit with non-zero status

*--------------------------------------------------------------
* dWrite
*    Write sector specified by secNum from secPtr
*--------------------------------------------------------------
dWrite	ldx	secPtr		;X->sector buffer

	ldaa	secNum		;give sector number to the 1771
	staa	SECREG

	ldaa	#cWRITE		;1771 write command
	sei			;disable interrupts
	staa	CMDREG		;issue command to 1771
	jsr	dCmdDly		;ensure command processing time

dwLoop	ldaa	CMDREG		;A=1771 status
	bita	#sBUSY		;write still in progress>?
	beq	dwDone		;no, write has finished

	bita	#sDRQ		;ready for data?
	beq	dwLoop		;no, keep waiting
	
	ldaa	0,x		;get next byte
	staa	DATAREG		;give it to the 1771
	inx
	bra	dwLoop

dwDone	cli			;re-enable interrupts
	anda	#eWRITE		;test and return write error bits
	rts

*--------------------------------------------------------------
* dWrtTrk
*    Write a 1771 full track from trkBuf for formatting
*--------------------------------------------------------------
dWrtTrk	ldx	#trkBuf		;X->formatted track buffer

	ldaa	#cWRTRK		;1771 write track command
	sei			;disable interrupts
	staa	CMDREG		;issue command to 1771
	jsr	dCmdDly		;ensure command processing time

dwtLoop	ldaa	CMDREG		;A=1771 status
	bita	#sBUSY		;write still in progress>?
	beq	dwtDone		;no, write has finished
	bita	#sDRQ		;ready for data?
	beq	dwtLoop		;no, keep waiting
	
	ldaa	0,x		;get next byte
	staa	DATAREG		;give it to the 1771
	inx
	bra	dwtLoop

dwtDone	cli			;re-enable interrupts
	anda	#eWRITE		;test and return write error bits
	rts
	
*--------------------------------------------------------------
* dVerify
*    Verify sector specified by secNum by validating its CRC
*--------------------------------------------------------------
dVerify	ldaa	secNum		;give sector number to the 1771
	staa	SECREG

	ldaa	#cREAD		;1771 read command
	bsr	dCmdWt		;issue command and wait
	anda	#eVERIFY	;test and return verify errors
	rts
	
*--------------------------------------------------------------
* dSeek
*    Seek to track in A and load the head. If A is track
*    zero (will be the 1st seek in this program), then the 
*    restore command is used to establish the track number
*    in the 1771. The returned status includes the write 
*    protect bit.
*--------------------------------------------------------------
dSeek	ldab	drvNum		;always go to side 0 after seek
	stab	DRVREG

	tsta			;requesting track zero?
	beq	dRestor		;yes, use restore instead
	
	staa	DATAREG		;give track to the 1771
	ldaa	#cSEEK		;A=seek to track and load head
	bsr	dCmdWt		;issue command and wait for it
	anda	#eSEEK
	rts
	
*--------------------------------------------------------------
* dRestore
*    Issue and wait on restore to track zero. The returned
*    status includes the write protect bit.
*--------------------------------------------------------------
dRestor	ldaa	#cRESTOR	;A=restore to track 0 and load head
	bsr	dCmdWt		;issue command and wait for it
	anda	#eSEEK		;status mask includes write protect
	rts
	
*--------------------------------------------------------------
* dCmdWt
*    Write command from A and wait for it to complete.
*--------------------------------------------------------------
dCmdWt	staa	CMDREG		;issue command from A
	bsr	dCmdDly		;ensure command processing time
	
dWtCmd	ldaa	CMDREG		;poll for busy bit to go false
	bita	#sBUSY
	bne	dWtCmd	

	rts

*--------------------------------------------------------------
* dSelect
*    Select the drive passed in A and set the track register
*    to the value in B. Wait 1 sec for motor if the motor is
*    not already on.
*--------------------------------------------------------------
dSelect	staa	DRVREG		;select the drive in A
	ldaa	CMDREG		;is motor already on?
	anda	#sNOTRDY	;  (ldaa also turned on the motor)
	beq	mtrIsOn		;yes

	ldx	#0		;wait 1 sec for motor start up	
motrDly	inx			;(4)
	dex			;(4)
	dex			;(4)
	bne	motrDly		;(4)
	
mtrIsOn	ldaa	#cRESET		;reset the 1771 to be safe
	bsr	dCmdWt
	stab	TRKREG		;set track drive is on
	rts

*--------------------------------------------------------------
* dSide1
*    Select side 1 (top) of disk
*--------------------------------------------------------------
dSide1	ldaa	drvNum		;set side 1
	oraa	#rSIDE1
	staa	DRVREG
	rts

*--------------------------------------------------------------
* dCmdDly
*    Wait 58us (including the calling JSR) to ensure a 1771
*    command is accepted and the status register can be read.
*
*    This delay had to be bumped to 76us (60us in the loop)
*    to prevent occasional errors in dReadAm.
*--------------------------------------------------------------
dCmdDly	ldaa	#10		;10*6=60us in loop

dlyLoop	deca
	bne	dlyLoop
	rts
	
*--------------------------------------------------------------
* dSecAdr
*    Convert the sector index (secIdx) into the sector number
*    on the disk (secNum) and a pointer into the track buffer
*    (secPtr). The top side is indexed if side <> 0.
*--------------------------------------------------------------
dSecAdr	ldx	#skewTbl	;X->side 0 sector translate table 
	ldaa	side		;side 0 or 1?
	beq	dsXlate		;side 0
	ldx	#skewTop	;else use side 1 table (top)

dsXlate	ldaa	secIdx		;index into skew table by secIdx
	staa	*+4		;modify index offset in next inst
	ldaa	0,x		;A=sector to look for on disk
	staa	secNum		;save in secNum
	
	deca			;zero-index the sector number
	bne	dNot1st		;not 1st sector

	tst	TRKREG		;on track 0?
	bne	dNot1st		;no
	dec	secNum		;else 1st sec on trk 0 is 0, not 1

dNot1st	adda	trkPtr		;compute MSB of secPtr into trkBuf
	staa	secPtr
	ldaa	trkPtr+1	;LSB is the same (256 byte sectors)
	staa	secPtr+1
	rts	

*---------------------------------------------------------------------
* Message constants
*---------------------------------------------------------------------
mBanner	fcb	CR,LF
	fcc	'FAST BACKUP v1.2'
	fcb	CR,LF
	fcc	'Source in drive 0 and destination in drive 1? '
	fcb	EOT

mTrks	fcb	CR,LF
	fcc	'Specify the disk layout'
	fcb	CR,LF
	fcc	'  1) 35 tracks, single-sided'
	fcb	CR,LF
	fcc	'  2) 40 tracks, single-sided'
	fcb	CR,LF
	fcc	'  3) 40 tracks, double-sided'
	fcb	CR,LF
	fcc	'Enter layout type or X to exit: '
	fcb	EOT

mFormat	fcc	'Format destination? '
	fcb	EOT

mFmting	fcc	'Formatting '
	fcb	EOT

mCopy	fcc	'Copying'
	fcb	EOT

mRdErr	fcc	'Source disk read error'
	fcb	CR,LF
	fcb	EOT

mWrtErr	fcc	'Destination disk write error'
	fcb	CR,LF
	fcb	EOT

mNoSec	fcc	'Sector not found - disk not formatted?'
	fcb	CR,LF
	fcb	EOT

mWrtPrt	fcc	'Disk is write protected'
	fcb	CR,LF
	fcb	EOT

mSkErr	fcc	'Restore or seek error'
	fcb	CR,LF
	fcb	EOT

mDone	fcc	'Backup complete'
	fcb	CR,LF
	fcb	EOT

*---------------------------------------------------------------------
*  Data area
*---------------------------------------------------------------------

* skewTbl - returns the formatted sector number in physical order on
*    the disk. This allows us to read the track in one revolution.
*    The second eight bytes are used when formatting a double-sided
*    disk. skewTop is for reading side 1 of a double-sided disk.

skewTbl	fcb	1,3,5,7,9,2,4,6,8,10
skewTop	fcb	11,13,15,17,19,12,14,16,18,20

* disk variables

numTrk	rmb	1		;number of tracks on the disk
trkLen	rmb	2		;length of a track
drvNum	rmb	1
srcTrk	rmb	1		;track number to read
destTrk	rmb	1	
secIdx	rmb	1		;ordinal sector on disk
secNum	rmb	1		;sector number in address mark
secCnt	rmb	1		;count of sectors to read
side	rmb	1		;disk side 0 or 1
maxSec	rmb	1		;maximum sector number on a track

trkPtr	rmb	2		;pointer into trkBuf at track boundaries
secPtr	rmb	2		;pointer to current sector in trkBuf
bufEnd	rmb	2		;end address + 1 of last track spot in RAM

fDouble	rmb	1		;1=double sided

rdRetry	rmb	1		;read retry counter
rtStep	rmb	1		;retry step
wrRetry	rmb	1		;write retry counter

* address mark buffer

amBuf	equ	*
amTrk	rmb	1		;LSB of track number
	rmb	1
amSec	rmb	1		;sector number
amSize	rmb	1		;sector length code
	rmb	4		;CRC and some spare

* misc variables

saveX	rmb	2		;generic save location for X

* track buffer runs from here to the end of memory

trkBuf	equ	*

	end	entry
