;******************************************************************************
;
;  PC2Flop - Transfer disk image from PC to floppy over serial port.
;	   (North Star DD FDC and Altair 88-2SIO or 88-SIO serial port)
;
;	This program writes a North Star double density disk from a disk 
;       image transmitted from a PC. The image is transmitted through an
;	Altair 88-2SIO or 88-SIO serial port using the XMODEM checksum
;	protocol. The image is written directly to the floppy in raw format
;	(512 bytes per sector, 10 sectors per track, 35-SS or 70-DS tracks).
;	The disk image is the linear sequence of 512 byte sectors from
;	track 0, sector 0 through track 34 (69), sector 9.
;
;	This program is for the North Star double density controller.
;       A different version of PC2Flop is required for the older single
;	density controller.
;
;	This program runs standalone at 0x100 or under CP/M. The program
;	is exited with a CTRL-C and either warm boots CP/M or just starts
;       over when running stand alone.
;
;	Written by Mike Douglas
;
;	Rev	 Date	    Desc
;	1.0	05/11/18    Original
;
;*****************************************************************************

; Disk information equates

NUMTRK	equ	70		;number of tracks (double sided)
NUMCYL	equ	35		;number of cylinders
NUMSEC	equ	10		;number of sectors per track
SECLEN	equ	512		;sector length (as transmitted)
MINDRV	equ	1		;first drive number
MAXDRV	equ	4		;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	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	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
	call	s2Init		;initialize the 88-2SIO ports
	mvi	a,MINDRV	;a=default drive
	sta	drvNum		;need for pgmExit
	mvi	a,NDBLDEN	;init order register to double density
	sta	ctlOrd

;  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
	jm	getDrv		;error, entry was < first drive
	cpi	MAXDRV-MINDRV+1	;validate <= max drive
	jnc	getDrv		;invalid, prompt again
	adi	MINDRV		;restore offset of 1st drive (if any)
	sta	drvNum		;save the drive number to use

; Verify drive is not write protected

	call	dRestor		;restore to track 0 (selects drive)
	call	dChkWP		;check write protect (aborts to getDrv)

; getPort - get transfer 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'		;un-ascii
	cpi	3		;validate ascii 1-3
	jnc	getPort		;invalid, prompt again
	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 cylinder 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
rtryWrt	mvi	d,NUMSEC	;d=count of sectors to write

; wrtLoop - write a full track starting with any sector

wrtLoop	call	dNxtSec		;wait for next (any) sector sync
	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

	call	dNxtSec		;force a 1 sector delay for trim erase
	mvi	d,NUMSEC	;d=count of sectors to write
vfyLoop	call	chkQuit		;check for ctrl-c
	call	dNxtSec		;wait for next (any) sector sync
	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		;wait for sector specified in e
	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 disk fail 
;-----------------------------------------------------------------------------
; 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
	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	;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 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
	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?
	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	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
