;******************************************************
; Northstar MDS-A Disk Formatter for CP/M
;
;
;******************************************************
; Revision History
; 1.00  28 November 2014  M. Eberhard
; Created
;******************************************************

;*************
; CP/M Equates
;*************
;------------------------------------------
;BDOS Entry Points and low-memory locations
;------------------------------------------
WBOOT	equ	0000H		;Jump to BIOS warm boot
WBOOTA	equ	WBOOT+1		;Address of Warm Boot
BDOS	equ	WBOOT+5		;BDOS Entry Point
COMBUF	equ	WBOOT+80H	;disk & cmd line buffer
USAREA	equ	WBOOT+100H	;User program area

LINBUF	equ	COMBUF		;use CP/M's buffer for input
LBSIZE	equ	64		;max input line (huge)

;------------------------------------------
;BDOS Function Codes, passed in register C
;------------------------------------------
;BRESET	equ	0	;System Reset
;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)
;BDRST	equ	13	;Reset Disk
;BSDISK	equ	14	;select disk
;BOPEN	equ	15	;Disk File Open
;BCLOSE	equ	16	;Close disk file, FCB at de
;BSERCH	equ	17	;Search dir for file, FCB at de
;BDELET	equ	19	;delete file, FCB at (de)
;BREAD	equ	20	;Read from Disk, 0=OK, <>0=EOF
;BWRITE	equ	21	;Write next record, 0=OK, <>0=ERR
;BMAKE	equ	22	;Make new file, 0FFH=BAD
;BCDISK	equ	25	;get current disk
;BSTDMA	equ	26	;Set disk buffer to (de)

;--------------------------------------------------------
;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

IDCSD	equ	0E5h	;CP/M initialization sector data

;************************************************
; Northstar disk Controller Addresses & Commands
; (Read or write from address to execute command)
;************************************************
NSCTRL	equ	0E800h		;Northstar disk controller

NSBOOT	equ	NSCTRL+100h	;disk boot PROM
NWRITE	equ	NSCTRL+200h	;write prefix (data in low byte)
NCOMND	equ	NSCTRL+300h	;commands

NASTAT	equ	NCOMND+10h	;read A status
NBSTAT	equ	NCOMND+30h	;read B status
NSTRST	equ	NCOMND+09h	;set track step flip flop
NCTRST	equ	NCOMND+08h	;clear track step flip flop
NSTOUT	equ	NCOMND+1Ch	;set step direction to 'out'
NSTIN	equ	NCOMND+1Dh	;set step direction to 'in'
NMOTRA	equ	NCOMND+90h	;motor on, read A stat
NRBYTE	equ	NCOMND+50h	;read a byte of data
NWRCRD	equ	NCOMND+04h	;start write record sequence
NRSEFL	equ	NCOMND+14h	;reset sector flag

;****************************
; Disk Controller Status Bits
;****************************
NSTR0	equ	1		;NASTAT: track 0
NSWP	equ	2		;NASTAT: write protect
NSBDY	equ	4		;NASTAT: body (sync chr found)
NSWRT	equ	8		;NASTAT: write ready
NSSP	equ	0Fh		;NBSTAT: sector position
NSMO	equ	10h		;NASTAT & NBSTAT: motor on
NSWN	equ	40h		;NASTAT & NBSTAT: window flag
NSSF	equ	80h		;NASTAT & NBSTAT: sector flag

;**************************
; Disk Controller constants
;**************************
SYNC	equ	0FBh		;header sync charactor
WRMUP	equ	32h		;warm up time in sectors
SETL	equ	0Dh		;head settling time in sectors
SWAIT	equ	08Ch		;max wait for body before error
MAXUNT	equ	3		;max disk drive unit (0 not allowed)
NSTRKS	equ	35		;tracks on normal N* disk
MAXTRK	equ	39		;max track
MAXSEC	equ	9		;max sector (SA400 and SA400L)

