; REVISION HISTORY                                            *
; 1.20  3 December 2021  M. Eberhard                          *
;   Add support for QD and HD drives, just by looking at the  *
;   last track specified by /L. Step faster when possible.    *
; 1.19  22 March 2021  Eberhard                               *
;   Restore to track 0 when done. Don't select drive A when   *
;   returning to CP/M (when done).                            *
; 1.18  17 March 2021  Eberhard                               *
;   Allow SD and various BPS for 3.5" disk format (even       *
;   though these may not be supported by the BIOS). But keep  *
;   the 3.5" disk format default to be DD, 512 BPS            *
; 1.17  6 January 2021  M. Eberhard                           *
;   Support 3.5" 2SDD drives. Reformat source code to use     *
;   lowercase. Change nomenclature to 5.25" instead of mini.  *
; 1.16  31 July 2020  M. Eberhard                             *
;   Debug 1.15                                                *
; 1.15  30 July 2020  Eberhard                                *
;   Support voicecoil drives too. Add 560 uS stall after      *
;   write, for trim erase.                                    *
; 1.14  17 May 2018  Eberhard                                 *
;   Eliminate BDOS call during INIT                           *
; 1.13  14 February 2018  Eberhard                            *
;   Require CR on Y/N question at beginning                   *
; 1.12  20 April 2017  Eberhard                               *
;   Fix system track count bug                                *
; 1.11 28 January 2017  Eberhard                              *
;   Speed up format loop and verify loops. Just barely fast   *
;   enough for a 2 MHz CPU to format double-density 8" disks. *
; 1.10  20 January 2017 Eberhard                              *
;   Support 2-sided minidisks. Format 40 tracks (not 35) on   *
;   minidisks. Mini default: format both sides. Format side 0 *
;   last, so 1-sided minidisks format right even if "both"    *
;   sides formatted. (But verify will fail in this case.)     *
;   Allow buffer to over-write CP/M's BDOS and CCP. (Use BIOS *
;   calls instead of BDOS calls)                              *
; 1.09  14 October 2010 Eberhard fix /F=/L bug                *
; 1.08  15 September 2010 Eberhard                            *
;   Code squeeze, fix DI/EI, SA851 step rate when 2-sided     *
;   detected, better ctrl-C detection                         *
; 1.07  14 September 2010 Eberhard                            *
;   Format system tracks first. Format multiple disks         *
; 1.06   9 September 2010 Eberhard                            *
;   Code squeeze, fix minor bug in system track test after    *
;   format.                                                   *
; 1.05   7 September 2010 Eberhard                            *
;   Detect & reject hard-sectored disks, fix 2-sided bug from *
;   version 1.04                                              *
; 1.04  29 August 2010 Eberhard                               *
;   Add /V verify option, detect ctrl-c, tighten code a bit   *
; 1.03  19 August 2010 Eberhard                               *
;   Fix error check and add delay in DODCMD, fix index timer  *
; 1.02   4 June 2010   Eberhard Step-in before restore        *
; 1.01  26 May 2010    Eberhard  Select drive A on exit       *
; 1.00  19 May 2010    Eberhard  Created                      *
;**************************************************************
FALSE	equ	0
TRUE	equ	not FALSE
;----------------------
;This program's version
;----------------------
VERSION	equ	1	;major version number
VERSMIN	equ	20	;minor version number
UNDERC	equ	FALSE	;TRUE if version is under construction

;**************************************************************
; This program formats a variety of disks via the California
; Computer Systems 2422 disk controller. (See the CCS 2422
; manual for details.) The CCS 2422 is 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
;   Single- or double-density disks
;
; 1-sided disks may be formatted in either a 1-sided or a
; 2-sided drive.
;
; The CSS 2422 disk controller board requires 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 8" disk with a 2 MHz CPU
; is only possible if no waitstates are inserted by either the
; CPU or the memory board. Note that the CCS 2810 Z/80 CPU
; board's M1 jumper causes the insertion of 1 waitstate every
; M1 cycle. This jumper must not be in the 'ON' position.
;
; 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 5.25" drives give no indication of sidedness, default
; (2-sided) formatting on a 1-sided 5.25" 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 5.25" 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 track buffer begins at TRKBUF, and the longest
; track is the one called 'BL3DD', which is 11628 bytes long
; (2D6Ch). Assuming  large (3K-byte) BIOS, this program will
; work in a system with as little as 16 kbytes of RAM
; (implying a 14K-byte CP/M build).
;
; If there is not enough room for any given track format, this
; program will detect the problem, notify the user, and exit
; without misbehaving.
;**************************************************************
;-------------------
;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
SYSTK8	equ	1	;number of 128 BPS tracks: 8" disks
SYSTK5	equ	1	;number of 128 BPS tracks: minidisks
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

