;USER AREA for CP/M2 on North Star
;Version 3.4 of Oct 7, 1980

;Copyright (C) 1980 Lifeboat Associates

;UPDATED 5/13/2018 BY MIKE DOUGLAS TO USE THE ALTAIR 88-2SIO
;FOR CONSOLE AND PRINTER I/O:
;   CONSOLOE I/O ON FIRST 2SIO PORT (10H/11H)
;   DEFAULT LST OUTPUT ON SECOND 2SIO PORT (12H,13H)

;This USER AREA is identical to that produced by
;CONFIG.COM Ver 3.x using configurations 0 thru 8.

;It may be used as is or as a prototype for
;your own drivers.  Two pages (512 bytes) are
;available for your custom I/O routines.

;The USER AREA contains a standard CONSOLE driver
;and a PRINTER driver with a choice of handshaking.
;Handshaking may be ETX/ACK, XON/XOFF or NONE.
;Printer may send 0-256 nulls after carriage return.
;The PUNCH and READER routines go to the console.

;The specific console ports and initialization strings
;will depend on the terminal number selected.
;Terminals 0 thru 7 are for specific I/O boards.

;CONFIG terminal #8 will configure for non standard
;console ports when the values for equates "1" thru "8"
;are placed in the data table at 130H together with
;initialization string if needed at "S" and length at "L".

;Type "CONFIG P<cr>" and answer console questions to
;install printer equates "9" thru "J" and init string "S".


;Change MSIZE to the desired CP/M memory size in K.
MSIZE	EQU	48		;Distribution size

; Set FASTOFF to 1 to turn off drive motors when CONIN
;   is called (i.e., program is not using disks).

FASTOFF	equ	0
DISKOFF	equ	0EB17h		;FDC reset command

;The following equates are automatically changed by MSIZE.
BIOS	EQU	5300H+(MSIZE-24)*1024
CCP	EQU	BIOS-1600H
BDOS	EQU	CCP+800H
USER	EQU	BIOS+700H	;For double/quad density
;USER	EQU	BIOS+500H	;For single density
OFFSET	EQU	2000H-BIOS	;To overlay SYSGEN IMAGE
IOBYT	EQU	3		;Storage location

;Hardware equates for Altair 2SIO as used in CONFIG.COM Terminal #0.

;Change to the appropriate values for your I/O hardware.
;See the instructions accompanying your I/O board for
;the correct ports, flags, initialization and driver code.

;Set hardware sense flags xxxFLG as follows:
;	Active HIGH bits to 1, active LOW to 0.
;	Usually xxxFLG = xxxMSK if active HIGH.
;		xxxFLG = 0	if active LOW.

;Hardware equates for console input (TTY). First port on 2SIO
TISPT	EQU	10h		;"1" TTY input status port
TDAMSK	EQU	01h		;"2" Data available mask
TDAFLG	EQU	01h		;"3" Hardware sense active HIGH
TDIPT	EQU	11h		;"4" TTY data input port

;Hardware equates for console output (TTY). First port on 2SIO
TOSPT	EQU	10h		;"5" TTY output status port
TBEMSK	EQU	02h		;"6" Tx buffer empty mask
TBEFLG	EQU	02h		;"7" Hardware sense active LOW
TDOPT	EQU	11h		;"8" TTY data output port

;Hardware equates for printer output (PTR). Second port on 2SIO
POSPT	EQU	12h		;"9" PTR output status port
PBEMSK	EQU	02h		;"A" Tx buffer empty mask
PBEFLG	EQU	02h		;"B" Hardware sense active LOW
PDOPT	EQU	13h		;"C" PTR data output port

;Hardware equates for printer handshaking (PTR input). 2nd port 2SIO
;Usually same ports as printer output with different MSK.
PISPT	EQU	12h		;"D" PTR input status port
PDAMSK	EQU	01h		;"E" Data available mask
PDAFLG	EQU	01h		;"F" Hardware sense active LOW
PDIPT	EQU	13h		;"G" PTR input data port

;Handshaking equates.
ETX	EQU	'C'-40H		;   Send ETX after a buffer
ACK	EQU	'F'-40H		;   and wait for printers ACK
BUFLEN	EQU	127		;   Buffer length for ETX/ACK
XOFF	EQU	'S'-40H		;   Printer says stop
XON	EQU	'Q'-40H		;   Printer ready for data

;HAND is type of handshaking which can be 0FFH for NONE,
;6 (ACK) for ETX/ACK or 11H (XON) for XON/XOFF.
HAND	EQU	0FFH		;"H" Type of handshaking

;DEFIOB is initial IOBYT if used.
;80H sets printer to LPT: device, 0 sets to TTY:.
;STAT.COM can modify IOBYT in running CP/M.
DEFIOB	EQU	80h		;"I" Default IOBYT

;NULLS is number of nulls sent after carriage return
;to allow printer time to return to left margin.
NULLS	EQU	0		;"J" Printer nulls


	ORG	USER		;Start of USER AREA

