;==============================================================
;                  RXMODEM by Martin Eberhard
;                    a PTDOS Device Driver
;
; A minimal Xmodem Checksum-only, Receive-Only Device Driver,
; intended for use during bootstrapping a system
;
; This program runs in Solos/Cuter "User Area," (at CAB4h) and
; should therefore be out of the way of any PTDOS programs. It
; also uses the Solos/Cuter cassette tape buffer for buffering
; XMODEM block data, again away from any PTDOS programs.
;
; This program is <= 250 bytes long. When PTDOS adds the
; 4-byte header and 2-byte execution address, it fits in one
; (256-byte) PTDOS sector.
;
;BOOTSTRAPPING A PTDOS SYSTEM THAT HAS NO COM PROGRAM
;
; 1. Set the Sol-20 serial port to no higher than 1200 baud.
;    (8 data bits, no parity, 1 stop bit, full duplex)
;
;      S4-1 off (even parity)     For 1200 Baud:
;      S4-2 off (8 bits)            S3-6 on
;      S4-3 off (8 bits)            All other S3 switches off
;      S4-4 on  (1 stop bit)
;      S4-5 off (no parity)
;      S4-6 off (full duplex)
;
; 2. ==>MAKE SURE THE SOL-20 LOCAL KEY IS OFF<==
;
; 3. Use Solos/Cuter to load RXMODEM.ENT:
;   >SE I=1
;   (Use e.g. Hyperterm to send (as a text file) the file
;   "RXMODEM.ENT" to the Sol-20 serial port)
;
; 4. Boot PTDOS:
;   >BO
;
; 5. Create an Image file of the loaded program on PTDOS disk:
;   *IMAGE RXMODEM CAB4,CBA2,CAB4
;
; 6. Convert the Image file to a Device Driver
;   *RETYPE RXMODEM,D
;
; Here is how load a binary file produced by an assembler such
; as ASM: Let's assume the file is called INTEL.COM, and it is
; written to be a Device Driver. Assume it is ORG'd at
; address 100h, and its last address in memory is 550h.
;
; 7. Use RXMODEM to load the binary file:
;   (Use e.g. Hyperterm to send (via XMODEM protocol) the file
;   "INTEL.COM" to the Sol-20 serial port. Note that you may
;    use any baud rate, for example 9600 baud.)
;
;   *COPY RXMODEM,INTEL.B
;
; 8. Read the binary file into memory:
;   *READ INTEL.B,100
;   (PTDOS should say that the file length was 450h.)
;
; 9. Create an Image file from the binary image in memory:
;   *IMAGE INTEL,<bang>4C0,100,550,100
;   (This assembler won't allow an exclaimation point. The
;   "<bang>" above is really an exclaimation point.)
;
;10. Convert the Image file to a Device Driver:
;   *RETYPE INTEL,D
;
;11. Delete the superfluous binary file:
;   *KILL INTEL.B
;    
;==============================================================
; Revision History
;
; 1.00  3 March 2015  M.Eberhard
;   Created
;==============================================================

;*******************
;SOLOS/CUTER Equates
;*******************
SOLOS	equ	0C000h		;Solos/Cuter base address
AOUT	equ	SOLOS+1Ch	;send b to pseudoport a
AINP	equ	SOLOS+22h	;receive a from pseudoport a

FBUF1	equ	SOLOS+0863h	;Solos/Cuter tape buffer
USARE	equ	SOLOS+0AB4h	;User area in Solos/Cuter RAM

;***************
;Program Equates
;***************
RTRIES	equ	10		;XMODEM error retry max
RUNADR	equ	USARE		;Run in User RAM
	
DATBUF	equ	(FBUF1+0FFh) and 0FF00h	;128-byte data buffer
				;must be page aligned

;****************
;ASCII characters
;****************
SOH	equ	1
EOT	equ	4
ACK	equ	6
NAK	equ	15H
CR	equ	0DH
LF	equ	0AH

;-----------------------------
;PTDOS definitions from PTDEFS
;-----------------------------
; 1) MAIN ENTRY POINTS  (EP AREA)