;--------------------------------
;CCS 2422 Disk Controller Equates
;--------------------------------
BCTRL	equ	4		;Control port 2
BSTAT	equ	4		;Status port 2
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 maches 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" step rate
STEP5	equ	RS10MS		;SA400L step rate=20 mS
				;SA450 (2-sided) is the same
				;(minidisk mode doubles RS10MS)
STEPQD	equ	RS03MS		;quad density step rate (6 mS)
STEPHD	equ	RS03MS		;HD step rate (3 mS)
STEP8	equ	RS15MS		;SA800 step rate=8 mS
STP8D	equ	RS15MS		;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

;Control register 2 (BCTRL) output bits

BCEJECT	equ	00000100b	;remote eject ctrl (PerSci)
BCFSEKN	equ	00010000b	;0=fast seek: voicecoil drives
BCSIDE0	equ	01000000b	;0=side 1, 1=side 0
BCROMEN	equ	10000000b	;enable onboard ROM

;Composite BCTRL values used to select disk side
;Note: ROM is actually disabled via the page register

SIDE0	equ	BCROMEN+BCFSEKN+BCSIDE0
SIDE1	equ	BCROMEN+BCFSEKN

;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

;Status register 1 (DFLAG) input bits

DFINTRQ	equ	00000001b	;WD1793 INTRQ, active high
DFDS1	equ	00000010b	;DS1 in ctrl reg 1
DFDS2	equ	00000100b	;DS2 in ctrl reg 1
DFDS3	equ	00001000b	;DS3 in ctrl reg 1
DFDS4	equ	00010000b	;DS4 in ctrl reg 1
DFSHLD	equ	00100000b	;WD1793 HLD, active high
DFAUTOB	equ	01000000b	;autoboot jumper, 0=autoboot
DFSDRQ	equ	10000000b	;WD1793 DRQ, 1=ready

;Status register 2 (BSTAT) input bits

BTRK0	equ	00000001b	;when on track 0: 1=maxi
BMINI	equ	00000010b	;-DCTRL reg bit: 1=5.25"
BWPRT	equ	00000100b	;0=disk is write-protected
BSIDE0	equ	00001000b	;SIDE0 bit in ctrl reg 2
BINDEX	equ	00010000b	;index, active LOW
BDDEN	equ	00100000b	;DDEN in ctrl reg 1
BSIDED	equ	01000000b	;0=selected disk is 2-sided
;-------------------------------------------------------------
;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, the sector preamble, the sector data, and
;the sector postamble varies depending on the 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.
;
;These values 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
;-------------
NULL	equ	0
CTRLC	equ	3		;ETX
BELL	equ	7		;bell character
BS	equ	8		;backspace
CR	equ	0Dh		;carriage return
LF	equ	0Ah		;line feed
DEL	equ	7Fh		;delete
;*************************************
;Beginning of format program execution	
;*************************************
	org	100H
	jmp	START		;skip over advertisement
	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}
;DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
;DET35=1 if 3.5" drive detected, 0 otherwise
;SAVDCL=mini/maxi + motor on + SD/DD + drive select
;SAVCMD=head load and 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)
; DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
; DET35=1 if 3.5" drive detected, 0 otherwise
; 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
; SAVCMD=head load and 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 BCTRL
;  b=side number
;  hl=address of the right format table for this format
;------------------------------------------------------
DOSIDE:	out	BCTRL

	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)
; DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
; DET35=1 if 3.5" drive detected, 0 otherwise
; 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
; SAVCMD=head load and 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
	dcr	b
	jnz	DOSEC1

	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
	dcr	b
	jnz	DODAT4

	dcr	c
	jnz	DODATA
;------------------------------------------
;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)
	jc	DOSECT		;N: do another sector
				;a=sector to do
;--------------------------------------------------------------
;All sectors on this track 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)
; DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
; DET35=1 if 3.5" drive detected, 0 otherwise
; 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
; SAVCMD=head load and 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
	jnz	PSTLUP		;N: loop to fill buffer
	mov	a,l		;low byte maxed out?
	cmp	e
	jnz	PSTLUP		;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
	jnz	DOSIDE		;do same track on side 0
				;..IN b, a set up for BCTRL
;---------------------------------------------
;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	BCTRL		;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

	jc	ASKAGN		;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?
	jc	ASKAGN		;Y: try again

;---------------------------------------------------------
;Test to see if the new disk has fewer sides than the
;previous one. Quit if so. If okay, go format another disk
;---------------------------------------------------------
	call	CHKSID
	jnz	DODISK		;sidedness OK: format the disk
	jmp	ASKAGN		;not OK: get another disk

;***Subroutine*********************************************
;Test for side-2 format on a 1-sided disk.
;If a 3.5" disk or a 2-sided 8" disk is detected, then
;set up for the faster SA851 step rate
;On Entry:
;  DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
;  DET35=1 if 3.5" drive detected, 0 otherwise
;On Exit:
;  Z set indicateS error, message has already been printed
;Trashes a
;**********************************************************
CHKSID:

;5.25" disks give no indication of sidedness

	lda	DETSIZ		;1=8" disk
	dcr	a		;NZ means 5.25" disk
	rnz			;assume 5.25" disk is 2-sided
	
;3.5" and 8" disks indicate sidedness in BSTAT

	in	BSTAT		;get sidedness from disk
				;NZ means 1-sided disk detected
	cma			;SO that 0 means 1-sided
	ani	BSIDED		;Z=1-sided disk was found
	sta	DETSID		;remember sidedness
	jz	SSDisk

;2-sided 3.5" or 8" disk - it must be an SA851
;or a 3.5" drive, so bump the step rate

CSID1:	mvi	a,hDLOAD+STP8D
	sta	SAVCMD	
	ret			;with Z flag cleared

;It's a single-sided disk. Error if side 1 requested.
;If both sides requested, just do side 0.

SSDisk:	lda	RQSIDE		;requested side (0,1,0FFh)
	dcr	a		;was it a 1?
	jz	SSDSK1		;Y: can't do that

	xra	a		;both becomes 1
	sta	RQSIDE
	inr	a		;clear Z
	ret

SSDSK1:	lxi	d,SERMSG	;'No side 1 on this disk'

;Fall into PCRSTR, which exits with Z flag set

;***Subroutine************************
;Print string at (de) on CP/M console
;bc and hl are preserved
;Entry at PCRSTR prints a CR-LF first.
;Entry at PRTSTR does not.
;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
;On Exit:
;  Z flag set
;Trashes a,de
;********************************************
PRTSTR:	ldax	d		;message chr
	call	PRINTA		;print chr (strips MSB)
	inx	d		;next byte
	ora	a		;MSB set?
	JP	PRTSTR		;N: keep printing

	xra	a		;set Z flag for ret
	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'
	jnz	ASKLOD		;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
	jnz	EXIT1		;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!
	jnz	EXIT0

EXIT1:	jmp	WBOOT

;***Subroutine*********************************************
;Get disk parameters based on drive size & density
;
;On Entry:
;  DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
;  DET35=1 if 3.5" drive detected, 0 otherwise
;  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 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

	lda	CURDNS		;0=single-density, 1=double
	rrc			;shove LSB into carry, a=0

	push	psw		;save carry bit=density
	rrc			;DCDDEN is 01000000b
	ora	b		;put DDEN state in SAVEDCL
	sta	SAVDCL
	out	DCTRL		;W/ correct density
	pop	psw		;recover carry=density

	lda	DETSIZ		;IS it a 5.25" disk?
	ral			;maxi and DDEN bits into place
	ral			;index into 16-bit/entry table	

	mvi	d,0		;d=0 for high byte of DAD
	mov	e,a		;index into table
	lxi	h,DFSTAB	;5.25" disk format select table
	dad	d