;JUMP TABLE - Jumps MUST remain here in same order.
CINIT	JMP	CINITR		;Cold boot init
WINIT	JMP	WINITR		;Warm boot init
CONST	JMP	UCONST		;Console status
CONIN	JMP	UCONIN		;Console input
CONOUT	JMP	UCONOUT		;Console output
LIST	JMP	ULIST		;Printer output
PUNCH	JMP	UCONOUT		;Punch output to console
READER	JMP	UCONIN		;Reader input to console
LISTST	JMP	ULISTST		;Printer status

;This 8 byte data area used externally MUST remain.
LENUA:	DW	USRLEN		;Length of USER AREA
USRIOB:	DB	DEFIOB		;"I" Initial IOBYT
HSTYPE:	DB	HAND		;"H" Handshaking type
NULLOC:	DB	NULLS		;"J" Printer nulls
	DB	0,0,0		;Reserved

;These routines use IOBYT to select CP/M CONSOLE and LIST.

UCONST:
	;Select CP/M CONSOLE status routine.
	LDA	IOBYT
	CALL	DEVSEL		;Select device from table.
	DW	TTYIST		;TTY:
	DW	TTYIST		;CRT:
	DW	TTYIST		;BAT:
	DW	PTRIST		;UC1:

UCONIN:
	;Select CP/M CONSOLE input routine.
	LDA	IOBYT
	CALL	DEVSEL		;Select device from table
	DW	TTYIN		;TTY:
	DW	TTYIN		;CRT:
	DW	TTYIN		;BAT:
	DW	PTRIN		;UC1:

UCONOUT:
	;Select CP/M CONSOLE output routine.
	LDA	IOBYT
	CALL	DEVSEL		;Select device from table
	DW	TTYOUT		;TTY:
	DW	TTYOUT		;CRT:
	DW	TTYOUT		;BAT:
	DW	PTROUT		;UC1:

ULIST:
	;Select CP/M LIST output routine.
	LDA	IOBYT
	RLC			;Rotate LIST selection
	RLC			;bits to 0,1
	CALL	DEVSEL		;Select device from table.
	DW	TTYOUT		;TTY:
	DW	TTYOUT		;CRT:
	DW	LPTOUT		;LPT:
	DW	TTYOUT		;UL1:

ULISTST:
	;Select CP/M LIST status routine.
	LDA	IOBYT
	RLC			;Rotate LIST selection
	RLC			;bits to 0,1
	CALL	DEVSEL		;Select device from table.
	DW	TTYOST		;TTY:
	DW	TTYOST		;CRT:
	DW	LPTST		;LPT:
	DW	TTYOST		;UL1:

DEVSEL:
	;Select routine from table of caller.
	ANI	3		;Mask IOBYT and
	RLC			;mult times 2.
	MOV	E,A		;Put index into
	MVI	D,0		;DE register.
	POP	H		;Get addr of table
	DAD	D		;and add index.
	MOV	E,M		;Get addr of routine
	INX	H		;into
	MOV	D,M		;DE first,
	XCHG			;then put into HL
	PCHL			;and transfer control.

;Console Physical Drivers

TTYIST:
	;Console input status routine.
	;Return 0FFH if char ready, 0 if not.
	XRA	A
	IN	TISPT		;"1" Read status port
	CMA			;Adjust sense
	ANI	TDAMSK		;"2" Mask status bits
	XRI	TDAFLG		;"3" Hardware sense
	RZ			;No key was pressed
	MVI	A,0FFH		;Char is ready
	RET

TTYIN:
	;Console input char to register A.
 IF FASTOFF
	lda	DISKOFF		;turn off drive motors
 ENDIF
	CALL	TTYIST		;Is char ready?
	JZ	TTYIN		;Not yet
	XRA	A
	IN	TDIPT		;"4" Read data port
	ANI	7FH		;Strip parity
	RET

TTYOST:
	;Console output status routine.
	;Ret 0FFH if ready for output, 0 if not.
	XRA	A
	IN	TOSPT		;"5" Read status port
	CMA			;Adjust sense
	ANI	TBEMSK		;"6" Mask status bits
	XRI	TBEFLG		;"7" Hardware sense
	RZ			;Ready to output
	MVI	A,0FFH		;Not ready
	RET

TTYOUT:
	;Console output char from register C.
	CALL	TTYOST		;Ready to output?
	JZ	TTYOUT		;Wait until not busy
	MOV	A,C		;Char into accumulator
	OUT	TDOPT		;"8" Output char
	RET

;Printer Physical Drivers

PTROST:
	;Printer output status routine.
	;Return 0FFH if ready, 0 if busy.
	XRA	A
	IN	POSPT		;"9" Read status port
	CMA			;Adjust sense
	ANI	PBEMSK		;"A" Mask status bits
	XRI	PBEFLG		;"B" Hardware sense
	RZ			;Printer busy
	MVI	A,0FFH		;Ready to print
	RET

PTROUT:
	;Printer output char from register C.
	CALL	PTROST		;Ready to print?
	JZ	PTROUT		;Wait until not busy
	MOV	A,C		;Char into accumulator
	OUT	PDOPT		;"C" Output char
	RET

