;
; CPT resident client.
;
; To modify the I/O function for most systems, you need only set the
; following values:
;	USTAT	= Uart status port I/O address
;	UDATA	= Uart data   port I/O address
;	RMASK	= Mask to test RX ready bit
;	RJMP	= Jump to take when RX-ready is TRUE
;	TMASK	= Mask to test TX ready bit
;	TJMP	= Jump to take when TX-ready if FALSE
;
; ** If you create definitions for any other common systems, please
; ** forward them to me so that I can include them in the distribution.
;
; Dave Dunfield - June 14, 2005
;
SYSTEM	SET	HORIZON
	IFEQ	SYSTEM,HORIZON
; Serial I/O definitions for NorthStar Horizon
USTAT	EQU	3		; Serial status register
UDATA	EQU	2		; Serial data register
RMASK	EQU	%00000010	; RX ready bit
RJMP	SET	JNZ		; True if one
TMASK	EQU	%00000001	; TX ready bit
TJMP	SET	JZ		; False if zero
	ENDIF
	IFEQ	SYSTEM,ORIONV
; Serial I/O definitions for Dy4 Orion-V
USTAT	EQU	$BD		; Serial status register
UDATA	EQU	$BC		; Serial data register
RMASK	EQU	%00000001	; RX ready bit
RJMP	SET	JNZ		; True if one
TMASK	EQU	%00000100	; TX ready bit
TJMP	SET	JZ		; False if zero
	ENDIF
	ORG	$100
;
; Protocol control characters
;
ENQ	EQU	'?'		; Enquiry
RDY	EQU	'.'		; Ready
STX	EQU	$A0		; Start of record character
ACK	EQU	$A1		; Positive Acknowlege
NAK	EQU	$A2		; Negative Acknowlege
ERR	EQU	$A3		; Error    Acknowlege
STACK	EQU	$0800		; Stack location
;
	LXI	SP,STACK	; Set up stack
	CALL	INIT		; Init serial port
; First - establish JMP table to BIOS functions
	LDA	2		; Get BIOS address
	LXI	H,VECTORS	; Point to JMP table
	MVI	C,NUMVEC	; All vectors
INI1	INX	H		; Skip JMP
	INX	H		; Skip LOW
	MOV	M,A		; Set HIGH
	INX	H		; Skip high
	DCR	C		; Reduce count
	JNZ	INI1		; Update them all
	LXI	H,HELLO		; Startup message
INI2	MOV	A,M		; Get data
	ANA	A		; End of string
	JZ	TOP		; Yes, stop
	PUSH	H		; Save HL
	MOV	C,A		; C = data
	CALL	CONOUT		; Send to console
	POP	H		; Restore H
	INX	H		; Next
	JMP	INI2		; And proceed
HELLO	STR	'ONLINE'
	DB	$0A,$0D,0
;
TOPS:	LXI	SP,STACK	; Reset stack
TOP	LXI	H,BUFFER	; Point to buffer
	CALL	RDREC		; Read record
	JNZ	TOP		; Process record
	LDA	BUFFER		; Get command
; Read MEMORY: M ll aaaa
MREAD	CPI	'M'		; Read memory
	JNZ	MWRITE		; No, try next
	LDA	BUFFER+1	; Get length
	MOV	B,A		; Working copy
	MOV	C,A		; Xmit copy
	LHLD	BUFFER+2	; Get address
	LXI	D,BUFFER	; Point to data
mread1	MOV	A,M		; Get data from memory
	INX	H		; Skip to next
	STAX	D		; Save in buffer
	INX	D		; Skip to next
	DCR	B		; Reduce count
	JNZ	mread1		; Read them all
	LXI	H,BUFFER	; Point to buffer
	CALL	WRREC		; Send it
	JMP	TOP		; Next command
; Set memory: S aaaa .. ..
MWRITE:	CPI	'S'		; Write memory
	JNZ	DREAD		; No, try next
	LHLD	BUFFER+1	; Get address
	DCR	C		; Adjust
	DCR	C		; Adjust
	DCR	C		; Adjust
	LXI	D,BUFFER+3	; Point to buffer
mwri1:	LDAX	D		; Get data
	INX	D		; Skip to next
	MOV	M,A		; Save in memory
	INX	H		; Skip to next
	DCR	C		; Reduce count
	JNZ	mwri1		; Do them all
DOACK	MVI	A,ACK		; Get ACK
	CALL	PUTC		; Write it
	JMP	TOP		; Next command
; Read disk: R aaaa ss ..
DREAD	CPI	'R'		; Read
	JNZ	DWRITE		; No, try next
	MOV	A,C		; Get length
	SUI	3		; Adjust
	LXI	H,BUFFER+3	; Point to sector list
