;==============================================================
;ADEXER - Altair Datakeeper Exerciser by Martin Eberhard
;
; For testing and servicing the Altair 88-HDSK Datakeeper Hard
; Disk Subsystem. See the substantial Adexer Users Manual for
; complete instructions, as well as for notes on servicing an
; Altair 88-HDSK Hard Disk subsystem.
;
;==============================================================
; Notes:
;
; During testing with the Disk Interface Card installed, but
; without a disk drive attached, you must install a jumper on
; the Disk Interface Card between the positive terminal of
; capacitor C3 and the positive terminal of capacitor C8.
; (This provides +5V to the termination pullup resistors, so
; that the four Seeking lines appear false.)
;
; You may find it convenient to build a test fixture that
; connects to the Disk Interface Card. Put DIP switches to
; ground for the 20 input lines, and put LEDs on the
; interesting outputs.
;
; The final stages of the IV Byte test requires a loopback
; cable from 88-Interface Card connector P1 to connector P2.
; It is *VERY IMPORTANT* that pins 1 and 2 are not connected
; in this loop-back cable.
;
; This program uses decimal numbers for IV Byte addresses, as
; well as for cylinder and sector numbers, because the MITS
; documentation does so. Data values, however, are in hex,
; since MITS documentation uses binary for IV byte data. 
;
; This program uses software timing loops that assume a 2 MHz
; 8080 with 0-wait state memory. Because of the timing loops,
; interrupts are blocked while this program runs.
;
; The "SE O=" command sets a "Read Track Offset" that is used
; for all subsequent reads, except reads of the destination
; disk during verifies. The offset is also active during
; either of the seek commands (SK, ST). No offsets are ever
; used during writes or formats. Offsets may be useful for
; reading a marginal disk, as well as for testing margins in
; the read hardware.
; The possible offsets are:
;   SE O=0 disables offsets
;   SE O=+ sets a positive offset
;   SE O=- sets a negative offset
;   SE O=G sets no offset, but sets the read amplifier for
;          reduced gain (useful for testing margins).
; The Read Track Offset is written to the controller
; immediately before each command that reads or writes the
; disk, or performs a seek, to set up the offset on the disk
; drive, and to warn the user that an offset is set up. The
; offset must be set up just prior to disk commands because
; the SE O= command may have been entered while the disk drive
; was not ready - in which case the offset did not get set on
; the disk drive yet. If the drive is ready when the "SE O="
; command is geven, then the requested offset is immediately
; sent to the disk drive.
;
; The XD commmand dumps the entire disk in XModem protocol,
; and the XL command receives a disk image in XModem protocol,
; and writes it to the disk. These operations will take about
; 2 hours at 9600 baud, assuming no retries. 
;
; Xmodem transfers cannot be performed with the Terminal port
; because the Terminal is needed for reporting progress and
; errors. The default port is set by the Altair Sense Switches.
; If bits <2:0> = 010, then the default transfer port is an
; 88-SIO at addressed at 00 and 01. Otherwise, the default
; transfer port is port B of an 88-2SIO, addressed at
; 12h and 13h. (you can change the other Xmodem port with the
; SE X= command.)
;
; A Program Switch below (CPMCOD) allows this program to run
; either stand-alone in an Altair with a "standard" (6850-
; based) serial Terminal port, or under CP/M, using CP/M's
; console interface. In either case, the 4PIO that interfaces
; to the 88-HDSK controller is expected to be at its Altair-
; standard port addresses, A0h to A7h.
;==============================================================
; Revision History:
;  Rev    Date       Author
; 1.00  06OCT2013  M.Eberhard
;  Created. Untested still are all functions that involve
;  reading and writing from the disks.
; 1.01  26Feb2014  M.Eberhard
;  A bit more verbose. Add TW command.
; 1.02 27Feb2014  M.Eberhard
;  Add the following commands:'CC','EU','FD','HD','HL','ST',
;  'SE E=', 'SE O='. Split up help screens. Tested read, write,
;  format routines.
; 1.03 05MAR2014 M.Eberhard
;  Still trying to get track offsets to work. Add 'DC' command.
; 1.04 07MAR2014 M. Eberhard
;  Fix various bugs, Add format and verify options to DC
;  command. Rename FD to be FS, and create FD command. Add XD
;  and XL commands. More verbose on SR, SW, CR, CW, etc.
;  Offsets still don't work.
; 1.05 19MAR2014 M.Eberhard
;  Fix bug in disk copy text. Allow platter to be specified
;  in the XD and XL commands. Change ?R to ?D. Add /P options.
;  Disabled Offsets until I can figure out how to fix them.
; 1.06 24MAR2014 M. Eberhard
;  Set up stack before any subroutine calls :-) 
; 1.07 14APR2014 M. Eberhard
;  Fix variable-clear loop in main
; 1.08 24APR2014 M.Eberhard
;  Fix bugs with fill data & verify, in FD and FS commands.
;  Fix bug when exiting XL command with short disk file.
; 1.09 10 June 1014 M.Eberhard
;  Fix bug in XL: call DORSTR instead of RESTOR
; 1.10 Check for phantomed TURMON ROM before jumping there.
;      Support the SIO as an alternate XMODEM port. Remove ROM
;      code support. Fix XMODEM transmit checksum bug.
; 1.11 3SEP2014 M. Eberhard
;      Fix error reporting on seeks. Default T = off. Verify
;      presence of Interface Card (ask user) before IV address
;      test in TI command. Add MS command.
; 1.12 12 Sep 2014 M. Eberhard
;      Round correctly in MS command. Improve accuracy and
;      precision of seek time measurement. Add SE O= command
;      to set track offsets during read operations (finally).
; 1.13 Fix error counting bug.Perform dummy read after seeks,
;      to re-select the correct head. Improve messages for ST
;
; To Do (maybe)
; Short seeks seem to take too long. Why? Some overhead?
; Retries on reads, heroics using track offsets
; Support platter copies from one unit to another
; Access sector by block number (linear)
;    or create a block number calculator
; Decode "pack descriptor page"
; Format blank disk, including writing a Pack Descriptor Page
; Super-clean disk, by formatting with each of +/0/- offsets
; Variable speed on ST, SK and RR
;==============================================================

FALSE	equ	0
TRUE	equ	not FALSE

;=================
; Program Switches
;=================

	CPMCOD	equ	TRUE	;TRUE runs under CP/M
				;FALSE runs standalone

;==================
; Memory Allocation
;==================
  if not CPMCOD
CODESP	equ	0		;start of program in RAM

WBOOT	equ	0FD00h		;QU jumps to TURMON here
 endif

 if	CPMCOD
CODESP	equ	0100h		;standard CP/M code start

;-------------------------------------------
; BDOS Entry Points and low-memory locations
;-------------------------------------------
WBOOT	equ	0000H		;QU warm-boots CP/M
WBOOTA	equ	WBOOT+1		;Address of BIOS

;---------------------------------------------------------
; BIOS Entry Points, relative to the base address in WBOOT
;---------------------------------------------------------
CONST	equ	06h	;Console Status
CONIN	equ	09h	;Console Input
CONOUT	equ	0Ch	;Console output
 endif

 if not CPMCOD
;=========================================================
; Terminal Port Equates - same for 88-2SIO port 0, Turnkey
; Module, and 88-UIO (all based on the Motorola 6850 ACIA)
;=========================================================

ACCTRL	EQU	10h		;ACIA Control output port
ACSTAT	EQU	10h		;ACIA Status input port
ACTXD	EQU	11h		;ACIA TX Data register
ACRXD	EQU	11h		;ACIA RX Data register

ACRSET	EQU	00000011b	;Master reset
ACINIT	EQU	00010101b	;/16, 8-bit, No Parity, 1Stop
ACRDF	EQU	00000001b	;RX Data register full
ACTDE	EQU	00000010b	;TX Data register empty
 endif

;============================================================
; 88-HDSK Ports (The interface board is actually an 88-4PIO.)
;============================================================
CREADY	equ	0A0h	;IN: Ctlr ready for command (bit7)
CSTAT	equ	0A1h	  ;IN: error flags, reset CREADY
ERDNR	  equ	  01h	  ;drive not ready
ERBADS	  equ	  02h	  ;illegal sector
ERSCRC	  equ	  04h	  ;CRC error during sector read
ERHCRC	  equ	  08h	  ;CRC error during header read
ERSWRG	  equ	  10h	  ;header has wrong sector
ERCWRG	  equ	  20h	  ;header has wrong cylinder
ERHWRG	  equ	  40h	  ;header has wrong head
WPROT	  equ	  80h	  ;Write Protect
ERMASK	  equ	  7Fh	  ;all the actual error bits
SERMSK	  equ	  7Fh	  ;error bits for seeks

ACSTA	equ	0A2h	;IN: Command Ack (bit 7)
ACMD	equ	0A3h	;IN: reset Command Ack
			;OUT: Command high byte/initiate
; 88-HDSK Commands

CINIT	equ	0E0h	;Initialize
CFORMT	equ	0C0h	;Format
			  ;Bits 3:2 = Unit #
		;ADATA Bits 7:6 = Platter #
		;      Bit    5 = Side #

CSEEK	equ	00h	;Seek
			  ;Bits 3:2 = Unit #
			  ;   Bit 0 = Cylinder # bit 8
		;ADATA Bits  7:0  = Cylinder # bits 7:0

CRDSEC	equ	030h	;Read Sector
CRUSEC	equ	0A0h	;Read Unformatted Sector
CWRSEC	equ	020h	;Write Sector
			  ;Bits 3:2 = Unit #
			  ;Bit  1:0 = Buffer #
		;ADATA Bits 7:6 = Platter #
		;      Bit    5 = Side #
		;      Bits 4:0 = Sector #

CRDBUF	equ	050h	;Read Buffer
CWRBUF	equ	040h	;Write Buffer
			  ;Bit  1:0 = Buffer #
		;ADATA Bits 7:0 = # bytes to transfer
		;(00 means 256)

CSETIV	equ	080h	;Set IV Byte
CRSTAT	equ	060h	;Read Status (IV Byte)
			  ;Bits 3:2 = Unit #
		;ADATA Bits 7:0 = IV Byte Address
	
CDSTA	equ	0A4h	;IN: data/stat availablr at CDATA
CDATA	equ	0A5h	;IN: Disk data or status from Ctlr
ADSTA	equ	0A6h	;IN: ADATA Port Available (bit 7)
ADATA	equ	0A7h	;OUT: Command low byte

;=================================
; Controller IV Bytes, as accessed
; via CSETIV, and CRSTAT commands
;=================================
; Processor Card IV Bytes
; (direction referenced to 8X300)
IVX	equ	01	;(output)IV Direction Control
IVA8	equ	02	;(outout)IV Error Codes
IVA9	equ	03	;(input)Command upper byte
IVA12	equ	06	;(output)Data from controller
IVA13	equ	07	;(input)Command low byte/data
IVA10	equ	04	;(output)handshakes
IVA11	equ	05	;(input)handshakes

; Disk Data Card IV Bytes
; (direction referenced to 8X300)
IVB	equ	34	;(output)FIFO write data
IVA	equ	33	;(input)FIFO read data
IVC	equ	35	;(output)Set Bit Counters
IVCLN0	  equ	  10h	  ;Load pulse 0 (active low)
IVCLN1	  equ	  20h	  ;Load pulse 1 (active low)
IVCLN2	  equ	  40h	  ;Load pulse 2 (active low)
IVCLN3	  equ	  80h	  ;Load pulse 3 (active low)
IVD	equ	36	;(input)Disk Status A
IVDTRR	  equ	  01h	  ;Transfer Request
IVDDTC	  equ	  02h	  ;Data Transfer Complete
IVDDCE	  equ	  04h	  ;Data CRC Error (after read)

IVE	equ	37	;(output)Disk Control A
IVESDS	  equ	  01h	  ;Source data select (set to 0)
IVETAS	  equ	  02h	  ;Transfer Ack Strobe
IVECLN	  equ	  08h	  ;Clear FIFOs (active low)
			  ;NOTE: MITS doc is wrong.
			  ;This is bit 3 not bit 2.
IVERDM	  equ	  20h	  ;Read mode (0 for write mode)
IVEDTS	  equ	  40h	  ;Dist transfer start
IVECAE	  equ	  80h	  ;CRC Apend Enable

; 88-HDSK Interface Card IV Bytes
; (direction referenced to 8X300)
IVH	equ	17	;(inverting output)Disk Function Control
IVHSS	  equ	  80h	  ;Start/Stop
IVHEXT	  equ	  40h	  ;Extension select
IVHPLA	  equ	  20h	  ;Platter select
IVHSID	  equ	  10h	  ;Side select
IVHUS4	  equ	  08h	  ;Select unit 4
IVHUS3	  equ	  04h	  ;Select unit 3
IVHUS2	  equ	  02h	  ;Select unit 2
IVHUS1	  equ	  01h	  ;Select unit 1
IVI	equ	18	;Disk Control B
IVIEUN	  equ	  80h	  ;Emergency Unload (active low)
IVIOPN	  equ	  40h	  ;Offset plus (active low)
IVIOMN	  equ	  20h	  ;Offset minus (active low)
IVIWEN	  equ	  10h	  ;Write enable (active low)
IVICRN	  equ	  08h	  ;Cyl Restore (active low)
IVICSN	  equ	  04h	  ;Cyl Strobe (low pulse)
IVIC8N	  equ	  01h	  ;Cyl address bit 8 (inverted)
IVIMSK	  equ	IVIEUN+IVIOPN+IVIOMN+IVIWEN

IVJ	equ	19	;Cyl address bits 7:0 (inverted)
IVK	equ	20	;(input)Disk Status B
IVL	equ	21	;(input)Disk Status C
IVLFP	  equ	  20h	  ;file protected
IVM	equ	22	;(input)Sector Count

;==================
; 88-HDSK Constants
;==================
MAXSEC	equ	23	;max sector
MAXCYL	equ	405	;max cylinder
MAXPLA	equ	3	;max platter number
MAXUNT	equ	4	;max unit number (starts with 1)
MAXBUF	equ	3	;max buffer number
MXFIFO	equ	16	;Data FIFO Depth
MAXIV	equ	255	;Max IV address	(allow freedom.)

SKCOH	equ	152/4	;uS of controller 8X300 code overhead
			;..for seek command
REVTIM	equ	25000	;uS per disk revolution
SECTIM	equ	REVTIM/(MAXSEC+1)	
SKLOH	equ	SECTIM/2    ;average sector latency for seek
SEEKOH	equ	SKLOH+SKCOH ;total avg overheed for seek command

;=============================
; XMODEM Transfer Port Equates
;=============================
SENSW	equ	0FFh		;Sense switches port
TXPMSK	equ	03h		;mask for transfer port
SIOSEL	equ	2		;selects SIO port as default

; 88-2SIO port B

X0CTRL	equ	12h		;2SIO port B control
X0STAT	equ	12h		;2SIO port B status
X0TXD	EQU	13h		;2SIO port B TX Data
X0RXD	EQU	13h		;2SIO port B RX Data

X0RSET	EQU	00000011b	;Master reset
X0INIT	EQU	00010101b	;/16, 8-bit, No Parity, 1Stop
X0RDF	EQU	00000001b	;RX Data register full
X0TDE	EQU	00000010b	;TX Data register empty

; 88-SIO

X1CTRL	equ	00h		;SIO control
X1STAT	equ	00h		;SIO status
X1TXD	EQU	01h		;SIO TX Data
X1RXD	EQU	01h		;SIO RX Data

X1RDF	EQU	00000001b	;-RX Data register full
X1TDE	EQU	10000000b	;-TX Data register empty

;=======================
;XModem Protocol Equates
;=======================
XERLIM	equ	10	;Max # of error-retries 10
			;is standard.

;Timeout values in seconds. Values in parenthesis are
;XMODEM standard values.

SOHTO	equ	10	;(10)sender to send SOH 
NAKTO	equ	90	;(90)receiver to send init NAK
ACKTO	equ	60	;(60)receiver to ACK (or NAK)
			;(time to write to disk)
BLKSIZ	equ	128	;bytes per XMODEM block
	;DO NOT CHANGE. BLKSIZ should be 128!

SELCRC	equ	'C'	;selects CRC mode at initiation

;================
; Program Equates
;================
LBSIZE	equ	80	;User-input line buffer size

;=================
; ASCII characters
;=================
BS	equ	08H
LF	equ	0AH
CR	equ	0DH
DEL	equ	7FH
SOH	equ	1	;Start of XMODEM block
EOT	equ	4	;End XMODEM session
ACK	equ	6	;XMODEM block acknowledge
NAK	equ	15H	;XMODEM block negative ACK
QUOTE	equ	27h	;single quote

;=====================
; Start of code
	ORG	CODESP
;=====================
	di
	lxi	SP,STACK	;SP=local stack

;--------------------------------------------
; Initialize RAM variables and Adexer buffer.
; Other RAM gets cleared too, harmlessly.
;--------------------------------------------
	lxi	h,VARSP		;everything to 0 first
	lxi	b,512		;more than enough cleared

INITLP:	mvi	m,0		;(10)set it all to 0
	inx	h		;(5)
	dcx	b		;(5)
	mov	a,b		;(5)
	ora	c		;(4)
	jnz	INITLP		;(10) 39 cycles=19.5 uS per pass
				;512 passes=about 9.98 mS

	inr	a		;a=1
	sta	PATBUF		;default pattern: seek track 0

;---------------------------------------
; Initialize 88-HDSK interface board
; (Actually ports 0 and 1 of an 88-4PIO)
;---------------------------------------
	xra	a

	out	0A0h		;Select port 0Ah DDR 
	out	0A2h		;Select port 0Bh DDR
	out	0A4h		;Select port 1Ah DDR
	out	0A6h		;Select port 1Bh DDR
	out	0A1h		;Port 0Ah is an input port
	out	0A5h		;Port 1Ah is an input port

	cma
	out	0A3h		;Port 0Bh is an output port
	out	0A7h		;Port 1Bh is an output port

	mvi	a,2Ch		;set up input port handshakes
	out	0A0h
	out	0A4h
	out	0a6h		;also output port 1Bh handshakes

	mvi	a,24h		;set up port 0Bh handshakes
	out	0A2h

;-------------------------------------------------
; Reset the 2nd serial port (port B) of an 88-2SIO
;-------------------------------------------------

	mvi	A,X0RSET
	out	X0CTRL		;reset 2SIO 2nd port
	mvi	A,X0INIT
	out	X0CTRL		;and initialize it

;-------------------------------------------------------------
; Set the default XMODEM port according to the sense switches.
; (Only 88-2SIO port B and 88-SIO are supported.)
;-------------------------------------------------------------
	in	SENSW
	ani	TXPMSK
	sui	SIOSEL		;SIO setting?
	jnz	XPOK		;n: default is ok

	inr	a		;a=1
	sta	XPORT
XPOK:

 if not CPMCOD
;--------------------------------------------
; Reset and initialize the Terminal port ACIA
;--------------------------------------------
	call	STALL		;Let ACIA empty first

	mvi	A,ACRSET	;Reset ACIA
	out	ACCTRL		;Initialize it too
	mvi	A,ACINIT
	out	ACCTRL
 endif

;-------------
; Print banner
;-------------
	call	CILPRT
	db	' =========================================',CR,LF
	db	' = Altair Datakeeper Exerciser ver. 1.13 =',CR,LF
	db	' =  By Martin Eberhard    October, 2014  =',CR,LF
	db	' =========================================',CR,LF
	db	CR,LF,'Type ? for hel','p'+80h

;======================================================
; Main loop: re-initialize several things,
; look for commands from user & dispatch.
; Subroutines often exit directly to MAIN on errors,
; hosing the stack. Therefore, MAIN restores the stack.
;======================================================
MAIN:	lxi	SP,STACK	;repair stack

	lxi	h,CLRVAR	;clear variables
	mvi	b,CLRCNT

MCLP:	mvi	m,0
	dcr	b
	inx	h
	jnz	MCLP

	lxi	b,MAIN		;create return address
	push	b

	call	CILPRT		;print prompt
	db	'%'+80h

;-------------------------------------
; Get CR-terminated line from console,
; with editing. Stash line in LINBUF.
; Terminate line with 0
;-------------------------------------
	lxi	h,LINBUF
	push	h
	mvi	c,0		;chr count

; get & echo characters, stashing them
; in LINBUF until a CR is encountered

GETLIN:	call	GETCHR		
	sui	CR		;end of input?
	jnz	GETLIN

; Overwrite CR with a null

	dcx	h		;overwrite CR
	mov	m,a		;null termination
	pop	h		;LINBUF

; Look for a command in LINBUF

	call	SKIPSP		;skip leading spaces
	rz			;null line: try again

; Look up command at (hl) in command table,
; and execute command if found

	lxi	d,CMDTAB-1

; fall into FNDCOM

;=====Subroutine==============================================
; Search cmd table for match to 1st 2 non-blank chrs in LINBUF
; On Entry:
;  hl=address of cmd 1st byte in LINBUF
;  de=command table address - 1
; On Exit:
;  returns to command execution routine
;  hl=address of next parameter in LINBUF
;  Rude jump to CMDERR if not found in table
;  a,bc, de trashed
;=============================================================
FNDCOM:	mov	b,m		;1st command chr
	inx	h
	mov	c,m		;2nd command chr
	inx	h
	push	h

	xchg			;table address to de

;search command table for match of b,c

NXTCMD:	inx	h		;next entry

	mov	a,m		;1st command chr
	inx	h
	ora	a		;table end?
	jz	CMDERR		;cmd not in table

	xra	b		;compare 1st chr
	mov	d,a		;temp save

	mov	a,m		;2nd command chr
	inx	h

	mov	e,m		;address low byte
	inx	h

	xra	c		;compare 2nd chr
	ora	d		;compare both chrs
	jnz	NXTCMD		;no match: keep looking

	mov	d,m		;de=exec address


	pop	h		;1st chr past command

	push	d		;'return' to execution routine

; fall into SKIPSP to skip spaces to 1st parameter

;=====Subroutine=================================
; Skip over spaces in LINBUF
; On Entry:
;  hl=address in cmd line
; On Exit:
;  a=next chr on line
;  hl=address of first chr past space
;  Z set, carry clear, and a=0 if no params found
;================================================
SKIPSP:	mov	a,m
	ora	a		;end of cmd already?
	rz			;Y: ret with z set

	cpi	' '		;space?
	rnz			;n: ret with z clear

	inx	h		;y: skip it
	jmp	SKIPSP

;---------------------
; Bogus command return 
;---------------------
CMDERR:	call	CILPRT
	db	' huh','?'+80h
	jmp	MAIN		;main fixes stack

;******============================================
;* SE *  Command: Set Variable or Show Adexer State
;******
; On Entry:
;   z set if end of cmd line already
;   hl=address of 1st chr after cmd
;==================================================
FSET:	lxi	d,SETTAB-1	;setting a parameter?
	jnz	FNDCOM		;y: go execute sub-command

; N: fall into DFSET to display current settings

;**********************--------------------------
;* SE <no parameters> * Display Settings & Status
;**********************--------------------------
DFSET:	call	CILPRT
	db	'-==Adexer Status==','-'+80h

	call	PECHO		;Echo mode
	call	PXPORT		;XModem port
	call	PAUTO		;auto or manual
	call	PVERB		;Verbose or not mode
	call	PSKCK		;Seek check mode
	call	PRNRTO		;Read Track Offset
	call	PUNIT		;print selected unit
	call	PPLAT		;current platter
	call	PSIDE		;current side
	call	PCYL		;current cylinder

	rnz			;drive not ready

;read & report various disk drive status

	mvi	c,IVK		;[IV 20]
	call	READIV

	rlc			;test Malfunction
	cc	MALFCT

	rrc
	rrc			;test double track
	cc	P200		;200 DPI
	cnc	P100		;100 DPI
	call	ILPRNT
	db	'00 TPI driv','e'+80h

	rrc
	rrc			;test dual platter
	cc	DUAL
	cnc	SINGLE
	call	ILPRNT
	db	' platte','r'+80h

	rrc
	rrc			;test extension status
	cc	EXTNSN

	mvi	c,IVL		;[IV 21]
	call	READIV
	jnz	ABDNR		;now not ready: can't read stat

;see if selected disk is write protected. Do a dummy read first
;to select the desired unit. (trashes buffer 3)

	call	CILPRT
	db	'Platter is',' '+80h

	call	DUMYRD		;test write protected
	cz	NOTST

	call	ILPRNT
	db	'write-protecte','d'+80h	

	call	CILPRT
	db	'Drive is read','y'+80h

	ret

MALFCT:	call	CILPRT
	db	'DRIVE MALFUNCTION','!'+80h
	ret

P200:	call	CILPRT		;part of 200 DPI
	db	'2'+80h
	ret

P100:	call	CILPRT		;part of 100 DPI
	db	'1'+80h
	ret

SINGLE:	call	CILPRT
	db	'Singl','e'+80h
	ret

DUAL:	call	CILPRT
	db	'Dua','l'+80h
	ret

