;******************************************************************************
;
;  PC2Flop - Transfer disk image from PC to floppy over serial port.
;		(for iCOM 3812 and 3712 disk controllers)
;
;	This program writes a soft-sectored 8" floppy with a disk image
;	transmitted from a PC. The image is transmitted using the XMODEM
;	protocol through either port on an 88-2SIO or through an 88-SIO.
;
;	The program runs standalone at 0x100 or under CP/M. The program
;	talks directly to the controller and the serial ports so it does
;	not require CP/M or any other OS to function.
;
;	Single and double density disks are supported on the 3812.
;       The 3712 supports single density only.
;
;	Since this is a soft sectored controller, the destination disk must
;	have been formatted at some point. When running on a 3812, this
;	program provides an option to format the disk. The 3712 does not
;	have the ability to format a disk.
;
;	This program works best if the console port and the transfer port
;	are not the same device. Otherwise, most error messages won't
;	be seen as the file transfer dialog box prevents their display.
;
;	Written by Mike Douglas
;
;	Rev	   Date	    Desc
;	1.0	 02/03/21   Original
;
;	1.1	 02/10/21   Determine controller type before any drive
;			    activity. Reset controller as part of drive
;			    select and after format. Ensure format bit
;			    is cleared if an abort is requested.
;
;	1.2	 05/01/21   Add missing RET in dClrErr.
;
;*****************************************************************************

; Disk information equates

MINDRV	equ	0		;first drive number
MAXDRV	equ	3		;max drive number 

NUMTRK	equ	77		;number of tracks
NUMSEC	equ	26		;maximum number of sectors
SDSECL	equ	128		;single density sector length
DDSECL	equ	256		;double density sector length
SDTRKL	equ	(NUMSEC*SDSECL)	;length of single density track
DDTRKL	equ	(NUMSEC*DDSECL)	;length of double density track

RDTRIES	equ	3		;number of read (verify) attempts
WRTRIES	equ	3		;number of write attempts

; Monitor, CP/M boot entry points

MONITOR	set	init		;no monitor, just start over
CPMBOOT	set	0		;CP/M warm boot jump vector

; XMODEM equates

PKTLEN	equ	128		;128 byte xmodem packet length
SOH	equ	01h
EOT	equ	04h
ACK	equ	06h
NAK	equ	15h
EOF	equ	1Ah		;ctrl-z character
XMTRIES	equ	10		;number of xmodem retries

; CP/M Equates

WBOOT	equ	0		;warm boot jump vector

; Misc equates

CR	equ	0Dh		;ascii for carriage return
LF	equ	0Ah		;ascii for line feed
DEL	equ	7Fh		;ascii DEL
BS	equ	08h		;ascii backspace
CTRLC	equ	03h		;ascii for control-c

	org	0100h	
;-----------------------------------------------------------------------------
;  Initialize for transfer
;-----------------------------------------------------------------------------
init	lxi	sp,ourStk	;initialize stack pointer
	call	chkCpm		;set flag for CP/M or not
	call	sizeRam		;determine amount of RAM available
	call	s2Init		;initialize the 88-2SIO ports

;  Display welcome message, then get the drive number and port number
;    to use for the transfer from the operator.

	lxi	h,mWelcom	;display welcome message
	call	dispMsg

; getDrv - get drive number or letter from the user.

getDrv	lxi	h,mDrive	;display drive number prompt
	call	dispMsg
	call	rcvCon		;get byte from the console
	ori	20h		;upper case letters to lower case
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	lxi	h,baseDrv	;ascii 1st drive - baseDrv = 0
	sub	m
	cpi	MAXDRV-MINDRV+1	;validate >=min drive and <= max drive
	jnc	getDrv		;invalid, prompt again

	adi	MINDRV		;restore offset of 1st drive (if any)
	sta	drvNum		;save the drive number to use
	mov	b,a		;save drive number in b

	lda	mDrvMin		;a='0' or 'A'	
	add	b		;a=ascii drive number or letter
	sta	mSrcDrv		;update message with drive designator

; Restore to track 0, verify drive is not write protected. If 3812,
;   check the density of the installed disk.

	call	ctlType		;get controller type, 3712 or 3812
	call	dSelDrv		;select drive
	call	dRestor		;restore to track 0
	call	dChkWP		;check write protect (aborts to getDrv)

	lda	ctl3712		;if 3712, skip density detection
	ora	a
	jnz	getPort

; For the 3812, test and display the disk density

	call	dGetDen		;get density of the disk that is inserted
	lxi	h,mNotFmt	;hl->disk not formatted message
	jnz	needFmt		;couldn't read disk at all, needs format?
	
	lxi	h,mSrcDet	;density detected message
	call	dispMsg

	lxi	h,mSD		;hl->single density message
	lda	dskDen		;test density
	ora	a		;single density?	
	jz	dispDen		;yes
	lxi	h,mDD		;else, hl->double density message

dispDen	call	dispMsg		;display the density
	lxi	h,mDrvDen	;display rest of message

needFmt	call	dispMsg		;display message pointed to by hl

; For 3812, prompt whether or not to format the disk

getFmt	lxi	h,mFormat	;hl->format prompt
	call	dispMsg
	call	rcvCon		;get response
	ori	20h		;upper to lower case, nums not affected
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	sui	'1'		;'1' - '3' to binary 0-2
	cpi	3		;validate ascii 1-3
	jnc	getFmt		;invalid, prompt again

	cpi	2		;don't format?
	jz	getPort		;skip formatting

	sta	dskDen		;if SD selected, dskDen now zero
	ora	a		;single density?
	jz	doFmt		;yes, dskDen already set

	mvi	a,rDDEN		;double density flag
	sta	dskDen		;disk will be double density

doFmt	call	dFormat		;format the disk

; getPort - get serial port number from the user.

getPort	lxi	h,mPort		;display transfer port prompt
	call	dispMsg
	call	rcvCon		;get byte from the console
	ori	20h		;upper to lower case, nums not affected
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	sui	'1'		;'1' - '3' to bianry 0-2
	cpi	3		;validate ascii 1-3
	jnc	getPort		;invalid, prompt again

	sta	xfrPort		;save the port to use

; Prompt the user to insert the disk to write, initialize, then start
;   sending NAK to start the XMODEM reception.

	lxi	h,mStart	;display start file transfer prompt
	call	dispMsg

	xra	a		;init track we're reading to zero
	sta	trkNum

	inr	a		;init xmodem block number to one
	sta	blkNum

	lxi	h,trkBuf	;trkBuf is initial starting point for
	shld	rcvPtr		;   XMODEM receive

	mvi	a,NAK		;send starting nak after purging input data
	call	purgSio

