;------------------------------------------------------------------------------
; IOBLOAD.ASM (loader portion of IOBYTE.COM)
;
;	IOBYTE.COM is built from IOBLOAD.ASM and IOBBIOS.ASM. IOBYTE.COM
;  	inserts code into unitialized space of the Tarbell 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 (IOBBIOS) 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 loaded into the BIOS by IOBLOAD.
;
;       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 file)
;	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
;------------------------------------------------------------------------------

; 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
;    0FCh or 0FDh (with RELBASE=FCxx). 0FCh is the call-if-minus opcode and
;    0FDh is undefined, so they are unlikely to appear in code. After any
;    modification of the IOBBIOS file, assemble the program at a different
;    address and verify that 0FCh and 0FDh do not appear in the code.

RELBASE	equ	0FC80h		;assembly address of the IOBBIOS code

IOBOFF	equ	0380h		;offset to IOBYTE code in BIOS
IOBPOFF	equ	IOBOFF SHR 8	;page offset of IOBYTE code in BIOS
IOBLEN	equ	0180h		;length of IOBYTE code region in BIOS

; CP/M page zero equates

WBOOT	equ	00h		;warm boot vector location
IOBYTE	equ	03h		;IOBYTE location
BDOS	equ	05h		;BDOS entry vector

DEFFCB	equ	5Ch		;default FCB created by CCP
DEFDMA	equ	80h		;default file I/O buffer address

; 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.

vSELDSK	equ	1Bh		;select disk
vSETTRK	equ	1Eh		;set track number
vSETSEC	equ	21h		;set sector number
vSETDMA	equ	24h		;set transfer address
vREAD	equ	27h		;sector read
vWRITE	equ	2Ah		;sector write

; Location of the CCP and the BIOS within the boot tracks

NUMSEC	equ	26		;sectors per track
SECLEN	equ	128		;sector length
TRKLEN	equ	SECLEN*NUMSEC	;track length

BOOTLEN	equ	128		;length of boot loader
CCPLEN	equ	0800h		;length of the CCP
BDOSLEN	equ	0E00h		;length of the BDOS

CCPSEC	equ	BOOTLEN/SECLEN+1   ;CCP starts after the boot sector
BIOSSEC	equ	(BOOTLEN+CCPLEN+BDOSLEN-TRKLEN)/SECLEN + 1  ;BIOS starts after BDOS

; Offset to the command string within the CCP and the MODE byte within the BIOS

CCPCMD	equ	07h		;offset to CCP command string (length byte is first)
DEFIOB	equ	34h		;offset to default IOBYTE (in BIOS)	
MODEBYT	equ	33h		;offset to MODE byte (in BIOS)

; Miscellaneous

CONIN	equ	01h		;BDOS console input
PRINT	equ	09h		;BDOS display message
CR	equ	0Dh
LF	equ	0Ah

;---------------------------------------------------------------------------
; Program entry point
;    Copy the BIOS code from this program up into the space reserved
;    in the BIOS for this code and then update the BIOS jump vectors
;    to point to the new code.
;---------------------------------------------------------------------------
	org	0100h		;start of CP/M program area

	lxi	h,0		;save stack pointer
	dad	sp
	shld	stkSave
	
	lxi	sp,lclStk	;switch to local stack
	
	lda	DEFFCB+1	;A=parameter from command line
	ori	20h		;convert upper to lower case

	cpi	'l'		;manual load?
	jz	load		;yes, go load the IOBYTE code
	
	cpi	'a'		;set automatic load?
	jz	bootUpd		;yes, update the boot option
	
	cpi	'd'		;disable automatic load?
	jz	bootUpd		;yes, update the boot option

	cpi	'c'		;load command from cold boot?
	mvi	a,CR		;get rid of line feed in "loaded" message
	sta	mLoad+1
	jz	load		;same load, just skip leading LF
	
	lxi	d,mHelp		;invalid parameter, display help
	mvi	c,PRINT		;display message pointed to by DE
	call	BDOS

	mvi	c,CONIN		;see if user wants more help displayed
	call	BDOS

	lxi	d,mCrLf		;assume no more help requested
	ori	20h		;convert upper case to lower case
	cpi	'n'		;display more?
	jz	exit		;stop if 'n', continue for anything else

	lxi	d,mHelp2	;display 2nd page of help and exit

; exit - Display the message pointed to by DE. Restore the incoming stack
;    pointer and exit
	
exit	mvi	c,PRINT		;display message pointed to by DE
	call	BDOS
	
	lhld	stkSave		;restore CCP stack pointer
	sphl
	ret			;exit to CCP

