;--------------------------------------------------------------------------
;
;    Track-buffered CP/M 2.2b BIOS for the Altair FDC and 8 inch
;	drives running on a Poly-88 Computer.
;
;	Based on "Burcon CP/M 2.2" for the Altair from 1980, this version
;	implements full track buffering on both reads and writes to
;	improve performance. Loading programs and other sequential reads
;	improve by more than 50%. Performance of virtually all programs
;	with substantial disk I/O improves by 20%-50%.
;
;	This version also provides full IOBYTE support to allow more
;	flexible use of the Poly-88 console and serial port and to
;	allow BAT redirection so programs like generic Kermit can
;	be used. Search for "IOBYTE" in this source file for details.
;
;		Item	  Offset from start of BIOS
;		------	  ----------------------------------
;		MODE	  0559 (25D9 in memory after MOVCPM)
;		IOBYTE	  055A (25DA in memory after MOVCPM)
;
;	Version History
;
;   1.0   06/02/16  M. Douglas. Created from Altair CP/M 2.2b
;   1.1   11/15/17  M. Douglas. In conOut, don't send two CR's in a row
;		    to the video output. Change doIo for serial I/O to
;		    preserve HL. Fix listSt status check when CRT is
;		    is the output device - always return FF (ready).
;
;--------------------------------------------------------------------------
;
;   Load this BIOS into a saved CP/M image from running the Altair
;   version of MOVCPM for CPM 2.2b as follows:
;
;	A>DDT CPMxx.COM
;
;	-IBIOS.HEX
;	-Rxxxx      (where xxxx = bias equate below)
;
;--------------------------------------------------------------------------
;  CPM location equates

memSize	 equ	48		;memory size in K bytes

biosLen	 equ	1900h		;BIOS length in bytes
ccpLen	 equ	0800h		;CPM 2.2 fixed
bdosLen	 equ	0e00h		;CPM 2.2 fixed

ccpBase	 equ	memSize*1024 - (ccpLen+bdosLen+biosLen)
bdosEnt	 equ	(ccpBase+ccpLen+6)	 ;entry address of BDOS
biosBase equ	(ccpBase+ccpLen+bdosLen) ;base address of this BIOS

bias	 equ	0A80h-ccpBase

bootSiz	 equ	3*128			 ;3 sectors for boot code
loadTk0	 equ	(ccpBase-bootSiz)	 ;wboot load address for track 0
loadTk1	 equ	(loadTk0 + 01000h)	 ;wboot load address for track 1

; CPM page zero equates

wbootv	equ	000h		;warm boot vector location
bdosv	equ	005h		;bdos entry vector location
cdisk	equ	004h		;CPM current disk
defDma	equ	080h		;default dma address

ioByte	equ	003h		;CPM IOBYTE, shift counts below
shftCon	equ	1		;left rotate count to align CON in bits 2,1
shftRdr	equ	7		;left rotate count to align RDR
shftPun	equ	5		;left rotate count to align PUN
shftLst	equ	3		;left rotate count to align LST

; MITS Disk Drive Controller Equates

drvSel	equ	080h		;drive select port (out)
drvStat	equ	080h		;drive status port (in)
drvCtl	equ	081h		;drive control port(out)
drvSec	equ	081h		;drive sector position (in)
drvData	equ	082h		;drive read/write data

enwdMsk	equ	001h		;enter new write data bit mask
headMsk	equ	004h		;mask to get head load bit alone
moveMsk	equ	002h		;mask to get head move bit alone
trk0Msk	equ	040h		;mask to get track zero bit alone
dSelect	equ	0ffh		;deselects drive when written to drvSel
selMask	equ	008h		;"not used" bit is zero when drive selected

dcStepI	equ	001h		;drive command - step in
dcStepO	equ	002h		;drive command - step out
dcLoad	equ	004h		;drive command - load head
dcUload	equ	008h		;drive command - unload head
dcWrite	equ	080h		;drive command - start write sequence

; disk information equates

numDisk	equ	4		;up to four drives in use
numTrk	equ	77		;number of tracks
numSect	equ	32		;number of sectors per track
altLen	equ	137		;length of Altair sector
secLen	equ	128		;length of CPM sector
dsm	equ	149		;max block number (150 blocks of 2048 bytes)
drm	equ	63		;max directory entry number (64 entries) 
cks	equ	(drm+1)/4	;directory check space
maxTry	equ	10		;maximum number of disk I/O tries
unTrack	equ	07fh		;unknown track number

; Track layout for tracks 0-5

t0Trk	equ	0		;offset of track number
t0Data	equ	3		;offset of 128 byte data payload
t0Stop	equ	131		;offset of stop byte (0ffh)
t0CSum	equ	132		;offset of checksum

; Track layout for tracks 6-76

t6Trk	equ	0		;offset of track number
t6Sec	equ	1		;offset of sector number
t6CSum	equ	4		;offset of checksum
t6Data	equ	7		;offset of 128 byte data payload
t6Stop	equ	135		;offset of stop byte (0ffh)

; POLEX (Poly-88 Executive by M Eberhard) entry points

PXCSTA	EQU	0F403h		;console input status
PXCIN	EQU	0F406h		;console input
PXCOUT	EQU	0F409h		;console output
PXSSTA	EQU	0F40Ch		;serial port input status
PXSIN	EQU	0F40Fh		;serial port input
PXSOUT	EQU	0F412h		;serial port output
PXSOST	EQU	0F415h	

; misc equates

cr	equ	13		;ascii for carriage return
lf	equ	10		;ascii for line feed

	org	biosBase
;-----------------------------------------------------------------------------
;
;  BIOS Entry Jump Table
;
;-----------------------------------------------------------------------------
	jmp	boot		;cold start
wboote	jmp	wboot		;warm start
	jmp	const		;console status
	jmp	conin		;console character in 
	jmp	conout		;console character out 
	jmp	list		;list character out 
	jmp	punch		;punch character out 
	jmp	reader		;reader character in
	jmp	home		;move head to home position
	jmp	seldsk		;select disk
	jmp	settrk		;set track number
	jmp	setsec		;set sector number
	jmp	setdma		;set dma address
	jmp	read		;read disk
	jmp	write		;write disk
	jmp	listst		;return list status
	jmp	sectran		;sector translate

;  CPM Welcome Message

