*************************************************************************
*
* NEWDISK for MiniFLEX (FLEX 1.0) on an Altair 680 connected to a
*    Pertec FD3712 dual 8" drive cabinet.
*
*    The FD3712 cannot format a disk, so this program cannot format
*    the disk either. This program does however, initialize data in
*    all sectors in the same manner expected from NEWDISK.
*
*    Ver      Date	    Description
*    ---   ----------   ---------------------------------------
*    1.0   03/20/2022	Mike Douglas, Original
*
*    2.0   05/26/2022   Mike Douglas, Write free chain links with an
*			interleave of 15 to provide better performance.
*			Completely re-write the "format" and disk verify
*			code
*
**************************************************************************

* FLEX and SWTBUG equates

WARMS	equ	$7103		;FLEX warm start entry
GETCHR	equ	$710F
PUTCHR	equ	$7112
PSTRNG	equ	$7118
PCRLF	equ	$711E
OUTDEC	equ	$7133
OUTHEX	equ	$7139
GETHEX	equ	$713F
FMSCALL	equ	$7806		;FMS call entry point
DWRITE	equ	$7F03		;write sector
RESTOR	equ	$7F09		;restore to track zero

* FMS Disk I/O commands

fmsREAD	equ	9		;read a sector 
fmsWRT	equ	10		;write a sector
fmsINFO	equ	7		;get info about a file
fmsSIR	equ	16		;get system information record

* Offsets into the FMS file control block (FCB)

fcbCMD	equ	0		;FMS command
fcbDRV	equ	3		;drive number
fcbFSIZ	equ	21		;FMS puts file size here
fcbCTRK	equ	30		;track of current file position
fcbCSEC	equ	31		;sector of current file position

fcbSBUF	equ	64		;the data sector is buffered here
fcbFTRK	equ	64		;track, sector link to next sector
fcbFSEC	equ	65

* System Information Record offsets (into the sector in the FCB)

sirFTRK	equ	85		;first track, sector in free chain
sirFSEC	equ	86
sirLTRK	equ	87		;last track, sector in free chain
sirLSEC	equ	88
sirCNT	equ	89		;count of sectors in free chain

* Miscellaneous equates

CR	equ	$0D
LF	equ	$0A
EOT	equ	$04

* Disk equates

NUMTRK	equ	77		
NUMSEC	equ	26
SECLEN	equ	128
TOTSEC	equ	NUMTRK*NUMSEC-NUMSEC	;sectors available for data

*------------------------------------------------------------------
* Program data
*------------------------------------------------------------------
	org	$20
trk16	equ	*		;track in LSB of word
secNum	rmb	1
trkNum	rmb	1
saveX	rmb	2		;temp storage for X
skewPtr	rmb	2		;pointer to current skew table

*------------------------------------------------------------------
* Start of code
*------------------------------------------------------------------
	org	$0200
STKPTR	equ	*-1		;stack grows down below code

entry	bra	start
	fcb	20		;version

* Validate drive number and confirm format request

start	lds	#STKPTR
	jsr	GETHEX		;get drive number
	bcc	haveDrv
	jmp	abort		;invalid drive spec

	ldx	#0		;TEMP pretend drive 0 entered
	
haveDrv	stx	saveX
	ldaa	saveX+1		;ensure drive is 0-3
	anda	#$03
	staa	fmsFcb+fcbDRV	;put drive number in the FCB

	ldx	#mSure		;"are you sure?"
	jsr	PSTRNG
	jsr	GETCHR
	cmpa	#'Y'
	bne	abort		;no, exit

	ldx	#mScrDsk	"scratch disk in drive"
	jsr	PSTRNG
	ldx	#fmsFcb+fcbDRV-1   ;form 16 bit drive number
	clr	0,x		;MSB is zero
	clrb			;no leading spaces
	jsr	OUTDEC		;display drive number

	ldaa	#'?'		;follow with '?'
	jsr	PUTCHR
	ldaa	#' '
	jsr	PUTCHR
	jsr	GETCHR		;confirmed?
	cmpa	#'Y'
	bne	abort		;no
	jsr	PCRLF
	
* Init for format: Restore drive to track 0 and clear sector buffer

	ldx	#fmsFcb		;point to FCB
	jsr	RESTOR		;restore to track 0
	bcs	abort		;restore failed
	bne	protErr		;write protected

	ldx	#sector		;zero the sector buffer
	ldab	SECLEN	
