;================================================================
; POLEX MONITOR EXTENSION FOR THE POLYMORPHIC SYSTEMS POLY-88
;
; POLEX RESIDES IN THE SECOND ROM SOCKET OF A POLY-88 COMPUTER,
; WITH THE STANDARD (REVISION 4.0) POLY-88 MONITOR IN THE FIRST
; ROM SOCKET. THE POLY-88 MONITOR AUTOMATICALLY INVOKES POLEX
; DURING INITIALIZATION.
;
; POLEX RELOCATES ITSELF TO HIGH MEMORY, AND DISABLES THE
; POLY-88 ONBOARD MEMORY, REPLACING IT WITH PHANTOM-ENABLED
; RAM, SO THAT PROGRAMS LIKE CP/M CAN RUN IN THE POLY-88,
; WITHOUT MAJOR MODIFICATIONS TO THE POLY-88.
;
; POLEX PROVIDES BASIC MONITOR COMMANDS FOR EXAMINING AND
; MODIFYING MEMORY, AS WELL AS AN INTEL HEX LOADER.
;
; IMPORTANTLY, POLEX PROVIDES INTERRUPT-DRIVEN CONSOLE AND
; SERIAL PORT ROUTINES FOR E.G. CP/M, WITH STANDARDIZED ENTRY
; POINTS. BOTH TRANSMIT AND RECEIVE SERIAL PORTS HAVE LARGE
; ENOUGH QUEUES TO KEEP UP EVEN WHILE THE POLY-88 CONSOLE IS
; SCROLLING (WHICH IT DOES THE HARD WAY).
;
; POLEX IS EXTENSABLE VIA MODULES (E.G. A DISK MODULE) IN THE
; THIRD ROM SOCKET ON THE CPU BOARD, TO PROVIDE FURTHER
; FUNCTIONALITY.
;
; WHEN POLEX IS RUNNING, THE POLY-88 FRONT PANEL MODE IS
; DISABLED, BECAUSE ITS STACK USAGE BLOWS AWAY PROGRAMS LIKE
; CP/M, WHERE THE STACK MAY BE IN THE LOW 4K OF MEMORY.
;
; THE POLY-88 CASSETTE TAPE INPUT ROUTINES CAN BE ACCESSED
; VIA THE 'PO' COMMAND.
;
; POLEX EXPECTS THE POLY-88 SERIAL PORTS TO BE CONFIGURED IN
; THE STANDARD WAY, WITH THE PRINTER PORT AT "DEVICE ADDRESS"
; 1, AND THE CASSETTE TAPE PORT AT "DEVICE ADDRESS" 0. (DEVICE
; ADDRESSES ARE SET BY JUMPERS ON THE POLY-88'S SERIAL PORT
; BOARDS THAT ATTACH TO THE REAR PANEL OF THE POLY-88 AND
; CONNECT THE ITS CPU BOARD WITH 16-PIN RIBBON CABLES.)
;
; POLEX REQUIRES THE MODS RECOMMENDED IN THE POLY-88 MANUAL,
; IN THE SECTION CALLED 'MEMORY FUNCTIONS OF THE POLY 88 CPU,'
; BUT CORRECTED. SPECIFICALLY, THE FOLLOWING MODS ARE REQUIRED:
;   1) CUT TRACE HH
;   2) JUMPER FROM THE RIGHT SIDE OF HH TO PAD H
;   3) JUMPER FROM S-100 PIN 67 (PHANTOM) TO PAD J
;
; FORMATTED TO ASSEMBLE WITH DIGITAL RESEARCH'S ASM.
;
;================================================================
; POLEX COMMANDS (ALL VALUES ARE IN HEX):
;
; DU <ADR1> <ADR2>       DUMP MEMORY FROM <ADR1> THROUGH <ADR2>
;
; EN <ADR>               ENTER HEX TO MEMORY AT <ADR>
;                        QUIT EN COMMAND WITH A BLANK LINE
;
; EX <ADR>               EXECUTE AT <ADR>
;
; FI <ADR1> <ADR2> <VAL> FILL MEMORY FROM <ADR1> THROUGH <ADR2>
;                        WITH <VAL>
;
; HL [0/1]               LOAD INTEL HEX FILE TO MEMORY.
;			 0=NO ECHO (DISPLAY PACIFIER PER LINE).
;			 1=ECHO INCOMING DATA. DEFAULT (NO PARAM)
;			 IS NO ECHO (REQ'D FOR 9600 BAUD).
;
; PO                     RETURN TO POLY-88 MONITOR LOOP
;
; SB <BAUD>              SET SERIAL BAUD RATE AND PORT
;                        LSD OF <BAUD> SELECTS THE BAUD RATE
;                        MSD OF <BAUD> SELECTS THE PORT
;
;                        BAUD RATES:
;                          0: DISABLED    8: 900 BAUD
;                          1: 50 BAUD     9: 1200 BAUD
;                          2: 75 BAUD     A: 1800 BAUD
;                          3: 110 BAUD    B: 2400 BAUD
;                          4: 134.5 BD    C: 3600 BAUD
;                          5: 150 BAUD    D: 4800 BAUD
;                          6: 300 BAUD    E: 7200 BAUD
;                          7: 600 BAUD    F: 9600 BAUD
;
;                        PORTS:
;                          0: CASSETTE TAPE INTERFACE
;                          1: RS232 PORT
;
; TE                     TERMINAL MODE (ALT-MODE TO EXIT)
;
;================================================================
; INTERFACES
;-------------------------------------------------------------
; POLEX PROVIDES THE FOLLOWING FIXED-LOCATION ENTRY POINTS FOR
; SOFTWARE ACCESS:
;
; PXINIT  (F400)   RESTART POLEX
;
; PXCSTA  (F403)   CONSOLE STATUS
;   A=0 & Z SET IF NOT READY (NO CONSOLE KBD CHR WAITING).
;   A=FF & Z CLEAR IF READY (CONSOLE KBD CHR WAITING)
;   ALL OTHER REGISTERS PRESERVED.
;
; PXCIN   (F406)   CONSOLE INPUT
;   WAIT FOR CONSOLE KBD CHARACTER, AND RETURN IT IN A.
;   ALL OTHER REGISTERS PRESERVED.
;
; PXCOUT  (F409)   CONSOLE OUTPUT
;   PRINT CHARACTER IN C ON CONSOLE. ON RETURN, A=C
;   ALL OTHER REGISTERS PRESERVED.
;
; PXSSTA  (F40C)   SERIAL INPUT PORT STATUS
;   A=0 & Z SET IF NOT READY (THE INPUT QUEUE IS EMPTY).
;   A=FF & Z CLEAR IF READY (THE INPUT QUEUE IS NOT EMPTY)
;   ALL OTHER REGISTERS PRESERVED.
;
; PXSIN   (F40F)   READ FROM SERIAL PORT INPUT
;   WAIT FOR SERIAL PORT CHR, AND RETURN IT IN A.
;   ALL OTHER REGISTERS PRESERVED.
;
; PXSOUT  (F412)   WRITE TO SERIAL PORT OUTPUT
;   SEND CHR IN C TO SERIAL PORT. ON RETURN, A TRASHED
;   ALL OTHER REGISTERS PRESERVED.
;
; PXSOST  (F415)   SERIAL PORT OUTPUT STATUS
;   A=0 & Z SET IF NOT READY (THE OUTPUT QUEUE IS FULL).
;   A=FF & Z CLEAR IF READY (THE OUTUT QUEUE IS NOT FULL)
;   ALL OTHER REGISTERS PRESERVED.
;
; PXBAUD  (F418)   SET SERIAL BAUD RATE AND PORT
;   A=BAUD RATE. (SEE ABOVE TABLE.) TRASHES A.
;
; PXIHEX  (F41B)   GET CONSOLE HEX INPUT
;   SCAN PAST BLANKS, RETURN HEX VALUE IN A
;   ON ENTRY: DE POINTS TO INPUT STRING, TERMINATED BY
;   POLY-88 CURSOR (FFH).
;   ON EXIT: HL=VALUE, DE ADVANCED PAST HEX VALUE, A TRASHED
;   Z  SET, CARRY CLEAR IF NO VALUE FOUND
;   BAD HEX ABORTS TO MAIN.
;
; PXOHEX  (F41E)  PRINT A AS HEX ON THE CONSOLE
;   ALL REGISTERS PRESERVED
;   
; PXWARM  (F421)   WARM-START RE-ENTRY
;   MAIN LOOP - RESTORES STACK, PRINTS PROMPT
;
; PXCIN2  (F424)  GET CONSOLE CHR, IF AVAILABLE
;   Z SET IF NO CHR READY, A=CHR IF READY.
;   'ALT-MODE' KEY ABORTS TO MAIN
;
; PXOFBD (F427)  ENABLE OFFBOARD MEMORY. DO NOT CALL THIS WHEN
;   THE STACK IS IN THE 1ST 4K OF MEMORY. 
;
; PXONBD (F42A)  ENABLE ONBOARD MEMORY. DO NOT CALL THIS WHEN
;   THE STACK IS IN THE 1ST 4K OF MEMORY. 
;
; PTMRISR (F7FA)  THREE BYTE ISR CALLED WHEN THE 60-HZ TIMER
;   OVERFLOWS. INITIALIZED TO "RET". USER CODE CAN MODIFY TO
;   A "JMP USERISR" INSTRUCTION. ISR LOCATION MUST BE >= 1000H.
;   COMPUTER IS "ONBRD", ALL REGISTERS CAN BE USED.
;
; PTIMER  (F7FD)  READ THIS 2-BYTE LOCATION TO GET THE CURRENT
;   VALUE OF THE 60-HZ RTC COUNT-UP TIMER. WRITE THIS
;   LOCATION TO SET THE TIMER.
;
; PTOVFL (F7FF) READ THIS VALUE TO SEE IF THE TIMER OVERFLOWED.
;   <>0 MEANS THAT IT DID OVERFLOW. wRITE 0 TO THIS VALUE TO
;   RESET THE OVERFLOW FLAG.
;
;--------------------------------------------------------------
; POLEX PROVIDES AN INTERFACE FOR AN EXTENSION MODULE LOCATED
; IN ROM AT ADDRESS 0800, WITH COMMAND PROCESSOR EXECUTION AT
; AN ADDRESS PROVIDED BY THE MODULE DURING INITIALIZATION.
;
; INITIALIZATION:
;
; POLEX CHECKS FOR A MODULE ROM AT ADDRESS 0800 DURING ITS
; INITIALIZATION, AND CALLS THIS ADDRESS IF A ROM IS FOUND. THE
; MODULE SHOULD PERFORM ANY REQUIRED INITIALIZATION, (INCLUDING
; RELOCATING ITS COMMAND PROCESSOR INTO RAM) AND RETURN TO
; POLEX WITH THE ADDRESS OF ITS COMMAND PROCESSOR IN HL.
;
; IF A MODULE HAS NO COMMAND PROCESSOR (E.G. A DRIVER MODULE),
; THEN IT SHOULD RETURN H=0. NOTE THAT A MODULE'S COMMAND
; PROCESSOR MUST NOT BE IN THE FIRST 4K OF MEMORY.
;
; COMMAND PROCESSOR:
;
; IF A MODULE ROM WITH A COMMAND PROCESSOR WAS FOUND DURING
; INITIALIZATION, THEN POLEX WILL CALL THE MODULE'S COMMAND
; PROCESSOR (AT THE ADDRESS THAT WAS IN HL WHEN THE MODULE'S
; INITIALIZATION COMPLETED) ON EACH LINE OF USER INPUT, BEFORE
; CHECKING ITS OWN COMMAND LIST.
;
;  ON MODULE COMMAND PROCESSOR ENTRY:
;    HL POINTS TO AN INPUT LINE, WITH THE FIRST BYTE BEING
;    THE FIRST CHARACTER OF A POTENTIAL 2-CHARACTER COMMAND
;
;  ON MODULE COMMAND PROCESSOR EXIT:
;    Z SET IF THE MODULE DID NOT RECOGNIZE THE COMMAND.
;    Z CLEARED IF THE COMMAND WAS TAKEN BY THE MODULE.
;    MODULE COMMANDS MAY ALSO RETURN BY JUMPING TO PXWARM,
;    WHERE THE STACK WILL GET REPAIRED. (THIS MAKES A
;    CONVENIENT ERROR-EXIT.)
;
;================================================================
; NOTES
;
; AFTER ASSEMBLY, YOU MUST CHECK:
; 1)  KBUFF+ROF < SRQ
; 2)  KBUFF < 0800H
; (THESE WILL CREATE ASSEMBLY ERRORS, DUE TO TESTS AT THE END.)
;
; POLEX IS AUTOMATICALLY INVOKED BY THE POLY-88 MONITOR ROM
; (VERSION 4.0), AFTER ITS INITIALIZATION.
;
; POLEX'S INSTALL CODE RELOCATES MOST OF THIS PROGRAM INTO HIGH
; MEMORY, AND THEN SWITCHES OFF THE ON-BOARD MEMORY (WHICH
; SWITCHES ON THE OFF-BOARD MEMORY). THIS WILL ALLOW OPERATING
; SYSTEMS (SUCH AS CP/M) THAT REQUIRE RAM STARTING AT ADDRESS
; 0000 TO RUN ON THE POLY-88.
;
; ALL ADDRESSES WITHIN THE RELOCATED CODE ARE OFFSET BY 'ROF',
; SO THAT THEY ARE CORRECT ONCE RELOCATED. THE ASSEMBLER'S
; LISTING OUTPUT (POLEX.PRN) SHOWS ADDRESSES FOR THE CODE
; BEFORE IT GETS RELOCATED, SO READING THE LISTING IS TRICKY.
;
; THE POLY-88 REAL-TIME CLOCK, KEYBOARD AND SERIAL PORT ARE
; INTERRUPT DRIVEN. POLEX INSTALLS INTERRUPT VECTORS IN ALL 8
; RESTART ADDRESSES IN OFFBOARD RAM. MOST OF THESE INTERRUPTS
; SHOULD NEVER OCCUR, AND JUST JUMP TO THE POLY-88 MONITOR. THE
; RTC, KEYBOARD, AND UART VECTORS ULTIMATELY JUMP TO SERVICE
; ROUTINES IN THE POLEX ROM.
;
; WHEN RUNNING IN ONBOARD MEMORY, THE KEYBOARD AND UART
; INTERRUPT ROUTINES ARE REPLACED BY SUBROUTINES THAT CALL
; THE POLEX SERVICE ROUTINES. THIS RELACEMENT IS VIA THE POLY-88
; 'WORMHOLES'.
;
; NOTE THAT THE RTC INTERRUPT WILL OCCUR 60 TIMES PER SECOND-
; THERE IS NO WAY TO DISABLE IT IN SOFTWARE. THE INTERRUPT
; MUST BE CLEARED (BY WRITING ANYTHING TO THE RTC PORT)
; BEFORE RETURNING FROM THAT INTERRUPT. HOWEVER, (UNLIKE THE
; KEYBOARD AND UART INTERRUPTS), THERE IS NO 'WORMHOLE' THAT
; ALLOWS REPLACEMENT OF THE RTC SERVICE ROUTINE. THIS MEANS
; THAT RTC INTERRUPTS THAT OCCUR WHILE ONBOARD MEMORY IS ENABLED
; WILL NOT BUMP THE TIMER COUNTERS IN POLEX MEMORY, AND THE
; TIMER WILL APPEAR TO BE RUNNING TOO SLOWLY.
;
; POLEX CALLS THE POLY-88 VIDEO DRIVER, DSPLY (IN ONBOARD
; MEMORY), FOR ITS CONSOLE OUTPUT. ON-BOARD MEMORY IS SWAPPED
; IN AND OUT AS REQUIRED. INTERRUPTS ARE ENABLED DURING DSPLY,
; BECAUSE IT TAKES MANY 9600 BAUD CHARACTER TIMES TO SCROLL
; THE SCREEN.
;
; THE POLEX UART ROUTINES HAVE BIG ENOUGH QUEUES THAT SERIAL
; TRANSFER CAN OCCUR WITHOUT HANDSHAKING, EVEN DURING POLY-88
; VIDEO SCROLLING (WHICH IT DOES THE HARD WAY). (AT 9600 BAUD,
; THE SERIAL TRANSFER CAN ONLY KEEP UP IF THE SENDER SENDS 2
; STOP BITS WITH EACH CHARACTER, OR IF A 5 mS MINIMUM DELAY
; IS INSERTED AFTER EACH LINE.)
;
; NOTE THAT THE UART ON THE CPU CARD IS ONLY READABLE WHILE
; ONBOARD MEMORY IS ENABLED VIA THE BRG PORT. HOWEVER, wRITING
; TO THE UART WORKS WHETHER OR NOT ONBOARD MEMORY IS ENABLED.
;
; POLEX DOES NOT SUPPORT BAUD RATES ABOVE 9600 BAUD, WHICH
; REQUIRE USE OF THE DIVIDE-BY-ONE CLOCK DIVIDER. THE 8251
; (PLAIN) UART DOES NOT DO WELL WITH THESE BAUD RATES.
;
; THE POLY-88 FRONT PANEL MODE IS DISABLED BECAUSE OF ITS
; EXCESSIVE STACK USAGE, AND BECAUSE IT IS INCOMPATIBLE WITH
; PROGRAMS (SUCH AS CP/M) THAT LOCATE THE STACK IN THE FIRST
; 4K OF RAM.
;
; CP/M AND CP/M PROGRAMS MAY HAVE LIMITED STACK SPACE FOR USE BY
; CALLS TO I/O ROUTINES, AND FOR USE BY INTERRUPTS. POLEX IS
; WRITTEN TO MINIMIZE STACK USAGE FOR CP/M COMPATIBILITY. WORST-
; CASE STACK USAGE OCCURS WHEN A UART INTERRUPT COMES WHILE IN
; ONE OF THE I/O ROUTINES THAT MAY BE CALLED BY CP/M:
;
; BYTES   SOURCE
;  2      RETURN ADDRESS FOR CP/M'S I/O ROUTINE CALL
;  2      WORST-CASE POLEX I/O ROUTINE (WHILE INTS ENABLED)
;  2      INTERRUPT RETURN ADDRESS
;  2      PUSH H IN VECTOR
;  2      PUSH PSW IN VECTOR
;
; 10 BYTES TOTAL
;
; NOTE ALSO THAT CALLING PROGRAMS (SUCH AS CP/M) MAY HAVE
; THEIR STACKS IN THE 1ST 4K OF MEMORY, WHICH WILL DISAPPEAR
; WHEN WE SWITCH TO ONBOARD MEMORY. THEREFORE A LOCAL STACK
; IS SWAPPED IN FOR INTERRUPTS AND FOR CALLS TO DSPLY, BOTH
; OF WHICH USE ONBOARD CODE.
;
; NOTE THAT OKINT AND OUINT WILL GET CALLED ONLY WHEN AN
; INTERRUPT OCCURS WHILE ONBOARD MEMORY IS ENABLED. WHILE CP/M
; IS RUNNING, THIS WILL ONLY HAPPEN DURING CALLS TO PXCOUT,
; WHICH USES THE POLY-88 ROM'S DISPLAY DRIVER, DSPLY. PXCOUT
; SWAPS IN A LOCAL STACK BEFORE ENABLING ONBOARD MEMORY.
;================================================================
; REVISION HISTORY
; VERS. 1.00 BY M. EBERHARD 30 OCT 2013
;   CREATED
;
; VERS. 1.01 BY M. EBERHARD 22 NOV 2013
;   ASSEMBLY OPTION (NOT SELECTED) TO MOVE THE DISK CONTROLLER
;   TO THE STANDARD MICROPOLIS ADDRESS F800 AND MOVE VIDEO
;   MEMORY TO FC00. ADJUST I/O ROUTINES & ENTRY POINTS TO MATCH
;   CP/M NEEDS.
;
; VERS. 1.02 BY M. EBERHARD 23 NOV 2013
;   REPLACE KBD INT IN ONBOARD RAM TO SUBSTITUTE CONTROL-A
;   FOR CONTROL-Z, SINCE CP/M USES CONTROL-Z
;
; VERS. 2.00 M. EBERHARD 23 NOV 2013
;   SPLIT OFF MICMOD ROM, MOVING ALL DISK COMMANDS THERE
;   ADD SERIAL PORT SUPPORT & VECTORS
;
; VERS. 2.01 M. EBERHARD 4 DEC 2013
;   ADD TE COMMAND (SO THE SERIAL PORT CAN BE TESTED)
;   DEBUG SERIAL PORT INTERRUPT CODE
;
; VERS. 2.02 M. EBERHARD 6 DEC 2013
;   USE MODULE EXEC ADDRESS RETURNED DURING INIT (SO A MODULE
;   COULD RELOCATE ITSELF ANYWHERE)
;
; VERS. 3.00 M. EBERHARD 16 DEC 2013
;   ELIMINATE SUPPORT FOR FRONT PANEL MODE, REDUCE STACK USAGE
;   (INCLUDING IN INTERRUPTS) FOR CP/M AND OTHER PROGRAMS WITH
;   SMALL STACKS. ELIMINATE ECHO-CONTROL DURING HEX LOAD.
;
; VERS. 3.01 M. EBERHARD 19 DEC 2013
;   FIX FOR INTERRUPTED PROGRAMS WITH STACKS IN THE 1ST 4K OF
;   MEMORY (WHICH DISAPPEAR WITH ONBRD).
;
; VERS. 3.02 M. EBERHARD 20 DEC 2013
;   ADD RTC COUNT-DOWN TIMER
;
; VERS. 3.03 M. EBERHARD 24 DEC 2013
;   SOME CODE SQUEEZING, DELETE HIGHER BAUD RATE SUPPORT IN
;   STBAUD, ALLOW SERIAL PORT (CASSETTE VS PRINTER) TO BE
;   SELECTED BY STBAUD/PXBAUD. ADD ADDRESS TO EACH LINE IN EN
;   COMMAND. CHANGE TIMER TO COUNT UP, AND ADD OVERFLOW FLAG.
;   TRANSLATE BS TO DEL IN CPRINT. PROVIDE EXTERNAL ACCESS TO
;   ONBRD AND OFFBRD SUBROUTINES.
;
; VERS. 3.04 M. EBERHARD 03 JUN 2014
;   REV F POLY-88 CPU BOARDS HAVE THE ROM DISABLE BIT (BRGOMD)
;   AT BIT 6 OF THE BRG PORT, INSTEAD OF BIT 5 (AS ON REV 0
;   BOARDS). THIS VERSION SUPPORTS BOTH. NOTE THAT BIT 7 OF
;   THIS PORT SEEMS TO DO SOMETHING ON THE REV F BOARD: WRITING
;   1 TO THIS BIT HANGS POLEX.
;
; VERS. 3.05 M. EBERHARD 26 APR 2016
;   STRIP KEYBOARD PARITY, ACCEPT ESC AS COMMAND ABORT KEY TOO.
;   USE ASSEMBLER TO CHECK FOR KBUFF SIZE/PLACEMENT ERRORS.
;   EX COMMAND DEFAULTS TO ADDRESS 0 (TO SAVE CODE SPACE).
;
; VERS. 3.06 M. EBERHARD 27 APR 2016
;   SQUEEZE CODE, CONVERT LOWERCASE ON COMMAND LINE TO UPPER-
;   CASE
;
; VERS. 3.07 M. DOUGLAS & M. EBERHARD 12 JUNE 2016
;   USE THE RTC INTERRUPT TO ENABLE THE UART TRANSMITTER SO
;   THAT MULTI-CHARACTER TRANSMISSIONS HAVE A CHANCE TO QUEUE
;   AND, IN TURN, PREVENT TRANSMITTER SHUTDOWN BETWEEN EACH
;   CHARACTER. MOVE EI FROM THE CHARACTER I/O SUBROUTINES INTO
;   THE CORRRESPONDING xxSTAT SUBROUTINES. THIS ENSURES EXTERNAL
;   CALLS TO xxSTAT BEHAVE AS EXPECTED. HEX LOAD MODIFIED TO
;   SQUEEZE CODE. RECORD TYPE AND ASCII TO HEX CONVERSION ARE
;   NO LONGER CHECKED. RECORD CHECKSUM REMAINS. UPDATE CODE
;   SIZE CHECKS AT THE END OF THIS FILE. CONVERT KBD BS TO DEL.
;   TRANSLATE BS TO DEL FOR TE COMMAND TOO.
;
; VERS. 3.08 M. EBERHARD 15 JUNE 2016
;   SPEED UP HL A TAD. DON'T ECHO SERIAL DATA DURING HL IF THE
;   RX QUEUE IS NEARLY FULL. (MAKES 9600 BAUD MOSTLY WORK.)
;
; VERS. 3.09 M. EBERHARD 18 JUNE 2016
;   BACK OUT ECHO LOGIC FROM 3.08. ADD ECHO PARAMETER TO HL, SO
;   THAT "HL 0" WILL PRINT A PACIFIER PER RECORD, RATHER THAN
;   ECHOING THE RECEIVED DATA. (REQUIRED FOR 9600 BAUD.)
;
; VERS. 3.10  M. EBERHARD 22 JUNE 2016
;   INITIALIZE POLY-88'S POS VARIABLE DURING INIT.
;
; VERS. 3.11  M. DOUGLAS & M. EBERHARD 11 NOV 2019
;   CALL PTMRISR WHEN PTIMER OVERFLOWS. PTMRISR IS A THREE BYTE
;   FIXED LOCATION VARIABLE. THE FIRST BYTE IS INITIALIZED TO
;   A RET INSTRUCTION. USER CODE CAN MODIFY PTMRISR TO INSTEAD
;   JUMP TO A USER ISR. ISR LOCATION MUST BE >= 1000H. COMPUTER
;   IS "ONBRD" UPON ISR ENTRY. ALL REGISTERS CAN BE USED.
;   CHANGED THE HL COMMAND TO DEFAULT TO NO ECHO.
;================================================================
;**********
;DEBUG FLAG
;**********
DEBUG	EQU	0	;1: "PROM" IS IN RAM AT E000

