;*****************************************************************************
;
;  COPY.ASM
;	Disk copy, verify, and format utility for CP/M on a Mostek AID-80F
;	computer (WD1771 based controller with SSSD drives).
;
;	Rev	  Date	    Desc
;	1.00	03/06/16   M. Douglas, Original
;
;*****************************************************************************

	maclib	z80		;include Z80 opcodes
; Drive equates

NUMDRVS	equ	4		;number of drives supported
NUMTRKS	equ	77		;35 tracks
NUMSECS	equ	26		;ten sectors per track
SECLEN	equ	128		;sector length
TRKLEN	equ	(NUMSECS*SECLEN)	;length of track
SYSTRKS	equ	2		;number of system tracks
DATATRK	equ	2		;starting data track

; 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

WRTRYS	equ	4		;write 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 
bList	ds	3		;list character out 
bPunch	ds	3		;punch character out 
bReader	ds	3		;reader character out
bHome	ds	3		;move head to home position
bSelDsk	ds	3		;select disk
bSetTrk	ds	3		;set track number
bSetSec	ds	3		;set sector number
bSetDma	ds	3		;set dma address
bRead	ds	3		;read disk
bWrite	ds	3		;write disk

	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

	xra	a		
	sta	secNum		;init current sector number

	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	'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		

	jmp	dspMenu

;-----------------------------------------------------------------------------
; cmdAll - Copy all tracks
;-----------------------------------------------------------------------------
cmdAll	lxi	d,mCmdAll	;display command name
	call	dispmsg

	mvi	a,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	cpyLoop		;single drive copy, skip next message

	lxi	d,mRdyCpy	;display insert disks prompt
	call	getRdy		;get ready confirmation from operator
	rnz			;user aborted

; 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
	jnz	rdFail		;read failed

	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?
	dcr	a		;negative or zero -> negative
	jp	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

	mvi	a,NUMTRKS-SYSTRKS	;copying just data tracks
	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	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

	mvi	a,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		;use BIOS to select drive
	call	selDsk

; vfyLoop - read all tracks which verifies checksum

vfyLoop	lxi	h,trkBuf	;restart at beginning of trkBuf each time
	shld	trkPtr

	call	readTrk		;read a track
	jnz	rdFail		;read error, verify failed

	lxi	h,srcTrk	;increment current track
	inr	m
	lxi	h,trkCnt	;decrement track counter
	dcr	m
	jnz	vfyLoop

; 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
;    Z set if OK, cleared for error
;    Clobbers all
;-----------------------------------------------------------------------------
; compute fillCnt, the number of tracks to read into trkBuf

fillBuf	lda	srcDrv		;use BIOS to select drive
	call	selDsk

	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 (may be negative)
	mov	a,b		;a=max tracks in buffer	
	jnc	doMax		;remaining >= max in buffer, do max in buffer

	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)
	rnz			;couldn't read, exit
	
	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		;use BIOS to select drive
	call	selDsk

	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). In order to read
;    consecutive sectors using the Mostek driver PROM, the timing
;    between sector reads has to be reduced to a minimum, so the 
;    "get1st" and "nxtSec" calls used in other version of this program
;    had to be eliminated and/or modified.
; On Entry:
;    srcDrv = drive to read
;    srcTrk = track to read
;    trkPtr->location within track buffer for this track
; On Exit: 
;    trkPtr->last byte of track+1
;    Z set if OK, cleared for error
;    Clobbers all
;-----------------------------------------------------------------------------
readTrk	lda	srcTrk		;set track in BIOS
	call	setTrk

	call	set1st		;set sector, dmaAddr for first read

	mvi	a,NUMSECS	;secCnt = sector counter
	sta	secCnt

readSec	call	read		;call BIOS read entry point
	rnz			;failure (Z flag assumed from BIOS)

; increment and wrap sector pointer

	lxi	h,secNum	;increment sector
	mov	a,m
	inr	a

	cpi	NUMSECS+1	;past last sector?
	jc	setSec1		;no

	mvi	a,1		;reset sector to 1
	mov	m,a
	call	setSec		;call BIOS to set the sector

	lhld	trkPtr		;reset sector pointer to start of track
	shld	secPtr
	
	jmp	setDec1		;set DMA address, decrement sector count

; setSec1 - Set the sector in A in BIOS. Increment the sector buffer
;    pointer and call BIOS setDma.

