;******************************************************************************
;
;  PC2Flop - Transfer disk image from PC to floppy over serial port.
;    (Vector Graphic Dual-Mode Controller and Tandon 100-4M floppy)
;
;	This program writes a Tandon 660K floppy disk with a disk image
;	transmitted from a PC. The image is received through an 8251
;	serial port at I/O address 2 (1st port on a Bitstreamer II) or
;	at I/O address 4 (default on ZCB, 2nd port on Bitstreamer II).
;	The XMODEM checksum protocol is used for transfer. The image is
;	transferred in raw sector format (see below) starting with track
;	0, sector 0, byte 0 on the bottom head through track 76, then
;	continuing from track 0, sector 0, byte 0 on the top head through
;	track 76.
;
;	This program runs standalone at 0x100 or under CP/M. The program
;	is exited with a CTRL-C and either cold boots CP/M or exits to
;	the VG ROM monitor. 
;
;	The disk image format matches the Vector Graphic Image (VGI) format
;	defined by Howard Harte for his VG work on the Simh emulator. Each
;	sector in the PC file is 275 bytes in length, organized as follows:
;
;	Length	Content
;	   1	Sync byte (always 0xff)
;	   1	Track number (0-76)
;	   1	Sector number (0-15)
;	 266	Sector data payload (CP/M uses last 256 bytes of this)
;	   1	Checksum
;	   4	ECC for HD-FD controller
;	   1	ECC valid flag
;
;	Written by Mike Douglas
;
;	Rev	 Date	    Desc
;	1.0	09/11/14    Original
;
;	1.1	01/21/16    Use generic algorithms for buffering disk
;			    and XMODEM I/O to handle sector sizes that
;			    aren't multiples of the 128 byte XMODEM
;			    packet. Create standard disk driver entry
;			    points as defined for the generic model.
;
;	1.2	03/14/16    Optimize sector order during write and verify
;			    to avoid slipping rotations.
;
;*****************************************************************************

; VG Monitor Locations

VGMON	equ	0e000h		;base address of VG monitor
MONIT	equ	VGMON+0		;monitor entry point

; Disk information equates

NUMCYL	equ	77		;number of clyinders
NUMTRK	equ	154		;number of tracks (double sided)
NUMSEC	equ	16		;number of sectors per track
SECLEN	equ	275		;sector length (as transmitted)
MINDRV	equ	0		;first drive number
MAXDRV	equ	3		;max drive number 

TRKLEN	equ	(NUMSEC*SECLEN)	;length of track in bytes
RDTRIES	equ	5		;number of read (verify) attempts
WRTRIES	equ	4		;number of write attempts

; Monitor, CP/M boot entry points

MONITOR	equ	MONIT		;ROM monitor entry point (stand alone mode)
CPMBOOT	equ	0e80ch		;cold boot ROM entry point

; XMODEM equates

PKTLEN	equ	128		;128 byte xmodem packet length
SOH	equ	001h
EOT	equ	004h
ACK	equ	006h
NAK	equ	015h
EOF	equ	01ah		;ctrl-z character

; CP/M Equates

WBOOT	equ	0		;warm boot jump location

; Misc equates

CR	equ	13		;ascii for carriage return
LF	equ	10		;ascii for line feed
DEL	equ	7fh		;ascii DEL
BS	equ	08h		;ascii backspace
CTRLC	equ	03		;ascii for control-c
JMPINST	equ	0c3h		;jump instruction opcode

	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
	mvi	a,MINDRV	;a=default drive
	sta	drvNum		;need for pgmExit
	sta	curSec		;init current sector to a valid value

;  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		;compute 0 indexed drive number
	cpi	MAXDRV-MINDRV+1	;validate <= max drive and >= MINDRV
	jnc	getDrv		;invalid, prompt again

	adi	MINDRV		;restore offset of 1st drive (if any)

; special case for the VG dual-mode controller: Logical drive 0 and
;    1 are mapped to physical drive 1 and 0.

	cpi	2		;drive 0 or 1?
	jnc	not01		;no
	xri	1		;swap 0 and 1
