;==============================================================
;DKCOPY - Altair Datakeeper Disk Copy Utility for CP/M
; by Martin Eberhard
>>>under construction!!!!!!!!!!!!!!!!!!!!>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>


>>>NEED TO THINK THROUGH RETRY ALGORITHM>>>>>>>>>>>>>>>>>>>>>>>>>>....




This should NOT be command-line driven - it should have a copy dialog
so that you can do 2 copies in a row: removable cartridge to fixed
platter, and then fixed platter to removable cartridge

Always do heroics. Count "soft errors" and "hard errors"
Heroics done on sector-by-sector basis:
1. read retry
2. +offset read
   -offset read
3. retry offsets

Source disk (A/B)?
Verify (Y/N)?

Copy from A (removable cartridge) to B (fixed disk)
Verify disk
Ready (Y/N)?

Formatting side 0
Formating side 1
Copying......
Verifying......

Soft read errors: 0
Hard read errors: 0
Verify errors:0

Copy another (Y/N)?



;
;==============================================================
; Notes:
;
;==============================================================
; Revision History:
;  Rev    Date       Author
; 1.00  06OCT2013  M.Eberhard
;  Created. from ADEXER 1.13
;==============================================================

FALSE	equ	0
TRUE	equ	not FALSE

CODESP	equ	0100h		;standard CP/M code start

;--------------------------------------------
; CP/M Entry Points and low-memory locations
;--------------------------------------------
WBOOT	equ	0000H		;Jump to BIOS Warm Boot code
WBOOTA	equ	WBOOT+1		;Address of Warm Boot code
BDOS	equ	WBOOT+5		;BDOS Entry Point

COMBUF	equ	WBOOT+80H	;command line buffer
USAREA	equ	WBOOT+100H	;User program area

;------------------------------------------
;BDOS Function Codes, passed in register C
;Note: CON:, RDR:, and PUN: I/O is done via
;direct BIOS calls, not BDOS calls.
;------------------------------------------
BCONIN	equ	1	;Read Console Chr
;BCONOT	equ	2	;Type Chr on Console
BPRINT	equ	9	;Print $-terminated String
BRDCON	equ	10	;Get Line from Console
BCONST	equ	11	;Console Status (<>0 IF CHR)

;============================================================
; 88-HDSK Ports (The interface board is actually an 88-4PIO.)
;============================================================
CREADY	equ	0A0h	;IN: Ctlr ready for command (bit7)
CSTAT	equ	0A1h	  ;IN: error flags, reset CREADY
ERDNR	  equ	  01h	  ;drive not ready
ERBADS	  equ	  02h	  ;illegal sector
ERSCRC	  equ	  04h	  ;CRC error during sector read
ERHCRC	  equ	  08h	  ;CRC error during header read
ERSWRG	  equ	  10h	  ;header has wrong sector
ERCWRG	  equ	  20h	  ;header has wrong cylinder
ERHWRG	  equ	  40h	  ;header has wrong head
WPROT	  equ	  80h	  ;Write Protect
ERMASK	  equ	  7Fh	  ;all the actual error bits
SERMSK	  equ	  7Fh	  ;error bits for seeks

ACSTA	equ	0A2h	;IN: Command Ack (bit 7)
ACMD	equ	0A3h	;IN: reset Command Ack
			;OUT: Command high byte/initiate
; 88-HDSK Commands

CINIT	equ	0E0h	;Initialize
CFORMT	equ	0C0h	;Format
			  ;Bits 3:2 = Unit #
		;ADATA Bits 7:6 = Platter #
		;      Bit    5 = Side #

CSEEK	equ	00h	;Seek
			  ;Bits 3:2 = Unit #
			  ;   Bit 0 = Cylinder # bit 8
		;ADATA Bits  7:0  = Cylinder # bits 7:0

CRDSEC	equ	030h	;Read Sector
CRUSEC	equ	0A0h	;Read Unformatted Sector
CWRSEC	equ	020h	;Write Sector
			  ;Bits 3:2 = Unit #
			  ;Bit  1:0 = Buffer #
		;ADATA Bits 7:6 = Platter #
		;      Bit    5 = Side #
		;      Bits 4:0 = Sector #

CRDBUF	equ	050h	;Read Buffer
CWRBUF	equ	040h	;Write Buffer
			  ;Bit  1:0 = Buffer #
		;ADATA Bits 7:0 = # bytes to transfer
		;(00 means 256)

CSETIV	equ	080h	;Set IV Byte
CRSTAT	equ	060h	;Read Status (IV Byte)
			  ;Bits 3:2 = Unit #
		;ADATA Bits 7:0 = IV Byte Address
	
CDSTA	equ	0A4h	;IN: data/stat availablr at CDATA
CDATA	equ	0A5h	;IN: Disk data or status from Ctlr
ADSTA	equ	0A6h	;IN: ADATA Port Available (bit 7)
ADATA	equ	0A7h	;OUT: Command low byte

;=================================
; Controller IV Bytes, as accessed
; via CSETIV, and CRSTAT commands
;=================================
; Processor Card IV Bytes
; (direction referenced to 8X300)
IVX	equ	01	;(output)IV Direction Control
IVA8	equ	02	;(outout)IV Error Codes
IVA9	equ	03	;(input)Command upper byte
IVA12	equ	06	;(output)Data from controller
IVA13	equ	07	;(input)Command low byte/data
IVA10	equ	04	;(output)handshakes
IVA11	equ	05	;(input)handshakes

; Disk Data Card IV Bytes
; (direction referenced to 8X300)
IVB	equ	34	;(output)FIFO write data
IVA	equ	33	;(input)FIFO read data
IVC	equ	35	;(output)Set Bit Counters
IVCLN0	  equ	  10h	  ;Load pulse 0 (active low)
IVCLN1	  equ	  20h	  ;Load pulse 1 (active low)
IVCLN2	  equ	  40h	  ;Load pulse 2 (active low)
IVCLN3	  equ	  80h	  ;Load pulse 3 (active low)
IVD	equ	36	;(input)Disk Status A
IVDTRR	  equ	  01h	  ;Transfer Request
IVDDTC	  equ	  02h	  ;Data Transfer Complete
IVDDCE	  equ	  04h	  ;Data CRC Error (after read)