;-----------------------------------------
;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	DET35		;3.5" drive?
	ora	a
	push	psw		;remember forlater
	jz	PARAM3

	lxi	d,SPT3DD-1	;special case for 3.5"

	lda	CURDNS		;0=single-density, 1=double
	ora	a		;single-density?
	jnz	PARAM3
	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
	dcr	b
	jnz	PARAM4

	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

;---------------------------------------------------------
;Put buffer end into BUFEND, point hl to the format table.
;Leave hl pointing to the beginning of the actual format
;---------------------------------------------------------
	mov	e,m		;buffer end from format table
	inx	h
	mov	d,m
	inx	h

;Override buffer end from format table if 3.5" disk

	pop	psw		;3.5" disk?
	jz	PARAM5

	lxi	d,TRKBUF+BL3DD+1 ;3.5" disk

PARAM5:	xchg			;buffer last address
	shld	BUFEND
	xchg			;hl points into format table
	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	
	dcr	b		;bump counter
	jnz	FILLUP

	jmp	PATFIL

;***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
;------------------------------------------------------
;Block interrupts and set disk controller for autowait
;------------------------------------------------------
	di
	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 29 cycles out-to-out, and THE
;unrolled postamble loop requires 30 cycles out-to-out.
;Thus, a 2 MHz 8080 can format double-density disks.
;-----------------------------------------------------------
	jmp	WTLENT	;(10)enter the loop at the right SPOT

WTLUP:	mov	a,h	;(4) write byte 4
	out	DDATA	;(11)

WTLENT:	pop	D	;(10) get bytes 1 & 2

	mov	a,e	;(4) write byte 1
	out	DDATA	;(11)

	pop	h	;(10) get bytes 3 & 4	

	mov	a,d	;(4) write byte 2
	out	DDATA	;(11)

	dcx	b	;(6) bump quad-byte count
	mov	a,b	;(4) check done
	ora	C	;(4)

	mov	a,l	;(4) write byte 3
	out	DDATA	;(11)

	jnz	WTLUP	;(10)

;--------------------------------------------------------
;Write track postamble until the WD1793 releases its busy
;bit, indicating that the sector pulse was detected.
;On Entry: a=d=E=H=L=postamble fill character
;This loop's worst case timing: 30 cycles out-to-out
;--------------------------------------------------------
POSTLP:	out	DDATA	;(11) a=L

	in	DFLAG	;(11) read status
	rar		;(4) test 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 to complete the trim erase
;-------------------------------------------
	mvi	a,160	;560 US
TERASE:	dcr	a	;(4)
	jnz	TERASE	;(10)

;-----------------------------------------------------------
;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

	ei
	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
;  SAVCMD=head load and 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 BCTRL
;  b=the side to verify
;----------------------------------
VSIDE:	out	BCTRL

	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

	di			;mask INTS
	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: 25 cycles IN-to-in
