;
; CBIOS (CUSTOM BASIC INPUT OUTPUT SYSTEM) MODULE FOR USE
; WITH CP/M AND A TARBELL/WD1771 DISK CONTROLLER
; USING PERSCI VOICE-COIL POSITIONER DISKETTE DRIVES
; IN A P.T. SOL-20 COMPUTER
;
; BY BARRY A. WATZMAN 8/4/78
;

;
;MEMORY SIZE
;
MEMT	EQU	0C000H		;MEMORY SIZE = 48K

;WHERE TO ENTER CP/M AFTER WARM OR COLD BOOT
CPMB	EQU	MEMT-1B00H


;SIZE OF CP/M MODULES
CCPSIZ	EQU	8*256		;8 FOR 1.4; 9 FOR 1.3
BDOSIZ	EQU	13*256		;13 FOR 1.4; 12 FOR 1.3

;BDOS INITIALIZATION ENTRY
BDOS	EQU	CPMB+CCPSIZ+6

;BIOS BASE
BIOS	EQU	CPMB+CCPSIZ+BDOSIZ

;I/O BYTE IN LOWER RAM
IOBYT	EQU	3

;WHERE CPM PUTS LOGGED IN DISK
LOGDSK	EQU	4

;NO OF SECTORS TO READ FROM DISK ON WARM BOOT
NSECTS	EQU	51		;READ IN ENTIRE SYSTEM

;NO OF RETRY'S ON ERROR
RTCNT	EQU	10

;I/O PORT EQUATES
DISK	EQU	0E8H		;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
TTY	EQU	0F9H
TTYS	EQU	0F8H
KEY	EQU	0FCH
KEYS	EQU	0FAH
VDM1	EQU	0CC00H
VDMPT	EQU	0FEH		;VDM-1 I/O PORT

	ORG	BIOS

;
; ENTRY POINT TABLE
;
	JMP	BOOT		;FROM COLD START LOADER
	JMP	WBOOT		;FROM WARM BOOT
	JMP	CONST		;CHECK CONSOLE STAT
	JMP	CONIN		;READ CONSOLE CHAR
	JMP	CONOUT		;WRITE CONSOLE CHAR
	JMP	LIST		;WRITE LISTING CHAR
	JMP	PUNCH		;WRITE PUNCH CHAR
	JMP	READER		;READ CHAR FROM READER
	JMP	HOME		;MOVE DISK TO TRACK ZERO
	JMP	SETDSK		;SELECT DISK DRIVE
	JMP	SETTRK		;SEEK TO TRACK IN REG A
	JMP	SETSEC		;SET SECTOR NO.
	JMP	SETDMA		;SET STARTING ADDR FOR R/W
	JMP	READ		;READ SELECTED SECTOR
	JMP	WRITE		;WRITE SELECTED SECTOR
;
; BOOT - EXECUTED FOR COLD START
;
BOOT:	IN	0FFH		;READ SENSE SWITCHES
	ORA	A		;ALL ZEROS ?
	JNZ	SETIOB		;IF NOT, USE THEM AS I/O BYTE
	MVI	A,95H		;ELSE USE 95H
SETIOB:	STA	IOBYT		;SET I/O BYTE
	LXI	H,SMSG		;PRINT SIGN-ON MSG
	CALL	PMSG
	STA	LOGDSK		;ZERO WAS LEFT FROM PMSG
GOCPM:	MVI	A,0C3H		;PUT JMP TO WBOOT
	LXI	H,BIOS+3
	STA	0		;AT ZERO
	SHLD	1
	LXI	H,BDOS		;PUT JUMP TO BDOS
	STA	5
	SHLD	6		;AT ADDR 5,6,7
	LXI	H,NXM
	STA	038H
	SHLD	038H+1
	LXI	H,80H		;SET DEFAULT DMA ADR
	SHLD	DMAADD
	LDA	LOGDSK		;GET DISK NO TO
	STA	DISKNO
	MOV	C,A		;PASS TO CCP IN C
	JMP	CPMB		;JUMP TO CCP.

;
; WARM-BOOT:  READ ALL OF CPM BACK IN
; EXCEPT BIOS, THEN JUMP TO CCP.
;
WBOOT:	LXI	SP,MEMT		;SET STACK POINTER
	MVI	A,0		;THIS BIOS ALWAYS BOOTS OFF 0
	STA	DISKNO		;SELECT A: DISK
	CALL	SELDSK		;SELECT DRIVE 0
	MVI	D,NSECTS	;GET # SECTORS FOR CPM READ
	LXI	B,2		;TRACK (B)=0, SECTOR (C)=2.
	LXI	H,CPMB		;GET STARTING ADDRESS
