;==============================================================
;ADEXER - Altair Datakeeper Disk Formatter for CP/M
;==============================================================
; This program formats either platter of an Altair Datakeeper
; hard disk drive.
;
; The disk is first formatted using the Datakeeper Controller's
; low-level format, which takes four passes over the disk to
; format and then verify each side. It then writes E5h to every
; byte in every sector of the disk, and verifies these writes.
; Finally, it writes a Pack Descriptor Page to sector 0,
; side 0, track 0, after giving the user an opportunity to
; provide a disk label and date for the Pack Descriptor.
;
; The entire formatting process takes about 3 minutes and 45
; seconds to complete.
;
; The Pack Descriptor page installed by DKFORMAT includes a
; little piece of code that will be loaded and executed if
; someone were to try and boot from a newly-formatted disk
; cartridge. A SYSGEN program for the Datakeeper must rewrite
; the Pack Descriptor Page to tell the loader where to find
; the boot image.
;
; Notes:
; Interleaving of reads and writes has been done with care,
; to minimize slipped revs. When writing track data, the
; interleave is 2:1, allowing one sector time (1041 mS) to
; prepare to write each sector. When verifying track data,
; the interleave is 4:1, allowing 3 sector times (3125 mS)
; between sectors to check the sector data and prepare for the
; next sector. The interleave starts at a different sector for
; operation, so that no revs are slipped when switching sides
; or switching from write to read. Including seek and settling
; times, the write/verify operation will take 13 or 14
; revolutions (325-350 mS) per track track, or about 2 minutes
; and 20 seconds to write/verify an entire platter. This is in
; addition to 1 minute 25 seconds to format the disk, which is
; entirely controlled by the disk controller.
;
;==============================================================
; Revision History:
; 1.00  20 Oct 2014  M.Eberhard
;  Created.
; 1.01  29 Oct 2014  M. Eberhard
; Ask fixed disk or removable, instead of command line input.
; a bit of streamlining.  
;
; To Do
; * Display default disk label.
;==============================================================
FALSE	equ	0
TRUE	equ	not FALSE

FILCHR	equ	0E5h		;Sector fill character (E5 for CP/M)

;-------------------------------------------
;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

;--------------------------------------------------------
;BIOS Entry Points, relative to the base address in WBOOT
;--------------------------------------------------------
CONST	equ	06h		;Console Status
CONIN	equ	09h		;Console Input
CONOUT	equ	0Ch		;Console output

;------------------------------------------
;BDOS Function Codes, passed in register c
;------------------------------------------
BRDCON	equ	10		;Get Line from Console

;===========================================================
;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
DERMSK	  equ	  81h	  ;error mask for dummy read

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

;=================
;88-HDSK Constants
;=================
MAXSEC	equ	23	;max sector
MAXCYL	equ	405	;max cylinder
NPLATS	equ	2	;number of platters

;================
;ASCII characters
;================
CTRLC	equ	03h
LF	equ	0Ah
CR	equ	0Dh

;=====================
;Start of code
	ORG	USAREA
;=====================
	di
	lxi	SP,STACK	;SP=local stack

;--------------------------------------
;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	' ====================================',CR,LF
	db	' = Altair Datakeeper Formatter 1.01 =',CR,LF
	db	' = Martin Eberhard    October, 2014 =',CR,LF
	db	' ===================================='
	db	CR,LF+80h

;----------------------------
;Get and validate the platter
;----------------------------
	mvi	a,64		;allow lots of typing
	sta	COMBUF		;set up for BDOS

ASKDSK:	call	CILPRT
	db	'Removable cartridge or Fixed disk (R/F)?',' '+80h

	lxi	d,'F'*256+'R'
	call	ASKDE
	jnz	ASKDSK		;bogus?

	mvi	a,0		;don't affect carry
	jc	REMOV		;removable cartridge?
	mvi	a,40h		;fixed disk bit

REMOV:	sta	CRHEAD

;------------------
;Ask for disk label
;------------------
ASKLAB:	call	CILPRT
	db	'Disk label (19 chrs max, Return for default):',' '+80h

	lxi	h,COMBUF
	mvi	m,75		;allow user to type extra chrs

	xchg			;buffer address to de
	mvi	c,BRDCON	;get a line of user input
	call	BDOS

	lxi	h,COMBUF+1	;count of chrs typed
	mov	a,m
	ora	a
	jz	SKPLAB

	mov	b,a	

	lxi	d,PDLABL-1	;label in pack descriptor
	mvi	c,19		;keep 1st 19 characters

LABLUP:	inx	h
	inx	d
	mvi	a,' '		;pad with spaces
	dcr	b
	jm	LL2
	mov	a,m
LL2:	stax	d
	dcr	c
	jnz	LABLUP
SKPLAB:

