;------------------------------------------------------------------------------
;
; IOBBIOS.ASM (BIOS portion of IOBYTE.COM)
;
;	IOBYTE.COM is built from IOBLOAD.ASM and IOBBIOS.ASM. IOBYTE.COM
;  	inserts code into unitialized space of the CP/M 2.2 BIOS to provide
;	full IOBYTE support. It is compatible with the following versions
;	of CP/M 2.2b (track buffered CP/M 2.2):
;
;	  - Tarbell for the Altair v1.4 or newer
;	  - FD3712 for the Altair v1.0 or newer
;
;	IOBLOAD is the CP/M program that runs at 0100h and loads the code
;	for IOBYTE support into the BIOS. It also provides options for
;	enabling and disabling automatic loading of the BIOS code after
;	cold boot.
;
;	IOBBIOS contains the code for implementing IOBYTE features that
;	is copied into the BIOS.
;
;       Use DDT to create IOBYTE.COM from IOBLOAD.HEX and IOBBIOS.HEX.
;
;	A>DDT IOBLOAD.HEX
;	-IIOBBIOS.HEX
;	-Rbbb		(where bbb is BIAS equate at end of IOBLOAD.PRN)
;	NEXT  PC	(displayed by DDT)
;	xxyy 0000	(displayed by DDT)
;	-G0		(go zero, not "oh")
;	A>SAVE p IOBYTE.COM   (where "p" (pages) is "xx" in decimal)
;	
;	Ver	  Date		     Description
;	---	----------	------------------------------------------
;	1.0 	11/17/2019	Mike Douglas, Original
;
;------------------------------------------------------------------------------
;
; The default IOBYTE installed by the BIOS at cold boot is at offset 34h from
;   the start of the BIOS. This byte can be patched by the end user to modify
;   port assignment upon power up. See BIOS.ASM for additional information.
;
; Following are the available port assignments:
;
; CON device (bits 1,0):                  PUN device (bits 5,4):
;   00 - TTY on 88-SIO at 0/1               00 - TTY on 88-SIO at 0/1
;   01 - CRT on 88-2SIO at 10h/11h*         01 - PTP on 88-2SIO at 10h/11h*
;   10 - BAT (indirect through RDR,LST)     10 - UP1 on cassette (SIO at 6/7)
;   11 - UC1 on 88-2SIO at 12h/13h          11 - UP2 on 88-2SIO at 12h/13h
;
;
; RDR device (bits 3,2):                  LST device (bits 7,6):
;   00 - TTY on 88-SIO at 0/1               00 - TTY on 88-SIO at 0/1
;   01 - PTR on 88-2SIO at 10h/11h*         01 - CRT on 88-2SIO at 10h/11h
;   10 - UR1 on cassette (SIO at 6/7)       10 - LPT on 88-2SIO at 12h/13h*
;   11 - UR2 on 88-2SIO at 12h/13h          11 - UL1 on 88-LPC at 2/3
; 
; *Default IOBYTE
;---------------------------------------------------------------------------

; Run-time relocation of IOBYTE code
;    The assembly address of IOBBIOS (RELBASE) is chosen so the MSB of the
;    address counter appears in code only as the MSB of an address reference
;    This allows runtime code relocation by looking for any bytes that match
;    FC or FD (with RELBASE=FCxx). FC is the call-if-minus opcode and FD is
;    undefined, so they are unlikely to appear in code. After any modification
;    of the IOBBIOS file, assemble the program program at a different address
;    and verify that FC and FD do not appear anywhere in the code.

RELBASE	equ	0FC80h		;assembly address of the IOBBIOS code

; CP/M page zero equates

WBOOT	equ	00h		;warm boot vector location

IOBYTE	equ	03h
SHFTCON	equ	1		;left rotate count to align CON in bits 2,1
SHFTRDR	equ	7		;left rotate count to align RDR in bits 2,1
SHFTPUN	equ	5		;left rotate count to align PUN in bits 2,1
SHFTLST	equ	3		;left rotate count to align LST in bits 2,1

; BIOS Jump Table Offsets. This table is used to compute BIOS entry
;     points by combining the symbol below with the MSB of the warm
;     boot address stored by CP/M at location 2 on page zero. Note
;     these offsets point to the address porition of the jump vector,
;     not the jump opcode.

vCONST	equ	07h		;console status
vCONIN	equ	0Ah		;console input
vCONOUT	equ	0Dh		;console output
vLIST	equ	10h		;list output 
vPUNCH	equ	13h		;punch output
vREADER	equ	16h		;reader input
vLISTST	equ	2Eh		;list status 

vSELDSK	equ	1Bh		;select disk (points to jump)

; 2SIO Serial Board 

