;
;512 byte deblock copy program
;written by g.w.mulchin 6-3-81
;tarbell electronics
;copies single or double sided
;last update - 02-11-82
;
;system equates
;
TRUE	EQU	0FFFFH
FALSE	EQU	NOT TRUE
;
DUAL	EQU	FALSE		;true if using persci
;
DISK	EQU	0F8H		;disk controller base addres
DMAP	EQU	0E0H		;dma controller base address
;
ADR0	EQU	DMAP+0		;dma adrress registers
WCT0	EQU	DMAP+1		;dma word count registers
CMND	EQU	DMAP+8		;dma command register
;
DCOM	EQU	DISK+0		;disk command port
DSTAT	EQU	DCOM		;disk status port
TRACK	EQU	DCOM+1		;disk track port
SECTP	EQU	DCOM+2		;disk sector port
DDATA	EQU	DCOM+3		;disk data port
WAIT	EQU	DCOM+4		;disk wait port
DCONT	EQU	WAIT		;disk control port
DMACHK	EQU	DCOM+5		;intrq check port
RTCNT	EQU	10		;retry count number
HLAB	EQU	008H		;head load at beginning of seek
STPRAT	EQU	001H		;0=3ms,1=6ms,2=10ms,3=15ms
VERIFY	EQU	004H		;verify bit
RESTORE	EQU	STPRAT		;restore command
SEEK	EQU	010H		;seek command
B128	EQU	128		;single density sector size
B512	EQU	512		;double density sector size
;
WBOOT	EQU	0		;warm boot entry
BDOS	EQU	5		;cpm entry point
PRINTF	EQU	9		;print buffer function
BUFIN	EQU	10		;console buffer input function
TERM	EQU	'$'		;function 9 terminator
CR	EQU	0DH		;carriage return
LF	EQU	0AH		;linefeed
BEL	EQU	07H		;bel code
;
;start of program
;
	ORG	100H		;runs in tpa area
;
START:	JMP	BEGIN		;jump around subroutines
;
;--------------------------------------------------------------
;			subroutine area
;--------------------------------------------------------------
;
;messages
;
OPENM:	DB	CR,LF,'Tarbell Deblock Copy Program ver 1.4'
	DB	' of 02-11-82',CR,LF,TERM
MENU:	DB	CR,LF,'Table of Functions'
	DB	CR,LF,'------------------'
	DB	CR,LF
	DB	CR,LF,'  1 - Copy System'
	DB	CR,LF,'  2 - Copy Data'
	DB	CR,LF,'  3 - Copy All'
	DB	CR,LF,'  4 - Select Drives'
	DB	' (or '
SRCD:	DB	'A: copied to '
DSTD:	DB	'B:)'
	DB	CR,LF,'  5 - Exit to System'
	DB	' (place system disk in first)'
	DB	CR,LF,CR,LF,'Function (1,2,3,4,5)? ',TERM
CRLF:	DB	CR,LF,TERM
SIZERR:	DB	CR,LF,BEL,'TPA space not large enough!'
	DB	CR,LF,BEL,'--------------------------'
	DB	CR,LF,TERM
MEMTST:	DB	CR,LF,'Testing buffer space. '
	DB	CR,LF,'---------------------',CR,LF,TERM
MEMERR:	DB	CR,LF,BEL,'512copy memory buffer error,'
	DB	' check for bad memory between '
MEMLOW:	DB	'0000 hex and '
MEMHGH:	DB	'0000 hex.',TERM
SRCCHG:	DB	CR,LF,'Source Drive? ',TERM
DSTCHG:	DB	CR,LF,'Destination Drive? ',TERM
IDERR:	DB	CR,LF,BEL,'Disk I.D. do not match each other'
	DB	' or they are not deblocked disks.'
	DB	CR,LF,'---------------------------------'
	DB	'---------------------------------',CR,LF,TERM
TRK0ER:	DB	CR,LF,BEL,'Track 0 read E R R O R on drive '
TK0DRV:	DB	' :',CR,LF,TERM
HMERR:	DB	CR,LF,BEL,'Home error on drive '
HMDRV:	DB	' :',CR,LF,TERM
CPYER:	DB	CR,LF,BEL,'Error on track '
CPYTK:	DB	'  , sector '
CPYSEC:	DB	'  , of drive '
CPYDRV:	DB	' :',CR,LF,TERM
COMERR:	DB	CR,LF,BEL,'Error in data transfered!'
	DB	'  Aborting copy operation.',CR,LF,TERM
COMFUN:	DB	CR,LF,BEL,'Function Complete.',CR,LF,TERM
TRKCPY:	DB	CR,'Copying Track '
TKCPY:	DB	'    ',TERM
CPYTYP:	DB	CR,LF,'Copying a ',TERM
CPYSIN:	DB	'Single Sided Disk',CR,LF,TERM
CPYDUB:	DB	'Double Sided Disk',CR,LF,TERM
MEMUSE:	DB	CR,LF,'Buffer Start: '
MEMUS1:	DB	'0000 hex.'
	DB	CR,LF,'Buffer End:   '
