;------------------------------------------------------------
;  USER AREA for Lifeboat CP/M2 for Sol-20
;
;  This code provides I/O support for the Sol-20 and differs
;  from the default Sol-20 support by mapping the Sol-20 DEL
;  key to perform "normal" destructive backspace. The serial
;  I/O below is mostly the diassembled code provided by
;  Lifeboat for the Sol-20. It avoids using stack for some
;  reason, I haven't investigated as to why.
;
;  If MSIZE is set to 61K, then it is assumed we are putting
;  CP/M into the address range D000-F7FF above the C000-CFFF
;  hole created by the Sol-20 hardware. On cold start, this
;  patch copies the BIOS jump table just below the Sol-20 ROM
;  at C000 and puts the BDOS jump entry just below that so
;  programs that look at these values to size memory see an
;  upper bound around C000. In addition, the patch disables
;  reloading of CP/M on warm boot since CP/M never actually
;  gets overlaid by programs. 
;
;  When running under the 61K patch, MOVCPM gets a serial 
;  verification error. Use DDT MOVCPM.COM to set locations
;  27A, 27B and 27C to zero (NOP’s a jump instruction). Save
;  the patched memory image to disk with SAVE 44 MOVCPM2.COM.
;
;  The MAXTRK equate can be set to 34 for Mod-I drives or
;  to 76 for Mod-II drives.
;
;  FSTEP0 and FSTEP1 can be set to specify that drive 0
;  and/or drive 1 are fast stepping drives (3ms) instead
;  of 30ms.
;  
;    1.0   11-2-14   Mike Douglas, Original
;
;    1.1   12-24-14  Add option for Tandon drive step rate
;		     and 61K build to put CP/M above the
;		     C000 hole in the Sol-20.
;
;    1.2   08-02-20  Add support to change the number of
;		     tracks per drive in the bytes just
;		     before the user area. This allows
;		     use with 48 tpi Mod-I drives as well
;		     as the 100tpi Mod-II drives. Fix
;		     the user input/output vector init
;		     in coldIni. The Lifeboat code put
;		     a RET instruction in the vector
;		     instead of an address.
;
;  To install this patch, run MOVCPM and save to a file
;  (e.g., CPM.COM) as instructed. Then use DDT to load
;  the patch:
;
;     A>DDT CPM.COM
;     -ISOLIO.HEX
;     -R6C00	(replace 6C00 with value of "OFFSET" below)
;     -G0
;     A>SYSGEN   (hit return at source prompt to use
;		 image in memory)
;------------------------------------------------------------
MSIZE	equ	48		;Distribution size

MAXTRK equ	76		;Max track (34 or 76)

; Set FSTEP0 and FSTEP1 to non-zero to indicate that drive 0
;   and/or drive 1 are fast stepping drives (3ms)

FSTEP0 	equ	0		;1=drive 0 fast step
FSTEP1 	equ	0		;1=drive 1 fast step

; These equates are automatically changed by MSIZE.

BIOS	equ	(MSIZE*1024)-0C00H	;Memory location of BIOS
USER	equ	BIOS+600H	;and of this USER AREA
OFFSET	equ	2600H-USER	;To overlay SYSGEN image

; Location of drive parameters to patch in the BIOS

BLOCKS	equ	USER+0245h	;location of blocks in BIOS
BSH	equ	USER+0297h	;location of BSH in DPH in BIOS
BLM	equ	USER+0298h	;location of BLM in DPH in BIOS
DRM	equ	USER+029Ch	;location of DRM in DPH in BIOS

; Virtual BIOS table equates

NUMVECT	equ	17		;number of BIOS jump vectors
WBOOT	equ	0		;CP/M warm boot jump
BDOS	equ	5		;CP/M BDOS jump
VBIOS	equ	0C000h-NUMVECT*3    ;start of virtual BIOS jump table
VBDOS	equ	0BF06h		;virtual BDOS entry jump (must end in 06)	

; Warm start BIOS initialization patches. These addresses are
;    the value loaded by a lxi instruction to store the warm boot
;    vector into location 1 and the BDOS vector into location 6.

