;===============================================================
; HLOAD:   INTEL HEX LOADER FOR SOLOS/CUTER
;
; BY MARTIN EBERHARD
;
; THIS PROGRAM ADDS A COMMAND 'HLOAD' TO SOLOS OR CUTER. THIS
; NEW COMMAND LOADS INTEL HEX FILES VIA THE SOL (OR 3P+S)
; SERIAL PORT (OR OTHER PSEUDOPORT) INTO RAM AT THE LOAD
; ADDRESSES SPECIFIED IN THE HEX FILE, WITH A POSSIBLE OFFSET.
;
;  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  18MAY2011  TEST FOR EXISTING HL COMMAND, SQUEEZE
;  1.08  22MAY2011  ALLOWS ADDRESS OFFSET VIA RAM VARIABLE
;  1.09  22MAY2011  COMPRESS MESSAGES TO KEEP CODE SHORT
;  2.00  23MAY2011  MOVE INSTALLER TO LOW MEM, RELOCATE CODE
;                   BASED ON SOLOS ADDRESS, DEFAULT ECHO OFF
;  2.01  26MAY2011  IMPROVED RELOCATER, RELOCATE INSTALLER TOO,
;                   ALLOW USER TO CHANGE LOAD PORT,
;                   SQUEEZE CODE TO MAKE IT ALL FIT
;  2.02  30MAY2011  BLOCK USER INPUT IF CONSOLE PORT=LOAD PORT
;
;===============================================================