EXTNSN:	call	CILPRT
	db	'Extension presen','t'+80h
	ret

NOTST:	call	ILPRNT
	db	'not',' '+80h
	ret

;*********----------------------
;* SE A= * Select Automatic Mode
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;      '0' means off
;      '1' means on
; On Exit:
;  AUTOMO updated and reported
;-------------------------------
FSETA:	call	GET01		;0 or 1
	sta	AUTOMO

; fall into PAUTO

;=====Subroutine======
; Print Seek Mode
;=====================
PAUTO:	call	CILPRT
	db	'Seek Mode:',' '+80h

	lda	AUTOMO
	ora	a		;NZ means auto
	jnz	PMOD1

	call	ILPRNT
	db	'Manua','l'+80h
	ret

PMOD1:	call	ILPRNT
	db	'Automati','c'+80h
	ret

;*********----------------------
;* SE E= * Set Echo mode
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;      '0' means off
;      '1' means on
; On Exit:
;   SKCKMO updated and reported
;-------------------------------
FSETE:	call	GET01
	sta	ECHOMO

; Fall into PECHO

;=====Subroutine=======
; Print Echo Mode
;======================
PECHO:	call	CILPRT
	db	'Hex load echo',':'+80h

	lda	ECHOMO
	call	PONOFF		;print on/off

	jmp	CDMSG		;warn about character echo

;*********----------------------------------------
;* SE O= * Set Read Track Offset
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;   Legal values:
;      '-' sets a negative offset
;      '+' sets a positive offset
;      '0' disables offsets, centering the head
;          on the track.
;      'G' sets both offsets, which centers on the
;          track, but reads with reduced gain
; On Exit:
;   CRRTO updated and reported:
;           no offset: 0
;     negative offset: IVIOMN
;     positive offset: IVIOPN
;        reduced gain: IVIOPN+IVIOMN
;-------------------------------------------------
FSRTO:	call	GETCL1		;get a character
	mvi	b,0
	jz	FSRTO1		;default to 0

	cpi	'0'
	jz	FSRTO1

	mvi	b,IVIOPN
	cpi	'+'
	jz	FSRTO1

	mvi	b,IVIOPN+IVIOMN
	cpi	'G'
	jz	FSRTO1

	mvi	b,IVIOMN
	cpi	'-'
	jnz	CMDERR

FSRTO1:	mov	a,b
	sta	CRRTO

; Fall into SARTO to set up the offset at the disk drive

;=====Subroutine=====================================
; Set up and announce the requested Read Track Offset
; trashes a,bc
;====================================================
SARTO:	call	SETRTO

; Fall into PRNRTO to announce the offset

;=====Subroutine=================
; Print Current Read Track Offset
; On exit:
; trashes a,b
;================================
PRNRTO:	lda	CRRTO
	cpi	IVIOMN+IVIOPN	;reduced gain?
	jz	PRNTRG

	call	CILPRT
	db	'Source read track offset:',' '+80h

	ora	a
	jz	PNONE		;no offset?

	mvi	b,'-'
	cpi	IVIOMN		;negative offset?
	jz	POFP

	mvi	b,'+'		;must be positive offset
		
POFP:	mov	a,b
	jmp	PRINTA

PRNTRG:	call	CILPRT
	db	'Reduced gain on source read','s'+80h
	ret

PNONE:	call	ILPRNT
	db	'Non','e'+80h
	ret

;*********---------------------------------
;* SE P= * Set Current Platter Number {0-3}
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;      '0' means removable cartridge
;      '1' means standard fixed platter
;      '2','3' are extension platters
; On Exit:
;   CRPLAT updated and reported
;-------------------------------------------
FSETP:	lxi	d,MAXPLA	;max value
	call	GETDC1		;result in a and C
	sta	CRPLAT		;remember current platter

	call	DUMYRD		;read to switch heads

; fall into PPLAT

;===Subroutine========================
; Print current platter as set by user
; trashes a
;=====================================
PPLAT:	call	CILPRT
	db	'Platter:',' '+80h
	lda	CRPLAT		;get current platter
	adi	'0'
	call	PRINTA		;print platter #

	call	ILPRNT
	db	','+80h

	lda	CRPLAT		;get current platter
	jmp	PNAME		;fixed or removable	

;*********-----------------------
;* SE S= * Set Current Side {0-1}
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;      '0' means top side
;      '1' means bottom side
; On Exit:
;   CRSIDE updated and reported
;--------------------------------
FSETS:	call	GET01		;get 0 or 1
	sta	CRSIDE		;remember current side

	call	DUMYRD		;read to switch heads

; fall into PSIDE

;===Subroutine======================
; Print current side, as set by user
; trashes a
;===================================
PSIDE:	call	CILPRT
	db	'Side:',' '+80h

	lda	CRSIDE		;get current side
	adi	'0'
	call	PRINTA		;print head #

	cpi 	'0'		;top or bottom?
	jnz	PBOTS

	call	ILPRNT
	db	', to','p'+80h

	ret	

PBOTS:	call	ILPRNT
	db	', botto','m'+80h

	ret	

;*********------------------------
;* SE T= * Set SK track-check mode
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;      '0' means off
;      '1' means on
; On Exit:
;   SKCKMO updated and reported
;---------------------------------
FTKCK:	call	GET01
	sta	SKCKMO

; Fall into PSKCK

;=====Subroutine=======
; Print Seek Check Mode
;======================
PSKCK:	call	CILPRT
	db	'Seek track check',':'+80h

	lda	SKCKMO
	jmp	PONOFF		;print on/off

;*********------------------------------
;* SE U= * Set Current Unit Number {1-4}
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;     expect ASCII unit number
; On Exit:
;   CRUNIT updated and reported
;---------------------------------------
FSETU:	lxi	d,MAXUNT	;max value
	call	GETDC1		;result in a and c
	dcr	a		;make it 0-3
	jm	CMDERR		;was 0 specified?	
	sta	CRUNIT		;remember current unit

; fall into PUNIT

;===Subroutine=======================
; Print current UNIT - as set by user
; trashes a
;====================================
PUNIT:	call	CILPRT
	db	'Unit:',' '+80h
	lda	CRUNIT		;get current unit
	inr	a		;make it 1-4
	push	h
	push	d
	push	b
	call	PDEC8
	pop	b
	pop	d
	pop	h
	ret

;*********---------------------
;* SE V= * Set Verbose Mode
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;      '0' means off
;      '1' means on
; On Exit:
;   VERBMO updated
;------------------------------
FSETV:	call	GET01
	sta	VERBMO

; Fall into PVERB

;=====Subroutine=====
; Print Verbose Mode
;====================
PVERB:	call	CILPRT
	db	'Verbose seek mode',':'+80h

	lda	VERBMO

; fall into PONOFF

;=====Subroutine====
; Print on or off
; On Entry:
;  a=0 for off
;===================
PONOFF:	ora	A		;0 means off
	jz	POFF
	call	ILPRNT
	db	' o','n'+80h
	ret

POFF:	call	ILPRNT
	db	' of','f'+80h
	ret	

;*********-------------------------
;* SE X= * Set XModem Transfer Port
;*********
; On Entry:
;   hl=addr of 1st chr after '='
;      '0' means 88-2SIO port B
;      '1' means 88-SIO
; On Exit:
;   XModem port set
;----------------------------------
FSETX:	call	GET01
	sta	XPORT		;set xmodem port

; fall into PXPORT

;===Subroutine============
;Print Current XModem port
;=========================
PXPORT:	call	CILPRT
	db	'XModem transfer','s'+80h

; fall into PVIAPT

;===Subroutine========
;Print via XModem port
;=====================
PVIAPT:	call	ILPRNT
	db	' via 88','-'+80h

	lda	XPORT
	ora	a		;2SIOB?
	jnz	RPTSIO		;n: it's an SIO

	call	ILPRNT
	db	'2SIO port ','B'+80h
	ret

RPTSIO:	call	ILPRNT
	db	'SI','O'+80h
	ret

;**************************
;* Memory Buffer Commands *
;**************************

;******=================================================
;* CR *  Command: Read from Controller Buffer into
;******  Adexer Buffer & Display
;
; Parameters:
;  [<B>] specifies the controller buffer (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=======================================================
FCRD:	lxi	d,MAXBUF	;max ctlr buffer number
	call	GETDEC		;get c=ctlr buffer number
				;default to 0
	mov	b,c		;b=buffer number

	call	RDBUF		;read buffer now

	call	CILPRT
	db	'Controller buffer',' '+80h
	mov	a,b
	call	PDEC8
	call	ILPRNT
	db	' copied to RAM buffe','r'+80h

	xra	a		;set z flag

; fall into FDISP to display

;******=======================================
;* BD *  Command: Display Entire Adexer Buffer
;******
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=============================================
FDISP:	jnz	CMDERR		;no params allowed

	lxi	h,SECBUF	;buffer pointer
	mvi	e,0		;buffer address

FDISP1:	call	PADDR		;print address in e

	push	h		;save for ASCII print
	push	d

FDISP2: call	ILPRNT		;print a space
	db	' '+80h

;print 16 bytes in hex
	mov	a,m		;data
	call	PRHEX
	inr	e		;next
	inx	h

	mov	a,e
	ani	0Fh		;new line every 16
	jnz	FDISP2

;print them again in ASCII
	 call	ILPRNT		;print a space
	db	' '+80h

	pop	d
	pop	h

FDISP3:	mov	a,m
	cpi	' '		;non-printing?
	jc	FDISP4
	cpi	DEL		;more non-printing
	jc	FDISP5
FDISP4:	mvi	a,'.'

FDISP5:	call	PRINTA
	inx	h		;next addr
	inr	e		;256 bytes?

	rz			;done with display

	mov	a,e
	ani	0Fh		;new line every 16
	jnz	FDISP3
	jmp	FDISP1

;******=================================================
;* BE *  Command: Edit Adexer Buffer
;******
; allow user to edit buffer contents
; Parameters:
;  [<A>] specifies Adexer buffer address (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=======================================================
FEDIT:	call	GETHEX		;get buffer address in e
	mvi	d,0		;msb

FEDIT0:	lxi	h,SECBUF	;buffer base address
	dad	d		;HL points to buff addr

FEDIT1:	call	PADDR		;print address in e
	 call	ILPRNT		;print a space
	db	' '+80h

FEDIT2:	mov	a,m		;data
	call	PRHEX

	 call	ILPRNT		;seperator
	db	'.'+80h

	push	h
	push	d

;Loop to get input from console until either a
;space or a Q to quit. Store chrs in LINBUF.

	lxi	h,LINBUF
	push	h
	mvi	c,0		;chr count

FEILUP:	call	GETCHR		;get w/ echo & deletes
	cpi	'Q'
	jz	MAIN

	sui	' '		;space means done
	jnz	FEILUP

;got complete input into LINBUF.

	mov	m,a		;null termination
	pop	h		;point to LINBUF			

;get hex data from input

	call	GETHEX		;e=hex input
				;..sets carry if no input
	mov	a,e
	pop	d
	pop	h

	jc	FEDIT3		;no input=no change?

	mov	m,a		;update w/ low byte

FEDIT3:	inx	h
	inr	e		;256-byte buffer

	mov	a,e		;need an address?
	ani	07H		;every 8
	jnz	FEDIT2

	jmp	FEDIT0		;wrap to beginning

;******=========================================
;* BF *  Command: Fill Adexer Buffer
;******
; Parameters:
;  [<H>] specifies the fill data (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;===============================================
FFILL:	call	GETHEX		;get fill value in e

	call	DOFILL		;fill buffer, set Z
	jmp	FDISP		;..and display it

;===Subroutine=====
;Fill SECBUF
; On Entry:
;   e=fill value
; On Exit:
;   Z set
; Trashes b,hl
;==================
DOFILL:	lxi	h,SECBUF
	mvi	b,0		;256-byte buffer

FILLUP:	mov	m,e		;loop to fill buffer
	inx	h
	dcr	b
	jnz	FILLUP

	ret

;******=================================================
;* CC *  Command: Compare Controller buffer
;******  to Adexer Buffer and Report Differences
;
; Parameters:
;  [<B>] specifies the controller buffer (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=======================================================
FCCOMP:	lxi	d,MAXBUF	;max ctlr buffer number
	call	GETDEC		;get c=ctlr buffer number
				;default to 0
	mov	b,c

	call	BUFCMP		;compare to buffer b

;report error count

	call	CILPRT
	db	'Mismatches:',' '+80h

	xchg			;error count to hl
	jmp	PDEC16

;******=====================================================
;* CW *  Command: Write Adexer Buffer into Controller Buffer
;******
; Parameters:
;  [<B>] specifies the controller buffer (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;===========================================================
FCWR:	lxi	d,MAXBUF	;max ctlr buffer number
	call	GETDEC		;get c=ctlr buffer number
				;default to 0
	mov	b,c		;b=buffer number

	push	b
	call	CILPRT
	db	'Copying RAM buffer to controller buffer',' '+80h
	mov	a,b
	call	PDEC8
	pop	b

; Fall into WRBUF

;===Subroutine=====================================
; Write SECBUF to controller buffer without fanfare
; On Entry:
;   b = controller buffer number
; On Exit:
;   a,trashed, all others preserved
;==================================================
WRBUF:	push	h
	push	b

	in	ADATA		;reset ADPA in ADSTA
	in	ACMD		;reset CMDACK in ACSTA

	xra	a		;transfer 256 bytes
	out	ADATA		;low byte of command 1st
	mov	c,a		;c=0

	mov	a,b		;cmd high byte gets buffer #
	ori	CWRBUF		;create write buffer command
	out	ACMD		;and issue command

	mov	b,c		;b=c=0

FCWWT:	dcr	b		;timeout?
	jz	ADPERR		;yes:abort

	in	ADSTA		;Wait for data port to be ready
	rlc			;msb=ADPA
	jnc	FCWWT

	lxi	h,SECBUF	;source

; loop to write 256 bytes to controller (c=0)

FCWDAT:	mov	a,m		;get RAM buffer data
	out	ADATA		;send to controller
	inx	h
	dcr	c
	jnz	FCWDAT

	pop	b
	pop	h
	ret

;******=======================================================
;* HD *  Command: Dump Adexer Buffer in Intel Hex
;******
; Parameters:
;  [<P>] specifies the hex record page address (defaults to 0)
;        (records get this as the high address byte)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=============================================================
FHDUMP:	call	GETHEX		;get page address in e

	lxi	h,SECBUF	;buffer pointer

;loop to print 256-byte buffer as hex records
;e=page address

	mvi	d,0		;byte count

FHDLUP:	call	CILPRT
	db	':1','0'+80h	;record start, byte count

	mov	a,e		;high address is page address
	call	PRHEX

	mov	a,d		;low address
	call	PRHEX

	call	ILPRNT
	db	'0','0'+80h	;record type

	mvi	b,10h		;record byte count

	mov	a,d		;init checksum
	add	e
	add	b
	mov	c,a

;print 16 data bytes for this record

HDRLUP:	mov	a,m		;data byte
	call	PRHEX

	mov	a,m
	add	c		;compute checksum
	mov	c,a

	inx	h
	inr	d
	dcr	b
	jnz	HDRLUP

;print checksum

	xra	a
	sub	c
	call	PRHEX

;more records?

	mov	a,d
	ora	a
	jnz	FHDLUP

;print EOF record

	call	CILPRT
	db	':00000001FF',CR,LF+80h

	ret	
	
;******=======================================================
;* HL *  Command: Load Adexer Buffer in Intel Hex
;******
; Parameters:
;  [<P>] specifies the hex record page address (defaults to 0)
;        (records with a different page address are ignored.)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=============================================================
FHLOAD:	call	GETHEX		;get page address in e

	call	CILPRT
	db	'Send hex file now',CR,LF+80h

;read records until an EOF record (any 0 data byte record)
;e = page address

;wait for record-start colon

WAITCO:	call	SINP		;spin waiting for chr
	jz	WAITCO

	mov	c,a		;temp save
	lda	ECHOMO		;echo?
	ora	a
	mov	a,c
	cnz	PRINTA		;echo if enabled

	cpi	'Q'		;user abort?
	jz	HLQUIT		;print results 1st

	lxi	h,WAITCO	;create loop return
	push	h

	sui	':'		;record start?
	rnz			;to WAITCO

;get record header. a=0 here.

	mov	c,a		;init checksum
	mov	h,a		;record address high byte	

	call	GETSHB		;get byte count
	mov	b,a

	call	GETSHB		;get high address byte
	mov	d,a

	call	GETSHB		;get address low byte
	mov	l,a

	push	d		;compute buffer address
	lxi	d,SECBUF
	dad	d		;hl=address within buffer
	pop	d

	call	GETSHB		;get, ignore record type

	mov	a,b		;0-byte record?
	ora	a
	jz	LASTRC		;means end of transfer

	mov	a,e		;requested page
	cmp	d		;same page as record?
	jz	HLPAOK

	lxi	h,IGNCNT	;bump ignored record count
	inr	m
	mvi	h,0FFh		;force buffer miss
HLPAOK:

;Loop to get b data bytes, storing the data in the buffer,
;if they in fact land in the buffer. If not, still compute
;the checksum, but don't write to the buffer.
; b=byte count
; c=checksum
; e = requested page address
; hl = address within buffer

DATALP:	call	GETSHB		;get a data byte
	mov	d,a		;temp save

;don't write data if it will miss the buffer

	mov	a,l		;past buffer end now?
	sui	(SECBUF+256) and 0FFh
	mov	a,h
	sbi	(SECBUF+256)/256
	jnc	HLOFFP		;y:skip the rest of the data

	mov	m,d		;park data in buffer
	inx	h

	push	h		;bump byte count
	lhld	BYTCNT
	inx	h
	shld	BYTCNT
	pop	h

HLOFFP:	dcr	b
	jnz	DATALP

; fall into HLCKSM

;===Local Subroutine===
; Get, test checksum
;======================
HLCKSM:	call	GETSHB		;record checksum ok?
	rz			;y: get another at GETCO

	call	ILPRNT		;checksum error
	db	'?','C'+80h

	jmp	BMPERR		;returns to GETCO

;===Local Exit=====================
; Finish EOF record, report results
;==================================
LASTRC:	pop	h		;lose GETCO from stack

	call	HLCKSM		;get, test checksum

HLQUIT:	call	CILPRT
	db	'Bytes loaded:',' '+80h
	lhld	BYTCNT
	call	PDEC16

	lhld	RESULT		;error count
	mvi	h,0		;is 8 bits
	call	PERCNT		;print error count

	call	CILPRT
	db	'Off-page ignored records:',' '+80h
	lda	IGNCNT
	jmp	PDEC8		;returns to MAIN

;***********************
;* Controller Commands *
;***********************

;******================================
;* IC *  Command: Initialize Controller
;******
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;======================================
FINIT:	jnz	CMDERR		;No parameters

	call	CILPRT
	db	'Resetting Controlle','r'+80h

DOINIT:	mvi	h,CINIT		;l doesn't matter
	lxi	b,7Fh*256+11	;b=error mask
				;c=10 second timeout 
	call	HDCMD		;unit doesn't matter
	jnz	UNXERR		;shouldn't be any errors
	ret

;===Local Subroutine===
;Report IV Byte Error
; On Entry:
;   c=IV Byte Address
;   a=read data
; On Entry at IVTERR:
;  (hl)=expected data
; On Entry at IVTER2:
;    b=expected data
; On Exit:
;   RESULT <>0
;   a,b trashed
;======================
IVTERR:	mov	b,m		;b=expected result

IVTER2:	push	h
	push	d

	push	b		;IV address & expected data
	push	psw		;read data

	call	CILPRT
	db	'    Error: IV Address (on',' ' +80h

	mov	a,c		;IV address
	sta	RESULT		;remember an error

	cpi	20h		;which card?
	cnc	PDDC		;Disk Data card IV >=20h
	jnc	IVTE1
	ani	10h		;Interface card IV=1xh
	cnz	PIVC
	cz	PCPC
IVTE1:
	call	ILPRNT
	db	'):',' '+80h

	mov	a,c		;IV address
	call	pdec8		;print IV address

	call	ILPRNT
	db	' Data:',' '+80h

	pop	psw		;read data
	call	PRHEX

	call	ILPRNT
	db	' Expected:',' '+80h

	pop	b		;IV address & expected data
	mov	a,b		;expected data
	call	PRHEX

	call	TESTQ		;user abort?
	pop	d
	pop	h
	ret		

;******====================================
;* IR *  Command: Read IV Byte
;******
; Parameters:
;  <I> specifies the IV address, in decimal
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;==========================================
FIVRD:	call	IVADDR

	call	CILPRT
	db	'IV Byte',' '+80h

	push	b		;save address

	call	PDEC8		;print it

	pop	b		;recover address

	call	READIV		;read the IV byte at c
	jnz	UNADNR		;drive not ready: no IV

	call	ILPRNT
	db	' =',' '+80h

	call	PRHEX		;print a=IV data

	call	ILPRNT
	db	'h'+80h

	ret

;******====================================
;* IW *  Command: Write IV Byte
;******
; Parameters:
;  <I> specifies the IV address, in decimal
;  <H> specifies the IV data, in hex
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;==========================================
FIVWR:	call	IVADDR

; IV Byte Address is in c

	call	GETHEX		;get a=e=value for IV Byte
	jc	CMDERR		;no default allowed

	call	CILPRT
	db	'Write IV Byt','e'+80h

	call	ASKYN		;Y/N?

; Write e to IV Byte c

	mov	a,e
	call	WRITIV		;write a to IV c

	call	CILPRT
	db	'IV Byte Writte','n'+80h

	ret

;******=======================================================
;* TB *  Command: Test Controller Buffer Memory
;******
; This memory test algorithm is designed to ferret out a
; variety of memory problems. It is based on a test pattern
; that contains two walking bit patterns - one with the bit
; high, and the other with the bit low. It also contains
; various high-frequency patterns, such as 55 and AA. All 
; These pattern elements are scrambled to maximize the number
; of times each memory bit changes value during the test. The
; entire pattern is deliberately 29 bytes  long - a  prime
; number. The pattern is written repeatedly through the
; memory, and then read back and checked. Then the pattern
; pointer is then incremented for another pass through memory.
; This is repeated until every memory location has been tried
; with each byte in the test pattern. This should catch all
; address-line shorts, data line shorts, coupling between
; nearby cells, stuck bits, and even catch some longer-term
; memory loss faults.
; On Entry:
;   Z set if end of cmd line already
;=============================================================
FTCBM:	jnz	CMDERR		;no parameters allowed

	call	PTESTG		;print 'Testing '
	call	ILPRNT
	db 'buffer memory (Q to quit',')'+80h

	lxi	h,0		;init error count on stack
	push	h		;no errors so far

	lxi	h,MTPAT		;pattern pointer

MTLUP0:	push	h		;pattern starting point
	lxi	b,0		;b=HDSK buffer number
				;c=sector byte count

;--------------------------------
; loop to fill all controller
; buffers with continuous pattern
; hl = starting point in pattern
;--------------------------------
MTLUP1:	lxi	d,SECBUF

; Loop to fill SECBUF with pattern, and then write it to
; to controller buffer b

MTLUP2:	mov	a,m		;get pattern byte
	stax	d		;write it to SECBUF

	call	PPBUMP		;circular-inc hl

	inx	d		;next SECBUF location
	inr	c		;256 bytes yet?
	jnz	MTLUP2		;No: keep writing

	call	WRBUF		;write SECBUF to ctlr buffer b

	inr	b		;next controller buffer
	mov	a,b
	cpi	MAXBUF+1	;done with controller buffers?
	jnz	MTLUP1		;No: keep writing	

;---------------------------
; Read each buffer back, and
; compare to written pattern
;---------------------------
	pop	h		;starting point in pattern
	lxi	b,0		;b=HDSK buffer number
				;c=sector byte count

; loop to test all controller buffers

MTLUP3:	call	RDBUF		;read controller buffer b

;loop to test one controller buffer

	lxi	d,SECBUF
	
MTLUP4:	ldax	d		;get buffer byte
	cmp	m		;compare to pattern

	jz	MTMOK		;match:okay

;report a buffer read-back error

	xthl			;get error count into hl
	inx	h		;bump it
	xthl			;put it back

	call	CILPRT
	db	'Buffer',' '+80h
	mov	a,c		;buffer number
	call	PRHEX
	call	ILPRNT
	db	' memory error at address:',' '+80h
	mov	a,b		;buffer address
	call	PRHEX
	call	ILPRNT
	db	'  Data written:',' '+80h
	mov	a,m		;pattern data
	call	PRHEX
	call	ILPRNT
	db	'  Data read:',' '+80h
	ldax	d		;buffer data
	call	PRHEX

	call	TESTQ		;Abort?

; next byte

MTMOK:	call	PPBUMP		;circular-inc hl

	inx	d		;next buffer address
	inr	c		;256 bytes yet?
	jnz	MTLUP4		;no: keep going

	inr	b		;next controller buffer
	mov	a,b
	cpi	MAXBUF+1	;done with controller buffers?
	jnz	MTLUP3		;no: keep going

;------------------------------------------
; one pass through all buffers is done. Now
; keep going until all patterns have been
; tried in all locations
;------------------------------------------
	call	ILPRNT		;pacifier
	db	'.'+80h

	mov	a,m		;where are we in the pattern?
	inr	a		;is it FF (the first pattern)?
	jnz	MTLUP0		;No: go do another pass

;-----------------------------------------------------------
; All done testing. Tell the user how many errors were found
;-----------------------------------------------------------
	pop	h		;get error count
	mov	a,l
	ora	h
	jnz	PERCNT		;non-zero error count

	call	ILPRNT
	db	'Buffers O','K'+80h
	ret

;******=======================================================
;* TF *  Command: Test Data FIFO
;******
; During normal disk write operations, the FIFO is loaded in
; parallel by the 8X300, and emptied serially by the write
; hardware. During normal disk read operations, the FIFO is
; loaded serially by the read hardware, and read in parallel by
; the 8X300. However, it is possible for the 8X300 to both
; write and read the FIFO in parallel, when the DISTRAN bit in
; IV byte 37 is low. This allows the FIFO the be tested, using
; the SET IV BYTE and READ STATUS disk controller commands. 
;
; This FIFO test algorithm is similar to the buffer memory test
; algorithm. It is based on a test pattern that contains two
; walking bit patterns - one with the bit high, and the other
; with the bit low. It also contains various high-frequency
; patterns, such as 55 and AA. All  These pattern elements are
; scrambled to maximize the number of times each FIFO bit
; changes value during the test. The entire pattern is
; deliberately 29 bytes  long - a  prime number. The pattern is
; written repeatedly through the FIFO, and read back and
; checked every time the FIFO become full. This is repeated
; until every FIFO location has been tried with each byte in
; the test pattern.
;
; This routine also checks to see that the FIFO is full/not
; full when it should be during writes, and empty/not empty
; when it should be during reads.
;
; Note that the FIFO gets an input glitch (most likely caused
; by IC X) whenever the Read Mode bit (IVERDM) toggles from 1
; to 0. This glitch hoses the first write into the FIFO
; following the change to IVERDM. For this reason, the FIFO
; must be cleared (reset) after its mode is changed from read
; to write.
; On Entry:
;   Z set if end of cmd line already
;=============================================================
FTFIFO:	jnz	CMDERR		;no parameters allowed

	call	PTESTG		;print 'Testing '
	call	ILPRNT
	db	'FIF','O'+80h

	lxi	h,MTPAT		;hl is pattern pointer

; Reset the FIFO, and select write mode

FTLUP0:	lxi	b,MXFIFO*256+IVE	;b=byte count,c=[IV 37]
	mvi	a,0		;reset FIFO
	call	WRITIV

	mvi	a,IVECLN	;end reset, select write
	call	WRITIV

; Loop to write b=16 pattern bytes to the FIFO

	push	h		;remember pattern position

FTLUP1:	call	TSTTRR		;check FIFO ready
	jz	FFERR		;TRR should be present

	mov	a,m		;get a pattern byte

	mvi	c,IVB		;[IV 34]
	call	WRITIV		;write e data to port

	mvi	c,IVE		;[IV 37]clock data in
	mvi	a,IVECLN+IVETAS	;TRAS
	call	WRITIV
	mvi	a,IVECLN	;no TRAS
	call	WRITIV

	call	PPBUMP		;Nwxt pattern byte

	dcr	b		;fill the FIFO
	jnz	FTLUP1

	pop	h		;recover pattern pointer

; Check that the FIFO is full

	call	TSTTRR		;check FIFO ready
	jnz	FNFERR		;TRR should not be present

;Loop to read back the FIFO data and compare to the pattern

	lxi	b,MXFIFO*256+IVE	;b=byte cnt, c=[IV 37]
	mvi	a,IVECLN+IVERDM	;select read mode
	call	WRITIV

FTLUP2:	call	TSTTRR		;check FIFO ready
	jz	FNRERR		;TRR should be present

	mvi	c,IVA		;read FIFO data
	call	READIV
	jnz	ABDNR		;abort: drive not ready

	cmp	m		;match pattern?
	cnz	FDERR

	mvi	c,IVE		;[IV 37] clock data
	mvi	a,IVECLN+IVERDM+IVETAS	;TRAS
	call	WRITIV
	mvi	a,IVECLN+IVERDM		;no TRAS
	call	WRITIV
		
	call	PPBUMP		;Next pattern byte

	dcr	b		;empty the FIFO
	jnz	FTLUP2
	
; Check that the FIFO is empty

	call	TSTTRR		;check FIFO ready
	jnz	FNEERR		;TRR should not be present

; print pacifier for the user

	call	ILPRNT
	db	'.'+80h		;pacifier

; test for done - all pattern bytes have been 1st in FIFO

	mov	a,m		;back to pattern start?
	inr	a		;the 1st pattern byte is FF
	jnz	FTLUP0		;no: keep going

; Success

	call	ILPRNT
	db	'FIFO O','K'+80h
	ret

;===Local Subroutine===============
; Get and test Transfer Request bit
; On Exit:
;   Z set if TRR is cleared
; trashes a,c
;==================================
TSTTRR:	mvi	c,IVD		;check FIFO ready
	call	READIV
	ani	IVDTRR		;transfer request
	ret

;******=======================================================
;* TI *  Command: Test IV bus, drivers and receivers,
;******  and read channel
;
; Only IV Output bytes can be verified by reading back what
; was written.  
; 1) Verify CPU Card by writing to IVA8, and reading the
;    written data back from the CSTAT port. Note that this test
;    still works when the Interface Card is not present.
;
; Note that the Ready line on the Interface Card must be true
; (low), for the Read Status commands used in the following
; tests. (This requires a jumper on the Inteface Card.)
;  
; 2) Verify CPU card by looking for the correct values at
;    input IV Bytes, especially those used for communicating
;    with the Altair. Note that the value at these IV Bytes
;    will change as we perform the tests.
;
; 3) Verify the existence of the Interface Card by reading
;    from IVH with each possible Unit number embedded in the
;    Read Status command, and verifying the Unit select bits in
;    the response. IVH is IV address is 17h. The equivalent
;    IV Byte on the Disk Data Card is an input, so this test
;    will fail if the upper IV address lines are stuck.
;
; 4) If the Interface Card exists, then verify the existence of
;    the Disk Data Card by writing to and then reading back
;    from IVE this IV register was chosen because the
;    equivalent IV byte on the Interface card (same low address
;    nibble) is an input - so a stuck address high nibble will
;    not give a false result. 
; 5) Perform a pattern write/readback test across most output
;    IV Bytes on the Interface Card and the Disk Data Card,
;    which should catch any bad IV address or data lines, as
;    well as bad 8T32s for the outbut bytes. Note that IVH
;    (which is an output port) cannot be tested this way,
;    because every Read Status command will reset this port.
;
; 6) Perform a few loop-back test on the Disk Drive I/O ports,
;    which require a cable from P1 to P2, with no connection on
;    pins 1 and 2. Note that these loopback tests are imperfect
;    because of the loopback cable limitations, and because of
;    the 8X300 firmware. 
; 7) Test the read channel logic and the CRC generator/checker
;    by shifting a pattern into the read channel (via the
;    loopback connector) and then reading the data out of the
;    FIFO - and also checking the result of the CRC checker.
;
; The following Disk I/O bits are NOT fully
;    tested here:
;      IVH bits 6:4 and 0
;      IVK bit 7
;      IVL bit 7
;=============================================================
FTIV:	call	CILPRT
	db	'-==IV Bus Test==-',CR,LF+80h
	call	PTESTG		;print 'Testing '
	call	ILPRNT
	db	'Processor Data Bu','s'+80h

