;SKELETAL CBIOS FOR FIRST LEVEL OF CP/M ALTERATION
;
;NOTE : MSIZE DETERMINES WHERE THIS CBIOS IS LOCATED
MSIZE	EQU	64	;CP/M VERSION MEMORY SIZE IN KILOBYTES
VERS	EQU	14	;CPM VERSION NUMBER
PATCH	EQU	MSIZE*1024-2*256	;START OF THE CBIOS PATCH
;
;
	ORG	PATCH	;0RGIN OF THIS PROGRAM
IOBYTE	EQU	3	;ADDRESS OF I/O BYTE.
CBASE	EQU	(MSIZE-16)*1024    ;BIAS FOR SYSTEMS LARGER THAN 16K
CPMB	EQU	CBASE+2900H        ;BASE OF CP/M (= BASE OF CCP)
BDOS	EQU	CBASE+3106H        ;BASE OF RESIDENT PORTION OF CP/M
CPML	EQU	$-CPMB             ;LENGTH OF THE CPM SYSTEM IN BYTES
NSECTS	EQU	CPML/128  ;NUMBER OF SECTORS TO LOAD ON WARM START
LBIAS	EQU	980H-CPMB ;LOADER BIAS VALUE USED IN SYSGEN
OFFSET	EQU	2         ;NUMBER OF DISK TRACKS USED BY CP/M
BUFF	EQU	80H       ;DEFAULT BUFFER ADDRESS
CR	EQU	0DH       ;CARRIAGE RETURN
LF	EQU	0AH       ;LINE FEED
;
;JUMP  VECTOR FOR INDIVIDUAL SUBROUTINES
	JMP	BOOT	;COLD START
WBOOTE:
	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	list$st	;list status
;
SIGNON:     ;SIGNON MESSAGE: XXK CP/M VERS Y.Y
	DB         CR, LF, LF
	DB         MSIZE/10+'0',MSIZE MOD 10 + '0'
	DB         '.K CP/M VERS '
	DB         VERS/10+'0','.',VERS MOD 10+'0'
	DB         CR,LF,0

;            TRACK   =  LAST SELECTED TRACK
;            SECTOR  =  LAST SELECTED SECTOR
;            DMAAD   =  LAST SELECTED DMA ADDRESS
;            DISKNO  =  LAST SELECTED DISK NUMBER
;(NOTE THAT ALL ARE BYTE VALUES EXCEPT FOR DMAAD)
;
;
SCRAT	EQU	$	;BASE OF SCRATCH AREA
TRACK	db	0	;CURRENTLY SELECTED TRACK
SECTOR	db	0	;CURRERILY SELECTED SECTOR
DMAAD	dw	0	;CURRENT DMA ADDRESS
DISKNO	db	0	;CURRENT DISK NUMBER
bt$cmd	db	0	;disk command (read or write)	
;
;
;INDIVIDUAL SUBROUTINES TO PERFORM EACH FUNCTION

BOOT:        ;SIMPLEST CASE IS TO JUST PERFORM PARAMETER INITIALIZATION
	LXI	SP,80H
	LXI	H,SIGNON
	CALL	PRMSG	;PRINT MESSAGE
	XRA	A	;CLEAR ACCUMULATOR
	STA	IOBYTE	;CLEAR I/O BYTE.
	STA	DISKNO	;SELECT DISK 0 ON ENTRY
	JMP	GOCPM	;INITIALIZE AND GO TO CP/M
;
WBOOT:       ;SIMPLEST CASE IS TO READ THE DISK UNTIL ALL SECTORS LOADED
	LXI         SP,80H              ;USE SPACE BELOW BUFFER FOR STACK
	mvi         C,0                 ;SELECT DISK 0
	CALL        SELDSK
	CALL        HOME                ;GO TO TRACK 00
;
	MVI         B,NSECTS  ;B COUNTS THE NUMBER OF SECTORS TO LOAD
	MVI         C,0                 ;C HAS THE CURRENT TRACK NUMBER
	MVI         D,2                 ;D HAS THE NEXT SECTOR TO READ
;NOTE THAT  WE BEGIN BY READING TRACK 0, SECTOR 2 SINCE SECTOR 1
;CONTAINS THE COLD START LOADER, WHICH IS SKIPPED IN A WARM START
	LXI         H,CPMB              ;BASE OF CP/M (INITIAL LOAD POINT)

LOAD1:       ;LOAD ONE MORE SECTOR
	PUSH        B         ;SAVE SECTOR COUNT, CURRENT TRACK
	PUSH        D         ;SAVE NEXT SECTOR TO READ
	PUSH        H         ;SAVE DMA ADDRESS
	MOV         C,D       ;GET SECTOR ADDRESS TO REGISTER C
	CALL        SETSEC    ;SET SECTOR ADDRESS FROM REGISTER C
	POP         B         ;RECALL DMA ADDRESS TO B,C
	PUSH        B         ;REPLACE ON STACK FOR LATER RECALL
	CALL        SETDMA    ;SET DMA ADDRESS FROM B,C