;***************************************************************
; OPERATION
;
; EXECUTING THIS PROGRAM ONLY INSTALLS THE CUSTOM COMMAND 'HL'
; INTO SOLOS/CUTER. IF SOLOS/CUTER'S CUSTOM COMMAND TABLE IS
; FULL, THE LAST ENTRY WILL BE REPLACED WITH 'HL'.
;
; THIS PROGRAM RUNS JUST ONCE IN THE FIRST 512 BYTES OF MEMORY
; (STARTING AT ADDRESS 0000H). ONCE INSTALLED, THIS PROGRAM NEEDS
; NO MEMORY OTHER THAN A SMALL SLICE OF UNUSED RAM IN SOLOS/
; CUTER'S 1K BLOCK OF MEMORY, CALLED THE USER AREA, WHICH IS
; NORMALLY AT CAB4H.
;
; TO USE THIS NEW COMMAND, TYPE 'HLOAD' (OR 'HL') AT THE '>'
; PROMPT. THE HEX LOADER WILL RESPOND WITH A RECORD COUNT OF 0,
; THEN BEGIN LOOKING AT THE LOAD PORT FOR VALID INTEL HEX
; RECORDS. CONTROL WILL RETURN TO SOLOS WHEN AN EOF RECORD IS
; FOUND, WHEN THE USER TYPES CONTROL-C, OR WHEN AN ERROR IN THE
; INTEL HEX FILE DATA IS DETECTED.
;
; PROGRAM EXIT MESSAGES:
;
;   ?A = ASCII ERROR - NON HEX DIGIT FOUND
;   ?C = CHECKSUM ERROR
;   ?T = RECORD TYPE ERROR
;
;   R:abcd = abcd RECORDS FOUND AND PARSED
;
; THE DEFAULT LOAD PORT IS SOLOS/CUTER'S STANDARD SERIAL PORT,
; WHICH IS PSEUDOPORT 1. IF YOU WANT TO LOAD FROM ONE OF
; SOLOS/CUTER'S OTHER PORTS, CHANGE THE PSEUDOPORT NUMBER THAT
; IS STORED AT LOCATION 'LPORT' IN MEMORY (CAB4H FOR NORMAL
; SOLOS OR CUTER). USE ONE OF THE VALID SOLOS PSEUDOPORT NUMBERS,
; 0-3. DO NOT SET THE LOAD PORT TO BE THE SAME AS THE CONSOLE.
;
; 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 LOAD PORT DATA TO THE
; CONSOLE. THE HEX LOADER WILL RESPOND WITH 'E+' TO INDICATE
; THAT ECHO IS NOW ON, OR 'E-' TO INDICATE THAT ECHO IS NOW OFF.
; ECHO DEFAULTS TO 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. WITH ECHO OFF, 9600 BAUD IS NO PROBLEM.
;
; ECHO-ON IS HANDY FOR DEBUGGING THE LOAD PORT CONNECTION -
; CONNECTOR PINOUT,BAUD RATE, FUNCTIONALITY OF THE TRANSFER
; PROGRAM ON THE OTHER END ETC.
;
; IF YOU WANT TO LOAD THE FILE AT A DIFFERENT ADDRESS THAN THE
; ADDRESSES SPECIFIED IN THE INTEL HEX FILE, YOU CAN SET AN
; OFFSET AT LOCATION 'USROF' IN MEMORY (CAB5H FOR NORMAL SOLOS
; OR CUTER), ONCE THE HEX LOADER COMMAND HAS BEEN INSTALLED.
; THE 16-BIT VALUE OF USROF WILL BE ADDED TO EVERY ADDRESS IN
; THE INTEL HEX FILE. uSE 2'S COMPLIMENT MATH TO SUBTRACT.
;
; IF YOU RESTART SOLOS (E.G. WITH UPPERCASE-REPEAT), THE CUSTOM
; COMMAND TABLE WILL GET CLEARED. YOU CAN RE-INSTALL HL BY
; RUNNING THE CODE AT USARE+3 (CAB7H FOR NORMAL SOLOS). NOTE
; THAT THE SIGN-ON MESSAGE WILL NOT GET PRINTED IF YOU DO THIS.
;
;***************************************************************
; PROGRAM NOTES
;
; THIS PROGRAM IS WRITTEN TO BE ASSEMBLED BY CP/M'S ASM.COM.
;
; THE 'EXECUTE' ADDRESS FOR THIS PROGRAM SHOULD BE THE SAME AS
; THE FILE LOAD ADDRESS, 0000H.THIS PROGRAM REQUIRES 512 BYTES
; OF RAM AT ADDRESS 0. THIS RAM IS NO LONGER NEEDED ONCE
; THE RELOCATER AND MOVER ARE DONE.
;
; THERE ARE 4 MAIN COMPONENTS TO THIS PROGRAM (ALONG WITH ERROR
; HANDLERS AND MESSAGE OUTPUT ROUTINES):
;
; 1. THE RELOCATER, WHICH ADJUSTS THE ADDRESSES IN THE OTHER
;    COMPONENTS BASED ON THE BASE ADDRESS OF SOLOS/CUTER,
;    WHICH CAN BE CHANGED EASILY
;
; 2. THE MOVER, WHICH MOVES THE INSTALLER AND THE HEX LOADER
;    INTO PLACE IN SOLOS/CUTER'S 'USER AREA' RAM
;
; 3. THE INSTALLER, WHICH INSTALLS THE CUSTOM COMMAND 'HL'
;    IN SOLOS/CUTER'S CUSTOM COMMAND TABLE
;
; 4. THE HEX LOADER, WHICH RUNS WHEN THE USER TYPES 'HL' AT
;    THE SOLOS/CUTER PROMPT, AND WHICH LOADS INTEL HEX FILES
;    INTO MEMORY FROM OF SOLOS/CUTER'S PSEUDOPORTS.
;
; THIS PROGRAM ASSUMES SOLOS/CUTER RUNS AT ITS NORMAL LOCATION,
; 0C000H. HOWEVER, THE BASE ADDRESS OF SOLOS/CUTER CAN BE
; CHANGED TO ANY OTHER ADDRESS BY CHANGING THE 16-BIT VALUE OF
; 'BASEA', WHICH IS AT THE FIXED ADDRESS 0003H IN RAM.
;
; WHEN INSTALLATION OF THE HL COMMAND IS DONE, THE INSTALLER
; JUMPS BACK TO SOLOS/CUTER RATHER THAN USING A 'RET'. THIS WAY,
; IF IT IS INVOKED FROM (FOR EXAMPLE) N* DISK, IT WILL JUMP TO
; SOLOS/CUTER, ONCE THE NEW COMMAND HAS BEEN ADDED TO
; SOLOS/CUTER - RATHER THAN RETURNING TO N* DOS.
;
; THE COMPLETE PROGRAM HAS BEEN CRAFTED TO FIT WITHIN 512 BYTES
; IN MEMORY STARTING AT ADDRESS 0000. THUS, IT WILL TAKE UP
; ONLY 2 SECTORS ON A SINGLE-DENSITY N* DISK.
;
; THE HEX LOADER COMMAND 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 (WHEN SOLOS/CUTER
; IS AT ITS NORMAL C000H ADDRESS) AND ENDS AT THE END OF RAM,
; 0CBFFH. HOWEVER, SOLOS'S STACK BEGINS AT 0CBFF, AND GROWS
; TOWARD 0 -  SO IF THE LOADER IS TO LARGE, SOLOS'S STACK WILL
; OVERWRITE IT. WITH VERSION 2.02 OF THIS PROGRAM, THE STACK HAS
; ABOUT 47 BYTES, ALLOWING FOR ABOUT 23 PUSHES - ENOUGH SPACE.
;
; NOTE: SOLOS/CUTER NEEDS ABOUT 22 BYTES OF STACK SPACE. SO THE
; LAST ADDRESS MUST BE BEFORE CBEAH (FOR SOLOS AT C000H).
;
; ALL OF THE ADDRESSES TO BE RELOCATED ARE FLAGGED WITH LABELS
; 'RL00', 'RL01', AND ONWARD. THESE ARE ALWAYS THE ADDRESS
; OF THE INSTRUCTION THAT INCLUDES AN ABSOLUTE ADDRESS - THE
; 16-BIT ADDRESS TO BE RELOCATED IS 1 MORE THAN THE ADDRESS OF
; THE LABEL.
;
; 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.
;
; SOLOS/CUTER PROVIDES NO METHOD FOR PASSING COMMAND LINE
; PARAMETERS TO CUSTOM COMMANDS. THIS MAKES IT DIFFICULT TO
; ENTER THE ADDRESS OFFSET OR ECHO STATE AS COMMAND LINE
; OPTIONS. PERHAPS WE MIGHT DECIDE TO PARSE THE INPUT BUFFER
; (AT ITS ABSOLUTE ADDRESS) FROM THIS PROGRAM IN THE FUTURE?
; EVEN THIS IS TOUGH: CUTER PARKS THE COMMAND LINE IN A RAM
; BUFFER CALLED 'INLIN', BUT SOLOS SEEMS TO PULL THE COMMAND
; LINE DIRECTLY FROM ITS VIDEO MEMORY.
;
;***************************************************************
; 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 = NORMAL 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.
;
; 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 TYPE 0 RECORD, REGARDLESS OF ADDRESS (BUT WITH
; CORRECT CHECKSUM) WILL BE ACCEPTED AS AN END-OF-FILE MARKER.
;
; ANY RECORD TYPE BESIDES 00 AND 01 IS INTERPRETED AS AN ERROR.
;
; NOTE: ANY CHARACTERS THAT OCCUR BEFORE THE INITIAL COLON (OF
; ANY RECORD) 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