PTRIST:
	;Printer input (ack) status.
	;Return 0FFH if ready, 0 if busy.
	XRA	A
	IN	PISPT		;"D" Read status port
	CMA			;Adjust sense
	ANI	PDAMSK		;"E" Mask status bits
	XRI	PDAFLG		;"F" Hardware sense
	RZ			;No char avail
	MVI	A,0FFH		;Ready to get ack
	RET

PTRIN:
	;Printer input ack char to register A.
	CALL	PTRIST		;Is char ready?
	JZ	PTRIN		;Not yet
	XRA	A
	IN	PDIPT		;"G" Read data port
	ANI	7FH		;Strip parity
	RET

NULLOUT:
	;Null handler for printer output.
	CALL	PTROUT		;Print the char.
	CPI	0DH		;Was it a CR?
	RNZ			;Finished if not.
	LDA	NULLOC		;Get nr of nulls to send
	MOV	B,A		;into B reg to count.
	ORA	A		;We are finished
	RZ			;if NULLS = 0.
	MVI	C,0		;This is a null.
NLOOP:
	CALL	PTROUT		;Print a null,
	DCR	B		;decrement count
	JNZ	NLOOP		;and loop until 0.
	MVI	C,0DH		;Restore CR to C.
	RET

;LPT logical printer driver does handshaking
;and calls PTR physical drivers. Reg C preserved.

LPTST:
	;LPT logical status routine.
	;Return 0FFH if ready, 0 if busy.
	CALL	PTROST		;Is hardware busy?
	RZ			;Yes
	LDA	HSTYPE		;Should be 0FFH, ACK or XON
	MOV	B,A		;Save type.
	CPI	ACK		;ETX/ACK?
	JZ	PROTO		;Yes, on to handler
	CPI	XON		;XON/XOFF?
	MVI	A,0FFH		;No handshaking in use
	RNZ			;and hardware is ready.
PROTO:
	;Mark ready if ACK rvcd when ETX/ACK in use
	;or respond to XON/XOFF.
	CALL	PTRIST		;Is hs char ready?
	CNZ	PTRIN		;Yes, get it.
	CMP	B		;Proper go ahead char?
	JZ	READY		;Yes, must be ACK or XON.
	CPI	XOFF		;XOFF rcvd?
	JNZ	NLEGAL		;No, ignore char.
	INR	B		;Make XON
	INR	B		;into XOFF to make sure
	CMP	B		;XON/OFF in use.
	JZ	BUSY		;XOFF properly rcvd.
NLEGAL:
	LDA	LPTFLG		;Not legal hs char so
	ORA	A		;return with prev status.
	RET
READY:
	MVI	A,0FFH		;Mark ready
	STA	LPTFLG		;at software flag
	ORA	A		;and return NZ.
	RET

LPTOUT:
	;LPT output routine from register C.
	CALL	LPTST		;Get status
	JZ	LPTOUT		;Wait until ready
	CALL	NULLOUT		;Then print char
	LDA	HSTYPE		;Load protocol type
	CPI	ACK		;Using ETX/ACK?
	RNZ			;No, exit.
	;Process ETX/ACK protocol here.
	;Check for ESCAPE sequence first.
	LXI	H,BUFCNT	
	MOV	A,C		;Was last char
	CPI	1BH		;an ESCAPE?
	JNZ	ETXOUT		;No
	MOV	A,M		;Get ETX count
	CPI	4		;If over 3 left
	JNC	ETXOUT		;process normally.
	MVI	M,3		;Send 3 char before ETX.
ETXOUT:
	;Count down until BUFLEN characters sent,
	;then send ETX and wait for printers ACK.
	DCR	M		;Count down but
	RNZ			;do nothing until 0
	MVI	M,BUFLEN	;Then reset count
	MVI	C,ETX		;and send ETX 
	CALL	PTROUT		;to printer.
BUSY:
	XRA	A		;Mark busy
	STA	LPTFLG		;at software flag
	RET			;and ret Z set.

;Handshaking variables
BUFCNT:	DS	1		;ETX/ACK buffer count.
LPTFLG:	DS	1		;LPT status flag.
				;Ready=0FFH, Busy=0

WINITR:
	;Any warm boot initialization goes here.
	DB	0,0,0		;Patch room
	RET

CINITR:
	;Hardware initialization on cold boot goes
	;here if needed.  Make sure it ends with a RET.

	MVI	A,3		;Reset 2SIO ports
	OUT	10h		;1st port
	OUT	12h		;2nd port
	MVI	A,11h		;8N1
	OUT	10h		;1st port
	OUT	12h		;2nd port

	MVI	A,0FFH		;Mark printer ready
	STA	LPTFLG		;at software flag.
	MVI	A,BUFLEN	;Initialize
	STA	BUFCNT		;buffer count.
	LDA	USRIOB		;Load initial IOBYT
	STA	IOBYT		;and store.
	DB	0,0,0		;Patch room

	;Initialization string for terminal #8
	;of length "L" begins at "S" below.
STRING:				;"S"
	RET	
STRLEN	EQU	$-STRING	;"L"

USRLEN	EQU	$-CINIT		;Length of USER AREA