cpmStr	db	cr,lf,cr,lf
cpmSize db	'59K CP/M 2.2b Ver 1.1', cr, lf
	db	'Poly-88 with Altair FDC', cr, lf, 0
	ds	64-($-cpmStr)	;adjust to make 64 byte stack
lclStk	equ	$		;local stack

;----------------------------------------------------------------------------
; boot - Cold boot BIOS entry. CPM is already loaded in memory. Hand
;    control to the CCP.
;----------------------------------------------------------------------------
boot	xra	a
	sta	cdisk		;current CPM disk is drive zero
	sta	cldDone		;clear cold start done flag
	sta	bfDirty		;clear track buffer dirty flag

	dcr	a		;invalidate buffered drive and track numbers
	sta	bfDrive
	sta	bfTrack

	lda	dIoByte		;initialize CP/M's IOBYTE
	sta	ioByte

; form the two digit CPM size in ascii (e.g., "60") for "60K")

	lxi	h,biosEnd	;cpm size marker
	mov	a,h		;convert MSB of CPM marker to 1K count
	ani	0fch		;get bits for 1K inrcrements
	rar
	rar
	mvi	l,'0'		;start with ascii "0" for the 10's digit

tenCnt	sui	10		;divide by 10 to get the 10's digit
	jm	tenDone
	inr	l		;count the number of 10's in ascii
	jmp	tenCnt

tenDone adi	03ah		;compute final 1's digit in ascii
	mov	h,a		;h = 1's digit in ascii
	shld	cpmSize		;store two digit ascii size in welcome msg

;  Output the CPM welcome message

	lxi	h,cpmStr	;hl = address of cpm welcome message
welOut	mov	c,m
	call	conout
	inx	h
	mov	a,m
	ora	a
	jnz	welOut		;string ends with terminating null
	jmp	entCpm

;----------------------------------------------------------------
; wboot - Warm boot BIOS entry. Reload CPM from disk up to, but
;    not including the BIOS. Re-enter CPM after loading.
;----------------------------------------------------------------
wboot	lxi	sp,0100h	;init stack pointer
	xra	a
	sta	diskNum		;disk zero is boot disk
	call	selTrk0		;select disk and seek to track 0
	mvi	c,0		;select disk zero
	call	seldsk
	mvi	c,0		;select track zero
	call	settrk
	lxi	h,loadTk0	;track 0 load address  
	mvi	a,1		;start on sector 1 (1-32, not 0-31)
	call	loadTrk
	mvi	c,1		;now do track 1
	call	settrk
	lxi	h,loadTk1	;track 1 load address
	mvi	a,1		;start on sector 1 (1-32, not 0-31)
	call	loadTrk
	jmp	entCpm

; loadTrk - load one track into memory. Read odd sectors into every other 
;    128 bytes of memory until the BIOS base address is reached or all
;    32 sectors in the track have been read. Then read even sectors into
;    the opposite 128 byte sections of memory until the BIOS base address
;    is reached or all 32 sectors in the track have been read.

