;*****************************************************************************
;
;  Flop2PC - Transfer floppy image to PC over a serial port
;		(for MFA Modular computer with 720K disks)
;
;	This program transmits an image of a 720K floppy disk to a PC
;       through a serial port using the XMODEM protocol. The transfer
;	can optionally stop when end of data on the floppy is detected
;	(a full track of E5h).
;
;	The program talks directly to the floppy controller and does
;	not require CP/M to function. The program runs standalone at
;	0x100 or under CP/M. The program exits with a warm boot to
;	CP/M or jumps back to the ROM monitor at F800h if running
;	standalone (see the MONITOR equate below).
;
;	This program works best if console port and the transfer port
;	are not the same device. Otherwise, most error messages won't
;	be seen as the file transfer dialog box prevents their display.
;
;	Written by Mike Douglas
;
;	Rev	 Date	    Desc
;	1.0	01/28/19    Original
;
;*****************************************************************************

; Disk information equates

NUMCYL	equ	80
NUMTRK	equ	160		;number of tracks 
NUMSEC	equ	9		;number of sectors per track
SECLEN	equ	512		;sector length
MINDRV	equ	0		;first drive number
MAXDRV	equ	1		;max drive number 

TRKLEN	equ	(NUMSEC*SECLEN)	;length of track in bytes
RDTRIES	equ	5		;number of disk read retries

; Specify head settle delay and the amount of time to delay between
;    reversing step direction 

MTRDLY	equ	8		;motor start delay in 0.1 sec intervals
HDSETL	equ	25		;25ms head settle delay after step
REVDIR	equ	25		;25ms delay when reversing step direction
HEADDLY	equ	50		;head load delay in ms

; Monitor, CP/M boot entry points

MONITOR	set	0F800h		;monitor entry point
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
XMTRIES	equ	10		;number of xmodem retries

; CP/M Equates

WBOOT	equ	0		;warm boot jump vector

; 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

	org	0100h		;load address
;-----------------------------------------------------------------------------
;   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

;  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	xra	a		;deselect drives
	out	FDCSEL
	sta	selByte		;save most recent select byte

	lxi	h,mDrive	;display drive number prompt
	call	dispMsg
	call	rcvCon		;get byte from the console
	ori	20h		;upper case letters to lower case
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	lxi	h,baseDrv	;ascii 1st drive - baseDrv = 0
	sub	m
	cpi	MAXDRV-MINDRV+1	;validate >=min drive and <= max drive
	jnc	getDrv		;invalid, prompt again

	adi	MINDRV		;restore offset of 1st drive (if any)
	sta	drvNum		;save the drive number to use

; Prompt for partial (end of data) transfer

	lxi	h,mXfrEod	;display end of data prompt
	call	dispMsg
	call	rcvCon		;get byte
	ori	20h		;upper to lower case, nums not affected
	sui	'n'		;check for y or n
	sta	eodFlag		;eodFlag cleared if 'no'

; getPort - get serial port number from the user.

getPort	lxi	h,mPort		;display transfer port prompt
	call	dispMsg
	call	rcvCon		;get byte from the console
	ori	20h		;upper to lower case, nums not affected
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	sui	'1'		;'1' - '2' to binary 0-1
	cpi	2		;validate ascii 1-2
	jnc	getPort		;invalid, prompt again

	sta	xfrPort		;save the port to use

; Prompt the user to start the XMODEM receive operation on the PC. 
;    Wait for a clear receive line for a least one second, then start
;    looking for NAK or 'C' (crc protocol) from the PC

	lxi	h,mStart	;display start file transfer prompt
	call	dispMsg

clrRcv1	call	rcvByte		;loop until input clear for 1 second
	jnz	clrRcv1

	xra	a		;set CRC flag to false (checksum mode)
	sta	crcFlag

waitNak	call	chkQuit		;give user chance to abort
	call	rcvByte		;wait for a character
	cpi	NAK
	jz	haveNak		;have a NAK, use checksum protocol

	cpi	'C'		;CRC protocol requested?
	jnz	waitNak		;no

	sta	crcFlag		;set crc flag non-zero = CRC mode

;  NAK or 'C' received meaning the XMODEM receive has started. Initialize
;     for the transfer.

haveNak	xra	a		;init track we want to zero
	sta	trkNum

	inr	a		;init xmodem block number to one
	sta	blkNum

	lxi	h,trkBuf	;trkBuf is initial starting point for
	shld	sndPtr		;   XMODEM send

;-----------------------------------------------------------------------------
;  mainLp - Loop through all tracks on the disk buffering as many tracks
;    as will fit in RAM before sending via xmodem, then repeat.
;-----------------------------------------------------------------------------
mainLp	lxi	h,trkBuf	;disk buffering always start at trkBuf
	shld	trkPtr

	lda	drvNum		;select drive
	call	dSelDrv
	jnz	selErr		;select error

bufLoop	call	readTrk		;read into trkBuf
	lda	trkNum		;increment track number (preserve HL)
	inr	a
	sta	trkNum	
	cpi	NUMTRK		;done all tracks on the disk?
	jz	sendBuf		;yes, go send the buffered tracks

	lda	trkErr		;read failure on the track? (<>FF)
	inr	a		;if so, send buffered tracks now
	jnz	sendBuf		;  so XMODEM won't time out

	xchg			;de=current track pointer
	lhld	bufEnd		;hl=end of buffering space
	call	cmpDeHl		;compare current-end
	jc	bufLoop		;still room, keep going	

; Track buffer is full or all tracks have been read. Send the buffered
;    tracks via xmodem.