SIO1CTL	equ	10h		;1st port on 2SIO board - control register
SIO1DAT	equ	11h		;1st port on 2SIO board - data register

SIO2CTL	equ	12h		;2nd port on 2SIO board - control register
SIO2DAT	equ	13h		;2nd port on 2SIO board - data register

SIORDRF	equ	01h		;read data register full flag
SIOTDRE	equ	02h		;transmit data register empty flag

; SIO Serial Board 

SIOCTL	equ	00h		;SIO board at 0 - control register
SIODAT	equ	01h		;SIO board at 0 - data register

ACRCTL	equ	06h		;SIO board at 6 - control register (cassette)
ACRDAT	equ	07h		;SIO board at 6 - data register (cassette)

SIORCV	equ	01h		;data received bit (inverted)
SIOXMT	equ	80h		;read to transmit (inverted)

; LPC Line Printer Controller

LPCCTL	equ	02h		;line printer control port
LPCDAT	equ	03h		;line printer data port

LPCBSY	equ	02h		;mask to test printer busy flag

; Miscellaneous equates

NULLOFF	equ	35h		;offset to sndNull flag
CR	equ	0Dh

;---------------------------------------------------------------------------
; Initialization - Called right after IOBLOAD loads and relocates
;    this code into the BIOS. Updates the BIOS jump vectors to point
;    to the new I/O entry points in this code.
;---------------------------------------------------------------------------
	org	RELBASE

init	lxi	h,WBOOT+2	;HL->MSB of BIOS base address
	mov	d,m		;D=MSB of BIOS address

	lxi	h,vecTbl	;HL->vector address table
	mvi	b,7		;number of entries to process

vecLoop	mov	e,m		;DE->LSB in BIOS jump vector

	inx	h		;HL->LSB of new jump address
	mov	a,m		;A=LSB of jump address
	stax	d		;update LSB of BIOS jump vector

	inx	d		;DE->MSB of BIOS jump vector
	inx	h		;HL->MSB of new jump address
	mov	a,m		;A=MSB of new jump address		
	stax	d		;update MSB of BIOS jump vector

	inx	h		;HL->next jump vector ofset
	dcr	b		;decrement entry count
	jnz	vecLoop
	
	ret

; vecTbl - For each of the seven BIOS jump vectors that must be updated,
;    this table defines the offset to the jump address in the vector
;    and the offset to the routine address as copied into the BIOS. 

vecTbl	db	vCONST
	dw	conSt

	db	vCONIN
	dw	conIn

	db	vCONOUT
	dw	conOut

	db	vLIST
	dw	list

	db	vPUNCH
	dw	punch

	db	vREADER
	dw	reader

	db	vLISTST
	dw	listSt

;---------------------------------------------------------------------------
;  CON device I/O
;---------------------------------------------------------------------------

; conSt - Console status BIOS entry point. Return FFh if character ready,
;    return zero otherwise. 

conSt	mvi	b,SHFTCON	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below

	dw	sioSt		;TTY use SIO at I/O address 0
	dw	sio1St		;CRT use 2SIO port 1
	dw	batchSt		;BAT indirect through RDR logical device
	dw	sio2St		;UC1 use 2SIO port 2

; conIn - Console input BIOS entry point. Wait for console input and
;    return character in A. Console input is used as an idle indicator
;    and as a good time to flush a dirty track buffer, unload the head, 
;    and invalidate the track buffer.

conIn	push	h		;preserve HL in case caller assumes it
	call	invFlsh		;invalidate and flush track buffer
	pop	h
	
	mvi	b,SHFTCON	;B=console IOBYTE alignment shift count
	mvi	c,07Fh		;C=AND mask for input byte
	call	doIo		;call the proper I/O routine below

	dw	sioIn		;TTY use SIO at I/O address 0
	dw	sio1In		;CRT use 2SIO port 1
	dw	reader		;BAT indirect through RDR logical device
	dw	sio2In		;UC1 use 2SIO port 2

; conOut - Console output BIOS entry point. Output the character in C.
;   If the sndNull flags is true, then follow CR with a null for Teletype.

conOut	call	doConO		;send the character passed in C

	lda	WBOOT+2		;A=MSB of BIOS address
	mov	d,a		;form DE=address of sndNull flag
	mvi	e,NULLOFF	;offset to sndNull flag
	ldax	d		;sending null for CR?
	ora	a
	rz			;no, exit
	
	mov	a,c		;was character a CR?
	sui	CR
	rnz			;no, exit
	
	mov	c,a		;put a zero in C and send it
				;fall into doConO

doConO	mvi	b,SHFTCON	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below

	dw	sioOut		;TTY use SIO at I/O address 0
	dw	sio1Out		;CRT use 2SIO port 1
	dw	list		;BAT indirect through LST logical device
	dw	sio2Out		;UC1 use 2SIO port 2

