;******************************************************************************
;
;  PC2Flop - Transfer disk image from PC to floppy over serial port.
;		(for MFA Modular computer with 720K disks)
;
;	This program writes a soft-sectored floppy with a disk image
;	transmitted from a PC. The image is sent through a serial port
;	using the XMODEM protocol. The disk image can be a partial image
;	if the remainder of the disk image would have been nothing but
;	E5's (e.g., empty space on a CP/M disk).
;
;	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).
;
;	Since this is a soft sectored controller, the destination disk
;	must have been formatted at some point. To allow this program
;	to create a new disk for a "cold" machine, it offers a format
;	disk option. 
;
;	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/26/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 

DDTRKL	equ	(NUMSEC*SECLEN)	;length of track
RDTRIES	equ	5		;number of read (verify) attempts
WRTRIES	equ	4		;number of write attempts

; 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

; 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

	org	0100h	
;-----------------------------------------------------------------------------
;  Initialize for transfer
;-----------------------------------------------------------------------------
init	lxi	sp,ourStk	;initialize stack pointer
	call	chkCpm		;set flag for CP/M or not
	call	sizeRam		;determine amount of RAM available

	mvi	a,MINDRV	;a=default drive
	sta	drvNum		;need for pgmExit

;  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 whether or not to format the disk

	lxi	h,mFormat	;display format prompt
	call	dispMsg
	call	rcvCon		;get byte
	ori	20h		;upper to lower case, nums not affected
	cpi	'y'		;format requested?
	cz	format		;yes, go format disk

; getPort - get serial port number from the user.

getPort	lxi	h,mPort		;display transfer port prompt
	call	dispMsg
	call	rcvCon		;get byte from the console
	ori	20h		;upper to lower case, nums not affected
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	sui	'1'		;'1' - '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 insert the disk to write, initialize, then start
;   sending NAK to start the XMODEM reception.

	lxi	h,mStart	;display start file transfer prompt
	call	dispMsg

	xra	a		;init track we're reading to zero
	sta	trkNum
	sta	fmtFlag		;set formatting flag false

	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.
	
	lda	drvNum		;select drive
	call	dSelDrv
	jnz	selErr		;select error
		
	lda	fWrtPrt		;check write protect flag
	ora	a
	jnz	wpErr		;write protect error
	
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, less than a full image sent

	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
	
msgExit	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	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

	ret

;-----------------------------------------------------------------------------
; rcvPkt - receive an xmodem format 128 byte packet. HL points to the 128 byte
;    buffer to receive. HL is updated by 128 bytes after each succuessful
;    reception of a new 128 byte packet.
;-----------------------------------------------------------------------------
nakPkt	pop	h		;get HL back
	mvi	a,NAK
	call	purgSio		;purge receive for 1s then transmit NAK

;wait for SOH or EOT

rcvPkt	push	h		;save HL for retries
waitSoh	call	chkQuit		;check for user abort
	mvi	a,3		;3 second timeout for soh
	call	rcvByte		;receive character with timeout
	jz	nakPkt		;timeout

	cpi	SOH		;SOH received?
	jz	haveSoh

	cpi	EOT		;EOT to say we're done?
	jnz	waitSoh		;no, keep looking for SOH

	pop	h		;undo the entry push
	ret			;return with EOT

;  Have SOH, receive block number and not block number

haveSoh	mvi	a,1		;one second timeout once inside a packet
	call	rcvByte		;get the block number
	jz	nakPkt		;timeout, NAK and start over

	sta	rcvBlk		;save the received block number
	mvi	a,1		;one second timeout
	call	rcvByte		;get not block number
	jz	nakPkt		;timeout, NAK and start over

	sta	rcvNBlk		;save not block number

;  Receive the 128 byte block

	mvi	e,0		;init checksum
	mvi	d,pktLen	;d is byte counter
