;***************************************************************
; INTEL HEX LOADER FOR SOLOS/CUTER
; BY MARTIN EBERHARD
;
; VERS   DATE      NOTE
;  01   8MAY2011  CREATED, ALLOWS CUTER RELOCATION
;  02   8MAY2011  FIXED LOCATION FOR SOL20, RUNS IN USER RAM
;                 INSTALLS CUSTOM COMMAND 'HL'
;
; TO DO:
; + CHECK FOR HL COMMAND ALREADY IN TABLE
; + CHECK FOR KBD ENTRY TO ABORT LOAD
;***************************************************************

VERSH	EQU	'0'	;HIGH BYTE OF VERSION
VERSL	EQU	'1'	;LOW BYTE OF VERSION
	
;***************************************************************
; THIS PROGRAM ASSUMES SOLOS/CUTER RUNS AT ITS NORMAL LOCATION,
; C000H. THIS PROGRAM ALSO ASSUMES THAT SOLOS/CUTER CALLED THIS
; PROGRAM, SO THAT A RET INSTRUCTION WILL RETURN TO "RETRN".

; THE 'EXECUTE' ADDRESS FOR THIS PROGRAM SHOULD BE THE ADDRESS
; OF 'INSTALL'. THIS WILL INSTALL CUSTOM COMMAND 'HL' INTO
; SOLOS/CUTER. IF SOLOS/CUTER'S CUSTOM COMMAND TABLE IS FULL,
; THE LAST ENTRY WILL BE REPLACED WITH 'HL'. 
;***************************************************************

SOLBAS	EQU	0C000H	;SOLOS BASE ADDRESS

; CUTER/SOLOS ENTRY POINTS, OFFSET FROM SOLOS BASE ADDRESS

INIT	EQU	01H	;COLD-START SOLOS
RETRN	EQU	04H	;RETURN CONTROL TO SOLOS
FOPEN	EQU	07H	;OPEN TAPE FILE
FCLOS	EQU	0AH	;CLOSE TAPE FILE
RDBYT	EQU	0DH	;READ TAPE BYTE
WRBYT	EQU	10H	;WRITE TAPE BYTE
RDBLK	EQU	13H	;READ TAPE BLOCK
WRBLK	EQU	16H	;WRITE TAPE BLOCK
SOUT	EQU	19H	;WRITE CHR IN B TO CURRENT PSEUDO PORT
AOUT	EQU	1CH	;WRITE CHR IN B TO PSEUDO PORT IN A
SINP	EQU	1FH	;READ CURRENT PSEUDO PORT INTO A
AINP	EQU	22H	;READ FROM PSEUDO PORT IN A INTO A

; LOCATIONS IN SOLOS/CUTER 1K RAM BLOCK

CUTAB	EQU	083CH	;CUSTOM CMD TABLE, OFFSET FROM SOLBAS
MAXCU	EQU	6	;MAX # OF CUSTOM COMMAND ENTRIES
USARE	EQU	0AB4H	;USER AREA IN RAM, OFFSET FROM SOLBAS

; CUTER/SOLOS PSEUDO PORT DEFINITIONS

KBDPP	EQU	0	;KEYBOARD PSEUDO PORT
VDMPP	EQU	0	;VDM PSEUDO PORT
SERPP	EQU	1	;SERIAL PSEUDO PORT
PARPP	EQU	2	;PARALLEL PSEUDO PORT
USRPP	EQU	3	;USER-WRITTEN ROUTINE PSEUDO PORT

;ASCII EQUATES

CR	EQU	0DH	;ASCII CARRIAGE RETURN
LF	EQU	0AH	;ASCII LINE FEED

;***************************************************************
ORG	SOLBAS+USARE	;RUNS IN SOLOS/CUTER RAM USER AREA
;***************************************************************
;ENTRY POINT FOR ACTUAL HEX LOAD
;
; REGISTER USAGE:
; B: SCRATCH
; C: RECORD BYTE COUNT
; D: RECORD CHECKSUM
; E: RECORD TYPE
; HL: MEMORY ADDRESS
;***************************************************************
HEXLD:	MVI	C,RDYMSG-MSBASE	;READY MESSAGE
	CALL	PRINTF

; WAIT FOR RECORD START COLON

GETCOL:	CALL	GETCHR
	CPI	':'
	JNZ	GETCOL