;------------
;Ask for date
;------------
ASKYR:	call	CILPRT
	db	'Year (YYYY, or Return to skip)?',' '+80h
	call	GETDEC		;result in de
	jc	ASKYR		;bogus?
	jz	NODATE		;no input?

	lxi	h,-1900		;offset to 1900
	dad	d
	mov	a,h		;too high?
	ora	a
	jnz	ASKYR

	mov	a,l
	sta	PDYEAR

ASKMON:	call	CILPRT
	db	'Month (1-12)?',' '+80h
	call	GETDEC		;result in de
	jc	ASKMON		;bogus?
	mov	a,d		;too high?
	ora	a
	jnz	ASKMON
	mov	a,e
	sta	PDMON
	dcr	a
	cpi	12		;12 months per year
	jnc	ASKMON

	lxi	h,DPMTAB-1	;days per month
	dad	d		;look up for this month
	mov	b,m		;remember month

ASKDAY:	call	CILPRT
	db	'Day (1','-'+80h
	mov	l,b
	mvi	h,0
	push	b
	call	PDEC16
	call	ILPRNT

	db	')?',' '+80h
	call	GETDEC		;result in de
	pop	b
	jc	ASKDAY		;bogus?

	mov	a,e
	sta	PDDAY
	dcr	a
	cmp	b		;too high?
	jnc	ASKDAY
NODATE:

;-----------------
; Get confirmation
;-----------------
	call	CILPRT
 db 'Write-protect on the other disk is recommended','.'+80h

GETCNF:	lda	CRHEAD		;0 for removable cartridge
	ora	a
	jz	PNAME1
	call	CILPRT
	db	'Format the fixed dis','k'+80h
	jmp	ASKRDY

PNAME1:call	CILPRT
	db	'Install removable cartridge to format. Read','y'+80h

ASKRDY:	call	ASKYN
	jnz	GETCNF		;Bogus?
	jc	DONE		;Not ready?

;-----------------------------------------
;Perform dummy read into buffer 3, to test
;write protect and drive ready
;-----------------------------------------
	lxi	h,DONE		;exit onto stack
	push	h

	lda	CRHEAD		;sector,side & platter into a
	mov	l,a
	mvi	h,CRUSEC+3	;command, buffer 3
	call	HDCMD1		;read sector 0 now
	ani	DERMSK		;write protect or not ready?
	jnz	ERDCOD		;report either error

;-------------------------
;Format both sides of Disk
;-------------------------
FORMAT:	call	CILPRT
	db	'Formatting side',' '+80h

	mvi	h,CFORMT
	lda	CRHEAD		;get platter. Side bit=0
	mov	l,a		;hl for format command

	rlc			;put side bit in bit 0
	rlc
	rlc
	ani	1		;just the side bit
	adi	'0'
	call	PRINTA

	mvi	c,61		;60 second timeout 
	call	HDCMD		;format now
	jnz	ERDCOD		;error exit, returns to DONE

	call	TOGSID		;Format the other side
	jnz	FORMAT		;unless we already did

;---------------------------------------------------
;Fill controller buffer 0 with fill character FILCHR
;---------------------------------------------------
	call	CBPREP		;prepare to write to buffer 0
				;(sets c=0 too)

; loop to write 256 bytes to controller (c=0)

	mvi	a,FILCHR	;sector fill data

FILLUP:	out	ADATA		;send to controller
	dcr	c
	jnz	FILLUP

;--------------------------------------------------------
;Write then verify every cylinder on the disk. Use
;buffer 0 to write, and and verify using buffer 1.
;Write/Verify from insided diameter to outside diameter
;because the format left the head at the inside diameter.
;--------------------------------------------------------
	call	CILPRT
	db	'Writing & verifying sector data. ^C to abort.'
	db	CR,LF+80h

	lxi	h,MAXCYL	;initial cylinder

;------------------------------------
;Seek the next cylinder, with timeout
;------------------------------------
FCLOOP:	shld	CRCYL		;save cylinder

	in	CSTAT		;clear CREADY just in case
	in	ACMD		;reset CMDACK in ACSTA

	mov	a,l		;low byte of command
	out	ADATA

	mov	a,h		;cyl high bit & seek cmd
	out	ACMD		;seek now

	call	TESTC		;opportunity to abort

	lxi	h,4082		;100 mS timeout (should take 10 ms)

SKWAIT:	dcx	h		;(5)bump timer
	mov	a,h		;(5)
	ora	l		;(4)
	jz	HDCTO		;(10)timeout?

	in	CREADY		;(10+1)is the controller done? 
	rlc			;(4)look at msb=CRDY
	jnc	SKWAIT		;(10)N: keep waiting

;check for seek errors

	in	CSTAT		;reset CRDY flag, get errors
	ani	SERMSK		;any other error bits?
	jnz	SEKERR		;error exit, returns to DONE

