;+
;
; PROGRAM TO FORMAT DISKS FOR 35.477 COMPUTER HARDWARE DESIGN LAB #5.
;
; LOAD THIS PROGRAM USING NIMAL'S LOADER AND THEN "S 1000".
;
; DOUG SHOAF AND JOHN WILSON LAB #5 UVAX CONVERSION, SPRING 1992.
;
;-
	.PSECT	ABS,LONG	;ADDRESSES ARE ABSOLUTE
	.DSABL	GBL		;UNDEFINED SYMBOLS CAUSE ERROR
;
; DISK GEOMETRY.
;
; DISKS ARE SINGLE-SIDED, SINGLE-DENSITY, 5.25" 35-TRACK DRIVES (SHUGART SA400
; OR SIMILAR).  THIS IS A VERY OLD FORMAT, MOST 5.25" DRIVES SINCE THE SA400L
; (UPGRADED SA400) HAVE 40 OR 80 TRACKS, AND USE DOUBLE OR QUAD DENSITY,
; WHICH IS A DIFFERENT DATA FORMAT (MFM) AT TWICE OR FOUR TIMES THE BIT RATE.
;
; OUR DATA RATE IS 125,000 BITS PER SECOND AND THE DISK TURNS AT 300 RPM.
;
NTRKS=	35		;NUMBER OF TRACKS PER DISK (40 ON TM100-1)
NSECS=	16		;NUMBER OF SECTORS PER TRACK
SECLEN=	128		;SECTORS ARE 128 BYTES
;
; FORMAT CONSTANTS:
;
TOTAL=	3125		;TOTAL BYTES PER TRACK INCLUDING FORMAT
			;=7.5*<BIT RATE>/RPM, BIT RATE IS 125KHZ, 5.25"=300 RPM
