;********************************************************
;*							*
;*	CUSTOM BIOS FOR CP/M VERSION 1.4 and 2.2 	*
;*			           			*
;*	Mark Stieglitz                   		*
;*      Gary Kaufman					*	
;********************************************************
     .z80
     title Big Board Z80 CBIOS 
     name ('CBIOS')
;
parallel  EQU  0	 ;1 for parallel driver, 0 for serial
handsh	  EQU  1	 ;1 for serial handshaking, 0 for always RTS
cpm14     EQU  0         ;1 for version 1.4 (EQU 0 for 2.2)
sovers    EQU  'X6'      ;version identifier for signon
;
          if1
          if CPM14
          .PRINTX /Assembling CBIOS for CPM V1.4/
          else
          .PRINTX /Assembling CBIOS for CPM V2.2/
          endif
	  if parallel
	  .PRINTX /With Parallel List Device/
	  else
	  .PRINTX /With Serial List Device/
	  if handsh
	  .PRINTX /    With Handshaking/
	  else
	  .PRINTX /    No Handshaking/
	  endif
	  endif
          endif
;
;
;Derived heavily from BIOS by Russel Smith
;Modifications:
;X0  1-8-82    Syntax Changes for Microsoft M80 Assembler
;              Correct WBOOT to retain selected drive
;              Add switch for CP/M 1.4 compatibility
;X1 1-11-82    Add list dev driver (pioa w/ints)
;X2 1-17-82    Mod to defer drive select until actual I/O
;X3 2-09-82    Add conditional assy to ORG address for 2.2
;X4 2-18-82    Add call to SELNOW in HOME to fix WBOOT problem
;              if called when not already on A:.
;x5 8-30-82    Added option for serial driver with handshaking  GEK
;
          PAGE 51
MSIZE	EQU	60		;MEMORY CAPACITY IN KBYTES
MONITR	EQU	0F000H		;BASE OF SYSTEM MONITOR
	
;
;	CP/M REFERENCE CONSTANTS
;
        if      CPM14
BIAS    EQU     (MSIZE-20)*1024
CCP     EQU     3500H+BIAS
BDOS    EQU     CCP+806H
CBIOS   EQU     CCP+1500H
        else
;
; *** COMMENT OUT THE APPROPRIATE ONE OF THE NEXT TWO LINES ***
BIAS	EQU     (MSIZE-20)*1024-200H ;E800H  BIOS (Digital Research)
;BIAS	EQU	(MSIZE-20)*1024      ;EA00H  BIOS (Home Grown)
;
CCP	EQU	3400H+BIAS
BDOS	EQU	CCP+806H
CBIOS	EQU	CCP+1600H
        endif
;
;	
;I/O Constants
LF	EQU	0AH		;LINE FEED
CR	EQU	0DH		;CARRIAGE RETURN
LSTDAT  EQU     8               ;List device, data
LSTAC   EQU     9               ;List device, control
LSTINT  EQU     1Ch             ;Vector Offset
LSVLOC  EQU     0FF00h+LSTINT   ;Hardware vector location
;
        page 
        ASEG
        if CPM14
        ORG  1E80h              ;sysgen load address
        else
        ORG  1F80h
        endif

       .PHASE CBIOS
;
	JP	BOOT		;STANDARD JUMP TABLE TO
        JP	WBOOT		;THE SUBROUTINES OF CBIOS
        JP	MONITR+6        ;Monitor console status rtn.
IVECTR:	JP	MONITR+9        ;Monitor console input rtn.
OVECTR:	JP	CONOUT
        JP	LSTOUT		;LIST DEVICE VECTOR
	JP	CONOUT		;PUNCH DEVICE VECTOR
	JP	MONITR+9	;READER DEVICE VECTOR
	JP	HOME
	JP	SELECT
	JP	SEEK
	JP	SETSEC
	JP	SETPTR
	JP	READ
	JP	WRITE
	JP	LSTST		;LIST DEVICE STATUS VECTOR
        JP	TRANS
;
;
;
BOOT:	XOR	A
	LD	(0003H),A	;RESET IOBYTE TO ZEROS
	LD      (4),A           ;Initialize CP/M unit to A:
        LD	HL,SIGNON
	CALL	PMSG		;PRINT SIGNON MESSAGE