clrLoop	clr	0,x
	inx
	decb
	bne	clrLoop
	bra	format

abort	ldx	#mAbort		;"Formatting aborted"

pgmExit	jsr	PSTRNG		;print string pointed to by X
	jmp	WARMS		;return to FLEX
	
* Loop through all 77 tracks and write sectors with a sector skew of 15.
*   The skew is implemented in the linked list of free sectors. See the 
*   skew tables below.

format	ldx	#mFormat	;"Formatting"
	jsr	PSTRNG
	
	clr	trkNum		;start on track zero
	ldx	#skew0-1	;special skew table for track 0
	stx	skewPtr
	
trkLoop	ldab	#1		;write odd sectors first
	
secLoop	stab	secNum		;save sector number we're on
	ldaa	trkNum		;A=track for forward link
	ldx	skewPtr		;index into skew table by sector-1
	stab	*+4		;modify the following load instruction
	ldab	0,x		;B=next sector in chain
	cmpb	#1		;pointing to sector 1?
	bne	savLink		;no

	inca			;otherwise, link to sector 1 on next track
	cmpa	#NUMTRK		;reach end of disk?
	bne	savLink		;not yet
	clra			;else zero the last forward link
	clrb

savLink	staa	linkTrk		;save the forward link
	stab	linkSec
	
* Write the sector and move to the next sector

	ldx	#sector		;X->sector buffer to write
	ldaa	trkNum		;A=track to write
	ldab	secNum		;B=sector to write
	jsr	DWRITE
	
	ldab	secNum		;write every other sector
	addb	#2
	cmpb	#NUMSEC+1	;past sector 26?
	bcs	secLoop		;no

	bne	wNxtTrk		;finished even sectors, track done
	ldab	#2		;else, do even sectors next
	bra	secLoop

* Track written, move to next track

wNxtTrk	ldx	#skew1-1	;use skew1 table for tracks 1-76
	stx	skewPtr
	
	inc	trkNum		;move to next track
	ldaa	trkNum
	cmpa	#NUMTRK
	bne	trkLoop		;do another track
	
	bra	chkDisk		;disk is written, go verify it

protErr	ldx	#mProt		;"Write protect error"
	bra	pgmExit

* Sector skew is implemented in the linked list of free sectors. A skew of
*   15 is used. Sector 1 points to sector 16, sector 2 points to sector 17,
*   etc. This provides 14 sector times (about 84ms) for the FMS to process
*   the current sector and then return to request the next sector. The
*   typical time for this is about 75ms on the Altair 680. Track 0 is
*   treated different to keep sectors 1-4 for system use.

LASTSEC	equ	12		;sec 12 points to sec 1 on next track

skew0	fcb	2,3,4,19,20,21,22,23,24,25,26,1,5
	fcb	6,7,8,9,10,11,12,13,14,15,16,17,18
	
skew1	fcb	16,17,18,19,20,21,22,23,24,25,26,1,2
	fcb	3,4,5,6,7,8,9,10,11,12,13,14,15

*------------------------------------------------------------------
* chkDisk - Format is complete. Now valodate the disk by
*   reading all sectors.
*------------------------------------------------------------------				
chkDisk	ldx	#mVerify	;"Verifying"
	jsr	PSTRNG
	clr	trkNum		;start on track zero
	
vfyTrk	ldab	#1		;write odd sectors first
	
vfySec	stab	secNum		;save sector we're on
	ldx	#fmsFcb		;X->FCB
	ldaa	trkNum
	staa	fcbCTRK,x	;set current track
	ldaa	secNum
	staa	fcbCSEC,x	;set current sector 
	bsr	fmsRead		;do the read
	bne	badSec

	ldab	secNum		;write every other sector
	addb	#2
	cmpb	#NUMSEC+1	;past sector 26?
	bcs	vfySec		;no

	bne	vNxtTrk		;finished even sectors, track done
	ldab	#2		;else, do even sectors next
	bra	vfySec

* Track verified, move to next track

vNxtTrk	inc	trkNum		;move to next track
	ldaa	trkNum
	cmpa	#NUMTRK
	bne	vfyTrk		;do another track
	
	bra	sysInfo		;disk verified, go write the SIR

* badSec - Verify failed, report and exit