;(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	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	DDATA		;(11) read disk byte 2

	cmp	e		;(4) test 2nd byte
	rnz			;(5/11) byte 2 fail?

	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	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

	ei
;-------------------------------------------
;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 BCTRL
;---------------------------------------------
;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	DDATA		;(11) read disk data
	in	DDATA		;(11) read disk data
	in	DDATA		;(11) read disk data
	in	DDATA		;(11) read disk data

	dcr	b		;(4) next quad byte
	jnz	VERROR		;(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
	jc	VERER3		;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 exit

;***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=BCTRL 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
	jc	ITSSYS		;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
	jnc	NWTR1
	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)
;---------------------------------------------------------
	call	DODCMD	;seek track (cmd in c), wait till done
	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:
;  DETSID ={0,NZ} for {1-sided, 2-sided} disk detected
;  RQSIDE ={0,1,0FFh} for do {SIDE0, SIDE1, both sides}
;  CURSID ={0,1} for just finished side {0,1}
;On Exit:
;  a=SIDE0 (for BCTRL) unless we are done
;  B=1 (for side 1) if Z is cleared
;  Z cleared if we need to do side 0
;********************************************************
NXTSID:	lda	DETSID		;0: this is a 1-sided disk
	ora	a
	rz			;Z set: side 1 does not exist

	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
;  SAVCMD=current head load and 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

	dcr	B
	jnz	STRLUP

;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:
;  SAVCMD=current head load and 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
;  SAVCMD=current head load and step rate
;On Exit:
;  carry set means error
;Trashes a,c
;***************************************************
DODCMD:	lda	SAVCMD		;current head load, step rate
	ora	c
	out	DCMMD		;send command to the WD1793

DCWAIT:	IN	DFLAG		;(10) wait for OP to complete
	rar			;(4)test WD1793 INTRQ flag.
	jnc	DCWAIT		;(10)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
	jz	DSKER1

	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
	jnc	DECLUP
	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
	jmp	GETCON		;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 Exit:
;  hl=(CPU cycles per index pulse)/55
;    for a 4 MHz Z80, time=hl * 13.75 uS
;    for a 2 MHz 8080 (without waitstates) time=hl * 27.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/13.75=12,121. Timeout will occur after
;about 5.4 revolutions. 10-15 revolutions for a slow CPU.
;On Exit:
;  carry set means error
;  if error, the error message has been printed
;Trashes a,c,de
;**************************************************************
TMINDX:	di		;No more ints while we time the disk
	call	TIMER0	;find first trsansition, 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
	ei		;interrupts okay now

	lxi	d,TOMSG	;in case of timeout error
	jz	TIMEOT
;-------------------------------------------------------------
;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 770.
;we can comfortably reject disks Where hl <1280 (500H)
;-------------------------------------------------------------
	mov	a,h		;high byte=timer result/256
	cpi	05h		;upper byte of 500H
	jc	HSERR		;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 TMINDEX
;55 cycles per pass
;-------------------------------------
TIMER0:	lxi	h,0

TIMER1:	IN	BSTAT		;get initial state
	ani	BINDEX
	mov	c,a

TIMER2:	IN	BSTAT		;(10) wait for index pulse
	ani	BINDEX		;(7)
	xra	c		;(4)
	rnz			;(5)
	inx	h		;(6) check for timeout
	mov	a,l		;(4)
	ora	h		;(4)
	rz			;(5)
	jmp	TIMER2		;(10)

;##############
;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)

;***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" double-density
SPT5SD:	db	18,10,5		;5.25" single-density
SPT8DD:	db	0FFh		;8" double-density
SPT8SD:	db	26,15,8,0FFh	;8" single-density

;*******************
;Disk format tables
;*******************