;
	if	parallel
        LD      A,0FFh          ;Initialize list device
        LD      (LSTRDY),A      ;  Set flag to "ready"
        LD      A,0Fh           ;  Select output mode for PIO
        OUT     (LSTAC),A
        LD      A,LSTINT        ;  Set vector in PIO
        OUT     (LSTAC),A
        LD      BC,LSTIH        ;  Store handler address
        LD      (LSVLOC),BC     ;    at vector location
        LD      A,87h           ;  Enable PIO interrupts
        OUT     (LSTAC),A       
	else
	LD	A,07		;SET BAUD RATE FOR SIO/B
	OUT	(0CH),A		; 7 is for 1200 baud
	LD	A,04		;and init SIO/B
	OUT	(07),A
	LD	A,4CH
	OUT	(07),A
	LD	A,05
	OUT	(07),A
	LD	A,0EAH
	OUT	(07),A
	JR	GOCPM
	endif
;
WBOOT:	LD	SP,STACK
	LD	C,0
	CALL	SELECT		;SELECT UNIT 0
	CALL	HOME		;SEEK TRACK ZERO
	LD	HL,CCP          ;Start from base of CP/M
	LD	BC,0D02H
	CALL	RDLOOP		;READ EVEN SECTORS ON TRK 0
	LD	HL,CCP+80h
	LD	BC,0C03H
       	CALL	RDLOOP		;READ ODD SECTORS ON TRK 0
	LD	C,1
	CALL	SEEK		;SEEK TO TRACK 1
	JR	NZ,BOMB
	LD	HL,CCP+0C80h
	LD	BC,0A01H	
	CALL	RDLOOP		;READ ODD SECTORS ON TRK 1
	LD	HL,CCP+0D00h
	LD	BC,0902H
	CALL	RDLOOP		;READ EVEN SECTORS ON TRK 1
;
GOCPM:	LD	A,0C3H		;STORE JUMP VECTORS IN RAM
	LD	(00H),A
	LD	HL,CBIOS+3	;JUMP TO CBIOS WARM BOOT AT 00H
	LD	(01H),HL
	LD	(05H),A
	LD	HL,BDOS		;JUMP TO BDOS GOES AT 05H
	LD	(06H),HL
	LD	(38H),A
	LD	HL,MONITR	;JUMP TO MONTR GOES AT 38H
	LD	(39H),HL
	LD	BC,0080H
	CALL	SETPTR		;MAKE DISK BUFFER=0080H
	LD	A,(4)           ;Remind CP/M of previously
        LD      C,A             ;  selected drive
	JP	CCP             
;
;Entry: B=Sector Count, C=Starting Sector, HL=Start Address
;Reads every other sector (1 sector interleve)
RDLOOP:	LD	(POINTR),HL	;STORE ADDR. PASSED IN HL
	LD	A,C
	LD	(SECTOR),A	;STORE SECT# PASSED IN C
	PUSH	HL
	PUSH	BC
	CALL	READ		;READ THE SPECIFIED SECTOR
	POP	BC
	POP	HL
	JR	NZ,BOMB
	INC	H		;BUMP LOAD ADDRESS BY 256
	INC	C
	INC	C		;BUMP SECTOR# BY 2
	DJNZ	RDLOOP
	RET
;
;
BOMB:	LD	HL,DEAD
	CALL	PMSG
LOOP:	JR	LOOP
DEAD:	DEFB	CR,LF
	DEFM	'boot er$'
        page 
;********************************************************
;*							*
;*	Simple Character I/O Routines              	*
;*			           			*
;********************************************************
;
CONOUT:	LD	A,C
	JP	MONITR+12	;MONITOR CONSOLE OUTPUT RTN.
;
;
LSTST:  
	if 	parallel
	LD      A,(LSTRDY)      ;Check List device status
        RET                     ;  A=0 if not ready, else FF
	else
	LD	A,0FFH		;just return list device ready;
	RET
	endif
;
LSTOUT: 
	if	parallel
	LD      A,(LSTRDY)      ;List output routine.
        OR      A               ;  Wait for device ready
        JR      Z,LSTOUT
;
        XOR     A               ;  Then set device busy
        LD      (LSTRDY),A
        LD      A,C
        OUT     (LSTDAT),A      ;    and output the char
        RET
;
;
;List device interrupt handler.  Simply sets "ready" flag.
LSTIH:  LD      (SAVSTK),SP     ;Save users stack pointer
        LD      SP,STACK
        PUSH    AF
        LD      A,0FFh          ;Set ready code into flag
        LD      (LSTRDY),A
        POP     AF
        LD      SP,(SAVSTK)     ;Restore users stack
        EI
        RETI
        PAGE 