;*****
;ASCII
;*****
BS	EQU	08H	;BASKSPACE
TAB	EQU	09H	;TAB. MOVES CURSOR TO EVEN/8 POS
VT	EQU	0BH	;VERTICAL TAB. JUST HOMES CURSOR.
FMFD	EQU	0CH	;FORM FEED. CLEARS SCRN, HOMES CURSOR
CR	EQU	0DH
CTLX	EQU	18H	;ASCII "CAN" CLEARS CUR. LINE
CTLZ	EQU	1AH	;CONTROL-Z ENTERS FRONT PANEL MODE
ESC	EQU	1BH	;ESCAPE
DEL	EQU	7FH	;DELETE
CURSOR	EQU	0FFH	;POLY-88 CURSOR

;***************
;PROGRAM EQUATES
;***************
PROMPT	EQU	'>'	;PROMPT CHARACTER
CABKEY	EQU	'}'	;COMMAND ABORT CHARACTER. NOTE: CODE
			;..ASSUMES THIS IS > ASCII'z'.
			;(POLY-88 ALT-MODE KEY)
PAUKEY	EQU	' '	;PAUSES DUMPING IN DU COMMAND
PCFIER	EQU	'.'	;HL PACIFIER FOR WHEN ECHO DISABLED

MAXBD	EQU	1CH	;MAX ALLOWED BAUD RATE

