;---------------------------------------------------------------------
;  Serial Buffer Utility
;	This program allows the Altair to serve as a serial buffer
;	between a PC connected to 2SIO port A and a device
;	connected to 2SIO port B or the ACR (SIO at 6/7). 
;
;	Data from the PC is received into a 32K circular buffer. 
;	Data is then immediately transmitted from the circular buffer
;	out through serial port 2. Serial port 2 can be at a lower baud
;	rate (e.g., 110 baud for a teletype) or have handshaking enabled
;	(e.g, 6850 CTS in to throttle transmission for the EP-1 EPROM
;	programmer).
;
;	When transmitting a file to a slower device, the file transfer
;	dialog of the terminal emulator will rapidly complete (based
;	on a 9600 baud serial port 1 link). When the actual buffered
;	transfer to the slower device completes, the message
;	**Buffered I/O Complete** is displayed. Note that this only
;	takes place upon "substantial" buffering of 256 or more bytes.
;
;	Data from the device is immediately transmitted to the PC without
;	buffering.
;
;	Usage: SERBUF 1|2|A
;	  Where "1" or "2" uses 2SIO port B with 1 or 2 stop bits
;	        "A" uses the ACR SIO port
;
;	Written by Mike Douglas, July 2013
;
;	Rev	 Date	  Desc
;	1.0	7/12/13   Original
;
;	1.1	8/23/16	  Manually deselect the current drive to unload
;			  the head. Normally this is done by the BIOS
;			  as soon as a program does console input, but
;			  this program does not perform console input. 
;
;---------------------------------------------------------------------

;  I/O equates

sio1Csr	equ	10h		;control, status register port 1
sio1Dr	equ	11h		;data register port 1
sio2Csr	equ	12h		;control, status register port 2
sio2Dr	equ	13h		;data register port 2
sioRdrf	equ	01h		;receive data available mask
sioTdre	equ	02h		;ready to transmit mask
sio8N1	equ	15h		;8 bits, no parity, 1 stop bit
sio8N2	equ	11h		;8 bits, no parity, 2 stop bits

acrCsr	equ	06h		;SIO control port for ACR
acrDr	equ	07h		;SIO data port for ACR
acrRdrf	equ	01h		;receive data available mask
acrTdre	equ	80h		;ready to transmit maske

dskSel	equ	08h		;floppy controller select register

;  CP/M Equates

bdos	equ	5		;BDOS entry point
print	equ	9		;BDOS print string command
fcb	equ	5ch		;system FCB
cr	equ	0dh		;carriage return
lf	equ	0ah		;line feed

	org	0100h		;CP/M program start address
;----------------------------------------------------------------------
;
; Entry point. Validate command line parameter
;
;----------------------------------------------------------------------

;  If parameter is "A" then use ACR SIO for port 2.

	lda	fcb+1		;get parameter byte 1
	ori	20h		;set bit 5 (A becomes a)
	cpi	'a'		;ACR specified?
	jnz	notAcr		;no

	sta	fAcr		;non-zero = ACR
	in	acrDr		;clear possible garbage

	lxi	d,mAcr		;ACR port message
	mvi	c,print		;BDOS print message command
	call	bdos
	jmp	run
 
;  notAcr - If parameter is 1 or 2, use 2SIO port B for the second
;     port and set stop bits to 1 or 2.

notAcr	mvi	b,sio8N1	;settings for 1 stop bit
	cpi	'1'
	jz	iniPort

	mvi	b,sio8N2	;settings for 2 stop bits
	cpi	'2'		;two stop bits specified?
	jnz	badParm		;invalid or no parameter specified

	sta	mBits		;store "2" in stop bits message
	mvi	b,sio8N2	;use 2 stop bits

iniPort	mvi	a,3		;reset port 2 (port 1 already working)
	out	sio2Csr
	mov	a,b		;a=1 or 2 stop bits
	out	sio2Csr		;set 8N1 or 8N2

	lxi	d,m2Sio		;address of 2SIO message
	mvi	c,print		;BDOS print message command
	call	bdos
	jmp	run

; init - init for the serial buffering operation

badParm	lxi	d,mUsage
	mvi	c,print
	call	bdos
	ret			;return to CP/M

;----------------------------------------------------------------------
;
;  run - Processing loop. DE is the buffer insert pointer. HL is the
;     buffer remove pointer.
;
;----------------------------------------------------------------------
run	mvi	a,0ffh		;de-select disk drive to unload head
	out	dskSel

	lxi	h,buffer
	lxi	d,buffer

;  Read from port 1 into 32K buffer.

chkRcv1	in	sio1Csr		;character received port 1?
	rrc			;receive flag in lsb
	jnc	chkXmt2		;no, go check if character in buffer

	in	sio1Dr		;read the character
	stax	d		;store in buffer
	inx	d		;increment and wrap receive pointer
	mov	a,d		;see if msb of address set (32K boundary)
	ora	a
	jp	chkXmt2		;no wrap yet

	lxi	d,buffer	;wrap back to beginning

;  If port 2 ready for transmit and a character is present in the
;     32K buffer, send it.

chkXmt2	call	xmt2Sts		;check of port 2 read to xmit
	jz	chkRcv2		;no, not ready to send

	call	subDH		;subtract D-H
	jnz	haveChr		;pointers not equal, we have a character

