;	COPYDSK - VERSION 1.2

;
;	COPYDSK UTILITY FOR CP/M DISK SYSTEM
;
;	DATE:	17-APR-77
;	PROGRAMMER:   FRANK MACLACHLAN
;
;	REVISED 2-NOV-77:	LOGICAL SECTORING OPTION ADDED
;				ERROR RETRYS ADDED
;				USER OPTION SELECTION ADDED
;
;	MODIFIED FOR CP/M ASSEMBLER  R S MASON  24-DEC-77
;
;
;////////////////////////////////////////////////////////

;
;   GLOBAL DEFINITIONS:
;
TRUE	EQU	0FFH
FALSE	EQU	0
CR	EQU	0DH		; CARRIAGE RETURN.
LF	EQU	0AH		; LINEFEED.
BEEP	EQU	7		; BELL CHAR.
CNTLC	EQU	3		; ^C CODE.
RDCONS	EQU	1		; CP/M READ CONSOLE FUNC NO.
DRVA	EQU	0		; DRIVE A UNIT NO.
DRVB	EQU	1		; DRIVE B UNIT NO.

;
;   EXTERNAL REFERENCES:
;
CPMBOT	EQU	0		; REBOOT ENTRY POINT FOR CP/M.
CPMSYS	EQU	5		; ENTRY POINT FOR CP/M SYSTEM 

;
;   ASSEMBLY OPTIONS:
;
SECSIZ	EQU 	128	;DATA BYTES/SECTOR
NOSECS	EQU	26	;SECTORS PER TRACK
NOTRKS	EQU	77	;TRACKS PER DISKETTE SIDE
RETRYS	EQU	5	;NUMBER OF RETRYS FOR ERRORS
PRINTTK	EQU	TRUE	;TRUE PRINTS CURRENT TRACK AT RUN 
;
; SET SKIP TO LOWEST NO. TO GIVE FAST COPY
; SKIP OF 0 GIVES 1 2 3 4 5 ..... SEQUENCE
; SKIP OF 1 GIVES 1 3 5 7 9 ..... SEQUENCE ETC.
; (USE 0 FOR TARBELL CONTROLLER & 5 FOR IMSAI FDC2-2)
;
SKIP	EQU	0	;SECTS TO SKIP DURING READS/WRITES
;
;/////////////////////////////////////////////////////////

	ORG	100H

;
;   PROGRAM ENTRY POINT.
;
START:	LXI	SP,STKTOP	; INITIALIZE STACK POINTER.
	CALL	INIT		; SET UP DISK I/O VECTORS.
	CALL	PRINT		; OUTPUT SIGN ON MSG.
	DB	CR,LF
	DB	'CP/M FULL DISK COPY UTILITY - VER 1.3'
	DB	CR,LF,'$'


MORE:	LXI	SP,STKTOP	; RESET STACK.
	CALL	OPTION		; INPUT OPTION.
	SHLD	TRKNO		; SAVE BEGINNING TRACK ADR.
	CALL	COPY		; COPY THE DISK.
	CALL	PRINT
	DB	CR,LF,'COPY COMPLETE: $'
	LDA	ERRFLG
	ORA	A		; ANY ERRORS ALONG THE WAY?
	JNZ	M0		; YES.
	CALL	PRINT
	DB	'NO $'
M0:	CALL	PRINT
	DB	'ERROR(S) WERE DETECTED$'
	JMP	MORE

;
;   INPUT USER COMMAND.
;
OPTION:	CALL	PRINT
	DB	CR,LF
	DB	'OPTIONS:',CR,LF
	DB	'  "A" = COPY ALL    (TRACKS 1-77)' ,CR,LF
	DB	'  "D" = COPY DATA   (TRACKS 2 - 77)',CR,LF
	DB	'  "S" = COPY SYSTEM (TRACKS 0 AND 1)',CR,LF
	DB	'  "E" = EXIT TO CP/M (INSERT SYSTEM '
	DB	'DISK FIRST)',CR,LF,CR,LF,'PUT SOURCE '
	DB	'IN DRIVE A, DESTINATION IN DRIVE B'
	DB	CR,LF,'AND ENTER SELECTION: $'
	CALL	GETCR		; INPUT SELECTION.
	LXI	H,NOTRKS*256 OR 0
	CPI	'A'		; COPY ALL TRACKS?
	RZ			; YES.
	MVI	H,2
	CPI	'S'		; COPY OPERATING SYSTEM ONLY?
	RZ			; YES.
	LXI	H,NOTRKS*256 OR 2
	CPI	'D'		; COPY FILES ONLY?
	RZ			; YES.
	CPI	'E'		; EXIT TO CP/M?
	JZ	CPMBOT		; YES
	CALL	PRINT		; PRINT ERROR MSG.
	DB	BEEP,'?ILLEGAL SELECTION?$'
	JMP	OPTION		; TRY AGAIN.

