;***************************************************************
; INTEL HEX LOADER FOR SOLOS/CUTER
;
; THIS PROGRAM ADDS A COMMAND 'HL' TO SOLOS OR CUTER. THIS NEW
; COMMAND LOADS INTEL HEX FILES VIA THE SOL (OR 3P+S) SERIAL
; PORT INTO RAM AT THE LOAD ADDRESSES SPECIFIED IN THE HEX FILE.
;
; 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
;  1.06  17MAY2011  A LITTLE MORE VERBOSE MESSAGES
;  1.07  18 MAY2011 TEST FOR EXISTING HL COMMAND, SQUEEZE
;
; TO DO:
; + ALLOW FOR LOAD ADDRESS OFFSET
;***************************************************************

VERSION	EQU	0107H	;VERSION 1.07 IN HEX-ENCODED-DECIMAL
	
;***************************************************************
; OPERATION
;
; EXECUTING THIS PROGRAM WILL ONLY INSTALL CUSTOM COMMAND 'HL'
; INTO SOLOS/CUTER. IF SOLOS/CUTER'S CUSTOM COMMAND TABLE IS
; FULL, THE LAST ENTRY WILL BE REPLACED WITH 'HL'.
;
; TO USE THIS NEW COMMAND, TYPE 'HL' AT THE SOLOS PROMPT. THE
; HEX LOADER WILL RESPOND WITH ITS VERSION NUMBER, THEN BEGIN
; LOOKING AT THE SERIAL PORT FOR VALID INTEL HEX RECORDS.
; CONTROL WILL RETURN TO SOLOS WHEN AN EOF RECORD IS FOUND, OR
; WHEN AN ERROR IN THE SERIAL DATA IS DETECTED.
;
; 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 FURTHER RECORDS WILL BE
; PROCESSED AFTER AN ERROR HAS BEEN DETECTED.
;
; TYPING CONTROL-C WILL ABORT THE HEX LOAD AND RETURN CONTROL
; TO SOLOS WITHOUT REPORTING THE RECORD COUNT.
;
; 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, OR 'E-' TO INDICATE THAT ECHO IS OFF.
;
; 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 RECEIVE A FILE RELIABLY WITH ECHO
; ON. WORSE, THERE IS NO WAY FOR SOFTWARE TO DETECT AN OVERRUN
; ERROR FROM THE UART. THE ALTERNATIVE TO INSERTING DELAYS IS
; TO TURN ECHO OFF. (PRESS 'E'.) WITH ECHO OFF, 9600 BAUD IS
; NO PROBLEM.
;
;***************************************************************
; PROGRAM NOTES
;
; THIS PROGRAM IS WRITTEN TO BE ASSEMBLED BY CP/M'S ASM.COM.
;
; THIS PROGRAM ASSUMES SOLOS/CUTER RUNS AT ITS NORMAL LOCATION,
; 0C000H. THIS PROGRAM ALSO ASSUMES THAT SOLOS/CUTER CALLED
; THIS PROGRAM, SO THAT A RET INSTRUCTION WILL RETURN TO SOLOS.
;
; SOLOS/CUTER GIVES NO PROGRAM ACCESS TO ITS CUSTOM COMMAND
; TABLE (CALLED CUTAB IN SOLOS SOURCE CODE). THIS PROGRAM
; THEREFORE ACCESSES THE TABLE DIRECTLY, AND ASSUMES A FIXED
; ADDRESS FOR THE TABLE. THE ADDRESS USED IN THIS CODE IS
; CORRECT FOR BOTH SOLOS AND CUTER VERSION 1.3.
;
; THE 'EXECUTE' ADDRESS FOR THIS PROGRAM SHOULD BE THE SAME AS
; THE LOAD ADDRESS. (0CAB4H FOR NORMAL SOLOS/CUTER VERSIONS.)
;
; THIS PROGRAM RUNS IN THE 'USER AREA' OF THE 1K RAM BUFFER
; ADJACENT TO SOLOS/CUTER (CALLED 'USARE' IN SOLOS SOURCE CODE).
; THIS AREA BEGINS AT 0CAB4H AND ENDS AT THE END OF RAM, 0CBFFH.
; HOWEVER, SOLOS'S STACK BEGINS AT 0CBFF, AND GROWS TOWARD 0 - 
; SO IF THIS PROGRAM IS TO LARGE, SOLSO'S STACK WILL OVERWRITE
; IT. SOLOS DOES NOT USE MUCH STACK WHILE EXECUTING A PROGRAM
; FROM CASSETTE, SO MOST OF THE AVAILABLE RAM CAN BE USED DURING
; INSTALLATION. BUT THE ACTUAL 'HL' COMMAND MUST LEAVE ROOM FOR
; A REASONABLE STACK, SINCE WE WANT IT TO STICK AROUND. FOR THIS
; REASON, THE INSTALLER IS AT THE END OF THE PROGRAM, KEEPING
; THE ACTUAL 'HL' COMMAND AS FAR FROM THE STACK AS POSSIBLE.
; WITH VERSION 1.07 OF THIS PROGRAM, THE STACK HAS ABOUT 97
; BYTES, ALLOWING FOR 48 PUSHES - PRETTY RESASONABLE.
;
;***************************************************************
; INTEL HEX RECORD FORMAT
;
; AFTER THE INITIAL COLON, EACH CHARACTER IS A HEX DIGIT.
;
; :CCAAAATTdataSS
;
;    : = (ASCII COLON) START-OF-RECORD INDICATOR
;   CC = THE TOTAL NUMBER OF DATA BYTES, IN HEX
; AAAA = THE MEMORY LOAD ADDRESS FOR THE FIRST DATA BYTE
;        SUBSEQUENT BYTES ARE WRITTEN TO SEQUENTIAL ADRESSES
;   TT = RECORD TYPE. THE FOLLOWING TYPES ARE SUPPORTED:
;        00 = NORRMAL DATA RECORD
;        01 = END-OF-FILE. EOF RECORD MAY CONTAIN DATA.
; data = CC DATA BYTES (2 HEX DIGITS EACH)
;   SS = RECORD CHECKSUM. WHEN ALL HEX BYTES OF THE RECORD ARE
;        ADDED TOGETHER (INCLUDING THE SS CHECKSUM BYTE) THE
;        RESULT SHOULD BE 0. ANYTHING ELSE IS A CHECKSUM ERROR.
;
; ANY RECORD TYPE BESIDES 00 AND 01 IS INTERPRETED AS AN ERROR.
;
; NOTE: THE RECORD ':0000000000', WHICH IS A 0-BYTE NORMAL DATA
; RECORD, IS ALSO ACCEPTED AS AN END-OF-FILE MARKER. IN FACT,
; ANY 0-BYTE NORMAL DATA RECORD, REGARDLESS OF ADDRESS (BUT WITH
; CORRECT CHECKSUM) WILL BE ACCEPTED AS AN END-OF-FILE MARKER.
;
; NOTE: ANY CHARACTERS THAT OCCUR BEFORE THE INITIAL COLON WILL
; BE IGNORED. THIS ALLOWS FOR CARRIAGE RETURNS AND LINE FEEDS
' TO MAKE THE HEX FILE PRETTY, AS WELL AS ANY COMMENTS, PROVIDED
; THE COMMENTS DO NOT INCLUDE ANY COLONS.
; 
;***************************************************************
SOLOS	EQU	0C000H	;SOLOS BASE ADDRESS

