; DISK I/O ROUTINES FOR THE ALTAIR FLOPPY DISK DRIVE TO WORK
;    WITH THE MITS PROGRAMMING SYSTEM II.
;
; THIS MODULE IS COPIED FROM PROM TO RAM BY PS2BOOT BECAUSE
;    DISK CODE CAN'T HANDLE THE TWO WAIT STATES REQUIRED
;    FOR 1702 EPROMS.

	ORG	7C00H		;EXECUTION ADDRESS

; DRIVE EQUATES

NUMTRK	EQU	77
RESTRK	EQU	87		;MAX STEPS FOR RESTORE
NUMSEC	EQU	32
SECLEN	EQU	128
TRKLEN	EQU	4096
WSTRKS	EQU	6		;NUM TRACKS IN WORKSPACE
NUMWS	EQU	13		;NUM WORKSPACES ON DISK
RDTRIES	EQU	5

; ALTAIR DISK CONTROLLER 

DRVSLCT	EQU	08H		;DRIVE SELECT REGISTER (OUT)
CDSLCT	EQU	80H		;DESELECT

DRVSTAT	EQU	08H		;DRIVE STATUS REGISTER (IN)
SENWD	EQU	01H		;WRITE DATA FLAG
SMOVEOK	EQU	02H		;OK TO MOVE HEAD
SHDSTAT	EQU	04H		;HEAD STATUS FLAG
SDSKEN	EQU	08H		;DISK IS SELECTED AND ENABLED
STRACK0	EQU	40H		;TRACK ZERO 
SNRDA	EQU	80H		;READ DATA FLAG

DRVCMD	EQU	09H		;DRIVE COMMAND REGISTER (OUT)
CSTEPI	EQU	01H		;STEP IN
CSTEPO	EQU	02H		;STEP OUT
CHDLOAD	EQU	04H		;HEAD LOAD
CHDUNLD	EQU	08H		;HEAD UNLOAD
CHCSON	EQU	40H		;REDUCE HEAD CURRENT
CWRTEN	EQU	80H		;WRITE ENABLE
CWRTRED	EQU	0C0H		;WRITE W/REDUCED CURRENT

DRVSEC	EQU	09H		;DRIVE SECTOR POSITION (IN)
SNEWSEC	EQU	01H		;SECTOR FLAG
SECMASK	EQU	1FH

DRVDATA	EQU	0AH		;DRIVE READ/WRITE DATA (IN/OUT)

; CONSOLE I/O

SIOCSR	EQU	10H
SIODR	EQU	11H
RCVRDY	EQU	01H
XMTRDY	EQU	02H

; MISC EQUATES

JMPINST	EQU	0C3H		;JMP INSTRUCTION
CR	EQU	00DH
LF	EQU	00AH
LFMSB	EQU	08AH		;LF WITH MSB SET
SPMSB	EQU	0A0H		;SPACE WITH MSB SET
UNDEF	EQU	0FFH

; JUMP TABLE

	JMP	BOOT		;LOAD WORKSPACE FOR COLD BOOT
	JMP	LOADWS		;LOAD WORKSPACE
	JMP	SAVEWS		;SAVE WORKSPACE

	JMP	RDTRK
	JMP	WRTRK

	JMP	GETCHR
	JMP	DSPCHR
	JMP	DSPMSG

; WORKSPACE LOAD AND SAVE ROUTINES
;   READ OR WRITE 6 TRACKS FROM/TO RAM AT 0000H. LAST WORKSPACE ON DISK
;   IS 5 TRACKS.

; BOOT IS A SPECIALIZED CASE OF LOAD WORKSPACE THAT INITIALIZES THE
;    CONTROLLER, SERIAL I/O, AND DOESN'T EXIT TO MONITOR.

BOOT:	LXI	SP,STACK
	LXI	H,RDTRK
	SHLD	DVADDR
	CALL	SIOINI		;CALLS DRVINI AS WELL

BOOTLP:	CALL	GETWSP		;GET WORKSPACE, PROMPT FORCED
	JNC	BOOTLP		;INVALID
	JMP	XFRWS	

LOADWS:	LXI	H,RDTRK
	JMP	XFRINI

SAVEWS:	LXI	H,WRTRK