;
;   COPY DSKA TO DSKB STARTING AT TRACK GIVEN BY TRKNO.
;
COPY:	XRA	A
	STA	ERRFLG		; CLEAR ERROR FLAG.
CPY0:	LXI	H,ERRCNT	; H/L= PTR TO CMPR RETRY CNTR.
	MVI	M,RETRYS	; INITIALIZE RETRY COUNTER.
CPY1:	DCR	M
	JM	CPY2		; TOO MANY ERRORS.
	PUSH	H		; SAVE ERROR CNTR PTR.
	MVI	A,DRVA
	CALL	SETDSK		; SELECT CURRENT TRK ON DRV A.
	CALL	GETTRK		; READ TRK INTO BUF0.
	CALL	VERIFY		; READ AGAIN TO BUF1 & COMPARE
	POP	H
	JC	CPY1		; COMPARE ERROR, TRY AGAIN.
CPY2:	MVI	M,RETRYS	; INITIALIZE ERROR COUNTER.
CPY3:	DCR	M		; DECREMENT ERROR COUNT.
	JM	CPY4		; TOO MANY ERRORS.
	PUSH	H
	MVI	A,DRVB
	CALL	SETDSK		; SELECT CURRENT TRK ON DRV B.
	CALL	PUTTRK		; WRITE BUF0 TO DRIVE B.
	CALL	VERIFY		; READ BACK TO BUF1 & VERIFY.
	IF PRINTTK
	CALL	PRNTRK		; PRINT TRACK NO TO SCREEN
	ENDIF
	POP	H
	JC	CPY3		; VERIFY ERROR.
CPY4:	LXI	H,TRKNO
	INR	M		; INCREMENT TRACK ADR.
	LDA	MAXTRK
	CMP	M		; DONE?
	JNZ	CPY0		; NOT YET.
	RET

;
;READ THE CURRENTLY SELECTED TRACK ON THE CURRENTLY SELECTED 
;DRIVE INTO BUF1 AND COMPARE THE CONTENTS OF BUF1 WITH BUF0.
;IF THE BUFFERS ARE DIFFERENT, THE ROUTINE CHECKS 'ERRCNT'.
;IF THE VALUE AT 'ERRCNT' IS NON-ZERO, THE ROUTINE RETURNS
;WITH THE CARRY BIT SET.  IF THE VALUE AT 'ERRCNT' IS 0, THE
;ROUTINE WILL PRINT THE TRACK AND SECTOR ADR OF EACH SECTOR
;WHERE A COMPARE ERROR OCCURRED.  IF NO CMPRE ERROR IS FOUND,
;THE ROUTINE RETURNS WITH THE CARRY BIT RESET.
;
;
VERIFY:	CALL	CCHK		; CHK FOR CONSOLE BRK.
	XRA	A
	STA	BADSEC		; RESET LAST BAD SECTOR ADR.
	LXI	H,BUF1
	PUSH	H
	CALL	READTK		; READ THE TRK.
	POP	H		; RESTORE BUF1 ADR.
	LXI	D,BUF0		; BUF0 ADR.
	LXI	B,NOSECS*SECSIZ	  ; BUFFER SIZE.
VER0:	LDAX	D		; GET BYTE FROM BUF0.
	CMP	M		; SAME?
	JZ	VER1		; YES.
	LDA	ERRCNT		; GET ERROR COUNT.
	ORA	A
	STC			; CY BIT INDICATES ERROR.
	RNZ			; NOT LAST TIME THRU.
	CALL	VERERR		; BLEW LAST CHANCE - RPT ERR
VER1:	INX	H		; BMP PNTRS.
	INX	D		; *
	DCX	B		; DECR CNTR.
	MOV	A,C
	ORA	B		; DONE?
	JNZ	VER0		; NO.
	RET			; BYE.