; LOCATIONS IN SOLOS/CUTER 1K RAM BLOCK, OFFSET FROM SOLOS BASE

CUTAB	EQU	083CH	;CUSTOM CMD TABLE, OFFSET FROM SOLOS
USARE	EQU	0AB4H	;USER AREA IN RAM, OFFSET FROM SOLOS

; 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

MAXCU	EQU	6	;MAX # OF CUSTOM COMMAND ENTRIES

; 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
CRLF	EQU	0D0AH	;CR LF
HLCMD	EQU	0484CH	;ASCII H L

;***************************************************************
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 16-BIT 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 RECORD BYTE COUNT INTO C & RESTART CHECKSUM

	CALL	GETBY
	MOV	C,B
	MOV	D,B		;RESET CHECKSUM IN D

; GET RECORD 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
; NOTE THAT E MUST HAVE BEEN 00H OR 80H FROM PRIOR RECORD

	CALL	GETBY

	MOV	A,E		;REMEMBER ECHO STATE
	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		;ALL 0-BYTE RECS ARE TYPE 1
	ORA	E		;PUT EOF BACK
	MOV	E,A

	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 MSG
	JNZ	ERROR	;NZ MEANS CHECKSUM ERROR

; ALL DONE WITH THIS 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 COUNT, RET TO SOLOS

; BAD RECORD TYPE

	MVI	C,'T'	;RECORD TYPE ERROR MSG

;	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 (OR VERSION NUMBER DURING INIT)

	LHLD	RCNT
	CALL	HEX16

	LXI	B,CRLF