RDBLK:	MOV	A,B		;GO TO TRACK IN B
	STA	TRK
	CALL	SEEK		;SEEK TO TRACK
	MOV	A,C		;READ STARTING AT SECTOR IN C
	CALL	READ1
RBLK1:	JNZ	BOOTER		;IF ERROR, PRINT MESSAGE
	DCR	D		;DCR SECTOR COUNT
	JZ	GOCPM
	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
RBLK2:	OUT	SECTP		;SET SECTOR NO INTO 1771
	MVI	A,88H		;CODE FOR READ W/O HEAD LOAD
	CALL	READE		;READ A SECTOR
	JMP	RBLK1
;
BOOTER:	LXI	H,BTMSG		;GET ADDRESS OF "BOOT ERROR"
	CALL	PMSG		;PRINT IT
	CALL	CONIN		;WAIT FOR CHAR TO TRY AGAIN
	JMP	WBOOT		;DO A WARM BOOT
;
;HOME - SEEK HEAD TO TRACK 0
;
HOME:	MVI	C,0		;TRACK 0
;
;SETTRK ROUTINE TO SET A TRACK ADDRESS
;
SETTRK:	MOV	A,C		;GET TRACK TO A
	STA	TRK		;
	RET			;DONE
;
;SETDSK - ROUTINE TO SELECT A DISK DRIVE
;
SETDSK:	MOV	A,C		;GET DISK
	STA	DISKNO		;SAVE IT
	RET			;DONE
;
; SET DISK SECTOR NUMBER.
;
SETSEC:	MOV	A,C		;GET SECTOR NO
	STA	SECT		;PUT AT SECT # ADDR
	RET			;DONE
;
; SET DISK DMA ADDRESS.
;
SETDMA:	MOV	H,B		;MOVE B,C TO H,L
	MOV	L,C
	SHLD	DMAADD		;PUT AT DMA ADDR
	RET			;DONE
;
; 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 ERR CTR
	CALL	SELDSK		;SELECT PROPER DISK
	CALL	SEEK		;SEEK TO TRACK
	LHLD	DMAADD		;GET START ADDR
	MVI	A,0D0H		;CAUSE INTERRUPT TO 1771
	OUT	DCOM
	XTHL			;SOME DELAY
	XTHL			;MORE
	IN	DSTAT		;READ STATUS
	ANI	20H		;LOOK AT HD LOAD BIT
	LDA	SECT		;GET SECTOR NO
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:	OUT	DCOM		;SENT COMMAND TO 1771
RLOOP:	IN	WAIT		;WAIT FOR DRQ OR INTRQ
	ORA	A		;SET FLAGS
	JP	RDDONE		;DONE IF INTRQ
	IN	DDATA		;READ A BYTE FM DISK
	MOV	M,A		;PUT IT INTO MEMORY
	INX	H		;INR MEMORY POINTER
	JMP	RLOOP		;KEEP ON READING
RDDONE:	IN	DSTAT		;READ DISK STATUS
	ANI	9DH		;LOOK AT ERROR BITS
	RZ			;RET IF NONE
	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		;DCR COUNT
	JNZ	RRETRY		;TRY AGAIN
	LXI	H,RDMSG		;PRINT "READ "
ERMSG:	CALL	PMSG		;PRINT ORIGIN MESSAGE
ERMSG1:	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 HIGH
	LXI	H,RNMSG		;PRINT "RECORD NOT FOUND"
	CNZ	PMSG
	MOV	A,D		;GET ERROR BITS
	ANI	08H		;IF BIT 3 HIGH
	LXI	H,CRCMSG	;PRINT "CRC ERROR"
	CNZ	PMSG
	MOV	A,D		;GET ERROR BITS
	ANI	04H		;IF BIT 2 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
	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 RCD NOT FOUND
	JNZ	CHKSK		;THEN DOUBLE CHECK SEEK
	MOV	A,D		;OTHERWISE RESTORE BITS
	ORA	A		;SET RET CODE
	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 ADDR
	MOV	B,A		;SAVE IN REG B
CHKS2:	IN	WAIT		;WAIT FOR INTRQ
	ORA	A		;SET FLAGS
	JP	CHKS3		;DONE WITH READ ADDR OP
	IN	DDATA		;READ ANOTHER BYTE
	JMP	CHKS2		;DO IT AGAIN