IPORT	EQU	0806H	;CURRENT CONSOLE INPUT PORT
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 SOLOS COMMANDS

; 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
COLON	EQU	3AH	;ASCII ':'
CRLF	EQU	0D0AH	;CR LF
HLCMD	EQU	0484CH	;ASCII 'H' 'L'

;***************************************************************
; BEGINNING OF CODE EXECUTION                                  *
;***************************************************************
ORG	0
MAIN:	JMP	MAKHL	;LOAD ADDRESS=EXECUTION ADDRESS

;***************************************************************
; 16-BIT SOLOS BASE ADDRESS - CHANGE TO RELOCATE HEXLD CODE
; THIS SHOULD ALWAYS BE AT ADDRESS 0003H
;***************************************************************
BASEA:	DW	SOLOS	;USER-CHANGEABLE SOLOS BASE ADDRESS

;***************************************************************
; RELOCATER                                                    *
;                                                              *
; RELOCATE CODE USING THE RELOCATE TABLE                       *
;   * THE FIRST TABLE ENTRY IS THE NUMBER OF REMAINING BYTES   *
;     IN THE TABLE. THE REST ARE RELOCATION ADDRESSES.         *
;   * EACH TABLE ENTRY POINTS TO AN ADDRESS IN THE CODE TO BE  *
;     RELOCATED. THIS 16-BIT ADDRESS IN THE CODE GETS ADDED TO *
;     THE VALUE STORED AT 'BASEA', AND THEN GETS PUT BACK INTO *
;     THE CODE.                                                *
;   * EACH ENTRY IS THE LOW BYTE OF AN ADDRESS. ADDRESSES ARE  *
;     IN INCREASING ORDER, AND NONE IS MORE THAN 255 BYTES     *
;     FROM ITS PREDICESSOR. IF THE TABLE ENTRY IS LESS THAN    *
;     THE LOW BYTE OF THE ADDRESS OF THE PREVIOUS TABLE ENTRY, *
;     THEN THE HIGH BYTE GETS INCREMENTED.                     *
;***************************************************************
MAKHL: 	LXI	H,RTABL		;RELOCATION ADDRESS TABLE
	MOV	C,M		;NUMBER OF ADDRESSES TO RELOCATE

	LXI	D,RL00		;INITIAL ADDRESS