;------------------------------------------------------------------
; 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,-((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.0 ====='
	db	cr,lf,lf	
	db	'This program writes a North Star double density floppy from'
	db	cr,lf
	db	'a disk image received through an Altair 88-2SIO or 88-SIO'
	db	cr,lf
	db	'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

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 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

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


;****************************************************************************
;
;  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		;return character 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		;return character 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		;return character 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		;return character 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


;****************************************************************************
;
; North Star disk I/O routines.
;   Based on code for the single density controller written by 
;   M Eberhard. Modified for this program and for the DD controller
;   by M Douglas.
;
; Commands are issued to the North Star controller by reading memory
; locations. Data and Control bits to the controller are embedded
; in the read addresses. What you get back depends on what you
; read - either one of 3 status bytes, or disk data. The commands
; constructed below take care of this.
;
; 3 RAM locations are needed:
;    curCyl is used to remember the current cylinder
;           and should be initialized to 0FFh
;    drvNum remembers the current unit. On the N* controller,
;           disks are 1 to 4. 0 means none selected.
;    ctlOrd is a saved copy of the controller order byte

;************************************************
; North Star disk Controller Addresses & Commands
; (Read or write from address to execute command)
;************************************************
NSCTRL	equ	0E800h		;North Star disk controller
NSBOOT	equ	NSCTRL		;disk boot PROM entry point
NWRITE	equ	NSCTRL+100h	;write prefix (data in low byte)
NORDER	equ	NSCTRL+200h	;controller order base address
NCOMND	equ	NSCTRL+300h	;commands base address

NASTAT	equ	NCOMND+10h	;read A status
NBSTAT	equ	NCOMND+20h	;read B status
NCSTAT	equ	NCOMND+30h	;read C status
NMOTRA	equ	NCOMND+15h	;motor on, read A status
NRBYTE	equ	NCOMND+40h	;read a byte of disk data
NWRCRD	equ	NCOMND+16h	;start write-sector sequence
NRSEFL	equ	NCOMND+11h	;reset sector flag, read A status


;****************************
; Controller Order Register Bits
;****************************
NDBLDEN	equ	080h		;select double density mode

NTOPSID	equ	040h		;select top side of DS disk

NSTPIN	equ	020h		;step in

NPREON	equ	020h		;write pre-comp on
NPREOFF	equ	0dfh		;write pre-comp off
NPRETRK	equ	21		;precomp on starting with this track

NSTEPH	equ	010h		;assert step line
NSTEPL	equ	0efh		;de-assert step line


;****************************
; Status Register Bits
;****************************
; Status Register A

NSBDY	equ	1		;body (sync chr found)
NSRE	equ	4		;read enable (PLL locked)
NSWIND	equ	8		;in 96us pre-sector window

; Status Register B

NSTR0	equ	1		;track 0
NSWP	equ	2		;write protect

; Status Register C (sector position

NSSP	equ	0Fh		;sector position

; All status registers

NSMO	equ	10h		;motor on flag
NSDD	equ	20h		;double density flag
NSWN	equ	40h		;window flag
NSSF	equ	80h		;Nsector flag

;**************************
; Disk Controller constants
;**************************
SYNC	equ	0FBh		;header sync charactor
WRMUP	equ	32h		;warm up time in sectors
SETL	equ	0Dh		;head settling time in sectors
SWAIT	equ	08Ch		;max wait for body before error
MAXUNT	equ	3		;max disk drive unit (0 not allowed)
MAXTRK	equ	39		;max track
MAXSEC	equ	9		;max sector (SA400 and SA400L)

;**************************
;Disk Subsystem Subroutines
;**************************

	if	0		;read not used in PC2FLOP
;***Subroutine**************************************
; dRead - Read sector on current track.
; On Entry:
;    Drive is selected, motor is running and sector
;        sync was just previously detected
;    hl->sector buffer
; On Exit:
;    clobbers b,c,h,l
;***************************************************
dRead:	mvi	b,SWAIT		;max time until body

;wait for RE bit (PLL lock) 

NSRD1:	dcr	b
	jz	NOSYNC		;timeout, sync not found
	lda	NASTAT		;look for RE, PLL lock flag
	ani	NSRE
	jz	NSRD1		

;wait for body bit (sync character found)

NSRD2:	dcr	b
	jz	NOSYNC		;timeout, sync not found
	lda	NASTAT		;look for "body" flag in stat A
	rrc			;test for NSBDY bit
	jnc	NSRD2

	lxi	b,0		;init CRC (b), byte count (c)

;read sector data two bytes at a time (512 bytes total)

NSRD3:	lda	NRBYTE		;read a byte
	mov	m,a		;save it
	xra	b		;compute checksum
	rlc
	mov	b,a
	inx	h		;bump pointer

	lda	NRBYTE		;read a byte
	mov	m,a		;save it
	xra	b		;compute checksum
	rlc
	mov	b,a
	inx	h		;bump pointer

	dcr	c		;256*2 byteS
	jnz	NSRD3

;check CRC

	lda	NRBYTE		;get disk CRC
	xra	b		;compare to computed CRC
	ret			;Z flag set if OK, else error
	endif

NOSYNC:	dcr	b		;b=0ffh
	ret			;return with zero false

;***Subroutine**************************************
; dVerify - Verify sector on current track
; On Entry:
;    Drive is selected, motor is running and sector
;        sync was just previously detected
;    hl->buffer to compare against
; On Exit:
;    clobbers b,c,h,l
;***************************************************
dVerify: mvi	b,SWAIT		;max time until body

;wait for RE bit (PLL lock) 

NSVF1:	dcr	b
	jz	NOSYNC		;timeout, sync not found
	lda	NASTAT		;look for RE, PLL lock flag
	ani	NSRE
	jz	NSVF1

;wait for body bit (sync character found)

NSVF2:	dcr	b
	jz	NOSYNC		;timeout, sync not found
	lda	NASTAT		;look for "body" flag in stat A
	rrc			;test for NSBDY bit
	jnc	NSVF2

	lxi	b,0		;init CRC (b), byte count (c)

;read and verify sector data two bytes at a time (512 bytes)

NSVF3:	lda	NRBYTE		;read a byte
	cmp	m		;compare to validate data
	rnz			;match fail
	xra	b		;compute checksum
	rlc
	mov	b,a
	inx	h		;bump pointer

	lda	NRBYTE		;read a byte
	cmp	m		;compare to validate data
	rnz			;match fail
	xra	b		;compute checksum
	rlc
	mov	b,a
	inx	h		;bump pointer

	dcr	c		;256*2 bytes
	jnz	NSVF3

;check CRC

	lda	NRBYTE		;get disk CRC
	xra	b		;compare to computed CRC
	ret			;Z flag set if OK, else error

;***Subroutine**************************************
; dWrite - Write a sector on current track
; On Entry:
;    Drive is selected, motor is running and sector
;        sync was just previously detected
;    hl->buffer address
; On Exit:
;   clobbers b,c,h,l
;***************************************************
dWrite:	push	d		;save de 
	lda	NWRCRD		;issue write command to controller

NSWR1:	lda	NASTAT		;wait for pre-sector window to pass
	ani	NSWIND
	jnz	NSWR1

;write sector header

	lxi	b,NWRITE	;bc = write command, data zero
	lxi	d,001FH		;number of header zeros (31)
				;init CRC calculation (d)

NSWR2:	ldax	b		;write a zero
	mov	a,a		;kill some time
	mov	a,a		;kill some time
	dcr	e		;count header zeros
	jnz	NSWR2

;write two sector sync characters

	mvi	c,SYNC		;write sync chr
	ldax	b
	mov	a,a		;kill some time
	mov	a,a		;kill some time
	mvi	c,SYNC		;kill some time
	ldax	b

;write  sector data 2 bytes at a time (512 bytes)

NSWR3:	mov	c,m		;get chr from RAM
	mov	a,c		;Calculate CRC
	xra	d		;keep it in d
	rlc
	mov	d,a
	ldax	b		;write chr to disk
	inx	h		;bump pointer

	mov	c,m		;get chr from RAM
	mov	a,c		;Calculate CRC
	xra	d		;keep it in d
	rlc
	mov	d,a
	ldax	b		;write chr to disk
	inx	h		;bump pointer

	dcr	e		;write 256*2 bytes
	jnz	NSWR3

;write sector CRC

	mov	c,d		;Write CRC
	ldax	b
	
	pop	d		;restore de
	ret			;don't wait for end of sector

;***Subroutine**************************************
; dChkWP - check if disk is write protected. 
; On Entry:
;    disk already selected
; On Exit:
;    returns if disk is not protected. Otherwise, an error
;    message is displayed and the program started over.
; clobbers h,l
;***************************************************
dChkWP	lda	NBSTAT		;check for write protect
	ani	NSWP
	rz			;return if not protected

; Disk write protected. Display error and restart program.

	lxi	h,mWrtPrt	;otherwise, display error message
	call	dispMsg		;display the error
	lxi	sp,ourStk	;re-init stack point
	jmp	getDrv

;***Subroutine**************************************
; dSeek - Seek Track. The motor timeout is restarted 
;    as a result of this call. After the seek, a controller
;    order is issued to turn on/off write precomp and
;    to select the bottom/top head.
; On Entry:
;    curCyl = current cylinder FFh means we don't know
;    l = desired track
; On Exit:
;    curCyl = current cylinder
; trashes all registers
;***************************************************
dSeek:	call	NSELCT		;select drive, spin up
	
;determine cylinder number from the track number and set the
;  side bit properly in a copy of the controller order register.

	lda	ctlOrd		;put the order register with bottom
	mov	c,a		;   selected into c
	mvi	a,NUMCYL-1	;a=maximum cylinder number
	sub	l		;a=max cylinder - requested track
	jp	NSSEEK		;tracks 0-34 are on bottom, go do seek

;track is on the top side. Tracks 35 to 69 correspond to
;  cylinders 34 down to 0.

	adi	NUMCYL		;a=(2*NUMCYL-1)-track
	mov	l,a		;update l with actual cylinder number
	mov	a,c		;set top side bit in order register
	ori	NTOPSID
	mov	c,a		;c=order register with top side set

;compute the required number of steps and the direction

NSSEEK:	push	b		;save c with the controller order

	lda	curCyl		;where are we?
	cpi	0ffh
	cz	dRestor		;lost: restore

	sub	l		;a= +/- steps required
	jz	NSSKPC		;already there, go do precomp

	mvi	b,0		;b=step direction, assume out for now
	jp	NSSKO		;current>desired, seek out

	mvi	b,NSTPIN	;desired<current, seek in
	cma			;compute a=-a
	inr	a

;put number of steps in h, set step direction in e, save target cylinder
;   in curCyl, then do the steps.

NSSKO:	mov	h,a		;h=number of steps
	mov	a,l		;save what will be the new cylinder
	sta	curCyl
	lda	ctlOrd		;form order register with step direction
	ora	b		;  in e
	mov	e,a		;
	call	NSSTEP		;step h tracks in or out

;update the order register with the precomp bit and the side select
;  bit and issue the order to the controller. This is the last
;  order issued before reads and writes take place.

NSSKPC:	pop	b		;c=order register with side select
	lda	curCyl		;a=current cylinder number
	cpi	NPRETRK		;at or above precomp track #?
	jm	NSSKNP		;no precomp needed
	mov	a,c		;a=current controller order
	ori	NPREON		;turn on precomp bit
	mov	c,a		;c=final order register value
NSSKNP:	mvi	b,NORDER/256	;b=msb of order command address
	ldax	b		;set side and precomp
	ret

;***Subroutine*************************
; dRestor - Restore to track 0
; On Exit:
;   Z set and curCyl=0 if track 0 found
; trashes psw,bc,e,h
;**************************************
dRestor: call	NSELCT		;select drive, spin up

;step in once, in case the head is lost

	lda	ctlOrd		;a=current order register
	ori	NSTPIN		;assert step in
	mov	e,a		;e=step in order
	mvi	h,1
	call	NSSTEP		;step in now
	jz	STUCK0		;z: Stuck on track 0

;step out enough times that we should find track 0 even
;if the SA400's actuator is out of its spiral groove

	mvi	h,MAXTRK+16	;more than enough
	lda	ctlOrd		;a=saved order register
	mov	e,a		;e=step out (always saved that way)
	call	NSSTEP		;steppin' out to track 0
	rz			;z set: found track 0

;Error: track 0 not detected despite many steps outward

	lxi	h,mNoTrk0
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;Error: stuck on track zero

STUCK0:	lxi	h,mStkTk0	;stuck on track zero message
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;***Subroutine*************************
; Step head
; On Entry:
;   h = number of steps, >0
;   e = step order (in or out)
; On Exit:
;   Z set and curCyl=0 if track 0 found
; trashes psw,bc,d,h
;**************************************
NSSTEP:	mvi	d,NORDER/256	;d=msb of order command address
	ldax	d		;set step direction

NSSTP1:	mov	a,e		;a=order command
	ori	NSTEPH		;set step line high
	mov	e,a
	ldax	d		;issue step

	mov	a,e		;a=order command
	ani	NSTEPL		;set step line back low
	mov	e,a
	ldax	d		;finish step

	mvi	b,2		;step timer
	call	NSWAIT		;wait 2 sector times

	lda	NBSTAT		;at track 0?
	ani	NSTR0
	jnz	NSSTP2		;yes, stop stepping

	dcr	h		;no, step again
	jnz	NSSTP1		;stepped enough?

	inr	a		;clear Z: not track 0
	ret

;on track 0. clear curCyl, and return with Z set

NSSTP2:	xra	a		;set Z: track 0
	sta	curCyl
	ret

;***Subroutine********************************
; NSELCT - Select drive. Turn on the motor if necessary,
;    wait for head settle time.
; On Entry:
;   drvNum = desired drive (1-4)
; trashes psw,bc
;*********************************************
NSELCT: mvi	b,WRMUP-SETL	;warmup motor time

;long delay if motors are off

	lda	NASTAT		;get current motor status
	ani	NSMO		;already on?
	lda	NMOTRA		;turn on drive motors
	cz	NSWAIT		;no, spin up

;encode drive  number as a bit position and store in the
;  memory image of the controller order register

	lda	drvNum
	cpi	3
	jc	drvSet		;1->1, 2->2
	rlc			;3->6, 4->8
	ani	0ch		;3->4, 8->8

;form and save controller order register with drive bit

drvSet:	mov	b,a		;b=encoded drive value
	lda	ctlOrd		;a=current order register
	ani	0f0h		;clear out drive bits
	ora	b		;a=order register with drive bits
	sta	ctlOrd		;save in ctlOrd
	mvi	b,NORDER/256	;b=msb of order command address
	mov	c,a		;c=order register to write
	ldax	b		;select drive

;wait for settling time after head load

	mvi	b,SETL		;head settling time

;fall into NSWAIT to stall

;***Subroutine***********************
; Wait for b sectors
; On Entry:
;   b = number of sectors to wait for
; trashes psw,b
;************************************
NSWAIT:	lda	NRSEFL		;reset sector flag
	lda	NMOTRA		;restart motor timeout	

NSWAT1:	lda	NASTAT		;get status a
	rlc			;test NSSF, sector flag set?
	jnc	NSWAT1		;no

	dcr	b		;wait for b sectors to pass
	rz

	jmp	NSWAIT

;***Subroutine******************************
; dWtSec - Wait for specified sector.
; On Entry:
;    Drive is selected, motor is running
;    e = desired sector number
; On Exit:
;    drive is at proper sector
;    e = sector number
;    bc = byte offset of this sector in a track
;*******************************************
dWtSec: lda	NRSEFL		;reset sector flag
NSWTS1:	lda	NMOTRA		;restart motor timeout	
NSWTS2:	lda	NCSTAT		;a=sector flag and sector number
	rlc			;test NSSF, sector flag set?
	jnc	NSWTS2

; Sector flag set, get sector number alone and see if this
;   is the right sector

	lda	NRSEFL		;reset sector flag (required)
	lda	NCSTAT		;get sector again to be safe
	ani	NSSP		;get sector bits alone
	cmp	e		;the sector we want?
	jnz	NSWTS1		;no, keep looking

; Compute the offset of this sector within the track and return in bc

	rlc			;512 bytes per sector
	mov	b,a
	mvi	c,0
	ret 

;***Subroutine******************************
; dNxtSec - Wait for next (any) sector
; On Entry:
;    Drive is selected, motor is running
; On Exit:
;    drive is at proper sector
;    e = sector number
;    bc = byte offset of this sector in a track
;*******************************************
dNxtSec: lda	NRSEFL		;reset sector flag
	lda	NMOTRA		;restart motor timeout	
NSWTS3:	lda	NCSTAT		;a=sector flag and sector number
	rlc			;test NSSF, sector flag set?
	jnc	NSWTS3

; Sector flag set, return the sector number in e and compute the offset
;    of this sector within the track.
	
	lda	NRSEFL		;reset sector flag (required)
	lda	NCSTAT		;get sector again to be safe
	ani	NSSP		;get sector bits alone
	mov	e,a		;return sector in e
	rlc			;512 bytes per sector
	mov	b,a
	mvi	c,0
	ret 			

;**************************************************************************
; 
;  Data area
;
;**************************************************************************
; disk variables

drvNum	ds	1		;drive number to use
curCyl	ds	1		;current cylinder number drive is on
ctlOrd	ds	1		;current "controller orders" byte
trkNum	ds	1		;track number to read
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