;
;   READ THE CURRENT TRACK INTO BUF0.
;
GETTRK:	LXI	H,BUF0		; START OF BUF0.

;
;   READ THE CURRENT TRACK INTO THE BUFFER POINTED TO BY H/L.
;
READTK:	MVI	B,1		; START WITH SECTOR 1.
RD0:	PUSH	B		; SAVE SECTOR NO.
	PUSH	H		; SAVE DMA ADR.
	IF	SKIP
	CALL	MAPSEC		; GET PHYSICAL SECTOR NO IN C.
	ENDIF
	IF NOT	SKIP
	MOV	C,B
	ENDIF
	CALL	SETSEC		; SET CURRENT SECTOR.
	POP	B		; RECALL DMA ADR.
	PUSH	B		; SAVE AGAIN.
	CALL	SETDMA		; SET CURRENT DMA ADR.
	MVI	B,RETRYS	; LOAD RETRY CNT.
RD1:	DCR	B
	JM	RD2		; TOO MANY ERRORS.
	PUSH	B
	CALL	READ		; READ CURRENT SECTOR.
	POP	B		; RECALL RETRY CNTR.
	ORA	A		; READ ERROR?
	JNZ	RD1		; INDEED!
RD2:	POP	H
	LXI	D,SECSIZ
	DAD	D		; BMP DMA ADR.
	POP	B
	CNZ	RDERR		; READ ERROR - TOO BAD!
	INR	B		; BMP SECTOR ADR.
	MVI	A,NOSECS+1
	CMP	B		; MORE SECTORS TO GO?
	JNZ	RD0		; YES.
	RET			; DONE.

;
;   WRITE BUF0 CONTENTS INTO CURRENT TRACK.
;
PUTTRK:	LXI	H,BUF0		; START ADR FOR WRITE.
	MVI	B,1		; START AT SECTOR 1.
P0:	PUSH	B		; SAVE SECTOR ADR.
	PUSH	H		; SAVE DMA ADR.
	IF	SKIP
	CALL	MAPSEC		; FORM PHYSICAL SECTOR NO IN C
	ENDIF
	IF NOT	SKIP
	MOV	C,B
	ENDIF
	CALL	SETSEC		; SET SECTOR ADR.
	POP	B		; B/C= CURRENT DMA ADR.
	PUSH	B
	CALL	SETDMA		; SET CURRENT DMA ADR.
	MVI	B,RETRYS	; LOAD RETRY CNTR.
P1:	DCR	B
	JM	P2		; TOO MANY ERRORS.
	PUSH	B		; SAVE RETRY CNTR.
	CALL	WRITE		; WRITE SECTOR.
	POP	B		; RECALL RETRY CNTR.
	ORA	A		; ANY ERRORS THIS TIME?
	JNZ	P1		; OH WELL
P2:	POP	H		; COMPUTE NEW DMA ADR.
	LXI	D,SECSIZ	; *
	DAD	D		; *
	POP	B		; RECALL SECTOR ADR.
	CNZ	WRTERR		; REPORT WRITE ERROR.
	INR	B		; BMP SECTOR ADR
	MVI	A,NOSECS+1	; DONE?
	CMP	B		; *
	JNZ	P0		; NO.
	RET

;
;   SELECT THE DRIVE IN A AND THE TRACK IN 'TRKNO'.
;
SETDSK:	STA	DSKNO		; KEEP TRACK OF CURRENT DRIVE.
	MOV	C,A
	CALL	SELDSK		; SELECT THE DRIVE.
	LDA	TRKNO		; GET CURRENT TRK NO.
	MOV	C,A
	JMP	SETTRK		; SET TRK ADR AND RET.

;
;THE FOLLOWING ROUTINES TEST FOR DISK COPY ERRORS AND REPORT
;ERRORS IF NONE HAVE OCCURRED PREVIOUSLY AT THE SAME SECTOR.
;
VERERR:	PUSH	H
	PUSH	D
	PUSH	B
	LXI	D,-BUF1
	DAD	D		; FIG REL ADR OF ERR IN BUF1
	DAD	H
	INR	H		; H := SECTOR ADR.
	MOV	B,H		; SAVE IN B.
	LXI	H,BADSEC	; H/L= PTR TO LAST BAD SECT 
	MOV	A,B
	CMP	M		; PREVIOUS ERROR HERE?
	MOV	M,A		; UPDATE IN ANY CASE.
	JZ	EREXIT		; YES - DON'T REPORT IT.
	CALL	PRINT
	DB	'COMPARE$'
	JMP	ERRORS		; REPORT ERROR.