;=====================================
;         8" single-density          =
;        3.5" single-density         =
;           IBM FM format            =
; (SPT pointer and buffer last       =
; address get replaced if 3.5" disk) =
;=====================================
T8SD:	dw	SPT8SD-1	;SPT table -1 offset
	db	0E5h		;fill the data field with this
	dw	TRKBUF+BL8SD+1	;buffer last address

;---------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

;=====================================
;          8" double-density         =
;         3.5" double density        =
;           IBM MFM format           =
; (SPT pointer and buffer last       =
; address get replaced if 3.5" disk) =
;=====================================
T8DD:	dw	SPT8DD-1	;SPT table -1 offset
	db	0E5h		;fill the data field with this
	dw	TRKBUF+BL8DD+1	;buffer last address
;---------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

;=======================
; 5.25" single-density =
;=======================
T5SD:	dw	SPT5SD-1	;SPT table -1 offset
	db	0E5h		;fill the data field with this
	dw	TRKBUF+BL5SD+1	;buffer last address
;--------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

;=======================
; 5.25" double-density =
;    "ISO" format"     =
;=======================
T5DD:	dw	SPT5DD-1	;SPT table with -1 offset
	db	0E5h		;fill data field with this
	dw	TRKBUF+BL5DD+1	;buffer last address
;--------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 )
;********************************************
SAVCMD:	db	HDLOAD+STEP5	;head load & 5.25" 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}
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, 1=YES)
BPS4:	db	0	;bytes per sector / 4
DETSIZ:	db	0	;detected size {0,1,2}={5.25",8" or 3.5"}
DET35:	db	0	;3.5" drive detected
DETSID:	db	0FFh	;detected sides {0,NZ}={single, double}
ERRCNT:	db	0	;verify error count

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,' *  CCS 2422 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:',CR,LF
 db '  FORMAT <DSK>: [/D<n>] [/V] [/B<BPS>]'
 db ' [/F<1ST>] [/L<LST>] [/S<SID>] [/N<NST>]'
 db CR,LF,'  <DSK> = Disk in drive {A-D}'
 db CR,LF,'  /D1   = single density, /D2 = double density (default)'
 db CR,LF,'  /V    = Verify after format (Default: no verify)'
 db CR,LF,'  <BPS> = {0-3} for {128,256,512,1024} bytes/sector'
 db CR,LF,'           (Default: {0,1} for {single,double} density disk)'
 db CR,LF,'  <1ST> = First track {0-99} (Default: 0)'
 db CR,LF,'  <LST> = Last track {0-99}'
 db CR,LF,'           (Default: '
 db DMAXT8/10 +'0',DMAXT8 MOD 10 +'0',' for 8" disks, '
 db DMAXT5/10 +'0',DMAXT5 MOD 10 +'0',' for 5.25" disks, '
 db DMTQH3/10 +'0',DMTQH3 MOD 10 +'0',' for 3.5" disks)'
 db CR,LF,'  <SID> = Format only side {0,1}'
 db CR,LF,'           (Default: both sides)'
 db CR,LF,'  <NST> = Number of 128 byte/sector system tracks {0-9}'
 db CR,LF,'           (Default: ',SYSTK8+'0'

 if SYSTK8-SYSTK5
 db ' for 8" and 3.5" disks, ',SYSTK5+'0',' for 5.25" disks'
 endIF

 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
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
SUMS08:	db	'le sided',CR,LF,' ',' '+80h
DENSST:	db	'le density',' '+80h
DHDST:	db	'HD '			;Fall into D525ST
D525ST:	db	'5.2','5'+80h
DQDST:	db	'QD 5.2','5'+80h 
D8ST:	db	'8'+80h
D35ST:	db	'3.','5'+80H
SUMS09:	db	'" '
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 parameter, then the max value
;for the parameter plus 1, then the address of the
;memory location Where that parameter 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	'V',2	;verify, max value is 1
	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
;  SAVCMD=head load + step rate
;  RQDRIV=requested disk (0-3)
;  DETSID={0,NZ}={single, double} sided disk detected
;  DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
;  DET35=1 if 3.5" drive detected, 0 otherwise
;  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
	jz	HLPEXT		;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
	jz	HLPEXT		;No options: print help string

	cpi	'?'		;Is user asking for help?
	jz	HLPEXT		;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)
	jnc	INPERR
	sta	RQDRIV		;remember requested drive
	
	call	GETCHR		;get mandatory colon
	jz	INPERR
	cpi	':'		;test for colon and clear a
	jz	GETOPT
;---------------------
;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	'/'
	jnz	INPERR

	call	GETCHR		;get option character
	jz	INPERR		;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
	jnz	CHKLUP	;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
	jnz	OPTLUP

;-----------------------------------------------
;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

;----------------------------------------------
;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	BCTRL		;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	c,a
	inr	c		;c has disk number 1-4
	xra	a
	stc			;prepare to set select bit
SBLOOP:	RAL			;loop select bit into place
	dcr	c
	jnz	SBLOOP

	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
	jc	SETASK		;error: ask for a new disk
;----------------------------------------------------------
;Check if the disk is spinning, and if so, how fast. 8"
;and HD drive index pulses nominally come every 166.7 mS,
;while 3.5" and 5.25" drive (40-track or QD) index pulses
;come every 200 mS.
;----------------------------------------------------------
	call	TMINDX
	jc	SETASK	;carry: timeout or hard sectored
