;******************************************************************************
;
;  PC2Flop - Transfer disk image from PC to floppy over serial port.
;		(for Vector Graphic 8 inch controller)
;
;	This program writes a soft-sectored 8" floppy with a disk image
;	transmitted from a PC. The image is sent through a Bitstreamer II 
;	or ZCB 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 Vector Graphic 8 inch FDC
;	and does not require CP/M or an OS to function. The program runs
;	standalone at 0x100 or under CP/M.
;
;	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	04/25/16    Original
;
;	1.1	05/08/16    Add support for VG SSSD and IBM SSSD disks.
;			    Implement software fix for race condition
;			    in Rev 0 of the controller. See comments
;			    in dRdSec and dWrSec.
;
;*****************************************************************************

	maclib	z80		;bring in Z80 opcodes

; VG Monitor Locations

VGMON	equ	0E000h		;base address of VG monitor

; Disk information equates

NUMCYL	equ	77
NUMTRK	equ	154		;number of tracks (double density)
NUMSEC	equ	26		;number of sectors per track
SECLEN	equ	256		;sector length (as double density)
MINDRV	equ	0		;first drive number
MAXDRV	equ	3		;max drive number 

DDTRKL	equ	(NUMSEC*SECLEN)	;length of track (double density)
SDTRKL	equ	DDTRKL/2	;length of track (single density)
RDTRIES	equ	5		;number of read (verify) attempts
WRTRIES	equ	4		;number of write attempts

IBMSSSD	equ	0		;disk type code saved in dskType
VGSSSD	equ	1
VGDSDD	equ	2

; Specify head settle delay (in addition to the 10ms step delay provided
;    by the 1793), and the amount of time to delay between reversing
;    track steps (e.g., step in/out or step out/in sequence). 
;
; Set ONEMS to give a 1ms delay for a 19 cycle loop cycle loop

HDSETL	equ	10		;10ms additional head settle after step
STPREV	equ	10		;10ms delay when reversing step direction
;ONEMS	equ	105		;105*19 cycles = 1ms at 2Mhz (8080)
ONEMS	equ	211		;211*19 cycles = 1ms at 4Mhz (Z80)

; Monitor, CP/M boot entry points

MONITOR	set	VGMON		;Vector Graphic 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
JMPINST	equ	0c3h		;jump instruction opcode
NMIVECT	equ	066h		;location of NMI interrupt vector

	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

	lxi	h,0C9F1h	;NMI routine: POP PSW then RET
	shld	NMIVECT		;install NMI interrupt routine

	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	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

; Restore to track 0, verify drive is not write protected

	call	dRestor		;restore to track 0 (selects drive)
	call	dChkWP		;check write protect (aborts to getDrv)

; getType - get disk type from the user. Save in dskType. Save tracks
;    to process based on disk type in trkCnt

getType	lxi	h,mType		;display disk type prompt
	call	dispMsg
	call	rcvCon		;get byte from the console
	ori	20h		;upper to lower case, nums not affected
	cpi	'x'		;exit requested?
	jz	pgmExit		;yes

	sui	'1'		;'1' - '3' to binary 0-2
	cpi	3		;validate ascii 1-3
	jnc	getType		;invalid, prompt again

	sta	dskType		;save the disk type

	cpi	VGDSDD		;double sided?
	mvi	a,NUMTRK	;A=154 tracks for double sided
	jz	savTrks		;yes, save track count of 154

	rrc			;else, A=77 tracks for single sided	

savTrks	sta	trkCnt

; 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	lastSec		;last sector accessed

	inr	a		;init xmodem block number to one
	sta	blkNum

	lxi	h,trkBuf	;trkBuf is initial starting point for
	shld	rcvPtr		;   XMODEM receive

	mvi	a,NAK		;send starting nak after purging input data
	call	purgSio

;-----------------------------------------------------------------------------
;  mainLp - Receive as many packets as will fit into RAM and then write
;      tracks to disk. Repeat until entire disk is written. 
;-----------------------------------------------------------------------------
mainLp	call	bufPkts		;buffer as many packets as possible
	xchg			;de=start of last packet received
	lxi	h,trkBuf	;hl=start of track buffer
	shld	trkPtr		;writing will start from here
	call	cmpDeHl		;compare last packet start - trkBuf
	jc	allDone		;nothing received but an EOT

; trkLoop - write tracks until all disk tracks have been written or all
;    the tracks in the buffer have been written.
	
trkLoop	call	wrtTrk		;write a track (hl returns->next track)

	lda	trkCnt		;put tracks to process in B
	mov	b,a

	lda	trkNum		;increment track number (preserve HL)
	inr	a
	sta	trkNum	

	cmp	b		;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
	call	dispMsg
	jmp	getDrv		;start over asking for drive num

;-----------------------------------------------------------------------------
; bufPkts - Receive XMODEM packets until we fill all the track buffer
;   space available. Reception starts at the address pointed to by
;   rcvPtr. Reception is stopped when the last packet exactly
;   fills or "crosses" the end of the last track spot in RAM. rcvPtr is
;   updated on exit to point to the start of the last packet received.
;   The disk write routines use this pointer to know where data ends and
;   then update rcvPtr to start to where to begin receiving new packets.
;-----------------------------------------------------------------------------
bufPkts	call	rcvPkt		;receive a packet
	xchg			;de=current xmodem receive pointer
	sta	eotFlag		;save status returned (may be EOT)
	cpi	EOT		;EOT received?
	jz	bufDone		;yes, buffering is done

	lhld	bufEnd		;hl=end of buffering space
	call	cmpDeHl		;compare current-end	
	jnc	bufDone		;past or at end, buffering is done

	mvi	a,ACK		;otherwise, ACK the packet
	call	sndByte
	xchg			;put xmodem rcv pointer back in HL
	jmp	bufPkts		;buffer some more

