*****************************************************************************
;
;   CP/M 2.2 BIOS for MFA modular computer with 720K disks
;
;	This BIOS uses full track buffering on both reads and writes to
;	improve performance. This BIOS performs 20%-70% faster than a
;       non-track buffered BIOS for typical disk based operations.
;
;	Full IOBYTE support is provided and supports three 8251 serial
;	ports and the 8255 based parallel printer port.
;
;	Includes extended function support for non-standard BIOS features
;	via the select disk (selDsk) entry point. This BIOS supports 
;	function 0, Flush Track Buffer. See the selDsk routine for details.
;
;	An assembly option allows this BIOS to be assembled for 320K 
;	disks as used with the original CP/M.
;
;--------------------------------------------------------------------------
;
;	Several parameter bytes are in fixed locations in the BIOS and can
;       be patched in DDT and written back to disk using SYSGEN. When a
;	MOVCPM or SYSGEN image is loaded into memory using DDT, the param-
;	eter bytes are at these locations:
;
;	2133h - MODE byte (see MODE byte flags in code comments below)
;	2134h - Default IOBYTE (see IOBYTE options in code comments below)
;	2135h - Console input mask (input AND'd with this mask) 
;	2136h - Reader input mask (input AND'd with this mask)
;	2137h - Console output mask (output bytes AND'd with this mask)
;	2138h - Punch output mask (output bytes AND'd with this mask)
;	2139h - List output mask (output bytes AND'd with this mask)
;	213Ah - Motor startup delay in 0.1 sec intervals
;	213Bh - Head settle delay after final step in ms
;	213Ch - Delay before reversing step direction in ms
;	213Dh - Head settle delay after head load in ms (zero for none)
;
;--------------------------------------------------------------------------
;	
;       To patch an update to this BIOS into a CP/M image saved from
;	MOVCPM or SYSGEN, use the following commands:
;
;	   A>DDT CPM64.COM    (specify the saved CP/M image file)
;	   -IBIOS.HEX
;	   -Rxxxx      	      (xxxx = the value of the BIAS equate below)
;	   -G0		      (Go zero, not "oh")
;
;	Then save or SYSGEN the image in memory
;
;--------------------------------------------------------------------------
;
;	Ver	  Date		     Description
;	---	----------	------------------------------------------
;	1.0 	01/24/2019	Mike Douglas, Original release
;
;****************************************************************************

MEMSIZE	equ	64		;set memory size in K bytes
VERSION	equ	10		;BIOS version (major.minor)

; Drive type selection. Only one type can be true (1)

DISK720	equ	1		;720K disks
DISK320	equ	0		;320K disks

;  CP/M location equates

CCPLEN	equ	0800h		;CPM 2.2 fixed length
BDOSLEN	equ	0E00h		;CPM 2.2 fixed length
BIOSLEN	equ	1A00h		;length of this BIOS

CCPBASE	equ	MEMSIZE*1024 - BIOSLEN - BDOSLEN - CCPLEN
BDOSENT	equ	(CCPBASE+CCPLEN+6)	 ;entry address of BDOS
BIOSBAS	equ	(CCPBASE+CCPLEN+BDOSLEN) ;base address of this BIOS

BIAS	equ	0B00h-CCPBASE

; CP/M page zero equates

WBOOTV	equ	00h		;warm boot vector location
BDOSV	equ	05h		;bdos entry vector location
CDISK	equ	04h		;CPM current disk
DEFDMA	equ	80h		;default dma address
IOBYTE	equ	03h

; CP/M (logical) disk information

 if DISK720
NUMDRV	equ	2		;two drives supported
NUMTRK	equ	160		;number of tracks on the disk
NUMSEC	equ	36		;logical (128 byte) sectors per track
SECLEN	equ	128		;CP/M sector length
WBSTART	equ	5		;warm boot starting sector
WBSECS	equ	(CCPLEN+BDOSLEN)/SECLEN   ;#sectors to read for warm boot
 endif

 if DISK320
NUMDRV	equ	2		;two drives supported
NUMTRK	equ	80		;number of tracks on the disk
NUMSEC	equ	32		;logical (128 byte) sectors per track
SECLEN	equ	128		;CP/M sector length
WBSTART	equ	5		;warm boot starting sector
WBSECS	equ	(CCPLEN+BDOSLEN)/SECLEN   ;#sectors to read for warm boot
 endif

; 8251 Serial Port and 8255 Parallel Port Equates

SIO1ST	equ	0A1h		;console status port
SIO1DT	equ	0A0h		;console data port
SIO2ST	equ	091h		;2nd port status
SIO2DT	equ	090h		;2nd port data
SIO3ST	equ	0F1h		;3rd port status
SIO3DT	equ	0F0h		;3rd port data

SIOTBE	equ	1		;transmit buffer entry
SIORDA	equ	2		;receive data available

PIO1CTL	equ	0E3h		;parallel port control register
PIO1ST	equ	0E2h		;parallel port status register
PIO1DT	equ	0E0h		;parallel port data register

PIOTBE	equ	80h		;mask and value for transmit ready

; Misc equates

UNDEF	equ	0FFh		;undefined value
RDTRIES	equ	5		;number of read attempts
CR	equ	13		;ascii for carriage return
LF	equ	10		;ascii for line feed

	org	BIOSBAS
;-----------------------------------------------------------------------------
;  BIOS Entry Jump Table. 
;-----------------------------------------------------------------------------
	jmp	boot		;cold start
wboote	jmp	wBoot		;warm start
	jmp	conSt		;console status
	jmp	conIn		;console character in (and disk flush)
	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

;---------------------------------------------------------------------------
; MODE byte - BIOS flags. 
;     Must remain at offset 33h from start of the BIOS
;---------------------------------------------------------------------------
fCLDCMD	equ	01h		;true = CCP process cmd on cold start
fWRMCMD	equ	02h		;true = CCP process cmd on warm start
fENAINT	equ	10h		;true = re-enable interrupts after I/O

mode	db	fENAINT		;re-enable interrupts, no command line

;---------------------------------------------------------------------------
;  IOBYTE - at offset 34h from start of the BIOS
;
;    CON device (bits 1,0):
;	00 - TTY serial port at A0h*
;	01 - CRT serial port at F0h
;	10 - BAT indirect through RDR/LST
;	11 - UC1 serial port at 90h
;
;    RDR device (bits 3,2): 
;	00 - TTY serial port at A0h
;	01 - PTR serial port at F0h*
;	10 - UR1 serial port at 90h
;	11 - UR2 serial port at A0h
;
;    PUN device (bits 5,4):
;	00 - TTY serial port at A0h
;	01 - PTP serial port at F0h*
;	10 - UP1 serial port at 90h
;	11 - UP2 serial port at A0h
;
;    LST device bits (7,6):
;	00 - TTY serial port at A0h
;	01 - CRT serial port at F0h
;	10 - LPT parallel port at E0h*
;	11 - UL1 serial port at 90h
;
;* Default setting
;---------------------------------------------------------------------------
defIob	db	94h		;LST = LPT (parallel at E0h)
				;PUN = PTP (serial at F0h)
				;RDR = PTR (serial at F0h)
				;CON = TTY (serial at A0h)

;---------------------------------------------------------------------------
; MASK bytes for device input and output. The data byte read or
;    written is AND'd with the mask specified. These must remain 
;    at offset 35-39h from the start of the BIOS.
;---------------------------------------------------------------------------
conMskI	db	07Fh		;console input
rdrMskI	db	0FFh		;reader input

conMskO	db	07Fh		;console output
punMskO	db	0FFh		;punch output
lstMskO	db	0FFh		;list output

;---------------------------------------------------------------------------
; Motor and head delay parameters. These bytes must remain at 
;    offset 3Ah-3Dh from the start of the BIOS.
;---------------------------------------------------------------------------
mtrDly	db	8		;motor start delay in 0.1 sec intervals
hdSetl	db	25		;25ms head settle delay after step
revDir	db	25		;25ms delay when reversing step direction
headDly	db	50		;head load delay in ms

;----------------------------------------------------------------------------
; boot - Cold boot BIOS entry. CPM is already loaded in memory. Hand
;    control to the CCP.
;----------------------------------------------------------------------------
boot	lxi	sp,0100h	;init stack pointer

	xra	a
	sta	CDISK		;current CPM disk is drive zero

	call	serInit		;init serial ports
	lda	defIob		;initialize IOBYTE
	sta	IOBYTE

	call	dispMsg		;display welcome message
	db	CR,LF,LF
	db	MEMSIZE/10 + '0',MEMSIZE mod 10 + '0'
	db	'K CP/M 2.2 v'
	db	VERSION/10 + '0','.',VERSION mod 10 + '0',CR,LF

 if DISK720
	db	'MFA 720K Disks',CR,LF,0
 endif

 if DISK320
	db	'MFA 320K Disks',CR,LF,0
 endif

; Determine whether or not execution of a cold start command line is
;    requested, then enter CP/M.

	lda	mode		;see if cold start command line requested
	ani	fCLDCMD
	push	psw		;save result

	jmp	coldCpm		;enter CP/M

;----------------------------------------------------------------
; 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

	mvi	c,0		;boot from drive zero
	call	selDsk		;select drive zero

	mvi	b,WBSECS	;B=number of sectors to read
	mvi	c,WBSTART	;C=starting secctor
	lxi	h,CCPBASE	;HL->CP/M load address
	xra	a		;starting track = 0

wbTrk	sta	trkNum		;save track number to use

; wbLoop - read sector loop for warm boot

wbLoop	shld	dmaAddr		;set read destination
	call	setSec		;set sector from C

	push	b		;save sector count and number
	call	read		;read next sector
	pop	b
	jnz	wBoot		;failed read, start over
	
	dcr	b		;decrement total sector count
	jz	entCpm		;warm boot is done, enter CP/M
	
	inr	c		;bump to next sector number
	mvi	a,NUMSEC	;A=number of sectors per track
	cmp	c		;compare NUMSEC - next sector
	jnc	wbLoop		;sector is 32 or lower, go read it
	
	mvi	c,1		;resume with C=sector 1 and...
	mov	a,c		;  A=track 1
	jmp	wbTrk

; entCpm - Enter CPM. Set page zero variables, enter CPM with or without
;   command line based on MODE flags.

entCpm	lda	mode		;see if warm start command line requested
	ani	fWRMCMD
	push	psw		;save result

coldCpm	lxi	h,DEFDMA	;set the default dma address (080h)
	shld	dmaAddr

	mvi	a,JMP		;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

	lda	mode		;see if interrupts should be re-enabled
	ani	fENAINT
	jz	noInt1		;no

	ei			;else, enable interrupts

noInt1	pop	psw		;restore command line test
	jz	CCPBASE+3	;enter CCP and don't run command line
	jmp	CCPBASE		;enter CCP with possible cmd line

;---------------------------------------------------------------------------
;  CON device I/O
;---------------------------------------------------------------------------

; conSt - Console status BIOS entry point. Return FFh if character ready,
;    return zero otherwise. 

conSt	lda	IOBYTE		;A=CP/M IOBYTE
	call	doIoCon		;call the proper I/O routine below
	dw	sio1ISt		;TTY serial port at A0h
	dw	sio3ISt		;CRT serial port at F0h
	dw	rdrSt		;BAT indirect through RDR logical device
	dw	sio2ISt		;UC1 serial port at 90h

; 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 and invalidate
;    the track buffer content.

conIn	call	invFlsh		;invalidate and flush track buffer

	lda	conMskI		;put input data mask in C
	mov	c,a	
	
	lda	IOBYTE		;A=CP/M IOBYTE
	call	doIoCon		;call the proper I/O routine below
	dw	sio1In		;TTY serial port at A0h
	dw	sio3In		;CRT serial port at F0h
	dw	reader		;BAT indirect through RDR logical device
	dw	sio2In		;UC1 serial port at 90h

; conOut - Console output BIOS entry point. Output the character in C.

conOut	lda	conMskO		;mask output byte
	ana	c
	mov	c,a
	
	lda	IOBYTE		;A=CP/M IOBYTE
	call	doIoCon		;call the proper I/O routine below
	dw	sio1Out		;TTY serial port at A0h
	dw	sio3Out		;CRT serial port at F0h
	dw	list		;BAT indirect through LSTlogical device
	dw	sio2Out		;UC1 serial port at 90h

;---------------------------------------------------------------------------
;  RDR device status and input
;---------------------------------------------------------------------------
rdrSt	lda	IOBYTE		;A=CP/M IOBYTE
	call	doIoRdr		;call the proper I/O routine below
	dw	sio1ISt		;TTY serial port at A0h
	dw	sio3ISt		;PTR serial port at F0h
	dw	sio2ISt		;UR1 serial port at 90h
	dw	sio1ISt		;UR2 serial port at A0h
	
; reader - Reader input BIOS entry point. Wait for input from the
;    reader and return the character in A.

reader	lda	rdrMskI		;put input data mask in C
	mov	c,a	
	
	lda	IOBYTE		;A=CP/M IOBYTE
	call	doIoRdr		;call the proper I/O routine below
	dw	sio1In		;TTY serial port at A0h
	dw	sio3In		;PTR serial port at F0h
	dw	sio2In		;UR1 serial port at 90h
	dw	sio1In		;UR2 serial port at A0h

;---------------------------------------------------------------------------
;  PUN device output
;---------------------------------------------------------------------------

; punch - Punch output BIOS entry point. Output the character in C.

punch	lda	punMskO		;mask output byte
	ana	c
	mov	c,a

	lda	IOBYTE		;A=CP/M IOBYTE
	call	doIoPun		;call the proper I/O routine below
	dw	sio1Out		;TTY serial port at A0h
	dw	sio3Out		;PTR serial port at F0h
	dw	sio2Out		;UR1 serial port at 90h
	dw	sio1Out		;UR2 serial port at A0h

;---------------------------------------------------------------------------
;  LST device status and output
;---------------------------------------------------------------------------

; listSt - List status BIOS entry point. Return FFh if read to send.
;     Return 0 if not ready.

listSt	lda	IOBYTE		;A=CP/M IOBYTE
	call	doIoLst		;call the proper I/O routine below
	dw	sio1OSt		;TTY serial port at A0h
	dw	sio3OSt		;CRT serial port at F0h
	dw	pio1OSt		;LPT parallel port at E0h
	dw	sio2OSt		;UL1 serial port at 90h


; list - List output BIOS entry point. Output the character in C.

list	lda	lstMskO		;mask output byte
	ana	c
	mov	c,a

	lda	IOBYTE		;A=CP/M IOBYTE
	call	doIoLst		;call the proper I/O routine below
	dw	sio1Out		;TTY serial port at A0h
	dw	sio3Out		;CRT serial port at F0h
	dw	pio1Out		;BAT indirect through LSTlogical device
	dw	sio2Out		;UC1 serial port at 90h

;---------------------------------------------------------------------------
; doIoXxx - Jump to the proper physical I/O routine based on the 
;    IOBYTE value passed in A. Preseves H,L.
;--------------------------------------------------------------------------- 
doIoRdr	rlc			;RDR rotates left 7 times
	rlc

doIoPun	rlc			;PUN rotates left 5 times
	rlc

doIoLst	rlc			;LST rotates left 3 times
	rlc

doIoCon	rlc			;CON rotates left 1 time

; 2 bit field aligned in bits 2 and 1. Use this as a word index
;    into jump table of caller.

	ani	06		;A=IOBYTE bit field * 2
	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

;---------------------------------------------------------------------------
; Port input status routines. Return FF if character ready, else zero.
;---------------------------------------------------------------------------
sio1ISt	in	SIO1ST		;A=port status register
	jmp	sioISt		;test and return status

sio2ISt	in	SIO2ST		;A=port status register
	jmp	sioISt		;test and return status

sio3ISt	in	SIO3ST		;A=port status register

sioISt	ani	SIORDA
	rz			;return zero if no data
	mvi	a,0FFh		;else, return FF if data is present
	ret

;---------------------------------------------------------------------------
; Port output status routines. Return FF if ready to send, else zero.
;---------------------------------------------------------------------------
sio1OSt	in	SIO1ST		;A=port status register
	jmp	sioOSt		;test and return status

sio2OSt	in	SIO2ST		;A=port status register
	jmp	sioOSt		;test and return status

sio3OSt	in	SIO3ST		;A=port status register

sioOSt	ani	SIOTBE
	rz			;return zero if not ready to send
	mvi	a,0FFh		;else, return FF if ready to send
	ret

pio1OSt	in	PIO1ST		;A=port status register
	ani	PIOTBE		;test status
	rz			;return zero if not ready to send
	mvi	a,0FFh		;else return FF if OK to send
	ret

;---------------------------------------------------------------------------
; Port input routines
;---------------------------------------------------------------------------
sio1In	call	sio1ISt		;wait for character
	jz	sio1In

	in	SIO1DT		;read character
	ana	c		;mask with value provided in C
	ret

sio2In	call	sio2ISt		;wait for character
	jz	sio2In

	in	SIO2DT		;read character
	ana	c		;mask with value provided in C
	ret

sio3In	call	sio3ISt		;wait for character
	jz	sio3In

	in	SIO3DT		;read character
	ana	c		;mask with value provided in C
	ret

;---------------------------------------------------------------------------
; Port output routines
;---------------------------------------------------------------------------
sio1Out	call	sio1OSt		;wait for character
	jz	sio1Out

	mov	a,c		;A=character to send
	out	SIO1DT		;send it
	ret

sio2Out	call	sio2OSt		;wait for character
	jz	sio2Out

	mov	a,c		;A=character to send
	out	SIO2DT		;send it
	ret


sio3Out	call	sio3OSt		;wait for character
	jz	sio3Out

	mov	a,c		;A=character to send
	out	SIO3DT		;send it
	ret


pio1Out	call	pio1OSt		;wait for character
	jz	pio1Out

	mov	a,c		;A=character to send
	out	PIO1DT		;send it
	ret

;----------------------------------------------------------------------------
; 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
;    disk number is stored in drvNum. No drive activity takes place.
;
; selDsk is also used as the "Extended Function" entry point. If a drive
;    number of 0FFh is passed in C, then this call is an extended function
;    request and not a disk select request. Register B contains the
;    extended function number (see list below). HL is returned with H=0
;    and L=the number of extended functions supported. This return value
;    is neither the "invalid drive" response (HL=0) or a valid DPH pointer
;    when a drive is selected. This allows the caller to detect whether a
;    a BIOS supports extended functions or not. If the request was processed
;    successfully, zero status is returned true, else zero status is false.
;
;    Extended BIOS Functions supported by this BIOS:
;	00h - Invalidate and flush track buffer.
;----------------------------------------------------------------------------
selDsk	lxi	h,0		;init HL=0 for invalid drive request

	inr	c		;test for drive number of 0FFh
	jnz	notExt		;not an extended function request

	inr	l		;H=0, L=1 (number of functions supported)
	mov	a,b		;A=extended function code
	ora	a		;code 0 = flush track buffer?
	cz	invFlsh		;yes, invalidate and flush track buffer
	ret			;exit with HL=1 and proper zero status

notExt	mov	a,c		;A=drive number + 1
	dcr	a		;A=drive number
	cpi	NUMDRV		;valid drive number?
	rnc			;invalid drive number, return with HL=0=error	
	
	sta	drvNum		;save new drive number
	add	a		;compute disk parameter header address
	add	a		;   as (16*drvNum) + dpHead
	add	a
	add	a		;A=16*drvNum

	lxi	h,dpHead  	;HL->start of disk parameter headers
	mov	e,a
	mvi	d,0
	dad	d		;HL->DPH for the passed drive number
	ret

;----------------------------------------------------------------------------
; home - Home BIOS entry. Set trkNum to zero. No drive activity takes place.
;----------------------------------------------------------------------------
home	xra	a		;set trkNum to zero
	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		;save track number passed in C
	sta	trkNum
	ret

;----------------------------------------------------------------------------
; setSec - Set Sector BIOS entry. C contains the desired sector number.
;    The sector number is saved in secNum for later use. No actual
;    drive activity takes place.
;----------------------------------------------------------------------------
setSec	mov	a,c		;save sector number passed in 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		;save buffer address passed in BC
	mov	l,c
	shld	dmaAddr
	ret

;----------------------------------------------------------------------------
; secTran - Sector translation BIOS entry. Convert zero-indexed logical 
;    sector number in BC to a CPM one-indexed physical sector number in HL
;    based on the translate table passed in DE. No translate table is used
;    so output sector = input sector + 1.
;----------------------------------------------------------------------------
secTran	mov	h,b		;output = input + 1
	mov	l,c
	inr	l		
	ret

;----------------------------------------------------------------------------
; read - Read sector BIOS entry. Read one sector using the drvNum, 
;    trkNum, secNum, and dmaAddr previously specified.
; On Entry:
;    drvNum = drive to read from
;    trkNum = track to read from
;    secNum = physical sector number to read
;    dmaAddr = address of buffer to read into
; On Exit:
;   If read successful:
;      sector read into (dmaAddr)
;      HL->dmaAddr+SECLEN
;      Z flag = 1
;   if read fails:
;      Z flag = 0
;  Clobbers all (see HL note above)
;----------------------------------------------------------------------------
read	lxi	h,exitDio	;RETs will jump to exitDio
	push	h

	call	ckFlush		;see if track buffer should be flushed
	rnz			;track flush error (jump to exitDio)

	call	readTrk		;fill track buffer if needed
	rnz			;track read error (jump to exitDio)
	
; move sector from proper offset in trkBuf to (dmaAddr).

	call	secPtrs		;HL=(dmaAddr), DE->sector buffer in trkBuf
	jmp	moveD2H		;move sector to (dmaAddr), exit thru exitDio

;----------------------------------------------------------------------------
; write - Write sector BIOS entry. Write one sector using the drvNum,
;    trkNum, secNum and dmaAddr specified. 
; On Entry:
;    drvNum = drive to write
;    trkNum = track to write
;    secNum = physical sector number to write
;    dmaAddr = address of buffer to write from
; On Exit:
;   If successful:
;      sector written to trkBuf from (dmaAddr)
;      DE->dmaAddr+SECLEN
;      Z flag = 1
;   if write fails:
;      Z flag = 0
;  Clobbers all (see DE note above)
;----------------------------------------------------------------------------
write	lxi	h,exitDio	;RET will jump to exitDio
	push	h

	call	ckFlush		;see if track buffer should be flushed
	rnz			;error, jump to exitDio

	call	readTrk		;fill track buffer if needed
	rnz			;track read error, jump to exitDio

	lda	fWrtPrt		;write protected?
	ora	a
	rnz			;yes, jump to exitDio

	lxi	h,bfDirty	;set buffer dirty flag true
	mvi	m,UNDEF

; move sector from (dmaAddr) to proper offset in trkBuf

	call	secPtrs		;HL=(dmaAddr), DE->sector buffer in trkBuf
	xchg			;DE=(dmaAddr), HL->sector buffer in trkBuf
	jmp	moveD2H		;move data to trkBuf, exit through exitDio

; exitDio - exit disk I/O. Restore interrupts and properly set A and
;     the zero flag. Note that moveD2H exits with zero status true.

exitDio	push	psw		;save return status
	lda	mode		;see if interrupts should be re-enabled
	ani	fENAINT
	jz	noInt2		;no

	ei			;else, enable interrupts

noInt2	pop	psw		;restore return status 
	mvi	a,0		;return A=0 if Z true	
	rz

	dcr	a		;else, return A=FF if Z false
	ret

;------------------------------------------------------------------------------
; readTrk- read full track into track buffer if the requested 
;    drive (drvNum) or track (trkNum) does not match the buffered
;    drive (bfDrive) or buffered track (bfTrack).
; On Entry:
;    drvNum, trkNum hold drive and track for the requested read
; On Exit:
;    trkNum on drvNum read into trkBuf
;    Z set if track read, Z cleared if error
; Clobbers all
;------------------------------------------------------------------------------
readTrk	lhld	bfDrive		;L=buffered drive, H=buffered track

	lda	drvNum		;A=requested drive
	cmp	l		;same drive buffered?
	jnz	newBuf		;drive doesn't match, need a new buffer

	lda	trkNum		;A=requested track
	cmp	h		;same track buffered?
	rz			;yes, already have this buffer

; newBuf - new buffer (full track) needed

newBuf	mvi	a,UNDEF		;invalidate buffered data
	sta	bfDrive

	call	dSelDrv		;select the drive
	rnz			;error, no disk

	mvi	c,1		;C=restore-reseek counter

rdRseek	call	dSeek		;seek to trkNum, E=sec under head
	rnz			;unrecoverable seek error

	mvi	d,PNUMSEC	;D=physical sector counter

rdLoop	mvi	b,RDTRIES	;B=sector retry counter
	call	dNxtSec		;E=sector, HL->sector buffer

rdRtry	call	dRead		;read the sector
	jnz	rdError		;read error

	dcr	d		;decrement sector count
	jnz	rdLoop		;do next sector

	lhld	drvNum		;set the drive and track that are
	shld	bfDrive		;  now in the track buffer
	ret			;return with zero status

; rdError - try to read the failed sector again up to RDTRIES times.
;   Then try a restore to track zero and a re-read of the track
;   on time.

rdError	call	dSecAdr		;HL->sector buffer of failed read
	dcr	b		;decrement retry counter
	jnz	rdRtry		;re-read sector

	dcr	c		;decrement restore-reseek counter
	rm			;exit with error if it goes below zero

	call	dRestor		;restore to track zero
	jmp	rdRseek		;reseek and try to read track again

;------------------------------------------------------------------------------
; ckFlush - Check if track buffer should be flushed. This
;    function must be called before any drive selection or seek
;    operation. The buffer is flushed if the bfDirty flag is
;    set and the drive or track number are different than the
;    buffered track.
; On Entry:
;    drvNum, trkNum updated for the new I/O call
; On Exit:
;    Z set for no error, Z cleared if write error occured.
;    Clobbers all
;------------------------------------------------------------------------------
ckFlush	lda	bfDirty		;see if track buffer is dirty
	ora	a
	rz			;no, exit with Z set

	lhld	bfDrive		;L=buffered drive, H=buffered track
	lda	drvNum		;A=requested drive number
	cmp	l		;same drive?
	jnz	wrtTrk		;no, flush

	lda	trkNum		;A=requested track number
	cmp	h		;same track?
	rz			;yes, no need to flush

; wrtTrk - track buffer needs to be written

wrtTrk	xra	a		;clear buffer dirty flag
	sta	bfDirty
	
	call	dReSel		;re-select the same drive
	jnz	wrtErr		;error
	
	mvi	d,PNUMSEC	;D=sector counter

wrtLoop	call	dNxtSec		;E=sector, HL->sector buffer
	call	dWrite		;write the sector if not bad
	jnz	wrtErr		;hard write error
	
	dcr	d		;decrement sector count
	jnz	wrtLoop

	mvi	a,5		;delay 5ms to let last sector finish
	jmp	delayMs		;exit through delayMS (returns zero status)

; wrtErr - Display error message since CP/M won't for a delayed write
;   called at conIn time.

wrtErr	call	dispMsg		;A is zero on return
	db	CR,LF,'Delayed Write Err', CR, LF,0

	dcr	a		;exit with non-zero status
	ret	

;------------------------------------------------------------------------------
; invFlush - Invalidate and flush the track buffer.
;    Console input is used as an idle indicator and as a good time
;    to flush a dirty track buffer, so the conIn function calls this
;    subroutine. Even if not flushed, the track buffer is invalidated
;    to force a track read on the next disk I/O request. This helps
;    catch a disk swap.
; On Entry:
;    Same drive is on the same track as buffered in trkBuf
; On Exit:
;    Track buffer written if required and invalidated.
;    Z flag set
; Clobbers all except HL
;------------------------------------------------------------------------------
invFlsh	mvi	a,UNDEF		;invalidate track buffer
	sta	bfDrive

	push	h		;preserve HL in case conIn caller expects it
	call	ckFlush		;flush the track if needed
	pop	h

	lda	mode		;see if interrupts should be re-enabled
	ani	fENAINT
	jz	noInt3		;no

	ei			;else, enable interrupts

noInt3	lda	selByte		;see if the drives are deselected
	ora	a
	rz			;yes, just exit

	xra	a		;otherwise, de-select the drives
	sta	selByte
	out	FDCSEL 
	ret
	
;----------------------------------------------------------------------------
; secPtrs - Return HL=(dmaAddr), DE->sector buffer in trkBuf. 
; On Entry:
;    secNum = sector to read or write
;    dmaAddr = address of buffer to read or write
; On Exit:
;   HL=(dmaAddr)
;   DE->sector buffer in trkBuf (status byte)
; Clobbers none
;----------------------------------------------------------------------------
secPtrs	lda	secNum		;A=sector number
	sui	1		;zero index and clear carry
	rar			;form D=MSB of sector*128
	mov	d,a

	rar			;form E=LSB of sector*128	
	ani	80h
	mov	e,a		;DE=128*sector-1
	
	lxi	h,trkBuf	;compute HL=trkBuf+(sector*128)
	dad	d
	xchg			;DE->sector within trkBuf

	lhld	dmaAddr		;HL=(dmaAddr)
	ret

;------------------------------------------------------------------------------
; moveD2H - move 128 bytes from (DE) to (HL).
; On Entry:
;    DE->source buffer
;    HL->destination buffer
; On Exit:
;    Z flag set
; Clobbers B. DE and HL updated as buffer moved.
;------------------------------------------------------------------------------
moveD2H	mvi	b,SECLEN	;B=buffer length (128 bytes)

movLoop	ldax	d		;move from (DE) to (HL)
	mov	m,a
	inx	d		;increment both pointers
	inx	h
	dcr	b		;decrement character count
	jnz	movLoop		;loop until count = 0

	ret			;Z is set on exit

;------------------------------------------------------------------------------
; dispMsg - display null terminated string on console device. This
;    code assumes conOut does not clobber HL.
; On Entry: HL->message
; On Exit:
;    Z flag set
; Clobbers C,H,L
;------------------------------------------------------------------------------
dispMsg	pop	h

dmLoop	mov	a,m		;display next character
	inx	h		;move to next character
	ora	a		;null terminator?
	jz	dmExit		;yes, exit

	mov	c,a		;C=character to send
	call	conOut
	jmp	dmLoop		;do next character

dmExit	pchl			;return past message string

;------------------------------------------------------------------------------
; delayMs - delay ms specified in A
; On Entry: A = delay in ms
; On Exit:
;    Z flag set
; Clobbers none
;------------------------------------------------------------------------------
delayMs	push	b		;preserve B,C

dms1ms	mvi	b,2000/15	;B=1ms through following loop

dmsLoop	dcr	b		;(5) decrement B
	jnz	dmsLoop		;(10)

	dcr	a		;decrement ms left
	jnz	dms1ms		;do another ms

	pop	b		;restore B,C
	ret

;------------------------------------------------------------------------------
; serInit - Initialize the 8251 serial ports and 8255 parallel port
;------------------------------------------------------------------------------
serInit	xra	a
	out	SIO1ST		;1st, three zeros to satisfy possible
	out	SIO2ST		;   sync mode power-up
	out	SIO3ST

	out	SIO1ST
	out	SIO2ST
	out	SIO3ST

	out	SIO1ST
	out	SIO2ST
	out	SIO3ST

	mvi	a,40h		;reset 8251's
	out	SIO1ST
	out	SIO2ST
	out	SIO3ST

	mvi	a,4Eh		;async,	8N1
	out	SIO1ST
	out	SIO2ST
	out	SIO3ST
	
	mvi	a,37h		;enable xmit, receive
	out	SIO1ST
	out	SIO2ST
	out	SIO3ST

	mvi	a,0ABh		;init 8255 to PORT A out, PORT C handshake
	out	PIO1CTL

	ret


;****************************************************************************
;
; MFA floppy controller disk I/O routines
;
;****************************************************************************

; Physical disk parameters

PSECLEN	equ	512		;bytes per sector

 if DISK720
PNUMSEC	equ	9		;sectors per track
 endif

 if DISK320
PNUMSEC	equ	8		;sectors per track
 endif

; Disk controller equates (WD1793)

FDCBASE	equ	0C0H		;controller base address
FDCCMD	equ	FDCBASE		;1793 command register
FDCSTAT	equ	FDCBASE		;1793 status register
FDCTRK	equ	FDCBASE+1	;1793 track register
FDCSEC	equ	FDCBASE+2	;1793 sector register
FDCDATA	equ	FDCBASE+3	;1793 data in/out register
FDCSEL	equ	FDCBASE+4	;FDC drive select port
FDCWAIT	equ	FDCBASE+8	;FDC stall/wait port

; Commands for 1793

STPRATE	equ	0		;6ms step rate (1mhz clock)

cRESTOR	equ	008h+STPRATE	;restore, head load, no verify
cSEEK	equ	018h+STPRATE	;seek, head load, no verify
cSTPIN	equ	058h+STPRATE	;step in, update track reg, head loaded
cSTPOUT	equ	078h+STPRATE	;step out, update track reg, head loaded
cREAD	equ	080h		;read sector, head assumed settled
cRDADDR	equ	0C0h		;read address marker, head assumed settled
cWRITE	equ	0A0h		;write sector, head assumed settled
cWRTTRK	equ	0F0h		;write track, head assumed settled
cRESET	equ	0D0h		;reset 1793 to idle
cIDXINT	equ	0D4h		;force interrupt on index pulse

cSTPTGL	equ	020h		;xor toggles between step in and out
cWAITHD	equ	004h		;bit for read/write to wait for head settle

; Status bits from 1793

sNOTRDY	equ	80h		;1=not ready
sWRPROT	equ	40h		;1=write protected
sHDLOAD	equ	20h		;1=head is loaded
sSKERR	equ	10h		;1=seek error
sCRCERR	equ	08h		;1=CRC error
sTRACK0	equ	04h		;1=on track zero
sINDEX	equ	02h		;1=index detected
sBUSY	equ	01h		;1=1793 busy

sSECERR	equ	10h		;1=sector not found
sWRTFLT	equ	20h		;1=write fault
sDLOST	equ	04h		;1=lost data 

; Combined status flag masks for status of a seek, read, and write

smTRK0	equ	sSKERR+sNOTRDY+sBUSY+sTRACK0
smSEEK	equ	sSKERR+sNOTRDY+sBUSY
smREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY+sBUSY
smWRITE	equ	smREAD+sWRPROT+sWRTFLT

; Drive select register parameters

pDRIVE0	equ	001h		;1=drive 0 select
pDRIVE1	equ	002h		;1=drive 1 select
pDENS	equ	004h		;1=single density, 0=double density
pSIDE	equ	008h		;1=side, 0=side 0

; Misc Equates

FDCINTV	equ	02Ch		;RST5.5 interrupt vector for FDC
SIM	equ	030h		;8085 SIM opcode

;--------------------------------------------------------------
; dSelDrv - Select drive, start and wait on motor startup.
;    Updates the track register in the 1793 with the track
;    the newly selected drive is on. Updates the write protect
;    flag (fWrtPrt) for the selected drive.
; On Entry:
;    drvNum = drive to be selected
; On Exit:
;    fWrtPrt = non-zero if drive write protected		
;    Z flag set = drive selected and ready
;    Z flag clear = error
; Clobbers B,C,H,L
;--------------------------------------------------------------
dSelDrv	lda	drvNum		;A=drive number
	ani	01h		;limit to physical drives 0 and 1
	inr	a		;drv 0 sets 1's bit, drv 1 sets 2's bit
	mov	b,a		;B=drive select bits

	lxi	h,selByte	;HL->current select value
	mov	c,m		;C=current select value
	mov	m,b		;store new select value in selByte
	
	in	FDCSTAT		;see if drive ready (motor running)
	ani	sNOTRDY		;Z true if drive is ready

	mov	a,b		;select drive, start motor
	out	FDCSEL

	jz	dMtrRdy		;motor is already up to speed

	call	dWtMtr		;wait for motor to get up to speed
	jz	dSkipHd		;drive, head, and disk ready

	ret			;else return with error (no disk)

dMtrRdy	sub	c		;compare new select byte to previous
	ani	3		;only look at the drive selects
	jz	dSkipHd		;same drive so head already loaded

	call	dWtIdx		;wait for an index pulse on new drive
	rnz			;no disk

	mov	a,h		;if > 28ms waiting for index, then
	ani	0FCh		;   skip the head load delay
	jnz	dSkipHd		;   (test for HL >= 1024, 27us lsbit)

	lda	headDly		;A=head load delay 
	ora	a		;test for zero
	cnz	delayMs		;delay headDly if not zero

dSkipHd	mvi	a,cRESET	;ensure 1793 is idle
	out	FDCCMD		;follow with min 28us delay

	mvi	a,JMP		;(7) rewrite FDC interrupt vector to be safe
	sta	FDCINTV		;(13)
	lxi	h,fdcIsr	;(10) ISR address
	shld	FDCINTV+1	;(16)
	shld	FDCINTV+1	;(16) just waste time

	in	FDCSTAT		;read 1793 status
	ani	sWRPROT		;get write protect bit alone
	sta	fWrtPrt		;save write protect status flag

	call	dReadAm		;returns A=track from address mark
	out	FDCTRK		;update track register in 1793
	rz			;exit if address mark worked
	
	jmp	dRestor		;else, restore to track 0 and exit

;--------------------------------------------------------------
; dReSel - Reselect the drive already in the select byte. 
; On Entry:
;    selByte = drive select byte already set
; On Exit:
;    E = sector under head
;    Z flag set = drive selected and ready
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dReSel	in	FDCSTAT		;see if drive ready (motor running)
	ani	sNOTRDY		;Z true if drive is ready

	lda	selByte		;select same drive
	out	FDCSEL

	jz	drMtRdy		;motor is already up to speed

	call	dWtMtr		;wait for motor to get up to speed
	rnz			;error, no disk

drMtRdy	call	dReadAm		;returns A=track from address mark
	lxi	h,pTrkNum	;HL->track the drive is on
	cmp	m		;should be same track still
	ret

;--------------------------------------------------------------
; dWtMtr - Wait the motor startup time specified in the mtrDly
;    parameter. Fall into dWtIdx to wait for an index pulse
;    to ensure drive ready is asserted.
; On Entry:
;    mtrDly = motor delay in 0.1s increments
; On Exit:
;    Z flag set if disk present
;    Z flag clear if no disk present
; Clobbers H
;--------------------------------------------------------------
dWtMtr	lda	mtrDly		;motor delay in 0.1s increments
	ani	0Fh		;limit to 0-15 (0-1.5s)
	mov	h,a		;H=number of 100ms delays

dw100	mvi	a,100		;delay 100ms
	call	delayMs
	dcr	h		;decrement number of 100ms's to wait
	jnz	dw100
				;fall into dWtIdx to verify index pulse

;--------------------------------------------------------------
; dWtIdx - Wait for an index pulse to come by. This ensures
;    the READY line is asserted because of the currently 
;    selected drive and not the previously selected drive.
; On Entry:
; On Exit:
;    HL = time spent looking for index pulse (27us lsbit)
;    Z flag set if index detected
;    Z flag false if no index (disk not present)
; Clobbers H,L
;--------------------------------------------------------------
dWtIdx	mvi	a,cRESET	;force type 1 status response
	out	FDCCMD

	xthl			;(18) min 28us delay before status read
	xthl

	lxi	h,0		;timeout counter, 54 cycle loop

dwIdx1	inx	h		;(5) increment timeout
	mov	a,h		;(5) compare MSB value
	sui	0E0h		;(7) reach E000h (1.55s)?
	jz	dNoDisk		;(10) index not changing, no disk

	in	FDCSTAT		;(10) wait for NOT index pulse
	ani	sINDEX		;(7)
	jnz	dwIdx1		;(10) loop while index asserted

dWtIdx2	inx	h		;(5) increment timeout
	mov	a,h		;(5) compare MSB value
	sui	0E0h		;(7) reach E000h (1.55s)?
	jz	dNoDisk		;index not detected, no disk

	in	FDCSTAT		;(10) wait for index pulse
	ani	sINDEX		;(7)
	jz	dWtIdx2		;(10) loop while index not asserted

	xra	a		;have index, return zero
	ret

dNoDisk dcr	a		;no index, return non-zero
	ret

;--------------------------------------------------------------
; dSeek - Seek to the track specified in trkNum on the
;    currently selected drive. After the seek completes, an
;    address mark is read to verify the seek and to determine 
;    what sector is under the head.
; On Entry:
;    trkNum = desired track number from CPM
; On Exit:
;    E = physical sector number from the address mark
;    Z flag set = seek worked and verified
;    Z flag clear = error
; Clobbers D,E,H,L
;--------------------------------------------------------------

; Convert the logical track (trkNum) to the physical track (pTrkNum).
;    Even tracks are on side 0, odd tracks are on side 1. Update
;    the drive select byte to include the side bit based on the
;    specified trkNum.

dSeek	lda	selByte		;A=current select byte
	ani	3		;drive bits alone
	mov	d,a		;D=drive select bits alone

	lda	trkNum		;A=desired track number
	rar			;A=physical track number (cylinder)
	sta	pTrkNum		;pTrkNum contains physical track number

	mov	a,d		;A=drive select bits alone
	jnc	dSide0		;side 0 for even tracks (no carry)

	ori	pSIDE		;else, set side 1 bit

dSide0	sta	selByte		;save select byte
	out	FDCSEL		;and output to the controller

; Issue the seek command and verify the proper track is under the
;    head by reading an address mark. 

	mvi	d,3		;D=retry counter

dsRetry	call	dSeekC		;issue seek command
	rnz			;hard error

	call	dReadAm		;returns A=track from address mark
	jnz	dsFail		;couldn't read address mark

	lxi	h,pTrkNum	;HL->desired track number
	cmp	m		;does actual match requested?
	rz			;yes, we're done

dsFail	dcr	d		;decrement retry counter
	rm			;went below zero, exit with <> 0

	call	dRestor		;restore to track zero
	jz	dsRetry		;try again
	
	ret			;return with error
	
;--------------------------------------------------------------
; dSeekC - Issue seek command. Delay head settle time after
;    performing the seek.
; On Entry:
;    pTrkNum = Physical track to seek to
; On Exit:
;    A = 1793 status register
; Clobbers none
;--------------------------------------------------------------
dSeekC	lda	pTrkNum		;give track to seek to 1793
	out	FDCDATA

	mvi	a,cSEEK		;issue seek command
	call	dDoCmd
	ani	smSEEK		;any of these bits is an error
	rnz			;hard seek failure

	lda	hdSetl		;wait for head settle time
	jmp	delayMs		;return with zero through delayMs

;--------------------------------------------------------------
; dRestor - Restore the currently selected drive to track 0.
;    Waits for the direction reversal delay (revDir) before
;    exiting in case a subsequent seek is immediately done.
; On Entry:
;    
; On Exit:
;    Z flag set = restore operation worked
;    Z flag clear = restore failed
; Clobbers none
;--------------------------------------------------------------
dRestor	mvi	a,cRESTOR	;issue restore to track zero command
	call	dDoCmd
	ani	smSEEK		;any of these bits is an error
	rnz			;hard restore failure
	
	lda	revDir		;wait direction reversal delay
	jmp	delayMs		;return with zero through delayMs

;--------------------------------------------------------------
; dDoCmd - Issue command passed in A to the 1793. Control
;    is returned to caller upon command completion by the
;    interrupt service routine.
; On Entry:
;    A = command to write to 1793
; On Exit:
;    A = 1793 status register
; Clobbers none
;--------------------------------------------------------------
dDoCmd	push	psw		;save passed command
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	pop	psw		;A = command for 1793
	out	FDCCMD
	
dsWait	jmp	dsWait		;wait for completion interrupt

;--------------------------------------------------------------
; dRead - Read sector on current track.
; On Entry:
;    Drive is selected, on proper track, head loaded
;    HL->sector buffer
;    E = sector number to read
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dRead	mov	a,e		;give sector number to FDC
	out	FDCSEC
	
	mvi	a,cREAD		;output read command
	call	dReadC		;do the read
	ani	smREAD		;any of these bits is an error
	ret

;--------------------------------------------------------------
; dReadAm - Read address mark from disk. Returns physical track
;    in A and sector in E from the first address mark found.
; On Entry:
;    Drive is selected
; On Exit:
;    Z flag set = success
;    Z flag clear = error
;    A = track number from disk
;    E = sector number from disk
; Clobbers H,L
;--------------------------------------------------------------
dReadAm	lxi	h,amBuf		;HL->address mark buffer
	mvi	a,cRDADDR	;read address mark command
	call	dReadC		;read with command in A
	ani	smREAD		;any of these status bits is an error
	rnz			;exit with error

	lxi	h,amSec		;HL->sector from address mark
	mov	e,m		;E=sector from address mark

	lda	amTrk		;A=track from address mark
	ret			;exit with data and status
	
;------------------------------------------------------------------
; dReadC - Issue read command from A and then go into a loop
;    receiving the bytes into the buffer pointed to by HL.
;    The ISR returns control directly to the caller of this
;    subroutine when the 1793 signals completion of the read.
; On Entry:
;    A = read sector or read address mark
;    HL->sector buffer
; On Exit:
;    A = completion status from 1793.
; Clobbers H,L
;------------------------------------------------------------------
dReadC	push	psw		;save command from A
	lda	selByte		;keep motor running
	out	FDCSEL
	
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	pop	psw		;A=read or read address mark
	out	FDCCMD		;issue read or read address command

drLoop	out	FDCWAIT		;wait for data byte
	in	FDCDATA		;get byte
	nop			;make sure interrupt occurs
	mov	m,a		;save in memory
	inx	h
	jmp	drLoop		;loop until completion interrupt
	
;--------------------------------------------------------------
; dWrite - Write a sector on current track.
; On Entry:
;    Drive is selected, on proper track, head loaded
;    HL->buffer address
;    E = sector number to write
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dWrite	mov	a,e		;give sector number to FDC
	out	FDCSEC
	
	call	dWriteC
	ani	smWRITE		;any of these bits is an error
	ret

;------------------------------------------------------------------
; dWriteC - Issue write command and then go into a loop
;    sending the bytes from the buffer pointed to by HL.
;    The ISR returns control directly to the caller of this
;    subroutine when the 1793 signals completion of the write.
; On Entry:
;    HL->sector buffer
; On Exit:
;    A = completion status from 1793.
; Clobbers HL
;------------------------------------------------------------------
dWriteC	lda	selByte		;keep motor running
	out	FDCSEL
	
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	mvi	a,cWRITE	;issue write command
	out	FDCCMD

dwLoop	out	FDCWAIT		;wait for controller	
	mov	a,m		;A=next byte to write
	out	FDCDATA		;give byte to 1793
	inx	h
	jmp	dwLoop		;interrupt will exit this loop

;--------------------------------------------------------------
; dNxtSec - Increment and wrap the physical sector number 
;    passed in E, then fall into dSecAdr to return HL with
;    a pointer to the physical sector address within the
;    track buffer.
; On Entry:
;    E = most recent physical sector number
; On Exit:
;    E = next physical sector number
;    HL->physical sector within trkBuf
; Clobbers none
;--------------------------------------------------------------
dNxtSec	inr	e		;bump to next physical sector
	jz	dSec1		;in case uninitialized rolled to 0

	mvi	a,PNUMSEC	;A=number of physical sectors
	cmp	e		;compare PNUMSEC - next sector
	jnc	dSecAdr		;next is good (1-8 or 9)

dSec1	mvi	e,1		;restart at physical sector 1
				;fall into dSecAdr

;--------------------------------------------------------------
; dSecAdr - Return HL with address of the sector buffer within
;    trkBuf for the physical sector number in E. 
; On Entry:
;    E = physical sector number
; On Exit:
;    E = sector number (not changed)
;    HL->sector within trkBuf
; Clobbers none
;--------------------------------------------------------------
dSecAdr	mov	a,e		;A=physical sector number
	dcr	a		;zero index the sector
	add	a		;A=MSB of sector*512

	lxi	h,trkBuf	;HL->track buffer
	add	h		;add MSB of offset
	mov	h,a		;HL->sector in track buffer
	ret

;------------------------------------------------------------------
; fdcIsr - Interrupt service routine when the 1793 signals command
;    completion. The interrupted address is removed from the stack
;    and control returns to the caller of the interrupted routine.
;    The status from the 1793 is returned in A.
;------------------------------------------------------------------
fdcIsr	pop	psw		;remove ISR return address

wtBusy	in	FDCSTAT		;get 1793 status
	rrc			;make sure it's not busy
	jc	wtBusy		;shouldn't happen, but be safe

	rlc			;store status in A
	ret			;return to caller of command


;*****************************************************************************
;
;   BIOS Data Area
;
;******************************************************************************


;-----------------------------------------------------------------------------
; Disk parameters for 720K with 2K blocks
;    Physical: DSDD, 2x80 tracks, 9 sectors/track (512 byte)
;    CP/M: 36 sectors/track (128 byte), 2K blocks, 128 directory entries
;-----------------------------------------------------------------------------
if DISK720
SPT	equ	36		;128 byte sectors per track
BSH	equ	4		;block shift factor (2K blocks)
BLM	equ	0Fh		;block mask (2K blocks)
EXM	equ	0		;extent mask
DSM	equ	354		;max block number (total-1)			
DRM	equ	127		;max directory mumber (total-1)
AL0	equ	0C0h		;directory block map (two blocks used)
AL1	equ	000h
 endif

;-----------------------------------------------------------------------------
; Disk parameters for 320K with 2K blocks
;    Physical: DSDD, 2x40 tracks, 8 sectors/track (512 byte)
;    CP/M: 32 sectors/track (128 byte), 2K blocks, 64 directory entries
;-----------------------------------------------------------------------------
 if DISK320	
SPT	equ	32		;128 byte sectors per track
BSH	equ	4		;block shift factor (2K blocks)
BLM	equ	0Fh		;block mask (2K blocks)
EXM	equ	1		;extent mask
DSM	equ	155		;max block number (total-1)			
DRM	equ	63		;max directory mumber (total-1)
AL0	equ	80h		;directory block map (one block used)
AL1	equ	00h
 endif

;-----------------------------------------------------------------------------
; Disk parameters for 720K with 4K blocks (NOT USED)
;    Physical: DSDD, 2x80 tracks, 9 sectors/track (512 byte)
;    CP/M: 36 sectors/track (128 byte), 4K blocks, 128 directory entries
;-----------------------------------------------------------------------------
 if 0
SPT	equ	36		;128 byte sectors per track
BSH	equ	5		;block shift factor (4K blocks)
BLM	equ	1Fh		;block mask (4K blocks)
EXM	equ	3		;extent mask
DSM	equ	176		;max block number (total-1)			
DRM	equ	127		;max directory mumber (total-1)
AL0	equ	80h		;directory block map (one block used)
AL1	equ	00h
 endif
 
dpHead	dw	0,0,0,0,dirBuf,dpb,csv0,alv0
	dw	0,0,0,0,dirBuf,dpb,csv1,alv1
	
dpb	dw	SPT		;sectors per track
	db	BSH		;allocation block shift factor (BSH) 4K
	db	BLM		;data location block mask (BLM) 4K blocks
	db	EXM		;extent mask (EXM)
	dw	DSM		;max block num (DSM) total-1	
	dw	DRM		;max directory entry number (DRM) total-1
	db	AL0,AL1		;AL0, AL1, one 2K block of directories
	dw	(DRM+1)/4	;CKS=(DRM+1)/4
	dw	2		;reserved tracks for CPM and bootloader

; Data initialized at cold start

selByte	db	0		;current value written to select register
bfDirty	db	0		;non-zero if buffer has been written to
bfDrive	db	UNDEF		;drive currently in track buffer 

;----------------------------------------------------------------------------
; Non-initialized data
;----------------------------------------------------------------------------
bfTrack	ds	1		;currently buffered track (MUST FOLLOW bfDrive)

drvNum	ds	1		;drive number from CP/M call
trkNum	ds	1		;track number from CP/M call (MUST FOLLOW drvNum)
secNum	ds	1		;sector number from CP/M call
dmaAddr	ds	2		;dma address for disk operations from CP/M call

pTrkNum	ds	1		;physical track (cylinder) number
fWrtPrt	ds	1		;non-zero if drive is write protected

; Address mark buffer

amBuf	equ	$
amTrk	ds	1		;track
	ds	1
amSec	ds	1		;sector
amSecL	ds	1		;sector length code
amCrc	ds	2		;CRC 
	ds	4		;safety space

; Track buffer

trkBuf	ds	PNUMSEC*PSECLEN

;-----------------------------------------------------------------------------
;  Disk scratchpad areas defined in the DPH table
;-----------------------------------------------------------------------------
dirBuf	ds	128		;bdos directory scratchpad
alv0	ds	(DSM/8 + 1)	;allocation vector storage
csv0	ds	(DRM+1)/4	;change disk scratchpad
alv1	ds	(DSM/8 + 1)
csv1	ds	(DRM+1)/4

ACTLEN	equ	$-BIOSBAS	;actual length of BIOS 

	end