;
	else
;Serial output driver
	if handsh
	LD	A,10H
	OUT	(7),A
	IN	A,(7)
	AND	20H
	JR	Z,LSTOUT
	endif
	LD	A,C
	CALL	MONITR+24
	LD	A,C
	RET
	endif
;********************************************************
;*							*
;*	DISK I/O SUBROUTINES FOR CP/M CBIOS		*
;*							*
;********************************************************
;
;
;	SECTOR TRANSLATE TABLE FOR STANDARD
;	1 IN 6 INTERLEAVE FACTOR
;
SECTAB:	DEFB	1,7,13,19
	DEFB	25,5,11,17
	DEFB	23,3,9,15
	DEFB	21,2,8,14
	DEFB	20,26,6,12
	DEFB	18,24,4,10
	DEFB	16,22
;
;
;	DISK PARAMETER BLOCK FOR STANDARD 8" FLOPPY
;
DPBLK:	DEFW	26		;SECTORS PER TRACK
	DEFB	3		;BLOCK SHIFT CONST.
	DEFB	7		;BLOCK MASK CONST.
	DEFB	0		;EXTENT MASK CONST.
	DEFW	242		;MAX BLOCK#
	DEFW	63		;MAX DIRECTORY ENTRY#
	DEFB	11000000B	;ALLOCATION MASK MSB
	DEFB	00000000B	;'             ' LSB
	DEFW	16		;CHECK SIZE
	DEFW	2		;RESERVED TRACKS
;
;
;	DISK PARAMETER HEADERS FOR A 4 DISK SYSTEM
;
DPHTAB:	DEFW	SECTAB,0000H	;DPH FOR UNIT 0
	DEFW	0000H,0000H
	DEFW	DIRBUF,DPBLK
	DEFW	CHK0,ALL0
 
	DEFW	SECTAB,0000H	;DPH FOR UNIT 1
	DEFW	0000H,0000H
	DEFW	DIRBUF,DPBLK
	DEFW	CHK1,ALL1
 
	DEFW	SECTAB,0000H	;DPH FOR UNIT 2
	DEFW	0000H,0000H
	DEFW	DIRBUF,DPBLK
	DEFW	CHK2,ALL2
 
	DEFW	SECTAB,0000H	;DPH FOR UNIT 3
	DEFW	0000H,0000H
	DEFW	DIRBUF,DPBLK
	DEFW	CHK3,ALL3
;
;
;
;
;
TRANS:	EX	DE,HL		;ADD TRANSLATION TABLE ADDRESS
	ADD	HL,BC		; PASSED IN DE TO SECTOR# IN BC
	LD	L,(HL)
	LD	H,0		;LOOKUP PHYSICAL SECTOR NUMBER
	RET			; AND RETURN IT IN HL
;
;
SETSEC:	LD	A,C
	LD	(SECTOR),A	;STORE SECTOR NUMBER PASSED
	RET			; VIA BC
;
SETPTR:	LD	(POINTR),BC	;STORE DATA POINTER PASSED
	RET			; VIA BC
        page
;SELECT returns the correct Disk Parameter Block pointer
;immediatly and sets the new unit number in TUNIT for the 
;official select call to SELNOW in the read, write, 
;and seek routines.
;
SELECT:	LD	HL,0		;PREP TO CHECK FOR MAX UNIT#
	LD	A,C
	CP	4
	RET	NC		;RETURN WITH HL=0 IF C > 3
	LD	(TUNIT),A	;STORE C AS NEW DRIVE UNIT#
	LD	L,A
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL		;MULTIPLY UNIT# BY 16
	LD	DE,DPHTAB
	ADD	HL,DE		;ADD START ADDRESS OF DHP BLOCK
        RET
;
SELNOW: LD      A,(UNIT)        ;Do select now.  Retreive
        LD      C,A             ;  old unit and
        LD      A,(TUNIT)       ;  check if same as request
        CP      C               ; 
        RET     Z               ;  and return if same
        LD      (UNIT),A        ;otherwise save new unit as perm.
SEL2:   LD      C,A             ;  put in C for monitor call
        LD      B,0             ;seek speed (useless??)
        CALL    MONITR+27       ;  and do seek thru monitor
        RET     Z               ;  exiting if no error
        LD      C,1             ;else report error
        CALL    REPORT
        JR      NZ,SELERR       ;give up if ^C typed after msg.
        LD      A,(UNIT)        ;  else retry
        JR      SEL2