DEBAUD	EQU	1FH	;DEFAULT 9600 BAUD, PRINTER PORT

;*******************************************
;CODE LOCATIONS BEFORE & AFTER CODE IS MOVED
;*******************************************
 IF DEBUG
ROMLOC	EQU	0E000H	;PROM IN RAM FOR DEBUG
 ENDIF

 IF NOT DEBUG
ROMLOC	EQU	0400H	;LOCATION IN ROM
 ENDIF

RAMLOC	EQU	0F400H	;LOCATION AFTER MOVED
MODROM	EQU	00800H	;ADDR OF OPTIONAL ROM MODULE

;******
;STACKS
;******
VSSIZE	EQU	30	;LOCAL STACK SPACE FOR PRINTC	
			;ROOM FOR AT LEAST 28 BYTES

ISTACK	EQU	0FF8H	;INTERRUPT STACK IN ONBOARD RAM

;************************
;FIXED-LOCATION VARIABLES
;************************
FXDRAM	EQU	RAMLOC+3F7H

;********************************************************
;UART QUEUES AND POINTERS
;
;NOTE THAT THE CODE MAKES SIGNIFICANT ASSUMPTIONS ABOUT
;THE SIZE AND ALIGNMENT OF THE SERIAL PORT QUEUES IN RAM.
;WITH SRQSIZ=32, RQLOC MUST BE XXXX XXXX XX00 0000B
;WITH STQSIZ=8, TQLOC MUST BE XXXX XXXX XXXX 0000B
;********************************************************
SRQSIZ	EQU	32		;RECEIVE QUEUE SIZE
				;MUST BE POWER OF 2
STQSIZ	EQU	4		;TRANSMIT QUEUE SIZE
				;MUST BE POWER OF 2

RQLOC	EQU	RAMLOC+380H	;SERIAL RX QUEUE LOCATION
TQLOC	EQU	RAMLOC+3A0H	;SERIAL TX QUEUE LOCATION

;**************************************
;POLY-88 CPU BOARD I/O PORT ASSIGNMENTS
;**************************************
UARTD	EQU	00H	;SERIAL DATA
UARTS	EQU	01H	;SERIAL STATUS PORT
UTXRDY	  EQU	  01H	  ;TRANSMITTER READY
URXRDY	  EQU	  02H	  ;RECEIVER READY
UTXMTY	  EQU	  04H	  ;TRANSMITTER EMPTY
UMOD16	  EQU	  4EH	  ;8 BITS, 1 STOP, NO PARITY, X16
UMOD01	  EQU	  4DH	  ;8 BITS, 1 STOP, NO PARITY, X1
UCMDRE	  EQU	  40H	  ;RESET UART
UCRXEN	  EQU	  26H	  ;ENABLE RX, DTR, RTS ON
UCMDEN	  EQU	  27H	  ;ENABLE TX & RX, DTR, RTS ON

BRG	EQU	04h	;BAUD RATE GENERATOR PORT
BRGBD	  EQU	  0Fh	  ;BAUD RATE MASK
BRGDEV	  EQU	  10h	  ;0=CASSETTE, 1=RS232
BRGOMD	  EQU	  60h	  ;1=DISABLE ONBOARD MEMORY
			  ;REV 0 BOARDS USE BIT 5
			  ;REV F BOARDS USE BIT 6

RTC	EQU	08H	;OUTPUT ANYTHING TO THIS PORT
			;..TO RESET RTC INTERRUPT

;**********************************
;POLYMORPHIC VIDEO BOARD PARAMETERS
;**********************************
VBASE	EQU	0F800H	;STANDARD VIDEO MEM ADDR
VDMEM	EQU	VBASE	;BEGINNING OF VIDEO MEMORY
VDROW	EQU	40H	;64 BYTES/ROW
VDSIZ	EQU	0400H	;1K SCREEN BUFFER

KBD	EQU	0F8H	;KEYBOARD DATA

;*************************
;POLY-88 MONITOR LOCATIONS
;*************************

;ROM ROUTINES
USRTSR	EQU	0054H	;USART INTERRUPT SERVICE
KSR	EQU	010DH	;KEYBOARD INTERRUPT SERVICE
IORET	EQU	0064H	;SHARED EXIT FOR INT INTS
DSPLY	EQU	007FH	;FAMOUS DISPLAY ROUTINE

;RAM LOCATIONS

TIMER	EQU	0C00H	;4-BYTE RTC COUNTER
POS	EQU	0C0EH	;CURSOR POSITION
SRA4	EQU	0C16H	;UART IN VECTOR
SRA5	EQU	0C18H	;KBD INT VECTOR
POLYSP	EQU	0FFEH	;STACK POINTER FOR RETURN TO
			;POLY-88 ROM. ASSUMES RETURN ADDRESS
			;IS ON THE POLY-88 STACK

;==============================================================
; START-UP ROM CODE
; THE POLY-88 MONITOR CALLS INSTAL WHEN ITS OWN INITIALIZATION
; IS COMPLETE. THIS ROUTINE INSTALLS POLEX IN RAM AT RAMLOC AND
; THEN JUMPS TO THE NEWLY-INSTALLED CODE. 
;==============================================================
	ORG	ROMLOC

INSTAL:	DI

;INSTALL ONBOARD INTERRUPT VECTORS FOR NEW KEYBOARD AND UART
;INTS, IN CASE WE GET AN INTERRUPT WHILE ONBOARD

 IF DEBUG
	CALL	ONBRD			;REAL PROM IS ONBRD HERE
 ENDIF

	LXI	H,OKINT
	SHLD	SRA5
	MVI	L,OUINT AND 0FFH	;IN SAME PAGE
	SHLD	SRA4

;THE POLY88 MONITOR DOES NOT INITIALIZE POS, ITS CURSOR POSITION.
;THE FIRST CALL TO DSPLY WILL CAUSE 7FH TO BE WRITTEN TO WHATEVER
;RANDOM ADDRESS POS POINTS TO. SO WE POINT POS TO A HARMLESS
;ADDRESS HERE.

	LXI	H,RAMEND-RAMSRC+RAMLOC	;END OF DEST
	SHLD	POS			;POINT POS PAST CODE

;MOVE CODE INTO RAM, BACKWARDS, SO HL IS RIGHT AT THE END

	LXI	B,RAMEND-RAMSRC		;BYTE COUNT
	LXI	D,RAMEND		;END OF RAM SOURCE

MOVER:	DCX	D
	DCX	H

	LDAX	D			;GET A BYTE
	MOV	M,A			;PUT A BYTE

	DCX	B			;BUMP COUNT
	MOV	A,B
	ORA	C			;DONE?
	JNZ	MOVER

;HL = EXECUTION ADDRESS
;GO EXECUTE MOVED CODE

	PCHL			;GO TO LOADED CODE

;==============================================================
; ROM SUBROUTINES THAT DON'T GET RELOCATED INTO RAM
; THESE ROUTINES RUN IN ROM, AFTER ONBRD HAS BEEN CALLED
;==============================================================

;***ROM INTERRUPT SERVICE ROUTINE**************
; REPLACEMENT CODE FOR KEYBOARD INTERRUPT
; WHILE ONBOARD MEMORY IS ENABLED, KEYBOARD
; INTERUPTS EXECUTE HERE.
; ON ENTRY:
;   ALL REGISTERS ARE ON THE STACK
; ON EXIT:
;   INTERRUPTS ENABLED
;**********************************************
OKINT:	CALL	KINT		;MUST CALL, SO WE CAN
	JMP	IORET		;RESTORE REGS, ENABLE
				;..INTS. RET

;***ROM INTERRUPT SERVICE ROUTINE**********************
; REPLACEMENT CODE FOR SERIAL INTERRUPT
; WHILE ONBOARD MEMORY IS ENABLED, UART INTERRUPTS 
; EXECUTE HERE. INTERRUPTS ARE ENABLED ON EXIT, IN
; CASE WE GET MORE THAN ONE INTERRUPT WHILE ONBOARD,
; WHICH HAPPENS DURING DSPLY SCROLLING.
; ON ENTRY:
;   ALL REGISTERS ARE ON THE STACK
; ON EXIT:
;   INTERRUPTS ENABLED
;******************************************************
OUINT:	CALL	UINT		;MUST CALL, SO WE CAN
	JMP	IORET		;RESTORE REGS, ENABLE
				;..INTS. RET

;***ROM INTERRUPT SERVICE ROUTINE*********************
; RTC INTERRUPT SERVICE
; INCREMENTS 2-BYTE 60-HZ TIMER IN RAM. UPON OVERFLOW,
; PTOVFL IS SET TO NON-ZERO AND PTMRISR IS CALLED.
; INTERRUPTS STILL DISABLED ON EXIT
;
; ENABLES UART TRANSMITTER IF CHARACTERS ARE QUEUED
; AND THE TRANSMITTER IS PRESENTLY DISABLED.
;
; NOTE THAT RTC INTERRUPTS WHEN ONBRD WILL BE MISSED.
; WHEN RUNNING AN OS (E.G. CP/M), THIS WILL OCCUR
; OCCASIONALLY WHEN CALLING PXCOUT.
;*****************************************************
RINT:	OUT	RTC		;CLEAR INTERRUPT

;ENABLE UART XMIT IF TX QUEUE NOT EMPTY, BUT UART XMIT DISABLED

	LXI	H,STQCNT+ROF	;TRANSMIT QUEUE SIZE
	MOV	A,M		;ANY XMIT DATA QUEUED?
	DCX	H		;(URTDIS) XMIT DISABLED?
	ANA	M		;FF MEANS DISABLED BUT ENQUEUED
	JZ	DOTIMR		;EMPTY OR NOT DISABLED?

	MVI	A,UCMDEN	;ENABLE XMIT
	OUT	UARTS

	INR	M		;FF -> 0: REMEMBER XMIT ENABLED

;INCREMENT 60HZ COUNTER/TIMER

