;****************************************************************************
;
; CP/M 2.2 BIOS for a Polymorphic Poly-8813 computer using the single
;    density Polymorphic disk controller. Requires the Polymorphic
;    designed mods to the CPU board, video board, and one RAM board
;    that were required to support Polymorphic's own version of CP/M. 
;    These mods allow software to switch the hardware between CP/M 
;    mode and normal mode. In CP/M mode, 0000h-1FFFh is RAM. In normal
;    mode, 0000h-0BFFh is ROM, 0C00h-0FFFh is RAM, and 1800h-1BFFh is
;    video RAM.
;
;    This BIOS implements full track buffering on both reads and writes
;    to improve performance. Compared to non-buffered versions of CP/M,
;    loading programs and other sequential reads improve by more than
;    50%. Performance of virtually all programs with substantial disk
;    I/O improves by 25%-75%.
;
;    MODE byte bits 0 and 1 are used to run a command at CP/M cold start
;    and warm start respectively. Write verify can be enabled/disabled
;    with the fWRTVFY bit (bit 6). The MODE byte is at offset 33h from
;    the start of this BIOS (2133h in memory after MOVPCM). Search for 
;    "MODE" in this file for more information.
;
;    Full IOBYTE support is provided inlcuding redirection through the
;    BAT device. The default IOBYTE is at offset 34h from the start of
;    this BIOS (2134h in memory after MOVPCM). Search for "IOBYTE" in
;    this file for more information.
;
;    Extended function support is provided via the select disk (selDsk)
;    entry point. This BIOS supports "Flush Track Buffer" (function 0)
;    and "Write without Read" (function 1). See selDsk for details.
;
;    In order to fit in the three boot tracks the code and initialized
;    data portion of this BIOS can only be 780h bytes long. The source
;    file has a conditional assembly statement to detect when this size
;    limit is exceeded. Look for "BIOS IS TOO BIG ****" as an error when
;    assembling. 
;
;    Version History
;
;    Ver     Date	  Description
;    ---   ---------	---------------------
;    1.0    10/26/19 	Original, Mike Douglas with interrupt handling
;			concepts from POLEX by Martin Eberhard.
;
;--------------------------------------------------------------------------
;
;  In order the fit the CP/M image into the three boot tracks, the last
;  180h bytes of the BIOS must be split into two C0h byte sections and
;  moved into the last C0h bytes each of sectors 0 and 1 (256 byte phys-
;  ical sectors). This results in a SYSGEN image that is laid out in RAM
;  as follows (sector 0 starts at 900h in RAM):
;
;    1) 900-93F from BOOT.HEX (1F00-1F3F as assembled)
;    2) 940-9FF from CP/M image bytes 2700-27BF
;    3) A00-A3F from BOOT.HEX (2000-203F as assembled)
;    4) A40-AFF from CP/M image bytes 27C0-287F
;
;  To patch changes made to this BIOS into a CP/M image saved from MOVCPM
;  (e.g., CPMxx.COM), use the following commands:
;
;    A>DDT CPMxx.COM
;    -IBIOS.HEX
;    -Rxxxx     	(where xxxx = BIAS computed below)
;    -M2700,27BF,940	(takes care of item 2 above)
;    -M27C0,287F,A40	(takes care of item 4 above)
;    -G0	   	(Go zero, not "oh")
;    A>SAVE 38 CPMxx.COM
;
;****************************************************************************

;-----------------------------
; CP/M size & locations
;-----------------------------
MEMSIZE	equ	48		;set memory size in K bytes
BIOSLEN	equ	1300h		;length of this BIOS
CCPLEN	equ	0800h		;CP/M 2.2 fixed length
BDOSLEN	equ	0E00h		;CP/M 2.2 fixed length

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	;bias in DDT to patch in this BIOS

;-----------------------------
; CP/M page zero equates
;-----------------------------
WBOOTV	equ	00h		;warm boot vector location
BDOSV	equ	05h		;BDOS entry vector location
CDISK	equ	04h		;CP/M current disk
DEFDMA	equ	80h		;default dma address

IOBYTE	equ	03h		;CP/M IOBYTE address
SHFTCON	equ	1		;left rotate count to align CON in bits 2,1
SHFTRDR	equ	7		;left rotate count to align RDR in bits 2,1
SHFTPUN	equ	5		;left rotate count to align PUN in bits 2,1
SHFTLST	equ	3		;left rotate count to align LST in bits 2,1

;-----------------------------
; CP/M disk equates
;-----------------------------
NUMCSEC	EQU	20		;CP/M sectors per track
CSECLEN	equ	128		;CP/M sector length
WBSSEC	equ	4		;starting sector of CP/M image
WBSECS	equ	(CCPLEN+BDOSLEN)/CSECLEN   ;# warm boot sectors to read

BSH	equ	3		;allocation block shift factor
BLM	equ	07h		;allocation block mask
EXM	equ	0		;extent mask
DSM	equ	79		;max block number (80 blocks of 1K bytes)
DRM	equ	63		;max directory entry number (64 entries) 
AL0	equ	0C0h		;directory allocation block bits byte 0
AL1	equ	0		;directory allocation block bits byte 1
RESTRK	equ	3		;reserved tracks for boot image
CKS	equ	(DRM+1)/4	;directory check space

;-----------------------------
; Additional Disk parameters
;-----------------------------
NUMDISK	equ	3		;three drives supported
NUMTRK	equ	35		;number of tracks on the disk
NUMPSEC	equ	10		;physical secctors per track
PSECLEN	equ	256		;physical sector length

;-----------------------------
; Poly-88 single density FDC 
;-----------------------------
; Polymorphic Sector Format
;
; 10 bytes 00h
;  1 byte  sector number with msb set
;  1 byte  track number
;  256 bytes data
;  2 bytes 16-bit checksum
;-----------------------------
MTOSTRT	equ	80h-60		;1 sec motor start count up to 80h @ 60hz
MTOSYNC	equ	80h-20		;1/3 sec index sync count up to 80h @ 60hz
SYNC	equ	0E6h		;disk sync character

DCBASE	equ	20h		;disk controller base I/O address

; 6852 Control Registers

DSKSCTL	equ	DCBASE+00h	;6852 Status/Control Register 1
				;6852 status bits:
RDA	equ	01h		;RX data available
TDRA	equ	02h		;TX data register available
DCDN	equ	04h		;active-low DCD
CTSN	equ	08h		;active-low CTS
TUF	equ	10h		;transmit underflow
RXOVRN	equ	20h		;receiver overrun
PE	equ	40h		;parity error
IRQ	equ	80h		;interrupt request

; 6852 Control Register 1 Commands

CR2SEL	equ	0Bh		;select Control Reg 2, reset things
CR3SEL	equ	40h		;select Control Reg 3
SYNSEL	equ	80h		;select sync-code register
IDLE68	equ	0C0h		;no command, select data register
RSET68	equ	0CBh		;reset things, select data register

; 6852 Control Register 2 Commands

CR2RES	equ	18h		;8 bits, no parity (see data sheet)

; 6852 Control Register 3 Commands

CR3RES	equ	0Ch		;clear status, internal sync, 2-sync chr mode

DSKSDAT	equ	DCBASE+01h	;6852 Data/Control

; 8255 Registers

DSKSEL	equ	DCBASE+08h	;8255 Port A (output), drive select
				;<1:0> = Unit number
				;0 means no unit selected

DSKSEC	equ	DCBASE+09h	;8255 Port B (input), sector register
SECMASK	equ	0Fh		;bits 3:0 are the current sector
TRACK0	equ	10h		;bit 4 is track 0 detect
WPROT	equ	20h		;bit 5 is write protect

DSKCTL	equ	DCBASE+0Ah	;8255 Port C write label
DSKSTAT	equ	DCBASE+0Ah	;8255 Port C read label
				;<0> = Sector pulse interrupt
				;      (Mode 1 interrupt output)
				;<1> = Sector pulse
				;      (Mode 1 input buffer full)
				;<2> = Sector int enable (internal write)
				;      (Mode 1 data strobe input)
				;<3> = Motor On
				;<4> = Enable Write
				;<5> = Enable Read
				;<6> = Direction Select
				;<7> = Step

SIMASK	equ	01h		;sector interrupt mask
SPMASK	equ	02h		;sector pulse mask
MOTMASK	equ	08h		;motor on/off mask

; Disk control commands

DSKCMD	equ	DCBASE+0Bh	;8255 Control 