;----------------------------------------------------------
;Decide whether this disk is spinning at 300 RPM or at 360
;RPM. This is how we tell A 3.5" drive from an 8" or HD
;5.25" drive. 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  7272     800000  14545
; 360    333333  6060     666666  12121
;
;If hl > 9728 (2600H) 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>6656 (1A00H)
;  hl=index time, in units of 55 CPU cycles
;---------------------------------------------------------
	mov	a,h		;2MHz or 4 MHz CPU?
	cpi	26h		;carry set if 2 MHz
	jc	SETUP3
	rar			;divide 4 MHz Measurement by 2
SETUP3:
	cpi	1Ah		;carry set if 360 RPM
	mvi	a,1
	sbi	0		;a=1 if 300 RPM, 0 if 360 RPM
	mov	h,a	
;----------------------------------------------------
;h=1 for 300 RPM disks (3.5" and 5.25"), and 0 for
;360 RPM disks (8" or HD 5.25")
;
;Discover whether or not it is a non-HD 5.25" disk by
;reading BSTAT and testing the BTRK0 bit, valid after
;the above restore. Set c to default last track number
;for the disk size. 
;-----------------------------------------------------
	mvi	c,DMAXT5

	in	BSTAT		;esp. The maxi bit (bit 0)
	ani	BTRK0		;isolate maxi-disk bit
	sta	DETSIZ		;0=5.25", 1=8" or 3.5"

	jz	SETUP5		;5.25"
;-------------------------------------------------
;It's either a 3.5" disk or an 8" or HD 5.25" disk
;so set up default max track appropriately.
; h=0 for HD/8", 1 for 3.5"
;-------------------------------------------------
	mov	a,h
	sta	DET35		;3.5" if 300 RPM and not 5.25"

	mvi	c,DMTQH3
	ora	a
	jnz	SETUP4		;8" or 3.5"?
	mvi	c,DMAXT8
				;It's an 8" or HD 5.25" drive
SETUP4:
	lda	SAVDCL
	ori	DCMAXI		;Turn on maxi bit
	sta	SAVDCL		;..for faster data rate
;-------------------------------------------
;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
;-------------------------------------------
SETUP5:	lxi	h,RQDENS

	mov	b,m		;density request is 0 or 1
	dcx	h		;point to RQBPS

;Special treatment for 3.5" disks

	lda	DET35		;3.5" disk?
	ora	a
	mov	a,m		;RQBPS
	jz	SETUP6		;N: It's 8"

;Special default BPS for 3.5"

	inr	a		;RQBPS=FFh?
	jnz	SETUP6
	mvi	m,DBPQH3	;default 3.5" BPS

;3.5" or 8" drive
;b=RQDENS

SETUP6:	inr	a		;0FFh=default:
				;SD=128 BPS, DD=256 BPS
	jnz	SETUP7

	mov	m,b		;(128 BPS or 256 BPS)	
SETUP7:

;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
	jz	CHKDD
	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
	jnz	NODLST		;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, which may be a function of disk size. (This code
;is wasteful when both are the same.) 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
	jnz	NSTCHK

	lda	DETSIZ		;8" disk?
	ora	A
	mvi	a,SYSTK8	;last system track for 8"
	jnz	MAXSYS
	mvi	a,SYSTK5	;last system track for 5.25"
MAXSYS:

	mov	m,a		;number of system tracks
	mov	a,c		;last track

NSTCHK:	cmp	m		;last SYS track > last track?
	jnc	NSTOK
	mov	m,c		;Y: last SYS track=last track
NSTOK:

;-------------------------------------------
;Check or a side-2 request on a 1-sided disk
;-------------------------------------------
	call	CHKSID		;sidedness issue?
	jz	SETASK		;Y: ask for another disk
;-------------------------------------------------------------
;All user parameters are in & tested good. Get disk parameters
; RQDRIV=requested disk (0-3)
; DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
; DET35=1 if 3.5" drive detected, 0 otherwise
; CURDNS=requested density (0=single, 1=double)
; CURBPS=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
; DETSID=NON-ZERO for 2-sided disk, 0 for 1-sided disk
;-------------------------------------------------------------
	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