MEMUS2:	DB	'0000 hex.',CR,LF,TERM
SEKERR:	DB	CR,LF,BEL,'Seek Error on Drive '
SEKDRV:	DB	' :',TERM
;
;internal skew table for double density - skew = 4
;
TABLE:	DB	1,5,9,13,2,6,10,14,3,7,11,15,4,8,12,16
;
;seek error report routine
;
ERSEEK:	MOV	A,B		;get drive number
	ADI	'A'		;ascii adjust it
	STA	SEKDRV		;save for message
	LXI	D,SEKERR	;point to message
	CALL	PRINT		;print it
	MVI	A,1		;error code
	RET
;
;initialization subroutine
;entry:			none
;exit:			constants in program zeroed
;registers used:	reg. a,h,l
;
INIT:	XRA	A		;get a zero
	STA	CPYTK		;clear track
	STA	CPYSEC		;clear copy sector
	MOV	L,A		;clear reg l
	MOV	H,A		;clear reg h
	SHLD	TRKTAB		;clear track table
	SHLD	TRKTAB+2	;clear track table+2
	RET
;
;hex to ascii conversion
;entry:			h,l = memory address
;			d,e -> memory storage
;exit:			none
;registers used:	all
;
HEXOUT:	MOV	A,H		;get high byte
	CALL	HXOUT		;print high byte address
	MOV	A,L		;get low byte
HXOUT:	PUSH	PSW		;save it
	RRC			;shift
	RRC			;right
	RRC			;4
	RRC			;times
	CALL	OUTCHR		;save the ascii byte
	POP	PSW		;restore for low nibble
OUTCHR:	ANI	0FH		;strip upper nibble
	ADI	90H		;add offset
	DAA			;decimal adjust
	ACI	40H		;add offset
	DAA			;decimal adjust
	STAX	D		;save the char
	INX	D		;bump for next
	RET
;
;binary to decimal conversion
;entry:			reg. a = value to convert
;			reg. h,l -> memory for storage
;exit:			none
;registers used:	reg. a,b,h,l
;
BINDEC:	MVI	B,10		;radix value
	CALL	BID1		;convert the value
	ADI	'0'		;add ascii 0
	MOV	M,A		;place into memory
	RET
;
BID1:	MVI	M,'0'-1
BID2:	INR	M
	SUB	B
	JNC	BID2
	ADD	B
	INX	H
	RET
;
;read disk id on track 0 of each disk
;entry:			none
;exit:			condition of disk id
;			e.g. - do they match?
;			drive physical code saved
;			type disk printed (sing or doub sided)
;registers used:	all
;
IDREAD:	LDA	SRCDRV		;get the source disk
	CALL	IDRD		;read the disk id byte
	ORA	A		;any errors
	RNZ			;return if error found
	MOV	A,B		;get the id byte
	STA	SRCID		;save the byte
	LDA	CLATCH		;get src clatch value
	STA	SLATCH		;save for copy operation
	LDA	DSTDRV		;get the destination disk
	CALL	IDRD		;read its id byte
	ORA	A		;any errors
	RNZ			;return if yes
	LDA	CLATCH		;get dst clatch value
	STA	DLATCH		;save for copy operation
	LDA	SRCID		;get source id byte
	CMP	B		;do they match?
	JZ	IDMTCH		;jump if so
NOMTCH:	LXI	D,IDERR		;point to id error msg
	CALL	PRINT		;print and return to main line
	MVI	A,1		;we had an error
	RET
;
IDMTCH:	CPI	0DEH		;single sided 512 x 16 ?
	MVI	A,0		;0 = single sided
	JZ	IDMTC1		;jump if so
	MOV	A,B		;recover id byte
	CPI	0DCH		;doub sided 512 x 16?
	MVI	A,1		;1 = doub sided
	JZ	IDMTC1		;jump if double sided
	JMP	NOMTCH		;error in id
IDMTC1:STA	SIDED		;save the code
	LXI	D,CPYTYP	;point to the type of disk
	CALL	PRINT		;print it
	LDA	SIDED		;check which type
	ORA	A		;single sided?
	LXI	D,CPYSIN	;single sided message
	JZ	IDMTYP		;print its single sided
	LXI	D,CPYDUB	;double sided message
IDMTYP:	CALL	PRINT		;print the message
	XRA	A		;no errors found
	RET
;
IDRD:	PUSH	PSW		;save drive select code
	MOV	C,A		;pass it in reg c
	CALL	SELDSK		;select the disk
	CALL	SIDE0		;read side 0
	CALL	SDEN		;set it for single density
	STA	CLATCH		;save this drive phisical code
	CALL	DSKSEL		;physically select the drive
	CALL	HOME		;home the drive
	ANI	91H		;check for errors
	JNZ	HOMER		;jump if we have an error
	LXI	B,RBUFF		;use buffer address
	CALL	SETDMA		;set the memory address
	MVI	C,1		;sector 1
	CALL	SETSEC		;save the sector
	CALL	READSD		;read track 0 sector 1
	ORA	A		;read error?
	JNZ	RDTKER		;jump if so
	POP	PSW		;clean up stack
	LDA	RBUFF+7EH	;get id byte
	MOV	B,A		;save id in reg b
	XRA	A		;return no error code
	RET			;return id byte
