; BASIC INPUT/OUTPUT OPERATING SYSTEM
; TARBELL ELECTRONICS
; 4-DRIVE VERSION OF 5-24-'78
; (NOTE THAT CP/M VERSION 1.3 ONLY SUPPORTS 2 DRIVES,
; WHILE CP/M VERSION 1.4 WILL SUPPORT 4 DRIVES.)
;
; JPL VERSION  01-29-79
;
; THIS MODULE CONTAINS ALL THE INPUT/OUTPUT
; ROUTINES FOR THE CP/M SYSTEM, INCLUDING
; THE DISK ROUTINES.
;
MSIZE	EQU  48		;MEMORY SIZE.

; THIS SECTION DEFINES THE I/O PORTS AND
; STATUS BITS.  BY SETTING THE PROPER VALUES
; FOR THE EQU STATEMENTS, THE I/O MAY BE
; AUTOMATICALLY RECONFIGURED TO FIT MOST
; SITUATIONS.  THE TRUE AND FALSE ONES
; CONTROL CONDITIONAL ASSEMBLIES OF DIFFERENT
; SECTIONS OF I/O ROUTINES TO FIT DIFFERENT
; INTERFACE REQUIREMENTS.

TRUE	EQU  0FFFFH	;DEFINE VALUE OF TRUE.
FALSE	EQU  NOT TRUE	;DEFINE VALUE OF FALSE.

Z80	EQU	FALSE	;TRUE IF Z-80 PROCESSOR
INTRP	EQU  FALSE	;TRUE IF INTERRUPTS ALLOWED.

STD	EQU  FALSE	;TRUE IF STANDARD I/O.
MSIO2	EQU  FALSE	;TRUE IF MITS 2SIO.
ISIO2	EQU  FALSE	;TRUE IF IMSAI SIO-2.
TUART	EQU  TRUE	;TRUE IF CROMEMCO TUART.
VDM	EQU  FALSE	;TRUE IF PROC TECH VDM.
SIO2	EQU  MSIO2 OR ISIO2 OR TUART
OTHER	EQU  FALSE	;TRUE IF SOMETHING ELSE.

CSTAT	EQU  0		;CONSOLE STATUS PORT.
CCOM	EQU  0		;CONSOLE COMMAND PORT.
CDATA	EQU  1		;CONSOLE DATA PORT.
CADR	EQU  3		;INTERRUPT ADDRESS
CPAR	EQU  4		;PARALLEL DATA PORT

	IF   STD	;IF STANDARD I/O,
CKBR	EQU  00000001B	;KEYBOARD READY BIT.
CPTR	EQU  10000000B	;CONS OUTPUT RDY BIT.
	ENDIF

	IF   MSIO2	;IF MITS 2SIO,
CKBR	EQU  00000001B	;KEYBOARD READY BIT.
CPTR	EQU  00000010B	;PRINT READY BIT.
	ENDIF

	IF   ISIO2	;IF IMSAI SIO-2,
CKBR	EQU  00000010B	;KEYBOARD READY BIT.
CPTR	EQU  00000001B	;PRINT READY BIT.
	ENDIF

	IF   TUART	;IF CROMEMCO TUART,
CKBR	EQU  01000000B	;KEYBOARD READY BIT.
CPTR	EQU  10000000B	;PRINT READY BIT.
CINT	EQU  00100000B 	;INTERRUPT PENDING STATUS
	ENDIF

	IF OTHER	;IF SOMETHING ELSE,
CKBR	EQU  00000010B	;KEYBOARD READY BIT.
CPTR	EQU  10000000B	;PRINTER READY BIT.
	ENDIF

CNULL	EQU  3		;CONSOLE NULL COUNT.

LSTAT	EQU  CSTAT		;LIST STATUS PORT.
LCOM	EQU  2		;LIST COMMAND PORT.
LDATA	EQU  CDATA		;LIST DATA PORT.
LRBIT	EQU  CPTR	;LIST READY BIT.
LNULL	EQU  2		;LIST NULL COUNT.

DUAL	EQU  TRUE	;TRUE IF DUAL DRIVE.
FAST	EQU  TRUE	;TRUE IF FAST SEEK.

DISK	EQU  0F8H	;DISK BASE ADDRESS.
DCOM	EQU  DISK	;DISK COMMAND PORT.
DSTAT	EQU  DISK	;DISK STATUS PORT.
TRACK	EQU  DISK+1	;DISK TRACK PORT.
SECTP	EQU  DISK+2	;DISK SECTOR PORT.
DDATA	EQU  DISK+3	;DISK DATA PORT.
WAIT	EQU  DISK+4	;DISK WAIT PORT.
DCONT	EQU  DISK+4	;DISK CONTROL PORT.

RTCNT	EQU  10		;RETRY COUNT.
LAST	EQU	MSIZE*1024
FIRST	EQU	LAST-1536
LASTM	EQU	FIRST+4A0H
LISTR	EQU	LASTM+3
VIOMOD	EQU	LISTR+3
TBUFF	EQU	80H

	ORG	FIRST

CBASE	EQU  (MSIZE-17)*1024  ;BIAS FOR LARGER THAN 17K.
CPMB	EQU  CBASE+2900H	;START OF CPM.
BDOS	EQU  CBASE+3106H	;START OF BDOS.
CPML	EQU  $-CPMB	;LENGTH OF CPM SYSTEM-BIOS.
NSECTS	EQU  CPML/128	;NUMBER OF SECTORS IN IT.
;
; I/O JUMP VECTOR
; THIS IS WHERE CPM CALLS WHENEVER IT NEEDS
; TO DO ANY INPUT/OUTPUT OPERATION.
; USER PROGRAMS MAY USE THESE ENTRY POINTS
; ALSO, BUT NOTE THAT THE LOCATION OF THIS
; VECTOR CHANGES WITH THE MEMORY SIZE.
;
	JMP  BOOT	;FROM COLD START LOADER.