SYS	equ	0BCBCh
RB	equ	SYS-3	;READ SINGLE BYTE
WB	equ	SYS-6	;WRITE SINGLE BYTE
UTIL	equ	SYS-9	;UTILITY HANDLER
RESET	equ	SYS-12	;RESET TO SYSTEM
ERRL0	equ	SYS-15	;ERROR LEVEL 0 RETURN FROM DRIVER
ERRL1	equ	SYS-18	;ERROR LEVEL 1 RETURN FROM DRIVER
ERRL2	equ	SYS-21	;ERROR LEVEL 2 RETURN FROM DRIVER
SYSGLO	equ	SYS-23	;POINTER TO SYSTEM GLOBAL AREA
CONIN	equ	SYS-26	;CONSOLE INPUT ONE CHARACTER
CONOUT	equ	SYS-29	;CONSOLE OUTPUT ONE CHARACTER
CONTST	equ	SYS-32	;CONSOLE INPUT READY TEST
PSCAN	equ	SYS-35	;PARAMETER SCANNER ENTRY
INTDN	equ	SYS-38	;INTERRUPT DONE ENTRY POINT
INTDK	equ	SYS-41	;INTERRUPT PROCESSING ENTRY

; 2) SYS CALL OPERATIONS  (CODE FOR OPERATION BYTE)

CREOP	equ	0	;CREATE FILE  (DE -> PARMS)
OPEOP	equ	1	;OPEN FILE  (DE -> NAME, HL = BUFF OPT)
KILOP	equ	2	;KILL FILE  (DE -> NAME)
RBLOP	equ	3	;READ BLOCK OF DATA
WBLOP	equ	4	;WRITE BLOCK OF DATA
SPAOP	equ	5	;MOVE FILE CURSOR
EOFOP	equ	6	;ENDFILE A FILE
CLOOP	equ	7	;CLOSE A FILE
CHTOP	equ	8	;CHANGE TYPE OF FILE
CHAOP	equ	9	;CHANGE ATTRIBUTES OF A FILE
CHNOP	equ	10	;CHANGE NAME OF A FILE
INFOP	equ	11	;GET INFO ABOUT A FILE
SUNOP	equ	12	;SET DEFAULT UNIT
RETOP	equ	13	;RETURN TO CI
RESOP	equ	14	;RESET
ABTOP	equ	15	;ABORT, THEN RETURN TO CI
SEKOP	equ	16	;SET FILE CURSOR TO THIS POINT (SEEK)
RNDOP	equ	17	;MAKE FILE RANDOMLY ACCESSIBLE
CAOP 	equ	18	;CLOSE ALL FILES (OR ALL ON A UNIT)
CTLOP	equ	19	;CONTROL/STATUS OF DRIVER
SREOP	equ	20	;SHORT RESET TO CI
DRDOP	equ	21	;DELIMITED READ
DWROP	equ	22	;DELIMITED WRITE
DSPOP	equ	23	;DELIMITED SPACE
RTROP	equ	24	;RETURN AND SET TRAP

; 3) FILE PROTECTION ATTRIBUTES

PKIL	equ	1	;DELETE PROTECT
PWRI	equ	2	;WRITE PROTECTION
PREA	equ	4	;READ PROTECTION
PFINF	equ	8	;INFORMATION PROTECT (FILES WON'T LIST)
PATR	equ	10h	;ATTRIBUTE CHANGE PROTECTION
PNAT	equ	20h	;NAME AND TYPE CHANGE PROTECTION
PALO	equ	40h	;DISK ALLOCATION PROHIBITION
PUSR	equ	80h	;USER ATTRIBUTE

; 4) SYSTEM GLOBAL AREA