setSec1	mov	m,a		;save incremented sector number
	call	setSec		;call BIOS to set the sector

	lhld	secPtr		;increment sector pointer by SECLEN
	lxi	b,SECLEN
	dad	b
	shld	secPtr

; setDec1 - Set DMA address, decrement sector count, loop until all
;    sectors read.

setDec1	call	setDma		;call BIOS to set DMA address
	lxi	h,secCnt	;decrement sector count
	dcr	m
	jnz	readSec		;repeat for all sectors

; rtExit - track read complete, update trkPtr by the length of a
;    full track and return with zero status

rtExit	call	chkQuit		;check for ctrl-c
	mvi	a,'.'		;display '.' pacifier per track
	call	CONOUT

updtTrk	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

;-----------------------------------------------------------------------------
; wrtTrk - write a track with retries from (hl). In order to write
;    consecutive sectors using the Mostek driver PROM, the timing
;    between sector reads has to be reduced to a minimum, so the 
;    "get1st" and "nxtSec" calls used in other version of this program
;    had to be eliminated and/or modified.
; On Entry:
;    dstDrv = drive to write
;    dstTrk = track to write
;    hl->track buffer
; On Exit: 
;    hl->last byte of track+1 if successful, start of bad track otherwise
;    Z set if OK, cleared for error
;    Clobbers all
;-----------------------------------------------------------------------------
wrtTrk	mvi	a,WRTRYS	;init write retry counter
	sta	rtryCnt

	lda	dstTrk		;use BIOS to specify track
	call	setTrk

wrtRtry	call	set1st		;set sector, dmaAddr for first write

	mvi	a,NUMSECS	;secCnt = sector counter
	sta	secCnt

wrtSec	call	write		;call BIOS write entry point
	rnz			;write failure

; increment and wrap sector pointer

	lxi	h,secNum	;increment sector
	mov	a,m
	inr	a

	cpi	NUMSECS+1	;past last sector?
	jc	setSec2		;no

	mvi	a,1		;reset sector to 1
	mov	m,a
	call	setSec		;call BIOS to set the sector

	lhld	trkPtr		;reset sector pointer to start of track
	shld	secPtr

	jmp	setDec2		;set dma, decrement sector counter

; setSec2 - Set the sector in A in BIOS. Increment the sector buffer
;    pointer and call BIOS setDma.

setSec2	mov	m,a
	call	setSec		;call BIOS to set the sector

	lhld	secPtr		;increment sector pointer by SECLEN
	lxi	b,SECLEN
	dad	b
	shld	secPtr

; setDec2 - Set DMA address, decrement sector count, loop until all
;    sectors written

setDec2	call	setDma		;call BIOS to set DMA address

	lxi	h,secCnt	;decrement sector count
	dcr	m
	jnz	wrtSec	

;--------------------------------------------------------------------
; Track write complete, verify by reading and getting a good CRC
;--------------------------------------------------------------------
	mvi	a,NUMSECS	;secCnt = sector counter
	sta	secCnt

	lxi	h,secBuf	;read into unused secBuf
	call	setDma		;set buffer address using BIOS call
	
vfySec	call	read		;call BIOS read entry point
	jnz	vfyErr		;error				

	lxi	h,secNum	;increment sector
	mov	a,m
	inr	a

	cpi	NUMSECS+1	;past last sector?
	jc	setSec3		;no

	mvi	a,1		;reset sector to 1

setSec3	mov	m,a
	call	setSec		;call BIOS to set the sector

; Decrement sector count, loop until all verified

	lxi	h,secCnt	;decrement sector count
	dcr	m		;decrement sector count
	jnz	vfySec

; verify successful, display "*" pacifier, then update hl and exit

	call	chkQuit		;check for ctrl-c
	mvi	a,'*'		;display '*' pacifier
	call	conOut
	jmp	updtTrk		;update trkPtr and exit (in readTrk)

; vfyErr - verify error, retry the write WRTRYS times.

vfyErr	lxi	h,rtryCnt	;decrement retry counter
	dcr	m
	jnz	wrtRtry		;try again

	inr	m		;force non-zero status
	ret			;and exit