;---------------------------------------------------------------------------
; RDR device - Reader input character BIOS entry point. Return in A
;---------------------------------------------------------------------------
reader	mvi	b,SHFTRDR	;B=reader IOBYTE alignment shift count
	mvi	c,0FFh		;C=AND mask for input byte
	call	doIo		;call the proper I/O routine below

	dw	sioIn		;TTY SIO at I/O address 0
	dw	sio1In		;PTR use 2SIO port 1
	dw	acrIn		;UR1 use cassette port (SIO 6/7)
	dw	sio2In		;UR2 use 2SIO port 2 

;---------------------------------------------------------------------------
; PUN device - Punch output character BIOS entry point. Byte to send in C.
;---------------------------------------------------------------------------
punch	mvi	b,SHFTPUN	;B=punch IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below

	dw	sioOut		;TTY SIO at I/O address 0
	dw	sio1Out		;PTP use 2SIO port 1
	dw	acrOut		;UP1 use cassette port (SIO 6/7)
	dw	sio2Out		;UP2 use 2SIO port 2

;---------------------------------------------------------------------------
;  LST device I/O
;---------------------------------------------------------------------------

; listSt - List output test BIOS entry point. Return 0FFh if list device
;    is ready, 0 if not ready

listSt	mvi	b,SHFTLST	;B=list IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below

	dw	sioOSt		;TTY use SIO at I/O address 0
	dw	sio1OSt		;CRT use 2SIO port 1
	dw	sio2OSt		;LPT use 2SIO port 2
	dw	lpcOSt		;UL1 use LPC board

; list - List output character BIOS entry point. Send the character
;    passed in C out the list port

list	mvi	b,SHFTLST	;B=list IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below

	dw	sioOut		;TTY use SIO at I/O address 0
	dw	sio1Out		;CRT use 2SIO port 1
	dw	sio2Out		;LPT use 2SIO port 2
	dw	lpcOut		;UL1 use LPC board

;---------------------------------------------------------------------------
;  BAT device I/O - The BAT device is assigned as a physical device
;     for CON. However, BAT is actually a logical device that uses
;     whatever RDR is assigned to for input and whatever LST is assigned
;     to for output. The only jump table that does not already exist
;     is the input test routine for the reader devices
;---------------------------------------------------------------------------

; batchSt - BAT device input status

batchSt	mvi	b,SHFTRDR	;BAT in uses RDR device
	call	doIo		;call the proper I/O routine below

	dw	siost		;TTY test SIO at I/O address 0	
	dw	sio1St		;RDR test 2SIO port 1
	dw	acrSt		;UR1 test cassette port (SIO 6/7)
	dw	sio2St		;UR2 test 2SIO port 2

;---------------------------------------------------------------------------
; doIo - Perform physical I/O device based on the IOBYTE. The value passed
;    in B contains the left-shift count to align the calling device's IOBYTE
;    field into bits 2 and 1 (not 1 and 0). Preserves H,L
;--------------------------------------------------------------------------- 
doIo	lda	IOBYTE		;A=CP/M IO BYTE

ioAlign	rlc			;rotate into bits 2 and 1
	dcr	b
	jnz	ioAlign

	ani	6		;get the 2 bit field alone

; 2 bit field aligned in bits 2 and 1. Index into jump table of caller.

	xthl			;hl=table address from stack, save hl

	mov	e,a		;form DE=table offset
	mvi	d,0
	dad	d		;HL->address in table based on IOBYTE

	mov	e,m		;E=lsb of where to jump
	inx	h
	mov	d,m		;D=msb of where to jump
	xchg			;HL->where to jump

	xthl			;jump address on stack, restore HL
	ret			;jump to address specified in table

;---------------------------------------------------------------------------
; Input status routines
;---------------------------------------------------------------------------

; sio1St - Console status routine for 2SIO port 1. Return FFh 
;    if character ready, return zero otherwise. 

sio1St	in	SIO1CTL		;read 2SIO #1 status/control register
	ani	SIORDRF		;data present?
	rz			;no, return zero

	mvi	a,0FFh		;else, return FF
	ret

; sio2St - Console status routine for 2SIO port 2. Return 0ff 
;    if character ready, return zero otherwise. 

sio2St	in	SIO2CTL		;read 2SIO #2 status/control register
	ani	SIORDRF		;data present?
	rz			;no, return zero

	mvi	a,0FFh		;else, return FF
	ret

; sioSt - Console status routine for SIO at I/O address 0. Return FFh 
;    if character ready, return zero otherwise. 

sioSt	in	SIOCTL		;read SIO status/control register
	xri	0FFh		;convert to positive logic
	ani	SIORCV		;data present?
	rz			;no, return zero

	mvi	a,0FFh		;else, return FF
	ret

