;	Support for CP/M 2.0 added 5 Sept 79 - J R Pierce

;HARD DISK VERSION 8-26-79
; BIOS FOR MICRO-2 COMPUTER
;
;THE IOBYTE IS IMPLEMENTED
;
;INTERRUPTS ARE NOT IMPLEMENTED
;
	maclib	diskdef
;
;THIS VERSION CONTAINS DISK DRIVERS FOR THE DIGITAL SYSTEMS
;FDC-3 CONTROLLER BOARD.  THIS BOARD CAN HANDLE DOUBLE DENSITY
;
;	NOTE :  MSIZE DETERMINES WHERE THIS CBIOS IS LOCATED
MSIZE	EQU	64	;CP/M VERSION MEMORY SIZE IN KILOBYTES
VERS	EQU	20
ndisks	equ	2
ram$top	equ	msize*1024
bios	equ	ram$top-6*256
bdos	equ	bios-0e00h
ccp	equ	bdos-0800h
warm$boot equ	ccp-0080h

	org	bios

;	WE WILL USE A SCRATCH AREA STARTING AT 40H
SCRAT	EQU	40H		;START OF SCRATCH AREA
TRACK	EQU	SCRAT		;CURRENT TRACK ON DRIVE 0
TRAK1	EQU	TRACK+1		;CURRENT TRACK ON DRIVE 1
TRAK2	EQU	TRAK1+1
TRAK3	EQU	TRAK2+1
SECTOR	EQU	TRAK3+1		;CURRENTLY SELECTED SECTOR
DMAAD	EQU	SECTOR+1		;CURRENT DMA ADDRESS
DISKNO	EQU	DMAAD+2		;CURRENT DISK NUMBER
DUMMY	EQU	DISKNO+1	;MUST BE 0 FOR DOUBLE ADD
ERRORS	EQU	DUMMY+1
PORT	EQU	4AH
PORTOUT	EQU	PORT+1
DENSITY	EQU	PORTOUT+1
;
IOBYTE	EQU	3
;
;SET UP INPUT OUTPUT PORTS
DATA	EQU	40H
STATUS	EQU	DATA+1
DATA1	EQU	48H
STATUS1	EQU	DATA1+1
DATA2	EQU	50H
STATUS2	EQU	DATA2+1
PARALLEL EQU	61H
;
;
;	JUMP VECTOR FOR INDIVIDUAL SUBROUTINES
	JMP	WARMBOOT	;COLD START
WBOTE:
	JMP	WBOOT		;WARM START
	JMP	CONST		;CONSOLE STATUS
	JMP	CONIN		;CONSOLE CHARACTER IN
	JMP	CONOUT		;CONSOLE CHARACTER OUT
	JMP	LIST		;LIST CHARACTER OUT
	JMP	PUNCH		;PUNCH CHARACTER OUT
	JMP	READER		;READER CHARACTER OUT
	JMP	HOME		;MOVE HEAD TO HOME POSITION
	JMP	SELDSK		;SELECT DISK
	JMP	SETTRK		;SET TRACK NUMBER
	JMP	SETSEC		;SET SECTOR NUMBER
	JMP	SETDMA		;SET DMA ADDRESS
	JMP	READ		;READ DISK
	JMP	WRITE		;WRITE DISK
	jmp	lstat		; list status routine
	jmp	sectran		; logical->physical sector mapping
;THE WARM BOOT ROUTINE EXPECTS TO FIND THE LOG ON MESSAGE HERE
;SINCE THERE WAS NOT ENOUGH ROOM IN IT
	DW	0D0AH		;CR,LF
	DB	MSIZE/10+'0',MSIZE MOD 10+'0'
	DB	'k CP/M Vers'
	DB	VERS/10+'0','.',VERS MOD 10+'0'
	DB	0
;
;	INDIVIDUAL SUBROUTINES TO PERFORM EACH FUNCTION
;
WBOOT:
;READ IN TRACK 0 SECTOR 2 WHICH WILL DO THE REST OF THE WARM START
;
	LXI	SP,80H		;USE SPACE BELOW BUFFER FOR STACK
	MVI	C,0		;SELECT DISK 0
	CALL	SELDSK
	CALL	HOME		;GO TO TRACK 00
;
;SET SINGLE DENSITY
	LDA	PORT
	ANI	0F7H
	STA	PORT
;SET DMA ADDRESS TO 80 BELOW START OF CCP
	LXI	B,WARMBOOT
	CALL	SETDMA
	MVI	C,2
	CALL	SETSEC