not01	sta	drvNum		;save the drive number to use

; Verify drive is not write protected

	call	dRestor		;restore to track 0
	call	dChkWP		;check write protect (aborts to getDrv)

; getPort - get pseudo port number 1-2 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' goes to zero
	cpi	2		;validate <= '2' and >= '1'
	jnc	getPort		;invalid, prompt again

	inr	a		;binary 1-2
	sta	xfrPort		;save the pseudo 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

	mvi	a,0ffh		;init disk driver track number
	sta	curCyl		;to "invalid"
	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
	lda	eotFlag		;see if EOT terminated the reception
	cpi	EOT	
	jz	allDone		;if so, probably a single side disk
	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
	sta	eotFlag		;save status returned (may be EOT)
	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
	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	lxi	h,trkNum	;get desired track from trkNum	
	mov	l,m		;and put into l
	call	dSeek		;seek to the track

	call	dChkWP		;check write protect

	mvi	a,WRTRIES	;init write retry counter
	sta	wrRetry

; Writing on a new track is started at the the last sector accessed
;     plus 4. This delay accounts for track step and head settle time
;     plus some sector processing time.

	lda	curSec		;start at last track + 4
	adi	2		;(2 here, 2 in dNxtSec call)
	sta	curSec		;dNxtSec takes care of wrapping

rtryWrt	mvi	d,NUMSEC	;d=count of sectors to write

; wrtLoop - write a full track. Note that dNxtSec sequences through
;     sectors by 2 (or 3 at the odd/even switch) since this controller
;     can't read/write consecutive sectors. 

wrtLoop	call	dNxtSec		;curSec=next sector, bc=sector offset
	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;bc has offset of sector within track
	call	dWrite		;write the sector
	dcr	d		;decrement sector count
	jnz	wrtLoop		;loop until all sectors written

; Verify the track just written.

	mvi	d,NUMSEC	;d=count of sectors to write

vfyLoop	call	chkQuit		;check for ctrl-c
	call	dNxtSec		;curSec=next sector, bc=sector offset
	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;bc has offset of sector within track
	call	dVerify		;verify the sector
	jnz	vfyRtry		;error, go to retry logic

vfyNext	dcr	d		;decrement sector count
	jnz	vfyLoop		;loop until all sectors verified

; Track verified, increment trkPtr to memory location for next track
;    and exit.

	lhld	trkPtr		;hl=current track pointer
	lxi	d,TRKLEN	;de=bytes in a track
	dad	d		;hl=start of next track in trkBuf
	shld	trkPtr
	ret

;-------------------------------------------------------------------
; vfyRtry - verify failed, retry reads followed by a re-write
;   of the track in needed.
;-------------------------------------------------------------------
vfyRtry	mvi	a,RDTRIES	;init retry counter
	sta	rdRetry

retryLp	call	chkQuit		;check for ctrl-c
	call	dWtSec		;puts trkBuf offset of curSec in bc
	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;bc has offset of sector within track
	call	dVerify		;verify the sector
	jz	vfyNext		;success, go verify next sector

; Re-verify failed. Decrement retry count and try again if not zero. Once
;   retry counter expires, do another write.

	lxi	h,rdRetry	;decrement the read retry counter
	dcr	m
	jnz	retryLp		;try again

; Read re-tries expired, decrement the write retry count and re-write
;    if not expired

	lxi	h,wrRetry	;decrement the write retry counter
	dcr	m
	jnz	rtryWrt		;retry starting with the write
				;fall through to wrtFail

; Disk write failure. Display the track and sector with the
;    error. Restart the program.

	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
	jmp	dspAbrt		;display "aborted"

;-----------------------------------------------------------------------------
; badHdr - bad data in header that was to be written. Display the track,
;    sector, and buffer address. Restart the program.
; On entry, hl->sector in track buffer, stack has sector number in e
;    from pushing de.
;-----------------------------------------------------------------------------
badHdr	xchg			;de=sector buffer address
	lxi	h,hdrAddr	;hl->where to put ascii address
	call	ascAddr		;convert address in de to ASCII (hl)

	lxi	h,hdrTrk	;hl->where to put ascii decimal
	lda	trkNum		;a=track with error on it
	call	bin2dec		;track to ascii

	lxi	h,hdrSec	;hl->where to put ascii sector
	pop	d		;get sector number back in e
	mov	a,e		;a=sector where error occured
	call	bin2dec

	lxi	h,mHdrErr	;display the error message
	call	dispMsg