sendBuf	call	sndTrks		;send the buffered tracks via xmodem
	lda	trkNum
	cpi	NUMTRK		;done all tracks?
	jnz	mainLp		;no, not done yet

; The entire disk has been read and (mostly) transmitted. See if there are
;    left over bytes to send. If so, send them.

	mov	a,l		;hl->next packet to send, a=lsb of ptr
	cpi	trkBuf AND 0FFh	;pointing to lsb of address of trkBuf?
	jz	noExtra		;same, no extra bytes to send
	call	sndPkt		;otherwise, send one last packet

; Send EOT and wait for response. Then display the success message and
;    start the program over.

noExtra	call	sndEot		;send and get response for EOT
	
	lxi	h,mDone		;print the all done message
msgExit	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num
	
;-----------------------------------------------------------------------------
; readTrk - read NUMSEC sectors from the current track into a trkBuf as
;   pointed to by trkPtr. After the track is read, trkPtr is updated
;   by the length of a track to point to the next track buffer. This
;   saved value is also returned in HL.
;-----------------------------------------------------------------------------
readTrk	call	chkQuit		;allow operator abort
	mvi	c,1		;C=restore-reseek counter
	
rtRseek	lda	trkNum		;seek to trkNum
	call	dSeek		;returns E=sector under head
	jnz	seekErr		;seek error

	mvi	a,0FFh		;init track error flag to no errors
	sta	trkErr		;no error on this track yet
	
rtTrkLp	mvi	d,NUMSEC	;D=physical sector counter

rtSecLp	mvi	b,RDTRIES	;B=sector retry count
	call	dNxtSec		;E=sector, HL->sector in track buffer

rtRtry	call	dRead		;read the sector
	jnz	rtError		;read error

	dcr	d		;decrement sector count
	jnz	rtSecLp		;read another sector

	call	trackE5		;check for full track of E5h (empty)
			
	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

; rtError - try to read the failed sector again up to RDTRIES times.
;   Then try a restore to track zero and a re-read of the track
;   one time. Restore isn't tried again for subsequent sector errors.

rtError	call	dSecAdr		;HL->sector buffer of failed read
	dcr	b		;decrement sector retry counter
	jnz	rtRtry		;try again
	
	dcr	c		;decrement restore-reseek counter
	jm	rtSecBd		;sector is bad

	call	dRestor		;else try a restore to track zero
	jmp	rtRseek		;reseek and try to read track again

rtSecBd	call	dspErr		;display the sector error
	jmp	rtSecLp		;continue with next sector

;-----------------------------------------------------------------------------
; dspErr - display the current track and sector number which just had
;     a read error. If this is the first error on a track, the track
;     number is displayed first.
;
;  on entry:
;     E = sector number
;-----------------------------------------------------------------------------
dspErr	push	b		;preserve BC
	lxi	h,trkErr	;HL->track error count
	inr	m		;increment track error count 
	jnz	dspSec		;track already displayed, go display sector

; First error on this track. Display "Track xx errors: "

	lxi	h,errTrk	;hl->where to put ascii decimal
	lda	trkNum		;a=track with error on it
	call	bin2dec		;track to ascii
	lxi	h,mTrkErr	;display the track error message
	call	dispMsg

; dspSec - display the sector number with an error

dspSec	lxi	h,errSec	;hl->where to put ascii sector
	mov	a,e		;a=sector where error occured
	call	bin2dec

	lxi	h,mTrkErr	;hl->entire message
	call	dispMsg		;display the error
	pop	b		;restore BC
	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

;-----------------------------------------------------------------------------
; trackE5 - check if the track just read is all E5h (empty). If so and
;    the eodFlag flag is true, then set trkNum to NUMTRK-1 so the main
;    loop will stop.
; On Entry:
;    trkPtr->track buffer to check
;    eodFlag = stop on full track of E5's
; On Exit: 
;    trkNum set to TRKNUM-1 if eodFlag true and full track of E5h
;    Clobbers B,D,E,H,L
;-----------------------------------------------------------------------------
trackE5	lda	eodFlag		;stopping on track of all E5?
	ora	a
	rz			;no
	
	lhld	trkPtr		;HL=current track pointer
	lxi	d,TRKLEN	;DE=bytes in a track
	mvi	b,0E5h		;compare for E5h
	
teLoop	mov	a,m		;next byte=E5?
	cmp	b
	rnz			;no, exit
	
	inx	h		;move to next byte in track
	dcx	d		;decrement byte counter
	mov	a,d		;reach zero?
	ora	e
	jnz	teLoop		;try next byte

	mvi	a,NUMTRK-1	;force main loop to exit
	sta	trkNum
	ret

;-----------------------------------------------------------------------------
; sndTrks - send the tracks buffered in trkBuf via xmodem. trkPtr points
;    to the end+1 of the data to send
;-----------------------------------------------------------------------------
sndTrks	lhld	trkPtr		;hl=end of buffered data + 1
	xchg			;de=end of buffered data + 1
	lhld	sndPtr		;hl=start tranmission address

sndLoop	push	d		;save end pointer
	call	sndPkt		;send a packet
	pop	d		;de=end pointer

; At this point, hl->start of next packet and de->last byte read from disk+1.
;    If the next XMODEM packet will go past the end of the disk data,
;    we don't want to send it yet.

	lxi	b,PKTLEN	;bc=length of XMODEM packet
	dad	b		;hl=address at end of NEXT packet + 1
	call	cmpDeHl		;compare disk end - end of next packet
	jc	sndDone		;next packet will pass the end, stop

	lxi	b,-PKTLEN	;restore hl
	dad	b
	jmp	sndLoop

