;***************************************************************
; INTEL HEX LOADER FOR SOLOS/CUTER
; BY MARTIN EBERHARD
;
; VERS   DATE      NOTE
;  1.01   8MAY2011  CREATED, ALLOWS CUTER RELOCATION
;  1.02   8MAY2011  FIXED LOCATION FOR SOL20, RUNS IN USER RAM
;                 INSTALLS CUSTOM COMMAND 'HL'
;  1.03  12MAY2011  CHECK FOR CTRL-C WHILE WAITING FOR CHR
;  1.04  17MAY2011  'E' TOGGLES ECHO
;  1.05  17MAY2011  COUNT & REPORT NUMBER OF RECORDS
;
; TO DO:
; + CHECK FOR HL COMMAND ALREADY IN TABLE
;***************************************************************

VERSION	EQU	0105H	;VERSION IN HEX
	
;***************************************************************
; 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 SAME AS
; THE LOAD ADDRESS. 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'.
;
; TYPING CONTROL-C WILL ABORT THE HEX LOAD.
;
; TYPING E WILL TOGGLE ECHOING OF THE SERIAL DATA TO THE
; CONSOLE. THE HEX LOADER WILL RESPOND WITH 'e+' TO INDICATE
; THAT ECHO IS ON, AND 'e-' TO INDICATE THAT ECHO IS OFF. 
;
; UPON COMPLETION OR ERROR, THIS PROGRAM WILL REPORT (IN HEX)
; THE TOTAL NUMBER OF RECORDS RECEIVED, INCLUDING THE ERROR
; RECORD, IF THERE WAS AN ERROR. NO RECORDS WILL BE PROCESSED
; AFTER AN ERROR.
;
; THE VDM SCREEN SOMETIMES TAKES QUITE A BIT OF TIME TO SCROLL
; TO THE NEXT LINE. AT 9600 BAUD, YOU WILL NEED AROUND 30 mS
; DELAY AFTER LINE FEEDS TO RELIABLY RECEIVE A FILE WITH ECHO
; ON. (AND THERE IS NO WAY FOR SOFTWARE TO DETECT AN OVERRUN
; ERROR FROM THE UART!) THE ALTERNATIVE IS TO TURN ECHO OFF.
; WITH ECHO OFF, 9600 BAUD IS NO PROBLEM. 
;***************************************************************
SOLOS	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 SOLOS
MAXCU	EQU	6	;MAX # OF CUSTOM COMMAND ENTRIES
USARE	EQU	0AB4H	;USER AREA IN RAM, OFFSET FROM SOLOS

; 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

CTRLC	EQU	03H	;CONTROL-C
CR	EQU	0DH	;ASCII CARRIAGE RETURN
LF	EQU	0AH	;ASCII LINE FEED

;***************************************************************
ORG	SOLOS+USARE	;RUNS IN SOLOS/CUTER RAM USER AREA

  JMP INSTL		;SO THAT GO ADDRESS = LOAD ADDRESS
;***************************************************************
;ENTRY POINT FOR ACTUAL HEX LOAD
;
; REGISTER USAGE:
; B: SCRATCH
; C: RECORD BYTE COUNT
; D: RECORD CHECKSUM
; E: RECORD TYPE; MSB SET DISABLES ECHO
; HL: MEMORY ADDRESS
;***************************************************************
HEXLD:	LXI	H,0
	SHLD	RCNT		;CLEAR RECORD COUNT

	MOV	E,H		;ENABLE ECHO

	CALL	PRNTR		;PRINT RECORD COUNT=0

; EAT ALL CHRS UNTIL WE GET RECORD START COLON

GETCO:	CALL	GETCH
	CPI	':'
	JNZ	GETCO

; BUMP RECORD COUNT
	LHLD	RCNT
	INX	H		;16-BIT INCREMENT
	SHLD	RCNT

; GET BYTE COUNT INTO C & RESTART CHECKSUM

	CALL	GETBY
	MOV	C,B
	MOV	D,B		;RESET CHECKSUM IN D

; GET ADDRESS INTO HL

	CALL	GETBY
	MOV	H,B		;HIGH BYTE OF ADDRESS
	CALL	GETBY
	MOV	L,B		;LOW BYTE OF ADDRESS

; GET RECORD TYPE IN E, PRESERVING MSB=ECHO OFF

	CALL	GETBY

	MOV	A,E		;REMEMBER ECHO STATE
	ANI	80H
	ORA	B		;THIS RECORD TYPE
	MOV	E,A