dspAbrt	lxi	h,mAbort	;hl->"Disk creation aborted"
	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. Timing assumes a 4MHz
;     processor and about 45 cycles to call serIn and return without a
;     character. Clobbers a, b and c.
;-----------------------------------------------------------------------------
rcvByte	push	d		;save d, e
	mov	d,a		;save timeout in d

initSec	lxi	b,ONESEC	;bc=cycles through this loop for 1s
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 3 ASCII digits at (HL)
;    with leading zero suppression.
;--------------------------------------------------------------
bin2dec	mvi	m,'0'		;initial 100's digit

loop100	sui	100		;count 100's
	jc	do10s		;done with 100's
	inr	m		;increment 100's digit
	jmp	loop100

do10s	adi	100		;replace extra 100 taken out
	inx	h		;move to 10's digit
	mvi	m,'0'		;initial 10's digit

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

; zero supress

	dcx	h		;back to start
	dcx	h
	mvi	a,'0'		;look for leading zero
	cmp	m		;100's zero?
	rnz			;no, we're done

	mvi	m,' '		;suppress leading 0 in 100's
	inx	h		;look at 10's
	cmp	m		;10's leading zero?
	rnz			;no

	mvi	m,' '		;suppress 10's leading zero
	ret

;-------------------------------------------------------------------------
; ascAddr - convert 16 bit address in de to ASCII and move to
;   buffer pointed to by hl.
;
; bin2Hex - convert byte in a to ASCII and move to buffer pointed 
;   to by hl.
;
; clobbers d,h,l
;-------------------------------------------------------------------------
ascAddr	mov	a,d		;do msb of addr in de first
	call	bin2hex
	mov	a,e		;do lsb of address
	
; bin2hex - convert value in a to ASCII and move to buffer pointed
;   to by hl. Clobbers d,h,l

bin2hex	mov	d,a		;save value in d
	rrc			;put msn into lsn
	rrc
	rrc
	rrc
	ani	0fh		;get nibble alone
	adi	'0'		;convert to printable ascii
	cpi	'9'+1		;see if > '9'
	jc	msnDone
	adi	7		;make A to F printable

msnDone	mov	m,a		;store in display buffer
	inx	h		;point to next spot

	mov	a,d		;get original byte again
	ani	0fh		;get lsb alone
	adi	'0'		;convert to printable ascii
	cpi	'9'+1		;see if > '9'
	jc	lsnDone		;no, lsn is done
	adi	7		;make A-F printable

lsnDone	mov	m,a		;store lsn in h
	inx	h		;point to next sport
	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			;nothing typed

	ani	7fh
	cpi	CTRLC		;abort requested?
	rnz			;no

; Ctrl-C typed while program is running. Return to drive prompt.

	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?
	ani	03h		;get drive number alone
	sui	MINDRV
	dcr	a		;test for drive 1 (0 and 1 swapped)
	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	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	JMPINST
	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	JMPINST		;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