WBOOTE:	JMP  WBOOT	;FROM WARM BOOT.
	JMP  CONST	;CHECK CONSOLE KB STATUS.
	JMP  CONIN	;READ CONSOLE CHARACTER.
	JMP  CONOT	;WRITE CONSOLE CHARACTER.
	JMP  LIST	;WRITE LISTING CHAR.
	JMP  PUNCH	;WRITE PUNCH CHAR.
	JMP  READER	;READ READER CHAR.
	JMP  HOME	;MOVE DISK TO TRACK ZERO.
	JMP  SELDSK	;SELECT DISK DRIVE.
	JMP  SETTRK	;SEEK TO TRACK IN REG A.
	JMP  SETSEC	;SET SECTOR NUMBER.
	JMP  SETDMA	;SET DISK STARTING ADR.
READN:	JMP  READ	;READ SELECTED SECTOR.
WRITEN:	JMP  WRITE	;WRITE SELECTED SECTOR.
; THESE ENTRY POINTS ADDED BY TARBELL ELECTRONICS.
	JMP  READN	;READ WITH NO HEAD LOAD.
	JMP  WRITEN	;WRITE WITH NO HEAD LOAD.
;
; BOOT
; THIS SECTION IS EXECUTED WHENEVER RESET AND RUN
; IS PUSHED, AFTER THE COLDSTART LOADER READS IN
; THE CPM SYSTEM.
;
BOOT:	LXI  SP,80H	;SET STACK POINTER.
;  IOBYTE   CONSOLE=CRT, READER=TTY, PUNCH=TTY, LIST=TTY
	MVI	A,01H	;SET IOBYTE
	STA	3	;STORE IOBYTE
	IF  INTRP	;IF INTERRUPTS ALLOWED,
	EI		;ENABLE THEM HERE.
	ENDIF

	IF   VDM	;IF PROC TECH VDM,
	CALL CLR	;CLEAR VDM SCREEN.
	ENDIF

	IF   STD	;IF STANDARD I/O,
	NOP!NOP!NOP!NOP	;LEAVE SPACE FOR INIT.
	NOP!NOP!NOP!NOP
	NOP!NOP!NOP!NOP
	NOP!NOP!NOP!NOP
	ENDIF

	IF   MSIO2	;IF MITS 2SIO,
	MVI  A,3	;INITIALIZE 2SIO.
	OUT  CCOM
	OUT  LCOM
	MVI  A,11H
	OUT  CCOM
	OUT  LCOM
	ENDIF

	IF   ISIO2	;IF IMSAI SIO2,
	MVI  A,0AAH	;INITIALIZE SIO 2-2.
	OUT  CCOM
	MVI  A,40H
	OUT  CCOM
	MVI  A,0CEH
	OUT  CCOM
	MVI  A,37H
	OUT  CCOM
	ENDIF

	IF   TUART	;IF CROMEMCO TUART,
	MVI  A,9	;SET A = 1.
	OUT  02H	;RESET A
	OUT  52H	;RESET DEVICE B
	MVI  A,0	;SET A = 0
	OUT  03H	;MASK A SET
	OUT  53H	;MASK B SET
	MVI  A,84H	;300, 1 STOP BIT
	OUT  0
	ENDIF

	CALL	GOCPM
;
	MVI	C,0DH	;RESET THINGS
	CALL	5
	MVI	C,15	;OPEN FILE
	CALL	FDOS
	XRA	A
	STA	TFCB+32	;SET REC COUNT=0
;
	LXI	H,LASTM	;ADDR TO LOAD SYSTEM
	PUSH	H	;SAVE ORIGIN OF LOAD
MLOOP:	MVI	C,20	;READ REC FROM DISK
	CALL	FDOS
	ORA	A
	JNZ	CLOSE	;READ OK ?
	POP	H
	CALL	SAVIT	;SAVE CHAR
	PUSH	H
	JMP	MLOOP
;
CLOSE:	POP	H
	MVI	C,16	;CLOSE FILE
	CALL	FDOS
	CALL	VIOMOD	;INITIALIZE IMSAI DTV BOARD
	LXI  H,SMSG	;PRINT OPENING MESSAGE.
	CALL PMSG
	LDA	DISKNO	;FETCH DISK NUMBER
	MOV	C,A	;PASS TO CCP
	JMP	CPMB
;
FDOS:	LXI	D,TFCB
	JMP	5
;
SAVIT:	LXI	D,TBUFF	;GET ADDR OF BUFFER
SAV1:	LDAX	D	;PICK UP CHAR FROM DISK BUFFER
	MOV	M,A	;SAVE IT AFTER CBIOS
	INX	D
	INX	H
	MOV	A,D
	CPI	1	;80 BYTES XFERED ?
	JNZ	SAV1
;
	RET
;
TFCB:	DB	0,'SYSTEM$$COM',0,0,0,0,0
	DS	17
;
GOCPM:	MVI  A,0C3H	;PUT JMP TO WBOOT
	STA  0		;ADR AT ZERO.
	LXI  H,WBOOTE
	SHLD 1
	STA  5
	LXI  H,BDOS	;PUT JUMP TO BDOS
	SHLD 6		;AT ADR 5,6,7.
	LXI  H,80H	;SET DEFAULT DMA ADR.
	SHLD DMAADD
	RET


;
; WARM-BOOT:  READ ALL OF CPM BACK IN
; EXCEPT BIOS, THEN JUMP TO CCP.
;
WBOOT:   LXI SP,80H	;SET STACK POINTER.
;  DO NOT RESET IOBYTE, USE DEFAULT.  CONSOLE=CRT
;	XRA     A	;SET IOBYTE
;	STA     3	;ALL=TTY
	IF  INTRP	;IF INTERRUPTS ALLOWED,
	EI		;ALLOW THEM HERE.
	ENDIF

	LDA  DISKNO	;SAVE DISK NUMBER.
	STA  TEMP
	MVI  C,0	;SELECT DISK ZERO.
	CALL SELDSK
	CALL HOME	;MOVE TO TRACK ZERO.
         JNZ  RDERR	;IF ERROR, PRINT MESSAGE.
         MVI  D,NSECTS	;GET # SECTORS FOR CPM READ.
         LXI  B,2	;TRACK (B)=0, SECTOR (C)=2.
         LXI  H,CPMB	;GET STARTING ADDRESS.

	IF  INTRP	;IF INTERRUPTS ALLOWED,
	DI		;DISABLE THEM HERE.
	ENDIF