SINTOFF	equ	04h		;Port C:2 (sector interrupt) off
SINTON	equ	05h		;Port C:2 (sector interrupt) on
MOTOFF	equ	06h		;Port C:3 (spindle motor) off
MOTON	equ	07h		;Port C:3 (spindle motor) on
WRDIS	equ	08h		;Port C:4 (enable write) off
WREN	equ	09h		;Port C:4 (enable write) on
RDDIS	equ	0Ah		;Port C:5 (enable read) off
RDEN	equ	0Bh		;Port C:5 (enable read) on
STPOUT	equ	0Ch		;Port C:6 low  (step out)
STPIN	equ	0Dh		;Port C:6 high (step in)
UNSTEP	equ	0Eh		;Port C:7 low (cancel step pulse)
STEP	equ	0Fh		;Port C:7 high (step)
INIT82	equ	86h		;initialize 8255:
				;  Group A: Mode 0, output
				;  Group B: Mode 1, input
				;  Port C set up for output on all bits
				;    but some bits forced to input for
				;    mode 1 handshake signals

;-----------------------------
; 8080 interrupt vector offsets
;-----------------------------
ISR0	equ	00h		;reset (jump to restart)
ISR1	equ	08h		;not assigned (jump to restart)
ISR2	equ	10h		;disk sector (jump to restart)
ISR3	equ	18h		;not assigned (jump to restart)
ISR4	equ	20h		;serial port interrupt
ISR5	equ	28h		;keyboard interrupt
ISR6	equ	30h		;60hz clock
ISR7	equ	38h		;single step (jump to restart)

;-----------------------------
; Poly CPU board I/O ports
;   8251 UART, 60hz tic
;   CP/M on/off control
;-----------------------------
UDATA	equ	00h		;UART data register
UCMD	equ	01h		;UART command register
USTAT	equ	01h		;UART status register

UTXRDY	equ	01h	  	;transmitter ready
URXRDY	equ	02h	  	;receiver ready
UTXMTY	equ	04h	  	;transmitter empty
UMOD16	equ	4Eh	  	;8N1, x16 clock
UMOD01	equ	4Dh	 	;8N1, x1 clock
UCMDRE	equ	40h	  	;reset UART
UCRXEN	equ	26h	  	;enable RX, DTR, RTS ON
UCMDEN	equ	27h	 	;enable TX & RX, DTR, RTS ON

BRG	equ	04h		;baud rate generator port
SERBAUD	equ	1Fh		;serial is printer port at 9600 baud

RTC	equ	08h		;60hz RTC - OUT clears interrupt

CPMON	equ	0Ch		;IN for RAM at 0000h-1FFFh
CPMOFF	equ	08h		;IN enables CPU ROM/RAM, I/O ports, video board

;-----------------------------
; Poly Video Board I/O port
;-----------------------------
KBD	equ	18h		;keyboard data

;-----------------------------
; Poly ROM monitor equates
;-----------------------------
; ROM routines

DSPLY	equ	007Fh		;display character in A

; RAM locations

SRA4	equ	0C16h		;UART interrupt jump vector
SRA5	equ	0C18h		;keyboard interrupt jump vector

;-----------------------------
; Misc equates
;-----------------------------
RCVSIZE	equ	32		;receiver buffer size (must be power of 2)
XMTSIZE	equ	4		;transmit buffer size (must be power of 2)

RDTRIES	equ	6		;read tries
VFTRIES	equ	4		;verify tries per write
WRTRIES	equ	3		;track writes to try

UNDEF	equ	0FFh		;undefined value
CR	equ	13		;ASCII carriage return
LF	equ	10		;ASCII line feed
BS	equ	8		;ASCII backspace
DEL	equ	7Fh		;ASCII delete

	org	BIOSBAS
;-----------------------------------------------------------------------------
;  BIOS Entry Jump Table
;-----------------------------------------------------------------------------
	jmp	boot		;cold start
wbJump	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 (MODE byte) - BIOS flags. 
;     Must remain at offset 33h from start of BIOS
;---------------------------------------------------------------------------
mode	db	fWRTVFY		;default mode byte, write verify on

fCLDCMD	equ	01h		;true = CCP process cmd on cold start
fWRMCMD	equ	02h		;true = CCP process cmd on warm start
fWRTVFY	equ	40h		;true = verify writes

;---------------------------------------------------------------------------
;  IOBYTE - Must remain at offset 34h from start of BIOS
;
;    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)

;----------------------------------------------------------------------------
; boot - Cold boot BIOS entry. CP/M is already loaded in memory. Initialize
;    interupts, serial ports, display welcome banner and enter the CCP.
;----------------------------------------------------------------------------
boot	in	CPMON		;RAM at 0000h-1FFFh
	lxi	sp,0100h	;init stack pointer

	call	initInt		;initialize interrupts

	lda	dIoByte		;initialize IOBYTE
	sta	IOBYTE

	call	dispMsg		;display welcome message
	db	CR
	db	MEMSIZE/10 + '0',MEMSIZE mod 10 + '0'
	db	'K CP/M 2.2 Ver 1.0',CR
	db	'Poly-8813 SDC',CR,LF,0

	sta	CDISK		;current CP/M disk is drive zero (A=0 here)
	call	initCpm		;common cold/warm boot CP/M initialization

	lda	mode		;see if cold start command line requested
	ani	fCLDCMD
	jz	CCPBASE+3	;enter CCP and don't run command line

	jmp	CCPBASE		;enter CCP with possible cmd line

;--------------------------------------------------------------------------
; initInt - Initialize interrupts. The keyboard and serial interrupts
;   from ROM are directed to the rKbdInt and rSerInt entry points via
;   the SRA5 and SRA4 jump vectors in RAM supported by the Poly ROM.
;   Interrupt vectors while in CP/M mode (0000h-1FFFh is RAM) are created
;   with the keyboard, serial port, and 60hz interrupts pointing to the
;   kbdInt, serInt, and rtcInt routines below. All other interrupts while
;   in CP/M mode cause the computer to restart as if reset was pressed.
;
; Exits with RAM enabled at 0000h-1FFFh (CPMON) and interrupts enabled.
;--------------------------------------------------------------------------
initInt	di			;protect from interrupts
	mvi	a,SINTOFF	;disable sector interrupts from drive
	out	DSKCMD

	in	CPMOFF		;enable CPU ROM, I/O and video board

	lxi	h,rKbdInt	;keyboard ISR from CPU ROM
	shld	SRA5

	lxi	h,rSerInt	;serial ISR from CPU ROM
	shld	SRA4

	in 	kbd		;clear any keyboard interrupt

; Create interrupt vectors in CP/M RAM at 0000h-003Fh

	in	CPMON		;back to RAM at 0000h-1FFFh
	lxi	h,ISR7+8	;HL->byte after last interrupt vector

nextVec	lxi	d,vector+8	;DE->byte after prototype vector

moveVec	dcx	h		;decrement both pointers	
	dcx	d
	ldax	d		;move prototype vector byte
	mov	m,a		;   to actual location

	mov	a,l		;reach 8 byte boundary?
	ani	7
	jnz	moveVec		;not yet, keep moving bytes

	ora	l		;all done (at address zero)?
	jnz	nextVec		;no, do next vector

	sta	timer		;initialize 60hz timer to zero

; Update the serial, keyboard, and 60hz vectors to jump to the proper ISRs

	lxi	h,serInt	;install serial port ISR address
	shld	ISR4+5

	lxi	h,kbdInt	;install keyboard ISR address
	shld	ISR5+5

	lxi	h,rtcInt	;install 60hz ISR address
	shld	ISR6+5
	
; Initialize the 8251 UART

	mvi	a,SERBAUD	;set serial to printer port, 9600 baud
	out	BRG

; initialize the 8251 UART

	xra	a		;three zeros to satisfy possible
	out	UCMD		;   power up into sync mode
	out	UCMD
	out	UCMD

	mvi	a,UCMDRE	;reset the 8251 to MODE
	out	UCMD

	mvi	a,UMOD16	;8N1, 16x clock
	out	UCMD
	
	mvi	a,UCRXEN	;receive on, xmit off
	out	UCMD

	ei			;let interrupts run
	ret

;--------------------------------------------------------------------------
;  Prototype interrupt vector and restart for unsupported interrupt 
;--------------------------------------------------------------------------
; vector - default content for each 8 byte vector location

vector	push	psw		;save A and flags
	push	h		;save H
	mvi	a,CPMON		;enable CP/M RAM on ISR exit
	jmp	restart		;ISR address placed here
				;8th byte is don't care

; restart - enters reset routine on CPU ROM for unhandled interrupts

restart	di			;in case we didn't get here from an interrupt
	in	CPMOFF		;enable CPU ROM, I/O, and video board
	jmp	0		;jump to reset vector in CPU ROM.

;----------------------------------------------------------------------------
; initCpm - Initialization for CP/M that is common to both warm
;    boot and cold boot.
;----------------------------------------------------------------------------
initCpm	lxi	h,DEFDMA	;set the default dma address (80h)
	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,wbJump	;get the warm boot jump 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 CP/M in register C
	ret

;----------------------------------------------------------------------------
; wBoot - Warm boot BIOS entry. Reload CP/M from disk up to, but
;    not including the BIOS. Re-enter CP/M after loading.
;----------------------------------------------------------------------------
wbRetry	sta	bfDrive		;force re-read of disk (A=0FFh)