;
;DRIVE SET TO 0, TRACK SET, SECTOR SET, DMA ADDRESS SET
	CALL        READ
	CPI         00H       ;ANY ERRORS?
	JNZ         WBOOT     ;RETRY THE ENTIRE BOOT IF AN ERROR OCCURS
;
;NO ERROR, MOVE TO NEXT SECTOR
	POP         H         ;RECALL DMA ADDRESS
	LXI         D,128     ;DMA=DMA+128
	DAD         D         ;NEW DMA ADDRESS IS IN H,L
	POP         D         ;RECALL SECTOR ADDRESS
	POP         B         ;RECALL NUMBER OF SECTORS REMAINING, AND CURRENT TRK
	DCR         B         ;SECTORS=SECTORS-1
	JZ          GOCPM     ;TPANSFER TO CP/M IF ALL HAVE BEEN LOADED
;
;MORE SECTORS REMAIN  TO LOAD, CHECK FOR TRACK CHANGE
	INR         D
	MOV         A,D       ;SECTOR=27?, IF SO, CHANGE TRACKS
	CPI         27
	JC          LOAD1     ;CARRY GENERATED IF SECTOR<27
;
;END OF CURRENT TRACK, GO TO NEXT TRACK
	MVI         D,1       ;BEGIN WITH FIRST SECTOR OF NEXT TRACK
	INR         C         ;TRACK=TRACK+1
;
;SAVE REGISTER STATE, AND CHANGE TRACKS
	PUSH        B
 	PUSH        D
	PUSH        H
	CALL        SETTRK    ;TRACK ADDRESS SET FROM REGISTER C
	POP         H
	POP         D
	POP         B
	JMP         LOAD1     ;FOR ANOTHER SECTOR
;
;END OF LOAD OPERATION, SET PARAMETERS AND GO TO CP/M
GOCPM:
	MVI         A,0C3H    ;C3 IS A JMP INSTRUCTION
	STA         0         ;FOR JMP TO WBOOT
	LXI         H,WBOOTE  ;WBOOT ENTRY POINT
	SHLD        1         ;SET ADDRESS FIELD FOR JMP AT 0
;
	STA         5         ;FOR JMP TO BDOS
	LXI         H,BDOS    ;BDOS ENTRY POINT
	SHLD        6         ;ADDRESS FIELD OF JUMP AT 5 TO BDOS
;
	LXI         B,80H     ;DEFAULT DM ADDRESS IS 80H
	CALL        SETDMA
;
;	EI                    ;ENABLE THE INTERRUPT SYSTEM
;FUTURE VERSIONS OF CCP WILL SELECT THE DISK GIVEN BY REGISTER
;C UPON ENTRY, HENCE ZERO IT IN THIS VERSION OF THE BIOS FOR
;FUTURE COMPATIBILITY.
	lda	DISKNO	;SELECT DISK 
	mov	c,a	;put diskno in c
	JMP	CPMB	;GO TO CP/M FOR FURTHER PROCESSING
;
;
;SIMPLE I/0 HANDLERS. (MUST BE FILLED IN BY USER)
;IN EACH CASE, THE ENTRY POINT IS PROVIDED, WITH SPACE RESERVED
;TO INSERT YOUR OWN CODE

;define console io ports

cni$sp	equ	7dh
cni$sb	equ	02h
cni$si	equ	00h
cni$dp	equ	7ch

cno$sp	equ	7dh
cno$sb	equ	01h
cno$si	equ	00h
cno$dp	equ	7ch

io$rdy	equ	0FFh	;ready
asc$msk	equ	7Fh	;ascii mask

cntl$z	equ	1Ah
;
CONST:       ;CONSOLE STATUS,RETURN 0FFH IF CHARACTER READY, 00H IF NOT
cns$ck	in	cni$sp		;in status
	xri	cni$si		;adjust polarity
	ani	cni$sb		;mask status bit
	rz			;return zero if not ready
	mvi	a,io$rdy	;set ready
	ret			;return
;
CONIN:       ;CONSOLE CHARACTER INTO REGISTER A
cns$in	call	cns$ck		;check status
	jz	cns$in		;repeat untill ready
	in	cni$dp		;in character
	ani	asc$msk		;mask to 7 bit ascii
	ret			;return
;
CONOUT:      ;CONSOLE CHARACTER OUTPUT FROM REGISTER C
cns$ot	in	cno$sp		;in status
	xri	cno$si		;adjust polarity
	ani	cno$sb		;mask status bit
	jz	cns$ot		;repeat untill ready
	mov	a,c		;get character in a
	ani	asc$msk		;mask to 7 bit ascii
	out	cno$dp		;out character
	ret			;return
;
;printer

lst$dp	equ	7eH		;list data port
lst$st	equ	7fH		;list status port
lst$ctl	equ	lst$st		;control port
lst$msk	equ	00000001B	;status mask (ORPLY)
ctl$msk	equ	00000001b	;control mask (OSTB)
mem$msk	equ	00000010b	;sbc mem mask 10=out 00=in