;NOW READ
	CALL	READ
	ORA	A	;ANY ERRORS?
	JNZ	WBOOT	;RETRY THE ENTIRE BOOT IF AN ERROR OCCURS
;
;
	JMP	WARMBOOT+3	;GO FINISH THE WARM START
;I/O HANDLERS
;
CONST:	CALL	CONS
	ORA	A
	RZ
	MVI	A,0FFH
	RET
;
CONS:	
	CALL	condsptch
	DW	TTYST
	DW	CRTST
	DW	BATST
	DW	UC1ST
;
CONIN:
	CALL	condsptch
	DW	TTYIN
	DW	CRTIN
	DW	BATIN
	DW	UC1IN
;
CONOUT:
	CALL	condsptch
	DW	TTYOUT
	DW	CRTOUT
	DW	BATOUT
	DW	UC1OUT
;
LIST:
	LDA	IOBYTE
	RLC! RLC
	CALL	IODSPRLC
	DW	TTYOUT
	DW	CRTOUT
	DW	LPTOUT
	DW	UL1OUT
lstat:			; Dummy Routine
	xra a ! ret

PUNCH:
	LDA	IOBYTE
	RRC! RRC! RRC
	CALL	IODISPATCH
	DW	TTYOUT
	DW	PTPOUT
	DW	UP1OUT
	DW	UP2OUT
;
READER:
	LDA	IOBYTE
	RRC
	CALL	IODISPATCH
	DW	TTYIN
	DW	PTRIN
	DW	UR1IN
	DW	UR2IN
;
condsptch:
	lda	io$byte
IODSPRLC:
	RLC
IODISPATCH:
	ANI	6H
	XTHL		;GET TABLE ADDRESS
	PUSH	D
	MOV	E,A
	MVI	D,0
	DAD	D
	MOV	A,M
	INX	H
	MOV	H,M
	MOV	L,A
	POP	D
	XTHL
	RET
;
;
PORT0ST:	;CONSOLE STATUS, RETURN 0FFH IF CHARACTER READY, 00H IF NOT
	IN	STATUS
	ANI	2
	RZ
	MVI	A,0FFH
	RET
;
;
PORT0IN:	;CONSOLE CHARACTER INTO REGISTER A
	CALL	PORT0ST
	JZ	PORT0IN
	IN	DATA
	ANI	7FH	;STRIP PARITY BIT
	RET
;
;
PORT0OUT: ;CONSOLE CHARACTER OUTPUT FROM REGISTER C
	in	status
	rrc
	jnc	port0out
	MOV	A,C
	OUT	DATA
	RET
;
;
PORT1ST:
	IN	STATUS1
	ANI	2
	RZ
	MVI	A,0FFH
	RET
;
PORT1IN:
	CALL	PORT1ST
	JZ	PORT1IN
	IN	DATA1
	ANI	7FH	;STRIP PARITY BIT
	RET
;
PORT1OUT:	;LIST CHARACTER FROM REGISTER C
	IN	STATUS1
	RRC
	JNC	PORT1OUT
	MOV	A,C	;CHARACTER TO REGISTER A
	OUT	DATA1
	RET		;NULL SUBROUTINE
;
PORT2ST:
	IN	STATUS2
	ANI	2
	RZ
	MVI	A,0FFH
	RET
;
PORT2IN:
	CALL	PORT2ST
	JZ	PORT2IN
	IN	DATA2
	ANI	7FH
	RET
;
PORT2OUT:
	IN	STATUS2
	RRC
	JNC	PORT2OUT
	MOV	A,C
	OUT	DATA2
	RET
;
;
PORTPOUT:
	IN	PARALLEL
	ANI	80H
	JNZ	PORTPOUT
	MOV	A,C
OUTIT:
	ORI	80H
	OUT	PARALLEL
	ANI	7FH
	OUT	PARALLEL
	ORI	80H
	OUT	PARALLEL
	RET
;
;CP/M PHYSICAL DEVICE TO MICRO2 PHYSICAL DEVICE MAP
;
TTYST	EQU	PORT0ST
TTYIN	EQU	PORT0IN
TTYOUT	EQU	PORT0OUT
CRTST	EQU	PORT1ST
CRTIN	EQU	PORT1IN
CRTOUT	EQU	PORT1OUT
BATST	EQU	PORT2ST
BATIN	EQU	PORT2IN
BATOUT	EQU	PORT2OUT
UC1ST	EQU	PORT0ST
UC1IN	EQU	PORT0IN
UC1OUT	EQU	PORT0OUT
LPTOUT	EQU	PORTPOUT
UL1OUT	EQU	PORT2OUT
PTPOUT	EQU	PORT2OUT
PTRIN	EQU	PORT2IN
UP1OUT	EQU	PORT0OUT
UP2OUT	EQU	PORT1OUT
UR1IN	EQU	PORT0IN
UR2IN	EQU	PORT1IN
;
;
COMAND1	EQU	80H
STAT	EQU	80H
HADDR	EQU	81H
LADDR	EQU	82H
COMAND2	EQU	83H
;
;
;	I/O DRIVERS FOR THE DISK FOLLOW
;
HOME:	;MOVE TO THE TRACK O0 POSITION OF CURRENT DRIVE
	CALL	HEADLOAD