RDBLK:   MOV  A,B	;GO TO TRACK IN B.
         CALL SEEK
         JNZ  RDERR	;IF ERROR, PRINT MESSAGE.
         MOV  A,C	;READ STARTING AT SECTOR IN C.
         CALL  READ1
RBLK1    JNZ  RDERR	;IF ERROR, PRINT MESSAGE.
         DCR  D		;DECREMENT SECTOR COUNT.
	JZ   ALDON	;ALL DONE WHEN ZERO.
         INR  C		;INCREMENT SECTOR NUMBER.
	 MOV  A,C	;IF SECTOR NUMBER
         CPI  27	;IS NOT 27,
         JC   RBLK2	;HOP OUT OF LOOP.
         MVI  C,1	;OTHERWISE, RESET SECTOR=1
         INR  B		;INCREMENT TRACK NUMBER,
         JMP  RDBLK	;AND READ NEXT TRACK.
ALDON:	LDA  TEMP	;RESTORE DISK NUMBER.

	IF  INTRP	;IF INTERRUPTS ALLOWED,
	EI		;ALLOW THEM AGAIN HERE.
	ENDIF

	STA  DISKNO
	CALL	GOCPM
	LDA	DISKNO	;FETCH DISK NUMBER
	MOV	C,A	;PASS TO CCP
	JMP	CPMB
;
RBLK2:   CALL READ2	;READ ANOTHER TRACK.
         JMP  RBLK1
;
RDERR:   LXI  H,BTMSG	;GET ADDRESS OF "BOOT ERROR".
         CALL PMSG	;PRINT IT.
         CALL CONIN	;READ A CHAR FROM CONSOLE.
         JMP  WBOOT	;DO A WARM BOOT.
;
; CHECK CONSOLE INPUT STATUS.
;

CONST:	IN   CSTAT	;READ CONSOLE STATUS.
	ANI  CKBR	;LOOK AT KB READY BIT.
	MVI  A,0	;SET A=0 FOR RETURN.

	IF   STD	;IF STANDARD I/O,
	RNZ		;NOT READY WHEN NOT 0.
	ENDIF

	IF SIO2		;IF MITS OR IMSAI OR TUART,
	RZ		;NOT READY WHEN ZERO.
	ENDIF

	IF OTHER	;IF SOMETHING ELSE,
	RNZ		;IT MIGHT BE THIS.
	ENDIF

	CMA		;IF READY A=FF.
	RET		;RETURN FROM CONST.

;
; READ A CHARACTER FROM CONSOLE OR PARALLEL KEYBOARD.
;
CONIN:
	IF  TUART	;IF TUART
	MVI	A,04H	;GET INT. MASK FOR SENS
	OUT	CADR	;OUTPUT IT
	ENDIF
	 IN   CSTAT		;READ CONSOLE STATUS.
	IF  TUART	;IF TUART
	STA	STATIN	;SAVE INPUT STATUS
	ANI	CINT	;ISOLATE INT. PENDING BIT
	JZ	CONIN1	;IF NOT, GO CHECK TTY STATUS
	IN	CADR	;GET INT. ADDR.
	IN	CPAR	;GET DATA FROM PARALLEL PORT
	ANI	7FH	;MAKE MOST SIG. BIT=0.
	RET
CONIN1: LDA	STATIN	;RESTORE STATUS
	ENDIF
         ANI  CKBR	;IF NOT READY,

	IF   STD	;IF STANDARD I/O,
	JNZ  CONIN	;READY WHEN LOW.
	ENDIF

	IF SIO2		;IF MITS OR IMSAI OR TUART,
	JZ   CONIN	;READY WHEN HIGH.
	ENDIF

	IF OTHER	;IF SOMETHING ELSE,
	JNZ  CONIN	;IT MIGHT BE THIS.
	ENDIF

         IN   CDATA	;READ A CHARACTER.
         ANI  7FH	;MAKE MOST SIG. BIT = 0.
         RET
;
; WRITE A CHARACTER TO THE CONSOLE DEVICE.
;

	IF NOT VDM	;IF NOT PROC TECH VDM,
CONOT:	LDA	3	;FETCH IOBYTE
	ANI	1	;CRT ?
	JZ	CONTTY
	CALL	LASTM
	RET
;
CONTTY:	MVI  A,0DH	;IF IT'S A CR,
	CMP  C		;THEN HOP OUT
	JZ   CONUL	;TO NULL ROUTINE.
CONOT1:	IN   CSTAT	;READ CONSOLE STATUS.
	ANI  CPTR	;IF NOT READY,
	ENDIF

	IF STD AND NOT VDM  ;IF STANDARD I/O,
	JNZ  CONOT1	;READY WHEN LOW.
	ENDIF

	IF SIO2		;IF MITS OR IMSAI,
	JZ   CONOT1	;READY WHEN HIGH.
	ENDIF

	IF NOT VDM	;IF NOT PROC TECH VDM,
	MOV  A,C	;GET CHARACTER.
	OUT  CDATA	;PRINT IT.
	RET		;RETURN.
CONUL:	PUSH B		;SAVE B&C.
	MVI  B,CNULL	;GET NULL COUNT.
CONUL1:	CALL CONOT1	;PRINT CR.
	MVI  C,0	;GET NULL CHAR.
	DCR  B		;DECREMENT COUNTER.
	JNZ  CONUL1	;DO NEXT NULL.
	POP  B		;RESTORE B&C.
	MOV  A,C	;RESTORE A.
	RET		;RETURN.
	ENDIF

	IF   VDM	;IF PROC TECH VDM,
; VDM DRIVER FOR CBIOS.
; 9-24-77 VERSION
;
VDMB   EQU  0CC00H
VDMP   EQU  0CCH
VDMD   EQU  0C8H
CONOT: MOV  A,C		;PUT CHAR IN A.
       SHLD HLSAV	;SAVE H&L.
       LXI  H,0		;CLEAR H&L.
       DAD  SP		;HL=SP.
       LXI  SP,VSTACK	;SET STACK PTR.
       PUSH H		;SAVE OLD SP.
       PUSH D		;SAVE D&E.
       PUSH B		;SAVE B&C.
       PUSH PSW		;SAVE A&PSW.
       CALL SCREEN	;WRITE ON SCREEN.
       POP  PSW		;RESTORE A&PSW.
       POP  B		;RESTORE B&C.
       POP  D		;RESTORE D&E.
       POP  H		;RESTORE H&L.
       SPHL		;RESTORE SP.
       LHLD HLSAV	;RESTORE H&L.
       RET		;RETURN FROM CONOT.