; SAVCMD=head load + 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	DETSID		;IS this a 1-sided disk?
	mov	b,a		;save for a moment
	ora	a
	jz	CONF0		;1-sided disks only have side 0

	lda	RQSIDE		;both sides?
CONF0:	inr	a
	jnz	CONF1
	lxi	d,SUMS05	;CRLF'  on both sides of a'
	jmp	CONF2

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	DETSIZ		;figure out disk size
	ora	a		;5.25" disk?
	jnz	CONF3

	lda	RQLAST		;mini or 5.25" QD?
	cpi	DMAXT5+1

	lxi	d,D525ST
	mvi	a,HDLOAD+STEP5	
	jc	CONF4		;carry clear for QD

	lxi	d,DQDST		;QD 5.25"
	mvi	a,HDLOAD+STEPQD
	jmp	CONF4

CONF3:	lda	DET35		;3.5" disk?
	ora	a

	lxi	d,D35ST	
	mvi	a,HDLOAD+STEP3
	jnz	CONF4		;jump if 3.5"

	lda	RQLAST		;8" or 5.25" HD?
	cpi	DMAXT8+1

	lxi	d,DHDST		;HD 5.25"
	mvi	a,HDLOAD+STEPHD
	jnc	CONF4		;carry clear for HD

				;we can only tell sidedness for 8"
	mov	a,b		;how many sides on this disk?
	call	SINDUB		;'sing' or 'doub' based on a

	lxi	d,SUMS08	;'le sided,',CRLF,'  '
	call	PRTSTR
	lxi	d,D8ST		;8"

	mvi	a,HDLOAD+STEP8	;fix step rate

CONF4:	sta	SAVCMD		;step rate + head load

	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 size
	
	call	PRTSTR		;print drive size

	lxi	d,SUMS09	;'" disk'
	call	PRTSTR

	lda	RQBPS		;128 bytes/sector?
	ora	a
	jz	CONF5		;Y: no need to do system tracks

	lda	RQNSTK		;number of system tracks
	ora	a		;0 no system tracks to do
	jz	CONF5		;jump if none

	dcr	a		;a=last system track number

	cmp	c		;touch any system tracks?
	jc	CONF5		;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
	jz	CONF6
	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
	jz	ASKYN6

	cpi	DEL		;backing up?
	jz	ASKYN4
	cpi	BS		;DEL or BS backs up
	jnz	ASKYN5

ASKYN4:	dcr	b
	jnz	ASKYN2		;nothing to delete

	lxi	d,BUMSG		;erase old chr
	call	PRTSTR		;BS,' ',BS
	jmp	ASKYN2

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
	jmp	ASKYN3

ASKYN6:	mvi	a,5Fh		;lowercase is ok
	ana	c		;single chr from user

	cpi	'N'
	jz	GUDBYE		;Goodbye message, and quit

	cpi	'Y'
	jnz	ASKYN1		;Ask again if not Y or N

;------------------------------------------------------------
;On Exit:
;  SAVDCL is VALId, with the possible exceptION of density
;  SAVCMD=head load + step rate
;  RQDRIV=requested disk (0-3)
;  DETSID={0,NZ}={single, double} sided disk detected
;  DETSIZ={0,1}={5.25" disk, 8" disk or 3.5" disk}
;  DET35=1 if 3.5" drive detected, 0 otherwise
;  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 de
;***********************************************************
SINDBD:	lda	RQDENS
SINDUB:	lxi	d,SNGLST
	ora	a
	jz	SINGLE
	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
	jz	SKIPW

	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
	ani	7Fh		;strip parity
	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:

;Set value for options that require no input value

	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
	jnz	GETV1		;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
	jmp	PRTEX1

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
	jmp	CREXIT		;print a CR LF then quit

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

	jmp	CREXIT

PRTEX3:	call	PCRSTR		;print CRLF then string
	jmp	CREXIT

;---------------------------------------------
;Stack used during SETUP only. (During format,
;the stack is in the command line buffer.)
;---------------------------------------------
Sspace:	DS	32
LSTACK:	equ	$

	END	100H
		