;  Insert=remove pointer. If fBigBuf is set, then we have just
;     finished sending out buffered data through port 2. Display
;     *DONE* message and clear fBigBuf

	lda	fBigBuf		;big buffering flag set?
	ora	a		;test for zero
	jz	chkRcv2		;no, go on

	xra	a		;clear the flag
	sta	fBigBuf

	lxi	b,mDone		;display "*DONE*"
	call	dispMsg
	jmp	chkRcv2

;  haveChr - pull next character from the buffer and sent it
;     out serial port 2

haveChr	mov	b,m		;get character from the buffer
	call	xmt2Dat		;send out the data
	inx	h		;increment and wrap remove pointer
	mov	a,h		;see if msb of address set (32K boundary)
	ora	a
	jp	chkRcv2		;no wrap yet

	lxi	h,buffer	;wrap back to beginning

;  Read from port 2 and send out port 1

chkRcv2	call	rcv2Sts		;character present on port 2?
	jz	chkRcv1		;no, go back and check port 1

	call	rcv2Dat		;get the character
	out	sio1Dr		;echo on port 1
	jmp	chkRcv1

;----------------------------------------------------------------------
; subDH - Compute DE-HL and return zero if equal. Also sets the
;   fBigBuf flag true if we have substantial buffering going on
;   (DE-HL > 255). 
;----------------------------------------------------------------------
subDH	mov	a,e		;subtract lsbs first
	sub	l
	mov	c,a		;save lsb in c
	mov	a,d		;subtract msbs
	sbb	h
	ani	07fh		;wrap at 0x7fff instead of 0xffff
	jz	tstZero	
	
	sta	fBigBuf		;set fBigBuf true if > 255 bytes of buffering

tstZero	ora	c		;test for MSB and LSB = 0
	ret

;---------------------------------------------------------------------
; 
;  Port 2 I/O. Use 2SIO or ACR SIO based on the ACR flag (fAcr)
;
;---------------------------------------------------------------------

; xmt2Sts - Return transmit ready status. Zero=not ready

xmt2Sts	lda	fAcr		;ACR in use?
	ora	a
	jnz	xStsAcr

	in	sio2Csr		;use 2SIO
	ani	sioTdre		;test transmit ready bit
	ret			;zero=not ready

xStsAcr	in	acrCsr		;use ACR SIO port
	xri	0ffh		;convert to positive logic
	ani	acrTdre		;test transmit ready bit
	ret			;zero=not ready

; rcv2Sts - Return received data available status. Zero=no data available

rcv2Sts	lda	fAcr		;ACR in use?
	ora	a
	jnz	rStsAcr

	in	sio2Csr		;use 2SIO
	ani	sioRdrf		;receive data present
	ret			;zero=no data present

rStsAcr	in	acrCsr		;use ACR SIO port
	xri	0ffh		;convert to positive logic
	ani	acrRdrf		;receive data present
	ret			;zero=no data present

; xmt2Dat - Transmit the character passsed in B

xmt2Dat	lda	fAcr		;ACR in use?
	ora	a
	jnz	xDatAcr

	mov	a,b		;put character to xmit in a
	out	sio2Dr		;send via 2SIO
	ret

xDatAcr	mov	a,b		;put character to xmit in z
	out	acrDr		;send via ACR SIO port
	ret
	
; rcv2Dat - Return character in A

rcv2Dat	lda	fAcr		;ACR in use?
	ora	a
	jnz	rDatAcr

	in	sio2Dr		;receive via 2SIO
	ret

rDatAcr	in	acrDr		;send via ACR SIO port
	ret

;---------------------------------------------------------------------
;  dispMsg - display message pointed to by BC via serial port 1
;---------------------------------------------------------------------
dispMsg	in	sio1Csr		;wait for transmit ready
	ani	sioTdre
	jz	dispMsg

	ldax	b		;get character to send
	ora	a		;test for null terminator
	rz

	out	sio1Dr		;send the character
	inx	b
	jmp	dispMsg

;---------------------------------------------------------------------
;  
;  Data Area
;
;---------------------------------------------------------------------

fAcr	db	0		;ACR flag, not-zero = ACR
fBigBuf	db	0		;not-zero = Buffering > 255 bytes occuring

;  messages

mDone	db	cr,lf,lf,'**Buffered I/O Complete**',cr,lf,lf,0

mUsage	db	lf,'Serial I/O Buffer Program v1.1',cr,lf,lf
	db	'Provides bi-directional exchange of data between two',cr,lf
	db	'serial ports. Port 1 is always the first port on the 2SIO',cr,lf
	db	'board. Port 2 is either the second port on the 2SIO or',cr,lf
	db	'the ACR SIO port. 32K of buffering is provided from port 1',cr,lf 
	db	'to port 2.',cr,lf,lf
	db	'Usage: SERBUF 1|2|A',cr,lf
	db	'  where 1 or 2 uses 2SIO port with and 1 or 2 stop bits',cr,lf
	db	'        A uses the ACR SIO port',cr,lf,lf
	db	'Reset computer to exit the program.',cr,lf,lf,'$'

m2Sio	db	'Port 2 is 2SIO with '
mBits	db	'1 stop bit(s).',cr,lf,lf,'$'

mAcr	db	'Port 2 is the ACR SIO port.',cr,lf,lf,'$'

;  32K buffer starts here

buffer	equ	$

	end