;
;
SCREEN: MOV  B,A	;GET CHARACTER.
       MOV  A,B
       ANI  7FH		;NOT DELETE.
       CPI  0DH		;IF IT'S CR,
       JZ   CHOT2	;TAKE CARE OF IT.
       CPI  20H		;CTL CHAR?
       RC		;DON'T DISPLAY.
	JMP  CHOUT	;GO TO CHOUT.
;
; CLEAR SCREEN & INIT CURSOR.
;
CLR:   LXI  H,VDMB	;GET VDM MEM ADR.
       MOV  A,H		;FIGURE VDM MEM TOP.
       ADI  4
CLR2:  MVI  M,' '	;PUT SPACE IN MEMORY.
       INX  H		;INCREMENT POINTER.
       CMP  H		;AT TOP YET?
       JNZ  CLR2	;KEEP GOING IF NOT.
       XRA  A		;SET A = 0.
       STA  BOSL	;BEG SCRN LINE = 0.
       STA  BOTL	;BEG TEXT LINE = 0.
       STA  CCP		;CURSOR PTR = 0.
	CMA		;SET A = FF.
	STA CURF	;SET CURSOR ON.
       MVI  A,15	;SET CURSOR AT BOTTOM.
       STA  CLN
       CALL VDMOT	;SET VDM UP.
       RET		;RETURN FROM CLR.
;
; OUTPUT BOSL & BOTL TO VDM.
;
VDMOT: LDA  BOSL	;INITIALIZE VDM.
       RLC		;SHIFT LEFT 4.
       RLC
       RLC
       RLC
       LXI  H,BOTL
       ORA  M
       OUT  VDMD	;OUT TO VDM PORT.
       RET		;RETURN FROM VDMOT.
;
; STORE CHAR IN VDM MEMORY.
;
CHOUT: MOV  C,A		;SAVE CHARACTER.
       LDA  CCP		;GET CURSOR PTR.
       MOV  B,A		;PUT IN B.
       LDA  CLN		;GET LINE NUMBER.
       CALL CLNA	;CONVERT TO ADR.
       MOV  M,C		;PUT CHAR ON SCREEN.
       LDA  CCP		;ADVANCE CURSOR.
       INR  A
       CPI  64		;WRAP AROUND?
       JNZ  CHOT1
CHOT2: LDA  CCP		;GET CURSOR POS.
       MOV  B,A
       LDA  CLN		;GET LINE NUMBER.
       CALL CCUR	;CLEAR CURSOR.
       CALL SCRL	;SCROLL UP.
       SUB  A		;CURSOR TO LEFT MARGIN.
CHOT1: STA  CCP
       MOV  B,A
       LDA  CLN
       JMP  SCUR	;SET CURSOR ON/OFF.
;
;
SCRL:  LXI  H,BOTL	;SAVE BEG. TEXT LINE.
       PUSH H
       MOV  A,M
       INR  M
       SUB  M
       LXI  B,0
       CALL CLNA	;CONVERT LINE NO.
       LXI  B,2040H
SCRL2: MOV  M,B		;CLEAR BOTTOM LINE.
       INR  L
       DCR  C
       JNZ  SCRL2
       POP  H
       MOV  A,M
       ANI  0FH
       MOV  M,A
       JMP  VDMOT
;
; CONVERT LINE NUMBER IN REG A AND CHR
; POSITION IN REG B TO ADDRESS IN H&L.
;
CLNA:  MOV  L,A
       LDA  BOTL
       ADD  L
       RRC
       RRC
       MOV  L,A
       ANI  3
       ADI  VDMP
       MOV  H,A
       MOV  A,L
       ANI  0C0H
       ADD  B
       MOV  L,A
       RET
;
;
SCUR:  ANI  0FH
       STA  CLN
       CALL CLNA
       MOV  A,B
       STA  CCP
       LDA  CURF
       ORA  A
       MOV  A,M
       JZ   CCUR2
       ORI  80H
       MOV  M,A
       RET
CCUR2: ANI  7FH
       MOV  M,A
       RET
;
;
CCUR:  CALL CLNA
       MOV  A,M
       ANI  7FH
       MOV  M,A
       RET
;
;
CLN:   DS   1
CCP:   DS   1
CURF:  DS   1
BOSL:  DS   1
BOTL:  DS   1
HLSAV: DS   32
VSTACK: DS   1
	ENDIF		;END OF VDM DRIVER.

;
; MOVE DISK TO TRACK ZERO.
;
HOME:	MVI  A,RTCNT	;GET RETRY COUNT.
HRETRY:	STA  ERCNT	;STORE IN ERROR CTR.
	MVI  A,0D0H	;CLEAR ANY PENDING COMMAND.
	OUT  DCOM
	LDA  DISKNO	;GET DISK NUMBER.
	MOV  E,A	;PUT IN D&E.
	XRA  A	
	MOV  D,A
	LXI  H,TRTAB	;GET ADR OF TRK TABLE.

	IF  NOT DUAL	;IF NOT A DUAL DRIVE.
	DAD  D		;ADD DISK NUMBER.
	ENDIF

	MOV  M,A	;PUT ZERO INTO TABLE.
HOME1:	IN   DSTAT	;READ DISK STATUS.
	RRC		;LOOK AT LSB.
	JC   HOME1	;WAIT FOR NOT BUSY.

	IF   FAST	;IF FAST SEEK,
	LDA  NODSKS	;GET NUMBER OF DISKS.
	DCR  A		;IF ONLY ONE DISK,
	JZ   HOME2	;SKIP OVER NEXT PART.
	LDA  DISKNO	;GET DISK NUMBER.
	RLC!RLC!RLC!RLC	;SHIFT LEFT 4 BITS.
	ANI  30H	;LOOK AT BITS 4 AND 5.
HOME2:	CMA		;INVERT.
	MOV  E,A	;SAVE IT.
	ANI  0B2H	;SET PERSCI
	OUT  DCONT	;RESTORE LINE.