; H,L POINT TO WORD WITH TRACK FOR SELECTED DISK
HOMEL:
	MVI	M,00	;SET CURRENT TRACK PTR BACK TO 0
	IN	STAT	;READ FDC STATUS
	ANI	4	;TEST TRACK 0 BIT
	RZ		;RETURN IF AT 0
	STC		;DIRECTION=OUT
	CALL	STEP	;STEP ONE TRACK
	JMP	HOMEL	;LOOP
;
SELDSK:	;SELECT DISK GIVEN BY REGISTER C
;MAKE SURE DUMMY IS 0 (FOR USE IN DOUBLE ADD TO H,L)
	cpi ndisks ! jnc bad$drive
	XRA	A
	STA	DUMMY
	MOV	A,C
	ANI	07H	;GET ONLY DISK SELECT BITS
	STA	DISKNO
	MOV	C,A
	MOV	B,A
;GET DENSITY OF SELECTED DRIVE
;B HAS DRIVE NUMBER FROM 0-7
	LDA	DENSITY
;DENSITY BIT 0= DENSITY FOR DRIVE A, BIT 1=DRIVE B ETC
;A 1 MEANS DOUBLE DENSITY
	
GETDR:
	DCR	B
	RRC
	JP	GETDR	;NOT AT PROPER BIT YET
	JC	SETDOB
SETSING:
	LXI	D,SINGTAB
	lxi	h,stagger
	JMP	OVERBOTH
SETDOB:
	LXI	D,DOBTAB
	lxi	h,0	; no stagger table
	MOV	A,C
;SET THE CHANGE DENSITY BIT
	ORI	08
	MOV	C,A
OVERBOTH:

	lda port ! ani 0f0h ! ora c ! sta port

	push	h	; save stagger table address
	lhld	diskno
	dad h ! dad h ! dad h ! dad h
	lxi	b,dpbase+10
	dad	b	; points HL to right disk parameter table 
	mov m,e ! inx h ! mov m,d	; save diskdef address
	lxi	b,-10	; point back to tran table address
	dad	b
	pop	d	; get current tran table
	mov m,d ! dcx h ! mov m,e  ; store its address
  ; HL now points to appropriate drive parameter block
	RET

bad$drive:	; here if invalid drive code
	lxi h,0 ! ret	; zero means select error

SETTRK:	;SET TRACK GIVEN BY REGISTER C
	CALL	HEADLOAD
;H,L REFERENCE CORRECT TRACK INDICATOR ACCORDING TO
;SELECTED DISK
	MOV	A,C	;DESIRED TRACK
	CMP	M
	RZ		;WE ARE ALREADY ON THE TRACK
SETTKX:
	CALL	STEP	;STEP TRACK-CARRY HAS DIRECTION
			;STEP WILL UPDATE TRACK INDICATOR
	MOV	A,C
	CMP	M	;ARE WE WHERE WE WANT TO BE
	JNZ	SETTKX	;NOT YET
;HAVE STEPPED ENOUGH
SEEKRT:
;DELAY 10 MSEC FOR FINAL STEP TIME AND HEAD SETTLE TIME
;THE DELAY ROUTINE DELAYS .5 MILLISECOND
	MVI	A,20D
	CALL	DELAY
	RET		;END OF SETTRK ROUTINE
;
DELAY:	;ROUTINE TO DELAY C(A)  .5 MILLISECONDS
	PUSH	B
DELAY2:
	MVI	C,086H	;ADJUST FOR .5 MSEC LOOP DELAY
			;THIS IS THE VALUE FOR OUR IMSAI
LDXA:
	DCR	C
	JNZ	LDXA	;LOOP 1 MSEC
	DCR	A
	JNZ	DELAY2
	POP	B
	RET		;END OF DELAY ROUTINE
;
sectran:
	mov h,b ! mov l,c ! inx h	; in case we aren't using translation
	mov a,d ! ora e ! rz		; we return logical+1
	xchg ! dad b ! mov l,m ! mvi h,0 ; else fetch physical
	ret	; back to bdos