rread1:	STA	BUFFER		; Save for later
	PUSH	H		; Save
	LHLD	BUFFER+1	; Get DMA address
	MOV	B,H		; B = HIGH
	MOV	C,L		; C = LOW
	LXI	D,128		; Advance to next
	DAD	D		; Offset
	SHLD	BUFFER+1	; And resave
	CALL	SETDMA		; Select address
	POP	H		; Restore address
	MOV	C,M		; Get sector #
	MVI	B,0		; Zero high
	INX	H		; Skip to next
	PUSH	H		; Save location
	CALL	XLTSEC		; Select sector
	CALL	READ		; Read the sector
	POP	H		; Restore H
	MVI	B,ACK		; Assume OK
	ANA	A		; Zero return?
	JZ	rread2		; Report success
	MVI	B,ERR		; Indicate error
rread2:	MOV	A,B		; Get indicator
	CALL	PUTC		; Output
	LDA	BUFFER		; Get counter
	DCR	A		; Reduce count
	JNZ	rread1		; Do them all
	JMP	TOP		; And continue
; Write disk drive: W aaaa ss ..
DWRITE	CPI	'W'		; Write disk
	JNZ	DTRACK		; Set track
	MOV	A,C		; Get count
	SUI	3		; Adjust
	LXI	H,BUFFER+3	; Get sector list
dwrit1:	STA	BUFFER		; Save count
	PUSH	H		; Save
	LHLD	BUFFER+1	; Get DMA address
	MOV	B,H		; B = HIGH
	MOV	C,L		; C = LOW
	LXI	D,128		; Advance to next
	DAD	D		; Offset
	SHLD	BUFFER+1	; And resave
	CALL	SETDMA		; Select address
	POP	H		; Restore address
	MOV	C,M		; Get sector #
	MVI	B,0		; Zero high
	INX	H		; Skip to next
	PUSH	H		; Save location
	CALL	XLTSEC		; Select sector
	MVI	C,0		; Normal write
	CALL	WRITE		; Read the sector
	POP	H		; Restore H
	MVI	B,ACK		; Assume OK
	ANA	A		; Zero return?
	JZ	dwrit2		; Report success
	MVI	B,ERR		; Indicate error
dwrit2:	MOV	A,B		; Get indicator
	CALL	PUTC		; Output
	LDA	BUFFER		; Get counter
	DCR	A		; Reduce count
	JNZ	dwrit1		; Do them all
	JMP	TOP		; And continue
; Select track: T tttt
DTRACK:	CPI	'T'		; Track?
	JNZ	SELECT		; No, try next
	LHLD	BUFFER+1	; Get track
	MOV	B,H		; Copy high
	MOV	C,L		; Copy low
	CALL	SETTRK		; Select track
	JMP	DOACK		; Success
; Select drive: D dd
SELECT:	CPI	'D'		; Select
	JNZ	DOXLT
	MVI	C,0Dh		; Reset disk system
	CALL	5		; BDOS call
	LDA	BUFFER+1	; Get drive
	MOV	E,A		; E = Drive
	MVI	C,0Eh		; Activate disk
	CALL	5		; BDOS call
	LXI	H,0		; Get OFF
	SHLD	XLT		; Disable XLT
	LDA	BUFFER+1	; Get drive
	MOV	C,A		; C=drive
	CALL	SELDSK		; Select drive
RREG:	STA	BUFFER+1	; Save A
	SHLD	BUFFER+6	; Save HL
	MOV	H,B		; Get B
	MOV	L,C		; Get C
	SHLD	BUFFER+2	; Save BC
	XCHG			; Get DE
	SHLD	BUFFER+4	; Save DE
	MVI	C,8		; 8 byte return
	LXI	H,BUFFER	; Point to buffer
	CALL	WRREC		; Write it
	JMP	TOP		; Next command
; Set translate table address
DOXLT	CPI	'X'		; XLT?
	JNZ	DOERR		; No, try next
	LHLD	buffer+1	; Get address
	SHLD	XLT		; Set translate address
	JMP	DOACK		; Acknowlege & exit
; Send NAK
DOERR:	CALL	GETC		; Test for character
	JNC	DOERR		; Wait for timeout
	MVI	A,ERR		; Indicate ERR
	CALL	PUTC		; Send
	JMP	TOPS
;
; Write record[HL] for length in C
;
WRREC	PUSH	H		; Save HL
	PUSH	D		; Save DE
	PUSH	B		; Save BC
	MVI	A,STX		; Start of record
	CALL	PUTC		; Write it
	MOV	A,C		; Get length
	CALL	PUTC		; Write it
	LXI	D,0		; Begin checksum