; sndDone - all the packets we can send have been sent. Move any bytes
;    left over to just before the start of trkBuf. The next group of
;    xmodem packets will be transmitted starting there.

sndDone	call	subDeHl		;hl=de-hl = left over byte count - PKTLEN
	mov	a,l		;a=left over count - PKTLEN
	adi	PKTLEN		;a=left over byte count
	mov	b,a		;b=count of bytes to move
	lxi	h,trkBuf	;hl->start of trkBuf
	jz	mvDone		;no bytes to move, we're done
	
; Copy the leftover data backwards to just before the track buffer. The
;   next XMODEM send sequence will start at the beginning of this copied
;   data instead of at the start of trkBuf.

moveLp	dcx	h		;hl->memory just before trkBuf
	dcx	d		;de->unsent bytes from end of trkBuf
	ldax	d		;move from end of trkBuf to before trkBuf
	mov	m,a	
	dcr	b
	jnz	moveLp

mvDone	shld	sndPtr		;save address from which to send next time
	ret

;-----------------------------------------------------------------------------
; sndPkt - send an xmodem format 128 byte packet. HL points to the 128 byte
;    buffer to send. On exit, HL points to the next 128 byte boundary.
;-----------------------------------------------------------------------------
sndPkt	mvi	a,XMTRIES	;init retry counter
	sta	xmRetry

; First, send header bytes

reSend	call	chkQuit		;check for ctrl-c
	push	h		;save hl for possible re-send
	mvi	a,SOH		;1st byte is SOH
	call	sndByte
	lda	blkNum		;2nd byte is the block number
	call	sndByte
	cma			;2nd complement of block number
	call	sndByte

; Init checksum and CRC and packet length for transmission of data portion

	xra	a		;init crc
	sta	crc16
	sta	crc16+1
	lxi	d,PKTLEN*256	;d=byte counter, e=0 (checksum)

; Loop sending the data bytes and updating checksum and CRC

pktLoop	mov	a,m
	call	sndByte		;send and update checksum in e
	call	calCrc		;update the CRC
	inx	h		;point to next byte
	dcr	d		;decrement bytes remaining
	jnz	pktLoop

; Send checksum or CRC based on crcFlag

	lda	crcFlag		;crc or checksum?
	ora	a
	jz	sndCsum		;flag clear = checksum

	lda	crc16+1		;a=high byte of CRC
	call	sndByte		;send it
	lda	crc16		;a=low byte of crc
	jmp	sndSkip		;skip next instruction	

sndCsum	mov	a,e		;send the checksum byte

sndSkip	call	sndByte

;  All bytes sent. Wait for the response.

	call	rcvByte		;get the response character
	jz	sndFail		;timeout on response

	cpi	ACK		;ack received?
	jnz	sndFail		;no, send failed

	lda	blkNum		;increment block number
	inr	a
	sta	blkNum

	pop	b		;remove pushed hl, but don't clobber hl
	ret

; sndFail - ACK not received, decrement retry and try again.

sndFail	lxi	h,xmRetry	;point to retry counter
	dcr	m
	jz	xmFail		;retries used up, failed xmodem transfer

; If we've had 3 NAKs on the 1st packet and CRC is selected, assume
;   we took so long to send the 1st packet due to disk retries that
;   the receiver has since timed out and switched to checksum

	lda	crcFlag		;are we in CRC mode
	jz	clrRcv2		;no, ignore the rest of this

	lda	blkNum		;on block 1?
	dcr	a
	jnz	clrRcv2		;no, go on

	lda	xmRetry		;failed three times in a row on block 1?
	sui	XMTRIES-3
	jnz	clrRcv2		;no

	sta	crcFlag		;clear crcFlag to force checksum

;  clrRcv2 - wait for one second of line clear time and send packet again.

clrRcv2	call	rcvByte		;wait for 1 second of clear line
	jnz	clrRcv2

	pop	h		;restore pointer to the packet
	jmp	reSend		;re-send the packet

;  xmFail - Display failure message then restart program

xmFail	lxi	h,mXmdm		;xmodem failure message
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive

;-----------------------------------------------------------------------------
; calCrc - update the 16-bit CRC with one more byte. 
;    (Copied from M. Eberhard)
; On Entry:
;   a has the new byte
;   crc16 is current except this byte
; On Exit:
;   crc16 has been updated
;   Trashes a,bc
;-----------------------------------------------------------------------------
calCrc	push	d
	push	h
	lhld	crc16		;get CRC so far
	xra	h		;XOR into CRC top byte
	mov	h,a
	lxi	b,1021h		;bc=CRC16 polynomial
	mvi	d,8		;prepare to rotate 8 bits

; do 8 bit shift/divide by CRC polynomial

cRotLp	dad	h		;16-bit shift
	jnc	cClr		;skip if bit 15 was 0
	mov	a,h		;CRC=CRC xor 1021H
	xra	b
	mov	h,a
	mov	a,l
	xra	c
	mov	l,a
cClr	dcr	d
	jnz	cRotLp		;rotate 8 times

; save the updated CRC and exit

	shld	crc16		;save updated CRC
	pop	h
	pop	d
	ret

;-----------------------------------------------------------------------------
;  sndEot - send EOT character and wait for ACK response
;-----------------------------------------------------------------------------
sndEot	mvi	a,XMTRIES	;init retry counter
	sta	xmRetry