; GET TABLE ENTRY - ADDRESS OF LOW BYTE OF ADDRESS TO FIX

RCLUP:	INX	H
	MOV	A,M		;LOW BYTE OF ADDRESS

; SEE IF WE CROSSED A PAGE BOUNDARY - THIS ASSUMES RELOCATION
; ADDRESSES ARE IN INCREASING ORDER AND THAT NONE IS MORE THAN
; 255 BYTES FROM ITS PREDICESSOR

	CMP	E		;LOWER THAN PREVIOUS ADDRESS?
	JNC	RCLP1
	INR	D		;ADDRESS IS IN NEXT PAGE
RCLP1:	MOV	E,A		;DE HAS ADDR OF ADDR IN CODE

; RELOCATE A 16-BIT ADDRESS WITH A 16-BIT OFFSET
; DE=POINTER TO ADDRESS IN CODE

	PUSH	H		;SAVE TABLE POINTER
	PUSH	D		;REMEMBER ADDR FOR NEXT PASS

	XCHG			;MOVE ADDR OF ADDR TO HL

	MOV	E,M		;GET UNRELOCATED LOW BYTE
	INX	H
	MOV	D,M		;HIGH BYTE

	PUSH	H		;SAVE ADDRESS
	LHLD	BASEA		;16-BIT RELOCATION
	DAD	D		;OFFSET IT
	XCHG			;NEW ADDRESS IN DE
	POP	H

	MOV	M,D		;PUT FIXED ADDRESS IN CODE
	DCX	H
	MOV	M,E

	POP	D
	POP	H		;RECOVER TABLE POINTER

; NEXT TABLE ENTRY

	DCR	C
	JNZ	RCLUP

;***************************************************************
; MOVER                                                        *
;                                                              *
; MOVE THE RELOCATED CODE INTO PLACE.                          *
; NOTE THAT THE RELOCATER FIXES THIS CODE TOO.                 *
; SOURCE=HLBEG                                                 *
; DEST ADDRESS=USARE+(BASEA) (THIS GETS CREATED BY RELOCATER)  *
; RL00 *MUST* BE THE FIRST ADDRESS TO BE RELOCATED.            *
;***************************************************************
RL00:	LXI	H,USARE		;RELOCATER WILL FIX THIS ADDRESS

	PUSH	H		;SAVE DEST FOR LATER

	LXI	D,HLBEG		;DE HAS SOURCE ADDRESS
	LXI	B,HLEND-HLBEG	;16-BIT BYTE COUNT

; LOOP TO MOVE CODE

MVLUP:	LDAX	D
	MOV	M,A
	INX	H
	INX	D
	DCX	B
	MOV	A,B
	ORA	C
	JNZ	MVLUP

;***************************************************************
; NOW INSTALL 'HL' CUSTOM COMMAND IN SOLOS'S TABLE.
; THE INSTALLER REMAINS IN USER AREA MEMORY SO HL CAN BE RE-
; INSTALLED AFTER A SOLOS RESTART.
;***************************************************************
RL01:	CALL	INSTL+OFFST

;***************************************************************
; PRINT SIGN-ON MESSAGE
;
; NOTE THAT THIS USES SOME OF THE RELOCATED CODE
;***************************************************************		
	POP	H		;RECOVER BASE ADDRESS=LPORT
	PUSH	H		;SAVE FOR OTHER MESSAGE

	LXI	D,SOMSG		;SIGN-ON MESSAGE
	CALL	PRNTF

;PRINT OFFSET ADDRESS MESSAGE

	POP	H
	INX	H		;POINT TO USEROF

	LXI	D,OSMSG		;OFFSET ADDRESS MESSAGE

; PUSH SOLOS'S WARM-START ADDRESS ONTO STACK FOR RETURN
; (IF WE JUST RETURN, THEN WE WILL RETURN TO N*DOS (NOT CUTER)
; IF WE INSTALL VIA N* DISK.)

RL02:	LXI	B,RETRN		;RETRN WILL FIX STACK
	PUSH	B

; FALL INTO PRINTF, RETURN TO SOLOS