;
HOMER:	POP	PSW		;recover drive code
	ADI	'A'		;adjust back to ascii
	STA	HMDRV		;show the drive in msg
	LXI	D,HMERR		;point to home error
	CALL	PRINT		;print the message
	MVI	A,1		;error found
	RET
;
RDTKER:	POP	PSW		;recover drive code
	ADI	'A'		;ascii adjust
	STA	TK0DRV		;show drive in error
	LXI	D,TRK0ER	;point to error msg
	CALL	PRINT		;print and ret to main line
	MVI	A,1		;error found
	RET
;
;select source/destination drives
;entry:			none
;exit:			default drives changed
;			same drive not allowed
;registers used:	all
;
DRIVE:	LXI	D,SRCCHG	;point to source change
	CALL	PRINT		;print the message
	CALL	INPUT		;get the drive
	LDA	TEMP		;get the char
	CPI	'E'		;check for out of range
	JNC	SAMDRV		;return if to big
	STA	SRCD		;modify message
	SUI	'A'		;make physical drive code
	STA	SRCDRV		;save it
	LXI	D,DSTCHG	;point to dst. change msg
	CALL	PRINT		;print the message
	CALL	INPUT		;get a keyboard char.
	CPI	'E'		;check for out of range
	JNC	SAMDRV		;return if to big
	LDA	TEMP		;get destination drive
	STA	DSTD		;modify drive msg
	SUI	'A'		;make physical drive
	STA	DSTDRV		;save the code
SAMDRV:	LDA	SRCDRV		;check for using same drive
	MOV	B,A		;save in reg b
	LDA	DSTDRV		; for the copy operation
	CMP	B		;are they the same?
	JZ	DRIVE		;cant have this!
	RET
;
;select disk routine:
;entry:			reg c = drive to select
;exit:			drive hardware code (clatch)
;registers used:	reg. a
;
SELDSK:	MOV	A,C		;get drive to select
	ADD	A		;shift
	ADD	A		;four
	ADD	A		;places
	ADD	A		;left
	RET			;return hardware select code
;
;side select subroutines
;entry:			hardware select code (reg. a)
;exit:			side selected
;registers used:	reg. a
;
SIDE0:	ANI	10111000B	;strip side bit
	RET
;
SIDE1:	ORI	01000000B	;set side 1 bit on
	RET
;
;density change subroutines
;entry:			hardware select code
;exit:			density change code
;registers used:	reg. a
;
SDEN:	ANI	11110111B	;strip to single density
	RET
;
DDEN:	ORI	00001000B	;set to double density
	RET
;
;keyboard input routine
;entry:			none
;exit:			temp = key pushed
;registers used:	all
;
INPUT:	MVI	A,3		;buffer length
	LXI	H,0		;clear h,l
	SHLD	KEYBUF+1	;clear keyboard buffer
	STA	KEYBUF		;set buffer length
	LXI	D,KEYBUF	;point to buffer
	MVI	C,BUFIN		;function 10
	CALL	BDOS		;get key from bdos
	LDA	KEYBUF+2	;get key pushed
	CPI	'a'		;check the case of char.
	JC	NOCONV		;jump if upper case
	CPI	'z'+1
	JNC	NOCONV		;jump if upper
	ANI	5FH		;strip to upper case if not.
NOCONV:	STA	TEMP		;save the character
	RET			;return to caller
;
;print buffer routine
;entry:			d,e -> message to print
;exit:			message printted by bdos
;registers used:	all saved and restored
;
PRINT:	PUSH	PSW		;save the world
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,PRINTF	;function 9 code
	CALL	BDOS		;use bdos to print
	POP	H
	POP	D
	POP	B
	POP	PSW		;restore the world
	RET
;
;select drive hardware subroutine and delay
;on a new drive select
;entry:			reg. a = physical drive select
;exit:			drive physically selected
;registers used:	reg. a
;
DSKSEL:	OUT	DCONT		;select it now
	PUSH	PSW		;save accum and flags
	IN	TRACK		;get present track value
	OUT	DDATA		;place into data register
	MVI	A,SEEK+STPRAT	;get seek without headload
	OUT	DCOM		;send it
	CALL	SLOOP		;wait till done
	POP	PSW		;restore accum and flags
	RET			;return to caller
;
;head loaded check subroutine
;entry:			none
;exit:			reg. a = load or un-loaded byte
;registers used:	reg. a
;
HDLD:	IN	DSTAT		;check disk status
	ANI	00100000B	;look at head load bit
	MVI	A,00000100B	;if not loaded, then set it
	RZ			; and return if not loaded
	MVI	A,00000000B	;else,
	RET			;head already loaded
;
;status loop subroutine
;entry:			none
;exit:			disk status register
;registers used:	reg. a
;
SLOOP:	IN	DMACHK		;check intrq bit (bit 7)
	RLC			;rotate into carry flag
	JC	SLOOP		;loop till not busy
	XRA	A		;clear dma chip
	OUT	CMND		;stop any possible dma req.
	IN	DSTAT		;get disk status
	RET			;and return to caller
;
;home subroutine
;entry:			none
;exit:			drive selected is homed
;registers used:	reg. a
;
HOME:	MVI	A,RESTORE	;home using restore command
	OUT	DCOM		;send the command
	JMP	SLOOP		;wait till done and ret. status