reEot	mvi	a,EOT		;send the EOT
	call	sndByte
	call	rcvByte		;wait for response
	jz	eotFail		;timeout

	cpi	ACK		;ack received?
	jnz	eotFail		;no, eot send failed
	ret			;otherwise, we're done.

; timeout waiting for ACK to EOT. Decrement retry counter and try again

eotFail	lxi	h,xmRetry	;point to retry counter
	dcr	m
	rz			;just give up - xfer was probably good

clrRcv3	call	rcvByte		;wait for 1 second of clear line
	jnz	clrRcv3

	jmp	reEot		;try again

;-----------------------------------------------------------------------------
; 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

;-----------------------------------------------------------------------------
; sndByte - send the byte in a through the specified transfer port. 
;     Adds the byte to register e for checksum. Clobbers b.
;     XMODEM send routine assumes serOut returns with a containing
;     the character sent.
;-----------------------------------------------------------------------------
sndByte	mov	b,a		;b=byte to transmit
	add	e		;update checksum
	mov	e,a		;e=updated checksum
	lda	xfrPort		;a=port to use for transfer
	jmp	serOut		;send the character

;-----------------------------------------------------------------------------
; rcvByte - Receive a byte from specified transfer port with a one second
;     timeout. 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 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	lxi	b,ONESEC	;bc=cycles through this loop for 1s
rcvWait	lda	xfrPort		;(13) a=port to use for transfer
	call	serIn		;(17+cycles in serIn) look for a byte
	rnz			;(5) byte received

	dcx	b		;(5) otherwise, decrement timer
	mov	a,b		;(5) one second expire?
	ora	c		;(4)
	jnz	rcvWait		;(10) no, keep waiting

	ret			;return with timeout (zero true and in a)

;--------------------------------------------------------------
; 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	JMP
	rnz			;no, not CP/M

; A jump instruction is present at the CP/M warm boot location (0),
;   now see if that jump points to five more jumps. If so, assume CP/M

	lxi	h,WBOOT+1	;point to lsb of jump address
	mov	e,m		;e=low byte of jump
	inx	h
	mov	d,m		;de=destination of jump
	mvi	b,5		;look for 5 more jumps (a jump table)
	
jmpTest	ldax	d		;a=opcode at jump destination
	sui	JMP		;another jump present?
	rnz			;no, not CP/M
	
	inx	d		;move to next jump
	inx	d
	inx	d
	dcr	b
	jnz	jmpTest
	
	dcr	a		;a=0ffh
	sta	cpmFlag		;CP/M flag to non-zero = true

; We're running under CP/M. Change drive prompt message to show drive
;    letters instead of drive numbers and change baseDrv to convert
;    an 'A' to the base drive number (MINDRV).

	mvi	a,'A'		;'A' in drive message instead of number
	sta	mDrvMin
	adi	MAXDRV-MINDRV	;max drive letter
	sta	mDrvMax
	mvi	a,'a'		;ascii 1st drive - baseDrv = 0
	sta	baseDrv
	ret

;------------------------------------------------------------------
; sizeRam - determine how much RAM we have for buffering tracks.
;   Sets the bufEnd variable which points to end address of 
;   the last possible track buffer + 1
;------------------------------------------------------------------
; If running under CP/M, use the BIOS pointer to determine the top
;    of available RAM. Otherwise, size RAM manually.

sizeRam	lhld	WBOOT+1		;get address of BIOS jump table+3
	dcx	h		
	dcx	h
	dcx	h		;hl->top of usable ram+1
	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	ramEnd		;yes, we have the end of RAM

;  Not under CP/M. Find the end of RAM manually.

	lxi	h,(trkBuf+0ffh) AND 0ff00h   ;next 256 byte boundary
	
ramLoop	mov	a,m		;a=current RAM content
	inr	m		;change RAM
	cmp	m		;did RAM change?
	mov	m,a		;restore RAM
	jz	ramEnd		;end of RAM found
	
	inr	h		;next page
	jnz	ramLoop

	dcr	h		;force end to FF00 instead of 0000

; ramEnd - end of RAM found. Now 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	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

;--------------------------------------------------------------------
; subDeHl - HL=DE-HL
;--------------------------------------------------------------------
subDeHl	mov	a,e		;subtract DE-HL, do lsbs first
	sub	l
	mov	l,a		;lsb result in l
	mov	a,d		;do msbs
	sbb	h
	mov	h,a		;msb result in h	
	ret

;-----------------------------------------------------------------------------
; Error message output and abort
;-----------------------------------------------------------------------------
selErr	lxi	h,mSelErr	;select error message
	jmp	msgExit		;display error and exit

seekErr	lxi	h,mSeekEr	;seek error message
	jmp	msgExit		;display error and exit

;---------------------------------------------------------------------
; Message constants
;---------------------------------------------------------------------
mWelcom	db	cr,lf,lf
	db	'===== Floppy to PC Image Transfer =====',cr,lf
	db 	'  (MFA Computer, 720K Disks, ver 1.0)',cr,lf,lf
	db	'Sends an image of a 720K disk to a PC through a serial port'
	db	cr,lf
	db	'using the XMODEM protocol.',0

mDrive	db	cr,lf,lf,'Insert and specify source drive ('
mDrvMin	db	'x-'
mDrvMax	db	'x) or X to exit: ',0

mXfrEod	db	cr,lf,lf,'Stop transfer when empty track (all E5h) detected? ',0

mPort	db	cr,lf,lf
	db	'Specify the port to use for file transfer',cr,lf
	db	'  1) Console serial port at A0/A1',cr,lf
	db	'  2) Second serial port at 90/91',cr,lf
	db	'Enter port or X to exit: ',0