;**************
; ASCII equates
;**************
CTRLC	equ	03h		;^C
CR	equ	0Dh		;carriage return
LF	equ	0Ah		;line feed
ESC	equ	1Bh		;"ESCAPE" KEY

;================
; Initialization
;================
	org	USAREA

	di
	lxi	sp,STACK	;local stack

	lxi	h,COMBUF	;CP/M put cmd line here

	mov	b,m		;1st byte is byte count
	inx	h		;point to the string

; skip initial spaces in command line

SSKIP:	call	CMDCHR
	jz	HLPEXT		;no parameters?

	cpi	' '		;white space?
	jz	SSKIP

; Get destination drive number from command line

	ani	('a'-'A') xor 0FFh ;uppercase
	sui	'A'		;un-ASCII
	cpi	3		;A-C only
	jnc	BADINP

	inr	a		;make it 1-3
	mov	c,a		;drive to format (1-3)

	call	CMDCHR		;get mandatory colon
	cpi	':'
	jnz	BADINP

;=================
; Get Confirmation
;=================
CAGAIN:	call	CILPRT
 db 'Put diskette to format in drive',' '+80h

	push	b
	mov	a,c		;drive 1-3
	adi	'A'-1		;drive A-C
	mov	c,a
	mvi	a,CONOUT
	call	GOBIOS
	pop	b

	call	ASKRDY		;Ready?
	cpi	'N'
	jz	DONE

	cpi	'Y'
	jnz	CAGAIN

;============
; Format Disk
;============
FORMAT:	call	SELECT		;select drive in c

	lda	NASTAT		;check for write protect
	ani	NSWP
	jnz	WPERR

	call	RESTOR		;restore to track 0

	call	CILPRT
	db	'Formattin','g'+80h

;Format one track, starting at whichever sector arrives
;first. Do this as fast as possible so we don't miss
;any sectors.

	mvi	b,NSTRKS	;b counts tracks

FDSKLP:	push	b		;track count, drive
	mvi	h,MAXSEC+1	;sector count

;find a sector

FTRKLP:	lda	NRSEFL		;reset sector flag

FMTW1:	lda	NMOTRA		;get A status, bump motor
	ora	a		;test NSSF: sector hole?
	jp	FMTW1		;nope.

	lda	NWRCRD		;prepare to write

FMTW2:	lda	NASTAT		;wait for controller
	ani	NSWRT		;..to get ready to write.
	jz	FMTW2

;write sector header

	lxi	b,NWRITE	;C=0,b=write prefix
	lxi	d,000FH		;number of header zeros,
				;init CRC calculation

FMTW3:	ldax	b		;write now.
	dcr	e		;count header zeros
	jnz	FMTW3

;write sector sync character

	mvi	c,SYNC		;write sync chr
	ldax	b

	mvi	c,IDCSD		;data fill chr

;write sector data (40 cycles/byte)

FMTW4:	mov	a,c		;(5)Calculate CRC
	xra	d		;(4)keep CRC in d
	rlc			;(4)
	mov	d,a		;(5)
	ldax	b		;(7)write chr in c to disk
	dcr	e		;(5)write 256 bytes
	jnz	FMTW4		;(10)

;write sector CRC

	mov	c,d		;Write CRC to disk
	ldax	b

;next sector

	dcr	h
	jnz	FTRKLP

;end of track

	call	ILPRNT		;print a pacifier
	db	'.'+80h

;wait for end of sector before stepping

	mvi	b,1
	call	WAITBS

;next track

	pop	b		;track number
	dcr	b
	jz	FMTDON

	lxi	d,NSTIN		;step 1 track
	call	STEP1		;step in one track

	jmp	FDSKLP
FMTDON:

;=============================
;verify the format
;c=selected drive
;=============================
	call	CILPRT
	db	'Verifyin','g'+80h

	mvi	b,NSTRKS-1	;b=track number