;-----------------------------------------------------
; Test Processor Card IV data bus using the Error port
;-----------------------------------------------------
	lxi	h,MTPAT
	mvi	c,IVA8		;write to IV Byte A8

CPUT1:	in	ACMD		;reset CMDACK in ACSTA

	mov	a,c
	out	ADATA		;low byte of command

	in	ADATA		;clear ADPA

	mvi	a,CSETIV	;Set IV Byte command
	out	ACMD		;initiate command now

	mov	d,a		;128x19 uS=2.4 mS timeout

CPUT2:	dcr	d		;(4)timeout?
	jz	ADPERR		;(10)
	in	ADSTA		;(10)wait for ADPA
	rlc			;(4)test ADPA
	jnc	CPUT2		;(10)

	mov	a,m		;send data
	out	ADATA

	MVI	d,128		;128x19 uS=2.4 mS timeout

CPUT3:	dcr	d		;(4)timeout?
	jz	HDCTO		;(10)

	in	CREADY		;(10)Is the controller done? 
	rlc			;(4)look at msb=CRDY
	jnc	CPUT3		;(10)No: keep waiting

	in	CSTAT		;read back on the other side
	cmp	m
	cnz	IVTERR

	mov	a,m
	inx	h
	ora	a		;done?
	jnz	CPUT1

;-------------------------------------------------------------
; test the Processor Card's input IV bytes via the IVRCT table
;-------------------------------------------------------------
	call	ILPRNT
	db	' OK',CR,LF,CR,LF
	db	'   Next test: Card Presence',CR,LF
	db	'   Please confirm:',CR,LF
	db	'     No drives connected,',CR,LF
	db	'     Interface Card installed,',CR,LF
	db	'     Interface Card jumper from C3+ to C8+',CR,LF
	db	'     Ready line tru','e'+80h
	call	ASKCON

	call	PTESTG		;print 'Testing '
	call	ILPRNT
	db	'Processor IV Byte','s'+80h

	lxi	h,IVRCT
CPUT4:	mov	c,m		;IV address
	inx	h
	call	READIV
	cmp	m		;match expected data?
	cnz	IVTERR
	inx	h
	mov	a,m
	ora	a		;table end?
	jnz	CPUT4

;print test result

	call	ILPRNT
	db	CR,LF,'--','>'+80h
	call	PCPC		;cpu card

	lda	RESULT		;any errors?
	ora	a
	cnz	PRNOT		;not

;------------------------------------------------------
; Is the 88-HDSK Interface Card present? Test with IVH.
; IVH gets overwritten whenever a Read IV command runs,
; and We take advantage of this, using the unit update
; to test the low 4 bits of IVH.
;------------------------------------------------------		
	lda	CRUNIT		;save so we can put it back
	push	psw

	lxi	h,4*256+0F7h	;h=unit, l=unit val
	lxi	b,IVH		;b=no error so far
				;c=test unit bits here

HIC1:	mov	a,h		;select a unit
	dcr	a		;so it is 0-3
	sta	CRUNIT
	call	READIV

	xra	l
	ani	0Fh		;just look at unit bits

	ora	b		;remember errors
	mov	b,a

	mov	a,l		;next mask
	rrc
	mov	l,a

	dcr	h		;next unit
	jnz	HIC1

; Print test result

	call	ILPRNT
	db	CR,LF,'--','>'+80h
	call	PIVC	

	pop	PSW		;restore orig. unit
	sta	CRUNIT

	mov	a,b		;any errors?
	ora	a

	call	PRESNT		;present or not present

	rnz			;Done if no Interface card

;---------------------------------------------------------
; Is the Disk Data Card present? Test with IVE because
; the equivalent port on the Interface card is an input
; port, so a stuck adress line will likely get caught.
; Note that no IV Bytes can be read without the Disk
; Interface Card present and the Ready line on that card
; active, because the Read Status command insists that
; the selected Unit be ready before it will return status.
;---------------------------------------------------------
	mvi	c,IVE		;[IV 37]look for disk data card
	mvi	e,0AAh
	mov	a,e
	call	WRITIV

	call	READIV
	cmp	e		;readback match?

; Print test result, set b according to result

	call	ILPRNT
	db	CR,LF,'--','>'+80h
	call	PDDC		;Disk Data Card

	mvi	b,IVLEND-IVLIST	;full table length

	call	PRESNT		;present or not present
	jz	DDPRES		;Disk Data Card present?

	mvi	b,IVLST2-IVLIST	;no Data Card IV Bytes

DDPRES:
;---------------------------------------	
; Test IV Bytes in IVLST, doing b bytes.
;---------------------------------------
	call	CILPRT
	db	CR,LF,'   Next test: ',' '+80h
	call	PIADB		;'IV Address/Data bus'
	call	ASKCON		;ask if we should continue

	call	PTESTG		;print 'Testing '
	call	PIADB		;'IV Address/Data bus'

	xra	a
	sta	RESULT		;no errors so far

	lxi	h,MTPAT		;test pattern start

IVT0:	push	h		;save pattern position
	push	b		;remember IV count
	lxi	d,IVLIST	;list of testable IV bytes

IVT1:	ldax	d		;get IV address
	mov	c,a		;..into c
	mov	a,m		;a gets pattern byte
	call	WRITIV		;write to IV Byte

	call	PPBUMP		;circular-inc pattern

	inx	d		;next IV Byte
	dcr	b
	jnz	IVT1		;Loop thru all output IVs

	pop	b		;recover IV count
	pop	h		;recover pattern position

	push	b		;save IV count
	lxi	d,IVLIST	;list of testable IV bytes

IVT2:	ldax	d		;get IV address
	mov	c,a		;..into c
	call	READIV		;read IV byte

	cmp	m		;match?
	cnz	IVTERR		;No: report error

	call	PPBUMP		;circular-inc pattern

	inx	d		;next IV Byte
	dcr	b
	jnz	IVT2

	pop	b		;recover count

	mov	a,m		;end of pattern?
	ora	a
	jnz	IVT0

; Print test result

	call	CILPRT
	db	'--','>'+80h
	call	PIADB

	lda	RESULT		;any errors?
	ora	a
	cnz	PRNOT

	call	ILPRNT
	db	' O','K'+80h

;------------------------------------------------------------
; Disk Port Loopback Pattern Walk Test
;
; This test walks the test pattern through the output ports,
; and reads them back from the input ports. Note that the
; port bits are scrambled, and require some unscrambling here.
;
; This test requires straight through loopback cable from P1
; to P2, EXCEPT Pins 1 and 2 not connected <==SUPER IMPORTANT
;
; Note that the Ready line must be held high during this
; test, otherwise, the Read Status command will not complete.
;
; Also note that the Read Status command overwrites IVH bits
; 6:0 with 7xh, where x is the selected unit passed in the
; command.
;
; This tests the following output bits:
;  IVH bit 7
;  IVI bits 7:6 and 4:0
;  IVJ bits 5:0
;
; This tests the following input bits
;  IVL bits 7:3
;  IVM bits 6:0
;------------------------------------------------------------
	xra	a
	sta	RESULT	;no errors so far

	call	CILPRT
	db	CR,LF
	db	'   Next Test:',' '+80h
	call	PDDPLT	;'Disk Drive Loopback Test'
	call	CILPRT
	db	'   Please confirm:',CR,LF
	db	'     Loopback cable from Interface Card P1'
	db	' to P2',CR,LF
	db	'     No Connection on pins 1 & 2','!'+80h
	call	ASKCON

	lxi	h,MTPAT	;test pattern

;write 3 pattern bytes to the output ports, always forcing
; Ready high.
LUPBAK:	push	h	;pattern position
	mvi	c,IVH	;[IV 17]

	mov	a,m
	
	cma		;IVH is an inverting port
	call	WRITIV

	call	PPBUMP	;circular-inc pattern

	inr	c	;IVI [IV 18]
	mov	a,m
	
	ori	20h	;set Ready high
	cma		;IVI is an inverting port
	call	WRITIV

	call	PPBUMP	;circular-inc pattern

	inr	c	;IVJ [IV 19]
	mov	a,m
	
	cma		;IVJ is an inverting port
	call	WRITIV

; read back each byte, and compare
; Note that IVH gets rewritten by the Read Status command,
; so it can't be tested here.

	pop	h	;recover pattern position
	
; put bits in place to match what was read in IVL, & compare
; remember that the Ready line (bit 7) must be high,
; and that the controller sets IVH bits 6:0 to 3xh

	mov	a,m	;low bits from 1st pattern byte
	rlc		;put bit 7 into bit 2
	rlc		;(bits 6:5 get overwritten
	rlc		;..by the Read Status cmd)
	ani	04h	;also clear Extension Select
	mov	d,a	;save partial result

	call	PPBUMP	;circular-inc pattern

	mov	a,m
	rlc		;put bits 5:2,0 into 7:4,2
	rlc
	ani	0F4h
	mov	b,a

	ani	04h	;shove bit 2 into bit 3
	add	b
	ora	d	;combine results
	ori	81h	;ready line high, Platter Sel high
	mov	b,a	;b has expected result

	mvi	c,IVL	;c=21
	call	READIV	;what do we read?	
	cmp	b	;match what we read?
	cnz	IVTER2	;n:report error

; put bits in place to match what was read in IVM, & compare

	mov	a,m	;low bits from 2nd IV byte
	rlc		;out bits 7:6 into bits 1:0
	rlc
	ani	03h
	mov	b,a

	call	PPBUMP	;circular-inc pattern

	mov	a,m	;high bits from 3rd IV byte
	rlc		;put bits 5:0 into bits 7:2
	rlc
	ani	0FCh

	ora	b	;combine
	mov	b,a	;b has expected result

	inr	c	;c=22 (IVM)
	call	READIV	;what do we read?

	cmp	b	;match what we read?
	cnz	IVTER2	;n:report error

; One pass to disk ports done. Next pass with new data.

	call	PPBUMP	;circular-inc pattern

	mov	a,m
	ora	a	;end of pattern?
	jnz	LUPBAK

;------------------------------------------------------
; use loopback to test select 2-4, using the fact that
; Read Status sets the Unit number. Note that this test
; relies on the prior test leaving Start/Stop low.
;
; This tests the following output bits:
;   IVH bits 3:1
; This tests the following input bits:
;   IVK bits 4,2,0
;------------------------------------------------------
	lda	CRUNIT		;save init unit number
	push	psw

	lxi	h,UNITAB	;expected values
	mvi	c,IVK
	mvi	d,4		;Unit number

TSELP:	mov	a,d
	dcr	a
	STA	CRUNIT
	call	READIV
	cmp	m
	cnz	IVTERR
	inx	h
	dcr	d
	jnz	TSELP

	POP	psw		;restore orig. unit
	STA	CRUNIT

 ; Print test result

	call	CILPRT
	db	'--','>'+80h
	call	PDDPLT

	lda	RESULT	;any errors?
	ora	a
	cnz	PRNOT

	call	ILPRNT
	db	' O','K'+80h

;--------------------------------------------------------
; Read Channel Loopback Test
; uses loopback cable to clock data into the read channel
; until the FIFO is full, then read it back and compare.
; The CRC checker is tested here too.
; This also tests the following output bits:
;   IVJ bits 7:6
;--------------------------------------------------------
	xra	a
	sta	RESULT		;no errors so far

	call	CILPRT
	db	CR,LF
	db	'   Next Test:',' '+80h
	call	PRCCLT		;'Rd Chan/CRC loopback tst
	call	ASKCON

;flush the data pipeline with 0's

	xra	a
	call	WRFM

; set up IVE for read, and clear the FIFO

	mvi	a,IVERDM	;reset hardware
	mvi	c,IVE		;[IV 37]
	call	WRITIV
	mvi	a,IVERDM+IVECLN	;end CLRn pulse
	call	WRITIV

; set up data bit count for 16 bytes=128 bits
;clear all ctrs first

	mvi	c,IVC		;[IV 35]
	mvi	a,IVCLN0+IVCLN1+IVCLN2+IVCLN3
	call	WRITIV
	xra	a	;strobe all 4 counters
	call	WRITIV
	mvi	a,IVCLN0+IVCLN1+IVCLN2+IVCLN3
	call	WRITIV

;make it 128 bits (80h)

	mvi	a,08h+IVCLN0+IVCLN1+IVCLN2+IVCLN3
	call	WRITIV
	mvi	a,08h+IVCLN0+IVCLN2+IVCLN3
	call	WRITIV
	mvi	a,08h+IVCLN0+IVCLN1+IVCLN2+IVCLN3
	call	WRITIV

;Set Disk Transfer Start

	mvi	c,IVE		;[IV 37]
	mvi	a,IVERDM+IVEDTS+IVECLN
	call	WRITIV
 
; loop to send preamble, sync byte, 13 data bytes
; 2 CRC bytes, postamble
	mvi	d,18		;byte count
	lxi	h,WRPAT

RCWLUP:	mov	a,m
	call	WRFM
	inx	h
	dcr	d
	jnz	RCWLUP

; check to see if transfer completed
; and check CRC
; Check that the FIFO is full

	mvi	c,IVD
	call	READIV
	rrc
	rrc			;bit 1 is IVDDTC
	cnc	PNODTR		;transfer complete?
	rrc			;bit 2 is IVDDCE

	cc	PCRCER		;CRC error?

;clear Disk Transfer Start

	lxi	b,16*256+IVE	;[IV 37]b for following loop
	mvi	a,IVERDM+IVECLN
	call	WRITIV
 
; loop to read 16 bytes from the FIFO and compare

	lxi	h,WRPAT+1	;sync byte is in fifo
	
RCRLUP:	call	TSTTRR		;check FIFO ready
	cz	FNRERR		;TRR should be present

	mvi	c,IVA		;read FIFO data
	call	READIV
	jnz	ABDNR		;abort: drive not ready

	cmp	m		;match pattern?
	cnz	FDERR

	mvi	c,IVE		;[IV 37]clock data
	mvi	a,IVECLN+IVERDM+IVETAS	;TRAS
	call	WRITIV
	mvi	a,IVECLN+IVERDM		;no TRAS
	call	WRITIV
		
	inx	h		;Next pattern byte

	dcr	b		;empty the FIFO
	jnz	RCRLUP

; Print test result

	call	CILPRT
	db	'--','>'+80h
	call	PRCCLT		;'IV Read Channel/CRC test'
	lda	RESULT		;any errors?
	ora	a
	cnz	PRNOT

	call	ILPRNT
	db	' O','K'+80h

	call	CILPRT
	db	CR,LF
	db	'-==Controller IV Bus Test Complete==-',CR,LF+80h

	xra	a		;clear z for FINIT

; Note that the above tests left the 4 Seeking lines
; inactive, since the loopback cable is still installed.

	jmp	FINIT		;restore IV Bytes & ret

;====Local Subroutine===========================
; Encode a as FM and write it out to the read
; channel via the loopbacl cable.
; Each bit is 1 data bit and then 1 clock bit,
; starting and ending with  clock and data high.
; Trashes bc
;===============================================
WRFM:	lxi	b,8*256+IVJ	;8 bits, [IV 19]

WRFMLP:	rlc			;get a data bit msb 1st
	push	psw

	mvi	a,0		;IVJ data, keep carry
	rar			;carry data into msb
	cma			;active low bits
	call	WRITIV		;write data bit

	mvi	a,0FFh
	call	WRITIV		;end data
	mvi	a,0BFh		;clock bit
	call	WRITIV
	mvi	a,0FFh
	call	WRITIV

	pop	psw		;recover data
	dcr	b		;next bit
	jnz	WRFMLP

	ret

;===TABLE===================================
;IV Output Bytes
; Interface Card IV Ports 1st
; Disk Data Card IV ports next
; Note: we can't test IVH here, because the
; Read Status command rewrites this IV byte.
;===========================================
IVLIST:	db	IVI	;12h (output) Disk Control B
	db	IVJ	;13h (output) Cylinder bits 7:0

IVLST2:	db	IVB	;22h (output) FIFO write data
	db	IVC	;23h (output) Set Bit Counters
	db	IVE	;25h (output) Disk Control A
IVLEND:

;===TABLE===========================
; IV Read-Only Bytes
; First value is the IV Port address
; second value is the expected data
; table terminated with 0
;===================================
IVRCT:	db	IVA10,0FFh	;Handshakes to Altair
	db	IVA12,0FFh	;previous CDATA
	db	IVA9,60h	;READIV command
	db	IVA12,60h	;previous CDATA
	db	IVA11,0FFh	;Handshakes from Altair
	db	IVA12,0FFh	;previous CDATA
	db	IVA13,07h	;Command low byte
	db	IVA12,07h	;previous CDATA
	db	0

;===TABLE===========================
; Expected loopback values on IVK as
; the various drives are selected
;===================================
UNITAB:	db	0FAh		;Drive 4 selected
	db	0EEh		;Drive 3 selected
	db	0EBh		;Drive 2 selected
	db	0EAh		;Drive 1 or no drive

;===TABLE=================
;Read channel test pattern
;=========================
WRPAT:	db	000h			;preamble
	db	080h			;sync byte
	db	001h,002h,005h,00Ah	;pattern
	db	015h,02Ah,054h,0A8h
	db	050h,0A0h,040h,080h
	db	0FFh
PATCRC:	db	0A9h,075h		;correct CRC
	db	000h			;postamble

;****************************
;* Disk Positioner Commands *
;****************************