mStart	db	cr,lf,lf,'Start XMODEM receive operation on the PC now...',0

mDone	db	cr,lf,lf,'Transfer complete!',cr,lf,0

mXmdm	db	cr,lf,lf,'XMODEM communication failure',cr,lf,0

mSelErr	db	cr,lf,lf,'Drive select error',cr,lf,0
mSeekEr	db	cr,lf,lf,'Seek error',cr,lf,0

mTrkErr	db	cr,lf,'Track '
errTrk	db	'xx failed sectors: ',0
errSec	db	'xx ',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	20000		;rcvByte loop count for 1 second

; 8251 Serial Port and 8255 Parallel Port Equates

SIO1ST	equ	0A1h		;console status port
SIO1DT	equ	0A0h		;console data port
SIO2ST	equ	091h		;2nd port status
SIO2DT	equ	090h		;2nd port data

SIOTBE	equ	1		;transmit buffer entry
SIORDA	equ	2		;receive data available

;----------------------------------------------------------------------------
; conIn - input character from console
;    inputs:
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;----------------------------------------------------------------------------
conIn	in	SIO1ST		;see if a new character is present
	ani	SIORDA
	rz			;no character, return zero status

	in	SIO1DT		;return character and non-zero status
	ret	

;----------------------------------------------------------------------------
; conOut - output character to console
;    inputs: b = character to send
;    clobbers a
;----------------------------------------------------------------------------
conOut	in	SIO1ST		;wait for OK to transmit
	ani	SIOTBE
	jz	conOut

	mov	a,b		;a=character to transmit
	out	SIO1DT		;send it
	ret

;----------------------------------------------------------------------------
; 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
;    41 cycles when not returning a character
;-----------------------------------------------------------------------------
serIn	ora	a		;(4) port b?
	jnz	serInB		;(10) yes

; input from port A

	in	SIO1ST		;(10) a=port status register
	ani	SIORDA		;(7) isolate receive ready bit
	rz			;(10) nothing present

	in	SIO1DT		;a=character from serial port
	ret			

; input from port B

serInB	in	SIO2ST		;a=status register
	ani	SIORDA		;isolate receive ready bit
	rz			;nothing present

	in	SIO2DT		;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	ora	a		;port b?
	jnz	serOutB		;yes

; output through port A

serOutA	in	SIO1ST		;a=port status register
	ani	SIOTBE		;isolate transmit ready bit
	jz	serOutA		;not ready

	mov	a,b		;a=character to transmit
	out	SIO1DT		;transmit the character
	ret	

; output through port B

serOutB	in	SIO2ST		;a=port status register
	ani	SIOTBE		;isolate transmit ready bit
	jz	serOutB		;not ready

	mov	a,b		;a=character to transmit
	out	SIO2DT		;transmit the character
	ret	

;****************************************************************************
;
; MFA floppy controller disk I/O routines
;
;****************************************************************************

; Disk controller equates (WD1793)

FDCBASE	equ	0C0H		;controller base address
FDCCMD	equ	FDCBASE		;1793 command register
FDCSTAT	equ	FDCBASE		;1793 status register
FDCTRK	equ	FDCBASE+1	;1793 track register
FDCSEC	equ	FDCBASE+2	;1793 sector register
FDCDATA	equ	FDCBASE+3	;1793 data in/out register
FDCSEL	equ	FDCBASE+4	;FDC drive select port
FDCWAIT	equ	FDCBASE+8	;FDC stall/wait port

; Commands for 1793

STPRATE	equ	0		;6ms step rate (1mhz clock)

cRESTOR	equ	008h+STPRATE	;restore, head load, no verify
cSEEK	equ	018h+STPRATE	;seek, head load, no verify
cSTPIN	equ	058h+STPRATE	;step in, update track reg, head loaded
cSTPOUT	equ	078h+STPRATE	;step out, update track reg, head loaded
cREAD	equ	080h		;read, head assumed settled
cRDADDR	equ	0C0h		;read address marker, head assumed settled
cWRITE	equ	0A0h		;write, head assumed settled
cWRTTRK	equ	0F0h		;write track command, head settled
cRESET	equ	0D0h		;reset 1793 to idle
cIDXINT	equ	0D4h		;force interrupt on index pulse

cSTPTGL	equ	020h		;xor toggles between step in and out
cWAITHD	equ	004h		;bit for read/write to wait for head settle

; Status bits from 1793

sNOTRDY	equ	80h		;1=not ready
sWRPROT	equ	40h		;1=write protected
sHDLOAD	equ	20h		;1=head is loaded
sSKERR	equ	10h		;1=seek error
sCRCERR	equ	08h		;1=CRC error
sTRACK0	equ	04h		;1=on track zero
sINDEX	equ	02h		;1=index detected
sBUSY	equ	01h		;1=1793 busy

sSECERR	equ	10h		;1=sector not found
sWRTFLT	equ	20h		;1=write fault
sDLOST	equ	04h		;1=lost data 

; Combined status flag masks for status of a seek, read, and write

smTRK0	equ	sSKERR+sNOTRDY+sBUSY+sTRACK0
smSEEK	equ	sSKERR+sNOTRDY+sBUSY
smREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY+sBUSY
smWRITE	equ	smREAD+sWRPROT+sWRTFLT

; Drive select register parameters

pDRIVE0	equ	001h		;1=drive 0 select
pDRIVE1	equ	002h		;1=drive 1 select
pDENS	equ	004h		;1=single density, 0=double density
pSIDE	equ	008h		;1=side, 0=side 0