DOTIMR:	LXI	H,PTIMER 	;BUMP 16-BIT COUNTER
	INR	M
	RNZ

	INX	H		;MS BYTE
	INR	M
	RNZ

	INX	H		;POINT TO PTOVFL
	MOV	M,L		;L=FF: INDICATE OVERFLOW

	JMP	PTMRISR		;CALL USER ISR AND EXIT

;***ROM INTERRUPT SERVICE ROUTINE****
; KEYBOARD INTERRUPT SERVICE
; ELIMINATES FRONT PANEL ACCESS
; INTERRUPTS STILL DISABLED ON EXIT
; TRASHES PSW
;************************************
KINT:	IN	KBD
	STA	KBUFF+ROF	;SAVE KBD CHR
	RET

;***ROM INTERRUPT SERVICE ROUTINE*****
; COMBINED UART INTERRUPT SERVICE
; INTERRUPTS STILL DISABLED ON EXIT
; TRASHES PSW, HL
;*************************************
UINT:	LXI	H,SRQCNT+ROF	;RECEIVE QUEUE SIZE

	IN	UARTS		;GET UART STATUS
	ANI	URXRDY		;RECEIVE INTERRUPT?
	JNZ	URXINT		;Y: GO RECEIVE

;NOT A RECIEVE INTERRUPT, SO MUST BE TRANSMIT

;FALL INTO UTXINT

;***ROM ROUTINE****************************
; UART TRANSMIT INTERRUPT SERVICE
; SEND A QUEUE CHR. IF THE QUEUE IS EMPTY,
; THEN DISABLE THE TRANSMITTER TO DISABLE
; FURTHER TRANSMIT INTERRUPTS.
; ON ENTRY:
;   HL=ADDRESS OF SRQCNT, WHICH IS STQCNT+1
;******************************************
UTXINT:	DCX	H		;POINT TO STQCNT
	MOV	A,M		;ANYTHING IN THE QUEUE?
	ORA	A

	JZ	WTXMTY		;N:SHUT IT DOWN

	DCR	M		;ONE FEWER BYTE IN QUEUE

;GET A QUEUE CHR AND SEND IT

	LHLD	STQOPT+ROF
	MOV	A,M		;GET QUEUE CHR
	OUT	UARTD		;AND TRANSMIT IT

;CIRCULAR-INC STQOPT. ASSUME SUPER-NICE QUEUE ALIGNMENT

	INR	L
	MVI	A,STQSIZ XOR 0FFH
	ANA	L
	STA	STQOPT+ROF	;H NEVER CHANGES

	RET

;WAIT FOR THE 8251 TRANSMITTER TO EMPTY
;OTHERWISE THE LAST CHR WILL BE CHOPPED OFF

WTXMTY:	IN	UARTS
	ANI	UTXMTY
	JZ	WTXMTY	

	JMP	UTXDIS+ROF	;GO DISABLE THE INTERRUPT

;***ROM ROUTINE****************************
; UART RECEIVE INTERRUPT SERVICE
; RECEIVE AND ENQUEUE A UART CHR.
; IF THE QUEUE IS FULL, CHUCK THE CHR.
; ON ENTRY:
;   HL=ADDRESS OF SRQCNT
;******************************************
URXINT:	MOV	A,M		;CHECK SRQCNT
	CPI	SRQSIZ		;ROOM IN QUEUE?

	IN	UARTD		;RECEIVE CHR

	RZ			;NO ROOM: DONE
	
	INR	M		;BUMP Q CHR COUNT

	LHLD	SRQIPT+ROF	;RX Q INPOINTER
	MOV	M,A		;ENQUEUE NEW CHR

;CIRCULAR-INC SRQIPT. ASSUME SUPER-NICE QUEUE ALIGNMENT

	INR	L
	MVI	A,SRQSIZ XOR 0FFH
	ANA	L
	STA	SRQIPT+ROF	;H NEVER CHANGES

	RET

;==============================================================
; RAM CODE, WHICH GETS MOVED INTO RAM BY THE START-UP CODE.
; "ROF" IS THE RAM ADDRESS OFFSET, WHICH IS THE DIFFERENCE
; BETWEEN ITS ADDRESS WHILE IN ROM AND ITS ADDRESS WHEN
; INSTALLED IN RAM.
;==============================================================

RAMSRC:
ROF	EQU	RAMLOC-RAMSRC	;RAM OFFSET

;*********************
; POLEX ENTRY POINTS *
;*********************

PXINIT:	JMP	INIT+ROF	;COLD-START SYSTEM ENTRY POINT

;THE FOLLOWING 7 ENTRY POINTS ARE CALLED BY CP/M,
;AND HAVE BEEN DESIGNED WITH MINIMAL STACK USAGE.

PXCSTA:	JMP	KSTAT+ROF	;FOR CP/M: TEST CONSOLE KBD
				;Z SET, 0 MEANS NO DATA
				;Z CLEAR, A=FF IF DATA AVAIL
PXCIN:	JMP	KDATA+ROF	;FOR CP/M: GET CONSOLE CHR IN A
PXCOUT:	JMP	PRINTC+ROF	;FOR CP/M: SEND C TO CONSOLE
				;A=C=CHR ON RET

PXSSTA:	JMP	SISTAT+ROF	;FOR CP/M: TEST UART INPUT
				;Z SET, A=0 IF NO DATA
				;Z CLEAR, A=FF IF DATA
PXSIN:	JMP	SIDATA+ROF	;FOR CP/M: GET UART CHR IN A
PXSOUT:	JMP	SODATA+ROF	;FOR CP/M: SEND C TO UART
PXSOST:	JMP	SOSTAT+ROF	;FOR CP/M: GET UART OUTPUT STAT
				;A=0 & Z SET IF NOT READY
				;A=FF & Z CLEAR IF READY

PXBAUD:	JMP	STBAUD+ROF	;SET UART BAUD RATE & PORT

PXIHEX:	JMP	FNDHEX+ROF	;SCAN PAST BLANKS, GET HEX VAL
				;HL=VALUE
				;DE ADVANCED PAST CHR
				;Z SET, CARRY CLEAR IF VALUE
				;CARRY SET IF NO VALUE FOUND
PXOHEX:	JMP	PAHEX+ROF	;PRINT A AS HEX ON THE CONSOLE
PXWARM:	JMP	MAIN+ROF	;WARM-START RE-ENTRY
PXCIN2:	JMP	CHKKBD+ROF	;GET KBD CHR IF ONE IS THERE
PXOFBD:	JMP	OFFBRD+ROF	;SELECT OFFBOARD MEMORY			
PXONBD:
;FALL INTO ONBRD

;***EXTERNAL SUBROUTINE*******************************
; SELECT ONBOARD MEMORY
; NOTE: YOU CAN'T CALL THIS SUBROUTINE IF THE STACK IS
; (OR MIGHT BE) IN THE 1ST 4K OF MEMORY, AS THE STACK
; WILL DISAPPEAR WHEN SWITCHING ONBOARD.
;*****************************************************
ONBRD:	PUSH	PSW
	LDA	BRGSAV
	ANI	BRGOMD XOR 0FFH	;ONBOARD MEMORY

	OUT	BRG
	POP	PSW
	RET

;***INTERRUPT SERVICE ROUTINE**********************************
; DISPATCH TO SPECIFIC INTERRUPT ROUTINE
; ON ENTRY FROM A VECTOR:
;   INTS ARE DISABLED VIA THE INTERRUPT
;    (SP) = ORIGINAL HL
;  (SP+2) = ORIGINAL PSW
;  (SP+4) = RETURN ADDRESS FOR INTERRUPTED PROGRAM
;
; THIS SWITCHES TO A PRIVATE STACK, BECAUSE THE INCOMING STACK
; MIGHT BE IN THE 1ST 4K OF RAM, WHICH DISAPPEARS WITH ONBRD.
;
; UPON 'RET' TO THE INTERRUPT SERVICE ROUTINE:
;  STACK IS NOW ISTACK, IN ONBOARD RAM
;  (SP) = ISRRET+ROF FOR RETURN FROM THE ISR
;  (SP+2) = ORIGINAL SP VALUE
;  HL, PSW SAVED ON INCOMING STACK
;  ALL OTHER REGISTERS UNCHANGED
;  INTERRUPTS STILL DISABLED FROM THE INTERRUPT
;  ONBOARD MEMORY ENABLED
;
; INTERRUPTS REMAIN DISABLED UNTIL ISRRET, SO THAT WE WON'T GET
; ANY INTERRUPTS WHILE ONBOARD SERVICING ANOTHER INTERRUPT.
;**************************************************************
DSPTCH:	LDA	BRGSAV
	ANI	BRGOMD XOR 0FFH	;ONBOARD MEMORY
	OUT	BRG		;..NOW

	SHLD	ISTACK-6	;ISR ADDR ONTO INT STACK
				;AT (ISTACK-5) & (ISTACK-6)

	LXI	H,0		;GET INCOMING SP
	DAD	SP		;INTO HL

	LXI	SP,ISTACK	;SWITCH TO INTERRUPT STACK

	PUSH	H		;SAVE INCOMING SP
				;AT (ISTACK-1) & (ISTACK-2)

	LXI	H,ISRRET+ROF	;CREATE RET ADDRESS
	PUSH	H		;AT (ISTACK-3) & (ISTACK-4)

	DCX	SP		;POINT TO ISR ADDRESS
	DCX	SP
	RET			;GO TO ISR

;***INTERRUPT EXIT*******************
; INTERRUPT SERVICE ROUTINE RETURN
; RESTORES OFFBOARD INTERRUPTED
; PROGRAM'S CONTEXT AND RETURNS THERE
;************************************
ISRRET:	POP	H		;GET ORIGINAL SP

	LDA	BRGSAV		;OFFBOARD MEMORY
	OUT	BRG		;..NOW

	SPHL			;RESTORE ORIGINAL SP
	POP	H		;ORIGINAL HL
	POP	PSW		;ORIGINAL PSW
	EI			;FINALLY ENABLE INTS
	RET			;AND RETURN FROM INTERRUPT

;==========================================================	
; PROTOTYPE 8-BYTE INTERRUPT VECTOR
; 8 OF THESE GET INSTALLED BY INIT, STARTING AT ADDRESS 0
; IN OFFBOARD MEMORY. THE DEFAULT INTERRUPT SERVICE
; ROUTINE IS GOPOLY, WHICH WILL RETURN TO THE POLY-88 MAIN
; LOOP. (YOU CAN THEN TYPE ^Z TO BRING UP THE FRONT-PANEL
; MONITOR TO DEBUG.) WE SHOULD ONLY EVER GET THE FOLLOWING
; INTERRUPTS: UART, KEYBOARD, AND RTC. INIT REPLACES GOPOLY
; WITH THE APPROPRIATE ROUTINE ADDRESSES FOR THESE THREE.
; SINCE WE GOT HERE FROM AN INTERRUPT, INTERRUPTS ARE
; ALREADY DISABLED.
;==========================================================
PROTOV:	PUSH	PSW		;INCOMING PSW
	PUSH	H		;INCOMING HL VALUE

	LXI	H,GOPOLY+ROF	;DEFAULT VECTOR

	JMP	DSPTCH+ROF

;=============================
;= COLD-START INITIALIZATION =
;=============================
INIT:	DI			;INTS OFF WHILE WE WORK

;CREATE LOCAL STACK IN MEMORY THAT IS UNAFFECTED BY BRG

	LXI	SP,SYSTP

;SET UP THE SERIAL PORT: INITIALIZE BRGSAV, DISABLE ONBOARD
;MEMORY & ENABLE OFFBOARD MEMORY

	MVI	A,DEBAUD	;DEFAULT BAUD RATE
	CALL	STBAUD+ROF	;ALSO ENABLES OFFBOARD RAM

;INSTALL INTERRUPT VECTORS IN OFFBOARD PAGE 0 MEMORY BEFORE
;ENABLING INTERRUPTS WITH OFFBOARD MEMORY SELECTED. THESE
;ALL JUST GO TO GOPOLY FOR NOW,SO THAT UNEXPECTED INTERRUPTS
;WILL RETURN TO THE POLY-88 MONITOR'S MAIN LOOP.

	LXI	H,8*8		;EIGHT 8-BYTE VECTORS

VLOOP1:	LXI	D,PROTOV+ROF+8	;PROTOTYPE VECTOR