list$st	in	lst$st		;
	ani	lst$msk		;
	rz			;not ready
	mvi	a,0ffh		;ready
	ret

LIST:       ;LIST CHARACTER FROM REGISTER C
list$ck	call	list$st		;get status
	jz	list$ck		;wait for ready
	mov	a,c		;get char from c
	out	lst$dp		;out char to data port
	mvi	a,ctl$msk	;strobe data
	out	lst$ctl		;strobe
	ret			;return
;
PUNCH:       ;PUNCH CHARACTER FROM REGISTER C
	MOV         A,C       ;CHARACTER TO REGISTER A
	RET                   ;NULL SUBROUTINE
;
READER: 	;READ CHARACTER INTO REGISTER A FROM READER DEVICE
	MVI         A,1AH     ;ENTER END OF FILE FOR NOW (REPLACE LATER)
	ANI         7FH       ;REMEMBER TO STRIP PARITY BIT
	RET
;
;
; I/0 DRIVERS FOR THE DISK FOLLOW
; FOR NOW, WE WILL SIMPLY STORE THE PARAMETERS AWAY FOR USE
; IN THE READ AND WRITE SUBROUTINES
;
HOME:        ;MOVE TO THE TRACK 00 POSITION OF CUPRENT DRIVE
;TPANSLATE  THIS CALL INTO A SETTRK CALL WITH PARAMETER 00
	MVI         C,0       ;SELECT TRACK 0
	CALL        SETTRK
	RET         ;WE WILL MOVE TO 00 ON FIRST READ/WRITE
;
SELDSK:      ;SELECT DISK GIVEN BY REGISTER C
	MOV         A,C
	STA         DISKNO
	RET
;
SETTRK:          ;SET TRACK GIVEN BY REGISTER C
	MOV         A,C
	STA         TRACK
	RET
;
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

; added for z80 emulator
dsk$cmd 	equ 	0D0h 
dsk$sts 	equ 	0D0h 
dsk$drv		equ	0D1h ;drv
dsk$trkL	equ	0D2h ;trkLow
dsk$trkH	equ	0D3h ;trkHigh
dsk$secL	equ	0D4h ;secLow
dsk$secH	equ	0D5h ;secHigh
dsk$dmaL	equ	0D6h ;dmaLow
dsk$dmaH	equ	0D7h ;dmaHigh
dsk$sptL	equ	0D8h ;sptLow
dsk$sptH	equ	0D9h ;sptHigh
dsk$dmaB	equ	0dah ;Disk DMA Bank
dsk$order	equ	0dbh ;track order

cmd$read	equ	1
cmd$write	equ	2

READ:	;PERFORM A READ OPERATION 
diskrd:
	mvi	a,cmd$read
	sta	bt$cmd
	call	dsk$ex		;perform operation
	jnz	dsk$er		;exit error
	jmp	dsk$ok		;normal exit
                  ;
WRITE:	;PERFORM A WRITE OPERATION
diskwr:	
	mvi	a,cmd$write
	sta	bt$cmd
	call	dsk$ex		;perform operation
	jz	dsk$ok		;normal exit if write ok
	jmp	dsk$er		;exit error
;
;ENTER HERE FROM READ AND WRITE TO PERFORM THE ACTUAL I/0
;OPERATION. 
; IN THIS CASE, WE HAVE SAVED THE DISK NUMBER IN 'DISKNO' (0,1)
;                                THE TRACK NUMBER IN 'TRACK' (0-76)
;                                THE SECTOR NUMBER IN 'SECTOR' (1-26)
;                                THE DMA ADDRESS IN 'DMAAD' (0-65535)
WAITIO:      
dsk$ex
	lda	DMAAD
	out	dsk$dmaL
	lda	DMAAD+1	
	out	dsk$dmaH
	lda	DISKNO
	out	dsk$drv
	xra	a
	out	dsk$trkH
	out	dsk$secH
	lda	TRACK
	out	dsk$trkL
	lda	SECTOR
	out	dsk$secL
	lda	bt$cmd
	out	dsk$cmd
	in	dsk$sts
	ana	a		;set ccr
	ret

;RETURN A 00H IN REGISTER A IF THE OPERATION COMPLETES
;PROPERLY, AND 01H IF AN ERROR OCCURS DURING THE READ OR WRITE
;
dsk$ok:	
	xra	a		;clear a to indicate OK
	ret
dsk$er:	
	mvi	a,01h		;indicate ERROR
	ora	a
	ret

;UTILITY SUBROUTINES
PRMSG:       ;PRINT MESSAGE AT H,L TO 0
	MOV         A,M
	ORA         A         ;ZERO?
	RZ
;MORE TO PRINT
	PUSH        H
	MOV         C,A
	CALL        CONOUT
	POP         H
	INX         H
	JMP         PRMSG

EOB	equ	$
;ALL REMAINING SPACE FROM $ THRU MSIZE*1024-1 IS AVAILABLE:
LEFT	EQU	(MSIZE*1024-1)-$	;SPACE REMAINING IN CBIOS
	END