;***************************************************************
; SUBROUTINE TO PRINT 0-TERMINATED STRING AT DE
; FOLLOWED BY 4 HEX DIGITS FOR THE VALUE IN HL
; FOLLOWED BY CRLF
;***************************************************************
PRNTF:	LDAX	D		;GET A CHR

	ORA	A		;NULL TERMINATOR?
RL03:	JZ	H16CR+OFFST	;PRINT 4 HEX DIGITS & CRLF

	MOV	B,A		;FOR SOUT
RL04:	CALL	SOUT
	INX	D
	JMP	PRNTF
	
;***************************************************************
; SIGN-ON MESSAGES
;***************************************************************
SOMSG:	DW	CRLF
	DB	'HLOAD 2.02'
	DW	CRLF
	DB	' HL to start'
	DW	CRLF
	DB	' ^C quits'
	DW	CRLF
	DB	' E toggles echo'
	DW	CRLF
	DB	' Port at '
	DB	0

OSMSG:	DB	' Offset at '
	DB	0

;===============================================================
; THIS IS THE BEGINNING OF THE CODE THAT WILL BE RELOCATED AND
; MOVED INTO SOLOS'S USER AREA.
;===============================================================
HLBEG	EQU	$		;1ST ADDR OF PRE-RELOCATED CODE
OFFST	EQU	USARE-HLBEG	;DISTANCE TO 0-BASED USER AREA

;***************************************************************
; RAM STORAGE
;
; THIS IS PLACED FIRST SO THAT ADDRESSES WILL REMAIN FIXED
; WITH DIFFERENT VERSIONS OF THIS PROGRAM.
;***************************************************************
LPORT:	DB	SERPP	;USER-SET LOAD PORT, DEFAULT=SERIAL
USROF:	DW	0	;USER-SET HEX LOAD ADDRESS OFFSET

;***************************************************************
; INSTALLER                                                    *
;                                                              *
; SUBROUTINE TO INSTALL 'HL' CMD IN SOLOS'S CUSTOM CMD TABLE   *
;                                                              *
; 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.                *
;                                                              *
; THIS SUBROUTINE SHOULD REMAIN HERE, AT USARE+3               *
;***************************************************************
INSTL:	MVI	B,MAXCU		;MAX CUST CMD ENTRIES
	LXI	D,HLCMD		;D,E=2 ASCII CHR COMMAND
RL05:	LXI	H,CUTAB-4	;CUST CMD TABLE ADDR
	
INLUP:	CALL	CHKCU		;LOOP TO FIND TABLE LOCATION
RL06:	JNZ	INLUP+OFFST

; INSTALL HL COMMAND INTO SOLOS CUSTOM COMMAND TABLE AT HL

INST2:	MOV	M,D		;1ST ASCII CHR
	INX	H
	MOV	M,E		;2ND ASCII CHR
	INX	H
RL07:	LXI	D,HEXLD+OFFST
	MOV	M,E		;LOW BYTE OF ADDRESS
	INX	H
	MOV	M,D		;HIGH BYTE OF ADDRESS

	RET

;***************************************************************
; HEX LOADER                                                   *
;                                                              *
; CUSTOM SOLOS COMMAND TO LOAD AN INTEL HEX FILE INTO MEMORY   *
;                                                              *
; REGISTER USAGE DURING HEX LOAD:                              *
; B: SCRATCH                                                   *
; C: RECORD BYTE COUNT                                         *
; D: RECORD CHECKSUM                                           *
; E: RECORD TYPE, MSB SET DISABLES ECHO                        *
; HL: MEMORY ADDRESS AND RECORD COUNT (ON STACK MOSTLY)        *
;***************************************************************
HEXLD:	LXI	H,0		;CLEAR 16-BIT RECORD COUNT
	MVI	E,80H		;DISABLE ECHO

RL08:	CALL	PRNTR+OFFST	;PRINT RECORD COUNT=0

; EAT ALL CHRS UNTIL WE GET RECORD-START COLON
RL09:
GETCO:	CALL	GETCH+OFFST
	CPI	':'
RL10:	JNZ	GETCO+OFFST

; BUMP RECORD COUNT AND PARK IT ON THE STACK

	INX	H		;16-BIT INCREMENT
	PUSH	H		;PUT RECORD COUNT ONTO STACK

; GET RECORD BYTE COUNT INTO C & RESTART CHECKSUM

RL11:	CALL	GETBY+OFFST
	MOV	C,B		;BYTE COUNT
	MOV	D,B		;RESET CHECKSUM