IVE	equ	37	;(output)Disk Control A
IVESDS	  equ	  01h	  ;Source data select (set to 0)
IVETAS	  equ	  02h	  ;Transfer Ack Strobe
IVECLN	  equ	  08h	  ;Clear FIFOs (active low)
			  ;NOTE: MITS doc is wrong.
			  ;This is bit 3 not bit 2.
IVERDM	  equ	  20h	  ;Read mode (0 for write mode)
IVEDTS	  equ	  40h	  ;Dist transfer start
IVECAE	  equ	  80h	  ;CRC Apend Enable

; 88-HDSK Interface Card IV Bytes
; (direction referenced to 8X300)
IVH	equ	17	;(inverting output)Disk Function Control
IVHSS	  equ	  80h	  ;Start/Stop
IVHEXT	  equ	  40h	  ;Extension select
IVHPLA	  equ	  20h	  ;Platter select
IVHSID	  equ	  10h	  ;Side select
IVHUS4	  equ	  08h	  ;Select unit 4
IVHUS3	  equ	  04h	  ;Select unit 3
IVHUS2	  equ	  02h	  ;Select unit 2
IVHUS1	  equ	  01h	  ;Select unit 1
IVI	equ	18	;Disk Control B
IVIEUN	  equ	  80h	  ;Emergency Unload (active low)
IVIOPN	  equ	  40h	  ;Offset plus (active low)
IVIOMN	  equ	  20h	  ;Offset minus (active low)
IVIWEN	  equ	  10h	  ;Write enable (active low)
IVICRN	  equ	  08h	  ;Cyl Restore (active low)
IVICSN	  equ	  04h	  ;Cyl Strobe (low pulse)
IVIC8N	  equ	  01h	  ;Cyl address bit 8 (inverted)
IVIMSK	  equ	IVIEUN+IVIOPN+IVIOMN+IVIWEN

IVJ	equ	19	;Cyl address bits 7:0 (inverted)
IVK	equ	20	;(input)Disk Status B
IVL	equ	21	;(input)Disk Status C
IVLFP	  equ	  20h	  ;file protected
IVM	equ	22	;(input)Sector Count

;==================
; 88-HDSK Constants
;==================
MAXSEC	equ	23	;max sector
MAXCYL	equ	405	;max cylinder
MAXPLA	equ	3	;max platter number
MAXUNT	equ	4	;max unit number (starts with 1)
MAXBUF	equ	3	;max buffer number
MXFIFO	equ	16	;Data FIFO Depth
MAXIV	equ	255	;Max IV address	(allow freedom.)

SKCOH	equ	152/4	;uS of controller 8X300 code overhead
			;..for seek command
REVTIM	equ	25000	;uS per disk revolution
SECTIM	equ	REVTIM/(MAXSEC+1)	
SKLOH	equ	SECTIM/2    ;average sector latency for seek
SEEKOH	equ	SKLOH+SKCOH ;total avg overheed for seek command

;=================
; ASCII characters
;=================
BS	equ	08H
LF	equ	0AH
CR	equ	0DH
DEL	equ	7FH
SOH	equ	1	;Start of XMODEM block
EOT	equ	4	;End XMODEM session
ACK	equ	6	;XMODEM block acknowledge
NAK	equ	15H	;XMODEM block negative ACK
QUOTE	equ	27h	;single quote

;=====================
; Start of code
	ORG	CODESP
;=====================
	di
	lxi	SP,STACK	;SP=local stack

;--------------------------------------------
; Initialize RAM variables
; Other RAM gets cleared too, harmlessly.
;--------------------------------------------
	lxi	h,VARSP		;everything to 0 first
	lxi	b,512		;more than enough cleared

INITLP:	mvi	m,0		;(10)set it all to 0
	inx	h		;(5)
	dcx	b		;(5)
	mov	a,b		;(5)
	ora	c		;(4)
	jnz	INITLP		;(10) 39 cycles=19.5 uS per pass
				;512 passes=about 9.98 mS

	inr	a		;a=1
	sta	PATBUF		;default pattern: seek track 0

;---------------------------------------
; Initialize 88-HDSK interface board
; (Actually ports 0 and 1 of an 88-4PIO)
;---------------------------------------
	xra	a

	out	0A0h		;Select port 0Ah DDR 
	out	0A2h		;Select port 0Bh DDR
	out	0A4h		;Select port 1Ah DDR
	out	0A6h		;Select port 1Bh DDR
	out	0A1h		;Port 0Ah is an input port
	out	0A5h		;Port 1Ah is an input port

	cma
	out	0A3h		;Port 0Bh is an output port
	out	0A7h		;Port 1Bh is an output port

	mvi	a,2Ch		;set up input port handshakes
	out	0A0h
	out	0A4h
	out	0a6h		;also output port 1Bh handshakes

	mvi	a,24h		;set up port 0Bh handshakes
	out	0A2h


;-------------
; Print banner
;-------------

	call	CILPRT
	db	' ================================================='
	db	CR,LF
	db	' = Altair Datakeeper Disk Copy Utility ver. 1.00 ='
	db	CR,LF
	db	' =  By Martin Eberhard            October, 2014  ='
	db	CR,LF
	db	' ================================================='
	db	CR,LF+80h

;======================================================
; Main loop: re-initialize several things,
; look for commands from user & dispatch.
; Subroutines often exit directly to MAIN on errors,
; hosing the stack. Therefore, MAIN restores the stack.
;======================================================
MAIN:	lxi	SP,STACK	;repair stack

;Get copy parameters from the user