VLOOP2:	DCR	L
	DCX	D
	LDAX	D		;GET A VECTOR BYTE
	MOV	M,A		;PUT IT IN PLACE
	MOV	A,L		;DONE WITH A VECTOR?
	ANI	7
	
	JNZ	VLOOP2+ROF	;N: KEEP GOING

	ORA	L		;DONE WITH ALL VECTORS?
	JNZ	VLOOP1+ROF	;N:KEEP GOING

;NOW PASTE IN SPECIFIC VECTORS THAT WE ACTUALLY PROCESS

	LXI	H,UINT		;UART INTERRUPT
	SHLD	0023H		;VI3

	MVI	L,KINT AND 0FFH	;KEYBOARD INTERRUPT
	SHLD	002BH		;VI2

	MVI	L,RINT AND 0FFH	;RTC INTERRUPT
	SHLD	0033H		;VI2

	MVI	A,RET		;INITIALIZE TIMER ISR TO RET
	STA	PTMRISR

;FLUSH ANY GARBAGE FROM THE KEYBOARD AS LATE AS POSSIBLE

	IN	KBD		;ALSO CLEARS KBD INT

;INITIALIZE THE VIDEO DISPLAY, & PRINT BANNER (ENABLES INTS)

	CALL	ILPRNT+ROF	;(ENABLES INTS TOO)
	DB	FMFD		;FORM FEED, CLEARS SCREEN
	DB	'Polex 3.1','1'+80H

;TEST FOR MODULE ROM, CALL ITS INIT CODE IF ONE IS THERE
;SAVE MODULE'S COMMAND PROCESSOR ADDRESS, RETURNED IN HL

	CALL	ONBRD+ROF	;ENABLE ROMS
	LDA	MODROM		;IS THERE A MODULE ROM?
	INR	A		;FF MEANS NO ROM
	MOV	H,A		;IN CASE NO MODULE
	CNZ	MODROM		;INITIALIZE MODULE
	SHLD	MODGO		;REMEMBER CMD PROC ADDR
	CALL	OFFBRD+ROF	;DISABLE ROMS

;FALL INTO MAIN

;*************************************************
; COMMAND PROCESSOR MAIN ENTRY POINT
; GETS AND PROCESS COMMANDS
; SP GETS FIXED, IN CASE OF RE-ENTRY FROM AN ERROR
;*************************************************
MAIN:	LXI	SP,SYSTP	;FIX STACK POINTER

;PRINT THE PROMPT, AND GET A LINE OF KBD INPUT

	CALL	ILPRNT+ROF	;PRINT CR, PROMPT
	DB	CR,PROMPT+80H	;WILL ALSO ENABLE INTS

	LXI	H,MAIN+ROF	;CREATE RETURN ADDRESS
	PUSH	H		;..ON THE STACK

	CALL	GETLIN+ROF	;GET USER INPUT LINE
				;DE=BEGINNING OF LINE
				;Z SET IF NO CHR FOUND
				;CURSOR AT END OF LINE

	RZ			;NO COMMAND? NO ERROR.

;IF A MODULE WAS DETECTED DURING INIT, THEN GO THERE
;SO THE MODULE CAN CHECK ITS COMMAND LIST FIRST

	LXI	H,MODRET+ROF	;CREATE MODULE
	PUSH	H		;..RETURN ADDRESS
	LHLD	MODGO		;GET THE MODULE'S ADDR
	MOV	A,H		;MODULE DETECTED?
	ORA	A
	RZ			;N: RET TO MODRET

	PCHL			;Y: GO TO MODULE CMD PROC

MODRET:	RNZ			;DONE IF MOD TOOK THE CMD

;THE COMMAND WAS NOT RECOGNIZED BY THE MODULE. CHECK
;POLEX'S COMMAND LIST, AND EXECUTE THE COMMAND IF FOUND

	XCHG			;COMMAND TO HL
	LXI	D,COMTAB+ROF-1	;POINT TO COMMAND TABLE

;SEARCH THROUGH TABLE AT DE FOR A 2-CHR MATCH OF (HL)

	MOV	C,M		;C=1ST COMMAND CHR
	INX	H		;M=2ND CMD CHR

NXTCOM:	INX	D		;SKIP OVER ADDRESS OFFSET

	LDAX	D
	ORA	A		;TEST FOR TABLE END
	JZ	ERROR+ROF	;NOT IN TABLE

	XRA	C		;TEST FIRST CHR
	MOV	B,A		;TEMP SAVE RESULT
	INX	D		;2ND TABLE CHR
	LDAX	D
	XRA	M		;TEST 2ND CHR

	INX	D		;POINT TO ADDRESS OFFSET
	ORA	B		;BOTH CHRS MATCH?
	JNZ	NXTCOM+ROF	;NO MATCH: KEEP LOOKING

;GOT A MATCH. COMPUTE ROUTINE ADDRESS AND PUT IT ON STACK
;A=B=0 HERE

	XCHG			;(HL)=OFFSET ADDR OF ROUTINE
				;DE=POINTS TO 2ND INPUT CHR

	MOV	C,M		;BC=ADDRESS OFFSET
	LXI	H,CMDBAS+ROF	;BASE OF COMMAND ROUTINES
	DAD	B		;HL=ADDRESS OF ROUTINE

	INX	D		;SKIP PAST 2-LETTER COMMAND
	XTHL			;UNDO UPCOMING XTHL


;PUT ADDRESS OF ROUTINE ON THE STACK
;GET FOLLOWING PARAMETER (IF ANY) AND PUT IT IN HL.
;SET THE CARRY FLAG IF NO PARAMETER PRESENT.
;LEAVE DE POINTING TO THE 1ST CHR AFTER THE 1ST PARAMETER.
;'RETURN' TO THE COMMAND ROUTINE ON THE STACK, WITH CARRY SET
;IF NO HEX VALUE WAS FOUND. B=0.

;FALL INTO HLFHEX

;***SUBROUTINE************************
; SCAN PAST BLANKS AND GET A HEX VALUE
; ON EXIT:
;   TOP-OF-STACK=PREVIOUS HL VALUE
;   HL=VALUE
;   DE ADVANCED PAST CHR
;   CARRY SET IF NONE FOUND
;   CARRY CLEAR IF CHR FOUND
;*************************************
HLFHEX:	XTHL			;PUT HL ON STACK, GET RET ADDR
	PUSH	H		;REINSTALL RET ADDR

;FALL INTO FNDHEX

;***EXTERNAL SUBROUTINE***************
; SCAN PAST BLANKS AND GET A HEX VALUE
; ON EXIT:
;   HL=VALUE
;   DE ADVANCED PAST CHR
;   CARRY SET IF NONE FOUND
;   CARRY CLEAR IF CHR FOUND
;*************************************
FNDHEX:	CALL	SKIPB+ROF	;Z SET IF NO CHR FOUND
	STC

;FALL INTO INPHEX WITH Z AND CARRY SET IF NO CHR

;***SUBROUTINE******************************************
; CONVERT ASCII HEX DIGITS FROM INPUT BUFFER TO BINARY
; STOP WHEN A SPACE OR THE CURSOR IS ENCOUNTERED.
; BAD HEX ABORTS THROUGH ERROR.
; ON ENTRY:
;    ASCII HEX AT (DE)
;    Z SET CAUSES IMMEDIATE RETURN WITH CARRY SET & HL=0
; ON EXIT:
;    HL=VALUE
;    DE ADVANCED PAST CHR
;    CARRY CLEAR, Z SET UNLESS Z & CARRY SET ON INPUT
;*******************************************************
INPHEX:	LXI	H,0		;INITIAL VALUE

	RZ			;EXIT WITH CARRY SET FOR
				;..FNDHEX IF NO CHR FOUND

IHEXLP:	LDAX	D		;GET CHARACTER
	ANI	7FH		;VIDEO CHRS HAVE MSB SET

	CPI	CURSOR AND 7FH	;CURSOR MEANS END OF INPUT
	RZ

	CPI	' '		;VALUE SEPARATOR
	RZ

	DAD	H		;MAKE ROOM FOR THE NEW ONE
	DAD	H
	DAD	H
	DAD	H
	CALL	HEXCON+ROF	;DO THE CONVERSION
	JNC	ERROR+ROF	;NOT VALID HEXIDECIMAL VALUE

	ADD	L
	MOV	L,A		;MOVE IT IN
	INX	D		;BUMP THE POINTER

	JMP	IHEXLP+ROF

;==========================
; BASE ADDRESS FOR COMMANDS
;==========================
CMDBAS	EQU	$

;***COMMAND ROUTINE*****************************
; TE (TERMINAL MODE)
;
; SEND ALL KEYBOARD DATA TO THE SERIAL PORT, AND
; SEND ALL SERIAL PORT DATA TO THE SCREEN.
; 'ALT-MODE' KEY ON THE KEYBOARD TO EXIT
;
; ON ENTRY:
;   E<>0 (FORCES GETSER TO ECHO)
;***********************************************
TERMNL:	CALL	CHKKBD+ROF	;ANY KBD DATA?
				;ALSO CHECKS FOR ABORT

	MOV	C,A		;CHR IN C FOR SODATA
	CNZ	SODATA+ROF

	CALL	SISTAT+ROF	;ANY SERIAL DATA?
	CNZ	GETSER+ROF	;GET & ECHO SERIAL DATA

	JMP	TERMNL+ROF

;***COMMAND ROUTINE********************************
; DU <ADR1> <ADR2> (DUMP MEMORY)
;
; PRINT MEMORY CONTENTS FROM <ADR1> TO <ADR2> ON
; THE CONSOLE IN HEX.  IF ONLY <ADR1> IS SPECIFIED,
; THEN PRINT JUST THE CONTENTS OF THAT ADDRESS.
; ON ENTRY:
;    HL=ADDR1
;    (DE) POINTS TO <ADR2>, IF ANY
;**************************************************
DUMP:	CALL	HLFHEX+ROF	;PUSH 1ST ADDRESS,
				;GET HL=SECOND ADDRESS OR 0

	POP	D		;RECOVER START ADDRESS
	XCHG			;HL HAS START, DE HAS END

;PRINT THE ADDRESS AT THE BEGINNING OF EACH LINE

DLINE:	CALL	PHLADR+ROF	;PRINT HL AS AN ADDRESS

;PRINT 16 BYTES OF HEX DATA (SEPARATED BY SPACES) ON EACH LINE

DLOOP:	CALL	ILPRNT+ROF	;PRINT A SPACE
	DB	' '+80H

	MOV	A,M		;GET THE CHR
	CALL	PAHEX+ROF	;SEND IT OUT WITH A BLANK

	MOV	A,L		;COMPARE DE AND HL
	SUB	E
	MOV	A,H
	SBB	D
	RNC			;ALL DONE

	INX	H

	MOV	A,L
	ANI	0FH		;NEW LINE EVERY XXX0 HEX

	JNZ	DLOOP+ROF	;NOT ZERO IF MORE FOR THIS LINE

;GIVE USER A CHANCE TO PAUSE OR QUIT AT THE END OF EACH LINE

	CALL	CHKKBD+ROF	;SEE IF A CHAR WAITING
				;..AND ABORT IF IT'S ALT-MODE
	CPI	PAUKEY		;IS IT A PAUSE?
	CZ	GETKBD+ROF	;Y: GO WAIT FOR ANOTHER CHR
				;..OR USER ABORT

	JMP	DLINE+ROF	;NEXT LINE

;***COMMAND ROUTINE****************************************
; EN <ADR> (ENTER DATA INTO MEMORY)
;
; GET HEX VALUES FROM THE KEYBOARD AND ENTER THEM
; SEQUENTIALLY INTO MEMORY, STARTING AT <ADR>. A BLANK
; LINE ENDS THE ROUTINE AND RETURNS CONTROL TO THE
; COMMAND MODE. VALUES MAY BE SEPARATED BY SPACES OR CR'S.
; PRINT THE CURRENT ADDRESS AT THE BEGINNING OF EACH LINE.
; ON ENTRY:
;  HL = ADDRESS
;  CARRY SET IF NONE ENTERED
;**********************************************************
ENTER:	JC	ERROR+ROF	;MUST HAVE ADDRESS

;PRINT THE ADDRESS AT THE BEGINNING OF EACH LINE

ENLINE:	CALL	PHLADR+ROF	;PRINT HL AS AN ADDRESS

;GET A LINE OF USER INPUT

	PUSH	H		;SAVE ADDRESS
	CALL	GETLIN+ROF	;INIT AND PROCESS A LINE
	POP	H		;HL=BEGINNING OF LINE,
				;..PAST ANY LEADING BLANKS
	RZ			;Z=BLANK LINE TERMINATES