; FALL INTO PRTBC TO PRINT CRLF, THEN RETURN VIA 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	HEX8	;H FIRST
	MOV	H,L	;THEN L
;
HEX8:	MOV	A,H	;GET THE CHARACTER
	RRC		;MOVE THE HIGH FOUR DOWN
	RRC
	RRC
	RRC
	CALL	HEX4	;PUT THEM OUT
	MOV	A,H	;THIS TIME THE LOW FOUR
;
HEX4:	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 FROM THE SERIAL PORT,
; 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 MSG
	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 SOLOS CONSOLE IF ECHO IS ENABLED
; ON ENTRY:
;    MSB OF E IS THE CURRENT ECHO STATE - 0 MEANS ECHO
; ON EXIT:
;    CHR IS IN A
;    MSB OF E TOGGLED IF 'E' TYPED
;    ABORT TO SOLOS IF CTRL-C TYPED
;    ALL REGISTERS PRESERVED
;***************************************************************
GETCH:	PUSH	B
GETC1:	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	GETC1

; GOT A CHARACTER. ECHO IT IF ALLOWED

	MOV	B,A		;SOLOS 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 DEAL WITH POTENTIAL KEYBOARD INPUT

CHKBD:	CALL	SOLOS+SINP	;CHECK FOR KBD CHR
	RZ			;NO KBD CHR: DONE

	ANI	7FH		;STRIP PARITY

	CPI	CTRLC		;CONTROL-C TYPED?
	JZ	SOLOS+RETRN	;YES: SOLOS WILL FIX STACK

	CPI	'E'		;ECHO TOGGLE?
	RNZ

; TOGGLE ECHO

	MOV	B,A		;SAVE ECHO CHR FOR MSG

	MOV	A,E		;TOGGLE ECHO
	XRI	80H
	MOV	E,A		

;PRINT TOGGLE MESSAGE

	ANI	80H		;JUST THE ECHO BIT
	RLC			;PUT ECHO BIT IN CARRY AND LSB
	ACI	'+'		;ADD 2 IF NO ECHO: MAKES '-'

	MOV	C,A
	JMP	PRTBC		;PRINT MESSAGE THEN RETURN
	
;***************************************************************
; RAM STORAGE
; THIS PLACED BEFORE THE INSTALLER SO THAT A LARGE SOLOS STACK
; WON'T OVERWRITE THE DATA.
;***************************************************************
RCNT:	DW	VERSION		;COUNT OF RECORDS, INITIALLY VER

;***************************************************************
; COLON-TERMINATED STRINGS
; NOTE THAT VERMS IS ONLY USED DURING INSTALL, SO A LARGE SOLOS
; STACK CAN OVERWRITE IT WITHOUT HARM.
;***************************************************************
RECMS:	DB	CR,LF,'Recs:'
VERMS:	DB	' HL 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 WITHOUT HARM, 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. IF THE HL COMMAND
; ALREADY EXISTS IN THE TABLE, THEN REPLACE IT.


INSTL:	LXI	H,CUTAB+SOLOS-4	;CUST CMD TABLE ADDR
	MVI	B,MAXCU		;MAX CUST CMD ENTRIES
	LXI	D,HLCMD		;2 ASCII CHR COMMAND
	
INST0:	INX	H		;NEXT ENTRY
INST1:	INX	H
	INX	H
	INX	H

	DCR	B		;ANY MORE ENTRIES?
	JZ	INST2		;FULL? OVERWRITE THE LAST ENTRY

	MOV	A,M		;GET FIRST TABLE ENTRY
	ORA	A
	JZ	INST2		;FOUND AN EMPTY ENTRY?

	CMP	D		;FIRST BYTE MATCH?
	JNZ	INST0		;NO: KEEP LOOKING

	INX	H		;CHECK SECOND BYTE
	MOV	A,M
	CMP	E		;SECOND BYTE MATCH?
	JNZ	INST1		;NO: HL ALREADY BUMPED

	DCX	H		;UNBUMP POINTER

; INSTALL HL COMMAND INTO CUSTOM COMMAND TABLE AT HL

INST2:	MOV	M,D
	INX	H
	MOV	M,E
	INX	H
	MVI	M,HEXLD AND 0FFH	;LOW BYTE OF ADDRESS
	INX	H
	MVI	M,(HEXLD/256)		;HIGH BYTE OF ADDRESS

; INSTALL IS COMPLETE. PRINT READY MESSAGE & RETURN TO SOLOS
		
	LXI	H,VERMS		;VERSION MESSAGE
	JMP	PRINT		;PRINT MSG & RETURN TO SOLOS
	
	END