wBoot	lxi	sp,0100h	;init stack pointer
	
	mvi	a,1		;boot from drive one
	sta	drvNum
	
	lxi	b,WBSECS*256+WBSSEC    ;B=secs to read, C=starting sec
	lxi	h,CCPBASE	;HL->CP/M load address
	xra	a		;start on track zero

wbTrk	sta	trkNum		;save track number to use

; wbLoop - read sector loop for warm boot

wbLoop	shld	dmaAddr		;set read destination
	mov	a,c		;set sector number from C
	sta	secNum

	push	b		;save sector count and sector number
	call	read		;read next sector
	pop	b
	jnz	wbRetry		;failed read, start over
	
	dcr	b		;decrement total sector count
	jz	wbCpm		;all done reading, go enter CP/M
	
	inr	c		;bump to next sector number
	mvi	a,NUMCSEC	;A=number of CP/M sectors in a track
	sub	c		;all sectors in track done?
	jnz	wbLoop		;no, read next sector

	mov	c,a		;start with C=sector 0 on text track
	lda	trkNum		;increment track number
	inr	a		;move to next track
	
	jmp	wbTrk		;continue reading

; wbCpm - The CCP and BDOS have been reloaded, enter CP/M

wbCpm	call	initCpm		;common warm/cold boot CP/M initialization

	lda	mode		;see if warm start command line requested
	ani	fWRMCMD
	jz	CCPBASE+3	;enter CCP and don't run command line

	jmp	CCPBASE		;enter CCP with possible cmd line

;---------------------------------------------------------------------------
;  CON device BIOS entry points
;---------------------------------------------------------------------------

; conSt - Console status BIOS entry point. Return FFh if character ready,
;    return zero otherwise. 

conSt	mvi	b,SHFTCON	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	serInSt		;TTY use serial port
	dw	kbdSt		;CRT use keyboard input
	dw	batchSt		;BAT indirect through RDR logical device
	dw	serInSt		;UC1 use serial port

; 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.

conIn	call	invFlsh		;invalidate and flush track buffer

	mvi	b,SHFTCON	;B=console IOBYTE alignment shift count
	call	doIo		;call the proper I/O routine below
	dw	serIn		;TTY use serial port
	dw	kbdIn		;CRT use keyboard input
	dw	reader		;BAT indirect through RDR logical device
	dw	serIn		;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	serOut		;TTY use serial port
	dw	vidOut		;CRT use video output
	dw	list		;BAT indirect through LST logical device
	dw	serOut		;UC1 user serial port
	
;---------------------------------------------------------------------------
; RDR device 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	serIn		;TTY use serial port
	dw	serIn		;PTR use serial port
	dw	serIn		;UR1 use serial port
	dw	kbdIn		;UR2 use keyboard in

;---------------------------------------------------------------------------
; PUN device 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	serOut		;TTY use serial port
	dw	serOut		;PTP use serial port
	dw	serOut		;UP1 use serial port
	dw	vidOut		;UP2 use video output

;---------------------------------------------------------------------------
;  LST device BIOS entry points
;---------------------------------------------------------------------------

; 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	srOutSt		;TTY use serial port
	dw	vidSt		;CRT use video output
	dw	srOutSt		;LPT use serial port
	dw	srOutSt		;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	serOut		;TTY use serial port
	dw	vidOut		;CRT use video output
	dw	serOut		;LPT use serial port
	dw	serOut		;UL1 use serial port

;---------------------------------------------------------------------------
;  BAT device - 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	serInSt		;TTY use serial port	
	dw	serInSt		;RDR use serial port
	dw	serInSt		;UR1 use serial port
	dw	kbdSt		;UR2 use keyboard input

;---------------------------------------------------------------------------
; 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
	mov	d,b		;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

;--------------------------------------------------------------------------
; Keyboard device I/O
;--------------------------------------------------------------------------	
; kbdSt - Get keyboard status. Return FFh if a key is present, zero if not.

kbdSt	ei			;in case they were disabled
	lda	kbdBuf		;new key present?
	ora	a		;test A for zero
	rz			;exit if zero

	mvi	a,0FFh		;else return FF
	ret

; kbdIn - Wait for and return keypress in A.

kbdIn	ei			;in case they were disabled
	lxi	h,kbdBuf	;HL->keyboard buffer
	
kiLoop	mvi	a,7Fh		;strip parity and check for keypress
	ana	m
	jz	kiLoop
	
	mvi	m,0		;indicate key is taken
	ret			;exit with key in A

;--------------------------------------------------------------------------
; Video (CRT) device I/O
;--------------------------------------------------------------------------
; vidSt - Status for output to video display. Always return ready (FF).

vidSt	mvi	a,0FFh		;always return "ready" for video out
	ret

;  vidOut - Output character in C to video display. Convert BS to DEL. Do
;    not send two CR's in a row since CR's also line feed on this display.
;    The DSPLY routine in the Poly ROM on the CPU board is called to update
;    the display.

vidOut	mvi	a,BS		;translate BS to DEL
	cmp	c
	jnz	voNotBs		;char isn't BS
	
	mvi	c,DEL		;send DEL instead of BS
	
voNotBs	lda	lastChr		;A=last character sent
	cmp	c		;sending same character?
	mov	a,c		;update last character sent
	sta	lastChr
	jnz	voDsply		;not same, OK to display it

	cpi	CR		;two CR's in a row?
	rz			;yes, skip the CR

voDsply	push	h		;preserve caller's HL

	lxi	h,0		;switch to a local stack and save the	
	dad	sp		;   incoming SP before we go CPMOFF
	lxi	sp,LCLSTK	;   to call DSPLY
	push	h
	
	in	CPMOFF		;enable CPU ROM, I/O and video board
	mov	a,c		;A=character to display
	call	DSPLY		;call video display routine in ROM
	in	CPMON		;back to RAM at 0000-1FFFh
	
	pop	h		;restore original SP
	sphl
	
	pop	h		;restore caller's HL
	ret
	
;--------------------------------------------------------------------------
; Serial port device I/O
;--------------------------------------------------------------------------
; serInSt - Get serial port input status. Return FFh if a byte has been
;     received, zero if not.

serInSt	ei			;in case they were disabled
	lda	rcvCnt		;any buffered characters?
	ora	a		;test A for zero
	rz			;exit if zero

	mvi	a,0FFh		;else return FF
	ret	
	
; serIn - Wait for byte to come in serial port and return it in A

serIn	call	serInSt		;wait for character
	jz	serIn
	
	lhld	pRcvRem		;HL=receive remove pointer
	mov	a,l		;increment and wrap pointer
	inr	a		;(H never changes)
	ani	RCVSIZE xor 0FFh	
	sta	pRcvRem

	mov	a,m		;A=received character
	
	lxi	h,rcvCnt	;decrement count of buffered chars
	dcr	m		;atomic read/write
	ret

; srOutSt - Get serial port output status. Return FFh if ready to send,
;     zero if not.

srOutSt	ei			;in case they were disabled
	lda	xmtCnt		;how many bytes already buffered?
	sui	XMTSIZE
	rz			;exit if zero

	mvi	a,0FFh		;else return FF
	ret		

; serOut - Output the character passed in C

serOut	call	srOutSt		;wait for room in xmit queue
	jz	serOut
	
	lhld	pXmtIns		;HL=transmit insert pointer
	mov	m,c		;put character in xmit queue	

	mov	a,l		;increment and wrap pointer
	inr	a		;(H never changes)
	ani	XMTSIZE xor 0FFh	
	sta	pXmtIns

; Increment the count of bytes in the transmit queue. The RTC interrupt
; enables the UART transmitter if needed. This will usually give time
; for the calling program to queue at least two characters for a multi-
; byte transmission. Having multiple queued characters, in turn, keeps
; the transmit ISR from shutting down the UART between each byte.

	lxi	h,xmtCnt	;increment count of bytes in queue
	inr	m		;atomic read/write
	ret

;--------------------------------------------------------------------------
; rKbdInt, kbdInt
;     Keyboard interrupt - Put the typed character in kbdBuf
;--------------------------------------------------------------------------
; rkbdInt- Keyboard interrupt from the CPU ROM (CPMOFF mode). Modify the
;    stack to look the same as when interrupts come in from CP/M mode
;    (just PSW and HL on stack). 

rKbdInt	pop	h		;restore HL from stack
	pop	psw		;discard DE from stack
	pop	psw		;discard BC from stack
	push	h		;put HL on stack, now matches CP/M ISR stack
	
	mvi	a,CPMOFF	;exit ISR with CPU ROM, I/O still enabled
				;fall into the CP/M ISR

; kbdInt - Keyboard interrupt from RAM vector (CPMON mode)