; GET BYTE COUNT INTO C AND RESTART CHECKSUM

	CALL	GETBYT
	MOV	C,B
	MOV	D,B		;RESET CHECKSUM IN D

; GET ADDRESS INTO HL

	CALL	GETBYT
	MOV	H,B		;HIGH BYTE OF ADDRESS
	CALL	GETBYT
	MOV	L,B		;LOW BYTE OF ADDRESS

; GET RECORD TYPE IN E

	CALL	GETBYT
	MOV	E,B

; TEST FOR 0-BYTE RECORD, WHICH IS AN ALTERNATE EOF

	MOV	A,C		;CHECK FOR 0-BYTE RECORD
	ORA	A
	JNZ	BYTLUP

	INR	A
	ORA	E
	MOV	E,A		;REMEMBER EOF

	JMP	GCHKSM

; LOOP TO GET DATA INTO MEMORY

BYTLUP:	CALL	GETBYT
	MOV	M,B
	INX	H
	DCR	C
	JNZ	BYTLUP	

; GET AND CHECK RECORD CHECKSUM

GCHKSM:	CALL	GETBYT
	MVI	C,CERMSG-MSBASE
	JNZ	EXIT		;TEST CHECKSUM, ERROR IF BAD

; ALL DONE WITH A RECORD
	MVI	C,DONMSG-MSBASE	;DONE, NOT ERROR
	DCR	E		;RECORD TYPE 1 IS EOF
	JZ	EXIT

	INR	E		;TEST FOR DATA RECORD
	JZ	GETCOL		;YES: GO GET ANOTHER RECORD

; BAD RECORD TYPE

	MVI	C,BRTMSG-MSBASE

;	FALL INTO EXIT

;***************************************************************
; PRINT EXIT MESSAGE AT C, THEN RETURN TO SOLOS
; THE STACK ALREADY HAS SOLOS WARM ENTRY POINT
;***************************************************************
EXIT:
;	FALL INTO PRINTF, RETURN TO SOLOS

;***************************************************************
; SUBROUTINE TO PRINT STRING AT MSBASE + C TO VDM. STRING IS
; TERMINATED WITH MSB SET.
; TRASHES BC	
;***************************************************************
PRINTF:	LXI	H,MSBASE
	MVI	B,0
	DAD	B		;HL POINTS TO MESSAGE STRING

MSGLUP:	MOV	A,M		;GET STRING CHR

	ANI	7FH		;STRIP MSB=END OF STRING MARK
	CALL	PRINTV		;PRINTED CHR RETURNS IN B
	MOV	A,M
	CMP	B		;WAS MSB SET FOR THIS CHR?
	RNZ

	INX	H
	JMP	MSGLUP	

;***************************************************************
; SUBROUTINE TO GET 2 HEX DIGITS AND COMBINE THEM INTO A BYTE
; AND ADD RESULT TO CHECKSUM
; ON ENTRY:
;   D = CHCKSUM SO FAR
; ON EXIT:
;   B=BYTE OF DATA
;   A=D=NEW CHECKSUM VALUE
;   Z FLAG SET IF CHECKSUM IS 0
;   ALL OTHER REGISTERS PRESERVED
;***************************************************************
GETBYT:	CALL	GETHEX
	RLC			;SHIFT HIGH NIBBLE IN PLACE
	RLC
	RLC
	RLC
	MOV	B,A
	CALL	GETHEX

	ORA	B		;COMBINE NIBBLES
	MOV	B,A		;SAVE RESULT FOR RETURN
	ADD	D		;COMPUTE CHECKSUM
	MOV	D,A
	RET

; SUBROUTINE TO GET A HEX DIGIT IN A

GETHEX:	CALL	GETCHR
	CPI	'9'+1		;IS CHR BELOW ASCII 9
	JC	GH1		;YES: GO DEAL WITH DIGIT

	CPI	'A'		;IS CHR BETWEEN 9 AND A?
	JC	AERROR		;YES: ERROR
	SUI	'A'-'9'-1	;NO: SUBTRACT OFFSET	

GH1:	SUI	'0'
	CPI	10H
	RC

;ASCII CHARACTER ERROR - NOT A VALID HEX DIGIT