;-----------------------------------------------------------------------------
;  mainLp - Receive as many packets as will fit into RAM and then write
;      tracks to disk. Repeat until entire disk is written. 
;-----------------------------------------------------------------------------
mainLp	call	bufPkts		;buffer as many packets as possible
	xchg			;de=start of last packet received
	lxi	h,trkBuf	;hl=start of track buffer
	shld	trkPtr		;writing will start from here
	call	cmpDeHl		;compare last packet start - trkBuf
	jc	allDone		;nothing received but an EOT

; trkLoop - write tracks until all disk tracks have been written or all
;    the tracks in the buffer have been written.
	
trkLoop	call	wrtTrk		;write a track (hl returns->next track)

	lda	trkNum		;increment track number (preserve HL)
	inr	a
	sta	trkNum	
	cpi	NUMTRK		;done all tracks?
	jz	allDone		;yes

	xchg			;de=next track pointer
	lhld	rcvPtr		;hl=start of last packet received
	call	cmpHlDe		;compare rcvPtr-start of next track
	jnc	trkLoop		;another track is present

; All the buffered tracks have been written. Move any bytes left over
;    from the xmodem receive to the start of trkbuf. Adjust rcvPtr
;    to the end of that data which is where reception of the next
;    packet will begin.

	call	subHlDe		;hl=hl-de = left over byte count - PKTLEN
	mov	a,l		;a=left over count - PKTLEN
	adi	PKTLEN		;a=left over byte count
	mov	b,a		;b=left over byte count (bytes to move)
	lxi	h,trkBuf	;hl->start of trkBuf
	jz	mvDone		;no bytes to move, we're done
	
; Copy the leftover data to the start of trkBuf. The next XMODEM receive
;   sequence will start at the end of this copied data instead of at the
;   start of trkBuf.

moveLp	ldax	d		;move from end of trkBuf to start of trkBuf
	mov	m,a
	inx	h		;hl->starting bytes of trackbuf
	inx	d		;de->unwritten bytes from end of trkBuf		
	dcr	b
	jnz	moveLp

mvDone	shld	rcvPtr		;save address where to receive next packet

	mvi	a,ACK		;ACK the last packet or EOT
	call	sndByte
	jmp	mainLp		;go receive more packets

; allDone - The disk is completely written. ACK the final packet (or EOT),
;     display the "transfer complete" message and start the program over.

allDone	mvi	a,ACK		;ACK the final packet or EOT
	call	sndByte

	lxi	h,mDone		;hl->done message
	call	dispMsg
	jmp	getDrv		;start over asking for drive num

;-----------------------------------------------------------------------------
; bufPkts - Receive XMODEM packets until we fill all the track buffer
;   space available. Reception starts at the address pointed to by
;   rcvPtr. Reception is stopped when the last packet exactly
;   fills or "crosses" the end of the last track spot in RAM. rcvPtr is
;   updated on exit to point to the start of the last packet received.
;   The disk write routines use this pointer to know where data ends and
;   then update rcvPtr to start to where to begin receiving new packets.
;-----------------------------------------------------------------------------
bufPkts	call	rcvPkt		;receive a packet
	xchg			;de=current xmodem receive pointer
	cpi	EOT		;EOT received?
	jz	bufDone		;yes, buffering is done

	lhld	bufEnd		;hl=end of buffering space
	call	cmpDeHl		;compare current-end	
	jnc	bufDone		;past or at end, buffering is done

	mvi	a,ACK		;otherwise, ACK the packet
	call	sndByte
	xchg			;put xmodem rcv pointer back in HL
	jmp	bufPkts		;buffer some more

; bufDone - no more room for packets. Update rcvPtr to point to the 
;   start of the last packet received so the disk write routines know
;   where data ends.

bufDone	xchg			;hl=current xmodem receive pointer
	lxi	d,-PKTLEN
	dad	d		;hl=hl-PKTLEN = start of last rcv'd packet
	shld	rcvPtr		;save use by write tracks

	lhld	bufEndN		;use this bufEnd for 2nd-Nth transfer
	shld	bufEnd
	ret

;-----------------------------------------------------------------------------
; rcvPkt - receive an xmodem format 128 byte packet. HL points to the 128 byte
;    buffer to receive. HL is updated by 128 bytes after each succuessful
;    reception of a new 128 byte packet.
;-----------------------------------------------------------------------------
nakPkt	pop	h		;get HL back
	mvi	a,NAK
	call	purgSio		;purge receive for 1s then transmit NAK

;wait for SOH or EOT

rcvPkt	push	h		;save HL for retries
waitSoh	call	chkQuit		;check for user abort
	mvi	a,3		;3 second timeout for soh
	call	rcvByte		;receive character with timeout
	jz	nakPkt		;timeout

	cpi	SOH		;SOH received?
	jz	haveSoh

	cpi	EOT		;EOT to say we're done?
	jnz	waitSoh		;no, keep looking for SOH

	pop	h		;undo the entry push
	ret			;return with EOT

;  Have SOH, receive block number and not block number

haveSoh	mvi	a,1		;one second timeout once inside a packet
	call	rcvByte		;get the block number
	jz	nakPkt		;timeout, NAK and start over

	sta	rcvBlk		;save the received block number
	mvi	a,1		;one second timeout
	call	rcvByte		;get not block number
	jz	nakPkt		;timeout, NAK and start over

	sta	rcvNBlk		;save not block number

;  Receive the 128 byte block

	mvi	e,0		;init checksum
	mvi	d,pktLen	;d is byte counter
pktLoop	mvi	a,1		;one second timeout
	call	rcvByte		;get next data byte
	jz	nakPkt		;timeout

	mov	m,a		;store the character
	inx	h		;point to next byte
	add	e		;update the checksum
	mov	e,a
	dcr	d		;decrement bytes remaining
	jnz	pktLoop

;  Verify the checksum

	mvi	a,1		;one second timeout
	call	rcvByte		;get the checksum
	jz	nakPkt		;timeout

	cmp	e		;checksum match?
	jnz	nakPkt		;no, packet error