CHKS3:	IN	DSTAT		;READ DISK STATUS
	ORA	A		;SET FLAGS
	JZ	CHKS4		;READ ADDR OK IF 0
	MVI	A,0B2H		;RESTORE A: DRIVE
	OUT	DCONT		;DO IT (RESTORES BOTH)
	MVI	A,3		;1771 HOME COMAND
	OUT	DCOM		;ISSUE IT (RESTORES 1771)
	IN	WAIT		;WAIT TILL COMPLETE
	CALL	SELDSK		;SELECT CURRENT DISK
	JMP	CHKS5
CHKS4:	MOV	A,B		;UPDATE TRACK REGISTER
	OUT	TRACK
CHKS5:	CALL	SEEK		;MOVE HEAD TO IT
	MOV	A,D		;GET ERROR BITS
	ORA	A		;SET FLAGS
	RET			;RETURN FROM ERCHK
;
; SELECT DISK NUMBER ACCORDING TO REGISTER C.
;
SELDSK:	LDA	DISKNO		;GET DISK NUMBER
	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			;RET FROM SELDSK
;
; 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
	CALL	SELDSK		;SELECT DISK
	CALL	SEEK		;SEEK TO TRACK
	LHLD	DMAADD		;GET STARTING ADDR
	MVI	A,0D0H		;STAT INTERUPT FOR 1771
	OUT	DCOM		;COMMAND 1771
	XTHL			;A SMALL DELAY
	XTHL			;MORE
	IN	DSTAT		;GET 1771 STAT
	ANI	20H		;CHECK FOR HEAD LOAD
	LDA	SECT		;GET SECTOR NO
	OUT	SECTP		;SET THE SECTOR INTO 1771
	MVI	A,0ACH		;SET UP FOR WRITE
	JZ	WRITE2		;HEAD IS NOT LOADED
	MVI	A,0A8H		;CODE FOR WRITE W/O HD LD
WRITE2:	OUT	DCOM
WLOOP:	IN	WAIT		;WAIT FOR READY
	ORA	A		;SET FLAGS
	JP	WDONE		;HOP OUT WHEN DONE
	MOV	A,M		;GET BYTE FROM MEM
	OUT	DDATA		;WRITE TO DISK
	INX	H		;INR MEM PTR
	JMP	WLOOP		;KEEP WRITING
WDONE:	IN	DSTAT		;READ DISK STATUS
	ANI	0FDH		;LOOK AT THESE BITS
	RZ			;RET IF NO ERROR
	CALL	ERCHK		;CHECK CORRECT SEEK
	LXI	H,WECNT		;GET ADR OF WRITE ERR CTR
	INR	M		;ONE MORE WRITE ERROR
	LDA	ERCNT		;GET ERROR COUNT
	DCR	A		;DCR COUNT
	JNZ	WRETRY		;TRY TO WRITE AGAIN
	LXI	H,WTMSG		;PRINT "WRITE "
	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
;
; MOVE THE HEAD TO THE TRACK IN REGISTER A.
;
SEEK:	PUSH	B		;SAVE B,C
	LDA	TRK		;GET TRACK TO SEEK TO
	MOV	B,A		;SAVE DEST TRACK
	MVI	A,RTCNT		;GET RETRY COUNT
SRETRY:	STA	SERCNT		;STORE IN ERR COUNTER
	IN	TRACK		;READ PRESENT TRACK NO
	MOV	C,A		;SAVE IN C
	CMP	B		;SAME AS NEW TRACK NO. ?
	JZ	THERE		;JUMP IF NOT THERE
	MVI	A,40H		;IF CARRY = 1
	JC	SDIR		;STEP IN
	MVI	A,60H		;OTHERWISE OUT
SDIR:	OUT	DCOM		;ISSUE STEP DIR
	MVI	A,20		;DELAY LOOP COUNT
DLOOP:	DCR	A		;DCR COUNTER
	JNZ	DLOOP
	MOV	A,C		;GET PRESENT TRK
	SUB	B		;FIGURE TRACKS TO STEP
	JP	STEP		;IF NEG,
	CMA			;FIGURE THE
	INR	A		;TWO'S COMPL
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 NO
	RLC			;SHIFT LEFT 4 BITS
	RLC
	RLC
	RLC
	ANI	10H		;LOOK AT BIT 4
	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 RET
THERE:	POP	B		;RESTORE B,C
	RET
;
;**********************************************
;
;	LOGICAL DEVICE ROUTINES
;
; IMPLEMENTATION OF IOBYTE AS
; DESCRIBED IN CPM ALTERATION GUIDE