XFRINI:	SHLD	DVADDR		;SAVE READ OR WRITE
	LXI	SP,STACK
	CALL	GETWS
	JNC	BADWS

; XFRWS - TRANSFER THE WORKSPACE (LOAD OR SAVE)

XFRWS:	MVI	A,JMPINST	;INIT THE DISK VECTOR JUMP
	STA	DSKVEC
	LXI	H,TRKCNT	;INIT TRACK COUNTER
	MVI	M,WSTRKS
	LXI	H,0		;STARTING AT ADDRESS ZERO 
	SHLD	DMAADR
	XRA	A		;DRIVE 0
	STA	DRVNUM

TRKLP:	CALL	DSKVEC		;READ OR WRITE TRACK
	EI
	JNZ	DSKERR

	LDA	TRKNUM		;UPDATE TRACK NUMBER
	INR	A
	STA	TRKNUM
	CPI	NUMTRK		;LAST WS IS SHORT
	JZ	EXTMON		;EXIT

	LXI	H,TRKCNT	;DECR TRACK COUNT
	DCR	M
	JNZ	TRKLP	

EXTMON:	MVI	A,CDSLCT	;DESELECT DRIVE
	OUT	DRVSLCT

	JMP	0 		;JUMP TO LOADED WORKSPACE

; ERROR HANDLERS

BADWS:	CALL	DSPMSG
	DB	'WE',CR,LFMSB
	JMP	0		;EXIT TO MONITOR

DSKERR:	MVI	A,CDSLCT	;DESELECT DRIVE
	OUT	DRVSLCT
	CALL	DSPMSG
	DB	'DE',CR,LFMSB
	JMP	BOOTLP		;TRY AGAIN 

; GETWS - PROMPT FOR AND INPUT WORKSPACE NUMBER TO LOAD/SAVE. RETURNS
;    CARRY SET IF VALID, CARRY CLEAR IF INVALID.

GETWS:	LDAX	B		;GET COMMAND LINE LENGTH
	CPI	6		;NOT 6, PROMPT FOR WORKSPACE
	JNZ	GETWSP

	INX	D		;MOVE TO WS #
	INX	D
	LDAX	D		;A=WS #
	JMP	GWCONV

GETWSP:	CALL	DSPMSG
	DB	'Workspace #:',SPMSB
	CALL	GETCHR
	CALL	DSPCHR		;ECHO

	PUSH	PSW		;SAVE VALUE TYPED
	CALL	DSPMSG
	DB	CR,LFMSB
	POP	PSW

GWCONV:	SUI	30H		;ASCII OFFSET
	CPI	0AH		;0-9?
	JC	HAVEWS
	SUI	7		;'A'-'F' TO A-F
	
HAVEWS:	CPI	NUMWS		;VALID?
	RNC			;NO, ABORT
	
	ADD	A		;*2
	MOV	C,A		;C=TRACK*2
	ADD	C		;*4
	ADD	C		;*6
	STA	TRKNUM
	STC			;CARRY=VALID
	RET

; RDTRK - TRANSFER A FULL TRACK OF SECTORS
	
RDTRK:	CALL	SELDRV
	MVI	A,1		;SEEK RETRY COUNTER
	STA	SKRTRY

SKLOOP:	CALL	SEEK
	JNZ	DSKERR
	MVI	A,RDTRIES
	STA	RDRTRY
	
RETRY:	MVI	E,NUMSEC	;E=SECTOR COUNTER

RSLOOP:	CALL	NXTSEC		;WHATEVER SECTOR IS NEXT
	CALL	RDSEC
	JNZ	RDERR
	DCR	E
	JNZ	RSLOOP
	
UPDPTR:	LHLD	DMAADR		;UPDATE RAM PTR
	LXI	B,TRKLEN
	DAD	B
	SHLD	DMAADR
	XRA	A		;SUCCESS
	RET
	
RDERR:	LXI	H,RDRTRY	;DECR READ RETRIES
	DCR	M
	JNZ	RETRY
	
	LXI	H,SKRTRY	;DECR SEEK RETRIES
	DCR	M
	RNZ			;FAIL
	
	CALL	RESTOR
	RNZ
	JMP	SKLOOP

; WRTRK - WRITE A FULL TRACK OF SECTORS