kbdInt	sta	intExit+1	;determines CPMON or CPMOFF at ISR exit
	in	CPMOFF		;enable CPU ROM, I/O, and video board

	in	KBD		;load keypress, clears interrupt
	sta	kbdBuf		;store new key

	jmp	intExit		;exit ISR

;--------------------------------------------------------------------------
; rSerInt, serInt
;     Serial port receive or transmit interrupt. 
;--------------------------------------------------------------------------
; rSerInt - Serial interrupt from the CPU ROM (CPMOFF mode). Modify the
;    stack to look the same as when interrupts come in from CP/M mode
;    (just PSW and HL on stack). 

rSerInt	pop	h		;restore HL from stack
	pop	psw		;discard DE from stack
	pop	psw		;discard BC from stack
	push	h		;put HL on stack, now matches CP/M ISR stack
	
	mvi	a,CPMOFF	;exit ISR with CPU ROM, I/O still enabled
				;fall into the CP/M ISR

; serInt - serial interrupt from RAM vector (CPMON mode)

serInt	sta	intExit+1	;determines CPMON or CPMOFF at ISR exit
	in	CPMOFF		;enable CPU ROM, I/O, and video board

	lxi	h,rcvCnt	;HL->count of chars in rcv buffer
	
	in	USTAT		;get UART status
	ani	URXRDY		;receive interrupt?
	jnz	serRcv		;yes
	
; Not receive interrupt, must be transmit interrupt

	dcx	h		;HL->count of chars in xmit buffer
	mov	a,m		;anything in the queue?
	ora	a
	jz	waitXmt		;no, wait for current xmit to finish
	
	dcr	m		;one fewer character
	lhld	pXmtRem		;HL=tranmsit remove pointer
	mov	a,m		;A=next character to send
	out	UDATA		;send it	
	
	mov	a,l		;increment and wrap pointer
	inr	a		;(H never changes)
	ani	XMTSIZE xor 0FFh
	sta	pXmtRem

	jmp	intExit		;exit ISR

; waitXmt - Wait for current character in the UART to finish shifting out,
;   then shut down transmitter.

waitXmt	in	USTAT		;wait for xmit to finish
	ani	UTXMTY		;transmit shift register empty?
	jz	waitXmt		;not yet, keep waiting
		
	mvi	a,UCRXEN	;receive still on and xmit off
	out	UCMD
	
	mvi	a,0FFh		;transmitter status = disabled
	sta	xmtStat

	jmp	intExit
	
; serRcv - The interrupt was a received character interrupt

serRcv	mov	a,m		;A=number of chars in buffer
	cpi	RCVSIZE		;room for another character?
	in	UDATA		;get the character and clear interrupt
	jz	intExit		;no room, exit
	
	inr	m		;increment character count
	lhld	pRcvIns		;HL=receive insert pointer
	mov	m,a		;put character in the queue
	
	mov	a,l		;increment and wrap pointer
	inr	a		;(H never changes)
	ani	RCVSIZE xor 0FFh	
	sta	pRcvIns

	jmp	intExit		;exit ISR
	
;--------------------------------------------------------------------------
; rtcInt - 60hz real time clock interrupt. Increments the one byte 60hz
;    timer. If the timer overflows the drive is deselected and the motor
;    is turned off. Also enables the UART transmitter if characters are
;    present in the transmit buffer and the transmitter is not enabled.
;
;    RTC interrupts while in CPMOFF (CPU RAM/ROM enabled) will be
;    missed. This will occur while sending characters to the video
;    display, especially when the display has to scroll.
;--------------------------------------------------------------------------
rtcInt	sta	intExit+1	;determines CPMON or CPMOFF at ISR exit
	in	CPMOFF		;enable CPU ROM, I/O, and video board

	out	RTC		;clear the RTC interrupt

; Enable the UART transmitter if the transmit queue is not empty and
;     the transmitter is not already enabled

	lxi	h,xmtCnt	;HL->count of xmit bytes in queue
	mov	a,m		;A=number of bytes in queue
	dcx	h		;HL->transmitter status (FF=disabled,
	ana	m		;   0=enabled
	jz	rtcInc		;nothing queued or xmit already enabled
	
	inr	m		;xmit status to 0 = xmit enabled	
	mvi	a,UCMDEN	;enable UART transmitter
	out	UCMD

rtcInc	lxi	h,timer		;increment the 60hz timer
	inr	m
	jnz	intExit

	xra	a		;timer overflow: deselect the drive
	out	DSKSEL

	mvi	a,MOTOFF	;and turn off motor
	out	DSKCMD

; intExit - Restore register and exit the ISR. Turn CP/M mode on or
;    off before exiting. The IN instruction below is patched at the
;    start of each ISR with CPMON or CPMOFF.

intExit	in	UNDEF		;set to CPMON or CPMOFF based on ISR entry
	pop	h		;restore registers
	pop	psw
	ei
	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. See
;    extFunc below.
;
; On Entry
;    C = disk number to select (or FFh, see extFunc below)
;        CP/M disk numbers are zero-indexed
;
; On Exit
;    drvNum = disk number to select (one-indexed)
;    Success: HL->DPH for this drive. Error: HL=0
;    Clobbers A,D,E,H,L
;----------------------------------------------------------------------------
selDsk	lxi	h,0		;init HL=0 for invalid drive request

	inr	c		;test for drive number of 0FFh
	jz	extFunc		;drive is FF, do extended function

	mov	a,c		;A=CP/M drive number + 1
	cpi	NUMDISK+1	;verify drive number less than number of disks
	rnc			;invalid drive number, return with HL=0=error

	sta	drvNum		;save new drive number (one-indexed)

	dcr	a		;A=zero-indexed drive number
	rlc			;compute disk parameter header address
	rlc			;   as (16*CP/M drive) + dpHead
	rlc
	rlc			;A=16*CP/M drive

	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
	
;----------------------------------------------------------------------------
; extFunc
;    selDsk is also used as the "Extended Function" entry point. If a 
;    drive number of FFh is passed in C, then this call is an extended
;    function request and not a disk select request. In this case:
;
; On Entry
;    C = FFh (extended function request)
;    B = extended function number (see list below)
;
; On Exit
;    H = 0, L = number of 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
;	BIOS supports extended functions or not.
;    Z flag set true if the extended function executed without error, Z flag
;	false otherwise.
;    Clobbers All
;
;    Extended Functions supported by this BIOS:
;	00h - Invalidate and flush track buffer.
;	01h - Write sector without track pre-read. Generally used to
;	      write a full track for formatting and for fast disk to
;	      disk copy operations.
;----------------------------------------------------------------------------
; Check for track buffer flush function 0

extfunc	mov	a,b		;A=extended function code
	ora	a		;code 0 = flush track buffer?
	jnz	efWrite		;no, go see if write w/o read request
		
	call	invFlsh		;invalidate and flush track buffer
	lxi	h,0002h		;H=0, L=number of functions
	ret

; efWrite - Check for write w/o read function 1

efWrite	dcr	a		;function 1 - write w/o read?
	jnz	efExit		;no, exit
	
	dcr	a		;set write w/o read flag non-zero
	sta	wrtNoRd
	call	wrtNr		;do write without track read
	
efExit	lxi	h,0002h		;H=0, L=number of functions
	ret
	
;----------------------------------------------------------------------------
; home - Home BIOS entry. Set trkNum to zero. No drive activity takes place.
;
; On Entry
;
; On Exit
;    trkNum set to zero
;    Clobbers A
;----------------------------------------------------------------------------
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.
;
; On Entry
;    C = track number to set
;
; On Exit
;    trkNum = track specified in C
;    Clobbers A
;----------------------------------------------------------------------------
setTrk	mov	a,c		;save track number passed in C
	sta	trkNum
	ret

;----------------------------------------------------------------------------
; setSec - Set Sector BIOS entry. C contains the 1-indexed CP/M physical 
;   sector. Saved in secNum as a zero-indexed value.  
;
; On Entry
;    C = 1-indexed sector number to set
;
; On Exit
;    secNum = zero-indexed sector number
;    Clobbers A
;----------------------------------------------------------------------------
setSec	mov	a,c		;A=1-indexed sector number
	dcr	a		;convert to zero indexed
	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.
;
; On Entry
;    BC = I/O buffer address
;
; On Exit
;    dmaAddr = address specified in BC
;    Clobbers H,L
;----------------------------------------------------------------------------
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 CP/M one-indexed physical sector number in HL.
;    No interleave is used.
;
; On Entry
;    BC = logical sector number
;    DE->sector translate table for selected drive
;
; On Exit
;    HL = physical sector number
;    Clobbers H,L
;----------------------------------------------------------------------------
sectran	mov	h,b		;copy BC to HL
	mov	l,c
	inr	l		;convert to 1-indexed
	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 = sector number to read (0-31)
;    dmaAddr = address of buffer to read into
;
; On Exit
;    If read successful
;	sector read into (dmaAddr)
;	HL = (dmaAddr) + CSECLEN
;	A=0, Z flag set true
;    Else
;	A=1, Z flag set false
;    Interrupts enabled
;    Clobbers all
;----------------------------------------------------------------------------
read	xra	a		;ensure write w/o read flag is false
	sta	wrtNoRd

	call	ckFlush		;flush track buffer if needed
	call	readTrk		;fill track buffer if needed
	jnz	exitDio		;track read error, exit
	
	call	sec2Adr		;DE->sector in trkBuf
	jnz	exitDio		;bad sector, return error

	lhld	dmaAddr		;HL->destination for data

; movSec - move CP/M sector from (DE) to (HL)
; moveD2H - move B bytes from (DE) to (HL)
	
movSec	mvi	b,CSECLEN	;B=number of bytes to move

moveD2H	ldax	d		;move from (DE) to (HL)
	mov	m,a
	inx	h		;increment pointers
	inx	d
	dcr	b		;decrement byte count
	jnz	moveD2H	

exitDio	mvi	a,0		;if zero is true, return zero
	rz

	inr	a		;else return A<>0, Z false
	ret
	
;----------------------------------------------------------------------------
; 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 = sector number to write (0-31)
;    dmaAddr = address of buffer to write from
;
; On Exit
;    If successful
;	sector written to trkBuf from (dmaAddr)
;	A=0, Z flag set true
;    Else
;	A=1, Z flag set false
;    Interrupts enabled
;    Clobbers all
;----------------------------------------------------------------------------
write	xra	a		;ensure write w/o read flag is false
	sta	wrtNoRd

wrtNr	call	ckFlush		;flush track buffer if needed
	call	readTrk		;fill track buffer if needed		
	jnz	exitDio		;track read error, exit

	lda	wrtProt		;is drive write protected?
	ora	a
	jnz	exitDio		;yes, exit
		
	mvi	a,0FFh		;set dirty flag true
	sta	bfDirty

	call	sec2Adr		;DE->sector, HL->sector flag
	mvi	m,0		;flag sector as good	

	lhld	dmaAddr		;HL->user buffer with write data
	xchg			;DE->write data, HL->sector in trkBuf
	
	jmp	movSec		;move sector to trkBuf and exit

;----------------------------------------------------------------------------
; 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). The status byte
;    at the start of each sector is set to zero if the sector is
;    good, non-zero if the sector couldn't be read.
;
; On Entry
;    drvNum = drive to read
;    trkNum = track to read
;    bfDrive = drive from which trkBuf was filled
;    bfTrack = track from which trkBuf was filled
;
; On Exit
;    trkNum on drvNum read into trkBuf
;    Zero true if track read or already there, zero false otherwise
;    Clobbers all
;----------------------------------------------------------------------------
readTrk	lhld	bfDrive		;L=buffered drive, H=buffered track

	lda	drvNum		;A=requested drive
	cmp	l		;same drive buffered?
	jnz	rtNew		;drive doesn't match, need a new buffer

	lda	trkNum		;A=requested track
	cmp	h		;same track buffered?
	rz			;yes, already have this buffer