SGA	equ	0	;System Global Area base
GLCIF	equ	SGA	;CI INPUT FILE #
GLCOF	equ	SGA+1	;CI OUTPUT FILE #
GLUTF	equ	SGA+2	;UTILITY FILE #
GLECF	equ	SGA+3	;CI ECHO FILE #
GLUNI	equ	SGA+4	;DEFAULT UNIT #
GLPRM	equ	SGA+5	;# OF PERMANENTLY OPEN FILES
GLMXU	equ	SGA+6	;MAX UNIT NUMBER (= # DRIVES+1)
GLSWI	equ	SGA+7	;SYSTEM SWITCHES
GLERS	equ	SGA+9	;SOFT (LEVEL 2) ERROR WORD
GLERM	equ	SGA+11	;MODERATE (LEVEL 1) ERROR WORD
GLERH	equ	SGA+13	;HARD (LEVEL 0) ERROR WORD
GLTRP	equ	SGA+15	;CI RETURN TRAP WORD
GLPRO	equ	SGA+17	;USER MEMORY PROTECT WORD
GLFLG	equ	SGA+19	;CHARACTER WAITING FLAG
GLBYT	equ	SGA+20	;THE CHARACTER
GLRCH	equ	SGA+21	;FILE #0 READ CHRR ROUTINE POINTER
GLWCH	equ	SGA+23	;FILE #1 WRITE CHR ROUTINE POINTER
GLTCH	equ	SGA+25	;CONSOLE TEST CHR READY ROUTINE PTR
GLNCT	equ	SGA+27	;NULL COUNT FOLLOWING CR
GLVER	equ	SGA+28	;SYSTEM VERSION
GLDAT	equ	SGA+29	;DATE (MM,DD,YY)
GLNAM	equ	SGA+32	;DISK NAME (8 ASCII CHARACTERS)
GLPAS	equ	SGA+40	;PASSWORD
GLBD1	equ	SGA+48	;WAIT FOR INTERRUPT ROUTINE ADDRESS
GLBD2	equ	SGA+50	;RESERVED FOR FUTURE USE
GLIF1	equ	SGA+52	;INTERRUPT ENABLE FLAG
GLIF2	equ	SGA+53	;DISK INT PROCESSING COMPLETE FLAG
GLLOW	equ	SGA+54	;LOW ADDRESS OF BUFFER AREA
GLECH	equ	SGA+56	;ECHO DISABLE SWITCH
GLLOK	equ	SGA+57	;DISK WRITE LOCK SWITCH
GLUPS	equ	SGA+58	;UPSHIFT SWITCH
GLRBC	equ	SGA+59	;READ-BACK CHECK SWITCH
GLBIO	equ	SGA+60	;BINARY CONSOLE I/O SWITCH
GLLOG	equ	SGA+61	;LOG FILE SWITCH
GLVRB	equ	SGA+62	;VERBOSE SWITCH
GLFTR	equ	SGA+63	;SWITCH RESERVED FOR FUTURE USE

; 6) CXBUF AND OLBUF

CXBUF	equ	0BCC0H	;COMMAND EXECUTION BUFFER
OLBUF	equ	0BDC0H 	;OVERLAY BUFFER

; 7) STATUS BITS IN USER DEVICE WORD

SBPRO	equ	1	;SETTABLE PROMPT MESSAGE
SBINT	equ	2	;INTERRUPT DRIVEN
SBATB	equ	4	;DOES ABSOLUTE TABS
SBFF	equ	8	;DOES FORM-FEEDS
SBSTB	equ	10h	;SETTABLE TABS
SBTAB	equ	20h	;DOES TAB'S (CONTROL-I)
SBIAT	equ	40h	;INTERACTIVE
SBDUP	equ	80h	;DUPLICATIVE

; 8)  ERROR NUMBERS

ERNEX	equ	1	;NONEXISTENT FILE
ERAEX	equ	2	;FILE ALREADY EXISTS
ERUFN	equ	3	;UNASSIGNED FILE NUMBER
ERPRO	equ	4	;PROTECTION VIOLATION
ERNIF	equ	5	;FILE NOT TYPE IMAGE
ERMOP	equ	6	;ILLEGAL OP ON MULTIPLY OPEN FILE
ERRAC	equ	7	;RANDOM ACCESS TO NON-RANDON FILE
ERRDV	equ	8	;RANDOM ACCESS TO DEVICE FILE
ERZBC	equ	9	;ZERO BLOCK COUNT IN DEVICE IMAGE FILE

ERIOP	equ	10	;ILLEGAL PTDOS OPERATION
ERIBF	equ	11	;ILLEGAL BUFFER ADDRESS
ERIBS	equ	12	;ILLEGAL BLOCK SIZE
ERINM	equ	13	;ILLEGAL CHARACTER IN NAME
ERICU	equ	14	;ILLEGAL CHARACTER IN UNIT
ERIUN	equ	15	;ILLEGAL UNIT
ERNTL	equ	16	;NAME TOO LONG
ERMEM	equ	17	;USER MEMORY PROTECT VIOLATION
ERSMP	equ	18	;SYSTEM MEMORY PROTECT VIOLATION
ERUCN	equ	19	;UNIT CONFLICT
ERIUP	equ	20	;ILLEGAL UTILITY OPERATION
ERDRI	equ	21	;DRIVER ERROR
ERIDA	equ	22	;ILLEGAL DRIVER ACCESS
ERNCT	equ	23	;DRIVER NOT PARTICIPATING IN THIS CTRL