ASKCRC:	call	CILPRT
	db	'Source disk (A/B)?',' '+80h
	mov	b,a		;save for confirmation
	sui	'a'		;valid response?
	mov	c,a		;save for confirmation
	cpi	2
	jnc	ASKSRC

	sta	SRCDSK

ASKVER:	call	CILPRT
	db	'Verify cop','y'+80h
	call	ASKYN
	jc	ASKVER		;bogus - ask again

	sta	VERIFY		;1 means yes
	push	psw

;print planned copy and ask for confirmation

	call	CILPRT
	db	'Copy from',' '+80h
	mov	a,b
	call	PRINTA
	call	ILPRNT
	db	': to',' '+80h
	mov	a,c
	xri	1		;dest is the other disk
	adi	'A'
	call	PRINTA

	pop	psw
	ora	a		;verify?
	jz	PNOVER

	call	ILPRNT
	db	' with verif','y'+80h
PNOVER:

ASKRDY:	call	CILPRT
	db	'Read','y'+80h
	call	ASKYN
	jc	ASKRDY		;bogus?
	jz	ASKRDY		;not ready?


;-------------------------------------------------
; Copy both sides of either platter to the
; other platter in the selected unit. (Does
; not support extension platters.)
; Always format the disk first
;
; On Entry:
;  SRCDSK = source disk
;  SRCDSK XOR 1 = destination disk
;  VERIFY=1 to  verify the destination when done
;-------------------------------------------------

;-------------------------------------------------------
;Read a sector to force controller to test write-protect
;Abort if destination is write protected
;-------------------------------------------------------
	xra	a		;initially top side
	sta	CRSIDE

	lda	SRCDSK
	xri	1		;destination platter
	sta	CRPLAT

	lxi	b,0300h		;sector 0, buffer 3
	lxi	d,8000h+CRUSEC	;read bad sector, ignore all
	call	DORDWR
	jnz	FPERR		;y: write protect error<<<<<<<<<<<

;---------------
;format the disk
;---------------
	call	FORMAT		;format with message
	call	TOGSID		;other side
	call	FORMAT		;format with message
	call	TOGSID		;back to top

;-----------------------------------
;Copy disk SRCDSK to the other disk,
; using all 4 controller buffers
;-----------------------------------
	call	CILPRT
	db	'Copying. Q to qui','t'+80h

	lxi	h,-1		;for initial inc

;Loop to copy all cylinders from SRCPLA to DSTPLA

COPDSK:	inx	h		;bump cylinder count
	push	h		;cylinder onto stack

	call	SEEK		;seek hl

	call	TESTQ		;opportunity to abort
	call	ILPRNT
	db	'.'+80h		;pacifier

;loop to copy both sides of this cylinder

COPCYL:	mvi	c,MAXSEC	;c=sector counter

;Loop to copy all 24 sectors on this side

COPTRK:	push	b		;save sect count for write

;Read 4 interleaved sectors from the source disk
;into 4 controller buffers, with 2:1 interleave

	call	SETRTO		;set up source offset
	lda	SRCDSK
	lxi	d,7F00h+CRDSEC	;read sector, check errors
	call	RW4		;read 4 sectors now

	pop	b		;recover sector count

;Write 4 interleaved sectors from the 4 controller
;buffers to the destination disk, with 2:1 interleave

	call	CLRRTO		;no offset during write
	lda	SRCDSK
	xri	1		;destination disk
	lxi	d,0FF00h+CWRSEC	;write sector, check errors
	call	RW4		;write 4 sectors now

;next 4 sectors

	mov	a,c		;all sectors done?
	rlc			;c underflow?
	jnc	COPTRK		;n: more on this track

;next side

	lda	CRSIDE
	xri	1		;toggle
	sta	CRSIDE
	jnz	COPCYL		;do bottom side

;next cylinder

	pop	h		;cylinder number

	mov	a,l
	sui	MAXCYL and 0FFH
	mov	a,h
	sbi	MAXCYL/256
	jnz	COPDSK

;---------------------------------
;verify, if verify was selected
;---------------------------------
	lda	VERIFY
	ora	a
	cnz	PVERFY		;compare the 2 platters

;----------------------------
;Copy is done. Report results
;----------------------------

>>>>WRITE THIS<<<<


;****************************
; Disk Controller Subroutines
;****************************

;===Subroutine==========================
; Read or write 4 interleaved sectors
; into 4 controller buffers
; On entry:
;  a = platter number
;  b = read or write command
;  c = sector counter, with bits 1:0=11b
; On exit:
;  c decremented 4
;  CRPLAT=a
; trashes a,b, hl
;========================================
RW4:	sta	CRPLAT

RW4LUP:	push	b		;save sector count
	call	I2RDWR		;read or write sector now
	pop	b
	cnz	RWERR		error: retry if read

	mov	a,c		;4 buffers full?
	dcr	c		;next
	ani	3
	jnz	Rw4LUP

	ret

;Read or write error. If read error, try heroics.
;If write error, just do what?

;on entry:
;  a=error code
;  e=CRDSEC or CWRSEC
; CURTRK=currnet track
; CURSID=current side
;
; read retry strategy:
; offset = b and 03h...




RWERR:	mov	a,b		;read or write?
	cpi	CWRSEC
	jz	WRTERR		;write error

	mvi	d,RETRYS	;max retries

RETRY:	mov	a,d
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>		




;===Subroutine==============================
; Read or Write a sector with 2:1 interleave	
; On Entry:
;   b=CRDSEC, CRUSEC, or CWRSEC
;   c=sector count
;   CRPLAT = platter number
;   CRSIDE = current side
; On Exit:
;  command has been issued and completed
;  a = error flags anded with mask
;  c = all error flags
;  z set if a=0 
; trashes all registers
; (?? cycles)
;===========================================
I2RDWR:	mov	a,c		;compute buffer number
	ani	3		;..is lsb 2 of sector count
	mov	d,a		;d=buffer #

	mov	e,b		;read or write command

	lxi	h,INLV2		;interleave sector lookup
	mvi	b,0
	dad	b
	mov	c,m		;c=interleaved sector