pktLoop	mvi	a,1		;one second timeout
	call	rcvByte		;get next data byte
	jz	nakPkt		;timeout

	mov	m,a		;store the character
	inx	h		;point to next byte
	add	e		;update the checksum
	mov	e,a
	dcr	d		;decrement bytes remaining
	jnz	pktLoop

;  Verify the checksum

	mvi	a,1		;one second timeout
	call	rcvByte		;get the checksum
	jz	nakPkt		;timeout

	cmp	e		;checksum match?
	jnz	nakPkt		;no, packet error

;  Checksum good. Verify block numbers

	lda	rcvNBlk		;verify rcvBlk = not (rcvNBlk)
	cma			;A should = block number now
	mov	b,a		;save in b
	lda	rcvBlk
	cmp	b		;compare rcvBlk = not(rcvNBlk)?
	jnz	nakPkt		;no, error

	lda	blkNum		;compare rcvBlk = expected blkNum
	cmp	b
	jnz	nakPkt		;nak if not the same (also nak's re-send)

	inr	a		;increment expected block number
	sta	blkNum
	pop	b		;get HL off stack, but don't clobber HL
	xra	a		;return a zero
	ret

;-----------------------------------------------------------------------------
; wrtTrk - write and verify NUMSEC sectors to the current track in trkBuf
;   as pointed to by trkPtr. After the track is written, trkPtr is updated
;   by the length of a track to point to the next track buffer. This
;   saved value is also returned in HL.
;-----------------------------------------------------------------------------
wrtTrk	call	chkQuit		;allow operator to abort
	
	lda	trkNum		;seek to trkNum	
	call	dSeek
	jnz	seekErr
	
	mvi	b,WRTRIES	;b=write retry counter

rtryWrt	mvi	d,NUMSEC	;d=count of sectors to write
	
wrtLoop	call	dNxtSec		;get next sector number and sector ptr
	call	dWrite		;write the sector
	jnz	wrtErr		;write failed

	dcr	d		;decrement sector count
	jnz	wrtLoop		;loop until all sectors written

; Verify the track just written by reading to verify CRC

	mvi	d,NUMSEC	;d=count of sectors to verify

vfyLoop	call	dNxtSec		;get next sector number and offset
	lxi	h,secBuf	;hl->unused sector buffer
	call	dRead		;verify the sector by reading
	jnz	vfyErr		;error, go to retry logic

vfyNext	dcr	d		;decrement sector count
	jnz	vfyLoop		;loop until all sectors verified

	lhld	trkPtr		;hl=current track pointer
	lxi	d,DDTRKL	;de=bytes in a double density track
	dad	d		;hl=start of next track in trkBuf
	shld	trkPtr
	ret

; vfyErr - verify failed, retry reads followed by a re-write
;   of the track in needed.

vfyRtry	mvi	c,RDTRIES	;c=read retry counter

retryLp	lxi	h,secBuf	;hl->unused sector buffer
	call	dRead		;verify the sector by reading
	jz	vfyNext		;success, go verify next sector

	dcr	c		;decrement retry counter
	jnz	retryLp		;try again

	dcr	b		;decrement write retry counter
	jnz	rtryWrt		;retry starting with the write

	jmp	vfyErr		;display the error and restart

;-----------------------------------------------------------------------------
; 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 + 64.
;     Clobbers a, b and c.
;-----------------------------------------------------------------------------
rcvByte	push	d		;save d, e
	mov	d,a		;save timeout in d

initSec	lxi	b,ONESEC	;one second timeout 105 cycles per loop

rcvWait	lda	xfrPort		;(13) a=port to use for transfer
	call	serIn		;(17+cycles in serIn) 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	mvi	a,MINDRV	;re-select drive A for CP/M 1.4
	sta	drvNum
	call	dSelDrv

	lxi	h,mExit		;display "exiting" message
	call	dispMsg
	jmp	CPMBOOT		;reboot CP/M

;--------------------------------------------------------------
; chkCpm - check if running under CP/M. CP/M flag is set true
;   (non-zero) if yes, cleared otherwise.
;--------------------------------------------------------------
; First, initialize entries for stand-alone

chkCpm	xra	a
	sta	cpmFlag		;clear CP/M flag
	mvi	a,MINDRV+'0'	;ascii for minimum drive number
	sta	mDrvMin		;store in the drive prompt message
	sta	baseDrv		;ascii 1st drive - baseDrv = 0
	mvi	a,MAXDRV+'0'	;ascii for max drive number
	sta	mDrvMax

; Determine if we're under CP/M or standalone. CP/M is assumed if
;   a jump instruction is present at the CP/M warm start location (0)
;   and five more jumps (e.g., a jump table) is present at the
;   jump-to destination.
		
	lda	WBOOT		;see if jump instruction present for CP/M
	cpi	JMP
	rnz			;no, not CP/M

; A jump instruction is present at the CP/M warm boot location (0),
;   now see if that jump points to five more jumps. If so, assume CP/M

	lxi	h,WBOOT+1	;point to lsb of jump address
	mov	e,m		;e=low byte of jump
	inx	h
	mov	d,m		;de=destination of jump
	mvi	b,5		;look for 5 more jumps (a jump table)

jmpTest	ldax	d		;a=opcode at jump destination
	sui	JMP		;another jump present?
	rnz			;no, not CP/M

	inx	d		;move to next jump
	inx	d
	inx	d
	dcr	b
	jnz	jmpTest

	dcr	a		;a=0ffh
	sta	cpmFlag		;CP/M flag to non-zero = true

; We're running under CP/M. Change drive prompt message to show drive
;    letters instead of drive numbers and change baseDrv to convert
;    an 'A' to the base drive number (MINDRV).

	mvi	a,'A'		;'A' in drive message instead of number
	sta	mDrvMin
	adi	MAXDRV-MINDRV	;max drive letter
	sta	mDrvMax
	mvi	a,'a'		;ascii 1st drive - baseDrv = 0
	sta	baseDrv
	ret

;------------------------------------------------------------------
; sizeRam - determine how much RAM we have for buffering tracks.
;   Sets the bufEnd variable which points to end address of 
;   the last possible track buffer + 1
;------------------------------------------------------------------
; If running under CP/M, use the BIOS pointer to determine the top
;    of available RAM. Otherwise, size RAM manually.

sizeRam	lhld	WBOOT+1		;get address of BIOS jump table+3
	dcx	h		
	dcx	h
	dcx	h		;hl->top of usable ram+1
	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	ramEnd		;yes, we have the end of RAM

;  Not under CP/M. Find the end of RAM manually.

	lxi	h,(trkBuf+0ffh) AND 0ff00h   ;next 256 byte boundary

ramLoop	mov	a,m		;a=current RAM content
	inr	m		;change RAM
	cmp	m		;did RAM change?
	mov	m,a		;restore RAM
	jz	ramEnd		;end of RAM found

	inr	h		;next page
	jnz	ramLoop

	dcr	h		;force end to FF00 instead of 0000

; ramEnd - end of RAM found. Subtract 256 bytes from this to make room
;   for xmodem packet overflow at the end. Determine the end address + 1
;   of the last track buffer that will fit in RAM. Store in bufEnd

ramEnd	xchg			;de=end of RAM + 1
	mvi	a,-((DDTRKL SHR 8) + 1) AND 0ffh
	cmp	d		;force de < (10000h - DDTRKL)
	jnc	topOk

	mov	d,a		;limit max address

topOk	dcr	d		;subtract 256 bytes from end of RAM
	lxi	h,trkBuf	;hl=start of track buffer
	lxi	b,DDTRKL	;bc=length of track in bytes

; Loop increasing hl by DDTRKL until hl > end of RAM.

bfEndLp	dad	b		;hl=hl+track length
	call	cmpHlDe		;compare hl-de
	jc	bfEndLp		;still more room, keep going

; Subtract one track length from hl, this will be the end address + 1 of
;   the the last track buffer that will fit in RAM

	lxi	b,-DDTRKL	;subtract one track length
	dad	b		;hl = end address of last track + 1
	shld	bufEnd		;save as bufEnd
	ret

;--------------------------------------------------------------------
; cmpHlDe - 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

;--------------------------------------------------------------------
; cmpDeHl - 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

;-----------------------------------------------------------------------------
; Error message output and abort
;-----------------------------------------------------------------------------
wrtErr	ani	sSECERR		;sector not found error?
	jz	vfyErr		;no

	lxi	h,mNoSec	;'sector not found, format needed?'
	call	dispMsg

vfyErr	lxi	h,errTrk	;hl->where to put ascii decimal
	lda	trkNum		;a=track with error on it
	call	bin2dec		;track to ascii

	lxi	h,errSec	;hl->where to put ascii sector
	mov	a,e		;a=sector where error occured
	call	bin2dec

	lxi	h,mDskErr	;display the error message
	jmp	msgExit		;and exit to start

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
	
wpErr	lxi	h,mWpErr	;write protect error message
	jmp	msgExit		;display error and exit

fmtErr	lxi	h,mFmtErr	;format error message
	jmp	msgExit		;display error and exit

;--------------------------------------------------------------------
; format - format the disk
;--------------------------------------------------------------------
format	lxi	h,mCrLf		;move down one line
	call	dispMsg

	mvi	a,0FFh		;set formatting flag true
	sta	fmtFlag

	lda	drvNum		;select the disk
	call	dSelDrv
	jnz	selErr
	
	lda	fWrtPrt		;write protected?
	ora	a
	jnz	wpErr		;yes
	
	call	initDD		;initialize a DD track buffer
	xra	a		;start on track zero
	sta	trkNum

nextTrk	call	dSeek		;seek to track in A
	jnz	seekErr		;error

	mvi	b,0		;B=disk side, assume side 0
	lda	selByte		;get side bit from selByte
	ani	pSIDE		;isolate side bit
	jz	ftSide0		;it's side 0
	inr	b		;else, side 1

ftSide0	lxi	h,DDTRKID	;HL->location of track ID in track
	lxi	d,DDSECL-1	;DE=length to add per sector 
	mvi	c,NUMSEC	;C=sector counter
	lda	cylNum		;A=current cylinder

idLoop	mov	m,a		;store cylinder# in sector track ID
	inx	h		;move to side byte
	mov	m,b		;store side value

	dad	d		;compute address of next sector
	dcr	c		;decrement sector counter
	jnz	idLoop

; Issue the write track command

	lxi	h,trkBuf	;HL->track buffer
	call	dWrtTrk		;write the track
	jnz	fmtErr		;error, exit
	
; display '.' pacifier, check for operator abort, loop to next cylinder
		
	mvi	b,'.'		;display '.' pacifier per track
	call	conOut		;display pacifier
	call	chkQuit		;see if abort requested

	lxi	h,trkNum	;increment track number
	inr	m
	mov	a,m		;A=next track number
	cpi	NUMTRK		;done?
	jnz	nextTrk		;no, do next track

	ret			;format done

;-------------------------------------------------------------------
;  initDD - initialize a full track of data for formatting tracks
;-------------------------------------------------------------------
initDD	lxi	h,trkBuf	;HL->track of format data

	mvi	b,80		;80 bytes of 4E
	mvi	a,04Eh		;A=fill value
	call	fill

	mvi	b,12		;12 bytes of 00
	xra	a
	call	fill

	mvi	b,3		;3 bytes of F6(C2)
	mvi	a,0F6h
	call	fill

	mvi	m,0FCh		;index mark of FC
	inx	h

	mvi	b,50		;50 bytes of 4E
	mvi	a,04Eh
	call	fill

; sectDD - fill repeating sector portion of the track

	mvi	c,1		;c=sector number

sectDD	mvi	b,12		;12 bytes of 00
	xra	a
	call	fill

	mvi	b,3		;3 bytes of F5(A1)
	mvi	a,0F5h
	call	fill

	mvi	m,0FEh		;ID address mark of FE
	inx	h

	mvi	m,1		;track number 1
	inx	h
	mvi	m,0		;side 0
	inx	h

	mov	m,c		;sector number
	inx	h	
	mvi	m,2		;sector length 512
	inx	h
	
	mvi	m,0F7h		;F7 generates CRC
	inx	h

	mvi	b,22		;22 pre-data bytes of 4E
	mvi	a,04Eh
	call	fill

	mvi	b,12		;12 pre-data bytes of 00
	xra	a
	call	fill

	mvi	b,3		;3 bytes of F5
	mvi	a,0F5h
	call	fill

	mvi	m,0FBh		;data address mark of FB
	inx	h

	mvi	b,0		;512 sector bytes of E5
	mvi	a,0E5h
	call	fill		;1st 256 bytes
	call	fill		;2nd 256 bytes

	mvi	m,0F7h		;F7 generates CRC
	inx	h

	mvi	b,54		;54 post data bytes of 4E
	mvi	a,04Eh
	call	fill

	inr	c		;move to next sector number
	mov	a,c		;done all sectors?
	cpi	NUMSEC+1
	jc	sectDD		;no, do next sector

; sectors done, fill with 768 trailing 4E for rest of track

	mvi	b,0		;256 bytes of FF
	mvi	a,04Eh
	call	fill
	call	fill		;another 256
	jmp	fill		;another 256 and return

; fill - store A (HL++) B times

fill	mov	m,a
	inx	h
	dcr	b
	jnz	fill

	ret

;-----------------------------------------------------------------------------
;   Data Area
;-----------------------------------------------------------------------------
mWelcom	db	cr,lf,lf
	db 	'===== PC to Floppy Disk Image Transfer =====',cr,lf
	db 	'    (MFA Computer, 720K Disks, ver 1.0)',cr,lf,lf
	db	'Writes a 720K floppy with a disk image received through'
	db	cr,lf
	db	'a 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 serial 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
	db	'Start XMODEM send now...',0

mDone	db	cr,lf,lf,'Creation of new disk successful!',cr,lf,0

mNoSec	db	cr,lf,lf,'Sector not found - disk not formatted?',0

mDskErr	db	cr,lf,lf,'Write failure on track '
errTrk	db	'xx, sector '
errSec	db	'xx. Disk creation aborted.',cr,lf,0

mSelErr	db	cr,lf,lf,'Drive select error',cr,lf,0
mSeekEr	db	cr,lf,lf,'Seek error',cr,lf,0
mWpErr	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

mFormat	db	cr,lf,lf,'Format the target disk? ',0
mFmting	db	cr,lf,'  Formatting',0
mFmtDon	db	cr,lf,'  Format complete',0
mFmtErr	db	cr,lf,lf,'Formatting failed'
mCrLf	db	cr,lf,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 + 64 cycles for the rcvByte code. 

ONESEC	equ	19047		;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

	lda	fmtFlag		;are we formatting?
	ora	a
	jnz	dRestor		;yes, don't read an address mark, force track 0

	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

	lda	fmtFlag		;are we formatting?
	ora	a
	jz	dsVfyTk		;no, go verify track

	xra	a		;return zero status
	rz

dsVfyTk	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 (start of next track)
bufEnd	ds	2		;end address + 1 of last track spot in RAM

; 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
secBuf	ds	512		;sector buffer for verify reads
fmtFlag	ds	1		;non-zero when formatting

; 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

trkBuf	equ	$

; Equates for formatting

DDTRKID	equ	trkBuf+162	;offset of 1st track ID (double density)
DDSECL	equ	512+114		;sector length when formatting (DD)

	end