EREOF	equ	24	;END OF FILE ENCOUNTERED (OR BOF)
ERSEK	equ	25	;SEEK ADDRESS OUT OF RANGE

ERTOP	equ	26	;TOO MANY FILES OPEN
ERMOV	equ	27	;MEMORY OVERFLOW
ERNID	equ	28	;NO FILE ID'S LEFT
ERDFL	equ	29	;DISK FULL
ERDIR	equ	30	;DIRECTORY FULL

ERFSB	equ	32	;FILE STRUCTURE BAD
ERFIC	equ	33	;FILE ID CONFLICT
ERBSC	equ	34	;BLOCK SIZE CONFLICT
ERSCC	equ	35	;SECTOR CONFLICT
ERCFS	equ	36	;CAN'T FIND SECTOR
ERCRC	equ	37	;CRC ERROR OR READ/WRITE ABORTS
ERCFT	equ	38	;CAN'T FIND TRACK
ERRBC	equ	39	;READ-BACK-CHECK FAILED
ERLOK	equ	40	;DISK IS WRITE LOCKED
ERXXX	equ	41	;CATESTROPHIC ERROR
ERSYN	equ	42	;COMMAND SYNTAX ERROR
ERNAX	equ	43	;NAME EXPECTED
ERNUX	equ	44	;FILE NUMBER EXPECTED
ERBCX	equ	45	;BYTE COUNT EXPECTED
ERADX	equ	46	;ADDRESS EXPECTED
ERVAX	equ	47	;VALUE EXPECTED
EROPX	equ	48	;OPTIONAL PARAMETER EXPECTED
ERINA	equ	49	;ILLEGAL FILE NAME
ERIFI	equ	50	;ILLEGAL FILE NUMBER
ERIAD	equ	51	;ILLEGAL ADDRESS
ERIBC	equ	52	;ILLEGAL BYTE COUNT
ERIVA	equ	53	;ILLEGAL VALUE
ERIOS	equ	54	;ILLEGAL OPTION SPECIFIER
ERITY	equ	55	;ILLEGAL TYPE
ERIAT	equ	56	;ILLEGAL ATTRIBUTES
ERRED	equ	57	;DRIVE NOT READY

; 9) PARAMETER SCANNER OPERATIONS

PSCO	equ	0	;CREATE IF NONE EXIST AND OPEN
PSOP	equ	1	;OPEN FILE
PSFCO	equ	2	;FORCE CREATE AND OPEN FILE
PSC	equ	4	;CREATE FILE IF NONE EXIST
PSN	equ	5	;READ NAME TO REG DE
PSFC	equ	6	;FORCE CREATE FILE
PSOPT	equ	8 	;READ ALL CHRS UNTIL , ; cr 0
PSV	equ	85H	;CONVERT VALUE INTO REG DE
PSONE	equ	0A5H	;READ SINGLE BYTE

;NOTE: ABOVE VALUES + 40H = USER INPUT BUFFER IN REG HL

PSCN	equ	0D5H	;CONVERT MY VALUE TO DE, BASE IN REG B

; 10) UTILITIES

UXOP	equ	0	;EXPLAIN ERROR
UXOP1	equ	1	;DATA FOR UXOP
UXOP2	equ	2	;DITTO, PART 2
UCAT	equ	3	;CATALOG ROUTINE

;=================
;= Start of Code =
;=================
	ORG RUNADR

;******************************
;* PTDOS DEVICE DRIVER HEADER *
;******************************
DTRB	dw	DORB		;Read Block
DTRNB	dw	DORB		;Read Next Block
DTRLB	dw	0		;(No Support)
DTWBR	dw	0		;(No Support)
DTWB	dw	0		;(No Support)
DTREW	dw	DOREW		;Rewind
DTEOF	dw	0		;(No Support)
DTCLO	dw	DOCLO		;CLOSE DRIVER
DTSEK	dw	0000h		;(No Support)
DTCTL	dw	0000h		;(No Support)
DTBLK	dw	128		;Block size
DTITO	db	0		;No Immediate-Transfer Option
DTINI	dw	DOINI		;Initialize Driver

;******************************
;Load-Initialized RAM variables
;Note: the order here matters
;******************************	
BUFPTR:	dw	DATBUF+80h	;Xmodem buffer pointer
BLKNUM:	db	0		;Most recent block number
ERRCNT:	db	0		;Block error counter
ACKCHR:	db	NAK		;next EOF chr
				;(must follow ERRCNT)