;
; CONSOLE STATUS
;
CONST:	CALL	CONS		;GET STAT OF SPECIFIC DEVICE
	ORA	A
	RZ			;IF NOT READY RETURN 0 IN A
	MVI	A,0FFH		;ELSE RETURN FF
	RET
;
CONS:	LDA	IOBYT		;USE BITS 1-0 FOR CON DEV.
	CALL	INDXIT
	DW	TTYSTAT
	DW	KEYSTAT
	DW	RDRST		;2: BATCH MODE, USE READER DEV.
	DW	RDRST
;
;READER STATUS FOR BATCH MODE
;
RDRST:	LDA	IOBYT
	RRC
	CALL	GOTOIT
	DW	TTYIN
	DW	RDRIN
	DW	KEYIN
	DW	CUTSIN
;
; CONSOLE IN
;
CONIN:	LDA	IOBYT
	CALL	INDXIT
	DW	TTYIN		;0: TTY
	DW	KEYIN		;1; CRT
	DW	READER		;2: BATCH MODE: READER INPUT
	DW	READER
;
; CONSOLE OUT
;
CONOUT:	MOV	A,C		;GET CHAR
	CPI	7FH		;DEL ?
	RZ			;RETURN IF SO
	LDA	LASTIN		;GET LAST CHAR INPUT
	CPI	7FH		;WAS IT A DELETE ?
	JNZ	NORM		;NORMAL PROCESSING IF NOT
	MVI	C,08H		;ASCII BACKSPACE
	CALL	NORM		;SEND IT OUT
	MVI	C,' '		;GET A SPACE
	CALL	NORM		;SEND IT OUT
	MVI	C,08H		;ANOTHER BACKSPACE
NORM:	LDA	IOBYT
	CALL	INDXIT		;GO TO ONE OF FOLLOWING ADDRS
	DW	TTYOUT		;BITS=0: USE TTY AS CONSOLE
	DW	CRTOUT		;1: CRT
	DW	LIST		;2: BATCH MODE: OUTPUT TO LST:
	DW	PUNCH
;
; LIST OUT
;
LIST:	LDA	IOBYT
	RLC			;BITS 7-6 TO 2-1
	RLC
	CALL	INDXIT
	DW	TTYOUT		;0: TTY
	DW	CRTOUT		;1: CRT
	DW	LPTOUT		;2; LINE PRINTER
	DW	PUNCH
;
; PUNCH OUT
;
PUNCH:	LDA	IOBYT		;BITS 4-5 TO 1-2
	RRC
	RRC
	RRC
	CALL	GOTOIT
	DW	TTYOUT		;0: TTY
	DW	PUNO		;1: HIGH SPEED PUNCH
	DW	CRTOUT		;2: CRT
	DW	CUTSOT
;
; READER IN
;
READER:	LDA	IOBYT		;BITS 3-2 TO 2-1
	RRC
	CALL	GOTOIT
	DW	TTYIN		;0: TTY
	DW	RDRIN		;1: HIGH SPEED READER
	DW	KEYIN		;2: CRT
	DW	CUTSIN
;
;SUBROUTINE TO DISPATCH TO ONE OF 4 FOLLOWING ADDRESSES
;DEPENDING ON IOBYT BITS CALLER HAS POSITIONED IN
;BITS 2 AND 1 OF A.
;RETURNS TO SUBROUTINE CALL PRIOR TO CALL TO GOTOIT.
;
INDXIT: RLC
GOTOIT:	ANI	06H		;MASK BITS
	XTHL			;SAVE CALLER'S H, GET TABLE ADDR
	PUSH	D		;
	MOV	E,A
	MVI	D,0		;SET UP FOR DAD
	DAD	D		;INDEX INTO TABLE
	MOV	A,M
	INX	H
	MOV	H,M		;TABLE WORD TO HL
	MOV	L,A		;
	POP	D		;
	XTHL			;PUT ADDRESS OF ROUTINE, GET CALLER'S H
	RET			;GO TO ROUTINE !
;
;*****************************************************
;
;	PHYSICAL DEVICE ROUTINES
;
; ACCESSED VIA LOGICAL DEVICE ROUTINES ABOVE

;
; TELETYPE INPUT
;
TTYIN:	CALL	TTYSTAT
	JZ	TTYIN		;WAIT FOR CHAR TO BE AVAILABLE
	IN	TTY		;INPUT IT
	ANI	7FH		;REMOVE PARITY
	STA	LASTIN
	RET