; Misc Equates

FDCINTV	equ	02Ch		;RST5.5 interrupt vector for FDC
SIM	equ	030h		;8085 SIM opcode

;--------------------------------------------------------------
; dSelDrv - Select drive, start and wait on motor startup.
;    Updates the track register in the 1793 with the track
;    the newly selected drive is on. Updates the write protect
;    flag (fWrtPrt) for the selected drive.
; On Entry:
;    A = drive to be selected
; On Exit:
;    fWrtPrt = non-zero if drive write protected		
;    Z flag set = drive selected and ready
;    Z flag clear = error
; Clobbers B,C,H,L
;--------------------------------------------------------------
dSelDrv	ani	01h		;limit to physical drives 0 and 1
	inr	a		;drv 0 sets 1's bit, drv 1 sets 2's bit
	mov	b,a		;B=drive select bits
	
	lxi	h,selByte	;HL->current select value
	mov	c,m		;C=current select value
	mov	m,b		;store new select value in selByte
	
	in	FDCSTAT		;see if drive ready (motor running)
	ani	sNOTRDY		;Z true if drive is ready

	mov	a,b		;select drive, start motor
	out	FDCSEL

	jz	dMtrRdy		;motor is already up to speed

	call	dWtMtr		;wait for motor to get up to speed
	jz	dSkipHd		;drive, head, and disk ready

	ret			;else return with error (no disk)

dMtrRdy	sub	c		;compare new select byte to previous
	ani	3		;only look at the drive selects
	jz	dSkipHd		;same drive so head already loaded

	call	dWtIdx		;wait for an index pulse on new drive
	rnz			;no disk

	mov	a,h		;if > 28ms waiting for index, then
	ani	0FCh		;   skip the head load delay
	jnz	dSkipHd		;   (test for HL >= 1024, 27us lsbit)

	mvi	a,HEADDLY	;A=head load delay 
	ora	a		;test for zero
	cnz	delayMs		;delay headDly if not zero

dSkipHd	mvi	a,cRESET	;ensure 1793 is idle
	out	FDCCMD		;follow with min 28us delay

	mvi	a,JMP		;(7) rewrite FDC interrupt vector to be safe
	sta	FDCINTV		;(13)
	lxi	h,fdcIsr	;(10) ISR address
	shld	FDCINTV+1	;(16)
	shld	FDCINTV+1	;(16) just waste time

	in	FDCSTAT		;read 1793 status (after delay)
	ani	sWRPROT		;get write protect bit alone
	sta	fWrtPrt		;save write protect status flag

	call	dReadAm		;returns A=track from address mark
	out	FDCTRK		;update track register in 1793
	rz			;exit if address mark worked
	
	jmp	dRestor		;else, restore to track 0 and exit

;--------------------------------------------------------------
; dWtMtr - Wait the motor startup time specified in the mtrDly
;    parameter. Fall into dWtIdx to wait for an index pulse
;    to ensure drive ready is asserted.
; On Entry:
;    mtrDly = motor delay in 0.1s increments
; On Exit:
;    Z flag set if disk present
;    Z flag clear if no disk present
; Clobbers H
;--------------------------------------------------------------
dWtMtr	mvi	a,MTRDLY	;motor delay in 0.1s increments
	ani	0Fh		;limit to 0-15 (0-1.5s)
	mov	h,a		;H=number of 100ms delays

dw100	mvi	a,100		;delay 100ms
	call	delayMs
	dcr	h		;decrement number of 100ms's to wait
	jnz	dw100
				;fall into dWtIdx to verify index pulse

;--------------------------------------------------------------
; dWtIdx - Wait for an index pulse to come by. This ensures
;    the READY line is asserted because of the currently 
;    selected drive and not the previously selected drive.
; On Entry:
; On Exit:
;    HL = time spent looking for index pulse (27us lsbit)
;    Z flag set if index detected
;    Z flag false if no index (disk not present)
; Clobbers H,L
;--------------------------------------------------------------
dWtIdx	mvi	a,cRESET	;force type 1 status response
	out	FDCCMD		;follow with min 28us delay

	xthl			;(18)
	xthl

	lxi	h,0		;timeout counter, 54 cycle loop

dwIdx1	inx	h		;(5) increment timeout
	mov	a,h		;(5) compare MSB value
	sui	0E0h		;(7) reach E000h (1.55s)?
	jz	dNoDisk		;(10) index not changing, no disk

	in	FDCSTAT		;(10) wait for NOT index pulse
	ani	sINDEX		;(7)
	jnz	dwIdx1		;(10) loop while index asserted

dWtIdx2	inx	h		;(5) increment timeout
	mov	a,h		;(5) compare MSB value
	sui	0E0h		;(7) reach E000h (1.55s)?
	jz	dNoDisk		;index not detected, no disk

	in	FDCSTAT		;(10) wait for index pulse
	ani	sINDEX		;(7)
	jz	dWtIdx2		;(10) loop while index not asserted

	xra	a		;have index, return zero
	ret

dNoDisk dcr	a		;no index, return non-zero
	ret

;--------------------------------------------------------------
; dSeek - Seek to the track specified in A on the currently
;    selected drive. After the seek completes, an address
;    mark is read to verify the seek and to determine what
;    sector is under the head.
; On Entry:
;    A = desired track number
; On Exit:
;    E = sector number from the address mark
;    Z flag set = seek worked and verified
;    Z flag clear = error
; Clobbers D,E,H,L
;--------------------------------------------------------------