EOFFLG:	db	0		;<>0 when EOF has been seen
				;(must follow ACKCHR)
ABTMSG:	db	'XMODEM ERROR',0 ;(must follow EOFFLG)


;***Function**************
;CLOSE
;REWIND
;*************************
DOINI:
DOCLO:
DOREW:	lxi	h,0
	ret

;***Function**************************************
;READ BLOCK
;On Entry:
;  hl = PTDOS buffer address
;  de = Max bytes in PTDOS buffer
;  EOFFLG = 0 until EOF received
;  BLKNUM = most recent block number received
;  BUFPTR = address of next byte to send to PTDOS
;         = DATBUF+80h if buffer has been emptied
;On Return (EOF or APORT)
;  EOFFLG <> 0
;  RETURN1
;On Return (No EOF or abort):
;  RETURN2
;  ACKCHR = ACK
;  EOFFLG <>0 1 if EOF has been received
;  BUFPTR = address of next byte to send to PTDOS
;         = DATBUF+80h if buffer has been emptied
;         = xx80h if the EOF has been received
;   BLKNUM = most recent block number received
;   hl = number of bytes transferred with this call
;**************************************************
DORB:	push	d		;save for end calculation
	mov	b,h
	mov	c,l		;bc=PTDOS buffer address

	lhld	BUFPTR		;XMODEM data pointer

;loop to transfer bytes until EOF or PTDOS buffer is full
;or until EOF encountered

XFRBYT:	mov	a,l		;Buffer is on 256-byte page
	ral			;Buffer is exactly 128 bytes
	push	d
	push	b
	cc	GDATA		;empty: get more XMODEM records
	pop	b
	pop	d

	jc	FNDEOF		;Carry: EOF or error detected
				;(there will be no data)

;Transfer data to PTDOS
;bc = dest address
;hl = source address, 128 bytes are here.
;de = max bytes to transfer

	mov	a,d		;any room in PTDOS buffer?
	ora	e		;also clears carry
	jz	XFRDON		;n: done for now

	mov	a,m		;get an XMODEM data byte
	stax	b		;and give it to PTDOS

	dcx	d		;less available space
	inx	b		;bump PTDOS buffer pointer
	inr	l		;bump XMODEM data pointer
	jmp	XFRBYT

;--------------------------------------------------------------
;EOF found or the PTDOS buffer is full. Report results to PTDOS
;--------------------------------------------------------------
FNDEOF:	mvi	l,80h		;force next transfer to
				;..be RETURN1

XFRDON:	shld	BUFPTR		;data pointer for next time

	pop	b		;original buffer space

	mov	a,c		;calculate bytes sent
	sub	e		;hl:= bc-de
	mov	l,a
	mov	a,b
	sbb	d
	mov	h,a		;hl=bytes sent

	ora	l		;Zero bytes?
	rz			;y: that means EOF: RETURN1

	pop	d		;Adjust for RETURN2
	inx	d
	inx	d
	inx	d
	push	d

	ret			;RETURN2, hl=byte count	
	
;***Subroutine*************************
;Get and validate one XMODEM record
; ==> ENTRY IS AT GDATA <===
;On Entry:
;  EOFFLG set if EOF has been received
;  BLKNUM = last block recveived
;On Exit with data:
;  hl=DATBUF unless error
;On Exit with out data (EOF or error)
;  Carry set
;  hl undefined
;trashes a,bc,de
;**************************************
IGNORE:	pop	h		;chuck ret to BLKERR

;==> ENTRY <==

GDATA:	lxi	h,ERRCNT	;reset block error count
	mvi	m,RTRIES	;max allowed errors

	inx	h		;hl=ACKCHR
	mov	b,m		;(initially a NAK)

	inx	h		;hl=EOFFLG

;RETRY for XMODEM errors enters here,
;with b=ACK or NAK, and hl=EOFFLG

RETRY:	xra	a
	cmp	m		;since a=0, anything but 0
	rc			;sets carry, meaning EOF

;Send ACK for the previous block, unless this is
;the 1st block - then send NAK.
;a=0, b=ACK or NAK, and hl=EOFFLG

	inr	a		;pseudoport 1
	call	AOUT		;send ACK or NAK

	lxi	b,BLKERR	;error-return address
	push	b		;..onto stack