badSec	ldx	#mBadSec	"Verify Failed"
	jsr	PSTRNG

	ldx	#trk16		;form 16 bit track number
	clr	0,x		;MSB is zero
	clrb			;no leading spaces
	jsr	OUTDEC		;display track number
	bra	fatal
	
*------------------------------------------------------------------
* fmsRead - Perform a sector read using the FMS
*------------------------------------------------------------------
fmsRdX	ldx	#fmsFcb		;read and load X
fmsRead	ldaa	#fmsREAD	;read sector command
	staa	fcbCMD,x
	jmp	FMSCALL

*------------------------------------------------------------------
* fmsWrt - Perform a sector write using the FMS
*------------------------------------------------------------------
fmsWrt	ldx	#fmsFcb
	ldaa	#fmsWRT		;write sector command
	staa	fcbCMD,x
	jsr	FMSCALL
	beq	fmsRdX		;verify the write
	rts

*------------------------------------------------------------------
* sysInfo - Update the System Information Record
*------------------------------------------------------------------
sysInfo	ldx	#fmsFcb		;X->FCB
	clr	fcbCTRK,x	;trk 0, sec 3 is the System
	ldaa	#3		;  Information Record
	staa	fcbCSEC,x
	jsr	fmsRead
	bne	fatal

	ldx	#fmsFcb		;X->FCB
	clr	fcbFTRK,x	;clear forward link
	clr	fcbFSEC,x	;  (end of sys info data)

	ldaa	#1		;set first track, sector
	staa	sirFTRK,x
	staa	sirFSEC,x

	ldaa	#NUMTRK-1	;set last track, sector
	staa	sirLTRK,x
	ldaa	#LASTSEC
	staa	sirLSEC,x
	
	ldaa	#TOTSEC/256	;set free sector count
	staa	sirCNT,x
	ldaa	#TOTSEC & $FF	;set free sector count
	staa	sirCNT+1,x

	jsr	fmsWrt		;write the sys info record
	beq	finish		;go finish up

fatal	ldx	#mFatal		;"fatal error" and exit
	jmp	pgmExit
	
*------------------------------------------------------------------
* finish - Zero the forward link at the end of the directory,
*   write the boot loader to track 0, dispay results and exit.
*------------------------------------------------------------------
finish	ldx	#fmsFcb		;read last directory sector
	ldaa	#LASTSEC
	staa	fcbCSEC,x
	jsr	fmsRead
	bne	fatal

	ldx	#fmsFcb		;X->FCB
	clr	fcbFTRK,x	;clear forward link
	clr	fcbFSEC,x	;  (end of directory)
	jsr	fmsWrt		;write final directory sector
	bne	fatal

* Write the boot loader to track zero, sectors 1 and 2

	ldx	#BOOTIMG	;X->boot loader
	clra			;track 0
	ldab	#1		;sector 1
	jsr	DWRITE
	bne	fatal

	ldx	#BOOTIMG+SECLEN	;X->2nd 128 bytes of loader
	clra			;track 0
	ldab	#2		;sector 2
	jsr	DWRITE
	bne	fatal

* Display number of free sectors available and exit

	ldx	#fmsFcb		;open the system information record
	ldaa	#fmsSIR
	staa	fcbCMD,x
	jsr	FMSCALL
	bne	fatal

	ldaa	#fmsINFO	;get first info entry 
	staa	fcbCMD,x
	jsr	FMSCALL
	bne	fatal

	ldx	#mFmtCmp	;"Total Sectors ="
	jsr	PSTRNG
	ldx	#fmsFcb+fcbFSIZ	;"file" size will be all free space
	clrb
	jsr	OUTDEC		;display the count 
	jmp	WARMS

*------------------------------------------------------------------
* Messages
*------------------------------------------------------------------
mSure	fcc	"ARE YOU SURE? "
	fcb	EOT

mProt	fcc	"DISK IS PROTECTED!"
	fcb	EOT

mScrDsk	fcc	"SCRATCH DISK IN DRIVE "
	fcb	EOT

mFormat	fcc	"FORMATTING..."
	fcb	EOT
	
mVerify	fcc	"VERIFYING DISK..."
	fcb	EOT
	
mFatal	fcc	"FATAL ERROR --- "
mAbort	fcc	"FORMATTING ABORTED"
	fcb	EOT

mBadSec	fcc	"VERIFY FAILED ON TRACK "
	fcb	EOT

mFmtCmp	fcc	"FORMATTING COMPLETE"
	fcb	CR,LF
	fcc	"TOTAL SECTORS = "
	fcb	EOT