;
SELERR: POP     HL              ;Skip return address and return
        LD      A,1             ;  to CP/M w/ A=1 to
        RET                     ;  indicate error
;
;
;
HOME:	CALL    SELNOW          ;Select drive for home
        CALL	MONITR+30	;CALL HOME ROUTINE IN MONITOR
	RET	Z		;RETURN IF ALL WENT WELL
	LD	C,2
	CALL	REPORT
	JR	Z,HOME   	;RE-TRY HOME IF ERROR INDICATED
	RET
;
;
SEEK:	LD	A,C		;GET TRACK # FROM C
	LD	(TRACK),A       ;Save for possible error 
	CALL    SELNOW          ;Select drive
SEEK1:  LD      A,(TRACK)       ;put back in C for monitor
        LD      C,A
        CALL	MONITR+33	;CALL SEEK ROUTINE IN MONITOR
	RET	Z		;EXIT IF NO ERRORS INDICATED
	LD	C,2
	CALL	REPORT		;REPORT SEEK ERROR TO CONSOLE
	RET	NZ		;RETURN PERMANENT ERROR UNLESS
	JR	SEEK1
;
;
;
READ:	CALL    SELNOW          ;drive select
        LD	HL,(POINTR)
	LD	A,(SECTOR)
	LD	C,A
	CALL	MONITR+36	;CALL READ ROUTINE IN MONITOR
	RET	Z		;RETURN IF NO ERRORS
	LD	C,3		;INDICATE READ ERROR TO HANDLER
	CALL	REPORT		;REPORT DISK ERROR TO CONSOLE
	JR	Z,READ   	;RE-TRY READ IF INDICATED
	RET
;
;
;
WRITE:	CALL    SELNOW
        LD	HL,(POINTR)
	LD	A,(SECTOR)
	LD	C,A
	CALL	MONITR+39	;CALL WRITE ROUTINE IN MONITOR
	RET	Z		;RETURN IF NO ERRORS
	LD	C,4		;INDICATE WRITE ERROR TO HANDLER
	CALL	REPORT		;REPORT DISK ERROR TO CONSOLE
	JR	Z,WRITE  	;RE-TRY WRITE IF INDICATED
	RET			;ELSE RETURN PERMANENT ERROR
;
;
REPORT:	LD	(FLAGS),A	;STORE 1771 I/O STATUS FLAGS
	LD	A,C
	LD	(CLASS),A	;STORE COMMAND CLASS OF ERROR
	LD	HL,DSKMSG
	CALL	PMSG		;PRINT OUT START OF MESSAGE
	DEC	HL
	LD	A,(CLASS)
	LD	B,A
REP1:	CALL	SKIP		;SKIP TO NEXT '$' IN STRING @ HL
	DJNZ	REP1
	CALL	PMSG		;PRINT STRING NOW POINTED TO BY HL
	LD	HL,ERRMSG
	CALL	PMSG		;PRINT 'error  ' AFTER TYPE
	LD	A,(FLAGS)
	RLA			;TEST FIRST FOR DRIVE-NOT-READY ERROR
	JR	C,REP8   	; AND JUMP IF THAT IS THE PROBLEM
	LD	E,A		;GET REMAINING 1771 ERROR BITS INTO E
	LD	HL,RWERRS
	LD	A,(CLASS)
	CP	3		;DETERMINE IF SELECT/SEEK OF R/W ERROR
	JR	NC,REP2
        LD	HL,SKERRS	;POINT HL TO PROPER SET OF MESSAGES
REP2:	LD	B,5
	RES	0,D
REP4:	SLA	E		;SHIFT OUT A 1771 STATUS REG BIT
	JR	NC,REP5
	LD	C,','
	BIT	0,D
	CALL	NZ,OVECTR	;PRINT COMMA BETWEEN STRINGS IF D=1
	CALL	PMSG		;THEN PRINT ERROR MESSAGE @ HL
	SET	0,D		;FLAG THAT A STRING WAS PRINTED
	JR	REP6

REP5:	CALL	SKIP		;SKIP TO NEXT STRING @ HL
	RES	0,D		;FLAG THAT A STRING WAS SKIPPED
REP6:	DJNZ	REP4		;REPEAT FOR ALL 5 POSSIBLE ERRORS
	LD	HL,TSMSG
	CALL	PMSG		;PRINT TRACK/SECTOR# HEADER
	LD	A,(TRACK)
	CALL	PUT2HX		;PRINT TRACK# IN HEX
	LD	C,'/'
	CALL	OVECTR
	LD	A,(SECTOR)
	CALL	PUT2HX
