	.title	'Harddisk to Floppy Backup Utility'
	.sbttl	'5HDBACK'
	.ident	HD5BAK	; can't start with a number here
version ==	1
revision==	8
patch	==	'e'	;last changed 20 Oct 1983 
	.pabs
	.phex
	.loc	100h
;----------
; Harddisk Backup Utility
; Written by Peter Kavaler, February 1981
;
; 1.1	(05/01) Initial Release
; 1.2	(06/15) Minor Improvements
; 1.3	(06/18) Handle Shugart Fixed heads
; 1.4	(08/17) Use correct pre-comp when formatting
;		Retry floppy errors
; 1.5	(06/10/82) use new cpmmap returned info about
;		multi hard disks and mini floppies
; 1.6	(7/14/82) allow backing up of any of many
;		volumes (Les Wilson)
;    a	(02/01/83) clean up code (Somewhat) to 
;		   prepare for 1.7 (DSB)
;    b begin changing format for fox-floppies
;    c begin mucking hard disk stuff
;    h hard disk seems ok, working on flopsies
;    r pretty near works, has upper bound on
;	retries when restoring to hard disk
;      always formats unit 0
;    s gap smaller for r/w than for format
;      sped FORMdlay up to 0B0 from 900
;    t doesn't scan as it formats, only after
;    u prints hard disk error buffer
;
; 1.7a 06/28/83   Changed user interface.  2-sided 
;		  disks only.
;    b 06/29/83   Debug
;    c 06/29/83   Sub-menu for backup allows choice
;		  to back up only flagged partitions
;    d 06/30/83   Fix bug in firmware read.
;    e 06/30/83   Fix bug in size figuring.
;    f 06/30/83   Fix bug in NEXTf.
;    g 06/30/83   And one in FULLBACK.
;    h 06/30/83   Retry floppy errors
;		  Make messages indicate direction
;		   of data xfer (load or backup)
;		  Use ESC instead of control-S
;    i 07/01/83   Requires all diskettes in LOAD
;		   to have same date/time.
;    j 07/01/83   Fix stack bug in load.
;    k 07/01/83   Improve message clarity
;    l 07/26/83   Cosmetic
	.page
; 1.8a 10/06/83   Allow backup/restore of Unit 0   
;		  Include Unit 0 in ALL or FLAGGED
;		   backups.
;		  Don't ask which volume if there is
;		   only one volume present.
;		  Exit with message if firmware is bad.
;		  On reloading Unit 0, keep the same
;		   firmware as already on the HD, and
;		   notify the user if the drive is not
;		   the type the backup was made from.
;    b 10/07/83   Preserve the bad track table.
;    c 10/10/83   Don't crash on hard disk errors;
;		   transfer data on correctable errors
;    d 10/11/83   Fix floppy error retry bug
;		  Get rid of bogus, non-functional 
;		   duplicate routines (artifacts of the
;		   appending of FORMAT.ASM to the rest
;		   of this code long ago by a nameless
;		   programmer)
;		  Put test so this can't be run from
;		   the Master
;    e 10/20/83   Fixed date/time bug.	If user shuffles
;		   together two sets of disks made from
;		   the same partition but at different
;		   times, program won't load from them.
;		  Improved variable and label names and
;		   removed offensive comments
;		  Require confirmation before warm-boot
;		   if warm-boot will cause floppy access
;
	.page
minute	==	42h
hour	==	43h
day	==	45h  ; day and month are not
month	==	44h  ; in the obvious order
year	==	46h

GOCPM	==	0
cpmdrive==	4
BDOS	==	5

true	==	0FFh
false	==	0
cr	==	0Dh	; carriage return
lf	==	0Ah	; linefeed
bs	==	08h	; backspace
ctrlC	==	03h	; abort
escape	==	1Bh	; return to main menu
bell	==	07h	; bell
endcom	==	0FEh	; end of floppy command
DMA	==	38h	; DMA data port
FLOP	==	18h	; NEC765 DMA port
FLOPSR	==	10h	; NEC765 status port
FLOPDR	==	11h	; NEC765 data port
PIOAD	==	08h	; PIO channel A, data
PIOBD	==	09h	; PIO channel B, data
STOPFLOP==	03h	; NEC765 terminal count
onprecmp==	22	; turn on pre-compensation
maxprec ==	59	; turn off S/L bit
stepset ==	900h	; head step settle time ==15ms
RxRDY	==	0	; receiver ready bit
TxRDY	==	2	; transmitter ready bit
WBOOT	==	1	; for bios calls
SIO1AC	==	2Ah	; SIO-1 channel A, control
SIO1AD	==	28h	; SIO-1 channel A, data -port 0
SETMAP	==	03h	; Set memory map register

MINI2	==	0A0h	 ; device type in DPB
;
	.page
;----------
;	-- XEBEC s1410 controller equates --
CMDmode 	==	00011b	; command mode
wrDATAmode	==	00001b	; write mode
rdDATAmode	==	01001b	; read data mode
rdERRmode	==	01011b	; read error mode
LASTmode	==	01111b	; ret zero byte

xNOseek 	==	2	; error returned
xNOTready	==	4	; likewise

xBUSY		==	0	; busy true high
xREQxfer	==	4	; data xfer request bit
DAT$CMDport	==	1	; mode is set by toggle
toggle		==	0
; output to port0 toggles between cmd and data modes

xDRVready	==	0	; test drive ready
xREAD		==	08	; read command
xWRITE		==	0Ah	; write commend
xSENSE		==	03	; status sense
xSEEK		==	0Bh	; seek
xINITdrv	==	0Ch	; initialize drive
xRECAL		==	1	; recalibrate head
xERROR		==	02	; illegal command
xIDLE		==	40h	; idle command
xINITcont	==	0	; reset controller
xSELcont	==	0C1h	; select controller
xDESELcont	==	041h	; deselect controller

secSIZE 	==	256
lenXEBcmd	==	6

	.page
.insert volinfo

	.page
	lxi	SP,stack
	call	INIT	; print msg; set up DMA
	call	HDtype	; find out all about HD
	jz	MENU
	call	prtmsg
	jmp	GOCPM

;------------------------
; Ask user what she wants to do
MENU:
	lxi	H,select$
	call	PRTMSG
..ask:	call	CONIN
	cpi	ctrlC
	jz	bye	; warm boot if ctrl-C
	cpi	escape
	jz	bye
	mov	C,A
	call	CONOUT	; echo it out
	ani	0DFh	; uppor case

	lxi	H,GOformat
	cpi	'F'	; check if format option
	jrz	..call	; go do it if so

	lxi	H,GObackup
	cpi	'B'
	jrz	..call

	lxi	H,GOload
	cpi	'L'
	jrz	..call

	lxi	H,DIRHARD
	cpi	'D'
	jrz	..call

	jmpr	MENU	; bad input, try again
..call:  
	lxi	D,MENU	; this is our "return" address
	push	D	; so put it onto the stack
	pchl		; "call" address -- go there

bye:	lxi	H,byemsg
	jmpr	leave
badbye: lxi	H,abortmsg
leave:	call	PRTMSG
	call	OkToGo	; verify ok to leave
	jmp	GOCPM

	.sbttl	'Routines to guarantee safe warm-boot'	
	.page
;-----------------------------
; This program will often be run by people who have
; booted from a floppy disk.  When they exit to CP/M,	
; they will of course return to that floppy.  In all
; probability they will ask to exit without having
; put a CP/M floppy in the drive, however.  Here we
; find out whether we have floppies on the boot drive,	
; the A: drive, or the currently logged drive (these
; may or may not all be the same, but all will be
; accessed on a warm boot.)  If any of these is a
; floppy, the user is asked to make sure there's a
; good floppy in the drive before rebooting.

OkToGo:
	mvi	A,0FFh	; initialize flag
	sta	..flop
	mvi	C,0FFh	; select boot disk
	call	chktype ; 0 if a minifloppy
	jrnz	..a
	sta	..flop	; store the zero
..a:	mvi	C,0	; select A:
	call	chktype
	jrnz	..cur
	sta	..flop
..cur:	lda	cpmdrive ; select current drive
	mov	C,A
	call	chktype
	jrnz	..chk
	sta	..flop
..chk:	lda	..flop
	ora	A
	rnz		; return if no floppies
	lxi	H,PutFlop$
	call	WAITCR
	ret
..flop: .byte	0

;---------------------
; Select a disk and find its medium type
; Regs in:  C = logical drive
; Regs out: A = 0 if a minifloppy, nonzero if not
; Trashed:  all

chktype:
	call	seldsk	; select the disk
	call	cpmmap	; find out its characteristics
	dcx	H	; point to media type
	mvi	A,MINI2
	sub	M
	ret


 .sbttl  'Initialization subroutines'
	.page
;------------------------
; Set things up; greet the user
INIT:
	lxi	H,hiuser$
	call	PRTMSG

	call	noMAST	; don't let master run

; Setup DMA vector at base of BIOS
	lhld	1
	mvi	L,0
	mvi	M,DMAdone&0FFh
	inx	H
	mvi	M,(DMAdone>8)&0FFh
;
; Specify stuff about the floppy
	lxi	H,specFLOP
	call	COMMAND
	ret