PWBOOT	equ	BIOS+58h	;address of warm boot vector in init code
PBDOS	equ	BIOS+5eh	;address of BDOS vector in init code
PLOAD	equ	BIOS+4ch	;address of call to load CP/M

; Fast step patches in the BIOS

STPLOAD	equ	BIOS+03C2h	;address of lxi d,30 instruction in BIOS
DRVNUM	equ	BIOS+08A4h	;address of drive number in BIOS

; SOLOS/CUTER entry points

AOUT	equ	0C01Ch		;send byte in b out pseudo port in a
AINP	equ	0C022h		;return byte in a from port in a
UIPRT	equ	0C800h		;user defined input routine for port 3
UOPRT	equ	0C802h		;user defined output outine for port 3
STAPT	equ	0FAh		;Sol-20 control/status port

; Misc equates

IOBYTE	equ	3		;location of CP/M IOBYTE
EMPTY	equ	080h		;empty byte in receive buffer
CR	equ	00Dh		;ASCII carriage return
DEL	equ	07Fh		;Sol-20 DEL on keyboard
BS	equ	008h		;ASCII backspace
SOLBS	equ	081h		;makes Sol-20 screen to backspace

;----------------------------------------------------------------------
;  Disk size parameters and the mode byte are in the BIOS image just
;  prior to the USER area.
;----------------------------------------------------------------------
	ORG	USER-6		;Disk size parameters in BIOS image

drvTrks	db	MAXTRK,MAXTRK,MAXTRK,MAXTRK	;Max track per drive
	db	01Bh		;This constant is in the BIOS
mode	db	0C0h		;Mode byte with interrupt enable set

;----------------------------------------------------------------------
; JUMP TABLE - Jumps MUST remain here in same order.
;----------------------------------------------------------------------
cInit	jmp	coldIni		;cold start init
	jmp	warmIni		;warm start init
	jmp	conStat		;BIOS console status call
	jmp	conIn		;BIOS console input call
	jmp	conOut		;BIOS console output call
	jmp	listOut		;BIOS list output call
	jmp	punOut		;BIOS punch output call
	jmp	rdrIn		;BIOS reader input call
	jmp	lstStat		;BIOS list status call

; This 8 byte data area used externally MUST remain.

lenUser	dw	USRLEN		;Length of USER AREA
defIob	db	94h		;defalt I/O byte 10 01 01 00
hsType	db	0		;Handshaking type (not used)
nullOc	db	0		;Printer nulls (not used)
	db	0,0,0		;Reserved

;-----------------------------------------------------------------------
; coldIni - cold start initialization
;-----------------------------------------------------------------------
coldIni	nop			;reserve spot to patch
	nop	
	nop

	xra	a
	out	STAPT		;ensure tape drives stopped
	lda	defIob		;initialize default I/O Byte
	sta	IOBYTE

	lxi	h,uiRet		;HL->user input routine
	shld	UIPRT		;  (defaults to RET)
	lxi	h,uoRet		;HL->user output routine
	shld	UOPRT		;  (defaults to RET)

	lxi	h,EMPTY*256+EMPTY	;init buffer table to empty
	shld	bufTbl		;one spot per pseudo port number
	shld	bufTbl+2

 if MAXTRK shr 1		;Test for 35 track disks
	mvi	a,131		;Use 132 1K blocks vs 66 2K blocks	
	sta	BLOCKS
	mvi	a,3		;Shift factor for 1K blocks
	sta	BSH
	mvi	a,7		;Mask for 1K blocks
	sta	BLM
	mvi	a,63		;64 directory entries vs 128
	sta	DRM
 endif

;------------------
; If a 61K build, copy the BIOS jump table just below the Sol-20 ROM
;   at C000 and put the BDOS jump entry just below the virtual BIOS
;   table so that programs that look at these values to size memory
;   see C000'ish.
;------------------
 if NOT(MSIZE-61)	;if MSIZE=61
	lxi	h,BIOS		;hl->real BIOS jump table
	lxi	d,VBIOS		;bl->virtual BIOS jump table
	mvi	b,NUMVECT*3	;b=bytes in vector table