WRTRK:	CALL	SELDRV
	CALL	SEEK
	RNZ

	LDA	CURTRK		;REDUCED CURRENT TRACKS 43-76
	LXI	H,WRTCMD
	MVI	M,CWRTEN	;NORMAL CURRENT
	CPI	42
	JNC	WRTINI
	MVI	M,CWRTRED	;ELSE REDUCED CURRENT

WRTINI: MVI	E,NUMSEC	;E=SECTOR COUNTER

WSLOOP:	CALL	NXTSEC
	LDA	WRTCMD		;ISSUE WRITE ASAP
	OUT	DRVCMD
	CALL	WRSEC
	DCR	E
	JNZ	WSLOOP
	
	CALL	NXTSEC		;ENSURE SECTOR FINISHED
	MVI	A,6		;  THEN 600US TRIM ERASE DELAY
	CALL	DELAY
	JMP	UPDPTR		;UPDATE XFER ADDR AND EXIT

; SELDRV - SELECT DRVNUM AND LOAD THE HEAD
;    CLOBBERS C,D,E,H,L

SELDRV:	LXI	H,CURDRV	;E=CURRENTLY SELECTED DRIVE
	MOV	E,M

	LDA	DRVNUM		;C=NEW DRIVE
	MOV	C,A
	CMP	E
	JNZ	NEWDRV		;NEW DRIVE SELECTED

	IN	DRVSTAT		;STILL SELECTED?
	ANI	SDSKEN
	JZ	SELEXT		;YES, EXIT

	JMP	RESEL		;ELSE, RE-SELECT

NEWDRV:	MOV	M,C		;SAVE NEW IN CURDRV
	
	MVI	D,0		;SAVE OLD DRIVE'S TRACK
	LXI	H,TRKTBL
	DAD	D
	LDA	CURTRK
	MOV	M,A

	LXI	H,TRKTBL	;LOAD NEW DRIVE'S TRACK
	MOV	E,C
	DAD	D
	MOV	A,M
	STA	CURTRK

RESEL:	MVI	A,CDSLCT	;DESELECT
	OUT	DRVSLCT
	MOV	A,C		;SELECT
	OUT	DRVSLCT

	IN	DRVSTAT		;SELECTED?
	ANI	SDSKEN
	JNZ	RESEL		;NO

SELEXT:	MVI	A,CHDLOAD	;LOAD HEAD
	OUT	DRVCMD
	RET

; SEEK - SEEK TO TRACK IN TRKNUM
;    ZERO TRUE FOR SUCCESS, FALSE FOR FAILURE (RESTORE FAILED)
;    CLOBBERS B,C,H,L

SEEK:	DI			;PROTECT DISK I/O FROM HERE ON
	LDA	CURTRK
	CPI	UNDEF		;DRIVE TRACK OK?
	JNZ	TRKVLD		;YES
	CALL	RESTOR		;ELSE, RESTORE
	RNZ

TRKVLD:	LXI	H,TRKNUM	;COMPUTE STEP COUNT
	SUB	M		;A=CURRENT-REQUESTED
	RZ

	MVI	B,CSTEPO	;B=STEP OUT
	JNC	DOSEEK
	
	MVI	B,CSTEPI	;ELSE B=STEP IN
	CMA			;COMPUTE A=-A
	INR	A

DOSEEK:	MOV	C,A		;C=NUMBER OF STEPS
	MOV	A,M		;SAVE TRACK IN CURTRK
	STA	CURTRK	
	CALL	STEP
	XRA	A
	RET
	
; RESTOR - RESTORE TO TRACK 0 AND THEN DELAY TO ENSURE ANY SUBSEQUENT
;    SEEK MEETS THE MINIMUM DIRECTION CHANGE PERIOD.
;    IF SUCCESSFUL CURTRK=0, A=0, ZERO TRUE, OTHERWISE, CURTRK=UNDEF,
;    ZERO FALSE.
;    CLOBBERS B,C

RESTOR:	XRA	A
	STA	CURTRK
	IN	DRVSTAT		;ON TRACK 0?
	ANI	STRACK0
	RZ			;YES

	MVI	C,3		;C=STEP IN 3 TRACKS
	MVI	B,CSTEPI
	CALL	STEP
	MVI	A,200		;20MS DELAY PUTS US PAST THE 1MS STEP WINDOW
	CALL	DELAY		;  AND ENSURES PROPER DIR CHANGE DELAY

	MVI	C,RESTRK	;C=MAXIMUM STEPS
	MVI	B,CSTEPO
	CALL	STEP
	JNZ	RSFAIL

	MVI	A,200
	CALL	DELAY
	RET