VDSKLP:	push	b		;track number, drive

	lxi	h,(MAXSEC+1)*256+IDCSD	;check all sectors
					;l=sector data

VTRKLP:	lda	NRSEFL		;reset sector flag

;find a sector

FMTV1:	lda	NMOTRA		;get a status, bump motor
	ora	a		;test NSSF: sector hole?
	jp	FMTV1		;nope.

	mvi	b,SWAIT		;max time until body

FMTV2:	dcr	b		;(5)
	jz	NOSYNC		;(10)no sync character
	lda	NASTAT		;(13)look for sync chr
	ani	NSBDY		;(7)
	jz	FMTV2		;(10)keep looking

	lxi	b,0		;initial CRC, 256 bytes

;read sector data (55 cycles per byte)
;Max allowed = 64 uS = 128 cycles

FMTV3:	lda	NRBYTE		;(13)read a byte

	cmp	l		;(4)check for data error
	jnz	DATERR		;(10)

	xra	b		;(4)compute checksum
	rlc			;(4)
	mov	b,a		;(5)

	dcr	c		;(5)do 256 byteS
	jnz	FMTV3		;(10)

;check CRC

	lda	NRBYTE		;get disk CRC
	cmp	b		;compare to computed CRC
	jnz	CRCERR

;next sector

	dcr	h
	jnz	VTRKLP

;end of track

	call	ILPRNT		;print a pacifier
	db	'.'+80h

;next track

	pop	b		;track number
	dcr	b
	jm	DONE		;done?

	lxi	d,NSTOUT	;step 1 track
	call	STEP1		;step in one track

	jmp	VDSKLP

;***Subroutine********************************
; Turn on the motor if necessary, Select drive
; On Entry:
;   c = desired drive (1-3)
; trashes psw,b
;*********************************************
SELECT:	mvi	b,WRMUP-SETL	;motor warmup time

;long delay if motors are off

	lda	NMOTRA		;turn on drive motors
	ani	NSMO		;were they already on?
	cz	WAITBS		;n: spin them up

;select drive, load head

	mvi	b,NCOMND/256	;select drive
	ldax	b		;Load head on drive c

;wait for settling time after head load

	mvi	b,SETL		;head settling time

;fall into WAITBS to stall

;***Subroutine***********************
; Wait for b sectors
; On Entry:
;   b = number of sectors to wait for
; trashes psw,b
;************************************
WAITBS:	lda	NRSEFL		;reset sector flag

WBS1:	lda	NMOTRA		;get a status, bump motor
	ora	a		;test NSSF: sector hole?
	jp	WBS1		;nope.

	dcr	b		;wait for b sectors to pass
	rz

	jmp	WAITBS

;***Subroutine************************
; Step Head once
; On Entry:
;  de = step command: NSTIN or NSTOUT
; On Exit:
;   Z set, a=0 if track 0 found
;   a = 2 otherwise
; trashes psw,h
;*************************************
STEP1:	call	CCTRLC		;user abort?
	mvi	h,1

; fall into STEPH

;***Subroutine*******************************
; Step Head
; On Entry:
;   h = number of steps, >0
;  de = step command: NSTIN or NSTOUT
; On Exit:
;   Z set, a=0, and CRTRAK=0 if track 0 found
;   a = 2 otherwise
; trashes psw,h
;********************************************
STEPH:	ldax	d		;set step direction
	push	b

STEPH1:	lda	NSTRST		;Reset step flipflop
	xthl			;(18)must be here for delay.
	xthl			;(18)at least 10 uSec

	mvi	b,2		;(7)step timer
	lda	NCTRST		;set step flipflop

	call	WAITBS		;wait 2 sector times

	lda	NASTAT		;at track 0?
	rar			;NSTR0 is bit 0
	jc	STEPH2		;y: stop stepping

	dcr	h		;n: step again
	jnz	STEPH1		;stepped enough?

	inr	h		;clear Z: not track 0
	pop	b
	ret