; GET RECORD ADDRESS INTO HL

RL12:	CALL	GETBY+OFFST
	MOV	H,B		;HIGH BYTE OF ADDRESS
RL13:	CALL	GETBY+OFFST
	MOV	L,B		;LOW BYTE OF ADDRESS

; OFFSET ADDRESS, PUT RESULT IN HL

	PUSH	D		;PRESERVE CHECKSUM, TOGGLE STATE
	XCHG			;DE=ADDRESS FROM FILE
RL14:	LHLD	USROF+OFFST
	DAD	D		;HL=ADDRESS WITH OFFSET
	POP	D

; GET RECORD TYPE IN E, PRESERVING MSB=ECHO OFF
; NOTE THAT E MUST HAVE BEEN 00H OR 80H FROM PRIOR RECORD

RL15:	CALL	GETBY+OFFST

	MOV	A,E		;REMEMBER ECHO STATE
	ORA	B		;THIS RECORD TYPE
	MOV	E,A

; TEST FOR 0-BYTE RECORD, WHICH IS AN ALTERNATE EOF
; DON'T LOOP TO GET DATA FOR 0-BYTE RECORDS

	MOV	A,C		;0-BYTE RECORD?
	ORA	A
RL16:	JNZ	BYTLP+OFFST	;NO: GO GET DATA

	INR	A		;MAKE TYPE 0 INTO TYPE 1
	ORA	E		;PUT EOF BACK
	MOV	E,A

RL17:	JMP	GCKSM+OFFST

; LOOP TO GET DATA INTO MEMORY AT HL

RL18:
BYTLP:	CALL	GETBY+OFFST
	MOV	M,B
	INX	H
	DCR	C
RL19:	JNZ	BYTLP+OFFST

; GET AND CHECK RECORD CHECKSUM

GCKSM:	POP	H		;RECOVER RECORD COUNT

RL20:	CALL	GETBY+OFFST	;GET CHECKSUM BYTE
	MVI	C,'C'		;CHECKSUM ERROR MSG
RL21:	JNZ	ERROR+OFFST	;NZ MEANS CHECKSUM ERROR

; ALL DONE WITH THIS RECORD - CHECK RECORD TYPE

	MOV	A,E
	ANI	7FH		;STRIP OFF ECHO BIT, TEST 0
RL22:	JZ	GETCO+OFFST	;YES: GO GET ANOTHER RECORD

	DCR	A		;RECORD TYPE 1 IS EOF
RL23:	JZ	PRNTR+OFFST	;DONE: PRINT RECORD COUNT

; 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
; ON ENTRY:
;    C=ERROR CODE
;    HL=RECORD COUNT
; TRASHES BC, HL	
;***************************************************************
ERROR:	MVI	B,'?'
RL24:	CALL	PRTBC+OFFST

; FALL INTO PRNTR TO PRINT RECORD COUNT

;***************************************************************
; SUBROUTINE TO PRINT THE RECORD-COUNT MESSAGE
; ON ENTRY:
;    HL=RECORD COUNT
;***************************************************************
RL25:
PRNTR:	CALL	PCRLF+OFFST	;INITIAL CRLF

	LXI	B,'R'*256+COLON

RL26:	CALL	PRTBC+OFFST	;ACTUAL MESSAGE

; FALL INTO H16R TO PRINT RECORD COUNT

;***************************************************************
; SUBROUTINE TO PRINT VALUE IN HL AS 4 HEX DIGITS,
; FOLLOWED BY CR LF
; ON ENTRY:
;    HL=RECORD COUNT
;***************************************************************
RL27:
H16CR:	CALL	HEX16+OFFST

; FALL INTO PCRLF FOR FINAL CRLF

;***************************************************************
; SUBROUTINE TO PRINT CR LF ON CONSOLE
;***************************************************************
PCRLF:	LXI	B,CRLF

; FALL INTO PRTBC, THEN RETURN VIA SOLOS

;***************************************************************
; SUBROUTINE TO PRINT B THEN C TO CONSOLE
;***************************************************************
RL28:
PRTBC:	CALL	SOUT
	MOV	B,C
RL29:	JMP	SOUT		;PRINT, RETURN VIA SOLOS

;***************************************************************
; SUBROUTINE TO PRINT HL AS 4 HEX DIGITS ON THE CONSOLE
;***************************************************************
RL30:
HEX16:	CALL	HEX8+OFFST	;H FIRST
	MOV	H,L		;THEN L