RDERR:	PUSH	H
	PUSH	D
	PUSH	B
	CALL	PRINT
	DB	'READ$'
	JMP	ERRORS

WRTERR:	PUSH	H
	PUSH	D
	PUSH	B
	CALL	PRINT
	DB	'WRITE$'
;
ERRORS:	CALL	PRINT
	DB	' ERROR AT:$'
	CALL	PRNTRK		; PRINT TRK ADR.
	CALL	PRNSEC		; PRINT SECTOR ADR.
	CALL	PRNDSK		; PRINT DRIVE NO.
	CALL	PRINT
	DB	CR,LF,'$'
	MVI	A,TRUE
	STA	ERRFLG		; SET ERROR FLAG.
	CALL	CCHK		; CHK FOR CONSOLE BRK (^C).
EREXIT:	POP	B
	POP	D
	POP	H
	RET

;
;   PRINT THE CURRENT DRIVE NO. AT THE CONSOLE.
;
PRNDSK:	CALL	PRINT
	DB	' ON DRIVE $'
	LDA	DSKNO
	ADI	'A'		; FORM A OR B.
	CALL	COUT		; OUTPUT DRIVE NO.
	RET

;
;   PRINT THE ADDRESS OF THE CURRENT TRACK AT THE CONSOLE.
;
PRNTRK:	CALL	PRINT
	DB	CR,LF,'     TRACK $'
	LDA	TRKNO		; FETCH TRK NO.
	JMP	HEXLSB		; OUTPUT THE HEX ADR.

;
;   PRINT THE ADDRESS OF THE CURRENT SECTOR AT THE CONSOLE.
;   ENTER WITH THE SECTOR ADR IN B.
;
PRNSEC:	CALL	PRINT
	DB	' - SECTOR $'
	IF	SKIP
	CALL	MAPSEC		; FORM PHYSICAL SECT ADR IN A
	ENDIF
	IF NOT	SKIP
	MOV	A,B		; GET SECTOR ADR.
	ENDIF
;
;   OUTPUT THE BYTE IN A AS TWO ASCII CHARS FOLLOWED BY 'H'.
;
HEXLSB:	CALL	HEXBYT		; OUTPUT BYTE IN ASCII-HEX.
	MVI	A,'H'
	JMP	COUT		; INDICATE BASE 16 AND EXIT.

;
;OUTPUT THE HEX BYTE IN A AS TWO ASCII CHARS AT THE CONSOLE.
;
HEXBYT:	PUSH	PSW		; SAVE BYTE.
	RRC			; ROTATE UPPER NIBBLE INTO POS
	RRC			; *
	RRC			; *
	RRC			; *
	CALL	HEXNBL		; OUTPUT UPPER NIBBLE.
	POP	PSW		; RECALL LOWER NIBBLE.

;
;   OUTPUT THE LOWER NIBBLE IN A AS ONE ASCII CHAR.
;
HEXNBL:	ANI	0FH
	ADI	90H		; CONVERT TO ASCII.
	DAA			; *
	ACI	40H		; *
	DAA			; *

;
;   OUTPUT THE CHAR IN A TO THE CONSOLE DEVICE.
;
COUT:	PUSH	H
	PUSH	D
	PUSH	B
	MOV	C,A		; C := CHAR TO BE TYPED.
	CALL	CONOUT		; DO IT.
	POP	B
	POP	D
	POP	H
	RET			; DONE.

;
;PRINT THE STRING FOLLOWING CALL AT THE CONSOLE UNTIL '$'
;
PRINT:	XTHL			; H/L POINTS TO CHAR.
	MOV	A,M		; PICK UP.
	INX	H		; MOVE PAST CHAR.
	XTHL
	CPI	'$'		; STOPPER?
	RZ			; YES
	CALL	COUT		; PRINT CHAR.
	JMP	PRINT		; CONTINUE.

