;----------------------------------------------------------------
; USERIMS.ASM
;
; USER AREA for CP/M 2.2b (Single Density North Star Controller)
;
;  This file provides I/O routines for CP/M using the 
;  IMSAI SIO-2 serial board.
;
;  1.0	 09-21-19   Mike Douglas, original
;
;  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
;			
;  Default IOBYTE location: 1Dh from start of USER area
;
;  To install this patch:
;    1) Set MSIZE immediately 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
;       -IUSERIMS.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).
;----------------------------------------------------------------

; 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

; SIO-2 equates

CSRA	equ	03h		;port a control/status register
DRA	equ	02h		;port a data register
CSRB	equ	05h		;port b control/status register
DRB	equ	04h		;port b data register
SIOCTL	equ	08h		;interrupt control register

RDRF	equ	02h		;receive data bit mask
TDRE	equ	01h		;transmit ready bit mask

; IOBYTE related equates

IOBYTE	EQU	03H		;run-time location of IOBYTE in CP/M
SHFTCON	EQU	1		;left rotate count to align CON in bits 2,1
SHFTRDR	EQU	7		;left rotate count to align RDR
SHFTPUN	EQU	5		;left rotate count to align PUN
SHFTLST	EQU	3		;left rotate count to align LST

;----------------------------------------------------------------
; 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	list		;BIOS list output call
upunch	jmp	punch		;BIOS punch output call
ureader	jmp	reader		;BIOS reader input call
ulistst	jmp	listst		;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

;--------------------------------------------------------------------------
; IOBYTE Assignments
;
;    CON device (bits 1,0):
;	00 - TTY uses SIO-2 serial port A*
;	01 - CRT uses SIO-2 serial port B
;	10 - BAT indirect through RDR logical device
;	11 - UC1 uses SIO-2 serial port A
;
;    RDR device (bits 3,2):
;	00 - TTY uses SIO-2 serial port A
;	01 - PTR uses SIO-2 serial port B*
;	10 - UR1 uses SIO-2 serial port A
;	11 - UR2 uses SIO-2 serial port B
;
;    PUN device (bits 5,4):
;	00 - TTY uses SIO-2 serial port A
;	01 - PTP uses SIO-2 serial port B*
;	10 - UP1 uses SIO-2 serial port A
;	11 - UP2 uses SIO-2 serial port B
; 
;    LST device bits (7,6):
;	00 - TTY uses SIO-2 serial port A
;	01 - CRT uses SIO-2 serial port B
;	10 - LPT uses SIO-2 serial port B*
;	11 - UL1 uses SIO-2 serial port A
;
;* Default setting
;---------------------------------------------------------------------------
defIob	db	94h		;LST = LPT (SIO-2 port B)
				;PUN = PTP (SIO-2 port B)
				;RDR = PTR (SIO-2 port B)
				;CON = TTY (SIO-2 port A)
	db	0,0,0,0,0	;reserved

;-----------------------------------------------------------------------
; coldIni - cold start initialization
;   This entry is called during cold boot.
;-----------------------------------------------------------------------
coldIni	xra	a
	out	SIOCTL		;no interrupts
	
	out	CSRA		;account for possible power up into
	out	CSRB		;   synchronous mode
	out	CSRA
	out	CSRB
	out	CSRA
	out	CSRB

	mvi	a,40h		;reset 8251
	out	CSRA
	out	CSRB

	mvi	a,4Eh		;8N1, /16
	out	CSRA
	out	CSRB
	
	mvi	a,37h		;reset errors, enable xmit, rcv
	out	CSRA
	out	CSRB

	lda	defIob		;store default IOBYTE for CP/M
	sta	IOBYTE

	ret

;-----------------------------------------------------------------------
; warmIni - warm start initialization
;   This entry is called during each warm boot.
;-----------------------------------------------------------------------
warmIni	ret

;-----------------------------------------------------------------------
; CON device I/O
;-----------------------------------------------------------------------
conStat	mvi	b,SHFTCON	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	inStatA		;TTY - SIO-2 port A
	dw	inStatB		;CRT - SIO-2 port B
	dw	batchSt		;BAT - indirect through RDR
	dw	inStatA		;UC1 - SIO-2 port A

conInp	mvi	b,SHFTCON	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	inputA		;TTY - SIO-2 port A
	dw	inputB		;CRT - SIO-2 port B
	dw	reader		;BAT - indirect through RDR
	dw	inputA		;UC1 - SIO-2 port A

conOut	mvi	b,SHFTCON	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	outputA		;TTY - SIO-2 port A
	dw	outputB		;CRT - SIO-2 port B
	dw	list		;BAT - indirect through LST
	dw	outputA		;UC1 - SIO-2 port A

;---------------------------------------------------------------------------
; RDR device input
;---------------------------------------------------------------------------
reader	mvi	b,SHFTRDR	;B=reader IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	inputA		;TTY - SIO-2 port A
	dw	inputB		;PTR - SIO-2 port B
	dw	inputA		;UR1 - SIO-2 port A
	dw	inputB		;UR2 - SIO-2 port B