; LOCAL SUBROUTINE TO PRINT H AS 2 HEX DIGITS

HEX8:	MOV	A,H		;GET THE CHARACTER
	RRC			;MOVE THE HIGH FOUR DOWN
	RRC
	RRC
	RRC
RL31:	CALL	HEX4+OFFST	;PUT THEM OUT
	MOV	A,H		;THIS TIME THE LOW FOUR

; LOCAL SUBROUTINE TO PRINT A<3:0> AS A HEX DIGIT

HEX4:	ANI	0FH		;FOUR ON THE FLOOR
	ADI	48		;WE WORK WITH ASCII HERE
	CPI	58		;0-9?
RL32:	JC	OUTB+OFFST	;YUP
	ADI	7		;MAKE IT A LETTER

OUTB:	MOV	B,A		;OUTPUT IT FROM REGISTER 'B'
RL33:	JMP	SOUT		;PRINT, RETURN VIA SOLOS

;***************************************************************
; SUBROUTINE TO GET 2 HEX DIGITS FROM THE SPECIFIED PSEUDOPORT,
; 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
;***************************************************************
RL34:
GETBY:	CALL	GETHX+OFFST	;GET HIGH NIBBLE
	RLC			;SHIFT HIGH NIBBLE IN PLACE
	RLC
	RLC
	RLC
	MOV	B,A
RL35:	CALL	GETHX+OFFST	;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 & VALIDATE A HEX DIGIT IN A<3:0>

RL36:
GETHX:	CALL	GETCH+OFFST
	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?
RL37:	JC	AERR+OFFST	;YES: ERROR
	CPI	10H		;ABOVE ASCII F?
	RC			;NO: DONE

; ASCII CHARACTER ERROR - NOT A VALID HEX DIGIT

AERR:	POP	H		;REPAIR STACK: CALL GETHX
	POP	H		;REPAIR STACK: CALL GETBY
	POP	H		;RECOVER RECORD COUNT IN HL
	MVI	C,'A'		;ASCII ERROR MSG
RL38:	JMP	ERROR+OFFST	;PRINT ERROR MESSAGE & EXIT

;***************************************************************
; SUBROUTINE TO GET ONE CHR FROM SPECIFIED PSEUDOPORT
; IGNORE NULLS, STRIP PARITY, AND CHECK 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

; CHECK FOR USER INPUT, UNLESS LOAD PORT=CONSOLE PORT

	PUSH	H
RL39:	LXI	H,LPORT+OFFST	;SPECIFIED PSEUDO PORT

RL40:
GETC1:	LDA	IPORT		;CURRENT CONSOLE PORT
	CMP	M		;NO USER INPUT IF SAME
RL41:	CNZ	CHKBD+OFFST

; CHECK FOR LOAD PORT CHARACTER, SPIN UNTIL WE GET ONE

	MOV	A,M		;RECOVER LPORT
RL42:	CALL	AINP		;A=0 AND Z SET IF NO CHR
	ANI	7FH		;STRIP PARITY, TEST NULL, NO CHR
RL43:	JZ	GETC1+OFFST

	POP	H

; GOT A CHARACTER IN A. ECHO IT IF ALLOWED

	MOV	B,A		;SOLOS WANTS CHR IN B

	MOV	A,E		;ECHO ENABLED?
	RLC

RL44:	CNC	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

RL45:
CHKBD:	CALL	SINP		;CHECK FOR KBD CHR
	RZ			;NO KBD CHR: DONE

	ANI	7FH		;STRIP PARITY

	CPI	CTRLC		;CONTROL-C TYPED?
RL46:	JZ	RETRN		;YES: SOLOS WILL FIX STACK

	CPI	'E'		;ECHO TOGGLE?
	RNZ			;NO: IGNORE ANYTHING ELSE

; TOGGLE ECHO

	MOV	B,A		;SAVE 'E' FOR ECHO MESSAGE

	MOV	A,E		;TOGGLE ECHO
	XRI	80H
	MOV	E,A		

; SET C='+' OR '-' BASED ON MSB OF A

	ANI	80H		;ISOLATE ECHO BIT
	RLC			;PUT BIT IN LSB AND CARRY
	ACI	'+'
	MOV	C,A

; PRINT TOGGLE MESSAGE IN BC

RL47:	JMP	PRTBC+OFFST	;PRINT MESSAGE THEN RETURN