loadTrk	sta	secNum		;save the sector number to we're on
	shld	dmaAddr		;save the destination address
	mov	a,h
	cpi	(biosBase shr 8)  ;current address >= bios start address?
	jnc	nxtSec		;yes, skip read (don't overwrite bios)
	cpi	(ccpBase shr 8)	;current address < ccp start address?
	jc	nxtSec		;yes, skip read (not to valid data yet)
	call	read		;read a sector
	jnz	wboot		;fatal read error
nxtSec	lhld	dmaAddr		;hl = destination address
	lxi	d,0100h		;increment destination address by 256 bytes
	dad	d
	lda	secNum		;a = sector number
	adi	2		;jump 2 sectors each read
	cpi	numSect+1	;past the last sector in the track?
	jc	loadTrk		;not yet, keep reading
	sui	numSect-1	;compute starting even sector number
	lxi	d,-0f80h	;compute load address for 1st even sector
	dad	d
	cpi	3		;done both odd and even sectors in a track?
	jnz	loadTrk		;no, go do even sectors
	ret

; entCpm - enter CPM. Set page zero variables, enter CPM with or without
;   command line based on the MODE byte.

entCpm	lxi	b,defDma	;set the default dma address (080h)
	call	setdma
	mvi	a,0c3h		;8080 "jump" opcode
	sta	wbootv		;store in 1st byte of warm boot vector
	sta	bdosv		;and 1st byte of bdos entry vector
	lxi	h,wboote	;get the warm boot address
	shld	wbootv+1	;and put it after the jump
	lxi	h,bdosEnt	;BDOS entry address
	shld	bdosv+1		;put it after the jump opcode
	lda	cdisk		;get current disk number
	mov	c,a		;pass it to CPM in register c
	lxi	h,cldDone	;get the "cold start done" flag
	mov	a,m
	ora	a
	mvi	m,1		;set the flag true - cold start has been done
	lda	mode		;check the "process command line" flag
	jz	coldSt		;jump if this is cold start
	ani	fWrmCmd		;warm start - run default command line?
	jmp	cmdChk
coldSt	ani	fCldCmd		;cold start - run default command line?
cmdChk	jz	ccpBase+3	;no, enter ccp and clear cmd line
	jmp	ccpBase		;yes, enter ccp with possible cmd line

;----------------------------------------------------------------------------
; seldsk - Select Disk BIOS entry. C contains the disk number to select.
;    Validate the disk number and return a pointer to the disk parameter
;    header in HL. Zero is returned in HL for invalid drive number. The
;    selected disk number is stored in diskNum. No actual drive activity 
;    takes place.
;----------------------------------------------------------------------------
seldsk	lxi	h,0		;hl of zero is error flag
	mov	a,c		;a = drive number
	cpi	numDisk		;verify drive number less than number of disks
	rnc			;invalid drive number, return with hl=0=error
	mov	a,c
	sta	diskNum		;save the selected disk number
	rlc			;compute disk parameter header address
	rlc			;(16*drvNum) + dpHead
	rlc
	rlc
	lxi	h,dpHead  	;start of disk parameter headers.
	mov	e,a
	mvi	d,0
	dad	d		;hl points to the DPH for the passed drive
	ret

;----------------------------------------------------------------------------
; home - Home BIOS entry. Set trkNum to zero like a setTrk call. No actual
;    drive activity takes place..
;----------------------------------------------------------------------------
home	xra	a		;set trkNum to zero (home)
	sta	trkNum
	ret

;----------------------------------------------------------------------------
; settrk - Set Track BIOS entry. C contains the desired track number.
;    The track number is saved in trkNum for later use. No actual
;    drive activity takes place.
;----------------------------------------------------------------------------
settrk	mov	a,c
	sta	trkNum
	ret

;----------------------------------------------------------------------------
; setsec - Set Sector BIOS entry. C contains the desired sector number.
;    The sector number has already been translated through the skew table.
;    The sector number is saved in secNum for later use. No actual
;    drive activity takes place.
;----------------------------------------------------------------------------
setsec	mov	a,c
	sta	secNum
	ret

;----------------------------------------------------------------------------
; setdma - Set DMA BIOS entry. BC contains the address for reading or
;    writing sector data for subsequent I/O operations. The address is
;    stored in dmaAddr.
;----------------------------------------------------------------------------
setdma	mov	h,b
	mov	l,c
	shld	dmaAddr
	ret

;----------------------------------------------------------------------------
; read - Read sector BIOS entry. Read one sector using the trkNum, secNum
;    and dmaAddr specified for diskNum. Returns 0 in A if successful, 1 in A
;    if a non-recoverable error has occured.
;----------------------------------------------------------------------------
read	call	swapStk		;swap to local stack
	call	rTrkSec		;retrieve track in c, physical sector in b
	di
	call	readSec		;read the sector
	jmp	exitDio		;disk i/o exit routine

;----------------------------------------------------------------------------
; write - Write sector BIOS entry. Write one sector using the trkNum, secNum
;    and dmaAddr specified for diskNum. Returns 0 in A if successful, 1 in A
;    if a non-recoverable error has occured.
;----------------------------------------------------------------------------
write	call	swapStk
	call	rTrkSec		;retrieve track in c, physical sector in b
	di
	call	wrtSec		;write sector

;  exitDio - exit disk i/o. Restore interrupts if enable interrupt flag is
;     set.  

exitDio	push	psw
	lda	mode
	ani	fEnaInt		;should we re-enable interrupts?
	jz	noInts		;no
	ei
noInts	pop	psw
	mvi	a,0		;return error code of zero (success)
	rz
	mvi	a,1		;return fail code of one
	ora	a
	ret

;----------------------------------------------------------------------------
; sectran - Sector translation BIOS entry. Convert logical sector number in
;    BC to physical sector number in HL using the skew table passed in DE.
;----------------------------------------------------------------------------
sectran	xchg			;put table address (DE) into HL
	dad	b		;offset by logical sector number
	mov	l,m		;get physical sector number in L
	mvi	h,0		;for H to zero
	ret

;  rTrkSec - return track in c, physical sector in b

rTrkSec	lhld	trkNum		;L = track number, H = sector number
	mov	c,l
	dcr	h		;convert 1-32 to 0-31 for altair hardware
	mov	b,h
	ret

;--------------------------------------------------------------------------
; swapStk - switch to local stack. When the caller of this routine
;   does a ret, the return jumps to "resStk" below which restores
;   the original stack pointer.
;--------------------------------------------------------------------------
swapStk	pop	d		;de = return address. Return address popped
	lxi	h,0
	dad	sp		;hl = stack pointer after return addr popped
	lxi	sp,lclStk	;use lclStk as the new stack area
	push	h		;save original stack pointer
	lxi	h,resStk	;next "ret" will go to resStk, below
	push	h
	xchg			;put original return address in HL
	pchl			;jump to the "return" address

resStk	pop	h		;HL = original stack pointer
	sphl			;put original stack pointer back
	ret			;return to caller of swapStk caller

;-----------------------------------------------------------------------------
; readSec - read sector. Selects the drive in diskNum, seeks to the track
;    in C and then reads the sector specified in B into the buffer
;    pointed to by dmaAddr.
;----------------------------------------------------------------------------
readSec	call	selTrk		;select the drive, seek to the proper track
	rnz			;exit if error
	call	trkRead		;fill track buffer if needed
	rnz			;exit if error
	call	altSkew		;do Altair BASIC skew: in b, out e
	call	sec2Adr		;sector in e to address in hl
	lxi	d,t6Data	;de = offset of CP/M sector in tracks 6-76
	mov	a,c		;see which track we're on
	cpi	6
	jnc	rTrk676		;tracks 6-76 (offset of data already in de)
	lxi	d,t0Data	;load sector offset within tracks 0-5 instead
rTrk676	dad	d		;hl points to 128 byte payload in sector
	lda	dmaAddr		;set bc to point to destination address
	mov	c,a
	lda	dmaAddr+1
	mov	b,a
	call	moveBuf		;move (hl) to (bc)
	xra	a		;return success code
	ret

;------------------------------------------------------------------------------
; wrtSec - Write a sector. Selects the drive in diskNum, seeks to the
;    track in C and then writes the sector specified in B from the
;    buffer pointed to by dmaAddr.
;-----------------------------------------------------------------------------
wrtSec	call	selTrk		;select the drive, seek to the proper track
	rnz			;return if it failed
	call	trkRead		;fill track buffer if needed
	rnz			;exit if error
	call	altSkew		;do Altair BASIC skew: in b, out e
	call	sec2Adr		;sector in e to address in hl
	mov	a,c		;a = track number
	ori	80h		;set sync bit on first byte
	mov	m,a		;HL points to sector in track buffer
	inx	h
	cpi	86h		;tracks 0-5 or 6-76?
	jnc	wTrk676		;tracks 6-76

; Create 137 byte Altair sector for tracks 0-5

	xra	a		;put 0100h (16 bit) at offset 1,2
	mov	m,a
	inx	h
	inr	a
	mov	m,a
	inx	h		;hl-> 128 byte CPM sector in Altair sector
	mov	b,h		;bc-> 128 byte CPM sector in Altair sector
	mov	c,l
	lhld	dmaAddr		;hl-> location from which to read data
	call	moveBuf		;move cpm sector from (dmaAddr) to trkbuf
	mvi	a,0ffh		;offset 131 is stop byte (0ffh)
	stax	b
	inx	b		;offset 132 is checksum
	mov	a,d		;put checksum in a
	stax	b		;store checksum at offset 132
	jmp	wrtExit	

;  wTrk676 - create 137 byte Altair sector for tracks 6-76

wTrk676	mov	m,b		;store logical sector number at offset 1
	inx	h
	xra	a		;store zero at offsets 2-6
	mov	m,a		;offset 2
	inx	h
	mov	m,a		;offset 3
	inx	h
	push	h		;save address of offset 4 = checksum
	inx	h
	mov	m,a		;offset 5
	inx	h
	mov	m,a		;offset 6
	inx	h		;hl-> 128 byte CPM sector in Altair sector
	mov	b,h		;bc-> 128 byte CPM sector in Altair sector
	mov	c,l
	lhld	dmaAddr		;hl-> location from which to read data
	call	moveBuf		;move cpm sector from (dmaAddr) to trkBuf
	mvi	a,0ffh		;offset 135 is stop byte (0ffh)
	stax	b
	inx	b		;offset 136 is unused
	xra	a		;store a zero (Burcon CP/M checks it)
	stax	b
	pop	h		;hl-> checksum byte in Altair sector
	mov	m,d		;store the checksum
	
; wrtExit - set dirty flag true, return success status

wrtExit	mvi	a,0ffh		;set dirty flag true
	sta	bfDirty
	xra	a		;return success status
	ret

;------------------------------------------------------------------------------
; moveBuf - move sector buffer (128 bytes) from (hl) to (bc). Compute
;   checksum on all bytes and return in a and d.
;
;   Clobbers d,e. bc and hl updated as buffer moved.
;------------------------------------------------------------------------------
moveBuf	mvi	d,0		;d = checksum
	mvi	e,secLen	;e = buffer length (128 bytes)
movLoop	mov	a,m		;move from (hl) to (bc)
	stax	b
	add	d		;add byte to checksum
	mov	d,a
	inx	b		;increment both pointers
	inx	h
	dcr	e		;decrement character count
	jnz	movLoop		;loop until count = 0
	ret

;------------------------------------------------------------------------------
; altSkew - Do sector skew like Altair BASIC. Track comes in c, Sector comes
;    in b, skewed sector returned in e (also in a for tracks 6-76). For 
;    tracks 0-5, no skew takes place, e = b. For sectors 6-76, 
;    e = (b * 17) mod 32.
;
;    This same routine can be called to convert from physical to logical
;    as well - the process happens to be symmetrical for these numbers.
;
;    Clobbers e with result
;------------------------------------------------------------------------------
altSkew	mov	e,b		;e = sector requested
	mov	a,c		;see if track number >= 6
	cpi	6
	rc			;track < 6, exit with same sector in e
	mov	a,b		;multiply by 17
	add	a
	add	a
	add	a
	add	a
	add	b
	ani	01Fh		;keep lower 5 bits (0-31)
	mov	e,a
	ret
	
;------------------------------------------------------------------------------
;  selTrk - select the drive, go to the proper track (specified in diskNum 
;     and C)
;
;     Clobbers d,e,h,l
;------------------------------------------------------------------------------
selTrk	mvi	a,maxTry	;initialize retry count
	sta	rtryCnt
	call	selDrv		;returns with HL=trkTable entry for this drive
	rnz			;select error, exit
	mov	a,m		;a = track this drive is presently on
	cpi	unTrack		;if unknown track number, seek to track zero
reSeek	cz	ldSeek0		;see to track zero
	call	ldHead		;force 50ms dly for possible direction change
	mov	a,m		;a = track number drive is presently on
	mov	m,c		;put destination track num in the track table
	sub	c		;a = current - desired
	rz			;difference is zero - on proper track already
	call	trkWrt		;different track, write track buffer if dirty
	mvi	d,dcStepI	;step in to higher tracks if desired > current
	jc	mulStep
	mvi	d,dcStepO	;step out to lower tracks if desired < current
	cma			;make it a negative value (1's complement + 1)
	inr	a

; mulStep - issue multiple track steps in or out (controller step command
;    in d). The number of tracks to step is passed as -steps. E.g., 10
;    steps passed as -10.

mulStep	mov	e,a		;e = -tracks left to step
wMoveOk	in	drvStat		;get drive status register
	ani	moveMsk		;wait until it's OK to move the head
	jnz	wMoveOk
	mov	a,d		;issue step in or out per command in d
	out	drvCtl	
	mov	a,e		;e has -tracks to step, counting up to zero
	inr	a
	jnz	mulStep		;loop until we've stepped to the desired track
	call	rtryChk		;decrement and check retry counter
	rz			;retry count OK, return
	call	selTrk0		;max tries reached. Return error.
	jmp	retErr

;-----------------------------------------------------------------------------
;  selDrv - select the drive specified in diskNum. Return with HL pointing 
;     to the trkTbl entry for diskNum.
;
;     Clobbers d,e. hl points to trkTbl entry for diskNum
;-----------------------------------------------------------------------------
selDrv	lda	diskNum		;diskNum is the disk that CPM wants to use
	mov	e,a
	lda	selNum		;disk currently selected on the controller
	cmp	e		;same drive as already selected?
	cnz	trkWrt		;different drive, flush dirty track buffer
	mov	a,e
	sta	selNum		;set selected drive = diskNum
	jnz	selNew		;not the same drive, select the new drive
	in	drvStat		;make sure the drive is still selected
	ani	selMask
	jz	rTrkTbl		;drive selected, retrieve the track table ptr

;  selNew - select a new drive, deselect the current, then select the new

selNew	mvi	a,dSelect	;deselect the attached drive
	out	drvSel
	lda	selNum		;drive number to select
	out	drvSel
	in	drvStat		;loop until drive is selected
	ani	selMask
	jnz	selNew

; rTrkTbl - return track table pointer. Also validates the drive number 
;    passed in e. Returns with HL pointing to the trkTbl entry for drive in e.

rTrkTbl	mvi	a,numDisk-1	;drive number OK? 
	cmp	e		;e has drive number
	jc	retErr		;drive number error
	lxi	h,trkTbl   	;index by drive number (e) into the track table
	mvi	d,0		;de = 16 bit drive number
	dad	d		;hl-> trkTble entry for this drive
	xra	a		;return zero status
	ret

;------------------------------------------------------------------------------
;  ldHead - If head already loaded and settled, return. Otherwise, issue the
;       head load command and wait for it to settle.
;
;       Clobbers none (a)
;------------------------------------------------------------------------------
ldHead	in	drvStat		;get drive status
	ani	headMsk		;get head loaded bit alone
	rz			;zero = true, head is loaded
	mvi	a,dcLoad	;issue the load head command
	out	drvCtl
wtHead	in	drvStat		;get drive status
	ani	headMsk		;wait for head loaded to be true
	jnz	wtHead
	ret

;------------------------------------------------------------------------------
;  uldHead - flush the track buffer if needed, invalidate the track buffer
;      to force a disk read no matter what next time, unload the head.
;
;    Clobbers none (a)
;------------------------------------------------------------------------------
uldHead	call	trkWrt		;write track buffer if dirty
	mvi	a,0ffh		;a=invalid track number
	sta	bfTrack		;buffer won't match any track
	sta	bfDrive		;or drive (just to be safe)
	mvi	a,dcUload	;issue head unload command
	out	drvCtl

; trkWrt may have disabled interrupts if a flush was required. Restore
;    interrupts based on mode flag

	lda	mode		;should we re-enable interrupts?
	ani	fEnaInt
	rz			;no
	ei			;otherwise, re-enable and exit
	ret

;-----------------------------------------------------------------------------
; selTrk0 - Select drive in diskNum, load head and seek to track 0. A
;   flush of the track buffer (if dirty) is forced.
;
;   ldSeek0 - Load head and seek to track 0
;
;   Clobbers d,e,h,l. c is loaded with zero for track zero
;-----------------------------------------------------------------------------
selTrk0	call	trkWrt		;flush track buffer to disk if dirty
	call	selDrv		;select drive in diskNum
ldSeek0	call	ldHead		;load the drive head
seek0	in	drvStat		;get drive status register
	ani	moveMsk		;loop until OK to move the head
	jnz	seek0		
	mvi	a,dcStepO	;issue step out command
	out	drvCtl
	in	drvStat
	ani	trk0Msk		;loop until we get to track zero
	jnz	seek0
	lda	selNum		;selNum is diskNum once drive is selected
	mov	e,a
	call	rTrkTbl		;HL = track table pointer for this drive
	mvi	m,0		;set track number for this drive to zero
	ret

;------------------------------------------------------------------------------
; rtryChk - retry counter check. Decrement retry counter. Returns zero if
;     more tries left, non-zero if retry counter reaches zero.
;
;     Clobbers none (a)
;------------------------------------------------------------------------------
rtryChk	lda	rtryCnt
	dcr	a
	sta	rtryCnt
	jz	retErr
	xra	a
	ret

; retErr - Return error code with 1 in accumulator and non-zero status flag

retErr 	mvi	a,1
	ora	a
	ret

;------------------------------------------------------------------------
; trkRead - read full track into track buffer (if needed).
;    If the current drive (in selNum) or track (in c) do not match
;    bfDrive or bfTrack, then the new track is read into trkBuf. 
;    Each sector is validated. This routine assumes drive selection
;    and track movement is complete before it is called. 
;	
;    Clobbers h,l
;------------------------------------------------------------------------
trkRead	lxi	h,bfDrive	;see if buffered drive matches selected
	lda	selNum
	cmp	m
	inx	h		;assumes bfTrack follow bfDrive
	jnz	newBuf		;drive doesn't match, need a new buffer
	mov	a,c		;see if buffered track matches selected
	cmp	m	
	rz			;same track, already have this buffer

; newBuf - new buffer (full track) needed

newBuf	mvi	a,0ffh		;invalidate buffered track and drive
	mov	m,a		;bfTrack
	dcx	h
	mov	m,a		;bfDrive
reReadP	push	b		;save bc
reRead	call	rtryChk		;decrement and check retry counter
	jz	rdInit		;still ok, init for the read
	pop	b		;restore bc and exit
	ret			;return z false (error)

; wait for any sector, then read the next 32 sectors

rdInit	mvi	b,numSect	;b = number of sectors to read
rdSect	call	secSync		;sync to and get next sector in e
	call	sec2Adr		;get hl-> sector address in trkBuf
	mvi	c,altLen	;c = length of Altair sector (137 bytes)

; rdLoop - read physical sector loop

rdLoop	in	drvStat		;get drive status byte
	ora	a		;wait for NRDA flag true (zero)
	jm	rdLoop

	in	drvData		;read the byte 
	mov	m,a		;store in the read buffer
	inx	h		;increment buffer pointer
	dcr	c		;decrement characters remaining counter
	jz	rdDone		;count is zero (done), can't use rz for timing
	dcr	c		;decrement count for 2nd byte about to be read
	nop			;timing
	in	drvData		;get the 2nd byte
	mov	m,a		;store in the read buffer
	inx	h		;increment buffer pointer
	jnz	rdLoop		;loop until all bytes read

; rdDone - done reading a sector

rdDone	dcr	b		;decrement sector count
	jnz	rdSect		;do next sector

;-----------------------------------------------------------------------
; Track has been read. Validate track number and then validate
;    sector data.
;-----------------------------------------------------------------------
	lxi	h,trkBuf	;hl-> sector 0 in track buffer
	mov	a,m		;get track number byte
	ani	07fh		;get track number alone
	mov	c,a		;c = track number from disk
	lda	trkNum		;a = desired track number
	cmp	c		;track number match?
	jnz	rdTrkEr		;no, have track number error
	cpi	6		;tracks 0-5 processed directly below
	jnc	vTrk676		;jump for tracks 6-76

; validate data for sectors in tracks 0 - 5

	mvi	b,numSect	;repeat for all sectors
vrfyLp1	inx	h		;move to byte 3 where CP/M sector starts
	inx	h
	inx	h
	call	chkSum		;compute checksum, return in d
	mov	a,m		;verify stop byte is FF
	inr	a
	jnz	reRead		;wasn't FF, re-read the track
	mov	a,d		;a = computed checksum
	inx	h		;move to checksum in sector
	cmp	m		;computed = checksum from sector?
	jnz	reRead		;checksum fail, re-read the track
	lxi	d,altLen-t0CSum	;de = number of bytes to next sector
	dad	d		;hl = start of next sector
	dcr	b		;repeat for all sectors
	jnz	vrfyLp1
	jmp	trkGood		;track is good	

;  vTrk676 - validate data for sectors in tracks 6-76

vTrk676	mvi	b,0		;b = sector number we're checking
vrfyLp2	call	altSkew		;physical in b to logical in a
	inx	h		;move to sector byte
	cmp	m		;sector match?
	jnz	reRead		;no, re-read all sectors
	inx	h		;move to file number (BASIC)
	mov	a,m		;initialize checksum with 1st byte
	inx	h		;move to bytes written (BASIC)
	add	m		;add to checksum
	inx	h
	mov	e,m		;save checksum byte from sector in e
	inx	h		;move to group pointer byte 1 (BASIC)
	add	m		;add to checksum
	inx	h		;move to group pointer byte 2 (BASIC)
	add	m		;add to checksum
	inx	h		;move to data sector
	call	chkSum2		;continue checksum on data, return in d
	mov	a,m		;verify stop byte is FF
	inr	a
	jnz	reRead		;wasn't FF, re-read the track
	mov	a,d		;compare checksums
	cmp	e		;computed = checksum from sector?
	jnz	reRead		;checksum fail, re-read the track
	inx	h		;move to start of next sector
	inx	h
	inr	b		;increment sector number
	mov	a,b
	cpi	numSect		;done all 32 sectors?
	jnz	vrfyLp2		

; trkGood - track is good. Update bfDrive and bfTrack with the drive and
;    track number present in trkBuf and exit

trkGood	pop	b		;restore bc (sector and track)
	lxi	h,bfDrive	;hl-> buffered drive number
	lda	selNum		;load and save the buffered drive number
	mov	m,a
	inx	h		;move to track number
	mov	m,c		;save the buffered track number
	xra	a		;return success code
	ret

; rdTrkEr - Track number error during the read operation

rdTrkEr	pop	b		;restore track number into c
	xra	a		;forces a track 0 reset
	call	reSeek		;retry the track seek
	jmp	reReadP		;retry the track read

;-------------------------------------------------------------------
; trkWrt - write track buffer if dirty flag is set.
;    This routine assumes the same drive and track are
;    still selected as when the track was read.
;
;    Clobbers h,l, Saves a and status flags
;-------------------------------------------------------------------
trkWrt	push	a		;save a and status flags
	lda	bfDirty		;see if track buffer is dirty
	ora	a
	jnz	flush		;it is dirty, we must write it
	pop	a		;restore a and status
	ret

; flush - track buffer needs to be written.

flush	xra	a		;clear buffer dirty flag
	sta	bfDirty
	mvi	a,maxTry	;initialize retry count
	sta	rtryCnt
	push	b		;save bc
	push	d		;save de
	di			;disable interrupts for disk i/o

; Form the the head current reduction (bit 6) for tracks 43-76.
;    and the write command bit (bit 7)

rtryWrt	lda	bfTrack		;a = track number we're presently on
	adi	(-43 and 0ffh)	;add -43 (1st track for HCS bit = 1)
	mvi	a,0		;set a=0 without affecting carry
	rar			;080h if track >= 43
	stc
	rar			;0c0 if track >= 43, else 080h
	push	a		;save the command byte on the stack

; wait for any sector true and then write the next 32 sectors

	mvi	b,numSect	;b = number of sectors to read
wrSect	call	secSync		;sync to and get next sector in e
	pop	a		;get command formed above
	out	drvCtl		;issue write command
	push	a
	call	sec2Adr		;get hl-> sector address in trkBuf
	mvi	c,altLen	;c = length of Altair sector (137 bytes)
	mvi	d,enwdMsk	;Enter New Write Data flag mask
	mov	e,m		;e = next byte to write
	inx	h

; wrLoop - write physical sector loop

wrLoop	in	drvStat		;read drive status register
	ana	d		;write flag (ENWD) asserted (zero)?
	jnz	wrLoop		;no, keep waiting

	add	e		;put byte to write into accumulator
	out	drvData		;write the byte
	mov	a,m		;a = next byte to write
	inx	h		;increment source buffer pointer
	mov	e,m		;e = byte to write next time through this loop
	inx	h		;increment source buffer pointer
	dcr	c		;decrement chars remaining (byte just written)
	jz	wrDone		;zero - sector data written
	dcr	c		;dec chars remaining (byte about to write)
	out	drvData		;write 2nd byte
	jnz	wrLoop		;loop if count <> 0

; wrDone - sector write is done, write trailing zero, then do next sector

wrDone	in	drvStat		;wait for another write flag
	ana	d
	jnz	wrDone

	out	drvData		;write trailing zero
	dcr	b		;decrement sector count
	jnz	wrSect		;do next sector

	pop	a		;pull drive command off stack
				;fall through to verify the track

;------------------------------------------------------------------------
; Verify the track just written
;------------------------------------------------------------------------

; Wait for a sector to pass under the head before starting the verify
;    process. The drive needs time after a write before a read can 
;    be performed.

	call	secSync		;wait for next sector
wtFalse	in	drvSec		;now wait for sector false
	rar
	jnc	wtFalse

; Wait for next sector true and then verify the next 32 sectors

	mvi	b,numSect	;b = number of sectors to read
vfySect	call	secSync		;sync to and get next sector in e
	call	sec2Adr		;get hl-> sector address in trkBuf

	lxi	d,vfyDone	;vfyDone as return address on stack
	push	d

	lxi	d,altlen	;d=0, e=num bytes to verify

vfyLoop	in	drvStat		;(10) get drive status byte
	ora	a		;(4) wait for NRDA flag true (zero)
	jm	vfyLoop		;(10)

	in	drvData		;(10) read the byte 
	xra	m		;(7) verify data matches buffer
	ora	d		;(4) accumulate errors in d
	mov	d,a		;(5)

	inx	h		;(5) increment buffer pointer
	dcr	e		;(5) decrement characters remaining counter
	rz			;(5) done, jumps to vfyDone

	in	drvData		;(10) get the 2nd byte
	xra	m		;(7) verify data matches buffer
	ora	d		;(4) accumlate errors in d
	mov	d,a		;(5)

	inx	h		;(5) increment buffer pointer
	dcr	e		;(5) decrement characters remaining counter	
	jnz	vfyLoop		;(10) loop until all bytes read
				;(111) per two bytes
	pop	h		;get rid of jump via rz

; vfyDone - all bytes read. If d is zero, data matched.

vfyDone	ora	a		;all data match?
	jnz	vfyErr		;no
	dcr	b		;otherwise, decrement sector counter
	jnz	vfySect		;do next sector

; vfyExit - Verify exit. Restore saved registers and exit.

vfyExit	pop	d		;restore de
	pop	b		;restore bc
	pop	a		;restore a and flags
	ret

; vfyErr - verify error. Decrement retry counter and jump to retry the write
;   and verify. Exit if when retry counter expires.

vfyErr	call	rtryChk		;decrement and check retry counter
	jz	rtryWrt		;try again?
	jmp	vfyExit		;no, give up and exit


;-------------------------------------------------------------------------
; secSync - wait for next sector true to assert. Return the sector
;    number in e.
;-------------------------------------------------------------------------
secSync	in	drvSec		;read sector position register
	rar			;wait for sector true flag (zero = true)
	jc	secSync

	ani	01fh		;get sector number alone
	mov	e,a		;e = sector number under head
	ret

;-------------------------------------------------------------------------
; sec2Adr - Given sector number in e, return the address of the sector
;    within trkBuf in hl. This is done by multiplying the sector
;    number by 137 and adding it to the address of trkBuf.
;  
;    154 cycles (77us) total including call and return. 
;    Clobbers h,l (return parameter)
;-------------------------------------------------------------------------
; First, multiply sector by 128

sec2Adr	push	b		;(11) save b,c
	mov	a,e		;(5) setup to do 128*sector
	ora	a		;(4) start with carry clear
	rar			;(4)
	mov	b,a		;(5) b=msb of 128*sector
	rar			;(4) form lsb of 128*sector
	ani	80h		;(7)
	mov	c,a		;(5) c=lsb of 128*sector
	lxi	h,trkBuf	;(10) hl->trkBuf
	dad	b		;(10) hl->trkbuf + 128*sector

; Now add 9*sector to the address (total of 137*sector)

	mov	a,e		;(5) 1*sector
	add	a		;(4) 2*sector
	add	a		;(4) 4*sector
	add	a		;(4) 8*sector (still less than 256)
	add	e		;(4) 9*sector (may overflow)
	mov	c,a		;(5) c=final lsb
	mvi	a,0		;(7) clear A without affecting carry
	adc	a		;(4) account for carry
	mov	b,a		;(5) b=final msb
	dad	b		;(10) hl->trkbuf + 137*sector
	pop	b		;(10) restore bc
	ret			;(10) entry call adds 17 cycles

	if	0

; with table = 88 cycles (44us) including call and return
;  clobbers d 

sec2Adr	lxi	h,secAddr	;(10) hl->sector address table
	mvi	d,0		;(7) for de=16 bit sector number
	dad	d		;(10) index by sector * 2
	dad	d		;(10)
	mov	a,m		;(7) a=lsb of sector address in trkBuf
	inx	h		;(5)
	mov	h,m		;(7) h=msb of sector address in trkBuf
	mov	l,a		;(5) l=lsb
	ret			;(10) entry call adds 17 cycles

;------------------------------------------------------------------
; secAddr - converts sector number to 16 bit address within trkBuf
;------------------------------------------------------------------
secAddr	dw	trkBuf+00*137,trkBuf+01*137,trkBuf+02*137,trkBuf+03*137
	dw	trkBuf+04*137,trkBuf+05*137,trkBuf+06*137,trkBuf+07*137
	dw	trkBuf+08*137,trkBuf+09*137,trkBuf+10*137,trkBuf+11*137
	dw	trkBuf+12*137,trkBuf+13*137,trkBuf+14*137,trkBuf+15*137
	dw	trkBuf+16*137,trkBuf+17*137,trkBuf+18*137,trkBuf+19*137
	dw	trkBuf+20*137,trkBuf+21*137,trkBuf+22*137,trkBuf+23*137
	dw	trkBuf+24*137,trkBuf+25*137,trkBuf+26*137,trkBuf+27*137
	dw	trkBuf+28*137,trkBuf+29*137,trkBuf+30*137,trkBuf+31*137
	
	endif

;--------------------------------------------------------------------
; chkSum - compute checksum over the next 128 bytes pointed to
;	by hl. Result returned in d.
;
; chkSum entry initializes checksum to zero
; chkSum2 entry expects begining checksum in a
;
;    Clobbers: d with return checksum, hl updated by 128 bytes
;--------------------------------------------------------------------
chkSum	xra	a		;init checksum to zero

chkSum2	mvi	d,secLen	;d = byte counter (128)

cksLoop	add	m
	inx	h
	dcr	d
	jnz	cksLoop

	mov	d,a		;return result in d
	ret

;---------------------------------------------------------------------------
;  CON device I/O
;---------------------------------------------------------------------------

; const - Console status BIOS entry point. Return 0ff 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	PXSSTA		;TTY use serial port
	dw	PXCSTA		;CRT use keyboard/video
	dw	batchSt		;BAT indirect through RDR logical device
	dw	PXSSTA		;UC1 use serial port

; conin - Console input BIOS entry point. Return character in A 

conin	call	uldHead		;if waiting for input, assume disk I/O is done
	mvi	b,shftCon	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	PXSIN		;TTY use serial port
	dw	PXCIN		;CRT use keyboard/video
	dw	reader		;BAT indirect through RDR logical device
	dw	PXSIN		;UC1 use the serial port

; conout - Console output BIOS entry point. Output the character in C.

conout	mvi	b,shftCon	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	PXSOUT		;TTY use serial port
	dw	crtOut		;CRT use keyboard/video
	dw	list		;BAT indirect through LST logical device
	dw	PXSOUT		;UC1 user serial port

; crtOut - Output character in C to video display. Do not send two
;    CR's in a row since CR's also line feed on this display.

crtOut	lda	lastChr		;A=last character sent
	cmp	c		;sending same character?
	mov	a,c		;update last character sent
	sta	lastChr
	jnz	PXCOUT		;not same, OK to display it

	cpi	CR		;two CR's in a row?
	jnz	PXCOUT		;no, OK to display it
	
	ret			;else, skip the CR
	
;---------------------------------------------------------------------------
; RDR device - Reader input character BIOS entry point. Return in A
;---------------------------------------------------------------------------
reader	mvi	b,shftRdr	;B=reader IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	PXSIN		;TTY use serial port
	dw	PXSIN		;PTR use serial port
	dw	PXSIN		;UR1 use serial port
	dw	PXCIN		;UR2 use keyboard/video

;---------------------------------------------------------------------------
; 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	PXSOUT		;TTY use serial port
	dw	PXSOUT		;PTP use serial port
	dw	PXSOUT		;UP1 use serial port
	dw	PXCOUT		;UP2 use keyboard/video

;---------------------------------------------------------------------------
;  LST device I/O
;---------------------------------------------------------------------------

; listst - List output test BIOS entry point. Return FFh 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	PXSSTA		;TTY use serial port
	dw	crtSt		;CRT use keyboard/video 
	dw	PXSSTA		;LPT use serial port
	dw	PXSSTA		;UL1 use serial port

; 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	PXSOUT		;TTY use serial port
	dw	crtOut		;CRT use keyboard/video
	dw	PXSOUT		;LPT use serial port
	dw	PXSOUT		;UL1 use serial port

crtSt	mvi	a,0FFh		;always return "ready" for video out
	ret

;---------------------------------------------------------------------------
;  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	PXSSTA		;TTY use serial port	
	dw	PXSSTA		;RDR use serial port
	dw	PXSSTA		;UR1 use serial port
	dw	PXCSTA		;UR2 use keyboard/video

;---------------------------------------------------------------------------
; 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).
;--------------------------------------------------------------------------- 
doIo	lda	ioByte		;A=CP/M IO BYTE

ioAlign	rlc			;rotate into bits 2 and 1
	dcr	b
	jnz	ioAlign

; 2 bit field aligned in bits 2 and 1. Index into jump table of caller.

	ani	6		;get the 2 bit field alone
	mov	e,a		;form DE=table offset
	mvi	d,0

	xthl			;HL->jump table for device and save HL
	dad	d		;HL->jump address 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			;restore HL, put jump address on stack
	ret			;jump to address specified in table

;**************************************************************************
; MODE, IOBYTE
;    For backward compatibility, these bytes must be at offset 559-55A
;    from the start of the BIOS. That's why they're stuck here in the
;    middle of other code.
;***************************************************************************
	ds	20h		;force to offset 559h

;---------------------------------------------------------------------------
;  mode (MODE byte) - bios behavior flags. Must stay at offset 559h
;     (25D9 in memory after MOVCPM) for backward compatibility.
;---------------------------------------------------------------------------
mode	db	10h		;enable interrupts after disk i/o is done

fCldCmd	equ	01h		;true = CCP process cmd on cold start
fWrmCmd	equ	02h		;true = CCP process cmd on warm start
fEnaInt	equ	10h		;enable interrupts after disk I/O

;---------------------------------------------------------------------------
;  IOBYTE - at offset 55Ah (25DAh in memory after MOVCPM)
;
;    CON device (bits 1,0):
;	00 - TTY uses serial port
;	01 - CRT uses keyboard/video*
;	10 - BAT indirect through RDR logical device
;	11 - UC1 uses serial port
;
;    RDR device (bits 3,2):
;	00 - TTY uses serial port
;	01 - PTR uses serial port*
;	10 - UR1 uses serial port
;	11 - UR2 uses keyboard/video
;
;    PUN device (bits 5,4):
;	00 - TTY uses serial port
;	01 - PTP uses serial port*
;	10 - UP1 uses serial port
;	11 - UP2 uses keyboard/video
; 
;    LST device bits (7,6):
;	00 - TTY uses serial port
;	01 - CRT uses keyboard/video
;	10 - LPT uses serial port*
;	11 - UL1 uses serial port
;
;* Default setting
;---------------------------------------------------------------------------
dIoByte	db	095h		;LST = LPT (serial port)
				;PUN = PTP (serial port)
				;RDR = PTR (serial port)
				;CON = CRT (keyboard/video)

;-----------------------------------------------------------------------------
; dpHead - disk parameter header for each drive
;-----------------------------------------------------------------------------
dpHead	dw	xlate,0,0,0,dirBuf,mitsDrv,csv0,alv0
	dw	xlate,0,0,0,dirBuf,mitsDrv,csv1,alv1
	dw	xlate,0,0,0,dirBuf,mitsDrv,csv2,alv2
	dw	xlate,0,0,0,dirBuf,mitsDrv,csv3,alv3

;-----------------------------------------------------------------------------
; mitsdrv - disk parameter block. This table gives a block size of 2048 bytes.
;   Per CPM docs, EXM should be 1 and AL0,AL1 should be 080h, 000h.
;   This would give two logical extents per physical extent. (32K per
;   physical extent). The settings here give one logical extent per
;   physical extent (16K per physical extent). This was probably done
;   to maintain compatibility with CPM 1.4 disks.
;-----------------------------------------------------------------------------
mitsDrv	dw	numSect		;sectors per track
	db	4		;allocation block shift factor (BSH)
	db	0fh		;data location block mask (BLM)
	db	0		;extent mask (EXM), see note above
	dw	dsm		;maximum block number (DSM)	
	dw	drm		;maximum directory entry number (DRM)
	db	0c0h, 0		;AL0, AL1, see note above
	dw	cks		;CKS (DRM+1)/4
	dw	2		;reserved tracks for CPM and bootloader

;---------------------------------------------------------------------------
; xlate - sector translation table for skew. This skew has been kept
;     for compatibility with Lifeboat and Burcon CP/M.
;---------------------------------------------------------------------------
xlate	db	01,09,17,25,03,11,19,27,05,13,21,29,07,15,23,31
	db	02,10,18,26,04,12,20,28,06,14,22,30,08,16,24,32

;-----------------------------------------------------------------------------
; disk control data
;-----------------------------------------------------------------------------
; trkTbl - track information for each drive. Init to unknown track number.

trkTbl	db	unTrack, unTrack, unTrack, unTrack

lastChr	db	0		;last character sent to video display
cldDone	ds	1		;set true after cold start has completed
diskNum	ds	1		;current disk number
trkNum	ds	1		;track num (sector num MUST follow in memory)
secNum	ds	1		;sector number for disk operations
dmaAddr	ds	2		;dma address for disk operations
selNum	ds	1		;disk number currently selected on controller
rtryCnt	ds	1		;retry counter

;-----------------------------------------------------------------------------
; track buffering data
;-----------------------------------------------------------------------------
bfDrive	ds	1		;drive currently in track buffer
bfTrack	ds	1		;currently buffered track (must follow bfDrive)
bfDirty	ds	1		;track buffer needs to be written
trkBuf	ds	numSect*altLen

;-----------------------------------------------------------------------------
;  Disk scratchpad areas defined in the DPH table
;-----------------------------------------------------------------------------
dirBuf	ds	128		;bdos directory scratchpad
alv0	ds	(dsm/8 + 1)	;allocation vector storage
csv0	ds	cks		;change disk scratchpad
alv1	ds	(dsm/8 + 1)
csv1	ds	cks
alv2	ds	(dsm/8 + 1)
csv2	ds	cks
alv3	ds	(dsm/8 + 1)
csv3	ds	cks

;  End of BIOS equate

biosEnd	equ	$+255		;round msb up to next 256 byte boundary

	end