;------------------------------------------------
;Loop to write/verify both sides of current track
;------------------------------------------------
WSIDLP:	lxi	d,MAXSEC	;(10)sector counter, buffer #

;--------------------------------------------------------
;Write controller buffer 0 to every sector on the current
;side of the current track. This takes 305 cycles =
;152 uS per sector. 2:1 interleave allows 1 sector time,
;or 1.04 mS to prepare to write each sector.
;--------------------------------------------------------
WSECLP:	lxi	h,INLV2		;(10)2:1 interleave sector
	call	INTRLV		;(17+67)hl=interleaved sector
	lda	CRHEAD		;(10)side & platter into a
	ora	m		;(4)combine with sector
	mov	l,a		;(5)for low command byte
	mvi	h,CWRSEC	;(7)command, buffer 0

	call	HDCMD1		;(17+158)Write the sector now
	cnz	SWRERR		;(10)report write error

;---------------------
;Write the next sector
;---------------------
	dcr	e		;(5)next sector
	jp	WSECLP		;(10)

;-------------------------------------------
;Write the other side, unless we already did
;-------------------------------------------
	call	TOGSID
	jnz	WSIDLP

;------------------------------------------------------
;Verify every sector on both sides of the current track
;------------------------------------------------------
VSIDLP:	lxi	d,MAXSEC	;(10)sector counter

	lxi	h,VDMERR	;(13) 'return' address on stack
	push	h		;(11)because rnz is faster

	lxi	h,INLV4		;(10)4:1 interleave sector

;----------------------------------------------------
;Verify every sector on the current side/track. This
;requires 6025 cycles (3013 uS) to test each sector.
;Three sector times=3125 uS, so 4:1 interleave works.
;de=sector counter
;h = interleave table address
;error reporting address is top of stack
;----------------------------------------------------
VSECLP:	call	INTRLV		;(17+67)hl=table, de=sector count
				;trashes c
	lda	CRHEAD		;(13)get side & platter into a
	ora	m		;(7)a=interleaved sector number
	mov	l,a		;(5)combine for low command byte
	mvi	h,CRDSEC+1	;(7)read command, buffer 1
				;hl has complete command

;read a sector into buffer 1. hl=command.

	call	HDCMD1		;(17+158)read sector now
	cnz	SRDERR		;(11) announce any error

	in	CDATA		;(10+1)reset CDA in CDSTA
	in	ACMD		;(10+1)reset CMDACK in ACSTA

;set up to read 256 bytes from buffer 1

	xra	a		;(4)transfer 256 bytes
	out	ADATA		;(10)low byte of command 1st

	mvi	a,CRDBUF+1	;(7)read buffer 1
	out	ACMD		;(10)and issue command

; The 8x300 is ready to transmit data in 8 uS. This code
; takes 20 cycles, or 10 uS to get around to reading the
; data - so there is no need to wait on CDSTA

	lxi	h,INLV4		;(10)load here for delay
	lxi	b,32*256+FILCHR	;(10)b=32 8-byte groups, c=FILCHR

	in	CDSTA		;(10+1)data port should be ready
	rlc			;(4)msb=CDA
	jnc	HDCTO		;(10) controller timeout

;------------------------------------------------
;compare every byte of the sector we just read to
;the data that we wrote. Unrolled loop for speed.
;This takes 5600 cycles to check 256 data bytes.
;------------------------------------------------
VSDLP:	in	CDATA		;(10+1)get a data byte
	cmp	c		;(4)compare to correct data
	rnz			;(5) report error

	in	CDATA		;(10+1)get a data byte
	cmp	c		;(4)compare to correct data
	rnz			;(5) report error

	in	CDATA		;(10+1)get a data byte
	cmp	c		;(4)compare to correct data
	rnz			;(5) report error

	in	CDATA		;(10+1)get a data byte
	cmp	c		;(4)compare to correct data
	rnz			;(5) report error

	in	CDATA		;(10+1)get a data byte
	cmp	c		;(4)compare to correct data
	rnz			;(5) report error

	in	CDATA		;(10+1)get a data byte
	cmp	c		;(4)compare to correct data
	rnz			;(5) report error

	in	CDATA		;(10+1)get a data byte
	cmp	c		;(4)compare to correct data
	rnz			;(5) report error

	in	CDATA		;(10+1)get a data byte
	cmp	c		;(4)compare to correct data
	rnz			;(5) report error

	dcr	b		;(5)next 8 bytes
	jnz	VSDLP		;(10) (39 cycles per pass)

;------------------
;Verify next sector
;------------------
VDMRET:	dcr	e		;(5)next sector
	jp	VSECLP		;(10)