; Convert the logical track in A to the physical track (cylNum).
;    Even tracks are on side 0, odd tracks are on side 1. Update
;    the drive select byte to include the side bit based on the
;    specified track number.

dSeek	ora	a		;make sure carry is clear
	rar			;convert track number to cylinder
	sta	cylNum		;save cylinder to seek to

	mvi	a,pSIDE		;assume side 1
	jc	dSide1		;side 1 is correct (odd tracks)
	xra	a		;else, force side 0
dSide1	mov	d,a		;D=side bit in proper position

	lxi	h,selByte	;HL->drive select byte
	mov	a,m		;A=current select byte
	ani	3		;drive bits alone
	ora	d		;include the side bit
	mov	m,a		;save updated selByte
	out	FDCSEL		;and output to the controller

; Issue the seek command and verify the proper cylinder is under the
;    head by reading an address mark. 

	mvi	d,3		;D=retry counter

dsRetry	call	dSeekC		;issue seek command
	rnz			;hard error

	call	dReadAm		;returns A=track from address mark
	jnz	dsFail		;couldn't read address mark

	lxi	h,cylNum	;HL->desired cylinder number
	cmp	m		;does actual match requested?
	rz			;yes, we're done

dsFail	dcr	d		;decrement retry counter
	rm			;went below zero, exit with <> 0

	call	dRestor		;restore to track zero
	jz	dsRetry		;try again
	
	ret			;return with error
	
;--------------------------------------------------------------
; dSeekC - Issue seek command. Delay head settle time after
;    performing the seek.
; On Entry:
;    cylNum = cylinder to seek to
; On Exit:
;    A = 1793 status register
; Clobbers none
;--------------------------------------------------------------
dSeekC	lda	cylNum		;give track to seek to 1793
	out	FDCDATA

	mvi	a,cSEEK		;issue seek command
	call	dDoCmd
	ani	smSEEK		;any of these bits is an error
	rnz			;hard seek failure

	mvi	a,HDSETL	;wait for head settle time
	jmp	delayMs		;return with zero through delayMs

;--------------------------------------------------------------
; dRestor - Restore the currently selected drive to track 0.
;    Waits for the direction reversal delay (revDir) before
;    exiting in case a subsequent seek is immediately done.
; On Entry:
;    
; On Exit:
;    Z flag set = restore operation worked
;    Z flag clear = restore failed
; Clobbers none
;--------------------------------------------------------------
dRestor	mvi	a,cRESTOR	;issue restore to track zero command
	call	dDoCmd
	ani	smSEEK		;any of these bits is an error
	rnz			;hard restore failure
	
	mvi	a,REVDIR	;wait direction reversal delay
	jmp	delayMs		;return with zero through delayMs

;--------------------------------------------------------------
; dDoCmd - Issue command passed in A to the 1793. Control
;    is returned to caller upon command completion by the
;    interrupt service routine.
; On Entry:
;    A = command to write to 1793
; On Exit:
;    A = 1793 status register
; Clobbers none
;--------------------------------------------------------------
dDoCmd	push	psw		;save passed command
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	pop	psw		;A = command for 1793
	out	FDCCMD
	
dsWait	jmp	dsWait		;wait for completion interrupt

;--------------------------------------------------------------
; dRead - Read sector on current cylinder and side.
; On Entry:
;    Drive is selected, on proper track, head loaded
;    HL->sector buffer
;    E = sector number to read
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dRead	mov	a,e		;give sector number to FDC
	out	FDCSEC
	
	mvi	a,cREAD		;output read command
	call	dReadC		;do the read
	ani	smREAD		;any of these bits is an error
	ret

;--------------------------------------------------------------
; dReadAm - Read address mark from disk. Returns track number
;    in A and sector in E from the first address mark found.
; On Entry:
;    Drive is selected
; On Exit:
;    Z flag set = success
;    Z flag clear = error
;    A = track number from disk
;    E = sector number from disk
; Clobbers H,L
;--------------------------------------------------------------
dReadAm	lxi	h,amBuf		;HL->address mark buffer
	mvi	a,cRDADDR	;read address mark command
	call	dReadC		;read with command in A
	ani	smREAD		;any of these status bits is an error
	rnz			;exit with error

	lxi	h,amSec		;HL->sector from address mark
	mov	e,m		;E=sector from address mark

	lda	amTrk		;A=track from address mark
	ret			;exit with data and status
	
;------------------------------------------------------------------
; dReadC - Issue read command from A and then go into a loop
;    receiving the bytes into the buffer pointed to by HL.
;    The ISR returns control directly to the caller of this
;    subroutine when the 1793 signals completion of the read.
; On Entry:
;    A = read sector or read address mark
;    HL->sector buffer
; On Exit:
;    A = completion status from 1793.
; Clobbers H,L
;------------------------------------------------------------------
dReadC	push	psw		;save command from A
	lda	selByte		;keep motor running
	out	FDCSEL
	
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	pop	psw		;A=read or read address mark
	out	FDCCMD		;issue read or read address command

drLoop	out	FDCWAIT		;wait for data byte
	in	FDCDATA		;get byte
	nop			;make sure interrupt occurs
	mov	m,a		;save in memory
	inx	h
	jmp	drLoop		;loop until completion interrupt
	
;--------------------------------------------------------------
; dWrite - Write a sector on current track.
; On Entry:
;    Drive is selected, on proper track, head loaded
;    HL->buffer address
;    E = sector number to write
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dWrite	mov	a,e		;give sector number to FDC
	out	FDCSEC
	
	mvi	a,cWRITE	;issue write command
	call	dWriteC
	ani	smWRITE		;any of these bits is an error
	ret