; rtNew - New track needs to be read

rtNew	mvi	a,UNDEF		;invalidate buffered data
	sta	bfTrack

	call	dSelDrv		;select drive
	rnz			;drive select failed, exit with error

	mvi	a,2		;init restore/seek try counter
	sta	skRetry
	
rtRtry	call	dSeek		;seek to trkNum
	rnz			;a restore was required and failed
	
	lda	wrtNoRd		;writing without pre-read of track?
	ora	a
	jnz	rtForce		;force good status and exit without read

	call	initTrk		;init all sectors to "bad" (not read)
	lxi	d,(RDTRIES*NUMPSEC) SHL 8 + NUMPSEC
				;D=max sector reads = RDTRIES revolutions
				;E=sectors remaining to fill

; Read sector loop - All sectors are originally marked "bad" (unread) and
;    a sector counter in E is started at NUMPSEC (a full track of sectors).
;    Sectors are read until sector counter E reaches zero or the maximum
;    reads counter in D reaches zero (RDTRIES revolutions). The 1st time
;    the max reads counter reaches zero, a restore and re-seek is per-
;    formed in case we're on the wrong track. The second time it reaches
;    zero, the bad sectors remain marked as bad in the track buffer.

	call	dSkpSec		;make sure we're between two sector pulses
				;returns HL->next sector flag byte

rdLoop	mov	a,m		;A=flag byte of upcoming sector
	ora	a		;this sector already read?
	jnz	rdNew		;no, go read the new sector
	
	call	dSkpSec		;else, skip the sector
	jmp	rdNext

rdNew	call	dRead		;read the sector, returns HL->next sector
	jnz	rdNext		;read error

	dcr	e		;decrement sectors left to fill
	jz	rtExit		;all sectors read without error

rdNext	dcr	d		;decrement max sector reads counter
	jnz	rdLoop		;go read next sector

	lxi	h,skRetry	;HL->seek retry counter
	dcr	m		;decrement the counter
	jz	rtExit		;re-seek(s) done, we're finished

	ei			;allow some interrupts
	call	dRestor		;restore to track zero
	jz	rtRtry		;start over after the re-seek
	
	ret			;else, exit with error

rtForce	xra	a		;force good status

rtExit	ei			;allow interrupts
	lhld	drvNum		;L=drvNum, H=trkNum
	shld	bfDrive		;set the buffered drive and track values
	ret			;exit with Z flag true

;----------------------------------------------------------------------------
; initTrk - Set the flag byte in each sector in trkBuf to "bad" to
;    indicate none of the sectors have been read.
;
; On Entry
;
; On Exit
;    All sectors in trkBuf marked "bad" (unread)
;    Clobbers all
;----------------------------------------------------------------------------
initTrk	lxi	b,(UNDEF SHL 8)+NUMPSEC;  B=UNDEF (bad), C=NUMPSEC
	lxi	h,trkBuf	;HL->track buffer
	lxi	d,PSECLEN+1	;DE=length of each sector in track buffer

itLoop	mov	m,b		;mark sector as bad (not read)
	dad	d		;HL->next sector in trkBuf
	dcr	c		;repeat for all sectors
	jnz	itLoop
	
	ret

;----------------------------------------------------------------------------
; invFlsh - 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:
;
; On Exit:
;    Track buffer written if required and invalidated.
;    Z set for no error, Z cleared if write error occured.
; Clobbers all except HL
;----------------------------------------------------------------------------
invFlsh	mvi	a,UNDEF		;invalidate the track buffer
	sta	bfTrack

	push	h		;preserve HL in case conIn caller expects it
	call	ckFlush		;flush the track if needed
	pop	h

	ret

;----------------------------------------------------------------------------
; 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
;    Zero true for no error, zero false 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	xra	a		;clear buffer dirty flag
	sta	bfDirty

	mov	a,l		;Set A=drive to flush
	call	dSelDrA		;select drive in A
	jnz	wtFail		;drive select failed, exit with error

	mvi	a,WRTRIES	;initialize write retry count
	sta	wrRetry

; rtryWrt - Write track retry entry point. Write a full track starting
;    with any sector. Don't write sectors that are flagged as bad. When
;    re-entered as part of verify failures, sectors that have already
;    been verified are skipped as well.

rtryWrt	mvi	d,NUMPSEC	;D=count of sectors to write
	xra	a		;zero the count of sectors actually written
	sta	secCnt
	
	call	dSkpSec		;make sure we're in between two sector pulses
	call	rst6852		;reset the 6852
				;at this point, HL->next sector flag byte
wrtLoop	mov	a,m		;check sector flag
	ora	a
	jz	wrtNew		;not written yet and not bad - write it
	
	call	dSkpSec		;otherwise, skip this sector
	jmp	wrtNext

wrtNew	call	dWrite		;write the sector

	lda	secCnt		;increment number of sectors written	
	inr	a		;  preserving HL
	sta	secCnt

wrtNext	dcr	d		;decrement write sector count
	jnz	wrtLoop		;loop until all sectors processed

; If the verify flag is set in the mode byte, verify the track just written.
;    At this point, secCnt has the number of sectors actually written.

	ei			;allow some interrupts
	lda	mode		;verifying writes?
	ani	fWRTVFY
	jnz	doVfy		;yes, go do verify

	call	dSkpSec		;ensure write finishes before exit
	ei			;re-enable interrupt
	ret

doVfy	mvi	d,NUMPSEC*VFTRIES   ;D=maximum number of sector reads
	
vfyLoop	mov	a,m		;test sector flag, only good and
	ora	a		;   not previously verified sectors
	jnz	vfySkip		;   are verified
	
vfyNew	call	dVerify		;verify the sector
	jz	vfyNext		;didn't match

	lda	secCnt		;decrement number of sectors to verify	
	dcr	a		;  preserving HL
	sta	secCnt
	jnz	vfyNext		;still some sectors left

	ei			;allow interrupts
	ret			;and exit