;---------------------------------------------------------------------------
; PUN device output
;---------------------------------------------------------------------------
punch	mvi	b,SHFTPUN	;B=punch IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	outputA		;TTY - SIO-2 port A
	dw	outputB		;PTP - SIO-2 port B
	dw	outputA		;UP1 - SIO-2 port A
	dw	outputB		;UP2 - SIO-2 port B

;---------------------------------------------------------------------------
;  LST device output
;---------------------------------------------------------------------------
listst	mvi	b,SHFTLST	;B=list IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	oStatA		;TTY - SIO-2 port A
	dw	oStatB		;CRT - SIO-2 port B
	dw	oStatB		;LPT - SIO-2 port B
	dw	oStatA		;UL1 - SIO-2 port A

list	mvi	b,SHFTLST	;B=list IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	outputA		;TTY - SIO-2 port A
	dw	outputB		;CRT - SIO-2 port B
	dw	outputB		;LPT - SIO-2 port B
	dw	outputA		;UL1 - SIO-2 port A

;---------------------------------------------------------------------------
;  BAT device status - The BAT device is assigned as a physical device
;     for CON. However, BAT is actually a logical device that uses
;     whatever RDR is assigned to for input and whatever LST is assigned
;     to for output. The only jump table that does not already exist
;     is the input test routine for the reader devices
;---------------------------------------------------------------------------
batchSt	mvi	b,SHFTRDR	;BAT in uses RDR device
	call	doIo
	dw	inStatA		;TTY - SIO-2 port A
	dw	inStatB		;PTR - SIO-2 port B
	dw	inStatA		;UR1 - SIO-2 port A
	dw	inStatB		;UR2 - SIO-2 port B

;---------------------------------------------------------------------------
; doIo - Perform physical I/O device based on the IOBYTE. The value passed
;    in B contains the left-shift count to align the calling device's IOBYTE
;    field into bits 2 and 1 (not 1 and 0).
;--------------------------------------------------------------------------- 
doIo	xthl			;save HL, HL=table address from stack
	push	d		;save DE
	lda	IOBYTE		;A=CP/M IO BYTE

ioAlign	rlc			;rotate into bits 2 and 1
	dcr	b
	jnz	ioAlign

; The 2 bit device field is aligned in bits 2 and 1. Index into the
;    jump table of the caller and call the proper I/O routine.

	ani	6		;get the 2 bit device field alone
	mov	e,a		;form DE=offset into jump table
	mvi	d,0
	dad	d		;HL->specified device jump address

	mov	e,m		;E=lsb of where to jump
	inx	h
	mov	d,m		;D=msb of where to jump
	xchg			;HL=where to jump

	pop	d		;restore DE
	xthl			;restore HL, jump address on stack
	ret			;jump to proper I/O routine

;-----------------------------------------------------------------------
; inStatA, inStatB - return input status of SIO-2 port A or B.
;    Return 0FFh if character ready, zero if not
;-----------------------------------------------------------------------
inStatA	in	CSRA		;A=port a status
	ani	RDRF		;read data present?
	rz			;no, exit with zero status
	mvi	a,0FFh		;character is ready
	ret

inStatB	in	CSRB		;A=port a status
	ani	RDRF		;read data present?
	rz			;no, exit with zero status
	mvi	a,0FFh		;character is ready
	ret

;-----------------------------------------------------------------------
; oStatA, oStatB - return output status of SIO-2 port A or B.
;    Return 0FFh if ready to send, zero if not
;-----------------------------------------------------------------------
oStatA	in	CSRA		;A=port a status
	ani	TDRE		;ok to send?
	rz			;no, exit with zero status
	mvi	a,0FFh		;ready to send
	ret

oStatB	in	CSRB		;A=port a status
	ani	TDRE		;ok to send?
	rz			;no, exit with zero status
	mvi	a,0FFh		;ready to send
	ret

;-----------------------------------------------------------------------
; inputA, inputB - input character from SIO-2 port A or B
;-----------------------------------------------------------------------
inputA	call	inStatA		;character present?
	jz	inputA		;loop if not

	in	DRA		;a=data from serial port
	ani	07Fh		;strip parity
	ret

inputB	call	inStatB		;character present?
	jz	inputB		;loop if not

	in	DRB		;a=data from serial port
	ani	07Fh		;strip parity
	ret

;-----------------------------------------------------------------------
; outputA, outputB - output character in C through SIO-2 port A or B
;-----------------------------------------------------------------------
outputA	call	oStatA		;ok to send?
	jz	outputA		;loop if not

	mov	a,c		;a=byte to send
	out	DRA		;send character
	ret

outputB	call	oStatB		;ok to send?
	jz	outputB		;loop if not

	mov	a,c		;a=byte to send
	out	DRB		;send character
	ret

USREND	equ	$		;end of user area