;--------------------------------------------------------------
; dWrtTrk - Write a complete track for formatting
; On Entry:
;    Drive is selected, on proper track, head loaded
;    HL->track buffer address
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dWrtTrk	mvi	a,cWRTTRK	;issue write track command
	call	dWriteC
	ani	smWRITE		;any of these bits is an error
	ret
	
;------------------------------------------------------------------
; dWriteC - Issue write command and then go into a loop
;    sending the bytes from the buffer pointed to by HL.
;    The ISR returns control directly to the caller of this
;    subroutine when the 1793 signals completion of the write.
; On Entry:
;    A = Write command for the 1793
;    HL->sector buffer
; On Exit:
;    A = completion status from 1793.
; Clobbers HL
;------------------------------------------------------------------
dWriteC	push	psw		;save command
	lda	selByte		;keep motor running
	out	FDCSEL
	
	mvi	a,0Eh		;unmask RST5.5
	db	SIM		;8085 SIM (set interrupt mask)
	ei			;enable completion interrupt

	pop	psw		;A=write command to use
	out	FDCCMD

dwLoop	out	FDCWAIT		;wait for controller	
	mov	a,m		;A=next byte to write
	out	FDCDATA		;give byte to 1793
	inx	h
	jmp	dwLoop		;interrupt will exit this loop

;--------------------------------------------------------------
; dNxtSec - Increment and wrap the sector number passed in E,
;    then fall into dSecAdr to return HL with a pointer to
;    the sector address within the track buffer.
; On Entry:
;    E = most recent sector number
; On Exit:
;    E = next sector number
;    HL->sector within trkBuf
; Clobbers none
;--------------------------------------------------------------
dNxtSec	inr	e		;bump to next sector
	jz	dSec1		;in case uninitialized rolled to 0

	mvi	a,NUMSEC	;A=number of sectors
	cmp	e		;compare NUMSEC - next sector
	jnc	dSecAdr		;next is good (1-9)

dSec1	mvi	e,1		;restart at sector 1
				;fall into dSecAdr

;--------------------------------------------------------------
; dSecAdr - Return HL with a pointer to the sector buffer in
;    the track buffer pointed to by trkPtr.
; On Entry:
;    E = sector number
;    trkPtr->start of track buffer to use
; On Exit:
;    E = sector number (not changed)
;    HL->sector within trkBuf
; Clobbers none
;--------------------------------------------------------------
dSecAdr	mov	a,e		;A=physical sector number
	dcr	a		;zero index the sector
	add	a		;A=MSB of sector*512
	
	lhld	trkPtr		;HL->current track buffer
	add	h		;compute MSB of sector address
	mov	h,a		;HL->sector in track buffer
	ret

;------------------------------------------------------------------
; fdcIsr - Interrupt service routine when the 1793 signals command
;    completion. The interrupted address is removed from the stack
;    and control returns to the caller of the interrupted routine.
;    The status from the 1793 is returned in A.
;------------------------------------------------------------------
fdcIsr	pop	psw		;remove ISR return address

wtBusy	in	FDCSTAT		;get 1793 status
	rrc			;make sure it's not busy
	jc	wtBusy		;shouldn't happen, but be safe

	rlc			;store status in A
	ret			;return to caller of command

;------------------------------------------------------------------------------
; delayMs - delay ms specified in A
; On Entry: A = delay in ms
; On Exit:
;    Z flag set
; Clobbers none
;------------------------------------------------------------------------------
delayMs	push	b		;preserve B,C

dms1ms	mvi	b,2000/15	;B=1ms through following loop

dmsLoop	dcr	b		;(5) decrement B
	jnz	dmsLoop		;(10)

	dcr	a		;decrement ms left
	jnz	dms1ms		;do another ms

	pop	b		;restore B,C
	ret	
	
;**************************************************************************
; 
;  Data area
;
;**************************************************************************
; disk variables

drvNum	ds	1		;drive number to use
trkNum	ds	1		;track number to read
cylNum	ds	1		;cylinder number
selByte	ds	1		;drive select byte
fWrtPrt	ds	1		;write protect flag
trkPtr	ds	2		;pointer into trkBuf at track boundaries
bufEnd	ds	2		;end address + 1 of last track spot in RAM

; retry logic variables

dskRtry	ds	1		;disk read retry counter
rtStep	ds	1		;current retry step
trkErr	ds	1		;non zero if error occured on track

; xmodem variables

xfrPort	ds	1		;pseudo port for file transfer
xmRetry	ds	1		;xmodem retry counter
blkNum	ds	1		;current xmodem block number
crcFlag	ds	1		;non zero if using CRC instead of checksum
crc16	ds	2		;crc-16 result
sndPtr	ds	2		;pointer for start of XMODEM send

; misc variables

cpmFlag	ds	1		;non-zero if running under CP/M
eodFlag	ds	1		;non-zero to stop xfer with end of data
baseDrv	ds	1		;ascii 1st drive - baseDrv = 0

; Address mark buffer

amBuf	equ	$
amTrk	ds	1		;track
amSide	ds	1		;side
amSec	ds	1		;sector
amSecL	ds	1		;sector length code
amCrc	ds	2		;CRC

	ds	64		;stack space
ourStk	equ	$

; track buffer runs from here to the end of memory

	ds	PKTLEN		;space for pre-pended unsent data
trkBuf	equ	$

	end