SETSEC:	;SET SECTOR GIVEN BY REGISTER C
	MOV	A,C
	STA	SECTOR
	RET
;
SETDMA:	;SET DMA ADDRESS GIVEN BY REGISTERS B AND C
	MOV	L,C	;LOW ORDER ADDRESS
	MOV	H,B	;HIGH ORDER ADDRESS
	SHLD	DMAAD	;SAVE THE ADDRESS
	RET
;
;
READ:	;PERFORM READ OPERATION.
	;THIS IS SIMILAR TO WRITE, SO SET UP READ COMMAND AND USE
	;COMMON CODE IN WRITE
	MVI	B,040H	;SET READ FLAG
	JMP	WAITIO	;TO PERFORM THE ACTUAL I/O
;
WRITE:	;PERFORM A WRITE OPERATION
	MVI	B,080H	;SET WRITE COMMAND
;
WAITIO:
;ENTER HERE FROM READ AND WRITE TO PERFORM THE ACTUAL I/O 
;OPERATION.  RETURN A 00H IN REGISTER A IF THE OPERATION COMPLETES
;PROPERLY, AND 01H IF AN ERROR OCCURS DURING THE READ OR WRITE
;
;IN THIS CASE, WE HAVE SAVED THE DISK NUMBER IN 'DISKNO' 
;			THE TRACK NUMBER IN 'TRACK' 
;			THE SECTOR NUMBER IN 'SECTOR' 
;			THE DMA ADDRESS IN 'DMAAD' 
			;B STILL HAS R/W FLAG
	MVI	A,10D	;SET ERROR COUNT
	STA	ERRORS	;RETRY SOME FAILURES 10 TIMES
			;BEFORE GIVING UP
TRYAGN:
	PUSH	B
	CALL	HEADLOAD
;H,L POINT TO TRACK BYTE FOR SELECTED DISK
	POP	B
	MOV	C,M
; DECIDE WHETHER TO ALLOW DISK WRITE PROCOMPENSTATION
	MVI	A,39D	;PRECOMP SHOULD BE INHIBITED ON TRACKS 
			;0-39
	CMP	C
	JC	ALLOWIT
;INHIBIT PRECOMP
	MVI	A,10H
	ORA	B
	MOV	B,A	;GOES OUT ON THE SAME PORT AS READ/WRITE
ALLOWIT:
	LHLD	DMAAD	;GET BUFFER ADDRESS
	PUSH	B	;B HAS R/W CODE   C HAS TRACK
	DCX	H	;SAVE AND REPLACE 3 BYTES BELOW
			;BUF WITH TRACK,SECTOR,ADDRESS MARK
	MOV	E,M
;FIGURE CORRECT ADDRESS MARK

	LDA	PORT
	ANI	08H
	MVI	A,0FBH
	JZ	SIN
	ANI	0FH	;WAS DOUBLE 
			;0BH IS DOUBLE DENSITY 
			;0FBH IS SINGLE DENSITY
SIN:
	MOV	M,A
;FILL IN SECTOR
	DCX	H
	MOV	D,M
	LDA	SECTOR	;NOTE THAT INVALID SECTOR NUMBER
			;WILL RESULT IN HEAD UNLOADED
			;ERROR, SO DONT CHECK
	MOV	M,A
;FILL IN TRACK
	DCX	H
	POP	B
	MOV	A,C
	MOV	C,M
	MOV	M,A
	MOV	A,H	;SET UP FDC DMA ADDRESS
	OUT	HADDR	;HIGH BYTE
	MOV	A,L
	OUT	LADDR	;LOW BYTE
	MOV	A,B	;GET R/W FLAG
	OUT	COMAND1	;START DISK READ/WRITE
RWWAIT:	IN	STAT	;READ FDC STATUS
	ANI	088H	;TEST FOR HEAD UNLOAD OR IOF
	JZ	RWWAIT
	MOV	M,C	;RESTORE 3 BYTES BELOW BUF
	INX	H
	MOV	M,D
	INX	H
	MOV	M,E
	IN	STAT	;TEST FOR ERRORS
	ANI	0F0H
	RZ		;A WILL BE 0 IF NO ERRORS
ERRTN:
;COME HERE ON ERROR FROM DISK
	PUSH	PSW	;SAVE ERROR CONDITION
;CHECK FOR 10 ERRORS
	LXI	H,ERRORS
	DCR	M
	JNZ	REDO	;NOT TEN YET.  DO A RETRY