;on track 0. Rreturn with Z set

STEPH2:	xra	a		;set Z: track 0
	pop	b
	ret

;***Subroutine**************
; Restore Disk to Track 0
; On Entry:
;  drive is already selected
; On Exit:
;   a=0
; trashes psw,de,h
;***************************
RESTOR:

;step in once, in case the head is lost

	lxi	d,NSTIN		;step in once
	call	STEP1		;step in now
	jz	STUCK0		;z: Stuck on track 0

;step out enough times that we should find track 0 even
;if the SA400's actuator is out of its spiral groove

	mvi	h,MAXTRK+16	;more than enough
	dcr	e		;e=NSTOUT to step out

	call	STEPH		;steppin' out to track 0
	rz			;z set: found track 0

;Error: track 0 not detected despite many steps outward
;Fall into NOTK0

;*********************************
; Disk Error Reporting Subroutines
;*********************************
NOTK0:	call	CILPRT		;can't find track 0
	db	'No track ','0'+80h
	jmp	DONE		;repair stack & quit

STUCK0:	call	CILPRT
	db	'Track 0 stuc','k'+80h
	jmp	DONE

WPERR:	call	CILPRT
	db	'Write protec','t'+80h
	jmp	DONE

;For the following errors, the track number is on the stack.

DATERR:	call	CILPRT
	db	'Data verif','y'+80h
	jmp	TRKERR

CRCERR:	call	CILPRT
	db	'CR','C'+80h
	jmp	TRKERR

NOSYNC:	call	CILPRT
	db	'No syn','c'+80h

; Fall into TRKERR

;***Exit**************************
;Print track number error message
;including track number in decimal
; On Entry: 
;  track number is on stack
;*********************************
TRKERR:	call	ILPRNT
	db	' error on track',' '+80h

	pop	psw		;a=track number

	mvi	c,'0'-1

DIV10:	mov	b,a		;remember remainder
	inr	c		;compute 10s digit
	sui	10
	jnc	DIV10

	push	b		;remainder in b
	mvi	a,CONOUT
	call	GOBIOS		;print digit
	pop	psw		;recover remainder

	adi	'0'		;make it ASCII
	mov	c,a
	mvi	a,CONOUT
	call	GOBIOS		;print digit

; Fall into DONE

;========================
; Format is complete
; On Entry:
;  c=selected drive (1-3)
;========================
DONE:	call	CILPRT
	db	'Format anothe','r'+80h

	call	ASKYN
	cpi	'Y'
	jz	CAGAIN

WAITY:	call	CILPRT
	db 'Put CP/M diskette in drive ','A'+80h

	call	ASKRDY		;ready?
	cpi	'Y'
	jnz	WAITY

	jmp	NSBOOT		;cold boot

;***Subroutine********************************
; Ask Ready (Y/N) (accepting lowercase too)
; On Exit:
;  a=character, unless >1 chr typed (then a=0)
;  carry set if bogus
; trashes psw,de,hl
;*********************************************
ASKRDY:	call	ILPRNT
	db	'. Read','y'+80h

;Fall into ASKYN

;***Subroutine********************************
; Ask (Y/N) (accepting lowercase too)
; On Exit:
;  a=character, unless >1 chr typed (then a=0)
;  carry set if bogus
; trashes psw,de,hl
;*********************************************
ASKYN:	call	ILPRNT
	db	' (Y/N)','?'+80h

	push	b
	lxi	d,COMBUF
	mvi	c,BRDCON	;get a line of user input
	call	BDOS
	pop	b

	xra	a
	lxi	h,COMBUF+1
	dcr	m
	rnz			;too many digits?

	inx	h		;get the only chr
	mov	a,m

	ani	('a'-'A') xor 0FFh
	ret

;***Subroutine*******
; Print CR, LF
; Trashes psw
;********************
PCRLF:	call	ILPRNT
	db	CR,LF+80H
	ret
		