;fall into DORDWR

;===Subroutine==========================
; Read or Write a sector	
; On Entry:
;   c=sector number
;   d=buffer number
;   e=CRDSEC, CRUSEC, or CWRSEC
;   CRPLAT = platter number
;   CRSIDE = current side
; On Exit:
;  command has been issued and completed
;  a = error flags anded with mask
;  c = all error flags
;  z set if a=0 
; trashes a,bc,hl
; (288 cycles)
;=======================================
DORDWR:	call	GETHED		;(17+57)side & platter into a
	ora	c		;(4)combine with sector
	mov	l,a		;(5)for low command byte

	mov	a,d		;(5)buffer number
	ora	e		;(4)combine with command
	mov	h,a		;(5)hl has complete command

	sui	CWRSEC+3	;(7)set a=7Fh for CRDSEC/CRUSEC
	mvi	a,0FFh		;(7)set a=0FFh for CWRSEC
	cmc			;(4)
	rar			;(4)
	mov	b,a		;(5)error mask

	mvi	c,2		;(7)1 second timeout

; fall into HDCMD

;===Subroutine==========================================
; Issue a disk command to the unit specified by CRUNIT
;
; Note: this just assumes the controller is ready
; On Entry:
;   b = error mask
;   c = timeout in seconds+1, c>1
;   hl = complete command
; On Exit:
;   a = masked error flags
;   Z set if no errors after mask with b
;   The command is completed and the controller is ready
;   Carry cleared
; Trashes a,c,hl
; (160 cycles, assuming fast response fom controller) 
;=======================================================
HDCMD:	in	CSTAT		;(10+1) Clear CREADY in case
	in	ACMD		;(10+1)reset CMDACK in ACSTA

	mov	a,l		;(4)low byte of command 1st
	out	ADATA		;(10)

	mov	a,h		;(5)
	out	ACMD		;(10)and issue command

HDWAIT:	dcr	c		;(4)timeout?
	jz	HDCTO		;(10)y:timeout error

	lxi	h,39216		;(10)c x 1 sec timeout


HDW1:	in	CREADY		;(10+1)Is the controller done?
	rlc			;(4)look at msb=CRDY
	jc	HDWDON		;(10)y: done

	dcx	h		;(7)1-sec timeout?
	mov	a,h		;(5)
	ora	l		;(4)
	jnz	HDW1		;(10)

	jmp	HDWAIT		;(10)keep waiting

HDWDON:	in	CSTAT		;(10+1)reset CRDY flag
	ana	b		;(4)mask errors
	ret			;(10)Z set if okay

;===Subroutine======================================
; Get head bits in position for a controller command
; On Entry:
;   CRPLAT = platter number
;   CRSIDE = current side
; On Exit:
;   l<7:6> = platter
;    l<5>  = side
;   l<4:0> =0
;   a=l
;(61 cycles)
;===================================================
GETHED:	xra	a		;(4)clear carry bit
	lda	CRSIDE		;(13)
	rar			;(4)into carry bit
	lda	CRPLAT		;(13)
	rar			;(4)
	rar			;(4)
	rar			;(4)into 7:6

	mov	l,a		;(5)result in l for return
	ret			;(10)


;===SUBROUTINE=================================
; Format the selected side of the selected disk
; On Entry:
;  CRSIDE=the disk side to format
;  CRPLAT=the platter to format
; Trashes a,hl
;==============================================
FORMAT:	push	b
	push	d
	call	CLRRTO		;turn off any track offset

	call	CILPRT
	db	'Formatting platter',' '+80h
	lda	CRPLAT
	call	PDEC8
	call	ILPRNT
	db	', side',' '+80h
	lda	CRSIDE
	call	PDEC8

	mvi	h,CFORMT
	call	GETHED		;get l=head from side and platter
	lxi	b,0FF00h+61	;b=error mask,c=60 second timeout 

	call	HDCMD		;format now
	pop	d
	pop	b
	rz

	jmp	ERDCOD		;error: go decode errors<<<<<<<<<<<<<<<<<<

;===Subroutine===============================
; Verify Disk
; Compare all bytes of platter SRCPLA to
; all bytes of DSTPLA, and report differences
;============================================ 
PVERFY:	call	CILPRT
	db	'Verifying. Q to qui','t'+80h

;Compare SRCPLA to DSTPLA, using all 4 controller buffers

	lxi	h,0		;clear error count
	shld	BYTCNT

	dcx	h		;for initial inc

;Loop to verify all cylinders from SRCPLA to DSTPLA

VERDSK:	inx	h		;bump cylinder count
	push	h		;cylinder onto stack

	call	SEEK		;seek hl

	call	TESTQ		;opportunity to abort
	call	ILPRNT
	db	'.'+80h		;pacifier

;loop to verify both sides of this cylinder

VERCYL:	lxi	b,MAXSEC	;c=sector counter

;Loop to verify all 24 sectors on this side

VERTRK:	push	b		;save sect count for compare

;Read 4 interleaved sectors from the source disk
;into 4 controller buffers, with 2:1 interleave

	call	SETRTO		;offset on source disk
	lda	SRCPLA
	lxi	d,7F00h+CRDSEC	;read sector, check errors
	call	RW4		;read 4 sectors now

;Read these 4 sectors into the verify buffer

	lxi	h,VERBUF
	mvi	b,3		;buffer #
	mvi	d,0		;byte count	

VRBLP:	call	RBSTUP		;trashes c

VRDLP:	in	CDATA		;get a data byte
	mov	m,a		;stash it
	inx	h
	dcr	d		;next byte
	jnz	VRDLP

	dcr	b		;next buffer
	jp	VRBLP	
	
;Read/compare these  4 sectors from the other platter

	call	CLRRTO		;no offset on the dest platter
	pop	b		;recover sector count
	lda	DSTPLA
	lxi	d,7F00h+CRDSEC	;read sector, check errors
	call	RW4		;read 4 sectors now
	push	b		;c=sector count

	lxi	h,VERBUF
	mvi	b,3		;c=buffer #