vfySkip	call	dSkpSec		;skip the upcoming sector

vfyNext	dcr	d		;decrement sector count
	jnz	vfyLoop		;loop until all verify tries expired

; Read re-tries expired, decrement the write retry count and write again

	ei			;allow some interrupts
	lxi	h,wrRetry	;decrement the write retry counter
	dcr	m
	jnz	rtryWrt		;retry starting with the write

wtFail	call	dispMsg
	db	CR,'Delayed Write Err', CR,LF, 0

	inr	a		;dispMsg returns zero
	ret			;exit with non-zero status
	
;----------------------------------------------------------------------------
; sec2Adr - Convert CP/M sector number in secNum to the sector address
;    in the track buffer. Returns pointer to CP/M sector in DE, returns
;    pointer to the flag byte of the 256 byte physical sector in HL,
;    returns zero true if flag byte is zero.
;
; On Entry
;    secNum = CP/M sector number (0-19)
;
; On Exit
;    DE->CP/M sector in trkBuf
;    HL->flag byte of physical sector in trkBuf
;    Zero true if flag byte is zero (good sector)
;    Clobbers all
;----------------------------------------------------------------------------
sec2Adr	lxi	d,1		;DE=offset to 1st half of physical sector
	xra	a		;clear carry
	lda	secNum		;A=CP/M sector number
	rar			;A=physical sector number
	jnc	secAdr2		;even sector, DE is already correct

	mvi	e,81h		;DE=offset to 2nd half of physical sector

secAdr2	lxi	h,trkBuf	;HL->track buffer
	mov	b,a		;form BC=physical sector*257
	mov	c,a
	dad	b		;HL->flag byte of physical sector

	xchg			;DE->flag byte, HL=logical offset
	dad	d		;HL->logical sector
	xchg			;DE->logical, HL->flag byte

	mov	a,m		;test flag byte for zero
	ora	a		;  and return result
	ret

;------------------------------------------------------------------------------
; dispMsg - Display the null-terminated string following the dispMsg call
;
; On Entry SP->message
;
; On Exit
;    Clobbers C,H,L
;------------------------------------------------------------------------------
dispMsg	pop	h		;HL->string

dmLoop	mov	a,m		;A=next character
	inx	h		;bump string pointer
	ora	a		;null terminator?
	jz	dmExit		;yes, exit

	mov	c,a		;conOut needs character in C
	call	conOut		;send the character
	jmp	dmLoop

dmExit	pchl			;return past message string

;****************************************************************************
;
; Polymorphic single density disk controller I/O routines
;
;****************************************************************************

;---------------------------------------------------------------------------
; dSelDrv - Select the drive specified in drvNum and start the motor if
;    needed. On a drive change, save the track (curTrk) the current drive
;    is on into the track table, load curTrk for the new drive from the
;    track table.
;
; dSelDrA - Same as dSelDrv but the drive to select is in A
;
; On Entry
;    drvNum = drive to be selected
;    curDrv = drive currently selected
;    curTrk = track the current drive is on
;
; On Exit
;    Drive selected and motor up to speed
;    curDrv = drvNum
;    curTrk = track the newly selected drive is on
;    Zero true for success, zero false for error
;    Clobbers all
;---------------------------------------------------------------------------
dSelDrv	lda	drvNum		;A=desired drive

dSelDrA	out	DSKSEL		;select the new drive on the controller
	mov	c,a		;C=newly selected drive number
	
	lxi	h,curDrv	;HL->previously selected drive
	mov	e,m		;E=previously selected drive

	cmp	e		;same or different drive selected?
	jz	dsMotor		;same drive - go check the motor

; A different drive is selected. Save current track (curTrk) for the
;    current drive to the track table, load curTrk for the new drive 
;    from the track table.

	mov	m,c		;save new drive in curDrv
	
	mvi	d,0		;DE=currently selected drive
	lxi	h,trkTbl	;HL->track table
	dad	d		;HL->trkTbl entry for current drive

	lda	curTrk		;A=track current drive is on
	mov	m,a		;save it in the track table

	lxi	h,trkTbl	;HL->track table
	mov	b,d		;BC=offset in trkTbl for new drive
	dad	b		;HL->trkTbl for new drive

	mov	a,m		;A=track new drive is on
	sta	curTrk		;update current track
	
; dsMotor- If motor is not on, wait one second for motor startup. If
;    if motor is already on and a different drive is selected, wait
;    0.33s (1.67 revolutions) to ensure index sync has occurred. During
;    either wait, verify a disk is inserted by making sure sector
;    pulses have occurred during the wait.

dsMotor	xra	a		;make sure motor off timeout won't occur
	sta	timer		
	
	in	DSKSTAT		;see if actual motor on or not
	ani	MOTMASK		;result zero if motor is off
	
	mvi	a,MOTON		;turn (or ensure) motor on
	out	DSKCMD
	
	mvi	a,MTOSTRT	;A=motor timeout for start up
	jz	dsMto		;motor was off, do startup timeout
	
	mov	a,c		;A=new drive to select
	cmp	e		;compare to previously selected drive
	rz			;same drive, so drive is ready
	
	mvi	a,MTOSYNC	;A=motor timeout for index sync
	
dsMto	ei			;make sure 60hz interrupts will work
	sta	timer		;starting count-up value in 60hz RTC
	mvi	b,0		;B=sector pulse counter
	
dsLoop	in	DSKSEC		;clear sector interrupt

dsSecWt	lda	timer		;timeout expire? (timer reaches >= 80h)
	ora	a		
	jm	dsDone		;yes, timeout done
	
	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dsSecWt		;no, keep waiting

	inr	b		;count the sector pulse
	jmp	dsLoop
	
dsDone	in	DSKSEC		;update write protect status
	ani	WPROT
	sta	wrtProt

	mvi	a,8		;more than 8 sectors seen?
	cmp	b		;compare 8 - sector count
	mvi	a,0		;A=0 for success
	jc	dsExit		;drive selected and ready
	
	dcr	a		;A=0FFh for error (no disk)
	
dsExit	ora	a		;set or clear zero status
	ret

;---------------------------------------------------------------------------
; dSeek - Seek to track in trkNum
;
; On Entry
;    Drive selected and ready
;    trkNum = desired track
;    curTrk = current track, UNDEF means we don't know
;
; On Exit
;    curTrk = trkNum
;    Zero true for success, zero false for failure
;	(failure is a failed restore to track zero)
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dSeek	lda	curTrk		;A=current track
	cpi	UNDEF		;valid track number?
	jnz	dTrkVld		;yes, track is valid

	call	dRestor		;otherwise, restore to track zero
	rnz			;exit if restore failed

; dTrkVld - Current track is valid, compute direction and number
;     of steps

dTrkVld	lxi	h,trkNum	;HL->desired track
	sub	m		;A=current-requested
	rz			;return if already on correct track

	mvi	b,STPOUT	;B=step out (assume requested<current)
	jnc	dDoSeek		;it is
	
	inr	b		;B=step in command (requested>current)
	cma			;compute A=-A
	inr	a

; Save target track in curTrk, step there, then verify track

dDoSeek	mov	c,a		;C=number of steps

	mov	a,m		;save new track in curTrk
	sta	curTrk
	
	call	dStep		;do the seek
	xra	a		;return zero
	ret
	
;---------------------------------------------------------------------------
; dRestor - Restore to track 0
;
; On Entry
;    Drive selected and ready
;
; On Exit
;    If successful
;       curTrk = 0
;	A=0
;	Zero true
;    else
;       curTrk = UNDEF
;       Zero false
;    Clobbers A,B,C,D
;---------------------------------------------------------------------------
dRestor	mvi	c,3		;C=step in 3 tracks
	mvi	b,STPIN		;B=step in command
	call	dStep		;do 3 steps in

	mvi	c,NUMTRK+10	;C=maximum number of step outs to try
	mvi	b,STPOUT	;B=step out command
	call	dStep		;step until track 0 detected
	rz			;exit if restore worked

	mvi	a,UNDEF		;restore failed, track is undefined
	sta	curTrk
	ret			;return with non-zero

;---------------------------------------------------------------------------
; dStep - Step number of steps in C, direction in B. Since more than
;   one sector time (20ms) will pass after this seek and before
;   reading or writing data, the 10ms head settle wait after a seek
;   is not needed here.
;
;   If track zero is detected during a step-out operation, curTrk
;   is forced to zero and zero is returned in A.
;
; On Entry
;    C = number of steps > 0
;    B = step direction (STPOUT or STPIN)
;
; On Exit
;    If stepping out and track zero hit
;	curTrk = 0
;	A = 0
;	Zero true
;    else
;	Zero false
;    Clobbers A,C,D
;---------------------------------------------------------------------------
dStep	mov	a,b		;set step direction
	out	DSKCMD

dStep1	mvi	a,STEP		;set step bit
	out	DSKCMD
	dcr	a		;clear step bit
	out	DSKCMD

	mvi	a,40		;delay step time of 40ms 