;
;set sector subroutine
;entry:			reg. c = sector
;exit:			none
;registers used:	reg. a
;
SETSEC:	PUSH	PSW		;save accum and flags
	MOV	A,C		;get the sector
	STA	SECTOR		;save the sector
	POP	PSW		;restore accum and flags
	RET
;
;set dma address
;entry:			reg b,c = dma address
;exit:			none
;registers used:	h,l
;
SETDMA:	MOV	H,B		;get high byte
	MOV	L,C		;get low byte
	SHLD	DMAADR		;save the dma address
	RET
;
;seek track routine
;entry:			reg. b = drive #
;			reg. c = track to seek
;exit:			reg. a = 0 if ok, 1 if error
;registers used:	all
;
SEEKT:
	LDA	DISKNO		;get last drive

	IF	DUAL
	ANI	0FEH		;isolate low bit
	ENDIF

	MOV	E,A		;put into reg.e
	MVI	D,0		;clear reg d
	LXI	H,TRKTAB	;point to track table
	PUSH	H		;save track table pointer
	DAD	D		;get index
	IN	TRACK		;get present track position
	MOV	M,A		;save it for later
	MOV	A,B		;get new disk number

	IF	DUAL
	ANI	0FEH		;isolate low bit
	ENDIF

	POP	H		;recover track table pointer
	MOV	E,A		;get new disk number
	DAD	D		;new table
	MOV	A,M		;get new track number
	OUT	TRACK		;send to floppy chip
	MOV	A,B		;get last disk in use
	STA	DISKNO		;save it
	MVI	A,RTCNT		;get max retrys to do
SEKRTY:	STA	RETRY		;save it
	IN	TRACK		;get the present track
	CMP	C		;do they match
	MVI	A,0		;condition code = good
	RZ			;return if good seek
	MOV	A,C		;else, get track to seek
	OUT	DDATA		;send it to disk chip
	MVI	A,SEEK+VERIFY+STPRAT+HLAB
	OUT	DCOM		;send seek command
	CALL	SLOOP		;loop till done
	ANI	91H		;check for errors
	MVI	A,0		;condition code = good
	RZ			;return if none
	LDA	RETRY		;error detected, get retry
	DCR	A		;decrease it
	JNZ	SEKRTY		;try again
	INR	A		;error return code if failed
	RET
;
;read single density sector subroutine
;entry:			none
;exit:			reg. a = error condition
;registers used:	all
;
READSD:	MVI	A,RTCNT		;get max retry count
RDSDRTY:STA	RETRY		;set retry counter
	LXI	B,80D0H		;floppy read , force intrp
	LXI	D,4080H		;dma read, count byte
	CALL	DMARW		;use common routine
	ANI	9DH		;check for errors
	JZ	RDSDOK		;jump if ok
	LDA	RETRY		;get retry counter
	DCR	A		;decrease it
	JNZ	RDSDRTY		;retry if not = 0
	MVI	A,1		;return error condition
	RET			;1 = error in reading of sector
;
RDSDOK:	SHLD	DMAADR		;save pointer for next read
	MVI	A,0		;return no error code
	RET
;
;read double density sector routine
;entry:			none
;exit:			reg. a = error condition
;registers used:	all
;
READDD:	MVI	A,RTCNT		;get max retry count
RDDDRTY:STA	RETRY		;save retry count
	LXI	B,80D0H		;floppy read , force intrp
	LXI	D,4200H		;dma read, count byte (512 byte)
	CALL	DMARW		;use common routine
	ANI	9DH		;check for errors
	JZ	RDDDOK		;jump if ok
	LDA	RETRY		;get retry counter
	DCR	A		;decrease it
	JNZ	RDDDRTY		;retry if not = 0
	MVI	A,1		;return error condition
	RET			;1 = error in reading of sector
;
RDDDOK:	SHLD	DMAADR		;save pointer for next read
	MVI	A,0		;return no error code
	RET
;
;write single density sector subroutine
;entry:			none
;exit:			reg. a = error condition
;registers used:	all
;
WRITSD:	MVI	A,RTCNT		;get max retry count
WTSDRTY:STA	RETRY		;save retry count
	LXI	B,0A0D0H	;floppy write , force intrp
	LXI	D,8080H		;dma write, count byte
	CALL	DMARW		;use common routine
	ANI	0FDH		;check for errors
	JZ	WTSDOK		;jump if ok
	LDA	RETRY		;get retry counter
	DCR	A		;decrease it
	JNZ	WTSDRTY		;retry if not = 0
	MVI	A,1		;return error condition
	RET			;1 = error in writing of sector
;
WTSDOK:	SHLD	DMAADR		;save pointer for next write
	MVI	A,0		;return no error code
	RET
;
;write double density sector routine
;entry:			none
;exit:			reg. a = error condition
;registers used:	all
;
WRITDD:	MVI	A,RTCNT		;get max retry count
WTDDRTY:STA	RETRY		;save retry count
	LXI	B,0A0D0H	;floppy write , force intrp
	LXI	D,8200H		;dma write, count byte (512 byte)
	CALL	DMARW		;use common routine
	ANI	0FDH		;check for errors
	JZ	WTDDOK		;jump if ok
	LDA	RETRY		;get retry counter
	DCR	A		;decrease it
	JNZ	WTDDRTY		;retry if not = 0
	MVI	A,1		;return error condition
	RET			;1 = error in writing of sector