;--------------------------------------------------------------
; set1st - Determine the 1st sector number to read on a new
;    track, then call the BIOS to set the sector and DMA
;    address for the read. The starting sector is set to
;    be 45ms from the end of the last sector read to give
;    time for the track step and verify to complete and then
;    be quickly coming upon the computed sector number.
; On Entry:
;    secNum = last sector accessed
;    trkPtr = address of track buffer
; On Exit:
;    secNum = 1st sector to read
;    secPtr = address of sector secNum
; Clobbers all
;--------------------------------------------------------------
set1st	lxi	h,secNum	;compute 1st sector to read
	mov	a,m		;a=last sector read
	adi	8		;8 sectors gives 7*6.4ms=45ms
	cpi	NUMSECS+1	;past 26?
	jc	noWrap		;no

	sui	NUMSECS		;wrap to sector 1-26

noWrap	mov	m,a		;save updated sector back in secNum
	call	setSec		;set sector in BIOS

; compute and set dmaAddr based on start sector

	lda	secNum		;compute bc=128*(secNum-1)
	sui	1		;zero index and clear carry
	rar
	mov	b,a		;b=sector >> 1

	rar			;put lsbit into msbit	
	ani	80h
	mov	c,a		;bc=128*secNum

	lhld	trkPtr		;compute hl=trkPtr+sector offset
	dad	b
	shld	secPtr		;save as sector pointer
	jmp	setDma		;call BIOS setDma entry and exit

;----------------------------------------------------------------------
; 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	NUMDRVS		;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	NUMDRVS		;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	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

;-----------------------------------------------------------------------------
; 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

;-------------------------------------------------------------------
; rdFail, wrFail, fmtFail - display fail message and return to main loop
;-------------------------------------------------------------------
rdFail	lxi	d,mRdErr
	jmp	dispMsg

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. Clobbers all.
;--------------------------------------------------------------
chkQuit	call	conSt		;check for console input
	ora	a		;set z flag
	rz			;nothing present

	call	conIn		;read the character
	ani	7fh
	cpi	CTRLC		;abort requested?
	rnz			;no

; Ctrl-C typed while program is running. Return to drive prompt.

	lxi	sp,ourStk	;restore 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

selDsk	mov	c,a		;selDsk wants param in c
	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bSelDsk	;DE=offset of select disk function
	dad	d
	pchl			;jump to select disk function

setTrk	mov	c,a		;setTrk wants param in c
	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bSetTrk	;DE=offset of set track function
	dad	d
	pchl			;jump to set track function

setSec	mov	c,a		;setSec wants param in c
	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bSetSec	;DE=offset of set sector function
	dad	d
	pchl			;jump to set sector function

setDma	mov	b,h		;BC=HL
	mov	c,l
	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bSetDma	;DE=offset of set dma function
	dad	d
	pchl			;jump to set dma function (dma addr in bc)

read	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bRead		;DE=offset of disk read function
	dad	d
	pchl			;jump to disk read function

write	lhld	WBOOT+1		;HL->warm start entry in BIOS
	lxi	d,bWrite	;DE=offset of disk write function
	dad	d
	pchl			;jump to disk read function


;**************************************************************
;
; format - The disk format routine is custom to the controller
;    and firmware disk drivers in the Mostek computer. The
;    firmware write track command is used to format the disk.
;
;**************************************************************

; PROM Disk Driver Equates

DSKDRV	equ	0EC00h		;disk driver entry point

dcSTAT	equ	10h		;return 1771 status
dcREAD	equ	11h		;read sector
dcWRITE	equ	12h		;write sector
dcSEEK	equ	13h		;seek track with verify
dcRESTR	equ	14h		;restore to track 0
dcGETRK	equ	15h		;read track # from disk, put in 1771
dcDEL	equ	16h		;write deleted sector data mark
dcFMT	equ	17h		;seek track, issue write track for fmt

dsPARAM	equ	80h		;bad parameter passsed in DCB
dsNRDY	equ	40h		;disk not ready
dsSKERR	equ	20h		;seek error

; Floppy controller board equates

FDBCTL	equ	0E3h		;floppy board control register
FFRES1	equ	020h		;bit mask to assert FIFO reset
FFRES0	equ	01Fh		;clear FIFO reset (and buffer bits)

; format - entry point for formatting the disk

format	call	initFmt		;initialize trkBuf for format

	lda	dstDrv		;put drive to format in DCB
	sta	dcbDrv

	lxi	h,trkBuf	;put track buffer address in DCB
	shld	dcbDma

	mvi	a,dcFMT		;put format track command in DCB
	sta	dcbCmd

	xra	a		;put starting track number in DCB
	sta	dcbTrk

	inr	a		;make sure sector has a valid value
	sta	dcbSec

	lxiy	dcb		;point iY to the DCB

; nextTrk - Format the next track.