delayMs	mvi	d,(1843/15)+1	;15 cycles in the loop below

delayLp	dcr	d		;decrement inner loop counter
	jnz	delayLp

	dcr	a		;decrement ms counter
	jnz	delayMs

; See if track zero hit while stepping out

	in	DSKSEC
	ani	TRACK0		;track 0 detected?
	jz	dStep2		;no, keep stepping
	
	mov	a,b		;stepping out?
	sui	STPOUT
	jz	dStep0		;yes, we've hit track 0
	
dStep2	dcr	c		;decrement step counter
	jnz	dStep1		;loop until count reaches zero
	
	inr	c		;force non-zero status for exit
	ret

; Track zero while stepping out. Set curTrk to zero and exit with
;    zero status true.

dStep0	sta	curTrk		;curTrk=0
	ret			;return with zero true and A=0
	
;----------------------------------------------------------------------------
; rst6852 - Reset the 6852 serial adapter on the floppy controller
;
; On Entry
;
; On Exit
;    Clobbers A
;----------------------------------------------------------------------------
rst6852	mvi	a,RSET68	;reset sync, Tx, Rx
	out	DSKSCTL		;6852 Control Register 1
	mvi	a,IDLE68	;..and release them
	out	DSKSCTL		;6852 Control Register 1 
	
	ret

;---------------------------------------------------------------------------
; dRead - Read a sector, verify checksum, sector number, track number
;
; On Entry
;    Drive is selected and ready, known to be in between sector pulses
;    HL->flag byte in sector buffer
;
; On Exit
;    Sector read to (HL)
;    HL->flag byte of next sector
;    Flag byte of sector cleared/set for sector good/fail
;    Zero true if no error, false otherwise
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dRead	call	rst6852		;reset the 6852
	mvi	a,RDEN		;enable read on the controller
	out	DSKCMD

	push	d		;preserve DE
	push	h		;and HL
	xchg			;DE->flag byte of sector buffer
	inx	d		;DE->1st data byte in sector buffer
	lxi	h,0		;HL=initial checksum
	
; Wait for next sector pulse
	
	in	DSKSEC		;clear sector interrupt

drSecWt	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	drSecWt		;no, keep waiting

; Wait up to 1.75ms for start of data

	mvi	c,83		;C=timeout counter (83*39 cycles)

drWait1	dcr	c		;(5) timeout?
	jz	drError		;(10) yes, preamble error abort

	in	DSKSCTL		;(10) A=6852 status
	rrc			;(4) data byte present?
	jnc	drWait1		;(10) Wait for data

; Read and verify sector and track from disk

	in	DSKSDAT		;read disk data 1st byte (sector)
	mov	b,a		;B=sector
	in	DSKSDAT		;read disk data 2nd byte (track)
	mov	c,a		;C=track

	in	DSKSEC		;A=sector from controller
	ani	SECMASK		;get the sector bits alone
	ori	80h		;set the flag bit
	cmp	b		;sector match?
	jnz	drError		;no, exit
	
	lda	curTrk		;A=expected track
	cmp	c		;match the track we found?
	jnz	drError		;no, exit
	
	mvi	a,PSECLEN/2	;128 pairs of bytes/sector

; drLoop - Read sector data into memory at (DE)

drLoop	push	psw		;save loop counter on stack

drWait2	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	drWait2		;no, keep waiting
	
	in	DSKSDAT		;read disk data even byte
	mov	c,a		;C=even byte
	stax	d		;write to buffer
	inx	d
	
	in	DSKSDAT		;read disk data odd byte
	mov	b,a		;B=odd byte
	stax	d		;write to buffer
	inx	d

	dad	b		;update 16-bit checksum

	pop	psw		;A=byte pair count
	dcr	a		;decrement loop count
	jnz	drLoop		;loop through sector data

; Read and verify 1's complement 16-bit checksum from disk

drWait3	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	drWait3		;no, keep waiting

	in	DSKSDAT		;read low byte of checksum
	mov	c,a		;C=checksum low byte
	in	DSKSDAT		;read high byte of checksum
	mov	b,a		;BC=1's complement checksum from disk
	
	dad	b		;add to positive computed checksum
	inx	h		;plus 1 should give zero
	mov	a,h		;start test for HL=0
	
; drExit - Set read status in flag byte of the current sector. Disable
;    read signal, put address of next sector in HL, return read status.

drExit	ora	l		;test A and L for zero
	pop	h		;HL->flag byte of current sector
	mov	m,a		;clear/set flag for good/bad read
	
; dNext - Disable read and write output, put address of next sector
;    in HL, pop PSW and DE from stack and exit.

dNext	push	psw		;save the status on the stack

	mvi	a,RDDIS		;disable read and write
	out	DSKCMD
	mvi	a,WRDIS
	out	DSKCMD

	call	dNxtSec		;HL->next sector in trkBuf
	
	pop	psw		;restore the result status
	pop	d		;restore DE
	ret			;and exit

; drError - Force non-zero status and exit through drExit

drError	mvi	a,1		;force non-zero status
	jmp	drExit		;exit read

;---------------------------------------------------------------------------
; dVerify - Verify a sector: header, data, and checksum
;
; On Entry
;    Drive is selected and ready, known to be in between sector pulses.
;    HL->flag byte in sector buffer
;
; On Exit
;    HL->flag byte of next sector
;    Flag byte of sector set/clear for sector good/fail - This is 
;        OPPOSITE of dRead status
;    Zero FALSE if no error, TRUE otherwise. This is OPPOSITE of dRead
;        status
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dVerify	call	rst6852
	mvi	a,RDEN		;enable read on the controller
	out	DSKCMD

	push	d		;preserve DE
	push	h		;and HL
	inx	h		;HL->1st data byte in sector
	lxi	d,0		;DE=initial checksum
	
; Wait for next sector pulse
	
	in	DSKSEC		;clear sector interrupt

dvSecWt	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dvSecWt		;no, keep waiting

; Wait up to 1.75ms for start of data

	mvi	c,83		;C=timeout counter (83*39 cycles)

dvWait1	dcr	c		;(5) timeout?
	jz	dvError		;(10) yes, preamble error abort

	in	DSKSCTL		;(10) A=6852 status
	rrc			;(4) data byte present?
	jnc	dvWait1		;(10) Wait for data

; Read and verify sector and track from disk

	in	DSKSDAT		;read disk data 1st byte (sector)
	mov	b,a		;B=sector
	in	DSKSDAT		;read disk data 2nd byte (track)
	mov	c,a		;C=track

	in	DSKSEC		;A=sector from controller
	ani	SECMASK		;get the sector bits alone
	ori	80h		;set the flag bit
	cmp	b		;sector match?
	jnz	dvError		;no, exit

	lda	curTrk		;A=expected track
	cmp	c		;match the track we found?
	jnz	dvError		;no, exit

	mvi	a,PSECLEN/2	;128 pairs of bytes/sector

; dvLoop - Verify sector data against memory at (HL)

dvLoop	push	psw		;save loop counter on stack

dvWait2	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	dvWait2		;no, keep waiting
	
	in	DSKSDAT		;read disk data even byte
	cmp	m		;match byte in RAM buffer?
	jnz	dvCmpFl		;compare failure
	
	mov	c,a		;C=even byte	
	inx	h		;bump buffer pointer
	
	in	DSKSDAT		;read disk data odd byte
	cmp	m		;match byte in RAM buffer?
	jnz	dvCmpFl		;compare failure
	
	mov	b,a		;B=odd byte
	inx	h		;bump buffer pointer

	xchg			;put checksum in HL
	dad	b		;update 16-bit checksum
	xchg			;put buffer pointer back in HL

	pop	psw		;A=byte pair count
	dcr	a		;decrement loop count
	jnz	dvLoop		;loop through sector data

; Read and verify 1's complement 16-bit checksum from disk

dvWait3	in	DSKSCTL		;A=6852 status
	rrc			;data available?
	jnc	dvWait3		;no, keep waiting

	in	DSKSDAT		;read low byte of checksum
	mov	c,a		;C=checksum low byte
	in	DSKSDAT		;read high byte of checksum
	mov	b,a		;BC=1's complement checksum from disk
	
	xchg			;HL=computed checksum
	dad	b		;add to positive computed checksum
	inx	h		;plus 1 should give zero
	
	mov	a,h		;test HL for zero
	ora	l
	jnz	dvError		;error if not zero
	
	dcr	a		;non-zero status for good verify
	
; dvExit - Update the verify status in the flag byte of the current sector. 
;    Disable read signal, put address of next sector in HL, return the
;    status of the verify operation.

dvExit	pop	h		;HL->flag byte of current sector
	mov	m,a		;set/clear flag for good/bad verify
	jmp	dNext		;exit through dNext
	
; dvCmpFl, dvError - Force zero status for error and exit through dvExit
;    dvCmpFl also pops the stack to remove the transfer counter