;
WTDDOK:	SHLD	DMAADR		;save pointer for next write
	MVI	A,0		;return no error code
	RET
;
;dma read/write subroutine
;entry:			reg.b = floppy read/write command
;			reg.c = floppy force intrp command
;			reg.d = dma read/write cmnd + high byte
;			reg.e = dma low byte count
;exit:			reg.h,l = dma address + byte count
;			reg.a = disk status byte
;registers used:	all
;
DMARW:	STA	RETRY		;save retry counter
	XRA	A		;get a zero
	OUT	CMND		;clear dma chip
	LDA	SECTOR		;get sector to do
	OUT	SECTP		;send to floppy chip
	LHLD	DMAADR		;get the memory address to use
	MOV	A,C		;get force intrp command
	OUT	DCOM		;send to floppy
	DCX	D		;byte count - 1
	MOV	A,E		;get low byte
	OUT	WCT0		;send to word count low byte
	MOV	A,D		;get dma read/write code
	OUT	WCT0		;send to word count high byte
	INX	D		;restore d,e
	MOV	A,L		;get low memory address
	OUT	ADR0		;send to dma addr low byte reg
	MOV	A,H		;get high memory address
	OUT	ADR0		;send to dma addr high byte reg
	MVI	A,41H		;request channel 0 of dma.
	OUT	CMND		;send to dma command register
	CALL	HDLD		;check for head loaded
	ORA	B		;add in read/write command
	OUT	DCOM		;do the read/write operation
	MOV	A,D		;get dma command byte
	ANI	3FH		;strip command bits
	MOV	D,A		;restore for add of address
	DAD	D		;h,l = h,l + d,e
	JMP	SLOOP		;check operation and return
;
;check tpa size subroutine
;entry:			none
;exit:			0 = tpa large enough, 1 = tpa to small
;registers used:	all
;
SIZE:	LXI	D,-(RBUFF)	;point to start of buffers
	LHLD	BDOS+1		;get top of cpm memory
	DCX	H		;decrease by 1
	DAD	D		;add clear space needed
	XCHG			;place into d,e
	LXI	H,-(B512*16*2)	;need 2 tracks worth of memory
	XRA	A		;clear carry flag
	DAD	D		;compute the size
	MVI	A,0		;condition code
	RC			;return if tpa large enough
	INR	A		;else,
	RET			;return error, tpa to small
;
;check buffer memory subroutine
;entry:			none
;exit:			0 = ok, 1 = memory error
;registers used:	all
;
CHECK:	LHLD	BDOS+1		;get top of memory
	DCX	H		;decrease by 1
	LXI	D,-(RBUFF)	;start of buffers
	DAD	D		;get the length of buffers
	MOV	B,H		;get high byte
	MOV	C,L		;length in b,c
	LXI	H,RBUFF		;point to start of buffers
	LXI	D,0FFH		;reg d = 0, reg e = ff
CHKLOP:	MOV	A,E		;put in a ff
	MOV	M,A		;put into memory
	MOV	A,M		;recover it
	CMP	E		;match?
	MVI	A,1		;error code if not
	RNZ			;return if not
	MOV	A,D		;get a zero
	MOV	M,A		;put into memory
	MOV	A,M		;recover it
	CMP	D		;match?
	MVI	A,1		;error code if not
	RNZ			;return if not
	INX	H		;bump pointer
	DCX	B		;decrease count
	MOV	A,C		;check for end count=0
	ORA	B		;end yet?
	JNZ	CHKLOP		;loop till done with check
	XRA	A		;return no error condition
	RET
;
;do requested function
;
;entry:			none
;exit:			routine executed
;registers used:	reg. a,d,e,h,l
;
DOIT:	LDA	TEMP		;get key pushed
	SUI	'1'		;adjust it
	CPI	5		;check range
	RNC			;return if out of range
	ADD	A		;double it
	MVI	D,0		;index setup
	MOV	E,A		; value
	LXI	H,FUNTAB	;point to function table
	DAD	D		;get the routine
	MOV	E,M		;get low byte
	INX	H		;bump pointer
	MOV	D,M		;get high byte
	XCHG			;flip
	PCHL			;go to routine
;
;exit to system
;entry:			none
;exit:			none
;registers used:	none
;
EXIT:	JMP	WBOOT		;go to cpm
;
;function table
;
FUNTAB:	DW	SYSTEM		;doing system
	DW	DATA		;doing data
	DW	ALL		;doing all
	DW	DRIVE		;doing drive change
	DW	EXIT		;done with copy
;
;buffer compare subroutine
;entry:			b,c = length to read
;exit:			error condition on compare
;			or condition code if ok
;registers used:	all
;
BUFCOM:	LXI	H,RBUFF		;point to primary buffer
	LXI	D,RBUFF+(512*16);compare buffer
BUFLOP:	LDAX	D		;get a byte
	CMP	M		;match original?
	JNZ	COMER		;jump if not
	INX	H		;bump
	INX	D		;pointers
	DCX	B		;decrease count
	MOV	A,C		;check
	ORA	B		;end
	JNZ	BUFLOP		;loop if not done
	XRA	A		;clear accum
	RET			;return no errors