VWBLP:	call	RBSTUP		;trashes c
	lxi	d,0		;d=byte count, e=error accumulate

VWRLP:	in	CDATA		;get a data byte
	xra	m		;compare to correct data
	ora	e		;combine with previous errors
	mov	e,a		;accumulate errors in d
	inx	h
	dcr	d		;next byte
	jnz	VWRLP

	ora	a		;all sector bytes ok?
	cnz	CSCERR		;n: report mismatch

	dcr	b		;next buffer
	jp	VWBLP

;next 4 sectors

	pop	b		;recover sector count
	mov	a,b		;all sectors done?
	ora	a		;bc underflow?
	jz	VERTRK		;n: more on this track

;next side

	lda	CRSIDE
	xri	1		;toggle side
	sta	CRSIDE
	jnz	VERCYL

;next cylinder

	pop	h		;cylinder number

	mov	a,l
	sui	MAXCYL and 0FFH
	mov	a,h
	sbi	MAXCYL/256
	jnz	VERDSK

;report errors

	call	DORSTR		;restore

	lhld	BYTCNT
	jmp	PERCNT		;print # of verify errors

;===Local Subroutine=====================
;Report verify error
; On Entry:
;       c=buffer number 
;  Stack: Return
;         Sector number-1 (uninterleaved)
;         Cylinder
;  CRSIDE = side
; On Exit:
;  Error count in BYTCNT bumped
;  error reported
; trashes a,de,hl
;=======================================
CSCERR:	lhld	BYTCNT
	inx	h
	shld	BYTCNT

	pop	h	;return address
	pop	d	;e=sector number
	xthl		;d=0, hl=cyl number

	call	CILPRT
	db	'Error: Cylinder',' '+80h
	push	d	;sector number
	push	h	;cylinder
	push	b
	call	PDEC16	;print cylinder

	call	ILPRNT
	db	', Side:',' '+80h
	lda	CRSIDE	;side
	call	PDEC8
	pop	b

	call	ILPRNT
	db	', Sector:',' '+80h

	pop	h	;cylinder
	pop	d	;sector

	push	d	;sector
	push	h	;cylinder

	inx	d
	mov	a,e	;sector
	add	c	;which one of 4
	mov	e,a

	lxi	h,INLV2	;interleave sector
	dad	d
	mov	a,m	;actual sector number
	call	PDEC8	;cylinder

	pop	h	;cylinder
	pop	d	;sector

	xthl		;hl=return address, restore cyl
	push	d	;restore sector number

	pchl		;return		

;===Subroutine================
; Toggle the side
; all regs and flags preserved
;=============================
TOGSID:	push	psw
	lda	CRSIDE
	xri	1		;other side
	sta	CRSIDE
	pop	psw
	ret

;===Subroutine==========================
; Set up to read a controller buffer
; On Entry:
;   b = controller buffer number
; On Exit:
;   controller is ready to transfer data
; a,c trashed
; (158 cycles, assuming quick response)
;=======================================
RBSTUP:	in	CDATA		;(10+1)reset CDA in CDSTA
	in	ACMD		;(10+1)reset CMDACK in ACSTA

	xra	a		;(4)transfer 256 bytes
	mov	c,a		;(5)c=0
	out	ADATA		;(10)low byte of command 1st

	mov	a,b		;(5)buffer number
	ori	CRDBUF		;(7)create read buffer command
	out	ACMD		;(10)and issue command

; b=0 here for a reasonable timeouts

RBCAWT:	dcr	c		;(5)timeout?
	jz	CAKERR		;(10)Yes: abort
	in	ACSTA		;(10+1)check command ack
	rlc			;(4)
	jnc	RBCAWT		;(10)

; continuing with same timeout

RBSUWT:	dcr	c		;(5)timeout?
	jz	CDPERR		;(10)Yes: abort

	in	CDSTA		;(10+1)Wait for data port to be ready
	rlc			;(4)msb=CDA
	jnc	RBSUWT		;(10)

	ret			;(10)

;===Subroutine==============================
; Clear Read Track Offset in the controller
; without changing CRRTO value.
; Trashes psw. All other registers preserved
;===========================================
CLRRTO:	push	b
	mvi	b,0
	jmp	RTOB

;===Subroutine==============================
; Set Read Track Offset in the controller
; Trashes psw. All other registers preserved
;===========================================
SETRTO:	push	b
	lda	CRRTO
	mov	b,a

; Fall into RTOB

;===Subroutine end==============================
; Write b to Read Track Offset in the controller
; On Entry:
;   bc is on stack
;    b = value for offset
; Trashes psw. All other registers preserved
;===============================================
RTOB:	mvi	c,IVI		;[IV 18]
	call	READIV		;get IV with current offset
	jnz	OFDONE		;done if drive not ready

	mov	c,a		;temp save IV value

	ori	IVIOMN+IVIOPN	;delete old offset
	xra	b		;install new offset

	cmp	c		;offset changed?
	jz	OFDONE		;n: done

	mvi	c,IVI		;[IV 18]set new offset
	call	WRITIV		;write the new offset

; stall for 5 mS to let the head settle

	lxi	b,416	;(10) 416 X 24 X 0.5uS = 5 mS

HSTALL:	dcx	b	;(5)
	mov	a,b	;(5)
	ora	c	;(4)
	jnz	HSTALL	;(10) 24 cycles through loop

OFDONE:	pop	b
	ret