; Copy the BIOS jump table to just below C000

movBios	mov	a,m		;copy the vector table
	stax	d
	inx	h	
	inx	d
	dcr	b
	jnz	movBios

; Create the BDOS jump entry just below the virtual BIOS jump table

	mvi	a,JMP		;store a jump
	sta	VBDOS
	lhld	PBDOS		;hl=BDOS entry from warm start code
	shld	VBDOS+1

; Patch CP/M boot code in the BIOS to point to these new tables.
;    Disable warm boot from actually loading CP/M by NOP'ing the
;    call to load CP/M.

	lxi	h,VBIOS+3	;hl->warm boot entry in virtual table
	shld	PWBOOT		;patch init code
	lxi	h,VBDOS		;hl->BDOS entry in virtual table
	shld	PBDOS		;patch init code
	lxi	h,0		;nop call to load CP/M
	shld	PLOAD
	xra	a
	sta	PLOAD+2
 endif

;------------------
; Patch for optional fast stepping drives.
;   Patch the BIOS with a call to stepDly below where the LXI D,30
;   instruction to set a 30ms step time is located.
;------------------
 if (FSTEP0 or FSTEP1)
	mvi	a,CALL		;call instruction
	sta	STPLOAD		;patch the BIOS
	lxi	h,stepDly	;address of stepDly routine
	shld	STPLOAD+1	;put in the call destination address
 endif
	ret

;-----------------------------------------------------------------------
; stepDly - return track step delay in ms in DE based on drive
;   number. Step time is 3ms for fast step, 30ms for slow step
;   (Micropolis drives)
;-----------------------------------------------------------------------
stepDly	lxi	d,3		;assume 3ms Tandon step rate
	lda	DRVNUM		;a=drive number

 if FSTEP0
	ora	a		;test for drive 0
	rz			;fast step drive 0, return with 3ms
 endif

 if FSTEP1
	cpi	1		;test for drive 1
	rz			;fast step drive 1, return with 3ms
 endif
	lxi	d,30		;otherwise, use 30ms Micropolis step
	ret

;-----------------------------------------------------------------------
; warmIni - warm start initialization
;-----------------------------------------------------------------------
warmIni	nop			;reserve sport to patch
	nop
	nop
	ret

;-----------------------------------------------------------------------
; conStat - console status check
;   Use SOLOS AINP routine to test for character. AINP accepts a
;   pseudo port number in a to do input from one of three ports.
;   Since AINP returns a character if present, we have to buffer
;   the returned value for a later call to conIN.
;-----------------------------------------------------------------------
conStat	lda	IOBYTE		;get IOBYTE = pseudo port
	ani	3		;get console bits alone
doStat	shld	saveHL		;save HL (not push due to stack issues?)
	mov	h,b
	mov	l,c
	shld	saveBC		;save BC
	mov	c,a		;c=pseudo port number
	mvi	b,0		;form bc=index by pseudo port
	lxi	h,bufTbl
	dad	b		;hl->buffer table per port
	shld	bufPtr		;pointer to buffered char
	mov	a,m		;a=buffered char for this port
	cpi	EMPTY
	jnz	haveChr		;not empty, already have byte
	mov	a,c		;pseudo port=IOBYTE
	call	AINP		;SOLOS read from port in A
	mov	m,a		;save possible char in buffer table
	jnz	haveChr		;not-zero = have a character
	mvi	m,EMPTY		;otherwise, force empty
	xra	a		;CP/M no character status
	jmp	inExit		;exit input routine	

; haveChr - a character is present and has been buffered for a later
;   call from conIn. Return FF and non-zero status to CP/M

haveChr	mvi	a,0FFH		;CP/M character present status
inExit	lhld	saveBC		;restore BC, HL and exit
	mov	b,h
	mov	c,l
	lhld	saveHL
	ret