;------------------------------------
; Exit the program if attempting to run
;  on the Master.
; Regs in: none
; Regs out: none
; Trashed:  none (that's why we push PSW)

noMAST:
	push	PSW
	lda	47h
	ora	A
	jrz	..bad
	pop	PSW
	ret
..bad:	lxi	H,badmast
	call	PRTMSG
	jmp	GOCPM
;------------------------------------
; get disk size from hdstat
; and locate bad sector table.
; Return Z if everything is fine.
;  else return message address in HL.
HDtype:
	call	findXEB
	jrnz	..ok
	lxi	H,noHDmsg
	dcr	A		; not there
	ret
..ok:
	call	XEBcold
..tellpresent:
	lxi	h,mbf1		;tell which volumes are 
	call	prtmsg		;  present
	xra	a		;start at zero
	call	..volinfo	; put info onto CRT
	call	..whichvol	; ask for volume
	jrnz	..tellpresent
	call	..size		; get size of vol
	lxi	H,badctl$
	rnz
						    
	call	..bst	     ; find bst
	xra	A	     ; ok return
	ret
;--
; Put up vol info onto screen
..volinfo:    
	sta	volnum
	call	setvolptr
	mov	a,vlipresent(x)
	cpi	true
	jrnz	..nextvol
	lxi	h,crlf$
	call	prtmsg
	lda	volnum
	sta	..prtd		;show one printed
	call	prtbyt		;print volume number
	call	prtvol		;print volume label
..nextvol:
	lda	volnum
	inr	a
	cpi	4
	jrnz	..volinfo
	ret
..prtd: .byte	0
;---
; Ask user which vol she wants to work with
; Return Z if ok, NZ if invalid selection
..whichvol: 
	lda	..prtd	; highest one printed
	ora	A
	jrz	..goodans; if 0, don't ask
	lxi	H,mbf2
	call	prtmsg
	call	CONIN
	cpi	ctrlc
	jz	0	;warm boot 
	mov	C,A
	call	CONOUT	;echo answer
	sui	'0'
	jrc	..badans
	cpi	4
	jrc	..goodans
..badans:
	lxi	h,mbf3
	call	prtmsg
	jmpr	..whichvol
..goodans:
	sta	volnum
	call	setvolptr
	mov	a,vlipresent(x)
	cpi	true
	rz		; ok
	lda	volnum
	ora	A
	jrz	..ool	; if vol 0 is not present then
	lxi	H,mbf4	; we ae o.o.l (out of luck)
	call	prtmsg
	ori	0FFh	; not ok
	ret
..ool:	lxi	H,mbf5
	call	prtmsg
	jmp	0
;--
; Get disk size from hard stat
; Return Z if valid size, NZ if not
..size:
	mov	A,vlisize(x) ;head mask
	sui	1	     ; xebec disk types
	jrc	..bad	     ; range from 1 to 6
	cpi	6
	jrnc	..bad
	mov	B,A
	add	A
	add	B	     ; triple it
	mov	C,A	     ; to point into
	mvi	B,0	     ; table of 3-byte
	lxi	H,TYPtab     ; values.
	dad	B
	lxi	D,maxsec
	lxi	B,3
	ldir
	xra	A	     ; validate
	ret
..bad:	ori	0FFh	     ; invalidate
	ret
;---
; locate bad sector table and save its address
..bst:
	lxi	H,17	; block #'s start at 1
	call	CVTblk
	lxi	H,xHIGHadr
	lxi	D,BADsec
	lxi	B,3
	ldir		; save adr of bad sector table
	ret

	.sbttl	'ALLOC table display'
	.page
;--------------------------------
; Get and display the ALLOC table
DIRHARD:
	lxi	H,DHmsg$
	call	PRTMSG
	call	ALLOC	     ; get the table
	lxi	H,ALLOCtab+1 ; first name
..loop: mov	A,M	     ; is it ASCII?
	ora	A
	jrz	..done
	cpi	80h
	jrnc	..done
	push	H
	mvi	B,8	     ; names are 8 chars
..put:	mov	C,M	
	push	H	
	push	B
	call	CONOUT
	pop	B
	pop	H
	inx	H
	djnz	..put
	mvi	C,' '	     ; put two more blanks
	call	CONOUT	     ; i.e. 8 names per line	
	mvi	C,' '
	call	CONOUT
	pop	H
	lxi	D,16
	dad	D	    ; point to next name
	jmpr	..loop
..done: lxi	H,crlf$
	call	PRTMSG
	ret


	.sbttl	'Backup subroutines'
	.page
;--------------------------
; Backup from disk to floppy
GObackup:
	call	getdate
..menu: lxi	H,BACKmenu  ; display options
	call	prtmsg
	call	CONIN	    ; get response
	cpi	ctrlC
	jz	badbye	    ; inelegant abort
	cpi	escape
	rz		    ; return to main menu
	mov	C,A
	call	CONOUT	    ; echo choice
	ani	0DFh	    ; make upper case
	lxi	H,BACKspec
	cpi	'S'	    ; specified partitions
	jrz	..call
	lxi	H,BACKflag
	cpi	'F'	    ; flagged partitions
	jrz	..call
	lxi	H,BACKall 
	cpi	'A'
	jrz	..call
	lxi	H,BACK0
	cpi	'Z'
	jrz	..call 
	jmpr	..menu

..call:  
	lxi	D,..menu
	push	D	; return address
	pchl		; call address
;------------------
; Back up Unit 0 by faking up its name
BACK0:	 
	lxi	H,name0  ; get the name
	lxi	D,conbuf
	lxi	B,8
	ldir
	call	BACK1up
	ret
;------------------
; Back up user-specified partitions
BACKspec:
	call	NEXTbackup  ; get the name
	rz
	call	BACK1up
	jmpr	BACKspec
;-----------------
; Get name of next partition to back up
; <ESC>= return to main menu
; Regs out: Z if escape, NZ if other
NEXTbackup:
	lxi	H,partb$
	call	PRTMSG
	call	INSTRING; get partition name
	lda	conbuf-1
	ora	A	; show if empty return
	jrz	NEXTbackup
	lda	conbuf
	cpi	escape
	ret
;---------------
; Backup a single partition
BACK1UP:
	lxi	H,conbuf
	lxi	D,part%
	lxi	B,8
	ldir		; save partition name for later
	call	ALLOC	; read allocation table
	call	FINDPART; find partition name
	ora	A
	jrnz	..foundpart
	lxi	H,nopart$
	call	PRTMSG	; tell the user she goofed
	ret
..foundpart:
	call	PARTBACK
	ret
;----------
; Back up all partitions that are flagged for backup.
BACKflag:
	mvi	A,0FFh
	sta	flagflag
	call	FULLback
	ret
;----------
; Back up absolutely all partitions.
BACKall:
	xra	A
	sta	flagflag
	call	FULLback
	ret
;------------
; Backup many partitions, start each on new disk
FULLback:  
	call	ALLOC	; read the ALLOC table
	lxi	H,ALLOCtab; start w/ unit 0
..next: lxi	B,16
	dad	B	; point to ending block #
	mov	A,M	; end block of this partition
	cpi	0FFh	; check for end of table
	rz		; return if last entry
	lda	flagflag; does backup flag matter?
	ora	A
	jrz	..doit
	dcx	H	; point at control byte
	bit	7,M	; backup flag
	inx	H
	jrz	..next	; no flag for backup, skip it

..doit: push	H	; save ending block
	lxi	B,16
	ora	A	; clear carry
	dsbc	B	; point to beginning block
	push	H	; save it
	inx	H	; aim at name
	push	H
	lxi	D,part% ; save it here
	lxi	B,8	;  (for message & header)
	ldir
	pop	H	
	lxi	D,backpart ; save it here too
	lxi	B,8	
	lxi	H,backing$
	call	PRTMSG
	pop	H	  ; beginning block #
	call	PARTBACK
	pop	H	  ; ending block for this entry
	jmpr	..next	  ; go do following one
;----------
; Partition backup routine
PARTBACK:
	mov	A,M
	push	PSW	; save starting block #
	lxi	B,16
	dad	B
;
; Compute size
	mov	B,A	; starting block
	mov	A,M	; ending block
	sub	B	; size = multiples of 256
	sta	realsize
	push	H	; save pointer to ending block
	lxi	H,SIZEtab
	mvi	B,6
..look: cmp	M
	jrz	..ok
	lxi	D,6
	dad	D
	djnz	..look
	lxi	H,death$
	call	PRTMSG 
	rst	6	; internal error. Warm boot if
			; being run normally, break if
			; being run under ZDTI.
..ok:	inx	H	
	lxi	D,size% 
	lxi	B,5
	ldir
	pop	H	; restore pointer to ending blk
	mov	H,M	; ending block #
	mvi	L,0
	call	CVTBLK	; compute ending HML
	lxi	H,xHIGHadr
	lxi	D,endsec
	lxi	B,3
	ldir
	pop	PSW	; restore starting block #
	mov	H,A
	mvi	L,1
	call	CVTblk	; compute starting THS
	call	BACKUP
	call	offMOTOR; unload floppy head
	ret
	.page
;----------
; Backup from current THS to ending THS
BACKUP: 
	lda	realsize; 256k block count
	mov	H,A	; 256k/256 = 1k, so--
	mvi	L,0	; HL = 1K block count
	lxi	B,632	; 632K on double-sided disk
..size: call	DIVIDE	; compute number of diskettes
	mov	A,E
	inr	A
	call	CVTDEC	; convert to decimal ASCII
	shld	last%
	shld	last
	lxi	H,disks$
	call	PRTMSG	; tell how many disks needed
	sub	A
	sta	count
	lxi	H,newback$; mount first diskette
	jmpr	back2
BACKgo: 
	lxi	H,freshB$
back2:	call	WAITCR	; mount fresh diskette
	cpi	escape
	rz
	lda	count
	inr	A
	sta	count
	sta	realcount
	call	CVTDEC
	shld	count%
	call	HOME	; home to track 0
	call	WRITE0	; write info to track 0
	mvi	A,1
	sta	FLOPtrk
;
; Read 4K from the hard disk
..copy: lxi	H,iobuff
	push	H
	push	D
	call	READh	; read 4K chunk from harddisk
	call	NEXTh
	sta	endflg	; non-zero if end of disk
	pop	D
	pop	H
;
; Write 4K to the floppy disk
..write:call	SEEK
	call	SENSE	; get seek status
	cnz	NRDYerr ; error if diskette removed
	call	WRITEf	; write track to floppy
	jrnz	..retry
	call	SCANf	; read track from floppy
	jrz	..ok
..retry:
	mvi	C,'#'
	call	CONOUT 
	mvi	C,bs
	call	CONOUT
	jmpr	..write ; retry if error
;
; Increment to next floppy track until done
..ok:	ori	0FFh	; ok to print errors
	sta	prtflag
	mvi	C,'.'
	call	CONOUT	; indicate track was OK
	lda	endflg
	ora	A
	jrnz	..done
	call	NEXTf
	jrnz	..copy
	call	offMOTOR; unload the head
	jmpr	BACKgo
;
; Finished
..done: lxi	H,doneB$
	call	PRTMSG
	ret


	.sbttl	'Loading subroutines'
	.page
;---------------------------------------------------
; Load from floppy disk to hard disk

GOload:
	call	LOAD
	push	PSW
	call	offMOTOR; unload floppy head
	pop	PSW
	cpi	escape
	rz
	jmpr	GOload	; do another partition
;----------
; Process first diskette
LOAD:	
	xra	A
	sta	FIRMdiff; firmware change flag
	inr	A   
	sta	count
	lxi	H,newload$
	call	WAITCR	; mount first diskette
	cpi	cr	; see if <CR>
	jrz	..go
	cpi	escape
	rz
..go:	call	ld1setup; set up that 1st disk
	rnz		; give up if bad name
	jmpr	check	; jump past re-init stuff
;----------
; Process next diskette
LOADgo:
	lxi	H,count
	inr	M
LOADnext:
	call	offMOTOR; unload head
	lxi	H,freshL$
	call	WAITCR	; mount fresh diskette
	cpi	escape
	rz		; abort if escape
	call	ldNsetup ; set up next disk stuff
	jrnz	LOADnext ; wrong disk inserted
;----------
; Check if partition size matches
check:
	lda	realsize
	lxi	H,loadsize
	cmp	M	; compare partition sizes
	jrz	..ok2
	lxi	H,badsize$
	call	PRTMSG	; size doesn't compare
	jmp	LOADnext
;
; Check if diskette number matches
..ok2:
	lda	realcount
	lxi	H,count
	cmp	M	; compare diskette count
	jrz	..ok3
	lxi	H,badcount$
	call	PRTMSG
	jmp	LOADnext
;
; Ask user to verify as well
..ok3:
	lxi	H,verifL$
	call	WAITCR	; ask user if OK to continue
	cpi	escape
	jrnz	..ok4
	lda	count
	cpi	1
	rz		; if first diskette, abort
	jmp	LOADnext; if not first, let user retry
..ok4:	mvi	A,1
	sta	FLOPtrk
       ;xra	A
       ;sta	prtflag
;
; Read 4K from floppy
	lxi	B,1024	; max retries
..copy: push	B
	call	SEEK
	call	SENSE
	cnz	NRDYerr ; floppy removed during copy

	xra	A	; allow unlimited retries
	sta	prtflag ; with no err messages
	call	READf
	jrz	..ok5
	mvi	C,'#'
	call	CONOUT
	pop	B	; retries
	dcx	B
	mov	A,B
	ora	C
	jrz	..ok5
	mvi	C,bs
	call	CONOUT
	jmpr	..copy	; retry if error
;
; If this is Unit 0 on Volume 0, we need to put in the
; real firmware rather than the possibly wrong firmware 
; from the floppy.
..ok5:
	lda	volnum
	ora	A	; see if it's volume 0
	jrnz	..ok7
	lxi	H,xHIGHadr
	mvi	B,3
..ora:	mov	A,M	; see if HD is at first sector
	ora	A
	inx	H
	jrnz	..ok6
	djnz	..ora

	lda	iobuff+vlisize	; check drive type so 
	lxi	H,FIRMWARE+vlisize; we can tell the 
	xra	M		; user if it doesn't
	sta	FIRMdiff	; match up.

	lxi	H,firmware;yep. get real firmware in 
	lxi	D,iobuff      ; out with the old
	lxi	B,vlibufsize*4; in w/ this much new
	ldir

;
; If this is Unit 0 on Volume 0, we need to put in the
; real Bad Track Table than the possibly wrong table
; from the floppy.
..ok6:
	lda	volnum
	ora	A	; see if it's volume 0
	jrnz	..ok6
	lxi	H,xHIGHadr
	mvi	B,2
..or6:	mov	A,M	; see if HD is at first sector
	ora	A
	inx	H
	jrnz	..ok7
	djnz	..or6

	mov	A,M
	cpi	40h	; if doing 30-4F, watch out
	jrnz	..ok7	; 'cause table is at 30

	push	H	
	lxi	H,iobuff; first block in buffer
	lxi	B,1024	; read 1k table
	call	RECHARD ; overlay it in our memory
	pop	H	; restore disk ptr to start

; Write 4K to harddisk
..ok7:	pop	B	; even stack, toss retry count
	ori	0FFh	; reenable err messages
	sta	prtflag
	lxi	H,iobuff
	push	H
	push	D
	call	WRITEh
	call	NEXTh
	pop	D
	pop	H
	ora	A
	jrnz	..done
;
; Increment to next floppy track
	ori	0FFh
	sta	prtflag
	mvi	C,'.'
	call	CONOUT
	call	NEXTf
	jnz	..copy
	jmp	LOADgo
;
; Finished
..done: 
	lxi	H,doneL$
	call	PRTMSG

	lda	FIRMdiff    ; print msg if we restored 
	ora	A	    ; a Unit 0 from some other
	rz		    ; type of hard disk

	lxi	H,firmL$
	call	PRTMSG
	ret

FIRMdiff: .byte 0 
	.page
;--------------------------------
; Set up load for the first floppy
; Return NZ if partition not found
;	  Z if so far so good
ld1setup:
	call	HOME	; home to track 0
	call	READ0	; read info from track 0
	lxi	H,info%
	call	PRTMSG	; print info from track 0

	lxi	H,date% ; save date & time
	lxi	D,curdate; to compare subsequent
	lxi	B,21	 ; disks against
	ldir

	lxi	H,part%
	lxi	D,curpart
	lxi	B,8
	ldir
;
; Make sure the partition is in the ALLOC table
	call	ALLOC
	call	FINDPART
	ora	A
	jrnz	..intable
	lxi	H,nopart$
	call	PRTMSG	; partition not in ALLOC table
	jmpr	..bad
;
; Compute beginning and ending HD addresses
..intable:
	mov	A,M
	push	PSW	; save starting block #
	lxi	B,16
	dad	B
	mov	B,A	; starting block #
	mov	A,M	; ending block #
	sub	B	; compute size
	sta	loadsize
	mov	H,M	; ending block #
	mvi	L,0
	mov	A,H	; special case: if table has NO 
	cpi	0FFh	; entries, this byte will be the
	jrnz	..cvt	; end-of-table marker.	We must
	lda	size%+1 ; be loading Unit 0 and we must 
	lxi	H,512	; trust the floppy as to the
	cpi	'5'	; size, 256k or 512k. For safety
	jrz	..cv0	; we say if it isn't 512k then
	lxi	H,256	; it must be 256k.
..cv0:	mov	A,H	; ending block = block count
	sta	loadsize; in this particular case.
..cvt:	call	CVTblk	; compute ending HML
	lxi	H,xHIGHadr
	lxi	D,endsec
	lxi	B,3
	ldir
	pop	PSW	; restore starting block #
	mov	H,A
	mvi	L,1
	call	CVTblk	; compute starting THS
	xra	A	; Z flag if good
	ret
..bad:	ori	0FFH
	ret
;--------------------------------
; Set up load for subsequent floppies
; return Z if ok, NZ if bad name or date
ldNsetup:
	call	HOME	; home to track 0
	call	READ0	; read info from track 0
	lxi	H,info%
	call	PRTMSG	; print info from track 0
;
; Check whether partition name matches
	lxi	H,part%
	lxi	D,curpart
	mvi	B,8
..comp: ldax	D
	cmp	M
	jrz	..same
	push	PSW
	lxi	H,diffname$ ; different partition names
	call	PRTMSG
	pop	PSW	; make NZ
	ret		; bounce back

..same: inx	H
	inx	D
	djnz	..comp

; Check whether date/time matches 
	lxi	H,date%
	lxi	D,curdate
	mvi	B,21
..cmp2: ldax	D
	cmp	M
	jrz	..sam2
	push	PSW
	lxi	H,diffdate$ ; different partition names
	call	PRTMSG
	pop	PSW	    ; restore NZ flag
	ret

..sam2: inx	H
	inx	D
	djnz	..cmp2
	ret	  

	.sbttl	'Utility subroutines'
	.page

; Read the allocation table and compute beginning
; block numbers
ALLOC:
	lxi	H,16
	call	CVTBLK	; compute track, head, sector
	lxi	H,ALLOCtab
	call	READh	; read allocation table
	lxi	H,ALLOCtab
	lxi	D,16
	mvi	B,0	; B = cumulative block count
..alloc:
	mov	A,M	; get partition size
	mov	M,B	; replace with starting block #
	ora	A
	jrz	..end	; jump if at end of table
	cpi	10	; too big, E5 for instance, is
			; also a bad value to back up.
	jrnc	..end
			; NOTE: carry must be set here!
	mvi	C,0	; compute C = partition blocks
..rot:	ralr	C
	dcr	A
	jrnz	..rot
	mov	A,B
	add	C	; increment total block count
	mov	B,A
	dad	D	; move to next partition
	jmpr	..alloc
..end:	dad	D
	mvi	M,0FFh	; mark end-of-table

	lxi	D,ALLOCtab+1	; name of unit 0
	lxi	H,name0
	lxi	B,8
	ldir
	ret
name0:	.ascii	"CtrlArea"
;----------
; Search ALLOC table for partition name match
;  Regs out:  A  = 0 if partition not found
;	      A  = 0FFh if partition found
;	      HL = address of ALLOC entry
FINDPART:
	lxi	H,ALLOCtab
..next: mov	A,M	; check for end-of-table
	inr	A
	rz
	inx	H
	lxi	D,part%
	lxi	B,8<8+0FFh; compare 8 bytes
..cmp:	ldax	D
	cmp	M
	jrz	..same
	mvi	C,0	; set "not equal" flag
..same: inx	D
	inx	H
	djnz	..cmp
	mov	A,C
	ora	A	; check "not equal" flag
	jrnz	..equal
;
; Match not found, so continue searching
	lxi	B,7	; move to next entry
	dad	B
	jmpr	..next
;
; Match found, so return entry address
..equal:
	lxi	B,9
	dsbc	B
	ret
;----------
; Convert block number to absolute sector addr
; Blocks are 1k and start at 1, sectors are
; 256 bytes and start at 0.
;  Regs in:    HL = block number
;  Destroyed:  All
CVTblk:
	dcx	H	; start at 0
	ora	A	; clear carry
	dad	H	; mult by 2
	jrc	..squk	; too big! 
	dad	H	; there's 4

	mvi	A,0
	adc	A	; get carry
	sta	xHIGHadr
	mov	A,H
	sta	xMIDadr
	mov	A,L
	sta	xLOWadr
	ret

..squk: lxi	H,toobig
	call	prtmsg
	jmp	0

	.sbttl	'Floppy Data Transfer'
	.page
;----------
; Write a track on the floppy
WRITEf:
	mvi	A,10	; retries before giving up
..retry:
	sta	nretry
	sub	A
	out	PIOAD	; multiplex DMA to NEC765
	lxi	H,iobuff
	shld	wrfadr
	lxi	H,lenbuff-1
	shld	wrflen
	lxi	H,DMAwrf
	lxi	B,lenwrf<8+DMA
	outir		; program the DMA chip
	mvi	A,0FFh
	sta	FLOPstuff; set DTL in write command
	lxi	H,FLOPcom
	mvi	M,wrDDFLOP
	call	FLOPop
	rz		; return if it was ok.
	lda	nretry
	dcr	A
	rm		; run out of retries
	jmpr	..retry ; or not.
;----------
; Read a track on the floppy
READf:
	mvi	A,10	; retries before giving up
..retry:
	sta	nretry
	sub	A
	out	PIOAD	; multiplex DMA to NEC765
	lxi	H,iobuff
	shld	rdfadr
	lxi	H,lenbuff-1
	shld	rdflen
	lxi	H,DMArdf
	lxi	B,lenrdf<8+DMA
	outir		; program the DMA chip
	mvi	A,0FFh
	sta	FLOPstuff; set DTL in read command
	lxi	H,FLOPcom
	mvi	M,rdDDFLOP
	call	FLOPop
	rz		; return if no error
	lda	nretry
	dcr	A
	rm
	jmpr	..retry
;----------
; Scan a track on the floppy
SCANf:
	mvi	A,1	; retries before giving up
..retry:
	sta	nretry
	sub	A
	out	PIOAD	; multiplex DMA to NEC765
	lxi	H,iobuff
	shld	wrfadr
	lxi	H,lenbuff; give NEC765 one more byte
	shld	wrflen	 ; than it really needs
	lxi	H,DMAwrf ; (stupid, but necessary)
	lxi	B,lenwrf<8+DMA
	outir
	mvi	A,1
	sta	FLOPstuff; set STP in scan command
	lxi	H,FLOPcom
	mvi	M,scanFLOP
	call	FLOPop
	rz		; return if it was ok.
	lda	nretry
	dcr	A
	jp	..retry ; any more retries...
			; no. check for comp error
	lda	FLOPstat+2
	bit	2,A
	cnz	COMPerr ; scan failed
	ret
;----------
; Compute next floppy track and side
;  Regs out:   Z flag if this increment would
;		   place us beyond last track
;	      NZ if we're still on the disk
NEXTf:
	lxi	H,FLOPsid
	xra	A	  ; side is 0 or 1
	cmp	M	  ; if 0, make it 1.
	jrz	..inr	  ; if 1, bump the
	mvi	M,0	  ; track unless
	dcx	H	  ; we have reached the
	mvi	A,lasTRK  ; end already
	sub	M
	rz
..inr:	inr	M	; this makes NZ.
	ret
;----------
; Write to track 0, sector 3
WRITE0:
	lxi	H,info% ; where info is
	lxi	D,IObuff; where we want it
	lxi	B,128	; how much we want
	ldir
	call	POINT0	; aim
	call	WRITEf	; shoot
	ret
;----------
; Read from track 0, sector 3
READ0:
	call	POINT0	; aim at track 0
	call	READf	; read it on in
	lxi	H,IObuff; where info is
	lxi	D,info% ; where we want it
	lxi	B,128	; how much we want
	ldir
	ret
;----------
; Aim the command block at track 0

POINT0:
	lxi	H,FLOPdsk
	mvi	M,0	; disk
	inx	H
	mvi	M,0	; track
	inx	H
	mvi	M,0	; head
	inx	H
	mvi	M,1	; sector
	ret
;-----------

; Process a floppy operation
FLOPop:
	lda	FLOPsid
	slar	A
	slar	A
	sta	FLOPdsk
	call	COMMAND ; program the NEC765 chip
	call	RESULT	; get result status from floppy
	lda	FLOPstat
	ani	0C0h	; Z flag = 0 if no error
	rz		; return if no errors

	lda	nretry	; see if we're retrying
	ora	A
	rnz		; yep. return.

			; nope. determine error type(s)
	lxi	B,FLOPstat+3; point to THS
	lda	FLOPstat+2
	bit	5,A
DATAerr:cnz	IOerr	; data CRC error
	bit	4,A	
TRACerr:cnz	IOerr	; on wrong track
	bit	0,A
MADRerr:cnz	IOerr	; missing address mark
	lda	FLOPstat+1
	bit	7,A
ENDTerr:cnz	IOerr	; read beyond end-of-track
	bit	5,A
IDerr:	cnz	IOerr	; ID CRC error
	bit	4,A
ORUNerr:cnz	IOerr	; overrun (DMA failure)
	bit	2,A
SECTerr:cnz	IOerr	; cannot find sector
	bit	1,A
PROTerr:cnz	IOerr	; write-protected
	bit	0,A
DENSerr:cnz	IOerr	; missing ID address mark
	push	PSW	; save Z/NZ status
	ani	01001000b ; remaining bits -- NEC
			; manual says they're never
			; ever used.
	cnz	IOerr	; unknown error
	pop	PSW	; restore Z/NZ flag
	ora	A
	ret

NRDYerr:call	IOerr
	ret
COMPerr:call	IOerr
	ret

	.page
;----------
; Seek a track
SEEK:
	lda	FLOPtrk
	sta	seekFLOP+2
	lxi	H,seekFLOP
SEEKER:
	sub	A	; select floppy drive 0
	call	onMOTOR ; load the head
	jmp	COMMAND ; issue the seek command
;----------
; Wait for seek complete
SENSE:
	mvi	B,0	; clear seek flag
..sense:lxi	H,senseFLOP
	call	COMMAND
	call	RESULT
	lda	FLOPstat
	bit	3,A	; retry if not ready
	rnz
	bit	5,A	; check for seek complete
	jrnz	..done
	mvi	B,1	; set seek flag
	jmpr	..sense
..done: mov	A,B
	ora	A	; no need to delay ...
	rz		; if already over track
	lxi	B,stepset; delay 15ms for step settle
			; fall through to delay
;----------
; Delay loop
;  Regs in:   BC = delay time
;  Regs out:  none
;  Destroyed: A, BC
DELAY:	dcx	B
	mov	A,B
	ora	C
	jrnz	DELAY
	ret
;----------
; Load floppy head
;  Regs in:   A = floppy drive number
;  Regs out:  none
;  Destroyed: A, B
onMOTOR:
	mov	B,A
	set	2,B	; turn on head load bit
      ; lda	FLOPtrk
      ; cpi	onprec	; small precomp tracks 22-58
      ; jrc	..load
      ; set	5,B
      ; cpi	maxprec ; large precomp tracks 59-79
      ; jrnc	..load
      ; set	4,B
..load: mov	A,B
	out	PIOBD
	ret
;----------
; Unload floppy head
offMOTOR:
	sub	A
	out	PIOBD
	ret
;----------
; Send a command to the floppy controller
;  Regs in:   HL = address of command string
;  Regs out:  none
;  Destroyed: A, HL
COMMAND:
	mov	A,M
	cpi	endcom
	rz
	call	WAITDR
	jrc	SYNCerr
	mov	A,M
	out	FLOPDR
	inx	H
	jmpr	COMMAND
SYNCerr:call	IOerr
	call	RESULT
	ret
;----------
; Get result status from the floppy controller
;  Regs in:   none
;  Regs out:  none
;  Destroyed: A, HL
RESULT:
	lxi	H,FLOPstat
..loop: call	WAITDR
	rnc
	mvi	A,0C3h
	out	DMA
	in	FLOPDR
	mov	M,A
	inx	H
	jmpr	..loop
;----------
; Wait for floppy data register ready
;  Regs in:   none
;  Regs out:  A = status register, shifted left by 1
WAITDR:
	in	FLOPsr
	rlc
	jrnc	WAITDR
	rlc
	ret

	.sbttl	'Hard Disk Data Transfer'
	.page
;----------
; Read 4K from the harddisk
;  Regs in:   HL = Input buffer address
;  Regs out:  A  = nonzero if disk error
READh:
	call	noMAST
	shld	..pntr
	lxi	H,xHIGHadr
	lxi	D,xSAVEadr
	lxi	B,3
	push	H
	push	D
	push	B
	ldir		; save in case of trouble
	lxi	B,4096	; read 4K
	lhld	..pntr	; into this place
	call	RECHARD
	pop	B
	pop	H	; pop H, D in opposite order
	pop	D	; to do restorative ldir
	push	PSW	; save Z/NZ
	ldir		; reinstall disk addr
	pop	PSW
	rz		; return if no error
..ask:	lxi	H,qretry$
	call	PRTMSG	; error: ask user what to do.
	call	CONIN	; get response
	ani	0DFh	; upper case
	push	PSW
	mov	C,A
	call	CONOUT	; echo it
	lxi	H,crlf$ ; and print <Cr><Lf>
	call	PRTMSG
	pop	PSW	; interpret the response:
	cpi	'Q'	; Q: Quit the program
	jz	0
	cpi	'C'	; C: Continue (accept loss)
	rz
	cpi	'R'	; R: Retry this read
	lhld	..pntr
	jrz	READh
	jmpr	..ask	; Else: ask again
..pntr: .word	0
;----------
; Write 4K to the harddisk
;  Regs in:   HL = Input buffer address
;  Regs out:  A  = nonzero if disk error
WRITEh:
	call	noMAST
	lxi	B,4096	; write 4K
	call	SENDHARD
	sub	A	; return OK status
	ret
;----------
; Compute next physical sector
;  Regs out:  A = nonzero if beyond last sector
NEXTh:
	xra	A	; clear carry
	mov	B,A	; utility zero for later
	lda	xLOWadr
	mov	L,A
	lda	xMIDadr
	mov	H,A
	lxi	D,(4096/256); number of sectors
	dad	D
	lda	xHIGHar
	adc	B	; add 0 + carry
	sta	xHIGHadr
	mov	A,H
	sta	xMIDadr
	mov	A,L
	sta	xLOWadr
;
; Check whether beyond last sector
	lxi	H,xHIGHadr
	lxi	D,ENDsec
	ldax	D
	sub	M
	rc		; return if "high" too large
	jrnz	..ok
	inx	H	; point to xMIDadr
	inx	D
	ldax	D
	sub	M
	rc		; return if "mid" too large
	jrnz	..ok
	inx	H	; point to xLOWadr
	inx	D
	ldax	D
	sub	M
	rc		; return if sector too large

..ok:	sub	A	; return OK status
	ret

	.page
;----------
; Send a block to the harddisk controller
;  Regs in:   HL = block address
;	      BC = byte count
;  Regs out:  none
;  Destroyed: all
SENDHARD:
	call	justWRITE
	ret
;----------
; Receive a block from the harddisk controller
;  Regs in:   HL = block address
;	      BC = byte count
;  Regs out:  Z if ok, NZ if trouble
RECHARD:
	call	justREAD
	ret

;-----------
; Error calls

HARCerr:cnz	IOerr	; command error
	ret
HARDerr:cnz	IOerr	; data error
	ret
HARSerr:cnz	IOerr	; sector error
	ret
HARFerr:cnz	IOerr	; fault error
	ret

;--------------------------
; Find out whether the hard disk exists
; Regs in:  none
; Regs out: NZ if present, Z if not
; trashed:  most if not all
findXEB:
	call	xCONTinit  ; reset xebec
	call	tBUSYfalse ; see if unbusy
	rz		   ; nope.
	mvi	A,xSELcont
	call	xOUTstatus
	call	tBUSYtrue
	ret		   ; flag is set

	.sbttl	'Hard Disk Primitives'
	.page
;-------------------------------
XEBcold:
	call	xCONTinit
	call	xDRIVEinit    ; fake initialize to get
	call	qREADY	      ; us to where we can read
	call	doRECAL       ; the firmware.
	call	getFIRM
	call	xDRIVEinit    ; real initialize.
	ret
;--------------------------
; Read the firmware from track 0 sector 1
; and set up the drive characteristics
; ENTRY>  none
;  EXIT>  none
; TRASHED> all

getFIRM:
	lxi	H,iobuff     ; firmware won't hold 256
	push	H
	lxi	B,256
	call	justREAD
	pop	H	    ; iobuff
	lxi	D,FIRMWARE
	lxi	B,vlibufsiz*4 ; as much as is used
	ldir
	lxi	H,FIRMWARE+22; disk characteristics
	lxi	D,DRVCHR    ;	(for vol 0)
	lxi	B,7
	ldir
	ret
;
xselect:
	call	wBUSYfalse
	mvi	A,xSELcont
	call	xOUTstatus
	call	wBUSYtrue
	ret
;
deselect:
	call	wBUSYtrue
	mvi	A,xDESELcont
	call	xOUTstatus
	ret
;
xWAITreq:
	in	PIOAD
	bit	xREQxfer,A	; (4)
	jrz	xWAITreq
	ret
;
xCMDsend:
	call	xWAITreq
	mov	A,M
	out	DAT$CMDport
	inx	H
	djnz	xCMDsend
	ret
;
wBUSYfalse:
	call	xINstatus
	bit	xBUSY,A
	jrnz	wBUSYfalse	; wait till not busy
	ret
;
wBUSYtrue:
	call	xINstatus
	bit	xBUSY,A
	jrz	wBUSYtrue	; wait till busy
	ret
;
;----------------------------------
; tBUSYfalse and tBUSYtrue are similar to
; wBUSYfalse and wBUSYtrue, but rather than
; waiting forever they return Z if timed out
; (NZ if good).  Therefore Z may be used to
; indicate there is no working Xebec.

tBUSYfalse:		    ; wait till unbusy,
	lxi	B,4000h     ; up to about 1 sec.
..loop: call	xINstatus   ; but not forever.
	bit	xBUSY,A
	jrz	..ok
	dcx	B
	mov	A,C
	ora	B	    ; missed our chance.
	rz		    ; ret with Z set
	jmpr	..loop
..ok:	ori	0FFh	    ; NZ if ok
	ret

tBUSYtrue:		
	lxi	B,4000h 
..loop: call	xINstatus
	bit	xBUSY,A
	rnz		   ; ret w/ no Z flag
	dcx	B
	mov	A,C
	ora	B
	rz		    ; ret w/ Z flag
	jmpr	..loop
;-------------------------------
SASIstat:
	call	xWAITreq
	call	xINstatus	; get bus status
	ani	0Fh
	ret
;
xSTATrcv:
	call	SASIstat
	cpi	rdERRmode
	cnz	xERRcode	; not in result mode!
;
	in	DAT$CMDport
	push	PSW
	call	xWAITreq
	in	DAT$CMDport
	pop	PSW	; the only byte we care about
	bit	1,A	; the only bit we care about
	ret		; if it's 0, no error.
;

;
xERRrcv:
	call	xERRin 
	lxi	H,xERR0str
	call	prtmsg
	call	xPRTerr
	ret
;
xERRin:
	mvi	A,xSENSE
	call	cmdFOXhard
	lxi	H,xERRbuf
	mvi	B,4
..inERR:
	call	xWAITreq	; get the 4 error bytes
	in	DAT$CMDport
	mov	M,A
	inx	H
	djnz	..inERR

	call	xSTATrcv	; any error now must
	cnz	xERRcode	; certainly be fatal!
	ret
;
xPRTcom:
	lxi	H,xERR1str
	call	prtmsg
	lxi	H,xOPcode
	mvi	B,6	   ; dump command buffer
	call	pbyt  
	call	space
	ret
xPRTerr:
	lxi	H,xERR2str
	call	prtmsg
	lxi	H,xERRbuf
	mvi	B,4	   ; dump error buffer
	call	pbyt
	ret
pbyt:  
	mov	A,M
	push	H
	call	prtbyt
	call	space 
	pop	H
	inx	H
	djnz	pbyt
	ret		
;
xERRcode:
	pop	H	    ; addr
	push	H	    ; restore stack
	push	PSW	    ; mode
	push	H
	lxi	H,xFATALerr
	call	prtmsg
	pop	H	    ; get addr back
	push	H
	mov	A,H
	call	prtbyt
	pop	H
	mov	A,L
	call	prtbyt
	call	space
	pop	PSW	    ; get mode
	call	prtbyt
	call	xPRTerr
	jmp	0
;
xOUTstatus:
	out	toggle
	out	DAT$CMDport
	out	toggle
	ret
;
xINstatus:
	out	toggle
	in	DAT$CMDport
	out	toggle
	ret
;
xCONTinit:
	mvi	A,xINITcont	; reset the controller
	out	toggle
	out	DAT$CMDport
	mvi	A,xIDLE
	out	DAT$CMDport
	out	toggle
	ret
;
xDRIVEinit:
	mvi	A,xINITdrv
	call	cmdFOXhard
	lxi	H,DRVCHR
	mvi	B,lenDVC	; (8)
..init: call	xWAITreq
	mov	A,m
	out	DAT$CMDport
	inx	H
	djnz	..init
	call	xSTATrcv
	cnz	xERRrcv
	ret
;
doRECAL:
	mvi	A,xRECAL
	call	cmdFOXhard
	call	xSTATrcv
	cnz	xERRrcv
	ret
;
qREADY:
	mvi	A,xDRVready
	call	cmdFOXhard
	call	xSTATrcv
	rz		      ; return if ok 
	lxi	H,xWAITmsg    ; else put message
	call	prtmsg	      ; output msg
	jmpr	qREADY	      ; and try again
;
HDSEEK:
	mvi	A,xSEEK
	call	cmdFOXhard
	call	xSTATrcv	; get result bytes

	rz			; return if ok!

	lxi	H,xERR0str
	call	PRTMSG
	call	xPRTcom 	; say what we tried
	call	xERRin
	call	xPRTerr 	; and what happened
	lxi	H,retry$	; and what we'll do
	call	PRTMSG
	mvi	c,lf		; don't overwrite msg
	call	CONOUT
	call	qREADY		; make sure ready
	jmpr	HDSEEK

;--------------------------
; ENTRY>	A  =	command code
; EXIT> 	-- none --
cmdFOXhard:
	push	H
	push	B
	lxi	H,xOPcode
	mov	M,A
	call	xselect
	call	SASIstat
	cpi	CMDmode
	cnz	xERRcode	; not in command mode
;
	call	deselect
	mvi	B,lenXEBcmd	; (6)
	call	xCMDsend
	pop	B
	pop	H
	ret
;
;-------------------------------
; ENTRY>	HL =	buffer adr
;		BC =	byte count
; EXIT> 	-- data in buffer --
;		A  =	error code (0 if ok)
;		Z if ok, NZ if foulup
;		 (e.g. hard data error)
justREAD:
	sub	A		; hard error flag
	sta	..errf
..retry:
	mov	A,B
	sta	skew$blk

	mvi	A,xREAD
	call	cmdFOXhard	; send read command
	call	xRCVdata	; get data
	push	B		; leftover loop ctr
	push	H		; next mem loc used
	call	xWAITreq
	call	xSTATrcv	; get status
	jrz	..ok		; report if bad
	lxi	H,xERR0str
	call	PRTMSG
	call	xERRin		; get error bytes
	call	xPRTcom 	; print command buf
	call	xPRTerr 	; and error bytes

	pop	H		; next mem loc
	pop	B		; blocks to go
	mov	A,B
	ora	A
	jrz	..out		; none left
	lda	xERRbuf 	; some left. error.
	ani	3Fh		; what kind.
	cpi	18h
	jrz	..derr	       ; correctable error
	sta	..errf	       ; hard(er) error
	cpi	11h
	jrnz	..out
	lxi	D,100h	       ; bump up buf pointer
	dad	D
	dcr	B	       ; bump down loop ctr

..derr: lda	xERRbuf+3      ; yes it was.  Set us up
	inr	A	       ; to get the rest of the
	sta	xLOWadr        ; block.
	mov	A,B
	sta	skew$blk
	mvi	C,0	       ; BC = new byte count
	jmpr	..retry 	


..ok:
	pop	H	       ; align stack by popping
	pop	B	       ; pointer & counter
..out:	lda	..errf	       ; error flag
	ora	A
	ret

..errf: .byte	0	
;-------------------------------
; ENTRY>	HL =	buffer adr
;		BC =	byte count (moltiple of 256)
; EXIT> 	Z if read completed
;	       NZ if bad mode on SASI bus

xRCVdata:
	push	B	      ; outer loop
	call	xWAITreq
	call	SASIstat
	cpi	rdDATAmode
	pop	B	      ; die (w/even stack)
	rnz		      ; if error to xfer
	push	B

	lxi	B,0 + DAT$CMDport
	inir

	pop	B
	djnz	xRCVdata
	ret
;
;-------------------------------
; ENTRY>	HL =	buffer adr
;		BC =	byte count
; EXIT> 	-- data to Xebec --
justWRITE:
	mov	A,B
	sta	skew$blk
	mvi	A,xWRITE
	call	cmdFOXhard	; send read command
	call	xSENDdata	 ; get data
	call	xSTATrcv	; get result bytes
	cnz	xERRrcv
	ret
;
;-------------------------------
; ENTRY>	hl =	buffer adr
;		bc =	block count
xSENDdata:
	call	SASIstat
	cpi	wrDATAmode	
	cnz	xERRcode
;
..xread:
	call	xWAITreq
	mov	A,M
	out	DAT$CMDport
	inx	H
	dcx	B
	mov	A,B
	ora	C
	jrnz	..xread
	ret
;
;-------------------------------------------
;	****	data storage   and   messages	****
;	-- Drive characteristics block --
;	    (default values for CMI)
DRVCHR:
	.byte	01		; max number
	.byte	(306-256)	; of cylinders
	.byte	06		; number of heads
	.byte	01		; reduce write
	.byte	(306-256)	; current cylinder
	.byte	01		; write
	.byte	(306-256)	; precompensation
	.byte	04		; max ECC burst length
lenDVC	==	.-DRVCHR

CMDtable:	.byte	xREAD	; op code
		.byte	0	; high adr
		.byte	0	; mid adr
		.byte	0	; track 0, sec 1
		.byte	1	; block count/ (interleave)
		.byte	0	; control field
		.blkb	4	; error buffer
lenMOVE ==	.-CMDtable

xOPcode:	.byte	0
xHIGHadr:	.byte	0
xMIDadr:	.byte	0
xLOWadr:	.byte	0
skew$blk:	.byte	1
ctl$fld:	.byte	07	; fast step
xERRbuf:	.byte	0,0,0,0

xSAVEadr:	.byte	0,0,0

nretry: .byte	0
;
;
	.sbttl	'Floppy primitives'
	.page

;----------
; Floppy commands
;
homeFLOP:.byte	7,0,endcom
seekFLOP:.byte	15,0,0,endcom
senseFLOP:.byte 8,endcom

wrDDFLOP==	45h
rdDDFLOP==	46h
scanFLOP==	51h

FLOPcom:.byte	0	; command
FLOPdsk:.byte	0	; disk
FLOPtrk:.byte	0	; track
FLOPsid:.byte	0	; side
	.byte	1	; sector
	.byte	$4Ksect ; 4K sector size
	.byte	1	; last sector
	.byte	gapDD	; gap length
FLOPcrap:.byte	0	; DTL or STP
	.byte	endcom

FLOPstat:.blkb	7	; floppy status
	.page
;----------
; Home selected disk drive to track 0
HOME:
	mvi	A,0	; current track now zero
	sta	curTRK
	sta	FLOPtrk
	lxi	H,homeFLOP+1 ; point to drive number
	call	bseek	; share code with SETTRK
	ret
;----------
; Set track
SETTRK:
	lda	curTRK	; get current track
	lxi	H,seekFLOP+2 ; point to track number
	mov	M,A	; store into command
	dcx	H
	call	bseek
	ret
;
; Seek a track (used by SETTRK and HOME)
bseek:
	lda	curDSK	; get current disk
	mov	M,A	; store into command
	dcx	H
	call	onMOTOR ; turn on the drive motor
	mvi	B,2	; allow 2 tries because the 
			; commendable NEC chip returns 
			; an error if a recalibrate 
			; requires more than 77 steps.
			; Fox-floppies may need 80.
reseek: mov	D,H	; save ptr to seek command
	mov	E,L
	call	COMMAND ; seek

bsense: lxi	H,IsenseFLOP
	call	COMMAND ; sense interrupt status
	call	RESULT
	mov	H,D	; restore ptr to seek command
	mov	L,E
	lda	FLOPstat; get first result byte
	bit	3,A	; check ready bit
	jrnz	reseek	; jump if not ready
	bit	5,A	; check whether seek is
	jrz	bsense	; completed, loop til yes

	ani	0D0h	; mask off bit 5 (and 0-3)
	jrnz	..ugh	; something remains.
	lxi	B,stepset; delay ms for step settle
	call	DELAY
	ret
..ugh:
	djnz	reseek	; fault- may indicate we were
			; >77 tracks out. Retry ONCE.
	lxi	B,FLOPtrk
HOMEerr:call	IOerr
	ret

	.sbttl	'Floppy Format Routines'
	.page	
;----------
; Format the entire disk double density
; Magic Number Area
lasTRK	==	79	; highest track number
$8ksect ==	6
$4ksect ==	5
SperT	==	16	; since sectors are 256 bytes.
gapDD	==	35h	; value found empirically.  Gap
biggap	==	74h	;  size affects reliability. We
			;  use a larger one in format
			;  than in read.
formDD	==	4Dh
FORMdlay==	0B0h ; works in FORMAT5 so use it here	

GOformat:
	lxi	H,0	 ; initialize retry counters
	shld	scansoft
	shld	scanhard

	lxi	H,FORMstr ; print start-up message
	call	PRTMSG


..form:
	lxi	H,WCRqst
	call	WAITCR	; wait for return
	cpi	escape
	rz		; escape requested

	lxi	H,formhead
	call	PRTMSG
	call	HOME   ; recalibrate the disk drive

..1:	mvi	B,2	; do both sides
..frmX: push	B
..set:	call	SETTRK	; Absolute certainty in track
			; position requires doing this
			; every time, even though that
			; may mean twice per cylinder
	lda	FLOPstat+1
	lxi	H,curTRK
	cmp	M
	jrz	..go1
	lxi	H,BSfrm
	call	PRTMSG
	jmpr	..set

..go1:	mvi	D,$4ksect; large sectors
	mvi	B,1	 ; 1 sect per track
	call	TAB1TK	 ; 
	call	FORM1TK  ; format a track
..go3:	mvi	C,'f'	 ; put small or capital f,
	call	putlet	 ; according as we are on the
	call	FLIPSIDE ; first or second side.
	pop	B
	djnz	..frmX

	call	INRTRK
	cpi	lasTRK+1 ; are all tracks formatted
	jrnz	..1	 ; if not, loop

	call	HOME
	call	SCANall   ; verify them
	lxi	H,FORMdon ; say 'done'
	call	PRTMSG
	call	offMOTOR
	call	PRTfail ; print errors
	jmp	..form

;----------
SCANall:
	mvi	C,bs
	call	CONOUT
	mvi	C,cr
	call	CONOUT
	call	fillE5
	xra	A
	sta	FLOPsid ; start on side 0
	sta	FLOPtrk ; start on track 0

..scan: call	..sc80 
	jrz	..oops	; done after odd #!
	call	..sc80
	rz		; done ok.
	jmpr	..scan

..oops: lxi	H,f$error
	call	PRTMSG
	ret
;--
..sc80:
	xra	A	; clear flags
	lxi	H,FLOPdsk
	res	2,M
	lda	FLOPsid ; make sure dsk
	ral		; agrees with sid.
	ral		; (NEXTf incrs sid
	add	M	; but not dsk.)
	mov	M,A	
	sta	curDSK

..seek: call	SEEK	; get there
	call	SENSE	; see if ready
	jrnz	..seek
	lxi	B,stepset
	call	DELAY
	lda	FLOPstat+1 ; cyl info
	lxi	H,FLOPtrk
	cmp	M
	jrz	..go1
	lxi	H,bsscn
	call	PRTMSG
	jmpr	..seek

..go1:	mvi	B,64	; tries
	xra	A	; disable errmsgs
	sta	prtflag

..go2:	push	B
	call	COMPtrk ; see what ID info says
	call	SCANf	; see what data's like
	pop	B
	jrz	..good
	lhld	scansoft
	inx	H
	shld	scansoft
	djnz	..go2	; retry a few times

	lhld	scanhard
	inx	H
	shld	scanhard
	ori	0FFh	; bad, bad
..good: mvi	C,'v'
	lda	FLOPdsk
	call	putlet
	lxi	B,FORMdlay   ; oughta be enough
	call	DELAY
	ori	0FFh
	sta	prtflag
	call	NEXTf
	ret

;----------
; Put a letter on the screen, lowercase
; if side 0, backsp+uppercase if side 1,
; but x if bad.
; Regs in:  C ==letter, lower case
;	    Z set if good, NZ if bad
putlet:
	jrz	..go
	mvi	C,'x'
..go:	lda	curDSK
	bit	2,A
	jrz	..1	; jmpr if side 0
	push	B
	mvi	C,bs
	call	CONOUT
	pop	B
	mvi	A,0DFh	  ; upper case
	ana	C
	mov	C,A
..1:	call	CONOUT
	ret

;----------
fillE5:
	lxi	H,iobuff
	lxi	B,lenbuff
..loop: mvi	M,0E5h
	inx	H
	dcx	B
	mov	A,B
	ora	C
	jrnz	..loop
	ret  

;----------
; Set up table for a track.
; Regs in:  D = code for sector size
;	    B = sectors per track
TAB1TK:
	lxi	H,bufFLOP
	xra	A
..sect: inr	A	; start w/ #1
	call	TAB1SEC
	cmp	B
	jrnz	..sect
	ret
;----------
; Lay down a sector's table
; Regs in:   A = sector number
;	    HL = ptr to track place
; Regs out:  A = sector number
;	    HL = ptr to NEXT track place
TAB1SEC:
	push	PSW	; save sector
	lxi	H,bufFLOP
	lda	curTRK
	mov	M,A	; fill in TRACK
	inx	H
	lda	curDSK	; fill in HEAD
	rar
	rar
	ani	01
	mov	M,A	; (head)
	inx	H
	pop	PSW
	mov	M,A	; fill in SECTOR
	inx	H
	mov	M,D	 ; BYTES/SECTOR
	inx	H
	ret
;----------
; Format an entire track
FORM1TK:
;
; Set up the DMA chip
	mvi	A,0	; multiplex Floppy to DMA
	out	PIOAD
	lxi	H,DMAfmt
	lxi	B,DMAfm$<8+DMA
	outir		; program the chip
;
;
; Set up the floppy format command
..1:	lda	curDSK	; set up current disk
	sta	FORMdsk
	mov	A,D	; set up floppy density
	sta	FORMmod 
	mvi	A,formDD; MFM for double density
	sta	FORMcom ; format command
	mvi	A,biggap ; set gap size
	sta	FORMgap
	mvi	A,1	; sectors per track
	sta	FORMsec
..3:
	lxi	H,FORMcom
	call	EXflop
	push	PSW
	lxi	B,FORMdlay  
	call	DELAY
	pop	PSW
	ret
;
;----------------
; Flip heads on format
FLIPSIDE:
	lda	curDSK
	xri	04     ; switch sides
	sta	FORMdsk
	sta	curDSK
	ret
;-----------------
; Increment to next track
INRTRK:  
	lda	curTRK
	inr	A
	sta	curTRK
	sta	FLOPtrk
	ret		; return
;--------
; Execute the floppy command
EXflop: ei		; turn on interupts
	call	COMMAND
	call	RESULT
	lda	FLOPstat; get first result byte
	ani	0C0h	; mask out top 2 bits
	cnz	IOERR	; jump if abnormal termination
	lda	FLOPstat+1 ; get second result byte
	ani	33h	; check for CRC error
	cnz	IOERR
	ret
	.page
;----------
; See whether the track we're on is formatted ok
COMPtrk:
	mvi	B,1		; tries  
cmp1:	push	B
	call	CHEKID
	jrnz	..bad
	lxi	H,FLOPstat+3	; cylinder
	lda	FLOPtrk
	cmp	M
	jrnz	..bad
	inx	H	; head
	lda	FLOPsid
	cmp	M
	jrnz	..bad
	inx	H
	mvi	A,1	; sector
	jrnz	..bad
	pop	B
	ret		; ok

..bad:	lxi	B,FLOPtrk
FORMerr:call	IOerr
	pop	B	; retries
	dcr	B
	rz		; give up.
	push	B
	lxi	H,retry$
	call	PRTMSG
	pop	B
	jmpr	cmp1
;----------
; Sense what's happening to ID fields
CHEKID:
	lxi	H,readID
	push	H
	res	6,M
	lda	FLOPtrk
	ora	A
	jrz	..shoo
	set	6,M
..shoo: inx	H
	lda	FLOPdsk
	mov	M,A
	sub	A
	pop	H
	call	COMMAND
	call	RESULT
	ret
;---------------
; Print format & verify failure count
PRTfail:
	lxi	H,formsoft
	xra	A
	mvi	B,8	; check next 8 bytes
..chek: ora	M
	jrnz	..oops	; something nonzero
	inx	H
	djnz	..chek
	ret		; nope, all ok

..oops: lxi	H,prtfl1; "Format retries: "
	call	PRTMSG
	lhld	formsoft
	call	prtword
	lxi	H,prtfl2; "Format failures: "
	call	PRTMSG
	lhld	formhard
	call	prtword
	lxi	H,prtfl3; "Verify retries: "
	call	PRTMSG
	lhld	scansoft
	call	prtword
	lxi	H,prtfl4; "Verify failures: "
	call	PRTMSG
	lhld	scanhard
	call	prtword
	ret
;-----------
prtword:
	push	H
	mov	A,H
	call	prtbyt
	pop	H
	mov	A,L
	call	prtbyt
	ret

	.page
	.sbttl	'Utility subroutines'
;----------
; Convert binary to ASCII
;  Reg in:   A	= number
;  Reg out:  HL = ASCII representation (00-99)
CVTDEC:
	ora	A
	jrz	..ok
	mov	B,A
	sub	A
..adj:	inr	A
	daa
	djnz	..adj
..ok:	push	PSW
	rlc
	rlc
	rlc
	rlc
	call	..nib
	mov	L,A
	pop	PSW
	call	..nib
	mov	H,A
	ret
..nib:	ani	0Fh
	adi	'0'
	cpi	'9'+1
	rc
	adi	'A'-('9'+1)
	ret
;----------
; Divide HL by BC, put result in DE and remainder in HL
;  (Use a very simple algorithm - speed isnt important)
DIVIDE:
	lxi	D,0
	ora	A
..div:	dsbc	B	; subtract BC from HL
	jrc	..ret
	inx	D	; increment result each pass
	rz
	jmpr	..div
..ret:	dad	B	; compute correct remainder
	ret
;----------
; Process an I/O error
;
IOerr:
	pop	D	; save address of caller
	push	D	; & push it on down
	push	PSW	; save what caused call

	lda	prtflag ; see if ok to do this
	ora	A
	jrz	..bye	; nope.

	push	B	; save pointer to THS
	push	B	; save it some more
	dcx	D
	dcx	D
	dcx	D
;
; Search for error in table
	lxi	H,errtab
	mvi	B,numerr
..look: mov	A,M
	cmp	E
	inx	H
	mov	A,M
	inx	H
	jrnz	..again
	cmp	D
	jrz	..found
..again:inx	H
	inx	H
	inx	H
	inx	H
	djnz	..look
;
; Print an error message
..found:push	H	; save address of error message
	lxi	H,ermsg1; print heading message
	call	PRTMSG
	pop	H
	mvi	B,4	
..prt:	mov	C,M
	push	H
	call	CONOUT	; print 4 letter error code
	pop	H
	inx	H
	djnz	..prt
	lxi	H,ermsg2
	call	PRTMSG	; print trailing message
;
; Print track, head, sector of error
	pop	B
	ldax	B
	push	B
	call	PRTBYT	; print track
	lxi	H,hmsg
	call	PRTMSG
	pop	B
	inx	B
	ldax	B
	push	B
	call	PRTBYT	; print head
	lxi	H,smsg
	call	PRTMSG
	pop	B
	inx	B
	ldax	B
	call	PRTBYT	; print sector
	call	FLOPdump; print floppy buffer
	pop	B	; restore THS pointer
..bye:	pop	PSW	; restore cause of call
	ret
;----------
; Error table
errtab:
	.word	SYNCerr ; FDC out of sync with CPU
	.ascii	'SYNC'
	.word	DATAerr ; DATA CRC error
	.ascii	'DATA'
	.word	TRACerr ; head positioned on wrong trk
	.ascii	'TRAC'
	.word	ENDTerr ; access beyond end-of-track
	.ascii	'ENDT'
	.word	IDerr	; ID CRC error
	.ascii	' ID '
	.word	ORUNerr ; FDC not service in time
	.ascii	'ORUN'
	.word	SECTerr ; sector not found
	.ascii	'SECT'
	.word	PROTerr ; disk write-protected
	.ascii	'PROT'
	.word	DENSerr ; missing ID address mark
	.ascii	'DENS'
	.word	MADRerr ; missing DATA address mark
	.ascii	'MADR'
	.word	HOMEerr ; cannot home for format
	.ascii	'HOME'
	.word	FORMerr ; format went wrong
	.ascii	'FORM'
	.word	NRDYerr ; disk popped
	.ascii	'OOPS'
	.word	COMPerr ; backup compare error
	.ascii	'COMP'
	.word	HARDerr ; hard disk data error
	.ascii	'HARD'
	.word	HARCerr ; hard disk command error
	.ascii	'HARC'
	.word	HARSerr ; hard disk sector error
	.ascii	'HARS'
	.word	HARFerr ; hard disk fault
	.ascii	'HARF'
numerr	=	(.-errtab)/6
	.ascii	'I/O '

;---------------
; dump oot the floppy buffer
; preserve the PSW
FLOPdump:
	push	PSW
	lxi	H,FBUFmsg
	call	PRTMSG
	lxi	H,FLOPcom
	mvi	B,17

..dump: push	B
	mov	A,M
	push	H
	call	PRTBYT
	mvi	C,' '
	call	CONOUT
	pop	H
	inx	H
	pop	B
	djnz	..dump

	pop	PSW
	ret
;-------------------------
; Get the date from the BIOS (if possible)
;  else ask the user.
getdate:
	lda	year
	cpi	83
	jc	..ask
	lda	month
	cpi	13
	jnc	..ask
	call	trandate
	ret
..ask:
	lxi	H,date$
	call	PRTMSG
	call	INSTRING; get date
	lxi	H,conbuf
	lxi	D,date%
	lxi	B,8
	ldir		; save date for later
	lxi	H,time$
	call	PRTMSG
	call	INSTRING; get time
	lxi	H,conbuf
	lxi	D,time%
	lxi	B,5
	ldir		; save time for later
	ret
;----------
; translate BIOS date/time into
; the printable format.
trandate:
	lda	month
	call	cvtdec
	shld	date%
	lda	day
	call	cvtdec
	shld	date%+3
	lda	year
	call	cvtdec
	shld	date%+6

	lda	hour	
	call	cvtdec
	shld	time%
	lda	minute
	call	cvtdec
	shld	time%+3
	ret
;----------
; Wait for carriage return
;  Regs in:   HL = message to print before waiting
;  Regs out:  A  = cr or cntl-C
;  Destroyed: all
WAITCR:
	call	PRTMSG
..wait: call	CONIN
	cpi	escape	; wait for escape
	rz
	cpi	ctrlC	; or control-C
	jz	badbye	;  (warm-boot if ctrl-C)
	cpi	cr	; or carriage-return
	jrnz	..wait
	lxi	H,crlf$
	call	PRTMSG
	mvi	A,cr
	ret
;----------
; Print a message on the console
;  Regs in:   HL = address of message
;  Regs out:  none
;  Destroyed: all
PRTMSG:
	mov	A,M
	ora	A
	rz
	mov	C,A
	push	H
	call	CONOUT
	pop	H
	inx	H
	jmpr	PRTMSG
;----------
; Print a byte in hex format
;  Regs in:   A = byte to be printed
PRTBYT:
	push	PSW
	rrc
	rrc
	rrc
	rrc
	call	..nib	; print high nibble
	pop	PSW
..nib:	ani	0Fh
	adi	'0'
	cpi	'9'+1
	jrc	..out
	adi	'A'-('9'+1)
..out:	mov	C,A
	jmp	CONOUT
;------------
; print a blank
SPACE:
	mvi	C,' '
	call	CONOUT
	ret
;----------
; Input a string from the console
;
	.byte	lencon,0
conbuf: .blkb	10	; console buffer
lencon	==	.-conbuf; length of console buffer
INSTRING:
	lxi	H,conbuf
	mvi	M," "
	lxi	D,conbuf+1
	lxi	B,lencon-1
	ldir		; blank-fill console buffer
	lxi	D,conbuf-2
	mvi	C,10
	call	BDOS	; ask the BDOS to do the work
	lda	conbuf-1
	ora	A
	rz
;
; Convert lower to upper case
	mov	B,A	; B = number of characters
	lxi	H,conbuf
..cvt:	mov	A,M
	cpi	'a'
	jrc	..next
	cpi	'z'+1
	jrnc	..next
	sui	'a'-'A'
	mov	M,A
..next: inx	H
	djnz	..cvt
	ret
;----------
; Output a character to the console
;  Regs in:   C = character
;  Regs out:  none
;  Destroyed: all
CONOUT:
	lhld	1
	lxi	D,09h
	dad	D
	pchl		; jump directly to the BIOS
;----------
; Input a character from the console
;  Regs in:   none
;  Regs out:  A = character
;  Destroyed: all
CONIN:
	call	noMAST
	lhld	1
	lxi	D,06h
	dad	D
	pchl		; jump directly to the BIOS

;----------
; Select a disk
; Regs in:  C = the drive to select
; Regs out: none
; Destroyed: all

seldsk:
	call	noMAST
	lhld	1
	lxi	D,18h
	dad	D
	pchl

;----------
; Get the hard disk system status
; Regs in: BC = buffer address
; Regs out: none
; Destroyed: all

hdstat:
	call	noMAST
	lhld	1
	lxi	D,81h
	dad	D
	pchl

;----------
; Get the information about a disk
; Regs in:  none
; Regs out: HL = ptr to unit number
; Destroyed: all

cpmmap:
	lhld	1
	lxi	D,60h
	dad	D
	pchl

	.page
	.sbttl	'volume info routines'

;---------------------------------------------
;	set the x register to point to volume
;	info for volume in volnum
;	regs in: none
;	regs out: x points to volume info
;	regs destroyed: a,de,hl
setvolptr:
	lda	volnum
	slar	A	;*2
	mov	E,A
	mvi	D,0
	lxi	H,vliindex
	dad	D
	mov	E,m
	inx	H
	mov	D,M
	push	D
	pop	X
	ret

;
;	routine to print volume label
prtvol:
	mvi	C,' '
	call	conout
	mov	C,vlivollabel(x)
	call	conout
	mov	C,vlivollabel+1(x)
	call	conout
	mov	C,vlivollabel+2(x)
	call	conout
	mov	C,vlivollabel+3(x)
	call	conout
	mov	C,vlivollabel+4(x)
	call	conout
	mov	C,vlivollabel+5(x)
	call	conout
	mov	C,vlivollabel+6(x)
	call	conout
	mov	C,vlivollabel+7(x)
	call	conout
	mov	C,vlivollabel+8(x)
	call	conout
	mov	C,vlivollabel+9(x)
	call	conout
	mvi	C,' '
	call	conout
	ret

	.sbttl	'DMA commands'
	.page

; Read 4K from floppy to memory
DMArdf:
	.byte	0C3h
	.byte	0C7h
	.byte	0CBh
	.byte	7Dh
rdfadr: .word	0	; DMA address
rdflen: .word	0	; DMA length - 1
	.byte	14h
	.byte	28h
	.byte	95h
	.byte	FLOP
	.byte	12h
	   .byte   0	   ; DMA vector
	.byte	9Ah
	.byte	0CFh
	.byte	1
	.byte	0CFh
	.byte	0ABh
	.byte	87h
lenrdf	==	.-DMArdf
;----------
; Write 4K from memory to floppy
DMAwrf:
	.byte	0C3h
	.byte	0C7h
	.byte	0CBh
	.byte	79h
wrfadr: .word	0	; DMA address
wrflen: .word	0	; DMA length - 1
	.byte	14h
	.byte	28h
	.byte	95h
	.byte	FLOP
	.byte	12h
	.byte	0	; DMA vector
	.byte	9Ah
	.byte	0CFh
	.byte	5
	.byte	0CFh
	.byte	0ABh
	.byte	87h
lenwrf	==	.-DMAwrf
	.page
;----------
; DMA commands for FORMAT command
DMAfmt:
	.byte	0C3h	; master reset
	.byte	0C7h	; reset port A
	.byte	0CBh	; reset port B
	.byte	079h	; read from memory
	.word	bufFLOP ; address
	.word	103	; size - 1
	.byte	14h	; port A inc, memory
	.byte	28h	; port B fixed, I/O
	.byte	95h	; byte mode
	.byte	FLOP	; port B
	.byte	12h	; interrupt at end of block
	.byte	00	; interrupt vector
	.byte	9Ah	; stop at end of block
	.byte	0CFh	; load starting address
	.byte	05	; write to I/O
	.byte	0CFh	; load starting address
	.byte	0ABh	; enable interrupts
	.byte	87h	; enable DMA
DMAfm$	=	.-DMAfmt
	.xlist
	.word	6F47h,2064h,6173h
	.word	6576h,7420h,6568h
	.word	5120h,6575h,6E65h
	.list
bufFLOP =	3000h	; location of I/O buffer
;---------------------------
; Process an interrupt from the DMA chip
DMAflag:.byte	0	; non-zero signals DMA complete
DMAdone:
	push	PSW
	in	STOPFLOP; turn off floppy controller
	mvi	A,0C3h
	out	DMA	; reset the DMA chip
	sta	DMAflag ; signal DMA completion
	pop	PSW
	ei
	ret
;
	.page
	.sbttl 'Floppy controller commands'

;----------
; Floppy controller commands
IsenseFLOP:
	.byte	8	; sense interrupt status
	.byte	endcom

readID:
	.byte	0Ah
	.byte	0	; dsk
	.byte	endcom

specFLOP:
	.byte	3
	.byte	0AFh	; step rate & head unload
	.byte	02	; head load.
	.byte	endcom

; FORMAT command for Floppy Controller
FORMcom:
	.byte	formDD	; format command
FORMdsk:.byte	00	; curDSK
FORMmod:.byte	01	; 0=SD, 1=DD
FORMsec:.byte	01	; 1 sectors/track
FORMgap:.byte	biggap	; gap length
	.byte	0E5h	; fill byte
	.byte	endcom

	.sbttl	'Storage'
	.page
vliindex:
	.word	vli0
	.word	vli1
	.word	vli2
	.word	vli3
STATbuf:
	.blkb	8	;command status
FIRMWARE:
vli0:	.blkb	vlibufsiz
vli1:	.blkb	vlibufsiz
vli2:	.blkb	vlibufsiz
vli3:	.blkb	vlibufsiz
 
;
volnum: .blkb	1	;current volume
curDSK: .byte	0	; current physical drive
curTRK: .byte	0	; current physical track

formsoft:.word	0	; error counters for format
formhard:.word	0
scansoft:.word	0
scanhard:.word	0

endflg: .byte	0	; non-zero if beyond last sect
endsect:.byte	0,0,0	; last sect
maxsect:.byte	0,0,0	; max sector
badsect:.byte	0,0,0	; bad sector

loadsiz:.byte	0	; number of 256K blocks to load
count:	.byte	0	; diskette count
curpart:.blkb	8	; current load partition name
curdate:.blkb	21	; and its date & time of backup
flagflag:.byte	0	; flag to back up flagged partitions	
prtflag:.byte	0FFh   ; ok to print error messages in IOerr

SIZEtab:
	.ascii	[1]' 256K'
	.ascii	[2]' 512K'
	.ascii	[4]'1024K'
	.ascii	[8]'2048K'
	.ascii	[16]'4096K'
	.ascii	[32]'8192K'
	.ascii	[64]'  16M'
	.ascii	[128]'  32M'
TYPtab:
	.byte	00h, 0E5h, 80h	; cmi size
	.byte	00h, 0F0h, 00h	; miniscribe size
	.byte	00h,  00h, 00h
	.byte	00h,  00h, 00h
	.byte	00h, 0F0h, 00h	; rodime size
	.byte	00h, 0E5h, 80h	; imi size	
;----------
; Information on track 0, sector 3 of BACKUP diskette
;
info%:
	.blkb	128	; reserve one sector
	.loc	info%
	.ascii	[cr][lf]"Date: "
date%:	.ascii	"XX/XX/XX"
	.ascii	[cr][lf]"Time: "
time%:	.ascii	"XX:XX"
	.ascii	[cr][lf]"Partition: "
part%:	.ascii	"XXXXXXXX"
	.ascii	[cr][lf]"Size: "
size%:	.ascii	"XXXXX"
	.ascii	[cr][lf]"Diskette: "
count%: .ascii	"XX"
	.ascii	" of "
last%:	.ascii	"XX"
	.asciz	[cr][lf]
realsize:.byte	0	; multiples of 256K
realcoun:.byte	0	; diskette number
	.reloc	
	.page
	.sbttl	'Messages'	
;----------
; Messages

hiuser$:.ascii	[cr][lf]'Harddisk to Floppy '
	.ascii	'Backup Utility '
	.byte	version+'0','.',revision+'0',patch
	.ascii	[cr][lf]'(To abort, enter CTRL-C)'
	.asciz	[cr][lf]

badmast:.ascii	[cr][lf]'This program cannot be run on'
	.ascii	' the Master!'[cr][lf]'You can run 5HD'
	.ascii	'BACK on this machine if you'[cr][lf]
	.asciz	'boot from a floppy disk.'[cr][lf]

PutFlop$:.ascii  [cr][lf]'Make sure there is a CP/M flo'
	.asciz	'ppy in the drive, then hit return:'

badctl$:.ascii	[cr][lf][lf]
	.asciz	'Bad controller firmware'
select$:.ascii	[cr][lf][lf][lf]
	.ascii	'Select one of the following options:'
	.ascii	[cr][lf]'opt  meaning  action'
	.ascii	[cr][lf]' B   BACKUP   Back up from hard disk to floppy'
	.ascii	[cr][lf]' L   LOAD     Load from floppy to hard disk'
	.ascii	[cr][lf]' D   DISPLAY  Display hard disk partition names'
	.ascii	[cr][lf]' F   FORMAT   Format floppy for backup'
	.ascii	[cr][lf]'ESC  -------  Exit to CP/M'
	.ascii	[cr][lf]
	.asciz	[cr][lf]'Enter choice: '

DHmsg$: .ascii	[cr][lf][lf]"Partitions on this volume"
	.asciz	":"[cr][lf][lf]

BACKmenu:
	.ascii	[cr][lf][lf][lf]
	.ascii	'BACKING UP TO FLOPPIES FROM HARD DISK'
	.ascii	[cr][lf]'Select one of the following options:'
	.ascii	[cr][lf]'opt  meaning   action'
	.ascii	[cr][lf]' A   ALL       Back up all data partitions'
	.ascii	[cr][lf]' F   FLAGGED   Back up all flagged data partitions'
	.ascii	[cr][lf]' S   SELECT    Back up selected data partitions'
	.ascii	[cr][lf]' Z   ZERO      Back up Unit Zero (control area)'
	.ascii	[cr][lf]'ESC  ------    Return to main menu'	 
	.ascii	[cr][lf]
	.asciz	[cr][lf]'Enter choice: '

date$:	.asciz	[cr][lf]"Enter date: "
time$:	.asciz	[cr][lf]"Enter time: "

byemsg: .asciz	[cr][lf][lf]"Exiting gracefully to CP/M"
abortmsg:.asciz [cr][lf]"Return to CP/M requested"

noHDmsg:.asciz	[cr][lf]"Hard disk not operational"
xWAITmsg:.asciz  [cr]'Waiting for hard disk'
mbf1:	.asciz	[cr][lf]'Volumes present are:'
mbf2:	.asciz	[cr][lf]'Which volume do you wish to work with (0-3):'
mbf3:	.asciz	[cr][lf]'Answer must be 0,1,2 or 3.'
mbf4:	.asciz	[cr][lf]'Volume is not present.'
mbf5:	.ascii	[cr][lf]'Bad firmware! Hard disk firm'
	.ascii	'ware must be initialized with 5HDHELP'
	.asciz	[cr][lf]'before hard disk can be used.'

partb$: .ascii	[cr][lf]"Enter partition name to back "
	.asciz	"up (ESC to end):"
nopart$:.asciz	[cr][lf]"*** Partition not found"[cr][lf]

diffname$:.ascii  [cr][lf]"*** Error - partition name "
	.ascii	"does not match name on first diskette."
	.asciz	[cr][lf]

diffdate$:.ascii [cr][lf]"*** Error - date & time of "
	.ascii "backup do not match date & time on "
	.asciz	"first diskette."[cr][lf]

badsiz$:.ascii	[cr][lf]"*** Error - partition size "
	.ascii	"does not match size in ALLOC table."
	.asciz	[cr][lf]

toobig: .ascii	[cr][lf]"*** Error - hard disk too "
	.ascii	"large for this program."
f$error:.asciz	[cr][lf]"*** Fatal internal bug."
death$: .ascii	[cr][lf]"*** Error - Unrecognizable "
	.ascii		"size in ALLOC table."
	.ascii	[cr][lf]"    (Run ALLOC to assure the"
	.asciz		" table is correct.)"
badcou$:.ascii	[cr][lf]"*** Error - diskette number "
	.asciz	"does not match expected number."[cr][lf]

backing$:.ascii [cr][lf][lf]"Partition to back up is "
backpar:.asciz	"XXXXXXXX"
disks$: .ascii	[cr][lf]
last:	.asciz	'XX diskettes required'

newload$:.ascii [cr][lf][lf]
	.ascii	'Insert first diskette to load from.'
	.ascii	[cr][lf]'Type RETURN to begin loading,'
	.asciz	' ESC to abort.'

freshL$:.ascii [cr][lf][lf]
	.ascii	'Insert next diskette to load from.'
verifL$:.asciz	[cr][lf]'Type RETURN to load, ESC to abort.'

doneL$: .ascii	[cr][lf]'Done reloading from floppies.'
	.asciz	[cr][lf]
firmL$: .ascii	'***** This Control Area was backed up'
	.ascii	' from a different model hard disk.'
	.ascii	[cr][lf]'Check your manual to determine'
	.ascii	' whether there are any other steps you'
	.ascii	[cr][lf]'need to take, such as verifying'
	.ascii	' that the ALLOC table from that other '
	.ascii	[cr][lf]'disk will be valid on this one.'
	.asciz	[cr][lf]

newback$:.ascii [cr][lf][lf]
	.ascii	'Insert first diskette to back up onto.'
	.ascii	[cr][lf]'Type RETURN to begin backing '
	.asciz	'up, ESC to abort.'

freshB$: .ascii  [cr][lf][lf]'Insert next diskette '
	.ascii	'and type RETURN to continue backup, '
	.asciz	'ESC to abort.'

doneb$: .asciz	[cr][lf]'Done backing up.'[cr][lf]

crlf$:	.asciz	[cr][lf]

formhead:
	.ascii	[cr]'0               1               2       '
	.ascii	    '        3               4               '
	      .ascii  [cr]'0123456789ABCDEF0123456789ABCDEF01234567'
	.asciz	    '89ABCDEF0123456789ABCDEF0123456789ABCDEF'
FORMstr:.ascii	[cr][lf][lf]
	.ascii	'Note:  The diskette will be formatted with '
	.ascii	'1 sector per track.'[cr][lf]
	.asciz	'It can be read or written only by 5HDBACK.'
WCRqst: .asciz	[cr][lf]'Type return to format, ESC to abort:'
FORMdon:.asciz	[cr][lf]'FORMAT COMPLETE'

BSfrm:	.asciz	[cr][lf]"retrying bad seek in format"
BSscn:	.asciz	[cr][lf]"retrying bad seek in scan"
prtfl1: .asciz	[cr][lf]"Format retries:  "
prtfl2: .asciz	[cr][lf]"Format failures: "
prtfl3: .asciz	[cr][lf]"Verify retries:  "
prtfl4: .asciz	[cr][lf]"Verify failures: "


; Xebec error messages

retry$: .asciz	' - will retry'

qretry$:.ascii	[cr][lf]"Choose one of the following:"
	.ascii	[cr][lf]"   R = Retry this read"
	.ascii	[cr][lf]"   C = Continue copy (lose some data)"
	.ascii	[cr][lf]"   Q = Quit the program"
	.asciz	[cr][lf]"Your choice => "

xFATALerr:.ascii  [cr][lf]'*** XEBEC s1410 '
	  .asciz  'FATAL ERROR  --- addr, mode = '
xERR0str: .asciz  [cr][lf]'*** XEBEC s1410 error'
xERR1str: .asciz  [cr][lf]'combuf = '
xERR2str: .asciz  [cr][lf]'errbuf = '

; Floppy error messages

FBUFmsg:	.asciz	[cr][lf]"Floppy buffer: "
ermsg1: .asciz	[bell][cr][lf]'*** '
ermsg2: .asciz	' error T'
hmsg:	.asciz	' H'
smsg:	.asciz	' S'

;----------
; Buffer and stack area
; put stack on page boundary & make at least 32 levels
	.loc	(.+0FFh) & 0FF00h
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
stack	==	.
ALLOCtab==	stack
iobuff	==	ALLOCtab+1024
lenbuff ==	4096

	.end

 
	.byte	76h,76h,76h,76h,76h,76h,76h,76h 
stack	==	.
ALLOCtab==	stack
iobuff	==	ALLOCtab+1024