AERROR:	POP	B		;REPAIR STACK: CALL GETHEX
	POP	B		;REPAIR: CALL GETBYT
	MVI	C,AERMSG-MSBASE
	JMP	EXIT		;PRINT ERROR MESSAGE & EXIT

;***************************************************************
; SUBROUTINE TO GET ONE CHR FROM SERIAL PORT, IGNORING NULLS
; ECHO CHR TO VDM SCREEN
; ON ENTRY:
; ON EXIT:
;    CHR IS IN A
;    ALL REGISTERS PRESERVED
;***************************************************************
GETCHR:	PUSH	B

GETCH2:	MVI	A,SERPP		;SELECT SERIAL PSEUDO PORT
	CALL	SOLBAS+AINP
GCRET:	JZ	GETCH2		;WAIT FOR AVAILABLE CHR

	ANI	7FH		;STRIP PARITY, TEST FOR NULL
	JZ	GETCH2		;SKIP OVER NULLS

	CALL	PRINTV		;ECHO TO VDM SCREEN, PUT CHR INTO B
	MOV	A,B		;CHR WAS SAVED IN B

	POP	B
	RET

;***************************************************************
; SUBROUTINE TO PRINT CHR ON VDM SCREEN VIA SOLOS/CUTER
; THE BASE ADDRESS OF SOLOS/CUTER IS STORED AT SOLBAS
; ON ENTRY:
;    A=CHR TO PRINT
;    SOLBAS HAS BASE ADDRESS FOR SOLOS/CUTER
;    TOP OF STACK HAS RETURN ADDRESS
; ON EXIT:
;    B=PRINTED CHARACTER
;    A TRASHED
;***************************************************************
PRINTV:	MOV	B,A		;VDM WANTS CHR IN B
	MVI	A,VDMPP		;SELECT VDM PSEUDO PORT
	JMP	SOLBAS+AOUT

;***************************************************************
; MSB-TERMINATED STRINGS
;***************************************************************
MSBASE:
BRTMSG:	DB	'? RTYP','E'+80H
AERMSG:	DB	'? ASCI','I'+80H
CERMSG:	DB	'? CKSU','M'+80H
DONMSG:	DB	CR,LF,'O','K'+80H
RDYMSG:	DB	CR,LF,'HL Vers. ',VERSH,VERSL,CR,LF+80H

;***************************************************************
; INSTALL HEXLOAD COMMAND IN CUSTOM COMMAND TABLE AND RETURN TO
; SOLOS. 'INSTAL' ADDRESS SHOULD BE THE AUTO-EXECUTE ADDRESS
; FOR SOLOS HEXLOAD. THIS CODE IS AT THE END SO THAT THE STACK
; CAN WIPE IT OUT.
;***************************************************************

; SEARCH FOR NULL ENTRY IN TABLE. IF THE FIRST 5 ENTRIES ARE
; FULL, THEN INSTALL 'HL' IN THE 6TH (LAST) ENTRY, OVERWRITING
; ANY COMMAND THAT MAY HAVE BEEN THERE. NOTE THAT THIS DOES NOT
; CHECK FOR 'HL' COMMAND ALREADY IN THE TABLE.

INSTAL:	LXI	H,CUTAB+SOLBAS-4	;CUST CMD TABLE ADDR
	LXI	B,MAXCU*256+(DONMSG-MSBASE)
				;MAX CUST CMD ENTRIES, DONE MSG
	XRA	A		;HANDY 0
	
SRCH0:	INX	H		;NEXT ENTRY
	INX	H
	INX	H
	INX	H

	DCR	B		;ANY MORE ENTRIES?
	JZ	INSNOW		;FULL? OVERWRITE THE LAST ENTRY

	CMP	M		;A IS ZERO
	JNZ	SRCH0		;FOUND AN EMPTY ENTRY?

; INSTALL HEX LOAD COMMAND INTO CUSTOM COMMAND TABLE AT HL

INSNOW:	MVI	M,'H'
	INX	H
	MVI	M,'L'
	INX	H
	MVI	M,HEXLD AND 0FFH	;LOW BYTE OF ADDRESS
	INX	H
	MVI	M,(HEXLD/256)		;HIGH BYTE OF ADDRESS
	INX	H
	M0V	M,A			;A=0: NEW TABLE END

; INSTALL IS COMPLETE. PRINT MESSAGE AT C AND RETURN TO SOLOS
	
	JMP	EXIT
	
	END