;GET HEX DATA FROM THE USER INPUT LINE AND WRITE IT TO MEMORY

ENLOOP:	PUSH	H		;SAVE ADDRESS
	CALL	INPHEX+ROF	;GET/CONVERT VALUE (Z CLEAR HERE)

	MOV	A,L		;GET LOW BYTE AS CONVERTED
	POP	H		;RECOVER MEMORY ADDRESS

	MOV	M,A		;PUT IN THE VALUE
	INX	H		;NEXT ADDRESS

	CALL	SKIPB+ROF	;SCAN TO NEXT INPUT VALUE
	JNZ	ENLOOP+ROF	;NOT END OF LINE: CONTINUE

	JMP	ENLINE+ROF	;END OF LINE: START NEW LINE

;***COMMAND ROUTINE*******************
; EX <ADR> (EXECUTE)
;
; JUMP TO <ADR>
; ON ENTRY:
;  HL = ADDRESS
;  CARRY SET IF NONE ENTERED
;*************************************
EXEC:	PCHL

;***COMMAND ROUTINE***********************************
; FI <ADR1> <ADR2> <VAL> (FILL MEMORY)
;
; FILL MEMORY WITH <VAL> FROM <ADR1> THROUGH <ADR2>.
; IF <VAL> IS NOT PROVIDED, THEN FILL THE SPECIFIED
; RANGE WITH 00. IF <ADR2> IS NOT PROVIDED, THEN FILL
; JUST ONE BYTE AT <ADR1> WITH 00.
; ON ENTRY:
;  HL=<ADR1>
;  CARRY SET IF NONE ENTERED
;  (DE) POINTS TO <ADR2>, <VAL> FOLLOWS, IF ANY
;*****************************************************
FILMEM:	JC	ERROR+ROF	;MUST PROVIDE A START, SO THAT
				;DEFAULT DOESN'T KILL INT VECTOR

	CALL	HLFHEX+ROF	;PUSH 1ST ADDRESS,
				;GET HL=SECOND ADDRESS OR 0
	CALL	HLFHEX+ROF	;PUSH 2ND ADDRESS,
				;GET HL=SECOND ADDRESS OR 0
	MOV	C,L		;FILL DATA IN C

	POP	D		;DE HAS END ADDRESS
	POP	H		;HL HAS START ADDRESS

FMLOOP:	MOV	M,C

	MOV	A,L		;COMPARE DE AND HL
	SUB	E
	MOV	A,H
	SBB	D
	RNC			;HL>DE: ALL DONE

	INX	H
	JMP	FMLOOP+ROF

;***COMMAND ROUTINE*************
; PO (RETURN TO POLY-88 MONITOR)
;*******************************
GOPOLY:	XRA	A		;RESET BAUD RATE,
	OUT	BRG		;..ENABLE ONBOARD MEMORY

	LXI	H,KSR		;RESTORE POLY-88 INT ROUTINES
	SHLD	SRA5		;..IN ONBOARD MEMORY
	LXI	H,USRTSR
	SHLD	SRA4

;REVERT TO POLY-88 STACK IN ONBOARD RAM AND RETURN

	LXI	SP,POLYSP	;ORIGINAL STACK POINTER
	RET			;RETURN TO POLY-88 MONITOR

;***COMMAND ROUTINE**********************
; SB <BAUD> (SET BAUD RATE)
;
; ON ENTRY: L=<BAUD> VALUE FOR BRG:
;    <BAUD> BITS 3:0 SELECT THE BAUD RATE
;    <BAUD> BIT 4 SELECTS THE PORT:
;       0 SELECTS THE CASSETTE TAPE PORT
;       1 SELECTS THE PRINTER PORT
;    BITS 7:5 ARE IGNORED
; ON EXIT:
;    BRG AND BRGSAV HAVE BEEN UPDATED
;    ONBOARD MEMORY IS DISABLED
;    UART RX IS ENABLED
;    UART TX IS DISABLED
;    TRASHES A
; STACK USAGE:
;   0 BYTES
;*****************************************
SBCMD:	MOV	A,L		;BAUD VALUE TO A

;FALL INTO STBAUD

;***EXTERNAL SUBROUTINE******************
; SET BAUD RATE
;
; ON ENTRY: A= VALUE FOR BRG:
;    BITS 3:0 SELECT THE BAUD RATE
;    BIT 4 SELECTS THE PORT:
;     0 SELECTS THE CASSETTE TAPE PORT
;     1 SELECTS THE PRINTER PORT
;    BITS 7:5 ARE IGNORED
; ON EXIT:
;    BRG AND BRGSAV HAVE BEEN UPDATED
;    ONBOARD MEMORY IS DISABLED
;    UART RX IS ENABLED
;    UART TX IS DISABLED
;    TRASHES A
; STACK USAGE:
;   0 BYTES
;*****************************************

; SET THE BAUD RATE DIVISOR, ENABLE OFFBOARD MEMORY

STBAUD:
	ANI	1FH		;BIT 7 MUST BE LOW
	ORI	BRGOMD		;SELECT OFFBOARD MEM
	STA	BRGSAV		;..AND SET BAUD RATE
	OUT	BRG

;SEND COMPLETE (4-BYTE) ASYNC RESET SEQUENCE TO 8251 UART

	MVI	A,0AAH		;<1> FAKE SYNCH CHR
	OUT	UARTS		;ALSO DIABLES TX, RX INTS

	MVI	A,UCMDRE	;<2> RESET TO MODE
	OUT	UARTS

	MVI	A,UMOD16	;<3> CLOCK DIVISOR ETC.
	OUT	UARTS

;FALL INTO UTXDIS FOR THE FINAL RESET SEQUENCE BYTE

;***SUBROUTINE****************************
;DISABLE UART TRANSMITTER, ENABLE RECEIVER
; TRASHES A
;*****************************************
UTXDIS:	MVI	A,0FFH		;REMEMBER DISABLED STATE
	STA	URTDIS+ROF
	MVI	A,UCRXEN	;<4> ENABLE RECEIVER
	OUT	UARTS		;..DISABLE TRANSMITTER
	RET

;***COMMAND ROUTINE***************************************
; HL [0/1] (INTEL HEX LOAD)
;
; LOAD AN INTEL HEX FILE FROM THE POLY-88 SERIAL PORT INTO
; MEMORY AT THE ADDRESSES SPECIFIED IN THE HEX FILE. DONE
; WHEN ANY 0-BYTE RECORD ENCOUNTERED, OR IF THE ALT-MODE
; OR ESCAPE KEY IS TYPED. PARAMETER>0 MEANS ECHO RECEIVED
; CHRS. PARAMETER=0 OR NO PARAMETER MEANS NO ECHO;
; PRINT A PACIFIER DOT PER RECORD INSTEAD.
;
; ON ENTRY:
;    B=0
;   HL=0 AND CARRY SET IF NO PARAMETERS
;   CARRY SET IF NO PARAMETER WAS INCLUDED
;
; REGISTER USAGE DURING HEX LOAD:
;   B: SCRATCH
;   C: RECORD BYTE COUNT
;   D: RECORD CHECKSUM
;   E: ECHO STATE (0 MEANS PACIFIER DOTS, <>0 MEANS ECHO)
;   HL: MEMORY ADDRESS (AND RECORD COUNT, ON STACK MOSTLY)
;*********************************************************
HEXLOD:	MOV	E,L		;PARAMETER LOW BYTE
				;0 OR NO PARAMETER MEANS
				;NO ECHO

	CALL	ILPRNT+ROF	;BE PRETTY: NEW LINE
	DB	CR+80H

;INITIALIZE

	MOV	H,B		;B=0: INIT RECORD COUNT
	MOV	L,B
	MOV	D,B		;INIT CKSUM

;FLUSH SERIAL PORT RECEIVE QUEUE IN CASE OF OLD JUNK

SFLUSH:	CALL	SISTAT+ROF	;ANYTHING IN THE QUEUE?
	CNZ	GSDATA+ROF	;Y:GO GET IT & FORGET IT
	JNZ	SFLUSH+ROF	;Y:SEE IF THERE'S MORE

;EAT ALL CHRS UNTIL WE GET RECORD-START COLON

GETCOL:	CALL	GETSER+ROF
	CPI	':'
	JNZ	GETCOL+ROF

	MVI	A,PCFIER	;PACIFIER CHARACTER
	DCR	E		;PRINT PACIFIER IF ECHO DISABLE
	INR	E		;E=0 MEANS ECHO DISABLED
	CZ	PRINTA+ROF

;BUMP RECORD COUNT AND PARK IT ON THE STACK (D=0 HERE)

	INX	H		;16-BIT INCREMENT
	PUSH	H		;PUT RECORD COUNT ONTO STACK

;GET 4-BYTE RECORD HEADER: (CHECKSUM=D=0 HERE)
; C GETS 1ST BYTE = DATA BYTE COUNT
; H GETS 2ND BYTE = ADDRESS HIGH BYTE
; L GETS 3RD BYTE = ADDRESS LOW BYTE
; 4TH BYTE = RECORD TYPE GETS CHUCKED
; D = CHECKSUM OF THE ABOVE 4 BYTES

;SHIFT IN THE FOUR HEADER BYTES: C <- H <- L <- B
	MVI	A,4		;LOOP COUNTER

HEDRLP:	MOV	C,H		;C=BYTE 1: BYTE COUNT
	MOV	H,L		;H=BYTE 2: ADDRESS MSB
	MOV	L,B		;L=BYTE 3: ADDRESS LSB
	PUSH	PSW
	CALL	GETBYT+ROF	;GET B=HEADER BYTE, DO CKSUM
	POP	PSW
	DCR	A		;NEXT HEADER BYTE
	JNZ	HEDRLP+ROF

;REMEMBER BYTE COUNT ON STACK, BUMP C TO INCLUDE CKSUM BYTE.

	PUSH	B		;REMEMBER C=BYTE COUNT
				;(B=RECORD TYPE...)

	DB	JNZ		;JNZ OPCODE SKIPS 2 BYTEs


;LOOP TO GET C DATA BYTES INTO MEMORY AT HL. C HAS BEEN BUMPED
;SO THAT THIS LOOP WILL ALSO READ THE CHECKSUM BYTE. EXIT WITH
;THE RECORD CHECKSUM IN A.

DATALP:	MOV	M,B
	INX	H		;NEXT MEMORY LOC

	CALL	GETBYT+ROF	;GET B=DATA BYTE, A=D=CKSUM

	DCR	C		;NEXT DATA (OR CKSUM) BYTE
	JP	DATALP+ROF

;VERIFY THE RESULT, AND CONTINUE LOADING UNLESS AN EOF RECORD
;WAS RECEIVED. (AN EOF RECORD IS ONE WITH 0 DATA BYTES.)

	POP	B		;RECOVER C=BYTE COUNT
				;(AND B=RECORD TYPE...)
	POP	H		;RECOVER RECORD COUNT

	ORA	A		;VALIDATE CKSUM (IN A AND D)
	JNZ	HLERR+ROF	;NZ MEANS CKSUM ERROR

;	DCR	B		;TYPE 1 RECORD?
;	JZ	PRECS+ROF

	ORA	C		;ZERO BYTE RECORD?
	JNZ	GETCOL+ROF	;NO: GO GET ANOTHER RECORD

	JMP	PRECS+ROF	;YES: PRINT REC COUNT AND EXIT

;***SUBROUTINE***************************************
; GET 2 HEX DIGITS FROM THE SERIAL PORT, COMBINE THEM
; INTO A BYTE, AND ADD RESULT TO THE CHECKSUM IN D
; ON ENTRY:
;   D = CHCKSUM SO FAR
;   E = ECHO FLAG
; ON EXIT:
;   B=BYTE OF DATA
;   A=D=NEW CHECKSUM VALUE
;   Z FLAG SET IF CHECKSUM IS 0
;   ALL OTHER REGISTERS PRESERVED, UNLESS ERROR ABORT
;****************************************************
GETBYT:	CALL	SERHEX+ROF	;GET HIGH NIBBLE
	ADD	A		;SHIFT HIGH NIBBLE IN PLACE
	ADD	A
	ADD	A
	ADD	A
	MOV	B,A
	CALL	SERHEX+ROF	;GET LOW NIBBLE

	ORA	B		;COMBINE NIBBLES
	MOV	B,A		;SAVE RESULT FOR RETURN
	ADD	D		;COMPUTE CHECKSUM
	MOV	D,A		;RET WITH CKSUM IN A & D

	RET

