;**************************************************************
; Cromemco 16FDC/64FDC Z-80-Compatible Floppy Disk Formatter  *
; By Martin Eberhard                                          *
;                                                             *
; REVISION HISTORY                                            *
; 1.04  28 May 2021  Martin Eberhard                          *
;   Acknowledge 64FDC compatibility too                       *
; 1.03  9 May 2021  Martin Eberhard                           *
;   Rename to be CroFORM                                      *
; 1.01  2 May 2021  Martin Eberhard                           *
;   Tighten up help menu                                      *
; 1.00  28 April 2021   Martin Eberhard                       *
;   Created from Eberhard's CCS Format rev 1.19               *
;**************************************************************
FALSE	equ	0
TRUE	equ	not FALSE
;----------------------
;This program's version
;----------------------
VERSION	equ	1	;major version number
VERSMIN	equ	04	;minor version number
UNDERC	equ	FALSE	;TRUE if version is under construction

;**************************************************************
; This program formats a variety of disks via the Cromemco
; 16FDC and 64FDC disk controllers. (See the appropriate
; Cromemco manual for details.) The 16FDC and 64FDC are both
; built around the Western Digital WD1793 disk controller chip,
; and supports any combination of
; the following disk types:
;   3.5, 5.25, and 8" drive
;   1- or 2-sided drives and disks
;   Single- or double-density disks
;
; 1-sided disks may be formatted in either a 1-sided or a
; 2-sided drive.
;
; The 16FDC and 64FDC disk controller boards require software
; to write and read disk data at the disk speed. This program
; does everything possible to write and verify as fast as
; possible. Even so, formatting a double-density maxi disk
; with a 2 MHz CPU is not quite possible. If you try to format
; a Double Density maxi disk with the CPU set at 2 MHz, an
; error will be reported.
;
; Step rates are correct for SA400L 1-sided "minidisk" drives,
; SA450 2-sided minidisk drives, SA800 1-sided 8" drives,
; SA850 2-sided 8" drives, and most 3.5" drives. These can be
; changed in the equates. 
;
; The default 8" format is IBM 3740-compliant, single-density,
; 128 bytes/sector, 26 sectors/track. The default 5.25" format
; is single-density, 128 bytes/sector, 18 sectors/track. The
; default 3.5" format is (PC-compatibe) double-density 512
; bytes/sector, 18 sectors/track.
;
; For 3.5" and 8" disks, the default is to format both sides if
; a 2-sided disk if one is detected once the disk is spun up.
; Since the 16FDCand 64FDC (as well as  5.25" drives) give no
; indication of sidedness, default (2-sided) formatting on a 1-
; sided drive will attempt to format both sides. This will
; result in a correctly  formatted disk, since side 0 gets
; formatted last. However, it will fail verification (If you
; enable verification), unless you also specify a 1-sided
; format when enabling verification on a 1-sided drive.
;
; If you specify double-density, then the default sector size
; for 5.25" and 8" drives is 256 bytes. You may specify 128,
; 256, or 512 bytes/sector for single-density disks, and 256,
; 512, or 1024  bytes/sector for double-density disks.
; Requesting a sector size that is incompatible with the density
; results in an error message.
;
; By default if track 0 is to be formatted, it is formatted
; as 128 bytes/sector, single-density. (On default 8" double-
; density disks, the resulting format will be IBM system 34
; compliant.) You can change the number of these 128 bytes/
; sector tracks with the /N option.
;
; The number of sectors/track comes from a table, indexed on
; the disk size, bytes/sector, and density.
;
; By default, this program will format the entire disk.
; However, you may specify a starting track and/or an ending
; track, as well as a particular side to format. If you type
; control-C while a disk is being formatted, the program will
; terminate (cleanly) after the current track formatting
; completes.
;
;Strategy
;
;  1. Gather input option parameter values from CP/M's command
;     line buffer
;  2. For any undefined parameter values, apply defaults
;  3. Validate input parameters with several bounds-checks
;  4. Spin the disk up and restore it to track 0
;  5. Discover what sort of disk it is - 3.5", 5.25" or 8"?
;     1-sided or 2-sided?
;  6. Assign defaults and validate option parameters that
;     depend on the type of disk discovered
;  7. If a double-density format is requested on an 8" disk,
;     validate that CPU is fast enough by timing instruction
;     cycles against the index pulses.
;  8. Get a pointer to one of the 4 format tables (5,25" or 8",
;     single-density or double-density)
;  9. Based on command line parameters and disk type, look up
;     sectors/track and the format buffer end address
; 10. Check if the buffer end address is still in the TPA by
;     comparing it to the address of the BIOS, found in page 0
;   10A. Input errors (including a CPU speed error) result in a
;        terse-but-meaningful error message followed by a
;        'usage' message that describes how to invoke this
;        program. Invoking this program with no options and no
;        specified disk results in this usage string without
;        any error message.
; 11. Print details of planned format and get user confirmation
;   11A. Non-confirmation causes program termination with a
;        message that says so.
; 12. Using the identified format table and the current track
;     and side numbers, fill the track buffer with the data for
;     the track. Note that this data will overwrite most of the
;     messages strings for this program. We won't need them
;     anymore, and we must keep things small to work in a 20K
;     system. (The track buffer can be as large as 9.5 kbytes.)
;     the track buffer gets all the data up to the track
;     postamble (just fill W/ 0FFh). for smaller track formats,
;     the end of the buffer gets filled with the postamble chr.
; 13. Load the head and restore selected drive in the correct
;     mode (5.25"/8" disk, motor on)
; 14. For each track:
;   14A. Check if it a system track. If so, set up for single-
;        density, 128 BPS. Otherwise, use input values.
;   14B. Seek the requested track
;   14C. Print track number on the screen.
;   14D. If side 1 or both side formatting is requested, select
;        side 1. Otherwise, select side 0.
;   14E. Using the WD1793 "write track" command and a tight
;        code loop, pop the data from the track buffer and
;        write it to the disk, until the end of the data in the
;        buffer. The disk controller is set up in autowait
;        mode, so there is no need to wait for it - the
;        hardware will stall on each write until it is ready to
;        accept the written byte.
;   14F. Immediately loop to write the track postamble
;        character (which was the last byte in the track
;        buffer) over and over to the disk until the disk
;        controller deasserts its busy bit, which happens at
;        the next sector pulse.
;   14G. Any disk errors (write protect, drive not ready, seek
;        error, etc.) cause a terse-but-meaningful error
;        message, and a termination of the format.
;   14h. If both sides are selected for format (no /S option),
;        then go back to 14E to format side 0.
;   14I. If this track is not the last one requested, then
;        step in one track, and go back to 14A to continue
;        formatting.
; 15. If requested, go back and reread every sector of every
;     track we formatted, checking for disk errors and also
;     checking the sector data. Report errors to the screen.
;     If too many sector errors, then stop verifying.
; 16. See if the user wants to format another disk.
;   16A. If so, ask the user to insert the new disk, and check
;        if it okay. Then go back to 12
; 17. Restore the drive, print 'format complete', and jump to
;     CP/M's warm boot. (If we formatted on drive A, then give
;     the user a chance to replace the CP/M disk first.)
;
; Once per track (during format and verify), check if the user
; typed a control-C. If so, abort with message.
;
; Most loops are designed so as not to hang forever. But if the
; format track-write loop itself hangs, there is no timeout -
; there simply isn't enough time in the loop to check for
; timeout while also keeping the hungry WD1793 disk controller
; satisfied. We are therefore dependent on the WD1793 to
; timeout and issue a 'drive not ready' error if e.g. the disk
; gets ejected.
;
;RAM requirements:
;
; All input processing is in a subroutine at the end of the
; program, such that it gets overwritten by the track buffer
; once the input has been validated, and that code is no longer
; needed. Additionally, this program makes no BDOS calls.
; Instead, all console I/O is through BIOS calls, so that
; CPM's BDOS (AND CCP) can be overwritten. We do all this so
; the format program can be used with all disk types,
; particularly the largest (3.5" disks), while requiring
; relatively little system RAM.
;
; The last RAM address used by format is the end of the track
; buffer. The largest track buffer ends at address'BL8SD+TRKBUF',
; which is about 13,500. Assuming  large (3K-byte) BIOS,
; this program will work in a system with as little as 17
; kbytes of RAM (implying a 15K-byte CP/M build). For all
; drive types except 3.5" drives, 16K (and a 14K CPM build) is
; sufficient.
;
; If there is not enough room for any given track format, this
; program will detect the problem, notify the user, and exit
; without misbehaving.
;
; Written to assemble with Digital Research's ASM.COM.
; Z-80 instructions are done the hard way with db's and dw's.
;**************************************************************
;-------------------
;Default Disk Values
;-------------------
DMAXT8	equ	76	;max track number for 8" disks
DMAXT5	equ	39	;max track for 5.25" minidisks
DMTQH3	equ	79	;Max track for QD, HD, and 3.5" disks
DBPQH3	equ	2	;default BPS for QD, HD, & 3.5" disks
MAXDRV	equ	'D'	;max drive letter
MAXERRS	equ	11	;max allowed verify errors+1
;------------
;CP/M Equates
;------------
WBOOT	equ	0000h		;jump to BIOS warm boot
WBOOTA	equ	WBOOT+1		;address of BIOS
CDISK	equ	0004h		;login drive and current user

COMBUF	equ	0080h		;command line parameters buffer
COMEND	equ	COMBUF+80	;max chrs in buffer
;---------------------------------------------------------
;BIOS Entry Points, relative to the base address in WBOOTA
;---------------------------------------------------------
CONST	equ	06h		;console status
CONIN	equ	09h		;console input into reg a
CONOUT	equ	0Ch		;console output reg c
;-------------------------------------
;16FDC & 64FDC Disk Controller Equates
;-------------------------------------
ADCMD	equ	4		;Auxiliary disk command
ASTAT	equ	4		;Auxiliary disk status
DSTAT	equ	30h		;disk stat port
DCMMD	equ	DSTAT		;disk command port
DTRCK	equ	DSTAT+1		;track port
DSCTR	equ	DSTAT+2		;sector port
DDATA	equ	DSTAT+3		;data port
DFLAG	equ	DSTAT+4		;disk flag port
DCTRL	equ	DSTAT+4		;disk ctrl port

;WD1793 commands

RSTRCM	equ	00000000b	;(I) restore
SEEKCM	equ	00010000b	;(I) seek
STEPCM	equ	00100000b	;(I) step
STPICM	equ	01000000b	;(I) step-in
STPOCM	equ	01100000b	;(I) step out
RSECCM	equ	10000000b	;(II) read sector
WSECCM	equ	10100000b	;(II) write sector
RADRCM	equ	11000000b	;(III) read addr
RTRKCM	equ	11100000b	;(III) read track
WTRKCM	equ	11110000b	;(III) write track
FINTCM	equ	11010000b	;(IV) force interrupt

;WD1793 command options (1st letter matches WD1793 data sheet)

RS03MS	equ	00000000b	;(I) 3 mS step rate
RS06MS	equ	00000001b	;(I) 6 mS step rate
RS10MS	equ	00000010b	;(I) 10 mS step rate
RS15MS	equ	00000011b	;(I) 15 mS step rate
VERTRK	equ	00000100b	;(I) verify on dest track
HDLOAD	equ	00001000b	;(I) head load
UPDTTR	equ	00010000b	;(I) update track reg
F1SCF	equ	00000010b	;(II) side compare flag
F2SSF	equ	00001000b	;(II) side select flag
MULTCM	equ	00010000b	;(II) read/write multiple
ED15MS	equ	00000100b	;(II & III) settling delay

;WD1793 step rates, tailored to meet drive requirements:

; Value    5.25"    3.5" & 8"
; RS03MS    6 mS      3 mS
; RS06MS   12 mS      6 mS
; RS10MS   20 mS     10 mS
; RS15MS   30 mS     15 mS

STEP3	equ	RS03MS		;3.5" drive step rate
STEP5	equ	RS10MS		;SA400L step rate=20 mS
				;SA450 (2-sided) is the same
				;(minidisk mode doubles RS10MS)
STEP8	equ	RS10MS		;SA800 step rate=8 mS
STP8D	equ	RS10MS		;SA851 2-sided step rate=3 mS
				;but PerSci 299B step rate=10 mS
RSMASK	equ	11111100b	;mask to remove step rate

;Composite WD1793 commands used in this program

CCSEEK	equ	SEEKCM+HDLOAD		;seek command
CCSEEKV	equ	CCSEEK+VERTRK		;seek command with verify
CCSTEPI	equ	STPICM+UPDTTR+HDLOAD	;step-in command
CCWTRAC	equ	WTRKCM+ED15MS		;write track command
CCRDSEC	equ	RSECCM+F1SCF		;read sect W/ side compare
CCRDADD	equ	RADRCM+ED15MS		;read addr command
CCRSTOR	equ	RSTRCM+HDLOAD		;restore

;Control register 1 (DCTRL) output bits

DCDS1	equ	00000001b	;drive select 1
DCDS2	equ	00000010b	;drive select 2
DCDS3	equ	00000100b	;drive select 3
DCDS4	equ	00001000b	;drive select 4
DCMAXI	equ	00010000b	;0=minidisk (5.25") drive
DCMOTO	equ	00100000b	;motor on
DCDDEN	equ	01000000b	;double density enable
DCAUTOW	equ	10000000b	;autowait

DCDSMSK	equ	DCDS1+DCDS2+DCDS3+DCDS4	;drive select mask

;Auxiliary Disk Command (ADCMD) output bits
;(All bits are active-low)

ADSIDE0	equ	00000010b	;0=side 1, 1=side 0
ADCTRL0	equ	00000100b	;-Control Out (test)
ADRSTOR	equ	00001000b	;-Restore (PerSci, 16FDC only)
ADFSEKN	equ	00010000b	;-Fast Seek (PerSci, 16FDC only)
ADDSO	equ	00100000b	;-Drive Select Override (test)
ADEJECT	equ	01000000b	;-Remote eject (PerSci, 16FDC only)

;Composite ADCMD values used to select disk side

SIDE0	equ	0FFh		 ;Needs to be <>0
SIDE1	equ	0FFh xor ADSIDE0 ;Needs to be <>0

;WD1793 status (DSTAT) after a command

SBUSY	equ	00000001b	;(all) busy
SCRCERR	equ	00001000b	;(all) CRC error
SWRPROT	equ	01000000b	;(all) write protect
SNOTRDY	equ	10000000b	;(all) drive not ready
SINDEX	equ	00000010b	;(I) index
STRACK0	equ	00000100b	;(I) track 0
SSEEKER	equ	00010000b	;(I) seek error
SHEADLD	equ	00100000b	;(I) head loaded
SWFAULT	equ	00100000b	;(write cmds) write fault
SDATRQ	equ	00000010b	;(II & III) data req state
SLOSTD	equ	00000100b	;(II & III) lost data error
SRNFER	equ	00010000b	;(II & III) rec not found error
SRCTYF	equ	00100000b	;(II & III) record type fault

;Disk Flag (DFLAG) input bits

DEOJ	equ	00000001b	;WD1793 INTRQ signal, active high
DAWTO	equ	00000010b	;Autowait Timeout
DMOTO	equ	00000100b	;Motor Timeout
DMOTON	equ	00001000b	;Motor On
DINHIN	equ	00010000b	;-Inhibit Init
DFSHLD	equ	00100000b	;WD1793 HLD, active high
DFAUTOB	equ	01000000b	;autoboot jumper, 0=autoboot
DFSDRQ	equ	10000000b	;WD1793 DRQ signal, 1=ready

;Auxiliary Status Register (ASTAT) input bits

BSW8	equ	00000001b	;Switch 8
BSW7	equ	00000010b	;Switch 7
BSW6	equ	00000100b	;Switch 6
BSW5	equ	00001000b	;Switch 5
BSIP	equ	01000000b	;Seek in Progress (PerSci, 16FDC only)
BRTC	equ	10000000b	;512 m,Sec reat-time clock
;-------------------------------------------------------------
;Buffer Lengths
;Each entry is the maximum number of bytes needed in the disk
;buffer for given disk size (8",5.25",3.5") & density (single
;or double). The actual number of bytes for the track header,
;sector preamble, sector data, and sector postamble varies
;depending on the density and bytes/sector specified by the
;user, and the associated sectors/track. The number here is
;the largest value for all possible choices of bytes/sector
;(plus a few). The size of the largest of these will
;determine how much memory this program will require. The
;others don't really matter, so long as they are big enough.
;(hence the round numbers...) However, they must be shorter
;than the total track length, including the track postamble,
;so that we don't write too many bytes to the WD1793. The
;buffer-fill routine will fill the tail end (when the WD1793
;interrupts out - when the index pulse comes) with the track
;postamble character. The format subroutine will continue to
;write the track postamble character even after it has
;exhausted this buffer, until the WD1793 releases its busy
;bit, indicating that the track write is done.
;
;The values for these buffer lengths must be multiples of 4
;-------------------------------------------------------------
BL8SD:	equ	4916	;8" (and HD) single-density
BL8DD:	equ	9772	;8" (and HD) double-density
BL5SD:	equ	3084	;5.25" (and QD) single-density
BL5DD:	equ	6052	;5.25" (and QD) double-density
BL3SD:	equ	5844	;3.5" double density
BL3DD:	equ	11532	;3.5" double density
;-------------
;ASCII Equates
;-------------
CTRLC	equ	3		;Control-C
BS	equ	8		;backspace
CR	equ	0Dh		;carriage return
LF	equ	0Ah		;line feed
DEL	equ	7Fh		;delete
;-----------------------
;Z80 instruction opcodes
;-----------------------
JR	equ	18h
JRNZ	equ	20h
JRZ	equ	28h
JRNC	equ	30h
JRC	equ	38h
DJNZ	equ	10h

;2-byte instructions have bytes reversed for dw

BIT0A	equ	047CBh		;BIT 0,a
BIT4A	equ	067CBh		;BIT 4,a
BIT5A	equ	06FCBh		;BIT 5,a
BIT6A	equ	077CBh		;BIT 6,a
BIT1B	equ	048CBh		;BIT 1,b
BIT5B	equ	068CBh		;BIT 5,b
BIT6B	equ	070CBh		;BIT 6,b
BIT7B	equ	078CBh		;BIT 7.b
BIT4C	equ	061CBh		;BIT 4,c
BIT5C	equ	069CBh		;BIT 5,c	
BIT6C	equ	071CBh		;BIT 6,c	
BIT0D	equ	042CBh		;BIT 0,d
BIT5D	equ	06ACBh		;BIT 5,d
BIT6D	equ	072CBh		;BIT 6,d
BIT2M	equ	056CBh		;BIT 2,(hl)
BIT3M	equ	05ECBh		;BIT 3,(hl)
BIT4M	equ	066CBh		;BIT 4,(hl)
BIT5M	equ	06ECBh		;BIT 5,(hl)
BIT6M	equ	076CBh		;BIT 6,(hl)
BIT7M	equ	07ECBh		;BIT 7,(hl)

SET3B	equ	0D8CBh		;SET 3,b
SET4M	equ	0E6CBh		;SET 4,(hl)
SET6M	equ	0F6CBh		;SET 6,(hl)
SET7M	equ	0FECBh		;SET 7,(hl)

RES4B	equ	0A0CBh		;RES 4,b
RES7E	equ	0BBCBh		;RES 7,e
RES4M	equ	0A6CBh		;RES 4,(HL)
RES5M	equ	0AECBh		;RES 5,(hl)
RES6M	equ	0B6CBh		;RES 6,(hl)
RES7M	equ	0BECBh		;RES 7,(hl)

LDIR	equ	0B0EDh
CPIR	equ	0B1EDh
ZCPI	equ	0A1EDh		;Z80 CPI
CPD	equ	0A9EDh

OUTCA	equ	079EDh		;OUT (c),a
OUTCB	equ	041EDh		;OUT (c),b
OUTCH	equ	061EDh		;OUT (c),h
OUTCL	equ	069EDh		;OUT (c),l

INI	equ	0A2EDh
OUTI	equ	0A3EDh
INIR	equ	0B2EDh
OTIR	equ	0B3EDh
LDED	equ	05BEDh		;LD de,(nn)
SBCD	equ	043EDh		;LD (nn),bc
RLCC	equ	001CBh		;RLC c
;*************************************
;Beginning of format program execution	
;*************************************
	org	100H
	di			;no interrupts ever
	jmp	START		;skip over advertizement
	db	'2021 Martin Eberhard'

START:	lxi	sp,LSTACK	;stack for setup only
	call	SETUP		;note: SETUP invokes 'PARAMS'
;-----------------------------------------------------------
;Restore and load the heads on the disk that we will format,
;then seek the first track, if needed
;
;RQDRIV=requested disk (0-3)
;CURDNS=requested density (0=single, 1=double)
;CURBPS=bytes/sector ({0,1,2,3}={128,256,512,1024})
;BPS4=number of bytes per sector /4 (256 is encoded as 0)
;RQFRST=starting track number
;RQLAST=ending track number
;RQSIDE={0,1,0FFh}={side 0, side 1, both}
;RQTYPE={0,1,2,3,4} for {8",mini,QD,HD,3.5"}
;SAVDCL=mini/maxi + motor on + SD/DD + drive select
;STEPRT=step rate for DCMMD
;BUFEND=(16-bit) last addr in format buffer for this format
;MAXSEC=maximum sector number 
;-----------------------------------------------------------
DODISK:	lxi	sp,COMEND	;put stack in COM buffer
				;..TO save RAM space

	lxi	d,FMTMSG	;'formatting track: '
	call	PCRSTR

	mvi	b,1		;step in one before restore
	call	STPRST		;step in, then restore
	jc	ASKAGN		;error: ask for a new disk
;------------------------------------
;Begin with the first requested track
;------------------------------------
	lda	RQFRST		;set up track pointer
;----------------------------------------------------------
;Begin building the track format in the buffer
;
; a=the current track number to format
; RQDRIV=requested disk (0-3)
;RQTYPE={0,1,2,3,4} for {8",mini,QD,HD,3.5"}
; CURDNS=requested density (0=single, 1=double)
; CURBPS=bytes/sector ({0,1,2,3}={128,256,512,1024})
; BPS4=number of bytes per sector /4 (256 is encoded as 0)
; RQSIDE={0,1,0FFh}={side 0, side 1, both}
; CURTRK=current track
; RQLAST=ending track number
; SAVDCL=mini/maxi + motor on + SD/DD + drive select
; STEPRT=step rate bits for the WD1793
; MAXSEC=maximum sector number
; BUFEND=(16-bit) last addr in format buffer for this format
;----------------------------------------------------------
DOTRAK:	mvi	c,CCSEEK	;seek, no verify
	call	NWTRAC		;print track pacifier, 
				;call PARAMS
				;find side to format
;------------------------------------------------------
;Loop through both sides, as needed
;On Entry: 
;  a=side select value for ADCMD
;  b=side number
;  hl=address of the right format table for this format
;------------------------------------------------------
DOSIDE:	out	ADCMD

	mov	a,b		;recover current side	
	sta	CURSID		;remember current side
;--------------------------------------
;Write one track header into the buffer
;--------------------------------------
	push	h	 	;beginning of format table
	lxi	d,TRKBUF	;build track data in RAM
	call	PATFIL	 	;write header in track buffer

	mvi	a,1		;start with sector 1
;-----------------------------------------------------------
;For each sector, write into the buffer:
;  the sector preamble
;  sector data fill byte (once for each byte in the sector)
;  the sector postamble
;On Entry:
;  a has the sector number
;  hl points to the sector beginning in the format table
;
; a=1 for initial value for current sector to go in CURSEC
; de points to the first sector position in the buffer
; hl points to the sector data in the format table
; RQDRIV=requested disk (0-3)
;RQTYPE={0,1,2,3,4} for {8",mini,QD,HD,3.5"}
; CURDNS=requested density (0=single, 1=double)
; CURBPS=bytes/sector ({0,1,2,3}={128,256,512,1024})
; BPS4=number of bytes per sector /4 (256 is encoded as 0)
; RQSIDE={0,1,0FFh}={side 0, side 1, both}
; RQFRST=starting track number
; RQLAST=ending track number
; SAVDCL=mini/maxi + motor on + SD/DD + drive select
; STEPRT=step rate bits for the WD1793
; MAXSEC=maximum sector number
; BUFEND=(16-bit) last addr in format buffer for this format
; CURTRK=current track
; CURSID=current side
;-----------------------------------------------------------
DOSECT:	sta	CURSEC		;current sector number
	push	h		;sector data in format table
;--------------------------------------------------
;Write sector preamble, up through the address mark
;--------------------------------------------------
	call	PATFIL		;Go fill the buffer

;----------------------------------------------------
;Write the track, side, sector and sector size (which
;is 0-3) from CURTRK, SURSID, CURSEC, and CURBPS
;----------------------------------------------------
	push	h		;save format table pointer
	lxi	h,cURTRK	;this is followed by the others
	mvi	b,4		;4 bytes to write

DOSEC1:	mov	a,m
	stax	d
	inx	h
	inx	d
	db	DJNZ,(DOSEC1-$-1) xor 0FF00h

	pop	h		;recover format table pointer
;-------------------------------------------
;Write the post-address mark sector preamble
;-------------------------------------------
	call	PATFIL
;----------------------------------------------
;Loop to fill the data field in the buffer with
;BPS4*4 copies of the byte value in CURFIL
;----------------------------------------------
	lda	BPS4		;get BPS/4
	mov	c,a		;note:256 is encoded as 0
	lda	CURFIL		;get the data field fill byte

DODATA:	mvi	b,4
DODAT4:	stax	d		;another fill chr in buffer
	inx	d
	db	DJNZ,(DODAT4-$-1) xor 0FF00h

	dcr	c
	db	JRNZ,(DODATA-$-1) xor 0FF00h
;------------------------------------------
;Write the sector postamble into the buffer
;------------------------------------------
	call	PATFIL
;------------------------------------------------------------
;Next sector, unless we just did the last sector on the track
;------------------------------------------------------------
	mov	b,m		;track postamble character

	lxi	h,MAXSEC	;test for last sector done
	lda	CURSEC
	cmp	m		;done all sectors on track?
	pop	h		;point again to sector data

	inr	a		;next sector
				;(doesn't touch carry)
	db	JRC,(DOSECT-$-1) xor 0FF00h
				;N: do another sector
				;a=sector to do
;--------------------------------------------------------------
;All sectors on this track are now written.
;Now write the track postamble character into into the buffer.
;the actual track length (for a given disk size and density)
;varies depending on the bytes/sector, but we write the same
;number of bytes into the buffer for any BPS. The buffer end
;was chosen to ensure that the buffer size with this much of
;the track postamble is the same length for all sector/track
;choices for this size and density of disk. This does not
;represent the  entire postamble - the rest will be written by
;a loop at the end of the track format that runs until an
;index pulse is encountered.
;
; b=track postamble character
; de points to the beginning of the track end GAP in the buffer
; hl points to beginning of sector data in the format table
; RQDRIV=requested disk (0-3)
;RQTYPE={0,1,2,3,4} for {8",mini,QD,HD,3.5"}
; CURDNS=requested density (0=single, 1=double)
; CURBPS=bytes/sector ({0,1,2,3}={128,256,512,1024})
; BPS4=number of bytes per sector /4 (256 is encoded as 0)
; RQSIDE={0,1,0FFh}={side 0, side 1, both}
; RQFRST=starting track number
; RQLAST=ending track number
; SAVDCL=mini/maxi + motor on + SD/DD + drive select
; STEPRT=step rate bits for the WD1793
; MAXSEC=maximum sector number
; BUFEND=(16-bit)last addr in format buffer for this format
; CURTRK=current track
; CURSID=current side
;--------------------------------------------------------------
	lhld	BUFEND		;last addr in buffer to fill

PSTLUP:	mov	a,b		;recover postamble character
	stax	d		;fill until we Reach BUFEND
	inx	d
	mov	a,h		;high byte maxed out?
	cmp	d
	db	JRNZ,(PSTLUP-$-1) xor 0FF00h
				;N: loop to fill buffer

	mov	a,l		;low byte maxed out?
	cmp	e
	db	JRNZ,(PSTLUP-$-1) xor 0FF00h
				;N: loop to fill buffer
;-----------------------------------------------
;Buffer is complete: time to kick off the format
;-----------------------------------------------
	call	FORMAT		;Go format the track now
;-------------------------------------------
;Next side, unless we just did the last side
;-------------------------------------------
	pop	h		;start of data format table
	call	NXTSID
	db	JRNZ,(DOSIDE-$-1) xor 0FF00h
				;do same track on side 0
				;..in b, a set up for ADCMD
;---------------------------------------------
;Next track, unless we just did the last track
;---------------------------------------------
	call	NXTRAC		;need to do another track?
	jnc	DOTRAK		;do next track if needed
;-----------------------
;Verify, if so requested
;-----------------------
	lda	RQVRFY		;did the user ask for verify?
	ora	a
	cnz	VERIFY
;------------------------------------------------
;Ask if we should format another disk.
;Error routines jump here too - so we must repair
;the stack and fix up all variables for new disk.
;DODISK fixes the stack.
;------------------------------------------------
ASKAGN:	call	RESTOR		;to track 0

	xra	a
	out	DCTRL		;de-select drive to unlock door
	sta	ERRCNT		;clear verify error count

	mvi	a,SIDE0
	out	ADCMD		;in case new disk is 1-sided

	lxi	d,AGNSTR
	call	PCRSTR

	call	ASKLOD		;ask user to load disk
				;Trashes all regs

	lda	SAVDCL
	out	DCTRL		;select drive again

	db	JRC,(ASKAGN-$-1) xor 0FF00h
				;error: ask for a new disk
;-------------------------------------------
;Check the disk to see if it is spinning and
;if it is hard-sectored or soft-sectored
;-------------------------------------------
	call	TMINDX		;not ready or hard sector?
	db	JRC,(ASKAGN-$-1) xor 0FF00h
				;Y: try again
;----------------------
;Go format another disk
;----------------------
	jmp	DODISK		;sidedness OK: format the disk

;***Subroutine********************************
;Print CR, then string at (de) on CP/M console
;On Exit:
;  Z flag set
;Trashes a,de
;*********************************************
PCRSTR:	push	d
	lxi	d,CRMSG
	call	PRTSTR
	pop	d

;Fall into PRTSTR

;***Subroutine*******************************
;Print message at de, terminated with MSB set
;On Entry:
;  de=string address
;Trashes psw,de
;********************************************
PRTSTR:	ldax	d		;message chr
	call	PRINTA		;print chr (strips MSB)
	inx	d		;next byte
	rlc			;MSB set?
	db	JRNC,(PRTSTR-$-1) xor 0FF00h
				;N: keep printing
	ret

;***Subroutine***************************************
;Ask the user to load disk, then validate input.
;Exit with stack hosed if the user types Q or ctrl-C
;Trashes all registers
;****************************************************
ASKLOD:	lda	RQDRIV		;convert drive number to ASCII
	adi	'A'
	sta	LDMSG2		;install letter in string
	
	lxi	d,lDMSG1
	call	PRTGET		;print string, get one chr
				;Trashes all regs

	cpi	CR		;CR makes us go
	rz

	ani	05Fh		;strip lowercase-ness
	cpi	'Q'
	db	JRNZ,(ASKLOD-$-1) xor 0FF00h
				;anything else, ask again

;Fall into GUDBYE
	
;***Assorted Exits**********************************
;Since these all exit through warm boot, it is okay
;to leave the stack hosed.
;Entry at PRTEXT prints string at de preceeded
;  and followed by a CR LF, then exits
;Entry at GUDBYE prints the BYEMSG first
;Entry at PRTEXT prints the string at de first
;Entry at CREXIT prints just a CR LF before exiting.
;***************************************************
GUDBYE:	lxi	d,BYEMSG	;message for user termination

PRTEXT:	call	PCRSTR		;print CR, then message at de

CREXIT:	lxi	d,CRMSG		;CR/LF to look pretty
	call	PRTSTR
;-------------------------------------------------------------
;Check to see if we formatted (or tried to format) on the same
;drive as CP/M. If so, ask the user to replace the CP/M disk.
;-------------------------------------------------------------
	lda	RQDRIV		;formatting on drive A?
	ora	a		;0=drive A
	db	JRNZ,EXIT1-$-1	;different drive: just exit

EXIT0:	lxi	d,REPSTR	;'Put CP/M disk in A:...'
	call	PRTGET		;print string and wait for user
	cpi	CR		;must be a CR only!
	db	JRNZ,(EXIT0-$-1) xor 0FF00h

EXIT1:	jmp	WBOOT

;***Subroutine*********************************************
;Get disk parameters based on drive size & density
;
;On Entry:
;RQTYPE={0,1,2,3,4} for {8",mini,QD,HD,3.5"}
;  CURDNS=0 for single-density, 1 for double-density
;  CURBPS=(0-3) for sector size (bytes/sector)
;  SAVDCL=drive select, mini/maxi, motor-on all valid
;On Exit:
;  hl=address of the correct format table for this format
;  de=BUFEND
;  BUFEND has last address we will use in the track buffer
;  SAVDCL=mini/maxi + motor on + SD/DD + drive select bits
;  SAVDCL has been written to DCTRL
;  MAXSEC has the highest sector number for every track
;  BPS4=bytes per sector / 4
;Trashes a,b
;**********************************************************
PARAMS:
;---------------------------------
;Look up and save the buffer size,
;based of disk type and density
;---------------------------------
	lda	CURDNS		;0=single-density, 1=double
	rrc			;shove LSB into carry, a=0
	push	psw		;need this again

	lda	RQTYPE
	ral			;*2, offset for density
	rlc			;2 bytes per entry

	lxi	h,BETAB
	add	l
	mov	l,a
	mvi	a,0		;catch carry
	adc	h
	mov	h,a

	mov	a,m		;low byte of length
	inx	h
	mov	h,m		;high byte
	mov	l,a
	shld	BUFEND
;-----------------------------------------------------------
;look up the address of the correct disk format table in THE
;disk format select table, based on disk size and density.
;include density in SAVDCL and write SAVDCL to DCTRL. Note
;that 3.5" disks use the same table as 8" disks.
;-----------------------------------------------------------
	lda	SAVDCL
	ani	0FFh XOR DCDDEN	;clear double-density bit
	mov	b,a		;save it for a bit

	pop	psw
	push	psw		;save carry bit=density
	rrc			;DCDDEN is 01000000b
	ora	b		;put DDEN state in SAVEDCL
	sta	SAVDCL
	out	DCTRL		;W/ correct density

	lxi	d,2

	lda	RQTYPE		;T1 and T2 are low data rate
	dcr	a
	cmp	e		;carry set for low data rate
	db	JRNC,PARAM1-$-1
	mov	e,d		;0
PARAM1:				;de=0 or 2
	
	pop	psw		;recover carry=density
	db	JRNC,PARAM2-$-1
	inx	d		;offset for density
PARAM2:

	lxi	h,DFSTAB	;disk format select table
	dad	d
	dad	d		;2 bytes per entry
;-----------------------------------------
;Point hl to the correct disk format table
;-----------------------------------------
	mov	e,m
	inx	h
	mov	d,m
	xchg
;---------------------------------------------
;Look up the address of the correct SPT table.
;Note the address in the table is offset by -1
;---------------------------------------------
	mov	e,m		;de gets SPT table address
	inx	h
	mov	d,m
	inx	h

;Override SPT pointer from format table if 3.5" disk
;3.5" drives use the 8" format table)

	lda	RQTYPE		;3.5" drive?
	cpi	4
	db	JRNZ,PARAM3-$-1

	lxi	d,SPT3DD-1	;special case for 3.5"

	lda	CURDNS		;0=single-density, 1=double
	ora	a		;single-density?
	db	JRNZ,PARAM3-$-1

	inx	d		;y:point to SPT3SD-1
PARAM3:
;--------------------------------------------------------
;Compute the number of bytes per sector / 4 and also look
;up sectors/track, both  based on CURBPS between 0 and 3.
;de points to the SPT table within the format table -1
;hl has our place in the format table
;CURBPS=(0-3) for sector size (bytes/sector)
;--------------------------------------------------------
	lda	CURBPS		;get sector size (0-3)
	mov	b,a
	inr	b		;b=sector size (1-4)

	mvi	a,16
PARAM4:	add	a		;a=a*2
	inx	d		;get sectors per track pointer
	db	DJNZ,(PARAM4-$-1) xor 0FF00h

	sta	BPS4		;save BPS/4

	ldax	d		;finally, get SPT from table

	sta	MAXSEC		;highest sector number
;-------------------------------------------------
;Get the data field fill byte and put it in CURFIL
;-------------------------------------------------
	mov	a,m		;get this format's fill byte
	inx	h
	sta	CURFIL
	ret

;***Subroutine************************************************
;Fill track buffer according to the plan in the format table.
;The table starts with a single byte that specifies how many
;patterns to do. The following data are pairs of bytes- the
;first byte of each pair is the number of times the 2nd byte
;should be written into the track buffer.
;On Entry:
;  hl points to the plan in the correct format table
;  de points to the correct place in the track buffer
;On Exit:
;  de points to the next place in the track buffer
;  hl points to the next entry in the format table
; trashes a,b
;*************************************************************
PATFIL:	mov	a,m		;# of copies - 0 means done
	inx	h
	ora	a		;return if done
	rz

	mov	b,a		;b gets number of copies
	mov	a,m		;a gets fill byte
	inx	h

;Write (b) copies of (a) in TRKBUF starting at (de)

FILLUP:	stax	d		;write it once
	inx	d		;point to next location	
	db	DJNZ,(FILLUP-$-1) xor 0FF00h

	db	JR,(PATFIL-$-1) xor 0FF00h

;***Subroutine************************************************
;Format one track from the buffer
;On Entry:
;  SAVDCL=mini/maxi + motor on + SD/DD + drive select
;  track buffer starting at TRKBUF is all valid
;  BUFEND has the addr of the last byte written in the buffer
;  the disk drive is all setup on the right track, etc.
;Trashes a,bc,hl
;*************************************************************
FORMAT:
;-------------------------------------------
;Setup bc as quad-byte counter for the track
;-------------------------------------------

;Calculate buffer length for this disk type

	lhld	BUFEND		;buffer end addr for this disk
	lxi	b,0-TRKBUF-2	;subtract buffer beginning
	dad	b		;(this will set the carry bit)

;Divide result by 4, since loop writes 4 bytes per pass

	xra	a		;clear the carry bit
	mov	a,h
	rar
	mov	b,a
	mov	a,l
	rar
	mov	c,a		;result goes into bc

	xra	a		;clear the carry bit again
	mov	a,b
	rar
	mov	b,a
	mov	a,c
	rar
	mov	c,a		;result goes into bc
;--------------------------------
;Set disk controller for autowait
;--------------------------------
	in	DDATA		;FLUSH WD1793 read buffer

	lda	SAVDCL		;disk command for DCTRL
	ori	DCAUTOW		;set the autowait bit
	out	DCTRL
;------------------------------------------------------------
;Save the real stack pointer, and set up sp as a pointer into
;the track buffer, so we can pop the data from the buffer and
;send it to the hungry WD1793 as quickly as possible
;------------------------------------------------------------
	lxi	h,0		;save the sp in SPSAVE
	dad	sp
	shld	SPSAVE

	lxi	sp,TRKBUF	;use sp to pop data from TRKBUF
;--------------------------------
;Initiate the write track command
;--------------------------------
	mvi	a,CCWTRAC	;write track command
	out	DCMMD		;(11)kick off write track command
;-----------------------------------------------------------
;Fast loop to write an entire track from the buffer to the
;WD1793 data port
;On Entry:
;  bc has the byte count/4
;  sp points to the first byte in the buffer
;  SPSAVE has the real stack pointer
;  autowait was set in DCTRL, so that this loop will stall
;  until the WD1793 is ready on every "out DDATA".
;Numbers in parenthesis are Z80 CPU clock cycles.
;
;Worst case write-to-write times:
;(see "worst case 179X service time" in figure 3, Western
;Digital 179X Application Notes,June, 1980.)
;  Mode  Service time  2MHz clockS   4MHz clockS
;   DD     16 uS        32              64
;   SD     32 uS        64             128
;
;This unrolled loop requires 40 cycles out-to-out, and the
;unrolled postamble loop requires 41 cycles out-to-out.
;Thus, a 2 MHz 8080 can not format double-density disks.
;-----------------------------------------------------------
	jmp	WTLENT	;(10)enter the loop at the right SPOT

WTLUP:	in	DFLAG	;(11) autowait
	mov	a,h	;(4) write byte 4
	out	DDATA	;(11) 36 cycles, out-to-out

WTLENT:	pop	d	;(10) get bytes 1 & 2

	in	DFLAG	;(11) autowait
	mov	a,e	;(4) write byte 1
	out	DDATA	;(11) 36 cycles, out-to-out

	pop	h	;(10) get bytes 3 & 4	

	in	DFLAG	;(11) autowait
	mov	a,d	;(4) write byte 2
	out	DDATA	;(11) 36 cycles, out-to-out

	dcx	b	;(6) bump quad-byte count
	mov	a,b	;(4) check done
	ora	c	;(4)

	in	DFLAG	;(11) autowait
	mov	a,l	;(4) write byte 3 (clears carry)
	out	DDATA	;(11) 40 cycles, out-to-out

	jnz	WTLUP	;(10)

;--------------------------------------------------------
;Write track postamble until the WD1793 releases its busy
;bit, indicating that the sector pulse was detected.
;On Entry: d=e=h=l=postamble fill character
;This loop's worst case timing: 41 cycles out-to-out
;--------------------------------------------------------
	in	DFLAG	;(11) autowait
	mov	a,l	;(4) track-end fill byte

POSTLP:	out	DDATA	;(11) a=track-end fill byte

	in	DFLAG	;(11) read status, autowait
	rar		;(4) carry=interrupt bit

	mov	a,l	;(4) track-end fill byte
	out	DDATA	;(11)

	jnc	POSTLP	;(10) no int: send another byte
;-------------------------------------------------
;Stall for 560 uS (min) to complete the trim erase
;-------------------------------------------------
	mvi	b,172	;560 US
TERASE:	db	DJNZ,(TERASE-$-1) xor 0FF00h ;(13)
;-----------------------------------------------------------
;Repair the stack, check for errors, and return if no errors
;-----------------------------------------------------------
	lhld	SPSAVE		;repair the stack pointer
	sphl

	in	DSTAT		;read the final status
	ani	SWRPROT+SWFAULT+SLOSTD

	rz			;keep going if no errors
;------------------------------------------------------
;Report details about a disk error, then quit this disk
;------------------------------------------------------
	lxi	h,eRTABW-ETSZ	;write error table, with offset
	mvi	b,0		;don't print sector number
	call	DSKERR
	jmp	ASKAGN

;***Subroutine**********************************************
;Verify what's been formatted
;On Entry:
;  The correct disk is already selected with head loaded
;  CURBPS=bytes/sector ({0,1,2,3}={128,256,512,1024})
;  BPS4=number of bytes per sector /4 (256 is encoded as 0)
;  RQSIDE={0,1,0FFh}={side 0, side 1, both}
;  RQFRST=starting track number
;  RQLAST=ending track number
;  SAVDCL=mini/maxi + motor on + SD/DD + drive select
;  STEPRT=step rate bits for the WD1793
;  MAXSEC=maximum sector number
;Trashes all registers, CURTRK, and CURSID
;***********************************************************
VERIFY:	lxi	d,VERMSG	;'verifying: '
	call	PCRSTR

	lda	RQFRST		;set up track pointer
;-----------------------
;Loop through all tracks
;On Entry:
;  a=track to verify
;-----------------------
VTRAK:	mvi	c,CCSEEKV	;seek with verify command
	call	NWTRAC		;print track pacifier, 
				;seek with verify
				;find side to verify 1st
;----------------------------------
;Loop through both sides, as needed
;On Entry: 
;  a=side select bits for ADCMD
;  b=the side to verify
;----------------------------------
VSIDE:	out	ADCMD

	lxi	h,cURSID
	mov	m,b

	dcx	h		;skip over CURTRK
	dcx	h		;point to CURFIL
	mov	e,m		;e=expected value

	dcx	h		;point to SAVDCL
	mov	a,m
	ori	DCAUTOW		;turn on autowait
	mov	d,a		;..in DCTRL image in d

	dcx	h		;point to MAXSEC
	mov	h,m		;H=sector count

	mvi	l,1		;initial sector

	in	DDATA		;clear out any garbage
;------------------------------------------------------
;Verify one sector
;Needs to be fast so we don't slip revs between sectors
;On Entry: 
;  d=DCTRL value with autowait & side
;  e=expected sector data
;  h=sector count-down for loop termination
;  l=current sector number
;
;127+121=248 cycles in-to-in between sectors
;124=uSec worst case
;must be done in 54 byte times=865 uS worst case
;------------------------------------------------------
VSEC:	mov	a,l		;(4) sector number
	out	DSCTR		;(10) WD1793 sector port

	lxi	b,VERROR	;(10) set up error exit
	push	b		;(11) ..SO we can rnz quickly

	lda	BPS4		;(13) bytes/sector / 4
	mov	b,a		;(5) b is quad byte count

	mov	a,d		;(5) set up autowait
	out	DCTRL		;(10)

	lda	CURSID		;(13) test current side
	ora	a		;(4)
	mvi	a,CCRDSEC	;(7) read sector with side cmd
	jz	VSEC1		;(10) side 0 or 1?
	ori	F2SSF		;(7)tell WD1793 to expect side 1
VSEC1:
	out	DCMMD		;(10)
;---------------------------------------------------------
;Read sector and verify data 4 bytes at a time
; b=quad byte count (256 is encoded as 0)
; d=DCTRL value with autowait
; e=expected sector data
; h=sector count-down for loop termination
; l=current sector number
; top-of-stack=address of VERROR routine
;worst case timing: 36 cycles in-to-in
;(Not fast enough to verify a DD 8" disk with a 2 MHz CPU)
;---------------------------------------------------------
	mov	a,e		;(4) pass 1st byte 4 test

VSECLP:	cmp	e		;(4) test 4th byte
	in	DFLAG		;(11) autowait
	in	DDATA		;(11) read disk byte 1

	rnz			;(5/11) byte 4 fail?

	cmp	e		;(4) test 1st byte
	rnz			;(5/11) byte 1 fail?

	in	DFLAG		;(11) autowait
	in	DDATA		;(11) read disk byte 2

	cmp	e		;(4) test 2nd byte
	rnz			;(5/11) byte 2 fail?

	in	DFLAG		;(11) autowait
	in	DDATA		;(11) read disk byte 3

	cmp	e		;(4) test 3rd byte
	rnz			;(5/11) byte 3 fail?

	dcr	b		;(4) next quad byte
	in	DFLAG		;(11) autowait
	in	DDATA		;(11) read disk byte 4

	jnz	VSECLP		;(10) more quads to test?

;All done reading, but we must still test the last byte

	pop	b		;(10) chuck VERROR address

	xra	e		;(4) test last byte
	mov	b,a		;(4) mismatch will set B<>0
;--------------------------------------------------------
;Wait for command to complete, then check for read errors
;b=0 if no compare errors, b<>0 if any compare errors
;--------------------------------------------------------
VWAIT:	in	DFLAG		;(10) wait for OP to complete
	rar			;(4) test the WD1793 INTRQ flag
	jnc	VWAIT		;(10) loop until done

	in	DSTAT		;(10)ANY errors from WD1793?
	ani	SCRCERR+SNOTRDY+SLOSTD+SRNFER	;(7)
	cnz	VERER2		;(11) returns with b=0
;------------------------------------------
;Test a for incorrect data and report if so
;b<>0 if any sector data errors
;------------------------------------------
	mov	a,b		;(4) error flag
	ora	a		;(4) any incorrect data?
	cnz	VERER1		;(11) Y: report error
;------------------------------------------------------------
;Next sector, unless we just did the last sector on the track
;------------------------------------------------------------
	inr	l		;(4) bump current sector number
	dcr	h		;(4) test for last sector done
	jnz	VSEC		;(10) N: do another sector

;-------------------------------------------
;Next side, unless we just did the last side
;-------------------------------------------
	call	NXTSID
	jnz	VSIDE	;if not done: do same track on side 0
			;specified in b, a set up for ADCMD
;---------------------------------------------
;Next track, unless we just did the last track
;---------------------------------------------
	call	NXTRAC		;need to do another track?
	jnc	VTRAK		;Y: do next track
;------------------------
;Print verify error count
;------------------------
	lda	ERRCNT		;how many errors did we find?
	lxi	d,VDONMS	;'verify errors: '
	jmp	STRVAL		;print message and return
;-------------------------------------------
;Flush the remainder of a bad sector
;On Entry:
;  b=number of quad-bytes left to read
;     (may do an extra 3 ins, which is OK)
;On Exit:
;  b=1, indicating error
;worst-case error-to-this-in = 31 cycles
;-------------------------------------------
VERROR:	in	DFLAG		;(11) autowait
	in	DDATA		;(11) read disk data
	in	DFLAG		;(11) autowait
	in	DDATA		;(11) read disk data
	in	DFLAG		;(11) autowait
	in	DDATA		;(11) read disk data
	in	DFLAG		;(11) autowait
	in	DDATA		;(11) read disk data

				;(4) next quad byte
	db	DJNZ,(VERROR-$-1) xor 0FF00h
				;(10)

	inr	b		;(4)b=1: indicate error
	jmp	VWAIT		;(10)finish up and report

;***Subroutine***********
;Deal with verify errors
;On Entry:
;  l=sector number
;On Entry: at VERER2:
;  a=error bits
;On Exit:
;  b=0
;Trashes psw,c
;************************
VERER1:	mvi	a,1		;data error: fake a disk error

;Fall into VERER2

VERER2:	push	d
	push	h

	mov	b,l		;tell DSKERR to print sector #
	lxi	h,eRTABR-ETSZ	;with offset for initial dad d
	call	DSKERR

	lxi	h,ERRCNT	;check for too many errors
	inr	m
	mov	a,m
	cpi	MAXERRS		;too many errors?

	push	psw		;remember Z flag
	lxi	d,VERMSG
	cnz	PCRSTR		;reprint verifying message

	lda	CURTRK
	cpi	10		;check for 1-digit prev. Track
	db	JRC,VERER3-$-1	;extra space for > 10
	mvi	a,' '
	call	PRINTA
VERER3:
	pop	psw		;recover Z flag

	pop	h
	pop	d
	mvi	b,0		;TO block data error test
	rnz

;Too many errors on this disk

	lxi	d,BADDSK	;'too many errors'
	call	PCRSTR
	jmp	ASKAGN		;print message then try again

;***Subroutine****************************************
;Start new track
; Set up BPS and density for requested or system track
; Invoke PARAMS to compute parameters for track
; Print track pacifier
; Kick off command (in c)
; Figure out which side to start with
;On Entry:
;  a=current track
;  c=seek command (with or without verify)
;  RQSIDE={0,1,0FFh}={side 0, side 1, both}
;On Exit:
;  a=ADCMD value for side 0 or 1 (SIDE0 or SIDE1)
;  b={0,1} for {side 0, side 1}
;  hl=address of correct format table (via PARAMS)
;  CURTRK=current track, passed here in a
;  PARAMS has set up all parameters for track
;  head is on the desired track
;Trashes de
;*****************************************************
NWTRAC:	out	DDATA		;track num to WD1793 for seek
	sta	CURTRK		;save current track
;----------------------------------------------------------
;Set density and BPS either as requested or as system track
;----------------------------------------------------------
	lxi	h,RQNSTK
	cmp	m
	lxi	h,0		;assume system track
	db	JRC,ITSSYS-$-1	;carry means system track

	lhld	RQBPS		;gets both RQBPS and RQDENS
ITSSYS:	shld	CURBPS		;writes both CURBPS and CURDNS
;---------------------------------------------------
;Get disk parameters, set hl to correct format table
;---------------------------------------------------
	call	PARAMS		;this writes SAVDCL to DCTRL
;------------------------------------
;Print pacifier
;  hl=pointer to correct format table
;------------------------------------
	lxi	d,BSMSG		;BS over previous track number
	lda	CURTRK
	cpi	11		;check for 1-digit prev. track
	db	JRNC,NWTR1-$-1
	inx	d		;decrease # of backspaces by 1
NWTR1:
	call	STRVAL		;string at de then value in a
;---------------------------------------------------------
;Seek track now, using command in reg c
;(DCTRL is valid already, and CURTRK was written to DDATA)
;  c=seek command (with or without verify)
;---------------------------------------------------------
	call	DODCMD		;seek track (cmd in c)
	jc	ASKAGN		;error: ask for a new disk
;----------------------------------------------------------
;Figure out the initial side. We do side 1 1st, then side 0
;----------------------------------------------------------
	lda	RQSIDE		;a={0,1,0FFh}
				;..for {side 0,side 1,both}
	mov	b,a
	ora	a		;just do side 0?
	mvi	a,SIDE0
	rz			;Y: start with side 0

	mvi	b,1		;both or side 1: do side 1 1st
	mvi	a,SIDE1
	ret

;***Subroutine*******************************************
;Next side (counting down)
;On Entry:
;  RQSIDE ={0,1,0FFh} for do {SIDE0, SIDE1, both sides}
;  CURSID ={0,1} for just finished side {0,1}
;On Exit:
;  a=SIDE0 (for ADCMD) unless we are done
;  B=1 (for side 1) if Z is cleared
;  Z cleared if we need to do side 0
;********************************************************
NXTSID:	lda	RQSIDE		;only side 1 requested?
	dcr	a	
	rz			;Y: done

;Both sides requested. Do the other side if we haven't already

	lda	CURSID		;did we just do side 0?
	ora	a		;set Z if side 0 is done

	mvi	a,SIDE0		;if we must do side 0 next
	ret			;NZ: still must do side 0

;***Subroutine***********************
;Next track
;On Entry:
;  CURTRK=current track
;  RQLAST=last track to do
;On Exit:
;  a=next track to do
;  carry set if no more tracks to do
;Trashes bc,de,hl
;************************************
NXTRAC:	call	CHKBRK		;abort if user types ctrl-C
				;Trashes all regs
	lxi	h,cURTRK
	inr	m		;next track
	lda	RQLAST
	cmp	m		;would this be to big?
	mov	a,m		;track no in a for loop
	ret

;***Subroutine**********************************************
;Step in a few, then restore selected disk, then wait till
;it is done. If the WD1793 reportS any sort of error,
;report it on the console and quit. Do not verify on-track.
;On Entry:
;  SAVDCL is setup
;  STEPRT=current step rate
;  b=number of steps before restore
;On Exit:
;  carry set means error
;Trashes a,bc
;***********************************************************
STPRST:	lda	SAVDCL
	out	DCTRL		;select drive again

STRLUP:	mvi	c,CCSTEPI	;step-in command with options
	call	DODCMD
	rc			;return if step error

	db	DJNZ,(STRLUP-$-1) xor 0FF00h

;Fall into RESTOR
	
;***Subroutine*************************************
;Restore selected disk, then wait until it is done.
;This restore command does not verify on-track.
;If the WD1793 reportS any sort of error, report
;it on the console and quit. 
;On Entry:
;  STEPRT=current step rate
;On Exit:
;  carry set means error
;Trashes a,c
;***************************************************
RESTOR:	mvi	c,CCRSTOR

;Fall into DODCMD

;***Subroutine**************************************
;Send type I command to the WD1793, then wait until
;it is done. If the 1793 reportS any sort of error,
;report it on the console and quit.
;On Entry:
;  c=command for the WD1793 (with head load)
;  STEPRT=current step rate
;On Exit:
;  carry set means error
;Trashes a,c
;***************************************************
DODCMD:	lda	STEPRT		;current step rate
	ora	c		;Type I command w/headload
	out	DCMMD		;send command to the WD1793

DCWAIT:	in	DFLAG		;(10) wait for op to complete
	rar			;(4)test WD1793 INTRQ flag.
	db	JRNC,(DCWAIT-$-1) xor 0FF00h
				;(11)loop until done
;------------------------------------------------------
;Western Digital's 179X Application Notes (June, 1980)
;specify a 56 uS minimum delay between writing to the
;command register and reading the status register.
;(This is the worst case - single-density on a 5.25"
;drive.) with a 4 MHz processor, we need 224 cycles for
;56 uS. The spin loop needs (224-31)/15= 13 cycles.
;------------------------------------------------------
	mvi	a,13	;(7)
DL1793:	dcr	a	;(5)
	jnz	DL1793	;(10)

	in	DSTAT			;WD1793 status
	ani	SNOTRDY+SCRCERR+SSEEKER ;any errors?
	rz				;return if no errors
;------------------------------------------------------
;Report disk error and return
;On Entry:
; a has error bits that *must* match one of the
; table entries. (The loop only terminateS on a match.)
;------------------------------------------------------
	lxi	h,eRTAB1-ETSZ	;with offset for initial dad d

	mvi	b,0		;don't print sector number
	call	DSKERR
	stc			;report error
	ret

;***Subroutine**********************************************
;Print disk error details
;each table entry has 3 elements:
; byte 1: bit error mask
; byte 2: location control. If 1, print track, side, sector
; bytes 3 on: error message
;On Entry:
;  a=error bits
;  b=sector number. If b=0, don't print sector number
;  hl=address of disk error table offset by -ETSZ
;Trashes a,bc,de,hl
;***********************************************************
DSKERR:	mov	c,a		;error bits to C
	lxi	d,ETSZ		;table line length

DSKER1:	dad	d
	mov	a,m
	ana	c
	db	JRZ,(DSKER1-$-1) xor 0FF00h

	inx	h		;point to location control bit
	mov	c,m

	inx	h		;point to actual error string
	xchg			;string address in de
	call	PCRSTR

	mov	a,c		;should we print the location?
	dcr	a
	rnz

	lxi	d,TRAKST	;CRLF,', track '
	lda	CURTRK		;current track
	call	STRVAL

	lxi	d,SIDEST	;'side '
	lda	CURSID		;current side
	call	STRVAL

	mov	a,b		;sector number or 0
	ora	a		;print sector as well?
	rz

	lxi	d,SECTST	;', sector '

;Fall into STRVAL

;***Subroutine**************************
;Print string followed by decimal value
;On Entry:
;  a=binary value between 0 and 99
;  de=address of string
;Trashes a,de
;***************************************
STRVAL:	push	psw
	call	PRTSTR		;print string at de
	pop	psw		;recover value

;Fall into PRTVAL

;***Subroutine***********************
;Print a 2-digit decimal ASCII number
;Leading zeros are suppressed.
;On Entry:
;  a=binary value between 0 and 99
;Trashes a,d
;************************************
PRTVAL:	mvi	d,0FFh		;we will loop one extra time
DECLUP:	inr	d		;compute high decimal digit
	sui	10
	db	JRNC,(DECLUP-$-1) xor 0FF00h
	adi	10		;a=LOW digit, d=high digit

	push	psw		;save low digit in a on stack
	mov	a,d		;print high digit first
	ora	a		;suppress leading zeros
	cnz	PRTDIG		;print high digit if It's not 0
	pop	psw		;recover low digit in a	

;Fall into PRTDIG

;***Subroutine**************
;Print a as a decimal digit
;Trashes psw
;***************************
PRTDIG:	ADI	'0'		;convert to ASCII
	jmp	PRINTA		;..and print it

;***Subroutine************************************
;Print message at (de), then get input from user.
;Return chr in a. If the user types a control-c,
;then quit the program.
;Trashes all registers
;*************************************************
PRTGET:	call	PCRSTR		;print message to console
	db	JR,GETCON-$-1	;get chr and check for CTRLC

;***Subroutine********************
;Check for control-C from console
;abort format if detected
;Trashes all registers
;*********************************
CHKBRK:	mvi	a,CONST		;address offset
	call	DOBIOS		;returns status in a
	ora	a		;NONZERO means chr ready
	rz			;just return if no chr

;Fall into GETCON to get the waiting console character

;***Subroutine****************************************
;Get input character from user. Echo, return chr in a.
;If the user types a control-c, then quit the program.
;On Exit:
;  a=chr, parity stripped
;Trashes bc,de,hl
;*****************************************************
GETCON:	mvi	a,CONIN		;address offset
	call	DOBIOS		;returns chr in a

;Fall into PRINTA to echo, and abort on control-C

;***Subroutine**********************
;Print chr in a on console via BIOS.
;Control-C will quit the program.
;On Entry:
;  a=chr to print
;All registers preserved
;***********************************
PRINTA:	push	psw
	ani	7Fh		;strip end marker/parity

	cpi	CTRLC		;ctrl-C will always abort
	jz	GUDBYE

	push	b
	push	d
	push	h

	mov	c,a		;BIOS wants chr in c
	mvi	a,CONOUT

	call	DOBIOS

	pop	h
	pop	d
	pop	b
	pop	psw
	ret

;***Subroutine**********************
;Go to a BIOS driver directly
;On Entry:
;  a=BIOS address offset
;  c=character to print
;On Exit:
;  All registers as BIOS left them
;***********************************
DOBIOS:	lhld	WBOOTA		;BIOS base addr
	mov	l,a		;address offset

	pchl			;Go to BIOS routine

;***Subroutine*************************************************
;Count CPU cycles between index pulses
;On Entry:
;  The previous WD1793 command was a Type I command
;   (Meaning that the Index signal is visible in DSTAT)
;On Exit:
;  hl=(CPU cycles per index pulse)/57
;    for a 4 MHz Z80, time=hl * 14.25 uS
;    for a 2 MHz 8080 (without waitstates) time=hl * 28.5 uS
;For one disk rev (at 360 RPM), the maximum value of the
;timeout counter in hl (assuming a 4 MHz Z80 running with 0
;wait states) is 166667/14.25=11,696. Timeout will occur after
;about 5.5 revolutions. 10-15 revolutions for a 2 MHz CPU.
;On Exit:
;  carry set means error
;  if error, the error message has been printed
;Trashes a,c,de
;**************************************************************
TMINDX:	call	TIMER0	;find first transition, which may be
			;caused by drive select change
	cnz	TIMER0	;find next transition, which must be a
			;real index pulse
	cnz	TIMER0	;restart timer & time to next trans.
	cnz	TIMER1	;keep timing to next transition, so
			;we include both high & low time

	lxi	d,TOMSG	;in case of timeout error
	db	JRZ,TIMEOT-$-1
;-------------------------------------------------------------
;Detect and reject hard-sectored disks
;A hard-sectored disk will have 16 to 32 hard sector holes
;generating index pulses. The worst case would be 1 hole every
;1/16 revolution, or once every 10.4 mSec. with a 4 MHz CPU,
;This would take about 812 cycles, and hl would be about 743.
;we can comfortably reject disks Where hl <1280 (500H)
;-------------------------------------------------------------
	mov	a,h		;high byte=timer result/256
	cpi	05h		;upper byte of 500H
	db	JRC,HSERR-$-1	;hard sector disk error
	rnc			;return with good result in hl
;------------------------
;Index error of some sort
;------------------------
HSERR:	lxi	d,HSMSG		;'hard sectored disk'

TIMEOT:	call	PCRSTR
	stc			;indicate error
	ret
;------------------------------------
;Timer subroutine used only by TMINDX
;57 cycles per pass
;------------------------------------
TIMER0:	lxi	h,0

TIMER1:	in	DSTAT		;get initial state
	ani	SINDEX		;..of the index signal
	mov	c,a

TIMER2:	in	DSTAT		;(11) wait for index pulse
	ani	SINDEX		;(7)
	xra	c		;(4)
	rnz			;(5)

	inx	h		;(6) check for timeout
	mov	a,l		;(4)
	ora	h		;(4)
	rz			;(5)

	db	JR,(TIMER2-$-1) xor 0FF00h
				;(11)

;##############
;Debug Routines
;##############
 if UNDERC

;===Debug Subroutine===========================
;Print hl as 4 hex digits surrounded by @ and #
;On Entry
;  a = value to print
;Trashes psw
;==============================================
PHLHEX:	mvi	a,'@'
	call	PRINTA

	mov	a,h
	call	PAHEX
	mov	a,l
	call	PAHEX

	mvi	a,'#'
	jmp	PRINTA

;===Debug Subroutine====
;Print a as 2 hex digits
;On Entry
;  a = value to print
;Trashes psw
;=======================
PAHEX:	push	psw		;save digit
	rrc			;hi nibble 1st
	rrc
	rrc
	rrc
	call	PHXNIB
	pop	psw		;recover lo nibble

;fall into PHXNIB

;---Local Subroutine----
;Print hex nibble
;On Entry:
;  Nibble in low 4 of a
;Trashes psw
;-----------------------
PHXNIB:	ani	0Fh		;get low
	cpi	0Ah		;A-F?
	jc	PHX1
	adi	'A'-'9'-1
PHX1:	adi	'0'

	jmp	PRINTA

 endif ;UNDERC

;***Permanent message strings**********************************
;Note: several strings are at the end, and get wiped out when
;the track buffer gets filled. (We don't need those strings
;anymore by the time the buffer fills.) These strings are here
;because they might be needed after the format operation -
;after the other strings have been overwritten.
;**************************************************************
REPSTR:	db	'Put CP/M disk in A: and press Return',':'+80H
VDONMS:	db	CR,LF,'Verify errors:',' '+80H
DONMSG:	db	'Format complete'	;Fall into CRMSG
CRMSG:	db	CR,LF+80H
TRAKST:	db	': Track',' '+80H
SIDEST:	db	', Side',' '+80H
SECTST:	db	', Sector',' '+80H
BSMSG:	db	BS,BS+80H
BADDSK:	db	'Too many errors- '	;Fall into BYEMSG
BYEMSG:	db	'Format terminate','d'+80H
AGNSTR:	db	'Ready to format another dis','k'+80H
FMTMSG:	db	'  Formatting track: ',' '+80H
VERMSG:	db	'  Verifying track: ',' '+80H
HSMSG:	db	'Unsupported hard-sectored dis','k'+80H
SERMSG:	db	'No side 1 on this dis','k'+80H
LDMSG1:	db	'Put disk in '		;Fall into LDMSG2
LDMSG2:	db	'X: and press Return (Q to quit)',':'+80H

;***drive error message table*****************************
;Note: ERTABW falls into ERTABR, which falls into ERTAB1.
;All entries except the last one must be exactly ETSZ
;bytes long. Each entry is a 1-byte bit-mask followed by
;a 1 or 0 (to control printing the location), followed by
;an error string.	
;*********************************************************
ETSZ	equ	18		;bytes/table entry

;All table entries except the last one must be ETSZ bytes long.

ERTABW:	db	SWFAULT,1,'Write Faul','t'+80h,'     '	;bit S5

ERTABR:	db	SLOSTD,1, 'Lost Data Erro','r'+80h,' ' 	;bit S2
	db	SRNFER,1, 'sector Not Foun','d'+80h	;bit S4
	db	1,1,      'Incorrect Dat','a'+80h,'  '	;bit 0
;
ERTAB1:	db	SNOTRDY,0
TOMSG:	db	          'Drive Not Read','y'+80h,' ' 	;bit S7
	db	SSEEKER,1,'seek Erro','r'+80h,'      '	;bit S4
	db	SWRPROT,0,'Write Protecte','d'+80h,' '	;bit S6
	db	SCRCERR,1,'CRC Erro','r'+80h		;bit S3

;*************************
;Disk format select table
;*************************
DFSTAB:	dw	T5SD		;SD 5.25" disk format table
	dw	T5DD		;DD 5.25" disk format table
	dw	T8SD		;SD 8" disk format table
	dw	T8DD		;DD 8" disk format table
				;(shared with 3.5" disks)


;************************
;Buffer End Address Table
;************************
BETAB:	DW	BL8SD+TRKBUF+1		;T0 SD
	dw	BL8DD+TRKBUF+1		;T0 DD

	DW	BL5SD+TRKBUF+1		;T1 SD
	DW	BL5DD+TRKBUF+1		;T1 DD

	DW	BL5SD+TRKBUF+1		;T2 SD
	DW	BL5DD+TRKBUF+1		;T2 DD

	DW	BL8SD+TRKBUF+1		;T3 SD
	dw	BL8DD+TRKBUF+1		;T3 DD

	dw	BL3SD+TRKBUF+1		;T4 SD
	dw	BL3DD+TRKBUF+1		;T4 DD

;***sector per track tables**********************************
;Each table is 4 bytes long, with SPT for (128,256,512,1024)
;bytes/sector. Note that the tables overlap.
;Illegal formats:
; 8" DD 128 BPS
; 8" SD 1024 BPS
; 5.25" DD 128 BPS (but this table has a good value)
; 5.25" SD 1024 BPS
; 3.5" DD 128 BPS
; 3.5" SD 1024 BPS (but this table has a good value)
;************************************************************
SPT3DD:	equ	$-1		;3.5" double density
SPT3SD:				;3.5" single density
SPT5DD:	db	31		;5.25" (and QD) double-density
SPT5SD:	db	18,10,5		;5.25" (and QD) single-density

SPT8DD:	db	0FFh		;8" and HD double-density
SPT8SD:	db	26,15,8,0FFh	;8" and HD single-density

;*******************
;Disk format tables
;*******************

;===============================
; IBM FM single-density format =
;    T0: 8"                    =
;    T3: HD 5.25"              =
;    T4: 3.5"                  =
; (SPT pointer and buffer last =
; address get replaced if this =
; is a 3.5" disk)              =
;===============================
T8SD:	dw	SPT8SD-1	;SPT table -1 offset
	db	0E5h		;fill the data field with this

;---------track header----------
	db	40,0FFh		;40 bytes of 0FFh
	db	6,0		;6 bytes of 0
	db	1,0FCh		;1 byte of FCh (index mark)
	db	26,0FFh		;26 bytes of FFh
	db	0		;end of pattern sequence
;-------sector preamble---------
	db	6,0		;6 bytes of 0
	db	1,0FEh		;1 byte of FEh (address mark)
	db	0		;end of pattern sequence
;track number (0-77)
;side number (0-1)
;sector number (1-26)
;sector size (0-3)
	db	1,0F7h		;write 2 CRC bytes
	db	11,0FFh		;11 bytes of FFh
	db	6,0		;6 bytes of 0
	db	1,0FBh		;1 byte FBh (data address mark)
	db	0		;end of pattern sequence
;---------sector data-----------
;{128,256,512} bytes of fill data
;-------sector postamble--------
	db	1,0F7h		;write 2 CRC bytes
	db	27,0FFh		;27 bytes of FFh
	db	0		;end of pattern sequence
;---track postamble character---
	db	0FFh		;this byte until index pulse

;================================
; IBM MFM double-density format =
;    T0: 8"                     =
;    T3: HD 5.25"               =
;    T4: 3.5"                   =
; (SPT pointer and buffer last  =
; address get replaced if this  =
; is a 3.5" disk)               =
;================================
T8DD:	dw	SPT8DD-1	;SPT table -1 offset
	db	0E5h		;fill the data field with this
;---------track header----------
	db	80,4Eh		;80 bytes of 4Eh
	db	12,0		;12 bytes of 0 
	db	3,0F6h		;write 3 bytes of C2h with missing clock
	db	1,0FCh		;1 byte of FCh (index mark)
	db	50,4Eh		;50 bytes of 4Eh
	db	0		;end of pattern sequence
;--------sector preamble--------
	db	12,0		;12 bytes of 0
	db	3,0F5h		;write 3 bytes of A1h with missing clock
	db	1,0FEh		;1 byte of FEh (address mark)
	db	0		;end of pattern sequence
;track number (0-77)
;side number (0-1)
;sector number (1-26)
;sector size (0-3)
	db	1,0F7h		;write 2 CRC bytes
	db	22,4Eh		;22 bytes of 4Eh
	db	12,0		;12 bytes of 0
	db	3,0F5h		;write 3 bytes of A1h with missing clock
	db	1,0FBh		;1 byte FBh (data address mark)
	db	0		;end of pattern sequence
;--------sector data------------
;{256,512,1024} bytes of fill data
;------sector postamble---------
	db	1,0F7h		;write 2 CRC bytes
	db	54,04Eh		;54 bytes of 4Eh
	db	0		;end of pattern sequence
;---track postamble character---
	db	4Eh		;this byte until index pulse

;======================================
; Low data-rate single density format =
;    T2: 5.25" "minidisk" (40-track)  =
;    T3: 5.25" QD (80-track)          =
;======================================
T5SD:	dw	SPT5SD-1	;SPT table -1 offset
	db	0E5h		;fill the data field with this
;--------track header-----------
	db	16,0FFh		;16 bytes of FFh
	db	0		;end of pattern sequence
;------sector preamble----------
	db	6,0		;6 bytes of 0
	db	1,0FEh		;1 byte FEh (address mark)
	db	0		;end of pattern sequence
;track number (0-77)
;side number (0-1)
;sector number (1-26)
;sector size (0-3)
	db	1,0F7h		;write 2 CRC bytes
	db	11,0FFh		;11 bytes of FFh
	db	6,0		;6 bytes of 0
	db	1,0FBh		;1 byte FBh (data address mark)
	db	0		;end of pattern sequence
;--------sector data------------
;{128,256,512} bytes of fill data
;------sector postamble---------
	db	1,0F7h		;write 2 CRC bytes
	db	11,0FFh		;11 bytes of FFh
	db	0		;end of pattern sequence
;---track postamble character---
	db	0FFh		;this chr until index pulse

;============================================
; "ISO" low data-rate double-density format =
;    T2: 5.25" 40-track                     =
;    T3: 5.25" QD 80-track                  =
;============================================
T5DD:	dw	SPT5DD-1	;SPT table with -1 offset
	db	0E5h		;fill data field with this
;--------track header-----------
	db	32,04Eh		;32 bytes of 4Eh
	db	0		;end of pattern sequence
;------sector preamble----------
	db	8,0		;8 bytes of 0
	db	3,0F5h		;write 3 bytes of A1h with missing clock
	db	1,0FEh		;1 byte FEh (address mark)
	db	0		;end of pattern sequence
;track number (0-77)
;side number (0-1)
;sector number (1-26)
;sector size (0-3)
	db	1,0F7h		;write 2 CRC bytes
	db	22,4Eh		;22 bytes of 4Eh
	db	12,0		;12 bytes of 0
	db	3,0F5h		;3 bytes of F5h
	db	1,0FBh		;1 of FBh (data address mark)
	db	0		;end of pattern sequence
;---------sector data-------------
;{256,512,1024} bytes of fill data
;-------sector postamble----------
	db	1,0F7h		;write 2 CRC bytes
	db	22,4Eh		;22 bytes of 4Eh
	db	0		;end of pattern sequence
;---track postamble character---
	db	4Eh		;this byte until index pulse
	
;***Load-Initialized Storage*****************
;(For some of these, 0FFh means use default )
;********************************************
STEPRT:	db	STEP5	;drive step rate
BUFEND:	DS	2	;(16-bit) this format's last addr in BUF

;the following 12 bytes must be in this order.
MAXSEC:	db	0	;max sector number
SAVDCL:	db	0	;current DCTRL value
CURFIL:	db	0	;current data fill byte
CURTRK:	db	0	;current track
CURSID:	db	0	;current side
CURSEC:	db	0	;current sector
CURBPS:	db	0FFh	;current BPS ({0-3}={128-1024})
CURDNS:	db	0	;current density (0=single, 1=double)
RQSIDE:	db	0FFh	;{0,1,0FFh}={side 0, side 1, both}
RQTYPE:	db	0FFh	;Drive type 0-4
RQNSTK:	db	0FFh	;number of 128 BPS system tracks
RQLAST:	db	0FFh	;last track number
RQFRST:	db	0	;requested first track
RQBPS:	db	0FFh	;requested BPS ({0-3}={128-1024})
RQDENS:	db	2	;requested density (1=single, 2=double)
RQDRIV:	db	0	;requested disk (0-3)
RQVRFY:	db	0	;verify (0=NO, NZ=YES)
BPS4:	db	0	;bytes per sector / 4
ERRCNT:	db	0	;verify error count
CPUMHZ:	db	0	;NZ if CPU speed = 4 MHz

SPSAVE:	dw	LSTACK	;save stack pointer here
	
;***Buffer Space***********************************************
;Note: everything that is used only by SETUP is here, so that
;it gets wiped out when the track buffer gets filled. We did
;this because memory is tight in a small memory system when
;formatting a 3.5" disk - the track buffer consumes about 11
;Kbytes by itself, and CPM's BIOS consumes another 3 KB.	
;**************************************************************
TRKBUF:	equ	$	;Grows toward 0FFFFh, overwrites SETUP

;***transient message strings*****************************
;These get overwritten by the track buffer once the input
;string has been parsed without errors.	
;**********************************************************
BANNER:
 db CR,LF,' *********************************************************'
 db CR,LF,' * Cromemco 16FDC/64FDC Floppy Disk Formatter Vers. '
 db VERSION+'0'
 db '.',VERSMIN/10+'0',(VERSMIN MOD 10)+'0',' *'
 db CR,LF,' *                   By Martin Eberhard                  *'

	IF UNDERC
 db CR,LF,' *         WARNING: version under construction.          *'
	ENDIF

 db CR,LF,' *********************************************************'
 db CR,LF+80h

HLPMSG:
 db 'Usage:'
 db ' CROFORM <d>: [/T<t>] [/D<d>] [/V] [/B<b>]'
 db ' [/F<f>] [/L<l>] [/S<s>] [/N<n>]'
 db CR,LF,'  <d>  Disk drive {A-D}'
 db CR,LF,'  /T0  77-track 8" drive (default)'
 db CR,LF,'  /T1  40-track 5.25" drive (default)'
 db CR,LF,'  /T2  80-track (QD) 5.25" drive'
 db CR,LF,'  /T3  80-track High Density (HD) 5.25" drive'
 db CR,LF,'  /T4  80-track 3.5" drive'
 db CR,LF,'  /D1  Single density'
 db CR,LF,'  /D2  Double density (default)'
 db CR,LF,'  /V   Verify after format (Default: no verify)'
 db CR,LF,'  <b>  {0-3} for {128,256,512,1024} bytes/sector'
 db CR,LF,'         (Default: {0,1} for {single,double} density disk)'
 db CR,LF,'  <f>  First track {0-99} (Default: 0)'
 db CR,LF,'  <l>  Last track {0-99} (default: whole disk)'
 db CR,LF,'  <s>  Format only side {0,1} (Default: both sides)'
 db CR,LF,'  <n>  Number of 128 byte/sector system tracks {0-9}'
 db ' (Default: 1)'
 db CR,LF,'Control-C aborts format','.'+80h

GOTAB1:	db	'12','8'+80h	;entries must be 3 bytes
	db	'25','6'+80h
	db	'51','2'+80h
	db	'102','4'+80h	;the last one can be longer

UNRMSG:	db	'Unrecognized: ','/'+80h
BVMSG:	db	'Bad value for ','/'+80h
TERMSG:	db	'First track > last trac','k'+80h
MEMMSG:	db	'Not enough memory for this forma','t'+80h
BPMSG1:	db	'Illegal sector size for a',' '+80h
RPMMSG:	db	'Disk type/RPM mismatc','h'+80h
MHZMSG:	db	'Double-density requires 4 MHz CP','U'+80h
ERRMSG:	db	'Bad inpu','t'+80h

SUMS00:	db	CR,LF,'Ready to format drive '; fall
SUMS01:	db	'X:',CR,LF,' ',' '+80h	;drive letter pasted overwrites

SUMS02:	db	' bytes/sector,',' '+80h
SUMS03:	db	' sectors/track',CR,LF
	db	'  tracks',' '+80h
SUMS04:	db	' through',' '+80h
SUMS06:	db	CR,LF,'  on side',' '+80h
SUMS05:	db	CR,LF,'  on both sides'	;Fall into SUMS07
SUMS07:	db	' of a',' '+80h
DUBLST:	db	'dou','b'+80h
SNGLST:	db	'sin','g'+80h
DENSST:	db	'le density',' '+80h
DQDST:	db	'Quad density '		;fall into D525ST
D525ST:	db	'5.2','5'+80h
DHDST:	db	'High Density 5.2','5'+80h
D8ST:	db	'8'+80h
D35ST:	db	'3.','5'+80H
SUMS09:	db	'" '			;fall into DSKST
DISKST:	db	'dis','k'+80h
SUMS10:	db	CR,LF,'  128 bytes/sector system tracks'
	db	' through track',' '+80h
SUMS11: db      ' ',' '+80h
SUMS12:	db	'no '	;fall into SUMS13
SUMS13: db	'verify after forma','t'+80h
CONFST:	db	'Correct? (Y/N):',' '+80h
BUMSG:	db	BS,' ',BS+80h
BDSTR:	db	'Illegal /D0'

;***command line input table******************************
;Each entry is an ASCII option chararacter, then the max
;value for the option plus 1, then the address of the
;memory location Where that option is saved.
;The table must be in alphabetic order and is terminated
;with 0FFh.	
;*********************************************************
OPTTAB:	db	'B',4	;bytes/sector, max value is 3
	dw	RQBPS
	db	'D',3	;density, max value is 2
	dw	RQDENS
	db	'F',100	;track to format, max value is 99
	dw	RQFRST
	db	'L',100	;max track number, max value is 99
	dw	RQLAST
	db	'N',10	;number of SD SYS tracks, max=9
	dw	RQNSTK
	db	'S',2	;side, max value is 1
	dw	RQSIDE
	db	'T',5	;Drive type, max 4
	dw	RQTYPE
	db	'V',2	 ;verify, no value
	dw	RQVRFY
	db	0FFh	;END of table marker

;***Subroutine************************************************
;SETUP
;Parse command line options passed to us by CP/M, validate
;and save resulting values in memory for the format, print
;user confirmation message, and get a Y/N confirmation

;This code gets overwritten by the track buffer during format
;On Entry:
;  sp is valid
;  COMBUF contains a valid string from CP/M
;  program load initialized RAM variables
;On Exit:
;  SAVDCL is valid, with the possible exception of density
;  STEPRT=step rate
;  RQDRIV=requested disk (0-3)
;  RQTYPE={0,1,2,3,4} for {8",mini,QD,HD,3.5"}
;  RQDENS=requested density (0=single, 1=double)
;  RQBPS=requested bytes/sector ({0,1,2,3}={128,256,512,1024})
;  RQSIDE={0,1,0FFh}={side 0, side 1, both}
;  RQFRST=starting track number
;  RQLAST=ending track number
;*************************************************************

;--------------------
;Print sign-on banner
;--------------------
SETUP:	lxi	d,BANNER
	call	PCRSTR
;--------------------------------------------------
;Begin to parse options passed via the command line
;--------------------------------------------------
	lxi	h,COMBUF	;Where CP/M put the command

	mov	a,m		;first byte is byte count
	ora	a		;Zero bytes means help
	db	JRZ,HLPEXT-$-1	;No options: print help string

	mov	b,a		;B keepS byte count
	inr	b		;B gets byte count + 1
	inx	h		;PAST byte count to string
;------------------------------------------------------------
;Get the requested drive number followed by a mandatory colon
;------------------------------------------------------------
	call	SKIPW		;skip white space to get chr
	db	JRZ,HLPEXT-$-1	;No options: print help string

	cpi	'?'		;Is user asking for help?
	db	JRZ,HLPEXT-$-1	;Y: help user

	ani	05Fh		;strip lowercase-ness
	sui	'A'		;minimum drive letter
	cpi	MAXDRV-'A'+1	;One plus max drive (e.g.1-4)
	db	JRNC,INPERR-$-1

	sta	RQDRIV		;remember requested drive
	
	call	GETCHR		;get mandatory colon
	db	JRZ,INPERR-$-1

	cpi	':'		;test for colon and clear a
	db	JRZ,GETOPT-$-1
;---------------------
;deal with input error
;---------------------
INPERR:	lxi	d,ERRMSG	;General bad input message
	call	PCRSTR		;print CRLF then string

;Fall into HLPEXT

HLPEXT:	mvi	a,0FFh		;don't ask to replace disk
	sta	RQDRIV
	lxi	d,HLPMSG	;print help string, then exit
	jmp	PRTEXT
;----------------------------------------------------------
;Get options and parameter values from the cmd line buffer.
;Each option must be preceeded by a slash and followed by 1
;or 2 numeric characterS. Options may also be precveeded by
;any number of spaces.
;Entry to this loop is at GETOPT near the end
;----------------------------------------------------------
OPTLUP:	cpi	'/'
	db	JRNZ,(INPERR-$-1) xor 0FF00h

	call	GETCHR		;get option character
	db	JRZ,(INPERR-$-1) xor 0FF00h
				;error: nothing after /

	ani	05Fh		;strip lowercase-ness
	mov	c,a		;save it while we get value

	call	GETVAL	;d=value that came with option
			;deal with value error (carry=1) later
;----------------------------------------------------------
;Got a command line option in c and a value for that option
;in d. (Carry set means bad value found.) Loop through
;valid option table to find matching option. Option-error
;exit if no match. Only of there is a match, check for a
;legit value, and check max size of value. Value-error exit
;if bad value or value too big. Save good parameter value
;in the location specified in the table.
;Trashes de and hl	
;-----------------------------------------------------------
	push	h	;pointer into cmd line entry
	push	psw	;carry flag from GETVAL
	mov	a,c	;option chr

	lxi	h,OPTTAB-3

CHKLUP:	inx	h
	inx	h
	inx	h

	cmp	m	;matching option? (alphabetic order)
	inx	h	;prepare for upper bounds check
	jc	UNRERR	;exit leaving stack hosed bad opt in C
	db	JRNZ,(CHKLUP-$-1) xor 0FF00h
			;no match: go try next option in table

	pop	psw	;recover carry from GETVAL
	jc	BVERR	;bad value of some sort

	mov	a,d	;match: get value that came with option
	cmp	m	;Is value bigger than max?
	jnc	BVERR	;too big: exit leaving stack hosed

	inx	h	;point de to storage address
	mov	e,m
	inx	h
	mov	d,m
	stax	d	;save the option parameter value

	pop	h	;restore pointer to command line entry
;---------------------------------------
;Get the next input chr, if there is one
;---------------------------------------
GETOPT:	call	SKIPW	;look for input or end of parameters
	db	JRNZ,(OPTLUP-$-1) xor 0FF00h
;-----------------------------------------------
;All command line parameters have been processed
;Test for illegal /D0
;-----------------------------------------------
	lxi	d,BDSTR		;bad density
	lxi	h,RQDENS
	mov	a,m
	ora	a
	jz	PRTEX3

	dcr	m		;Make it 0 or 1
;----------------------------------------------
;Ask the user to load the disk and press return
;we come back here for disk errors during SETUP
;----------------------------------------------
SETASK:	xra	a
	out	DCTRL		;de-select drive to unlock door

	mvi	a,SIDE0		;select side 0
	out	ADCMD		;in case new disk is 1-sided

	call	ASKLOD
;------------------------------------------------
;Set the drive select and motor-on bits in SAVDCL
;------------------------------------------------
	lda	RQDRIV		;get selected disk number
	mov	b,a
	inr	b		;b has disk number 1-4

	xra	a
	stc			;prepare to set select bit

SUP2:	ral			;loop select bit into place
	db	DJNZ,(SUP2-$-1) xor 0FF00h

	ori	DCMOTO		;motor on
	sta	SAVDCL
;------------------------------------------
;Select the disk, and restore it to track 0
;------------------------------------------
	mvi	b,2		;several steps in first
	call	STPRST		;step in, restore the drive
	db	JRC,(SETASK-$-1) xor 0FF00h
				;error: ask for a new disk
;----------------------------------------------------------
;Check if the disk is spinning, and if so, how fast. 8"
;and HD 5.25" drive index pulses nominally come every
;166.7 mS, while 3.5" and 5.25" drive index pulses come
;every 200 mS.
;----------------------------------------------------------
	call	TMINDX
	db	JRC,(SETASK-$-1) xor 0FF00h
			;carry: timeout or hard sectored
;----------------------------------------------------------
;Decide whether this disk is spinning at 300 RPM (5.25 or
;3.5" disk) or at 360 RPM (8" or HD disk). Remember that
;the CPU may be running at 2 MHz or 4 MHz.
;
;Nominal values:
;         2 MHz CPU        4 MHz CPU
; RPM    cycles    hl     cycles   hl
; 300    400000  7017     800000  14035
; 360    333333  5848     666666  11696
;
;If hl > 9216 (2400H) then assume the CPU is running
;at 4 MHz, and divide hl by 2. Next, assume the disk
;is spinning at 300 RPM if hl>6400 (1900H)
;  hl=index time, in units of 56 CPU cycles
;---------------------------------------------------------
	mov	a,h		;2MHz or 4 MHz CPU?
	cpi	24h		;carry set if 2 MHz
	db	JRC,SUP3-$-1
	rar			;divide 4 MHz Measurement by 2
	sta	CPUMHZ		;NZ means 4 MHz
SUP3:
	cpi	19h		;carry set if 360 RPM
	mvi	a,1
	sbi	0		;catch borrow
	mov	h,a		;h=1 if 300 RPM, 0 if 360 RPM

;---------------------------------------
;If no drive type specified, then set it
;to the default, based on the disk's RPM
;---------------------------------------
	lda	RQTYPE
	mov	l,a
	inr	a		;FFh (for default)?
	db	JRNZ,SUP4-$-1

	mov	a,h		;h=1 if 300 RPM, 0 if 360 RPM
	sta	RQTYPE		;360 RPM: type 0
	mov	l,a		;..300 RPM: Type 1
SUP4:
;----------------------------------------------------
;See if the disk RPM makes sense for the specified
;drive type, and error-exit if not.
; Type        Disk        RPM   Data Rate  Max Track
;  0           8"         360     MAXI        76
;  1      5.25" 40-track  300     MINI        39
;  2      5.25" QD        300     MINI        80
;  3      5.25" HD        360     MAXI        80
;  4          3.5"        300     MAXI        80
;l=RQTYPE=specified drive type
;----------------------------------------------------
	mov	a,l
	ora	a		;8"?
	db	JRZ,SUP5-$-1

	xri	3
	db	JRZ,SUP5-$-1
	mvi	a,1

SUP5:	cmp	h
	jnz	RPMERR		;Wrong RPM for drive	
;--------------------------------------------------------
;Set up SAVDCTRL with the correct data rate for the drive
;--------------------------------------------------------
	mov	a,l		;RQTYPE
	sui	1
	cpi	2		;T1 and T2 are mini-speed
	mov	a,l		;RQTYPE
	db	JRC,SUP6-$-1	

	lxi	h,SAVDCL
	dw	SET4M		;DCMAXI on
SUP6:
;----------------------------------------------
;Set step rate, and put default max track in c.
;  a=RQTYPE
;----------------------------------------------
	lxi	b,STEP3*256 + DMTQH3
	ora	a
	db	JRNZ,SUP7-$-1
	lxi	b,STEP8*256 + DMAXT8
SUP7:	dcr	a
	db	JRNZ,SUP8-$-1
	lxi	b,STEP5*256 + DMAXT5
SUP8:
	mov	a,b		;step rate
	sta	STEPRT
;-------------------------------------------
;If default bytes/sector requested, then set
;RQBPS according to the requested density,
;or the seperate default, if 3.5" disk. Make
;sure a 3.5" disk is DD while we are at it.
; c=default last track number
;-------------------------------------------
	lxi	h,RQDENS

	mov	b,m		;density request is 0 or 1
	dcx	h		;point to RQBPS

;All drive types except 0 and 1 are always DD
;and default to 512BPS

	lda	RQTYPE		;QD, HD, or 3.5"?
	cpi	2
	mov	a,m		;RQBPS
	db	JRC,SUP9-$-1	;N: It's 8" or mini

;Default BPS for QD, HD, and 3.5"

	inr	a		;RQBPS=FFh?
	db	JRNZ,SUP9-$-1
	mvi	m,DBPQH3	;default BPS

;b=RQDENS

SUP9:	inr	a		;0FFh=default:
				;SD=128 BPS, DD=256 BPS
	db	JRNZ,SUP10-$-1

	mov	m,b		;(128 BPS or 256 BPS)	
SUP10:

;Defaults are valid combos, so we will pass BPS check below.
;-------------------------------------------------------------
;Check density/sector size combo - there are 2 illegal
;combinations: SD, 1024 bytes/sector and DD, 128 bytes/sector.
;On Entry:
;  (hl)=RQBPS (0-3)
;  b=RQDENS (0-1)
;  c=default last track number
;-------------------------------------------------------------
	mov	a,m
	dcr	b		;Z means DD
	db	JRZ,CHKDD-$-1
	sui	3		;1024 BPS is illegal for SD

CHKDD:	ora	a		;128 BPS is illegal for DD
	jz	BPSERR
;----------------------------------------------------------
;Set default last track number, if no last track specified.
;On Entry:
;  c=default last track number
;----------------------------------------------------------
	lxi	h,RQLAST
	mov	a,m		;did user specify last track?
	inr	a		;0FFh means no
	db	JRNZ,NODLST-$-1	;Y: use specified last track

	mov	m,c		;set last track to default
NODLST:	mov	c,m		;last track number in c	
;-------------------------------------------
;Check that first track <= last track. Start
;building the disk type confirmation string.
;On Entry:
;  c=RQLAST
;  hl points to RQLAST
;-------------------------------------------
	inx	h		;point to RQFRST
	mov	a,c
	cmp	m		;c should be >= RQFRST

	lxi	d,TERMSG	;'First track > last track'
	jc	PRTEXT		;error exit
;-------------------------------------------------------------
;If the user didn't specify how many system tracks, set up
;the default, 1 system track. If the last system track is
;higher than the last requested track to format, adjust the
;last system track to equal the last track to format.
;On Entry:
;  c=RQLAST
;  hl points to RQFRST
;-------------------------------------------------------------
	dcx	h
	dcx	h		;point hl to RQNSTK

	mov	a,m
	inr	a		;0FFh=default
	db	JRNZ,NSTCHK-$-1

	inr	a		;default 1 system track
	mov	m,a		;number of system tracks
	mov	a,c		;last track

NSTCHK:	cmp	m		;last SYS track > last track?
	db	JRNC,NSTOK-$-1
	mov	m,c		;Y: last SYS track=last track
NSTOK:

;-------------------------------------------------
;Check if trying to format a MAXI disk at double-
;density, with a 2 MHz CPU (which won't work)
;-------------------------------------------------
	lda	CPUMHZ		;NZ means 4 MHZ
	ora	a
	db	JRNZ,MHZOK-$-1

	lda	SAVDCL		;Maxi mode?
	ani	DCMAXI
	db	JRZ,MHZOK-$-1	;N: ok

	lda	RQDENS		;requesting double density?
	ora	a		;0 means single-density

	lxi	d,MHZMSG	;CPU to slow
	jNZ	PRTEXT		;error exit
MHZOK:
;-------------------------------------------------------------
;All user parameters are in & tested good. Get disk parameters
; RQDRIV=requested disk (0-3)
; RQDENS=requested density (0=single, 1=double)
; RQBPS=bytes/sector ({0,1,2,3}={128,256,512,1024})
; RQTYPE={0,1,2,3,4} for {8",mini,QD,HD,3.5"}
; RQSIDE={0,1,0FFh}={side 0, side 1, both}
; RQFRST=starting track number
; RQLAST=ending track number
;-------------------------------------------------------------
	lhld	RQBPS		;RQBPS & RQDENS
	shld	CURBPS		;CURBPS & CURDNS
	call	PARAMS		;Go lookup BUFEND
;-----------------------------------------------------------
; de=BUFEND
; Address of the right format table for this format on stack
; SAVDCL=mini/maxi + motor on + SD/DD + drive select bits
; STEPRT=step rate
; BUFEND has last address we will use in the track buffer
; MAXSEC has the highest sector number for every track
; BPS4=bytes per sector / 4
;Trashes psw,bc,de
;------------------------------------------------------------
;Now that we Know BUFEND, check if we have enough memory in
;the transient program area to format this kind of disk. 
;------------------------------------------------------------
	lhld	WBOOTA		;get BIOS address
	mov	a,e		;16-bit compare
	sub	l
	mov	a,d
	sbb	h		;carry set if de<(WBOOTA)

	lxi	d,MEMMSG	;'not enough memory...'
	jnc	PRTEXT		;error exit
;-----------------------------------
;Print details of planned format and
;wait for confirmation from the user
;-----------------------------------
	lda	RQDRIV		;put drive letter 'A'-'D'
	adi	'A'
	sta	SUMS01		;...into the string

	lxi	d,SUMS00	;'Ready to format drive X:'CRLF
	call	PCRSTR

;Compute and print the bytes/sector string

	lda	RQBPS		;figure out bytes/sector
	mov	e,a
	add	a		;a=RQBPS*2
	add	e		;a=RQBPS*3

	mov	e,a
	mvi	d,0
	lxi	h,GOTAB1	;add to BPS string table base
	dad	d
	xchg			;string address in de

	call	PRTSTR		;print bytes/sector string

	lxi	d,SUMS02	;' bytes/sector, '
	lda	MAXSEC		;print maximum sector number
	call	STRVAL		;string then value			
	
	lxi	d,SUMS03	;' sectors/track,',CRLF
				;'   on tracks ' 
	lda	RQFRST		;first track number
	mov	c,a		;C=first track to do
	call	STRVAL		;string then value

	lxi	d,SUMS04	;' through '
	lda	RQLAST		;last track number
	call	STRVAL		;string then value

	lda	RQSIDE		;both sides?
	inr	a
	db	JRNZ,CONF1-$-1
	lxi	d,SUMS05	;CRLF'  on both sides of a'
	db	JR,CONF2-$-1

CONF1:	lxi	d,SUMS06	;' on side '
	dcr	a		;print side number
	call	STRVAL		;string then value

	lxi	d,SUMS07	;' of a '
CONF2:	call	PRTSTR

	lda	RQTYPE
	lxi	d,D8ST		;8"
	ora	a
	db	JRZ,CONF3-$-1

	lxi	d,D525ST	;5.25"
	dcr	a
	db	JRZ,CONF3-$-1

	lxi	d,DQDST		;Quad density 5.25"
	dcr	a
	db	JRZ,CONF4-$-1

	lxi	d,DHDST		;high density 5.25"
	dcr	a
	db	JRZ,CONF4-$-1

	lxi	d,D35ST		;3.5"
CONF3:
	push	d		;remember result for a moment
	call	SINDBD		;print 'sing' or 'doub'
				;..based on CURDNS

	lxi	d,DENSST	;'le density '
	call	PRTSTR

	pop	d		;drive type	

CONF4:	call	PRTSTR		;print it

	lxi	d,SUMS09	;'" disk'
	call	PRTSTR

	lda	RQBPS		;128 bytes/sector?
	ora	a
	db	JRZ,CONF5-$-1	;Y: no need to do system tracks

	lda	RQNSTK		;number of system tracks
	ora	a		;0 no system tracks to do
	db	JRZ,CONF5-$-1	;jump if none

	dcr	a		;a=last system track number

	cmp	c		;touch any system tracks?
	db	JRC,CONF5-$-1	;N: nothing to say

	lxi	d,SUMS10	;'system tracks...'
	call	STRVAL		;value in a,string at de
CONF5:

	lxi	d,SUMS11	;'  '
	call	PCRSTR

	lxi	d,SUMS12	;'no '
	lda	RQVRFY		;verify requested?
	ora	a
	db	JRZ,CONF6-$-1

	lxi	d,SUMS13	;'verify after format'
CONF6:	call	PRTSTR		

;Loop to get a Y/N response from the user, allowing deletes

ASKYN1:	lxi	d,CONFST	;'Correct? (Y/N): '
	call	PCRSTR

ASKYN2:	lxi	b,0		;c=1st chr, b=chr count

ASKYN3:	push	b
	mvi	a,CONIN		;Address offset to CONIN
	call	DOBIOS		;Returns chr in a
	pop	b

	cpi	CR
	db	JRZ,ASKYN6-$-1

	cpi	DEL		;backing up?
	db	JRZ,ASKYN4-$-1

	cpi	BS		;DEL or BS backs up
	db	JRNZ,ASKYN5-$-1

ASKYN4:	db	DJNZ,(ASKYN2-$-1) xor 0FF00h
				;nothing to delete

	lxi	d,BUMSG		;erase old chr
	call	PRTSTR		;BS,' ',BS
	db	JR,(ASKYN2-$-1) xor 0FF00h

ASKYN5:	mov	c,a		;got a character

	mvi	a,BS
	dcr	b		;already have a chr?
	cz	PRINTA		;y: delete it

	mov	a,c		;Echo the character
	call	PRINTA

	mvi	b,1		;count=1
	db	JR,(ASKYN3-$-1) xor 0FF00h

ASKYN6:	mvi	a,5Fh		;lowercase is ok
	ana	c		;single chr from user

	cpi	'N'
	jz	GUDBYE		;Goodbye message, and quit

	cpi	'Y'
	db	JRNZ,(ASKYN1-$-1) xor 0FF00h
				;Ask again if not Y or N
;------------------------------------------------------------
;On Exit:
;  SAVDCL is valid, with the possible exception of density
;  STEPRT=step rate
;  RQDRIV=requested disk (0-3)
;  RQTYPE={0,1,2,3,4} for {8",mini,QD,HD,3.5"}
;  RQDENS=requested density (0=single, 1=double)
;  RQBPS=requested bytes/sector ({0,1,2,3}={128,256,512,1024})
;  RQSIDE={0,1,0FFh}={side 0, side 1, both}
;  RQFRST=starting track number
;  RQLAST=ending track number
;-------------------------------------------------------------
	ret

;***Subroutine**********************************************
;Print either 'sing' or 'doub'
;On Entry at SINDUB: a=0 for single, 1 for double
;On Entry at SINDBD: RQDENS is used to decide which to print
;Trashes psw,de
;***********************************************************
SINDBD:	lda	RQDENS
SINDUB:	lxi	d,SNGLST
	ora	a
	db	JRZ,SINGLE-$-1
	lxi	d,DUBLST
SINGLE:	jmp	PRTSTR		;return through PRTSTR

;***Subroutine********************************************
;Skip over white space in the command
;line buffer until a character is found
;On Entry:
;  b has remaining byte count + 1
;  hl points to buffer
;On Exit:
;  a=buffer character, Z cleared
;  b has been decremented
;  hl has been advanced
;  Z=set means end of buffer without data (a is invalid)
;*********************************************************
SKIPW:	call	GETCHR
	rz			;Z set for end

	cpi	' '		;white space
	db	JRZ,(SKIPW-$-1) xor 0FF00h

	ret			;character in a, Z cleared

;***Subroutine******************************************
;Get the next character from the command line buffer
;On Entry:
;  b has remaining byte count + 1
;  hl points to buffer
;On Exit:
;  a=chr
;  b has been decremented
;  hl has been advanced
;  Z=1 means end of buffer without data (a is invalid)
;  carry=0
;*******************************************************
GETCHR:	mov	a,b		;already at end of buffer?
	ora	a		;also clears carry
	rz

	mov	a,m		;get next character
	inx	h		;bump buffer pointer
	dcr	b		;will set Z if 0	
	ret			;Z set/cleared by deC B

;***Subroutine*******************************************
;Get 1- or 2-digit decimal number from the buffer.
;If the option was /V, then return with d=1	
;On Entry:
;  a=option character
;  b has remaining byte count + 1
;  hl points to buffer
;On Exit:
;  d=value in binary
;  b has been decremented as needed
;  hl has been advanced as needed
;  carry set means bad value
;Trashes e
;********************************************************
GETVAL:	mvi	d,1		;do verify
	cpi	'V'		;/V has no value
	rz			;carry will be cleared too

	dcr	d		;d=0: accumulate result in D
	call	GETDIG		;get the first digit
	db	JRNZ,GETV1-$-1	;was there a 1st character?
	stc			;set carry to indicate error
GETV1:	rc			;error return

;Fall into GETDIG to get 2nd digit

GETDIG:	call	GETCHR		;also clears carry
	rz			;Z set for no character
	cpi	' '		;space means end, but not error
	rz
	cpi	'/'		;Params crammed together?
	jz	BACKUP		;OK, but backup
	sui	'0'		;Min value is 0
	rc			;carry set for bad value
	cpi	10
	cmc
	rc			;carry set for bad value
	
;Compute d=d*10 + a

	mov	e,a		;save new good digit
	mov	a,d		;move old digit over to 10's
	add	a		;a=D*2
	add	a		;a=D*4
	add	d		;a=D*5
	add	a		;a=D*10

	add	e		;a=d*10+a (add in new digit)
				;clears carry too
	mov	d,a		;result in d
	inr	e		;clear the Z flag
	ret			;result in d, carry=overflow

BACKUP:	dcx	h		;undo GETCHR
	inr	b
	xra	a		;set Z flag for no new digit
	ret			;result in d

;***Error Exits**************************
;All exit eventually via BIOS warm boot,
;so it is okay to hose the stack.
;****************************************
BVERR:	lxi	d,BVMSG		;bad parameter value
	db	JR,PRTEX1-$-1

UNRERR:	lxi	d,UNRMSG	;Unrecognized parameter

PRTEX1:	call	PCRSTR		;print CRLF then string
	mvi	a,0FFh		;don't ask user to replace disk
	sta	RQDRIV
	mov	a,c		;now print the offending chr
	call	PRINTA
	db	JR,GOCRXT-$-1

BPSERR:	lxi	d,BPMSG1	;'illegal sector size for a '
PRTEX2:	call	PCRSTR
	call	SINDBD		;'sing' or 'doub'

	lxi	d,DENSST	;'le density '
	call	PRTSTR
	lxi	d,DISKST	;'disk'
	call	PRTSTR

	db	JR,GOCRXT-$-1

RPMERR:	lxi	d,RPMMSG	;Speed problem
PRTEX3:	call	PCRSTR		;print CRLF then string

GOCRXT:	jmp	CREXIT
;---------------------------------------------
;Stack used during SETUP only. (During format,
;the stack is in the command line buffer.)
;---------------------------------------------
	ds	32
LSTACK:	equ	$

	END	100H
	