;******===================================
;* RE *  Command: Restore to Cylinder Zero
;******
; On Entry:
;   Z set if end of cmd line already
;=========================================
FRESTR:	jnz	CMDERR		;No parameters allowed

	call	CILPRT
	db	'Restorin','g'+80h

; fall into DORSTR

;===Subroutine===========
; Restore without fanfare
;========================
DORSTR:	call	RSETUP		;get ready to restore
	call	RESTOR		;actually restore now	

;fall into RSTEND

;===Subroutine=========
; Tidy up after restore
;======================

;write 0 to cyl msb, so it is right later, and cancel restore

RSTEND:	mvi	c,IVI		;[IV 18]cancel restore
	lda	CRRTO
	cma			;put offset back
	jmp	WRITIV		;write a to IV c

;******==============================================
;* RR *  Command: Repetative Restore to Cylinder Zero
;******
; On Entry:
;   Z set if end of cmd line already
;====================================================
FRREST:	jnz	CMDERR		;No parameters allowed

	call	CILPRT
	db	'Repetative Restore. Q to qui','t'+80h

	call	RSETUP		;get ready to restore

;restore over and over until someone types Q

RRLOOP:	call	RESTOR		;restore

	call	SINP		;Abort?
	cpi	'Q'
	jnz	RRLOOP

	jmp	RSTEND		;cleanup and quit

;******==============================================
;* SK * Command: Seek Tracks <trk1> [...trkn]
;******
; Parameters:
;  C1, [C2, [C3...]]
; Seek sequentially through the list of specified
; tracks, repeating the list until the user types
; 'Q'. If no list is specified, then use list from the
; previous SK command.
;
; If just one track is specified, then just seek that
; track and quit.
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;   AUTOMO = 0 for manual mode, <>0 for automatic
;   VERBMO <>0 for verbose mode, 0 for quiet mode
;====================================================

;Get complete list of target cylinders, convert
;them to binary, and store them back in LINBUF.

FSEEK:	mov	a,m
	ora	a		;any list of cyls?
	cnz	GCYLST		;get list if any

	call	SANRTO		;set up track offset, announce
				;..only if there is an offset

;If 0 or 1 track, then just seek a track and quit

	lxi	h,PATBUF	;point to track list
	mov	a,m		;first byte is pattern count
	cpi	2		;0 or 1 tracks?
	jc	SEEK1		;yes: seek just 1 track

;----------------
;Multi-track seek 
;----------------

;Print automatic mode or manual mode message

	lda	AUTOMO		;automatic mode?
	ora	a
	cz	MMMSG		;returns with Z flag set
	cnz	AMMSG

;Seek tracks listed in PATBUF

SEKAGN:	lxi	h,PATBUF	;point to track list
	mov	b,m		;first byte is pattern count

SEKLUP:	inx	h
	mov	e,m		;get track number
	inx	h
	mov	d,m		;..which is 16 bits

	push	b		;save count
	push	h		;save pointer

	xchg			;track number to hl
	call	SEEK		;seek specified track

	call	OPSKTK		;print time & track
	call	SEEKUI		;wait for user & ret

	pop	h		;pointer
	pop	b		;count

	dcr	b		;next track
	jnz	SEKLUP		;loop thru b tracks

	jmp	SEKAGN		;restart sequence

;------------------------------------------------
; Just seek one track, report the track, and quit
;------------------------------------------------

SEEK1:	inx	h
	mov	a,m		;get track no.
	inx	h
	mov	h,m		;which is 16 bits
	mov	l,a

	call	SEEK
	call	DUMYRD		;re-select the correct head

	jmp	OPSKTK		;print seek time and done

;******==============================================
;* ST * Command: Step {I/O/A}
;******
; Step in or out 1 track, repeating automatically or
; with each space until the user types 'Q'.
;
; Parameters:
;  I steps in once
;  O steps out once
;  A steps out and in automatically from end to end
; The user may type 'A' or "M' to switch to automatic
; or manual stepping.
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;   AUTOMO = 0 for manual mode, <>0 for automatic
;   VERBMO <>0 for verbose mode, 0 for quiet mode
;====================================================
FSTEP:	push	psw
	call	SANRTO		;set up track offset, announce
				;..only if there is an offset
	pop	psw

	lxi	d,1		;D=1 means step in
	jz	STPMAN		;no value means step in

	mov	a,m		;get value
	cpi	'A'		;go straight to automatic?
	jz	STAUTO		;y: yes, and start stepping in

	cpi	'I'		;Step in?
	jz	STPMAN		;Step in manually

	lxi	d,-1		;d=-1 means step out
	cpi	'O'
	jnz	CMDERR

STPMAN:	call	MMMSG		;report manual mode
	call	IOMSG		;I and O work too

	xra	a		;Start out manual
	sta	AUTOMO

;--------------------------------------------
; de = -1 for stepping out,+1 for stepping in
; AUTOMO=0 for manual mode, <>0 for automatic
;--------------------------------------------
STEPIO:	call	GETCYL		;get hl=current cylinder
	jnz	ABDNR		;abort: drive not ready

	mov	a,h		;reached cylinder 0?
	ora	l
	jnz	STIO1

;At track 0. Stop here if manual, switch direction if auto

	lda 	AUTOMO		;manual mode..
	xra	d		;..and stepping out?
	inr	a
	jz	STLIMT		;manual:stop at 0

	lxi	d,1		;step in now

	xra	a		;go to STEPOK

STIO1:	mov	a,l
	sui	MAXCYL AND 0FFh	;step in to max track?
	mov	a,h
	sbi	MAXCYL/256
	jnz	STEPOK

;At max cylinder. Stop here if manual, switch direction if auto

	lda 	AUTOMO		;manual mode
	xra	d		;..and stepping in?
	jz	STLIMT		;manual:stop at max

	lxi	d,-1		;step out now

;step in or out now.

STEPOK:	dad	d		;next track now
	push	d
	call	SEEK
	call	DUMYRD		;re-select the correct head
	call	OPSKTK		;report time & track
	pop	d

	lda	AUTOMO		;manual mode?
	ora	a
	jz	STWAIT		;y: go wait for user

;--------------------------------------------------
; Automatic stepping: give the user a chance to
; quit or change to manual mode, then keep stepping
;--------------------------------------------------
	call	TESTQ		;chance to abort
	sui	'M'		;switch to manual mode?
	jnz	STEPIO

	sta	AUTOMO		;manual mode now
	call	MMMSG		;report switch to manual mode
	call	IOMSG		;I and O work too

;fall into STLIMIT to report the starting track

;---------------------------------------------------
;Manual stepping: Wait for user input or Q to abort.
; Space means do the same thing as last step
; 'I' means step in now
; 'O' means step out now
; 'A' means switch to automatic mode
;---------------------------------------------------
STLIMT:	push	d
	call	PCYL		;report limit track
	pop	d

STWAIT:	call	TESTQ		;get chr, abort if Q
	jz	STWAIT		;wait for chr

	cpi	' '		;more of the same?
	jz	STEPIO		;keep stepping

	xchg			;save old direction

	lxi	d,1		;D=1 means step in
	cpi	'I'		;step in?
	jz	STEPIO		;y: de is set

	lxi	d,-1		;D=1 means step out
	cpi	'O'		;step out?
	jz	STEPIO		;y: de is set

	xchg			;put old direction back

	cpi	'A'		;switch to automatic mode?
	jnz	STWAIT		;n: ignore all else

;switch to automatim mode with announcement. a<>0 here.

STAUTO:	sta	AUTOMO		;a<>0 to switch to automatic
	call	AMMSG		;report automatic mode switch
	call	IOMSG		;I and O work too
	jmp	STEPIO		;and get going

;****************************
;* Disk Read/Write Commands *
;****************************

;******=======================================
;* DC *  Command: Disk Copy
;******
; copies both sides of either platter to the
; other platter in the selected unit. (Does
; not support extension platters.)
;
; Parameters:
;  <SP>  specifies the source platter
;  <DP>  specifies the destination platter
;  [/F]  formats the destination platter first
;  [/V]  verifies the destination when done
;  [/P]  parks the heads when done
; On Entry:
;   Z set if end of cmd line already
;   hl points to first chr of a parameter
;   if NZ, then all sectors will be written
;   the provided data
;=============================================
FDCOPY:	lxi	d,1		;max value
	call	GETDEC		;get source disk
	jz	CMDERR		;must specify source
	push	b		;save c=source
	call	GETDEC		;get dest
	jz	CMDERR		;mut specify source & dest

	mov	a,c
	pop	b
	cmp	c		;source=dest?
	jz	CMDERR		;y: error

;save source and destination platter number,
;in the right format. a=dest, c=source

	mov	b,a		;for message
	sta	DSTPLA
	mov	a,c		;and set up dest
	sta	SRCPLA

;check for & get /options

	lxi	d,0		;d:no format flag
				;e:no verify flag

DCOPLP:	call	GSLASH		;get a slash option
	jz	DCOP3		;got all?

	cpi	'V'		;Verify option?
	jnz	DCOP1
	inr	e
	jmp	DCOPLP

DCOP1:	cpi	'P'		;park-it option?
	jnz	DCOP2
	sta	PARKIT
	jmp	DCOPLP

DCOP2:	cpi	'F'		;format option?
	jnz	CMDERR
	inr	d		;format flag
	jmp	DCOPLP
DCOP3:

;print planned copy and ask for confirmation

	push	d		;options
	push	b		;source & dest

	call	CILPRT
	db	'Copy Unit',' '+80h

	lda	CRUNIT
	inr	a		;units are 1-4
	call	PDEC8

	call	ILPRNT
	db	QUOTE,'s'+80h

	pop	b

	mov	a,c		;platter
	call	PNAME

	call	ILPRNT
	db	' to it','s'+80h

	mov	a,b		;platter
	call	PNAME

	pop	d		;options

	mov	a,d		;format?
	ora	a
	jz	DCUI2
	call	CILPRT
	db	'with forma','t'+80h

	mov	a,e		;verify too?
	ora	a
	jz	DCUI4
	call	ILPRNT
	db	' an','d'+80h
	jmp	DCUI3

DCUI2:	ora	e		;just verify?
	jz	DCUI4
	call	CILPRT
	db	'wit','h'+80h

DCUI3:	call	ILPRNT
	db	' verif','y'+80h

DCUI4:	call	POPARK		;tell about park option
	call	SARTO		;set & announce offset

	call	CASKYN		;ask for confirmation

;------------------------------------------
;format the disk, if /F option was selected
;------------------------------------------
	xra	a		;initially top side
	sta	CRSIDE

	ora	d		;Format option?
	jz	DC2

	mov	a,b		;destination disk
	sta	CRPLAT

	call	FORMAT		;format with message
	call	TOGSID		;other side
	call	FORMAT		;format with message
	call	TOGSID		;back to top

DC2:	push	d		;save format & verify opts

;---------------------------------------------------------------
;Copy disk SRCPLA to disk DSTPLA, using all 4 controller buffers
;--------------------------------=------------------------------
	call	CILPRT
	db	'Copying. Q to qui','t'+80h

	lxi	h,-1		;for initial inc

;Loop to copy all cylinders from SRCPLA to DSTPLA

COPDSK:	inx	h		;bump cylinder count
	push	h		;cylinder onto stack

	call	SEEK		;seek hl

	call	TESTQ		;opportunity to abort
	call	ILPRNT
	db	'.'+80h		;pacifier

;loop to copy both sides of this cylinder

COPCYL:	lxi	b,MAXSEC	;c=sector counter

;Loop to copy all 24 sectors on this side

COPTRK:	push	b		;save sect count for write

;Read 4 interleaved sectors from the source disk
;into 4 controller buffers, with 2:1 interleave

	call	SETRTO		;set up source offset
	lda	SRCPLA
	lxi	d,7F00h+CRDSEC	;read sector, check errors
	call	RW4		;read 4 sectors now

	pop	b		;recover sector count

;Write 4 interleaved sectors from the 4 controller
;buffers to the destination disk, with 2:1 interleave

	call	CLRRTO		;no offset during write
	lda	DSTPLA
	lxi	d,0FF00h+CWRSEC	;write sector, check errors
	call	RW4		;write 4 sectors now

;next 4 sectors

	mov	a,b		;all sectors done?
	ora	a		;bc underflow?
	jz	COPTRK		;n: more on this track

;next side

	lda	CRSIDE
	xri	1		;toggle
	sta	CRSIDE
	jnz	COPCYL		;do bottom side

;next cylinder

	pop	h		;cylinder number

	mov	a,l
	sui	MAXCYL and 0FFH
	mov	a,h
	sbi	MAXCYL/256
	jnz	COPDSK

;---------------------------------
;verify, if /V option was selected
;---------------------------------
	pop	d		;e=verify option
	mov	a,e		;verify option?
	ora	a
	cnz	PVERFY		;compare the 2 platters

;Done. Restore or park as necessary

	jmp	DOPARK		;restore or park & done

;===Local Subroutine=====================
; Read or write 4 interleaved sectors
; into 4 controller buffers
; On entry:
;  a = (SRCPLA) or (DSTPLA)
;  bc = sector counter, with bits 1:0=11b
;  d = error mask
;  e = read or write command
; On exit:
;  bc decremented 4
;  CRPLAT=(SRCPLA) or (DSTPLA) as per a
; trashes a,hl
;========================================
RW4:	sta	CRPLAT

RW4LUP:	mov	a,c		;compute buffer number
	ani	3		;..is lsb 2 of sector count

	lxi	h,INLV2		;interleave sector lookup
	dad	b		;lookup with hl
	push	b		;save sector count
	mov	c,m		;c=interleaved sector #
	mov	b,a		;b=buffer #

	push	d		;save command & mask
	call	DORDWR		;read or write sector now
	pop	d

	pop	b		;sector count

	mov	a,c		;4 buffers full?
	dcx	b		;next
	ani	3
	jnz	Rw4LUP

	ret

;******======================================
;* DV *  Command: Disk Verify
;******
; compare both sides of either platter to the
; other platter in the selected unit. (Does
; not support extension platters.)
;
; Parameters:
;  <SP> specifies the source platter
;  <DP> specifies the destination platter
; On Entry:
;   Z set if end of cmd line already
;   hl points to first chr of a parameter
;   if NZ, then all sectors will be written
;   the provided data
;============================================
FVERFY:	lxi	b,1		;default
	jz	FVER1		;use defaults

	lxi	d,1		;max value
	call	GETDEC		;get source disk

	mov	a,c		;c=source
	push	psw		;save a=source

	call	GETDEC		;get c=dest
	jz	CMDERR		;must specify source & dest

	pop	psw		;recover source
	mov	b,a		;b=source

FVER1:	mov	a,b
	sta	SRCPLA
	cmp	c		;source=dest?
	jz	CMDERR		;y: error

	mov	a,c
	sta	DSTPLA

	call	SARTO		;set & announce source offset

; fall into PVERFY

;===Subroutine===============================
; Verify Disk
; Compare all bytes of platter SRCPLA to
; all bytes of DSTPLA, and report differences
;============================================ 
PVERFY:	call	CILPRT
	db	'Verifying. Q to qui','t'+80h

;Compare SRCPLA to DSTPLA, using all 4 controller buffers

	lxi	h,0		;clear error count
	shld	BYTCNT

	dcx	h		;for initial inc

;Loop to verify all cylinders from SRCPLA to DSTPLA

VERDSK:	inx	h		;bump cylinder count
	push	h		;cylinder onto stack

	call	SEEK		;seek hl

	call	TESTQ		;opportunity to abort
	call	ILPRNT
	db	'.'+80h		;pacifier

;loop to verify both sides of this cylinder

VERCYL:	lxi	b,MAXSEC	;c=sector counter

;Loop to verify all 24 sectors on this side

VERTRK:	push	b		;save sect count for compare

;Read 4 interleaved sectors from the source disk
;into 4 controller buffers, with 2:1 interleave

	call	SETRTO		;offset on source disk
	lda	SRCPLA
	lxi	d,7F00h+CRDSEC	;read sector, check errors
	call	RW4		;read 4 sectors now

;Read these 4 sectors into the verify buffer

	lxi	h,VERBUF
	mvi	b,3		;buffer #
	mvi	d,0		;byte count	

VRBLP:	call	RBSTUP		;trashes c

VRDLP:	in	CDATA		;get a data byte
	mov	m,a		;stash it
	inx	h
	dcr	d		;next byte
	jnz	VRDLP

	dcr	b		;next buffer
	jp	VRBLP	
	
;Read/compare these  4 sectors from the other platter

	call	CLRRTO		;no offset on the dest platter
	pop	b		;recover sector count
	lda	DSTPLA
	lxi	d,7F00h+CRDSEC	;read sector, check errors
	call	RW4		;read 4 sectors now
	push	b		;c=sector count

	lxi	h,VERBUF
	mvi	b,3		;c=buffer #

VWBLP:	call	RBSTUP		;trashes c
	lxi	d,0		;d=byte count, e=error accumulate

VWRLP:	in	CDATA		;get a data byte
	xra	m		;compare to correct data
	ora	e		;combine with previous errors
	mov	e,a		;accumulate errors in d
	inx	h
	dcr	d		;next byte
	jnz	VWRLP

	ora	a		;all sector bytes ok?
	cnz	CSCERR		;n: report mismatch

	dcr	b		;next buffer
	jp	VWBLP

;next 4 sectors

	pop	b		;recover sector count
	mov	a,b		;all sectors done?
	ora	a		;bc underflow?
	jz	VERTRK		;n: more on this track

;next side

	lda	CRSIDE
	xri	1		;toggle side
	sta	CRSIDE
	jnz	VERCYL

;next cylinder

	pop	h		;cylinder number

	mov	a,l
	sui	MAXCYL and 0FFH
	mov	a,h
	sbi	MAXCYL/256
	jnz	VERDSK

;report errors

	call	DORSTR		;restore

	lhld	BYTCNT
	jmp	PERCNT		;print # of verify errors

;===Local Subroutine=====================
;Report verify error
; On Entry:
;       c=buffer number 
;  Stack: Return
;         Sector number-1 (uninterleaved)
;         Cylinder
;  CRSIDE = side
; On Exit:
;  Error count in BYTCNT bumped
;  error reported
; trashes a,de,hl
;=======================================
CSCERR:	lhld	BYTCNT
	inx	h
	shld	BYTCNT

	pop	h	;return address
	pop	d	;e=sector number
	xthl		;d=0, hl=cyl number

	call	CILPRT
	db	'Error: Cylinder',' '+80h
	push	d	;sector number
	push	h	;cylinder
	push	b
	call	PDEC16	;print cylinder

	call	ILPRNT
	db	', Side:',' '+80h
	lda	CRSIDE	;side
	call	PDEC8
	pop	b

	call	ILPRNT
	db	', Sector:',' '+80h

	pop	h	;cylinder
	pop	d	;sector

	push	d	;sector
	push	h	;cylinder

	inx	d
	mov	a,e	;sector
	add	c	;which one of 4
	mov	e,a

	lxi	h,INLV2	;interleave sector
	dad	d
	mov	a,m	;actual sector number
	call	PDEC8	;cylinder

	pop	h	;cylinder
	pop	d	;sector

	xthl		;hl=return address, restore cyl
	push	d	;restore sector number

	pchl		;return		

;******=========================================
;* FD *  Command: Format Disk
;******
; Parameters:
;  [<H>] specifies optional disk data fill
;        (otherwise data fields are not written)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;   if NZ, then all sectors will be
;   written with the provided data
;===============================================
FMTDSK:	mvi	c,1		;do both sides
	jmp	FMTSD

;******=========================================
;* FS *  Command: Format Disk Current Side
;******
; Parameters:
;  [<H>] specifies optional disk data fill
;        (otherwise data fields are not written)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;   if NZ, then all sectors will be
;   written with the provided data
;===============================================
FMTSID:	mvi	c,0		;only do one side

; fall into FMTSD

;===================================
; Format Disk or Disk Side Command
;
; On Entry:
;   c=0 to format only CRSIDE
;   C=1 to format both sides
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;   if NZ, then all sectors will be
;   written with the provided data
;===================================
FMTSD:	call	GETHEX		;get e=fill value, if any
	mvi	d,0
	jc	FD1		;no fill: leave d=0
	inr	d		;d=1, e=fill data	

;print & confirm parameters of proposed format

FD1:	call	PUNIT		;print selected unit
	call	PPLAT		;current platter
	mov	a,c		;both sides?
	ora	a
	cz	PSIDE		;n:current side
	call	POVWRT		;overwrite data

	call	CILPRT
	db	CR,LF,'Format this dis','k'+80h

	mov	a,c		;both sides?
	ora	a
	jnz	FD2

	call	ILPRNT
	db	'sid','e'+80h

FD2:	call	ASKYN

;see if selected disk is write protected. Do a read first
;to select the desired unit. (trashes buffer 3)

	push	b		;c=both sides
	push	d		;d=fill info
	call	DUMYRD		;test write protected
	pop	d
	pop	b

	jnz	FPERR		;y: done

;actually format the disk dow

	call	FORMAT		;format with message

;Format the other side, if FD command

	mov	a,c
	ora	a

	cnz	TOGSID
	cnz	FORMAT		;format with message

;-------------------------------------------
; See if the user specified any format write
; data, and quit if not.
;-------------------------------------------
	dcr	d
	jnz	DORSTR		;..then done

;-----------------------------------------------------
; Write e to every byte of every sector on the disk
; surface. This uses 2:1 interleave, allowing 1 mS
; processing time between sectors. Including seek and
; settling time, this will take 2 or 3 revolutions (50
; 0r 75 mS) per track, or about 40 seconds to write
; every sector on the disk surface.
;-----------------------------------------------------
FSIDLP:	push	b		;save c=do both sides

	call	DOFILL		;fill SECBUF with e

	mvi	b,0		;use ctrlr buffer 0 for writing
	call	WRBUF		;write SECBUF to ctlr buf

; fill and verify both sides if c (on stack) is nz

	push	d		;save d=0 & e=data for verify

	call	CILPRT
	db	'Writing side',' '+80h

	lda	CRSIDE		;get current side
	adi	'0'
	call	PRINTA		;print head #

	call	ILPRNT
	db	' sector data. Q to qui','t'+80h

;loop to write controller buffer 0 to every disk sector
;on every track.
	lxi	h,0		;initial cylinder

FCLOOP:	shld	CRCYL		;save
	call	SEEK		;seek next cylinder

	lxi	b,MAXSEC	;sector counter, buffer #	

; Write controller buffer 0 to every sector on the current
; track. This takes 362 cycles = 181 uS per sector. 2:1
; interleave allows 1 sector time, or 1 mS to prepare
; to write each sector.

FSLOOP:	lxi	h,INLV2		;(10)2:1interleave sector
	dad	b		;(10)
	push	b		;(11)sec number & buffer number
	mov	c,m		;(7)interleaved sector #

	lxi	d,0FF00h+CWRSEC	;(10)d=error mask, e=command
	call	DORDWR		;(17+262)Write sector
	pop	b		;(10)

	jnz	FMTABT		;(10)error abort

	dcr	c		;(5)next sector
	jp	FSLOOP		;(10)

	call	VLPEND		;next cylinder
	jnz	FCLOOP

	pop	d		;recover d=0,e=written data
	push	d		;save in case we do 2 sides	

;------------------------------------------------------
; Loop to verify every sector on the disk surface. This
; uses 7:1 interleave, allowing 5.77 mS processing time
; between sectors. Including the 10 mS seek, this will
; take 8 revolutions (200 mS) per track, or about 92
; seconds to verify the entire disk surface.
;------------------------------------------------------
	call	CILPRT
	db	'Verifying sector data. Q to qui','t'+80h

	lxi	h,0		;cylinder counter

VCLOOP:	shld	CRCYL		;save cylinder #
	call	SEEK		;seek next cylinder
	lxi	b,MAXSEC	;sector counter, buffer #

; Verify every sector on the current track.  This loop
; requires 10557 cycles (5279.5 uS) to test each sector.
; 7:1 interleave allows 6 sector times (5769 uS) to
; complete each sector verification.

VSLOOP:	lxi	h,INLV7		;(10)7:1 interleave sector
	dad	b		;(10)
	push	b		;(11)sec number & buffer number
	mov	c,m		;(7)interleaved sector #

	call	GETHED		;(17+61)get side & platter into a
	ora	c		;(4)combine with sector
	mov	l,a		;(5)for low command byte

	mvi	h,CRDSEC+1	;(7)read command, buffer 1
				;hl has complete command

	lxi	b,7F00h+2	;(10)error mask, 1 sec timeout
	call	HDCMD		;(17+180)read sector now
	cnz	VRSERR		;(11) errors?

	mvi	b,1		;(4)for buffer 1
	call	RBSTUP		;(158)set up to read the buffer
				;(sets c=0)
	lxi	h,SECBUF	;(10)destination
	mvi	d,0		;(7)init error accumulator
	mov	c,d		;(4)256 bytes

; Loop to compare 256 sector bytes from the controller to e.
; This takes 9984 cycles to check 256 data bytes.