;
COMER:	LXI	D,COMERR	;point to compare error msg
	CALL	PRINT		;print the message
	MVI	A,1		;error code
	RET
;
;track read routine - single density
;entry:			reg. h,l -> sector to start with
;			reg. d = # of sectors to do
;exit:			buffer filled for 1 track of data
;registers used:	all
;
RDSDLP:	MOV	C,M		;get the first sector
RDSDL1:	PUSH	B		;save trk count and # of sects
	PUSH	D		;save sector to do
	CALL	SETSEC		;set the sector
	CALL	READSD		;read a sector
	POP	D		;restore sector number
	POP	B		;restore trk count & # of sects
	ORA	A		;any errors
	JNZ	CPYERR		;jump if so
	INR	C		;bump sector number
	DCR	D		;decrease the number of sectors
	JNZ	RDSDL1		;loop till all track is read
	RET			;track has been read
;
CPYERR:	MOV	A,C		;get the sector were at
	STA	SECTOR		;save it for later
	LDA	BEGTRK		;what track are we on now
	LXI	H,CPYTK		;point to track storage
	CALL	BINDEC		;convert to decimal
	LDA	SECTOR		;get last sector
	LXI	H,CPYSEC	;point to storage area
	CALL	BINDEC		;convert to decimal
	LDA	DISKNO		;get current error drive
	ADI	'A'		;convert to ascii
	STA	CPYDRV		;save for mesage
	LXI	D,CPYER		;point to error msg
	CALL	PRINT		;print and return
	MVI	A,1		;error code
	RET
;
;track write routine - single density
;entry:			reg. h,l -> sector to use
;			reg. d = # of sectors to do
;exit:			buffer written to disk
;registers used:	all
;
WTSDLP:	MOV	C,M		;get starting sector
WTSDL1:	PUSH	B		;save trk count and # of sects
	PUSH	D		;save sector to do
	CALL	SETSEC		;set the sector
	CALL	WRITSD		;write a sector
	POP	D
	POP	B
	ORA	A		;any errors
	JNZ	CPYERR		;jump if so
	INR	C		;bump sector number
	DCR	D		;decrease the number of sectors
	JNZ	WTSDL1		;loop till all track is read
	RET			;track has been written
;
;track read routine - double density
;entry:			reg. h,l -> sector to use
;			reg. d = # of sectors to do
;exit:			buffer filled for 1 track of data
;registers used:	all
;
RDDDLP:	PUSH	B		;save trk count and # of sects
	PUSH	D		;save sector to do
	PUSH	H		;save sector pointer
	MOV	C,M		;get the sector
	CALL	SETSEC		;set the sector
	CALL	READDD		;read a sector
	POP	H		;restore sector pointer
	POP	D
	POP	B
	ORA	A		;any errors
	JNZ	CPYERR		;jump if so
	INX	H		;bump sector pointer to next
	DCR	D		;decrease the number of sectors
	JNZ	RDDDLP		;loop till all track is read
	RET			;track has been read
;
;track write routine - double density
;entry:			reg. h,l -> sector to use
;			reg. d = # of sectors to do
;exit:			buffer written to disk
;registers used:	all
;
WTDDLP:	PUSH	B		;save trk count and # of sects
	PUSH	D		;save sector to do
	PUSH	H		;save sector pointer
	MOV	C,M		;get the sector
	CALL	SETSEC		;set the sector
	CALL	WRITDD		;write a sector
	POP	H		;restore sector pointer
	POP	D
	POP	B
	ORA	A		;any errors
	JNZ	CPYERR		;jump if so
	INX	H		;point to next sector
	DCR	D		;decrease the number of sectors
	JNZ	WTDDLP		;loop till all track is read
	RET			;track has been written
;
;system copy operation
;entry:			none
;exit:			system copied to dest disk
;registers used:	all
;
SYSTEM:	CALL	IDREAD		;read src and dst drives
	ORA	A		;any errors?
	RNZ			;return if so
	MVI	A,2		;ending track+1
	STA	ENDTRK		;save it
	MVI	A,0		;start track
	STA	BEGTRK		;save it
	STA	OPFLAG		;save doing system
	JMP	CPYTRK		;copy tracks and verify
;
;copy data operation
;entry:			none
;exit:			data files copied
;registers used:	all
;
DATA:	CALL	IDREAD		;check disk id
	ORA	A		;any errors?
	RNZ			;return if yes
	LDA	SIDED		;doing double sided?
	ORA	A
	JZ	DATA1
;
;double sided copy of data tracks
;
	MVI	A,76		;end track
	STA	ENDTRK
	MVI	A,0		;set for track -1
	STA	BEGTRK		;save it
	INR	A		;not doing system only
	STA	OPFLAG		;operation flag
	JMP	DREPEAT		;copy tracks and verify
;
;single sided copy of data tracks
;
DATA1:	MVI	A,75		;end track-1
	STA	ENDTRK
	MVI	A,1		;start track
	STA	BEGTRK
	STA	OPFLAG		;operation flag
	JMP	DREPEAT		;copy tracks and verify