;---------------------------------------------------------------------------
; bootUpd - Update the boot tracks to enable or disable automatic load
;    of the IOBYTE code.
;---------------------------------------------------------------------------
bootUpd	ani	01h		;'a'->01, 'd'->00
	mov	b,a		;save in B
	
	lxi	h,WBOOT+2	;HL->MSB of BIOS address
	mov	h,m		;H=MSB of BIOS address
	mvi	l,MODEBYT	;L=offset to MODE byte
	mov	a,m		;A=current MODE byte from memory

	ani	0FEh		;clear cold boot flag
	ora	b		;put in the new cold boot flag
	sta	newMode		;save for later
		
; Update the CCP command buffer on disk to contain "IOBYTE C"

	mvi	c,0		;select boot drive
	call	selDsk
	
	mvi	c,0		;CCP is in track 0
	call	setTrk
	
	mvi	c,CCPSEC	;specify the CCP sector
	call	setSec
	
	lxi	b,DEFDMA	;use CP/M default buffer at 80h
	call	setDma

	call	read		;read 1st sector of the CCP
	lxi	d,mRdErr	;DE->read error msg 
	jnz	exit		;exit if read failed
	
	lxi	h,mCmdStr	;HL->command string to insert into CCP
	lxi	d,DEFDMA+CCPCMD	;DE->CCP command string in disk buffer
	mov	b,m		;B=number of bytes to move-1

moveStr	mov	a,m		;move command string to disk buffer
	stax	d
	
	inx	h		;bump pointers
	inx	d

	dcr	b		;decrement byte count
	jp	moveStr		;move all bytes
	
	call	write		;write back the updated CCP	
	lxi	d,mWrtErr	;DE->write error msg 
	jnz	exit		;exit if write failed

; Update the MODE byte in the BIOS to run or not run IOBYTE at cold boot. Copy
;    the current IOBYTE from RAM into the default IOBYTE location in the BIOS.
				
	mvi	c,1		;BIOS is in track 1
	call	setTrk
	
	mvi	c,BIOSSEC	;BIOS starts at sector 20
	call	setSec
	
	call	read		;read 1st sector of the BIOS
	lxi	d,mRdErr	;DE->read error msg 
	jnz	exit		;exit if read failed
	
	lda	IOBYTE		;A=current IOBYTE from RAM page 0
	sta	DEFDMA+DEFIOB	;store in the BIOS boot sector

	lda	newMode		;A=new mode byte
	sta	DEFDMA+MODEBYT	;store in the BIOS boot sector
	
	call	write		;write the BIOS sector back to boot track
	lxi	d,mWrtErr	;DE->write error msg 
	jnz	exit		;exit if write failed
	
	lxi	d,mAuto		;DE->auto load enabled message
	lda	newMode		;A=MODE byte we just wrote
	ani	01h		;auto-run bit set?
	jnz	exit		;yes

	lxi	d,mNoAuto	;else, DE->auto load disabled message
	jmp	exit		
	

;---------------------------------------------------------------------------
; load - Load the IOBYTE code into the reserved area in the BIOS. Relocate
;    the address MSB as the code is moved.
;---------------------------------------------------------------------------
load	lxi	d,IOBBIOS	;DE=where to copy from

	lda	WBOOT+2		;A=MSB of BIOS address
	adi	IOBPOFF		;A=MSB offset into BIOS for IOBYTE code
	mov	h,a		;H=MSB of destination
	mvi	l,RELBASE and 0FFh   ;L=LSB of destination

	lxi	b,IOBLEN	;BC=number of bytes to copy

cpyCode	ldax	d		;get next byte to copy
	cpi	(RELBASE shr 8)	;page relocatable address (FC)?
	jz	reloc		;yes, relocate it to address in H

	cpi	(RELBASE shr 8)+1  ;page relocatable address (FD)?
	jnz	store		;no

reloc	sui	(RELBASE shr 8)-IOBPOFF	;A=page 3 or 4 relative to BIOS
	push	h		;preserve H
	lxi	h,WBOOT+2	;HL->MSB of BIOS address
	add	m		;A=relocated MSB
	pop	h		;restore HL

store	mov	m,a		;store byte in BIOS

	inx	h		;increment pointers
	inx	d

	dcx	b		;decrement byte count
	mov	a,b		;reach zero?
	ora	c	
	jnz	cpyCode		;no, do next byte

; Call the initialization entry point of the IOBYTE code 

	lxi	h,ldExit	;push "return" address onto stack
	push	h

	lxi	h,IOBOFF	;HL=offset of IOBYTE code within BIOS
	lda	WBOOT+2		;A=MSB of BIOS address
	add	h		;compute MSB of IOBYTE code
	mov	h,a		;HL=address of IOBYTE code

	pchl			;call IOBYTE initializaton entry

; Display "done" message and exit

ldExit	lxi	d,mLoad
	jmp	exit

;---------------------------------------------------------------------------
; BIOS calls
;---------------------------------------------------------------------------
selDsk	mvi	a,vSELDSK	;A=offset of selDsk in BIOS jump table
	jmp	goBios		;jump to selDsk