;--------------------------------------------
;Verify the other side, if we haven't already
;--------------------------------------------
	pop	d		;get rid of 'return'  address

	call	TOGSID		;other side
	jnz	VSIDLP		;unless we already did

;---------------------------
;Write/verify the next track
;---------------------------
	mvi	a,'.'
	call	PRINTA		;pacifier

	lhld	CRCYL
	mov	a,l		;just did cyl 0?
	ora	h

	dcx	h
	jnz	FCLOOP		;n: keep going

;----------------------------------------
;Write Pack Descriptor Page to controller
;buffer 0. The side bit in CRHEAD=0 here.
;The head is already on cylinder 0
;----------------------------------------
	call	CILPRT
	db	'Writing & verifying pack descripto','r'+80h
	call	CBPREP		;prepare to write buffer 0

	lxi	h,PDESC		;source

; loop to write 256 bytes to controller (c=0)

FCWDAT:	mov	a,m		;get RAM buffer data
	out	ADATA		;send to controller
	inx	h
	dcr	c
	jnz	FCWDAT

;---------------------------------------------------
;Write controller buffer 0 to track 0, side 0, sec 0
;---------------------------------------------------
	lda	CRHEAD		;sector,side & platter into a
	mov	l,a		;for low command byte
	mov	d,a		;save for verify
	mvi	h,CWRSEC	;command, buffer 0
	call	HDCMD1		;write sector 0 now
	cnz	SWRERR		;report any write error

;-------------------------------------------------------
;read track 0, side 0, sector 0 into controller buffer 1
;-------------------------------------------------------
	mov	l,d		;sector,side & platter
	mvi	h,CRDSEC+1	;command, buffer 1
	call	HDCMD1		;read sector 0 now
	cnz	SRDERR		;report any read error

;-----------------------------------------------------
;compare controller buffer 1 to Pack Descriptor in RAM
;-----------------------------------------------------
	in	CDATA		;reset CDA in CDSTA
	in	ACMD		;reset CMDACK in ACSTA

	xra	a		;transfer 256 bytes
	out	ADATA		;low byte of command 1st

	mvi	a,CRDBUF+1	;read buffer 1
	out	ACMD		;..issue command

; The 8x300 is ready to transmit data in 8 uS. This code takes
; 20 cycles, or 10 uS min to get around to reading the data -
; so there is no need to wait on CDSTA

	lxi	h,PDESC		;(10)
	lxi	d,0		;(10)byte count, init errors

	in	CDSTA		;check for data port ready
	rlc			;msb=CDA
	jnc	HDCTO		;timeout error returns to DONE

; Loop to compare 256 sector bytes from the controller to (hl).

VDLOOP:	in	CDATA		;get a data byte
	xra	m		;compare to correct data
	ora	e		;accumulate errors
	mov	e,a

	inx	h
	dcr	d		;next byte
	jnz	VDLOOP

	lhld	ERRCNT		;prepare to report

	ora	a		;any errors?
	jz	REPORT

;Pack descriptor failed verification - report

	inx	h		;bump error count

	call	CILPRT
	db	'Pack descriptor verify faile','d'+80h

;Done - report error count and return to CP/M

REPORT:	call	CILPRT
	db	'Total errors:',' '+80h

	jmp	PDEC16		;report total errors
				;& return to DONE

;**************
; Subroutines *
;**************
;===Subroutine==============================================
;Issue a disk command
;Note: this just assumes the controller is ready
;On Entry:
;  hl = complete command
;On Entry at HDCMD1:
;  1-second timeout
;On Entry at HDCMD:
;  c = timeout in seconds+1, c>1
;On Exit:
;  a = error flags
;  Z set if no errors
;  The command is completed and the controller is ready
;Trashes c,hl
;HDCMD1: (158 cycles, assuming fast response fom controller) 
;===========================================================
HDCMD1:	mvi	c,2		;(7)1 second timeout

HDCMD:	in	CSTAT		;(10+1) Clear CREADY in case
	in	ACMD		;(10+1)reset CMDACK in ACSTA

; issue both command bytes

	mov	a,l		;(4)low byte of command 1st
	out	ADATA		;(10)

	mov	a,h		;(5)cmd high byte
	out	ACMD		;(10)

;wait for controller ack, with timeout

HDWAIT:	dcr	c		;(4)timeout?
	jz	HDCTO		;(10)y:timeout error

	lxi	h,39216		;(10)c x 1 sec timeout

HDW1:	dcx	h		;(7)bump timer
	mov	a,h		;(5)
	ora	l		;(4)
	jz	HDWAIT		;(10)1-sec timeout?

	in	CREADY		;(10+1)Is the controller done?
	rlc			;(4)look at msb=CRDY
	jnc	HDW1		;(10)n: keep waiting

;clear handshake and return

	in	CSTAT		;(10+1)reset CRDY flag
	ora	a		;(4)test errors
	ret			;(10)Z set if okay