;===Subroutine================================================
;Read IV byte
; Print error message if CDATA is not ready
; Note that no IV Bytes can be read without the Disk Interface
; Card present and the Ready line on that card active, because
; the Datakeeper Read Status command insists that the selected
; Unit be ready before it will return status. This means that
; a jig will be needed for debugging the controller when no
; disk drive is connected.
;
; On entry:
;   c = IV byte address
; On Exit:
;   Z cleared means no IV data because the drive was not ready
;   a = IV Byte Status
;   all other regs preserved
; (318 cycles, assuming fast controller response)
;=============================================================
READIV:	push	b		;(11)
	push	h		;(11)
	mov	l,c		;(4)IV byte address is low byte
	mvi	h,CRSTAT	;(7)create status command

	in	CDATA		;(10)clear CDA

	lxi	b,ERDNR*256+2	;(10)b=drive not ready error mask
				;c=1 second timeout 
	call	HDCMD		;(180+17)puts unit into command
	pop	h		;(10)
	pop	b		;(10)

	rnz			;(5)can't read IV if not ready

; HDCMD returns when CRDY - so CDA should already be set
	in	CDSTA		;(10)
	rlc			;(4)test CDA
	cnc	CDPERR		;(5)not there? error
		
; Read & report IV Byte
	in	CDATA		;(10)

	cmp	a		;(4)set Z for normal return
	ret			;(10)	

;===Subroutine============
;Write IV byte
; On entry:
;   a = IV byte data
;   c = IV byte address
; On Exit:
; a trashed, Z set
; all other regs preserved
;=========================
WRITIV:	push	d
	mov	e,a		;save IV data
	in	ACMD		;reset CMDACK in ACSTA

	mov	a,c		;recover address
	out	ADATA		;low byte of command

	in	ADATA		;clear ADPA

	mvi	a,CSETIV	;Set IV Byte command
	out	ACMD		;initiate command now

	mvi	d,0		;256x19 uS=4.8 mS timeout

WRIVLP:	dcr	d		;(4)timeout?
	jz	ADPERR		;(10)
	in	ADSTA		;(10)wait for ADPA
	rlc			;(4)test ADPA
	jnc	WRIVLP		;(10)

	mov	a,e		;send data
	out	ADATA	

	mvi	d,0		;256x19 uS=4.8 mS timeout
WRIVWT:	dcr	d		;timeout?
	jz	HDCTO
	in	CREADY		;Is the controller done? 
	rlc			;look at msb=CRDY
	jnc	WRIVWT		;N: keep waiting

	in	CSTAT		;reset CRDY flag
	ora	a		;and get A=error code
	jnz	UNXERR		;No errors: happy return
		;Note: there should never be an error code

	pop	d
	ret			; with Z set

;===Subroutine===========================
; Seek Cylinder
; On Entry:
;   hl = cylinder
;   CRUNIT=selected unit
;   SKCKMO <>0 if seek should be verified
; On Exit:
;   hl = seek time in units of 5 uS, with
;        a resolution of 20 uS
;   z set for success
;   a=error codes for fail
; trashes bc
;========================================
SEEK:	push	d
	in	CSTAT		;clear CREADY just in case

	in	ACMD		;reset CMDACK in ACSTA

	mov	a,l		;low byte of command 1st
	out	ADATA

	lda	CRUNIT		;get current unit number
	rlc			;put into bits 3:2
	rlc
	ora	h		;combine with cyl high bit

; Time how long until for disk is ready. Result (in hl)
; is the time in units of 5 uS. Timeout after 327 mS.
; The SKWAIT loop is exactly 40 cycles = 20 uS long.
; a=seek command for correct unit & cylinder high bit here.

	lxi	d,HDCTO		;'return' if timeout
	push	d		;..(faster than jc if no carry)


	lxi	d,4		;set up timer
	lxi	h,0		;each tick of hl = 5 uS

	out	ACMD		;(10)issue seek command

SKWAIT:	dad	d		;(10)bump timer
	rc			;(5)timeout?
	in	CREADY		;(10+1)is the controller done? 
	rlc			;(4)look at msb=CRDY
	jnc	SKWAIT		;(10)N: keep waiting

	pop	d		;get rid of fake return on stack

;check for errors

	mvi	d,ERDNR		;drive not ready bit
	lda	SKCKMO		;look at more error bits?
	ora	a		;z means no
	jz	SK01
	mvi	d,SERMSK	;seek error bits
SK01:	
	in	CSTAT		;reset CRDY flag, get errors
	ana	d		;any other error bits?

	pop	d
	rz			;no errors: done

	jmp	ERDCOD		;Abort: go decode errors

;*****************************
;* Console Print Subroutines *
;*****************************

;=====Subroutine============
; Print a on the console
; On Exit:
;   a= chr with msb stripped
;===========================
PRINTA:	push	b
	ani	7Fh		;strip end-of-string mark
	push	psw
	mov	c,a		;in place for BIOS routine
	mvi	a,CONOUT
	call	GOBIOS
	pop	PSW		;recover chr
	pop	b
	ret

;===Subroutine=================================
; Call BIOS routine
; ON Entry:
;   a=low byte of routine's BIOS vector address
;   other registers as BIOS needs them
;==============================================

GOBIOS:	push	h
	push	d
	push	b

	call	GB1

	pop	b		;restore c
	pop	d
	pop	h
	ret

;===Local Subroutine==========
; go to BIOS
; a trashed on the way to BIOS
; all other regs  preserved.
;=============================
GB1:	push	h
	lhld	WBOOTA		;get BIOS base address
	mov	l,a		;a has jump vector
	xthl
	ret			;go to BIOS routine

;===Subroutine===============================================
; Print CR, LF, then inline string
; On Entry:
;    The string address is the "return address" on the stack.
;    The string is terminated by bit 7 set in its last chr.
;    The actual return address is the next address after the
;    last string character.
; On Exit:
;    all registers preserved.
;============================================================
CILPRT:	call	ILPRNT
	db	CR,LF+80h

; fall into ILPRNT to print inline string

;===Subroutine===============================================
; Print inline string on the Terminal
; On Entry:
;    The string address is the "return address" on the stack.
;    The string is terminated by bit 7 set in its last chr.
;    The actual return address is the next address after the
;    last string character.
; On Exit:
;    all registers preserved.
;============================================================
ILPRNT:	xthl			;get string address, save hl
	push	psw