;------------------------------------------------------------------
sizeRam	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,-((TRKLEN 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,TRKLEN	;bc=length of track in bytes

; Loop increasing hl by TRKLEN 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,-TRKLEN	;subtract one track length
	dad	b		;hl = end address of last track + 1
	shld	bufEnd		;save as bufEnd
	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

;-----------------------------------------------------------------------------
;   Data Area
;-----------------------------------------------------------------------------
mWelcom	db	cr,lf,lf
	db	'===== PC to Floppy Image Transfer, Version 1.2 ====='
	db	cr,lf
	db	'       (Vector Graphic Dual-Mode Controller)'
	db	cr,lf,lf
	db	'This program writes a 660K DSDD floppy from a disk image'
	db	cr,lf
	db	'received through a serial port using XMODEM.',0

mDrive	db	cr,lf,lf,'Insert and specify destination drive ('
mDrvMin	db	'x-'
mDrvMax	db	'x) or X to exit: ',0

mPort	db	cr,lf,lf
	db	'Specify the serial port to use for file transfer',cr,lf
	db	'  1) Bitstreamer II port A (2/3)',cr,lf
	db	'  2) Bitstreamer II port B or ZCB port A (4/5)',cr,lf
	db	'Enter port or X to exit: ',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

mStkTk0	db	cr,lf,lf,'Drive stuck on track 0',cr,lf,0

mNotRdy	db	cr,lf,lf,'Drive not ready',cr,lf,0

mDskErr	db	cr,lf,lf,'Write failure on track '
errTrk	db	'xxx, sector'
errSec	db	'xxx',0

mHdrErr	db	cr,lf,lf,'Bad header on track '
hdrTrk	db	'xxx, sector'
hdrSec	db	'xxx, address '
hdrAddr	db	'xxxx',0

mAbort	db	cr,lf,'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


;****************************************************************************
;
;  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.
;
;****************************************************************************

; 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	38462		;rcvByte loop count for 1 second (4MHz)

; VG Monitor I/O equates

KEYTST	equ	VGMON+3		;test keyboard in VG ROM monitor
KEYDATA	equ	VGMON+6		;input from keyboard in VG ROM monitor
CRT	equ	VGMON+9		;output to screen in VG ROM monitor

; Serial port equates for 8251 on Bitstreamer II and ZCB

PORTADR	equ	2		;port a is at 2/3
PORTASR	equ	3
PORTBDR	equ	4		;port b is at 4/5
PORTBSR	equ	5
XMTMASK	equ	1		;mask to isolate transmit ready bit
RCVMASK	equ	2		;mask to isolate receive ready bit

;----------------------------------------------------------------------------
; conIn - input character from console
;    inputs:
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;----------------------------------------------------------------------------
conIn	call	KEYTST		;call ROM monitor to test for console input
	rz			;return zero true if none

	jmp	KEYDATA		;get character typed from ROM monitor

;----------------------------------------------------------------------------
; conOut - output character to console
;    inputs: b = character to send
;    clobbers none
;----------------------------------------------------------------------------
conOut	mov	a,b		;monitor expects character in a
	jmp	CRT		;send/display it

;----------------------------------------------------------------------------
; serIn - input character from port specified in a
;    inputs: a = port to read from
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;    45 cycles when not returning a character
;-----------------------------------------------------------------------------
serIn	cpi	2		;port b?
	jz	serInB		;yes

; input from port A

	in	PORTASR		;a=port status register
	ani	RCVMASK		;isolate receive ready bit
	rz			;nothing present

	in	PORTADR		;a=character from serial port
	ret			

; input from port B

serInB	in	PORTBSR		;a=status register
	ani	RCVMASK		;isolate receive ready bit
	rz			;nothing present

	in	PORTBDR		;a=character from serial port
	ret			

;-----------------------------------------------------------------------------
; Hardware specific I/O
; serOut - output character to port specified in a
;    inputs: a = port to transmit through
;	     b = character to send
;    clobbers none
;-----------------------------------------------------------------------------
serOut	cpi	2		;port b?
	jz	serOutB		;yes

; output through port A

serOutA	in	PORTASR		;a=port status register
	ani	XMTMASK		;isolate transmit ready bit
	jz	serOutA		;not ready

	mov	a,b		;a=character to transmit
	out	PORTADR		;transmit the character
	ret	

; output through port B

serOutB	in	PORTBSR		;a=port status register
	ani	XMTMASK		;isolate transmit ready bit
	jz	serOutB		;not ready

	mov	a,b		;a=character to transmit
	out	PORTBDR		;transmit the character
	ret


;****************************************************************************
;
;  Disk driver for VG Dual-Mode Controller. Typically used with 5.25"
;  DSDD 100 tpi Tandon 100-4M drives.
;
;****************************************************************************

; Set STEPTIME to 3ms for Tandon drives, 30ms for Micropolis drives

STEPTIME	equ	3	;3ms step interval for Tandon
;STEPTIME	equ	30	;30ms step interval for Micropolis

; Controller bit masks

HEADMASK	equ	04H
WRITEMASK	equ	00H
READMASK	equ	20H
STPOUTMASK	equ	00H
STPINMASK	equ	40H
STEPMASK	equ	20h
TRK0MASK	equ	04h
WPROMASK	equ	01H
BUSYMASK	equ	02H
MOTORMASK	equ	04H
ECCMASK		equ	40H

; Controller port equates

CNTLPORT0	equ	0C0H
CNTLPORT1	equ	0C1H
STATUS0		equ	0C0H
STATUS1		equ	0C1H
DATAPORT	equ	0C2H
STARTPORT	equ	0C3H
RESETPORT	equ	0C3H


;-------------------------------------------------------------
; dWrite - Write a sector on current track
; On Entry:
;    curSec=sector to write
;    hl->buffer address
; On Exit:
;   clobbers b,c,h,l
;-------------------------------------------------------------
dWrite	push	d		;save registers
	push	h
	call	chkHdr		;writing reasonable data?
	pop	h
	jnz	badHdr		;no

; write 40 byte pre-amble of zeros

	in	RESETPORT	;reset FDC RAM counter
	mvi	b,40		;write 40 preamble zeros
	xra	a

dWrPre	out	DATAPORT	;store preamble bytes
	dcr	b
	jnz	dWrPre

; Write 270 bytes from RAM including the sync byte and checksum.
;    If wrong in the memory buffer, they'll be wrong on disk.

	mvi	b,(SECLEN-5)/2	;transferring 270 bytes

dWrLoop	mov	a,m		;get byte from RAM
	out	DATAPORT
	inx	h		;point to next byte

	mov	a,m		;two bytes per loop
	out	DATAPORT
	inx	h

	dcr	b		;decrement loop counter
	jnz	dWrLoop

; write four zeros for ECC bytes and AAh for ECC valid flag

	mvi	b,4		;write four zeros
	xra	a

dWrEcc	out	DATAPORT
	dcr	b
	jnz	dWrEcc

	mvi	a,0AAh		;ECC valid flag
	out	DATAPORT

; write 128 trailing zeros as post-amble

	mvi	b,128		;write 128 zeros
	xra	a

dWrPost	out	DATAPORT
	dcr	b
	jnz	dWrPost

; issue command to controller to write the data then exit

	call	doCmd		;b has 0 (write flag)
	pop	d		;restore de
	ret

	if	0		;dRead not used
;-------------------------------------------------------------
; dRead - Read sector on current track. Copies all 275 bytes
;    of the raw sector to (hl)
; On Entry:
;    curSec = sector number to read
;    hl->sector buffer
; On Exit:
;    Success = zero true (valid checksum, track, sector)
;    clobbers b,c,h,l
;-------------------------------------------------------------
dRead	push	d		;save de
	mvi	b,READMASK	;b=R/W bit for control 0
	call	doCmd		;do read command
	rnz			;timeout

	in	RESETPORT	;reset FDC RAM counter
	in	DATAPORT	;read sync byte	
	shld	hdrPtr		;save pointer to sector header
	mov	m,a		;save sync byte
	inx	h

; dRdLoop - Read loop. Read 268 bytes of sector content two bytes
;    at a time computing checksum in B. 

	xra	a		;clear carry for checksum calc
	lxi	b,(SECLEN-7)/2	;b=0 checksum, c=268 bytes/2

dRdLoop	in	DATAPORT	;get next byte
	mov	m,a		;store next byte in buffer
	inx	h		;bump memory pointer
	adc	b		;add to checksum
	mov	b,a		;and save

	in	DATAPORT	;two bytes per loop
	mov	m,a		;store next byte in buffer
	inx	h		;bump memory pointer
	adc	b		;add to checksum
	mov	b,a		;and save

	dcr	c		;decrement byte count
	jnz	dRdLoop

; read and verify checksum

	in	DATAPORT	;a=checksum byte
	mov	m,a		;store in read buffer
	inx	h
	cmp	b		;compare with computed checksum
	pop	d		;restore de
	rnz			;return if error

; move ECC bytes to buffer

	mvi	c,5		;move 5 ECC bytes

dRdEcc	in	DATAPORT
	mov	m,a
	inx	h
	dcr	c
	jnz	dRdEcc

	endif

; chkHdr - checksum OK, verify sync, track, and sector

	lhld	hdrPtr		;hl->sector header
chkHdr	mov	a,m		;a=expected sync byte
	inr	a		;ff goes to zero
	rnz			;not ff, error

	inx	h		;hl->track number read
	lda	curCyl		;a=expected track number
	cmp	m		;right track?
	rnz			;no, exit with error

	inx	h		;hl->sector number read
	lda	curSec		;a=sector requested
	cmp	m		;right sector found?
	ret			;return with status

;-------------------------------------------------------------
; dVerify - Verify sector on current track. Compares all 270
;    bytes of the sector including sync, sector header and
;    checksum.
; On Entry:
;    curSec = sector number to verify
;    hl->sector buffer to compare against
; On Exit:
;    Success = zero true (valid compare)
;    clobbers b,c,h,l
;-------------------------------------------------------------
dVerify	push	d		;save de
	mvi	b,READMASK	;b=R/W bit for control 0
	call	doCmd		;do read command
	rnz			;timeout

; dVfLoop - Verify loop. Compare 270 bytes to RAM buffer pointed
;    to by hl (sync byte, track #, sec #, 266 byte buffer, cksum). 

	in	RESETPORT	;reset FDC RAM counter
	mvi	c,(SECLEN-5)/2	;c=270 bytes/2

dVfLoop	in	DATAPORT	;get next byte
	cmp	m		;match buffer?
	jnz	dVfErr		;no, have an error
	inx	h		;bump memory pointer

	in	DATAPORT	;two bytes per loop
	cmp	m		;match buffer?
	jnz	dVfErr		;no, have an error
	inx	h		;bump memory pointer

	dcr	c		;decrement byte count
	jnz	dVfLoop

; All bytes verified including sync and checksum bytes

dVfErr	pop	d		;restore de
	ret			;return with status

;-------------------------------------------------------------
; doCmd - Issue command to controller and wait for it to
;    complete.
; On Entry:
;    b = R/W bit mask for output to control port 1
; On Exit:
;    Success = zero true
;    clobbers d,e
;-------------------------------------------------------------
doCmd	push	h		;preserve hl
	call	slctRw		;set R/W and select
	pop	h		;restore hl

	in	RESETPORT	;reset FDC RAM counter
	out	STARTPORT	;start the I/O command

	lxi	d,4000h		;de=timeout counter

cmdWait	in	STATUS1		;wait for completion with timeout
	ani	BUSYMASK	;done?
	rz			;yes
	
	dcx	d		;decrement timeout counter
	mov	a,e
	ora	d
	jnz	cmdWait

	inr	a		;force non-zero return
	ret			;exit with error

;-------------------------------------------------------------
; dChkWP - check if disk is write protected.
; On Entry:
;    drive already selected
; On Exit:
;    returns if OK to write, otherwise, restarts the program
;    asking for the disk prompt
; clobbers h,l
;-------------------------------------------------------------
dChkWP	in	STATUS0		;test for write protect
	ani	WPROMASK
	rz			;not protected

; Protected, display message and restore state to start
;    of program

	lxi	h,mWrtPrt	;otherwise, display error message
	call	dispMsg		;display the error
	lxi	sp,ourStk	;re-init stack pointer
	jmp	getDrv		;go back to drive prompt

;-------------------------------------------------------------
; dNxtSec - For this FDC, this function increments the sector
;    number by two, except for the call between the 8th and
;    9th sector for which the step is 3 to transition between
;    odd and even sectors. Processing then falls through to
;    dWtSec to get the address of the new sector.
; On Entry:
;    d=sector counter (16 down to 1)
;    curSec = most recent sector number
; On Exit:
;    curSec = next sector number
;    bc = byte offset of this sector in a track
;    clobbers h,l
;-------------------------------------------------------------
dNxtSec	lxi	h,curSec	;increment curSec by 2
	mov	c,m
	inr	c
	inr	c		;c=curSec+2

	mov	a,d		;a=sector down counter
	cpi	8		;halfway through?
	jnz	dnNot3		;no

	inr	c		;e=curSec+3, switch odd/even

dnNot3	mov	a,c		;past last sector?
	sui	NUMSEC
	jc	dnNoWrp		;no, sector in c is correct
	
	mov	c,a		;16->0, 17->1, etc.

dnNoWrp	mov	m,c		;update curSec
	jmp	dSecAdr		;get sector address

;-------------------------------------------------------------
; dWtSec - Wait for specified sector. For this FDC, this 
;    function simply returns BC with the offset of this
;    sector within a track.
; On Entry:
;    curSec = desired sector number
; On Exit:
;    e = sector number
;    bc = byte offset of this sector in a track
;    clobbers h,l
;-------------------------------------------------------------
dWtSec	lxi	h,curSec	;put curSec into c
	mov	c,m

dSecAdr	lxi	h,mult275	;hl->"multiply by 275" table
	mvi	b,0		;de=sector number
	dad	b		;compute offset of sector in table
	dad	b		;2 bytes per entry
	mov	c,m		;get bc=sec*275
	inx	h
	mov	b,m		;bc=byte offset of sector within a track
	ret

;------------------------------------------------------------------
; mult275 - table to multiply byte 0-15 by 275.
;------------------------------------------------------------------
mult275	dw	00*275,01*275,02*275,03*275,04*275,05*275,06*275,07*275
	dw	08*275,09*275,10*275,11*275,12*275,13*275,14*275,15*275	
	

;-------------------------------------------------------------
; dSeek - Seek Track. The motor timeout is restarted 
;    as a result of this call.
; On Entry:
;    curCyl = current cylinder, FFh means we don't know
;    l = desired track
; On Exit:
;    curCyl = current cylinder
;    drvNum updated with head select for bottom or top
; trashes all registers
;-------------------------------------------------------------
dSeek	mov	c,l		;save track number in c
	call	select		;ensure drive selected

; determine cylinder number from the track number and set the
;    head bit (top or bottom) in the drvNum byte.

	lxi	h,drvNum	;hl->drive/head select byte	
	mov	a,m		;a=drvNum byte
	ani	(NOT HEADMASK) AND 0ffh    ;assume bottom head
	mov	m,a		;drvNum with head bit cleared

	mvi	a,NUMCYL-1	;a=maximum cylinder number
	sub	c		;a=max cylinder - requested track
	jnc	doSeek		;tracks 0-76 are on bottom

; track is on the top side. Convert tracks 77-153 to 0-76 and set
;   head bit to 1 (top side)

	cma			;convert (76-track) to (track-77)
	mov	c,a		;c=target cylinder number

	mov	a,m		;update drvNum with top head set
	ori	HEADMASK
	mov	m,a

; c updated (if required) with proper cylinder number. Do the seek.

doSeek	lxi	h,curCyl	;hl->current cylinder
	mvi	a,0ffh		;has head position been initialized?
	cmp	m
	cz	dRestor		;no prev track, restore to zero

	mov	a,c		;a=requested cylinder
	sub	m		;a=requested-current
	rz			;already there

	mov	m,c		;save new cylinder number
	jc	seekOut		;requested < current, seek out

seekIn	call	stepIn		;step in # tracks in A
	dcr	a
	jnz	seekIn
	jmp	skExit

seekOut	call	stepOut		;step out negative # tracks in A
	inr	a
	jnz	seekOut

skExit	call	settle		;wait head settle time
	ret

;-------------------------------------------------------------
; dRestor - Restore to track 0. Selects the drive
;    and starts the motor if needed.
; On Exit:
;   curCyl=0 if track 0 found
; trashes d,e
;-------------------------------------------------------------
dRestor	push	h
	push	b

	call	select		;make sure drive is selected
	lxi	h,curCyl	;init current track to "bad"
	mvi	m,0ffh

	mvi	a,3		;first, step in a few tracks
rstor10	call	stepIn
	dcr	a
	jnz	rstor10

	mvi	c,NUMCYL+10	;c=maximum number of steps to try
rstor20	call	stepOut		;step out
	in	STATUS0	
	ani	TRK0MASK	;test for track 0
	jnz	rstor30		;have track 0

	dcr	c		;decrement track count
	jnz	rstor20		;and step out again

; max number of steps issued without finding track 0. Abort.

	lxi	h,mNoTrk0	;track 0 not found message
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

rstor30	call	settle		;wait head settling time
	mvi	m,0		;set cylNum to track 0

	pop	b
	pop	h
	ret

;-------------------------------------------------------------
; stepIn - step head in towards higher numbers
; stepOut - step head out towards lower numbers
;   Clobbers B,D,E preserves A
;-------------------------------------------------------------
stepIn	mvi	b,STPINMASK
	jmp	stepit

stepOut	mvi	b,STPOUTMASK

stepit	push	a
	push	h

	lda	drvNum		;get drive and head selection
	ora	b		;include step direction
	out	CNTLPORT0

	ori	STEPMASK	;now issue the step...
	out	CNTLPORT0	;set step bit
	nop
	ani	(NOT STEPMASK) AND 0ffh
	out	CNTLPORT0	;clear step bit

	lxi	d,STEPTIME	;3ms or 30ms step time
	call	timer		;delay step time

	pop	h
	pop	a
	ret

;-------------------------------------------------------------
; select - select drive specified in the drive bits of drvNum
; slctRw - R/W mask supplied in b
;
; clobbers d,e,l
;-------------------------------------------------------------
select	mvi	b,READMASK	;default to read
slctRw	lda	curSec		;form control 0 byte
	ora	b		;put in read/write bit
	ori	ECCMASK		;enable ECC logic
	out	CNTLPORT1

	in	status1		;get motor status
	ani	MOTORMASK
	mov	l,a		;l=motor status

	lda	drvNum		;current drive, head select
	out	CNTLPORT0
	
	mov	a,l		;test motor status
	ora	a
	cz	motDly		;wait for motor to spin up
	ret

;-------------------------------------------------------------
; motDly - Motor startup delay for 0.75 seconds (longer
;    than the "normal" 0.25 seconds to be safe).
; settle - Wait head settle time of 15ms
; timer - Wait # ms specified in DE
;   Clobbers DE
;-------------------------------------------------------------
motDly	lxi	d,750		;0.75s delay
	jmp	timer

; settle - Wait head settle time of 15ms

settle	lxi	d,15		;15ms delay
				;fall into timer

; timer - Wait # ms specified in DE

timer	lda	drvNum		;retrigger 4 second timer
	out	CNTLPORT0

timer10	mvi	a,191		;a=inner loop count, about 1ms

timer20	sui	1		;(7)	
	ora	a		;(4)
	jnz	timer20		;(10)

	dcx	d		;(4)	
	mov	a,e		;(4)
	ora	d		;(4)
	jnz	timer10		;(10)

	ret


;-------------------------------------------------------------
; RAM STORAGE REQUIRED FOR DRIVER
;-------------------------------------------------------------
hdrPtr	ds	2		;pointer to sector header (track #, sec #)


;**************************************************************************
; 
;  Data area
;
;**************************************************************************
; disk variables

curCyl	ds	1		;current cylinder number drive is on
trkNum	ds	1		;track number to read
curSec	ds	1		;current sector number
drvNum	ds	1		;current drive and head for CNTLPORT0
trkPtr	ds	2		;pointer into trkBuf (start of next track)
bufEnd	ds	2		;end address + 1 of last track spot in RAM

; retry logic variables

rdRetry	ds	1		;disk read retry counter
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
eotFlag	ds	1		;set to EOT if EOT terminated reception
	ds	64		;stack space
ourStk	equ	$

; track buffer runs from here to the end of memory

trkBuf	equ	$

	end