VSDLP:	in	CDATA		;(10+1)get a data byte
	xra	e		;(4)compare to correct data
	ora	d		;(4)combine with previous errors
	mov	d,a		;(5)accumulate errors in d
	dcr	c		;(5)next byte
	jnz	VSDLP		;(10) (39 cycles per pass)

	ora	a		;(4)any data mismatch errors?
	cnz	VDMERR		;(11)report bad sector

	pop	b		;(10)sec number & buffer number
	dcr	c		;(5)next sector
	jp	VSLOOP		;(10)

	call	VLPEND		;next cylinder
	jnz	VCLOOP

	pop	d		;fill data
	pop	b		;both sides?

;do the other side, if this was an FD command

	dcr	c
	cz	TOGSID
	jz	FSIDLP

;------------------------------------
;Data format done. Report error count
;------------------------------------
	call	DORSTR		;put the head back

	lhld	BYTCNT		;report total errors
	jmp	PERCNT

;===Local Subroutine=============
;Format loop end
; On Exit:
;   pacifier printed
;   user had opportunity to abort
;   hl=CRCYL+1
;   z set if CRCYL=MAXCYL
;================================
VLPEND:	call	ILPRNT		;pacifier
	db	'.'+80h

	call	TESTQ		;opportunity to abort

	lhld	CRCYL
	mov	a,l		;compare cyl number to max
	sui	MAXCYL and 0FFh
	mov	a,h
	sbi	MAXCYL/256

	inx	h
	ret

;===Local Subroutine==================
;Report sector overwrite data, if any
;On Entry:
;   d=0 of no overwrite data specified
;   e=overwrite data, if any
;=====================================
POVWRT:	mov	a,d	;any overwrite specified?
	ora	a
	rz		;n: done

	CALL	CILPRT
	db	'Overwrite sector data with',' '+80h
	mov	a,e
	jmp	PRHEX