; bufDone - no more room for packets. Update rcvPtr to point to the 
;   start of the last packet received so the disk write routines know
;   where data ends.

bufDone	xchg			;hl=current xmodem receive pointer
	lxi	d,-PKTLEN
	dad	d		;hl=hl-PKTLEN = start of last rcv'd packet
	shld	rcvPtr

	lhld	bufEndN		;use this bufEnd for 2nd-Nth transfer
	shld	bufEnd
	ret

;-----------------------------------------------------------------------------
; rcvPkt - receive an xmodem format 128 byte packet. HL points to the 128 byte
;    buffer to receive. HL is updated by 128 bytes after each succuessful
;    reception of a new 128 byte packet.
;-----------------------------------------------------------------------------
nakPkt	pop	h		;get HL back
	mvi	a,NAK
	call	purgSio		;purge receive for 1s then transmit NAK

;wait for SOH or EOT

rcvPkt	push	h		;save HL for retries
waitSoh	call	chkQuit		;check for user abort
	mvi	a,3		;3 second timeout for soh
	call	rcvByte		;receive character with timeout
	jz	nakPkt		;timeout

	cpi	SOH		;SOH received?
	jz	haveSoh

	cpi	EOT		;EOT to say we're done?
	jnz	waitSoh		;no, keep looking for SOH

	pop	h		;undo the entry push
	ret			;return with EOT

;  Have SOH, receive block number and not block number

haveSoh	mvi	a,1		;one second timeout once inside a packet
	call	rcvByte		;get the block number
	jz	nakPkt		;timeout, NAK and start over

	sta	rcvBlk		;save the received block number
	mvi	a,1		;one second timeout
	call	rcvByte		;get not block number
	jz	nakPkt		;timeout, NAK and start over

	sta	rcvNBlk		;save not block number

;  Receive the 128 byte block

	mvi	e,0		;init checksum
	mvi	d,pktLen	;d is byte counter
pktLoop	mvi	a,1		;one second timeout
	call	rcvByte		;get next data byte
	jz	nakPkt		;timeout

	mov	m,a		;store the character
	inx	h		;point to next byte
	add	e		;update the checksum
	mov	e,a
	dcr	d		;decrement bytes remaining
	jnz	pktLoop

;  Verify the checksum

	mvi	a,1		;one second timeout
	call	rcvByte		;get the checksum
	jz	nakPkt		;timeout

	cmp	e		;checksum match?
	jnz	nakPkt		;no, packet error