GAP4B=	34		;FF'S BETWEEN PHYSICAL INDEX HOLE AND INDEX MARK (FC)
SWIM=	0		;1 TO ACTUALLY WRITE SOFTWARE INDEX MARK (FC)
			;(NORMALLY USED BUT WD1771 DOESN'T REQUIRE IT)
GAP1=	26		;FF'S BETWEEN INDEX AND FIRST ID RECORD ADDRESS MARK
; ID RECORD COMES NEXT: TRACK (0-34), SIDE (0), SECTOR (1-16), LENGTH CODE (0)
GAP2=	17		;00'S BETWEEN ID RECORD AND DATA RECORD ADDRESS MARK
; DATA RECORD COMES NEXT: SECLEN BYTES OF DATA (BLANK DISKS USE SECLEN*E5)
GAP3=	15		;FF'S BETWEEN DATA RECORD AND NEXT ID RECORD
GAP4A=	TOTAL-<GAP4B+<SWIM*7>+GAP1+<NSECS*<6+1+4+2+GAP2+1+SECLEN+2+GAP3>>>
			;FF'S BETWEEN LAST SECTOR AND NEXT PHYSICAL INDEX
;
; DEVICE REGISTERS.
;
FDCSR=	^X20001E78	;COMMAND/STATUS REGISTER
FDCSRH= FDCSR+1		;HIGH BYTE OF CSR
FDTRK=	FDCSR+2		;WD 1771 TRACK REGISTER (BYTE)
FDSEC=	FDCSR+4		;WD 1771 SECTOR REGISTER (BYTE)
FDDAT=	FDCSR+6		;WD 1771 DATA REGISTER (BYTE)
;
VEC$A=	^X200+^O260	;INTR "A" VECTOR
VEC$B=	^X200+^O264	;INTR "B" VECTOR
;
; INTERNAL PROCESSOR REGISTER NUMBERS, ACCESSED WITH MTPR/MFPR INSTRUCTIONS.
;
PR$_SCBB=^X11		;SYS CTRL BLK BASE (INIT TO 0)
PR$_IPL=^X12		;INTR PRIO LEVEL (INIT TO ^X13, FDC INTS AT LEVEL ^X14)
;
; THE NEXT FOUR PROCESSOR REGISTERS DON'T EXIST ON ALL VAXEN BUT KA640 HAS 'EM.
; THEY ARE USED TO ACCESS THE CONSOLE TERMINAL UART.
;
RXCS=	^X20		;CONSOLE UART RX CSR (BIT 7 = READY)
RXDB=	^X21		;CONSOLE UART RX DATA BUF (LOW BYTE = CHAR)
TXCS=	^X22		;CONSOLE UART TX CSR (BIT 7 = READY)
TXDB=	^X23		;CONSOLE UART TX DATA BUF (LOW BYTE = CHAR)
;
; ASCII CHARACTERS.
;
CTRLC=	^O3		;^C
BEL=	^O7		;BELL
BS=	^O10		;BACKSPACE
LF=	^O12		;LINE FEED
CR=	^O15		;CARRIAGE RETURN
;
.=	^X1000		;STARTING ADDRESS
;
; INITIALIZE FOR FORMATTING
;
START:	MOVL	#STACK,SP	;;SET UP STACK
	CLRB	@#FDCSRH	;;REMOVE FIFO FROM DATA PATH, DISABLE INTS
	MOVL	#INTA,@#VEC$A	;;SET INTR "A" VECTOR (ALWAYS THE SAME)
	MTPR	#0,#PR$_SCBB	;;SET SCB BASE TO 0
	MTPR	#^X13,#PR$_IPL	;;SET PRIORITY TO ^X13 TO ALLOW DISK INTS
	MOVL	#BANNER,R0	;IDENTIFY THE PROGRAM
	JSB	@#PRINT		;(WHAT DO YOU *THINK* IT DOES?!)
NXTDSK:	; FORMAT NEXT DISK
	JSB	@#RECAL		;SEEK TRACK 0
	MOVL	#PROMPT,R0	;PROMPT
	JSB	@#PRINT
10$:	JSB	@#GETC		;TRY TO GET A CHARACTER FROM THE KEYBOARD
	BCS	10$		;LOOP IF NONE
	CMPB	R0,#CR		;CR?
	BEQL	20$		;YES, SKIP
	CMPB	R0,#CTRLC	;^C?
	BNEQ	10$		;IGNORE IF NOT
	HALT			;YES, HALT
	BRB	NXTDSK		;RESTART IF <C>ONTINUED FROM CONSOLE
20$:	CLRL	R9		;INIT CURRENT TRACK NUMBER
	MOVL	#RULER,R0	;RULER
	JSB	@#PRINT
;
NEXT:	; IF THEY TYPE ^C THEN ABORT
	JSB	@#GETC		;SEE IF THEY TYPED A CHARACTER
	BCS	NXTTRK		;NOPE, SKIP
	CMPB	R0,#CTRLC	;^C?
	BNEQ	NEXT		;NO, GET NEXT CHAR
	MOVL	#BREAK,R0	;POINT AT MSG
	BRW	ABORT		;GO ABORT
NXTTRK:	; OTHERWISE FORMAT NEXT TRACK
	MOVL	#DOT,R0		;PRINT DOT
	JSB	@#PRINT
	JSB	@#SEEK		;SEEK TO CURRENT TRACK
	MOVL	#2,R10		;RETRY COUNT
10$:	; INITIALIZE DATA FOR WRITE-TRACK COMMAND
	; THE MOVC INSTRUCTIONS ALL LEAVE R3 POINTING TO THE BYTE FOLLOWING
	; THE LAST BYTE WRITTEN, SO WE WILL USE R3 AS THE POINTER SO THAT
	; IT UPDATES AUTOMATICALLY.  MOVC TRASHES R0-R2, R4-R5 IN OTHER WAYS.
	MOVL	#BUFFER,R3	;POINT WITH R3
	MOVC5	#0,(R3),#^XFF,#GAP4B,(R3) ;WRITE 2ND PART OF GAP 4
.IF NE SWIM			;WRITE SOFTWARE INDEX MARK IF SELECTED
	MOVC5	#0,(R3),#0,#6,(R3) ;6 00'S
	MOVB	#^XFC,(R3)+	;INDEX MARK
.ENDC ; NE SWIM
	MOVC5	#0,(R3),#^XFF,#GAP1,(R3) ;WRITE GAP 1
	MOVL	#1,R6		;INIT CURRENT SECTOR NUMBER
20$:	; WRITE DATA FOR EACH SECTOR
	MOVC5	#0,(R3),#0,#6,(R3) ;6 00'S (SYNC)
	MOVB	#^XFE,(R3)+	;ID ADDRESS MARK
	MOVB	R9,(R3)+	;TRACK NUMBER
	CLRB	(R3)+		;HEAD NUMBER (ALWAYS 0 FOR SS)
	MOVB	R6,(R3)+	;SECTOR NUMBER
.IF EQ SECLEN-128
	CLRB	(R3)+		;LENGTH CODE (0=128 BYTES)
.ENDC
.IF EQ SECLEN-256
	MOVB	#1,(R3)+	;LENGTH CODE (1=256 BYTES)
.ENDC
.IF EQ SECLEN-512
	MOVB	#2,(R3)+	;LENGTH CODE (2=512 BYTES)
.ENDC
.IF EQ SECLEN-1024
	MOVB	#3,(R3)+	;LENGTH CODE (3=1024 BYTES)
.ENDC
	MOVB	#^XF7,(R3)+	;HEADER CRC (ACTUALLY WRITES 2 BYTES)
	MOVC5	#0,(R3),#0,#GAP2,(R3) ;GAP 2 (00'S BETWEEN HEADER AND DATA)
	MOVB	#^XF8,(R3)+	;DATA ADDRESS MARK
	; DATA VALUES MUST BE LESS THAN F7 (IBM 3740 FORMAT USES E5),
	; SINCE OTHER VALUES ARE CONVERTED TO MARKS 'N STUFF BY THE 1771
	MOVL	R3,R7		;SAVE ADDRESS OF DATA FIELD
	MOVC5	#0,(R3),#^A' ',#SECLEN,(R3) ;INIT DATA TO BLANKS
	MOVL	R3,R8		;SAVE R3 (TRASHED BY MOVC'S BELOW)
	; PUT A MESSAGE IN THE DATA FIELD SAYING WHICH SECTOR IT IS
	MOVC3	#6,@#TRACK,(R7)	;"TRACK " (NEXT ADDRESS IS IN R3)
	MOVZBL	R9,R0		;GET CURRENT TRACK
	JSB	@#PUTN		;CONVERT TO DECIMAL
	MOVC3	#9,@#SECTOR,(R3) ;", SECTOR "
	MOVL	R6,R0		;GET CURRENT SECTOR
	JSB	@#PUTN		;CONVERT TO DECIMAL
	MOVL	R8,R3		;RESTORE R3 TO POINT AFTER DATA FIELD
	MOVB	#^XF7,(R3)+	;DATA CRC (ACTUALLY WRITES 2 BYTES)
	MOVC5	#0,(R3),#^XFF,#GAP3,(R3) ;GAP 3 (FF'S BETWEEN DATA AND
				; NEXT HEADER)
	INCL	R6		;BUMP TO NEXT SECTOR NUMBER
	CMPL	R6,#NSECS	;DONE ALL SECTORS?
	BLEQU	20$		;LOOP IF NOT
	MOVC5	#0,(R3),#^XFF,#GAP4A+<TOTAL/10>,(R3) ;GAP 4 PLUS 10% TO ALLOW
				; FOR VARIATIONS IN MOTOR SPEED
	; WRITE THE TRACK TO THE DISK
	MOVL	#INTB$W,@#VEC$B	;SET INTR VECTOR TO "WRITE DATA" ROUTINE
	MOVL	#BUFFER,@#POINTR ;SET DMA ADDRESS
	MOVL	#TOTAL+<TOTAL/10>-<NSECS/2>,@#COUNTR ;SET DMA COUNT
				;(-NSECS/2 SINCE EACH SECTOR HAS 2 F7'S WHICH
				;ARE 2 BYTES ON THE DISK BUT WE WRITE ONLY 1
				;BYTE TO THE 1771)
	CLRB	@#DONE		;NOT DONE YET
	MOVW	#^XF8F4,@#FDCSR	;FIFO=1,FFD=1,FIFO-RESET=1,ENA=1,ENB=1,WRT TRK
30$:	TSTB	@#DONE		;HAS "DONE" INT HAPPENED?
	BEQL	30$		;SPIN UNTIL IT DOES
	BITB	#^XE4,@#FDCSR	;ERRORS?
	BEQL	35$		;NO
	  BRW	WTERR		;YES
35$:	; NOW READ EVERY SECTOR TO MAKE SURE THEY'RE ALL READABLE
	MOVL	#INTB$R,@#VEC$B	;RESET INTR "B" VECTOR FOR READING
	CLRL	R6		;SECTOR NUMBER FROM SECTAB
40$:	MOVL	#5,R7		;RETRY COUNT
	MOVB	L^SECTAB(R6),R5	;GET LOGICAL SECTOR NUMBER
	ADDB3	R5,#^A'A'-1,@#NUM+1 ;GET LETTER REPRESENTING SECTOR
	MOVL	#NUM,R0		;PRINT NUMBER OF SECTOR WE'RE READING
	JSB	@#PRINT
50$:	; TRY TO READ NEXT SECTOR
	MOVL	#BUFFER,@#POINTR ;BUFFER ADDRESS
	MOVL	#SECLEN,@#COUNTR ;LENGTH
	CLRB	@#DONE		;NOT DONE YET
	MOVB	R5,@#FDSEC	;SET SECTOR REGISTER
	MOVW	#^XB88C,@#FDCSR	;FIFO=1,FFD=0,ENA=1,ENB=1,FIFO-RESET=1,READ
60$:	TSTB	@#DONE		;DONE?
	BEQL	60$		;SPIN UNTIL SO
	BITB	#^X9C,@#FDCSR	;ERRORS?
	BEQL	90$		;NO
	; ERROR READING, RETRY
	MOVL	#QUES,R0	;REPLACE DOT WITH QUESTION MARK
	JSB	@#PRINT
	DECL	R7		;FINISHED RETRYING?
	BEQL	80$		;YES, HARD ERROR
	MOVB	#^XD0,@#FDCSR	;ABORT (FORCE INTERRUPT)
	MOVL	#RUBS,R0	;HACK -- SEND A COUPLE OF RUBOUTS
	JSB	@#PRINT		;(THIS GUARANTEES A >=16 USEC DELAY)
	TSTB	@#FDCSR		;GET STATUS (CLEAR INTRQ)
	JSB	@#RECAL		;RECALIBRATE DRIVE (SEEK TRACK 0)
	JSB	@#SEEK		;STEP BACK OUT TO OUR TRACK
	BRB	50$		;TRY AGAIN
80$:	DECL	R10		;RETRIED WRITING YET?
	BEQL	RSERR		;YES, GIVE UP
	BRW	10$		;NO, LOOP
90$:	; READ SECTOR OK
	INCL	R6		;SECTOR +1
	CMPL	R6,#NSECS	;DONE ALL?
	BGEQU	95$		;YES
	  BRW	40$		;LOOP IF NOT
95$:	; TRACK FINISHED
	MOVL	#STAR,R0	;PRINT A STAR
	JSB	@#PRINT
	; SEE IF WE'RE DONE
	INCL	R9		;MOVE TO NEXT TRACK
	CMPL	R9,#NTRKS	;DONE?
	BGEQU	100$		;YES
	BRW	NEXT		;NO, LOOP
100$:	MOVL	#HAPPY,R0	;FORMAT SUCCESSFUL
ABORT:	JSB	@#PRINT
	BRW	NXTDSK		;AROUND FOR MORE
;
WTERR:	; ERROR WRITING TRACK
	MOVL	#WERR,R0	;POINT AT MESSAGE
	BICB3	#^X1B,@#FDCSR,R3 ;GET ERROR FLAGS
	MOVL	#WERTAB,R4	;GET TABLE OF MEANINGS
	BRB	ERROR
;
RSERR:	; ERROR READING SECTOR
	MOVL	#RERR,R0	;POINT AT MESSAGE
	BICB3	#^X63,@#FDCSR,R3 ;GET ERROR FLAGS
	MOVL	#RERTAB,R4	;GET TABLE OF MEANINGS
	;BRB	ERROR
;
; HANDLE FDC ERRORS.
; R3	ERROR BITS FROM FDCSR
; R4	TABLE OF .LONG POINTERS TO MESSAGE FOR EACH POSSIBLE
;	BIT OF R3 (STARTING WITH BIT 7)
;
ERROR:	JSB	@#PRINT		;PRINT MESSAGE
10$:	MOVL	(R4)+,R0	;GET NEXT POINTER
	TSTB	R3		;IS THIS THE ERROR?
	BLSS	ABORT		;YES, PRINT MSG AND PUNT
	ASHL	#1,R3,R3	;LEFT 1
	BRB	10$		;KEEP LOOKING
;
; ROUTINE TO RECALIBRATE DRIVE.
;
RECAL:	MOVB	#^X03,@#FDCSR	;RECAL COMMAND
	BRB	POLL		;GO POLL UNTIL IT'S DONE
;
; ROUTINE TO SEEK TO CURRENT TRACK.
;
SEEK:	MOVB	R9,@#FDDAT	;LOAD TRACK NUMBER
	MOVB	#^X13,@#FDCSR	;SEEK COMMAND
POLL:	; BRIEF DELAY TO GIVE THE "BUSY" BIT TIME TO TURN ON
	MOVL	#20,R0		;WAIT LOOP COUNT
10$:	SOBGTR	R0,10$		;SIT 'N SPIN
	; WAIT FOR THE "BUSY" BIT TO TURN OFF
20$:	MOVB	@#FDCSR,R0	;READ STATUS
	BLBS	R0,20$		;WAIT UNTIL LOW BIT CLEARS
	RSB
;
; INTR A ("OPERATION COMPLETE") INTERRUPT SERVICE ROUTINE.
;
	.ALIGN	LONG		;LOW 2 BITS OF ADDRESS MUST BE 0
INTA:	CLRB	@#FDCSRH	;;DISABLE ALL FURTHER DISK INTS
	MOVB	#1,@#DONE	;;SET "DONE" FLAG
	REI
;
; INTR B ("FIFO READY") ISR FOR READING FROM FIFO.
;
	.ALIGN	LONG
INTB$R:	MOVL	R0,-(SP)	;;SAVE R0
	MOVL	@#POINTR,R0	;;FETCH POINTER
	MOVB	@#FDDAT,(R0)+	;;READ A BYTE
	MOVL	R0,@#POINTR	;;POINT AT NEXT BYTE
	MOVL	(SP)+,R0	;;RESTORE
	DECL	@#COUNTR	;;DECREMENT COUNT
	BEQL	NOMORE		;;0, NO MORE INTS
;	BITB	#2,@#FDCSRH	;;"FIFO OR" BIT SET?
;	BNEQ	INTB$R		;;YES, READ ANOTHER BYTE
	REI
;
; INTR B ("FIFO READY") ISR FOR WRITING TO FIFO.
;
	.ALIGN	LONG
INTB$W:	MOVL	R0,-(SP)	;;SAVE R0
	MOVL	@#POINTR,R0	;;FETCH POINTER
	MOVB	(R0)+,@#FDDAT	;;WRITE A BYTE
	MOVL	R0,@#POINTR	;;POINT AT TO NEXT BYTE
	MOVL	(SP)+,R0	;;RESTORE
	DECL	@#COUNTR	;;DECREMENT COUNT
	BEQL	NOMORE		;;0, NO MORE INTS
	BITB	#1,@#FDCSRH	;;"FIFO IR" BIT SET?
	BNEQ	INTB$W		;;YES, WRITE ANOTHER BYTE
	REI
;
NOMORE:	; BYTE COUNT EXHAUSTED, NO MORE INTS NEEDED (FROM INTB$R OR INTB$W)
	BICB	#8,@#FDCSRH	;;CLEAR "INTR ENB" BIT
	REI
;
; READ A CHARACTER IF ONE IS AVAILABLE.
; OTHERWISE RETURN C=1.
;
GETC:	MFPR	#RXCS,R0	;GET STATUS
	TSTB	R0		;IS A CHAR READY (C CLEARED)?
	BGEQ	10$		;NO
	MFPR	#RXDB,R0	;YES, GET THE CHAR
	MOVZBL	R0,R0		;CLEAR HIGH 24 BITS
	RSB
10$:	BISPSW	#1		;SET C
	RSB
;
; PRINT .ASCIZ STRING AT (R0).
;
PRINT:	MOVZBL	(R0)+,R1	;GET NEXT CHAR
	BEQL	20$		;END OF STRING, RETURN
10$:	MFPR	#TXCS,R2	;GET STATUS
	TSTB	R2		;READY?
	BGEQ	10$		;LOOP IF NOT
	MTPR	R1,#TXDB	;WRITE THE CHAR
	BRB	PRINT		;LOOP
20$:	RSB
;
; CONVERT NUMBER IN R0 TO A 1- OR 2-DIGIT DECIMAL NUMBER AT (R3)+.
;
PUTN:	CLRL	R1		;ZERO-EXTEND
	EDIV	#10,R0,R0,R1	;DIVIDE BY 10, R0=QUOTIENT, R1=REMAINDER
	BEQL	10$		;R0=0
	ADDB3	#^A'0',R0,(R3)+	;FIRST DIGIT IF NON-ZERO
10$:	ADDB3	#^A'0',R1,(R3)+	;SECOND DIGIT REGARDLESS
	RSB
;
BANNER:	.BYTE	CR,LF
	.ASCIZ	/35.477 CHD LAB 5 DISK FORMATTING PROGRAM/<CR><LF>
;
PROMPT:	.ASCIZ	<CR><LF>/PRESS <RETURN> TO FORMAT NEXT DISK, ^C TO HALT: /
;
RULER:	.BYTE	CR,LF
	.ASCII	/[....!..../	;HARD-CODED FOR 35 TRACKS.  SORRY.
	.ASCII	/1....!..../
	.ASCII	/2....!..../
	.ASCIZ	/3...]/<CR><LF>
;
; SECTOR INTERLEAVE TABLE.  THIS ENABLES US TO READ SECTORS BACK FOR
; VERIFICATION JUST AS THEY'RE PASSING UNDER THE HEAD, MUCH FASTER
; THAN READING THEM 1..NSECS.
;
SECTAB:	.BYTE	5,8,11,14,1,4,7,10 ;3:1 INTERLEAVE, SKEW=5
	.BYTE	13,16,3,6,9,12,15,2 ;HARD-CODED FOR 16 SECTORS.  SORRY.
;
HAPPY:	.ASCIZ	<CR><LF>/FORMAT SUCCESSFUL/<CR><LF>
BREAK:	.ASCIZ	<CR><LF><BEL>/? FORMAT ABORTED/<CR><LF>
WERR:	.ASCIZ	<CR><LF><BEL>/? WRITE ERROR - /
RERR:	.ASCIZ	<CR><LF><BEL>/? READ ERROR - /
;
TRACK:	.ASCII	/TRACK /	;MESSAGE TO GO IN EACH DATA FIELD
SECTOR:	.ASCII	/, SECTOR /
;
DOT:	.ASCIZ	/./		;PRINT A DOT FOR EACH TRACK BEGUN
QUES:	.ASCIZ	<BS>/?/		;OVERPRINT A ? WHILE RETRYING READS
NUM:	.ASCIZ	<BS>/#/		;SECTOR NUMBER BEING READ
STAR:	.ASCIZ	<BS>/*/		;OVERPRINT A STAR FOR EACH TRACK FINISHED
;
WERTAB:	.LONG	NOTRDY,WRTPRT,WRTFLT,0,0,LSTDAT
RERTAB:	.LONG	NOTRDY,0,0,RNTFND,CRCERR,LSTDAT
;
NOTRDY:	.ASCIZ	/NOT RDY/<CR><LF>
WRTPRT:	.ASCIZ	/WRT PRT/<CR><LF>
WRTFLT:	.ASCIZ	/WRT FLT/<CR><LF>
RNTFND:	.ASCIZ	/REC NOT FND/<CR><LF>
CRCERR:	.ASCIZ	/CRC ERR/<CR><LF>
LSTDAT:	.ASCIZ	/LST DAT/<CR><LF>
;
RUBS:	.BYTE	^O177,^O177,0	;RUBOUTS FOR DELAY
;
POINTR:	.LONG			;POINTER TO NEXT BYTE TO READ OR WRITE
COUNTR:	.LONG			;COUNTER OF BYTES REMAINING TO READ OR WRITE
DONE:	.BYTE			;FLAG - NON-ZERO => DONE WITH OPERATION
;
	.BLKL	^X100		;1KB STACK IS WAY MORE THAN ENOUGH
STACK=	.			;INITIAL VALUE OF SP
;
BUFFER:	.BLKB	TOTAL+<TOTAL/10> ;WRITE TRACK/READ SECTOR BUFFER
;
	.END	START		;(START ADDRESS WON'T WORK WITH NIMAL'S LOADER)