HLOOP:	IN   DSTAT	;LOOK AT STATUS
	ANI  4		;BIT 2 (TRACK 0)
	JZ   HLOOP	;AND WAIT TILL 1.
	MOV  A,E	;RESTORE OLD BITS
	ANI  0F2H	;IN LATCH AND CLEAR
	OUT  DCONT	;RESTORE LINE.
	ENDIF

	MVI  A,2	;10 MS STEP RATE.
	OUT  DCOM	;ISSUE HOME COMMAND.
	IN   WAIT	;WAIT FOR INTRQ.
	ORA  A		;SET FLAGS.
	JM   HERR	;ERROR IF DRQ.
	IN   DSTAT	;READ DISK STATUS.
	MOV  D,A	;SAVE IN REGISTER D.
	ANI  4		;LOOK AT BIT 2.
	JZ   HERR	;ERROR IF NOT TRK 0.
	MOV  A,D	;GET STATUS BACK.
	ANI  91H	;MASK NON-ERROR BITS.
	RZ		;RETURN IF NO ERROR.
HERR:	LDA  HECNT	;GET TOTAL ERROR COUNT.
	INR  A		;ADD ONE.
	STA  HECNT	;SAVE IT BACK.
	LDA  ERCNT	;GET THIS ERROR COUNT.
	DCR   A		;DECREMENT COUNT.
	JNZ  HRETRY	;TRY TO HOME AGAIN.
	LXI  H,HEMSG	;PRINT "HOME ".
	MOV  A,D	;MASK NON-ERROR BITS.
	ANI  91H
	MOV  D,A
	JMP  ERMSG	;DO COMMON ERROR MSGS.
;
; SELECT DISK NUMBER ACCORDING TO REGISTER C.
;
SELDSK:	MOV  A,C	;GET NEW DISK NUMBER.
	ANI  3		;ONLY LOOK AT 2 LSB'S.
	LXI  H,DISKNO	;GET ADR OF OLD DISK NO.
	CMP  M		;NEW = OLD?
	RZ		;IF SO, RETURN.
	PUSH A		;SAVE DISK NUMBER.
	LDA  NODSKS	;GET NUMBER OF DISKS.
	DCR  A		;IF MORE THAN ONE DISK,
	JNZ  SELMOR	;TAKE CARE OF IT.
	LXI  H,MNTMSG	;GET ADR OF MOUNT MESSAGE.
	CALL PMSG	;PRINT "MOUNT ".
	POP  A		;GET DISK NUMBER.
	STA  DISKNO	;UPDATE OLD WITH NEW.
	ADI  'A'	;ADD ASCII FOR 'A'.
	MOV  C,A	;PUT INTO C.
	CALL CONOT	;PRINT IT.
	CALL CONIN	;READ A CARRIAGE RETURN.
	XRA  A		;SET A=0 FOR NO ERRO IND.
	RET		;RETURN FROM SELDSK.
SELMOR:	POP  A		;MAKE STACK RIGHT.
	MOV  A,M	;GET OLD DISK NUMBER.

	IF   DUAL	;IF DUAL DRIVE,
	ANI  0FEH	;CLEAR OUT BIT 0.
	ENDIF

	MOV  E,A	;PUT OLD DISK NO. IN D&E.
	MVI  D,0
	LXI  H,TRTAB	;GET ADDRESS OF TRACK TABLE.
	DAD  D		;ADD DISK NO. TO ADDRESS.
	IN   TRACK	;READ 1771 TRACK REGISTER.
	MOV  M,A	;PUT INTO TABLE.
	MOV  A,C	;GET NEW DISK NUMBER.

	IF   DUAL	;IF A DUAL DRIVE,
	ANI  0FEH	;CLEAR BIT 0.
	ENDIF

	MOV  E,A	;PUT NEW DISK NO. IN D&E.
	LXI  H,TRTAB	;GET ADDRESS OF TRACK TABLE.
	DAD  D		;ADD DISK NO. TO ADDRESS.
	MOV  A,M	;GET NEW TRACK NUMBER.
	OUT  TRACK	;PUT INTO 1771 TRACK REG.
	MOV  A,C	;UPDATE OLD DISK NUMBER.
	STA  DISKNO
	CMA		;BITS INVERTED INTO LATCH.
	ADD  A		;PUT BITS 1&2 AT 4&5.
	ADD  A
	ADD  A
	ADD  A
	ORI  2		;MAKE LATCH COMMAND.
DSK1:	OUT  DCONT	;SET THE LATCH WITH CODE.
         XRA  A		;SET A = 0.
         RET		;RETURN FROM SELDSK.
;
; SET TRACK NUMBER TO WHATEVER IS IN REGISTER C.
; ALSO PERFORM MOVE TO THE CORRECT TRACK (SEEK).
;
SETTRK:	MOV  A,C	;GET NEW TRACK NUMBER.
	STA  TRK	;UPDATE OLD WITH NEW.
	CALL SEEK	;MOVE TO NEW TRACK.
	RET		;RETURN FROM SETTRK ROUTINE.
;
; SET DISK SECTOR NUMBER.
;
SETSEC:  MOV  A,C	;GET SECTOR NUMBER.
         STA  SECT	;PUT AT SECT # ADDRESS.
         RET		;RETURN FROM SETSEC.
;
; SET DISK DMA ADDRESS.
;
SETDMA:  MOV  H,B	;MOVE B&C TO H&L.
         MOV  L,C
         SHLD DMAADD	;PUT AT DMA ADR ADDRESS.
         RET		;RETURN FROM SETDMA.
;
; READ A SECTOR WITHOUT LOADING HEAD FIRST.
;
READ2:	OUT  SECTP	;SET SECTOR NUMBER INTO 1771.
	MVI  A,88H	;GET CODE FOR READ W/O HLD.
	JMP  READE	;READ A SECTOR.