; TEST FOR 0-BYTE RECORD, WHICH IS AN ALTERNATE EOF

	MOV	A,C		;CHECK FOR 0-BYTE RECORD
	ORA	A
	JNZ	BYTLP

	INR	A		;A=1
	ORA	E		;ANY TYPE BUT 0 OR 1 IS ERROR
	MOV	E,A		;REMEMBER EOF

	JMP	GCKSM

; LOOP TO GET DATA INTO MEMORY

BYTLP:	CALL	GETBY
	MOV	M,B
	INX	H
	DCR	C
	JNZ	BYTLP	

; GET AND CHECK RECORD CHECKSUM.

GCKSM:	CALL	GETBY
	MVI	C,'C'	;CHECKSUM ERROR
	JNZ	ERROR	;NZ MEANS CHECKSUM ERROR

; ALL DONE WITH A RECORD - CHECK RECORD TYPE
	MOV	A,E
	ANI	7FH	;STRIP OFF ECHO BIT, TEST 0
	JZ	GETCO	;YES: GO GET ANOTHER RECORD

	DCR	A	;RECORD TYPE 1 IS EOF
	JZ	PRNTR	;DONE: PRINT REC CNT, RETURN TO SOLOS

; BAD RECORD TYPE

	MVI	C,'T'	;RECORD TYPE ERROR

;	FALL INTO ERROR, RETURN TO SOLOS

;***************************************************************
; SUBROUTINE TO PRINT ? PLUS ERROR CHR IN C TO CONSOLE, 
; FOLLOWED BY RECORD COUNT
; TRASHES BC, HL	
;***************************************************************
ERROR:	MVI	B,'?'
	CALL	PRTBC

; FALL INTO PRNTR TO PRINT RECORD COUNT

;***************************************************************
; PRINT COLON-TERMINATED MESSAGE AT HL
; ENTRY AT PRNTR PRINTS THE RECORD COUNT MESSAGE
;***************************************************************
PRNTR:	LXI	H,RECMS
	
PRINT:	MOV	B,M		;GET STRING CHR

	CALL	SOLOS+SOUT
	MOV	A,M
	CPI	':'		;COLON IS LAST CHR
	INX	H
	JNZ	PRINT	

;PRINT RECORD COUNT (VERSION NUMBER DURING INIT)

	LHLD	RCNT
	CALL	HEX16

	LXI	B,CR*256+LF

; FALL INTO PRTBC TO PRINT CRLF, THEN EXIT TO SOLOS

;***************************************************************
; SUBROUTINE TO PRINT B THEN C TO CONSOLE
;***************************************************************
PRTBC:	CALL	SOLOS+SOUT
	MOV	B,C
	JMP	SOLOS+SOUT

;***************************************************************
; SUBROUTINE TO OUTPUT HL AS HEX 16 BIT VALUE
;***************************************************************
HEX16:	CALL	HEOUT	;H FIRST
	MOV	H,L	;THEN L
;
HEOUT:	MOV	A,H	;GET THE CHARACTER
	RRC		;MOVE THE HIGH FOUR DOWN
	RRC
	RRC
	RRC
	CALL	HEOU1	;PUT THEM OUT
	MOV	A,H	;THIS TIME THE LOW FOUR
;
HEOU1:	ANI	0FH	;FOUR ON THE FLOOR
	ADI	48	;WE WORK WITH ASCII HERE
	CPI	58	;0-9?
	JC	OUTH	;YUP
	ADI	7	;MAKE IT A LETTER
OUTH:	MOV	B,A	;OUTPUT IT FROM REGISTER 'B'
	JMP	SOLOS+SOUT

;***************************************************************
; 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
;***************************************************************
GETBY:	CALL	GETHX		;GET HIGH NIBBLE
	RLC			;SHIFT HIGH NIBBLE IN PLACE
	RLC
	RLC
	RLC
	MOV	B,A
	CALL	GETHX		;GET LOW NIBBLE

	ORA	B		;COMBINE NIBBLES
	MOV	B,A		;SAVE RESULT FOR RETURN
	ADD	D		;COMPUTE CHECKSUM
	MOV	D,A
	RET

; LOCAL SUBROUTINE TO GET A HEX DIGIT IN A<3:0>

