;==============================================================
; HEXER: Helios Floppy Drive Exerciser by Martin Eberhard
;
; For measuring and adjusting a Processor Technology Helios II
; or Helios IV Disk Memory System (with Persci 270/277 drives),
; in a computer with either SOLOS or CUTER monitor. This
; program is optimized for a 16X64 screen (e.g. the Sol-20 or
;  VDM-1) and a Sol-20 keyboard (with a "MODE/SELECT" key),
; though it will work fine with a larger screen and with an
; ordinary keyboard. It supports a Sol-20 (at 2.054 MHz),
; as well as a 2 MHz 8080 computer (such as an Altair). Zero
; wait-state RAM is required, since timeing loops are used to
; measure time. Any computer other than a Sol-20 requires
; either the Processor Technology Subsystem B board set or a
; modified version of CUTER that supports the computer's
; console port. The Helios controller must be configured for
; the standard Helios I/O addresses.
;==============================================================

VERSION	equ	0203h		;(use BCD encoding)

; Revision History
; 2.03  14 July 2023  M. Eberhard
;  Report track number during format errors. Don't stop format
;  on FD errors. Better user-abort during FD.
; 2.02  20 April 2018  M. Eberhard
;  Add '0' restore immediate command. Fix typo in message.
; 2.01  8 April 2018  M. Eberhard
;  Fix bug selecting unit
; 2.00 2 March 2018 Martin Eberhard
;  Pretty heavy rewrite: Rename many commands such that none
;  starts with 'H', and such that naming is more intuitive.
;  Create 'immediate' commands (H, +, -, >, <) so that the
;  head can be loaded/unloaded and nudged during ST, SK, and MS
;  operation. (Loading the head during SK operation makes 
;  positioner adjustment less tedious.) Simplify set commands
;  so that you don't need to type "SE" before the "X="
;  type commands. Eliminate HL command (since we added the H
;  immediate command), add WC command. Rearrange things in the
;  code. Overlay the format and compare buffers into the same
;  memory space as the boot buffer to reduce Hexer's memory
;  footprint. Use the format buffer instead of the block buffer
;  for the FT command. Improve comments.
; 1.05 Fix "reserved" in header display routine to show a
;  1-byte value (not 2 bytes).
; 1.04  28 MAy 2015  M. Eberhard
;  fix links during formatting, to make PTDOS happier. Fix bug
;  when selecting units higher than 1. Add BC (block compare)
;  command. Default to decimal mode. Don't restore if changing
;  disk unit within the same drive.
; 1.03  22 May 2015  M. Eberhard
;  Add FT, ID and IL commands. And ready-check to BO command.
; 1.02  20 May 2015  M. Eberhard
;  Fix timing problem reading block 0 header. Add BO command.
; 1.01  15 May 2015  M. Eberhard
;  Set CPU clock frequency based on Solos vs. Cuter detection.
;  Interleave block numbers on PTDOS directory track. Account
;  for Sol-20's I/O waitstate in timing loops.
; 1.00  2 January 2015  M.Eberhard
;  Created
;To do (maybe):
;   Create required initial files in FD command
;   read-write-seek test command, for use before alignment
;   Disk copy command
;
;==============================================================
; Notes:
;
; This code assumes that SOLOS or CUTER is at its standard
; address of C000h. If your Solos or Cuter is someplace else,
; change the equate for SOLOS to match, and then reassemble.
;
; Because of the timing loops, interrupts are blocked while 
; this program runs.
;
; Numbers in parenthesis are 8080 instruction cycle counts.
; These are in comments wherever the cycle count matters,
; either because the timing is critical or because Hexer is
; using cycle counts to measure time.
;
; Hidden deep in the Sol-20 schematic as a waitstate generator
; that imposes a single waitstate on all I/O instructions. This
; must be accounted for in any timing loops.
;
; This program requires around 16K of zero wait state RAM,
; starting at address 0.
;
; The FD format is not complete for PTDOS because the necessary
; files (DIRECTRY, NEXTID, FSMAP, BOOTLOAD, and SYSGLOBL) are
; not loaded onto the disk.
;
;--------------------------------------------------------------
; HELIOS "FIRM SECTORING" DISK FORMAT
;
; The controller ignores every other hard sector mark, dividing
; the disk into 16 "sectors," numbered 0 to 15. The sector that
; follows the index hole is sector 0.
;
; The disk is divided into "blocks", where a block may be any
; number of bytes from 1 up to 4095, with certain restrictions
; (see below). As such, a block may span up to 13 sectors.
;
; A block must begin at the beginning of a sector, and no block
; may span across from sector 15 to sector 0. (In other words,
; sector 0 must be the beginning of a block.)
;
; One poorly-documented "feature" of the Helios controller is
; that the last sector of a block cannot end fewer than 108
; bytes after a sector mark. Special code in the BW command
; pads blocks to meet this requirement (similar to PTDOS).
;
; Note that the Helios "Erase" command violates this rule,
; creating 1-byte block data fields. Hexer's FT and FD (format)
; commands fix this by writing 256-byte data fields. However,
; fixing this problem requires writing the disk one piece at a
; time: header 0, block 0, header 1, block 1, etc. This
; requires a full disk revolution for each piece, so a track
; format will require 2 revolutions to "Erase" the track, plus
; 32 revolutions to format it. A disk format will thus take
; almost 8 minutes to complete. (This seems outrageous, but
; PTDOS's disk initialization routine within its DISKCOPY
; program does the same thing.)
;
; Each block begins with a block header, which contains 13
; bytes of data that may be used by the operating system. PTDOS
; uses these bytes to indicate the track and sector number,
; as well as for other PTDOS functions. The controller does not
; interpret any bytes within this header. Hexer's HE command
; displays the contents of the header, interpreting its bytes
; as PTDOS does.
;
; The block header and block data fields are bracketed by
; preambles/postambles that include special sync marks, which
; are recognizeable by the controller. The headers and data
; fields are written by separate controller commands.
;
; TRACK 25 (DITRK) is the PTDOS directory track. (See page
; 5-5.) This track is the only track that has interleaved
; sector numbers written into its block headers.  The
; interleave pattern is: 0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15.
; (See FRONE routine in PTDOS's DISKCOPY program.)
;--------------------------------------------------------------
; DISK TIMING
; Since the Helios disk controller uses the system clock for
; timing disk data, and since the Sol-20 system clock is an
; oddball frequency (2.054 MHZ), the Helios subsystem cannot
; really be used in any machine other than a Sol-20. (The Sol-
; 20 has a jumper option for some other CPU frequencies, and
; the Helios will only work right if 2.054 MHz is selected.)
; The Helios will probably work OK in e.g. an Altair at 2 MHz,
; but its data rate will be 2.5% lower than data written on a
; Sol-20 (in addition to any data rate errors due to spindle
; speed differences), meaning that there will likely problems
; interchanging disks between a Sol-20-based Helios system and
; an Altair-based Helios system.
;==============================================================

;Define several overlapping buffers at address 0. The BOOT
;buffer must be at address 0 because the boot block expects to
;execute there. We overlay the format and compare buffers to
;save RAM space.

BOOTAD	equ	0		;boot load address
FMTDAT	equ	0		;..is also the format buffer
CMPBUF	equ	0		;..and also the compare buffer

MAXBPB	equ	4095		;max bytes per block
MXBOOT	equ	MAXBPB		;size of this triple-use buffer
				;PTDOS requires 896 minimum
EXADDR	equ	1000h		;higher address than all of the
				;..above buffers.

;Program constants

LBSIZE	equ	60		;cmd line buffer size
MAXHEX	equ	16		;max bytes/Intel hex record

;ASCII Characters

BS	equ	08h
LF	equ	0Ah
VT	equ	0Bh		;Solos clear screen chr
CR	equ	0Dh
QUOTE	equ	27h		;single quote
SOLDEL	equ	5Fh		;Solos destructuve delete
DEL	equ	7Fh
MODE	equ	80h		;MODE select
DELETE	equ	SOLDEL		;Solos destructuve delete

;CUTER/SOLOS Entry Points

CUTID:	equ	7Fh		;Value of first byte of Cuter

SOLOS	equ	0C000h		;Solos/Cuter base address
SOLID	equ	SOLOS+00h	;00 for solos, 7Fh for Cuter
INIT	equ	SOLOS+01h	;Cold-start SOLOS
RETRN	equ	SOLOS+04h	;Return to SOLOS
SOUT	equ	SOLOS+19h	;Write b to current pseudoport
AOUT	equ	SOLOS+1Ch	;Write b to pseudoport in a
SINP	equ	SOLOS+1Fh	;Read current pseudoport to a
AINP	equ	SOLOS+22h	;Read pseudoport a into a

;SOLOS Pseudoports

KBDPP	equ	0		;Keyboard
VDMPP	equ	0		;VDM
SERPP	equ	1		;Serial port
PARPP	equ	2		;Parallel port
USRPP	equ	3		;User-written port

;Helios Disk format equates

MAXDRV	equ	7		;max unit number
MAXTRK	equ	76		;disk max track #
MAXSEC	equ	15		;max Helios sector #
FULSIZ	equ	320		;bytes for each block's sector
				;..except the first one
MINSEC	equ	108		;Minimum last sector length
				;PTDOS BLKLN subroutine, F14#14
HELEN	equ	13		;total bytes in block header
DITRK	equ	25		;PTDOS dir. track (page 5-5)
MAXBP8	equ	2496		;max bytes per 8-sector block

;Helios Controller Ports (page 7-10)

HEBASE	equ	0F0h		;Helios controller base address
HESTAT	equ	HEBASE		;Status input
HETCMD	equ	HEBASE+1	;Transfer Command output
HETLL	equ	HEBASE+3	;Transfer Length Low output
HETLH	equ	HEBASE+4	;Transfer Length High output
HETAL	equ	HEBASE+5	;Transfer Address High output
				;also clears HESTAT
HETAH	equ	HEBASE+6	;Transfer Address High output
HEDCMD	equ	HEBASE+7	;Drive Command output

;Helios Status bits (page 7-4)

HSTC	equ	01h		;High=Transfer Complete
HSSRDY	equ	02h		;High=SREADY	
HSABRT	equ	04h		;High=Abort
HSCRCE	equ	08h		;High=CRC Error
HSCRCC	equ	10h		;High=CRC Checked
HSRDYN	equ	20h		;Low=Disk Ready (selected, loaded, spinning)
HSSKCN	equ	40h		;Low=Seek Complete
HSIDXN	equ	80h		;Low=Index Hole Present

HTXDON	equ	HSTC+HSSRDY+HSABRT+HSCRCE

;Helios Transfer Command Bits, all active low (page 7-4)

HTNONE	equ	0FFh		;no command
HTERAN	equ	0FEh		;Low=Erase (page 7-36)
HTWRN	equ	0FDh		;Low=Write, High=Read
HTDATN	equ	0FBh		;Low=Data, High=header
HTENN	equ	0F7h		;Low=enable transfer

HRHDR	equ	HTENN				;read header
HRDATA	equ	HTENN and HTDATN		;read data
HWHDR	equ	HTENN and HTWRN			;write header
HWDATA	equ	HTENN and HTWRN and HTDATN	;write data

;Helios Drive Command Bits, all active low (page 7-5)

HDSTPN	equ	0FEh		;Low=step
HDSIN	equ	0FDh		;Low = step in, high=step out
HDDS1N	equ	0FBh		;Drive Select 1, active low
HDDS2N	equ	0F7h		;Drive Select 2, active low
HDRSTN	equ	0EFh		;Restore Disk, active low
HDHL0N	equ	0DFh		;Load Even Head, active low
HDHL1N	equ	0BFh		;Load Odd Head, active low
HDDSN	equ	07Fh		;Select Odd Disk, active low

;===========================================================
;Initialization
; We initialize variables here (rather than just load-
;imitializing them) so that you can retart Hexer from Solos
;by typing EX 1000 (because EXADDR = 1000h).
;===========================================================

	org	EXADDR

;Create local stack

	di			;block ints always
	lxi	sp,STACK

;Clear some RAM variables to FFh

	lxi	h,FFBEG
	lxi	b,FFEND-FFBEG
	mvi	e,0FFh		;fill value=FF

	call	FILLUP

;Clear other RAM variables and some buffers to 00

;	lxi	h,CLRBEG
	lxi	b,CLREND-CLRBEG
	inr	e		;fill value=0

	call	FILLUP

;Start to print the sign-on banner

	call	CILPRT
 db VT				;Clear Sol-20 screen

 db '=====================================',CR
 db '= HEXER Helios Disk Drive Exerciser =',CR
 db '=      Running under',' '+80h

;Detect Solos or Cuter, set the CPU frequency accordingly,
;and print the correct OS for the banner

	call	SOLCUT		;Print Solos or Cuter
	sta	CPUFRQ		;1 selects 2.054 MHz

;Finish printing the banner

FINBAN:	call	ILPRNT
 db '          =',CR
 db '=  Version '
 db ((VERSION AND 0F00h)/256)+'0','.'
 db ((VERSION AND 0F0h)/16)+'0',(VERSION AND 0Fh) +'0'

 db '     by M. Eberhard  =',CR
 db '=====================================',CR,LF

 db 'Type ? for hel','p'+80h

;=======================================================
;Main loop: look for commands from the user & dispatch.
;Error routines often exit directly to MAIN, hosing
;the stack. Therefore, MAIN restores the stack.
;=======================================================
MAIN:	lxi	sp,STACK	;re-create local stack

	lxi	h,0		;cancel step mode
	shld	STEPMO		;..and AUTOMO

	lxi	h,MAIN		;create return address
	push	h

	call	CILPRT		;print prompt
	db	'%'+80h

	call	GETLIN		;get a 0-terminated line
				;(keeps head loaded too)

	call	SKIPSP		;skip leading spaces
	rz			;null line: try again

;Search for command in command table

	lxi	d,CMDTAB-2	;account for 2 initial inx's
	inx	h		;account for initial dcx

NCOM:	inx	d		;skip over execution address
	inx	d
	dcx	h		;back up to 1st chr

	ldax	d		;get table command 1st chr
	ora	a		;Table end?
	jz	CMDERR		;Not found: cmd error

	xra	m		;Test 1st chr
	mov	b,a		;temp save result

	inx	d
	inx	h
	ldax	d		;get table command 2nd chr
	inx	d
	xra	m		;Now test 2nd chr
	ora	b		;both match?
	jnz	NCOM		;n: keep looking

	inx	h		;point hl to next chr
	push	h		;..past 2-byte command

	xchg			;execution address to de
	mov	e,m
	inx	h
	mov	d,m

	pop	h

;Command found. skip following spaces and then dispatch to (de)

	push	d		;execution address onto stack

;Fall into SKIPSP to skip spaces, set Z if end of line

;===Subroutine=========================
;Skip over spaces in command line
;On Entry:
; hl=address in command line
;On Exit:
;  a=next non-space chr on line
;  hl=address of first chr past spaces
;  Z set and a=0 if no characters found
;======================================
SKIPSP:	mov	a,m
	ora	a		;end of cmd already?
	rz			;carry clr, Z set: No more

	cpi	' '
	rnz

	inx	h
	jmp	SKIPSP

;*****==============
;* ? *  Help Command
;*****==============
FHELP:	call	CILPRT
;    1234567890123456789012345678901234567890123456789012345678901234
 db '==HEXER Helios Exerciser=='
 db CR,LF
 db '?B  Buffer help: CB, DB, EB, EH, FB, ID, IL'
 db CR
 db '?I  Immediate help: H, +, -, >, <, 0'
 db CR
 db '?P  Positioner and Spindle help: MS, RE, SK, ST'
 db CR
 db '?R  Read/write help: CB, DF, ET, FT, RB, RH, WB, WC, WH, WT'
 db CR
 db '?S  Hexer state help: SE, C=, D=, U=, V='
 db CR
 db '?T  Print max bytes/block table'
 db CR,LF
 db 'BO  Boot disk 0'
 db CR
 db 'QU  Quit to',' '+80h

;Fall into SOLCUT

;===Subroutine====================
;Print Solos or Cuter
;On Entry:
;  SOLID=0 for Solos, NZ for Cuter
;On Exit:
;  a=1 for Solos, 0 for Cuter
;=================================
SOLCUT:	lda	SOLID		;Solos or Cuter?
	ora	a		;0 means Solos
	jnz	NOSOL		;otherwise assume Cuter

	call	ILPRNT
	db	'Solo','s'+80h

	mvi	a,1		;indicate Solos for return
	ret

NOSOL:	call	ILPRNT
	db	'Cute','r'+80h

	xra	a		;indicate Cuter for return
	ret

;******=====================
;* ?B *  Buffer Help Command
;******=====================
FBHELP:	call	CILPRT
;    1234567890123456789012345678901234567890123456789012345678901234
 db 'BUFFER COMMANDS'
 db CR,LF
 db 'DB <A> <B> Display block data buffer from <A> through <B>'
 db CR
 db 'EB <A>     Edit block data buffer at <A>'
 db CR
 db 'EH         Display block header buffer'
 db CR
 db 'EH <i> <H> Write <H> to block header buffer item <i>'
 db CR
 db 'FB <H>     Fill block data buffer with <H>'
 db CR
 db 'ID <C>     Dump block buffer in Intel hex out serial port'
 db CR
 db '           <C> = byte count, defaults to value from header'
 db CR
 db 'IL <O>     Load Intel hex from serial port into data buffer'
 db CR
 db '           Offset <O> is subtracted from record addresses.'
 db CR,LF
 db 'All values are in hex','.'+80h

	ret

;******========================
;* ?I *  Immediate Help Command
;******========================
FIHELP:	call	CILPRT
;    1234567890123456789012345678901234567890123456789012345678901234
 db 'IMMEDIATE COMMANDS'
 db CR,LF
 db 'H  Toggle head load'
 db CR
 db '+  Step in one track'
 db CR
 db '-  Step out one track'
 db CR
 db '>  Nudge the head in then out 1 track'
 db CR
 db '<  Nudge the head out then in 1 track'
 db CR
 db '0  Restore to track ','0'+80h

	ret

;******=====================================
;* ?P *  Positioner and Spindle Help Command
;******=====================================
FPHELP:	call	CILPRT
;    1234567890123456789012345678901234567890123456789012345678901234
 db 'POSITIONER AND SPINDLE COMMANDS'
 db CR,LF
 db 'MS      Measure spindle rev time (Target: 166.7 mS) (1)'
 db CR
 db 'RE      Restore to Track 0'
 db CR
 db 'SK <T1> Fast-Seek to <T1>'
 db CR
 db 'SK <T1> <T2> [...<Tn>]  Fast-seek between tracks (1)(2)'
 db CR
 db 'ST <T1>  Slow-step to Track <T1>'
 db CR
 db 'ST <T1> <T2> [...<Tn>]  Slow-step between tracks (1)(2)'
 db CR,LF
 db 'Track numbers <Tn> are between 0 and',' '+80h

	lda	DECMO
	ora	a		;decimal or hex?
	jz	FPH1

	call	ILPRNT
 db '76 decimal','.'+80h

	jmp	FPH2

FPH1:	call	ILPRNT
 db '4Ch','.'+80h

FPH2:	call	CILPRT
 db 'SK or ST with no arguments use the previous track list.'
 db CR
 db '(1) Q quits. Immediate commands control head load and position.'
 db CR
 db '(2) Space repeats, A toggles automatic mode','.'+80h

	ret

;******=========================
;* ?R *  Read/Write Help Command
;******=========================
FRHELP:	call	CILPRT
;    1234567890123456789012345678901234567890123456789012345678901234
 db 'READ/WRITE COMMANDS'
 db CR
 db 'BW <B> [<C>] Write data buffer to block <B> (1)'
 db CR
 db 'CB <B> [<C>] Compare block <B> to data buffer (1)'
 db CR
 db 'ET           Erase current track'
 db CR
 db 'FD <H>       Format entire disk with hex value <H>'
 db CR
 db 'FT <H>       Format current track with hex value <H>'
 db CR
 db 'RB <B> [<C>] Read block <B> into data buffer (1)'
 db CR
 db 'RH <B>       Read block <B> header into header buffer'
 db CR
 db 'WC <H1> <H2>..<Hn>  Write hex pattern continuously to track'
 db CR
 db 'WH <B>       Write header buffer to block <B> header'
 db CR
 db 'WT <H1> <H2>..<Hn>  Write hex pattern to entire current track'
 db CR
 db 'Blocks <B> are between 0 and',' '+80h

	lda	DECMO
	ora	a		;decimal or hex?
	jz	FRH1

	call	ILPRNT
 db '1','5'+80h

	jmp	FRH2

FRH1:	call	ILPRNT
 db '0F','h'+80h

FRH2:	call	ILPRNT
 db ', though the track will have'
 db CR
 db 'fewer blocks if any blocks span more than 1 sector.'
 db CR
 db '(1) If byte count <C> is not provided then the number of data'
 db CR
 db '    bytes per block comes from the block',QUOTE,'s header','.'+80h

	ret

;******==========================
;* ?S *  Hexer State Help Command
;******==========================
FSHELP:	call	CILPRT
;    1234567890123456789012345678901234567890123456789012345678901234
 db 'SETTINGS COMMANDS'
 db CR,LF
 db 'SE      Show settings and state'
 db CR
 db 'C={0/1} Set 8080 frequency to {2.0000 MHz/2.0454 MHz}'
 db CR
 db '        and set I/O wait states to {0/1}'
 db CR
 db 'D={0/1} Enter track and block numbers in {hex/decimal}'
 db CR
 db '        (Other values are always hex.)'
 db CR
 db 'U={0-7} Select disk unit (evens are left, odds are right)'
 db CR
 db 'V={0/1} Verbose seek mode {off/on','}'+80h

	ret

;******=====================================
;* ?T *  Print Max Bytes/Block Table Command
;******=====================================
FTHELP:	call	CILPRT
 db ' Sectors   Max bytes',CR
 db 'per Block (hex) (dec',')'+80h

	mvi	b,1		;Sectors/block
	lxi	h,100h

FTHLUP:	call	CILPRT
	db	' ',' '+80h

	mov	a,b		;need an extra space?
	cpi	10
	mvi	a,' '
	cc	PRINTA

	mov	a,b
	call	PADEC

	call	ILPRNT
	db	'      ',' '+80h

	mov	a,h		;hex
	call	PHXNIB
	mov	a,l
	call	PAHEX

	call	ILPRNT
	db	' ',' '+80h

	mov	a,b		;need an extra space?
	cpi	4
	mvi	a,' '
	cc	PRINTA

	push	h		;decimal
	push	b
	call	PHLDEC
	pop	b
	pop	h

	lxi	d,140h
	dad	d

	inr	b
	mov	a,b
	cpi	13
	jc	FTHLUP

	lxi	h,0FFFh		;max value
	jz	FTHLUP

	ret

;******============================
;* D= * Select decimal mode Command
;******
;On Entry:
;  hl=addr of 1st chr after '='
;On Exit:
; DECMO updated
;==================================
FSETD:	call	GET01		;get 1 chr from command line
	sta	DECMO

;Fall into PDECMO

;===Subroutine=====
;Print decimal mode
;==================
PDECMO:	call	CILPRT
	db	'Enter track and block numbers in',' '+80h

	lda	DECMO
	ora	a

	jz	PHEXMO

	call	ILPRNT
	db	'decima','l'+80h

	ret

PHEXMO:	call	ILPRNT
	db	'he','x'+80h

	ret

;******=========================
;* C= * Select CPU Speed Command
;******
;On Entry:
;  hl=addr of 1st chr after '='
;On Exit:
; CPUFRQ updated
;===============================
FSETC:	call	GET01		;get 1 chr from command line
	sta	CPUFRQ

;Fall into PFREQ

;===Subroutine======
;Print CPU frequency
;===================
PFREQ:	call	CILPRT
	db	'8080 frequency setting: 2.','0'+80h

	lda	CPUFRQ
	ora	a
	jnz	p204		;was 1?

	call	ILPRNT
	db	'000 MHz, 0 waitstat','e'+80h
	ret

P204:	call	ILPRNT
	db	'454 MHz, 1 waitstat','e'+80h
	ret

;===Subroutine=======================
;Check to see that a unit is selected
;Ask user for the unit if not
;Trashes all registers
;====================================
CHKSEL:	lda	CURDSK		;current disk
	ora	a		;any selected?
	rp			;y: ok

;No unit has yet been selected. Ask the user which unit.

CHKSLP:	call	CILPRT
	db	'Which unit (0-7)?',' '+80h

	call	GETLIN		;get a 0-terminated line

;Fall into FSETU to get the unit from the user

;******==============================
;* U= * Set Unit Number {0-7} Command
;******
;On Entry:
;  hl=addr of 1st chr after '='
;On Exit:
;  Heads are unloaded
;  CURDSK and HDCSAV are current
;  The unit has been restored to 0
;====================================
FSETU:	mvi	c,MAXDRV+1	;max value+1
	call	GETVAL		;result in a
	jz	CMDERR		;must specify

;Fall into SETU

;===Subroutine====================
; Set Unit Number {0-7}
;On Entry:
;  a=desired unit number
;On Exit:
;  Heads are unloaded
;  CURDSK and HDCSAV are current
;  The unit has been restored to 0
;=================================
SETU:	lxi	h,CURDSK
	mov	b,m	;temp remember old unit
	mov	m,a	;remember current unit

;Put drive and disk bits in place for HEDCMD register

	rlc		;Unit bits in place
	ani	0Ch	;just the unit bits
	mov	c,a	;Temp save

	mov	a,m	;original result
	ani	01h	;look at disk bit
	rrc		;move it to bit 7
	ora	c	;combine with unit bits
	cma		;all bits active low
	sta	HDCSAV	;save

	call	PUNIT	;report selection

	mov	a,m	;different unit, same drive?
	xra	b
	ani	0FEh	;(just the lsb will be different)
	cnz	RESTOR	;n: restore

	lda	HDCSAV	;actually select the drive
	out	HEDCMD	;..in case we did not restore

	jmp	PTRACK	;announce track too

;******==============================
;* V= * Set Verbose Seek Mode Command
;******
;On Entry:
;  hl=addr of 1st chr after '='
;On Exit:
;  VERBMO updated
;====================================
FSETV:	call	GET01
	sta	VERBMO

;Fall into PVERB

;===Subroutine==========
;Print Verbose Seek Mode
;=======================
PVERB:	call	CILPRT
	db	'Verbose seek mode: ','o'+80h

	lda	VERBMO
	ora	a		;0 means disabled
	jz	PVERB1

	call	ILPRNT
	db	'n'+80h
	ret

PVERB1:	call	ILPRNT
	db	'f','f'+80h
	ret

;******=======================================
;* BO *  Boot from Disk Command
;******
;Loads and executes Helios disk 0 boot sector.
;Unlike SOlos's BO command, this will load
;whatever block size is on the first one on
;the disk. (Solos always loads 832 bytes.)
;=============================================
FBOOT:	xra	a		;boot from disk 0
	call	SETU		;select disk 0

;Check that the disk is loaded and ready, and restore when so

	call	CHKDSK
	call	RESTOR

;Find and read block 0's header, to get the block size

	mvi	c,0		;boot from block 0
	call	FNDBLK		;find and read block header c
	ani	0C7h		;(7)Ignore CRC errors.
	jnz	HDRERR		;(10)..any other error aborts

	lhld	HDRBUF+9	;(10)byte count from header

;Look at the block size and make sure boot image will fit

	mvi	a,MXBOOT and 0FFh	;(7)
	sub	l			;(4)
	mvi	a,MXBOOT/256		;(7)
	sbb	h			;(4)
	jc	BOTERR			;(10)too big?	

;Load block 0 into memory at BOOTAD (address 0000)

	xchg			;(4) de=byte count from header
	lxi	b,HRDATA*256	;(10)no index pulses allowed
				;..read block data command
	lxi	h,BOOTAD	;(10)load data here
	call	HELIOS		;(17+75)read block into RAM
	push	psw		;save a=data errors

	call	WTSRDY		;wait for command to complete
	pop	b		;b=data errors
	ora	b		;combine SREADY timeout error
	mov	b,a

	in	HESTAT		;check for any errors again
	ani	HSABRT+HSCRCE	;CRC error or abort?

	ora	b		;combine errors

;Report errors, if any

	cnz	DARERR		;report any errors
	rc			;error: return back to MAIN

;No errors: jump to successfully loaded code

	rst	0

;-------------------------------------------
;Error: Boot image is too big. hl=image size
;-------------------------------------------
BOTERR:	call	CILPRT
	db	'Boot image is too big:',' '+80h
	call	PHLDEC
	call	ILPRNT
	db	' byte','s'+80h
	ret

;******============================
;* CB *  Compare Block Data Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;==================================
FBCOMP:	lxi	d,CMPBUF	;put data here
	push	d
	call	DREAD		;get user specs & read block
	pop	d

	lxi	h,0		;no errors so far
	shld	ERRCNT

	lxi	h,DATBUF

;Compare the block in CMPBUF to that in DATBUF
;bc = byte count
;de = CMPBUF
;hl = DATBUF

CMPLUP:	ldax	d
	cmp	m
	cnz	CMPERR

	inx	h
	inx	d
	dcx	b
	mov	a,b
	ora	c
	jnz	CMPLUP

;Done: report results

	call	CILPRT
	db	'Total mismatches:',' '+80h
	lhld	ERRCNT
	jmp	PHLDEC		;print and done

;---Local subroutine--------------
;Report compare error
;On Entry:
; hl = DATBUF address
; de = CMPBUF address
;trashes psw
;---------------------------------
CMPERR:	push	d
	push	h

	lxi	d,-DATBUF	;calc address within block
	dad	d		;..subtract off base address

	xchg			;address within block to de
	call	PADDR		;print address and a colon

	lhld	ERRCNT		;bump error count
	inx	h
	shld	ERRCNT

	pop	h
	call	ILPRNT
	db	' Expected:',' '+80h
	mov	a,m		;data from DATBUF
	call	PAHEX

	pop	d
	call	ILPRNT
	db	' Found:',' '+80h
	ldax	d		;data from CMPBUF
	call	PAHEX

	call	TESTQ		;quit or pause?
	cpi	' '		;space causes pause
	cz	WAITQS		;pause until another space

	ret

;******=============================
;* DB *  Display Data Buffer Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;===================================
FBDISP:	call	GETADR		;get start address in de

	push	d		;save start address

	call	GETADR		;get end address in de
	jnz	FBDSP1		;Anything entered?

	lxi	d,MAXBPB-1	;n: go to end
FBDSP1:

	pop	h		;start address into hl
	push	d		;end address on stack

	xchg			;start address to de
	lxi	h,DATBUF	;buffer pointer
	dad	d		;hl=offset into buffer

FBDSP2:	call	PADDR		;print this row's address

	mov	a,e		;space over if necessary
	ani	0Fh		;..so that data lines up
	jz	FBDSP4

FBDSP3:	call	ILPRNT
	db	'  ',' '+80h
	dcr	a
	jnz	FBDSP3

;Print 16 bytes in hex, separated by spaces

FBDSP4:	mov	a,m		;data
	call	PAHEX

	call	ILPRNT		;followed by a space
	db	' '+80h	

	xthl			;end address to hl
	mov	a,e		;compare to current address
	sub	l
	mov	a,d
	sbb	h		;just did end address?
	jnc	FBDSP5		;y:done
	xthl			;address pointer

	inx	d		;next
	inx	h

	mov	a,e
	ani	0Fh		;new line every 16
	jnz	FBDSP4

	call	TESTQ		;quit or pause?
	cpi	' '		;space causes pause
	cz	WAITQS		;pause until another space

	jmp	FBDSP2

FBDSP5:	pop	h		;chuck end address
	ret

;******=================================
;* EB *  Data Buffer Edit Command
;******
;Allow user to edit data buffer contents
;On Entry:
;  hl=address of 1st chr after cmd
;=======================================
FBEDIT:	call	GETADR		;get address in de

FBEDT0:	lxi	h,DATBUF	;buffer base address
	dad	d		;hl points to buff addr

FBEDT1:	call	PADDR		;print address

FBEDT2:	mov	a,m		;data
	call	PAHEX

	call	ILPRNT		;separator
	db	'.'+80h

	push	h
	push	d

;Loop to get input from console and store it in LINBUF,
;until either a space (end of value) or a Q (to quit)

	lxi	h,LINBUF
	push	h
	mvi	c,0		;chr count

FBEDT3:	call	GETKBD		;get, echo keyboard chr

	cpi	'Q'		;Done?
	jz	MAIN

	cpi	DELETE		;GETKBD converts BS & DEL
	jz	FBEDT6		;y:ignore

	cpi	' '		;space means done
	jz	FBEDT4
	jc	CMDERR		;below is bogus

	mov	m,a		;save new chr
FBEDT6	inx	h		;...in LINBUF
	inr	c
	jmp	FBEDT3

;Got a complete value input in LINBUF. Now convert the
;hex value to binary and write it to memory, or skip this
;memory location if no value was given.

FBEDT4:	mvi	m,0		;null termination after value
	pop	h		;point to LINBUF for GETHXA			
	call	GETHXA		;convert (hl) to hex
				;..sets Z if no input

	pop	d		;buffer address
	pop	h		;actual RAM address

	jz	FBEDT5		;no input=no change?

	mov	m,a		;update w/ low byte

;Next memory location.

FBEDT5:	inx	h
	inx	d

	mov	a,e		;need an address?
	ani	07h		;every 8
	jnz	FBEDT2

	mov	a,d		;end of buffer?
	cpi	MAXBPB/256
	jc	FBEDT1

	lxi	d,0		;block addr count
	jmp	FBEDT0		;wrap to beginning

;******===================================
;* EH *  Edit Header Buffer Command
;******
;Allow user to edit header buffer contents
;On Entry:
;  hl=address of 1st chr after cmd
;  Z set if end of cmd line already
;=========================================
FHEDIT:	jz	HDISP		;no params: display

;Get and validate the item number, and convert
;it to an offset into the header buffer

	mvi	c,9+1		;9 items in a header
	call	GETVAL		;get item number in a

	cpi	7		;adjust for 2-byte items
	jc	FHE1		;..in the header format
	inr	a
	cpi	10		;there are 2 of them.
	jc	FHE1
	inr	a
FHE1:				;a=offset to item number
	mov	e,a
	mvi	d,0		;de=offset to item number
	push	d

;Get and validate the new item value (in hex)

	call	GETHEX		;get value in de
				;..(may be 2 or 4 digits)
	jz	CMDERR		;must have a value

;Write the new value to the item in the buffer

	pop	b		;offset into buffer
	lxi	h,HDRBUF
	dad	b		;hl=address of item

	mov	m,e		;low byte
	mov	a,c		;2-byte item?
	cpi	6
	jc	FHE2
	cpi	8
	jz	FHE2
	inx	h		;y: write high byte
	mov	m,d
FHE2:

;Fall into HDISP to display the entire header buffer

;===Subroutine====================
;Display contents of header buffer
;=================================
HDISP:	call	CILPRT
	db	'ID   FUNCTION  HEX   DE','C'+80h

	lxi	h,HDRBUF

;Display each header component in decimal and hex

	call	CILPRT
	db	'0      Sector:',' '+80h
	call	FHDS1
	call	CILPRT
	db	'1       Track:',' '+80h
	call	FHDS1
	call	CILPRT
	db	'2 FBCP Sector:',' '+80h
	call	FHDS1
	call	CILPRT
	db	'3  FBCP Track:',' '+80h
	call	FHDS1
	call	CILPRT
	db	'4 BBCP Sector:',' '+80h
	call	FHDS1
	call	CILPRT
	db	'5  BBCP Track:',' '+80h
	call	FHDS1
	call	CILPRT
	db	'6     File ID:',' '+80h
	call	FHDS2
	call	CILPRT
	db	'7     Sectors:',' '+80h
	call	FHDS1
	call	CILPRT
	db	'8       Bytes:',' '+80h
	call	FHDS2
	call	CILPRT
	db	'9    Reserved:',' '+80h

;Fall into FHDS1

;---Local Subroutine------------------
;Print 1-byte value in hex and decimal
;On Entry:
;  hl points to byte
;On Exit:
;  hl points past byte
; trashes psw
;-------------------------------------
FHDS1:	mov	a,m
	call	PAHEX
	call	ILPRNT
	db	'h  ',' '+80h
	mov	a,m
	call	PADEC
	inx	h		;skip the byte we just printed
	ret

;---Local Subroutine------------------
;Print 2-byte value in hex and decimal
;On Entry:
;  hl points to low-order byte
;On Exit:
;  hl points past high-order byte
; trashes psw,bc,de
;-------------------------------------
FHDS2:	inx	h	;get msb
	mov	a,m
	mov	d,a	;save for decimal
	call	PAHEX
	dcx	h
	mov	a,m	;lsb
	call	PAHEX
	call	ILPRNT
	db	'h',' '+80h
	mov	a,m	;lsb again
	push	h
	mov	l,a
	mov	h,d	;msb	
	call	PHLDEC
	pop	h
	inx	h	;skip lsb
	inx	h	;skip msb
	ret

;******====================================================
;* ET *  Erase Track Command
;****** (See Helios II Disk Memory System Manual page 7-36)
;On Entry:
;  Z set if end of cmd line already
;==========================================================
FERASE:	jnz	CMDERR		;no parameters allowed

	call	CILPRT
 db 'Eras','e'+80h

	call	PWIPE		;common permission code

;Fall into TERASE

;===Subroutine=============
;Erase selected track
;Rude exit to main if error
;Trashes psw,de,hl
;==========================
TERASE:	call	CHKDSK		;check for disk, load head
	out	HETAL		;clear errors

	mvi	a,HTERAN	;issue erase command
	out	HETCMD		;This kicks off the erase

;erase for up to 2 full revs

	call	WINDEX		;wait for next INDEX
	cz	WINDEX		;wait for another INDEX
				;..if we found one

;Done: end the Helios erase command

	push	psw		;save error flags
	mvi	a,HTNONE
	out	HETCMD		;This ends the erase
	pop	psw		;error flags

	rz			;any errors?

;Explain error that occured while hunting for Index

	call	CILPRT
	db	'Erase error',':'+80h

	call	EXPERR		;explain error bits in a
	ret

;******============================
;* FB *  Fill Data Buffer Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;==================================
FBFILL:	call	GETHXA		;get fill value in e

;Fall into FILLDB

;===Subroutine==================
;Fill data buffer with constant
;On Entry:
;  e = fill value
;Trashes psw,bc,hl
;=============================== 
FILLDB:	lxi	h,DATBUF
	lxi	b,MAXBPB

;Fall into FILLUP

;===Subroutine=====================
;Fill RAM with constant
;On Entry:
;  e = fill value
;  hl = start address
;  bc = byte count
;On Exit:
;  bc = 0
;  hl = next addres past filled RAM
; trashes psw
;================================== 
FILLUP:	mov	m,e
	inx	h
	dcx	b
	mov	a,b
	ora	c
	jnz	FILLUP

	ret

;******====================================================
;* FD *  Format Disk Command
;******  This installs correct (for PTDOS) track and block
;        numbers in all block headers, and writes 256 bytes
;        of the specified data (which defaults to 0) to all
;        block data fields on every track of the disk. (The
;        data fields must be rewritten because the Helios
;        Erase command creates 1-byte data fields, in
;        violation of the Helios requirement that data
;        fields be at least 108 bytes (MINSEC bytes) long.)
;        On the directory track, the block numbers get
;        interleaved.
;On Entry:
;  hl=address of 1st chr after cmd
;==========================================================
FDFRMT:	call	GETHXA		;get e=data value (default 0)

;fill the format data buffer with the specified byte in e

	lxi	h,FMTDAT	;destination
	lxi	b,256		;byte count
	call	FILLUP

;Warn, and ask permission

	call	CILPRT
	db	'Format unit',' '+80h

	lda	CURDSK		;current unit number
	call	PASCII

	call	WRNING
	call	ILPRNT
	db	'disk.'
	db	CR
	db	'This takes about 8 minutes','.'+80h

	call	ASKRDY		;get confirmation

	call	CILPRT
	db	'Each dot is one trac','k'+80h

;Restore the disk to track 0

	call	RESTOR		;restore the disk

;Format each track on the disk, and restore when done

FDLOOP:	call	TFRMAT		;format the track

	lda	CURTRK
	cpi	MAXTRK		;did the last track?
	jz	RESTOR		;y: restore and done

	inr	a		;next track
	call	SEKSTP		;seek the next track

	call	ILPRNT
	db	'.'+80h		;pacifier

	jmp	FDLOOP		;next track

;******====================================================
;* FT *  Format Track Command
;******  This installs correct (for PTDOS) track and block
;        numbers in all block headers, and writes 256 bytes
;        of the specified data (which defaults to 0) to all
;        block data fields on the current track. (The data
;        fields must be rewritten because the Helios Erase
;        command creates 1-byte data fields, in violation
;        of the Helios requirement that data fields be at
;        least 108 bytes (MINSEC bytes) long.) If this is
;        the directory track, then the block numbers get
;        interleaved.
;On Entry:
;  hl=address of 1st chr after cmd
;==========================================================
FTFRMT:	call	GETHXA		;get e=data value (default 0)

;fill the format data buffer with the specified byte in e

	lxi	h,FMTDAT	;destination
	lxi	b,256		;byte count
	call	FILLUP

	call	CILPRT
	db	'Forma','t'+80h

	call	PWIPE		;common permission code

;Fall into TFRMAT to format the track

;===Subroutine=============================
;Format one track, report errors on comsole
;On Entry:
;  e = data field fill value
;  head is on the desired track
;  The user has given permission
;On Exit:
;  Trashes all registers
;==========================================
TFRMAT:	call	TERASE		;Lay down bare sectors

;Install this track number in the format header

	lda	CURTRK		;current track number
	sta	FMTHDR+1

;loop through all 16 sectors, writing first the header, and
;then the data. This will spin the disk a rev between each
;operation. This is quite slow, but works. (It is also how
;DISKCOPY formats.)

	mvi	c,0		;initial physical sec number

FMTLUP:	call	TESTQ		;user-abort opportunity

;Prepare and write the block header to disk
;c=physical sector number

	call	INTRLV		;compute logical block number
	lxi	h,FMTHDR
	mov	m,a		;put it in the format header

	push	b		;save c=physical sector number
	call	HWRITE		;write block header from (hl)
	pop	b
	jnz	FMTHER		;any errors?

;Re-seek the sector and write the block data to disk

	call	FNDBKH		;(203+17)find header again
	jnz	FMTHER		;(10)errors?

	mvi	b,HWDATA	;(7)write block data command
				;   c=physical block number	
	lxi	d,256		;(10)byte count
	lxi	h,FMTDAT	;(10)data source
	call	HELIOS		;(17+75)Write block fm FMTDAT

	jnz	FMTDER		;any errors?

;Next sector until done

	mov	a,c		;next physical sector
	inr	c
	cpi	MAXSEC		;done?
	jnz	FMTLUP		;n: do another sector

;delay for a rev to give plenty of time to finish writing

	call	WINDEX
	ret			;y: all done with track

;---Local Subroutine-----------------------------------------
;Compute logical (interleaved) block only if on index track.
;(See FRONE routine in DISKCOPY.)
;Interleave:
; 0,8,1,9,2,10,3,11,4,12,5,13,6,14,7,15
;On Entry:
;   c=physical sector number
;   CURTRK=current track number
;On Exit:
;   a=logical blocknumber
;------------------------------------------------------------
INTRLV:	lda	CURTRK
	cpi	DITRK		;index track?
	mov	a,c
	rnz			;n: logical=physical

	rar			;interleave
	rnc
	ori	08h
	ret

;-------------------------
;Error detected, code in a
;-------------------------
FMTHER:	push	psw		;save error codes
	jc	FMTHE1		;error during write or read?

	call	CILPRT
	db	'Header rea','d'+80h
	jmp	FMTERR

FMTHE1:	call	CILPRT
	db	'Header writ','e'+80h
	jmp	FMTERR

FMTDER:	push	psw		;save error codes

	call	CILPRT
	db	'Data writ','e'+80h

FMTERR:	mvi	a,HTNONE
	out	HETCMD		;end failed command

	call	ILPRNT
	db	' error on track',' '+80h

	lda	CURTRK		;track number
	call	PADEC

	call	ILPRNT
	db	', sector',' '+80h

	lda	FMTHDR		;sector number
	call	PADEC

	pop	psw		;error codes
	jmp	HDWERR

;******==============================
;* ID * Intel Hex Dump Buffer Command
;******
; Format: ID [byte count]
;On Entry:
;  hl=address of 1st chr after cmd
;====================================
FIDUMP:	call	GETHEX		;get de=byte count from user
	jnz	FID0		;any value provided?

	lhld	HDRBUF+9	;n: get value from header
	xchg

FID0:	mov	a,d		;zero value?
	ora	e
	jz	FID1		;y: dump whole buffer

	mov	a,d		;valid byte count?
	ani	0F0h		;max is 0FFFh
	jz	FID2		;truncate if larger

FID1:	lxi	d,MAXBPB	;max bytes in a block

FID2:	lxi	h,DATBUF	;data is here
	lxi	b,0		;init record address

; bc = record address
; de = byte count
; hl = buffer address

FID3:	push	d		;remaining bytes

;compute d=this record's byte count: max = MAXHEX

	mov	a,d
	mvi	d,MAXHEX
	ora	a
	jnz	FID4		;>256 bytes left?

	mov	a,e
	cpi	MAXHEX		;>MAXHEX bytes left?
	jnc	FID4

	mov	d,e		;n:short last record

FID4:	mvi	e,0		;initial checksum
	xthl			;hl=remaining bytes
	mov	a,l		;subtract this record's
	sub	d		;..byte count
	mov	l,a
	mov	a,h
	sbb	e		;e=0: borrow
	mov	h,a
	xthl			;remaining byte count

	mvi	a,':'		;record start
	call	SEROUT

	mov	a,d		;byte count
	call	SHEXO

	mov	a,b		;record address
	call	SHEXO
	mov	a,c
	call	SHEXO

	xra	a		;record type 0
	call	SHEXO

;Loop to send up to MAXHEX data bytes

FID5:	mov	a,m		;loop to write data
	call	SHEXO

	inx	h
	inx	b
	dcr	d
	jnz	FID5

	xra	a		;compute checksum
	sub	e
	call	SHEXO		;and send it

	call	SCRLF		;end of line

	pop	d		;remaining byte count

	call	TESTQ		;abort opportunity

	mov	a,d		;any left?
	ora	e
	jnz	FID3		;y: send another record

;Create end record

	mvi	a,':'		;record start
	call	SEROUT

	mvi	d,5		;5 bytes of 0

FID6:	xra	a
	call	SHEXO
	dcr	d
	jnz	FID6

; Fall into SCRLF to end line and end hex dump

;---Local Subroutine-------
;Send CR LF to serial port
;Trashes psw
;--------------------------
SCRLF:	mvi	a,CR		;end of line
	call	SEROUT
	mvi	a,LF
	jmp	SEROUT

;---Local Subroutine-------------------------------
;Convert a to hex and send it to Sol-20 serial port
;On Entry:
;   a = value to send
;   e = checksum so far
;On Exit:
;   Checksum in e updated
;Trashes psw
;--------------------------------------------------
SHEXO:	push	psw		;save value

	add	e		;update checksum
	mov	e,a

	pop	psw		;get high nibble
	push	psw		;save for low nibble
	rar			;shift high nibble down
	rar
	rar
	rar
	call	SHXNIB		;send high nibble

	pop	psw		;recover low nibble
		
;Fall into SHXNIB

;---Local Subroutine-------
;Print hex nibble in a<3:0>
;Trashes psw
;--------------------------
SHXNIB:	ani	0Fh		;get nibble
	adi	'0'		;convert to hex
	cpi	'9'+1
	jc	SHXN1
	adi	'A'-'9'-1
SHXN1:

; Fall into SEROUT

;---Local Subroutine---------
;Send a to Sol-20 serial port
;Trashes psw
;----------------------------
SEROUT:	push	b
	mov	b,a
	mvi	a,SERPP
	call	AOUT
	pop	b
	ret

;******==============================
;* IL * Intel Hex Load Buffer Command
;******
;Format: IL <Address Offset>
;On Entry:
;  hl=address of 1st chr after cmd
;====================================
FILOAD:	call	GETHEX		;get de=address offset
	shld	OFFSET		;defaults to 0

;Tell user to begin sending hex

	call	CILPRT
	db	'Send hex file now','.'+80h

;Wait for colon to start record

GETCO:	call	SERIN		;get one serial port chr
	cpi	':'
	jnz	GETCO

;Loop to shift the following into registers:
; d: address high byte
; e: address low byte
; b: record type (gets chucked)
; l: record byte count
; c: checksum of the above 4 bytes

	lxi	b,4*256		;b=loop 4 times, c=reset cksum

CATLP:	mov	l,d		;shift through regs so that
	mov	d,e		;..all is in place after
	mov	e,b		;..4 passes through loop
	call	SHEXI		;..data is in b, cksm in a & c
	dcr	h
	jnz	CATLP

	mov	b,l		;record byte count

;Subtract address offset from address in de, put result in hl

	lhld	OFFSET		;file address offset
	mov	a,e		;16-bit subtract
	sub	l
	mov	l,a
	mov	a,d
	sbb	h
	mov	h,a

;and add DATBUF offset to address in hl

	lxi	d,DATBUF
	dad	d

	mov	e,b		;record byte count

;Loop to get data
; c: checksum
; e: record byte count
; hl: buffer address for data

	mov	a,e		;0 byte record?
	ora	a
	jz	GCKSUM

	mov	d,e

FIDLUP:	call 	SHEXI		;get b= data byte

	mov	a,l		;fit in buffer?
	sui	(DATBUF+MAXBPB) and 0FFh
	mov	a,h
	sbi	(DATBUF+MAXBPB)/256	;16-bit compare
	jnc	NOROOM		;n: don't write it
	mov	m,b		;y: write data into buffer
NOROOM:
	inx	h		;next byte
	dcr	d
	jnz	FIDLUP

;Get & validate checksum	

GCKSUM:	call 	SHEXI		;get a=checksum, Z if zero
	jnz	CSMERR		;not 0:error

	mov	a,e		;0-byte record?
	jnz	GETCO		;n: get another record

	ret			;y: done

;---Local Subroutine-----------
; Get hex byte from serial port
;On Entry:
; c = checksum so far
;On exit:
; b = received byte
; a = c = updated checksum
; Z set if c=0
;------------------------------
SHEXI:	call	SHINIB		;get high nibble
	rlc			;move it up
	rlc
	rlc
	rlc
	mov	b,a

	call	SHINIB		;get low nibble
	add	b		;combine nibbles
	mov	b,a


	add	c		;update checksum
	mov	c,a
	ret

;---Local Subroutine-----------------------------------
; Get hex nibble from serial port, convert it to binary
;On exit:
; a = validated, received byte
;Rude exit if bad hex
;------------------------------------------------------
SHINIB:	call	SERIN
	sui	'0'
	cpi	'9'+1		;0-9?
	rc			;y: done

	sui	'A'-'9'-1	;ASCII gap
	cpi	6
	jnc	HEXERR		;bad hex chr?

	adi	10
	ret

;---------------
;Hex load errors
;---------------
HEXERR:	call	CILPRT
	db	'Abort: bad hex characte','r'+80h
	jmp	MAIN

CSMERR:	call	CILPRT
	db	'Abort: checksum erro','r'+80h
	jmp	MAIN

;******===============================================
;* MS *  Continuously Measure Spindle Rev Time Command
;******===============================================
FMOTOR:	call	CHKSEL		;any unit selected?

	call	CILPRT
	db	'Measuring spindle.',' '+80h
	call	Q2QUIT		;Q to quit

MOTLUP:	call	IMEASR		;measure spindle

	in	HESTAT		;Keep head loaded, if it is
	out	HETAL		;old versions of the controller

	call	TESTQ		;user quit?
	call	CKIMED		;immediate command?
	jmp	MOTLUP		;no: keep going

;******===========================
;* RB *  Read Block Data Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;=================================
FDREAD:	lxi	d,DATBUF	;where to put the data


;Fall into DREAD to read the block

;===Subroutine========================================
;Read a block of data
;On Entry:
;  hl=address of 1st chr after cmd
;     may include both a block number and a byte count
;  de = data buffer address
;On Exit:
;  bc = number of bytes read
;  data is in specified data buffer
;  data byte count has been printed
;=====================================================
DREAD:	push	d		;remember data buffer address

	call	BSETUP		;(291+17)c=block number,
	mov	a,b		;(5)..we still read the block
	ani	0C7h		;(7)..if just a CRC error

	pop	h		;(11)get data buffer address
	jnz	HDRERR		;(10)..any other error aborts
	push	d		;(10)de=byte count

	push	b		;(10)save header errors
	lxi	b,HRDATA*256+MAXSEC	;(10)index pulses ok,
				;..read block data command
	call	HELIOS		;(17+75)read block into buffer

;463 cycles <= 232 uS from end of header CRC to transfer
;command issued. Helios requires this to be less than 300 uS.

	pop	b		;b=header errors
	push	psw		;save a=data errors

	mov	a,b		;report any header errors
	ora	a		;..since a CRC error would
	cnz	HDRERR		;..not be reported earlier

;The data block may take a while to complete after the DMA
;transfer is done, particularly for blocks that were padded
;during write. Wait for SREADY, then check for errors again.

	call	WTSRDY		;wait for command to complete
	pop	b		;b=data errors
	ora	b		;combine SREADY timeout error
	mov	b,a

	in	HESTAT		;check for any errors again
	ani	HSABRT+HSCRCE	;CRC error or abort?

	ora	b		;combine errors

;report results

	cnz	DARERR		;report any errors

	call	ILPRNT		;report read byte count
	db	CR+80h

	pop	d		;byte count
	push	d		;remember
	call	PDEHEX

	call	ILPRNT
	db	'h bytes rea','d'+80h

	pop	b		;return byte count

	ret

;******===========================
;* RE * Restore to Track 0 Command
;******===========================
FRESTR:	call	RESTOR
	jmp	VPTRAK		;print track # and ret

;******===========================
;* RH *  Read Block Header Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;=================================
FHREAD:	call	GETBLK		;get and validate c=block #
	jnz	CMDERR		;no byte count allowed

	call	CHKDSK		;check for disk, load head

	call	FNDBLK		;find and read block header c
	jnz	HDRERR		;report any errors
	jmp	HDISP		;display what we found

;******===================================
;* SE *  Display Settings & Status Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;  Z set if end of cmd line already
;=========================================
FSET:	call	CILPRT
	db	'===Current Settings & State==','='+80h

	call	PFREQ		;CPU frequency setting
	call	PDECMO		;decimal or hex?
	call	PVERB		;Verbose or not mode
	call	PUNIT		;unit

	lda	CURDSK		;any unit selected?
	inr	a
	rz			;no:done

	call	PHLOAD		;head load state
	call	PTRACK		;current track

	call	CILPRT
	db	'Unit',' '+80h

	in	HESTAT		;Helios status port
	ani	HSRDYN		;Disk ready?
	jnz	IMNR		;n: can't measure speed

	call	ILPRNT
	db	'ready',CR
	db	'Spindle motor:',' '+80h

;Fall into IMEASR to measure spindle speed

;===Subroutine======================================
;Measure spindle motor speed by looking at the index
;pulses. The math assumes these timing loops take
;exactly 46 cycles for an 8080 with 0 waitstate I/O,
;and 47 cycles for an 8080 with 1 waitstat I/O (e.g.
;a Sol-20).
;
;If the unit is not ready, don't try to measure
;spindle speed.
;On Entry:
;  The unit is selected
;On Exit:
;  Rotation time has been printed, unless the unit
;    is not ready
;  Timeout message printed if timeout
;Trashes psw,c,de,hl
;===================================================
IMEASR:	in	HESTAT

	ani	HSRDYN		;unit ready?
	jnz	IMNR		;n: no point in measuring

	lxi	d,PTIMOT	;create timeout "return" address
	push	d

	lxi	d,21739		;3-rev timeout
	lxi	h,0		;initialize timer
	mov	b,h		;result high byte will be 0

;Wait for a leading edge of INDEX. All of the following
;timing assumes a loop time of 46/47 8080 cycles.

IMEAS0:	dcx	d		;(5)
	mov	a,d		;(5)
	ora	e		;(4)
	rz			;(5)timeout?

	in	HESTAT		;(10+1)wait low
	ani	HSIDXN		;(7)index mask for HESTAT
	jz	IMEAS0		;(10)wait for no index

IMEAS1:	dcx	d		;(5)
	mov	a,d		;(5)
	ora	e		;(4)
	rz			;(5)timeout?

	in	HESTAT		;(10+1)wait low
	ani	HSIDXN		;(7)index mask for HESTAT
	jnz	IMEAS1		;(10)wait for index

;Measure the time to the next leading edge of INDEX. The
;following loop takes exactly 46/47 cycles per pass. It takes
;exactly 46/47 cycles from the 1st edge (above) to the next
;read of HESTAT, and 46/47 cycles between HESTAT reads in the
;loop below.

IMEAS2:	inx	h		;(5)
	mov	a,h		;(5)
	ora	l		;(4)
	rz			;(5)timeout?

	in	HESTAT		;(10+1)wait low
	ani	HSIDXN		;(7)index mask for HESTAT
	jz	IMEAS2		;(10)wait for no index

IMEAS3:	inx	h		;(5)
	mov	a,h		;(5)
	ora	l		;(4)
	rz			;(5)timeout?

	in	HESTAT		;(10+1)wait low
	ani	HSIDXN		;(7)index mask for HESTAT
	jnz	IMEAS3		;(10)wait for index

	pop	d		;chuck timeout "return" addr

;Measurement is complete. Print result in mS from the
;result in [b,hl], in units of 46/47 8080 cycles. (b=0 here.)

	call	PRNTMS		;[b,hl]=timer result

	call	ILPRNT
	db	'/rev',CR+80h
	ret

IMNR:	call	ILPRNT
	db	'not ready',CR+80h
	ret

;******=========================================
;* ST * Step {I/O} or <trk1> [...<trkn>] Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;  Z set if end of cmd line already
;===============================================
FSTEP:	mvi	a,1
	sta	STEPMO		;step, don't fast-seek
; Fall into FSEEK

;******==============================================
;* SK * Seek <trk1> [...trkn] Command
;******
;Fast 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
;previous SK or ST command. If just one track is
;specified, then just seek that track and quit.
;On Entry:
;  STEPMO=0 for fast seek, nz to stop at each track
;  hl=address of 1st chr after cmd
;  Z set if end of cmd line already
;====================================================
FSEEK:	jz	SEKLST		;no input? use prior list

;Get complete list of target tracks from the user, convert
;them to binary, and store them in PATBUF.

	lxi	d,PATBUF	;pattern buffer dest.

	push	d		;save address for count
	mvi	b,0FFH		;value cnt, goes 1 extra

GETPRM:	inx	d		;bump pointer
	inr	b		;inc track count

	mvi	c,MAXTRK+1
	call	GETVAL		;get a track #

	stax	d		;save in list

	jnz	GETPRM		;keep looking until end

	pop	h		;recover start address
	mov	m,b		;save count in buffer

;If more than 1 track was specified, then loop through the
;list. If just 1 track, then just seek that track and quit.

SEKLST:	call	CHKSEL		;any unit selected?

	lxi	h,PATBUF	;point to track list
	mov	a,m		;first byte is track count
	cpi	2		;0 or 1 tracks?
	jc	FSEEK1		;yes: seek just 1 track

;-------------------------------
;Seek through the list of tracks
;in PATBUF until the user quits
;-------------------------------
	call	CILPRT
 db 'A toggles automatic mode, Space advances,',' '+80h
	call	Q2QUIT		;Q to quit

SEKAGN:	lxi	h,PATBUF	;point to track list
	mov	b,m		;first byte is pattern count

SEKLUP:	inx	h
	mov	a,m		;get track number

	push	h		;pattern pointer
	push	b		;pattern count

	call	SEKSTP		;returns time in c,hl
	call	TIMTRK		;print time & track

SEKLP1:	lxi	h,AUTOMO

	in	HESTAT	;Keep head loaded, if it is
	out	HETAL	;old versions of the controller

;The heads will unload after about 1 second, unless
;HESTAT is accessed. (Early versions of the controller
;required HETAL to be accessed.)

	call	TESTQ		;quiting?
	jz	SEKLP3		;nothing typed?

	cpi	' '		;next entry?
	jz	SEKLP4

	cpi	'A'		;toggle automatic mode?
	jnz	SEKLP2

	mov	a,m		;toggle AUTOMO
	cma			;00 or FF
	mov	m,a	

SEKLP2:	call	CKIMED		;immediate command?

SEKLP3:	mov	a,m		;automatic mode?
	ora	a
	jz	SEKLP1		;n: wait for user to type sometihng

SEKLP4:	pop	b		;pattern count
	pop	h		;pattern pointer
	dcr	b
	jnz	SEKLUP		;loop thru b tracks

	jmp	SEKAGN		;restart sequence

;----------------------------------------------------
;Just seek one track, report the track, and then quit
;----------------------------------------------------
FSEEK1:	ora	a		;no tracks specified?
	jz	FSEEK0		;y: seek track 0

	inx	h
	mov	a,m		;get the only track number

FSEEK0:	call	SEKSTP
	jmp	TIMTRK		;Print results if verbose

;******===========================
;* WB *  Write Block Data Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;=================================
;Read the block header, just to find out the block's byte count

FDWRIT:	call	BSETUP		;c=block number, de=byte count
	mov	a,b
	ora	a		;any errors reading header?
	jnz	HDRERR		;y: then don't write block

;c=block number
;de = block byte count, either from the header or from user
;No errors detected reading the header

;A block must end at least MINSEC (108) bytes after the
;occurance of its last sector mark. The first sector of a
;block has 256 bytes, all others have 320 (FULSIZ) bytes.
;If the last sector would be less than MINSEC bytes, then
;increase it to MINSEC bytes. (See BLKLN subroutine in PTDOS,
;F14#14)

	push	b		;c=block number
	push	d		;byte count

	dcr	d		;the first sector is 256 bytes
	jm	FDW2		;is the block < 256 bytes?

;de = byte count minus 256, the size of the 1st sector

	xchg
	lxi	d,-FULSIZ	;all other secs are this big

FDW0:	mov	a,h		;exact?
	ora	l
	jz	FDW1

	dad	d		;subtract FULSIZ
	jc	FDW0		;..until borrow

	lxi	d,FULSIZ	;undo the last one
	dad	d

	dcr	h		;compensate for upcoming inr d

FDW1:	xchg			;remainder to de


;de = the remainder in the last sector of the block, minus 256

FDW2:	lxi 	h,0		;default padding = 0
	inr	d		;de=remainder now. >=256?
	jnz	FDW3		;y: no padding necessary

	mvi	a,MINSEC	;pad if less than MINSEC bytes
	sub	e		;must we pad?	
	jc	FDW3		;n: the remainder is > MINSEC

	mov	l,a		;y: hl=padding factor

	push	h		;announce padding
	call	CILPRT
	db	'Block will be padded with',' '+80h
	call	PHLDEC
	call	ILPRNT
	db	' additional bytes','.'+80h
	pop	h

;hl = padding factor, which may be 0

FDW3:	pop	d		;de=byte count
	dad	d		;add the padding
	xchg			;de=padded byte count

	pop	b		;c=block number
	push	d		;save actual byte count for report

	call	CILPRT
	db	'Write block',' '+80h

	mov	a,c
	call	PADEC		;print a=block
	call	ASKRDY

;The above test takes too long, so we must wait for the disk
;to spin, and find our block header again

	push	d		;save padded byte count
	call	FNDBLK		;(203+17)find and read block
				;..header c
	pop	d		;(11)recover padded byte count
	jnz	DAWERR		;(10)error prevents writing

;Set up for transfer

	lxi	h,DATBUF	;(10)
	lxi	b,HWDATA*256+MAXSEC ;(10)write block data cmd
				;..index pulses are ok
	call	HELIOS		;(17+75)write the block from
				;..the buffer

;353 cycles <= 176.5 uS from end of header CRC to transfer
;command issued.

;Report results

	cnz	DAWERR		;report data write errors

	call	ILPRNT		;report written byte count
	db	CR+80h

	pop	d
	call	PDEHEX

	call	ILPRNT
	db	'h bytes writte','n'+80h
	ret

;******=====================================================
;* WC * Write Complete Track with Hex Value Continuously Command
;******
;This function writes a track continuously for writ-channel
;debugging. It is the same as the WT command, except that it
;repeats until the user stops it.
;On Entry:
;  hl=address of 1st chr after cmd
;===========================================================
FWCPAT:	mvi	a,1		;write continuously
	db	CPI		;skips one byte

;Skip over FEPAT and into WPAT

;******=======================================================
;* WT * Write Complete Track with Hex Value Command
;******
;This function creates a track for tuning the read channel.
;This actually writes two 8-sector blocks with the specified
;byte, with the headers of these blocks also filled with the
;specified value. (There will be a few bytes in the two
;headers that are not written with the specified byte, but the
;vast majority of bytes on the sector will be written with the
;specified byte, more than enough for read channel tuning.)
;On Entry:
;  hl=address of 1st chr after cmd
;Trashes the format buffer, FMTDAT
;=============================================================
FWTPAT:	xra	a		;write track just once

; Fall into WPAT

;===Subroutine=================================
;Write track with pattern
;On Entry:
;  a=0 for write once, 1 for write continuously
;  hl=address of pattern list
;==============================================
WPAT:	sta	CFLAG		;remember continuous mode

	lxi	d,FMTDAT-1
	lxi	b,MAXBP8+1	;byte count

;loop to get all pattern values from user input

FWLP1:	inx	d
	dcx	b
	push	d
	call	GETHXA
	pop	d

	stax	d
	jnz	FWLP1		;get all values

	mov	a,c		;final byte count
	cpi	(MAXBP8) and 0FFh ;zero bytes?
	jnz	FWP1
	dcx	b		;y: make it 1
	inx	d		;a zero is already there
FWP1:

;loop to fill data buffer with pattern

	lxi	h,FMTDAT
	
FWLP2:	mov	a,m
	stax	d
	inx	h
	inx	d
	dcx	b
	mov	a,b
	ora	c	
	jnz	FWLP2

;announce

	call	CILPRT
	db	'Write pattern',' '+80h

	lda	CFLAG		;continuous?
	ora	a
	jz	FWP2

	call	ILPRNT
	db	'continuously',' '+80h

FWP2:	call	ILPRNT
	db	't','o'+80h

	call	PWIPE		;common permission code

	lda	CFLAG		;continuous?
	ora	a
	jz	FWP3

	call	ILPRNT		;new line
	db	CR+80h
	call	Q2QUIT		;Q to quit
FWP3:

;Erase the track. This will create 16 1-sector blocks.

	call	TERASE		;erase track first

;------------------------
;Write the track.
;Loop here if WC command.
;------------------------
WCLOOP:

;Write header and data for the 1st 8-sector block

	mvi	c,0		;write header 0
	call	FILHDR

	mvi	c,0		;write block 0
	call	FILDAT

;Write header and data for the 2nd 8-sector block

	mvi	c,1		;write header 8
	call	FILHDR

	mvi	c,1		;write block 8
	call	FILDAT

;Repeat, if this was a WC command (CFLAG=<>0)

	lda	CFLAG
	ora	a		;WC command?
	rz			;N: WT command, so we are done

	call	TESTQ		;WC: Opportunity to quit
	jmp	WCLOOP

;---Local Subroutine----------------------------------------
;Fill header with fill byte data in header buffer
;On Entry:
;  c=desired header
;  DATBUF is filled with the fill byte
;  The disk is selected and the head is on the correct track
;Aborts if error during write
;Trashes all registers
;-----------------------------------------------------------
FILHDR:	lxi	h,DATBUF	;header data source
	call	HWRITE
	rz			;ret if no errors

;report error during header read or write

	pop	h		;chuck return address
	cc	HDWERR		;report header write errors?
	cnc	HDRERR		;error during header read?
	jmp	MAIN

;---Local Subroutine----------------------------------------
;Write one MAXBP8-sized block of data to disk
;On Entry:
;  c=desired block
;  DATBUF is filled with the fill byte
;  The disk is selected and the head is on the correct track
;Trashes all registers
;-----------------------------------------------------------
FILDAT:	lxi	h,JNKBUF	;use junk header buffer
	call	FNDBKH		;find block c
				;..sets hl to DATBUF too
	ani	47h		;any timeout or abort errors?
	jnz	FDERR		;y:abort with error message

	lxi	b,HWDATA*256+MAXSEC	;write data
				;..index pulses ok
	lxi	d,MAXBP8	;max bytes per 8-sector block
	call	HELIOS		;write the block data
	rz			;ret if no errors

;Report errors from writing the data and abort

FDERR:	call	DAWERR		;report any errors
	jmp	MAIN		;abort on error

;******============================
;* WH *  Write Block Header Command
;******
;On Entry:
;  hl=address of 1st chr after cmd
;==================================
FHWRIT:	call	GETBLK		;get and validate c=block
	jnz	CMDERR		;no byte count allowed

	call	CILPRT
	db	'Write header for block',' '+80h

	mov	a,c
	call	PADEC		;print a=block number
	call	ASKRDY

	call	CHKDSK		;check for disk, load head

	lxi	h,HDRBUF	;data source
	call	HWRITE		;write header
	rz
	jc	HDRERR		;error during header read?
	jmp	HDWERR		;n:error during header write

;***************
;* Subroutines *
;***************

;===Subroutine==========================================
;Select the unit and restore to track 0
;On Exit:
;  CURTRK=0 unless timeout. If timeout then CURTRK=0FFh
;  Leaves unit selected
;  Leaves head loaded/unloaded as it was
;  Rude exit if no unit selected or timeout
;Trashes psw,c,hl
;=======================================================
RESTOR:	call	CHKSEL	;make sure a unit is selected

	lda	HDCSAV	;saved select bits
	mov	c,a	;so we can put it back
	ani	HDRSTN	;select and restore
	out	HEDCMD	;To Helios

	lxi	h,0	;about 3.8 seconds

RESTLP:	call	TESTQ	;(~50)user aborting?

	inx	h	;(5)bump timer
	mov	a,h	;(5)
	ora	l	;(4)timeout?
	mov	a,c	;(5)prepare to end restore pulse
			;..(here for delay)
	jz	RESTTO	;(10)y:abort

	in	HESTAT	;(10)wait for seek complete
	ani	HSSKCN	;(7)active low: Z set when there

	sta	CURTRK	;(13)will be on track 0 when done
			;..(here for delay)
	mov	a,c	;(5)prepare to end restore pulse
			;..(here for delay)
	jnz	RESTLP	;(10)

	out	HEDCMD	;actually end the restore pulse
	ret

;===Subroutine================================================
;Seek target track & wait for seek to complete, with timeout.
;Either seek one track at a time or perform a fast (voice-
;coil) seek, depending on STEPMO. Restore first if necessary.
;On Entry
;  a=target track
;  STEPMO=0 for fast seek, <>0 for one track at a time
;On Exit:
;  [b,hl]=seek time in 46/47-cycle units
; Trashes a,c
; Rude exit with message if timeout
;=============================================================
SEKSTP:	lxi	h,0		;initialize seek timer
	mov	b,a		;target track
	lda	CURTRK		;get current track

	ora	a		;unknown track number?
	cm	RESTOR		;yes:restore first
				;returns with a=0
	sub	b		;compute number of of steps
				;..positive means step out
	mov	b,a		;,,in case the result is 0
	rz			;already on track?

	mvi	c,HDSTPN
	jnc	SKSTPO		
	mvi	c,HDSTPN and HDSIN	;prepare to step in
	cma			;..and make a positive
	inr	a		;..complement and add 1

SKSTPO:	mov	b,a		;b=number of steps
				;c=directional step command

	lda	STEPMO		;step or fast-seek?
	ora	a
	jz	SEEKF		;fast-seek

;-----------------------------------------------------------
;Step to desired track, one track at a time. Measure the
;total time taken for this track-at-a-time seek. This allows
;368 cycles settling time between steps -  184 uS for a
;2.000 MHz CPU.
;-----------------------------------------------------------
	mov	e,h		;initialize timer high byte=0

STEPLP:	push	b		;(11)direction, step count
	push	d		;(11) timer high byte
	push	h		;(11) timer low word
	mvi	b,1		;(7)one track at a time
	call	VSEEK		;(17+159=176)step once
	jc	SEEKTO		;(10)timeout error exit

	pop	d		;(10)timer low word
	dad	d		;(10)add this seek to time
	pop	d		;(10)e=timer high byte
	mov	a,e		;(5)
	adc	b		;(4)a=new timer high byte

;Add 368 cycles= 8*46 cycles overhead time for this loop

	xthl			;(18)stall
	xthl			;(18)stall
	mov	d,e		;(5)stall
	nop			;(4)
	nop			;(4)

	lxi	d,8		;(10)
	dad	d		;(10)hl has complete low 16
	adc	b		;(4)catch carry (b=0 here)
	mov	e,a		;(5)e=timer high byte

	pop	b		;(10)direction, step count

	dcr	b		;(5)loop 'till we arrive
	jnz	STEPLP		;(10)..at the target track

	mov	b,e		;timer high byte for return

	ret	

;===Subroutine===================================
;Directly seek desired track via fast (voicecoil)
;seek, and report errors, if any
;  b=number of tracks to seek
;  c=HDSTPN to seek outward
;  c=(HDSTPN & HDSIN) to seek inward
;  CURTRK=current track
;================================================	
SEEKF:	call	VSEEK		;step b tracks in c direction
	rnc			;carry means seek timeout

;Fall into SEEKTO to report seek timeout error

;===Error Routine===================================
;Report seek timeout and remember that we are lost
;On Entry:
;   CURTRK = 0FFh
;Jumps to MAIN, where the stack gets repaired
;===================================================
SEEKTO:	call	CILPRT
	db	'See','k'+80h

	jmp	REPTTO		;finish reporting error

;===Subroutine================================================
;Fast (voicecoil) seek in or out, with timing measurement
;On Entry: 
;  b=number of tracks to traverse
;  c=HDSTPN to seek outward (toward lower track numbers)
;  c=(HDSTPN & HDSIN) to seek inward (toward higher tracks)
;  CURTRK=current track
;On exit:
;  carry set if timeout
;    (about 1.5 seconds for a 2.0000 MHz CPU)
;  b=0
;  hl=time it took in 46/47-cycle increments
;  CURTRK updated
;Trashes a,de
;Overhead time outside the timing loop: 159 cycles
;============================================================
VSEEK:	lxi	h,0		;(10)timer
	lxi	d,1		;(10)bumper
	lda	HDCSAV		;(13)unit number
	ana	c		;(4)a=Drive Command
	push	b		;(11)save step count

;Step in or out the requested number of times, not waiting for
;The seek to complete between steps. The following loops are
;based on 46 8080 cycles per pass.

VSEEK1:	nop			;(4)
	nop			;(4)
	nop			;(4)
	inx	h		;(5)timer		
	out	HEDCMD		;(10+1)issue seek command
	dcr	b		;(5) send as many as required
	nop			;(4)
	jnz	VSEEK1		;(10)

;Now, wait until seek complete or timeout, 46 cycles/pass	

VSEEK2:	dad	d		;(10)bump timer
	rc			;(5)ret with carry if timeout
	in	HESTAT		;(10+1)drive status
	nop			;(4)
	ani	HSSKCN		;(7)0=seek complete
	jnz	VSEEK2		;(10)

;Successful seek. Update current track in CURTRK. This code
;is a bit funky to keep all paths the same number of cycles.

	pop	b		;(10)recover step count
	mov	a,c		;(5)get unit command
	ani	HDSIN xor 0FFh	;(7)0 means step in (+)
	lda	CURTRK		;(13)
	cz	VSEEK3		;(11/(17+18))step in
	cnz	VSEEK4		;(11/(17+18))step out
	sta	CURTRK		;(13)
	mvi	b,0		;(7)for return
	ret			;(10)

;---Local subrourtine----
VSEEK3:	add	b		;(4)
	cmp	a		;(4)set z
	ret			;(10)

;---Local subrourtine----
VSEEK4:	sub	b		;(4)
	cmp	a		;(4)set z
	ret			;(10)

;===Subroutine===============================================
;Wait for the end of the next index pulse, timeout
;after about 2 revolutions' time.
;The Persci drive generates a 1 mS index pulse.
;On Exit:
;  Z set, carry clear, a=0 if the end of the index was found.
;  a=40h, carry and Z cleared if timeout waiting for index
;============================================================
WINDEX:	push	d

;Wait up to 2 rev times or so for an index pulse
;51 cycles >= 25 uS/pass
;1 rev = 166667 uS, so timeout = 2 * (166667/25) = 13334

	lxi	d,13500		;>=2-rev timeout, < 32K

WINDX2:	dcx	d		;(5)
	mov	a,d		;(5)
	ora	e		;(4)timeout?
	jz	WINDX3		;(10)y: ret with Z cleared

	in	HESTAT		;(10)
	ani	HSIDXN		;(7)
	jnz	WINDX2		;(10)

;Wait for the end of the index pulse

	mov	d,a		;d=0: >=5.2 ms timeout

NONDX1:	dcr	d		;(5)timeout?
	jz	WINDX3		;(10) y: error exit

	in	HESTAT		;(10)
	ani	HSIDXN		;(7)
	jz	NONDX1		;(10)

;Success: end of index found. ret with Z set, a=0

	xra	a
	pop	d
	ret			;with Z set, a=0

;-------------
;Index timeout
;On Entry:
;   a=0
;-------------
WINDX3:	ori	40h		;index timeout flag, clear Z
	pop	d		;..and clear carry
	ret

;===Subroutine===============================================
;Set up for block data read or write
;On Entry:
;  hl=address of 1st chr after cmd
;On Exit:
;  b=error codes from header read
;  c=block number
;  de=block byte count
;  block header is in HDRBUF
;  header is in place to read or write block data
;trashes psw,hl
;291 8080 cycles from header-read complete to return
;===========================================================
BSETUP:	call	GETBLK	;get and validate c=block #,
			;..and de=byte count, if given
			;..otherwise de=0

	push	d	;de=user-supplied byte count
	call	CHKDSK	;check for disk, load head

;Timing numbers are from end of header's CRC to issuance of
;the transfer command to read the data block. The Helios spec
;says that this must be less than 300 uS.

	call	FNDBLK	;(203+17)find and read block header c
	pop	d	;(11)de=user-supplied byte count
	mov	b,a	;(5)save error codes

	mov	a,d	;(5)user-supplied byte count?
	ora	e	;(4)
	rnz		;(5/11)y: use it

	lhld	HDRBUF+9 ;(10)n: use byte count from header
	mov	a,h	;(5)truncate high nibble
	ani	0Fh	;(7)
	mov	h,a	;(5)
	xchg		;(4) de=byte count from header
	ret		;(10)

;===Subroutine================================================
;Find and read a block header into the header buffer
;On Entry:
;  c = desired block number
;Entry at FNDBKH:
;  hl = address of buffer for header data
;Entry at FNDBKW:
;  hl = address of buffer for header data
;  Index has already been found
;On Exit:
;  If no errors:
;    Z set
;    a = 0
;    header data is in HDRBUF (or where entry hl specified)
;    203 cycles from CRC checked through ret
;
;  Timeout Errors:
;    Z and carry clear
;    a<0> = 1 if timeout waiting for data
;    a<1> = 1 if timeout waiting for SREADY
;    a<6> = 1 if timeout waiting for index pulse
;
;  All other Errors:
;    Z and carry clear
;    a<2> = 1 if controller abort
;    a<3> = 1 if CRC error
;    a<5> = 1 for track from header does not match CURTRK
;    a<7> = 1 if unexpected index pulse
;    13 header bytes are in HDRBUF
;    through the return instruction
;
; trashes b,de. FNDBLK trashes hl too.
;
;Note: a read or write data command must occur within
;300 uS (600 cycles at 2MHz) of the completion of the header.
;=============================================================
FNDBLK:	lxi	h,HDRBUF	;where to put header

FNDBKH:	call	LDHEAD		;ensure head is still loaded
				;..it may have timed out

	call	WINDEX		;wait for index pulse
	rnz			;Timeout: ret with Z cleared
				;..and error code in a

;Loop to read c+1 block headers into the same header buffer.
;ignore CRC errors except on the last block - the one we want

FNDBKW:	mov	b,c		;b counts blocks
	jmp	FB1		;don't wait for SREADY on 1st

FSHUNT:	call	WTSRDY		;wait for SREADY
	rnz			;SREADY timeout?

FB1:	push	b
	lxi	d,HELEN		;bytes/header
	lxi	b,HRHDR*256+0C7h ;read header command
				;..no index pulses allowed
				;..also c=error mask
	call	HELIOS		;(17+85)read the next header

;Check for timeout or abort errors on intermediate blocks

	mov	e,a		;(5)save errors for final test
	ana	c		;(4)any timeout, abort errors
				;..or index problems?
	pop	b		;(11)
	rnz			;(5)y: return with Z cleared

	dcr	b		;(5)the block we wanted?
	jp	FSHUNT		;(10)n: look for another

;See if the track in the header is correct
;(e=controller errors here)

	inx	h		;(5)point to track in header
	lda	CURTRK		;(13)Hexer's track number
	xra	m		;(7)right track in the header?
	jz	FNDBK2		;(10)y: okay
	mvi	a,20h		;(7)n: track mismatch error
FNDBK2:	ora	e		;(4)combine, test errors,
				;..clear carry
	dcx	h		;(5)undo inx h
	ret			;(10)With error codes in a

;===Subroutine===============================
; Write block header
;On Entry:
;   c = block number
;  hl = source address
;On Exit:
;   Z set if no errors
;   Z clear and a=error bits otherwise
;   Carry set if error was during header read
;Trashes a,bc,de
;============================================
HWRITE:	call	WINDEX		;wait for index
	stc
	rnz			;error waiting for index?

;spin to the block before the requested block,
;unless we are looking for block 0

	dcr	c		;look for one before
	jm	HWRT1		;looking for block 0?

	push	h		;remember source address
	lxi	h,JNKBUF	;header data to junk buffer
	call	FNDBKW		;find and read block header c
	pop	h		;data source

	ani	47h		;timeout or abort errors?
	mov	b,a		;save errors

	call	WTSRDY		;wait for SREADY
	ora	b
	stc			;signal read error
	rnz			;SREADY timeout?

;Issue a write header command for the next block

HWRT1:	lxi	b,HWHDR*256	;write header command
				;..no index pulses allowed
	lxi	d,HELEN	;bytes/header

;Fall into HELIOS to write the next block header

;===Subroutine================================================
;Issue Helios Transfer Command and wait for it to complete,
;or timeout if it does not complete within 1 disk
;revolution's time
;On Entry:
; unit is selected already
;  (HEDCMD has been written)
; head is loaded and settled
; head is on desired track
; hl=transfer memory address
; de=transfer byte count
; b=transfer command
; c = MAXSEC means index pulse is allowed
;On Exit:
; Z set if okay
; Z clear if error
; Carry clear
; a = 00 no error
;     01 timeout waiting for data
;     02 timeout waiting for SREADY
;     04 Helios abort (HSABRT)
;     08 CRC error (HSCRCE)
;     80 Index found while c<>0
; 
;85 cycles max from CRC complete through return 
;75 cycles from entry until transfer command issued  
;Trashes b,de
;=============================================================
HELIOS:	mov	a,l		;(5)transfer address
	out	HETAL		;(10)also clears status
	mov	a,h		;(5)
	out	HETAH		;(10)

	mov	a,e		;(5)transfer length
	out	HETLL		;(10)
	mov	a,d		;(5)
	out	HETLH		;(10)

	mov	a,b		;(5)issue transfer command
	out	HETCMD		;(10)kicks off the transfer

;Index pulses are not allowed if C<>0. Set up b as mask.

	mvi	b,HSIDXN	;prepare index mask
	mov	a,c
	sui	MAXSEC
	jnz	HEL1
	mov	b,a
HEL1:				;b=either index mask or 0

;Wait for transfer to complete, with timeout, index check
;79 cycles = 38.5 uS per pass
;One revolution = 166,667 uS
;1 rev timeout: (166,667/38.5) = 4329

	lxi	d,4329		;1 disk rev's time timeout

HWLOOP:	dcx	d		;(5)timeout?
	mov	a,d		;(5)
	ora	e		;(4)
	jz	HDTTO		;(10)y: Disk transfer timeout

	in	HESTAT		;(10)check for index pulse
	cma			;(4)so 1 means present
	ana	b		;(4)index when not allowed?
	jnz	HABORT		;(10)y: abort the command

	in	HESTAT		;(10)controller status
	ani	HTXDON		;(7)Ready, tx complete,
				;..abort or CRC error
	jz	HWLOOP		;(10)wait for *something*

	ani	HSABRT+HSCRCE	;(7)CRC error or abort?
	rz			;(10done if no error

	mov	b,a		;error codes to b
	jmp	HABORT		;end command

;Data timeout error: exit with a=1, Z cleared.

HDTTO:	mvi	b,1

;Fall into HABORT

;Abort Helios command
;On Entry:
;  b = error code

HABORT:	mvi	a,HTNONE
	out	HETCMD		;this cancels the transfer
	mov	a,b		;error code for return
	ora	a		;clear Z and carry
	ret

;===Subroutine=========================
;Wait for SREADY, with timeout
;On exit if SREADY detected:
;  a=0
;  Z set
;On exit if timeout waiting for SREADY:
;   a=2
;   b=2
;  Z and carry cleared
;  command is aborted
;trashes de
;======================================
WTSRDY:	lxi	d,400		;about 10 mS

WTSRD1:	in	HESTAT		;(10)
	cma			;(4)so 0 means true
	ani	HSSRDY		;(7)
	rz			;(5)Z set if SREADY detected

	dcx	d		;(5)timeout?
	mov	a,d		;(5)
	ora	e		;(4)
	jnz	WTSRD1		;(10)n: keep waiting

	mov	b,a		;error code=HSRDY=2		
	jmp	HABORT		;abort the command
	
;===Subroutine============================
;Check if the disk is loaded and ready
; -->Entry is at CHKDSK<--
;Keep asking the user to load the disk, if
;it is not ready after the user says it is
;
;Trashes psw,de,hl
;=========================================
CHKD0:	call	ASKINS	;ask user to insert a disk

CHKDSK:	push	b
	call	CHKSEL	;make sure a unit is specified
	pop	b

	in	HESTAT
	ani	HSRDYN	;disk selected, loaded, spinning?
	jnz	CHKD0	;n: ask (again) for a disk

	mvi	a,HTNONE ;cancel any existing Helios
	out	HETCMD	;..transfer command

;Fall into LDHEAD to load the head

;===Subroutine===========================================
;Load or unload the correct head based on HDDSN in HDCSAV
;On Entry:
;  HDCSAV = saved control port value
;On Exit:
;  The correct head is loaded for about 1 second.
;  The status register is cleared.
;Trashes psw
;========================================================
LDHEAD:	mvi	a,1		;1 means load head

;Fall into DOHEAD to load the correct head

;===Subroutine===========================================
;Load or unload the correct head based on HDDSN in HDCSAV
;On Entry:
;  a=0 for unload, 1 for load
;  HDCSAV = saved control port value
;On Exit:
;  The correct head is either loaded or unloaded.
;  If loaded, it will stay loaded for about 1 second.
;  The status register is cleared.
;Trashes psw
;========================================================
DOHEAD:	push	d
	push	h
	mov	d,a		;d=0 for unload, 1 for load

	lxi	h,HDCSAV	;saved control port value
	mov	a,m		;left disk or right disk?

	ori	HDDSN		;which disk is selected?
	cma			;msb set means odd disk
	rrc
	rrc			;put in place for HDHL0N
	adi	HDHL0N xor 0FFH	;move to HDHL1N position
				;..if odd

;a=mask for correct head-load bit

	mov	e,a		;e=mask for head-load bit

	ora	m		;head unloaded
	dcr	d		;Z set means load head
	jnz	DOHED2
	xra	e		;head loaded

DOHED2:	out	HEDCMD		;load/unload the head 
	mov	m,a		;remember state in HDCSAV

;The head doesn't actually load until the status port is
;read. (Early versions of the controller required HETAL
;to be accessed.)

	in	HESTAT		;for new controllers
	out	HETAL		;for old controllers
				;..(also clears status)

;The Persci 277 spec says the head load time is 40 mS. Stall
;for at least 40 mS to give the head a chance to settle.
;40000/(24/2)=3333

	lxi	d,3334		;wait 40 mS or more

DOHED3:	dcx	d		;(5)
	mov	a,d		;(5)
	ora	e		;(4)
	jnz	DOHED3		;(10)

	pop	h
	pop	d
	ret

;===Subroutine==============
;Get current head-load state
;On Exit:
;  Z set and a=0 if unloaded
;  Z clear and a=1 if loaded
;===========================
CHKHED:	lda	CURDSK		;odd or even unit?
	rrc	
	lda	HDCSAV
	jc	GH1		;Test odd unit
	rlc

GH1:	rlc			;Test HDHL0N or HDHL1N
	rlc			;carry means unloaded

	cma			;so 1 means loaded
	ani	1		;chuck other bits, test
	ret			;result in a

;===Subroutine============================================
;Ask permission to wipe out the track
;(The first part of the message has already been printed.)
;trashes psw
;=========================================================
PWIPE:	call	ILPRNT
	db	' track',' '+80h

	lda	CURTRK		;current track number
	call	PADEC

	call	ILPRNT
	db	', unit',' '+80h

	lda	CURDSK		;current unit number
	call	PASCII

	call	WRNING		;warning string
	call	ILPRNT
	db	'track','.'+80h

	jmp	ASKRDY		;get confirmation

;===Error Routine===================================
;Report restore timeout.
;End the restore pulse, Remember that we are lost,
;and print timeout message
;On Entry:
;   a=(HDCSAV)
;On jump to MAIN:
;   CURTRK = 0FFh
;Jumps to MAIN, where the stack gets repaired
;===================================================
RESTTO:	out	HEDCMD

	call	CILPRT
	db	'Restore',' '+80h

;Fall into REPTTO to finish reporting the error

;===Error Routine===================================
;Report seek timeout.
;Remember that we are lost and print timeout message
;On jump to MAIN:
;   CURTRK = 0FFh
;Jumps to MAIN, where the stack gets repaired
;===================================================
REPTTO:	call	PTIMOT		;print Timeout

	mvi	a,0FFh		;no longer know where we are
	sta	CURTRK

	jmp	MAIN

;===Subroutine===========================
;Report error during data write
;
;On Entry:
;  a<0> = 1 if timeout waiting for data
;  a<1> - 1 if timeout waiting for SREADY
;  a<2> = 1 if controller abort
;  a<3> = 1 if CRC error
;  a<6> = 1 if timeout waiting for Index
;  a<7> = 1 for unexpected index pulse
;Trashes psw
;========================================
DAWERR:	push	psw
	call	WRERR
	jmp	DRWERR		;recycle some code

;===Subroutine===========================
;Report error during data read
;
;On Entry:
;  a<0> = 1 if timeout waiting for data
;  a<1> - 1 if timeout waiting for SREADY
;  a<2> = 1 if controller abort
;  a<3> = 1 if CRC error
;  a<6> = 1 if timeout waiting for Index
;  a<7> = 1 for unexpected index pulse
;Trashes psw
;========================================
DARERR:	push	psw
	call	RDERR

DRWERR:	call	ILPRNT
	db	'data:',' '+80h
	pop	psw

	jmp	EXPERR		;explain error bits in a

;===Subroutine===========================
;Report error during header write
;
;On Entry:
;  a<0> = 1 if timeout waiting for data
;  a<1> - 1 if timeout waiting for SREADY
;  a<2> = 1 if controller abort
;  a<3> = 1 if CRC error
;  a<5> = 1 for track mismatch
;  a<6> = 1 if timeout waiting for Index
;  a<7> = 1 for unexpected index pulse
;On Exit:
;  carry set
;  Z unchanged
;Trashes a
;========================================
HDWERR:	push	psw
	call	WRERR
	jmp	HRWERR		;recycle some code

;===Subroutine===========================
;Report error during header read
;
;On Entry:
;  a<0> = 1 if timeout waiting for data
;  a<1> - 1 if timeout waiting for SREADY
;  a<2> = 1 if controller abort
;  a<3> = 1 if CRC error
;  a<5> = 1 for track mismatch
;  a<6> = 1 if timeout waiting for Index
;  a<7> = 1 for unexpected index pulse
;On Exit:
;  carry set
;  Z unchanged
;Trashes a
;========================================
HDRERR:	push	psw
	call	RDERR

HRWERR:	call	ILPRNT
	db	'header',':'+80h
	pop	psw

;Fall into EXPERR to explain error bits in a

;===Subroutine==========================
;Explain error during data transfer
;On Entry:
;  a<0> = 1 if timeout waiting for data
;  a<1> - 1 if timeout waiting for SREADY
;  a<2> = 1 if controller abort
;  a<3> = 1 if CRC error
;  a<5> = 1 for track mismatch
;  a<6> = 1 for index timeout
;  a<7> = 1 for unexpected index pulse
;On Exit:
;  carry set
;  Z unchanged
;Trashes a
;=======================================
EXPERR:	rrc
	jnc	EXPER1
	call	CILPRT
	db	'Data timeout',CR+80h

EXPER1:	rrc
	jnc	EXPER2
	call	CILPRT
	db	'SREADY timeout',CR+80h

EXPER2:	rrc
	jnc	EXPER3
	call	CILPRT
	db	'Controller abort',CR+80h

EXPER3:	rrc
	jnc	EXPER4
	call	CILPRT
	db	'CRC error',CR+80h

EXPER4:	rrc
EXPER5:	rrc
	jnc	EXPER6
	call	CILPRT
	db	'Track mismatch',CR+80h

EXPER6:	rrc
	jnc	EXPER7
	call	CILPRT
	db	'Index timeout',CR+80h

EXPER7:	rrc
	rnc
	call	CILPRT
	db	'Unexpected index. Block number too high?',CR+80h

	stc
	ret

;===Subroutine========
;Print head-load state
;=====================
PHLOAD:	call	CILPRT
	db	'Head',' '+80h

	call	CHKHED
	jnz	PH1

	call	ILPRNT
	db	'u','n'+80h

PH1:	call	ILPRNT
	db	'loade','d'+80h

	ret

;===Subroutine======
;Print selected unit
;Trashes psw
;===================
PUNIT:	call	CILPRT
	db	'Unit:',' '+80h

;Fall into PRDRIV

;===Subroutine================
;Print the current unit number
;Trashes psw
;=============================
PRDRIV:	lda	CURDSK
	ora	a		;FF means none selected
	jp	PASCII

	call	ILPRNT
	db	'non','e'+80h
	ret

;===Subroutine===
;Print 'Timeout'
;================
PTIMOT:	call	ILPRNT
	db	'Timeout!',CR+80h
	ret

;===Subroutine==========
; Print "Error writing "
;=======================
WRERR:	call	CILPRT
	db	'Error writing',' '+80h
	ret

;===Subroutine==========
; Print "Error reading "
;=======================
RDERR:	call	CILPRT
	db	'Error reading',' '+80h
	ret

;===Subroutine====================
;Print beginning of warning string
;=================================
WRNING:	call	CILPRT
 db 'WARNING: this rewrites all formating and data on this',' '+80h
	ret

;===Subroutine====================
;Print Q to Quit string
;=================================
Q2QUIT:	call	ILPRNT
	db	'Q to qui','t'+80h
	ret

;===Subroutine=====================================
;Print CR, LF, then inline string
;The call to ILPRNT is followed by an ASCII string.
;The last string byte has its msb set.
;All registers preserved
;==================================================
CILPRT:	call	ILPRNT
	db	CR+80h

;Fall into ILPRNT

;===Subroutine=====================================
;Print inline string
;The call to ILPRNT is followed by an ASCII string.
;The last string byte has its msb set.
;On Exit:
;All registers preserved
;==================================================
ILPRNT:	xthl			;Save hl, get msg addr
	push	psw

IPLOOP:	mov	a,m
	ani	7Fh		;strip end marker
	call	PRINTA		;print byte

	cpi	CR		;follow CR with LF
	mvi	a,LF
	cz	PRINTA

	mov	a,m
	inx	h		;Next byte

	ora	a		;msb set?
	jp	IPLOOP		;Do all bytes of msg

	pop	psw
	xthl			;Restore hl,
				;..get return address
	ret

;===Subroutine=======================
;Print de as an address on a new line
;Trashes psw
;====================================
PADDR:	call	ILPRNT
	db	CR+80h
	mov	a,d		;addr high byte
	call	PAHEX
	mov	a,e		;addr low byte
	call	PAHEX
	call	ILPRNT
	db	':',' '+80h
	ret

;===Subroutine===========
;Print de as 4 hex digits
;On Entry
;  de = value to print
;Trashes psw
;========================
PDEHEX:	mov	a,d
	call	PAHEX
	mov	a,e

;Fall into PAHEX

;===Subroutine==========
;Print a as 2 hex digits
;On Entry
;  a = value to print
;Trashes psw
;=======================
PAHEX:	push	psw		;save digit
	rrc			;hi nibble 1st
	rrc
	rrc
	rrc
	call	PHXNIB
	pop	psw		;recover low nibble

;Fall into PHXNIB

;===Subroutine=========
;Print one hex nibble
;On Entry:
;  Nibble in low 4 of a
;Trashes psw
;======================
PHXNIB:	ani	0Fh		;get low
	cpi	0Ah		;A-F?
	jc	PHX1
	adi	'A'-'9'-1
PHX1:

;Fall into PASCII

;===Subroutine===========================
;Print a on the console as an ASCII digit
;Trashes psw
;========================================
PASCII:	adi	'0'

;Fall into PRINTA

;===Subroutine==========
;Print a on the console
;All registers preserved
;=======================
PRINTA:	push	b
	mov	b,a
	call	SOUT
	mov	a,b	;restore chr
	pop	b
	ret

;===Subroutine===================================
;Print the current track number only if VERBMO<>0
;On Entry:
;  VERBMO <> 0 enables printing
;Trashes psw
;================================================
VPTRAK:	lda	VERBMO
	ora	a
	rz

	jmp	PTRACK		;print track #

;===Subroutine===========================
;If VERBMO then print the seek time and
;the current track number. If the seek
;timed out, then abort to SEEKTO instead.
;On Entry:
;  carry set if the seek timed out
;  [b,hl]=seek time in 46/47-cycle units
;  VERBMO <> 0 enables printing
;Trashes all registers
;========================================
TIMTRK:	jc	SEEKTO		;Timeout error exit

	lda	VERBMO
	ora	a
	rz

	call	CILPRT
	db	'Seek Time:',' '+80h

	call	PRNTMS		;print seek time

;Fall into PTRACK

;===Subroutine==================
;Print the current track number,
; announce if on directory track
;Trashes psw
;===============================
PTRACK:	call	CILPRT
	db	'Track:',' '+80h

	lda	DECMO		;decimal or hex?
	ora	a
	lda	CURTRK		;get actual track
	jnz	PTDEC

	call	PAHEX		;print decimal track #
	call	ILPRNT
	db	'h'+80h
	jmp	TSTDIR

PTDEC:	call	PADEC		;print hex track #

TSTDIR:	lda	CURTRK		;is this the directory track?
	cpi	DITRK
	rnz			;n: done

	call	ILPRNT		;y: say so
	db	' (PTDOS Directory Track',')'+80h

	ret

;===Subroutine====================
;Print a in decimal on the console
;Trashes psw
;=================================
PADEC:	push	h
	push	d
	push	b
	mov	l,a
	mvi	h,0
	call	PHLDEC
	pop	b
	pop	d
	pop	h
	ret

;===Subroutine=====================
;Print hl in decimal on the console
;Trashes all registers
;==================================
PHLDEC:	lxi	d,-10000	;divisor
	lxi	b,0		;b=initial high digit
				;c=suppress leading 0s
	mvi	a,4		;digit count-1

PDLOOP:	call	DIVDGT		;compute & print digit,
				;..and compute divisor * 10
	dcr	a
	jnz	PDLOOP		;all but the last digit

	mvi	c,1		;don't suppress last 0

;Fall into DIVDGT to print last digit

;===Subroutine=============================================
;Divide [b,hl] by de and print 1-digit whole number quotent
;Note: for the most significant digit of a division, it is
;      is possible for the digit to be > 9. If so, 2 digits
;      get printed here.
;On Entry:
;  [b,hl]=dividend
;  de=negative of divisor
;  c = 0 to suppress leading 0s
;On Exit:
;  Quotent is printed always
;  [b,hl]=remainder X 10
;  c=0 if this and all prior digits were 0
;psw,de preserved
;==========================================================
DIVDGT:	push	psw
	push	d	;save divisor

	mov	a,b	;a=dividend high byte
	mvi	b,0FFh	;initialize quotient
			;..(will go 1 too many times)

;Divide [a,hl] by -[b,de]. The result will be an ASCII
;number in c. The remainder will be 16 bits on the stack.

	push	h	;set up stack

DGTLUP:	inx	sp	;chuck stack value
	inx	sp	;..pop to garbage
	push	h	;remember prior result
	inr	b	;bump quotent
	dad	d	;add a negative number
	aci	0FFh	;catch carry bit
	jc	DGTLUP

	pop	h	;hl=remainder

;b = quotent, hl = remainder.

	mov	a,b
	cpi	10	;1st digit overflow?
	cnc	DOVFLO	;y: print 2 digits

	ora	c	;suppress leading 0s?
	mov	c,a

	mov	a,b	;print digit as ascii
	cnz	PASCII	;..if not supressing 0's

;Compute remainder times 10 for return
;[b,hl] := hl X 10 (could be up to 19 bits.)

	mov	d,h	;Temp save X 1
	mov	e,l
	xra	a	;initialize high byte
	dad	h	;X 2
	adc	a	;a=0: catch carry
	dad	h	;x 4
	adc	a	;high byte		
	dad	d	;x 5
	aci	0	;catch carry in high byte
	dad	h	;hl = remainder X 10, 2 low bytes
	adc	a	;a = remainder X 10 high byte
	mov	b,a	;[b,hl] = remainder X 10

	pop	d	;restore divisor
	pop	psw
	ret

;---Local Subroutine---------------------------
;High digit overflow: allow one more high digit
;On Entry:
;  a=high digit value, between 10 and 99
;  (A>99 will print '?' as the high digit.)
;On exit:
;  high digit printed
;  b = remainder after high digit
;  c<>0 (to end leading 0 suppression)
;trashes psw
;----------------------------------------------
DOVFLO:	mvi	c,0FFh	;initialize quotient

DOVLUP:	inr	c	;compute high digit in c
	sui	10	;..and remainder in a
	jnc	DOVLUP

	adi	10	;a=remainder, c=value
	mov	b,a	;remainder for return

	mov	a,c	;highest digit
	cpi	10	;valid digit?
	jc	PASCII	;y: print high digit

	mvi	a,'?'	;n: error code
	jmp	PRINTA

;===Subroutine============================================
;Print [b,hl] in mS, with leading zeros suppressed.
;
;The max mS value this will print correctly is 9999.99 mS.
;The max value for [b,hl] depends on the CPU speed. For a
;2.0000 MHz CPU, the max value is [6 A25E] hex.
;
;On Entry:
;  [b,hl]=dividend in units of 46/47 cycles.
;  CPUFRQ=0 for 2 MHz, 0-waitstate CPU,
;         1 for 2.045, 1-waitstate MHz CPU
;On Exit:
;  'ABCD.EF mS' printed
;Trashes all registers
;=========================================================
PRNTMS:	mvi	c,0		;suppress leading 0s

	lda	CPUFRQ		;user-selected CPU frequency
	lxi	d,-43478	;2.00000000 MHz, 0 waitstate
				;(e.g. Altair)
	ora	a
	jz	PMS0
	lxi	d,-43520	;2.04542857 MHz, 1 waitstate
				;(standard Sol-20)
PMS0:
	mvi	a,6		;digit count

DIVLUP:	call	DIVDGT		;compute & print a digit,
				;..and compute divisor * 10

;End 0-suppression for the digit before the decimal point	

	cpi	4		;time to end 0-suppression?
	jnz	PMS1

	mov	c,a		;y: c<>0 to end suppression

;Print the decimal point after 3 digits

PMS1:	cpi	3		;time for decimal point?
	jnz	PMS2

	call	ILPRNT		;y: print decimal point
	db	'.'+80h

PMS2:	dcr	a		;loop for all digits
	jnz	DIVLUP

	call	ILPRNT
	db	' m','S'+80h
	ret

;===Subroutine================================
;Ask user to insert a disk in the current unit
;Trashes a
;=============================================
ASKINS:	call	CILPRT
	db	'Insert disk in unit',' '+80h
	lda	CURDSK
	call	PASCII

;Fall into ASKRDY

;===Subroutine====
;Ask user if ready
;Trashes a
;=================
ASKRDY:	call	CILPRT
	db	'Ready (Y/N)?',' '+80h

	push	b
	mvi	c,0		;don't echo deletes
	call	GETKBD
	pop	b

	cpi	'Y'
	rz

	cpi	'N'
	jnz	ASKRDY		;ignore all else

	call	CILPRT
	db	'Abor','t'+80h
	
	jmp	MAIN

;===Subroutine===========================
;Get a '0' or '1' from the command buffer
;On Entry:
;  hl points to first chr of a parameter
;On Exit:
;  a=value
;  Z set if value is 0
;  hl points to first chr of next param
;  Rude jump to CMDERR if:
;   no input or bogus value
;trashes bc
;========================================
GET01:	mvi	c,1+1		;0 or 1 only
	call	GETVAL		;result in a and b
	jz	CMDERR		;error if no chr

	ora	a		;test value
	ret			;with Z set if zero

;===Subroutine=====================================
;Get and validate a block number from the user
;Optionally also get and validate sector byte count
;On Entry:
;  hl=address of 1st chr after cmd
;On Exit:
;   c = block number
;   Z cleared and de=byte count if one was entered
;   Z set and de=0 if no byte count entered
;  de=byte count from user, 0 if none entered
;  hl advanced past block number
;Rude jump to CMDERR of bogus or none
;Trashes a,b
;==================================================
GETBLK:	mvi	c,MAXSEC+1	;max allowed block+1
	call	GETVAL		;get block number in a
	jz	CMDERR		;no default allowed

	mov	c,a		;c=block number

	call	GETADR		;get de=byte count
				;de defaults to 0
	ret			;Z set if no value entered

;===Subroutine================================
;Get a decimal or hex parameter, value<99
;On Entry:
;   c-max allowed value+1
;  hl points to first chr of a parameter
;  DECMO = 0 for hex, 1 for decimal
;On Exit:
;  a=value if chr found
;  hl points to first chr of next param
;  Z set if no parameters found
;  Rude jump to CMDERR if bogus chr or value>c
;=============================================
GETVAL:	lda	DECMO		;hex or decomal?
	ora	a
	jnz	GVDEC

;get value in hex

	push	d
	call	GETHXA		;value in a
	pop	d
	rz			;no value: done

	jmp	GVCHK

;Get value in decimal

GVDEC:	call	GETDEC
	rz			;no value: done

;check a for legal value, clear Z flag because we got input

GVCHK:	cmp	c		;max value plus 1
	rc			;ok: Z cleared

	jmp	CMDERR		;error: value is too big

;===Subroutine================================
;Get 1- or 2-digit decimal parameter
;On Entry:
;  hl points to first chr of a parameter
;On Exit:
;  a=value if chr found
;  hl points to first chr of next param
;  Z set if no parameters found
;  Rude jump to CMDERR if bogus chr or value>c
;=============================================
GETDEC:	call	GETDD		;get 1st digit into a
	rz			;nothing found

	push	b
	mov	b,a		;save 1st digit
	call	GETDD
	jz	GDDONE		;ret w/ 1-digit input

	push	psw		;save new digit

	mov	a,b		;compute b=b*10
	add	a		;*2
	add	a		;*4
	add	b		;*5
	add	a		;*10
	mov	b,a

	pop	psw		;recover 1st digit
	add	b		;combine digits
	mov	b,a		;into b for ret

	call	GETDIG		;any more digits?
	jnz	CMDERR		;abort if any

GDDONE:	call	SKIPSP		;advance to next param

	ora	h		;clear Z
	mov	a,b		;result in a
	pop	b
	ret

;---Local Subroutine------------------------------
;Get and convert one decimal digit at hl to binary
;On Exit:
;  Z set if end of cmd line or no digit
; hl points to next cmd line chr unless it's a ' '
;  a=chr converted to binary, unless Z
;-------------------------------------------------
GETDD:	call	GETDIG		;get 1 chr from command line
	rz

	sui	'0'		;de-ASCII
	cpi	10		;carry means below 10
	rc

;Fall into CMDERR

;===Error Routine============================
;Report bogus command from user
;Jumps to MAIN, where the stack gets repaired
;============================================
CMDERR:	call	CILPRT
	db	'huh','?'+80h

	jmp	MAIN		;fixes stack too

;===Subroutine=================================
;Get an hex hex value, max is FFh
;On Entry:
;  hl points to first hex digit
;On Exit:
; Z set and a=de=0 if no value found
; a=e=value
; d=0
; hl points to next input param
; Rude jump to CMDERR if bogus hex or >FFh
;==============================================
GETHXA:	call	GETHEX		;get value in de
	rz

	mov	a,d		;upper byte
	ora	a		;input greater than FFh?
	jnz	CMDERR		;y:error

	inr	a		;clear Z flag
	mov	a,e
	ret			;Z is cleared

;===Subroutine=================================
;Get a hex value, max value is MAXBPB (1000h)
;On Entry:
;  hl points to first hex digit
;On Exit:
; Z set and a=b=de=0 if no value found
; de=value
; hl points to next input param
; Rude jump to CMDERR if bogus hex or >MAXBPB
; trashes a
;==============================================
GETADR:	call	GETHEX		;get address in de
	rz

	mov	a,d
	cpi	MAXBPB/256	;past buffer end?
	jnc	CMDERR
	ret			;Z is cleared

;===Subroutine=======================================
;Get a 0 to FFFF hex value, tolerating leading zeros
;On Entry:
;  hl points to first hex digit
;On Exit:
; Z set and a=b=de=0 if no value found
; de=value
; hl points to next input param
;trashes a
; Rude jump to CMDERR if bogus hex or >FFFF
;====================================================
GETHEX:	push	b
	xra	a
	mov	d,a		;initial value
	mov	e,a
	mov	b,a		;no-digits flag

GHLUP:	push	psw
	mov	a,d		;overflow?
	ani	0F0h		;these bits will shift out
	jnz	CMDERR
	pop	psw

	inr	b		;digit count

	xchg			;shift prev digits left 4
	dad	h
	dad	h
	dad	h
	dad	h
	xchg

	add	e		;combine new digit
	mov	e,a

	call	GETHD		;get a=another digit
	jnz	GHLUP		;keep going until gone

	call	SKIPSP		;advance to next param

	dcr	b		;set Z if no digits
	pop	b
	ret
				
;---Local Subroutine----------------------------
;Get and convert one hex digit at (hl) to binary
;On Entry:
;  hl points to first hex digit
;On Exit:
;  Z set if no characters
;  Rude abort if bad hex
;-----------------------------------------------
GETHD:	call	GETDIG		;get next digit from buffer
	rz			;no buffer characters left?

	cpi	'9'+1		;below ASCII 9?
	jc	GHD1		;Yes: deal with digit

	cpi	'A'		;between 9 & A?
	jc	CMDERR		;yes: bogus
	sui	'A'-'9'-1	;no: subtract offset	

GHD1:	sui	'0'
	cpi	10H		;above 0Fh?
	jnc	CMDERR		;yes:bogus
	ret

;===Subroutine=======================================
;Get one ASCII digit from the command line
;On Exit:
;  a = ASCII digit
;  hl advanced 1 if character found
;  Z set if 0 or space encountered (end of parameter)
;====================================================
GETDIG:	mov	a,m		;any chrs?
	ora	a
	rz			;no:Z set

	cpi	' '		;no digit?
	rz

	inx	h
	ret

;===Subroutine============================
;Wait for Q (or Mode Select) or space
;  Q or Mode/Select quits via jump to MAIN
;  space returns
; Touches HESTAT and HETAL to keep the
; head loaded, if it is loaded.
;Trashes psw
;=========================================
WAITQS:	call	TESTQ
	cpi	' '		;space to advance
	rz

;The heads will unload after about 1 second, unless
;HESTAT is accessed. (Early versions of the controller
;required HETAL to be accessed.)

	in	HESTAT		;kick off head load
	out	HETAL		;old vers of the controller
	jmp	WAITQS

;===Subroutine===================================
;Test for Q or MODE/Select key pressed.
;Rude jump to MAIN if Q or MODE
;On Exit:
;  a=chr (converted to uppecase) if not Q or MODE
;  Z set if no character waiting
;================================================
TESTQ:	call	SINP		;anything from console?
	rz			;n: done

	cpi	MODE		;Sol's MODE/SELECT key
	jz	TQ2		;also quits us

	cpi	'a'
	jc	TQ1

	ani	('q'-'Q') xor 0FFh ;uppercase

TQ1:	cpi	'Q'
	rnz

TQ2:	call	CILPRT
	db	'Don','e'+80h
	jmp	MAIN		;MAIN fixes the stack

;===Subroutine==========================
;Get CR-terminated line from console
;with editing. Terminate line with 0,
;and stash line in LINBUF.
; On Exit:
;   hl = LINBUF
;   0-terminated line starting at LINBUF
;Trashes a,c,hl
;=======================================
GETLIN:	lxi	h,LINBUF
	push	h
	mvi	c,0		;chr count

GLLUP:	call	GETKBD		;get, echo keyboard chr
				;..and keep head loaded

	mov	m,a		;chr into buffer

	sui	CR		;end of input?
	jz	GLRET

	mov	a,c		;full line?
	cpi	LBSIZE
	jz	GLLUP		;yes: no more chrs

	ora	a		;1st chr on a line?

	mov	a,m		;recover character
	cz	CKIMED		;y: look for an immediate command
	jz	GLRET		;immediate: done

	inx	h		;next buffer entry
	inr	c
	jmp	GLLUP

GLRET:	mov	m,a		;null line termination
	pop	h		;hl=LINBUF for return
	ret

;===Subroutine=============================
;Check for an immediate command,
;and execute it if found.
;On Entry:
;  a = character from user
;On Exit:
;  a=0 and Z set if immediate command found
;==========================================
CKIMED:	push	b
	push	d
	push	h

	cpi	'H'		;head-load toggle?
	jnz	CKIM1

;Toggle head-load state

	lda	CURDSK
	ora	a		;any dosk selected?
	jm	CKIM3		;FF means no

	call	CHKHED		;Get current head state
	xri	1		;Toggle head state
	call	DOHEAD		;and put it back
	call	PHLOAD		;announce state
	jmp	 CKIM3		;exit with Z set

CKIM1:	cpi	'+'		;step in?
	jnz	CKIM5

;Step in one track, unless already all the way in

	lda	CURTRK
	ora	a		;any disk selected?
	jm	CKIM3		;FF means no

	mvi	c,HDSTPN and HDSIN ;step in

;Step one track

CKIM2:	lda	CURDSK
	ora	a		;any dosk selected?
	jm	CKIM3		;FF means no

	mvi	b,1		;just one track
	call	VSEEK		;step once
	call	TIMTRK		;report time & track

CKIM3:	xra	a		;Set Z for return

CKIM4:	pop	h
	pop	d
	pop	b
	ret

CKIM5:	cpi	'-'		;Step out?
	jnz	CKIM6

;Step out one track, unless already on track 0

	lda	CURTRK
	ora	a		;can we step out?
	jz	CKIM3		;n: ignore

	mvi	c,HDSTPN	;step out
	jmp	CKIM2		;Go step one track

CKIM6:	cpi	'>'		;nudge in?
	jnz	CKIM7

;Nudge inward: step in, then step out, unless on track 76	

	lda	CURDSK
	ora	a		;any dosk selected?
	jm	CKIM3		;FF means no

	lda	CURTRK		;on the last track?
	cpi	MAXTRK
	jz	CKIM3		;Y: done

	lxi	b,100h+(HDSTPN and HDSIN) ;step in
	call	VSEEK		;step once
	lxi	b,100h+(HDSTPN)	;step out
	call	VSEEK		;step once
	jmp	CKIM3		;done

CKIM7:	cpi	'<'		;nudge out?
	jnz	CKIM8

;Nudge outward: step out, then step in, unless on track 0

	lda	CURDSK
	ora	a		;any dosk selected?
	jm	CKIM3		;FF means no

	lda	CURTRK		;on track 0?
	ora	a
	jz	CKIM3		;Y: done

	lxi	b,100h+(HDSTPN)	;step out
	call	VSEEK		;step once
	lxi	b,100h+(HDSTPN and HDSIN) ;step in
	call	VSEEK		;step once
	jmp	CKIM3		;done

CKIM8:	cpi	'0'		;restore?
	jnz	CKIM4		;n: done, Z cleared

	call	FRESTR		;restore and report
	jmp	CKIM3		;done

;===Subroutine===========================================
;Get and echo a keyboard character, convert to uppercase.
;Ignore delete charaters if c=0. Ignore nulls. Nudge the
;controller while we wait, to keep the head loaded.
;On Entry:
;  c=character count in buffer
;On Exit:
;  (hl) = kbd chr
;  kbd chr in a
;  if BS or DEL and c<>0 then
;    c:=c-1
;    hl:=hl-1
;     a=(hl)
;========================================================
GETKBD:
;The heads will unload after about 1 second, unless
;HESTAT is accessed. (Early versions of the controller
;required HETAL to be accessed.)

	in	HESTAT		;Keep head loaded, if it is
	out	HETAL		;old versions of the controller

	call	SINP		;wait for keyboard chr
	jz	GETKBD

;Test for some sort of delete

	cpi	DEL
	jz	GCDEL
	cpi	BS
	jz	GCDEL

;Convert lowercase to uppercase

	cpi	'a'
	jc	GKECHO		;echo

	ani	('a'-'A') xor 7Fh
	jz	GETKBD		;ignore nulls & MODE

;Echo character unless it's a CR

GKECHO:	cpi	CR		;don't echo cr
	cnz	PRINTA		;echo	

	ret

;Got some sort of delete. Back up if possible.

GCDEL:	mov	a,c		;is delete possible?
	ora	a
	jz	GETKBD		;n: ignore character

	dcr	c		;back up
	dcx	h
	mvi	m,0		;potential termination

	dcr	c		;compensate for inc
	dcx	h

	mvi	a,DELETE	;back up on screen
	call	PRINTA

	mov	a,m		;for upcoming mov m,a
	ret

;===Subroutine=====================
;Get a byte from Sol-20 serial port
;On exit:
;  a = received byte
;==================================
SERIN:	call	TESTQ		;abort opportunity
	mvi	a,SERPP
	call	AINP
	jz	SERIN		;wait for character
	ret

;===Command Table===========================================
;Each entry is 4 bytes.
; Bytes 0-1 are the 2-byte command
; Bytes 2-3 are the address of the command execution routine
;The table is terminated when byte 0 = 0
;===========================================================
CMDTAB:	db	'?',0		;general help
	dw	FHELP
	db	'BO'		;Boot disk
	dw	FBOOT
	db	'QU'		;Quit Hexer
	dw	RETRN

	db	'?B'		;Help with buffer commands
	dw	FBHELP
	db	'DB'		;Display data Buffer
	dw	FBDISP
	db	'EB'		;Edit data Buffer
	dw	FBEDIT
	db	'FB'		;Fill data Buffer
	dw	FBFILL
	db	'EH'		;Edit header buffer
	dw	FHEDIT
	db	'ID'		;Intel hex dump buffer
	dw	FIDUMP
	db	'IL'		;Intel hex load buffer
	dw	FILOAD

	db	'?P'		;Help w/ positioner/Spindle cmds
	dw	FPHELP
	db	'MS'		;Measure Spindle Motor
	dw	FMOTOR
	db	'RE'		;Restore
	dw	FRESTR
	db	'SK'		;Seek
	dw	FSEEK
	db	'ST'		;step
	dw	FSTEP

	db	'?R'		;Help with read/write commands
	dw	FRHELP
	db	'CB'		;Compare Block
	dw	FBCOMP
	db	'RB'		;Read Block
	dw	FDREAD
	db	'WB'		;Write Block
	dw	FDWRIT
	db	'FD'		;Format Disk
	dw	FDFRMT
	db	'RH'		;Read block header
	dw	FHREAD
	db	'WH'		;write block header
	dw	FHWRIT
	db	'ET'		;Erase Track
	dw	FERASE
	dw	'FT'		;Format track
	dw	FTFRMT
	db	'WC'		;Write track continuously
	dw	FWCPAT
	db	'WT'		;Write track
	dw	FWTPAT

	db	'?S'		;Help with Hexer State commands
	dw	FSHELP
	db	'SE'		;Set
	dw	FSET
	db	'C='		;CPU frequency
	dw	FSETC
	db	'D='		;Decimal mode
	dw	FSETD
	db	'U='		;Unit
	dw	FSETU
	db	'V='		;Verbose mode
	dw	FSETV

	db	'?T'		;Max bytes/block table
	dw	FTHELP

	db	'?I'		;Imeediate command help
	dw	FIHELP

	db	0		;Table End

;======================
;Prototype Block header
;used only by TFRMAT
;======================
FMTHDR:	db	0	;sector
	db	0	;Track
	db	0	;forward block chaining pointer, sector
	db	80H	;forward block chaining pointer, track
	db	0FFh	;backward block chaining pointer, sector
	db	0FFh	;backward block chaining pointer, track
	dw	0	;file ID
	db	1	;block length in sectors, no protect
	dw	256	;block length in bytes
	DB	'PT'	;reserved (PT writes this here)
;=============
;RAM variables
;=============
FFBEG:			;RAM from here gets initialized to FFh

DECMO:	ds	1	;nz for decimal sectors & tracks
VERBMO:	ds	1	;Verbose seek mode

CURDSK:	ds	1	;Unit number (FF means none yet)
CURTRK:	ds	1	;current track number. FF means don't know
HDCSAV:	ds	1	;save for HEDCMD value
CPUFRQ:	ds	1	;CPU Frequency index for timing loops
			;0 for 2.00, 1 for 2.04
OFFSET:	ds	2	;hex file load address offset

FFEND:	equ	$	;end of FF-initialized RAM

CLRBEG:			;all RAM past here gets cleared to 0

CFLAG:	ds	1	;Continuous operation flag for WC/WT
STEPMO:	ds 	1	;force step mode
AUTOMO:	ds	1	;automatic mode (must follow STEPMO)
ERRCNT:	ds	2	;error count
;===================
;Block header buffer
;===================
HDRBUF:	ds	13

;====================
;Track Pattern Buffer
;====================
PATBUF:	ds	LBSIZE/2-2

;===================
;Command line buffer
;===================
LINBUF:	ds	LBSIZE

;======================================
;Junk buffer for reading block headers
;======================================
JNKBUF:	ds	HELEN

;=================
;Block data buffer
;=================
DATBUF:	ds	MAXBPB

CLREND	equ	$	;end of RAM clearing

;=====
;Stack
;=====
	ds	32
Stack:

	END

