;----------------------------------------------------------------
; USER.ASM
;
; USER AREA for CP/M 2.2b (Single Density North Star Controller)
;
;  The code below is present in the USER area on an unconfigured
;  distribution disk or after MOVCPM is executed. When CP/M boots,
;  the coldIni routine below is called during BIOS cold start.
;  coldIni stalls in an infinite loop so the user can then stop
;  the machine and use a front panel or monitor ROM to patch
;  memory to support console I/O on the target machine. Once the
;  patches are made, jump to the BIOS cold start entry point
;  (BIOS label below) to restart CP/M without a reboot. 
;  
;  This file provides I/O support through an Altair 88-2SIO 
;  serial board. Minor patches to this code should make most any
;  serial interface board function. In an unconfigured, 24K
;  distribution, the coldIni stall loop is at 5323h, the BIOS
;  cold start entry point is at 4E00h, the serial input code
;  (inputA) is at 539E, and the serial output code (outputA) is
;  at 53AC. View the ".PRN" version of this file to see these
;  addresses.
;
;  Once CP/M is running with I/O patches in memory, ASM
;  MOVCPM, DDT, and SYSGEN can be used to make a more complete
;  update to the USER.ASM file and to also make the changes
;  permanent on disk. See below for more information.
;
;  It is also possible to "cheat" and pre-patch the disk image
;  with the user area code for the target machine. The user 
;  area is at 1C00h-1D7F within the disk image file.
;
;--------------------------------------------------------------
;
;  1.0   11-12-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 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
;       -IUSER.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	24		;Distribution size

; BIOS and USER area equates

BIOSLEN	equ	1200h		;length of the BIOS
USROFF	equ	0500h		;offset of USER area in BIOS

BIOS	equ	MSIZE*1024 - BIOSLEN
USRAREA	equ	BIOS + USROFF

OFFSET	equ	2500h-USRAREA	;offset to overlay SYSGEN image

; 88-2SIO equates

CSRA	equ	10h		;port a control/status register
DRA	equ	11h		;port a data register
CSRB	equ	12h		;port b control/status register
DRB	equ	13h		;port b data register

RDRF	equ	1		;receive data bit mask
TDRE	equ	2		;transmit ready bit mask

; Misc equates

IOBYTE	equ	3		;location of CP/M IOBYTE

;----------------------------------------------------------------
; 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	conOut		;BIOS list output call
upunch	jmp	conOut		;BIOS punch output call
ureader	jmp	conInp		;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	00h		;LST = TTY (2SIO port A)
				;PUN = TTY (2SIO port A)
				;RDR = TTY (2SIO port A)
				;CON = TTY (2SIO port A)
	db	0,0,0,0,0	;reserved

;-----------------------------------------------------------------------
; coldIni - cold start initialization
;   This entry is called during cold boot.
;-----------------------------------------------------------------------
coldIni	jmp	coldIni		;stall to allow patching
	
	mvi	a,3		;reset ACIA
	out	CSRA		;port a
	out	CSRB		;port b

	mvi	a,11h		;8N1, /16
	out	CSRA
	out	CSRB

	lda	defIob		;store default IOBYTE for CP/M
	sta	IOBYTE

	ret

	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0		;patch space

;-----------------------------------------------------------------------
; warmIni - warm start initialization
;   This entry is called during each warm boot.
;-----------------------------------------------------------------------
warmIni	ret
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0		;patch space


;-----------------------------------------------------------------------
; conStat - console status check
;-----------------------------------------------------------------------
conStat	lda	IOBYTE		;select device based on IOBYTE 1:0
	call	devSel

	dw	statusA		;TTY
	dw	statusA		;CRT
	dw	statusA		;BAT
	dw	statusA		;UC1

;-----------------------------------------------------------------------
; conInp - console input
;-----------------------------------------------------------------------
conInp	lda	IOBYTE		;select device based on IOBYTE 1:0
	call	devSel

	dw	inputA		;TTY
	dw	inputA		;CRT
	dw	inputA		;BAT
	dw	inputA		;UC1

;-----------------------------------------------------------------------
; conOut - console output
;-----------------------------------------------------------------------
conOut	lda	IOBYTE		;select device based on IOBYTE 1:0
	call	devSel

	dw	outputA		;TTY
	dw	outputA		;CRT
	dw	outputA		;BAT
	dw	outputA		;UC1

;-----------------------------------------------------------------------
; lstStat - list status not supported. Return zero per CP/M spec.
;-----------------------------------------------------------------------
lstStat	xra	a
	ret

;-----------------------------------------------------------------------
; devSel - jump to proper I/O routine based on device in A
;-----------------------------------------------------------------------
devSel	xthl			;hl->table, old hl saved on stack
	push	d		;de and hl saved on stack

	ani	3		;get 2 bit device selection alone
	rlc			;two bytes per table entry
	mov	e,a		;de=offset into table
	mvi	d,0
	dad	d		;hl->table[device]

	mov	e,m		;load routine address into de
	inx	h
	mov	d,m		;de->address of I/O routine 

	xchg			;hl->address of I/O routine
	pchl			;jump to I/O routine

;-----------------------------------------------------------------------
; statusA - return status of 2SIO port A.
;    Return 0FFh if character ready, zero if not
;-----------------------------------------------------------------------
statusA	pop	d		;restore de, hl
	pop	h

	in	CSRA		;a=port a status
	ani	RDRF		;read data present?
	rz			;no, exit with zero status

	mvi	a,0ffh		;character is ready
	ret

;-----------------------------------------------------------------------
; inputA - input character from 2SIO port A.
;-----------------------------------------------------------------------
inputA	pop	d		;restore de, hl
	pop	h

inALoop	in	CSRA		;a=port a status
	ani	RDRF		;read data present?
	jz	inALoop		;no

	in	DRA		;a=data from serial port
	ani	07fh		;strip parity
	ret


;-----------------------------------------------------------------------
; outputA - output character in C through 2SIO port A.
;-----------------------------------------------------------------------
outputA	pop	d		;restore de, hl
	pop	h

outALp	in	CSRA		;a=port a status
	ani	TDRE		;transmit ready?
	jz	outALp		;no

	mov	a,c		;a=byte to send
	out	DRA		;send character

	ret


USREND	equ	$		;end of user area