;===Subroutine==========
;Toggle the current side
;On exit:
; z set if side 0
;trashes a
;=======================
TOGSID:	lda	CRHEAD
	xri	20h		;other side
	sta	CRHEAD
	ani	20h		;set z if side 0
	ret

;===Subroutine===================================
;Compute interleaved sector number
;Offset intnerleave by 8 table entries for side 1
;so that we don't slip a rev when switching sides
;On Entry:
;   de = sector number
;   hl = interleave table to use
;  CRHEAD bit 5 = current side
;On Exit:
;   (hl)=interleaved sector number
; trashes a,c,hl
; 67 cycles
;================================================
INTRLV:	dad	d		;(10)
	mov	c,e		;(5)temp save sector

	lda	CRHEAD		;(13)offset for side 1
	ani	20h		;(7)get side bit
	mov	e,a		;(5)
	dad	d		;(10)side offset into table

	mov	e,c		;(5)restore sector
	ret			;(10)

;===Subroutine===========================
;Prepare to write to controller buffer 0
;Since the controller is ready to accept
;data 8 uS after the command, there is
;no need to timeout for it to be ready-
;just a quick stall.
;On exit:
; c=0
; controller is ready to accept 256 bytes
;trashes a,b
;========================================
CBPREP:	in	ADATA		;reset ADPA in ADSTA
	in	ACMD		;reset CMDACK in ACSTA

	xra	a		;transfer 256 bytes
	mov	c,a		;c=0 for return
	out	ADATA		;low byte of command 1st

	mvi	a,CWRBUF	;write to buffer 0 command
	out	ACMD		;and issue command

	xthl			;(18)stall 18 uS
	xthl			;(18)

CBPWAT:	in	ADSTA		;data port should be ready
	rlc			;msb=ADPA
	rc			;error if not ready

;timeout: fall into HDCTO to abort

;*****************
; Error Routines *
;*****************
;===Error Routine========
;Controller Timeout Error
;========================
HDCTO:	call	ERRPF
	db	'Controller Timeout',CR,LF
	db	'Reset controller with ke','y'+80h
	jmp	DONE		;wait for user too

;====Subroutine===============================
;Report sector verify data mismatch and flush
;remaining sector bytes.
; For speed reasons, the verify loop gets
; here via a "return" and this subroutine
; actually jumps back to the verify loop.
; On Entry:
;   b=(remaining bytes to read)/8+1
;  de=sector counter
; On exit:
;   VDMERR is back on stack
; trashes a,bc
;=============================================
VDMERR:	push	h
	push	b
	call	RVMERR		;message
	pop	b
	pop	h

; flush rest of sector. This may read up to 7
; extra bytes from the controller, which will not
; cause any harm.

	mov	a,b		;remaining bytes/8
	rlc
	rlc
	rlc
	dcr	a
	mov	b,a		;remaining bytes

VEFLSH:	in	CDATA		;get a data byte
	dcr	b		;next byte
	jnz	VEFLSH

	lxi	b,VDMERR	;put 'return' address
	push	b		;back on stack
	jmp	VDMRET		;'return' to verify loop

;===Subroutine=========
;Report verify mismatch
;  de=sector counter
; CRCYL=cyl number
;======================
RVMERR:	call	ERRPF
	db	'Data mismatc','h'+80h

	jmp	RRDERR

;===Subroutine===========
;Report error during seek
; a=error code
;  de=sector counter
; CRCYL=cyl number
;========================
SEKERR:	call	ERDCOD
	call	CILPRT
	db	'Seekin','g'+80h

	push	d
	push	b
	jmp	CYLERR

;===Subroutine===================
;Report error during sector write
; a=error code
;  de=sector counter
; CRCYL=cyl number
;trashes a,hl
;================================
SWRERR:	call	ERDCOD		;decode error bits
	call	CILPRT
	db	'Wri','t'+80h

	lxi	h,INLV2		;2:1 interleave sector
	jmp	SRWERR

;===Subroutine==================
;Report error during sector read
;On Entry:
;  a=error code
;  de=sector count
; CRCYL=cyl number
;trashes a,hl
;===============================
SRDERR:	call	ERDCOD		;decode error bits

; Fall into RRDERR

;===Subroutine=============================
;Finish reporting a read error of some sort
;On Entry:
;  de=sector count
; CRCYL=cyl number
;trashes a,hl
;==========================================
RRDERR:	call	ILPRNT
	db	' rea','d'+80h
	lxi	h,INLV4		;4:1 interleave sector

; Fall into SRWERR