WRREC1	MOV	A,M		; Get data byte
	CALL	PUTC		; Write it
	ADD	E		; Add to chk
	MOV	E,A		; Resave
	JNC	WRREC2		; No carry
	INR	D		; Advance high
WRREC2	INX	H		; Skip to next
	DCR	C		; Reduce count
	JNZ	WRREC1		; Write record#1
	MOV	A,D		; Get high
	CALL	PUTC		; Write it
	MOV	A,E		; Get low
	CALL	PUTC		; Write it
	POP	B		; Restore BC
	POP	D		; Restore DE
	POP	H		; Restore HL
	XRA	A		; Return zero
	RET
;
; Read record into [HL], return length in C
;
RDREC	PUSH	H		; Save HL
	PUSH	D		; Save DE
RDREC1	CALL	GETC		; Get character
	JC	RDFAIL		; Report failure
	CPI	STX		; Start of record?
	JZ	RDREC2		; Yes, handle it
	CPI	ENQ		; Enquiry?
	JNZ	RDREC1		; No, wait
	MVI	A,RDY		; Get indicator
	CALL	PUTC		; Respond
	JMP	RDREC1		; And continue
RDREC2	CALL	GETC		; Get character
	JC	RDFAIL		; Report failure
	MOV	C,A		; Set length
	MOV	B,A		; Set working length
	LXI	D,0		; Zero checksum
RDREC3	CALL	GETC		; Get character
	JC	RDFAIL		; Report failure
	MOV	M,A		; Save for later
	ADD	E		; Add to chk
	MOV	E,A		; Resave
	JNC	RDREC4		; No carry
	INR	D		; Advance high
RDREC4	INX	H		; Skip to next
	DCR	B		; Reduce count
	JNZ	RDREC3		; Reduce count
	CALL	GETC		; Get HIGH chk
	JC	RDNAK		; Failed - NAK
	CMP	A,D		; Match?
	JNZ	RDNAK		; No, report fail
	CALL	GETC		; Get LOW chk
	JC	RDNAK		; Failed NAK
	CMP	A,E		; Match?
	JNZ	RDNAK		; No, report fail
	POP	D		; Restore DE
	POP	H		; Restore HL
	XRA	A		; Return 0 - OK
	RET
RDNAK	MVI	A,NAK		; Nak it
	CALL	PUTC		; And continue
RDFAIL	POP	D		; Restore DE
	POP	H		; Restore HL
	ORI	$FF		; Non-zero - FAIL
	RET
;
XLTSEC	LHLD	XLT		; Get translate table
	MOV	A,H		; Get high
	ORA	L		; Is it enabled
	JZ	SETSEC		; No, skip
	XCHG			; DE = table address
	CALL	SECTRAN		; Translate sector
	MOV	B,H		; Get high
	MOV	C,L		; Get low
;
; CP/M BIOS interface functions
VECTORS	EQU	*		; Vectors go here
SETSEC	JMP	$0021		; Select sector
CONST	JMP	$0006		; Check console status
CONIN	JMP	$0009		; Input from console
CONOUT	JMP	$000C		; Output to console
HOME	JMP	$0018		; Home head
SELDSK	JMP	$001B		; Select disk
SETTRK	JMP	$001E		; Select track
SETDMA	JMP	$0024		; Set DMA address
READ	JMP	$0027		; Read a sector
WRITE	JMP	$002A		; Write a sector
SECTRAN	JMP	$0030		; Sector translate
NUMVEC	EQU	11
;
;-----------------------------------------
; Serial I/O drivers (Modify if required)
;-----------------------------------------
;
; Initialize serial port
;
INIT	RET			; None required
;
; Write character A to I/O port
;
PUTC	PUSH	PSW		; Save A
putc1	IN	USTAT		; Read status
	ANI	TMASK		; TX ready?
	TJMP	putc1		; No, wait for it
	POP	PSW		; Restore A
	OUT	UDATA		; Write it
	RET
;
; Test for character received
;
GETC	PUSH	H		; Save HL
	LXI	H,0		; Zero timeout
getc1	IN	USTAT		; Read status
	ANI	RMASK		; RX ready?
	RJMP	getc2		; Yes, read it
	DCX	H		; Reduce count
	MOV	A,H		; Get high
	ORA	L		; Test == 0
	JNZ	getc1		; Not expired
	POP	H		; Restore H
	STC			; No data
	RET
; C has already been cleared by ANI
getc2	POP	H		; Restore H
	IN	UDATA		; Read data
	RET
;
; CPT Data areas (Do not modify)
;
XLT	DW	0		; Sector translate address
BUFFER	RMB	256		; I/O buffer