;******=================================================
;* SB *  Command: Read Bad Sector into Controller Buffer
;******  (Reads a sector without checking header)
;
; Parameters:
;  [/B]  specifies the controller buffer (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=======================================================
FRUNF:	lxi	b,7F00h+CRUSEC	;b=error mask, c=command
	jmp	DOREAD		;recycle some code

;******=================================================
;* SR *  Command: Read Sector into Controller Buffer
;******
; Parameters:
;  [/B]  specifies the controller buffer (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=======================================================
FREAD:	lxi	b,7F00h+CRDSEC	;b=error mask, c=command

DOREAD:	call	SARTO		;set & announce offset

	call	FRDWR		;read sector
	jnz	ERDCOD		;explain errors
	ret			;no errors:done

;******=================================================
;* SW *  Command: Write Sector from Controller Buffer
;******
; Parameters:
;  [/B]  specifies the controller buffer (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=======================================================
FWRITE:	lxi	b,0FF00h+CWRSEC	;b=error mask, c=command
	call	CLRRTO		;no offset during write
	call	FRDWR		;Write sector

	push	psw
	call	SETRTO		;Put track offset back
	pop	psw

	jnz	ERDCOD		;explain errors
	ret			;no errors:done

;******=================================================
;* TW *  Command: Write Track from Controller Buffer
;******
; Parameters:
;  [/B]  specifies the controller buffer (defaults to 0)
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=======================================================
FTRKWR:	lxi	d,MAXBUF	;max ctlr buffer number
	call	GETDEC		;get c=ctlr buffer number
	mov	e,c		;defaults to 0
	mvi	c,0FFh		;all sectors for ASKWR

	call	ASKWR		;Ask if user is ready

	call	CLRRTO		;no offset during write

;loop to write all sectors from buffer e, in reverse order

	mvi	d,MAXSEC	;d is the sector number

TWLOOP:	call	GETHED		;get side & platter into l
	mov	a,d		;sector
	ora	l		;combine with head & platter
	mov	l,a		;for low command byte

	mov	a,e		;buffer number
	ori	CWRSEC		;combine with write command
	mov	h,a		;hl has complete command

	lxi	b,0FFh*256+2	;mask & timeout
	call	HDCMD

	dcr	d
	jp	TWLOOP

	jmp	SETRTO		;Put track offset back & ret

;******===================================================
;* XD *  Command: XModem Disk Dump
;******
; Parameters:
;  [<P>] specifies a platter (defaults to current platter)
;  [/P]  parks the disk heads when done
; On Entry:
;   Z set if end of cmd line already
;   hl=address of 1st chr after cmd
;=========================================================
FXDUMP:	jz	FXD2		;use default platter?

	mov	a,m		;/ option?
	cpi	'/'
	jz	FXD1

	lxi	d,1
	call	GETDEC		;Get c=new platter number
	mov	a,c
	sta	CRPLAT		;remember as current platter

;get input parameter /P, if it exists, and set PARKIT if so

FXD1:	call	GSLASH		;get option
	jz	FXD2
	cpi	'P'		;Park-it option?
	jnz	CMDERR
	sta	PARKIT
FXD2:

;report planned dump and confirm with user

	call	CILPRT
	db	'Dump Unit',' '+80h

	lda	CRUNIT
	inr	a		;units are 1-4
	call	PDEC8

	call	ILPRNT
	db	QUOTE,'s'+80h

	lda	CRPLAT
	call	PNAME		;fixed or removable

	call	PVIAPT		;via port
	call	POPARK		;tell about park option with CRLF
	call	SARTO		;set & announce offset

	call	CASKYN		;ask for confirmation

	call	CILPRT
	db	'Sendin','g'+80h

	xra	a
	sta	RESULT		;no error so far
	sta	CRSIDE		;side 0 1st
	inr	a
	sta	CURBLK		;Xmodem initial block

	call	SETRTO		;Set up track offset for reads

;-------------------------------------
; Dump entire Disk contents via XModem
;-------------------------------------

;Wait for either NAK or 'C' to determine error checking mode

	call	GTMODE		;wait for NAK or SELCRC to
				;..determine cksum or CRC mode

	lxi	h,0		;intiial cylinder

;send each cylinder, and print a pacifier

XDCYLP:	shld	CRCYL		;seek cyl
	call	SEEK

	call	TESTQ		;user abort?

	call	ILPRNT		;pacifier
	db	'.'+80h

;send each side
	xra	a		;initial side

XdsDLP:	sta	CRSIDE
	lxi	b,0		;initial sector and buffer

;Read a sector into controller buffer 0

XdsCLP:	lxi	d,7F00h+CRDSEC	;read sector, check errors
	push	b		;c=sector number
	call	DORDWR
	cnz	ERDCOD		;report disk error

;read controller buffer 0 into SECBUF

	mvi	b,0		;controller buffer 0
	call	RDBUF

;send sector as 2 XModem blocks. 

	lxi	h,SECBUF	;Point to 1st block

TXBLK2:

;SOH first

TXRPT:	mvi	a,SOH		;SOH first
	call	PUTX

	lda	CURBLK		;8-bit block number
	call	PUTX		;(preserves A)

	cma			;complimented block no
	call	PUTX

;Send a BLKSIZ-byte block from SECBUF

	push	h
	lxi	h,0		;clr CRC for new block
	shld	CRC16
	pop	h

	lxi	b,BLKSIZ*256+0	;b=bytes/block,
				;...clear cksum in C

TXBLUP:	mov	a,m		;Get a data byte
	call	PUTX		;Send it, do cksum

	call	CALCRC		;combine a into the CRC

	inx	h		;Next byte
	dcr	b
	jnz	TXBLUP		;loop through block

; Data checksum is in C
; 16-bit data CRC is in CRC16
; CRCFLG <>0 if CRC mode enabled

;Send checksum or 16-bit CRC, based on CRCFLG

	lda	CRCFLG		;Checksum or CRC?
	ora	a		;clear Z if CRCFLG
	jz	TXBDON		;jump to send checksum

	push	h
	lhld	CRC16		;get calculated CRC
	mov	a,h
	call	PUTX		;send byte in a
	mov	c,l		;now the 2nd CRC byte
	pop	h

TXBDON:	mov	a,c		;a=cksum or CRC 2nd byte 
	call	PUTX		;send byte in a

;Wait for the ACK. If none arrives by the
;end of the timeout, or if a NAK is received
;instead of an ACK, then resend block.

	call	GETACK		;Wait for the ACK
				;Z flag set if ACK
	jnz	TXRPT		;NZ: timeout or NAK

;send second block for this sector, if we haven't already

	lda	CURBLK		;each sector is 2 blocks
	inr	a
	sta	CURBLK
	rrc			;second block?
	jnc	TXBLK2

;next sector

	pop	b
	mov	a,c
	inr	c
	cpi	MAXSEC
	jnz	XdsCLP

;next side

	lda	CRSIDE
	xri	1
	jnz	XdsDLP

;next cylinder

	lhld	CRCYL
	inx	h
	mov	a,l			;just did last cyl?

	sui	(MAXCYL+1) and 0FFh
	mov	a,h
	sbi	(MAXCYL+1)/256
	jnz	XDCYLP			;n: do another

;File send completed. Send EOT'S intil we get an ACK

TXEOF:	mvi	a,EOT			;Send an EOT
	call	PUTX

	call	GETACK			;Wait for an ACK
	jnz	TXEOF			;Loop until an ACK

;report success

	call	CILPRT
	db	'Dum','p'+80h

;fall into XREPRT

;===Subroutine==================
; Report Xmodem transfer results
; then park the drive as needed
;===============================
XREPRT:	call	ILPRNT
	db	' completed with',' '+80h
	lhld	BYTCNT
	call	PDEC16
	call	ILPRNT
	db	' XModem retries','.'+80h

;fall into DOPARK

;===Subroutine===============
;Restore or Park the drive if
;specified, and tell about it
;============================
DOPARK:	lda	PARKIT			;shutdown requested?
	ora	a
	jz	DORSTR			;n:restore

	call	CILPRT
	db	'Stopping driv','e'+80h
	jmp	DOSTST			;y:shut it down

;******===================================================
;* XL *  Command: XModem Disk Load
;******
; Parameters:
;  [<P>] specifies a platter (defaults to current platter)
;  [/C]  specifies checksum (rather than CRC) checking
;  [/P]  parks the disk heads when done
; On Entry:
;   Z set if end of cmd line already
;   hl points to first chr of a parameter
;========================================================
FXLOAD:	mvi	d,1		;default CRC mode

	jz	XDO2		;any parameters?

	mov	a,m		;slash?
	cpi	'/'
	jz	XDOLP		;y: get mode option

	call	GETDC1		;Get new platter number
	sta	CRPLAT		;remember as current platter


;check for & get /options

XDOLP:	call	GSLASH		;get a slash option
	jz	XDO2		;got all?

	cpi	'P'		;park-it option?
	jnz	XDO1
	sta	PARKIT
	jmp	XDOLP

XDO1:	sui	'C'		;checksum option?
	jnz	CMDERR
	mov	d,a		;d=0 for no-CRC flag
	jmp	XDOLP
XDO2:

;report load configuration, and confirm

	call	CILPRT
	db	'Load imag','e'+80h

	call	PVIAPT		;via port

	mov	a,d
	sta	CRCFLG		;remember CRC mode or not
	ora	a
	cnz	PCRC

	call	CILPRT
	db	'and write it to Unit',' '+80h
	lda	CRUNIT
	inr	a		;units are 1-4
	call	PDEC8

	call	ILPRNT
	db	QUOTE,'s'+80h
	lda	CRPLAT
	call	PNAME		;fixed or removable

	call	POPARK		;tell about park option
	call	CASKYN		;CR LF 1st

	call	CILPRT
	db	'Start sending file now','.'+80h
	call	ASKCON		;ready?

	call	CILPRT
	db	'Receivin','g'+80h
	
	call	CLRRTO		;no offset during write

;Prepare to send initial NAK or SELCRC to get things going

	lxi	h,NAKCHR
	mvi	m,NAK		;checksum-mode initial ACK

	lda	CRCFLG		;CRC or checksum?
	ora	a		;0 for cksum, nz for CRC
	jz	RXCSM
	mvi	m,SELCRC	;CRC-mode initial ACK

	call	PCRC		;print ' with CRC'

RXCSM:	mov	a,m		;the right kind of NAK
	sta	ACKCHR		;..is the initial ACK

;receive each cylinder

	lxi	h,0		;initial cylinder

XRCYLP:	shld	CRCYL
	call	SEEK		;trashes all regs	

	call	TESTQ		;user abort?

	call	ILPRNT		;pacifier
	db	'.'+80h

;receive each side

	xra	a		;initial side

XRSDLP:	sta	CRSIDE

;receive each sector

	lxi	b,0		;buffer 0, sector 0

;receive 2 blocks into the buffer. If we get an EOT on the
;1st block, then we are done. If we get an EOT on the 2nd
;block, then write to disk first, though the 2nd half of
;this last sector will be old data from the buffer.

XRSCLP:	lxi	h,SECBUF	;1st 1/2 sector for odd blocks

	push	b		;buffer & sector
	call	GETBLK		;Receive an XMODEM block
	pop	b

	jc	XLDONE		;carry set means EOT received

	lxi	h,SECBUF+128	;2nd 1/2 sector for even blocks

	push	b
	call	GETBLK		;Receive an XMODEM block
	pop	b		;b=0,c=sector number

	push	psw		;carry meant EOT

;write SECBUF to controller buffer 0, then to disk (b=0)

	call	WRBUF

	lxi	d,0FF00h+CWRSEC	;d=error mask, c=command

	push	b		;save buffer & sector number
	call	DORDWR
	cnz	ERDCOD		;report disk error, if any
	pop	b		;buffer & sector

	pop	psw		;EOT?
	jc	XLDONE		;done if so

; next sector

	mov	a,c
	inr	c
	cpi	MAXSEC		;just did last sector?
	jnz	XRSCLP		;n: do another

;next side

	lda	CRSIDE
	xri	1		;toggle side
	jnz	XRSDLP

;next cylinder

	lhld	CRCYL
	inx	h

	mov	a,l			;just did last cyl?
	sui	(MAXCYL+1) and 0FFh
	mov	a,h
	sbi	(MAXCYL+1)/256
	jnz	XRCYLP			;n: do another

	call	DORSTR			;put the head away

;disk is full. The next block should only be EOT

	call	GETBLK		;Receive an XMODEM block
	jnc	TMDERR		;C set means EOT received

;Received EOT. Ack and end

XLDONE:	call	TXACK		;ACK this sector

;report success

	call	CILPRT
	db	'Loa','d'+80h
	jmp	XREPRT		;report, and perhaps park

;******************
;* Other Commands *
;******************

;******================================
;* EU *  Command: Emergency Unload Unit
;******
;======================================
FUNLOD:	jnz	CMDERR		;no parameters allowed

	call	CILPRT
	db	'Emergency Unloa','d'+80h
	call	ASKYN

	mvi	c,IVI		;unload now
	mvi	a,IVIEUN xor 0FFh
	call	WRITIV

	lda	CRRTO		;end load pulse
	cma			;..and put offset back
	jmp	WRITIV

;******=====================================================
;* MS *  Command: Measure and Report Spindle Revolution Time
;******
; Unformatted-read sector 0, head 0 of the current track
; & unit (into buffer 3) 256 times, measuring the total
; time by counting cycles. Divide the result by 256 to
; get the average time for one revolution, and report it.
; This function should take about 11 seconds.
;===========================================================
FMSPIN:	call	CILPRT
	db	'Spindle test:',' '+80h

	in	CSTAT		;(10+1) Clear CREADY in case
	in	ACMD		;(10+1)reset CMDACK in ACSTA

	lxi	b,0		;b 256 revs, c=msb of timer
	mvi	e,16		;timeout value for REV256 loop

; Read sector 0 for the first time, to synchronize with the disk.
; Timeout here in 25.5 * 65K = 1.7 seconds

	lxi	h,0		;1.7 sec timeout value

	xra	a		;head 0, side 0, sector 0
	out	ADATA		;..is low command byte

	lda	CRUNIT		;get current unit number
	rlc			;put unit in bits 3:2
	rlc			;
	ori	CRUSEC+03h	;Read Unformatted Sector
				;..using buffer 3
	mov	d,a		;save for later
	out	ACMD		;and issue this command

RVSYNC:	dcx	h		;(7)timeout?
	mov	a,h		;(5)
	ora	l		;(4)
	jz	FMSTO		;(10) Y: abort

	in	CREADY		;(10+1)Is the controller done?
	rlc			;(4)look at msb=CRDY
	jnc	RVSYNC		;(10)n: keep waiting

	;51 cycles/pass = 25.5 uS/pass

; Time 256 revolutions of the disk. 
;    hl,c will be the timer, and c is already 0.
;    b counts revolutions, and is already 0, meaning 256
;      revolutions to be measured.
;    d is the read sector command, already set up above.
;    e is the timeout value, where the timeout = e * 2.09 sec.
;      (256 revolutions should take 10.24 seconds.)
;    ADATA is already set for head 0, side 0, sector 0

	lxi	h,0		;(10)initialize timer			

REV256:	in	CSTAT		;(10+1)reset CRDY flag
	in	ACMD		;(10+1)reset CMDACK in ACSTA

; Time one revolution of the disk. This code is a little
; weird, so that all paths through the loop take the same
; amount of time.

	mov	a,d		;(5)command with unit
	out	ACMD		;(10)issue command

REV1:	mov	a,e		;(5)prepare for timeout test
	inr	c		;(5)timer lsb overflow?
	jz	REV1A		;(10)

	mov	a,e		;(5)delay\
	nop			;(4)      > 19 cycles
	jmp	REV1B		;(10)    /

REV1A:	inx	h		;(5)timeout?\
	cmp	h		;(4)         > 19 cycles
	jz	FMSTO		;(10)       /

REV1B:	in	CREADY		;(10+1)Is the controller done?
	rlc			;(4)look at msb=CRDY
	jnc	REV1		;(10)

; 64 cycles per pass = 32 uS per pass

	dcr	b		;(5)done 256 revs?
	jnz	REV256		;(10)n: do another rev

	in	CSTAT		;Reset CRDY flag

; hl,c = number of times through the REV1 loop, which takes
; 32 uS per pass. Multiply this by 32 to get uS.

	mov	a,c
	mvi	b,5		;loop 5 times to multiply by 32

REVUS:	dad	h		;shift hl left 1
	add	a		;shift a left one
	jnc	REVUS1		;put any carry bit into hl
	inx	h
REVUS1:	dcr	b
	jnz	REVUS		;do it 5 times

; hl,a = total time for 256 revolutions, in uS. This should
; be a number near near 25,000 * 256, or near 6,400,000.
; However, this value does not include the time spent outside
; the REV1 loop. REV256 has 52 cycles outside this loop, for
; 26 uS. These instructions executed 256 times. So add 26 to
; hl to adjust. (b=0 here.)

	mvi	c,52/2		;uS spent outside REV1 loop
	dad	b

; hl,a = total time in uS for 256 revolutions. Round a so that
; hl = average time in uS for 1 revolution.

	adi	127		;lsb > 128 will round up
	jnc	REVRND		;otherwise round down
	inx	h
REVRND:

;Report result in hl

	call	PDEC16
	call	ILPRNT
 db ' uSec per rev +/- 5 uSec',CR,LF
 db '(Spec is 25000 uSec per rev  +/- 1%',')'+80h

	ret

; Timeout waiting for sector 0

FMSTO:	call	ILPRNT
	db	'timeout erro','r'+80h
	ret

;******======================
;* QU *  Command: Quit ADEXER
;******
;============================
FQUIT:	jnz	CMDERR		;no parameters allowed

 if not CPMCOD
	call	STALL		;delay to clear ACIA

;If TURMON is running in a Turnkey Module, then it may have
;been disabled by the Turnkey's "phantom" circuit. If so,
;there is no way to restore it without resetting the Altair.
;In this case, we cannot quit to TURMON.

	lxi	h,WBOOT		;test for ROM
	mov	b,m		;get initial value
	inr	m		;test for FF
	jz	NOWB		;FF means no ROM
	mov	a,b		;were we able to change it?
	cmp	m
	jz	WBOOT		;n: it's a ROM

;The ROM does not appear to be there anymore.

NOWB:	mov	m,b		;put memory back
	call	CILPRT
 db 'TURMON ROM appears to be disabled. Try Reset','.'+80h
	ret
 endif

 if CPMCOD
	jmp	WBOOT		;Warm boot to whatever OS
 endif

;******============================
;* SS *  Command: Start/Stop Drives
;******
;==================================
FSTST:	jnz	CMDERR		;no parameters allowed

; fall into DOSTST

;===SUBROUTINE=======
;Start/stop the drive
;====================
DOSTST:	call	GTUNIT		;get Unit bits for IVH
	push	psw

	ani	IVHSS XOR 0FFh	;start/stop bit
	mvi	c,IVH		;[IV 17]Note: IVH inverts
	call	WRITIV		;start/stop active

	pop	psw		;start/stop inactive
	jmp	WRITIV		;return from there

;*****===============================
;* ? *  Command: Display General Help
;*****
; Note: ASM restricts strings
; to 64 characters, max
;====================================
FHELP:	call	CILPRT
 DB '-==ADEXER Altair Datakeeper Exerciser==-',CR,LF
 DB CR,LF
 DB '?B for help with buffer commands:',CR,LF
 DB '  BD, BE, BF, CC, CR, CW, HD, and HL.',CR,LF
 DB CR,LF
 DB '?C for help with controller commands:',CR,LF
 DB '  IC, IR, IW, TB, TF, and TI.',CR,LF
 DB CR,LF
 DB '?D for help with disk read/write commands:',CR,LF
 DB '  DC, DV, FD, FS, SB, SR, SW, TW, XD, and XL.',CR,LF
 DB CR,LF
 DB '?P for help with positioner commands:',CR,LF
 DB '  RE, RR, SK, and ST.',CR,LF
 DB CR,LF
 DB '?S for help with Adexer state commands',CR,LF
 DB CR,LF
 DB 'EU executes an emergency head unload',CR,LF
 DB 'MS measures spindle rev time',CR,LF
 DB 'SS starts/stops the drive(s)',CR,LF
 DB 'QU quits to '
 if CPMCOD
 DB 'CP/M'
 endif
 if not CPMCOD
 DB 'TURMON'
 endif
 DB '.',CR,LF+80h

	ret

;*****=======================================
;* ?B *  Command: Display Buffer Command Help
;*****
; Note: ASM restricts strings
; to 64 characters, max
;============================================
FBHELP:	call	CILPRT
 DB '-==Buffer Commands==-',CR,LF
 DB CR,LF
 DB 'BD       Display RAM buffer',CR,LF
 DB 'BE [<A>] Edit RAM buffer starting at address <A>',CR,LF
 DB 'BF [<H>] Fill RAM buffer with hex value <H>',CR,LF
 DB 'CC [<B>] Compare controller buffer <B> to RAM buffer',CR,LF
 DB 'CR [<B>] Copy controller <B> buffer to RAM buffer',CR,LF
 DB 'CW [<B>] Copy RAM buffer to controller buffer <B>',CR,LF
 DB 'HD [<P>] Intel hex dump RAM buffer with'
 DB ' optional page address <P>',CR,LF
 DB 'HL [<P>] Load Intel hex file into RAM buffer. Only load records'
 DB CR,LF
 DB '         with high address bytes that match the page address <P>'
 DB CR,LF+80h

	call	CDMSG		;echo delay message?

	call	CILPRT
 DB CR,LF
 DB 'Buffer addresses <A>, data <H>, and'
 DB ' page addresses <P> are 2-digit hex.',CR,LF
 DB 'Controller buffers <B> are 0-3, and default to 0.',CR,LF+80h

	ret

;*****===========================================
;* ?C *  Command: Display Controller Command Help
;*****
; Note: ASM restricts strings
; to 64 characters, max
;================================================
FCHELP:	call	CILPRT
 DB '-==Controller Commands==-',CR,LF
 DB CR,LF
 DB 'IC         Initialize controller. (The controller may',CR,LF
 DB '           require a front panel reset.)',CR,LF
 DB 'IR <I>     Read IV byte <I>',CR,LF
 DB 'IW <I> <H> Write <H> hex data to IV Byte <I>',CR,LF
 DB 'TB         Test controller buffer memory',CR,LF
 DB 'TF         Test controller data FIFO',CR,LF
 DB 'TI         Test IV bus, read channel,and CRC generator. This',CR,LF
 DB '           requires the loopback cable for most of the tests.',CR,LF
 DB CR,LF
 DB 'Controller buffers <B> are 0-3.',CR,LF
 DB 'IV Addresses <I> are decimal, 0-37.',CR,LF+80h

	ret

;*****================================================
;* ?D *  Command: Display Disk Read/Write Command Help
;*****
; Note: ASM restricts strings
; to 64 characters, max
;=====================================================
FDHELP:	call	CILPRT
 DB '-==Disk Read/Write Commands==-',CR,LF
 DB CR,LF
 DB 'DC <SP> <DP> [/F] [/V] [/P] Copy <SP> platter to <DP> platter',CR,LF
 DB 'DV <SP> <DP> Verify disk (compares 2 platters)',CR,LF
 DB 'FD [<H>]     Format current disk, optionally fill with <H>',CR,LF
 DB 'FS [<H>]     Format current disk side, optionally fill'
 DB ' with <H>',CR,LF
 DB 'SB <S> [<B>] Read bad sector <S> to controller buffer <B>',CR,LF
 DB 'SR <S> [<B>] Read sector <S> into controller buffer <B>',CR,LF
 DB 'SW <S> [<B>] Write sector <S> from controller buffer <B>',CR,LF
 DB 'TW [<B>]     Write all sectors on current track from'
 DB ' controller buffer <B>',CR,LF
 DB CR,LF
 DB 'XD [<SP>] [/P]      Dump entire disk contents via XModem',CR,LF
 DB 'XL [<DP>] [/C] [/P] Load disk image via Xmodem',CR,LF
 DB CR,LF
 DB '<SP> and <DP> are {0/1} for {removable/fixed} disk.',CR,LF
 DB '<H> fill value is in hex.',CR,LF
 DB 'Controller buffers <B> are 0-3.',CR,LF
 DB 'Sectors <S> are in decimal, 0-23.',CR,LF
 DB '/C selects checksum mode, CRC error checking otherwise.',CR,LF
 DB '/F option formats first, /V verifies when done.',CR,LF
 DB '/P option parks the heads and stops the drive when done.',CR,LF+80h

	ret

;*****===========================================
;* ?P *  Command: Display Positioner Command Help
;*****
; Note: ASM restricts strings
; to 64 characters, max
;================================================
FPHELP:	call	CILPRT
 DB '-==Positioner Commands==-',CR,LF
 DB CR,LF
 DB 'RE       Restore to cylinder 0',CR,LF
 DB 'RR       Repetitive restore',CR,LF
 DB 'SK <C1>  Seek cylinder <C1>',CR,LF
 DB 'SK <C1> <C2> [...<Cn>] Seek through List of cylinders',CR,LF
 DB '         Space for next, A for Automantic, M for'
 DB ' Manual, Q to Quit',CR,LF
 DB 'ST {I/O} Manually step in/out 1 track at a time',CR,LF
 DB '         Space for next, A for automatic, Q to quit',CR,LF
 DB 'ST A     Continuously step in/out 1 track at a time',CR,LF
 DB '         M for manual, Q to quit',CR,LF
 DB CR,LF
 DB 'Cylinders <Cn> are decimal, 0-405.',CR,LF+80h

	ret

;*****=============================================
;* ?S *  Command: Display Adexer State Command Help 
;*****
; Note: ASM restricts strings
; to 64 characters, max
;==================================================
FSHELP:	call	CILPRT
 DB '-==ADEXER State Commands==-',CR,LF
 DB CR,LF
 DB 'SE             Show current state',CR,LF
 DB 'SE A={0/1}     Auto-seek off/on',CR,LF
 DB 'SE E={0/1}     Echo off/on for HL command',CR,LF
 DB 'SE O={0/-/+/G} Set source read track offset/gain',CR,LF
 db '               (None/Negative/Positive/Reduced read amp gain)'
 db CR,LF
 DB 'SE P={0-3}     Set current platter (0 is removable cartridge)'
 db CR,LF
 DB 'SE S={0/1}     Set current side (0 is top)',CR,LF
 DB 'SE T={0/1}     SK command track check off/on',CR,LF
 DB 'SE U={1-4}     Set current unit',CR,LF
 DB 'SE V={0/1}     Verbose seek off/on',CR,LF
 DB 'SE X={0/1}     XModem port: 0 for 88-2SIO port B, 1 for 88-SIO'
 	DB	CR,LF+80h

	ret

;*******************************
;* Disk Controller Subroutines *
;*******************************

;===Subroutine======================
; Read controller buffer into SECBUF
; On Entry:
;   b = controller buffer number
; On Exit:
;   Z set
;   a,trashed, all others preserved
;===================================
RDBUF:	push	h
	push	b

	call	RBSTUP
	lxi	h,SECBUF	;destination

; Loop to read 256 bytes from controller

	mvi	c,0		;256 bytes

FRCDAT:	in	CDATA		;get a data byte
	mov	m,a		;stash in buffer
	inx	h
	dcr	c
	jnz	FRCDAT

	pop	b
	pop	h
	ret

;===Subroutine======================
;Compare controller buffer to SECBUF
; On entry:
;   b=controller buffer number
; On Exit:
;   de=error count
; a,c, hl trashed
;===================================
BUFCMP:	call	RBSTUP		;set up to read buffer b

; Loop to compare 256 bytes from controller

	lxi	h,SECBUF	;destination
	lxi	d,0		;error counter
	mov	c,d		;256 bytes

;(14592 cycles for loop)

FCCDAT:	in	CDATA		;get a data byte
	cmp	m		;compare to buffer
	jz	CMATCH		;
	inx	d		;bump error count
CMATCH:
	inx	h		;next location
	dcr	c
	jnz	FCCDAT

	ret

;===Subroutine==========================
; Set up to read a controller buffer
; On Entry:
;   b = controller buffer number
; On Exit:
;   controller is ready to transfer data
; a,c trashed
; (158 cycles, assuming quick response)
;=======================================
RBSTUP:	in	CDATA		;(10+1)reset CDA in CDSTA
	in	ACMD		;(10+1)reset CMDACK in ACSTA

	xra	a		;(4)transfer 256 bytes
	mov	c,a		;(5)c=0
	out	ADATA		;(10)low byte of command 1st

	mov	a,b		;(5)buffer number
	ori	CRDBUF		;(7)create read buffer command
	out	ACMD		;(10)and issue command

; b=0 here for a reasonable timeouts

RBCAWT:	dcr	c		;(5)timeout?
	jz	CAKERR		;(10)Yes: abort
	in	ACSTA		;(10+1)check command ack
	rlc			;(4)
	jnc	RBCAWT		;(10)

; continuing with same timeout

RBSUWT:	dcr	c		;(5)timeout?
	jz	CDPERR		;(10)Yes: abort

	in	CDSTA		;(10+1)Wait for data port to be ready
	rlc			;(4)msb=CDA
	jnc	RBSUWT		;(10)

	ret			;(10)

;===Subroutine======================================
; Get head bits in position for a controller command
; On Entry:
;   CRPLAT = platter number
;   CRSIDE = current side
; On Exit:
;   l<7:6> = platter
;    l<5>  = side
;   l<4:0> =0
;   a=l
;(61 cycles)
;===================================================
GETHED:	xra	a		;(4)clear carry bit
	lda	CRSIDE		;(13)
	rar			;(4)into carry bit
	lda	CRPLAT		;(13)
	rar			;(4)
	rar			;(4)
	rar			;(4)into 7:6

	mov	l,a		;(5)result in l for return
	ret			;(10)

;===Subroutine===========================================
; Discover current cylinder on current unit
; On Exit:
;  hl = cylinder number
;  Z cleared if cyl not available because drive not ready
;  trashes c
;========================================================
GETCYL:	mvi	c,IVI		;get cyl msb
	call	READIV
	rnz			;ret if drive not ready

	cma			;cyl address bits are inverted
	ani	1		;just one bit
	mov	h,a

	mvi	c,IVJ		;get cyl lsB
	call	READIV
	rnz			;ret if drive not ready
	
	cma			;cyl address bits are inverted
	mov	l,a

	cmp	a		;clear z for happy return
	ret

;===Subroutine=====================
; Get current Unit bits for IVH
; On Entry:
;  CRUNIT = current unit {0:3}
; On Exit:
;  a<3:0> = correct unit bit is low
;  a<7:4> = 1111b
;  trashes c
;==================================
GTUNIT:	lda	CRUNIT
	mov	c,a		;CRUNIT is 0:3
	inr	c		;goes an extra time
	mvi	a,7Fh
GTULP:	rlc
	dcr	c
	jnz	GTULP

	ret

;===Subroutine==============================
; Clear Read Track Offset in the controller
; without changing CRRTO value.
; Trashes psw. All other registers preserved
;===========================================
CLRRTO:	push	b
	mvi	b,0
	jmp	RTOB

;===Subroutine===============================
; Set up Read Track Offset in the controller,
; and announce it only if there is an offset.
; Trashes psw. All other registers preserved.
;============================================
SANRTO:	lda	CRRTO		;any offset?
	ora	a		;a single bit set means offset
	cpo	PRNRTO		;y: announce it

; fall into SETRTO

;===Subroutine==============================
; Set Read Track Offset in the controller
; Trashes psw. All other registers preserved
;===========================================
SETRTO:	push	b
	lda	CRRTO
	mov	b,a

; Fall into RTOB

;===Subroutine end==============================
; Write b to Read Track Offset in the controller
; On Entry:
;   bc is on stack
;    b = value for offset
; Trashes psw. All other registers preserved
;===============================================
RTOB:	mvi	c,IVI		;[IV 18]
	call	READIV		;get IV with current offset
	jnz	OFDONE		;done if drive not ready

	mov	c,a		;temp save IV value

	ori	IVIOMN+IVIOPN	;delete old offset
	xra	b		;install new offset

	cmp	c		;offset changed?
	jz	OFDONE		;n: done

	mvi	c,IVI		;[IV 18]set new offset
	call	WRITIV		;write the new offset

; stall for 5 mS to let the head settle

	lxi	b,416	;(10) 416 X 24 X 0.5uS = 5 mS

HSTALL:	dcx	b	;(5)
	mov	a,b	;(5)
	ora	c	;(4)
	jnz	HSTALL	;(10) 24 cycles through loop

OFDONE:	pop	b
	ret

;===Subroutine============
;Write IV byte
; On entry:
;   a = IV byte data
;   c = IV byte address
; On Exit:
; a trashed, Z set
; all other regs preserved
;=========================
WRITIV:	push	d
	mov	e,a		;save IV data
	in	ACMD		;reset CMDACK in ACSTA

	mov	a,c		;recover address
	out	ADATA		;low byte of command

	in	ADATA		;clear ADPA

	mvi	a,CSETIV	;Set IV Byte command
	out	ACMD		;initiate command now

	mvi	d,0		;256x19 uS=4.8 mS timeout

WRIVLP:	dcr	d		;(4)timeout?
	jz	ADPERR		;(10)
	in	ADSTA		;(10)wait for ADPA
	rlc			;(4)test ADPA
	jnc	WRIVLP		;(10)

	mov	a,e		;send data
	out	ADATA	

	mvi	d,0		;256x19 uS=4.8 mS timeout
WRIVWT:	dcr	d		;timeout?
	jz	HDCTO
	in	CREADY		;Is the controller done? 
	rlc			;look at msb=CRDY
	jnc	WRIVWT		;N: keep waiting

	in	CSTAT		;reset CRDY flag
	ora	a		;and get A=error code
	jnz	UNXERR		;No errors: happy return
		;Note: there should never be an error code

	pop	d
	ret			; with Z set

;===Subroutine================================================
;Read IV byte
; Print error message if CDATA is not ready
; Note that no IV Bytes can be read without the Disk Interface
; Card present and the Ready line on that card active, because
; the Datakeeper Read Status command insists that the selected
; Unit be ready before it will return status. This means that
; a jig will be needed for debugging the controller when no
; disk drive is connected.
;
; On entry:
;   c = IV byte address
; On Exit:
;   Z cleared means no IV data because the drive was not ready
;   a = IV Byte Status
;   all other regs preserved
; (318 cycles, assuming fast controller response)
;=============================================================
READIV:	push	b		;(11)
	push	h		;(11)
	mov	l,c		;(4)IV byte address is low byte
	mvi	h,CRSTAT	;(7)create status command

	in	CDATA		;(10)clear CDA

	lxi	b,ERDNR*256+2	;(10)b=drive not ready error mask
				;c=1 second timeout 
	call	HDCMD		;(180+17)puts unit into command
	pop	h		;(10)
	pop	b		;(10)

	rnz			;(5)can't read IV if not ready

; HDCMD returns when CRDY - so CDA should already be set
	in	CDSTA		;(10)
	rlc			;(4)test CDA
	cnc	CDPERR		;(5)not there? error
		
; Read & report IV Byte
	in	CDATA		;(10)

	cmp	a		;(4)set Z for normal return
	ret			;(10)	

;===Subroutine=============
; set up drive for restore,
; cancelling any offset
; On Exit:
;   Z set
; trashes a,c
;==========================
; Select unit

RSETUP:	call	GTUNIT		;Get unit bits into a
	mvi	c,IVH		;[IV 17]
	call	WRITIV		;write a to IV c

; Write cylinder bits 0-7 low. Note that cyl address bits
; are inverted

	mvi	c,IVJ		;[IV 19]
	mvi	a,0FFh		;set cyl bits 0-7 low
	call	WRITIV		;write a to IV c

; Set the strobe line high the restore line low

	mvi	c,IVI		;[IV 18]
	mvi	a,IVICSN+IVIMSK
	jmp	WRITIV		;write a to IV c, set Z

;===Subroutine===============
; Actually restore the drive,
; cancelling any offsets
; On Exit:
; trashes a,c,hl
;============================
; With the Cylinder Restore bit low, pulse the cylinder strobe
; bit low then high, to initiate restore. Note that the address
; msb is low here, but is an inverted bit. Thus, a failed
; restore will instead seek cylinder 256.

RESTOR:	mvi	c,IVI		;[IV 18]
	mvi	a,IVIMSK	;strobe low, with restore low
				;..and no offsets 
	call	WRITIV		;write a to IV c

	mvi	a,IVICSN+IVIMSK	;strobe high to end pulse
	call	WRITIV		;write a to IV c

; wait for restore to complete, with timeout

	lxi	h,57972		;10 second timeout
	mvi	c,IVL		;[IV 21]

RZLOOP:	dcx	h		;(7)timeout?
	mov	a,h		;(5)
	ora	l		;(4)
	jz	DISKTO		;(10)abort: disk timout

	call	READIV		;(285~+17) get status
	ani	0Fh		;(7)wait 'till ALL drives
	jnz	RZLOOP		;(10).. are done seeking
				;(~345 cycles per pass)
	ret

;===Subroutine===========================
; Seek Cylinder
; On Entry:
;   hl = cylinder
;   CRUNIT=selected unit
;   SKCKMO <>0 if seek should be verified
; On Exit:
;   hl = seek time in units of 5 uS, with
;        a resolution of 20 uS
;   z set for success
;   a=error codes for fail
; trashes bc
;========================================
SEEK:	push	d
	in	CSTAT		;clear CREADY just in case

	in	ACMD		;reset CMDACK in ACSTA

	mov	a,l		;low byte of command 1st
	out	ADATA

	lda	CRUNIT		;get current unit number
	rlc			;put into bits 3:2
	rlc
	ora	h		;combine with cyl high bit

; Time how long until for disk is ready. Result (in hl)
; is the time in units of 5 uS. Timeout after 327 mS.
; The SKWAIT loop is exactly 40 cycles = 20 uS long.
; a=seek command for correct unit & cylinder high bit here.

	lxi	d,HDCTO		;'return' if timeout
	push	d		;..(faster than jc if no carry)


	lxi	d,4		;set up timer
	lxi	h,0		;each tick of hl = 5 uS

	out	ACMD		;(10)issue seek command

SKWAIT:	dad	d		;(10)bump timer
	rc			;(5)timeout?
	in	CREADY		;(10+1)is the controller done? 
	rlc			;(4)look at msb=CRDY
	jnc	SKWAIT		;(10)N: keep waiting

	pop	d		;get rid of fake return on stack

;check for errors

	mvi	d,ERDNR		;drive not ready bit
	lda	SKCKMO		;look at more error bits?
	ora	a		;z means no
	jz	SK01
	mvi	d,SERMSK	;seek error bits
SK01:	
	in	CSTAT		;reset CRDY flag, get errors
	ana	d		;any other error bits?

	pop	d
	rz			;no errors: done

	jmp	ERDCOD		;Abort: go decode errors

;===SUBROUTINE=============================
; Read a sector to actually switch the head
; On Exit: a=0 if not write protected
;          a=80h if write protected
; Trashes a
; Trashes controller buffer 3
;==========================================
DUMYRD:	push	h
	push	d
	push	b
	lxi	b,0300h		;sector 0, buffer 3
	lxi	d,8000h+CRUSEC	;read bad sector, ignore all
	call	DORDWR
	pop	b
	pop	d
	pop	h
	ret

;===SUBROUTINE=================================
; Format the selected side of the selected disk
; On Entry:
;  CRSIDE=the disk side to format
;  CRPLAT=the platter to format
; Trashes a,hl
;==============================================
FORMAT:	push	b
	push	d
	call	CLRRTO		;turn off any track offset

	call	CILPRT
	db	'Formatting platter',' '+80h
	lda	CRPLAT
	call	PDEC8
	call	ILPRNT
	db	', side',' '+80h
	lda	CRSIDE
	call	PDEC8

	mvi	h,CFORMT
	call	GETHED		;get l=head from side and platter
	lxi	b,0FF00h+61	;b=error mask,c=60 second timeout 

	call	HDCMD		;format now
	pop	d
	pop	b
	rz

	jmp	ERDCOD		;error: go decode errors

;===SUBROUTINE===============================
; Get parameters, then Read or write a sector
; On Entry:
;   b=error mask
;   c=CRDSEC, CRUSEC, or CWRSEC
;   CRUNIT=selected unit
;   CRPLAT = platter number
;   CRSIDE = current side
;   hl=address of next input buffer parameter
; On Exit:
;  command has been issued and completed
;  a = error flags without write protect
;  c = all error flags
;  z set if a=0 
;============================================
FRDWR:	push	b		;save command & mask

	lxi	d,MAXSEC	;max sector number
	call	GETDEC		;get c=sector number
	jz	CMDERR		;must specify

	push	b		;save c=sector

	lxi	d,MAXBUF	;max ctlr buffer number
	call	GETDEC		;get c=ctlr buffer number

	mov	a,c		;a=sector
	pop	b		;recover c=sector
	mov	b,a		;b=buffer (defaults to 0)

	pop	d		;command & mask

;if this is a write-sector, then ask Y/N before proceeding
;otherwise, tell of sector read that's about to happen

	mov	a,e		;write?
	cpi	CWRSEC		;ask Y/N if so
	cz	ASKWR		;ask about write
	cnz	TELLRD		;tell about read

; fall into DORDWR

;===Subroutine==========================
; Read or Write a sector without fanfare	
; On Entry:
;   b=buffer number
;   c=sector number
;   d=error mask
;   e=CRDSEC, CRUSEC, or CWRSEC
;   CRUNIT=selected unit
;   CRPLAT = platter number
;   CRSIDE = current side
; On Exit:
;  command has been issued and completed
;  a = error flags without write protect
;  c = all error flags
;  z set if a=0 
; trashes all regs
; (262 cycles)
;=======================================
DORDWR:	call	GETHED		;(17+57)side & platter into a
	ora	c		;(4)combine with sector
	mov	l,a		;(5)for low command byte

	mov	a,b		;(5)buffer number
	ora	e		;(4)combine with command
	mov	h,a		;(5)hl has complete command

	mov	b,d		;(5)mask in place

	mvi	c,2		;(7)1 second timeout

; fall into HDCMD

;===Subroutine==========================================
; Issue a disk command to the unit specified by CRUNIT
;
; Note: this just assumes the controller is ready
; On Entry:
;   b = error mask
;   c = timeout in seconds+1, c>1
;   hl = complete command
;   CRUNIT specifies the unit
; On Exit:
;   a = masked error flags
;   Z set if no errors after mask with b
;   The command is completed and the controller is ready
;   Carry cleared
; Trashes a,c,hl
; (180 cycles, assuming fast response fom controller) 
;=======================================================
HDCMD:	in	CSTAT		;(10+1) Clear CREADY in case
	in	ACMD		;(10+1)reset CMDACK in ACSTA

	mov	a,l		;(4)low byte of command 1st
	out	ADATA		;(10)

	lda	CRUNIT		;(13)get current unit number
	rlc			;(4)put unit in bits 3:2
	rlc			;(4)
	ora	h		;(4)combine w/ cmd high byte
	out	ACMD		;(10)and issue command

HDWAIT:	dcr	c		;(4)timeout?
	jz	HDCTO		;(10)y:timeout error

	lxi	h,39216		;(10)c x 1 sec timeout


HDW1:	in	CREADY		;(10+1)Is the controller done?
	rlc			;(4)look at msb=CRDY
	jc	HDWDON		;(10)y: done

	dcx	h		;(7)1-sec timeout?
	mov	a,h		;(5)
	ora	l		;(4)
	jnz	HDW1		;(10)

	jmp	HDWAIT		;(10)keep waiting

HDWDON:	in	CSTAT		;(10+1)reset CRDY flag
	ana	b		;(4)mask errors
	ret			;(10)Z set if okay

;*****************************
;* Console Print Subroutines *
;*****************************

;=====Subroutine========
;Print a as 2 hex digits
;On Entry
;  a = value to print
;Trashes a
;=======================
PRHEX:	push	psw		;save digit
	rrc			;hi nibble 1st
	rrc
	rrc
	rrc
	call	PHXNIB
	pop	psw		;recover low nibble

;fall into PHXNIB

;------------------------------------
;local subroutine to print hex nibble
;On Entry:
;  Nibble in low 4 of a
;Trashes a
;------------------------------------
PHXNIB:	ani	0Fh		;get low
	cpi	0Ah		;A-F?
	jc	PHX1
	adi	'A'-'9'-1
PHX1:	adi	'0'

;fall into PRINTA

;=====Subroutine============
; Print a on the console
; On Exit:
;   a= chr with msb stripped
;===========================
 if not CPMCOD
PRINTA:	push	psw		;save chr
CONWAT:	in	ACSTAT		;Wait for TX to be ready
	ani	ACTDE
	jz	CONWAT

	pop	psw		;recover chr
	ani	7Fh		;strip end-of-string mark
	out	ACTXD		;and send it
	ret
 endif

 if CPMCOD
PRINTA:	push	b
	ani	7Fh		;strip end-of-string mark
	push	psw
	mov	c,a		;in place for BIOS routine
	mvi	a,CONOUT
	call	GOBIOS
	pop	PSW		;recover chr
	pop	b
	ret

;===Subroutine=================================
; Call BIOS routine
; ON Entry:
;   a=low byte of routine's BIOS vector address
;   other registers as BIOS needs them
;==============================================

GOBIOS:	push	h
	push	d
	push	b

	call	GB1

	pop	b		;restore c
	pop	d
	pop	h
	ret

;===Local Subroutine==========
; go to BIOS
; a trashed on the way to BIOS
; all other regs  preserved.
;=============================
GB1:	push	h
	lhld	WBOOTA		;get BIOS base address
	mov	l,a		;a has jump vector
	xthl
	ret			;go to BIOS routine
 endif

;===Subroutine===============================================
; Print CR, LF, then inline string
; On Entry:
;    The string address is the "return address" on the stack.
;    The string is terminated by bit 7 set in its last chr.
;    The actual return address is the next address after the
;    last string character.
; On Exit:
;    all registers preserved.
;============================================================
CILPRT:	call	ILPRNT
	db	CR,LF+80h

; fall into ILPRNT to print inline string

;===Subroutine===============================================
; Print inline string on the Terminal
; On Entry:
;    The string address is the "return address" on the stack.
;    The string is terminated by bit 7 set in its last chr.
;    The actual return address is the next address after the
;    last string character.
; On Exit:
;    all registers preserved.
;============================================================
ILPRNT:	xthl			;get string address, save hl
	push	psw

PRNTLP:	mov	a,m		;get string character
	call	PRINTA

	cmp	m		;end of string?
	inx	h		;next chr
	jz	PRNTLP		;No parity: keep going

	pop	psw
	xthl			;restore hl, put return address
	ret			;..onto stack, and go there

;===Subroutine===================
;Print park-it option of selected
;================================
POPARK:	lda	PARKIT
	ora	a		;park enabled?
	rz			;n: print nothing

	call	CILPRT
	db	'Park drive when don','e'+80h
	ret

;===Subroutine===========
; Print error count in hl
; trashes all regs
;========================
PERCNT:	call	CILPRT
	db	'Errors:',' '+80h
	jmp	PDEC16

;=====Subroutine===================
; Print a as decimal on the console
; Trashes all registers
;==================================
PDEC8:	mov	l,a
	mvi	h,0

;fall into PDEC16
;=====Subroutine================================
;Print hl as decimal on the console
;Entry at PRFRAC prints 2 digits (for after a .)
; Trashes all registers
;===============================================
PDEC16:	mvi	d,0	;Suppress leading 0's

	lxi	b,-10000
	call	DECDIG
	lxi	b,-1000
	call	DECDIG
PRFRAC:	lxi	b,-100
	call	DECDIG
	lxi	b,-10
	call	DECDIG

	mov	a,l	;last digit is simple
	jmp	DECDG0	;with leading 0's

;------------------------------------------------
; Local subroutine to divide HL by power of 10 in
; bc and print result, unless it's a leading 0.
; On Entry:
;   hl=Dividend
;   bc=divisor (a negative power of 10)
;   d=0 if all prior digits were 0
; On Exit:
;   Quotent is printed, unless it's a leading 0
;   hl=remainder
;   d=0 iff this and all prior digits are 0
;------------------------------------------------
DECDIG:	mvi	a,0FFh	;-1 will go 1 too many times
	push	d	;leading zero state

DIGLP:	mov	d,h	;DE gets prev value
	mov	e,l
	inr	a
	dad	b	;subtract power of 10
	jc	DIGLP

	xchg		;HL has remainder
	pop	d	;leading 0 state

	mov	e,a	;E has digit to print
	ora	d	;leading 0 to suppress?
	rz		;yes: digit is done

	mov	d,a	;save for next digit
 
	mov	a,e	;make ASCII
DECDG0:	adi	'0'
	jmp	PRINTA	

;=====Subroutine=====================
; Print the seek time and the current
; cylinder number, only if VERBMO<>0
; On Entry:
;    hl=seek time in 5 uS units
;    VERBMO <> 0 enables printing
; Trashes all registers
;====================================
OPSKTK:	lda	VERBMO
	ora	a
	rz

	call	ILPRNT
	db	CR,LF,'Seek time:',' '+80h

; Subtract off the overhead of the disk controller
; firmware and average latency for it to read the next
; sector header (which it does to verify that it is on
; the correct track).

	lxi	d,-(SEEKOH/5)
	dad	d

; divide HL by 200 to get whole number mS portion

	mov	a,h

	lxi	b,-200
	lxi	d,-1		;initiaize quotent

PMSLP:	inx	d
	dad	b		;subtract 40
	jc	PMSLP

	lxi	b,200		;goes 1 too many times
	dad	b		;HL now has remainder

	push	h		;save remainder
	xchg			;whole # into hl

	call	PDEC16		;print whole #

; print decimal point

	call	ILPRNT
	db	'.'+80h

;multiply remainder by 5 to get fractional mS

	pop	h		;recover remainder
				;convert to uS
	mov	d,h
	mov	e,l
	dad	h		;X2
	dad	h		;X4
	dad	d		;X5
	
	mvi	d,1		;print leading 0's
	call	PRFRAC		;print fraction

	call	ILPRNT
	db	' m','S'+80h

; fall into PCYL

;=====Subroutine================
; Print the current cylinder
; On Exit:
;   Z cleared if drive not ready
; Trashes all registers
;===============================
PCYL:	call	CILPRT
	db	'Current cylinder:',' '+80h

	call	GETCYL		;get current cylinder
	jnz	UNADNR		;unavail: drive not ready

	call	PDEC16
	xra	a		;set z
	ret
	

;=====Subroutine===========================
;print e as an address, followed by a colon
;trashes A,C
;==========================================
PADDR:	call	ILPRNT		;new line
	db	CR,LF+80h

	mov	a,e		;buffer address
	call	PRHEX

	call	ILPRNT
	db	':'+80h

	ret

;===Subroutine===============================================
; Print CR, LF, 'Abort: ' then inline string
; On Entry:
;    The string address is the "return address" on the stack.
;    The string is terminated by bit 7 set in its last chr.
;    The actual return address is the next address after the
;    last string character.
; On Exit:
;    all registers preserved.
;============================================================
ABRTPF:	call	CILPRT
	db	'Abort:',' '+80h
	jmp	ILPRNT		;now print inline string

;===Subroutine===============================================
; Print CR, LF, 'Error: ' then inline string
; On Entry:
;    The string address is the "return address" on the stack.
;    The string is terminated by bit 7 set in its last chr.
;    The actual return address is the next address after the
;    last string character.
; On Exit:
;    all registers preserved.
;============================================================
ERRPF:	call	CILPRT
	db	'Error:',' '+80h
	jmp	ILPRNT		;now print inline string

;===Subroutine=====
; Decode error bits
; On Entry:
;   Error code in a
;==================
ERDCOD:	rar			;test bit 0
	cc	PDNR		;Drive not ready
	
	rar			;test bit1
	jnc	ERRD2
	call	ERRPF
	db	'Illegal Secto','r'+80h	
	
ERRD2:	rar			;test bit2
	jnc	ERRD3
	call	ERRPF
	db	'CRC error in secto','r'+80h	
	
ERRD3:	rar			;test bit3
	jnc	ERRD4
	call	ERRPF
	db	'CRC error in heade','r'+80h	
	
ERRD4:	rar			;test bit4
	jnc	ERRD5
	call	ERRPF
	db	'Header has wrong secto','r'+80h	
	
ERRD5:	rar			;test bit5
	jnc	ERRD6
	call	ERRPF
	db	'Header has wrong cylinde','r'+80h	
	
ERRD6:	rar			;test bit6
	jnc	ERRD7
	call	ERRPF
	db	'Header has wrong hea','d'+80h	
	
ERRD7:	rar			;test bit7
	jnc	ERRD8
FPERR:	call	CILPRT
	db	'Disk is write-protecte','d'+80h

ERRD8:	ret

;===Error Routine===========
;Print Abort Drive not ready
;===========================
ABDNR:	call	CILPRT
	db	'Abort: ',' '+80h
	call	PDNR
	jmp	MAIN

;===Subroutine=========================================
;Print Unavailable: Drive not ready during IV Byte read
; All registers and flags preserved
;======================================================
UNADNR:	call	ILPRNT
	db	' unavailable:',' '+80h

; fall into PDNR

;===Subroutine=====================
;Print Drive not ready
; All registers and flags preserved
;==================================
PDNR:	call	ILPRNT
	db	'Drive not read','y'+80h
	ret

;===Error Routine============
; Unexpected Controller Error
; On Entry:
;   Error code in a
;============================
UNXERR:	call	ERRPF		;print the following error string
	db	'Unexpected command error. Code:',' '+80h

	call	PRHEX
	jmp	MAIN

;===Error Routine=========
; Controller Timeout Error
;=========================
HDCTO:	call	ERRPF
	db	'Controller Timeout','!'+80h
	jmp	MAIN

;===Error Subroutine==================
; Controller Data Port Not Ready Error
;=====================================
CDPERR:	call	ERRPF
	db	'CDA flag not se','t'+80h

	ret

;===Error Subroutine===
; No Command Ack Error
;======================
CAKERR:	call	ERRPF
	db	'No Command Ac','k'+80h

	ret

;===Error Routine=================
; Altair Data Port Not Ready Error
;=================================
ADPERR:	call	ERRPF
	db	'ADPA flag not set',CR,LF
	db	'Try manually resetting controlle','r'+80h
	jmp	MAIN

;===Error Routine=========
; Timeout waiting for disk
;=========================
DISKTO:	call	ERRPF
	db	'Disk Timeou','t'+80h
	jmp	MAIN

;===Subroutine=========
; Print 'Fifo not full'
; set RESULT
;======================
FNFERR:	call	ERRPF
	db	'FIFO did not become ful','l'+80h
	jmp	REMERR		;remember error

;===Subroutine==========
; Print 'Fifo not empty'
; set RESULT
;=======================
FNEERR:	call	ERRPF
	db	'FIFO did not become empty at the en','d'+80h
	jmp	REMERR		;remember error

;===Subroutine=============
; Print 'Fifo full too soon
; set RESULT
;==========================
FFERR:	pop	h		;fix stack
	call	ERRPF
	db	'FIFO full too soo','n'+80h
	jmp	REMERR		;remember error

;===Subroutine==============
; Print 'Fifo Empty too soon
; set RESULT
;===========================
FNRERR:	call	ERRPF
	db	'FIFO empty too soo','n'+80h
	jmp	REMERR		;remember error

;===Subroutine==============
;Print FIFO error specifics,
; with opportunity to abort
; On Entry:
;   a=read data
;  (hl)=expected data
;===========================
FDERR:	call	REMERR		;remember error
	call	ERRPF
	db	'FIFO data mismatch. FIFO data:',' '+80h
	call	PRHEX
	call	ILPRNT
	db	'h, Expected:',' '+80h
	mov	a,m
	call	PRHEX
	call	ILPRNT
	db	'h'+80h
	jmp	TESTQ		;abort?

;===Subroutine===========
;Report verify read error
; On Entry:
;   a=error code
; On Exit:
;   c=0
; trashes a,b,hl
;========================
VRSERR:	call	ILPRNT
	db	CR,LF+80h

	lhld	BYTCNT		;bump error count
	inx	h
	shld	BYTCNT

	call	PRHEX		;error code
	call	ILPRNT
	db	' erro','r'+80h
	jmp	VERERR

;===Command Exit=======================
; Abort: Error during format data write
;  a=error code
;  c=sector number, b=0
;  CRCYL=cyl number
;======================================
FMTABT:	call	ABRTPF
	db	'error',' '+80h

	call	PRHEX		;print error code

	call	ILPRNT
	db	' writing sector',' '+80h

	lxi	h,MAIN		;create return address
	push	h		;MAIN will fix the stack

	lxi	h,INLV2		;2:1 interleave sector
	jmp	FMTERR		;recycle some code

;====Subroutine=============
;Report verify data mismatch
; On Exit:
; trashes a,bc,hl
;===========================
VDMERR:	call	CILPRT
	db	'Data mismatc','h'+80h

VERERR:	call	ILPRNT
	db	' reading sector',' '+80h

	pop	h		;temp save return address
	pop	b		;recover b=0,c=sector #
	push	b
	push	h		;put return address back

	lxi	h,INLV7		;7:1 interleave sector

FMTERR:	push	d
	dad	b
	mov	a,m		;interleaved sector number
	call	PDEC8

	lhld	BYTCNT		;bump error count
	inx	h
	shld	BYTCNT

	call	ILPRNT
	db	' cylinder',' '+80h
	lhld	CRCYL		;cylinder number
	call	PDEC16
	pop	d
	ret

;===Subroutine====
; Print 'Testing'
;=================
PTESTG	call	CILPRT
	db	'   Testing',' '+80h
	ret

;===Subroutine===============
; Print 'IV Address/Data Bus'
;============================
PIADB:	call	ILPRNT
	db	'IV Address/Data Bu','s'+80h
	ret

;===Subroutine====================
; Print 'Disk Drive Loopback Test'
;=================================
PDDPLT:	call	ILPRNT
	db	'Disk Drive Port Loopback Tes','t'+80h
	ret
;===Subroutine==========================
; Print 'Read Channel/CRC Loopback Test'
;=======================================
PRCCLT:	call	ILPRNT
	db	'Read Channel/CRC Loopback Tes','t'+80h
	ret

;===Subroutine===
; Print ' not'
;================
PRNOT:	call	ILPRNT
	db	' no','t'+80h
	ret

;===Subroutine====================
;Print 'present ' if Z
;Print 'not present ' if not Z
;All registers and flags preserved
;=================================
PRESNT:	cnz	PRNOT
	call	ILPRNT
	db	' presen','t'+80h
	ret

;===Subroutine==========
; Print 'Disk Data Card'
;=======================
PDDC:	call	ILPRNT
	db	'Disk Data Car','d'+80h
	ret

;===Subroutine==========
; Print 'Interface Card'
;=======================
PIVC:	call	ILPRNT
	db	'Interface Car','d'+80h
	ret

;===Subroutine==========
; Print 'Processor Card'
;=======================
PCPC:	call	ILPRNT
	db	'Processor Car','d'+80h
	ret

;===Subroutine=====
; Print 'CRC Error'
;==================
PCRCER:	call	ERRPF
	db	'CRC Erro','r'+80h
	jmp	REMERR

;===Exit===================
;Error: Blocks out of order
;==========================
BLKERR:	call	ABRTPF
	db	'Blocks out of orde','r'+80h
	jmp	MAIN

;===Subroutine==========================
; Print 'Disk transfer did not complete'
;=======================================
PNODTR:	call	ERRPF
	db	'Disk transfer did not complet','e'+80h

; fall into REMERR

;===Subroutine=================
; remember that we had an error
;==============================
REMERR:	push	psw
	mvi	a,01
	sta	RESULT	;remember error
	pop	psw
	ret

;===Subroutine===========
; Print name of platter
; On Entry:
;  a=0 for cartridge disk
;      otherwise, fixed
;========================
PNAME:	ora	a
	jz	PNAME1
	call	ILPRNT
	db	' fixed dis','k'+80h
	ret

PNAME1:	call	ILPRNT
	db	' removable cartridg','e'+80h
	ret

;*****************************
;* Console Input Subroutines *
;*****************************
;=====Subroutine=================
; Get one chr from console,
; convert lowercase to uppercase
; On Exit:
;   Z set and a=0 if no chr ready
;  chr in a otherwise
;================================
 if not CPMCOD
SINP:	in	ACSTAT
	ani	ACRDF
	rz			;no chr
	in	ACRXD
 endif

 if CPMCOD
SINP:	mvi	a,CONST
	call	GOBIOS
	ora	a		;any chr waiting?
	rz			;N: ret with z set

	mvi	a,CONIN
	call	GOBIOS		;get the chr
 endif

;Lowercase-to-uppercase conversion

	ani	7Fh		;strip parity
	cpi	'a'		;is it lowercase letter?
	rc			;no:done
	cpi	'z'+1
	rnc

	ani	5Fh		;convert lowercase to upper
	ret

;=====Subroutine=============================
;Get, echo, and store a console chr in LINBUF
; Handle deletes
; On Entry:
;   c = chr count
;  hl = next free spot in LINBUF
;  LBSIZE is bax chrs allowed in LINBUF
;On Exit (not full, no deletes):
;      a = chr
; (hl-1) = chr
;      c = c+1
;     hl = hl+1
;
;============================================
GETCHR:	call	SINP
	jz	GETCHR		;wait for chr

	mov	m,a		;store in LINBUF

	cpi	BS		;Backspace?
	jz	GCDEL
	cpi	DEL		;delete is same as backspace
	jz	GCDEL

	push	psw		;save chr for ret
	mov	a,c		;LINBUF full?
	cpi	LBSIZE
	cz	ESDEL		;y:backup & overwrite
	pop	psw		;recover chr for echo & ret

	inx	h		;bump pointers
	inr	c
	jmp	PRINTA		;echo & return

;----------------------------
; Local subroutine to back up
;----------------------------
GCDEL:	mov	a,c		;can we back up?
	ora	a
	rz			;done if not
	
ESDEL:	dcx	h		;back up
	dcr	c
	call	ILPRNT
	db	BS,' ',BS+80h	;erase old chr & back up	
	ret

;=====Subroutine=======================
;Tell user about sector read
; Abort with message if drive not ready
; On Entry:
;   b=buffer
;   c=sector
;   CRSIDE=side
;   CRPLAT=platter
;   CRUNIT=unit
; On Exit:
;   Z set
;Trashes a
;======================================
TELLRD:	push	h
	push	d
	push	b
	call	GETCYL		;get current cylinder in hl
	cnz	PDNR		;drve not ready
	jnz	MAIN

	call	CILPRT
	db	'Readin','g'+80h

	pop	b		;recover c=sector
	push	b		;and save it
	call	PPOSIT

	call	ILPRNT
	db	' int','o'+80h

	pop	b
	pop	d
	pop	h

; fall into PBUF

;===Subroutine===================
; Print buffer number
; On Entry:
;  b=buffer number
; trashes a, all others preserved
;================================
PBUF:	call	ILPRNT
	db	' buffer',' '+80h
	mov	a,b
	adi	'0'
	jmp	PRINTA

;=====Subroutine==========================
;Ask user whether or not to write a sector
; Abort with message if not
; Abort with message if drive not ready
; On Entry:
;   b=buffer
;   c=sector, FF for all sectors
;   CRSIDE=side
;   CRPLAT=platter
;   CRUNIT=unit
; On Exit:
;   Z set
;Trashes a
;=========================================
ASKWR:	push	h
	push	d
	push	b
	call	GETCYL		;get current cylinder in hl
	cnz	PDNR		;drve not ready
	jnz	MAIN

	call	CILPRT
	db	'Writ','e'+80h
	call	PBUF
	call	ILPRNT
	db	' t','o'+80h
	pop	b		;recover c=sector
	push	b		;and save it
	call	PPOSIT

	call	ILPRNT
	db	CR,LF+80h

	pop	b
	pop	d
	pop	h

	jmp	ASKYN

;===Subroutine==================
; Print Disk Position
; On Entry:
;  hl = current cylinder
;   c=sector, FF for all sectors
;   CRSIDE=side
;   CRPLAT=platter
;   CRUNIT=unit
; Trashes all registers
;===============================
PPOSIT:	push	b		;save c=sector
	call	ILPRNT
	db	' unit',' '+80h
	lda	CRUNIT
	adi	'1'
	call	PRINTA

	call	ILPRNT
	db	QUOTE,'s'+80h

	lda	CRPLAT
	call	PNAME

	call	ILPRNT
	db	', cyl',' '+80h
	call	PDEC16

	call	ILPRNT
	db	', side',' '+80h
	lda	CRSIDE
	call	PDEC8

	pop	b
	mov	a,c		;sector number
	inr	c		;all sectors?
	jz	PASECS		;y: say so

	call	ILPRNT
	db	', sec',' '+80h

	jmp	PDEC8

PASECS:	call ILPRNT
	db	', all sector','s'+80h
	ret

;=====Subroutine====================
;Ask user whether or not to continue
; Abort with message if not
;Trashes a
;===================================
ASKCON:	call	CILPRT
	db	'   Continu','e'+80h
	jmp	ASKYN

;=====Subroutine=================
;Ask user Y or N, with CRLF first
; Abort with message if not Y
;Trashes a
;================================
CASKYN:	call	ILPRNT
	db	CR,LF+80h

; fall into ASKYN

;=====Subroutine=============
;Ask user Y or N
; Abort with message if not Y
;Trashes a
;============================
ASKYN:	call	SINP		;flush any junk
	jnz	ASKYN

	call	ILPRNT
	db	' (Y/N)?',' '+80h

WAITYN:	call	SINP
	jz	WAITYN		;wait for response
	call	PRINTA		;echo

	cpi	'Y'
	rz

	call	ILPRNT		;not y: abort
	db	' Abor','t'+80h
	jmp	MAIN

;=====Subroutine===============================
; Check for user input during SK (seek) command
; If automatic mode enabled:
;   Return if nothing typed
;   Quit if 'Q'
;   Switch to manual mode if 'M'
; If manual mode enabled:
;   perform a dummy read, to re-select the head
;   Wait here until something acceptable typed
;   Return if Space
;   Quit if 'Q'
;   Switch to Automatic mode if 'A'
; Trashes a,c
;==============================================
SEEKUI:	lda	AUTOMO		;Auto mode?
	ora	A
	jz	WTQSA		;n: wait for user input

	call	SINP		;anything from console?
	rz			;n: done

	cpi	'Q'		;quit command?
	jnz	SKUI1

				;command done:
	call	DUMYRD		;re-select the correct head
	jmp	DONE		;print message & quit command

SKUI1:	sui	'M'		;switch to manual mode?
	rnz			;n: done

	sta	AUTOMO		;turn off automatic mode

	call	MMMSG		;manual mode message

;---------------------------------------
; Manual mode
; Wait for Q or space from user
; Rude jump to MAIN if Q, return if ' ',
; Switch to Automatic mode if 'A'
;---------------------------------------
WTQSA:	call	DUMYRD		;re-select the correct head
	call	TESTQ
	cpi	' '		;space to advance
	rz
	cpi	'A'		;switch to automatic?	
	jnz	WTQSA		;keep waiting

	sta	AUTOMO		;switch to automatic

; Fall into AMMSG

;=====Subroutine==============
; Print Automatic Mode message
; all regs and flags preserved
;=============================
AMMSG:	call	CILPRT
 db 'Automatic Seek Mode',CR,LF
 db 'M for manual, Q to qui','t'+80h

	ret

;=====Subroutine==============
; Print Manual Mode message
; all regs and flags preserved
;=============================
MMMSG:	call	CILPRT
 db 'Manual Seek Mode',CR,LF
 db 'Space for next, A for automatic, Q to qui','t'+80h

	ret
;=====Subroutine==============
; Print Manual Mode message
; all regs and flags preserved
;=============================
IOMSG:	call	CILPRT
 db 'I to step in, O to step ou','t'+80h

	ret

;=====Subroutine========
; Test for Q pressed.
; Rude jump to MAIN if Q
; A=chr if not Q
; Z set if no chr
; Trashes a
;=======================
TESTQ:	call	SINP		;anything from console?
	rz
	cpi	'Q'
	rnz

; Fall into DONE

;=====Subroutine========
; Print DONE message and
; Rude jump to MAIN
;=======================
Done:	call	CILPRT
	db	'Q',CR,LF,'don','e'+80h

	jmp	MAIN

;***************************************
;* Line Buffer Manipultion Subroutines *
;***************************************

;=====Subroutine===============================
; get a 0 or 1 from LINBUF
; On Entry:
;   hl points to first chr of a parameter
; On Exit:
;  a=c=value
;  z set if value is 0
;  hl points to first chr of next param
;  Rude jump to CMDERR if:
;   {no chr, bogus chr, value>1, another param}
;  Trashes b,de
;==============================================
GET01:	lxi	d,1		;max allowed value

; Fall into GETDC1

;=====Subroutine=================================
; get a single decimal value from LINBUF
; On Entry:
;   de = max value
;   hl points to first chr of a parameter
; On Exit:
;  bc=value
;  a=c=low byte of result
;  hl points to first chr of next param
;  Rude jump to CMDERR if no chr or another param
;  Rude jump to GPERR if bogus chr
;  Rude exit with message if value>de
;================================================
GETDC1:	call	GETDEC
	jz	CMDERR		;error if no chr

	call	SKIPSP		;should be no more
	jnz	CMDERR		;too much crap

	mov	a,c		;low result for return		
	ret

;=====Subroutine==============================
; get decimal parameter from LINBUF
; On Entry:
;   de=max allowed value
;   hl points to first chr of a parameter
; On Exit:
;   bc=value, 0 if no more chrs
;   hl points to first chr of next param
;   z set if no parameters found
;   a trashed
;  Rude jump to GPERR if bogus chr or value>de
;  Rude jump to GPERR if bogus chr
;  Rude exit with message if value>de
;=============================================
GETDEC:	lxi	b,0		;default value
	mov	a,m		;any more digits?
	ora	a
	rz			;z set if return

	push	h		;save input pointer
	lxi	h,0		;init result

GDLOOP:	xthl			;get input pointer
				;..and save result
	call	GETDD		;get new digit
	jz	CHKDEC		;done?

	xthl			;get result
				;..and save pointer
	dad	h		;x2
	mov	b,h
	mov	c,l
	dad	h		;x4
	dad	h		;x8
	dad	b		;x10

	mov	c,a		;add in new digit
	mvi	b,0
	dad	b
	jmp	GDLOOP

;We have a complete result. Check if it's too big (>de)

CHKDEC:	call	SKIPSP		;advance to next param

	pop	b		;recover result

	mov	a,e		;16-bit subtract
	sub	c		;max-result
	mov	a,d
	sbb	b		;carry means result>max

	inr	a		;clr z, don't touch carry
	rnc			;nc means okay, z cleared

;Error: value too high 

	call	CILPRT
	db	'Value too hig','h'+80h
	jmp	MAIN		;main fixes stack

;-------------------------------------------------
;Local subroutine to get & test a decimal digit
;On Exit:
;  Z set if end of cmd line or no digit (space)
; hl points to next cmd line chr unless it's a ' '
;  a=chr converted to binary, unless Z
;-------------------------------------------------
GETDD:	call	GETCL1
	rz			;ret if no chr

	sui	'0'		;de-ASCII
	cpi	10		;carry means below 10
	rc

; fall into GPERR

;----------------
;Error: Bad digit 
;----------------
GPERR:	call	CILPRT
	db	'Bad digi','t'+80h
	jmp	MAIN		;main fixes stack

;=====Subroutine===================================
; get a 2-digit (max) hex value from LINBUF
; On Entry:
;   hl points to first hex digit
; On Exit:
;  carry set and a=e=0 if no value found
;  e=value
;  trashes a,d
;  Rude jump to CMDERR if bogus hex found
;  Rude jump to CMDERR if >2 digits
;  Rude jump to CMDERR if any subsequent parameters
;==================================================
GETHEX:	call	GHNIB		;get a=1st digit
	mov	e,a		;save digit
	stc
	rz			;no digits found

	call	GHNIB
	rz			;ret w/ carry clear

	mov	d,a		;save low digit
	mov	a,e		;get high digit
	add	a		;shift into place
	add	a
	add	a
	add	a
	add	d		;combine w/ low digit
	mov	e,a		;result into e

	call	SKIPSP		;no more digits or params allowed
	jnz	CMDERR		;more:error
				;carry is clear for ret
	ret
		
;===Local Subroutine================
;Get & convert hex at (hl) to binary
; On Exit:
;   a=chr, 0 if none left
;  hl advanced 1
;   Z set and carry clear if no chr
;   carry set if good hex chr
;   Rude exit to CMDERR if bad hex
;===================================
GHNIB:	call	GETCL1		;get a character
	rz			;z:no more hex digits

	call	HEX2BN
	jnc	CMDERR		;bogus hex abort
	ret

;===Subroutine================
;Convert hex at (hl) to binary
; On Exit:
;   a=chr
;   carry set if good hex
;=============================
HEX2BN:	cpi	'9'+1		;below ASCII 9?
	jc	HC1		;Yes: deal with digit

	cpi	'A'		;between 9 & A?
	cmc			;so no carry means error
	rnc			;yes: bogus

	sui	'A'-'9'-1	;no: subtract offset	

HC1:	sui	'0'
	cpi	10H		;above 0Fh?
	ret			;carry set means ok


;=====Subroutine==================================
; Get slash option from LINBUF
; On Entry:
;   hl points to current position in LINBUF
; On Exit:
;   a=0 and Z if none left
;   chr in a and hl pointing to next item otherwise
;==================================================
GSLASH:	call	GETCL1		;any options?
	rz			;n:ret with Z set

	cpi	'/'		;valid option?
	jnz	CMDERR		;n:error

	call	GETCL1		;get option
	jz	CMDERR		;none:error

	push	psw		;save option
	call	SKIPSP		;skip to next param
	pop	psw		;recover option

	ret

;=====Subroutine===========================
; Get 1 chr from command line in LINBUF
; On Entry:
;   hl points to current position in LINBUF
; On Exit:
;   a=0 and Z if none left
;   chr in a and hl advanced otherwise
;==========================================
GETCL1:	mov	a,m		;any chrs?
	ora	a
	rz			;no:Z set

	cpi	' '		;no digit?
	rz
	inx	h
	ret

;=====Subroutine==========================
; Get list of cylinders/values from LINBUF
; On Entry:
;   hl=address of 1st chr after cmd
;   Destination is PATBUF
;   max allowed value is MAXCYL
; On Exit:
;   (PATBUF) = cylinder count
;   Cylinders are in PATBYF, 2 bytes each
;   hl=address of beginning of buffer
;    b=(hl)=count of values
; Trashes a,c,de
;=========================================
GCYLST:	lxi	d,PATBUF+1	;pattern buffer dest.
	mvi	b,0FFh		;value cnt, goes 1 extra

GCYLLP:	inr	b		;inc track count

	push	b		;save count
	push	d
	lxi	d,MAXCYL	;max for GETDEC
	call	GETDEC		;get a 16-bit track #
	pop	d		;result in bc, Z set if done

	mov	a,c		;save low byte of result
	stax	d
	inx	d
	mov	a,b		;and high byte
	stax	d
	inx	d
	pop	b		;recover count

	jnz	GCYLLP		;keep looking

	lxi	h,PATBUF
	mov	m,b		;save track count in buffer
	ret

;===Subroutine====================
; Get IV Address from LINBUF
;On Entry:
; hl points to next item in LINBUF
;On Exit:
; a=c=address
; b=0
;=================================
IVADDR:	lxi	d,MAXIV		;max IV Byte address
	call	GETDEC		;get c=IV byte address
				;b=0

	jz	CMDERR		;error if no parameter
	mov	a,c		;bc IV address, b=0
	ret

;*************************************
;* Intel Hex Dump & Load Subroutines *
;*************************************
;===subroutine=======================
; Get a hex byte from the serial port
; On Entry:
;   c=checksum so far
;  hl=address in LINBUF
; On Exit:
;   a=chr
;   c updated
;  hl incremented twice of good hex
;   z set if c=0
;====================================
GETSHB:	push	d

	call	GETSHD		;get high digit

	add	a		;shift up
	add	a
	add	a
	add	a
	mov	d,a		;temp save

	call	GETSHD		;get low digit

	add	d		;combine nibbles
	mov	d,a		;temp save

	add	c		;update checksum & Z
	mov	c,a

	mov	a,d		;recover byte
	pop	d

	ret

;===Subroutine=================================
;Get hex digit from the Terminal, abort if Q
;Print 2-chr error message and bump error count
;in RESULT if bad hex
; On Exit:
;   a=chr
; trashes e
;==============================================
GETSHD:	call	SINP		;get chr
	jz	GETSHD		;spin until chr

	mov	e,a		;temp save
	lda	ECHOMO		;echo?
	ora	a
	mov	a,e
	cnz	PRINTA		;echo if enabled

	cpi	'Q'		;abort?
	jz	MAIN

	call	HEX2BN		;convert to binary
	rc			;return if okay hex

	pop	h		;lose call GETSHD
	pop	d		;restore de
	pop	h		;lose call GETSHB
	
	call	ILPRNT
	db	'?','H'+80h

; fall into BMPERR

;===Subroutine=============
;Bump error count in RESULT
;==========================
BMPERR:	push	h
	lxi	h,RESULT
	inr	m
	pop	h
	ret

;===Subroutine=======================
; Print echo-requires-character-delay
; message, only if echo is enabled
; Trashes a
;====================================
CDMSG:	lda	ECHOMO		;echo enabled?
	ora	a		;warning if so.
	rz

	call	CILPRT
 DB 'Tx chr delay required if echo enabled','.'+80h

	ret

;**********************
;* XMODEM Subroutines *
;**********************
;===Subroutine======================
; Receive a byte, with 1-sec timeout
; On Entry:
; On Exit:
;   Carry set for timeout error
;   Character in a
;   trashes a,e
;===================================
RXBYT1:	mvi	e,2

;	fall into RXBYTE

;===Subroutine=====================================
; Receive a byte from the XModem port
; Note: CTSRDR option selects a custom reader port:
; rather than hanging and waiting for a byte, the
; driver will return with Z set if no byte is
; available. Otherwise, a=received byte
;
; On Entry:
;   e = timeout value in half-seconds
; On Exit:
;   Carry set for timeout error
;   a = received byte if no timeout
;   trashes e
;==================================================
RXBYTE:	push	h

; set HL for 1/2 second timeout

MS500:	lxi	h,13614		;1/2-sec count down

RXWAIT:	call	GETX		;(28+17)
	jnz	RXDON		;(5/11) carry is clear

	dcx	h		;(5)timeout timer
	mov	a,h		;(5)Test for 16-bit 0
	ora	l		;(4)
	jnz	RXWAIT		;(10)
		;inner loop:74 cycles=39.5 uS
		;0.5 sec / 26 uS = 13514 cycles

	call	TESTQ		;user abort?
	dcr	e
	jnz	MS500		;spin for time

;timeout waiting for chr.

	stc			;Carry for timeout

RXDON:	pop	h
	ret

;===Subroutine=========================================
;Get the error-checking mode
; Wait for initial NAK or a SELCRC from receiver to get
; going. (NAK means we use checksums, SELCRC means use
; CRC-16.) Ignore all other characters, w/ long timeout
; Abort if user types Control-C
; On Entry:
; On Succesful Exit:
;   CRCFLG = 0 if NAK received
;   CRCFLG = SELCRC (nz) if SELCRC received
;   Message printed if CRC mode
; Trashes a,bc,de,hl
;======================================================
GTMODE:	mvi	b,NAKTO		;Long timeout
	lxi	h,CRCFLG	;assume cksum for now
	mvi	m,0

WAITNK:	dcr	b		;Timeout?
	jz	XDABRT		;yes: abort

	call	RXBYT1		;trashes e
	cpi	NAK		;NAK for checksum?
	rz			;yes:done

	cpi	SELCRC		;'C' for CRC?
	jnz	WAITNK		;No: Keep looking

	mov	m,a		;remember CRC mode

;fall into PCRC

;===Subroutine====
;Print ' with CRC'
;=================
PCRC:	call	ILPRNT		;and brag about it
	db	' with CR','C'+80h
	ret

;===Subroutine=====
; Xmodem dump abort
;==================
XDABRT:	call	ABRTPF
	db	'No init from receive','r'+80h
	jmp	MAIN

;===Subroutine================================
; Get an ACK from the receiver.
; On Exit:
;   Z set and Carry clear if ACK received
;   Z clear and Carry clear if NAK received
;   Z clear, Carry set and ERRCNT bumped if
;      timeout or too many bogus chrs received
;   If too mant errors, abort
;   Trashes a,b,c, hl
;=============================================
GETACK:	

;-------------------------------
;Get a received byte, or timeout
;-------------------------------
	mvi	e,ACKTO*2	;ACK-wait timeout value
	call	RXBYTE		;go get a character
	jc	XERROR		;Carry means timeout

;------------------------------------------------
;Return form subroutine with Z set if it's an ACK
;------------------------------------------------
	cpi	ACK		;Did we get an ACK?
	rz			;Yes: return w/ carry cleared

;-----------------------------------------------------------
;If NAK, return with C & Z cleared unless the user aborts us
;-----------------------------------------------------------
	mov	e,a		;save received chr
	call	TESTQ		;user abort?
	mov	a,e		;recover received chr

	cpi	NAK		;NAK?
	jnz	XERROR		;NZ: bad byte received

	ora	a		;NAK: Clear Z & C
	ret	

;===Subroutine===============================
; Get an XMODEM block
; On Entry:
;  hl=address for data
;  NAKCHR is the current NAK character, which
;         is normally NAK, but starts out as
;         SELCRC if we are in CRC mode.
;  ACKCHR is the same as NAKCHR for the 1st
;         call, and is ACK thereafter.
;  CURBLK is the number of the most recently
;         received block.
; On Exit:
;   Carry set if EOT received
;   Trashes all registers
;============================================
GETBLK:

;Acknowledge the previous block, now that we ar ready
;to receive one. This will send the appropriate NAK
;(depending on error checking mode) for the 1st block.

	call	TXACK		;ack previous block (NAK at beginning)

;-----------------------------------------
;Wait for SOH from sender to start
;reception, checking for EOT while we wait
;-----------------------------------------
	xra	a
	sta	RESULT		;Clear error count

RXRPT:	mvi	e,SOHTO*2	;Timeout for SOH
	call	RXBYTE
	jc	RXSERR		;Carry means timeout

	cpi	SOH		;Did we get an SOH?
	jz	RXSOH		;If so, get the block

;-------------------------------------
;Set carry and return if we get an EOT
;-------------------------------------
	cpi	EOT
	stc
	rz

;-------------------------------------------
;No SOH or EOT - this is an invalid header.
;Eat all received chrs until a 1-sec timeout
;-------------------------------------------
PURGE:	call	RXBYT1		;Receive chr w/ timeout
	jnc	PURGE		;Carry means timeout

;------------------------------------------------
;Send a NAK to indicate receive error. If we are
;waiting to start (and we are in CRC mode (NAKCHR
;=SELCRC), then send SELCRC instead of NAK
;------------------------------------------------
RXSERR:	call	XERROR		;bump error counts

	lda	NAKCHR		;current NAK chr
	call	PUTX

	jmp	RXRPT		;try again

;--------------------------------------------------
;Got an SOH, at beginning of block. Now get header:
;Block number, Complemented block number
;--------------------------------------------------
RXSOH:	mvi	a,NAK		;we have received
	sta	NAKCHR		;..at least one SOH

	call	RXBYT1		;Get block number
	jc	RXSERR		;Carry means timeout

	mov	d,a		;Save block number

	call	RXBYT1		;compl'd block number
	jc	RXSERR		;Carry means timeout

	cma			;compliment to compare
	cmp	d
	jnz	PURGE		;No match: error

	sta	RXBLK		;Save block number

;--------------------------------------------------
;Loop to receive BLKSIZ bytes and store them in
;BLKBUF, computing the checksum & CRC along the way
;--------------------------------------------------
	push	h
	lxi	h,0		;clear CRC
	shld	CRC16
	pop	h		;data address

	lxi	b,BLKSIZ*256+0	;b=bytes, c=0 cksum

RXCHR:	call	RXBYT1		;Get one byte of data
	jc	RXSERR		;Carry means timeout

	mov	m,a		;Store byte in buffer

	add	c		;compute checksum
	mov	c,a
	mov	a,m		;recover received byte

	call	CALCRC		;calculate CRC too

	inx	h		;next byte
	dcr	b
	jnz	RXCHR

;------------------------------------------------------
;Verify checksum in C, or CRC in CRC16, based on CRCFLG
;------------------------------------------------------
	lda	CRCFLG		;CRC mode?
	ora	a		;0 means cksum
	jz	CKCKSM

	call	RXBYT1		;Get 1st byte of CRC
	jc	RXSERR		;Carry means timeout

	push	h
	lhld	CRC16
	mov	c,l		;put 2nd CRC byte in C
	cmp	h
	pop	h
	jnz	PURGE		;no: try again, but 1st
				;purge rest of CRC

CKCKSM:	call	RXBYT1		;2nd CRC byte or cksum
	jc	RXSERR		;Carry means timeout

	cmp	c		;Does it match?
	jnz	RXSERR		;No: error

;-----------------------------------------------
;Got a good block. See if we've already received
;this block. (It might be a retransmission.) If
;it's the most recently received block, then try
;again - otherwise it's an error.
;-----------------------------------------------
	lda	RXBLK		;this block's number
	mov	b,a		;b=this block's number

	lda	CURBLK		;a=previous block number
	cmp	b		;calc the difference
	cz	TXACK		;same as last block:
	jz	GETBLK		;..send ACK & try again

	inr	a		;should be next block
	cmp	b		;clears carry too

	jnz	BLKERR		;blocks out of order?

	sta	CURBLK
	ret			;correct block

;===Subroutine================================
; XModem tranmit or receive error. Bump total
; error count in BYTCNT and this character's
; error count in RESULT. If RESULT>ERLIM limit
; then abort with error message
; On Exit:
;  carry set, Z cleared
;  a trashed
;  BYTCNT incremented
;  RESULT incremented
;=============================================
XERROR:	push	h

	lhld	BYTCNT		;total error count
	inx	h
	shld	BYTCNT

	call	TESTQ		;user abort?

	lxi	h,RESULT	;bump error count
	inr	m

	mov	a,m		;too many errors?
	pop	h

	cpi	XERLIM
	rc			;No: Return w/ Carry set
				;and Z cleared

;---------------------------------------
; XModem transfer abort: Too many errors
;---------------------------------------
	call	ABRTPF
	db	'Too many error','s'+80h
	jmp	MAIN

;===Subroutine=============================
; Update the 16-bit CRC with one more byte.
; Speed matters here.
; On Entry:
;   a has the new byte
;   CRC16 is current except this byte
; On Exit:
;   CRC16 has been updated
;   Trashes a,de
;==========================================
CALCRC:	push	b
	push	h
	lhld	CRC16		;get CRC so far

	xra	h		;XOR into CRC top byte
	mov	h,a

	lxi	d,1021h		;CRC16's magic number
	mvi	b,8		;prepare to rot 8 bits

CROTLP:	dad	h		;16-bit shift
	jnc	CCLR		;skip if bit 15 was 0

	mov	a,h		;CRC=CRC xor 1021H
	xra	d
	mov	h,a
	mov	a,l
	xra	e
	mov	l,a

CCLR:	dcr	b
	jnz	CROTLP		;rotate 8 times

	shld	CRC16		;save CRC so far
	pop	h
	pop	b
	ret

;===Subroutine=======================
; Send ACK (will be NAK the 1st time)
; all regs and flags preserved
;====================================
TXACK:	push	psw
	lda	ACKCHR
	call	PUTX
	mvi	a,ACK
	sta	ACKCHR
	pop	psw
	ret

;===Subroutine======
;Print too much data
;===================
TMDERR:	call	ABRTPF
	db	'Too many block','s'+80h
	ret

;===Subroutine==========================
; Get one chr from the XModem port
; On Entry:
;   XPORT = 0 for 2SIO port B, 1 for SIO
; On Exit:
;   carry cleared
;   a=chr, if one was waiting
;   z set otherwise
; All other registers preserved
;=======================================
GETX:	lda	XPORT		;0=2SIOB, 1=SIO
	ora	a		;2SIO?
	jnz	GTSIO		;n: then it's an SIO

; try to get a character from 2SIO port B

	in	X0STAT
	ani	X0RDF		;clears carry too
	rz
	in	X0RXD		;get the character
	ret

; try to get a character from the SIO

GTSIO:	in	X1STAT
	cma			;active-low signal
	ani	X1RDF		;clears carry too
	rz
	in	X1RXD		;get the character
	ret

;===Subroutine==========================
; Send a to the XModem port,and add the
; value to c, the accumuliting checksum
; On Entry:
;   XPORT = 0 for 2SIO port B, 1 for SIO
;   a=chr to send
;   c=checksum so far
; On exit:
;   c=c+a
; all other registers preserved
;=======================================
PUTX:	push	psw		;Save character

;compute checksum = new character + c

	add	c		;update checksum
	mov	c,a

;figure out which port to use

	lda	XPORT		;0=2SIOB, 1=SIO
	ora	a		;2SIO?
	jnz	PXWAT1		;n: then it's an SIO

;Send character to 2SIO Port B

PXWAT0:	in	X0STAT
	ani	X0TDE
	jz	PXWAT0		;wait for port

	pop	psw		;recover character
	out	X0TXD		;send character
	ret

;Send character to the SIO

PXWAT1:	in	X1STAT
	ani	X1TDE		;wait for port
	jnz	PXWAT1		;active-low signal

	pop	psw		;recover character
	out	X1TXD		;send character
	ret

;*********************
;* Misc. Subroutines *
;*********************
;===Subroutine================
; Toggle the side
; all regs and flags preserved
;=============================
TOGSID:	push	psw
	lda	CRSIDE
	xri	1		;other side
	sta	CRSIDE
	pop	psw
	ret

;===Subroutine============================================
; Stall for 200 mS, so that any final characters in the
; ACIA don't get eaten by the ACIA reset, even at 110 baud
;=========================================================
 if not CPMCOD
STALL:	lxi	h,15833	;(10) 16667 X 24 X 0.5uS = 200 mS

STALLP:	dcx	h	;(5)
	mov	a,h	;(5)
	ora	l	;(4)
	jnz	STALLP	;(10) 24 cycles through loop

	ret
 endif

;===Subroutine============================
; Circular-increment pattern pointer in hl
; The assumes the last pattern byte is 00
;=========================================
PPBUMP:	mov	a,m
	inx	h		;next pattern
	ora	a		;wrap pattern?
	rnz
	lxi	h,MTPAT		;wrap
	ret

;===Pattern Table================================
; Memory, FIFO, and IV Byte Test Pattern Sequence
; See notes at beginning of TB command
;================================================
MTPAT:	db	0ffh,055h,0aah,033h,0cch,0f0h,00fh,0c3h
	db	03ch,066h,099h,078h,001h,0feh,002h,0fdh
	db	004h,0fbh,008h,0f7h,010h,0efh,020h,0fdh
	db	040h,0bfh,080h,07fh,000h

;******************************************************
;* COMMAND TABLES                                     *
;* Each 4-byte entry:                                 *
;*   Bytes 1:0 = 2-character command                  *
;*   Bytes 3:2 = Address of command execution routine *
;* Tables are terminated with 00 in byte 0.           *
;******************************************************

;===Table=========
; Primary Commands
;=================
CMDTAB:	DB	'?',0
	DW	FHELP		;help
	DB	'?B'
	DW	FBHELP		;Buffer help
	DB	'?C'
	DW	FCHELP		;Controller help
	DB	'?D'
	DW	FDHELP		;Disk Read/Write help
	DB	'?P'
	DW	FPHELP		;Positioner help
	DB	'?S'
	DW	FSHELP		;State help
	DB	'BE'
	DW	FEDIT		;Edit buffer
	DB	'BD'
	DW	FDISP		;Display Buffer
	DB	'BF'
	DW	FFILL		;fill buffer
	DB	'CC'
	DW	FCCOMP		;Compare controller buffer
	DB	'CR'
	DW	FCRD		;Read from Controller
	DB	'CW'
	DW	FCWR		;Write to controller
	DB	'DC'
	DW	FDCOPY		;disk copy
	DB	'DV'
	DW	FVERFY		;disk verify
	DB	'EU'
	DW	FUNLOD		;Emergency Unload
	DB	'FD'
	DW	FMTDSK		;format Disk
	DB	'FS'
	DW	FMTSID		;format Side
	DB	'HD'
	DW	FHDUMP		;hex dump
	DB	'HL'
	DW	FHLOAD		;hex load
	DB	'IC'
	DW	FINIT		;initialize controller
	DB	'IR'
	DW	FIVRD		;Read IV byte
	DB	'IW'
	DW	FIVWR		;Write IV Byte
	DB	'MS'
	DW	FMSPIN		;Measure spinde speed
	DB	'QU'
	DW	FQUIT		;quit to OS
	DB	'RE'
	DW	FRESTR		;restore to cyl 0
	DB	'RR'
	DW	FRREST		;repetitive restore to 0
	DB	'SB'
	DW	FRUNF		;Read bad Sector
	DB	'SE'
	DW	FSET		;set
	DB	'SK'
	DW	FSEEK		;Seek
	DB	'SS'
	DW	FSTST		;Start/Stop
	DB	'ST'
	DW	FSTEP		;Step
	DB	'SR'
	DW	FREAD		;Read sector
	DB	'SW'
	DW	FWRITE		;Write sector
	DB	'TB'
	DW	FTCBM		;Test controller memory
	DB	'TF'
	DW	FTFIFO		;Test FIFO
	DB	'TI'
	DW	FTIV		;Test IV Bytes
	DB	'TW'
	DW	FTRKWR		;Write Track
	DB	'XD'
	DW	FXDUMP		;XMODEM dump
	DB	'XL'
	DW	FXLOAD		;XMODEM load

	DB	0		;Table End

;===Table=========
; Set sub-commands
;=================
SETTAB:	DB	'A='
	DW	FSETA		;auto mode
	DB	'E='
	DW	FSETE		;Echo mode
	DB	'O='
	DW	FSRTO		;Read Track Offset
	DB	'S='
	DW	FSETS		;side
	DB	'P='
	DW	FSETP		;platter
	DB	'U='
	DW	FSETU		;unit
	DB	'V='
	DW	FSETV		;Verbose mode
	DB	'T='
	DW	FTKCK		;Seek track check
	Db	'X='
	DW	FSETX		;set XModem port

	DB	0		;Table End

;===Table=======================
; 2:1 Interleave
; This table is in reverse order
;===============================
INLV2:	db	23,21,19,17,15,13,11,09,07,05,03,01
	db	22,20,18,16,14,12,10,08,06,04,02,00

;===Table=======================
; 7:1 Interleave for Formatting
; This table is in reverse order
;===============================
INLV7:	db	17,10,03
	db	20,13,06
	db	23,16,09,02
	db	19,12,05
	db	22,15,08,01
	db	18,11,04
	db	21,14,07,00

;=============================================
; RAM variables and buffers
; Initialized at the beginning of code.
; The 1st 512 bytes get cleared, then non-zero
; variables get initialized, including setting
; the first byte of PATBUF to 1, which makes
; the default seek be a seek to cylinder 0.
;=============================================

VARSP:

;=============
;RAM variables
;=============
ECHOMO:	ds	1	;{0/NZ}Echo during HL
AUTOMO:	ds	1	;{0/NZ}automatic seek mode
VERBMO:	ds	1	;{0/NZ}Verbose display
SKCKMO:	ds	1	;{0/NZ}seek track check
XPORT:	ds	1	;{0/1} 0 for 2SIOB, 1 for SIO
CRRTO:	ds	1	;{IVIOMN/0/IVIOPN} read track offset

;CRPLAT must follow CRSIDE

CRCYL:	ds	2	;{0:405}current cylinder
CRSIDE:	ds	1	;{0/1}current side
CRPLAT:	ds	1	;{0:3}current platter
CRUNIT:	ds	1	;{0:3}current unit number
SRCPLA:	ds	1	;{0:3}source platter for DC & DV
DSTPLA:	ds	1	;{0:3}dest platter for DC & DV

; The following variables get cleared at MAIN
CLRVAR:

RESULT:	ds	1	;{0,NZ}Test Result:0 means ok
IGNCNT:	ds	1	;counts ignored Intel hex records.
			;..Must follow result
BYTCNT:	ds	2	;byte count or error count
RXBLK:	ds	1	;received xmodem block number
CURBLK:	ds	1	;current Xmodem block number
CRC16:	ds	2	;CRC calculation partial result

NAKCHR:	ds	1	;current NAK chr
ACKCHR:	ds	1	;current ACK chr
CRCFLG:	ds	1	;{0/NZ}nz means use CRC for Xmodem
PARKIT:	ds	1	;{0,NZ}Park at end of function

CLRCNT	equ $-CLRVAR

;===================
;Command line buffer
;===================
LINBUF:	ds	LBSIZE

;===========
;Local stack
;===========
	ds	32
STACK:				;top of stack

;=============
;Sector buffer
;=============
SECBUF:	ds	256

;===================
;Seek pattern buffer
;overlaps VERBUF
;===================
PATBUF:

;======================
;4-Sector verify buffer
;======================
VERBUF:	ds	256*4

	END