nextTrk	lxi	h,TRKID		;HL->1st track ID in trkBuf
	mvi	c,NUMSECS	;C=sector counter
	lxi	d,SECLEN+58	;DE=length of sector when formatting
	lda	dcbTrk		;A=current track number

; Put the current track number in the ID field of each sector on the track

tidLoop	mov	m,a		;store track # in sector track ID
	dad	d		;compute address of next sector
	dcr	c
	jnz	tidLoop

; Issue the write track command after issuing a FIFO reset

	in	FDBCTL		;reset FIFO buffer on FDC board
	ori	FFRES1		;set reset bit
	out	FDBCTL		;issue the reset

	ani	FFRES0		;clear reset bit, no buffering
	out	FDBCTL

	call	DSKDRV		;call the firmware disk driver
	rnz			;exit with failure
	
; display '.' pacifier, check for operator abort, then jump to
;     nextTrk to format the next track
		
	mvi	a,'.'		;display '.' pacifier per track
	call	conOut		;display pacifier
	call	chkQuit		;see if abort requested

	lxi	h,dcbTrk	;increment and check track number
	inr	m
	mov	a,m		;A=next track number
	cpi	NUMTRKS		;done?
	jnz	nextTrk		;no, do next track
	ret			;format done

;-------------------------------------------------------------------
;  initFmt - initialize a full track of data for formatting the
;     disk. The actual track number is filled in later at the
;     write track commands are issued.
;-------------------------------------------------------------------
initFmt	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

; fillSec - fill repeating sector portion of the track

	mvi	c,1		;c=sector number

fillSec	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 (updated later)
	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	NUMSECS+1
	jc	fillSec		;no, do next sector

; sectors done, fill with 256 trailing FF for rest of track and then some

	mvi	b,0		;256 bytes of FF
	mvi	a,0FFh
				;fall into fill and exit
; fill - store A (HL++) B times

fill	mov	m,a
	inx	h
	dcr	b
	jnz	fill

	ret

;-----------------------------------------------------------------------------
; dcb - disk control block. This structure is used to pass parameters
;    to the firmware disk driver.
;-----------------------------------------------------------------------------
dcb	equ	$
	db	0,0,0		;0-2 not used
dcbDrv	db	0		;drive number (0-3) at offset 3
dcbFn	db	0,0,0,0,0,0,0,0,0,0,0	;file name (not used) 04-0E
dcbCmd	db	0		;command at offset 0F
	db	0,0,0,0,0,0,0,0,0
dcbDma	db	0,0		;transfer buffer address at 19,1A
	db	0,0
dcbWSec	db	1		;number of sectors for dump (not used)
dcbSec	db	1		;sector to read/write at offset 1E
dcbTrk	db	0		;track to read/write at offset 1F
dcbLst4	db	0,0,0,0		;last four bytes of track are placed here

;-------------------------------------------------------------------
;  String Constants
;-------------------------------------------------------------------
mBanner
 db CR,LF,'COPY Version 1.0 by M. Douglas',CR,LF
 db 'Disk utility for Mostek AID-80F computer',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-76)',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: $'

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,'Source on drive '
mSrcDrv	db	'A, destination on 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)? $'

mSkErr	
 db CR,LF,'*** Restore/seek failure ***',CR,LF,'$'

mRdErr	
 db CR,LF,LF,'** Read or verify error **',CR,LF,'$'

mWrtErr	
 db CR,LF,LF,'** Unrecoverable write error **',CR,LF,'$'

mCpyDon
 db CR,LF,LF,'** Copy and verify complete **',CR,LF,'$'

mVfyDon
 db CR,LF,LF,'** Verify complete, no errors found **',CR,LF,'$'

mWrtPrt	
 db CR,LF,'** Disk is write protected **',CR,LF,'$'

mFmtDon
 db CR,LF,LF,'** Format complete **',CR,LF,'$'

mFmtErr
 db CR,LF,LF,'** Formatting failed **'

mCRLF
 db CR,LF,'$'


;-------------------------------------------------------------------
;  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

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
secPtr	ds	2		;pointer to sector within trkBuf

secNum	ds	1		;current sector number
secCnt	ds	1		;count of sectors left to process
secBuf	ds	128		;sector read buffer when verifying writes

rtryCnt	ds	1		;write retry counter

	ds	64		;our stack
ourStk	equ	$

trkBuf	equ	$

TRKID	equ	trkBuf+80	;offset of 1st track ID in trkBuf

	end