;
;copy all operation
;entry:			none
;exit:			all tracks copied
;registers used:	all
;
ALL:	CALL	IDREAD		;check disk id
	ORA	A		;any errors
	RNZ
	MVI	A,77		;ending track+1
	STA	ENDTRK
	STA	OPFLAG		;save operation flag
	MVI	A,0		;starting track
	STA	BEGTRK
CPYTRK:	ORA	A		;doing track 0?
	JNZ	SSIDED		;no, doing > track 0
;
;copying track 0 - single density
;
	LDA	SLATCH		;get source drive latch code
	CALL	SIDE0		;select side 0
	CALL	SDEN		;switch to single density
	STA	SLATCH		;save it
	CALL	DSKSEL		;select it
	LDA	BEGTRK		;get source begin track
	PUSH	PSW
	LXI	H,TKCPY
	CALL	BINDEC		;convert to decimal
	LXI	D,TRKCPY
	CALL	PRINT
	POP	PSW
	MOV	C,A		;pass to seek routine
	LDA	SRCDRV		;get source drive
	MOV	B,A		;pass to seek routine
	CALL	SEEKT		;seek to the track
	ORA	A
	JNZ	ERSEEK
	LXI	B,RBUFF		;point to read buffer
	CALL	SETDMA		;set the memory address
	MVI	D,26		;this = the number of sect/trk
	LXI	H,TABLE		;point to sector table
	CALL	RDSDLP		;read 1 tracks worth of data
	ORA	A		;any errors?
	RNZ			;return if there are.
	LDA	DLATCH		;get destination latch
	CALL	SIDE0		;switch to side 0
	CALL	SDEN		;switch to single density
	STA	DLATCH		;save it
	CALL	DSKSEL		;select it
	LDA	BEGTRK		;get dest begin track
	MOV	C,A		;pass to seek routine
	LDA	DSTDRV		;get dst drive
	MOV	B,A		;pass to seek routine
	CALL	SEEKT		;seek to the track
	ORA	A
	JNZ	ERSEEK
	LXI	B,RBUFF		;point to start of buffer
	CALL	SETDMA
	MVI	D,26		;26 sectors to write
	LXI	H,TABLE		;point to sector table
	CALL	WTSDLP		;write the data out
	ORA	A		;any errors?
	RNZ			;return if so
	LXI	B,RBUFF+(B512*16)	;next buffer
	CALL	SETDMA
	MVI	D,26		;sectors to read
	LXI	H,TABLE		;point to sector table
	CALL	RDSDLP		;read the data just written
	ORA	A		;any errors in reading
	RNZ			;return if so
	LXI	B,B128*26	;size of track 0
	CALL	BUFCOM		;compare the buffers
	ORA	A		;any errors
	RNZ
;
;now check for double sided operation on source disk
;
SSIDED:	LDA	ENDTRK		;get the ending track
	DCR	A		;subtract 1
	STA	ENDTRK		;save it
	RZ			;return if last track
	LDA	SIDED
	ORA	A		;doing doub sided?
	JZ	DREPEAT		;jump if not
	LDA	SLATCH		;get source latch code
	CALL	DDEN		;switch to double density
	CALL	SIDE1		;do side 1
	STA	SLATCH		;save it
	CALL	DSKSEL		;select the hardware
;
;side 1 ready for copy
;
	LXI	B,RBUFF		;point to read buffer
	CALL	SETDMA		;save memory pointer
	MVI	D,16		;doing 16 sectors
	LXI	H,TABLE		;point to sector table
	CALL	RDDDLP		;read  1 double density track
	ORA	A		;any errors?
	RNZ			;return if so
	LDA	DLATCH		;get destination latch code
	CALL	DDEN		;switch to double density
	CALL	SIDE1		;do side 1
	STA	DLATCH		;save it
	CALL	DSKSEL		;select the drive
;
;side 1 ready for write
;
	LXI	B,RBUFF		;point to buffer just written
	CALL	SETDMA		;set memory pointer
	MVI	D,16		;write 16 sectors
	LXI	H,TABLE
	CALL	WTDDLP		;write 1 tracks worth of data
	ORA	A		;any errors?
	RNZ
;
;now check the data just written doub sided
;
	LXI	B,RBUFF+(B512*16) ;point to top buffer
	CALL	SETDMA		;save the memory pointer
	MVI	D,16		;read 16 sectors
	LXI	H,TABLE
	CALL	RDDDLP		;read the track in to buffer
;
;now check for errors in buffer for doub sided
;
	LXI	B,B512*16	;size = 1 tracks worth
	CALL	BUFCOM		;compare the 2 buffers
	ORA	A		;any errors?
	RNZ
	LDA	SLATCH		;get source latch
	CALL	SIDE0		;switch back to side 0
	STA	SLATCH		;save for next track
	LDA	DLATCH		;get destination latch
	CALL	SIDE0		;switch back to side 0
	STA	DLATCH		;save for next track
	LDA	OPFLAG		;doing system only
	ORA	A		;does it = 0
	RZ			;return if so