;  Checksum good. Verify block numbers

	lda	rcvNBlk		;verify rcvBlk = not (rcvNBlk)
	cma			;A should = block number now
	mov	b,a		;save in b
	lda	rcvBlk
	cmp	b		;compare rcvBlk = not(rcvNBlk)?
	jnz	nakPkt		;no, error

	lda	blkNum		;compare rcvBlk = expected blkNum
	cmp	b
	jnz	nakPkt		;nak if not the same (also nak's re-send)

	inr	a		;increment expected block number
	sta	blkNum
	pop	b		;get HL off stack, but don't clobber HL
	xra	a		;return a zero
	ret

;-----------------------------------------------------------------------------
; wrtTrk - write and verify NUMSEC sectors to the current track in trkBuf
;   as pointed to by trkPtr. After the track is written, trkPtr is updated
;   by the length of a track to point to the next track buffer. This
;   saved value is also returned in HL.
;-----------------------------------------------------------------------------
wrtTrk	call	chkQuit		;check for ctrl-c from operator
	
	mvi	e,1		;e=sector, must be valid for error
	call	dSeek		;seek to the next track
	jnz	wrtErr		;disk bad or not formatted

	call	dChkWP		;make sure not write protected

	mvi	a,WRTRIES	;init write retry counter
	sta	wrRetry

; rtryWrt - write a track and the retry entry point.

rtryWrt	mvi	e,0FFh		;e=sector number, init to -1
	mvi	d,NUMSEC	;d=count of sectors to write
	
wrtLoop	call	dNxtSec		;increment and poin to next sector
	call	dWrite		;write the sector
	jnz	wrtErr		;write failed

	dcr	d		;decrement sector count
	jnz	wrtLoop		;loop until all sectors written

; Verify the track just written by checking the CRC. This can be done
;   with no interleave

	mvi	e,0		;e=sector number, init to 0
	mvi	d,NUMSEC	;d=count of sectors to verify

vfyLoop	inr	e		;move to next sector
	call	dVerify		;verify the sector (tries 3 times)
	jnz	vfyFail		;verify failed

vfyNext	dcr	d		;decrement sector count
	jnz	vfyLoop		;loop until all sectors verified

; Track written and verified, increment trkPtr to memory location f
;    for the next track and exit.

	lhld	trkLen		;hl=length of track
	xchg			;de=track length
	lhld	trkPtr		;hl=current track pointer
	dad	d		;hl->start of next track in trkBuf
	shld	trkPtr		;trkPtr->start of next track in trkBuf
	ret

; vfyFail - verify failed (3 attempts made to verify), rewrite the track
;    and try again until write retry count expires.

vfyFail	lxi	h,wrRetry	;decrement the write retry counter
	dcr	m
	jnz	rtryWrt		;retry starting with the write

	jmp	vfyErr		;display the error and restart

;-----------------------------------------------------------------------------
; Disk write verify failure. Display the track and sector with the error. 
;    Restart the program.
;-----------------------------------------------------------------------------
wrtErr	lxi	h,mNoSec	;hl->sector not found, format needed?
	call	dispMsg		

vfyErr	lxi	h,errTrk	;hl->where to put ascii decimal
	lda	trkNum		;a=track with error on it
	call	bin2dec		;track to ascii

	lxi	h,errSec	;hl->where to put ascii sector
	mov	a,e		;a=sector where error occured
	call	bin2dec

	lxi	h,mDskErr	;display the error message
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;-----------------------------------------------------------------------------
; dispMsg - display the null-terminated message passed in hl on the
;    console device. Clobbers b, hl
;-----------------------------------------------------------------------------
dispMsg	mov	a,m		;get the next message byte
	ora	a		;null terminates
	rz

	mov	b,a		;conOut wants character in b
	call	conOut
	inx	h		;move to next byte
	jmp	dispMsg

;-----------------------------------------------------------------------------
; rcvCon - Receive a character from the console device, echo it, then
;   wait for a CR. Exits program if Ctrl-c typed. Returns with invalid
;   character (null) if BS or DEL pressed after 1st character
;   Returns character in a, clobbers b.
;-----------------------------------------------------------------------------
rcvCon	call	conIn		;check for input
	jz	rcvCon		;nothing

	ani	7fh
	cpi	CTRLC		;abort requested?
	jz	pgmExit		;yes

	cpi	CR		;return pressed?
	rz			;yes, don't echo it

	mov	b,a		;conOut needs character in b
	call	conOut		;echo it

; Wait for CR, then return 1st character typed

rcvCr	call	conIn		;check for input
	jz	rcvCr		;nothing

	ani	7fh
	cpi	CTRLC		;abort requested?
	jz	pgmExit		;yes

	cpi	DEL		;delete
	rz			;yes, return DEL character

	cpi	BS		;backspace?
	rz			;yes, return BS character

	cpi	CR		;return pressed?
	jnz	rcvCr		;no, keep waiting

	mov	a,b		;return 1st character typed
	ret

;-----------------------------------------------------------------------------
; purgSio - wait for 1 second of idle time on receive line, then transmit the
;	character passed in A (usually ACK or NAK). Clobbers b
;-----------------------------------------------------------------------------
purgSio	push	a

purge	mvi	a,1		;1 second timeout
	call	rcvByte		;wait for 1 second without activity
	jnz	purge

	pop	a		;get back the character to send
				;fall through to sndByte
;-----------------------------------------------------------------------------
; sndByte - send the byte in a through the specified transfer port. 
;     Clobbers b. (assuming serOut clobbers only a).
;-----------------------------------------------------------------------------
sndByte	mov	b,a		;b=byte to transmit
	lda	xfrPort		;a=port to use for transfer
	jmp	serOut		;send the character

;-----------------------------------------------------------------------------
; rcvByte - Receive a byte from the specified transfer port. A wait timeout
;     in seconds is specified in a. If a timeout occurs, zero is returned in
;     a and the zero flag is true. Otherwise, the character is returned in a 
;     (could be zero) and the zero flag is false. ONESEC must be set based
;     on processor speed and the number of cycles in the serIn call + 59.
;     Clobbers a, b and c.
;-----------------------------------------------------------------------------
rcvByte	push	d		;save d, e
	mov	d,a		;save timeout in d

initSec	lxi	b,ONESEC	;one second timeout 104 cycles per loop

rcvWait	lda	xfrPort		;(13) a=port to use for transfer
	call	serIn		;(17+45) look for a byte
	jnz	haveChr		;(10) byte received

	dcx	b		;(5) otherwise, decrement timer
	mov	a,b		;(5) one second expire?
	ora	c		;(4)
	jnz	rcvWait		;(10) no, keep waiting

; one second timeout has occured. Decrement the seconds counter.

	dcr	d		;decrement seconds counter
	jnz	initSec		;initialize for another 1 second count

haveChr	pop	d		;restore d, e
	ret

;--------------------------------------------------------------
; bin2dec - Binary byte in A to 2 ASCII digits at (HL)
;   HL is preserved
;--------------------------------------------------------------
bin2dec	mvi	m,' '		;assume zero supression
	sui	10		;value less than 10?
	jc	do1s		;yes, leading blank
	mvi	m,'1'		;have one ten already

loop10	sui	10		;count 10s
	jc	do1s		;done with 10s, do 1s
	inr	m
	jmp	loop10

do1s	adi	'0'+10		;form ASCII 1s digit
	inx	h		;move to 1s position
	mov	m,a
	dcx	h		;restore hl
	ret

;--------------------------------------------------------------
; chkQuit - check for the user to request abort (ctrl-c). If
;    a character is present on the console port, read it and
;    see if ctrl-c. Clobbers A
;--------------------------------------------------------------
chkQuit	call	conIn		;check for console input
	rz

	ani	7fh
	sui	CTRLC		;abort requested?
	rnz			;no

; Ctrl-C typed while program is running. Return to drive prompt.

	sta	fmtCnfg		;clear format bit locally...
	mvi	c,cLDCFG	;  and in the controller
	call	dCmdDat

	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;--------------------------------------------------------------
; pgmExit - Exit to CP/M or to the monitor ROM based on the
;    CP/M flag
;--------------------------------------------------------------
pgmExit	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	cpmExit		;yes

; Exit to ROM monitor

	lxi	h,mExit		;display "exiting" message
	call	dispMsg
	jmp	MONITOR		;jump to ROM monitor entry point

; CP/M exit. If boot drive was used, prompt user to insert CP/M disk

cpmExit	lda	drvNum		;boot drive used?
	sui	MINDRV
	jnz	noDisk		;not 1, disk prompt not needed

	lxi	h,mCpm		;display "insert cp/m disk"	
	call	dispMsg
	call	rcvCon		;wait for a character

noDisk	mvi	a,MINDRV	;re-select drive A for CP/M 1.4
	sta	drvNum
	call	dSelDrv

	lxi	h,mExit		;display "exiting" message
	call	dispMsg
	jmp	CPMBOOT		;reboot CP/M

;--------------------------------------------------------------
; chkCpm - check if running under CP/M. CP/M flag is set true
;   (non-zero) if yes, cleared otherwise.
;--------------------------------------------------------------
; First, initialize entries for stand-alone

chkCpm	xra	a
	sta	cpmFlag		;clear CP/M flag
	mvi	a,MINDRV+'0'	;ascii for minimum drive number
	sta	mDrvMin		;store in the drive prompt message
	sta	baseDrv		;ascii 1st drive - baseDrv = 0
	mvi	a,MAXDRV+'0'	;ascii for max drive number
	sta	mDrvMax

; Determine if we're under CP/M or standalone. CP/M is assumed if
;   a jump instruction is present at the CP/M warm start location (0)
;   and five more jumps (e.g., a jump table) is present at the
;   jump-to destination.
		
	lda	WBOOT		;see if jump instruction present for CP/M
	cpi	JMP
	rnz			;no, not CP/M

; A jump instruction is present at the CP/M warm boot location (0),
;   now see if that jump points to five more jumps. If so, assume CP/M

	lxi	h,WBOOT+1	;point to lsb of jump address
	mov	e,m		;e=low byte of jump
	inx	h
	mov	d,m		;de=destination of jump
	mvi	b,5		;look for 5 more jumps (a jump table)

jmpTest	ldax	d		;a=opcode at jump destination
	sui	JMP		;another jump present?
	rnz			;no, not CP/M

	inx	d		;move to next jump
	inx	d
	inx	d
	dcr	b
	jnz	jmpTest

	dcr	a		;a=0ffh
	sta	cpmFlag		;CP/M flag to non-zero = true

; We're running under CP/M. Change drive prompt message to show drive
;    letters instead of drive numbers and change baseDrv to convert
;    an 'A' to the base drive number (MINDRV).

	mvi	a,'A'		;'A' in drive message instead of number
	sta	mDrvMin
	adi	MAXDRV-MINDRV	;max drive letter
	sta	mDrvMax
	mvi	a,'a'		;ascii 1st drive - baseDrv = 0
	sta	baseDrv
	ret

;------------------------------------------------------------------
; sizeRam - determine how much RAM we have for buffering tracks.
;   Sets the bufEnd variable which points to end address of 
;   the last possible track buffer + 1
;------------------------------------------------------------------
; If running under CP/M, use the BIOS pointer to determine the top
;    of available RAM. Otherwise, size RAM manually.

sizeRam	lhld	WBOOT+1		;get address of BIOS jump table+3
	dcx	h		
	dcx	h
	dcx	h		;hl->top of usable ram+1
	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	ramEnd		;yes, we have the end of RAM

;  Not under CP/M. Find the end of RAM manually.

	lxi	h,(trkBuf+0ffh) AND 0ff00h   ;next 256 byte boundary

ramLoop	mov	a,m		;a=current RAM content
	inr	m		;change RAM
	cmp	m		;did RAM change?
	mov	m,a		;restore RAM
	jz	ramEnd		;end of RAM found

	inr	h		;next page
	jnz	ramLoop

; ramEnd - end of RAM found. Subtract 256 bytes from this to make room
;   for xmodem packet overflow at the end. Determine the end address + 1
;   of the last track buffer that will fit in RAM. Store in bufEnd

ramEnd	xchg			;de=end of RAM + 1
	mvi	a,-((DDTRKL SHR 8) + 1) AND 0ffh
	cmp	d		;force de < (10000h - TRKLEN)
	jnc	topOk

	mov	d,a		;limit max address

topOk	dcr	d		;subtract 256 bytes from end of RAM
	lxi	h,trkBuf	;hl=start of track buffer
	lxi	b,DDTRKL	;bc=length of track in bytes

; Loop increasing hl by DDTRKL until hl > end of RAM.

bfEndLp	dad	b		;hl=hl+track length
	call	cmpHlDe		;compare hl-de
	jc	bfEndLp		;still more room, keep going

; Subtract one track length from hl, this will be the end address + 1 of
;   the the last track buffer that will fit in RAM

	lxi	b,-DDTRKL	;subtract one track length
	dad	b		;hl = end address of last track + 1
	shld	bufEndN		;save as bufEndN

; The first set of packets received includes track 0 which is a single
;    density track (SDTRKL instead of DDTRKL). Therefore, the first
;    time tracks are buffered, the last track buffered will end on a
;    single density boundary. Subtrack SDTRKL from bufEnd for the
;    first time through.

	lxi	b,-SDTRKL	;subtract a SD track length
	dad	b		;hl ends on 1*SD + n*DD track boundary
	shld	bufEnd
	ret

;--------------------------------------------------------------
; ctlType - determine the controller type (3712 or 3812) based
;   on the interface board type. The interface type is based on
;   the PROM on the board. Only one PROM was ever used with
;   the 3812, so unique code in the 3812 PROM is checked.
; 
; On Exit:
;   ctl3712 set true (non zero) for 3712, zero for 3812.
;   Zero status matches ctl3712 value
;--------------------------------------------------------------
ctlType	lhld	0F016h		;two bytes from PROM to check
	mvi	a,79h		;3812 is 79h here
	sub	l
	mov	l,a		;l=0 if match 3812
	
	mvi	a,32h		;3812 is 32h here
	sub	h		;a=0 if match 3812
	ora	l		;a=0 only if both bytes match
	
	sta	ctl3712		;set result
	ret	
	
;--------------------------------------------------------------------
; cmHlDe - compare HL-DE. Carry set if HL<DE, carry clear if HL>=DE
;--------------------------------------------------------------------
cmpHlDe	mov	a,l		;compare HL-DE, do lsbs first
	sub	e
	mov	a,h		;do msbs
	sbb	d
	ret			;carry set HL<DE, clear HL>=DE

;--------------------------------------------------------------------
; cmDeHl - compare DE-HL. Carry set if DE<HL, carry clear if DE>=HL
;--------------------------------------------------------------------
cmpDeHl	mov	a,e		;compare DE-HL, do lsbs first
	sub	l
	mov	a,d		;do msbs
	sbb	h
	ret			;carry set DE<HL, clear DE>=HL

;--------------------------------------------------------------------
; subHlDe - HL=HL-DE
;--------------------------------------------------------------------
subHlDe	mov	a,l		;subtract HL-DE, do lsbs first
	sub	e
	mov	l,a		;lsb result in l
	mov	a,h		;do msbs
	sbb	d
	mov	h,a		;msb result in h	
	ret
	
;--------------------------------------------------------------------
; errMsg - display message pointed to by hl and then start over
;--------------------------------------------------------------------
errMsg	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num
	
;-----------------------------------------------------------------------------
;   Data Area
;-----------------------------------------------------------------------------
mWelcom	db	cr,lf,lf
	db 	'===== PC to Floppy Disk Image Transfer =====',cr,lf
	db	'        (for iCOM 3712/3812, ver 1.2)',cr,lf,lf
	db	'Writes an 8" floppy with a disk image received via an'
	db	cr,lf
	db	'88-2SIO or 88-SIO serial port using the XMODEM protocol.',0

mDrive	db	cr,lf,lf,'Insert and specify destination drive ('
mDrvMin	db	'x-'
mDrvMax	db	'x) or X to exit: ',0

mSrcDet	db	cr,lf,lf,'Detected a ',0
mSD	db	'single',0
mDD	db	'double',0
mDrvDen	db	' density disk in drive '
mSrcDrv	db	'0.',0

mPort	db	cr,lf,lf
	db	'Specify the port to use for file transfer',cr,lf
	db	'  1) 88-2SIO port A (which is also the console)',cr,lf
	db	'  2) 88-2SIO port B',cr,lf
	db	'  3) 88-SIO',cr,lf
	db	'Enter 1-3 or X to exit: ',0

mNotRdy	db	cr,lf,lf,'Drive not ready. Insert disk or Ctrl-C...',0

mStart	db	cr,lf,lf
	db	'Start XMODEM send now...',0

mDone	db	cr,lf,lf,'Creation of new disk successful!',cr,lf,0

mNoTrk0	db	cr,lf,lf,'Seek to track 0 failed',cr,lf,0

mNoSec	db	cr,lf,lf,'Track/sector not found - disk not formatted?',0

mDskErr	db	cr,lf,lf,'Write failure on track '
errTrk	db	'xx, sector '
errSec	db	'xx. Disk creation aborted.',cr,lf,0

mWrtPrt	db	cr,lf,lf,'Disk is write protected',cr,lf,0

mExit	db	cr,lf,lf,'Exiting...',cr,lf,0

mCpm	db	cr,lf,lf
	db	'Insert CP/M disk into drive A, then press Return...',0

mNotFmt	db	cr,lf,lf,'Disk bad or not formatted',0
mFormat	db	cr,lf,lf,'Format the target disk?'
	db	cr,lf,'  1) Single Density'
	db	cr,lf,'  2) Double Density'
	db	cr,lf,'  3) Do Not Format'
	db	cr,lf,'Select Option or X to exit: ',0

mFmting	db	cr,lf,lf,'Formatting ',0

mFmtDon	db	cr,lf,'Format complete',0


;****************************************************************************
;
;  Hardware specific console and serial I/O routines. 
;     The following four routines must be written to provide a common
;     interface to the hardware on which this program is running. The
;     port number specified for serIn and serOut matches the port number
;     input from the operator via the port menu.
;
;****************************************************************************

; 88-2SIO Serial Board Equates

s2aCtl	equ	010h		;1st port on 88-2SIO board - control register
s2aDat	equ	011h		;1st port on 88-2SIO board - data register
s2bCtl	equ	012h		;2nd port on 88-2SIO board - control register
s2bDat	equ	013h		;2nd port on 88-2SIO board - data register
s2Rdrf	equ	001h		;read data register full flag
s2Tdre	equ	002h		;transmit data register empty flag
s2Rst	equ	003h		;reset command
s28n1	equ	015h		;8N1 selection
s28n2	equ	011h		;8N2 selection

; 88-SIO Serial board Equates

sioCtl	equ	00h		;control port
sioDat	equ	01h		;data port
sioRdrf	equ	00000001b	;-RX Data register full
sioTdre	equ	10000000b	;-TX Data register empty

; The rcvByte subroutine above times a one second timeout with a code
;    loop that calls the hardware specific serIn routine below. ONESEC
;    must be set based on processor speed and the number of cycles in 
;    the serIn call + 59 cycles for the rcvByte code. 

ONESEC	equ	19231		;rcvByte loop count for 1 second

;----------------------------------------------------------------------------
; s2Init - reset and initialize 88-2SIO ports for 8N1
;----------------------------------------------------------------------------
s2Init	mvi	a,s2Rst		;reset and init 2nd 88-2SIO port
	out	s2bCtl
	mvi	a,s28n1		;transfer port as 8N1
	out	s2bCtl

; if not running under CP/M, reset and init 1st 88-2SIO port

	lda	cpmFlag		;running under CP/M?
	ora	a
	rnz			;yes, 2SIO already initialized

	mvi	a,s2Rst		;reset and init 1st 88-2SIO port
	out	s2aCtl
	mvi	a,s28n2		;configure console for 8N2 in case Teletype
	out	s2aCtl
	ret	

;----------------------------------------------------------------------------
; conIn - input character from console
;    inputs:
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;----------------------------------------------------------------------------
conIn	in	s2aCtl		;see if a new character is present
	ani	s2Rdrf
	rz			;no character, return zero status
	in	s2aDat		;else return byte and non-zero status
	ret	

;----------------------------------------------------------------------------
; conOut - output character to console
;    inputs: b = character to send
;    clobbers a
;----------------------------------------------------------------------------
conOut	in	s2aCtl		;wait for OK to transmit
	ani	s2Tdre
	jz	conOut
	mov	a,b		;a=character to transmit
	out	s2aDat		;send it
	ret

;----------------------------------------------------------------------------
; Hardware specific I/O
; serIn - input character from port specified in a
;       0 = 88-2SIO port A
;       1 = 88-2SIO port B
;       2 = 88-SIO
;    inputs: a = port to read from
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
; 88-2SIO Port A: 53 cycles including return when no character present
; 88-2SIO Port B: 43 cycles including return when no character present
; 88-SIO: 57 cycles including return when no character present
;-----------------------------------------------------------------------------
serIn	dcr	a		;(5)3-way test
	jz	s2InB		;(10)a was 1: 88-2SIO port B
	jp	sioIn		;(10)a was 2: 88-SIO
				;    a was 0: 88-2SIO port A
; Input from 88-2SIO port A

s2InA	in	s2aCtl		;(10+1)see if a new character is present
	ani	s2Rdrf		;(7)
	rz			;(10)no character, return zero status
	in	s2aDat		;else return byte and non-zero status
	ret	
	
; Input from 88-2SIO port B

s2InB	in	s2bCtl		;(10+1)see if a new character is present
	ani	s2Rdrf		;(7)
	rz			;(10)no character, return zero status
	in	s2bDat		;else return byte and non-zero status
	ret	

; Input from 88-SIO

sioIn	in	sioCtl		;(10+1)see if a new character is present
	cma			;(4)invert Rx Data Ready flag
	ani	sioRdrf		;(7)
	rz			;(10)no character, return zero status
	in	sioDat		;else return byte and non-zero status
	ret	

;-----------------------------------------------------------------------------
; Hardware specific I/O
; serOut - output character to port specified in a
;       0 = 88-2SIO port A
;       1 = 88-2SIO port B
;       2 = 88-SIO
;    inputs: a = port to transmit through
;	     b = character to send
;    clobbers a
;-----------------------------------------------------------------------------
serOut	dcr	a		;3-way test
	jz	s2OutB		;a was 1: 88-2SIO port B
	jp	sioOut		;a was 2: 88-SIO
				;a was 0: 88-2SIO port A

; Send character through 88-2SIO port A

S2OutA	in	s2aCtl		;wait for OK to transmit
	ani	s2Tdre
	jz	S2OutA

	mov	a,b		;a=character to transmit
	out	s2aDat		;send it
	ret

; Send character through 88-2SIO port B

s2OutB	in	s2bCtl		;wait for OK to transmit
	ani	s2Tdre
	jz	s2OutB

	mov	a,b		;a=character to transmit
	out	s2bDat		;send it
	ret

; Send character through 88-SIO

sioOut	in	sioCtl		;wait for OK to transmit
	ani	sioTdre
	jnz	sioOut		;active-low flag

	mov	a,b		;a=character to transmit
	out	sioDat		;send it
	ret


;****************************************************************************
;
; iCOM 3812/3712 disk I/O routines
;
;****************************************************************************

; FD3812 controller commands

cSTATUS	equ	00h		;read status
cREAD	equ	03h		;read sector
cWRITE	equ	05h		;write sector
cRDCRC	equ	07h		;validate sector CRC
cSEEK	equ	09h		;seek to track
cCLRERR	equ	0Bh		;clear error flags
cRESTOR	equ	0Dh		;seek to track 0
cSETTRK	equ	11h		;set track to seek
cDRVSEC	equ	21h		;set unit and sector
cWRTBF8	equ	30h		;write buffer for 3812
cWRTBF7	equ	31h		;write buffer for 3712
cRDBUF	equ	40h		;read buffer
cSHIFT	equ	41h		;shift read buffer
cRESET	equ	81h		;reset the controller
cLDCFG	equ	15h		;load configuration register

; Controller status bits

sBUSY	equ	01h		;1=busy
sCRCERR	equ	08h		;1=CRC error
sWRTPRT	equ	10h		;1=write protected
sNOTRDY	equ	20h		;1=drive not ready
sERROR	equ	29h		;any error

; Controller configure register bits

rDDEN	equ	10h		;double density
rFORMAT	equ	20h		;format mode

; Interface board registers

CMDOUT	equ	0C0h		;command output register
DATAIN	equ	0C0h		;data input from controller
DATAOUT	equ	0C1h		;data out to controller

;--------------------------------------------------------------
; dSelDrv - selects drive in drvNum and waits for it to
;    be ready. Resets the controller prior to the select
;    operation.
; On Entry:
;    drvNum = drive to be selected
; On Exit:
;    drvSel = drive select bits in position
;    clobbers b,c,h,l
;--------------------------------------------------------------
dSelDrv	mvi	c,cRESET	;reset the controller
	call	dCmd		

	lda	drvNum		;a=desired drive
	rrc			;move drive to bits 6,7
	rrc
	sta	drvSel		;save drive select bits
	ori	1		;insert a valid sector number
	
	mvi	c,cDRVSEC	;c=set drive/sector command
	call	dCmdDat		;set data from a, command from c
	ani	sNOTRDY		;test ready status
	rz			;drive is ready, return

	lxi	h,mNotRdy	;display not ready message
	call	dispMsg

dWtRdy	call	chkQuit		;check for ctrl-c
	in	DATAIN		;check drive status
	ani	sNOTRDY		;drive ready yet?
	jnz	dWtRdy		;no, check again

	ret			;drive is ready, return

;--------------------------------------------------------------
; dGetDen - detect disk density. Seeks to track 1 in double
;    density, and if that fails, then in single density to
;    determine disk density.
; On Entry:
;    Drive is selected
; On Exit:
;    dskDen = 0 if single density, rDDEN if double density
;    z true if success, else z false.
;    clobbers b,c,d
;--------------------------------------------------------------
dGetDen	xra	a		;init density to single
	sta	dskDen	
	mvi	d,1		;d=max track to try
	
dgLoop	lda	dskDen		;toggle density setting
	xri	rDDEN
	sta	dskDen
	jz	dgTest		;test single density, same track		

	dcr	d		;any more tracks to try?
	rm			;no, give up with z false

dgTest	mvi	c,cLDCFG	;set density
	call	dCmdDat

	mov	a,d		;set track for the seek
	inr	a		;d contains track-1
	mvi	c,cSETTRK
	call	dCmdDat	

	mvi	c,cSEEK		;issue seek, reads sector header
	call	dCmd		;  to verify track number
	rz			;no error, density found

	call	dClrErr		;clear the error
	call	dRestor		;return to track 0
	jmp	dgLoop

;--------------------------------------------------------------
; dWrite - Write sector on current track.
; On Entry:
;    Drive is selected, on proper track
;    hl->sector buffer
;    e = sector number to write
; On Exit:
;    Z flag set = good, Z flag clear = error
;    clobbers b,c,h,l
;--------------------------------------------------------------
dWrite	lda	ctl3712		;3712 controller?
	ora	a
	jnz	mv3712		;yes

; 3812 move data to controller buffer 
	
	mvi	b,0		;assume 256 bytes (DD)
	lda	trkDen		;check track density
	ora	a
	jnz	ini3812		;double density, go start the move
	
	mvi	b,SDSECL	;else move 128 bytes (SD)

ini3812	mvi	a,cWRTBF8	;setup write buffer command 
	out	CMDOUT
	
lp3812	mov	a,m		;get next byte to move
	out	DATAOUT		;give to controller
	inx	h		
	dcr	b		;decrement byte count
	jnz	lp3812

	xra	a		;clear command register
	out	CMDOUT

	jmp	dwDoWrt		;go execute the write

; 3712 move data to controller buffer
	
mv3712	mvi	b,SDSECL	;move 128 bytes

lp3712	mov	a,m		;get next byte to move
	out	DATAOUT		;set data for the controller
	inx	h		;increment buffer pointer
	
	mvi	a,cWRTBF7	;write it to the controller buffer
	out	CMDOUT
	xra	a		;return bit 0 low
	out	CMDOUT
				
	dcr	b		;decrement byte counter
	jnz	lp3712

; dwDoWrt - write the sector from the buffer just loaded

dwDoWrt	lda	drvSel		;set drive and sector
	ora	e
	mvi	c,cDRVSEC
	call	dCmdDat
	
	mvi	c,cWRITE	;issue write command
	call	dCmd
	ani	sERROR		;isolate error bits
	ret			;exit with status

;--------------------------------------------------------------
; dVerify - Verify sector by validating CRC. Tries RDTRIES
;    times to have a valid read.
; On Entry:
;    Drive is selected, on proper track
;    e = sector number to verify
; On Exit:
;    Z flag set = good, Z flag clear = error
;    clobbers b,c
;--------------------------------------------------------------
dVerify	lda	drvSel		;set drive and sector
	ora	e
	mvi	c,cDRVSEC
	call	dCmdDat
	
	mvi	b,RDTRIES	;b=retry counter

dvLoop	mvi	c,cRDCRC	;issue verify command
	call	dCmd
	ani	sERROR		;isolate error bits
	rz			;verified, exit

	dcr	b		;decrement try counter
	jnz	dvLoop

	inr	b		;force non-zero status
	ret			;exit with error

;--------------------------------------------------------------
; dSeek - Seek to track trkNum and set density in controller 
;    based on the destination track.
; On Entry:
;    trkNum = desired track
;    fmtCnfg = format bit for config register
; On Exit:
;    trkLen = length of destination track
;    trkDen = destination density, SD or DD (zero or not zero)
;    Zero true for success, non-zero for failure.
;    clobbers b,c,h,l
;--------------------------------------------------------------
dSeek	lda	trkNum		;a=desired track number
	ora	a		;track 0 requested?
	jnz	dsNot0		;no

	sta	trkDen		;set trkDen=0 (single density)
	lxi	h,SDTRKL	;set single density track length
	shld	trkLen
	jmp	dRestor		;use restore for track 0, not seek

dsNot0	mvi	c,cSETTRK	;set desired track number
	call	dCmdDat

	lxi	h,DDTRKL	;assume double density track length
	lda	dskDen		;a=0 if single, rDDEN for double
	ora	a		;single or double density?
	jnz	dSetDen		;double, go set density
	
	lxi	h,SDTRKL	;else, set single density length

dSetDen	shld	trkLen		;set proper track length
	sta	trkDen		;save density flag for new track
	mov	b,a		;preserve in b as well
	
	lda	ctl3712		;3712 controller?
	ora	a
	jnz	ds3712		;yes, skip load config register
	
	lda	fmtCnfg		;a=format bit for config register
	ora	b		;include density bit
	mvi	c,cLDCFG	;set config for single or double
	call	dCmdDat

ds3712	mvi	c,cSEEK		;seek to track
	call	dCmd
	ret			;return with seek status

;--------------------------------------------------------------
; dRestor - restore drive to track 0
; On Entry:
;    drive is selected
;    fmtCnfg = format bit for config register
; On Exit:
;    drive on track zero, single density selected, z true
;    clobbers c
;--------------------------------------------------------------
dRestor lda	ctl3712		;3712 controller?
	ora	a
	jnz	dr3712		;yes, skip load config register
	
	lda	fmtCnfg		;single density (0) plus fmt bit
	mvi	c,cLDCFG	;set controller to SD
	call	dCmdDat

dr3712	mvi	c,cRESTOR	;restore to track 0
	call	dCmd
	rz			;exit if no error

	lxi	h,mNoTrk0	;restore failed
	jmp	errMsg		;display error, start over

;--------------------------------------------------------------
; dNxtSec - determine next sector to write and return pointer
;    to the sector within trkBuf in hl. Must start with sector
;    e=-1 and call 26 times to read all sectors on a track.
; On Entry:
;    e = sector (1-26)
; On Exit:
;    e = sector incremented with interleave of 2
;    hl->sector in trkBuf
; Clobbers b,c,h,l
;--------------------------------------------------------------
dNxtSec	inr	e		;increment sector by 2
	inr	e
	mov	a,e		;see if time to wrap
	cpi	NUMSEC+1
	jc	dSecPtr		;no wrap needed
	
	mvi	e,2		;start over with even sectors

; dSecPtr - return sector buffer pointer in hl based on
;   sector number in e.

dSecPtr	mov	b,e		;form bc=256*(sector-1)
	dcr	b
	mvi	c,0

	lda	trkDen		;single or double density track?
	ora	a		;test for zero (also clears carry)
	jnz	haveOff		;double density, already have bc

	mov	a,b		;compute bc=128*(sector-1)
	rar			;a=MSB of (sector-1)/128
	mov	b,a		;b=MSB of (sector-1)/128

	rar			;put lsbit into msbit	
	ani	80h		;keep only msbit
	mov	c,a		;bc=128*(sector-1)

haveOff	lhld	trkPtr		;hl = start of current track buffer
	dad	b		;add sector offset from bc
	ret			;return hl->sector buffer		

;--------------------------------------------------------------
; dCmdDat - stores A in DATAOUT before COMMAND from C
; On Entry:
;    a = data
;    c = command
;
; dCmd - send COMMAND from C to controller
;
; On Entry:
;    c = command
;
; On Exit:
;    a = status
;--------------------------------------------------------------
dCmdDat	out	DATAOUT		;data passed in A to controller

dCmd	mov	a,c
	out	CMDOUT		;command from C to controller
	xra	a		;3812 cannot be left with cDRVSEC in
	out	CMDOUT		;  CMDOUT and 3712 must clear bit 0

dWtCmd	in	DATAIN		;wait for execution to complete
	ani	sBUSY
	jnz	dWtCmd

	in	DATAIN		;return status in A
	ani	sERROR		;mask error bits
	ret

;--------------------------------------------------------------------------
; dClrErr - Issue the clear errors command to the controller
;--------------------------------------------------------------------------
dClrErr	mvi	a,cCLRERR	;clear controller errors
	out	CMDOUT		;give command to the controller
	xra	a		;leave with examine status in CMDOUT
	out	CMDOUT		;  (3712 requires)
	ret

;--------------------------------------------------------------
; dChkWp - check and return write protect flag
; On Entry:
;    drive is selected
; On Exit:
;    returns if disk is not protected. Otherwise, an error
;    message is displayed and the program started over.
;--------------------------------------------------------------
dChkWp	xra	a		;examine status command
	out	CMDOUT

	in	DATAIN		;read status
	ani	sWRTPRT		;isolate write protect bit
	rz			;return if not protected

	lxi	h,mWrtPrt	;otherwise, display error message
	jmp	errMsg		;display error and restart

;--------------------------------------------------------------
; dFormat - format disk (for FD3812 only)
; On Entry:
;    drive is selected
; On Exit:
;    disk formatted, restored to track 0
;--------------------------------------------------------------
dFormat	lxi	h,mFmting	;hl->Formatting message
	call	dispMsg

	lxi	h,mSD		;hl->single density message
	lda	dskDen
	ora	a
	jz	dfDen		;display the density

	lxi	h,mDD		;hl->double density message
dfDen	call	dispMsg

	lxi	h,mDrvDen
	call	dispMsg		

; initialize for format

	mvi	a,cWRTBF8	;setup write buffer command 
	out	CMDOUT

	mvi	b,0		;b=byte count
	mvi	a,0E5h		;fill buffer with E5h
	
dfInit	out	DATAOUT		;fill controller buffer with E5h
	dcr	b		;decrement byte count
	jnz	dfInit

	xra	a		;return command register to zero
	out	CMDOUT

	sta	trkNum		;init track number to zero
	mvi	d,NUMTRK	;d=track counter

	mvi	a,rFORMAT	;set format bit for config reg
	sta	fmtCnfg

; dfLoop - format all tracks

dfLoop	call	chkQuit		;see if abort requested
	call	dSeek		;seek to track in trkNum
	
	lda	drvSel		;set drive and sector
	ori	1		;select sector 1
	mvi	c,cDRVSEC
	call	dCmdDat
	
	mvi	c,cWRITE	;issue write command to format
	call	dCmd		;   the track

	mvi	b,'.'		;display pacifier per track
	call	conout

	lxi	h,trkNum	;increment the track number
	inr	m
	dcr	d		;decrement track count
	jnz	dfLoop		;loop for entire disk

	xra	a		;clear format mode flag
	sta	fmtCnfg		;  locally...
	mvi	c,cLDCFG	;  and in the controller
	call	dCmdDat

	mvi	c,cRESET	;reset any errors
	call	dCmd

	call	dRestor		;restore to track 0

	lxi	h,mFmtDon	;display format complete message
	call	dispmsg

	ret


;**************************************************************************
; 
;  Data area
;
;**************************************************************************

; Disk variables

drvNum	db	MINDRV		;drive number to use
fmtCnfg	db	0		;format bit for config register
dskDen	db	0		;0=SD,1=DD
trkNum	ds	1		;track number to read
drvSel	ds	1		;drive select bits
trkDen	ds	1		;track density
trkPtr	ds	2		;pointer into trkBuf (start of next track)
trkLen	ds	2		;length of current track
bufEnd	ds	2		;end address + 1 of last track spot in RAM
bufEndN	ds	2		;bufEnd for 2nd through Nth buffer fill
wrRetry	ds	1		;disk write retry counter

; XMODEM variables

xfrPort	ds	1		;pseudo port for file transfer
blkNum	ds	1		;current xmodem block number
rcvBlk	ds	1		;block number received
rcvNBlk	ds	1		;"not" block number received
rcvPtr	ds	2		;pointer for start of XMODEM send

; Misc variables

cpmFlag	ds	1		;non-zero if running under CP/M
baseDrv	ds	1		;ascii 1st drive - baseDrv = 0
ctl3712	ds	1		;non-zero for 3712, 0=3812
	ds	96		;stack space
ourStk	equ	$

; Track buffer runs from here to the end of memory

trkBuf	equ	$

	end