;
; READ THE SECTOR AT SECT, FROM THE PRESENT TRACK.
; USE STARTING ADDRESS AT DMAADD.
;
READ:	MVI  A,RTCNT	;GET RETRY COUNT.
RRETRY:	STA  ERCNT	;STORE IN ERROR CTR.
	LHLD DMAADD	;GET STARTING ADR.
	MVI  A,0D0H	;CAUSE INTERRUPT.
	OUT  DCOM
	XTHL		;SOME DELAY.
	XTHL

	IF  INTRP	;IF INTERRUPTS ALLOWED,
	DI		;DISABLE THEM HERE.
	ENDIF

	IN   DSTAT	;READ STATUS.
	ANI  20H	;LOOK AT HLD BIT.
	LDA  SECT	;GET SECTOR NUMBER.
READ1:	OUT  SECTP	;SET SECTOR INTO 1771.
	MVI  A,8CH	;READ WITH HEAD LOAD
	JZ   READE	;HEAD NOT LOADED.
	MVI  A,88H	;CODE FOR READ W/O HD LD.
READE:	PUSH	B	;SAVE BC REGISTER PAIR
	MVI	C,DDATA	;PICK UP DA OF DATA REGISTER IN 1771
	OUT  DCOM	;SEND COMMAND TO 1771.
RLOOP:	IN   WAIT	;WAIT FOR DRQ OR INTRQ.
	ORA  A		;SET FLAGS.
	JP   RDDONE	;DONE IF INTRQ.
	IF	NOT Z80	;IF 8080 PROCESSOR
	IN   DDATA	;READ A DATA BYTE FROM DISK.
	MOV  M,A	;PUT BYTE INTO MEMORY.
	INX  H		;INCREMENT MEMORY POINTER.
	ENDIF		;
	IF	Z80	;IF Z-80 PROCESSOR
	DW	0A2EDH	;Z-80 'INI' COMMAND
	ENDIF
	JMP  RLOOP	;KEEP READING.
RDDONE:	POP	B	;RESTORE BC REGISTER PAIR
	IN   DSTAT	;READ DISK STATUS.

	IF  INTRP	;IF INTERRUPTS ALLOWED,
	EI		;ALLOW AGAIN HERE.
	ENDIF

	ANI  9DH	;LOOK AT ERROR BITS.
	RZ		;RETURN IF NONE.
CHECK:	CALL ERCHK	;CHECK FOR SEEK ERROR.
	LXI  H,RECNT	;GET RD ERR COUNT ADDR.
	INR  M		;ONE MORE ERROR.
	LDA  ERCNT	;GET ERROR COUNT.
	DCR  A		;DECREMENT COUNT.
	JNZ  RRETRY	;TRY TO READ AGAIN.
	LXI  H,RDMSG	;PRINT "READ ".
ERMSG:	CALL PMSG	;PRINT ORIGIN MESSAGE.
ERMSG1:

	IF NOT VDM	;IF NOT PROC TECH VDM,
	MOV  A,D	;GET ERROR BITS.
	ANI  80H	;IF BIT 7 HIGH,
	LXI  H,NRMSG	;"NOT READY".
	CNZ PMSG
	MOV  A,D	;GET ERROR BITS.
	ANI  10H	;IF BIT 4 IS HIGH,
	LXI  H,RNMSG	;PRINT "RECORD NOT FOUND"
	CNZ PMSG
	MOV  A,D	;GET ERROR BITS.
	ANI  8H		;IF BIT 3 IS HIGH,
	LXI  H,CRCMSG	;PRINT "CRC ERROR".
	CNZ PMSG
	MOV  A,D	;GET ERROR BITS.
	ANI  4H		;IF BIT 2 IS HIGH,
	LXI  H,LDMSG	;PRINT "LOST DATA".
	CNZ PMSG
	MOV  A,D	;GET ERROR BITS.
	ANI  1		;IF BIT 1 IS HIGH,
	LXI  H,BSYMSG	;PRINT "BUSY".
	CNZ  PMSG
	ENDIF

PERMSG:	LXI  H,ERRMSG	;PRINT "ERROR."
	CALL PMSG
	MVI  A,1	;SET FOR PERM ERR MSG.
	ORA  A		;SET FLAGS.
	RET
;
; ERCHK - CHECK FOR RECORD NOT FOUND ERROR.
;
ERCHK:	MOV  D,A	;SAVE ERROR BITS IN D.
	ANI  10H	;IF RECORD NOT FOUND,
	JNZ  CHKSK	;DO A CHECK ON SEEK.
	MOV  A,D	;OTHERWISE RESTORE BITS
	ORA  A		;SET FLAGS,
	RET		;AND RETURN.
;CHECK FOR SEEK TO CORRECT TRACK,
;AND CHANGE IF NECESSARY.
CHKSK:	MVI  A,0C4H	;SEND COMMAND TO 1771
	OUT  DCOM	;TO READ ADDRESS.
	IN   WAIT	;WAIT FOR DRQ OR INTRQ.
	IN   DDATA	;READ THE TRACK ADDRESS.
	MOV  B,A	;SAVE IN REGISTER B.
CHKS2:	IN   WAIT	;WAIT FOR INTRQ.
	ORA  A		;SET FLAGS.
	JP   CHKS3	;DONE WITH READ ADR OP.
	IN   DDATA	;READ ANOTHER BYTE.
	JMP  CHKS2	;DO IT AGAIN.
CHKS3:	IN  DSTAT	;READ DISK STATUS.
	ORA  A		;SET FLAGS.
	JZ   CHKS4	;READ ADR OK IF 0.
	CALL HOME	;OTHERWISE, HOME FIRST.
	JMP  CHKS5
CHKS4:	MOV  A,B	;UPDATE TRACK REGISTER.
	OUT  TRACK
CHKS5:	LDA  TRK	;GET REQUIRED TRACK NO.
	CALL SEEK	;MOVE THE HEAD TO IT.
	MOV  A,D	;GET ERROR BITS.
	ORA  A		;SET FLAGS.
	RET		;RETURN FROM ERCHK.
;
; WRITE THE SECTOR AT SECT, ON THE PRESENT TRACK.
; USE STARTING ADDRESS AT DMAADD.
;
WRITE:	MVI  A,RTCNT	;GET RETRY COUNT.
WRETRY:	STA  ERCNT	;STORE IN ERROR COUNTER.
	LHLD DMAADD	;GET STARTING ADR.
	MVI  A,0D0H	;STATUS INTERUPT FOR 1771.
	OUT  DCOM	;COMMAND 1771.
	XTHL		;WAIT FOR STATUS.
	XTHL		;CHANGE IT BACK.

	IF  INTRP	;IF INTERRPUTS ALLOWED,
	DI		;DISABLE THEM HERE.
	ENDIF

	IN   DSTAT	;GET 1771 STATUS.
	ANI  20H	;CHECK FOR HEAD LOAD.
	LDA  SECT	;GET SECTOR NUMBER.