**************************************************************************
*
* FLEX cold boot loader
*   The following code is written to track 0, sectors 1 and 2 by the
*   NEWDISK routine. It is loaded into RAM at $2400 (BOOTLOC) by the
*   SWTBUG boot ROM to then load FLEX (DOS.SYS). As part of part of
*   this NEWDISK source code, it is not at $2400 in this file so a
*   few equates are used to allow assembly here but to run at $2400.
*
**************************************************************************
BOOTLOC	equ	$2400		;runtime address of the loader
BOOTIMG	equ	*		;location of the loader in NEWDISK

* FD3712 controller commands

cREAD	equ	$03		;read sector
cSEEK	equ	$09		;seek to track
cCLRERR	equ	$0B		;clear error flags
cSETTRK	equ	$11		;set track to seek
cDRVSEC	equ	$21		;set unit and sector
cRDBUF	equ	$40		;read buffer

* FD3712 controller status bits

sBUSY	equ	$01		;1=busy
sCRCERR	equ	$08		;1=CRC error
sWRTPRT	equ	$10		;1=write protected
sNOTRDY	equ	$20		;1=drive not ready
sERROR	equ	$29		;any error bit except write protect

* UIO interface board
*   PIA #1 (IC-C) port A is status/data in
*   PIA #1 (IC-C) port B is command out
*   PIA #2 (IC-B) port A is not used
*   PIA #2 (IC-B) port B is data out
*
*   Command bit 0 (the execute bit) comes from the OR of
*   the data-out and data-in handshake lines from the PIAs.
*   CA2 from PIA #1 is the data-in handshake and CB2 from
*   PIA #2 is the data-out handshake.

UIOBASE	equ	$F000	 	;base address of UIO board

P1ACSR	equ	UIOBASE+$08	;control/status for FDC data in
CMDOUT	equ	UIOBASE+$0B	;FDC command out (IC-C port B)
DATAIN	equ	UIOBASE+$09	;FDC data/status in (IC-C port A)
DATAOUT	equ	UIOBASE+$0F	;FDC data out (IC-B port B)

HSOFF	equ	$3C		;auto handshake off
HSAUTO	equ	$2C		;auto handshake on

* Start of code

boot	lds	#BOOTLOC-1	;init stack
	bra	bLdFlex		;jump to load entry point

bData	fcb	0,0,0,0,0,0,0,0,0  ;see data section at end

*------------------------------------------------------------------
* bLdFlex - Load FLEX entry point and main loop
*------------------------------------------------------------------
bLdFlex	bsr	bRd1st		;fetch the 1st sector of DOS.SYS

* bRecLp - Record loop for reading an FMS binary file

bRecLp	bsr	bGetByt		;get next byte
	cmpa	#$02		;data record header?
	beq	bHeader		;yes

	cmpa	#$16		;transfer address header?
	bne	bRecLp		;neither, get nextbyte

	bsr	bGetByt		;get transfer address
	staa	pFlex		;MSB
	bsr	bGetByt		;LSB
	staa	pFlex+1

	ldx	pFlex		;enter FLEX
	jmp	0,x					

* bHeader - Record header found. Get load address and length
*   and begin transfer of data.

bHeader	bsr	bGetByt		;A=MSB of load address
	psha
	bsr	bGetByt		;A=LSB of load address
	pulb			;B=MSB of load address
	staa	pDest+1		;save load address
	stab	pDest

	bsr	bGetByt		;A=byte count
	tab			;B=byte count
	beq	bRecLp		;byte count was zero

* bMoveLp - Move number of bytes specified in B

bMoveLp	pshb
	bsr	bGetByt		;get next data byte
	pulb
	ldx	pDest		;store in secBuf
	staa	0,x

	inx			;increment pointer
	stx	pDest
	decb			;decrement counter
	bne	bMoveLp		;loop for byte count in B

	bra	bRecLp		;get next record

*------------------------------------------------------------------
* bGetByt - Get next byte from sector buffer. Retrieve next
*    sector as needed.
*------------------------------------------------------------------
bGetByt	ldx	pSource		;X=pointer to sector data
	cpx	#secBuf+SECLEN	;at end of sector?
	beq	bNxtSec		;yes, go get another sector

	ldaa	0,x		;get next byte, update ptr
	inx
	stx	pSource
	rts