;Wait for SOH, test for EOF

	lxi	b,0		;max timeout (6.3 sec)
	call	RXBYT
	rz			;Timeout? 'ret' to BLKERR

	mvi	b,ACK

	cpi	SOH
	jz	GETBLK		;SOH: Get the block

;We either got an EOT or an error

	xri	EOT		;EOT?
	rnz			;n: error 'ret' to BLKERR

;EOT received. remember, and return with carry set

	pop	d		;chuck ret to BLKERR

	inr	a		;a=1, hl=EOFFLG
	mov	m,a		;remember we got EOF

	call	AOUT		;a=1, b=ACK: ACK the EOT

	stc			;got an EOF
	ret

;Got the SOH. Now get the rest of the header
;b=ACK and hl=EOFFLG here.

GETBLK:	dcx	h		;hl=ACKCHR
	mov	m,b		;from now on, start with ACK

	call	RXBY1		;Get the block number
	mov	d,a		;d=block number
	cnz	RXBY1		;Get inverted block number
				;..unless error
	rz			;Timeout on either call?
				;'ret' to BLKERR

	cma
	sub	d		;inverted block number correct?
	rnz			;N: error 'ret' to BLKERR

;Receive 128 data bytes into DATBUF, and compute checksum

	mov	e,a		;a=0: initialize checksum
	lxi	h,DATBUF

DATLUP:	call	RXBY1		;get a data byte
	rz			;timeout? 'ret' to BLKERR

	mov	m,a		;new byte into buffer
	add	e		;compute carry
	mov	e,a
	inr	l		;next memory address
	jp	DATLUP		;128 bytes, page aligned

;Verify checksum with computed checksum in e

	call	RXBY1
	rz			;Timeout? 'ret' to BLKERR

	cmp	e
	rnz			;bad checksum? 'ret' to BLKERR

;If this is a repeat of the last block, chuck it.

	lxi	h,BLKNUM	;m=previous block number
	mov	a,d		;d=received block number
	sub	m		;same block number as last time?
	jz	IGNORE		;y: ignore block

;if this is not the next block number then error

	dcr	a		;difference of 1 allowed
	rnz			;wrong block? 'ret' to BLKERR

;Got a good block. Clean up and return

	inr	m		;bump BLKNUM

	pop	h		;chuck ret to BLKERR
	lxi	h,DATBUF	;set up pointer for return
	ret			;good block: carry is clear

;---------------------------------------------
;Bad block - try again unless too many errors.
;First, eat all chrs until 1-second timeout
; On Entry:
;   Z cleared if we need to wait a second
;   Z set if not
;---------------------------------------------
BLKERR:	cnz	RXBY1		;1 second timeout if needed
	jnz	BLKERR		;wait until timeout

	mvi	b,NAK		;in case we retry

	lxi	h,ERRCNT
	dcr	m

	inx	h		;hl=ACKCHR
	inx	h		;hl=EOFFLG
	jp	RETRY

;-------------------------------------------------------
;Too many errors: abort, treat as EOF
;
;On Entry:
;  b <> 0
; hl = EOFFLG
; null-terminated error message is at EOFFLG+1
;On Exit (from GDATA):
;  Carry set and EOFFLG <>0 to indicate (fake) EOF
;trashes a,hl
;-------------------------------------------------------
	mov	m,b		;nz: pretend we got an EOF

;Print error string, return with carry set
;hl=ABTMSG-1

PRINTF:	inx	h
	mov	a,m
	ora	a
	stc
	rz

	call	CONOUT
	jmp	PRINTF

;***Subroutine*****************************
;Get 1 serial byte, with timeout
;Entry at RXBY1 is 1 1-second timeout
;Entry at RXBYT is a bc/9903-second timeout
;Max timeout (bc=0) is 6.3 seconds
;On Exit:
;  a=received chr
;  Z set if timeout
;trashes bc
;******************************************
RXBY1:	lxi	b,10409		;1-second timeout

RXBYT:

RXBLP:	xra	a		;(4)serial pseudoport
	inr	a		;(5)slow way on purpose
	call	AINP		;(17+136) Use Solos
	rnz			;(5)got a chr?

	dcx	b		;(5)Timeout?
	mov	a,b		;(5)
	ora	c		;(4)
	rz			;(5)Z set: error ret
	jmp	RXBLP		;(10)

;196 cycles per pass = 96.1 uS with 2.04 MHz CPU

	END