; acrSt - Console status routine for ACR status (could get called through BAT)

acrSt	in	ACRCTL		;read ACR (SIO) control register
	xri	0FFh		;convert to positive logic
	ani	SIORCV		;data present?
	rz			;no, return zero

	mvi	a,0FFh		;else, return FF
	ret

;---------------------------------------------------------------------------
; Character input routines
;---------------------------------------------------------------------------

; sio1In - input character from 1st port on a 2SIO board and return in A.
;    C contains AND mask.

sio1In	call	sio1St		;wait for character
	jz	sio1In		;no, loop until data available

	in	SIO1DAT		;read the character
	ana	c		;possibly clear msbit
	ret

; sio2In - input character from 2nd port on a 2SIO board and return in A
;    C contains AND mask.

sio2In	call	sio2St		;wait for character
	jz	sio2In		;no, loop until data available
	
	in	SIO2DAT		;read the character
	ana	c		;possibly clear msbit
	ret

; sioIn - input character from SIO board at address 0/1
;    C contains AND mask.

sioIn	call	sioSt		;wait for character
	jz	sioIn		;no, loop until data available
	
	in	SIODAT		;read the character
	ana	c		;possibly clear msbit
	ret

; acrIn - input character from ACR SIO board at address 6/7

acrIn	call	acrSt		;wait for character
	jz	acrIn		;no, loop until data available
	
	in	ACRDAT		;read the character
	ret

;----------------------------------------------------------------------------
; Output status routines
;----------------------------------------------------------------------------

; sio1OSt - Return FFh if 2SIO Port #1 is ready, 0 if not ready

sio1OSt	in	SIO1CTL		;read 2SIO #1 status/control register
	ani	SIOTDRE		;0=busy
	rz			;not ready, return 0

	mvi	a,0FFh		;else, return FF
	ret

; sio2OSt - Return FFh if 2SIO Port #1 is ready, 0 if not ready

sio2OSt	in	SIO2CTL		;read 2SIO #2 status/control register
	ani	SIOTDRE		;0=busy
	rz			;not ready, return 0

	mvi	a,0FFh		;else, return FF
	ret

; lpcOst - Return FFh if 88-LPC is ready, 0 if not ready

lpcOSt	in	LPCCTL		;88-LPC board at port 2,3
	ani	LPCBSY		;0=busy
	rz			;not ready, return 0

	mvi	a,0FFh		;else, return FF
	ret

; sioOSt - Return FFh if SIO at 0/1 is ready, 0 if not ready
 
sioOSt	in	SIOCTL		;read SIO status/control register
	xri	0FFh		;convert to positive logic
	ani	SIOXMT		;0=busy
	rz			;not ready, return 0

	mvi	a,0FFh		;else, return FF
	ret
	
;---------------------------------------------------------------------------
; Character output routines with byte to send in C
;---------------------------------------------------------------------------

; sio1Out - Transmit byte through 2SIO port 1

sio1Out	call	sio1OSt		;wait for OK to xmit
	jz	sio1Out

	mov	a,c		;get character to xmit
	out	SIO1DAT		;send it
	ret

; sio2Out - Transmit byte through 2SIO port 2

sio2Out	call	sio2OSt		;wait for OK to xmit
	jz	sio2Out

	mov	a,c		;get character to xmit
	out	SIO2DAT		;send it
	ret

; sioOut - Transmit byte through SIO at address 0/1

sioOut	call	sioOSt		;wait for OK to xmit
	jz	sioOut

	mov	a,c		;get character to xmit
	out	SIODAT		;send it
	ret

; acrOut - Transmit byte through ACR SIO at address 6/7

acrOut	in	ACRCTL		;read ACR (SIO) control register
	ani	SIOXMT		;transmit data register empty?
	jnz	acrOut		;not yet, keep waiting

	mov	a,c		;get character to xmit
	out	ACRDAT		;send it
	ret

; lpcOut - Transmit byte through 88-LPC board.

lpcOut	call	lpcOSt		;wait for printer		
	jz	lpcOut

	mov	a,c
	ani	07Fh		;force bit 7 to zero
	out	LPCDAT		;output the character
	ret

;---------------------------------------------------------------------------
; invFlsh - Invalidate and flush the disk track buffer. This is done
;    by using the selDsk entry point to call the extended functions
;    supported by the BIOS. In this case, the invalidate and flush
;    function.
;---------------------------------------------------------------------------
invFlsh	mvi	c,0FFh		;drive=FF means extended function request
	mvi	b,0		;B=request type, flush track buffer
	
	lhld	WBOOT+1		;H contains MSB of BIOS address
	mvi	l,vSELDSK	;HL->BIOS selDsk vector
	pchl			;flush track buffer
	
	end


