;----------------------------------------------------------------
; USERSOL.ASM
; 
; USER AREA for CP/M 2.2b (Single Density North Star Controller)
;
;  This file 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 disassembled code provided by
;  Lifeboat for the Sol-20.
;  
;  1.0   7-15-15   Mike Douglas
;
;  USER area location (180h bytes long)
;	0500h from start of BIOS
;	1B00h from start of CP/M
;	2500h in memory after loading or running MOVCPM
;	1C00h offset in the disk image file
;			
;  IOBYTE location: 1Dh from start of USER area
;
;  To install this patch:
;    1) Set MSIZE below and assemble this file.
;    2) Run MOVCPM and save the image to a file (e.g., CPM.COM)
;       as prompted.
;    3) Use DDT to install the patch in the CP/M image:
;       A>DDT CPM.COM
;       -IUSERSOL.HEX
;       -R7200		(replace 7200 with value of "OFFSET" below)
;       -G0		(G zero, not "oh", or type ctrl-c)
;       A>SYSGEN	(hit return at source prompt to use
;		 	 image in memory, then follow remaining
;			 prompts).
;
;  IOBYTE behavior in this code
;
;    CON device (bits 1,0):
;	00 - TTY Pseudo port 0, Sol-20 Keyboard/Video Display*
;	01 - CRT Pseudo port 1, serial port
;	10 - BAT Pseudo port 2, parallel port
;	11 - UC1 Pseduo port 3, user defined
;
;    RDR device (bits 3,2):
;	00 - TTY Pseudo port 0, Sol-20 Keyboard/Video Display
;	01 - PTR Pseudo port 1, serial port*
;	10 - UR1 Pseudo port 2, parallel port
;	11 - UR2 Pseduo port 3, user defined
;
;    PUN device (bits 5,4):
;	00 - TTY Pseudo port 0, Sol-20 Keyboard/Video Display
;	01 - PTP Pseudo port 1, serial port*
;	10 - UP1 Pseudo port 2, parallel port
;	11 - UP2 Pseduo port 3, user defined
; 
;    LST device bits (7,6):
;	00 - TTY Pseudo port 0, Sol-20 Keyboard/Video Display
;	01 - CRT Pseudo port 1, serial port
;	10 - LPT Pseudo port 2, parallel port*
;	11 - UL1 Pseduo port 3, user defined
;
;     *default setting, see defIob below
;----------------------------------------------------------------

; Change MSIZE to the desired CP/M memory size in K.

MSIZE	equ	48		;Distribution size

; BIOS and USER area equates

BIOSLEN	equ	1200h		;length of the BIOS
USROFF	equ	0500h		;offset of USER area in BIOS
USRAREA	equ	MSIZE*1024 - BIOSLEN + USROFF

OFFSET	equ	2500h-USRAREA	;offset to overlay SYSGEN image

; 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
RETINST	equ	0C9h		;8080 RET instruction
EMPTY	equ	080h		;empty byte in receive buffer
DEL	equ	07Fh		;Sol-20 DEL on keyboard
BS	equ	008h		;ASCII backspace
SOLBS	equ	081h		;makes Sol-20 screen to backspace
CR	equ	00dh		;ASCII carriage return


;----------------------------------------------------------------
; Jump Table
;    The BIOS accesses the user I/O routines through this jump
;    table. The table must remain at the start of the user area
;    and in the same order.
;----------------------------------------------------------------
	org	USRAREA

ucoldst	jmp	coldIni		;cold start init
uwarmst	jmp	warmIni		;warm start init
uconst	jmp	conStat		;BIOS console status call
uconin	jmp	conInp		;BIOS console input call
uconout	jmp	conOut		;BIOS console output call
ulist	jmp	listOut		;BIOS list output call
upunch	jmp	punOut		;BIOS punch output call
ureader	jmp	rdrInp		;BIOS reader input call
ulistst	jmp	lstStat		;BIOS list status call

; The next 8 bytes may be accessed externally and must remain in the
;     same order and immediately follow the jump table

usrLen	dw	USREND-USRAREA	;Length of USER AREA
defIob	db	94h		;LST = LPT (parallel port)
				;PUN = PTP (serial port)
				;RDR = PTR (serial port)
				;CON = TTY (Sol-20 keyboard/video)
	db	0,0,0,0,0	;reserved

;-----------------------------------------------------------------------
; coldIni - cold start initialization
;   This entry is called during cold boot.
;-----------------------------------------------------------------------
coldIni	xra	a
	out	STAPT		;ensure tape drives stopped

	lda	defIob		;initialize default I/O Byte
	sta	IOBYTE

	lxi	h,RETINST	;init SOLOS user routines to RET
	shld	UIPRT		;user input routine
	lxi	h,RETINST
	shld	UOPRT		;user output routine

	ret

;-----------------------------------------------------------------------
; warmIni - warm start initialization
;   This entry is called during each warm boot.
;-----------------------------------------------------------------------
warmIni	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	push	h		;save hl, bc
	push	b

	lxi	h,bufTbl	;compute buffer address for oprt
	mov	c,a		;c=pseudo port number
	mvi	b,0		;form bc=index by pseudo port
	dad	b		;hl->buffer table per port
	shld	bufPtr		;save pointer to buffered char

	mov	a,m		;a=buffered char for this port
	cpi	EMPTY		;empty?
	jnz	haveChr		;not empty, already have byte

	mov	a,c		;a=pseudo port
	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	pop	b		;restore bc, hl
	pop	h
	ret

;-----------------------------------------------------------------------
; conInp, rdrInp - console and reader input.
;   If a character is already buffered, return it. Otherwise, wait
;   for a new character. 
;-----------------------------------------------------------------------
conInp	lda	IOBYTE		;get IOBYTE = pseudo port
	jmp	doIn

rdrInp	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

	push	h		;save hl
	lhld	bufPtr		;hl=saved I/O table pointer
	ora	a		;Sol screen pseudo port?
	jnz	notSolI		;no

	mvi	a,DEL		;yes, see if DEL pressed
	cmp	m
	jnz	notSolI		;not DEL

	mvi	a,BS		;return backspace instead
	jmp	setEmp

notSolI	mov	a,m		;a=buffered character

setEmp	ani	07fh		;get rid of parity bit
	mvi	m,EMPTY		;set buffer spot to empty
	pop	h		;restore hl
	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	push	h		;save hl, bc
	push	b

	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		;is 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

notSolO	call	AOUT		;monitor AOUT

outExit	pop	b		;restore bc,hl
	pop	h
	mov	a,c		;return with output char in a
	ret

;-----------------------------------------------------------------------
; lstStat - list status not supported. Return zero per CP/M spec.
;-----------------------------------------------------------------------
lstStat	xra	a		;always or never ready?
	ret

;-----------------------------------------------------------------------
; Serial I/O data area
;-----------------------------------------------------------------------
bufPtr	ds	2		;pointer into buffer table
savPort	ds	1		;saved pseudo port number
prevChr	db	0		;previous character sent
bufTbl	db	EMPTY,EMPTY,EMPTY,EMPTY	   ;char buffer per pseudo-port

USREND	equ	$		;end of user area