;
;INPUT CHRS FROM THE CONSOLE UNTIL A CARRIAGE RETURN .
;THE CHR PRECEEDING THE CARRIAGE RETURN IS RETURNED IN REG A.
;
GETCR:	XRA	A
GC0:	MOV	C,A		; SAVE LAST CHARACTER.
	PUSH	B
	MVI	C,RDCONS
	CALL	CPMSYS		; GET NEXT CHARACTER.
	ANI	5FH		; CONVERT LC ALFA TO UC ALFA.
	POP	B
	CPI	CR		; DONE?
	JNZ	GC0		; NOT YET.
	MVI	A,LF
	CALL	COUT		; ISSUE A LINEFEED.
	MOV	A,C		; A := LAST CHAR BEFORE CR.
	RET

;
;CHECK FOR CONSOLE CONTROL C BREAK.
;
CCHK:	CALL	CONSTS
	ORA	A
	RZ
	CALL	CONIN
	CPI	CNTLC
	RNZ
	CALL	PRINT
	DB	'^C',CR,LF
	DB	'ABORTED$'
	JMP	MORE

	IF	SKIP
;
;   MAP FROM LOGICAL INTO PHYSICAL SECTOR ADR
;   ENTER WITH LOGICAL SECTOR ADR IN REG B
;   RETURNS PHYSICAL SECTOR ADR IN REGS A AND C
;
MAPSEC:	MVI	C,SKIP		; NUMBER OF SECTORS TO SKIP.
	DCR	B		; DECR LOGICAL SECTOR ADR.
	MOV	A,B
MAP0:	ADD	B
	CPI	NOSECS		; OUT OF RANGE?
	JC	MAP1		; NO.
	SUI	NOSECS-1	; ADJUST.
MAP1:	DCR	C		; COUNT SKIPS.
	JNZ	MAP0		; NOT DONE.
	INR	B		; RESTORE LOGICAL SECTOR ADR.
	INR	A		; A := PHYSICAL SECTOR ADR.
	MOV	C,A		; NEEDED IN REG C MOST OFTEN.
	RET
	ENDIF
;
;THE FOLLOWING ROUTINE INITIALIZES THE SYSTEM BY SETTING THE
;CORRECT PAGE ADDRESS OF THE CBIOS TRANSFER VECTORS IN XFRTBL.
;THIS IS DONE BY INSPECTING THE REBOOT VECTOR WHICH POINTS TO
;THE CBIOS TRANSFER TABLE AND USING ITS PAGE ADDR TO CORRECT
;THE PAGE ADDRESSES OF THE VECTORS IN XFRTBL.
;
INIT:	LDA	CPMBOT+2	; FETCH PAGE ADR OF CBIOS TBL.
	LXI	H,XFRTBL+2	; ADR OF 1ST VECT TO BE CORR
	MVI	B,NOVECS	; NUMBER OF VECTORS.
INIT0:	MOV	M,A		; FILL IN CORRECT PAGE ADR.
	INX	H		; POINT TO NXT ENTRY.
	INX	H		; *
	INX	H		; *
	DCR	B		; DECR CNT.
	JNZ	INIT0		; MORE TO GO.
	RET

;
;   CBIOS DRIVER TRANSFER TABLE.
;
XFRTBL	EQU	$
;
CONSTS:	DB	JMP,06H,0
CONIN:	DB	JMP,09H,0
CONOUT:	DB	JMP,0CH,0
SELDSK:	DB	JMP,1BH,0
SETTRK:	DB	JMP,1EH,0
SETSEC:	DB	JMP,21H,0
SETDMA:	DB	JMP,24H,0
READ:	DB	JMP,27H,0
WRITE:	DB	JMP,2AH,0
NOVECS	EQU	($-XFRTBL)/3

;
;   PROGRAM WORKSPACE.
;
	DS	64		; STACK AREA.
STKTOP	EQU	$
TRKNO:	DS	1		;  CURRENT TRK NO.
MAXTRK:	DS	1		;  LAST TRK TO BE COPIED+1.
BADSEC:	DS	1		;  LAST BAD SECTOR.
ERRFLG:	DS	1		; COPY ERROR FLAG.
ERRCNT:	DS	1		;  COMPARE RETRY CNTR.
DSKNO:	DS	1		;  CURRENT DRIVE NO.
BUF0:	DS	NOSECS*SECSIZ	; FIRST TRACK BUFFER.
BUF1:	DS	NOSECS*SECSIZ	; SECOND TRACK BUFFER.
;
;
	END	START