WRITE1:	OUT  SECTP	;SET THE SECTOR INTO 1771.
	MVI  A,0ACH	;SET UP 1771 FOR WRITE.
	JZ   WRITE2	;HEAD IS NOT LOADED.
	MVI  A,0A8H	;CODE FOR WRITE W/O HD LD.
WRITE2:	PUSH	B	;SAVE BC REGISTER PAIR
	MVI	C,DDATA	;PICK UP DA OF DATA REGISTER IN 1771
	OUT  DCOM
WLOOP:	IN   WAIT	;WAIT FOR READY.
	ORA  A		;SET FLAGS.
	JP   WDONE	;HOP OUT WHEN DONE.
	IF	NOT Z80	;IF 8080 PROCESSOR
	MOV  A,M	;GET BYTE FROM MEM.
	OUT  DDATA	;WRITE ONTO DISK.
	INX  H		;INCREMENT MEM PTR.
	ENDIF		;
	IF	Z80	;IF Z-80 PROCESSOR
	DW	0A3EDH	;Z-80 'OUTI' COMMAND
	ENDIF		;
	JMP  WLOOP	;KEEP WRITING.
WDONE:	POP	B	;RESTORE BC REGISTER PAIR
	IN   DSTAT	;READ DISK STATUS.

	IF  INTRP	;IF INTERRUPTS ALLOWED,
	EI		;ENABLE AGAIN HERE.
	ENDIF

	ANI  0FDH	;LOOK AT THESE BITS.
PROCER:	RZ		;RETURN IF NO ERR.
	CALL ERCHK	;CHECK/CORRECT SEEK ERR.
	LXI  H,WECNT	;GET ADR OF WRITE ERR CTR.
	INR  M		;ONE MORE WRITE ERROR.
	LDA  ERCNT	;GET ERROR COUNT.
	DCR  A		;DECREMENT COUNT.
	JNZ  WRETRY	;TRY TO WRITE AGAIN.
WERR0:	LXI  H,WTMSG	;PRINT "WRITE ".

	IF NOT VDM	;IF NOT PROC TECH VDM,
	CALL PMSG
	MOV  A,D	;GET ERROR BITS.
	ANI  40H	;LOOK AT BIT 6.
	LXI  H,WPMSG	;PRINT "PROTECT ".
	CNZ  PMSG
	MOV  A,D	;GET ERROR BITS.
	ANI  20H	;LOOK AT BIT 5.
	LXI  H,WFMSG	;PRINT "FAULT ".
	CNZ  PMSG
	JMP  ERMSG1	;DO COMMON MESSAGES.
	ENDIF

	IF   VDM	;IF PROC TECH VDM,
	JMP  ERMSG
	ENDIF

;
; MOVE THE HEAD TO THE TRACK IN REGISTER A.
;
SEEK:	PUSH B		;SAVE B&C.
	MOV  B,A	;SAVE DESTINATION TRACK.
	MVI  A,RTCNT	;GET RETRY COUNT.
SRETRY:	STA  SERCNT	;STORE IN ERROR COUNTER.
	IN   TRACK	;READ PRESENT TRACK NO.
	MOV  C,A	;SAVE IN C.
	MOV  A,C	;DELAY.
	CMP  B		;SAME AS NEW TRACK NO.?
	MOV  A,B	;RESTORE A FROM B.
	JNZ  NOTHR	;JUMP IF NOT THERE.
THERE:	POP  B		;RESTORE B&C.
	RET		;RETURN FROM SEEK.
NOTHR:

	IF NOT FAST	;IF NOT FAST SEEK,
	OUT  DDATA	;TRACK TO DATA REGISTER.
BUSY:	IN   DSTAT	;READ DISK STATUS.
	RRC		;LOOK AT BIT 0.
	JC   BUSY	;WAIT TILL NOT BUSY.
	MVI  A,12H	;SET FOR 10 MS STEP.
	ORI  4		;VERIFY ON LAST TRACK.
	OUT  DCOM	;ISSUE SEEK COMMAND.
	IN   WAIT	;WAIT FOR INTRQ.
	IN   DSTAT	;READ STATUS.
	ANI  91H	;LOOK AT BITS.
	JZ  THERE	;OK IF ZERO.
	ENDIF

	IF  FAST	;IF FAST SEEK,
	MVI  A,40H	;IF CARRY = 1,
	JC  SDIR	;STEP IN.
	MVI  A,60H	;OTHERWISE, OUT.
SDIR:	OUT  DCOM	;ISSUE STEP DIRECTION.
	MVI  A,20	;DELAY LOOP COUNT.
DLOOP:	DCR  A		;DECREMENT COUNTER.
	JNZ  DLOOP
	MOV  A,C	;GET PRESENT TRACK.
	SUB  B		;FIGURE TRACKS TO STEP.
	JP   STEP	;IF NEGATIVE,
	CMA		;FIGURE THE
	INR  A		;TWO'S COMPLEMENT.
STEP:	MOV  C,A	;GET DIFFERENCE.
	MVI  A,1	;PERSCI STEP COMMAND.