;===LOCAL SUBROUTINE===========================
; GET A HEX DIGIT FROM THE SERIAL PORT,
; CONVERT IT TO BINARY, AND RETURN IT IN A<3:0>
;==============================================
SERHEX:	CALL	GETSER+ROF

;FALL INTO HEXCON

;***SUBROUTINE*************************
;  CONVERT ASCII HEX DIGIT TO BINARY
; ON ENTRY:
;   A=CHR TO CONVERT
; ON EXIT:
;   A=BINARY, IF OK
;   CARRY SET IF OK, CLEAR IF BOGUS CHR
;**************************************
HEXCON:	SUI	'0'		;REMOVE ASCII BIAS
	CPI	10
	RC			;IF 0-9 THEN WE'RE DONE

	SUI	9+('A'-'9')	;SHOULD BE 0-5 NOW
	CPI	6		;GAP CHR OR TOO HIGH?
	RNC			;ERROR IF SO

	SUI	0F6H		;ADD 0AH, SET CARRY
	RET			;RET WITH CARRY SET

;***SUBROUTINE*********************
; PRINT HEX LOAD ERROR MESSAGE
; THEN PRINT RECORD COUNT AND ABORT
; ON ENTRY:
;    B = 1 CHARACTER ERROR MESSAGE
;    HL = RECORD COUNT
; TRASHES A
;********************************** 
HLERR:	CALL	ILPRNT+ROF
	DB	CR,'Er','r'+80H

;FALL INTO PRECS TO PRINT RECORD COUNT

;***SUBROUTINE******************
; PRINT THE RECORD-COUNT MESSAGE
; ON ENTRY:
;    HL=RECORD COUNT
; TRASHES A
;*******************************
PRECS:	CALL	ILPRNT+ROF
	DB	CR,'Recs',':'+80H

;FALL INTO PHLHEX TO PRINT HL IN HEX & RETURN TO MAIN

;***SUBROUTINE***********************
; PRINT HL ON CONSOLE AS 4 HEX DIGITS
; TRASHES A
;************************************
PHLHEX:	MOV	A,H		;H FIRST
	CALL	PAHEX+ROF
	MOV	A,L		;THEN L

;FALL INTO PAHEX

;***EXTERNAL SUBROUTINE*************
; PRINT A ON CONSOLE AS 2 HEX DIGITS
; TRASHES A
;***********************************
PAHEX:	PUSH	PSW		;GET THE CHARACTER
	RRC			;MOVE THE HIGH FOUR DOWN
	RRC
	RRC
	RRC
	CALL	PNIBLE+ROF	;PUT THEM OUT
	POP	PSW		;THIS TIME THE LOW FOUR

;FALL INTO PNIBLE

;===LOCAL SUBROUTINE==========
; PRINT LOW NIBBLE OF A IN HEX
; TRASHES A
;=============================
PNIBLE:	ANI	0FH		;FOUR ON THE FLOOR
	ADI	'0'		;WE WORK WITH ASCII HERE
	CPI	'9'+1		;0-9?
	JC	PRINTA+ROF	;YUP: PRINT & RETURN

	ADI	'A'-'9'-1	;MAKE IT A LETTER
	JMP	PRINTA+ROF	;PRINT IT & RETURN

;***CP/M EXTERNAL SUBROUTINE**********************************
; PRINT C ON THE CONSOLE SCREEN
; THIS CREATES A LOCAL STACK BECAUSE A CALLING PROGRAM'S STACK
; MAY BE IN THE 1ST 4K OF RAM, WHICH WILL DISAPPEAR WHEN WE
; GO ONBRD. THE LOCAL STACK MUST HAVE ROOM FOR 28 BYTES TO
; HANDLE DSPLY'S NEEDS, AS WELL AS SEVERAL STACKED ONBOARD
; INTERRUPTS.
;
; THE BS CHR IS TRANSLATED TO A DEL HERE.
;
; ON ENTRY:
;   THE CHARACTER FOR OUTPUT IS IN C
; ON EXIT:
;   C=CHR
;   ALL OTHER REGS PRESERVED
;   INTERRUPTS ENABLED
; STACK USAGE:
;   2 BYTES
;*************************************************************
PRINTC:	PUSH	H		;SAVE INCOMING HL

	LXI	H,0
	DAD	SP		;HL= INCOMING SP

	LXI	SP,VDSTAK	;CREATE LOCAL STACK

	MOV	A,C		;A=CHR TO PRINT
	CALL	PRINTA+ROF	;PRINT ON SCREEN
				;ENABLES INTS ON EXIT

	SPHL			;RESTORE INCOMING STACK
	POP	H		;RESTORE INCOMING HL
	RET

;***SUBROUTINE*******************************
; SERIAL PORT INPUT FOR HEX LOADER
; STRIPS PARITY, ECHOS TO THE CONSOLE.
; CHECKS FOR ABORT FROM THE KEYBOARD
; oN eBTRY:
;   E = 0 IF ECHO IS DISABLED
; ON EXIT:
;   CHR IN A, WITH PARITY STRIPPED
;   INTERRUPTS ARE ENANLED
;-->ENTRY IS AT GETSER<--
;********************************************
GSWAIT:	CALL	CHKKBD+ROF	;USER ABORT?

GETSER:	CALL	SISTAT+ROF	;ANY SERIAL CHRS?
	JZ	GSWAIT+ROF	;SISTAT ENABLES INTS

	CALL	GSDATA+ROF	;GET SERIAL CHR
	ANI	7FH		;STRIP PARITY

	DCR	E		;ECHO ENABLED?
	INR	E
	RZ			;N: DONE

;FALL INTO PRINTA TO ECHO THE CHR IN A

;***SUBROUTINE***************************************
; SEND A TO SCREEN (TRANSLATE BS TO DEL IF NECESSARY)
; ON ENTRY:
;   THE CHARACTER FOR OUTPUT IS IN A
; ON EXIT:
;   ALL REGS PRESERVED
;****************************************************
PRINTA:	CALL	BS2DEL+ROF	;TRANSLATE BACKSPACE TO DEL
	CALL	ONBRD+ROF	;ENABLE ONBOARD MEMORY
	CALL	DSPLY		;USE POLY88 VIDEO DRIVER
				;ALSO ENABLES INTS

;FALL INTO OFFBRD

;***EXTERNAL SUBROUTINE*******************************
; SELECT OFFBOARD MEMORY
; NOTE: YOU CAN'T CALL THIS SUBROUTINE IF THE STACK IS
; (OR MIGHT BE) IN THE 1ST 4K OF MEMORY, AS THE STACK
; WILL DISAPPEAR WHEN SWITCHING OFFBOARD.
;*****************************************************
OFFBRD:	PUSH	PSW
	LDA	BRGSAV		;OFFBOARD MEMORY
	OUT	BRG
	POP	PSW
	RET

;***SUBROUTINE***************************************
; PRINT HL AS AN ADDRESS (CR, ADDRESS, COLON)
; TRASHES A
;****************************************************
PHLADR:	CALL	ILPRNT+ROF	;CR BEGINS LINE
	DB	CR+80H

	CALL	PHLHEX+ROF	;HL=ADDRESS

	MVI	A,':'
	JMP	PRINTA+ROF	;RETURN FROM THERE

;***SUBROUTINE**********************************************
; READ A COMMAND LINE FROM THE KEYBOARD, ECHOING AND LETTING
; THE POLY-88 SUBROUTINE SAVE IT IN VIDEO MEMORY
;
; CR INPUT ENDS THE SEQUENCE. THE CR IS NOT SAVED IN VIDEO
; MEMORY. INSTEAD, THE LINE IS TERMINATED BY THE CURSOR THAT
; THE POLY-88 VIDEO ROUTINE LEAVES AT THE END OF THE LINE.
;
; ON EXIT:
;   COMPLETE COMMAND LINE IS IN VIDEO MEMORY, TERMINATED
;   WITH THE CURSOR CHARACTER (FFH)
;   DE=ADDRESS OF THE FIRST NON-BLANK CHR ON THE LINE
;   A = FIRST NON-BLANK VALUE FOUND
;   Z SET IF NOTHING BUT BLANKS FOUND
;   INTERRUPTS ENABLED
;   BC,HL TRASHED
;***********************************************************
GETLIN:	CALL	ONBRD+ROF	;ONBRD SO WE CAN ACCESS POS
	LHLD	POS		;GET POLY-88 CURSOR POS
	CALL	OFFBRD+ROF

	MOV	D,H		;SAVE INIT POS FOR RETURN
	MOV	E,L		;L STILL HAS LINE POS

;LOOP TO GET INPUT LINE

;WAIT FOR A KEY TO BE PRESSED, ABORT IF USER WANTS TO

GCLI1:	CALL	GETKBD+ROF	;GET NEXT KBD CHR INTO A
				;..ENABLES INTERRUPTS TOO

;IF CHR IS THE TERMINATING CR, THEN RETURN WITHOUT ECHOING

	CPI	CR		;TERMINATING CR?
	JZ	SKIPB+ROF	;YES: GO SKIP LEADING BLANKS
				;..& SET Z IF NO INPUT

;PUT LOOP BEGINNING ONTO STACK

	LXI	B,GCLI1+ROF
	PUSH	B

;CHUCK ANY CONTROL CHARACTERS, AS SOME OF THEM WILL MESS UP
;OUR CURSOR POSITION, AND WE DON'T USE ANY OF THEM ANYWAY

	CPI	' '
	RC			;RET TO GCLI1

;IF DEL, DELETE IF POSSIBLE

	CPI	DEL		;DEL?

	MOV	H,A		;TEMP SAVE CHR
	MOV	A,L		;GET CURSOR POS LOW BYTE

	JNZ	GCLI2+ROF

	CMP	E		;AT BEGINNING OF LINE?
	RZ			;Y: CAN'T DELETE
				;...SO RET TO GCLI1

	DCR	A		;DELETE
	DCR	A		;ACCOUNT FOR UPCOMING INR A

;ECHO, AND STORE BYTE IN VIDEO MEMORY, IF ROOM

GCLI2:	INR	A		;END OF LINE (X100 0000B)?
	ANI	VDROW-1		;VDROW IS A POWER OF 2
	RZ			;FULL LINE: CHUCK CHR
				;..AND RET TO GCLI1

	MOV	L,A		;SAVE LINE POSITION
	MOV	A,H		;RECOVER CHR
	JMP	PRINTA+ROF	;ECHO, ENABLE INTS
				;RET TO GCLI1

;***CP/M EXTERNAL SUBROUTINE*************
; GET SERIAL PORT RX STATUS
; ON EXIT:
;   A=0 & Z SET IF NO DATA
;   A=FF  & Z CLEAR IF DATA
;   INTERRUPTS ENABLED
; STACK USAGE:
;   0 BYTES
;****************************************
SISTAT:	LDA	SRQCNT+ROF

;FALL INTO DOSTAT

;***SUBROUTINE RETURN*********************
; CP/M-STYLE STATUS RETURN
; ON EXIT:
;   A=00 & Z SET MEANS NO CHR AVAILABLE
;   A= FFH & Z CLEARED MEANS CHR AVAILABLE
;   INTERRUPTS ENABLED
;*****************************************
DOSTAT:	EI			;IN CASE THEY WERE DISABLED
	ORA	A
	RZ
	MVI	A,0FFH
	RET

;***CP/M EXTERNAL SUBROUTINE*************
; WAIT FOR AND GET SERIAL RX PORT DATA
; ON EXIT:
;   A=BYTE FROM QUEUE
;   Z CLEARED
;   INTERRUPTS ENABLED
; STACK USAGE WHILE INT'S ENABLED:
;   2 BYTES
;****************************************

;SPIN UNTIL THE QUEUE IS NOT EMPTY

SIDATA:	CALL	SISTAT+ROF
	JZ	SIDATA+ROF

;FALL INTO GSDATA

;***SUBROUTINE****************************
; GET SERIAL RX PORT DATA FROM QUEUE
; ON ENTRY:
;   A CHR IS KNOWN TO BE IN THE QUEUE
; ON EXIT:
;   A=BYTE FROM QUEUE
;   Z CLEARED UNLESS WE GOT THE LAST BYTE
; STACK USAGE WHILE INT'S ENABLED:
;   2 BYTES
;****************************************
GSDATA:	PUSH	H
	LHLD	SRQOPT+ROF	;RX Q OUTPUT POINTER

;CIRCULAR-INC SRQOPT. ASSUME SUPER-NICE QUEUE ALIGNMENT
;AND DON'T MESS UP HL YET

	MOV	A,L
	INR	A
	ANI	SRQSIZ XOR 0FFH
	STA	SRQOPT+ROF	;H NEVER CHANGES