RSFAIL:	MVI	A,UNDEF		;TRACK  UNDEFINED
	STA	CURTRK
	RET

; STEP - STEP HEAD NUMBER OF STEPS IN C, DIRECTION COMMAND IN B.
;   IF TRACK ZERO IS DETECTED DURING A STEP-OUT OPERATION, CURTRK
;   IS FORCED TO ZERO AND ZERO IS RETURNED IN A, ELSE Z=FALSE.
;   CLOBBERS A,B,C

STEP:	IN	DRVSTAT		;WAIT FOR OK TO STEP
	ANI	SMOVEOK
	JNZ	STEP

	IN	DRVSTAT		;ON TRACK ZERO?
	ANI	STRACK0
	JNZ	STEP1		;NO

	MOV	A,B		;STEPPING OUT?
	SBI	CSTEPO
	JZ	STEP0		;YES, WE'VE HIT TRACK 0

STEP1:	MOV	A,B		;A=STEP COMMAND AND DIRECTION
	OUT	DRVCMD
	DCR	C
	JNZ	STEP

	INR	C		;FORCE NON-ZERO STATUS
	RET

STEP0:	STA	CURTRK		;CURTRK=0
	RET			;ZERO TRUE, A=0

; NXTSEC - WAIT FOR NEXT (ANY) SECTOR. RETURNS POINTER TO THE SECTOR
;    CLOBBERS B,C,H,L

NXTSEC:	IN	DRVSEC		;WAIT FOR SECTOR
	RAR
	JC	NXTSEC
	
	ANI	SECMASK		;SECTOR NUMBER ALONE AND CARRY CLEAR
	RAR			;COMPUTE BC=A*128
	MOV	B,A
	RAR
	ANI	80H
	MOV	C,A
	
	LHLD	DMAADR		;COMPUTE SECTOR ADDRESS
	DAD	B
	RET

; RDSEC - READ A SECTOR INTO (HL)
;    ZERO TRUE IF NO ERROR, FALSE FOR CHECKSUM OR TRACK NUM ERROR
;    CLOBBERS B,C,D,H,L

RDSEC:	LXI	B,SECLEN	;B=CHKSUM, C=BYTES TO READ

RDSEC1:	IN	DRVSTAT		;WAIT FOR TRACK BYTE
	ORA	A
	JM	RDSEC1

	IN	DRVDATA		;D=TRACK FROM DISK
	MOV	D,A
	
RDSEC2:	IN	DRVSTAT		;WAIT FOR NEXT BYTE
	ORA	A
	JM	RDSEC2

	IN	DRVDATA		;READ 1ST BYTE AT 24-48 CYCLES
	MOV	M,A
	ADD	B		;UPDATE CHECKSUM
	MOV	B,A
	INX	H
	DCR	C
	JZ	RDSEC3

	IN	DRVDATA		;READ 2ND BYTE AT 70-94 CYCLES
	MOV	M,A
	ADD	B		;CHECKSUM
	MOV	B,A
	INX	H
	DCR	C
	JNZ	RDSEC2

RDSEC3:	IN	DRVSTAT		;WAIT FOR CHECKSUM BYTE
	ORA	A
	JM	RDSEC3	

	IN	DRVDATA
	SUB	B		;MATCH COMPUTED?
	RNZ			;NO
	
	LDA	CURTRK		;A=TRACK WE SHOULD BE ON
	SUB	D		;SAME AS FROM SECTOR?
	ANI	7FH
	RET

; WRSEC - WRITE A SECTOR, WRITE COMMAND HAS ALREADY BEEN ISSUED
;   CLOBBERS B,C,D,H,L

WRSEC: 	LXI	B,SECLEN	;B=CHKSUM, C=BYTES TO WRITE

	LDA	CURTRK
	ORI	80H		;SECTOR SYNC BIT
	MOV	D,A
	