dvCmpFl	pop	psw		;remove transfer counter from stack

dvError	xra	a		;zero status to flag verify error
	jmp	dvExit		;exit verify

;---------------------------------------------------------------------------
; dWrite - Write a sector
;
; On Entry
;    Drive is selected and ready, known to be in between sector pulses
;    HL->flag byte in sector buffer
;
; On Exit
;   Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dWrite	mvi	a,WREN		;enable write on controller
	out	DSKCMD		;8255 Port C BSR mode control	

	push	d		;preserve DE
	xchg			;DE->flag byte in sector buffer
	inx	d		;DE->1st byte in sector buffer
	
; Wait for next sector pulse
	
	in	DSKSEC		;clear sector interrupt

dwSecWt	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dwSecWt		;no, keep waiting

;Write sector preamble to disk:
;  10 zeros
;  2 sync characters
;  1 byte sector number (msb set)
;  1 byte track number

	mvi	l,5		;write 10 0's

dwZeros	call	dwWtTx		;wait for transmitter to be available
	xra	a		;transmit a couple 0's
	out	DSKSDAT
	out	DSKSDAT
	dcr	l		;more 0's to write?
	jnz	dwZeros		;yes

	mov	h,l		;HL=initial checksum = 0
	call	dwWtTx		;wait for tranmsitter
	mvi	a,SYNC		;write 2 sync characters
	out	DSKSDAT
	out	DSKSDAT

	call	dwWtTx		;wait for transmitter to be available
	in	DSKSEC		;current sector from hardware
	ani	SECMASK		;just the sector bits
	ori	80h		;set msb to indicate sector number
	out	DSKSDAT		;write sector number

	lda	curTrk		;A=current track
	out	DSKSDAT		;write to disk

; Write sector data, computing 16-bit checksum as we go

	mvi	a,PSECLEN/2	;128 byte-pairs per sector

dwLoop	push	psw		;save byte-pair count on stack

	call	dwWtTx		;wait for transmitter
	ldax	d		;get data
	inx	d		;bump memory pointer
	out	DSKSDAT		;write data to disk
	mov	c,a		;save for checksum
	
	ldax	d		;get data
	inx	d		;bump pointer
	out	DSKSDAT		;write data to disk
	mov	b,a		;save for checksum
	dad	b		;update 16-bit checksum
	
	pop	psw		;A=byte-pair count
	dcr	a		;decrement byte-pair count
	jnz	dwLoop		;loop until done

	call	dwWtTx		;Wait for transmitter
	mov	a,l		;low byte of checksum
	cma			;inverted
	out	DSKSDAT		;Write to disk
	
	mov	a,h		;high byte of checksum
	cma			;inverted
	out	DSKSDAT		;Write to disk

	jmp	dNext		;compute next sector address and exit

;---------------------------------------------------------------------------
; dwWtTx - Wait for serial transmit ready in the 6852
;---------------------------------------------------------------------------
dwWtTx:	in	DSKSCTL		;6852 status
	ani	TDRA		;transmit ready?
	jz	dwWtTx		;no, keep waiting

	ret

;---------------------------------------------------------------------------
; dSkpSec - Wait for a sector pulse, then compute and return the buffer
;    address of the NEXT sector to come.
;
; dNxtSec - Compute and return in HL the buffer address of the NEXT
;    sector to come.
;
; Since these functions must be called before any sector I/O, they are
;    used as a common point to restart the motor timeout and to disable
;    interrupts.
;
; On Entry
;    Drive is selected and ready
;
; On Exit
;    Drive is known to be in between sector pulses
;    HL->flag byte of next sector
;    Interrupts disabled, motor timeout restarted
;    Clobbers A,B,C,H,L
;---------------------------------------------------------------------------
dSkpSec	in	DSKSEC		;clear sector pulse flag

dsWait	in	DSKSTAT		;look for sector pulse
	ani	SPMASK		;sector pulse set?
	jz	dsWait		;no, keep waiting
	
dNxtSec	in	DSKSEC		;A=sector we're on
	inr	a		;compute next sector number
	daa			;wrap 10 back to zero
	ani	SECMASK		;A=next sector
	
	lxi	h,trkBuf	;HL->track buffer
	mov	b,a		;form BC=sector*257
	mov	c,a
	dad	b		;HL->next sector in trkBuf

	xra	a		;restart motor timeout
	sta	timer
	di			;disable interrupts for upcoming I/O
	ret

;******************************************************************************
;
;   BIOS Data Area
;
;******************************************************************************
	
;-----------------------------------------------------------------------------
; dpHead - disk parameter header for each drive
;-----------------------------------------------------------------------------
dpHead	dw	0,0,0,0,dirBuf,dpb,csv0,alv0
	dw	0,0,0,0,dirBuf,dpb,csv1,alv1
	dw	0,0,0,0,dirBuf,dpb,csv2,alv2

;-----------------------------------------------------------------------------
; dpb - disk parameter block. This table gives a block size of 1024 bytes
;   and 64 directory entries.
;-----------------------------------------------------------------------------
dpb	dw	NUMCSEC		;sectors per track
	db	BSH		;allocation block shift factor (BSH)
	db	BLM		;data location block mask (BLM)
	db	EXM		;extent mask (EXM)
	dw	DSM		;maximum block number (DSM 242)	
	dw	DRM		;maximum directory entry number (DRM 63)
	db	AL0,AL1		;AL0, AL1 
	dw	CKS		;CKS=(DRM+1)/4
	dw	RESTRK		;reserved tracks for CP/M and bootloader

;----------------------------------------------------------------------------
; Initialized data
;----------------------------------------------------------------------------
curDrv	db	1		;current drive (drive 1 after cold boot)
curTrk	db	RESTRK-1	;current track (track 2 after cold boot)
trkTbl	db	UNDEF,UNDEF,UNDEF,UNDEF   ;track each drive is on (1-indexed)

bfDirty	db	0		;non-zero if buffer has been written to
bfDrive	db	UNDEF		;drive currently in track buffer
bfTrack	db	UNDEF		;currently buffered track (MUST follow bfDrive)

lastChr	db	0		;last character sent to display

; Serial xmit/receive data. The order of the next three items MUST NOT CHANGE

xmtStat	db	0FFh		;transmitter status, 0=TX enabled, FF=disabled
xmtCnt	db	0		;count of bytes in xmit queue
rcvCnt	db	0		;count of bytes in receive queue

pRcvIns	dw	rcvBuf		;receive buffer insert pointer
pRcvRem	dw	rcvBuf		;receive buffer remove pointer
pXmtIns	dw	xmtBuf		;transmit buffer insert pointer
pXmtRem	dw	xmtBuf		;transmit buffer remove pointer

kbdBuf	db	0		;keyboard buffer, 0=no key present

;----------------------------------------------------------------------------
;  Driver code size check. Must be less than 780h bytes in length here.
;
;  Note that ASM and MAC actually only check bit 0 of the expression
;  for zero, not the entire value, so the expression below moves the
;  sign bit to bit zero (sign bit set if BIOS is too big).
;----------------------------------------------------------------------------
SZDIFF	equ	780h-($-BIOSBAS)
SZFLAG	equ	(SZDIFF and 8000h) shr 15

	if SZFLAG

  BIOS IS TOO BIG ****

	endif

;----------------------------------------------------------------------------
; Non-initialized data
;----------------------------------------------------------------------------
drvNum	ds	1		;drive# from CP/M call
trkNum	ds	1		;track# from CP/M call (MUST follow drvNum)
secNum	ds	1		;sector number from CP/M call
dmaAddr	ds	2		;address for disk I/O from CP/M call

secCnt	ds	1		;count of bad sectors remaining in trkBuf
wrtProt	ds	1		;non-zero if disk is write protected
wrRetry	ds	1		;write retry counter
skRetry	ds	1		;restore and seek retry counter
wrtNoRd	ds	1		;non-zero if writing without track read

timer	ds	1		;60hz timer (counter)

; Serial receive and transmit buffers. The code makes assumptions about the
; size and alignment of the queues.
;    With RCVSIZE = 32, rcvBuf must be at a XX00 0000b boundary
;    With XMTSIZE = 4, xmtBuf must be at a XXXX X000b boundary

	org	($+RCVSIZE+3Fh) and 0FFC0h
rcvBuf	ds	RCVSIZE
xmtBuf	ds	XMTSIZE

	ds	64		;local stack space
LCLSTK	EQU	$

;-----------------------------------------------------------------------------
;  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

;-----------------------------------------------------------------------------
; Track buffer. One extra byte is allocated at the start of each sector
;    to flag a bad sector (read failure). This byte is zero if the sector
;    is good, non-zero if the sector is bad.
;-----------------------------------------------------------------------------
trkBuf	ds	NUMPSEC*(PSECLEN+1)

TOTLEN	equ	$-BIOSBAS

	end