GETHX:	CALL	GETCH
	SUI	'0'		;REMOVE ASCII BIAS
	CPI	10
	RC			;IF 0-9 THEN WE'RE DONE

	SUI	'A'-'9'-1	;SUBTRACT GAP TO ASCII A
	CPI	10		;BETWEEN ASCII 9 AND A?
	JC	AERR		;YES: ERROR
	CPI	10H		;ABOVE ASCII F?
	RC			;NO: DONE

;ASCII CHARACTER ERROR - NOT A VALID HEX DIGIT

AERR:	POP	B		;REPAIR STACK: CALL GETHX
	POP	B		;REPAIR: CALL GETBY
	MVI	C,'A'		;ASCII ERROR
	JMP	ERROR		;PRINT ERROR MESSAGE & EXIT

;***************************************************************
; SUBROUTINE TO GET ONE CHR FROM SERIAL PORT, IGNORING NULLS,
; STRIPPING PARITY, AND CHECKING FOR CONTROL-C OR 'E' FROM USER
; ECHO CHR TO VDM SCREEN
; ON ENTRY:
; ON EXIT:
;    CHR IS IN A
;    MSB OF E TOGGLED IF 'E' TYPED
;    ABORT TO SOLOS IF CTRL-C TYPED
;    ALL REGISTERS PRESERVED
;***************************************************************
GETCH:	CALL	CHKBD		;SEE IF USER TYPED ANYTHING

	MVI	A,SERPP		;SELECT SERIAL PSEUDO PORT
	CALL	SOLOS+AINP	;A=0 AND Z SET IF NO CHR

	ANI	7FH		;STRIP PARITY, TEST NULL, NO CHR
	JZ	GETCH

; GOT A CHARACTER. ECHO IT IF ALLOWED

	PUSH	B
	MOV	B,A		;VDM WANTS CHR IN B

	MOV	A,E		;ECHO ENABLED?
	RLC

	CNC	SOLOS+SOUT	;PRINT IF MSB OF E=0
	MOV	A,B		;RECOVER CHR W/ PARITY STRIPPED
	POP	B
	RET

; LOCAL SUBROUTINE TO CHECK FOR KEYBOARD INPUT

CHKBD:	CALL	SOLOS+SINP	;CHECK FOR KBD CHR
	RZ			;NO KBD CHR: TRY AGAIN

	ANI	7FH		;STRIP PARITY

	CPI	CTRLC		;CONTROL-C TYPED?
	JZ	SOLOS+RETRN	;YES: SOLOS WILL FIX STACK

	CPI	'E'		;ECHO TOGGLE?
	RNZ

	MOV	A,E		;TOGGLE ECHO
	XRI	80H
	MOV	E,A		

	RET			;DONE WITH USER	

;***************************************************************
; RAM STORAGE
;***************************************************************
RCNT:	DW	VERSION		;COUNT OF RECORDS, INITIALLY VER

;***************************************************************
; COLON-TERMINATED STRINGS
;***************************************************************
RECMS:	DB	CR,LF,'RECS:'
VERMS:	DB	' VER:'

;***************************************************************
; INSTALL HEXLOAD COMMAND IN CUSTOM COMMAND TABLE AND RETURN TO
; SOLOS. THIS CODE IS AT THE END SO THAT A LARGE SOLOS STACK
; CAN WIPE IT OUT AFTER THE INSTALL COMPLETES.
;***************************************************************

; 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.

INSTL:	LXI	H,CUTAB+SOLOS-4	;CUST CMD TABLE ADDR
	MVI	B,MAXCU
				;MAX CUST CMD ENTRIES, DONE MSG
	XRA	A		;HANDY 0
	
INST0:	INX	H		;NEXT ENTRY
	INX	H
	INX	H
	INX	H

	DCR	B		;ANY MORE ENTRIES?
	JZ	INST1		;FULL? OVERWRITE THE LAST ENTRY

	CMP	M		;A IS ZERO
	JNZ	INST0		;FOUND AN EMPTY ENTRY?

; INSTALL HEX LOAD COMMAND INTO CUSTOM COMMAND TABLE AT HL

INST1:	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
	MOV	M,A			;A=0: NEW TABLE END


; INSTALL IS COMPLETE. PRINT READY MESSAGE & RETURN TO SOLOS
		
	LXI	H,VERMS		;VERSION MESSAGE
	JMP	PRINT		;PRINT MSG & RETURN TO SOLOS
	
	END