;WE HAVE TOO MANY ERRORS. PRINT OUT HEX NUMBER FOR LAST
;RECEIVED ERROR TYPE. CPM WILL PRINT PERM ERROR MESSAGE.
	POP	PSW	;GET CODE
	RRC
	RRC
	RRC
	RRC
;MAKE IT ASCII
	ORI	030H
	STA	BDOS+0A4H
;SET ERROR RETURN FOR OPERATING SYSTEM
	MVI	A,1
	RET
REDO:
;B STILL HAS READ/WRITE FLAG
	POP	PSW	;GET ERROR CODE
	ANI	0E0H	;RETRY IF NOT TRACK ERROR
	JNZ	TRYAGN	;
;WAS A TRACK ERROR SO NEED TO RESEEK
	PUSH	B	;SAVE	READ/WRITE INDICATOR
;FIGURE OUT THE DESIRED TRACK
	LXI	D,TRACK
	LHLD	DISKNO	;SELECTED DISK
	DAD	D	;POINT TO CORRECT TRACK INDICATOR
	MOV	A,M	;DESIRED TRACK
	PUSH	PSW	;SAVE IT
	CALL	HOME
	POP	PSW
	MOV	C,A
	CALL	SETTRK
	POP	B	;GET READ/WRITE INDICATOR
	JMP	TRYAGN
;
;
;
STEP:				;STEP HEAD OUT TOWARDS ZERO
				;IF CARRY IS SET; ELSE
				;STEP IN
; H,L POINT TO CORRECT TRACK INDICATOR WORD
	JC	OUTX
	INR	M	;INCREMENT CURRENT TRACK BYTE
	MVI	A,04H	;SET DIRECTION = IN
DOSTEP:
	ORI	2
	OUT	COMAND1	;PULSE STEP BIT
	ANI	0FDH
	OUT	COMAND1	;TURN OFF PULSE
;THE FDC-2 HAD A STEPP READY LINE. THE FDC-3 RELIES ON
;SOFTWARE TIME OUT
	MVI	A,16D	;WAIT FOR STEP READY
;DELAY ROUTINE DELAYS FOR .5 MSEC TIMES THE CONTENTS OF REG A
	CALL	DELAY
	RET
;
OUTX:
	DCR	M	;UPDATE TRACK BYTE
	XRA	A
	JMP	DOSTEP
;
HEADLOAD:
;SELECT AND LOAD THE HEAD ON THE CORRECT DRIVE
	LXI	H,PORTOUT	;OLD SLECT INFO
	MOV	B,M
	DCX	H	;NEW SELECT INFO
	MOV	A,M
	INX	H
	MOV	M,A
	OUT	COMAND2	;SELECT THE DRIVE
;SET UP H.L TO POINT TO TRACK BYTE FOR SELECTED DISK
	LXI	D,TRACK
	LHLD	DISKNO
	DAD	D
;NOW CHECK FOR NEEDING A 35 MS DELAY
;IF WE HAVE CHANGED DRIVES OR IF THE HEAD IS UNLOADED
;WE NEED0TO WAIT 35 MS FOR HEAD SETTLE
	CMP	B	;ARE WE ON THE SAME DRIVE AS BEFORE
	JNZ	NEEDDLY
;WE ARE ON THE SAME DRIVE
;IS THE HEAD LOADED?
	IN	STAT
	ANI	80H
	RZ		;ALREADY LOADED
NEEDDLY:
	XRA	A
	OUT	COMAND1	;LOAD THE HEAD
;THE DELAY ROUTINE DELAYS FOR .5 MSEC
	MVI	A,70D
	CALL	DELAY
	RET
;
;	disks	2
dpbase:
dpe0:	dw	$-$, 0
	dw	0, 0
	dw	dirbuf, $-$
	dw	csv0, alv0
dpe1:	dw	$-$, 0
	dw	0, 0
	dw	dirbuf, $-$
	dw	csv1, alv1

dobtab:
;	diskdef	0,1,58,,2048,256,128,128,2
	dw	58
	db	4,15,1
	dw	255,127
	db	192,0
	dw	32,2


singtab:
;	diskdef	1,1,26,6,1024,243,64,64,2
	dw	26
	db	3,7,0
	dw	242,63
	db	192,0
	dw	16,2

stagger:		; Standard CP/M Stagger Table (Skew 6)
	db	1,7,13,19,25
	db	5,11,17,23
	db	3,9,15,21
	db	2,8,14,20,26
	db	6,12,18,24
	db	4,10,16,22

;	endef

begdat:
dirbuf:
	ds	128
alv0:
	ds	32
csv0:
	ds	32
alv1:	
	ds	32
csv1:
	ds	32
enddat	equ	$

	end