STEP1:	OUT  DCONT	;STEP PERSCI (E-14).
	DCR  C		;COUNT THE STEP.
	JNZ  STEP1	;STEP UNTIL C = 0.
	IN   WAIT	;CLEAR 1771.
	IN   DSTAT
	MOV  A,B	;GET DEST. TRACK.
	OUT  TRACK	;UPDATE TRACK REG.
	LDA  DISKNO	;GET DISK NUMBER.
	RLC!RLC!RLC!RLC	;SHIFT LEFT 4 BITS.
	ANI  30H	;LOOK AT BITS 4 AND 5.
	CMA		;INVERT.
	MOV  B,A	;SAVE IN B.
	ANI  72H	;MAKE COMMAND TO
	OUT  DCONT	;SWITCH WAIT FOR
	IN   WAIT	;SEEK COMPLETE.
	MOV  A,B	;RESTORE ORIG. BITS.
	ANI  0F2H	;SWITCH WAIT BACK.
	OUT  DCONT
	XRA  A		;MAKE GOOD RETURN.
	POP  B		;RESTOREE  B&C.
	RET
	ENDIF

	IF NOT FAST	;IF NOT FAST SEEK,
	PUSH H		;SAVE H&L.
	LXI  H,SECNT	;GET ADR OF SEEK ERR CTR.
	INR  M		;ONE MORE SEEK ERROR.
	POP  H		;RESTORE H&L.
	LDA  ERCNT	;GET ERROR COUNT.
	DCR  A		;DECREMENT COUNT.
	JNZ  SRETRY	;RETRY SEK.
	POP  B		;RESTORE B&C.
	LXI  H,SKMSG	;PRINT "SEEK ".
	IN   DSTAT	;READ DISK STATUS.
	ANI  91H	;LOOK AT ERROR BITS.
	MOV  D,A	;PUT IN REG D.
	JMP  ERMSG	;DO COMMON ERR MESSAGES.
	ENDIF

;
; PRINT THE MESSAGE AT H&L UNTIL A ZERO.
;
PMSG:    MOV  A,M	;GET A CHARACTER.
         ORA  A		;IF IT'S ZERO,
         RZ		;RETURN.
         MOV  C,A	;OTHERWISE,
         CALL CONOT	;PRINT IT.
         INX  H		;INCREMENT H&L,
         JMP  PMSG	;AND GET ANOTHER.
;
; CBIOS MESSAGES
;

	IF NOT VDM	;IF NOT PROC TECH VDM,
NRMSG:	DB   'NOT RDY ',0
RNMSG:	DB   'REC NOT FND ',0
CRCMSG:	DB   'CRC ',0
LDMSG:	DB   'LOST DATA ',0
BSYMSG:	DB   'BUSY ',0
WPMSG:	DB   'PROT ',0
WFMSG:	DB   'FAULT ',0
	ENDIF

ERRMSG:	DB   'ERR',0
RDMSG:	DB   0DH,0AH,'READ ',0
WTMSG:	DB   0DH,0AH,'WRITE ',0
BTMSG:	DB   'BOOT ERR',0
SKMSG:	DB   0DH,0AH,'SEEK ',0
HEMSG:	DB   0DH,0AH,'HOME ',0
MNTMSG:	DB   0DH,0AH,'MOUNT ',0
SMSG:	DB   0DH,0AH
	DB   'NWC  VER: 2.0  01-29-79'
	DB   0DH,0AH
	DB   MSIZE/10+'0',MSIZE MOD 10 + '0','K '

	IF  STD		;IF STANDARD I/O,
	DB   'STANDARD '
	ENDIF

	IF  MSIO2	;IF MITS 2SIO,
	DB   '2SIO '
	ENDIF

	IF  ISIO2	;IF IMSAI SIO-2,
	DB  'SIO-2 '
	ENDIF

	IF  TUART	;IF TUART,
	DB  'TUART '
	ENDIF

	IF   VDM	;IF PROC TECH VDM,
	DB   'VDM '
	ENDIF

	IF  FAST	;IF FAST SEEK,
	DB  'F SEEK '
	ENDIF

	IF  DUAL	;IF DUAL DRIVE,
	DB  'DUAL '
	ENDIF

	IF	Z80
	DB	'Z-80 '
	ENDIF
	IF NOT Z80
	DB	'8080 '
	ENDIF
	DB  'VER'
	DB   0DH,0AH,0
;
; WRITE A CHARACTER ON LISTING DEVICE.
;
LIST:
	CALL	LISTR	;CODE IN SYSTEM$$
	RET

;
; NORMALLY USED TO PUNCH PAPER TAPE, BUT IS
; NICE FOR AN INFINITE BIT BUCKET TO CHECK FILES.
;
PUNCH:
	IF NOT VDM	;IF NOT PROC TECH VDM,
	CALL	CONOT
	ENDIF

	RET		;RETURN FROM PUNCH.
;
;  NORMALLY USED TO READ PAPER TAPE.
; SET UP TO READ FROM CONSOLE IN STANDARD SYSTEM.
;
READER:
	IF NOT VDM	;IF NOT PROC TECH VDM,
	CALL CONIN	;READ FROM CONSOLE.
	ENDIF

	RET		;RETURN FROM READER.

;NOTE:  AS THERE ARE ONLY NINE SECTORS
;AVAILABLE FOR CBIOS ON THE SECOND SYSTEM TRACK (1),
;THE LAST ADDRESS BEFORE THIS POINT SHOULD BE NO
;GREATER THAN THE CBIOS STARTING ADDRESS + 047F (HEX).
;THIS WILL NORMALLY BE XE7F (HEX).

;
; ERROR COUNTS.  THESE LOCATIONS KEEP TRACK OF THE
; NUMBER OF ERRRS THAT OCCUR DURING READ, WRITE,
; OR SEEK OPERATIONS.  THEY ARE INITIALIZED ONLY
; WHEN A COLD-START IS PERFORMED BY THE BOOOTSTRAP.
;
HECNT:	DB   0		;HOME ERROR COUNT.
RECNT:	DB   0		;READ ERROR COUNT.
WECNT:	DB   0		;WRITE ERROR COUNT.
SECNT:	DB   0		;SEEK ERROR COUNT.
;
; TRTAB - DISK TRACK TABLE - PRESENT POSITION OF
;	HEADS FOR UP TO 4 DRIVES.
;
TRTAB:	DS   4
;
NODSKS:	DB   2		;NUMBER OF DISKS.
ERCNT:	DS   1		;ERROR COUNT FOR RETRIES.
SERCNT:	DS   1		;SEEK RETRY COUNTER.
TEMP:	DS   1		;TEMPORARY STORAGE.
TRK:	DS   1		;CURRENTLY SELECTED TRACK.
SECT:	DS   1		;CURRENTLY SELECTED SECTOR.
DMAADD:	DS   2		;CURRENT READ/WRITE ADDRESS.
DISKNO:	DB   0		;CURRENT DISK NUMBER.
STATIN: DB   0		;SAVED INPUT STATUS
	END