;  Checksum good. Verify block numbers

	lda	rcvNBlk		;verify rcvBlk = not (rcvNBlk)
	cma			;A should = block number now
	mov	b,a		;save in b
	lda	rcvBlk
	cmp	b		;compare rcvBlk = not(rcvNBlk)?
	jnz	nakPkt		;no, error

	lda	blkNum		;compare rcvBlk = expected blkNum
	cmp	b
	jnz	nakPkt		;nak if not the same (also nak's re-send)

	inr	a		;increment expected block number
	sta	blkNum
	pop	b		;get HL off stack, but don't clobber HL
	xra	a		;return a zero
	ret

;-----------------------------------------------------------------------------
; wrtTrk - write and verify NUMSEC sectors to the current track in trkBuf
;   as pointed to by trkPtr. After the track is written, trkPtr is updated
;   by the length of a track to point to the next track buffer. This
;   saved value is also returned in HL.
;-----------------------------------------------------------------------------
wrtTrk	lxi	h,trkNum	;get desired track from trkNum	
	mov	l,m		;and put into l
	call	dSeek		;seek to the track

	call	dChkWP		;check write protect

	mvi	a,WRTRIES	;init write retry counter
	sta	wrRetry

; To make sure the head is settled following the track step, yet still
;    minimize latency, add 6 to the last sector number accessed (and
;    wrap as needed). When this sector number is used in the call to
;    dNxtSec, a total delay of about 38ms has transpired since the step
;    was issued.

	lda	lastSec		;last sector accessed
	adi	6		;bump by six sector times
	cpi	NUMSEC+1	;past 26?
	jc	noWrap

	sui	NUMSEC		;wrap to sector 1-n

noWrap	mov	e,a		;e=new "last" sector number

; rtryWrt - write a track and the retry entry point.

rtryWrt	mvi	d,NUMSEC	;d=count of sectors to write
	
; wrtLoop - write a full track starting after the sector in e.

wrtLoop	call	dNxtSec		;get next sector number and offset
	lhld	trkPtr		;hl->start of current track buffer
	dad	b		;bc has offset of sector within track
	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	chkQuit		;check for ctrl-c
	call	dNxtSec		;get next sector number and offset
	lxi	h,secBuf	;hl->unused sector buffer
	call	dRead		;verify the sector by reading
	jnz	vfyRtry		;error, go to retry logic

vfyNext	dcr	d		;decrement sector count
	jnz	vfyLoop		;loop until all sectors verified

; Track verified, increment trkPtr to memory location for next track
;    and exit.

	lhld	trkPtr		;hl=current track pointer
	lxi	d,DDTRKL	;de=bytes in a double density track
	lda	secSize		;current track single or double density?
	ora	a
	jz	addLen		;0=256 bytes in sector (double density)

	lxi	d,SDTRKL	;de=bytes in a single density track	

addLen	dad	d		;hl=start of next track in trkBuf
	shld	trkPtr
	ret

; vfyRtry - verify failed, retry reads followed by a re-write
;   of the track in needed.

vfyRtry	mvi	a,RDTRIES	;init retry counter
	sta	rdRetry

retryLp	call	chkQuit		;check for ctrl-c
;	call	dWtSec		;wait for sector specified in e
	lxi	h,secBuf	;hl->unused sector buffer
	call	dRead		;verify the sector by reading
	jz	vfyNext		;success, go verify next sector

; Re-verify failed. Decrement retry count and try again if not zero. Once
;   retry counter expires, do another write.

	lxi	h,rdRetry	;decrement the read retry counter
	dcr	m
	jnz	retryLp		;try again

; read re-tries expired, decrement the write retry count and re-write
;    if not expired

	lxi	h,wrRetry	;decrement the write retry counter
	dcr	m
	jnz	rtryWrt		;retry starting with the write

	jmp	vfyErr		;display the error and restart

;-----------------------------------------------------------------------------
; Disk write verify failure. Display the track and sector with the error. 
;    Restart the program.
;-----------------------------------------------------------------------------
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
	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;-----------------------------------------------------------------------------
; dispMsg - display the null-terminated message passed in hl on the
;    console device. Clobbers b, hl
;-----------------------------------------------------------------------------
dispMsg	mov	a,m		;get the next message byte
	ora	a		;null terminates
	rz

	mov	b,a		;conOut wants character in b
	call	conOut
	inx	h		;move to next byte
	jmp	dispMsg

;-----------------------------------------------------------------------------
; rcvCon - Receive a character from the console device, echo it, then
;   wait for a CR. Exits program if Ctrl-c typed. Returns with invalid
;   character (null) if BS or DEL pressed after 1st character
;   Returns character in a, clobbers b.
;-----------------------------------------------------------------------------
rcvCon	call	conIn		;check for input
	jz	rcvCon		;nothing

	ani	7fh
	cpi	CTRLC		;abort requested?
	jz	pgmExit		;yes

	cpi	CR		;return pressed?
	rz			;yes, don't echo it

	mov	b,a		;conOut needs character in b
	call	conOut		;echo it

; Wait for CR, then return 1st character typed

rcvCr	call	conIn		;check for input
	jz	rcvCr		;nothing

	ani	7fh
	cpi	CTRLC		;abort requested?
	jz	pgmExit		;yes

	cpi	DEL		;delete
	rz			;yes, return DEL character

	cpi	BS		;backspace?
	rz			;yes, return BS character

	cpi	CR		;return pressed?
	jnz	rcvCr		;no, keep waiting

	mov	a,b		;return 1st character typed
	ret

;-----------------------------------------------------------------------------
; purgSio - wait for 1 second of idle time on receive line, then transmit the
;	character passed in A (usually ACK or NAK). Clobbers b
;-----------------------------------------------------------------------------
purgSio	push	a

purge	mvi	a,1		;1 second timeout
	call	rcvByte		;wait for 1 second without activity
	jnz	purge

	pop	a		;get back the character to send
				;fall through to sndByte
;-----------------------------------------------------------------------------
; sndByte - send the byte in a through the specified transfer port. 
;     Clobbers b. (assuming serOut clobbers only a).
;-----------------------------------------------------------------------------
sndByte	mov	b,a		;b=byte to transmit
	lda	xfrPort		;a=port to use for transfer
	jmp	serOut		;send the character

;-----------------------------------------------------------------------------
; rcvByte - Receive a byte from the specified transfer port. A wait timeout
;     in seconds is specified in a. If a timeout occurs, zero is returned in
;     a and the zero flag is true. Otherwise, the character is returned in a 
;     (could be zero) and the zero flag is false. ONESEC must be set based
;     on processor speed and the number of cycles in the serIn call + 69.
;     Clobbers a, b and c.
;-----------------------------------------------------------------------------
rcvByte	push	d		;save d, e
	mov	d,a		;save timeout in d

initSec	lxi	b,ONESEC	;one second timeout 104 cycles per loop

rcvWait	lda	xfrPort		;(14) a=port to use for transfer
	call	serIn		;(18+cycles in serIn) look for a byte
	jnz	haveChr		;(11) byte received

	dcx	b		;(5) otherwise, decrement timer
	mov	a,b		;(5) one second expire?
	ora	c		;(5)
	jnz	rcvWait		;(11) 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	JMPINST
	rnz			;no, not CP/M

; A jump instruction is present at the CP/M warm boot location (0),
;   now see if that jump points to five more jumps. If so, assume CP/M

	lxi	h,WBOOT+1	;point to lsb of jump address
	mov	e,m		;e=low byte of jump
	inx	h
	mov	d,m		;de=destination of jump
	mvi	b,5		;look for 5 more jumps (a jump table)

jmpTest	ldax	d		;a=opcode at jump destination
	sui	JMPINST		;another jump present?
	rnz			;no, not CP/M

	inx	d		;move to next jump
	inx	d
	inx	d
	dcr	b
	jnz	jmpTest

	dcr	a		;a=0ffh
	sta	cpmFlag		;CP/M flag to non-zero = true

; We're running under CP/M. Change drive prompt message to show drive
;    letters instead of drive numbers and change baseDrv to convert
;    an 'A' to the base drive number (MINDRV).

	mvi	a,'A'		;'A' in drive message instead of number
	sta	mDrvMin
	adi	MAXDRV-MINDRV	;max drive letter
	sta	mDrvMax
	mvi	a,'a'		;ascii 1st drive - baseDrv = 0
	sta	baseDrv
	ret

;------------------------------------------------------------------
; sizeRam - determine how much RAM we have for buffering tracks.
;   Sets the bufEnd variable which points to end address of 
;   the last possible track buffer + 1
;------------------------------------------------------------------
; If running under CP/M, use the BIOS pointer to determine the top
;    of available RAM. Otherwise, size RAM manually.

sizeRam	lhld	WBOOT+1		;get address of BIOS jump table+3
	dcx	h		
	dcx	h
	dcx	h		;hl->top of usable ram+1
	lda	cpmFlag		;running under CP/M?
	ora	a
	jnz	ramEnd		;yes, we have the end of RAM

;  Not under CP/M. Find the end of RAM manually.

	lxi	h,(trkBuf+0ffh) AND 0ff00h   ;next 256 byte boundary

ramLoop	mov	a,m		;a=current RAM content
	inr	m		;change RAM
	cmp	m		;did RAM change?
	mov	m,a		;restore RAM
	jz	ramEnd		;end of RAM found

	inr	h		;next page
	jnz	ramLoop

; ramEnd - end of RAM found. Subtract 256 bytes from this to make room
;   for xmodem packet overflow at the end. Determine the end address + 1
;   of the last track buffer that will fit in RAM. Store in bufEnd

ramEnd	xchg			;de=end of RAM + 1
	mvi	a,-((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	bufEndN		;save as bufEndN

; The first set of packets received includes track 0 which is a single
;    density track (SDTRKL instead of DDTRKL). Therefore, the first
;    time tracks are buffered, the last track buffered will end on a
;    single density boundary. Subtrack SDTRKL from bufEnd for the
;    first time through.

	lxi	b,-SDTRKL	;subtract a SD track length
	dad	b		;hl ends on 1*SD + n*DD track boundary
	shld	bufEnd
	ret

;--------------------------------------------------------------------
; cmHlDe - compare HL-DE. Carry set if HL<DE, carry clear if HL>=DE
;--------------------------------------------------------------------
cmpHlDe	mov	a,l		;compare HL-DE, do lsbs first
	sub	e
	mov	a,h		;do msbs
	sbb	d
	ret			;carry set HL<DE, clear HL>=DE

;--------------------------------------------------------------------
; cmDeHl - compare DE-HL. Carry set if DE<HL, carry clear if DE>=HL
;--------------------------------------------------------------------
cmpDeHl	mov	a,e		;compare DE-HL, do lsbs first
	sub	l
	mov	a,d		;do msbs
	sbb	h
	ret			;carry set DE<HL, clear DE>=HL

;--------------------------------------------------------------------
; subHlDe - HL=HL-DE
;--------------------------------------------------------------------
subHlDe	mov	a,l		;subtract HL-DE, do lsbs first
	sub	e
	mov	l,a		;lsb result in l
	mov	a,h		;do msbs
	sbb	d
	mov	h,a		;msb result in h	
	ret

;--------------------------------------------------------------------
; format - format the disk. Supports IBM SSSD, VG SSSD, and VG DSDD
;--------------------------------------------------------------------
format	lxi	h,mFmting	;'Formatting'
	call	dispMsg

; track zero is single density for all disk types

	call	initSD		;initialize a SD track buffer

	mvi	l,0		;seek to track 0
	call	dSeek
	jnz	fmtErr

	lxi	h,trkBuf	;HL->track buffer with SD format
	call	dWrtTrk		;write the track
	jnz	fmtErr		;error

	mvi	b,'.'		;display '.' pacifier per track
	call	conOut

; track one is double density for Vector Graphic SD and DD disks

	mvi	a,1		;assume loop will start on track 1
	sta	trkNum

	mov	l,a		;seek to track 1
	call	dSeek
	jnz	fmtErr

	lda	secSize		;dSeek sets sector size for current track
	ora	a
	jnz	sdParms		;single density (IBM SSSD disk)
	
	mvi	a,2		;start loop on track 2 instead of 1
	sta	trkNum

	call	initDD		;initialize a DD track buffer
	lxi	h,trkBuf	;HL->track buffer with DD format
	call	dWrtTrk		;format track 1 as DD
	jnz	fmtErr		;error

	mvi	b,'.'		;display '.' pacifier per track
	call	conOut

; set parameters based on whether the rest of the disk is to be formatted
;     single or double density.

	lxi	h,DDTRKID	;HL->location of track ID in DD track
	lxi	d,DDSECL-1	;DE=length to add per sector for DD

	lda	dskType		;check disk type
	cpi	VGDSDD		;Vector Graphic double density?
	jz	savePrm		;yes, save the params we've loaded

	call	initSD		;otherwise, restore SD track format

sdParms	lxi	h,SDTRKID	;HL->location of track ID in SD track
	lxi	d,SDSECL-1	;DE=length to add per sector for SD

savePrm	shld	pTrkId		;save pointer to track ID
	xchg
	shld	fmtSecL		;save sector length 

; nextTrk - Format next track

nextTrk	lxi	h,trkNum	;seek to track in trkNum
	mov	l,m
	call	dSeek		;also sets curCyl, curSide
	jnz	fmtErr

	lda	curCyl		;A=current cylinder
	lxi	h,curSide	;B=current side (0 or 1)
	mov	b,m
	lhld	fmtSecl		;DE=sector length - 1
	xchg
	lhld	pTrkId		;HL->track ID in trkbuf
	mvi	c,NUMSEC	;C=sector counter

; Put the current cylinder number and side in the ID field of each sector
;    on the track

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 with proper SD or DD formt
	call	dWrtTrk		;write the track
	jnz	fmtErr		;error
	
; 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	

	lda	trkCnt		;A=number of tracks to format
	cmp	m		;done?
	jnz	nextTrk		;no, do next track

	ret			;format done

fmtErr	lxi	h,mFmtErr	;display error message and return
	jmp	errMsg		;   to main loop	
		

;-------------------------------------------------------------------
;  initSD - initialize a full track of data for formatting track
;     zero (single density).
;-------------------------------------------------------------------
initSD	lxi	h,trkBuf	;HL->track of format data

	mvi	b,40		;gap 4 preindex 40 bytes of FF
	mvi	a,0FFh		;A=fill value
	call	fill

	mvi	b,6		;6 bytes of 00
	xra	a
	call	fill

	mvi	m,0FCh		;index mark of FC
	inx	h

	mvi	b,26		;26 bytes of FF
	mvi	a,0FFh
	call	fill

; sectSD - fill repeating sector portion of the track

	mvi	c,1		;c=sector number

sectSD	mvi	b,6		;6 bytes of 00
	xra	a
	call	fill

	mvi	m,0FEh		;ID address mark of FE
	inx	h

	mov	m,a		;track number zero
	inx	h
	mov	m,a		;00 after track
	inx	h

	mov	m,c		;sector number
	inx	h
	mov	m,a		;00 after sector
	inx	h

	mvi	m,0F7h		;force CRC code F7
	inx	h

	mvi	b,11		;11 pre-data bytes of FF
	mvi	a,0FFh
	call	fill

	mvi	b,6		;6 pre-data bytes of 00
	xra	a
	call	fill

	mvi	m,0FBh		;data address mark of FB
	inx	h

	mvi	b,128		;128 sector bytes of E5
	mvi	a,0E5h
	call	fill

	mvi	m,0F7h		;force CRC code F7
	inx	h

	mvi	b,27		;27 post data bytes of FF
	mvi	a,0FFh
	call	fill

	inr	c		;move to next sector number
	mov	a,c		;done all sectors?
	cpi	NUMSEC+1
	jc	sectSD		;no, do next sector

; sectors done, fill with 256 trailing FF for rest of track

	mvi	b,0		;256 bytes of FF
	mvi	a,0FFh
	jmp	fill		;call fill and return

;-------------------------------------------------------------------
;  initDD - initialize a full track of data for formatting tracks
;     1-153 (double density)
;-------------------------------------------------------------------
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
	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		;6 bytes of 00
	xra	a
	call	fill

	mvi	b,3		;3 bytes of F5
	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,1		;01 after sector
	inx	h

	mvi	m,0F7h		;force CRC code F7
	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		;256 sector bytes of E5
	mvi	a,0E5h
	call	fill

	mvi	m,0F7h		;force CRC code F7
	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,0FFh
	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 '     (Vector Graphic 8 in FDC, ver 1.1)',cr,lf,lf
	db	'Writes an 8 inch 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

mType	db	cr,lf,lf
	db 	'Specify the disk type',cr,lf
	db 	'  1) SSSD (IBM Format)',cr,lf
	db	'  2) SSSD (VG Format, track 1 is DD)',cr,lf
	db 	'  3) DSDD (VG Format)',cr,lf
	db 	'Enter disk type or X to exit: ',0

mPort	db	cr,lf,lf
	db	'Specify the serial port to use for file transfer',cr,lf
	db	'  1) Bitstreamer II port A (2/3)',cr,lf
	db	'  2) Bitstreamer II port B or ZCB port A (4/5)',cr,lf
	db	'Enter port or X to exit: ',0

mStart	db	cr,lf,lf
	db	'Start XMODEM send now...',0

mDone	db	cr,lf,lf,'Creation of new disk successful!',cr,lf,0

mNoTrk0	db	cr,lf,lf,'Seek to track 0 failed',cr,lf,0

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

mWrtPrt	db	cr,lf,lf,'Disk is write protected',cr,lf,0

mExit	db	cr,lf,lf,'Exiting...',cr,lf,0

mCpm	db	cr,lf,lf
	db	'Insert CP/M disk into drive A, then press Return...',0

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',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 + 69 cycles for the rcvByte code. 

ONESEC	equ	34188		;rcvByte loop count for 1 second (4MHz)

; VG Monitor I/O equates

KEYTST	equ	VGMON+3		;test keyboard in VG ROM monitor
KEYDATA	equ	VGMON+6		;input from keyboard in VG ROM monitor
CRT	equ	VGMON+9		;output to screen in VG ROM monitor

; Serial port equates for 8251 on Bitstreamer II and ZCB

PORTADR	equ	2		;port a is at 2/3
PORTASR	equ	3
PORTBDR	equ	4		;port b is at 4/5
PORTBSR	equ	5
XMTMASK	equ	1		;mask to isolate transmit ready bit
RCVMASK	equ	2		;mask to isolate receive ready bit

;----------------------------------------------------------------------------
; conIn - input character from console
;    inputs:
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;----------------------------------------------------------------------------
conIn	call	KEYTST		;call ROM monitor to test for console input
	rz			;return zero true if none
	jmp	KEYDATA		;get character typed from ROM monitor

;----------------------------------------------------------------------------
; conOut - output character to console
;    inputs: b = character to send
;    clobbers none
;----------------------------------------------------------------------------
conOut	mov	a,b		;monitor expects character in a
	jmp	CRT		;send/display it

;----------------------------------------------------------------------------
; serIn - input character from port specified in a
;    inputs: a = port to read from
;    outputs: z true if no character present
;	      z false if character returned in a
;    clobbers none
;    48 cycles when not returning a character
;-----------------------------------------------------------------------------
serIn	ora	a		;(5) port b?
	jnz	serInB		;(11) yes

; input from port A

	in	PORTASR		;(12) a=port status register
	ani	RCVMASK		;(8) isolate receive ready bit
	rz			;(12) nothing present
	in	PORTADR		;a=character from serial port
	ret			

; input from port B

serInB	in	PORTBSR		;a=status register
	ani	RCVMASK		;isolate receive ready bit
	rz			;nothing present
	in	PORTBDR		;a=character from serial port
	ret			

;-----------------------------------------------------------------------------
; Hardware specific I/O
; serOut - output character to port specified in a
;    inputs: a = port to transmit through
;	     b = character to send
;    clobbers none
;-----------------------------------------------------------------------------
serOut	ora	a		;port b?
	jnz	serOutB		;yes

; output through port A

serOutA	in	PORTASR		;a=port status register
	ani	XMTMASK		;isolate transmit ready bit
	jz	serOutA		;not ready

	mov	a,b		;a=character to transmit
	out	PORTADR		;transmit the character
	ret	

; output through port B

serOutB	in	PORTBSR		;a=port status register
	ani	XMTMASK		;isolate transmit ready bit
	jz	serOutB		;not ready

	mov	a,b		;a=character to transmit
	out	PORTBDR		;transmit the character
	ret		

;****************************************************************************
;
; Vector Graphic 8" FDC disk I/O routines
;
;****************************************************************************

; Controller Equates

FDCBASE	equ	0E0H		;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/status port
FDCWAIT	equ	FDCBASE+5	;FDC stall/wait port

FDMSIDE	equ	04h		;selects top side when set in FDCSEL
FDMDEN	equ	08h		;selects double density when set in FDCSEL

; Commands for 1793.

STPRATE	equ	0		;3ms step rate

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	088h		;read, head assumed settled
cWRITE	equ	0A8h		;write, head assumed settled
cRDADDR	equ	0C4h		;read address marker, wait head settle
cRESET	equ	0D0h		;reset 1793 to idle
cWRTRK	equ	0F4h		;write track command

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

smSEEK	equ	sSKERR+sCRCERR+sNOTRDY+sBUSY
smREAD	equ	sSECERR+sCRCERR+sDLOST+sNOTRDY+sBUSY
smWRITE	equ	smREAD+sWRPROT+sWRTFLT

;--------------------------------------------------------------
; dSelDrv - Select and wait for drive specified in drvNum 
;    to be ready. 
; On Entry:
;    drvNum = drive to be selected
; On Exit:
;    drive selected and ready, 1793 not busy
; Clobbers none
;--------------------------------------------------------------
dSelDrv	lda	drvNum		;A=drive number
	out	FDCSEL		;select the drive

; reset the 1793 and wait for not busy

	mvi	a,cRESET	;ensure 1793 is idle
	out	FDCCMD
	xthl			;waste some time
	xthl

; drive selected, 1793 idle, wait for drive ready

dWtRdy	in	FDCSTAT
	rlc			;not ready bit in carry
	jc	dWtRdy		;wait for ready
	ret

;--------------------------------------------------------------
; dSeek - Seek track specified in L. Selects drive and loads
;    head. If the head was not already loaded, then the E bit
;    (wait for head settle one-shot) is set in fWaitHd for
;    OR'ing into the subsequent read/write command.
; On Entry:
;    L=track to seek
;    drvNum = drive to use
; On Exit:
;    Z flag set = seek worked and verified
;    Z flag clear = error
;    curCyl = resulting cylinder of the track seek
;    curSide = resulting side (0 or 1) of the track seek
;    secSize = sector size (128 or 256) of current track
; Clobbers C,H,L
;--------------------------------------------------------------
dSeek	equ	$

; See if head is currently loaded. Use the reset (interrupt)
;    command to ensure the status register is type I so it
;    includes the head status bit. If not loaded, set the E
;    bit (wait for head-settle one-shot) in fWaitHd which is
;    OR'd into the subsequent read/write command.

	in	FDCSTAT		;see if head is currently loaded
	ani	sHDLOAD
	mvi	a,cWAITHD	;E flag set for R/W commands
	jnz	doSeek1 	;head already loaded

	sta	fWaitHd		;otherwise, set E flag

; Determine the track density, cylinder, and side based on the disk
;    type at the requested track number

doSeek1	mov	a,l		;A=requested track number
	lxi	h,dskType	;C=disk type
	mov	c,m

	mvi	h,0		;H=single density mask
	ora	a		;track zero?
	jz	doSeek2		;yes

	dcr	c		;IBM SSSD?
	jm	doSeek2		;yes, always single density

	mvi	h,FDMDEN	;H=double density, bottom side mask
	cpi	1		;track 1 always DD for VG disks
	jz	doSeek2

	mvi	h,0		;H=single density mask
	dcr	c		;VG SD disk?
	jm	doSeek2		;yes, single density if not track 1

	mvi	h,FDMDEN	;H=double density, bottom side mask
	cpi	NUMCYL		;top or bottom?
	jc	doSeek2		;bottom

	mvi	h,FDMSIDE+FDMDEN   ;H=double density, top side mask
	sui	NUMCYL		;A=proper cylinder

doSeek2	out	FDCDATA		;give 1793 the desired cylinder number
	sta	curCyl		;save the cylinder we're on

	mov	a,h		;save the side selected...
	ani	FDMSIDE		;bit 2 (4's) is side flag 
	rrc	
	rrc
	sta	curSide		;curside = 0 (bot) or 1 (top)	
		
	mov	a,h		;set 128 or 256 bytes (SD or DD)
	ani	FDMDEN		;A=density bit alone
	xri	FDMDEN		;A=0 if DDEN
	jz	savSlen		;save the sector length (0=256)

	mvi	a,128		;otherwise, length is 128

savSlen	sta	secSize
		
	lda	drvNum		;set density and side on FDC board
	ora	h		;include density and side bits
	out	FDCSEL		;this also selects drive

	mvi	a,cSEEK		;issue seek command
	out	FDCCMD
	call	dWtComp		;wait for seek to complete

	in	FDCSTAT		;test status of the seek
	ani	smSEEK		;any of these bits is an error
	ret

;--------------------------------------------------------------
; dRestor - Restore the currently selected drive to track 0.
; On Entry:
; On Exit:
;    Z flag set = restore operation worked
;    Z flag clear = restore failed
; Clobbers none
;--------------------------------------------------------------
dRestor	call	dSelDrv		;make sure drive is selected

	mvi	a,cRESTOR	;issue restore to track zero
	out	FDCCMD
	call	dWtComp		;wait for restore to complete

	in	FDCSTAT		;verify restore worked
	ani	smSEEK		;isolate seek bits
	rz			;restore worked

; restore failed

	lxi	h,mNoTrk0

errMsg	call	dispMsg
	lxi	sp,ourStk	;initialize stack pointer
	jmp	getDrv		;start over asking for drive num

;--------------------------------------------------------------
; dWtComp - Wait here for NMI to return to caller. The NMI
;    routine aborts the loop and does the return.
; On Entry:
;    A command has been issued to the 1793
; On Exit:
;    1793 has completed the command
; Clobbers none
;--------------------------------------------------------------
dWtComp	in	FDCSEL		;allows NMI to occur
	nop
	jmp	dWtComp

;--------------------------------------------------------------
; dNxtSec - wait for next (any) sector. For the 1793, this
;    routine simply increments and wraps the sector number
;    passed in E, then falls into dWtSec to return BC with
;    the offset of the sector within the track buffer.
; On Entry:
;    Drive is selected, motor is running
;    E = most recent sector number
; On Exit:
;    E = next sector sector number
;    BC = byte offset of this sector in a track
;--------------------------------------------------------------
dNxtSec	inr	e		;move to next sector
	jz	dSec1		;in case uninitialized rolled to 0

	mov	a,e		;see if past end
	cpi	NUMSEC+1	;verify between 1 and 26
	jc	dWtSec		;good sector, compute bc

dSec1	mvi	e,1		;restart at sector 1
				;fall into dWtSec

;--------------------------------------------------------------
; dWtSec - wait for sector specified in e. For the 1793
;    controller, this routine simply returns the offset
;    of the sector within this track in bc.
; On Entry:
;    e = desired sector number (must be 1-26 for 1793)
;    secSize = sector length on current track
; On Exit:
;    E = sector number
;    BC = byte offset of this sector in a track
;--------------------------------------------------------------
dWtSec	mov	a,e		;save the computed sector
	sta	lastSec

; compute bc=256*(sector-1)

	dcr	a		;zero index sector number
	mov	b,a		;B=MSB of offset
	mvi	c,0		;BC=256*(E-1)

; if single density, then shift offset in BC to the right
;    by one bit for 128 byte sectors vs 256 byte

	lda	secSize		;single or double density track?
	ora	a		;test for zero and clear carry
	rz			;0(256) bytes = double density

	mov	a,b		;shift BC right once (divide by 2)
	rar
	mov	b,a

	mov	a,c
	rar
	mov	c,a

	ret

;--------------------------------------------------------------
; dRead - Read sector on current track
; 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	di			
	mov	a,e		;give sector number to FDC
	out	FDCSEC

	lda	fWaitHd		;load head settle (E) flag
	ori	cREAD		;combine with read command

	push	b		;preserve BC, DE
	push	d
	call	dRdSec		;do the data transfer
	pop	d		;restore
	pop	b

	xra	a		;no head settle wait after 1st read
	sta	fWaitHd

	in	FDCSTAT		;get status of the read
	ani	smREAD		;any of these bits is an error
	ei			;re-enable interrupts
	ret		

;--------------------------------------------------------------
; dRdSec - Do the physical transfer of data from the disk
;    into memory. When the disk controller completes the
;    command, it generates an NMI interrupt that returns
;    control to the caller of this subroutine.
; 
;    The FDCWAIT register can't be used for data sync until
;    after the 1793 presents the 1st byte. Therefore, a poll
;    for DRQ must be used for the 1st byte. In order to meet
;    1793 timing requirements for DD data (13.5us worst case)
;    the poll loop uses a computed jump based on reading
;    the board status (select) register. When DRQ is asserted,
;    0FFh is returned, otherwise 0FCh is returned. This loop
;    must be located at xxFCh within a page.
;
;    To handle a race condition that can occur with the Rev 0
;    controller, B' is loaded with FFh (the LSB of the jump
;    address when DRQ is asserted) because the race condition
;    can cause the DRQ poll loop to jump into the middle of
;    the INP L instruction. The 2nd byte evaluates to a MOV L,B
;    instruction, and therefore, moves the LSB of the proper
;    jump address into L for subsequent PCHL jump.
;
; On Entry:
;    Read sector or read address command issued to controller
;    HL->sector (or address mark) buffer
; On Exit:
; Clobbers All (may exit while in alternate registers)
;
; One cycle has been added to all instruction times to account
; for the wait state added by the VG ZCB during M1.
;--------------------------------------------------------------

RDSIZE	set	drWt1st-dRdSec

;	org	(($+RDSIZE+100h-0FCh) AND 0FF00h) + (0FCh - RDSIZE)
	org	(($+0Ch+100h-0FCh) AND 0FF00h) + (0FCh - 12h)

dRdSec	out	FDCCMD		;issue command in A

	lda	secSize		;put sector size-1 in B
	dcr	a
	mov	b,a

	mvi	c,FDCDATA	;C=port to read for data byte

	push	h		;putting HL into DE'
	inx	h		;HL->2nd byte location
	exx
	pop	d		;DE'=original HL

	mvi	h,(drWt1st SHR 8) AND 0FFh   ;H'=msb of jump address
	lxi	b,((dRd1st AND 0FFh) SHL 8) + FDCSEL

drWt1st	inp	l		;(13) L=FCh (no DRQ) or FFh (DRQ)
	pchl			;(5) jump to drWt1st or dRd1st

dRd1st	in	FDCDATA		;(12) read 1st byte
	stax	d		;(8) store in buffer
	exx			;(5) back to regular registers

dRdLoop	in	FDCWAIT		;(12) wait for next byte
	ini			;(17) retrieve and store byte
	jnz	dRdLoop		;(11)

	jmp	dWtComp		;all bytes read, go wait for 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	di
	mov	a,e		;give sector number to FDC
	out	FDCSEC

	lda	fWaitHd		;load head settle (E) flag
	ori	cWrite		;combine with write command

	push	b		;save BC
	push	d		;save DE
	call	dWrSec		;do the data transfer
	pop	d		;restore
	pop	b

	xra	a		;no head settle wait after 1st write
	sta	fWaitHd

	in	FDCSTAT		;get status of the write
	ani	smWRITE		;any of these bits is an error
	ei			;re-enable interrupts
	ret

;--------------------------------------------------------------
; dWrtTrk - Write a track for formatting
; On Entry:
;    Drive is selected, on proper track
;    HL->track buffer
; On Exit:
;    Z flag set = success
;    Z flag clear = error
; Clobbers H,L
;--------------------------------------------------------------
dWrtTrk	di

	lda	fWaitHd		;load head settle (E) flag
	ori	cWRTRK		;combine with write track command

	push	b		;save BC
	push	d		;save DE
	call	dWrSec		;do the data transfer
	pop	d		;restore
	pop	b

	xra	a		;no head settle wait after 1st write
	sta	fWaitHd

	in	FDCSTAT		;get status of the write
	ani	smWRITE		;any of these bits is an error
	ei			;re-enable interrupts
	ret

;--------------------------------------------------------------
; dWrSec - Do the physical transfer of data from memory to
;    the disk. When the disk controller completes the
;    command, it generates an NMI interrupt that returns
;    control to the caller of this subroutine.
;
;    The FDCWAIT register can't be used for data sync until
;    after the 1793 requests the 2nd byte. Therefore, a poll
;    for DRQ must be used for the 1st and 2nd byte. The 1st
;    byte is not timing critical. The 2nd byte is. In order to
;    meet 1793 timing requirements for DD data (11.5us worst
;    case) the poll loop uses a computed jump based on reading
;    the board status (select) register. When DRQ is asserted,
;    0FFh is returned, otherwise 0FCh is returned. This loop
;    must be located at xxFCh within a page.
;
;    To handle a race condition that can occur with the Rev 0
;    controller, B' is loaded with FFh (the LSB of the jump
;    address when DRQ is asserted) because the race condition
;    can cause the DRQ poll loop to jump into the middle of
;    the INP L instruction. The 2nd byte evaluates to a MOV L,B
;    instruction, and therefore, moves the LSB of the proper
;    jump address into L for subsequent PCHL jump.
;
; On Entry:
;    Write sector command issued to controller
;    HL->sector (or address mark) buffer
; On Exit:
; Clobbers All (may exit while in alternate registers)
;--------------------------------------------------------------

WRSIZE	set	dwWt2nd-dWrSec

;	org	(($+WRSIZE+100h-0FCh) AND 0FF00h) + (0FCh - RDSIZE)
	org	(($+15h+100h-0FCh) AND 0FF00h) + (0FCh - 16h)

dWrSec	out	FDCCMD		;issue passed write command
	
	mvi	c,FDCDATA	;C=port to write with data byte

dwWt1st	in	FDCSEL		;wait for 1st DRQ
	rrc
	jnc	dwWt1st

	outi			;give 1793 1st byte from HL

	mov	a,m		;A=2nd byte from buffer
	inx	h		;HL->3rd byte location
	push	psw		;push to move to A'
	exx
	pop	psw		;A'=2nd byte to send

	mvi	h,(dwWt2nd SHR 8) AND 0FFh   ;H'=msb of jump address
	lxi	b,((dWr2nd AND 0FFh) SHL 8) + FDCSEL

dwWt2nd	inp	l		;(13) L=FCh (no DRQ) or FFh (DRQ)
	pchl			;(5) jump to dwWt1st or dRd1st

dWr2nd	out	FDCDATA		;(12) give 2nd byte to 1793
	exx			;(5) back to regular registers

dWrLoop	in	FDCWAIT		;(12) wait for next byte
	outi			;(17) give next byte to 1793
	jmp	dWrLoop		;(11)

;--------------------------------------------------------------
; dChkWP - check if disk is write protected. 
; On Entry:
;    disk already selected
; On Exit:
;    returns if disk is not protected. Otherwise, an error
;    message is displayed and the program started over.
; Clobbers none
;--------------------------------------------------------------
dChkWp	in	FDCSTAT		;read 1793 status
	ani	sWRPROT		;get write protect bit alone
	rz			;return if not protected

; Disk write protected. Display error and restart program.

	lxi	h,mWrtPrt	;otherwise, display error message
	jmp	errMsg		;display error and restart


;**************************************************************************
; 
;  Data area
;
;**************************************************************************
; Disk variables

drvNum	ds	1		;drive number to use
dskType	ds	1		;disk type (IBM, VG, and density)

trkNum	ds	1		;track number to read
lastSec	ds	1		;last sector accessed on track
fWaitHd	ds	1		;E bit (wait head settle) flag
curCyl	ds	1		;current cylinder
curSide	ds	1		;current side (0=bot, 1=top)
secSize	ds	1		;sector size (128 or 256)
trkCnt	ds	1		;number of tracks to format

trkPtr	ds	2		;pointer into trkBuf (start of next track)
bufEnd	ds	2		;end address + 1 of last track spot in RAM
bufEndN	ds	2		;bufEnd for 2nd-Nth full buffer transfer

; Retry logic variables

rdRetry	ds	1		;disk read retry counter
wrRetry	ds	1		;disk write retry counter

; Formatting paramters

pTrkId	ds	2		;pointer to track ID in trkbuf
fmtSecL	ds	2		;length of sector when formatting

; 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	256		;sector buffer for verify reads
	ds	64		;stack space
ourStk	equ	$

; Track buffer runs from here to the end of memory

trkBuf	equ	$

; Equates for formatting

SDTRKID	equ	trkBuf+80	;offset of 1st track ID (single density)
SDSECL	equ	128+58		;sector length when formatting (SD)

DDTRKID	equ	trkBuf+162	;offset of 1st track ID (double density)
DDSIDE	equ	trkBuf+163
DDSECL	equ	256+114		;sector length when formatting (DD)

	end