;GET QUEUE DATA BEFORE BUMPING COUNT, SO INTS
;WONT MESS IT UP.

	MOV	A,M		;GET Q DATA FOR RET

;BUMP QUEUE BYTE COUNT

	LXI	H,SRQCNT+ROF
	DCR	M		;ATOMIC READ THEN WRITE

	POP	H
	RET

;***CP/M EXTERNAL SUBROUTINE*************************
; GET SERIAL TX PORT QUEUE STATUS
; ON EXIT:
;   Z CLEAR AND A=FFH IF THE QUEUE CAN ACCEPT A CHR
;   Z SET AND A=0 OF THE SERIAL TX QUEUE IS FULL
;   INTERRUPTS ENABLED
; STACK USAGE:
;   0 BYTES
;****************************************************
SOSTAT:	LDA	STQCNT+ROF
	SUI	STQSIZ		;QUEUE EMPTY?

	JMP	DOSTAT+ROF	;CP/M-STYLE STAT RETURN

;***CP/M EXTERNAL SUBROUTINE***********
; SEND C TO SERIAL PORT QUEUE
; PSW TRASHED, ALL OTHER REGS PRESERVED
; STACK USAGE WHILE INTS ENABLED:
;   2 BYTES
;**************************************
SODATA:	PUSH	H


;SPIN UNTIL TX QUEUE IS NOT FULL

SOWAIT:	CALL	SOSTAT+ROF
	JZ	SOWAIT+ROF

;ENQUEUE THE CHR

	LHLD	STQIPT+ROF	;TX QUEUE IN-POINTER
	MOV	M,C		;PUT CHR IN QUEUE

;CIRCULAR-INC STQIPT. ASSUME SUPER-NICE QUEUE ALIGNMENT

	INR	L
	MVI	A,STQSIZ XOR 0FFH
	ANA	L
	STA	STQIPT+ROF	;H NEVER CHANGES

;BUMP THE QUEUE COUNT. THE RTC INTERRUPT WILL ENABLE THE
;UART TRANSMITTER IF NEEDED. THIS WILL USUALLY GIVE TIME
;FOR THE CALLING PROGRAM TO QUEUE AT LEAST TWO CHARACTERS
;FOR A MULTI-BYTE TRANSMISSION. HAVING QUEUED CHARACTERS,
;IN TURN, KEEPS THE TRANSMIT ISR FROM SHUTTING DOWN THE
;UART BETWEEN EVERY CHARACTER. 

	LXI	H,STQCNT+ROF
	INR	M		;ATOMIC READ THEN WRITE

	POP	H
	RET

;***EXTERNAL SUBROUTINE***************************
; GET KEYBOARD STATUS. IF A CHR IS WAITING,
; THEN CONVERT TO UPPERCASE AND RETURN IT IN A.
; IF THE CHR IS BS, CONVERT IT TO DEL.
; ABORT IF IT IS THE CABKEY (ALT-MODE) OR ESCAPE.
; ON EXIT:
;   IF A CHR IS WAITING, THEN CHR IS IN A
;   CONVERTED TO UPPERCASE, BS CONVERTED TO DEL
;   IF NO CHR WAITING OR NULL THEN Z SET, A=0
;   INTERRUPTS ENABLED
; NOTE: THIS WILL MESS UP '{' AND '|'. THIS IS
; OKAY BECAUSE NEITHER IS USED BY POLEX.
;*************************************************	
CHKKBD:	CALL	KSTAT+ROF	;ANYTHING TYPED?
	RZ			;N: RET W/ Z SET

	CALL	KDATA+ROF	;Y:GET THE DATA

	CPI	ESC		;ABORT?
	JZ	MAIN+ROF	;YES: MAIN LOOP FIXES STACK
	CPI	CABKEY		;OTHER ABORT CHARACTER TYPED?
	JZ	MAIN+ROF	;YES: MAIN LOOP FIXES STACK

	RNC			;DEL KEY SKIPPED, SINCE
				;CABKEY='}' (7DH)

	CPI	'a'		;LEAVE BELOW a ALONE, BUT
	JC	BS2DEL+ROF	;..TRANSLATE BACKSPACE TO DEL

	ANI	('a'-'A') XOR 0FFH ;MAKE IT UPPERCASE	

;FALL INTO BS2DEL TO RETURN WITH Z CLEARED

;***SUBROUTINE*******************************
; TRANSLATE BS TO DEL
; ON ENTRY:
;   CHR IN A
; ON EXIT:
;   CHR IN A, BS CONVERTED TO DEL
;   Z CLEARED
;********************************************
BS2DEL:	CPI	BS
	RNZ

	ADI	DEL-BS		;TRANSLATE BACKSPACE TO DEL
				;..AND CLEAR Z FLAG
	RET

;***SUBROUTINE*******************************
; GET A KEYBOARD CHARACTER, ABORT IF ALT-MODE
; OR ESCAPE
; ON EXIT:
;    A=KEYBOARD CHR, Z CLEARED
;   INTERRUPTS ENABLED
;********************************************
GETKBD:	CALL	CHKKBD+ROF	;GET KBD CHR IF THERE
				;..AND TEST FOR ABORT
	JZ	GETKBD+ROF	;WAIT FOR CHR
	RET

;***CP/M EXTERNAL SUBROUTINE*************
; GET KEYBOARD STATUS
; ON EXIT:
;   A=0 AND Z SET IF NOTHING WAITING
;   A=FF AND Z CLEARED IF KBD CHR WAITING
;   INTERRUPTS ENABLED
; STACK USAGE:
;   0 BYTES
;****************************************
KSTAT:	LDA	KBUFF+ROF	;ANYTHING THERE?
	JMP	DOSTAT+ROF	;RETURN CP/M-STYLE VALUE

;***CP/M EXTERNAL SUBROUTINE*************
; WAIT FOR AND GET KEYBOARD DATA
; ON EXIT:
;   A=KBD CHARACTER WITH PARITY STRIPPED
;   INTERRUPTS ENABLED
; STACK USAGE:
;   2 BYTES
;****************************************
KDATA:	PUSH	H
	LXI	H,KBUFF+ROF

KDWAIT:	EI			;IN CASE THEY WERE DISABLED
	MVI	A,7FH		;STRIP PARITY, CHECK FOR CHR
	ANA	M
	JZ	KDWAIT+ROF

	MVI	M,0		;CLEAR FLAG
	POP	H

	RET

;***SUBROUTINE************************
; SCAN PAST BLANK POSITIONS LOOKING
; FOR THE FIRST NON-BLANK CHARACTER
; ON EXIT:
;   Z SET IF NONE FOUND
;   Z CLEAR IF CHR FOUND
;   A=CHARACTER VALUE IF FOUND
;--> ENTRY IS AT SKIPB <--
;*************************************
SBLOOP:	INX	D		;NEXT SCAN ADDRESS

SKIPB:	LDAX	D		;GET NEXT CHARACTER
	ANI	7FH		;VIDEO CHRS HAVE MSB SET

	CPI	' '
	JZ	SBLOOP+ROF	;KEEP SKIPPING SPACES

	CPI	CURSOR AND 7FH	;END OF INPUT? (CURSOR=FF)
	RET			;CARRY SET IF CURSOR (NO VALUE)

;***SUBROUTINE******************************
; PRINT INLINE MESSAGE AT (SP)
; CALLS TO ILPRNT ARE FOLLOWED BY THE STRING
; THE LAST STRING BYTE HAS ITS MSB SET
; PSW TRASHED, ALL OTHER REGISTERS PRESERVED
; INTERRUPTS ENABLES ON EXIT
;*******************************************
ILPRNT:	XTHL			;SAVE HL, GET MSG ADDR

IPLOOP:	MVI	A,7FH		;STRIP END-MARKER
	ANA	M		;..FROM MESSAGE CHR
	CALL	PRINTA+ROF
	CMP	M		;END?
	INX	H
	JZ	IPLOOP+ROF

	XTHL			;RESTORE HL
				;..GET RET ADDRESS
	RET

;**********************
; GENERIC ERROR HANDLER
;**********************
ERROR:	CALL	ILPRNT+ROF
	DB	CR,'?'+80H

	JMP	MAIN+ROF	;MAIN WILL FIX STACK

;********************************
; COMMAND TABLE
; NOTE THAT ASCII CHRS STORED IN
; VIDEO MEMORY HAVE THEIR MSB SET
;********************************
COMTAB:	DB	'P'+80H,'O'+80H	;RETURN TO POLY-88 MONITOR
	DB	GOPOLY-CMDBAS

	DB	'S'+80H,'B'+80H	;SET BAUD RATE
	DB	SBCMD-CMDBAS
	DB	'T'+80H,'E'+80H	;TERMINAL MODE
	DB	TERMNL-CMDBAS

	DB	'D'+80H,'U'+80H	;DUMP
	DB	DUMP-CMDBAS
	DB	'E'+80H,'N'+80H	;ENTER
	DB	ENTER-CMDBAS
	DB	'E'+80H,'X'+80H	;EXECUTE
	DB	EXEC-CMDBAS
	DB	'F'+80H,'I'+80H	;FILL MEMORY
	DB	FILMEM-CMDBAS

	DB	'H'+80H,'L'+80H	;INTEL HEX LOAD
	DB	HEXLOD-CMDBAS
	DB	0		;END OF TABLE MARK

;====END OF PROGRAM============================================

;*******************************
; LOAD-INITIALIZED RAM VARIABLES
;*******************************

;THE ORDER OF THE FOLLOWING THREE MUST NOT CHANGE
URTDIS:	DB	0FFh	;0 IF TX ENABLED, FF IF DISABLED
STQCNT:	DB	0	;SERIAL TX QUEUE BYTE COUNT
SRQCNT:	DB	0	;SERIAL RX QUEUE BYTE COUNT

SRQIPT:	DW	SRQ	;SERIAL RX QUEUE IN-POINTER
SRQOPT:	DW	SRQ	;SERIAL RX QUEUE OUT-POINTER
STQIPT:	DW	STQ	;SERIAL TX QUEUE IN-POINTER
STQOPT:	DW	STQ	;SERIAL TX QUEUE OUT-POINTER

KBUFF:	DB	0	;KBD RECEIVE BUFFER: 0 MEANS NONE

RAMEND:			;END OF COPIED CODE
;====END OF CODE COPIED TO RAM=================================

;***********************************************************
; QUEUES IN FIXED RAM LOCATIONS
; LOCATIONS CHOSEN FOR EASY CIRCULAR-INCREMENT OF POINTERS:
;    SRQ=XXXX XXXX XX00 0000B
;    STQ=XXXX XXXX XXXX 0000B
;***********************************************************
	ORG RQLOC
SRQ:	DS	SRQSIZ

	ORG TQLOC
STQ:	DS	STQSIZ

;********
; STACKS
;********
	DS	VSSIZE	;STACK FOR CALLS TO VIDEO DRIVER
VDSTAK:	EQU	$

	ORG	FXDRAM
SYSTP	EQU	$	;MAIN STACK GROWS DOWN FROM HERE

;****************************
; UNINITIALIZED RAM VARIABLES
;****************************
MODGO:	DS	2	;MODULE RAM EXEC ADDRESS
BRGSAV:	DS	1	;CURRENT BAUD SETTING ETC

;***********************************************************
; FIXED-LOCATION VARIABLES THAT ARE DEFINED IN THE INTERFACE
;***********************************************************
PTMRISR	DS	3	;TIMER OVERFLOW ISR (RET OR JMP)
PTIMER	DS	2	;2-BYTE 60-HZ COUNT-DOWN TIMER
PTOVFL	DS	1	;<>0 MEANS TIMER OVERFLOWED

;===========================================================
;CHECK FOR ASSEMBLY ERRORS. THE IF DIRECTIVE ONLY TESTS
;   BIT ZERO, SO THE SIGN BIT OF THE COMPARISON IS SHIFTED
;   DOWN TO BIT ZERO.
;===========================================================

; RAMEND+ROF MUST BE LESS THAN OR EQUAL TO SRQ
 IF (SRQ - (RAMEND+ROF)) SHR 15
 ERROR: LOADED CODE OVERWRITES RX QUEUE (SRQ)
 ENDIF

; CODE MUST FIT IN 1K PROM

 IF (400H-(RAMEND-ROMLOC)) SHR 15
 ERROR: CODE LARGER THAN 1K
 ENDIF

	END