;===Subroutine end==================
;Finish reporting a read/write error
;On Entry:
;  de=sector count
;  hl=appropriate interleave table
; CRCYL=cyl number
;trashes a,hl
;===================================
SRWERR:	call	ILPRNT
	db	'ing sector',' '+80h

	push	d
	push	b

	call	INTRLV		;interleave sector to (hl)
	mov	l,m		;interleaved sector number
	mvi	h,0

	call	PDEC16

;fall into CYLERR

;===Subroutine end=================
;Report cylinder number for error
;On Entry:
;  CRCYL=cylinder
;  bc & de are already on the stack
;On Exit:
;  ERRCNT bumped
;trashes a,hl
;==================================
CYLERR:	lhld	ERRCNT		;bump error count
	inx	h
	shld	ERRCNT

	call	ILPRNT
	db	' cylinder',' '+80h
	lhld	CRCYL		;cylinder number

	call	PDEC16

	call	TESTC		;user abort?

	pop	b
	pop	d
	ret

;===Subroutine===============
;Decode and report error bits
;On Entry:
;  Error code in a
;trashes a
;============================
ERDCOD:	rar			;test bit 0
	jnc	ERRD1		;Drive not ready
	call	ERRPF
	db	'Drive not read','y'+80h
	
ERRD1:	rar			;test bit1
	jnc	ERRD2
	call	ERRPF
	db	'Illegal Secto','r'+80h	
	
ERRD2:	rar			;test bit2
	jnc	ERRD3
	call	ERRPF
	db	'Data CR','C'+80h	
	
ERRD3:	rar			;test bit3
	jnc	ERRD4
	call	ERRPF
	db	'Header CR','C'+80h	
	
ERRD4:	rar			;test bit4
	jnc	ERRD5
	call	ERRPF
	db	'Wrong secto','r'+80h	
	
ERRD5:	rar			;test bit5
	jnc	ERRD6
	call	ERRPF
	db	'Wrong cylinde','r'+80h	
	
ERRD6:	rar			;test bit6
	jnc	ERRD7
	call	ERRPF
	db	'Wrong hea','d'+80h	
	
ERRD7:	rar			;test bit7
	jnc	ERRD8
FPERR:	call	CILPRT
	db	'Write-protecte','d'+80h

ERRD8:	ret

;===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

;*****************************
;* Console Print Subroutines *
;*****************************

;===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 hl as decimal on the console
; Trashes all registers
;===============================================
PDEC16:	mvi	d,0	;Suppress leading 0's

	lxi	b,-10000
	call	DECDIG
	lxi	b,-1000
	call	DECDIG
	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'

; Fall into PRINTA

;=====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
;  bc,de as BIOS needs them
;Trashes a
;=============================================
GOBIOS:	push	h
	push	d
	push	b

	call	GB1

	pop	b
	pop	d
	pop	h
	ret

;===Local Subroutine==========
;Go to BIOS
; a trashed on the way to BIOS
; all other regs  preserved.
;=============================
GB1:	lhld	WBOOTA		;get BIOS base address
	mov	l,a		;a has jump vector
	pchl			;go to BIOS routine

;*****************************
;* Console Input Subroutines *
;*****************************
;=====Subroutine====================
;Get CR-terminated decimal parameter
;parameter the console
; On entry:
;  COMBUF(0) is initialized >>4
; On Exit:
;   de=value, 0 if no chrs
;   z set if no characters typed
;   carry set if bogus digit
; trashes all registers
;===================================
GETDEC:	lxi	d,COMBUF
	mvi	c,BRDCON	;get a line of user input
	call	BDOS

	lxi	h,COMBUF+1	;count of chrs typed
	mov	a,m
	ora	a		;no digits?
	rz

	cpi	5		;too many digits?
	cmc
	rc			;y: ret with carry set

	mov	b,a		;b=chr count

	lxi	d,0		;initial value
	xra	a		;clear carry

GDLOOP:	dcr	b		;does not affect carry
	rm			;get all digits

	inx	h		;next chr
	mov	a,m

	sui	'0'		;de-ASCII
	cpi	10		;carry means below 10
	cmc
	rc			;bogus? ret with carry set

	push	h		;save input pointer
	xchg			;previous value to hl

				;..and save pointer
	dad	h		;x2
	mov	d,h
	mov	e,l
	dad	h		;x4
	dad	h		;x8
	dad	d		;x10

	mov	e,a		;add in new digit
	mvi	d,0
	dad	d		;will clear carry if no error
	xchg			;result to de
	pop	h
	jmp	GDLOOP

;===Subroutine======================
;Ask yes or no
;On Exit:
; Z set if either Y or N
; carry and Z set if N
; trashes all registers
;===================================
ASKYN:	lxi	d,'Y'*256+'N'

	call	ILPRNT
	db	' (Y/N)?',' '+80h 

;fall into ASKDE