bNxtSec	ldx	#secBuf		;X->where to read sector
	ldaa	0,x		;A=next track num
	ldab	1,x		;B=next sector num
	bsr	bRdSec		;get next sector
	bra	bGetByt		;and continue
	
*------------------------------------------------------------------
* Sector read routines
*------------------------------------------------------------------
* bRdErr - Clear a read error and start over
 
bRdErr	ldaa	#cCLRERR	;clear error in the controller
	bsr	bDoCmd
bSkErr	ldaa	#$FF		;force re-seek
	staa	curTrk
	bra	boot		;start over
	
* bRd1st - Read first sector of the FLEX system file

bRd1st	ldaa	dosTrk		;read 1st sector of DOS.SYS
	ldab	dosSec		;fall into bRdSec
	ldx	#secBuf

* bRdSec - Read a sector specified by track in A, sector in B

bRdSec	bsr	bSeek		;seek to track in A
	ldaa	#cREAD		;issue read command
	bsr	bDoCmd
	bne	bRdErr		;error
	
	ldaa	#HSAUTO		;enable automatic handshake
	staa	P1ACSR
	ldaa	#cRDBUF		;set controller to read buffer mode
	staa	CMDOUT
	ldab	#SECLEN		;B=num bytes to transfer
	
bRdLoop	ldaa	DATAIN		;get next byte from fifo
	coma			;data in is inverted
	staa	0,x		;store in sector buffer
	inx
	decb
	bne	bRdLoop
	
	ldaa	#HSOFF		;disable automatic handshake
	staa	P1ACSR
	clr	CMDOUT		;restore normal status mode
		
	ldx	#secBuf+4	;data starts at offset 4
	stx	pSource		;pSource->data in secBuf
	rts

*------------------------------------------------------------------
* bSeek - seek to track in A, set sector to hunt from B
*------------------------------------------------------------------
bSeek	psha			;save track
	ldaa	#cDRVSEC	;set sector to read from B
	bsr	bDoCmd
	pulb
	
	cmpb	curTrk		;on correct track already?
	beq	bReturn		;yes

	cmpb	#NUMTRK		;valid track?
	bhs	bSkErr		;no

	stab	curTrk		;save new current track
	ldaa	#cSETTRK	;set track in controller
	bsr	bDoCmd
	ldaa	#cSEEK		;seek to track
	bsr	bDoCmd
bReturn	rts

*--------------------------------------------------------------
* doCmd - Write A to the FDC command register and B to the
*   FDC data register. Wait for the command to complete and
*   return the status register in A. The status is pre-tested
*   with all error bits (other than write protect).
*--------------------------------------------------------------
bDoCmd	staa	CMDOUT		;give command byte to controller
	stab	DATAOUT		;write data, toggle cmd handshake

bWtCmd	ldab	#3		;verify not busy 3 times in a row
bWtCm2	ldaa	DATAIN		;wait for not busy
	anda	#sBUSY
	beq	bWtCmd		;restart if busy
	decb			;verify not busy 3 times in a row
	bne	bWtCm2

	ldaa	DATAIN		;return status in A
	coma			;data in is inverted 
	bita	#sERROR		;test error bits
	rts

*------------------------------------------------------------------
* secBuf - The sector buffer immediately follows the boot code.
*   The label is used as an immediate argument for LDX so it needs
*   to be the actual address relative to BOOTLOC ($2400)
*------------------------------------------------------------------
secBuf 	equ	*-boot+BOOTLOC

*------------------------------------------------------------------
* FCB for FMS calls. Includes the sector buffer.
*------------------------------------------------------------------
fmsFcb	rmb	64		;parameter area in FCB
sector	equ	*		;sector buffer in the FCB
linkTrk	rmb	1		;track number of forward link
linkSec	rmb	1		;sector number of forward link

*------------------------------------------------------------------
* References to boot data items must be at the actual location
*   relative to BOOTLOC ($2400). These data items are present
*   near the start of the boot code (see bData above).
*------------------------------------------------------------------
	org	bData-boot+BOOTLOC
dosTrk	rmb	1		;DOS.SYS track and sector
dosSec	rmb	1		;  (filled in by LINK)

curTrk	rmb	1		;current track drive is on
pFlex	rmb	2		;FLEX entry address
pDest	rmb	2		;pointer to destination in memory
pSource	rmb	2		;pointer to data in secBuf

	end	entry