;***Subroutine*****************************************
; Print CR, LF, then In-line Message
;  The call to ILPRNT is followed by a message string.
;  The last message chr has its msb set.
; Trashes psw
;******************************************************
CILPRT:	call	PCRLF

; fall into ILPRNT

;***Subroutine*****************************************
; Print In-line Message
;  The call to ILPRNT is followed by a message string.
;  The last message chr has its msb set.
; On Exit:
;  Z cleared
; Trashes psw
;******************************************************
ILPRNT:	xthl			;Save hl, get msg addr
	push	b

IPLOOP:	mov	a,m
	ani	7Fh		;strip end marker
	call	PRINTA		;print byte
	mov	a,m		;end?
	inx	h		;Next byte
	ora	a		;msb set?
	jp	IPLOOP		;Do all bytes of msg

	pop	b
	xthl			;Restore hl,
				;..get return address
	ret

;***Subroutine*******************
; Print character in a on console
; trashes psw,c
;********************************
PRINTA:	mov	c,a		;value to c for PRINTC
	mvi	a,CONOUT

; fall into GOBIOS

;***Subroutine**********************
; Go call a BIOS driver directly
; On Entry:
;   c=value for BIOS routine, if any
;   a = BIOS call address offset
; On Return:
;   psw as BIOS left it
;   all other regs preserved
;***********************************
GOBIOS:	push	h
	push	d
	push	b

	call	DOBIOS

	pop	b
	pop	d
	pop	h
	ret

;***Subroutine**********************
; Go call a BIOS driver directly
; On Entry:
;   c=value for BIOS routine, if any
;   a = BIOS call address offset
; On Return:
;   all regs as BIOS left them
;***********************************
DOBIOS:	lhld	WBOOTA		;(get BIOS base address
	mov	l,a		;a has jump vector

	pchl			;'call' BIOS routine


;***Subroutine*************************************
;Check for Control-C on the console, and quit if so
; On Exit:
;   Z set if no chr was waiting
;   Z clear if anything but ^C was waiting
; trashes a
;**************************************************	
CCTRLC:	mvi	a,CONST		;anything on console?
	call	GOBIOS		;(about 200 cycles)
	ora	a		;Z means no chr waiting
	rz

; chr waiting: fall into GETCON to take a look

;***Subroutine*************************************
;Get console character, abort if it's control-C
; On Exit:
;   chr in a
;    Z cleared
; trashes a
;**************************************************	
GETCON:	mvi	a,CONIN		;read the typed chr
	call	GOBIOS
	cpi	CTRLC
	rnz			;ignore everything else

	jmp	DONE		;abort

;***Subroutine***************************
; Get next chr from command line buffer
; On Entry:
;    b has remaining COMBUF byte count
;    hl points to the next chr in COMBUF
; On Exit:
;    a = chr from COMBUF, parity stripped
;    b has been decremented
;    hl has been advanced
;    Z=1 means end of buffer, and a=0
;*****************************************
CMDCHR:	mov	a,b		;End of buffer already?
	ora	a		;also clears carry
	rz			;and clears a

	mov	a,m		;get buffer chr
	inx	h		;bump buffer pointers
	dcr	b	
	ani	7FH		;Strip parity, clear Z
	ret			;with Z cleared

;***Exit***************
;Print help screen
;**********************
BADINP:	call	CILPRT
	db	'Huh','?'+80h

HLPEXT:	call	CILPRT
 db '====================================================',CR,LF
 db ' CP/M Single-Density Diskette Formatter with Verify',CR,LF
 db '         for the Northstar MDS-A Controller',CR,LF
 db '         Version 1.00        by M. Eberhard',CR,LF
 db '====================================================',CR,LF,LF
 db 'Usage:  NSFORMAT {A/B/C}:',CR,LF,LF+80h

	jmp	WBOOT

;=============
; System Stack
;=============
	ds	64
STACK:

	END