;===Subroutine===========================
;Ask a or b type question
; On Entry:
; d="a" response
; e="b" response
;On Exit:
; Z set if match to either d or e
; carry and Z set if match to e
; trashes a,bc,hl
;========================================
ASKDE:	push	d
	lxi	d,COMBUF
	mvi	c,BRDCON	;get a line of user input
	call	BDOS
	pop	d

	lxi	h,COMBUF+1
	dcr	m
	rnz			;too many digits?

	inx	h		;get the only chr
	mov	a,m

	ani	('a'-'A') xor 0FFh

	cmp	d		;Z set if matching d
	rz

	cmp	e		;done if matching e
	stc
	ret

;=====Subroutine=========
;Test for ^C pressed.
;Rude jump to WBOOT if ^C
;On Exit:
; Trashes all registers
;========================
TESTC:	mvi	a,CONST
	call	GOBIOS
	ora	a		;any chr waiting?
	rz			;N: ret with z set

	mvi	a,CONIN
	call	GOBIOS		;get the chr

	cpi	CTRLC
	rnz

	call	CILPRT
	db	'Abor','t'+80h

; Fall into DONE

;=====Exit==================================
;Put the controller in a good state, ask
;the user to install the CP/M disk in A,
;and jump to CP/M.
;===========================================
DONE:	call	CILPRT
 db 'Install CP/M disk in drive A. Read','y'+80h

	call	ASKYN
	jnz	DONE		;bogus?
	jc	DONE		;No?	

;done: clean up controller and exit. 

	in	CSTAT		;clear CREADY
	in	ACMD		;reset CMDACK in ACSTA

	jmp	WBOOT

;===Table========================
;2:1 Interleave for writing
;This table is in reverse order.
;The table for side 1 is skewed
;from the side 0 so that we don't
;slip a rev when switching sides. 
;================================
INLV2:
; side 0 table

	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

	db	0,0,0,0,0,0,0,0	;spacer

;side 1 table

	db	01,23,21,19,17,15,13,11,09,07,05,03
	db	00,22,20,18,16,14,12,10,08,06,04,02

;===Table============================
;4:1 Interleave for verifying
;This table is in reverse order.
;The table for side 1 is skewed
;from the side 0 so that we don't
;slip a rev when switching sides. 
;====================================
INLV4:

;side 0 table

	db	04,00,20,16,12,08
	db	03,23,19,15,11,07
	db	02,22,18,14,10,06
	db	01,21,17,13,09,05

	db	0,0,0,0,0,0,0,0	;spacer

;side 1 table

	db	08,04,00,20,16,12
	db	07,03,23,19,15,11
	db	06,02,22,18,14,10
	db	05,01,21,17,13,09


;===Table===========================
; Days per month
; No attempt to deal with leap years
;===================================
DPMTAB:	db	31,28,31,30,31,30
	db	31,31,30,31,30,31

;=============
;RAM variables
;=============
CRCYL:	dw	0	;{0:405}current cylinder
CRHEAD:	db	0	;; <7:6>=platter,<5>=side
ERRCNT:	dw	0	;error counter

;========================================================
;Pack Descriptor Page
; This is a default pack descriptor for disks that do not
; contain a load image. If someone tries to boot such a
; disk, then the boot laoder will load this page as a
; load image, and then execute it at address 0. When
; executed, this page will use the 88-2SIO's Channel A to
; print the volume label, date, and a message that says
; that there is no load image. It will then hang in an 
; infinite loop.
;========================================================
ACCTRL	EQU	10h		;ACIA Control output port
ACSTAT	EQU	10h		;ACIA Status input port
ACTXD	EQU	11h		;ACIA TX Data register

ACRSET	EQU	00000011b	;Master reset
ACINIT	EQU	00010101b	;/16, 8-bit, No Parity, 1Stop
ACTDE	EQU	00000010b	;TX Data register empty


PDESC:	db	0FFh		;(00)RST 7 isntruction
				;jumps to 38h
PDLABL:	db	'Datakeeper Disk    '	;pack name (19 chrs)
PDYEAR:	db	0		;(14)year
PDMON:	db	0		;(15)month
PDDAY:	db	0		;(16)day
	db	0,0,0
PDCBS:	dw	0		;(1A)Current backup set
PDATP1:	dw	1		;(1C)Allocation Table Pointer
PDATP2:	dw	1		;(1E)Allocation Table Pointer
PDATP3:	dw	1		;(20)Allocation Table Pointer
PDDTB1:	dw	00C0h		;(22)Directory Table Pointer
PDDTB2:	dw	00C0h		;(24)Directory Table Pointer
PDDTB3:	dw	00C0h		;(26)Directory Table Pointer
PDOISP:	dw	0000h		;(28)OS Image Start Page
PDOIL:	dw	0001h		;(2A)OS Image Length (1 sector)
PDMF:	dw	1		;(2C)mount flag
	dw	0,0,0,0,0

	lxi	SP,256		;(38)local stack
	jmp	PDNUL-PDESC	;(3B) jump to null code
	dw	0		;(3E)