;
TTYSTAT:IN	TTYS		;GET STATUS
	ANI	40H		;MASK BIT
	RET			;A IS NON-0 IF CHAR AVAILABLE
;
; TELETYPE OUTPUT
;
TTYOUT:	IN	TTYS		;STATUS
	ANI	80H
	JZ	TTYOUT		;WAIT TILL READY TO ACCEPT CHAR
	MOV	A,C
	OUT	TTY		;OUTPUT THE CHAR
	RET			;DONE EXCEPT CR
;
; KEYBOARD INPUT
;
KEYIN:	CALL	KEYSTAT
	JZ	KEYIN
	IN	KEY
	PUSH	B		;SAVE B,C (NOT REALLY NEC)
	PUSH	H		;SAME
	LXI	H,TABLE		;POINT TO TRANSLATE TABLE
	MOV	C,A		;SAVE CHAR IN C
TT1:	MOV	A,M		;GET CHAR FROM TABLE
	INX	H		;POINT TO IT'S VALUE
	CMP	C		;CHAR SAME AS INPUT ?
	JZ	TT3		;YES - SUBSTITUTE
	ORA	A		;NO - SEE IF END OF TABLE
	INX	H		;POINT TO NEXT TABLE ENTRY
	JNZ	TT1		;TEST IT IF NOT TABLE END
	MOV	A,C		;END, NO MATCH, RESTORE CHAR
TT2:	POP	H		;RESTORE REGS
	POP	B		;
	STA	LASTIN		;UPDATE LAST CHAR INPUT
	RET			;DONE
TT3:	MOV	A,M		;MATCH, SUBSTITUTE
	JMP	TT2		;RETURN WITH IT
;
;TRANSLATE TABLE FOR THE ELECTRIC PENCIL & OTHERS
;
TABLE:	DB	0B0H		;LEFT
	DB	'A'-40H
	DB	0AEH		;RIGHT
	DB	'S'-40H
	DB	0B2H		;DOWN
	DB	'Z'-40H
	DB	0ABH		;ROLL DOWN
	DB	'X'-40H
	DB	0B1H		;UP
	DB	'W'-40H
	DB	0B3H		;ROLL UP
	DB	'E'-40H
	DB	0AAH		;INSERT
	DB	'F'-40H
	DB	0ADH		;DELT
	DB	'D'-40H
	DB	0B6H		;ERASE TO END OF LINE
	DB	'T'-40H
	DB	08EH		;HOME KEY
	DB	'N'-40H
	DB	0B7H		;DELETE LINE
	DB	'Y'-40H
	DB	0B8H		;INSERT LINE
	DB	'G'-40H
	DB	08CH		;FORM FEED
	DB	'L'-40H
	DB	081H		;LOAD/SAVE
	DB	']'-40H
	DB	093H		;PRINT
	DB	'P'-40H
	DB	08BH		;CLEAR SCREEN
	DB	'K'-40H
	DB	0B9H		;ERASE INPUT
	DB	'\'-40H
	DB	0AFH		;NEWLINE CURSOR ONLY
	DB	'J'-40H
	DB	097H		;RESET
	DB	'['-40H
	DB	09AH		;ENTER
	DB	'Q'-40H
	DB	0B4H		;NEXT PAGE
	DB	'^'-40H
	DB	0B5H		;PREV PAGE
	DB	1FH
	DB	0
;
KEYSTAT:IN	KEYS
	CMA
	ANI	01H
	RET
;
; CRT OUTPUT
;
CRTOUT:	LDA	LSTOUT		;GET LAST OUTPUT CHAR
	CPI	0DH		;WAS IT C/R ?
	JNZ	CRTOT1		;NO, DO OUTPUT
	CMP	C		;IS THIS CHAR C/R ALSO ?
	RZ			;DON'T SEND TWO IN A ROW
CRTOT1:	MOV	A,C		;CHAR TO A
	STA	LSTOUT		;SAVE AS LAST CHAR OUTPUT
	MVI	A,0		;USE VDM-1
	MOV	B,C		;GET CHAR TO B
	JMP	0C01CH		;SOLOS'S AOUT ENTRY POINT
;
; LINE PRINTER OUT
;
LPTOUT:	IN	0FDH		;PARALLEL DATA PORT
	ANI	1		;TEST BUSY BIT
	JNZ	LPTOUT		;LOOP TILL NOT BUSY
	MOV	A,C		;GET CHAR
	OUT	1		;WRITE IT
	RET			;DONE