setTrk	mvi	a,vSETTRK	;A=offset of setTrk in BIOS jump table
	jmp	goBios		;jump to setTrk

setSec	mvi	a,vSETSEC	;A=offset of setSec in BIOS jump table
	jmp	goBios		;jump to setSec

setDma	mvi	a,vSETDMA	;A=offset of setDma in BIOS jump table
	jmp	goBios		;jump to setDma

read	mvi	a,vREAD		;A=offset of read in BIOS jump table
	jmp	goBios		;jump to read

write	mvi	a,vWRITE	;A=offset of write in BIOS jump table
				;fall into goBios

; goBios - Jump to the BIOS entry vector whose offset is in A

goBios	lxi	h,WBOOT+2	;HL->MSB of BIOS address
	mov	h,m		;H=MSB of BIOS address
	mov	l,a		;HL->requested BIOS jump vector
	pchl			;jump into BIOS

;---------------------------------------------------------------------------
; Message Strings
;---------------------------------------------------------------------------
	
mHelp
 db CR,LF
 db 'IOBYTE.COM v1.0',CR,LF
 db LF
 db 'Loads IOBYTE support into the "Tarbell for Altair" BIOS in RAM',CR,LF
 db '  (Requres BIOS ver 1.4 or newer)',CR,LF
 db LF
 db 'Usage: IOBYTE L|A|D',CR,LF
 db LF
 db 'Where:',CR,LF
 db '   L = Load IOBYTE support into the BIOS in RAM',CR,LF
 db '   A = Auto-load IOBYTE support at cold start',CR,LF
 db '   D = Disable auto-load',CR,LF
 db LF
 db 'Note: The A and D options also write the current IOBYTE',CR,LF
 db '      from RAM to the boot tracks to become the default',CR,LF
 db '      IOBYTE upon cold boot',CR,LF
 db LF
 db 'More (Y/N)?$'

;---------------------------------------------------------------------------
; mHelp2 looks like this:
;
; 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
;---------------------------------------------------------------------------
mHelp2
 db CR,LF,LF
 db 'CON device (bits 1,0):                  '
 db 'PUN device (bits 5,4):',CR,LF
 db '  00 - TTY on 88-SIO at 0/1             '
 db '  00 - TTY on 88-SIO at 0/1',CR,LF
 db '  01 - CRT on 88-2SIO at 10h/11h*       '
 db '  01 - PTP on 88-2SIO at 10h/11h*',CR,LF
 db '  10 - BAT (indirect through RDR,LST)   '
 db '  10 - UP1 on cassette (SIO at 6/7)',CR,LF
 db '  11 - UC1 on 88-2SIO at 12h/13h        '
 db '  11 - UP2 on 88-2SIO at 12h/13h',CR,LF
 db LF,LF
 db 'RDR device (bits 3,2):                  '
 db 'LST device (bits 7,6):',CR,LF
 db '  00 - TTY on 88-SIO at 0/1             '
 db '  00 - TTY on 88-SIO at 0/1',CR,LF
 db '  01 - PTR on 88-2SIO at 10h/11h*       '
 db '  01 - CRT on 88-2SIO at 10h/11h',CR,LF
 db '  10 - UR1 on cassette (SIO at 6/7)     '
 db '  10 - LPT on 88-2SIO at 12h/13h*',CR,LF
 db '  11 - UR2 on 88-2SIO at 12h/13h        '
 db '  11 - UL1 on 88-LPC at 2/3',CR,LF,LF
 db '*Default IOBYTE'

mCrLf
 db CR,LF,'$'

mLoad
 db CR,LF
 db 'IOBYTE support loaded into the BIOS',CR,LF,'$'
 
mAuto
 db CR,LF
 db 'Automatic load at cold start enabled',CR,LF
 db 'Current IOBYTE saved as the default',CR,LF,'$'

mNoAuto
 db CR,LF
 db 'Automatic load at cold start disabled',CR,LF
 db 'Current IOBYTE saved as the default',CR,LF,'$'

mCmdStr
 db mCmdEnd-mCmdStr-1	;length of the string
 db 'IOBYTE C'
mCmdEnd equ $

mRdErr
 db CR,LF
 db 'Error reading boot tracks',CR,LF,'$'

mWrtErr
 db CR,LF
 db 'Error updating boot tracks',CR,LF,'$'

;---------------------------------------------------------------------------
; Data area
;---------------------------------------------------------------------------
newMode	ds	1		;new MODE byte to write to BIOS
stkSave	ds	2		;saved stack pointer from CCP

	ds	48		;stack space
lclStk	equ	$		;local stack


;---------------------------------------------------------------------------
; Place code from IOBBIOS here to be copied into the BIOS
;---------------------------------------------------------------------------
	org	($+07Fh) and 0FF80h	;80h boundary

IOBBIOS	equ	$
BIAS	equ	$-RELBASE		;use BIAS to load IOBBIOS in DDT

	end