PRNTLP:	mov	a,m		;get string character
	call	PRINTA

	cmp	m		;end of string?
	inx	h		;next chr
	jz	PRNTLP		;No parity: keep going

	pop	psw
	xthl			;restore hl, put return address
	ret			;..onto stack, and go there

;===Subroutine===========
; Print error count in hl
; trashes all regs
;========================
PERCNT:	call	CILPRT
	db	'Errors:',' '+80h
	jmp	PDEC16

;=====Subroutine===================
; Print a as decimal on the console
; Trashes all registers
;==================================
PDEC8:	mov	l,a
	mvi	h,0

;fall into PDEC16
;=====Subroutine================================
;Print hl as decimal on the console
;Entry at PRFRAC prints 2 digits (for after a .)
; Trashes all registers
;===============================================
PDEC16:	mvi	d,0	;Suppress leading 0's

	lxi	b,-10000
	call	DECDIG
	lxi	b,-1000
	call	DECDIG
PRFRAC:	lxi	b,-100
	call	DECDIG
	lxi	b,-10
	call	DECDIG

	mov	a,l	;last digit is simple
	jmp	DECDG0	;with leading 0's

;------------------------------------------------
; Local subroutine to divide HL by power of 10 in
; bc and print result, unless it's a leading 0.
; On Entry:
;   hl=Dividend
;   bc=divisor (a negative power of 10)
;   d=0 if all prior digits were 0
; On Exit:
;   Quotent is printed, unless it's a leading 0
;   hl=remainder
;   d=0 iff this and all prior digits are 0
;------------------------------------------------
DECDIG:	mvi	a,0FFh	;-1 will go 1 too many times
	push	d	;leading zero state

DIGLP:	mov	d,h	;DE gets prev value
	mov	e,l
	inr	a
	dad	b	;subtract power of 10
	jc	DIGLP

	xchg		;HL has remainder
	pop	d	;leading 0 state

	mov	e,a	;E has digit to print
	ora	d	;leading 0 to suppress?
	rz		;yes: digit is done

	mov	d,a	;save for next digit
 
	mov	a,e	;make ASCII
DECDG0:	adi	'0'
	jmp	PRINTA	


;===Subroutine===============================================
; Print CR, LF, 'Abort: ' then inline string
; On Entry:
;    The string address is the "return address" on the stack.
;    The string is terminated by bit 7 set in its last chr.
;    The actual return address is the next address after the
;    last string character.
; On Exit:
;    all registers preserved.
;============================================================
ABRTPF:	call	CILPRT
	db	'Abort:',' '+80h
	jmp	ILPRNT		;now print inline string

;===Subroutine===============================================
; Print CR, LF, 'Error: ' then inline string
; On Entry:
;    The string address is the "return address" on the stack.
;    The string is terminated by bit 7 set in its last chr.
;    The actual return address is the next address after the
;    last string character.
; On Exit:
;    all registers preserved.
;============================================================
ERRPF:	call	CILPRT
	db	'Error:',' '+80h
	jmp	ILPRNT		;now print inline string

;===Subroutine=====
; Decode error bits
; On Entry:
;   Error code in a
;==================
ERDCOD:	rar			;test bit 0
	cc	PDNR		;Drive not ready
	
	rar			;test bit1
	jnc	ERRD2
	call	ERRPF
	db	'Illegal Secto','r'+80h	
	
ERRD2:	rar			;test bit2
	jnc	ERRD3
	call	ERRPF
	db	'CRC error in secto','r'+80h	
	
ERRD3:	rar			;test bit3
	jnc	ERRD4
	call	ERRPF
	db	'CRC error in heade','r'+80h	
	
ERRD4:	rar			;test bit4
	jnc	ERRD5
	call	ERRPF
	db	'Header has wrong secto','r'+80h	
	
ERRD5:	rar			;test bit5
	jnc	ERRD6
	call	ERRPF
	db	'Header has wrong cylinde','r'+80h	
	
ERRD6:	rar			;test bit6
	jnc	ERRD7
	call	ERRPF
	db	'Header has wrong hea','d'+80h	
	
ERRD7:	rar			;test bit7
	jnc	ERRD8
FPERR:	call	CILPRT
	db	'Disk is write-protecte','d'+80h

ERRD8:	ret

;===Error Routine===========
;Print Abort Drive not ready
;===========================
ABDNR:	call	CILPRT
	db	'Abort: ',' '+80h
	call	PDNR
	jmp	MAIN

;===Subroutine=========================================
;Print Unavailable: Drive not ready during IV Byte read
; All registers and flags preserved
;======================================================
UNADNR:	call	ILPRNT
	db	' unavailable:',' '+80h

; fall into PDNR

;===Subroutine=====================
;Print Drive not ready
; All registers and flags preserved
;==================================
PDNR:	call	ILPRNT
	db	'Drive not read','y'+80h
	ret

;===Error Routine============
; Unexpected Controller Error
; On Entry:
;   Error code in a
;============================
UNXERR:	call	ERRPF		;print the following error string
	db	'Unexpected command error. Code:',' '+80h

	call	PRHEX
	jmp	MAIN

;===Error Routine=========
; Controller Timeout Error
;=========================
HDCTO:	call	ERRPF
	db	'Controller Timeout','!'+80h
	jmp	MAIN

;===Error Subroutine==================
; Controller Data Port Not Ready Error
;=====================================
CDPERR:	call	ERRPF
	db	'CDA flag not se','t'+80h

	ret

;===Error Subroutine===
; No Command Ack Error
;======================
CAKERR:	call	ERRPF
	db	'No Command Ac','k'+80h

	ret

;===Error Routine=================
; Altair Data Port Not Ready Error
;=================================
ADPERR:	call	ERRPF
	db	'ADPA flag not set',CR,LF
	db	'Try manually resetting controlle','r'+80h
	jmp	MAIN

;===Error Routine=========
; Timeout waiting for disk
;=========================
DISKTO:	call	ERRPF
	db	'Disk Timeou','t'+80h
	jmp	MAIN