;
; CUTS TAPE INPUT
;
CUTSIN:	RET
;
; CUTS TAPE OUTPUT
;
CUTSOT:	RET
;
; HEATHKIT PUNCH OUTPUT
;
PUNO:	RET
;
; HEATHKIT PUNCH IN
;
RDRIN:	RET
;
;
; PRINT THE MESSAGE AT H&L UNTIL A ZERO.
;
PMSG:	MOV	A,M		;GET A CHAR
	ORA	A		;IF IT'S A ZERO
	RZ			;RET
	MOV	C,A		;OTHERWISE
	CALL	CONOUT		;PRINT IT
	INX	H		;INR H,L
	JMP	PMSG		;AND GET ANOTHER
;
;NXM ROUTINE FOR SYSTEM CRASHES
;
NXM:	POP	B		;B HAS PC OF CRASH (MAYBE)
	LXI	SP,MEMT		;RESET STACK
	PUSH	B		;SAVE PC
	LXI	H,NXMMSG	;"CRASH" MESSAGE
	CALL	PMSG		;PRINT IT
	POP	H		;GET BACK PC OF CRASH
	MOV	A,H		;GET HIGH BYTE
	CALL	HOUT		;PRINT IT IN ASCII
	MOV	A,L		;LOW BYTE
	CALL	HOUT		;L, TOO
	MVI	C,' '		;PUT OUT A SPACE
	CALL	CONOUT		;
	DCX	H		;GET ADDR OF RST 7 INST
	MOV	A,M		;GET IT (USUALLY 0FFH)
	CALL	HOUT		;PRINT IT
	JMP	WBOOT		;REBOOT THE SYSTEM
;
;HEX OUTPUT ROUTINE FOR ABOVE
;
HOUT:	PUSH	PSW		;SAVE
	RRC
	RRC
	RRC
	RRC
	CALL	NIBBLE
	POP	PSW
NIBBLE:	ANI	0FH
	CPI	10
	JM	NIBBL1
	ADI	7
NIBBL1:	ADI	'0'
	MOV	C,A
	JMP	CONOUT
;
; CBIOS MESSAGES
;
NRMSG:	DB	'NOT READY ',0
RNMSG:	DB	'RECORD NOT FOUND ',0
CRCMSG:	DB	'CRC ',0
LDMSG:	DB	'LOST DATA ',0
BSYMSG:	DB	'BUSY ',0
WPMSG:	DB	'PROTECT ',0
WFMSG:	DB	'FAULT ',0
ERRMSG:	DB	'ERROR',0DH,0AH,0
RDMSG:	DB	0DH,0AH,'READ ',0
WTMSG:	DB	0DH,0AH,'WRITE ',0
BTMSG:	DB	0DH,0AH,'BOOT ERROR',0DH,0AH,0
SKMSG:	DB	0DH,0AH,'SEEK ',0
SMSG:	DB	0DH,0AH,'48K CP/M RELEASE 1.4.0',0AH,0DH,0H
NXMMSG:	DB	0DH,0AH,'"CRASH" ',0
;
; ERROR COUNTS.  THESE LOCATIONS KEEP TRACK OF THE
; NUMBER OF ERRRS THAT OCCUR DURING READ OR WRITE
; OPERATIONS.  THEY ARE INITIALIZED ONLY
; WHEN A COLD-START IS PERFORMED BY THE BOOOTSTRAP.
;
RECNT:	DB	0		;READ ERROR COUNT
WECNT:	DB	0		;WRITE ERROR COUNT.
;
;NOTE:  AS THERE ARE ONLY 9 SECTORS AVAILABLE FOR THE
;CBIOS ON THE 2ND TRACK, THE LAST ADDRESS
;BEFORE THIS POINT SHOULD BE NO HIGHER THAN CBIOS + 047FH.
;THIS WILL NORMALLY BE XE7F (HEX).
;
LASTIN:	DS	1		;LAST CHAR INPUT FROM CONSOLE
LSTOUT:	DS	1		;LAST CHAR OUTPUT TO VDM-1
ERCNT:	DS	1		;ERROR COUNT FOR RETRIES.
SERCNT:	DS	1		;SEEK RETRY COUNTER.
TRK:	DS	1		;CURRENTLY SELECTED TRACK.
SECT:	DS	1		;CURRENTLY SELECTED SECTOR.
DISKNO:	DS	1		;CURRENT DISK NO.
DMAADD:	DS	2		;CURRENT READ/WRITE ADDRESS.
	END