WRSEC1:	IN	DRVSTAT		;WAIT FOR WRITE FLAG
	RRC
	JC	WRSEC1

	MOV	A,D		;WRITE TRACK BYTE
	OUT	DRVDATA
	
WRSEC2:	IN	DRVSTAT		;WAIT FOR WRITE FLAG
	RRC
	JC	WRSEC2

	MOV	A,M		;DATA BYTE
	OUT	DRVDATA
	ADD	B		;UPDATE CHECKSUM
	MOV	B,A
	INX	H
	DCR	C
	JZ	WRSEC3

	MOV	A,M		;DATA BYTE
	OUT	DRVDATA
	ADD	B		;UPDATE CHECKSUM
	MOV	B,A
	INX	H
	DCR	C
	JNZ	WRSEC2 

WRSEC3:	IN	DRVSTAT		;WAIT TO WRITE CHECKSUM
	RRC
	JC	WRSEC3
	
	MOV	A,B		;WRITE CHECKSUM
	OUT	DRVDATA

WRSEC4:	IN	DRVSTAT		;WAIT FOR WRITE FLAG
	RRC
	JC	WRSEC4

	XRA	A		;WRITE A TRAILING ZERO
	OUT	DRVDATA
	RET

; SIOINI - INITIALIZE SERIAL PORT

SIOINI:	MVI	A,100		;DELAY 10MS, LET OUTPUT FINISH
	CALL	DELAY
 	
	MVI	A,3		;RESET 6850 UART
	OUT	SIOCSR
	MVI	A,15H		;8N1
	OUT	SIOCSR
				;FALL INTO DRVINI

; DRVINI - INIT DRIVE AND PARAMS

DRVINI:	MVI	A,CDSLCT	;DE-SELECT DRIVE
	OUT	DRVSLCT

	LXI	H,0FFFFH	;INIT STATE VARS TO 'UNKNOWN'
	SHLD	CURDRV
	SHLD	TRKTBL
	SHLD	TKTBL2

	XRA	A		;CURDRV MUST BE VALID
	STA	CURDRV
	RET

; DSPCHR - DISPLAY CHAR FROM A, CLOBBERS B

DSPCHR:	MOV	B,A

DCLOOP:	IN	SIOCSR
	ANI	XMTRDY
	JZ	DCLOOP

	MOV	A,B
	ANI	7FH
	OUT	SIODR
	RET

; GETCHR - WAIT FOR AND RETURN NEXT CHARACTER TYPED

GETCHR:	IN	SIOCSR
	ANI	RCVRDY
	JZ	GETCHR

	IN	SIODR
	ANI	7FH
	RET

; DISPLAY MSBIT TERMINATED MESSAGE, CLOBBERS B

DSPMSG:	POP	H		;HL->MSG

DMLOOP:	MOV	A,M
	INX	H
	CALL	DSPCHR
	CMP	B
	JZ	DMLOOP		;LOOP UNTIL MSB SET

	PCHL			;RETURN PAST MESSAGE

; DELAY - DELAY FOR A X 100US
;    CLOBBERS B

DELAY:	MVI	B,10		;100US=10*10US LOOP

DLYLP:	MOV	B,B		;(5)
	DCR	B		;(5)
	JNZ	DLYLP		;(10)

	DCR	A		;100US COUNT
	JNZ	DELAY
	RET

; RAM ALLOCATION

	ORG	7F00H		;OUTSIDE LOADED WORKSPACE
DRVNUM:	DS	1		;PARAMS FOR NEXT I/O
TRKNUM:	DS	1		;DO NOT MOVE!!
DMAADR:	DS	2

CURDRV:	DS	1		;CURRENT DRIVE
CURTRK:	DS	1		;CURRENT TRACK
TRKTBL:	DS	2		;TRACK EACH DRIVE IS ON
TKTBL2:	DS	2

DSKVEC:	DS	1		;JUMP TO DISK READ OR WRITE
DVADDR:	DS	2		;  ADDRESS IN JUMP INSTRUCTION

SKRTRY:	DS	1		;SEEK RETRY COUNT
RDRTRY:	DS	1		;READ RETRY COUNT
TRKCNT:	DS	1
WRTCMD:	DS	1	

	DS	32		;STACK SPACE
STACK:	DS	1		;EQU $ FAILS HERE

	END
	EOA