;-----------------------------------------------------------------------
; conIn, rdrIn - console and reader input.
;   If a character is already buffered, return it. Otherwise, wait
;   for a new character.
;-----------------------------------------------------------------------
conIn	lda	IOBYTE		;get IOBYTE = pseudo port
	jmp	doIn

rdrIn	lda	IOBYTE		;get IOBYTE = pseudo port
	rar			;put in ls bits
	rar
	jmp	doIn		;leave jump for possible patch

; doIn - do the input operation. If a character already buffered
;   return it. Otherwise, wait for a new character

doIn	ani	3		;get pseudo port alone
	sta	savPort		;save it
inLoop	call	doStat		;do status check on port in a
	lda	savPort		;restore port
	jz	inLoop		;no character present, keep waiting

; new byte present and buffered in the buffer table. Pull it out and
;   set the buffer to empty. If the pseudo device is the console,
;   translate DEL to BS

	shld	saveHL
	lhld	bufPtr		;hl=saved I/O table pointer
	ora	a		;Sol pseudo port?
	jnz	notSolI		;no
	mvi	a,DEL		;translate DEL to BS
	cmp	m
	jnz	notSolI
	mvi	a,BS		;return backspace instead
	jmp	setEmp		;set buffer to empty

notSolI	mov	a,m		;a=buffered character
setEmp	mvi	m,EMPTY		;set buffer spot to empty
	lhld	saveHL		;restore HL
	ani	07FH
	ret

;-----------------------------------------------------------------------
; conOut, listOut, punOut - console, list and punch output.
;   Output to the selected device. If sending to the Sol-20
;   screen, translate BS to 81h which is equivalent to BS.
;-----------------------------------------------------------------------
conOut	lda	IOBYTE		;get IOBYTE = pseudo port
	jmp	charOut

listOut	lda	IOBYTE		;get IOBYTE = pseudo port
	rlc			;put in ls bits
	rlc
	jmp	charOut

punOut	lda	IOBYTE		;get IOBYTE = pseudo port
	rar			;put in ls bits
	rar
	rar
	rar
	jmp	charOut		;leave for possible patch

; charOut - send the character in c

charOut	shld	saveHL		;save HL and BC
	mov	h,b
	mov	l,c
	shld	saveBC
	mov	b,c		;b=char to send
	ani	3		;a=pseudo port to use
	jnz	notSolO		;not-zero means not Sol-20 screen

; Sol-20 screen output, don't send multiple CR's in a row, translate
;   ASCII BS to 81h which does the equivalent operation on the Sol

	lda	prevChr		;what was the previous char?
	cpi	CR		;if CR, don't send again
	jnz	notCr
	cmp	b		;is this CR too?
	jz	outExit		;yes, just ignore
notCr	mov	a,b		;character to send in a
	cpi	BS		;it it a backspace?
	jnz	notBs		;no
	mvi	b,SOLBS		;send Sol BS (81h) instead
notBs	mov	a,b
	sta	prevChr		;save previous char
	xra	a		;pseudo port is zero

; send character in b through port in a using AOUT in monitor

notSolO	call	AOUT		;monitor AOUT
outExit	lhld	saveBC
	mov	b,h
	mov	c,l
	lhld	saveHL
	mov	a,c
	ret

;-----------------------------------------------------------------------
; lstStat - list status not supported. Return zero per CP/M spec.
;-----------------------------------------------------------------------
lstStat	xra	a		;always or never ready?
	ret


;-----------------------------------------------------------------------
; Patch space for SOLOS user input and output routines
;-----------------------------------------------------------------------
uiRet	ret			;just exit
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

uoRet	ret			;just exit
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0


;-----------------------------------------------------------------------
; Data area
;-----------------------------------------------------------------------
saveBC	ds	2		;saved BC
saveHL	ds	2		;saved HL
bufPtr	ds	2		;pointer into buffer table
savPort	ds	1		;saved pseudo port number
prevChr	ds	1		;previous character sent
bufTbl	ds	4		;character buffer per pseudo-port

USRLEN	EQU	$-cInit		;Length of USER AREA
	END