REP7:	LD	A,1
	OR	A		;RETURN PERM ERROR INDICATION IN A
	RET
;
REP8:	LD	HL,RDYMSG
	CALL	PMSG		;PRINT DISK-NOT-READY MESSAGE
	CALL	IVECTR		; AND WAIT FOR CONSOLE INPUT
	CP	'C'-64
	JR	Z,REP7
	XOR	A		;RETURN A=0 IF SOMETHING OTHER THAN
	RET			; CONTROL-C WAS TYPED AT THE CONSOLE
;
SKIP:	PUSH	BC		;SAVE BC
	LD	B,255
	LD	A,'$'
	CPIR			;SCAN MEMORY LOOKING FOR '$'
	POP	BC
	RET
;
;
;
;	CHARACTER STRING OUTPUT ROUTINE. PRINTS ASCII DATA
;	POINTED TO BY HL UNTIL A DOLLAR SIGN IS ENCOUNTERED

PMSG:	LD	A,(HL)		;HL POINTS TO ASCII STRING
	CP	'$'
	INC	HL
	RET	Z
	LD	C,A		;PRINT CHARACTER IF NOT DOLLAR SIGN
	CALL	OVECTR
	JR	PMSG
;
;
PUT2HX:	PUSH	AF
	RRA
	RRA
	RRA
	RRA
	CALL	PUTNIB
	POP	AF
PUTNIB:	AND	00001111B
	ADD	A,90H
	DAA
	ADC	A,40H
	DAA
	LD	C,A
	CALL	OVECTR		;PRINT A HEX-ASCII CHARACTER
	RET
;
        page 
DSKMSG:	DEFB	CR,LF
	DEFM	'bios $'
	DEFM	'sel $'
	DEFM	'seek $'
	DEFM	'rd $'
	DEFM	'wr $'
;
ERRMSG: DEFM    'error  $'	
;
SKERRS:	DEFM	'$'
	DEFM	'$'
	DEFM	'cannot seek$'
	DEFM	'crc$'
	DEFM	'no restore$'

RDYMSG:	DEFM	'not rdy -$'       ;drive not ready
RWERRS:	DEFM	'wp$'              ;write protect
	DEFM	'wf$'              ;write fault
	DEFM	'rnf$'             ;record not found
	DEFM	'crc$'             ;bad CRC
	DEFM	'do$'              ;data overrun
;
TSMSG:  DEFM	' t/s = $'         ;track/sector
;
SIGNON:	DEFB	CR,LF
        DEFB    1Ah                ;Clear screen
        DEFM    'Big Board CP/M  V'
;
        if      CPM14
        DEFM    '1.4'
        else
	DEFM	'2.2'
        endif
;
        DEFM    ' {'
        DEFB    (sovers AND 0FF00h) SHR 8        ;sign on version
        DEFB    sovers AND 0FFh
        DEFM    '}'
	DEFB	'$'
;
;
	if parallel
LSTRDY: DEFS    1   ;List device ready flag
	endif
UNIT:	DEFS	1
TUNIT:  DEFS    1   ;Temp unit number
TRACK:	DEFS	1
SECTOR:	DEFS	1
POINTR:	DEFS	1
FLAGS:	DEFS	1
CLASS:	DEFS	1
	DEFS	32
STACK:	DEFS	1		;LOCAL STACK FOR WARM BOOT
SAVSTK: DEFS    2
        page 
;********************************************************
;*							*
;*	DISK I/O BUFFERS FOR BDOS FILE HANDLER		*
;*							*
;********************************************************
;
;
;
DIRBUF:	DEFS	128		;SCRATCH DIRECTORY BUFFER
;
ALL0:	DEFS	32		;UNIT 0 ALLOCATION BUFFER
CHK0:	DEFS	16		;UNIT 0 CHECK VECTOR
ALL1:	DEFS	32		;UNIT 1 ALLOCATION VECTOR
CHK1:	DEFS	16		;UNIT 1 CHECK VECTOR
ALL2:	DEFS	32		;UNIT 2 ALLOCATION VECTOR
CHK2:	DEFS	16		;UNIT 2 CHECK VECTOR
ALL3:	DEFS	32		;UNIT 3 ALLOCATION VECTOR
CHK3:	DEFS	16		;UNIT 3 CHECK VECTOR
;
;
;
;
        .DEPHASE
;
	END