;===Subroutine===========
;Report verify read error
; On Entry:
;   a=error code
; On Exit:
;   c=0
; trashes a,b,hl
;========================
VRSERR:	call	ILPRNT
	db	CR,LF+80h

	lhld	BYTCNT		;bump error count
	inx	h
	shld	BYTCNT

	call	PRHEX		;error code
	call	ILPRNT
	db	' erro','r'+80h
	jmp	VERERR

;===Command Exit=======================
; Abort: Error during format data write
;  a=error code
;  c=sector number, b=0
;  CRCYL=cyl number
;======================================
FMTABT:	call	ABRTPF
	db	'error',' '+80h

	call	PRHEX		;print error code

	call	ILPRNT
	db	' writing sector',' '+80h

	lxi	h,MAIN		;create return address
	push	h		;MAIN will fix the stack

	lxi	h,INLV2		;2:1 interleave sector
	jmp	FMTERR		;recycle some code

;====Subroutine=============
;Report verify data mismatch
; On Exit:
; trashes a,bc,hl
;===========================
VDMERR:	call	CILPRT
	db	'Data mismatc','h'+80h

VERERR:	call	ILPRNT
	db	' reading sector',' '+80h

	pop	h		;temp save return address
	pop	b		;recover b=0,c=sector #
	push	b
	push	h		;put return address back

	lxi	h,INLV7		;7:1 interleave sector

FMTERR:	push	d
	dad	b
	mov	a,m		;interleaved sector number
	call	PDEC8

	lhld	BYTCNT		;bump error count
	inx	h
	shld	BYTCNT

	call	ILPRNT
	db	' cylinder',' '+80h
	lhld	CRCYL		;cylinder number
	call	PDEC16
	pop	d
	ret

;===Subroutine==========================
; Print 'Disk transfer did not complete'
;=======================================
PNODTR:	call	ERRPF
	db	'Disk transfer did not complet','e'+80h

; fall into REMERR

;===Subroutine=================
; remember that we had an error
;==============================
REMERR:	push	psw
	mvi	a,01
	sta	RESULT	;remember error
	pop	psw
	ret

;===Subroutine===========
; Print name of platter
; On Entry:
;  a=0 for cartridge disk
;      otherwise, fixed
;========================
PNAME:	ora	a
	jz	PNAME1
	call	ILPRNT
	db	' fixed dis','k'+80h
	ret

PNAME1:	call	ILPRNT
	db	' removable cartridg','e'+80h
	ret

;*****************************
;* Console Input Subroutines *
;*****************************
;=====Subroutine===========================
;Ask user Y or N question
;
; On Exit: 
;   a=0 and z set if "N"
;   a=1 and z clear if "Y"
;   carry set if bogus 
;Trashes a,hl
;==========================================
ASKYN:	call	ILPRNT
	db	' (Y/N)?',' '+80h

	call	GET1		;get 1-chr response

	sui	'N'
	rz			;z set for no

	sui	'Y'-'N'
	stc
	rnz	ASKYN		;carry set

	inr	a		;clear z, a=1
	ret			;z clear for yes

;=====Subroutine========================================
; Get and echo a single-character input from the console
; On exit:
;  if just 1 chr typed, then a=chr, converted
;  to uppercase
;  quit directly to CP/M if Q typed
;  a=0 if too many characters typed
;
; trashes a,hl
;======================================================
GET1:	lxi	h,COMBUF
	mvi	m,10		;allow the user to type 10 chrs

	push	b
	push	h
	push	d
	xchg
	mvi	c,BRDCON	;get a line of user input
	call	BDOS
	pop	d
	pop	h
	pop	b

	inx	h		;count of chrs typed
	xra	a
	dcr	m
	rnz			;must be just 1 chr there

	inx	h		;get the only chr
	mov	a,m
	ani	('a'-'A') xor 0FFh
	cpi	'Q'		;quit to CP/M?
	rnz			;n: ignore

	jmp	WBOOT

;=====Subroutine========
; Test for Q pressed.
; Rude jump to BDOS if Q
; A=chr if not Q
; Z set if no chr
; Trashes a
;=======================
TESTQ:	mvi	a,CONST
	call	GOBIOS
	ora	a		;any chr waiting?
	rz			;N: ret with z set

	mvi	a,CONIN
	call	GOBIOS		;get the chr

	ani	('a'-'A') xor 0FFh ;either case
	cpi	'Q'		;quit to CP/M?
	rnz

	jmp	WBOOT

;*********************
;* Misc. Subroutines *
;*********************
;===Table=======================
; 2:1 Interleave for Copying
; This table is in reverse order
;===============================
INLV2:	db	23,21,19,17,15,13,11,09,07,05,03,01
	db	22,20,18,16,14,12,10,08,06,04,02,00

;===Table=======================
; 7:1 Interleave for Verifying
; This table is in reverse order
;===============================
INLV7:	db	17,10,03
	db	20,13,06
	db	23,16,09,02
	db	19,12,05
	db	22,15,08,01
	db	18,11,04
	db	21,14,07,00

;=============================================
; RAM variables and buffers
; Initialized at the beginning of code.
; The 1st 512 bytes get cleared, then non-zero
; variables get initialized, including setting
; the first byte of PATBUF to 1, which makes
; the default seek be a seek to cylinder 0.
;=============================================

VARSP:

;=============
;RAM variables
;=============
CRRTO:	ds	1	;{IVIOMN/0/IVIOPN} read track offset

;CRPLAT must follow CRSIDE

CRCYL:	ds	2	;{0:405}current cylinder
CRSIDE:	ds	1	;{0/1}current side
CRPLAT:	ds	1	;{0:3}current platter
CRUNIT:	ds	1	;{0:3}current unit number

;===========
;Local stack
;===========
	ds	32
STACK:				;top of stack

;======================
;4-Sector verify buffer
;======================
VERBUF:	ds	256*4

	END