;
;copying double density tracks
;
DREPEAT:LDA	SLATCH		;get source latch code
	CALL	DDEN		;switch to double density
	CALL	SIDE0
	STA	SLATCH		;save it
	CALL	DSKSEL		;select the hardware now
	LDA	BEGTRK		;get start track value
	INR	A		;bump up by 1
	STA	BEGTRK		;save next track to do
	PUSH	PSW
	LXI	H,TKCPY
	CALL	BINDEC		;comvert to decimal
	LXI	D,TRKCPY
	CALL	PRINT
	POP	PSW
	MOV	C,A		;pass to seek routine
	LDA	SRCDRV
	MOV	B,A
	CALL	SEEKT		;seek to the track
	ORA	A
	JNZ	ERSEEK
;
;side 0 ready for copy - source disk
;
	LXI	B,RBUFF		;point to read buffer
	CALL	SETDMA		;save memory pointer
	MVI	D,16		;doing 16 sectors
	LXI	H,TABLE		;point to sector table
	CALL	RDDDLP		;read  1 double density track
	ORA	A		;any errors?
	RNZ			;return if so
	LDA	DLATCH		;get dest latch code
	CALL	DDEN		;set to double density
	CALL	SIDE0
	STA	DLATCH		;save it
	CALL	DSKSEL		;select the hardware now
	LDA	BEGTRK		;get present track
	MOV	C,A		;pass to seek routine
	LDA	DSTDRV
	MOV	B,A
	CALL	SEEKT		;seek to it
	ORA	A
	JNZ	ERSEEK
;
;side 0 ready for write - destination disk
;
	LXI	B,RBUFF		;point to buffer just written
	CALL	SETDMA		;set memory pointer
	MVI	D,16		;write 16 sectors
	LXI	H,TABLE
	CALL	WTDDLP		;write 1 tracks worth of data
	ORA	A		;any errors?
	RNZ
;
;now check the data just written to destination disk
;
	LXI	B,RBUFF+(B512*16) ;point to top buffer
	CALL	SETDMA		;save the memory pointer
	MVI	D,16		;read 16 sectors
	LXI	H,TABLE
	CALL	RDDDLP		;read the track in to buffer
;
;now check for errors - in buffer
;
	LXI	B,B512*16	;size = 1 tracks worth
	CALL	BUFCOM		;compare the 2 buffers
	ORA	A		;any errors?
	RNZ
	JMP	SSIDED		;loop till all tracks done
;
;------------------------------------------------------------
;			main program
;------------------------------------------------------------
;
BEGIN:	LXI	SP,STACK	;set local stack pointer
	CALL	SIZE		;check for enough buffer space
	ORA	A		;0 = ok, <> 0 = error
	JZ	BUFT		;jump if ok and test buffer
	LXI	D,SIZERR	;point to error meaasge
	CALL	PRINT		;print that buffer is to small
	JMP	WBOOT		;exit to cpm
;
BUFT:	LXI	D,MEMTST	;show what we are doing
	CALL	PRINT
	CALL	CHECK		;check the buffer memory
	ORA	A		;any errors?
	JZ	BUFOK		;proceed if none
	LXI	D,MEMLOW	;point to low memory error msg
	LXI	H,RBUFF		;rbuff is start of check
	CALL	HEXOUT		;save address in message
	LXI	D,MEMHGH	;point to high memory error msg
	LHLD	BDOS+1		;get cpm top of memory
	DCX	H		;decrease by 1
	CALL	HEXOUT		;save error address
	LXI	D,MEMERR	;point to error msg
	CALL	PRINT
	JMP	WBOOT		;exit to cpm
;
BUFOK:	LXI	H,RBUFF		;start of buffer
	LXI	D,MEMUS1	;storage
	CALL	HEXOUT
	LHLD	BDOS+1		;top of cpm memory
	LXI	D,MEMUS2	;storage
	CALL	HEXOUT
	LXI	D,MEMUSE
	CALL	PRINT
	LXI	D,OPENM		;say hi to the folks
	CALL	PRINT
AGAIN:	CALL	INIT		;init constants in program
	LXI	D,MENU		;point to menu of options
	CALL	PRINT		;show it
	CALL	INPUT		;get a key
	LXI	D,CRLF
	CALL	PRINT		;print a cr,lf
	CALL	DOIT		;go to the function requested
	ORA	A		;any errors
	JNZ	AGAIN		;jump and repeat if so
	LXI	D,COMFUN	;else, no errors found
	CALL	PRINT
	JMP	AGAIN		;loop till we quit
;
;storage area
;
SRCDRV:	DB	0		;source drive (a)
DSTDRV:	DB	1		;dest drive (b)
KEYBUF:	DS	6		;keyboard input buffer
	DS	40
STACK:	DS	1		;local stack area
CLATCH:	DS	1		;physical drive code
TEMP:	DS	1		;keyboard value
SECTOR:	DS	1		;current sector value
DMAADR:	DS	2		;current memory address
RETRY:	DS	1		;retry counter
SIDED:	DS	1		;0 = sing sided, 1 = doub sided
SRCID:	DS	1		;source id byte
ENDTRK:	DS	1		;ending track
BEGTRK:	DS	1		;starting track
SLATCH:	DS	1		;source drive select code
DLATCH:	DS	1		;dest drive select code
TRKTAB:	DS	4		;track table for 4 drives
DISKNO:	DS	1		;old disk selected
OPFLAG:	DS	1		;operation flag 0= system only
;
RBUFF	EQU	$		;start of buffer area
;
	END	START