PDCATP:	dw	1		;(40)Current Alloc. Table Ptr
PDATL:	dw	0131h		;(42)Allocation Table length
PDCDTP:	dw	00C0h		;(44)Current Directory Table Ptr
PDDTL:	dw	100h		;(46)Directory Table Length
	dw	3		;(48)
	dw	0		;(4A)
PDLVP:	dw	4C1Fh		;(4C)Last Page on Volume
PDNFG:	dw	0922h		;(4E)Number of Free Groups
PDNTG:	dw	39h		;(50)Number of Table Groups
PDUG:	dw	4		;(52)# of unallocatable groups
PDSPA1:	dw	0FFFFh		;(54)Swap area pointer
PDSPA2:	dw	0FFFFh		;(56)Swap area pointer

;-------------------------------------------------------------
;The following code gets installed in the pack descriptor
;page, and gets executed if no load image has been installed.
;-------------------------------------------------------------

; print 'No load image present on disk' & 'Volume:'

PDNUL:	lxi	h,NLPMSG-PDESC	;(58)
	mvi	b,NLPLEN	;(5B)
	call	PDPRNT-PDESC	;(5E)

;Print this disk's volume string, skipping the initial FF

	mvi	l,PDLABL-PDESC	;(60)point to volume label
	mvi	b,19		;(62)characters in vol label
	call	PDPRNT-PDESC	;(64)

;Print date

	mvi	l,DATMSG-PDESC	;(67)
	mvi	b,DATLEN	;(69)	
	call	PDPRNT-PDESC	;(6B)

	lhld	PDMON-PDESC	;(6E)Month & day
	mov	a,l		;(71)month
	call	PDPDEC-PDESC	;(72)

	mvi	c,'/'		;(75)
	call	PDPRTC-PDESC	;(77)

	mov	a,h		;(7A)day
	call	PDPDEC-PDESC	;(7B)

	mvi	c,'/'		;(7E)
	call	PDPRTC-PDESC	;(80)

	lda	PDYEAR-PDESC	;(83)year

	lxi	b,100*256+18	;(86)convert to 4-digit year

PDCYR:	inr	c		;(89)
	sub	b		;(8A)compute high 2 digits
	jnc	PDCYR-PDESC	;(8B)

	add	b		;(8E)went once too many
	mov	h,a		;(8F)remainder is low 2 digits

	mov	a,c		;(90)
	call	PDPDEC-PDESC	;(91)print high 2 digits
	mov	a,h		;(94)
	call	PDPDEC-PDESC	;(95)print low 2 digits

;Hang here forever

DIE:	ei			;(98) INTE light on
	jmp	DIE-PDESC	;(99)

;--Relocated Subroutine--
;Print string subroutine
; On Entry:
;  hl=string address
;   b=string byte count
;------------------------
PDPRNT:	mov	c,m		;(9C)
	call	PDPRTC-PDESC	;(9D)

	inx	h		;(A0)
	dcr	b		;(A1)end?
	jnz	PDPRNT-PDESC	;(A2)

	ret			;(A5)

;--Relocated Subroutine-------------------
;Print a as 2-digit decimal on the console
; Trashes a,bc
;-----------------------------------------
PDPDEC:	lxi	b,10*256+'0'-1	 ;(A6)-1 will go 1 extra

PDPDLP:	inr	c		;(A9)
	sub	b		;(AA)subtract 10
	jnc	PDPDLP-PDESC	;(AB)

	add	b		;(AE)undo extra sub
	adi	'0'		;(AF)ASCII
	mov	b,a		;(B1)b gets remainder

	call	PDPRTC-PDESC	;(B2)

	mov	c,b		;(B5)low digit

; fall into PDPRTC to print c

;--Relocated Subroutine---
;Print c on the console
;-------------------------
PDPRTC:	in	ACSTAT		;(B6)Wait for TX to be ready
	ani	ACTDE		;(B8)	
	jz	PDPRTC-PDESC	;(BA)

	mov	a,c		;(BD)recover chr
	out	ACTXD		;(BE)and send it
	ret			;(C0)

;--------
;Strings
;--------
NLPMSG:	db	CR,LF		;(C1)
	db	CR,LF,'No load image present on disk' ;(C3)
	db	CR,LF		;(E3)

	db	'Volume: ' 	;(E5)
NLPLEN	equ	$-NLPMSG

DATMSG:	db	CR,LF,'Date: '	;(ED)
DATLEN	equ	$-DATMSG

	dw	0,0,0,0,0	;(E6)Stack space (need 2 words)

;===========
;Local stack
;===========
	ds	64
STACK:				;top of stack

	END