;***************************************************************
; SUBROUTINE TO FIND MATCHING CMD OR END OF CUSTOM CMD TABLE
; ON ENTRY:
;   B=TABLE ENTRIES LEFT TO CHECK
;   D=FIRST BYTE OF DESIRED COMMAND
;   E=SECOND BYTE OF DESIRED COMMAND
;   HL+4 POINTS TO THE CUSTOM COMMAND TABLE ENTRY TO CHECK
; ON EXIT:
;   Z SET MEANS HL POINTS TO PLACE IN TABLE FOR NEW ENTRY.
;   Z CLEARED MEANS KEEP LOOKING, HL READY FOR NEXT TEST.
;***************************************************************
CHKCU:	INX	H		;NEXT ENTRY
	INX	H
	INX	H
	INX	H

	DCR	B		;ANY MORE ENTRIES?
	RZ			;FULL? OVERWRITE THE LAST ENTRY

	MOV	A,M		;GET TABLE ENTRY 1ST BYTE
	ORA	A		;EMPTY ENTRY?
	RZ			;YES: RET WITH Z

	CMP	D		;1ST BYTE MATCH?
	RNZ			;NO: RET WITHOUT Z

	INX	H		;CHECK 2ND BYTE
	MOV	A,M
	DCX	H		;UNBUMP POINTER
	CMP	E		;2ND BYTE MATCH?
	RET			;Z FLAG SET ACCORDINGLY

;===============================================================
; END OF CODE THAT GETS RELOCATED AND MOVED
;===============================================================
HLEND:
	
;***************************************************************
; RELOCATE TABLE
;
; THE FIRST ENTRY IS THE COUNT OF ADDRESSES TO RELOCATE IN THE
; TABLE. ALL SUBSEQUENT ENTRIES ARE THE LOW BYTES OF ADDRESSES
; WITHIN THE CODE, *IN SEQUENTIAL ORDER*. AT THESE ADDRESSES ARE
; 16-BIT ADDRESSES THAT NEED TO BE RECOCATED.
;***************************************************************
RTABL:	DB	RTEND-RTABL		;BYTE COUNT	
	DB	(RL00+1) AND 0FFH
	DB	(RL01+1) AND 0FFH
	DB	(RL02+1) AND 0FFH
	DB	(RL03+1) AND 0FFH
	DB	(RL04+1) AND 0FFH
	DB	(RL05+1) AND 0FFH
	DB	(RL06+1) AND 0FFH
	DB	(RL07+1) AND 0FFH
	DB	(RL08+1) AND 0FFH
	DB	(RL09+1) AND 0FFH
	DB	(RL10+1) AND 0FFH
	DB	(RL11+1) AND 0FFH
	DB	(RL12+1) AND 0FFH
	DB	(RL13+1) AND 0FFH
	DB	(RL14+1) AND 0FFH
	DB	(RL15+1) AND 0FFH
	DB	(RL16+1) AND 0FFH
	DB	(RL17+1) AND 0FFH
	DB	(RL18+1) AND 0FFH
	DB	(RL19+1) AND 0FFH
	DB	(RL20+1) AND 0FFH
	DB	(RL21+1) AND 0FFH
	DB	(RL22+1) AND 0FFH
	DB	(RL23+1) AND 0FFH
	DB	(RL24+1) AND 0FFH
	DB	(RL25+1) AND 0FFH
	DB	(RL26+1) AND 0FFH
	DB	(RL27+1) AND 0FFH
	DB	(RL28+1) AND 0FFH
	DB	(RL29+1) AND 0FFH
	DB	(RL30+1) AND 0FFH
	DB	(RL31+1) AND 0FFH
	DB	(RL32+1) AND 0FFH
	DB	(RL33+1) AND 0FFH
	DB	(RL34+1) AND 0FFH
	DB	(RL35+1) AND 0FFH
	DB	(RL36+1) AND 0FFH
	DB	(RL37+1) AND 0FFH
	DB	(RL38+1) AND 0FFH
	DB	(RL39+1) AND 0FFH
	DB	(RL40+1) AND 0FFH
	DB	(RL41+1) AND 0FFH
	DB	(RL42+1) AND 0FFH
	DB	(RL43+1) AND 0FFH
	DB	(RL44+1) AND 0FFH
	DB	(RL45+1) AND 0FFH
	DB	(RL46+1) AND 0FFH
RTEND:	DB	(RL47+1) AND 0FFH

	
	END
